Verifying with a certificate¶
WebAuthn Level 3 §8.2 to §8.4 ask a relying party to verify a packed (x5c), TPM or android-key attestation statement
"with the algorithm specified in alg", against the key of the attestation certificate. Going through
Algorithms::getOpensslAlgorithmFor() and openssl_verify() only reaches the algorithms an OPENSSL_ALGO_* digest
can describe, ECDSA and RSASSA-PKCS1-v1_5, because that digest implies PKCS #1 v1.5 padding; RSASSA-PSS, EdDSA,
Ed25519 and Ed448 cannot be expressed that way at all. That is the documented scope of Algorithms::COSE_ALGORITHM_MAP
and of the accessor that reads it.
Cose\Algorithm\Signature\CertificateSignatureVerifier takes the other route: the key of the certificate becomes a
Cose\Key\Key, and the Signature class registered for the identifier verifies with it.
use Cose\Algorithm\Manager;
use Cose\Algorithm\Signature\CertificateSignatureVerifier;
use Cose\Algorithm\Signature\ECDSA\ES256;
use Cose\Algorithm\Signature\ECDSA\ES256K;
use Cose\Algorithm\Signature\FullySpecified\Ed25519;
use Cose\Algorithm\Signature\RSA\PS256;
$manager = Manager::create()->add(ES256::create(), ES256K::create(), PS256::create(), Ed25519::create());
$verifier = CertificateSignatureVerifier::create($manager);
$isValid = $verifier->verify($alg, $certificatePem, $data, $signature);
The set of acceptable algorithms is the Manager the operator built, not a constant of this library: an alg that
comes from the wire cannot select a verifier that was never registered. RS1 in particular is only reachable when an
RS1 instance was registered, which can only be built by acknowledging what SHA-1 is.
The verifier configures nothing of its own, so every policy a registered algorithm carries applies unchanged: it is that very instance which verifies, and the minimum modulus length an RSA algorithm was created with (see Validating RSA keys) is the one enforced against the key of the certificate.
use Cose\Algorithm\Signature\RSA\RS256;
use Cose\Key\RsaKeyValidator;
// This RS256 refuses a certificate whose modulus is shorter than 3072 bits, through the verifier as anywhere else.
$manager = Manager::create()->add(RS256::create(RsaKeyValidator::create(minimumModulusLength: 3072)));
verify() returns false for every signature the algorithm rejects, and throws an InvalidArgumentException when the
certificate cannot be read, when no signature algorithm is registered for the identifier, or when the key of the
certificate cannot be used with that algorithm, the same contract as Signature::verify().
When the certificate travels in the message, verifyWithX5Chain() takes the x5chain header parameter
(RFC 9360) as CoseHeaders::getX5Chain() returns it and verifies with
its end-entity certificate; see X.509 header parameters, and in particular what it says about validating
the rest of the chain.
When the certificate itself is not at hand, verifySubjectPublicKeyInfo() takes a SubjectPublicKeyInfo instead, and
Cose\Key\PublicKeyLoader exposes the conversion on its own, see
Loading a key from a certificate.