From 7a78c76199bd2d45430bb479a4596c3e5561e140 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 10 Oct 2022 18:27:11 -0700 Subject: [PATCH] Add test simulating YubiKey v5.2.4 There are YubiKeys v5.2.4 where the attestation intermediate (f9) does not have a basic constraint extension, so that certificate is not marked as a CA. The test and CA in this commit imitates that use case. Currently the test case returns an error as we don't support it. But if we change the verification to support this use case, the test should change accordingly. --- acme/challenge_test.go | 94 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/acme/challenge_test.go b/acme/challenge_test.go index e452b175..bba2ccaa 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -2774,3 +2774,97 @@ func Test_doStepAttestationFormat(t *testing.T) { }) } } + +func Test_doStepAttestationFormat_noCAIntermediate(t *testing.T) { + ctx := context.Background() + + // This CA simulates a YubiKey v5.2.4, where the attestation intermediate in + // the CA does not have the basic constraint extension. With the current + // validation of the certificate the test case bellow returns an error, if + // we change the validation to support this use case, the test case bellow + // should change.. + // + // See https://github.com/Yubico/yubikey-manager/issues/522 + ca, err := minica.New(minica.WithIntermediateTemplate(`{"subject": {{ toJson .Subject }}}`)) + if err != nil { + t.Fatal(err) + } + caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Root.Raw}) + + makeLeaf := func(signer crypto.Signer, serialNumber []byte) *x509.Certificate { + leaf, err := ca.Sign(&x509.Certificate{ + Subject: pkix.Name{CommonName: "attestation cert"}, + PublicKey: signer.Public(), + ExtraExtensions: []pkix.Extension{ + {Id: oidYubicoSerialNumber, Value: serialNumber}, + }, + }) + if err != nil { + t.Fatal(err) + } + return leaf + } + + signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + serialNumber, err := asn1.Marshal(1234) + if err != nil { + t.Fatal(err) + } + leaf := makeLeaf(signer, serialNumber) + + jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) + if err != nil { + t.Fatal(err) + } + keyAuth, err := KeyAuthorization("token", jwk) + if err != nil { + t.Fatal(err) + } + keyAuthSum := sha256.Sum256([]byte(keyAuth)) + sig, err := signer.Sign(rand.Reader, keyAuthSum[:], crypto.SHA256) + if err != nil { + t.Fatal(err) + } + cborSig, err := cbor.Marshal(sig) + if err != nil { + t.Fatal(err) + } + + type args struct { + ctx context.Context + prov Provisioner + ch *Challenge + jwk *jose.JSONWebKey + att *AttestationObject + } + tests := []struct { + name string + args args + want *stepAttestationData + wantErr bool + }{ + {"fail no intermediate", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{ + Format: "step", + AttStatement: map[string]interface{}{ + "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + "alg": -7, + "sig": cborSig, + }, + }}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := doStepAttestationFormat(tt.args.ctx, tt.args.prov, tt.args.ch, tt.args.jwk, tt.args.att) + if (err != nil) != tt.wantErr { + t.Errorf("doStepAttestationFormat() error = %#v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("doStepAttestationFormat() = %v, want %v", got, tt.want) + } + }) + } +}