Add attestation certificate validation for Apple devices

This commit is contained in:
Mariano Cano 2022-07-14 17:10:03 -07:00 committed by max furman
parent 5f5315260a
commit 2b3b2c283a
No known key found for this signature in database
3 changed files with 2 additions and 108 deletions

View file

@ -399,8 +399,6 @@ func challengeTypes(az *acme.Authorization) []acme.ChallengeType {
} }
case acme.PermanentIdentifier: case acme.PermanentIdentifier:
chTypes = []acme.ChallengeType{acme.DEVICEATTEST01} chTypes = []acme.ChallengeType{acme.DEVICEATTEST01}
case acme.CA:
chTypes = []acme.ChallengeType{acme.APPLEATTEST01}
default: default:
chTypes = []acme.ChallengeType{} chTypes = []acme.ChallengeType{}
} }

View file

@ -1,7 +1,6 @@
package acme package acme
import ( import (
"bytes"
"context" "context"
"crypto" "crypto"
"crypto/ecdsa" "crypto/ecdsa"
@ -16,13 +15,11 @@ import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net" "net"
"net/url" "net/url"
"os"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@ -45,7 +42,6 @@ const (
TLSALPN01 ChallengeType = "tls-alpn-01" TLSALPN01 ChallengeType = "tls-alpn-01"
// DEVICEATTEST01 is the device-attest-01 ACME challenge type // DEVICEATTEST01 is the device-attest-01 ACME challenge type
DEVICEATTEST01 ChallengeType = "device-attest-01" DEVICEATTEST01 ChallengeType = "device-attest-01"
APPLEATTEST01 ChallengeType = "client-01"
) )
// Challenge represents an ACME response Challenge type. // Challenge represents an ACME response Challenge type.
@ -89,8 +85,6 @@ func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey,
return tlsalpn01Validate(ctx, ch, db, jwk) return tlsalpn01Validate(ctx, ch, db, jwk)
case DEVICEATTEST01: case DEVICEATTEST01:
return deviceAttest01Validate(ctx, ch, db, jwk, payload) return deviceAttest01Validate(ctx, ch, db, jwk, payload)
case APPLEATTEST01:
return appleAttest01Validate(ctx, ch, db, jwk, payload)
default: default:
return NewErrorISE("unexpected challenge type '%s'", ch.Type) return NewErrorISE("unexpected challenge type '%s'", ch.Type)
} }
@ -168,7 +162,7 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON
// [RFC5246] or higher when connecting to clients for validation. // [RFC5246] or higher when connecting to clients for validation.
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
ServerName: serverName(ch), ServerName: serverName(ch),
InsecureSkipVerify: true, //nolint:gosec // we expect a self-signed challenge certificate InsecureSkipVerify: true, // nolint:gosec // we expect a self-signed challenge certificate
} }
hostPort := net.JoinHostPort(ch.Value, "443") hostPort := net.JoinHostPort(ch.Value, "443")
@ -418,7 +412,6 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
return nil return nil
} }
<<<<<<< HEAD
// Apple Enterprise Attestation Root CA from // Apple Enterprise Attestation Root CA from
// https://www.apple.com/certificateauthority/private/ // https://www.apple.com/certificateauthority/private/
const appleEnterpriseAttestationRootCA = `-----BEGIN CERTIFICATE----- const appleEnterpriseAttestationRootCA = `-----BEGIN CERTIFICATE-----
@ -659,68 +652,6 @@ func doStepAttestationFormat(ctx context.Context, prov Provisioner, ch *Challeng
} }
return data, nil return data, nil
=======
type ApplePayload struct {
AttObj string `json:"attObj"`
Error string `json:"error"`
}
func appleAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, payload []byte) error {
var p ApplePayload
if err := json.Unmarshal(payload, &p); err != nil {
return WrapErrorISE(err, "error unmarshalling JSON")
}
fmt.Fprintf(os.Stderr, "p.AttObj: %v\n", p.AttObj)
attObj, err := base64.RawURLEncoding.DecodeString(p.AttObj)
if err != nil {
return WrapErrorISE(err, "error base64 decoding attObj")
}
att := AttestationObject{}
if err := cbor.Unmarshal(attObj, &att); err != nil {
return WrapErrorISE(err, "error unmarshalling CBOR")
}
if att.Format != "apple" {
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatement,
"unexpected attestation object format"))
}
x5c, x509present := att.AttStatement["x5c"].([]interface{})
if !x509present {
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatement,
"x5c not present"))
}
attCertBytes, valid := x5c[0].([]byte)
if !valid {
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType,
"error getting certificate from x5c cert chain"))
}
attCert, err := x509.ParseCertificate(attCertBytes)
if err != nil {
return WrapErrorISE(err, "error parsing AK certificate")
}
b := &pem.Block{
Type: "CERTIFICATE",
Bytes: attCert.Raw,
}
pem.Encode(os.Stderr, b)
// Update and store the challenge.
ch.Status = StatusValid
ch.Error = nil
ch.ValidatedAt = clock.Now().Format(time.RFC3339)
if err := db.UpdateChallenge(ctx, ch); err != nil {
return WrapErrorISE(err, "error updating challenge")
}
return nil
>>>>>>> 26e1b4ba (iOS 16 beta 1 support)
} }
// serverName determines the SNI HostName to set based on an acme.Challenge // serverName determines the SNI HostName to set based on an acme.Challenge

View file

@ -5,15 +5,11 @@ import (
"context" "context"
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"encoding/pem"
"net" "net"
"os"
"sort" "sort"
"strings" "strings"
"time" "time"
"github.com/google/go-attestation/oid"
attest_x509 "github.com/google/go-attestation/x509"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
) )
@ -28,7 +24,6 @@ const (
// PermanentIdentifier is the ACME permanent-identifier identifier type // PermanentIdentifier is the ACME permanent-identifier identifier type
// defined in https://datatracker.ietf.org/doc/html/draft-bweeks-acme-device-attest-00 // defined in https://datatracker.ietf.org/doc/html/draft-bweeks-acme-device-attest-00
PermanentIdentifier IdentifierType = "permanent-identifier" PermanentIdentifier IdentifierType = "permanent-identifier"
CA IdentifierType = "ca"
) )
// Identifier encodes the type that an order pertains to. // Identifier encodes the type that an order pertains to.
@ -155,12 +150,6 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
return NewErrorISE("unexpected status %s for order %s", o.Status, o.ID) return NewErrorISE("unexpected status %s for order %s", o.Status, o.ID)
} }
b := &pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: csr.Raw,
}
pem.Encode(os.Stderr, b)
// canonicalize the CSR to allow for comparison // canonicalize the CSR to allow for comparison
csr = canonicalize(csr) csr = canonicalize(csr)
@ -199,11 +188,6 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
data.SetSubjectAlternativeNames(sans...) data.SetSubjectAlternativeNames(sans...)
} }
deviceIDs, err := o.deviceIDs(csr)
if err != nil {
return err
}
// Get authorizations from the ACME provisioner. // Get authorizations from the ACME provisioner.
ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod)
signOps, err := p.AuthorizeSign(ctx, "") signOps, err := p.AuthorizeSign(ctx, "")
@ -269,7 +253,6 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ
case PermanentIdentifier: case PermanentIdentifier:
orderPIDs[indexPID] = n.Value orderPIDs[indexPID] = n.Value
indexPID++ indexPID++
case CA:
default: default:
return sans, NewErrorISE("unsupported identifier type in order: %s", n.Type) return sans, NewErrorISE("unsupported identifier type in order: %s", n.Type)
} }
@ -323,25 +306,6 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ
return sans, nil return sans, nil
} }
func (o *Order) deviceIDs(csr *x509.CertificateRequest) ([]x509util.PermanentIdentifier, error) {
var permIDs []x509util.PermanentIdentifier
for _, ext := range csr.Extensions {
if ext.Id.Equal(oid.SubjectAltName) {
san, err := attest_x509.ParseSubjectAltName(ext)
if err != nil {
return nil, err
}
for _, pi := range san.PermanentIdentifiers {
permIDs = append(permIDs, x509util.PermanentIdentifier{
Value: pi.IdentifierValue,
Assigner: pi.Assigner,
})
}
}
}
return permIDs, nil
}
// numberOfIdentifierType returns the number of Identifiers that // numberOfIdentifierType returns the number of Identifiers that
// are of type typ. // are of type typ.
func numberOfIdentifierType(typ IdentifierType, ids []Identifier) int { func numberOfIdentifierType(typ IdentifierType, ids []Identifier) int {
@ -360,6 +324,7 @@ func numberOfIdentifierType(typ IdentifierType, ids []Identifier) int {
// addresses or DNS names slice, depending on whether it can be parsed as an IP // addresses or DNS names slice, depending on whether it can be parsed as an IP
// or not. This might result in an additional SAN in the final certificate. // or not. This might result in an additional SAN in the final certificate.
func canonicalize(csr *x509.CertificateRequest) (canonicalized *x509.CertificateRequest) { func canonicalize(csr *x509.CertificateRequest) (canonicalized *x509.CertificateRequest) {
// for clarity only; we're operating on the same object by pointer // for clarity only; we're operating on the same object by pointer
canonicalized = csr canonicalized = csr