forked from TrueCloudLab/certificates
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:
parent
45af68b244
commit
54d92095ac
2 changed files with 61 additions and 7 deletions
|
@ -3,6 +3,10 @@ package acme
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"crypto/tls"
|
"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 {
|
if err := json.Unmarshal(payload, &p); err != nil {
|
||||||
return WrapErrorISE(err, "error unmarshalling JSON")
|
return WrapErrorISE(err, "error unmarshalling JSON")
|
||||||
}
|
}
|
||||||
|
fmt.Println(string(payload))
|
||||||
if p.Error != "" {
|
if p.Error != "" {
|
||||||
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType,
|
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType,
|
||||||
"payload contained error: %v", p.Error))
|
"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")
|
return WrapErrorISE(err, "error unmarshalling CBOR")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b, _ := json.Marshal(att)
|
||||||
|
fmt.Println(string(b))
|
||||||
|
|
||||||
switch att.Format {
|
switch att.Format {
|
||||||
case "apple":
|
case "apple":
|
||||||
data, err := doAppleAttestationFormat(ctx, ch, db, &att)
|
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"))
|
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatement, "permanent identifier does not match"))
|
||||||
}
|
}
|
||||||
case "step":
|
case "step":
|
||||||
data, err := doStepAttestationFormat(ctx, ch, db, &att)
|
data, err := doStepAttestationFormat(ctx, ch, jwk, &att)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var acmeError *Error
|
var acmeError *Error
|
||||||
if errors.As(err, &acmeError) {
|
if errors.As(err, &acmeError) {
|
||||||
fmt.Println(acmeError)
|
fmt.Printf("debug: %#v\n", acmeError)
|
||||||
if acmeError.Status == 500 {
|
if acmeError.Status == 500 {
|
||||||
return acmeError
|
return acmeError
|
||||||
}
|
}
|
||||||
|
@ -534,7 +541,7 @@ type stepAttestationData struct {
|
||||||
SerialNumber string
|
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))
|
root, err := pemutil.ParseCertificate([]byte(yubicoPIVRootCA))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, WrapErrorISE(err, "error parsing root ca")
|
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 := x509.NewCertPool()
|
||||||
roots.AddCert(root)
|
roots.AddCert(root)
|
||||||
|
|
||||||
|
// Extract x5c and verify certificate
|
||||||
x5c, ok := att.AttStatement["x5c"].([]interface{})
|
x5c, ok := att.AttStatement["x5c"].([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, NewError(ErrorBadAttestationStatement, "x5c not present")
|
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 {
|
if len(x5c) == 0 {
|
||||||
return nil, NewError(ErrorRejectedIdentifierType, "x5c is empty")
|
return nil, NewError(ErrorRejectedIdentifierType, "x5c is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
der, ok := x5c[0].([]byte)
|
der, ok := x5c[0].([]byte)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, NewError(ErrorBadAttestationStatement, "x5c is malformed")
|
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 {
|
if err != nil {
|
||||||
return nil, WrapError(ErrorBadAttestationStatement, err, "x5c is malformed")
|
return nil, WrapError(ErrorBadAttestationStatement, err, "x5c is malformed")
|
||||||
}
|
}
|
||||||
|
|
||||||
intermediates := x509.NewCertPool()
|
intermediates := x509.NewCertPool()
|
||||||
for _, v := range x5c[1:] {
|
for _, v := range x5c[1:] {
|
||||||
der, ok = v.([]byte)
|
der, ok = v.([]byte)
|
||||||
|
@ -571,7 +577,6 @@ func doStepAttestationFormat(ctx context.Context, ch *Challenge, db DB, att *Att
|
||||||
}
|
}
|
||||||
intermediates.AddCert(cert)
|
intermediates.AddCert(cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := leaf.Verify(x509.VerifyOptions{
|
if _, err := leaf.Verify(x509.VerifyOptions{
|
||||||
Intermediates: intermediates,
|
Intermediates: intermediates,
|
||||||
Roots: roots,
|
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")
|
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{
|
data := &stepAttestationData{
|
||||||
Certificate: leaf,
|
Certificate: leaf,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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)
|
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{
|
cert := &Certificate{
|
||||||
AccountID: o.AccountID,
|
AccountID: o.AccountID,
|
||||||
OrderID: o.ID,
|
OrderID: o.ID,
|
||||||
|
|
Loading…
Reference in a new issue