Skip to content

Commit

Permalink
feat: webauthn level 3 (#232)
Browse files Browse the repository at this point in the history
Adds some remaining elements from level 3.
  • Loading branch information
james-d-elliott authored Apr 27, 2024
1 parent 0c97761 commit 482cf89
Show file tree
Hide file tree
Showing 29 changed files with 220 additions and 217 deletions.
10 changes: 5 additions & 5 deletions metadata/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,16 +548,16 @@ type MDSGetEndpointsResponse struct {
func unmarshalMDSBLOB(body []byte, c http.Client) (MetadataBLOBPayload, error) {
var payload MetadataBLOBPayload

token, err := jwt.Parse(string(body), func(token *jwt.Token) (interface{}, error) {
token, err := jwt.Parse(string(body), func(token *jwt.Token) (any, error) {
// 2. If the x5u attribute is present in the JWT Header, then
if _, ok := token.Header["x5u"].([]interface{}); ok {
if _, ok := token.Header["x5u"].(any); ok {
// never seen an x5u here, although it is in the spec
return nil, errors.New("x5u encountered in header of metadata TOC payload")
}
var chain []interface{}
var chain []any
// 3. If the x5u attribute is missing, the chain should be retrieved from the x5c attribute.

if x5c, ok := token.Header["x5c"].([]interface{}); !ok {
if x5c, ok := token.Header["x5c"].([]any); !ok {
// If that attribute is missing as well, Metadata TOC signing trust anchor is considered the TOC signing certificate chain.
chain[0] = MDSRoot
} else {
Expand Down Expand Up @@ -600,7 +600,7 @@ func unmarshalMDSBLOB(body []byte, c http.Client) (MetadataBLOBPayload, error) {
return payload, err
}

func validateChain(chain []interface{}, c http.Client) (bool, error) {
func validateChain(chain []any, c http.Client) (bool, error) {
oRoot := make([]byte, base64.StdEncoding.DecodedLen(len(MDSRoot)))

nRoot, err := base64.StdEncoding.Decode(oRoot, []byte(MDSRoot))
Expand Down
2 changes: 1 addition & 1 deletion protocol/assertion.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func (p *ParsedCredentialAssertionData) Verify(storedChallenge string, relyingPa
sigData := append(p.Raw.AssertionResponse.AuthenticatorData, clientDataHash[:]...)

var (
key interface{}
key any
err error
)

Expand Down
6 changes: 3 additions & 3 deletions protocol/assertion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestParseCredentialRequestResponse(t *testing.T) {
Type: "public-key",
},
RawID: byteID,
ClientExtensionResults: map[string]interface{}{
ClientExtensionResults: map[string]any{
"appID": "example.com",
},
},
Expand Down Expand Up @@ -78,7 +78,7 @@ func TestParseCredentialRequestResponse(t *testing.T) {
ID: "AI7D5q2P0LS-Fal9ZT7CHM2N5BLbUunF92T8b6iYC199bO2kagSuU05-5dZGqb1SP0A0lyTWng",
},
RawID: byteID,
ClientExtensionResults: map[string]interface{}{
ClientExtensionResults: map[string]any{
"appID": "example.com",
},
},
Expand Down Expand Up @@ -135,7 +135,7 @@ func TestParseCredentialRequestResponse(t *testing.T) {
assert.Equal(t, tc.expected.Response.CollectedClientData, actual.Response.CollectedClientData)

var (
pkExpected, pkActual interface{}
pkExpected, pkActual any
)

assert.NoError(t, webauthncbor.Unmarshal(tc.expected.Response.AuthenticatorData.AttData.CredentialPublicKey, &pkExpected))
Expand Down
4 changes: 2 additions & 2 deletions protocol/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ type AttestationObject struct {
// The format of the Attestation data.
Format string `json:"fmt"`
// The attestation statement data sent back if attestation is requested.
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
AttStatement map[string]any `json:"attStmt,omitempty"`
}

type attestationFormatValidationHandler func(AttestationObject, []byte) (string, []interface{}, error)
type attestationFormatValidationHandler func(AttestationObject, []byte) (string, []any, error)

var attestationRegistry = make(map[AttestationFormat]attestationFormatValidationHandler)

Expand Down
20 changes: 10 additions & 10 deletions protocol/attestation_androidkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func init() {
// }
//
// Specification: §8.4. Android Key Attestation Statement Format (https://www.w3.org/TR/webauthn/#sctn-android-key-attestation)
func verifyAndroidKeyFormat(att AttestationObject, clientDataHash []byte) (string, []interface{}, error) {
func verifyAndroidKeyFormat(att AttestationObject, clientDataHash []byte) (string, []any, error) {
// Given the verification procedure inputs attStmt, authenticatorData and clientDataHash, the verification procedure is as follows:
// §8.4.1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract
// the contained fields.
Expand All @@ -48,7 +48,7 @@ func verifyAndroidKeyFormat(att AttestationObject, clientDataHash []byte) (strin
}

// If x5c is not present, return an error
x5c, x509present := att.AttStatement["x5c"].([]interface{})
x5c, x509present := att.AttStatement["x5c"].([]any)
if !x509present {
// Handle Basic Attestation steps for the x509 Certificate
return "", nil, ErrAttestationFormat.WithDetails("Error retrieving x5c value")
Expand Down Expand Up @@ -163,19 +163,19 @@ type authorizationList struct {
Padding []int `asn1:"tag:6,explicit,set,optional"`
EcCurve int `asn1:"tag:10,explicit,optional"`
RsaPublicExponent int `asn1:"tag:200,explicit,optional"`
RollbackResistance interface{} `asn1:"tag:303,explicit,optional"`
RollbackResistance any `asn1:"tag:303,explicit,optional"`
ActiveDateTime int `asn1:"tag:400,explicit,optional"`
OriginationExpireDateTime int `asn1:"tag:401,explicit,optional"`
UsageExpireDateTime int `asn1:"tag:402,explicit,optional"`
NoAuthRequired interface{} `asn1:"tag:503,explicit,optional"`
NoAuthRequired any `asn1:"tag:503,explicit,optional"`
UserAuthType int `asn1:"tag:504,explicit,optional"`
AuthTimeout int `asn1:"tag:505,explicit,optional"`
AllowWhileOnBody interface{} `asn1:"tag:506,explicit,optional"`
TrustedUserPresenceRequired interface{} `asn1:"tag:507,explicit,optional"`
TrustedConfirmationRequired interface{} `asn1:"tag:508,explicit,optional"`
UnlockedDeviceRequired interface{} `asn1:"tag:509,explicit,optional"`
AllApplications interface{} `asn1:"tag:600,explicit,optional"`
ApplicationID interface{} `asn1:"tag:601,explicit,optional"`
AllowWhileOnBody any `asn1:"tag:506,explicit,optional"`
TrustedUserPresenceRequired any `asn1:"tag:507,explicit,optional"`
TrustedConfirmationRequired any `asn1:"tag:508,explicit,optional"`
UnlockedDeviceRequired any `asn1:"tag:509,explicit,optional"`
AllApplications any `asn1:"tag:600,explicit,optional"`
ApplicationID any `asn1:"tag:601,explicit,optional"`
CreationDateTime int `asn1:"tag:701,explicit,optional"`
Origin int `asn1:"tag:702,explicit,optional"`
RootOfTrust rootOfTrust `asn1:"tag:704,explicit,optional"`
Expand Down
2 changes: 1 addition & 1 deletion protocol/attestation_androidkey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestVerifyAndroidKeyFormat(t *testing.T) {
name string
args args
want string
want1 []interface{}
want1 []any
wantErr bool
}{
{
Expand Down
4 changes: 2 additions & 2 deletions protocol/attestation_apple.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ func init() {
// }
//
// Specification: §8.8. Apple Anonymous Attestation Statement Format (https://www.w3.org/TR/webauthn/#sctn-apple-anonymous-attestation)
func verifyAppleFormat(att AttestationObject, clientDataHash []byte) (string, []interface{}, error) {
func verifyAppleFormat(att AttestationObject, clientDataHash []byte) (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
x5c, x509present := att.AttStatement["x5c"].([]interface{})
x5c, x509present := att.AttStatement["x5c"].([]any)
if !x509present {
// Handle Basic Attestation steps for the x509 Certificate
return "", nil, ErrAttestationFormat.WithDetails("Error retrieving x5c value")
Expand Down
6 changes: 2 additions & 4 deletions protocol/attestation_apple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func Test_verifyAppleFormat(t *testing.T) {
name string
args args
want string
want1 []interface{}
want1 []any
wantErr bool
}{
{
Expand All @@ -42,12 +42,10 @@ func Test_verifyAppleFormat(t *testing.T) {
t.Errorf("verifyAppleFormat() error = %v, wantErr %v", err, tt.wantErr)
return
}

if got != tt.want {
t.Errorf("verifyAppleFormat() got = %v, want %v", got, tt.want)
}
//if !reflect.DeepEqual(got1, tt.want1) {
// t.Errorf("verifyPackedFormat() got1 = %v, want %v", got1, tt.want1)
//}
})
}
}
Expand Down
10 changes: 5 additions & 5 deletions protocol/attestation_packed.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func init() {
// }
//
// Specification: §8.2. Packed Attestation Statement Format (https://www.w3.org/TR/webauthn/#sctn-packed-attestation)
func verifyPackedFormat(att AttestationObject, clientDataHash []byte) (string, []interface{}, error) {
func verifyPackedFormat(att AttestationObject, clientDataHash []byte) (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.

Expand All @@ -53,7 +53,7 @@ func verifyPackedFormat(att AttestationObject, clientDataHash []byte) (string, [
}

// Step 2. If x5c is present, this indicates that the attestation type is not ECDAA.
x5c, x509present := att.AttStatement["x5c"].([]interface{})
x5c, x509present := att.AttStatement["x5c"].([]any)
if x509present {
// Handle Basic Attestation steps for the x509 Certificate
return handleBasicAttestation(sig, clientDataHash, att.RawAuthData, att.AuthData.AttData.AAGUID, alg, x5c)
Expand All @@ -72,7 +72,7 @@ func verifyPackedFormat(att AttestationObject, clientDataHash []byte) (string, [
}

// Handle the attestation steps laid out in
func handleBasicAttestation(signature, clientDataHash, authData, aaguid []byte, alg int64, x5c []interface{}) (string, []interface{}, error) {
func handleBasicAttestation(signature, clientDataHash, authData, aaguid []byte, alg int64, x5c []any) (string, []any, error) {
// Step 2.1. 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.
for _, c := range x5c {
Expand Down Expand Up @@ -199,11 +199,11 @@ func handleBasicAttestation(signature, clientDataHash, authData, aaguid []byte,
return string(metadata.BasicFull), x5c, nil
}

func handleECDAAAttestation(signature, clientDataHash, ecdaaKeyID []byte) (string, []interface{}, error) {
func handleECDAAAttestation(signature, clientDataHash, ecdaaKeyID []byte) (string, []any, error) {
return "Packed (ECDAA)", nil, ErrNotSpecImplemented
}

func handleSelfAttestation(alg int64, pubKey, authData, clientDataHash, signature []byte) (string, []interface{}, error) {
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
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 @@ -24,7 +24,7 @@ func Test_verifyPackedFormat(t *testing.T) {
name string
args args
want string
want1 []interface{}
want1 []any
wantErr bool
}{
{
Expand Down
22 changes: 11 additions & 11 deletions protocol/attestation_safetynet.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ func init() {
}

type SafetyNetResponse struct {
Nonce string `json:"nonce"`
TimestampMs int64 `json:"timestampMs"`
ApkPackageName string `json:"apkPackageName"`
ApkDigestSha256 string `json:"apkDigestSha256"`
CtsProfileMatch bool `json:"ctsProfileMatch"`
ApkCertificateDigestSha256 []interface{} `json:"apkCertificateDigestSha256"`
BasicIntegrity bool `json:"basicIntegrity"`
Nonce string `json:"nonce"`
TimestampMs int64 `json:"timestampMs"`
ApkPackageName string `json:"apkPackageName"`
ApkDigestSha256 string `json:"apkDigestSha256"`
CtsProfileMatch bool `json:"ctsProfileMatch"`
ApkCertificateDigestSha256 []any `json:"apkCertificateDigestSha256"`
BasicIntegrity bool `json:"basicIntegrity"`
}

// Thanks to @koesie10 and @herrjemand for outlining how to support this type really well
Expand All @@ -40,7 +40,7 @@ type SafetyNetResponse struct {
// authenticators SHOULD make use of the Android Key Attestation when available, even if the SafetyNet API is also present.
//
// Specification: §8.5. Android SafetyNet Attestation Statement Format (https://www.w3.org/TR/webauthn/#sctn-android-safetynet-attestation)
func verifySafetyNetFormat(att AttestationObject, clientDataHash []byte) (string, []interface{}, error) {
func verifySafetyNetFormat(att AttestationObject, clientDataHash []byte) (string, []any, error) {
// The syntax of an Android Attestation statement is defined as follows:
// $$attStmtType //= (
// fmt: "android-safetynet",
Expand Down Expand Up @@ -73,8 +73,8 @@ func verifySafetyNetFormat(att AttestationObject, clientDataHash []byte) (string
return "", nil, ErrAttestationFormat.WithDetails("Unable to find the SafetyNet response")
}

token, err := jwt.Parse(string(response), func(token *jwt.Token) (interface{}, error) {
chain := token.Header["x5c"].([]interface{})
token, err := jwt.Parse(string(response), func(token *jwt.Token) (any, error) {
chain := token.Header["x5c"].([]any)

o := make([]byte, base64.StdEncoding.DecodedLen(len(chain[0].(string))))

Expand Down Expand Up @@ -108,7 +108,7 @@ func verifySafetyNetFormat(att AttestationObject, clientDataHash []byte) (string
}

// §8.5.4 Let attestationCert be the attestation certificate (https://www.w3.org/TR/webauthn/#attestation-certificate)
certChain := token.Header["x5c"].([]interface{})
certChain := token.Header["x5c"].([]any)
l := make([]byte, base64.StdEncoding.DecodedLen(len(certChain[0].(string))))

n, err := base64.StdEncoding.Decode(l, []byte(certChain[0].(string)))
Expand Down
4 changes: 3 additions & 1 deletion protocol/attestation_safetynet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func Test_verifySafetyNetFormat(t *testing.T) {
name string
args args
want string
want1 []interface{}
want1 []any
wantErr bool
}{
{
Expand All @@ -43,9 +43,11 @@ func Test_verifySafetyNetFormat(t *testing.T) {
t.Errorf("verifySafetyNetFormat() error = %v, wantErr %v", err, tt.wantErr)
return
}

if got != tt.want {
t.Errorf("verifySafetyNetFormat() 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
3 changes: 3 additions & 0 deletions protocol/attestation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ func TestAttestationVerify(t *testing.T) {
if err := json.Unmarshal([]byte(testAttestationOptions[i]), &options); err != nil {
t.Fatal(err)
}

ccr := CredentialCreationResponse{}

if err := json.Unmarshal([]byte(testAttestationResponses[i]), &ccr); err != nil {
t.Fatal(err)
}

var pcc ParsedCredentialCreationData
pcc.ID, pcc.RawID, pcc.Type, pcc.ClientExtensionResults = ccr.ID, ccr.RawID, ccr.Type, ccr.ClientExtensionResults
pcc.Raw = ccr
Expand Down
4 changes: 2 additions & 2 deletions protocol/attestation_tpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func init() {
RegisterAttestationFormat(AttestationFormatTPM, verifyTPMFormat)
}

func verifyTPMFormat(att AttestationObject, clientDataHash []byte) (string, []interface{}, error) {
func verifyTPMFormat(att AttestationObject, clientDataHash []byte) (string, []any, error) {
// Given the verification procedure inputs attStmt, authenticatorData
// and clientDataHash, the verification procedure is as follows

Expand All @@ -42,7 +42,7 @@ func verifyTPMFormat(att AttestationObject, clientDataHash []byte) (string, []in

coseAlg := webauthncose.COSEAlgorithmIdentifier(alg)

x5c, x509present := att.AttStatement["x5c"].([]interface{})
x5c, x509present := att.AttStatement["x5c"].([]any)
if !x509present {
// Handle Basic Attestation steps for the x509 Certificate
return "", nil, ErrNotImplemented
Expand Down
Loading

0 comments on commit 482cf89

Please sign in to comment.