Skip to content

mackms: support subject DN components in certificate URIs#1073

Draft
joshdrake wants to merge 2 commits into
masterfrom
feat/mackms-subject-search
Draft

mackms: support subject DN components in certificate URIs#1073
joshdrake wants to merge 2 commits into
masterfrom
feat/mackms-subject-search

Conversation

@joshdrake

@joshdrake joshdrake commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

What

Adds subject distinguished name components — cn, o, ou, l, st, c — to the mackms certificate URI vocabulary, so certificates can be selected by subject in LoadCertificate, LoadCertificateChain, and DeleteCertificate:

mackms:cn=My+Cert
mackms:cn=My+Cert;o=Smallstep;ou=Engineering;ou=Platform
mackms:label=test@example.com;ou=Engineering
mackms:serial=2c2739...;o=Smallstep

Constraint: subject matching happens in Go — we have to enumerate certificates

The Apple keychain cannot do this filtering for us:

  • There are no per-RDN attributes — nothing like "issuer CN" or "subject OU" exists at the keychain layer.
  • kSecMatchSubjectContains is a substring match over the whole subject and only works on legacy file-based keychains, not the data protection keychain.
  • kSecAttrSubject/kSecAttrIssuer hold the entire name as an Apple-normalized (partially uppercased) DER blob. They are read-only, system-derived, exact-match columns, and there is no public API to produce the normalized bytes from user-supplied strings — the SecCertificateCopyNormalized*Sequence APIs all require a SecCertificateRef in hand. The existing comment in LoadCertificateChain already rejects cert.RawIssuer/RawSubject matching for this reason.

So when a URI contains subject components, mackms fetches the candidates (kSecMatchLimitAll), parses every candidate with x509.ParseCertificate, and matches the components against cert.Subject in Go. label and/or serial in the same URI still narrow the keychain query natively first; a pure-DN query like mackms:ou=Engineering enumerates and parses every certificate in the keychain. That's fine at keychain scale (tens to a few hundred items), but combining with label is recommended, and the docs say so. This mirrors what capi does on Windows, where cn is matched in Go while iterating a native issuer-search cursor.

Semantics

  • All given components must match (logical AND) using exact, case-sensitive comparison.
  • cn is single-valued; o/ou/l/st/c can be repeated, and every given value must be present in the certificate subject (subset match on multi-valued RDNs).
  • When multiple certificates match, the existing selection applies (most recent already-valid NotBefore).
  • LoadCertificateChain: components select the leaf only; intermediates are still located via the authority key identifier.
  • DeleteCertificate: with subject components present, the certificate is located first (same rules as LoadCertificate) and then deleted by serial number plus, when available, subject key identifier. Still deletes at most one certificate.

Behavior change

Subject component attributes in certificate URIs were previously ignored; now they filter. For example, mackms:label=x;cn=y used to load by label alone and can now return not found, and mackms:cn=y used to fail with "label or serial is required" and now performs a lookup.

Testing

  • New Test_parseCertURI (first unit coverage for this function), Test_certAttributes_hasSubjectQuery, and Test_certAttributes_matchesSubject — pure, no keychain access.
  • New integration tests TestMacKMS_LoadCertificate_bySubject and TestMacKMS_DeleteCertificate_bySubject (including a no-SKID self-signed cert for the serial-only delete path), plus subject cases in TestMacKMS_LoadCertificateChain.
  • ⚠️ CI does not build or run mackms (darwin+cgo only; CI is Linux). Verified here: gofmt, go build ./..., go vet, and the kms/uri/kms/platform/kms/apiv1 tests. The package still needs go test ./kms/mackms/... on a Mac before this merges — keeping as draft until that run happens.

Possible follow-ups (out of scope)

  • Go-side issuer component filters (issuer-cn, …) for disambiguating same-subject certs from different CAs.
  • Chain building via kSecAttrSubject == SecCertificateCopyNormalizedIssuerSequence(child) (macOS 10.12.4+) as an alternative to the SKID walk — with a cert in hand the framework computes the normalized bytes, so no reimplementation is needed; kSecAttrIssuer is an indexed primary-key column, making this the one place issuer-based keychain-side narrowing is legitimate.
  • Because pure-DN queries enumerate the whole keychain, one unparseable foreign certificate fails the load loop (pre-existing behavior, now more exposed). Could skip unparseable certs when a subject query is present.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P3Nxd2zBbULcRAG6HrzQgR

joshdrake and others added 2 commits July 16, 2026 16:29
Support selecting certificates in the Apple Keychain by subject
distinguished name components using the new "cn", "o", "ou", "l", "st",
and "c" URI attributes, e.g. "mackms:cn=My+Cert;ou=Engineering".

The keychain cannot match individual subject components, and it stores
normalized (partially uppercased) subject values, so the components are
used to filter the parsed certificates after retrieving the candidates
from the keychain. All the given components must match using exact,
case-sensitive comparisons, and the multi-valued components can be
repeated, requiring every value to be present in the subject.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3Nxd2zBbULcRAG6HrzQgR
When subject components ("cn", "o", "ou", "l", "st", "c") are present in
the URI, find the matching certificate first, using the same selection
rules as LoadCertificate, and delete it by its serial number and, when
available, its subject key identifier.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P3Nxd2zBbULcRAG6HrzQgR
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants