Exemple #1
0
        public override (AttestationType, X509Certificate2[]) Verify()
        {
            // verify that aaguid is 16 empty bytes (note: required by fido2 conformance testing, could not find this in spec?)
            if (0 != AuthData.AttestedCredentialData.AaGuid.CompareTo(Guid.Empty))
            {
                throw new Fido2VerificationException("Aaguid was not empty parsing fido-u2f atttestation statement");
            }

            // https://www.w3.org/TR/webauthn/#fido-u2f-attestation
            // 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields.
            // (handled in base class)
            if (null == X5c || CBORType.Array != X5c.Type || X5c.Count != 1)
            {
                throw new Fido2VerificationException("Malformed x5c in fido - u2f attestation");
            }

            // 2a. Check that x5c has exactly one element and let attCert be that element.
            if (null == X5c.Values || 0 == X5c.Values.Count ||
                CBORType.ByteString != X5c.Values.First().Type ||
                0 == X5c.Values.First().GetByteString().Length)
            {
                throw new Fido2VerificationException("Malformed x5c in fido-u2f attestation");
            }

            var attCert = new X509Certificate2(X5c.Values.First().GetByteString());

            // TODO : Check why this variable isn't used. Remove it or use it.
            var u2ftransports = U2FTransportsFromAttnCert(attCert.Extensions);

            // 2b. 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
            var pubKey    = attCert.GetECDsaPublicKey();
            var keyParams = pubKey.ExportParameters(false);

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                if (!keyParams.Curve.Oid.FriendlyName.Equals(ECCurve.NamedCurves.nistP256.Oid.FriendlyName))
                {
                    throw new Fido2VerificationException("Attestation certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve");
                }
            }
            else
            {
                if (!keyParams.Curve.Oid.Value.Equals(ECCurve.NamedCurves.nistP256.Oid.Value))
                {
                    throw new Fido2VerificationException("Attestation certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve");
                }
            }

            // 3. Extract the claimed rpIdHash from authenticatorData, and the claimed credentialId and credentialPublicKey from authenticatorData
            // see rpIdHash, credentialId, and credentialPublicKey members of base class AuthenticatorData (AuthData)

            // 4. Convert the COSE_KEY formatted credentialPublicKey (see Section 7 of [RFC8152]) to CTAP1/U2F public Key format (Raw ANSI X9.62 public key format)
            // 4a. 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
            var x = CredentialPublicKey[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString();

            // 4b. 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
            var y = CredentialPublicKey[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString();

            // 4c.Let publicKeyU2F be the concatenation 0x04 || x || y
            var publicKeyU2F = new byte[1] {
                0x4
            }.Concat(x).Concat(y).ToArray();

            // 5. Let verificationData be the concatenation of (0x00 || rpIdHash || clientDataHash || credentialId || publicKeyU2F)
            var verificationData = new byte[1] {
                0x00
            };

            verificationData = verificationData
                               .Concat(AuthData.RpIdHash)
                               .Concat(clientDataHash)
                               .Concat(AuthData.AttestedCredentialData.CredentialID)
                               .Concat(publicKeyU2F.ToArray())
                               .ToArray();

            // 6. Verify the sig using verificationData and certificate public key
            if (null == Sig || CBORType.ByteString != Sig.Type || 0 == Sig.GetByteString().Length)
            {
                throw new Fido2VerificationException("Invalid fido-u2f attestation signature");
            }

            byte[] ecsig;
            try
            {
                ecsig = CryptoUtils.SigFromEcDsaSig(Sig.GetByteString(), pubKey.KeySize);
            }
            catch (Exception ex)
            {
                throw new Fido2VerificationException("Failed to decode fido-u2f attestation signature from ASN.1 encoded form", ex);
            }

            var coseAlg = CredentialPublicKey[CBORObject.FromObject(COSE.KeyCommonParameter.Alg)].AsInt32();
            var hashAlg = CryptoUtils.HashAlgFromCOSEAlg(coseAlg);

            if (true != pubKey.VerifyData(verificationData, ecsig, hashAlg))
            {
                throw new Fido2VerificationException("Invalid fido-u2f attestation signature");
            }

            // 7. Optionally, inspect x5c and consult externally provided knowledge to determine whether attStmt conveys a Basic or AttCA attestation
            var trustPath = X5c.Values
                            .Select(x => new X509Certificate2(x.GetByteString()))
                            .ToArray();

            return(AttestationType.AttCa, trustPath);
        }
        public override (AttestationType, X509Certificate2[]) Verify()
        {
            // 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields
            // (handled in base class)
            if (0 == attStmt.Keys.Count || 0 == attStmt.Values.Count)
            {
                throw new Fido2VerificationException("Attestation format android-key must have attestation statement");
            }

            if (null == Sig || CBORType.ByteString != Sig.Type || 0 == Sig.GetByteString().Length)
            {
                throw new Fido2VerificationException("Invalid android-key attestation signature");
            }
            // 2. Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash
            // using the attestation public key in attestnCert with the algorithm specified in alg
            if (null == X5c || CBORType.Array != X5c.Type || 0 == X5c.Count)
            {
                throw new Fido2VerificationException("Malformed x5c in android-key attestation");
            }

            if (null == X5c.Values || 0 == X5c.Values.Count ||
                CBORType.ByteString != X5c.Values.First().Type ||
                0 == X5c.Values.First().GetByteString().Length)
            {
                throw new Fido2VerificationException("Malformed x5c in android-key attestation");
            }

            X509Certificate2 androidKeyCert;
            ECDsa            androidKeyPubKey;

            try
            {
                androidKeyCert   = new X509Certificate2(X5c.Values.First().GetByteString());
                androidKeyPubKey = androidKeyCert.GetECDsaPublicKey(); // attestation public key
            }
            catch (Exception ex)
            {
                throw new Fido2VerificationException("Failed to extract public key from android key: " + ex.Message, ex);
            }

            if (null == Alg || true != Alg.IsNumber)
            {
                throw new Fido2VerificationException("Invalid android key attestation algorithm");
            }

            byte[] ecsig;
            try
            {
                ecsig = CryptoUtils.SigFromEcDsaSig(Sig.GetByteString(), androidKeyPubKey.KeySize);
            }
            catch (Exception ex)
            {
                throw new Fido2VerificationException("Failed to decode android key attestation signature from ASN.1 encoded form", ex);
            }

            if (true != androidKeyPubKey.VerifyData(Data, ecsig, CryptoUtils.HashAlgFromCOSEAlg(Alg.AsInt32())))
            {
                throw new Fido2VerificationException("Invalid android key attestation signature");
            }

            // 3. Verify that the public key in the first certificate in x5c matches the credentialPublicKey in the attestedCredentialData in authenticatorData.
            if (true != AuthData.AttestedCredentialData.CredentialPublicKey.Verify(Data, Sig.GetByteString()))
            {
                throw new Fido2VerificationException("Incorrect credentialPublicKey in android key attestation");
            }

            // 4. Verify that the attestationChallenge field in the attestation certificate extension data is identical to clientDataHash
            var attExtBytes = AttestationExtensionBytes(androidKeyCert.Extensions);

            if (null == attExtBytes)
            {
                throw new Fido2VerificationException("Android key attestation certificate contains no AttestationRecord extension");
            }

            try
            {
                var attestationChallenge = GetAttestationChallenge(attExtBytes);
                if (false == clientDataHash.SequenceEqual(attestationChallenge))
                {
                    throw new Fido2VerificationException("Mismatch between attestationChallenge and hashedClientDataJson verifying android key attestation certificate extension");
                }
            }
            catch (Exception)
            {
                throw new Fido2VerificationException("Malformed android key AttestationRecord extension verifying android key attestation certificate extension");
            }

            // 5. Verify the following using the appropriate authorization list from the attestation certificate extension data

            // 5a. The AuthorizationList.allApplications field is not present, since PublicKeyCredential MUST be bound to the RP ID
            if (true == FindAllApplicationsField(attExtBytes))
            {
                throw new Fido2VerificationException("Found all applications field in android key attestation certificate extension");
            }

            // 5bi. The value in the AuthorizationList.origin field is equal to KM_ORIGIN_GENERATED ( which == 0).
            if (false == IsOriginGenerated(attExtBytes))
            {
                throw new Fido2VerificationException("Found origin field not set to KM_ORIGIN_GENERATED in android key attestation certificate extension");
            }

            // 5bii. The value in the AuthorizationList.purpose field is equal to KM_PURPOSE_SIGN (which == 2).
            if (false == IsPurposeSign(attExtBytes))
            {
                throw new Fido2VerificationException("Found purpose field not set to KM_PURPOSE_SIGN in android key attestation certificate extension");
            }

            var trustPath = X5c.Values
                            .Select(x => new X509Certificate2(x.GetByteString()))
                            .ToArray();

            return(AttestationType.Basic, trustPath);
        }