1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
package protocol
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"github.com/go-webauthn/webauthn/metadata"
"github.com/go-webauthn/webauthn/protocol/webauthncbor"
"github.com/go-webauthn/webauthn/protocol/webauthncose"
)
var u2fAttestationKey = "fido-u2f"
func init() {
RegisterAttestationFormat(u2fAttestationKey, verifyU2FFormat)
}
// verifyU2FFormat - Follows verification steps set out by https://www.w3.org/TR/webauthn/#fido-u2f-attestation
func verifyU2FFormat(att AttestationObject, clientDataHash []byte) (string, []interface{}, error) {
if !bytes.Equal(att.AuthData.AttData.AAGUID, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) {
return "", nil, ErrUnsupportedAlgorithm.WithDetails("U2F attestation format AAGUID not set to 0x00")
}
// Signing procedure step - If the credential public key of the given credential is not of
// algorithm -7 ("ES256"), stop and return an error.
key := webauthncose.EC2PublicKeyData{}
webauthncbor.Unmarshal(att.AuthData.AttData.CredentialPublicKey, &key)
if webauthncose.COSEAlgorithmIdentifier(key.PublicKeyData.Algorithm) != webauthncose.AlgES256 {
return "", nil, ErrUnsupportedAlgorithm.WithDetails("Non-ES256 Public Key algorithm used")
}
// U2F Step 1. Verify that attStmt is valid CBOR conforming to the syntax defined above
// and perform CBOR decoding on it to extract the contained fields.
// The Format/syntax is
// u2fStmtFormat = {
// x5c: [ attestnCert: bytes ],
// sig: bytes
// }
// Check for "x5c" which is a single element array containing the attestation certificate in X.509 format.
x5c, present := att.AttStatement["x5c"].([]interface{})
if !present {
return "", nil, ErrAttestationFormat.WithDetails("Missing properly formatted x5c data")
}
// Check for "sig" which is The attestation signature. The signature was calculated over the (raw) U2F
// registration response message https://www.w3.org/TR/webauthn/#biblio-fido-u2f-message-formats]
// received by the client from the authenticator.
signature, present := att.AttStatement["sig"].([]byte)
if !present {
return "", nil, ErrAttestationFormat.WithDetails("Missing sig data")
}
// U2F Step 2. (1) Check that x5c has exactly one element and let attCert be that element. (2) Let certificate public
// key be the public key conveyed by attCert. (3) If certificate public key is not an Elliptic Curve (EC) public
// key over the P-256 curve, terminate this algorithm and return an appropriate error.
// Step 2.1
if len(x5c) > 1 {
return "", nil, ErrAttestationFormat.WithDetails("Received more than one element in x5c values")
}
// Note: Packed Attestation, FIDO U2F Attestation, and Assertion Signatures support ASN.1,but it is recommended
// that any new attestation formats defined not use ASN.1 encodings, but instead represent signatures as equivalent
// fixed-length byte arrays without internal structure, using the same representations as used by COSE signatures
// as defined in RFC8152 (https://www.w3.org/TR/webauthn/#biblio-rfc8152)
// and RFC8230 (https://www.w3.org/TR/webauthn/#biblio-rfc8230).
// Step 2.2
asn1Bytes, decoded := x5c[0].([]byte)
if !decoded {
return "", nil, ErrAttestationFormat.WithDetails("Error decoding ASN.1 data from x5c")
}
attCert, err := x509.ParseCertificate(asn1Bytes)
if err != nil {
return "", nil, ErrAttestationFormat.WithDetails("Error parsing certificate from ASN.1 data into certificate")
}
// Step 2.3
if attCert.PublicKeyAlgorithm != x509.ECDSA && attCert.PublicKey.(*ecdsa.PublicKey).Curve != elliptic.P256() {
return "", nil, ErrAttestationFormat.WithDetails("Attestation certificate is in invalid format")
}
// Step 3. Extract the claimed rpIdHash from authenticatorData, and the claimed credentialId and credentialPublicKey
// from authenticatorData.attestedCredentialData.
rpIdHash := att.AuthData.RPIDHash
credentialID := att.AuthData.AttData.CredentialID
// credentialPublicKey handled earlier
// Step 4. Convert the COSE_KEY formatted credentialPublicKey (see Section 7 of RFC8152 [https://www.w3.org/TR/webauthn/#biblio-rfc8152])
// to Raw ANSI X9.62 public key format (see ALG_KEY_ECC_X962_RAW in Section 3.6.2 Public Key
// Representation Formats of FIDO-Registry [https://www.w3.org/TR/webauthn/#biblio-fido-registry]).
// Let x be the value corresponding to the "-2" key (representing x coordinate) in credentialPublicKey, and confirm
// its size to be of 32 bytes. If size differs or "-2" key is not found, terminate this algorithm and
// return an appropriate error.
// Let y be the value corresponding to the "-3" key (representing y coordinate) in credentialPublicKey, and confirm
// its size to be of 32 bytes. If size differs or "-3" key is not found, terminate this algorithm and
// return an appropriate error.
if len(key.XCoord) > 32 || len(key.YCoord) > 32 {
return "", nil, ErrAttestation.WithDetails("X or Y Coordinate for key is invalid length")
}
// Let publicKeyU2F be the concatenation 0x04 || x || y.
publicKeyU2F := bytes.NewBuffer([]byte{0x04})
publicKeyU2F.Write(key.XCoord)
publicKeyU2F.Write(key.YCoord)
// Step 5. Let verificationData be the concatenation of (0x00 || rpIdHash || clientDataHash || credentialId || publicKeyU2F)
// (see ยง4.3 of FIDO-U2F-Message-Formats [https://www.w3.org/TR/webauthn/#biblio-fido-u2f-message-formats]).
verificationData := bytes.NewBuffer([]byte{0x00})
verificationData.Write(rpIdHash)
verificationData.Write(clientDataHash)
verificationData.Write(credentialID)
verificationData.Write(publicKeyU2F.Bytes())
// Step 6. Verify the sig using verificationData and certificate public key per SEC1[https://www.w3.org/TR/webauthn/#biblio-sec1].
sigErr := attCert.CheckSignature(x509.ECDSAWithSHA256, verificationData.Bytes(), signature)
if sigErr != nil {
return "", nil, sigErr
}
// Step 7. If successful, return attestation type Basic with the attestation trust path set to x5c.
return string(metadata.BasicFull), x5c, sigErr
}
|