Skip to content

Commit

Permalink
feat: enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
james-d-elliott committed Jan 4, 2025
1 parent 065a6b8 commit 99d717d
Show file tree
Hide file tree
Showing 17 changed files with 165 additions and 144 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ func beginLogin() {
allowList := make([]protocol.CredentialDescriptor, 1)
allowList[0] = protocol.CredentialDescriptor{
CredentialID: credentialToAllowID,
Type: protocol.CredentialType("public-key"),
Type: protocol.PublicKeyCredentialType,
}

user := datastore.GetUser() // Get the user
Expand Down
20 changes: 10 additions & 10 deletions protocol/assertion.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ func ParseCredentialRequestResponse(response *http.Request) (*ParsedCredentialAs
return nil, ErrBadRequest.WithDetails("No response given")
}

defer response.Body.Close()
defer io.Copy(io.Discard, response.Body)
defer func(request *http.Request) {
_, _ = io.Copy(io.Discard, request.Body)
_ = request.Body.Close()
}(response)

return ParseCredentialRequestResponseBody(response.Body)
}
Expand Down Expand Up @@ -98,17 +100,15 @@ func (car CredentialAssertionResponse) Parse() (par *ParsedCredentialAssertionDa
return nil, ErrBadRequest.WithDetails("CredentialAssertionResponse with ID not base64url encoded")
}

if car.Type != "public-key" {
if car.Type != string(PublicKeyCredentialType) {
return nil, ErrBadRequest.WithDetails("CredentialAssertionResponse with bad type")
}

var attachment AuthenticatorAttachment

switch car.AuthenticatorAttachment {
case "platform":
attachment = Platform
case "cross-platform":
attachment = CrossPlatform
switch att := AuthenticatorAttachment(car.AuthenticatorAttachment); att {
case Platform, CrossPlatform:
attachment = att
}

par = &ParsedCredentialAssertionData{
Expand Down Expand Up @@ -143,9 +143,9 @@ func (p *ParsedCredentialAssertionData) Verify(storedChallenge string, relyingPa
// Steps 4 through 6 in verifying the assertion data (https://www.w3.org/TR/webauthn/#verifying-assertion) are
// "assertive" steps, i.e. "Let JSONtext be the result of running UTF-8 decode on the value of cData."
// We handle these steps in part as we verify but also beforehand

//
// Handle steps 7 through 10 of assertion by verifying stored data against the Collected Client Data
// returned by the authenticator
// returned by the authenticator.
validError := p.Response.CollectedClientData.Verify(storedChallenge, AssertCeremony, rpOrigins, rpTopOrigins, rpTopOriginsVerify)
if validError != nil {
return validError
Expand Down
4 changes: 2 additions & 2 deletions protocol/assertion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestParseCredentialRequestResponse(t *testing.T) {
ParsedPublicKeyCredential: ParsedPublicKeyCredential{
ParsedCredential: ParsedCredential{
ID: "AI7D5q2P0LS-Fal9ZT7CHM2N5BLbUunF92T8b6iYC199bO2kagSuU05-5dZGqb1SP0A0lyTWng",
Type: "public-key",
Type: string(PublicKeyCredentialType),
},
RawID: byteID,
ClientExtensionResults: map[string]any{
Expand Down Expand Up @@ -74,7 +74,7 @@ func TestParseCredentialRequestResponse(t *testing.T) {
Raw: CredentialAssertionResponse{
PublicKeyCredential: PublicKeyCredential{
Credential: Credential{
Type: "public-key",
Type: string(PublicKeyCredentialType),
ID: "AI7D5q2P0LS-Fal9ZT7CHM2N5BLbUunF92T8b6iYC199bO2kagSuU05-5dZGqb1SP0A0lyTWng",
},
RawID: byteID,
Expand Down
79 changes: 5 additions & 74 deletions protocol/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package protocol
import (
"context"
"crypto/sha256"
"crypto/x509"
"encoding/json"
"fmt"

Expand Down Expand Up @@ -144,12 +143,12 @@ func (a *AttestationObject) VerifyAttestation(clientDataHash []byte, mds metadat
// list of registered WebAuthn Attestation Statement Format Identifier
// values is maintained in the IANA registry of the same name
// [WebAuthn-Registries] (https://www.w3.org/TR/webauthn/#biblio-webauthn-registries).

//
// Since there is not an active registry yet, we'll check it against our internal
// Supported types.

//
// But first let's make sure attestation is present. If it isn't, we don't need to handle
// any of the following steps
// any of the following steps.
if AttestationFormat(a.Format) == AttestationFormatNone {
if len(a.AttStatement) != 0 {
return ErrAttestationFormat.WithInfo("Attestation format none with attestation present")
Expand All @@ -173,7 +172,6 @@ func (a *AttestationObject) VerifyAttestation(clientDataHash []byte, mds metadat

var (
aaguid uuid.UUID
entry *metadata.Entry
)

if len(a.AuthData.AttData.AAGUID) != 0 {
Expand All @@ -188,75 +186,8 @@ func (a *AttestationObject) VerifyAttestation(clientDataHash []byte, mds metadat

var protoErr *Error

ctx := context.Background()

if entry, protoErr = ValidateMetadata(context.Background(), aaguid, attestationType, mds); protoErr != nil {
return ErrInvalidAttestation.WithInfo(fmt.Sprintf("Error occurred validating metadata during attestation validation: %+v", err)).WithDetails(protoErr.DevInfo)
}

if entry == nil {
return nil
}

if mds.GetValidateTrustAnchor(ctx) {
if x5cs == nil {
return nil
}

var (
x5c, parsed *x509.Certificate
x5cis []*x509.Certificate
raw []byte
ok bool
)

if len(x5cs) == 0 {
return ErrInvalidAttestation.WithDetails("Unable to parse attestation certificate from x5c during attestation validation").WithInfo("The attestation had no certificates")
}

for _, x5cAny := range x5cs {
if raw, ok = x5cAny.([]byte); !ok {
return ErrInvalidAttestation.WithDetails("Unable to parse attestation certificate from x5c during attestation validation").WithInfo(fmt.Sprintf("The first certificate in the attestation was type '%T' but '[]byte' was expected", x5cs[0]))
}

if parsed, err = x509.ParseCertificate(raw); err != nil {
return ErrInvalidAttestation.WithDetails("Unable to parse attestation certificate from x5c during attestation validation").WithInfo(fmt.Sprintf("Error returned from x509.ParseCertificate: %+v", err))
}

if x5c == nil {
x5c = parsed
} else {
x5cis = append(x5cis, parsed)
}
}

if attestationType == string(metadata.AttCA) {
if err = tpmParseSANExtension(x5c); err != nil {
return err
}

if err = tpmRemoveEKU(x5c); err != nil {
return err
}

for _, parent := range x5cis {
if err = tpmRemoveEKU(parent); err != nil {
return err
}
}
}

if x5c != nil && x5c.Subject.CommonName != x5c.Issuer.CommonName {
if !entry.MetadataStatement.AttestationTypes.HasBasicFull() {
return ErrInvalidAttestation.WithDetails("Unable to validate attestation statement signature during attestation validation: attestation with full attestation from authenticator that does not support full attestation")
}

verifier := entry.MetadataStatement.Verifier(x5cis)

if _, err = x5c.Verify(verifier); err != nil {
return ErrInvalidAttestation.WithDetails(fmt.Sprintf("Unable to validate attestation signature statement during attestation validation: invalid certificate chain from MDS: %v", err))
}
}
if protoErr = ValidateMetadata(context.Background(), mds, aaguid, attestationType, x5cs); protoErr != nil {
return ErrInvalidAttestation.WithInfo(fmt.Sprintf("Error occurred validating metadata during attestation validation: %+v", protoErr)).WithDetails(protoErr.DevInfo)
}

return nil
Expand Down
6 changes: 1 addition & 5 deletions protocol/attestation_androidkey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,10 @@ func TestVerifyAndroidKeyFormat(t *testing.T) {
t.Errorf("verifyAndroidKeyFormat() error = %v, wantErr %v", err, tt.wantErr)
return
}

if got != tt.want {
t.Errorf("verifyAndroidKeyFormat() got = %v, want %v", got, tt.want)
}
/*
if !reflect.DeepEqual(got1, tt.want1) {
t.Errorf("verifySafetyNetFormat() got1 = %v, want %v", got1, tt.want1)
}
*/
})
}
}
Expand Down
6 changes: 3 additions & 3 deletions protocol/attestation_apple.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ func init() {
func verifyAppleFormat(att AttestationObject, clientDataHash []byte, _ metadata.Provider) (string, []any, error) {
// 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.

// If x5c is not present, return an error
// If x5c is not present, return an error.
x5c, x509present := att.AttStatement[stmtX5C].([]any)
if !x509present {
// Handle Basic Attestation steps for the x509 Certificate
Expand Down Expand Up @@ -104,7 +103,8 @@ func verifyAppleFormat(att AttestationObject, clientDataHash []byte, _ metadata.
return string(metadata.AnonCA), x5c, nil
}

// Apple has not yet publish schema for the extension(as of JULY 2021.)
// AppleAnonymousAttestation represents the attestation format for Apple, who have not yet published a schema for the
// extension (as of JULY 2021.)
type AppleAnonymousAttestation struct {
Nonce []byte `asn1:"tag:1,explicit"`
}
9 changes: 3 additions & 6 deletions protocol/attestation_packed.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,8 @@ func init() {
func verifyPackedFormat(att AttestationObject, clientDataHash []byte, _ metadata.Provider) (string, []any, error) {
// 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.

// Get the alg value - A COSEAlgorithmIdentifier containing the identifier of the algorithm
// used to generate the attestation signature.

alg, present := att.AttStatement[stmtAlgorithm].(int64)
if !present {
return string(AttestationFormatPacked), nil, ErrAttestationFormat.WithDetails("Error retrieving alg value")
Expand Down Expand Up @@ -202,17 +200,14 @@ func handleECDAAAttestation(signature, clientDataHash, ecdaaKeyID []byte) (strin
}

func handleSelfAttestation(alg int64, pubKey, authData, clientDataHash, signature []byte) (string, []any, error) {
// §4.1 Validate that alg matches the algorithm of the credentialPublicKey in authenticatorData.

// §4.2 Verify that sig is a valid signature over the concatenation of authenticatorData and
// clientDataHash using the credential public key with alg.
verificationData := append(authData, clientDataHash...)

key, err := webauthncose.ParsePublicKey(pubKey)
if err != nil {
return "", nil, ErrAttestationFormat.WithDetails(fmt.Sprintf("Error parsing the public key: %+v\n", err))
}

// §4.1 Validate that alg matches the algorithm of the credentialPublicKey in authenticatorData.
switch k := key.(type) {
case webauthncose.OKPPublicKeyData:
err = verifyKeyAlgorithm(k.Algorithm, alg)
Expand All @@ -228,6 +223,8 @@ func handleSelfAttestation(alg int64, pubKey, authData, clientDataHash, signatur
return "", nil, err
}

// §4.2 Verify that sig is a valid signature over the concatenation of authenticatorData and
// clientDataHash using the credential public key with alg.
valid, err := webauthncose.VerifySignature(key, verificationData, signature)
if !valid && err == nil {
return "", nil, ErrInvalidAttestation.WithDetails("Unable to verify signature")
Expand Down
2 changes: 1 addition & 1 deletion protocol/attestation_packed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ func Test_verifyPackedFormat(t *testing.T) {
return
}

// TODO: Consider doing something with the second return value from verifyPackedFormat, x5c.
if got != tt.want {
t.Errorf("verifyPackedFormat() got = %v, want %v", got, tt.want)
}
// TODO: Consider doing something with the second return value from verifyPackedFormat, x5c.
})
}
}
Expand Down
1 change: 1 addition & 0 deletions protocol/attestation_safetynet.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func verifySafetyNetFormat(att AttestationObject, clientDataHash []byte, mds met
}

cert, err := x509.ParseCertificate(o[:n])

return cert.PublicKey, err
})

Expand Down
Loading

0 comments on commit 99d717d

Please sign in to comment.