Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion crates/psign-authenticode-trust/src/rfc3161_extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ fn timestamp_token_from_signer_unsigned_attrs(

fn timestamp_token_from_attribute(attr: &Attribute) -> Option<(ContentInfo, Vec<u8>, UtcDate)> {
for val in attr.values.iter() {
let token = decode_content_info_loose(attribute_value_bytes(val))?;
let token = timestamp_content_info_from_attribute_value(val)?;
if token.content_type != ID_SIGNED_DATA {
continue;
}
Expand All @@ -200,6 +200,14 @@ fn timestamp_token_from_attribute(attr: &Attribute) -> Option<(ContentInfo, Vec<
None
}

fn timestamp_content_info_from_attribute_value(val: &der::asn1::Any) -> Option<ContentInfo> {
val.to_der()
.ok()
.as_deref()
.and_then(decode_content_info_loose)
.or_else(|| decode_content_info_loose(attribute_value_bytes(val)))
}

fn digest_for_oid(oid: &str, data: &[u8]) -> Result<Vec<u8>> {
Ok(match oid {
"1.3.14.3.2.26" => sha1::Sha1::digest(data).to_vec(),
Expand Down
11 changes: 9 additions & 2 deletions crates/psign-codesigning-rest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,15 @@ fn acquire_codesigning_token(params: &CodesigningSubmitParams) -> Result<String>
Ok(t.to_string())
}
CodesigningAuth::ManagedIdentity => {
let endpoint = std::env::var("PSIGN_CODESIGNING_IMDS_ENDPOINT").unwrap_or_else(|_| {
"http://169.254.169.254/metadata/identity/oauth2/token".to_string()
});
let http = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(120))
.build()
.map_err(|e| anyhow!("HTTP client: {e}"))?;
let rsp = http
.get("http://169.254.169.254/metadata/identity/oauth2/token")
.get(endpoint)
.query(&[("api-version", "2018-02-01"), ("resource", MI_RESOURCE)])
.header("Metadata", "true")
.send()
Expand Down Expand Up @@ -244,7 +247,6 @@ pub fn submit_codesign_hash_blocking(
.map(|s| s.to_string());

let body_bytes = rsp.bytes().context(":sign body")?;
let accept_json: Value = serde_json::from_slice(&body_bytes).unwrap_or(Value::Null);

if !status.is_success() {
return Err(anyhow!(
Expand All @@ -253,6 +255,11 @@ pub fn submit_codesign_hash_blocking(
String::from_utf8_lossy(&body_bytes)
));
}
let accept_json: Value = if body_bytes.is_empty() {
Value::Null
} else {
serde_json::from_slice(&body_bytes).context(":sign JSON")?
};

let poll_url = if let Some(loc) = op_location {
operation_location_url(&base, &loc)
Expand Down
5 changes: 3 additions & 2 deletions docs/gap-analysis-signing-platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,10 @@ Details: [`migration-azuresigntool.md`](migration-azuresigntool.md).
|---------|----------------|
| Decoupled sign (`--dlib`, `--trusted-signing-dlib-root`, `--dmdf`) | **`psign-tool`** only |
| REST hash signing | **`artifact-signing-submit`** (`--features artifact-signing-rest`) on **`psign-tool`** or **`psign-tool portable`** |
| REST PE / WinMD embedding | **`psign-tool portable sign-pe --artifact-signing-*`** and **`psign-tool --mode portable sign --dmdf ... --artifact-signing-*`** build, remote-sign, optionally timestamp, and embed Authenticode |
| Metadata validation without signing | **`psign-tool portable artifact-signing-metadata-check`** |

**Gap:** REST output is **not** wired into a portable Authenticode embedder; docs state MVP is hash signing / diagnostics. [`migration-artifact-signing.md`](migration-artifact-signing.md).
**Gap:** REST output is wired into the portable **PE / WinMD** Authenticode embedder, but MSIX/AppX and other non-PE SIP subjects still require the Windows dlib path or future portable embedders. [`migration-artifact-signing.md`](migration-artifact-signing.md).

---

Expand All @@ -173,7 +174,7 @@ Portable support is intentionally split by lifecycle stage. This keeps Linux/mac

The compatibility rule is: **portable mode may prove digest/CMS consistency and explicit-anchor trust, but it must not silently emulate Windows policy.** When a user asks for a Windows-only lifecycle stage, the CLI should fail with an explicit unsupported/not-implemented message and point to the closest portable helper.

**Remote signing steps:** With **`--features azure-kv-sign-portable`**, **`sign-pe --azure-key-vault-*`** performs full PE Authenticode signing with Key Vault RSA signatures, while **`azure-key-vault-sign-digest`** performs Azure Key Vault **`keys/sign`** on a **raw digest file** for lower-level workflows. **`pe-signer-rs256-prehash`**, **`cab-signer-rs256-prehash`**, **`msi-signer-rs256-prehash`**, and **`catalog-signer-rs256-prehash`** (**`--encoding raw`**) emit the **32-byte** **`RS256`** input over **`SignerInfo.signedAttrs`** (distinct from subject-layout digests and from **`verify-catalog`**’s CTL **`eContent`** / PKCS#9 checks). With **`--features artifact-signing-rest`**, **`artifact-signing-submit`** calls Trusted Signing **`:sign`**. The PE/CAB/MSI CMS core can inject externally produced RSA/SHA-2 signature bytes; CAB/MSI/catalog remote-sign CLI routing, MSIX embedding, and broad native-shaped remote-sign routing remain future portable embedder work.
**Remote signing steps:** With **`--features azure-kv-sign-portable`**, **`sign-pe --azure-key-vault-*`** performs full PE Authenticode signing with Key Vault RSA signatures, while **`azure-key-vault-sign-digest`** performs Azure Key Vault **`keys/sign`** on a **raw digest file** for lower-level workflows. **`pe-signer-rs256-prehash`**, **`cab-signer-rs256-prehash`**, **`msi-signer-rs256-prehash`**, and **`catalog-signer-rs256-prehash`** (**`--encoding raw`**) emit the **32-byte** **`RS256`** input over **`SignerInfo.signedAttrs`** (distinct from subject-layout digests and from **`verify-catalog`**’s CTL **`eContent`** / PKCS#9 checks). With **`--features artifact-signing-rest`**, **`artifact-signing-submit`** calls Trusted Signing **`:sign`**, and **`sign-pe --artifact-signing-*`** / top-level **`--mode portable sign --artifact-signing-*`** use that REST signature to embed PE/WinMD Authenticode. CAB/MSI/catalog remote-sign CLI routing, MSIX embedding, and broader native-shaped remote-sign routing remain future portable embedder work.

**RFC 3161 TSA helpers:** **`rfc3161-timestamp-req`** builds **`TimeStampReq`** DER from **`--digest-hex`** / **`--digest-file`** (message-imprint preimage; optional **`--nonce`**, **`--cert-req`**) for **`curl`** / OpenSSL **`ts`** against a timestamp URL. **`rfc3161-timestamp-resp-inspect`** prints **`pki_status`** / **`pki_status_int`** (raw status INTEGER) / **`granted`** / token length, **`time_stamp_token_prefix_hex`** (first **16** octets of the raw **`timeStampToken`** TLV, or **`-`** when absent — handy for **`ContentInfo`** / CMS shape checks), **`status_strings_json`** (**`PKIFreeText`**), **`fail_info_tlv_hex`**, and **`fail_info_flags_json`** (RFC 2510 Appendix A **`PKIFailureInfo`** bit names through **`badPOP`**, then **`bit_N`**; **`null`** when the **`BIT STRING`** body is not decodable). Parseable CMS **`id-ct-TSTInfo`** tokens also surface structural **`tst_info_*`** diagnostics: policy OID, message-imprint digest OID/hash, serial, **`genTime`**, and nonce. Optional **`rfc3161-timestamp-http-post`** (**`--features timestamp-http`**) performs the HTTPS POST without **`curl`**. **`timestamp-pe-rfc3161`** can then attach a raw **`timeStampToken`** or granted **`TimeStampResp`** token to an existing PE Authenticode `SignerInfo` as the Microsoft RFC3161 unsigned attribute. This still does not clone every **`SignerTimeStampEx3`** policy branch or timestamp non-PE subjects.

Expand Down
Loading