X.509 header parameters¶
Warning
No chain validation and no network access is performed by this library. RFC 9360
defines how a COSE message carries or references X.509 certificates; what to make of them is the application's.
Chain building, path validation (RFC 5280 §6), revocation
checking and the choice of trust anchors happen in your code, before anything is acted upon. RFC 9360 §5: "both the
signature validation and the certificate validation MUST be completed successfully before acting on any requests."
getX5U() returns the URI as a string and nothing else is done with it: this library never fetches a URL.
The four parameters¶
The four header parameters of RFC 9360 §2 have typed accessors on
CoseHeaders, each looking in the protected bucket first, then in the unprotected one, and returning null when the
message carries neither:
| Name | Label | Type | Reference | Accessor |
|---|---|---|---|---|
x5bag |
32 (CoseHeaders::LABEL_X5BAG) |
COSE_X509 |
RFC 9360 §2 | getX5Bag(): ?X5Bag |
x5chain |
33 (CoseHeaders::LABEL_X5CHAIN) |
COSE_X509 |
RFC 9360 §2 | getX5Chain(): ?X5Chain |
x5t |
34 (CoseHeaders::LABEL_X5T) |
COSE_CertHash |
RFC 9360 §2 | getX5T(): ?CoseCertHash |
x5u |
35 (CoseHeaders::LABEL_X5U) |
uri |
RFC 9360 §2 | getX5U(): ?string |
use Cose\Algorithm\Hash\SHA256;
use Cose\Algorithm\Manager;
use Cose\Algorithm\Signature\CertificateSignatureVerifier;
use Cose\Algorithm\Signature\ECDSA\ES256;
use Cose\Structure\CoseHeaders;
use SpomkyLabs\Pki\X509\CertificationPath\CertificationPath;
use SpomkyLabs\Pki\X509\CertificationPath\PathValidation\PathValidationConfig;
// RFC 9360 §2: "applications that use this header parameter MUST support the hash algorithm 'SHA-256'".
$manager = Manager::create()->add(ES256::create(), SHA256::create());
$verifier = CertificateSignatureVerifier::create($manager);
$headers = CoseHeaders::fromMessage($coseSign1);
// x5chain: the signature verifies with the end-entity certificate, in one call ...
$chain = $headers->getX5Chain();
$isValid = $verifier->verifyWithX5Chain($alg, $chain, $toBeSigned, $signature);
// ... and the proposed path is yours to validate, against your trust anchors, before you rely on the result.
$path = CertificationPath::fromCertificateChain($chain->toCertificateChain());
$path->validate(PathValidationConfig::create(new DateTimeImmutable(), 5)->withTrustAnchor($yourTrustAnchor));
// x5t: the thumbprint names the certificate to use, in the bag that travels with it or in your own store.
$x5t = $headers->getX5T();
$hash = $x5t->hashAlgorithm($manager); // FilterOnlyHash: SHA1 is accepted here, on purpose
$leaf = $headers->getX5Bag()?->find($x5t, $hash); // the DER certificate, or null
foreach ($yourStore as $der) {
if ($x5t->matches($der, $hash)) { /* ... */ }
}
// x5u: a string. Whether to fetch it, over what, and whether to trust the answer is your decision.
$uri = $headers->getX5U();
getX5U() reads a text string, tagged 32 or not, with a scheme; HeaderMapHelper::assertUriValue() is the value
check behind it.
The classes¶
The classes live in Cose\Structure\X509:
CoseX509is the wire structure,COSE_X509 = bstr / [ 2*certs: bstr ]: one certificate travels as a single byte string, two or more as an array of byte strings, one DER certificate per entry. An array of length one is invalid CDDL:fromCBOR()rejects it with a message that says so, andtoCBOR()never produces it. The decoder checks the shape only;toCertificates()parses the entries with spomky-labs/pki-framework when asked.X5BagandX5Chainare the two parameters over it. A bag "is unordered and may contain self-signed certificates", duplicates, and "certificates that are completely extraneous to the message";toCertificateBundle()hands it to pki-framework for path building. A chain is "ordered starting with the certificate containing the end-entity key followed by the certificate that signed it, and so on", a candidate path that "the relying party is still required" to build and validate;endEntityCertificate()is its first entry andtoCertificateChain()theCertificateChainof pki-framework.CoseCertHashisCOSE_CertHash = [ hashAlg: (int / tstr), hashValue: bstr ], the thumbprintx5tcarries.hashAlgorithm(Manager $manager)resolves the identifier through the hash registry of the application;matches(string $der, FilterOnlyHash $hash)compares withhash_equals();compute()makes one.
What follows from the RFC¶
- Integrity. Each of the four parameters "can be in either the protected or unprotected header bucket", yet "The
end-entity certificate MUST be integrity protected by COSE": by the parameter being in the protected bucket, by an
x5tin the protected bucket naming the certificate, or by the certificate being in theexternal_aad. The accessors take the protected value first and do not tell you which bucket answered;getProtectedHeaderParameter(CoseHeaders::LABEL_X5CHAIN)does, when the distinction matters to your policy. - Thumbprints are computed over the bytes as carried.
compute()andmatches()take the DER string, never a parsed certificate: a certificate re-encoded by an ASN.1 library is not always the byte string that was sent (the cose-wg/Examples certificates are a case in point), and only the bytes on the wire give two implementations the same digest. A thumbprint "does not provide any trust" (§5): it selects a candidate, which is then verified for real, so SHA-1 (-14) and SHA-256/64 (-15) are legitimate here andhashAlgorithm()returnsFilterOnlyHash. - The certificate is untrusted input until validated: "The presence of a self-signed certificate in the parameter
MUST NOT cause the update of the set of trust anchors without some out-of-band confirmation." Before using its key,
"the key MUST be checked against the algorithm to be used", which the registered
Signatureinstance does, with the policies it was created with, whenverifyWithX5Chain()calls it.
The *-sender parameters¶
The three header algorithm parameters of RFC 9360 §3,
x5t-sender (-27), x5u-sender (-28) and x5chain-sender (-29), identify the sender's key exchange certificate of
an ECDH-SS recipient, and getX5TSender(), getX5USender() and getX5ChainSender() read them exactly as their §2
counterparts: same structures, same rules, same absence of trust. The key they name is what the application hands to
the algorithm through RecipientLayer::withSenderKey(), after validating the chain, out of its own store, or from
wherever the URI it chose to fetch led; see What each family enforces. C509
certificates (a draft) are out of scope.
examples/13-x509-header-parameters.php runs the whole of this on the
certificates of cose-wg/Examples.