@wynenterprise/sign-sbom
v1.0.4
Published
Sign CycloneDX SBOM JSON with an embedded signature using Azure Key Vault
Maintainers
Readme
@wynenterprise/sign-sbom
Sign a CycloneDX SBOM JSON file with an embedded signature using a private key held in Azure Key Vault. The key never leaves the vault — signing is performed by the Key Vault REST API.
The signature format and algorithm are byte-compatible with cdxgen cdx-verify:
- Strip any existing
signatureproperty from the document. - Canonicalize the JSON per RFC 8785 (JCS).
- SHA-256 the canonical bytes.
- Sign the digest with RS256 (RSASSA-PKCS1-v1.5) via Azure Key Vault.
- Embed the base64url result as
signature.value, together with the signingkeyIdand the leaf certificate insignature.certificatePath.
The result verifies cleanly with both cdx-verify and standard OpenSSL tooling.
Install
npm install -g @wynenterprise/sign-sbomThis provides the sign-sbom command. You can also run it without installing:
npx @wynenterprise/sign-sbom <file.json>Requirements
- Node.js >= 18 — uses the global
fetchAPI (no extra HTTP dependency). - An Azure service principal with the Key Vault
keys/signpermission on the target certificate's key. - A certificate (with an RSA key) stored in Azure Key Vault.
The only runtime dependency is canonicalize
(RFC 8785 implementation, zero transitive dependencies).
Configuration
Credentials can be supplied through environment variables or CLI flags. CLI flags take precedence over environment variables when both are present.
| Environment variable | CLI flag | Description |
| --------------------- | -------- | -------------------------------------------- |
| AZURE_TENANT_ID | -kvt | AAD tenant id |
| AZURE_CLIENT_ID | -kvi | Service principal app id |
| AZURE_CLIENT_SECRET | -kvs | Service principal secret |
| AZURE_VAULT_URI | -kvu | e.g. https://my-vault.vault.azure.net |
| AZURE_CERT_NAME | -kvc | Certificate name in the vault |
All five values are required for signing. They are not needed for exporting the public key (see below). See .env.example for a template.
Usage
sign-sbom <file.json> [signedfile.json]
sign-sbom <signedfile.json> --export-key [output]
sign-sbom --helpSign with environment variables
export AZURE_TENANT_ID=...
export AZURE_CLIENT_ID=...
export AZURE_CLIENT_SECRET=...
export AZURE_VAULT_URI=https://my-vault.vault.azure.net
export AZURE_CERT_NAME=my-certificate
# Sign in place (no output argument):
sign-sbom bom.json
# Or write the signed copy to a new file:
sign-sbom bom.json signed.jsonSign with CLI flags
Useful in CI or when you do not want credentials in the environment:
sign-sbom bom.json signed.json \
-kvt <tenant-id> \
-kvi <client-id> \
-kvs <client-secret> \
-kvu https://my-vault.vault.azure.net \
-kvc my-certificateOn success the command prints the output path and the signing key id:
Signed: signed.json
keyId: https://my-vault.vault.azure.net/keys/my-certificate/<version>Output
The tool adds a signature object to the document:
"signature": {
"algorithm": "RS256",
"keyId": "https://<vault>/keys/<name>/<version>",
"value": "<base64url RSA signature>",
"certificatePath": ["<base64url DER leaf certificate>"]
}Re-signing an already-signed document strips the existing signature first, so
the property is never duplicated and the signature always covers the same
canonical content.
Export the public key
Extract the public key (leaf certificate) from a signed SBOM into a file for use
with cdx-verify. No Azure credentials are needed — the certificate is read
straight from signature.certificatePath.
sign-sbom signed.json --export-key public.keyIt prints the certificate subject, issuer, and expiry, then writes the PEM to
public.key (or the path you pass as [output]).
Without sign-sbom
The leaf certificate can also be extracted with standard Unix tools — jq,
tr, awk, and fold — no sign-sbom required. The value is base64url without
padding, so the pipeline converts it to standard base64 and restores the =
padding before wrapping it in PEM markers:
{ printf '%s\n' '-----BEGIN CERTIFICATE-----';
jq -r '.signature.certificatePath[0]' signed.json | tr '_-' '/+' |
awk '{ while (length($0) % 4) $0 = $0 "="; print }' | fold -w64;
printf '%s\n' '-----END CERTIFICATE-----'; } > public.keyThe result is the same PEM certificate as --export-key produces and works
with cdx-verify and openssl alike.
Verify
Full verification has two independent parts:
- Signature integrity — the document was not modified and was signed by the key that belongs to this certificate.
- Certificate trust — that certificate really is who it claims to be (valid, not expired, issued by a trusted CA, intended for code/document signing).
cdx-verify covers part 1 only. Part 2 is a standard X.509 check with OpenSSL.
Both matter: a signature can be cryptographically valid while the certificate is
expired, self-signed, or untrusted.
1. Verify the signature
cdx-verify checks the cryptographic signature against the exported public key:
npx -p @cyclonedx/cdxgen cdx-verify -i signed.json --public-key public.keyA successful run reports:
✓ Signature is valid! (Matched KeyId: 'https://<vault>/keys/<name>/<version>')This proves the canonical SBOM bytes match signature.value for the public key
embedded in the certificate — but it says nothing about whether that certificate
is trustworthy.
2. Verify the certificate
The exported public.key is the leaf X.509 certificate in PEM. Inspect its
identity, validity window, and intended usage:
openssl x509 -in public.key -noout -subject -issuer -dates -purposeThen confirm it chains to a trusted Certificate Authority. The export contains the leaf certificate only, so supply the issuing CA chain (intermediate + root, obtained from your CA — e.g. GlobalSign) as a PEM bundle:
openssl verify -CAfile ca-chain.pem public.keyA trusted, in-date certificate reports:
public.key: OKopenssl verify fails if the certificate is expired, self-signed, or does not
chain to a CA in ca-chain.pem.
End-to-end example
# 1. Sign
sign-sbom bom.json signed.json
# 2. Export the certificate (public key) from the signed document
sign-sbom signed.json --export-key public.key
# 3. Verify the signature
npx -p @cyclonedx/cdxgen cdx-verify -i signed.json --public-key public.key
# 4. Verify the certificate identity and trust chain
openssl x509 -in public.key -noout -subject -issuer -dates -purpose
openssl verify -CAfile ca-chain.pem public.keyTroubleshooting
| Symptom | Cause / fix |
| ------------------------------------ | ------------------------------------------------------------------- |
| Missing required parameters: ... | One or more credentials are not set. Provide them via env or flags. |
| Token request failed: 401 | Wrong tenant/client id or client secret. |
| Key Vault sign failed: 403 | The service principal lacks the keys/sign permission. |
| cdx-verify reports an invalid sig | The document changed after signing. Re-sign, then re-export the key. |
How it works
src/keyvault.js— minimal Azure Key Vault REST client (client-credentials OAuth flow, get certificate, sign digest).src/signer.js— strips the old signature, canonicalizes (RFC 8785), hashes, requests the RS256 signature, and assembles thesignatureobject.src/crypto.js— SHA-256 and base64url helpers.src/exporter.js— readscertificatePathand emits the PEM public key.
