Skip to content

Commit

Permalink
fido-u2f attestation verification (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
e3b0c442 authored Jan 21, 2020
1 parent f22f3da commit 40cb145
Show file tree
Hide file tree
Showing 10 changed files with 752 additions and 157 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.3.0] - 2020-01-21]
### Added
- Attestation verification for the _fido-u2f_ format has been added. The attestation type and trust path are __not__ validated, only the signature against the provided certificate. It is up to the implementor to verify the trust chain using the `*AttestationObject` returned from `FinishRegistration`.
### Changed
- Minor changes to fix static analysis deficiencies discovered with `staticcheck`
- Added contributing instructions
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,8 @@ _warp_ was built with the following goals in mind:
* Supported: ES256, ES384, ES512, EdDSA, RS1, RS256, RS384, RS512, PS256, PS384, PS512
* To be implemented: None plannned
* Attestation formats
* Supported: _none_
* To be implemented: _packed_, _tpm_, _android-key_, _android-safetynet_, _fido-u2f_
* Supported: _fido-u2f_, _none_
* To be implemented: _packed_, _tpm_, _android-key_, _android-safetynet_
* Defined extensions
* Supported: _appid_
* To be implemented: _txAuthSimple_, _txAuthGeneric_, _authnSel_, _exts_, _uvi_, _loc_, _uvm_, _biometricPerfBounds_
Expand Down
101 changes: 101 additions & 0 deletions attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package warp

import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"

"github.com/fxamacker/cbor"
)
Expand Down Expand Up @@ -88,3 +91,101 @@ func VerifyNoneAttestationStatement(attStmt []byte, _ []byte, _ [32]byte) error
}
return nil
}

//FIDOU2FAttestationStatement represents a decoded attestation statement of type
//"fido-u2f"
type FIDOU2FAttestationStatement struct {
X5C [][]byte `cbor:"x5c"`
Sig []byte `cbor:"sig"`
}

//VerifyFIDOU2FAttestationStatement verifies that an attestation statement of
//type "fido-u2f" is valid
func VerifyFIDOU2FAttestationStatement(attStmt []byte, rawAuthData []byte, clientDataHash [32]byte) error {
//1. Verify that attStmt is valid CBOR conforming to the syntax defined
//above and perform CBOR decoding on it to extract the contained fields.
var att FIDOU2FAttestationStatement
if err := cbor.Unmarshal(attStmt, &att); err != nil {
return ErrVerifyAttestation.Wrap(NewError("fido-u2f attestation statement not valid cbor").Wrap(err))
}

//2. Check that x5c has exactly one element and let attCert be that element.
//Let certificate public key be the public key conveyed by attCert. 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.
if len(att.X5C) != 1 {
return ErrVerifyAttestation.Wrap(NewError("x5c has %d members, expected 1", len(att.X5C)))
}
cert, err := x509.ParseCertificate(att.X5C[0])
if err != nil {
return ErrVerifyAttestation.Wrap(NewError("error parsing attestation certificate"))
}
publicKey, ok := cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
return ErrVerifyAttestation.Wrap(NewError("certificate public key not ecdsa"))
}
if publicKey.Curve != elliptic.P256() {
return ErrVerifyAttestation.Wrap(NewError("certificate public key not on P-256 curve"))
}

//3. Extract the claimed rpIdHash from authenticatorData, and the claimed
//credentialId and credentialPublicKey from
//authenticatorData.attestedCredentialData.
var authData AuthenticatorData
if err := (&authData).UnmarshalBinary(rawAuthData); err != nil {
return ErrVerifyAttestation.Wrap(NewError("error parsing auth data").Wrap(err))
}

//4. Convert the COSE_KEY formatted credentialPublicKey (see Section 7 of
//[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]).
var cosePublicKey COSEKey
if err := cbor.Unmarshal(authData.AttestedCredentialData.CredentialPublicKey, &cosePublicKey); err != nil {
return ErrVerifyAttestation.Wrap(NewError("error parsing credential public key").Wrap(err))
}

//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, y []byte
if err := cbor.Unmarshal(cosePublicKey.XOrE, &x); err != nil {
return ErrVerifyAttestation.Wrap(NewError("error parsing public key x parameter").Wrap(err))
}
if len(x) != 32 {
return ErrVerifyAttestation.Wrap(NewError("unexpected length %d for public key x param", len(x)))
}

//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 err := cbor.Unmarshal(cosePublicKey.Y, &y); err != nil {
return ErrVerifyAttestation.Wrap(NewError("error parsing public key y parameter").Wrap(err))
}
if len(y) != 32 {
return ErrVerifyAttestation.Wrap(NewError("unexpected length %d for public key y param", len(y)))
}

//Let publicKeyU2F be the concatenation 0x04 || x || y.
publicKeyU2F := append(append([]byte{0x04}, x...), y...)

//Let verificationData be the concatenation of (0x00 || rpIdHash ||
//clientDataHash || credentialId || publicKeyU2F) (see Section 4.3 of
//[FIDO-U2F-Message-Formats]).
verificationData := append(
append(
append(
append(
[]byte{0x00}, authData.RPIDHash[:]...,
), clientDataHash[:]...,
), authData.AttestedCredentialData.CredentialID...,
), publicKeyU2F...,
)

if err = cert.CheckSignature(x509.ECDSAWithSHA256, verificationData, att.Sig); err != nil {
return ErrVerifyAttestation.Wrap(NewError("error verifying certificate").Wrap(err))
}

return nil
}
Loading

0 comments on commit 40cb145

Please sign in to comment.