From 54d92095acfad9a0a5411c70613fc65028077cf6 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 1 Sep 2022 10:45:31 -0700 Subject: [PATCH] Validate proof of possession signature On the step format, validate proof of possession of the private key validating the signature in the attestation statement. --- acme/challenge.go | 59 +++++++++++++++++++++++++++++++++++++++++------ acme/order.go | 9 ++++++++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/acme/challenge.go b/acme/challenge.go index 6cab0105..bf2e896d 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -3,6 +3,10 @@ package acme import ( "context" "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rsa" "crypto/sha256" "crypto/subtle" "crypto/tls" @@ -322,7 +326,7 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose if err := json.Unmarshal(payload, &p); err != nil { return WrapErrorISE(err, "error unmarshalling JSON") } - + fmt.Println(string(payload)) if p.Error != "" { return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, "payload contained error: %v", p.Error)) @@ -338,6 +342,9 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose return WrapErrorISE(err, "error unmarshalling CBOR") } + b, _ := json.Marshal(att) + fmt.Println(string(b)) + switch att.Format { case "apple": data, err := doAppleAttestationFormat(ctx, ch, db, &att) @@ -369,11 +376,11 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatement, "permanent identifier does not match")) } case "step": - data, err := doStepAttestationFormat(ctx, ch, db, &att) + data, err := doStepAttestationFormat(ctx, ch, jwk, &att) if err != nil { var acmeError *Error if errors.As(err, &acmeError) { - fmt.Println(acmeError) + fmt.Printf("debug: %#v\n", acmeError) if acmeError.Status == 500 { return acmeError } @@ -534,7 +541,7 @@ type stepAttestationData struct { SerialNumber string } -func doStepAttestationFormat(ctx context.Context, ch *Challenge, db DB, att *AttestationObject) (*stepAttestationData, error) { +func doStepAttestationFormat(ctx context.Context, ch *Challenge, jwk *jose.JSONWebKey, att *AttestationObject) (*stepAttestationData, error) { root, err := pemutil.ParseCertificate([]byte(yubicoPIVRootCA)) if err != nil { return nil, WrapErrorISE(err, "error parsing root ca") @@ -542,6 +549,7 @@ func doStepAttestationFormat(ctx context.Context, ch *Challenge, db DB, att *Att roots := x509.NewCertPool() roots.AddCert(root) + // Extract x5c and verify certificate x5c, ok := att.AttStatement["x5c"].([]interface{}) if !ok { return nil, NewError(ErrorBadAttestationStatement, "x5c not present") @@ -549,7 +557,6 @@ func doStepAttestationFormat(ctx context.Context, ch *Challenge, db DB, att *Att if len(x5c) == 0 { return nil, NewError(ErrorRejectedIdentifierType, "x5c is empty") } - der, ok := x5c[0].([]byte) if !ok { return nil, NewError(ErrorBadAttestationStatement, "x5c is malformed") @@ -558,7 +565,6 @@ func doStepAttestationFormat(ctx context.Context, ch *Challenge, db DB, att *Att if err != nil { return nil, WrapError(ErrorBadAttestationStatement, err, "x5c is malformed") } - intermediates := x509.NewCertPool() for _, v := range x5c[1:] { der, ok = v.([]byte) @@ -571,7 +577,6 @@ func doStepAttestationFormat(ctx context.Context, ch *Challenge, db DB, att *Att } intermediates.AddCert(cert) } - if _, err := leaf.Verify(x509.VerifyOptions{ Intermediates: intermediates, Roots: roots, @@ -581,6 +586,46 @@ func doStepAttestationFormat(ctx context.Context, ch *Challenge, db DB, att *Att return nil, WrapError(ErrorBadAttestationStatement, err, "x5c is not valid") } + // Verify proof of possession of private key validating the key + // authorization. Per recommendation at + // https://w3c.github.io/webauthn/#sctn-signature-attestation-types the + // signature is CBOR-encoded. + var sig []byte + csig, ok := att.AttStatement["sig"].([]byte) + if !ok { + return nil, NewError(ErrorBadAttestationStatement, "sig not present") + } + if err := cbor.Unmarshal(csig, &sig); err != nil { + return nil, NewError(ErrorBadAttestationStatement, "sig is malformed") + } + keyAuth, err := KeyAuthorization(ch.Token, jwk) + if err != nil { + return nil, err + } + + switch pub := leaf.PublicKey.(type) { + case *ecdsa.PublicKey: + if pub.Curve != elliptic.P256() { + return nil, WrapError(ErrorBadAttestationStatement, err, "unsupported elliptic curve %s", pub.Curve) + } + sum := sha256.Sum256([]byte(keyAuth)) + if !ecdsa.VerifyASN1(pub, sum[:], sig) { + return nil, NewError(ErrorBadAttestationStatement, "failed to validate signature") + } + case *rsa.PublicKey: + sum := sha256.Sum256([]byte(keyAuth)) + if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, sum[:], sig); err != nil { + return nil, NewError(ErrorBadAttestationStatement, "failed to validate signature") + } + case ed25519.PublicKey: + if !ed25519.Verify(pub, []byte(keyAuth), sig) { + return nil, NewError(ErrorBadAttestationStatement, "failed to validate signature") + } + default: + return nil, NewError(ErrorBadAttestationStatement, "unsupported public key type %T", pub) + } + + // Parse attestation data data := &stepAttestationData{ Certificate: leaf, } diff --git a/acme/order.go b/acme/order.go index ee76a364..2f62d764 100644 --- a/acme/order.go +++ b/acme/order.go @@ -5,7 +5,9 @@ import ( "context" "crypto/x509" "encoding/json" + "encoding/pem" "net" + "os" "sort" "strings" "time" @@ -203,6 +205,13 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques return WrapErrorISE(err, "error signing certificate for order %s", o.ID) } + pem.Encode(os.Stdout, &pem.Block{ + Type: "CERTIFICATE REQUEST", Bytes: csr.Raw, + }) + pem.Encode(os.Stdout, &pem.Block{ + Type: "CERTIFICATE", Bytes: certChain[0].Raw, + }) + cert := &Certificate{ AccountID: o.AccountID, OrderID: o.ID,