forked from TrueCloudLab/certificates
Add attestation certificate validation for Apple devices
This commit is contained in:
parent
5f5315260a
commit
2b3b2c283a
3 changed files with 2 additions and 108 deletions
|
@ -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{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue