web-auth/cose-lib
PHP 8.1+ COSE (RFC 9052/9053) library: sign, encrypt, and MAC with full tag support (Sign1/Sign, Encrypt0/Encrypt, Mac0/Mac). Supports ECDSA, EdDSA, RSA, and HMAC. Compatible with WebAuthn/FIDO2.
This library provides full support for COSE (CBOR Object Signing and Encryption) as defined in RFC 9052 and RFC 9053.
composer require web-auth/cose-lib
For COSE tag support, you also need:
composer require spomky-labs/cbor-php
COSE defines six main tags for different cryptographic operations:
| Tag | Value | Description |
|---|---|---|
| COSE_Sign1 | 18 | Single signature structure |
| COSE_Sign | 98 | Multiple signatures structure |
| COSE_Encrypt0 | 16 | Single recipient encrypted message |
| COSE_Encrypt | 96 | Multiple recipients encrypted message |
| COSE_Mac0 | 17 | MAC without recipients |
| COSE_Mac | 97 | MAC with recipients |
The COSE_Sign1 structure is used when a message has a single signer.
use CBOR\ByteStringObject;
use CBOR\MapItem;
use CBOR\MapObject;
use CBOR\NegativeIntegerObject;
use CBOR\UnsignedIntegerObject;
use Cose\Signature\CoseSign1Tag;
// Create headers
$protectedHeader = MapObject::create([
MapItem::create(
UnsignedIntegerObject::create(1), // alg label
NegativeIntegerObject::create(-7) // ES256 algorithm
),
]);
$unprotectedHeader = MapObject::create([
MapItem::create(
UnsignedIntegerObject::create(4), // kid label
ByteStringObject::create('my-key-id') // key identifier
),
]);
// Payload
$payload = ByteStringObject::create('Message to sign');
// Create signature (you would typically use a cryptographic library here)
$signature = ByteStringObject::create($yourSignatureBytes);
// Create the COSE_Sign1 tag
$coseSign1 = CoseSign1Tag::create(
$protectedHeader,
$unprotectedHeader,
$payload,
$signature
);
// Encode to CBOR
$encoded = (string) $coseSign1;
use CBOR\Decoder;
use CBOR\OtherObject\OtherObjectManager;
use CBOR\StringStream;
use CBOR\Tag\TagManager;
use Cose\Signature\CoseSign1Tag;
use Cose\Signature\Signature1;
// Setup decoder
$tagManager = TagManager::create()->add(CoseSign1Tag::class);
$decoder = Decoder::create($tagManager, OtherObjectManager::create());
// Decode CBOR data
$stream = new StringStream($encodedData);
$coseSign1 = $decoder->decode($stream);
// Access components
$protectedHeader = $coseSign1->getProtectedHeader(); // ByteStringObject
$protectedHeaderMap = $coseSign1->getProtectedHeaderAsMap(); // MapObject (decoded)
$unprotectedHeader = $coseSign1->getUnprotectedHeader(); // MapObject
$payload = $coseSign1->getPayload(); // ByteStringObject
$signature = $coseSign1->getSignature(); // ByteStringObject
// Use a custom decoder for protected header (e.g., with custom CBOR tags)
$customDecoder = Decoder::create(
TagManager::create()->add(MyCustomTag::class),
OtherObjectManager::create()
);
$protectedHeaderMap = $coseSign1->getProtectedHeaderAsMap($customDecoder);
// Create Sig_structure for verification
$sigStructure = Signature1::create(
$coseSign1->getProtectedHeader(),
$coseSign1->getPayload()
);
// Verify signature (example with OpenSSL and ECDSA)
$derSignature = ECSignature::toAsn1($signature->getValue(), 64);
$isValid = openssl_verify(
(string) $sigStructure,
$derSignature,
$publicKey,
'sha256'
);
The COSE_Sign structure supports multiple signatures from different signers.
use CBOR\ByteStringObject;
use CBOR\ListObject;
use CBOR\MapObject;
use Cose\Signature\CoseSignTag;
$protectedHeader = MapObject::create();
$unprotectedHeader = MapObject::create();
$payload = ByteStringObject::create('Document to be signed');
// Create signature structures for each signer
$signatures = ListObject::create([
ListObject::create([
ByteStringObject::create(''), // signature protected header
MapObject::create([/* signer 1 unprotected header */]),
ByteStringObject::create($signature1Bytes)
]),
ListObject::create([
ByteStringObject::create(''),
MapObject::create([/* signer 2 unprotected header */]),
ByteStringObject::create($signature2Bytes)
]),
]);
$coseSign = CoseSignTag::create(
$protectedHeader,
$unprotectedHeader,
$payload,
$signatures
);
use CBOR\ByteStringObject;
use CBOR\MapObject;
use Cose\Encryption\CoseEncrypt0Tag;
$protectedHeader = MapObject::create([/* algorithm, etc. */]);
$unprotectedHeader = MapObject::create([/* IV, kid, etc. */]);
$ciphertext = ByteStringObject::create($encryptedData);
$coseEncrypt0 = CoseEncrypt0Tag::create(
$protectedHeader,
$unprotectedHeader,
$ciphertext
);
use CBOR\ListObject;
use Cose\Encryption\CoseEncryptTag;
$recipients = ListObject::create([
ListObject::create([/* recipient 1 structure */]),
ListObject::create([/* recipient 2 structure */]),
]);
$coseEncrypt = CoseEncryptTag::create(
$protectedHeader,
$unprotectedHeader,
$ciphertext,
$recipients
);
use CBOR\ByteStringObject;
use CBOR\MapObject;
use Cose\Mac\CoseMac0Tag;
$protectedHeader = MapObject::create([/* algorithm */]);
$unprotectedHeader = MapObject::create();
$payload = ByteStringObject::create('Data to authenticate');
$tag = ByteStringObject::create($macTag);
$coseMac0 = CoseMac0Tag::create(
$protectedHeader,
$unprotectedHeader,
$payload,
$tag
);
use CBOR\ListObject;
use Cose\Mac\CoseMacTag;
$recipients = ListObject::create([/* recipient structures */]);
$coseMac = CoseMacTag::create(
$protectedHeader,
$unprotectedHeader,
$payload,
$tag,
$recipients
);
ECDSA
EdDSA
RSA
The following header parameters are commonly used in COSE structures:
| Label | Name | Type | Description |
|---|---|---|---|
| 1 | alg | int | Cryptographic algorithm |
| 2 | crit | [+label] | Critical headers |
| 3 | content type | tstr / uint | Content type of payload |
| 4 | kid | bstr | Key identifier |
| 5 | IV | bstr | Initialization Vector |
| 6 | Partial IV | bstr | Partial Initialization Vector |
Complete examples can be found in the tests/ directory:
tests/Signature/CoseSign1CreateAndVerifyTest.php - COVID certificate verificationtests/Signature/CoseSignTagTest.php - Multiple signaturestests/Encryption/CoseEncrypt0TagTest.php - Single recipient encryptiontests/Mac/CoseMac0TagTest.php - MAC without recipientsHow can I help you explore Laravel packages today?