forked from TrueCloudLab/certificates
Add support for yubikey attestation
This commit is contained in:
parent
ebce40e9b6
commit
735c9d49b0
1 changed files with 113 additions and 4 deletions
|
@ -19,6 +19,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -361,11 +362,19 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
|
||||||
if data.UDID != ch.Value && data.SerialNumber != ch.Value {
|
if data.UDID != ch.Value && data.SerialNumber != ch.Value {
|
||||||
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":
|
||||||
|
data, err := doStepAttestationFormat(ctx, ch, db, &att)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(mariano): debug - remove me
|
// Validate Apple's ClientIdentifier (Identifier.Value) with device
|
||||||
pem.Encode(os.Stderr, &pem.Block{
|
// identifiers.
|
||||||
Type: "CERTIFICATE", Bytes: data.Certificate.Raw,
|
//
|
||||||
})
|
// Note: We might want to use an external service for this.
|
||||||
|
if data.SerialNumber != ch.Value {
|
||||||
|
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatement, "permanent identifier does not match"))
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatement, "unexpected attestation object format"))
|
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatement, "unexpected attestation object format"))
|
||||||
}
|
}
|
||||||
|
@ -479,6 +488,106 @@ func doAppleAttestationFormat(ctx context.Context, ch *Challenge, db DB, att *At
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Yubico PIV Root CA Serial 263751
|
||||||
|
// https://developers.yubico.com/PIV/Introduction/piv-attestation-ca.pem
|
||||||
|
const yubicoPIVRootCA = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDFzCCAf+gAwIBAgIDBAZHMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNVBAMMIFl1
|
||||||
|
YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAwMDAwMFoY
|
||||||
|
DzIwNTIwNDE3MDAwMDAwWjArMSkwJwYDVQQDDCBZdWJpY28gUElWIFJvb3QgQ0Eg
|
||||||
|
U2VyaWFsIDI2Mzc1MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMN2
|
||||||
|
cMTNR6YCdcTFRxuPy31PabRn5m6pJ+nSE0HRWpoaM8fc8wHC+Tmb98jmNvhWNE2E
|
||||||
|
ilU85uYKfEFP9d6Q2GmytqBnxZsAa3KqZiCCx2LwQ4iYEOb1llgotVr/whEpdVOq
|
||||||
|
joU0P5e1j1y7OfwOvky/+AXIN/9Xp0VFlYRk2tQ9GcdYKDmqU+db9iKwpAzid4oH
|
||||||
|
BVLIhmD3pvkWaRA2H3DA9t7H/HNq5v3OiO1jyLZeKqZoMbPObrxqDg+9fOdShzgf
|
||||||
|
wCqgT3XVmTeiwvBSTctyi9mHQfYd2DwkaqxRnLbNVyK9zl+DzjSGp9IhVPiVtGet
|
||||||
|
X02dxhQnGS7K6BO0Qe8CAwEAAaNCMEAwHQYDVR0OBBYEFMpfyvLEojGc6SJf8ez0
|
||||||
|
1d8Cv4O/MA8GA1UdEwQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
|
||||||
|
DQEBCwUAA4IBAQBc7Ih8Bc1fkC+FyN1fhjWioBCMr3vjneh7MLbA6kSoyWF70N3s
|
||||||
|
XhbXvT4eRh0hvxqvMZNjPU/VlRn6gLVtoEikDLrYFXN6Hh6Wmyy1GTnspnOvMvz2
|
||||||
|
lLKuym9KYdYLDgnj3BeAvzIhVzzYSeU77/Cupofj093OuAswW0jYvXsGTyix6B3d
|
||||||
|
bW5yWvyS9zNXaqGaUmP3U9/b6DlHdDogMLu3VLpBB9bm5bjaKWWJYgWltCVgUbFq
|
||||||
|
Fqyi4+JE014cSgR57Jcu3dZiehB6UtAPgad9L5cNvua/IWRmm+ANy3O2LH++Pyl8
|
||||||
|
SREzU8onbBsjMg9QDiSf5oJLKvd/Ren+zGY7
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
|
// Serial number of the YubiKey, encoded as an integer.
|
||||||
|
// https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
|
||||||
|
var oidYubicoSerialNumber = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 41482, 3, 7}
|
||||||
|
|
||||||
|
type stepAttestationData struct {
|
||||||
|
Certificate *x509.Certificate
|
||||||
|
SerialNumber string
|
||||||
|
}
|
||||||
|
|
||||||
|
func doStepAttestationFormat(ctx context.Context, ch *Challenge, db DB, att *AttestationObject) (*stepAttestationData, error) {
|
||||||
|
root, err := pemutil.ParseCertificate([]byte(yubicoPIVRootCA))
|
||||||
|
if err != nil {
|
||||||
|
return nil, WrapErrorISE(err, "error parsing apple enterprise ca")
|
||||||
|
}
|
||||||
|
roots := x509.NewCertPool()
|
||||||
|
roots.AddCert(root)
|
||||||
|
|
||||||
|
x5c, ok := att.AttStatement["x5c"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatement, "x5c not present"))
|
||||||
|
}
|
||||||
|
if len(x5c) == 0 {
|
||||||
|
return nil, storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, "x5c is empty"))
|
||||||
|
}
|
||||||
|
|
||||||
|
der, ok := x5c[0].([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatement, "x5c is malformed"))
|
||||||
|
}
|
||||||
|
leaf, err := x509.ParseCertificate(der)
|
||||||
|
if err != nil {
|
||||||
|
return nil, storeError(ctx, db, ch, true, WrapError(ErrorBadAttestationStatement, err, "x5c is malformed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pem.Encode(os.Stderr, &pem.Block{
|
||||||
|
Type: "CERTIFICATE", Bytes: leaf.Raw,
|
||||||
|
})
|
||||||
|
|
||||||
|
intermediates := x509.NewCertPool()
|
||||||
|
for _, v := range x5c[1:] {
|
||||||
|
der, ok = v.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatement, "x5c is malformed"))
|
||||||
|
}
|
||||||
|
cert, err := x509.ParseCertificate(der)
|
||||||
|
if err != nil {
|
||||||
|
return nil, storeError(ctx, db, ch, true, WrapError(ErrorBadAttestationStatement, err, "x5c is malformed"))
|
||||||
|
}
|
||||||
|
intermediates.AddCert(cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := leaf.Verify(x509.VerifyOptions{
|
||||||
|
Intermediates: intermediates,
|
||||||
|
Roots: roots,
|
||||||
|
CurrentTime: time.Now().Truncate(time.Second),
|
||||||
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
||||||
|
}); err != nil {
|
||||||
|
return nil, storeError(ctx, db, ch, true, WrapError(ErrorBadAttestationStatement, err, "x5c is not valid"))
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &stepAttestationData{
|
||||||
|
Certificate: leaf,
|
||||||
|
}
|
||||||
|
for _, ext := range leaf.Extensions {
|
||||||
|
switch {
|
||||||
|
case ext.Id.Equal(oidYubicoSerialNumber):
|
||||||
|
var serialNumber int
|
||||||
|
rest, err := asn1.Unmarshal(ext.Value, &serialNumber)
|
||||||
|
if err != nil || len(rest) > 0 {
|
||||||
|
return nil, storeError(ctx, db, ch, true, WrapError(ErrorBadAttestationStatement, err, "error parsing serial number"))
|
||||||
|
}
|
||||||
|
data.SerialNumber = strconv.Itoa(serialNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
// serverName determines the SNI HostName to set based on an acme.Challenge
|
// serverName determines the SNI HostName to set based on an acme.Challenge
|
||||||
// for TLS-ALPN-01 challenges RFC8738 states that, if HostName is an IP, it
|
// for TLS-ALPN-01 challenges RFC8738 states that, if HostName is an IP, it
|
||||||
// should be the ARPA address https://datatracker.ietf.org/doc/html/rfc8738#section-6.
|
// should be the ARPA address https://datatracker.ietf.org/doc/html/rfc8738#section-6.
|
||||||
|
|
Loading…
Reference in a new issue