Skip to content

feat: hash algorithm, RSA padding (RS*), CBOR-valued headers, and Azure credential caching - #212

Open
Dawn Wang (DawnWang17) wants to merge 22 commits into
microsoft:mainfrom
DawnWang17:dawang/aas-hash-alg-cbor-header-credcache
Open

Dawn Wang (DawnWang17) wants to merge 22 commits into
microsoft:mainfrom
DawnWang17:dawang/aas-hash-alg-cbor-header-credcache

Conversation

@DawnWang17

@DawnWang17 Dawn Wang (DawnWang17) commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary Adds four signing options to CoseSignTool and removes a hardcoded RSA padding choice. ### 1. Hash algorithm selection --HashAlgorithm / --ha on sign, and --aas-hash-algorithm for the Azure Artifact Signing provider. Accepts SHA256 (default), SHA384, SHA512, case-insensitively and with an optional dash (sha-384). The indirect signature path already supported SHA384 end-to-end; the direct sign path was hardcoded to SHA256. ### 2. CBOR-valued custom headers --CborProtectedHeaders / --cbph and --CborUnProtectedHeaders / --cbuh take label=base64 pairs and place the decoded bytes into the header verbatim. The existing int and string header options encode values on the caller's behalf, so they cannot express a structure that is already CBOR-encoded — for example an existing vendor signature blob carried alongside the COSE signature. Each decoded value must be exactly one well-formed CBOR data item; malformed CBOR, trailing bytes, invalid base64, and empty values are rejected during argument parsing rather than producing a signature with an undecodable header. Labels that parse as an integer become integer header labels, anything else becomes a string label. ### 3. RSA signature padding (RS* algorithm support) --aas-rsa-padding selects PSS (default) or PKCS1. The COSE algorithm prefixes PS and RS are accepted as aliases. This is the part that actually makes RS256/RS384/RS512 reachable: CoseSign1MessageFactory previously hardcoded RSASignaturePadding.Pss for every RSA key, so no combination of options could produce an RS* signature. | --aas-hash-algorithm | PSS (default) | PKCS1 | |---|---|---| | SHA256 | PS256 | RS256 | | SHA384 | PS384 | RS384 | | SHA512 | PS512 | RS512 | The padding is exposed through a new optional capability interface, ISupportsRsaSignaturePadding, following the existing ISupportsScittCompliance pattern. It is deliberately not a member of ICoseSigningKeyProvider: that interface targets netstandard2.0, which does not support default interface implementations, so adding it there would be a breaking change for external implementers. Providers that do not implement it keep the previous PSS behavior. ### 4. Credential exclusion and caching --aas-exclude-credentials takes a comma-separated list, and the same list may be supplied as an ExcludeCredentials JSON array. Names are case-insensitive with an optional Credential suffix. An unrecognized name throws rather than being silently ignored, since silently ignoring it would leave a credential the caller believes is excluded still in the chain. DefaultAzureCredential probes its chain in order and a credential that cannot succeed still costs a timeout, so excluding known-inapplicable credentials removes that delay. Credentials are also now cached per distinct exclusion set rather than constructed per signing call. DefaultAzureCredential caches tokens for its own lifetime, so the previous per-call construction discarded the token cache and re-authenticated for every artifact. ## Testing Full suite passes: 1281 passed, 0 failed across all 13 test projects. New tests: - HashAlgorithmHelperTests — valid/invalid names, casing, dashes, defaults, configuration binding. - RsaSignaturePaddingHelperTests — all padding spellings and the full hash x padding -> COSE algorithm mapping. - CborHeaderHelperTests — integer vs string labels, protected vs unprotected placement, preservation of existing headers, base64 padding in values, and rejection of malformed specs, invalid base64, empty values, malformed CBOR, and trailing bytes. - RsaSignaturePaddingTests — signs real messages and asserts the COSE alg header is -257/-258/-259 (RS256/RS384/RS512) and -37/-38/-39 (PS256/PS384/PS512), plus a regression test that a default provider still produces PS256. - AzureCredentialFactoryTests — cache reuse for equivalent exclusion sets, distinct instances for different sets, name normalization, unknown-name rejection, and GetExclusions from both the CLI value and the JSON array. Also verified end-to-end from the built CLI: signing with --HashAlgorithm SHA384 --CborProtectedHeaders produced a COSE_Sign1 with alg = PS384 and both CBOR headers present with byte-identical values. New test project CoseSignTool.AzureArtifactSigning.Plugin.Tests is added to the solution and to .github/workflows/dotnet.yml, matching the existing plugin test projects. ## Docs docs/CoseSignTool.md and docs/CertificateProviders.md updated with the new options, the algorithm mapping table, and the credential exclusion list.

5. Azure Artifact Signing MST proxy

mst_register now accepts --proxy-endpoint, --account-name, --cert-profile-name, and optional --correlation-id. When --proxy-endpoint is supplied, the command calls Azure.ArtifactSigning.MST.TransparencyClient.RegisterTransparencyAsync; without it, the existing direct-ledger behavior is unchanged.

CoseSignTool mst_register \
  --endpoint https://debugruisuprivatetrust.confidential-ledger.azure.com \
  --proxy-endpoint https://api-canary.northcentralus.codesigning.azure.net/ \
  --account-name MyAccount --cert-profile-name MyProfile \
  --payload sample_payload.txt --signature sample_payload.cose \
  --output registration-result.json

The proxy authenticates with DefaultAzureCredential and returns the application/cose transparency receipt in the JSON output. It uses Azure.ArtifactSigning.MST 0.1.186-jaxel-add-artifact-signing-mst-sdk.5 from the Prod-RW-ACS feed; the package's Azure.Core 1.62 dependency is isolated to the MST plugin.

Proxy-path tests cover option wiring, SDK request arguments and COSE body, receipt output, validation, and preservation of the direct path. Full suite: 1285 passed, 5 skipped, 0 failed. Debug and Release solution builds pass with 0 warnings.

Adds four signing options and removes a hardcoded padding choice.

- --HashAlgorithm / --aas-hash-algorithm select SHA256, SHA384 or SHA512
  for the local and Azure Artifact Signing paths. The indirect signature
  path already supported SHA384; the direct sign path did not.

- --CborProtectedHeaders / --CborUnProtectedHeaders carry a header value
  that is already CBOR-encoded, supplied as base64. The existing int and
  string header options encode values on the caller's behalf and cannot
  express a pre-encoded structure such as a vendor signature blob. Each
  value must decode to exactly one well-formed CBOR data item, so a bad
  value fails during argument parsing rather than producing a signature
  with an undecodable header.

- --aas-rsa-padding selects PSS (default) or PKCS#1 v1.5. The padding
  chooses the COSE algorithm family, so this is what makes RS256, RS384
  and RS512 reachable; CoseSign1MessageFactory previously hardcoded PSS
  for every RSA key. Providers that do not implement the new optional
  ISupportsRsaSignaturePadding interface keep the PSS behavior, and the
  interface is separate from ICoseSigningKeyProvider because the latter
  targets netstandard2.0 and cannot carry a default implementation.

- --aas-exclude-credentials, and the equivalent ExcludeCredentials JSON
  array, drop credentials from the DefaultAzureCredential chain so their
  probe timeouts are not paid on every run. Credentials are now cached
  per exclusion set instead of being rebuilt per signing call, which
  previously discarded the token cache each time.

Adds CoseSignTool.AzureArtifactSigning.Plugin.Tests and wires it into
the build workflow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 47678fda-9dc8-4452-9130-806f921eae8c
public void CreateHeaderExtender_FromNullConfiguration_Throws()
{
Assert.ThrowsException<ArgumentNullException>(
() => CborHeaderHelper.CreateHeaderExtender((IConfiguration)null!));
Comment on lines +101 to +107
foreach (IConfigurationSection child in configuration.GetSection(ExcludeCredentialsSection).GetChildren())
{
if (!string.IsNullOrWhiteSpace(child.Value))
{
exclusions.Add(child.Value.Trim());
}
}
RgABAgME decodes to 0x46 followed by five bytes, which declares a
six-byte CBOR byte string with only five bytes available. Copying the
example straight from the help text therefore failed argument parsing
with "does not contain well-formed CBOR".

RAECAwQ= is 0x44 01 02 03 04, a well-formed four-byte CBOR byte string.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 47678fda-9dc8-4452-9130-806f921eae8c
@musabbir

Copy link
Copy Markdown
Member

 LoadSigningKeyProvider()  branches to  LoadSigningKeyProviderFromPlugin() , which calls  plugin.CreateProvider(configuration, …)  and never reads  HashAlgorithm . Only the local-certificate branch consumes it. So:

cosesigntool sign --HashAlgorithm SHA512 --aas-endpoint … → signs SHA256

The operator asked for SHA512, got SHA256, and nothing said otherwise. Should it fail when  --HashAlgorithm  is combined with a plugin provider.

@musabbir

Copy link
Copy Markdown
Member

Consider adding an option to specify a CBOR file for the protected header.
Base64 strings can be quite large and requires copy/pasting.

For file: --cbph ext-sig file.cbor
for base64 string --cbph ext-sig=gaUBOQEABFRTRvP3y8JLG....

@musabbir

Copy link
Copy Markdown
Member

Should we consider adding a size check to the cbor header?

Extend mst_register with an optional --proxy-endpoint mode backed by
Azure.ArtifactSigning.MST TransparencyClient.RegisterTransparencyAsync.
The existing direct-ledger path remains unchanged when the option is
not supplied.

Proxy mode retains the existing endpoint, payload, signature, timeout,
and output arguments, and adds the Artifact Signing account, certificate
profile, and optional correlation ID. The returned application/cose
receipt is included in the JSON output as base64.

Use the prerelease MST SDK from Prod-RW-ACS and isolate its Azure.Core
1.62 dependency inside the MST plugin. Add proxy-path tests and document
the canary endpoint invocation. Also repair the existing workflow test
command indentation so the test block remains valid YAML.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e18f1141-bfc7-48a8-b918-4f175d760d72
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e18f1141-bfc7-48a8-b918-4f175d760d72
Copilot-Session: e18f1141-bfc7-48a8-b918-4f175d760d72
Azure.CodeSigning.Sdk previously flowed in transitively at 0.1.135 via
Azure.Developer.ArtifactSigning.CryptoProvider. Pin it explicitly to the
Prod-RW-ACS build 0.1.186-jaxel-add-artifa0004 and add the feed as a
restore source for the Azure Artifact Signing projects so the plugin
deploys the newer SDK.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the corrected registerTransparency action route from Azure.ArtifactSigning.MST 0006 and pass the MST instance name required by its updated API. Keep Azure.CodeSigning.Sdk on the compatible transitive 0.1.135 version for signing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 485768f7-c951-4fbc-808b-7ee748e79f92
Use the trailing Azure Artifact Signing customer EKU when generating the SCITT CWT issuer so a statement identifies the selected certificate profile rather than the whole tenant.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 28815e2d-8608-4ab8-b531-d704d6f79085
@microsoftruisu

Copy link
Copy Markdown
Contributor

Follow-up fix for COSE statement issuer generation: DawnWang17#2

The Artifact Signing certificate carries both tenant- and profile-scoped EKUs. The current deepest/greatest heuristic selects the tenant EKU, causing MST policy rejection. This PR selects the trailing profile-scoped EKU. Verified with 61 tests and a live MST SDK registration returning HTTP 200.

…cwt-issuer

fix: select profile-scoped Artifact Signing issuer
microsoftruisu and others added 12 commits September 29, 2026 21:25
Keep the credential reuse, MST proxy, and profile-scoped issuer changes while relying on the generic hash, padding, and CBOR support now available in main.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4ccf81dc-70d3-461d-b6ac-04b133cf539c
Expose the proxy flow as aas_mst_register, reuse the Artifact Signing credential cache and exclusions, and restore the generic MST plugin to its main-branch behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4ccf81dc-70d3-461d-b6ac-04b133cf539c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4ccf81dc-70d3-461d-b6ac-04b133cf539c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4ccf81dc-70d3-461d-b6ac-04b133cf539c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4ccf81dc-70d3-461d-b6ac-04b133cf539c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4ccf81dc-70d3-461d-b6ac-04b133cf539c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4ccf81dc-70d3-461d-b6ac-04b133cf539c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4ccf81dc-70d3-461d-b6ac-04b133cf539c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4ccf81dc-70d3-461d-b6ac-04b133cf539c
Keep the workflow scoped to the Azure Artifact Signing plugin, persist the MST receipt in the transparent COSE statement, and cover failure/output behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e6f13c8b-c89e-4200-aa76-4965066f8a8f
Select the trailing profile-scoped Artifact Signing EKU instead of the broader tenant EKU so MST policy validation accepts the generated DID issuer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e6f13c8b-c89e-4200-aa76-4965066f8a8f
Pass the existing --ContentType/--cty value into the COSE message factory instead of always using application/cose.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e6f13c8b-c89e-4200-aa76-4965066f8a8f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants