|
1 | 1 | package protocol |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "crypto/ecdsa" |
| 4 | + "crypto" |
| 5 | + "crypto/ed25519" |
| 6 | + "crypto/rsa" |
5 | 7 | "crypto/x509" |
6 | 8 | "encoding/asn1" |
7 | 9 | "encoding/pem" |
8 | 10 | "errors" |
9 | 11 | "fmt" |
| 12 | + "math/big" |
10 | 13 | "net" |
11 | 14 | "net/url" |
12 | 15 | "strings" |
@@ -187,35 +190,58 @@ func certInsecureConditionalNotAfterMangle(cert *x509.Certificate, mangle bool, |
187 | 190 | return out |
188 | 191 | } |
189 | 192 |
|
190 | | -func verifyAttestationECDSAPublicKeyMatch(att AttestationObject, cert *x509.Certificate) (attPublicKeyData webauthncose.EC2PublicKeyData, err error) { |
191 | | - var ( |
192 | | - key any |
193 | | - ok bool |
| 193 | +// verifyAttestationPublicKeyMatch verifies the credentialPublicKey of the attested credential data is the public key |
| 194 | +// of the given attestation certificate, and returns the credential public key parsed from its COSE encoding so a |
| 195 | +// signature made with it can be verified. |
| 196 | +// |
| 197 | +// The attestation statement formats which perform this step place no restriction on the key type, so every type the |
| 198 | +// COSE parser produces is accepted rather than ECDSA alone. |
| 199 | +func verifyAttestationPublicKeyMatch(att AttestationObject, cert *x509.Certificate) (credentialPublicKey any, err error) { |
| 200 | + if credentialPublicKey, err = webauthncose.ParsePublicKey(att.AuthData.AttData.CredentialPublicKey); err != nil { |
| 201 | + return nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error parsing public key: %+v", err)).WithError(err) |
| 202 | + } |
194 | 203 |
|
195 | | - publicKey, attPublicKey *ecdsa.PublicKey |
196 | | - ) |
| 204 | + var public crypto.PublicKey |
197 | 205 |
|
198 | | - if key, err = webauthncose.ParsePublicKey(att.AuthData.AttData.CredentialPublicKey); err != nil { |
199 | | - return attPublicKeyData, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error parsing public key: %+v", err)).WithError(err) |
| 206 | + if public, err = attestationCredentialPublicKey(credentialPublicKey); err != nil { |
| 207 | + return nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error converting public key: %+v", err)).WithError(err) |
200 | 208 | } |
201 | 209 |
|
202 | | - if attPublicKeyData, ok = key.(webauthncose.EC2PublicKeyData); !ok { |
203 | | - return attPublicKeyData, ErrInvalidAttestation.WithDetails("Attestation public key is not ECDSA") |
| 210 | + // Each standard library public key type carries an Equal method which reports false for a key of another type, so |
| 211 | + // a credential public key and a certificate public key of differing types are a mismatch rather than an error. |
| 212 | + equatable, ok := public.(interface{ Equal(x crypto.PublicKey) bool }) |
| 213 | + if !ok { |
| 214 | + return nil, ErrInvalidAttestation.WithDetails("Public key does not support comparison") |
204 | 215 | } |
205 | 216 |
|
206 | | - if publicKey, ok = cert.PublicKey.(*ecdsa.PublicKey); !ok { |
207 | | - return attPublicKeyData, ErrInvalidAttestation.WithDetails("Credential public key is not ECDSA") |
| 217 | + if !equatable.Equal(cert.PublicKey) { |
| 218 | + return nil, ErrInvalidAttestation.WithDetails("Certificate public key does not match public key in authData") |
208 | 219 | } |
209 | 220 |
|
210 | | - if attPublicKey, err = attPublicKeyData.ToECDSA(); err != nil { |
211 | | - return attPublicKeyData, ErrInvalidAttestation.WithDetails("Error converting public key to ECDSA").WithError(err) |
212 | | - } |
| 221 | + return credentialPublicKey, nil |
| 222 | +} |
213 | 223 |
|
214 | | - if !attPublicKey.Equal(publicKey) { |
215 | | - return attPublicKeyData, ErrInvalidAttestation.WithDetails("Certificate public key does not match public key in authData") |
216 | | - } |
| 224 | +// attestationCredentialPublicKey converts a credential public key parsed from its COSE encoding into the equivalent |
| 225 | +// standard library type. |
| 226 | +func attestationCredentialPublicKey(credentialPublicKey any) (public crypto.PublicKey, err error) { |
| 227 | + switch k := credentialPublicKey.(type) { |
| 228 | + case webauthncose.EC2PublicKeyData: |
| 229 | + return k.ToECDSA() |
| 230 | + case webauthncose.RSAPublicKeyData: |
| 231 | + var exponent int |
| 232 | + |
| 233 | + if exponent, err = webauthncose.ParseRSAPublicKeyDataExponent(&k); err != nil { |
| 234 | + return nil, err |
| 235 | + } |
217 | 236 |
|
218 | | - return attPublicKeyData, nil |
| 237 | + return &rsa.PublicKey{N: new(big.Int).SetBytes(k.Modulus), E: exponent}, nil |
| 238 | + case webauthncose.OKPPublicKeyData: |
| 239 | + // The coordinate is of the length ed25519 requires as webauthncose.ParsePublicKey rejects any other, so no |
| 240 | + // length is asserted here. |
| 241 | + return ed25519.PublicKey(k.XCoord), nil |
| 242 | + default: |
| 243 | + return nil, fmt.Errorf("unsupported public key type %T", credentialPublicKey) |
| 244 | + } |
219 | 245 | } |
220 | 246 |
|
221 | 247 | // ValidateRPID performs non-exhaustive checks to ensure the string is most likely a domain string as |
|
0 commit comments