Validate proof of possession signature

On the step format, validate proof of possession of the private
key validating the signature in the attestation statement.
This commit is contained in:
Mariano Cano 2022-09-01 10:45:31 -07:00
parent 45af68b244
commit 54d92095ac
2 changed files with 61 additions and 7 deletions

View file

@ -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,
}

View file

@ -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,