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"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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 {
|
||||
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
|
||||
pem.Encode(os.Stderr, &pem.Block{
|
||||
Type: "CERTIFICATE", Bytes: data.Certificate.Raw,
|
||||
})
|
||||
// Validate Apple's ClientIdentifier (Identifier.Value) with device
|
||||
// identifiers.
|
||||
//
|
||||
// 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:
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
|
|
Loading…
Reference in a new issue