CBOR Object Signing and Encryption (COSE) for PHP: signing, MAC, encryption and key management over the COSE messages of spomky-labs/cbor-php, with the header rules and the cryptographic structures of RFC 9052 applied for you. Used by WebAuthn / FIDO2 relying parties, by verifiers of the EU Digital COVID Certificate, and wherever a CBOR Web Token or a COSE message has to be produced or checked.
This library implements:
- RFC 9052 - COSE: Structures and Process
- RFC 9053 - COSE: Initial Algorithms (signatures, HMAC, AES-CBC-MAC, the AEAD content encryption algorithms, the key management algorithms (direct, HKDF, AES Key Wrap, ECDH) and the key types)
- RFC 8230 - RSASSA-PSS (PS256, PS384, PS512) and the RSA key type
- RFC 8812 - RSASSA-PKCS1-v1_5 (RS256, RS384, RS512, RS1) and ECDSA with secp256k1 (ES256K)
- RFC 9864 - COSE: Fully-Specified Algorithms
- RFC 9596 - COSE "typ" (type) Header Parameter
- RFC 9597 - CWT Claims in COSE Headers
- RFC 9054 - COSE: Hash Algorithms
- RFC 9679 - COSE Key Thumbprint
- RFC 9360 - COSE: Header Parameters for Carrying and Referencing X.509 Certificates
- RFC 9995 - COSE Hash Envelope
- RFC 9338 - COSE: Countersignatures (version 2, labels 11 and 12)
- RFC 9964 - ML-DSA for JOSE and COSE: ML-DSA-44/65/87 and the AKP key type
- RFC 9942 - COSE Receipts: the
receipts,vdsandvdpheader parameters and theRFC9162_SHA256verifiable data structure - RFC 9921 - COSE Header Parameter for Timestamp Tokens as
Defined in RFC 3161:
3161-ttcand3161-ctt, carried and bound to the message, not validated
Every identifier the library ships is listed with the RFC section that defines it in Supported Algorithms, and a test keeps that list in step with the classes and with the IANA registry.
composer require web-auth/cose-lib "spomky-labs/cbor-php:^3.4"PHP 8.1 or later, with ext-openssl; ext-sodium for Ed25519, ext-gmp or ext-bcmath recommended. The
platform-dependent algorithms (Ed448, the Brainpool curves, AES-CCM, ChaCha20/Poly1305, SHAKE, and ML-DSA, which
needs PHP 8.4 and OpenSSL 3.5 at runtime) each expose an isSupported(). See Installation.
Verifying a COSE_Sign1: the message class is cbor-php's, the header rules and the Sig_structure are this
library's:
use CBOR\Decoder;
use CBOR\ListObject;
use CBOR\OtherObject\NullObject;
use CBOR\StringStream;
use CBOR\Tag\CoseSign1Tag;
use Cose\Algorithm\Signature\ECDSA\ES256;
use Cose\Key\Ec2Key;
use Cose\Signature\Signature1;
use Cose\Structure\CoseHeaders;
// The key of the signer you trust, and the algorithm you expect it to be used with.
$key = Ec2Key::create($theCoseKeyYouPinned);
$algorithm = ES256::create();
// The protected header labels this application knows how to process (1 = alg, 2 = crit).
$understoodLabels = [1, 2];
// cbor-php 3.4.0 registers the six COSE tags in the default decoder: tag 18 resolves on its own.
$coseSign1 = Decoder::create()->decode(new StringStream($encodedData));
if (! $coseSign1 instanceof CoseSign1Tag) {
throw new RuntimeException('Not a COSE_Sign1 message');
}
// cbor-php carries the header buckets; this library reads them the way RFC 9052 defines them.
$headers = CoseHeaders::fromMessage($coseSign1);
// RFC 9052 §3.1: bind the signature to the algorithm the protected header declares.
// The label is matched by type as well as by value, so the text string "1", a different label under §1.5,
// never answers a lookup for the integer label 1.
$alg = $headers->getProtectedHeaderParameter(1);
if ($alg === null || (int) $alg->normalize() !== $algorithm::identifier()) {
throw new RuntimeException('Unexpected or missing "alg" in the protected header');
}
// RFC 9052 §3.1: every parameter listed in "crit" must be processed, or the message must be rejected.
$crit = $headers->getProtectedHeaderParameter(2);
if ($crit !== null) {
if (! $crit instanceof ListObject) {
throw new RuntimeException('"crit" is not an array');
}
foreach ($crit as $label) {
if (! in_array((int) $label->normalize(), $understoodLabels, true)) {
throw new RuntimeException('Unsupported critical header parameter');
}
}
}
// RFC 9052 §4.2: a nil payload is detached and the application supplies the content itself.
$payload = $coseSign1->getPayload();
if ($payload instanceof NullObject) {
throw new RuntimeException('The payload is detached; supply it from the application');
}
// Verify the Sig_structure the signature covers
$sigStructure = Signature1::create($coseSign1->getProtectedHeader(), $payload);
$isValid = $algorithm->verify((string) $sigStructure, $key, $coseSign1->getSignature()->getValue());Important
The library verifies signatures; it does not decide what a message is allowed to say. Checking that alg is the one
expected for that key, and refusing any crit label the application does not process, are the caller's
responsibility (RFC 9052 §3.1); the snippet above is
the shape they take. tests/Signature/DocumentedVerifierTest.php runs exactly this code.
Signing is the same structure the other way round; examples/01-sign1.php is the whole
round trip, key generation included, and Signing and Verifying explains each step.
The documentation index lists every chapter:
| Chapter | What it covers |
|---|---|
| Installation | Requirements, optional extensions and platform checks, performance, running the tests |
| Messages, Structures and Headers | The six COSE tags, the Sig_structure / MAC_structure / Enc_structure classes, reading headers the RFC 9052 way |
| Signing and Verifying | COSE_Sign1 and COSE_Sign, what the application must check, verifying with a certificate |
| Countersignatures | The version 2 countersignatures of RFC 9338, full and abbreviated, on any of the eight targets |
| Message Authentication Codes | COSE_Mac0 and COSE_Mac |
| Encryption | COSE_Encrypt0 and COSE_Encrypt, the nonce |
| Key Management | direct, HKDF, AES Key Wrap and ECDH recipients |
| CBOR Web Tokens | Verifying a CWT, typ and CWT Claims |
| X.509 Header Parameters | x5bag, x5chain, x5t, x5u, and where the library stops |
| Hash Envelope | RFC 9995: a signature over the digest of a payload kept elsewhere |
| COSE Receipts | RFC 9942: receipts, vds, vdp and the RFC9162_SHA256 Merkle proofs, and where the library stops |
| RFC 3161 Timestamp Tokens | RFC 9921: 3161-ttc and 3161-ctt, the message imprint of each mode, and where the library stops |
| Supported Algorithms | Every identifier with its RFC reference; the Manager |
| Keys | Key types and curves, AKP keys, alg / key_ops restrictions, key validation, thumbprints |
| Upgrading | Moving off the deprecated Cose\...Tag classes |
examples/ holds a runnable program per topic, and RELEASES.md the supported
branches and what changed in each release.
- The library verifies, it does not decide. Binding
alg, processingcrit, validating a certificate chain, fetching anx5uor apayload-location, trusting the issuer of a receipt or a timestamp, comparing header claims with payload claims: all of that is the application's, and the documentation says so wherever it applies. No chain is validated, no URI is fetched and no trust is established by this library. - Weak-but-needed algorithms warn until acknowledged.
RS1(SHA-1), the non-standardEd256/Ed512, an HMAC key shorter than the hash output and an RSA modulus below 2048 bits emit anE_USER_WARNINGtoday and will throw in the next major version; each has an explicit acknowledgement, see Algorithms and Keys. - ES256 (-7) and EdDSA (-8) stay first-class although RFC 9864 marks them Deprecated at IANA: WebAuthn authenticators emit them and will for years. Register the fully-specified identifiers next to them.
- Key restrictions (
alg,key_ops) are enforced by default by the content encryption and key management algorithms, opt-in for signatures and MACs so that existing keys keep working.
Contributions are welcome! Please see CONTRIBUTING.md for details. The test suite runs
with castor phpunit in the project QA container, or composer test on a host that provides PHPUnit 11 as
phpunit-11; see Running the test suite.
For security vulnerabilities, do not open an issue: report them privately through GitHub private vulnerability reporting or by e-mail to security [at] spomky-labs.com. See SECURITY.md.
I bring solutions to your problems and answer your questions.
If you really love this project and the work I have done, or if you want me to prioritize your issues, you can support me:
This software is released under the MIT License.
Maintained by Florent Morselli and contributors.
Made with ❤️ for the PHP community