forked from TrueCloudLab/certificates
Merge pull request #1063 from smallstep/herman/acme-da-tpm
Add ACME DA TPM attestation
This commit is contained in:
commit
64e39cb0c9
8 changed files with 2312 additions and 43 deletions
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
|
@ -23,4 +23,5 @@ jobs:
|
||||||
os-dependencies: "libpcsclite-dev"
|
os-dependencies: "libpcsclite-dev"
|
||||||
run-gitleaks: true
|
run-gitleaks: true
|
||||||
run-codeql: true
|
run-codeql: true
|
||||||
|
make-test: true # run `make test` instead of the default test workflow
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
14
Makefile
14
Makefile
|
@ -90,13 +90,21 @@ generate:
|
||||||
#########################################
|
#########################################
|
||||||
# Test
|
# Test
|
||||||
#########################################
|
#########################################
|
||||||
test:
|
test: testdefault testtpmsimulator combinecoverage
|
||||||
$Q $(GOFLAGS) gotestsum -- -coverprofile=coverage.out -short -covermode=atomic ./...
|
|
||||||
|
testdefault:
|
||||||
|
$Q $(GOFLAGS) gotestsum -- -coverprofile=defaultcoverage.out -short -covermode=atomic ./...
|
||||||
|
|
||||||
|
testtpmsimulator:
|
||||||
|
$Q CGO_ENALBED=1 gotestsum -- -coverprofile=tpmsimulatorcoverage.out -short -covermode=atomic -tags tpmsimulator ./acme
|
||||||
|
|
||||||
testcgo:
|
testcgo:
|
||||||
$Q gotestsum -- -coverprofile=coverage.out -short -covermode=atomic ./...
|
$Q gotestsum -- -coverprofile=coverage.out -short -covermode=atomic ./...
|
||||||
|
|
||||||
.PHONY: test testcgo
|
combinecoverage:
|
||||||
|
cat defaultcoverage.out tpmsimulatorcoverage.out > coverage.out
|
||||||
|
|
||||||
|
.PHONY: test testdefault testtpmsimulator testcgo combinecoverage
|
||||||
|
|
||||||
integrate: integration
|
integrate: integration
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,14 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fxamacker/cbor/v2"
|
"github.com/fxamacker/cbor/v2"
|
||||||
|
"github.com/google/go-attestation/attest"
|
||||||
|
"github.com/google/go-tpm/tpm2"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
|
||||||
"go.step.sm/crypto/jose"
|
"go.step.sm/crypto/jose"
|
||||||
"go.step.sm/crypto/keyutil"
|
"go.step.sm/crypto/keyutil"
|
||||||
"go.step.sm/crypto/pemutil"
|
"go.step.sm/crypto/pemutil"
|
||||||
|
"go.step.sm/crypto/x509util"
|
||||||
|
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
)
|
)
|
||||||
|
@ -437,6 +442,38 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
|
||||||
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "permanent identifier does not match").AddSubproblems(subproblem))
|
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "permanent identifier does not match").AddSubproblems(subproblem))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update attestation key fingerprint to compare against the CSR
|
||||||
|
az.Fingerprint = data.Fingerprint
|
||||||
|
|
||||||
|
case "tpm":
|
||||||
|
data, err := doTPMAttestationFormat(ctx, prov, ch, jwk, &att)
|
||||||
|
if err != nil {
|
||||||
|
// TODO(hs): we should provide more details in the error reported to the client;
|
||||||
|
// "Attestation statement cannot be verified" is VERY generic. Also holds true for the other formats.
|
||||||
|
var acmeError *Error
|
||||||
|
if errors.As(err, &acmeError) {
|
||||||
|
if acmeError.Status == 500 {
|
||||||
|
return acmeError
|
||||||
|
}
|
||||||
|
return storeError(ctx, db, ch, true, acmeError)
|
||||||
|
}
|
||||||
|
return WrapErrorISE(err, "error validating attestation")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(hs): currently this will allow a request for which no PermanentIdentifiers have been
|
||||||
|
// extracted from the AK certificate. This is currently the case for AK certs from the CLI, as we
|
||||||
|
// haven't implemented a way for AK certs requested by the CLI to always contain the requested
|
||||||
|
// PermanentIdentifier. Omitting the check below doesn't allow just any request, as the Order can
|
||||||
|
// still fail if the challenge value isn't equal to the CSR subject.
|
||||||
|
if len(data.PermanentIdentifiers) > 0 && !slices.Contains(data.PermanentIdentifiers, ch.Value) { // TODO(hs): add support for HardwareModuleName
|
||||||
|
subproblem := NewSubproblemWithIdentifier(
|
||||||
|
ErrorMalformedType,
|
||||||
|
Identifier{Type: "permanent-identifier", Value: ch.Value},
|
||||||
|
"challenge identifier %q doesn't match any of the attested hardware identifiers %q", ch.Value, data.PermanentIdentifiers,
|
||||||
|
)
|
||||||
|
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, "permanent identifier does not match").AddSubproblems(subproblem))
|
||||||
|
}
|
||||||
|
|
||||||
// Update attestation key fingerprint to compare against the CSR
|
// Update attestation key fingerprint to compare against the CSR
|
||||||
az.Fingerprint = data.Fingerprint
|
az.Fingerprint = data.Fingerprint
|
||||||
default:
|
default:
|
||||||
|
@ -463,6 +500,301 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
oidSubjectAlternativeName = asn1.ObjectIdentifier{2, 5, 29, 17}
|
||||||
|
)
|
||||||
|
|
||||||
|
type tpmAttestationData struct {
|
||||||
|
Certificate *x509.Certificate
|
||||||
|
VerifiedChains [][]*x509.Certificate
|
||||||
|
PermanentIdentifiers []string
|
||||||
|
Fingerprint string
|
||||||
|
}
|
||||||
|
|
||||||
|
// coseAlgorithmIdentifier models a COSEAlgorithmIdentifier.
|
||||||
|
// Also see https://www.w3.org/TR/webauthn-2/#sctn-alg-identifier.
|
||||||
|
type coseAlgorithmIdentifier int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
coseAlgES256 coseAlgorithmIdentifier = -7
|
||||||
|
coseAlgRS256 coseAlgorithmIdentifier = -257
|
||||||
|
)
|
||||||
|
|
||||||
|
func doTPMAttestationFormat(ctx context.Context, prov Provisioner, ch *Challenge, jwk *jose.JSONWebKey, att *attestationObject) (*tpmAttestationData, error) {
|
||||||
|
ver, ok := att.AttStatement["ver"].(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "ver not present")
|
||||||
|
}
|
||||||
|
if ver != "2.0" {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "version %q is not supported", ver)
|
||||||
|
}
|
||||||
|
|
||||||
|
x5c, ok := att.AttStatement["x5c"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c not present")
|
||||||
|
}
|
||||||
|
if len(x5c) == 0 {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
akCertBytes, ok := x5c[0].([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c is malformed")
|
||||||
|
}
|
||||||
|
akCert, err := x509.ParseCertificate(akCertBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
||||||
|
}
|
||||||
|
|
||||||
|
intermediates := x509.NewCertPool()
|
||||||
|
for _, v := range x5c[1:] {
|
||||||
|
intCertBytes, vok := v.([]byte)
|
||||||
|
if !vok {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c is malformed")
|
||||||
|
}
|
||||||
|
intCert, err := x509.ParseCertificate(intCertBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
||||||
|
}
|
||||||
|
intermediates.AddCert(intCert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(hs): this can be removed when permanent-identifier/hardware-module-name are handled correctly in
|
||||||
|
// the stdlib in https://cs.opensource.google/go/go/+/refs/tags/go1.19:src/crypto/x509/parser.go;drc=b5b2cf519fe332891c165077f3723ee74932a647;l=362,
|
||||||
|
// but I doubt that will happen.
|
||||||
|
if len(akCert.UnhandledCriticalExtensions) > 0 {
|
||||||
|
unhandledCriticalExtensions := akCert.UnhandledCriticalExtensions[:0]
|
||||||
|
for _, extOID := range akCert.UnhandledCriticalExtensions {
|
||||||
|
if !extOID.Equal(oidSubjectAlternativeName) {
|
||||||
|
// critical extensions other than the Subject Alternative Name remain unhandled
|
||||||
|
unhandledCriticalExtensions = append(unhandledCriticalExtensions, extOID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
akCert.UnhandledCriticalExtensions = unhandledCriticalExtensions
|
||||||
|
}
|
||||||
|
|
||||||
|
roots, ok := prov.GetAttestationRoots()
|
||||||
|
if !ok {
|
||||||
|
return nil, NewErrorISE("no root CA bundle available to verify the attestation certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify that the AK certificate was signed by a trusted root,
|
||||||
|
// chained to by the intermediates provided by the client. As part
|
||||||
|
// of building the verified certificate chain, the signature over the
|
||||||
|
// AK certificate is checked to be a valid signature of one of the
|
||||||
|
// provided intermediates. Signatures over the intermediates are in
|
||||||
|
// turn also verified to be valid signatures from one of the trusted
|
||||||
|
// roots.
|
||||||
|
verifiedChains, err := akCert.Verify(x509.VerifyOptions{
|
||||||
|
Roots: roots,
|
||||||
|
Intermediates: intermediates,
|
||||||
|
CurrentTime: time.Now().Truncate(time.Second),
|
||||||
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate additional AK certificate requirements
|
||||||
|
if err := validateAKCertificate(akCert); err != nil {
|
||||||
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "AK certificate is not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(hs): implement revocation check; Verify() doesn't perform CRL check nor OCSP lookup.
|
||||||
|
|
||||||
|
sans, err := x509util.ParseSubjectAlternativeNames(akCert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed parsing AK certificate Subject Alternative Names")
|
||||||
|
}
|
||||||
|
|
||||||
|
permanentIdentifiers := make([]string, len(sans.PermanentIdentifiers))
|
||||||
|
for i, pi := range sans.PermanentIdentifiers {
|
||||||
|
permanentIdentifiers[i] = pi.Identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract and validate pubArea, sig, certInfo and alg properties from the request body
|
||||||
|
pubArea, ok := att.AttStatement["pubArea"].([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "invalid pubArea in attestation statement")
|
||||||
|
}
|
||||||
|
if len(pubArea) == 0 {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "pubArea is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, ok := att.AttStatement["sig"].([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "invalid sig in attestation statement")
|
||||||
|
}
|
||||||
|
if len(sig) == 0 {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "sig is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
certInfo, ok := att.AttStatement["certInfo"].([]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "invalid certInfo in attestation statement")
|
||||||
|
}
|
||||||
|
if len(certInfo) == 0 {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "certInfo is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
alg, ok := att.AttStatement["alg"].(int64)
|
||||||
|
if !ok {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "invalid alg in attestation statement")
|
||||||
|
}
|
||||||
|
|
||||||
|
// only RS256 and ES256 are allowed
|
||||||
|
coseAlg := coseAlgorithmIdentifier(alg)
|
||||||
|
if coseAlg != coseAlgRS256 && coseAlg != coseAlgES256 {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "invalid alg %d in attestation statement", alg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the hash algorithm to use to SHA256
|
||||||
|
hash := crypto.SHA256
|
||||||
|
|
||||||
|
// recreate the generated key certification parameter values and verify
|
||||||
|
// the attested key using the public key of the AK.
|
||||||
|
certificationParameters := &attest.CertificationParameters{
|
||||||
|
Public: pubArea, // the public key that was attested
|
||||||
|
CreateAttestation: certInfo, // the attested properties of the key
|
||||||
|
CreateSignature: sig, // signature over the attested properties
|
||||||
|
}
|
||||||
|
verifyOpts := attest.VerifyOpts{
|
||||||
|
Public: akCert.PublicKey, // public key of the AK that attested the key
|
||||||
|
Hash: hash,
|
||||||
|
}
|
||||||
|
if err = certificationParameters.Verify(verifyOpts); err != nil {
|
||||||
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "invalid certification parameters")
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode the "certInfo" data. This won't fail, as it's also done as part of Verify().
|
||||||
|
tpmCertInfo, err := tpm2.DecodeAttestationData(certInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed decoding attestation data")
|
||||||
|
}
|
||||||
|
|
||||||
|
keyAuth, err := KeyAuthorization(ch.Token, jwk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed creating key auth digest")
|
||||||
|
}
|
||||||
|
hashedKeyAuth := sha256.Sum256([]byte(keyAuth))
|
||||||
|
|
||||||
|
// verify the WebAuthn object contains the expect key authorization digest, which is carried
|
||||||
|
// within the encoded `certInfo` property of the attestation statement.
|
||||||
|
if subtle.ConstantTimeCompare(hashedKeyAuth[:], []byte(tpmCertInfo.ExtraData)) == 0 {
|
||||||
|
return nil, NewError(ErrorBadAttestationStatementType, "key authorization does not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode the (attested) public key and determine its fingerprint. This won't fail, as it's also done as part of Verify().
|
||||||
|
pub, err := tpm2.DecodePublic(pubArea)
|
||||||
|
if err != nil {
|
||||||
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed decoding pubArea")
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, err := pub.Key()
|
||||||
|
if err != nil {
|
||||||
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed getting public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &tpmAttestationData{
|
||||||
|
Certificate: akCert,
|
||||||
|
VerifiedChains: verifiedChains,
|
||||||
|
PermanentIdentifiers: permanentIdentifiers,
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Fingerprint, err = keyutil.Fingerprint(publicKey); err != nil {
|
||||||
|
return nil, WrapErrorISE(err, "error calculating key fingerprint")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(hs): pass more attestation data, so that that can be used/recorded too?
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
oidExtensionExtendedKeyUsage = asn1.ObjectIdentifier{2, 5, 29, 37}
|
||||||
|
oidTCGKpAIKCertificate = asn1.ObjectIdentifier{2, 23, 133, 8, 3}
|
||||||
|
)
|
||||||
|
|
||||||
|
// validateAKCertifiate validates the X.509 AK certificate to be
|
||||||
|
// in accordance with the required properties. The requirements come from:
|
||||||
|
// https://www.w3.org/TR/webauthn-2/#sctn-tpm-cert-requirements.
|
||||||
|
//
|
||||||
|
// - Version MUST be set to 3.
|
||||||
|
// - Subject field MUST be set to empty.
|
||||||
|
// - The Subject Alternative Name extension MUST be set as defined
|
||||||
|
// in [TPMv2-EK-Profile] section 3.2.9.
|
||||||
|
// - The Extended Key Usage extension MUST contain the OID 2.23.133.8.3
|
||||||
|
// ("joint-iso-itu-t(2) internationalorganizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)").
|
||||||
|
// - The Basic Constraints extension MUST have the CA component set to false.
|
||||||
|
// - An Authority Information Access (AIA) extension with entry id-ad-ocsp
|
||||||
|
// and a CRL Distribution Point extension [RFC5280] are both OPTIONAL as
|
||||||
|
// the status of many attestation certificates is available through metadata
|
||||||
|
// services. See, for example, the FIDO Metadata Service.
|
||||||
|
func validateAKCertificate(c *x509.Certificate) error {
|
||||||
|
if c.Version != 3 {
|
||||||
|
return fmt.Errorf("AK certificate has invalid version %d; only version 3 is allowed", c.Version)
|
||||||
|
}
|
||||||
|
if c.Subject.String() != "" {
|
||||||
|
return fmt.Errorf("AK certificate subject must be empty; got %q", c.Subject)
|
||||||
|
}
|
||||||
|
if c.IsCA {
|
||||||
|
return errors.New("AK certificate must not be a CA")
|
||||||
|
}
|
||||||
|
if err := validateAKCertificateExtendedKeyUsage(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := validateAKCertificateSubjectAlternativeNames(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateAKCertificateSubjectAlternativeNames checks if the AK certificate
|
||||||
|
// has TPM hardware details set.
|
||||||
|
func validateAKCertificateSubjectAlternativeNames(c *x509.Certificate) error {
|
||||||
|
sans, err := x509util.ParseSubjectAlternativeNames(c)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed parsing AK certificate Subject Alternative Names: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
details := sans.TPMHardwareDetails
|
||||||
|
manufacturer, model, version := details.Manufacturer, details.Model, details.Version
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case manufacturer == "":
|
||||||
|
return errors.New("missing TPM manufacturer")
|
||||||
|
case model == "":
|
||||||
|
return errors.New("missing TPM model")
|
||||||
|
case version == "":
|
||||||
|
return errors.New("missing TPM version")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateAKCertificateExtendedKeyUsage checks if the AK certificate
|
||||||
|
// has the "tcg-kp-AIKCertificate" Extended Key Usage set.
|
||||||
|
func validateAKCertificateExtendedKeyUsage(c *x509.Certificate) error {
|
||||||
|
var (
|
||||||
|
valid = false
|
||||||
|
ekus []asn1.ObjectIdentifier
|
||||||
|
)
|
||||||
|
for _, ext := range c.Extensions {
|
||||||
|
if ext.Id.Equal(oidExtensionExtendedKeyUsage) {
|
||||||
|
if _, err := asn1.Unmarshal(ext.Value, &ekus); err != nil || !ekus[0].Equal(oidTCGKpAIKCertificate) {
|
||||||
|
return errors.New("AK certificate is missing Extended Key Usage value tcg-kp-AIKCertificate (2.23.133.8.3)")
|
||||||
|
}
|
||||||
|
valid = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
return errors.New("AK certificate is missing Extended Key Usage extension")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 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-----
|
||||||
|
@ -760,10 +1092,10 @@ func uitoa(val uint) string {
|
||||||
var buf [20]byte // big enough for 64bit value base 10
|
var buf [20]byte // big enough for 64bit value base 10
|
||||||
i := len(buf) - 1
|
i := len(buf) - 1
|
||||||
for val >= 10 {
|
for val >= 10 {
|
||||||
q := val / 10
|
v := val / 10
|
||||||
buf[i] = byte('0' + val - q*10)
|
buf[i] = byte('0' + val - v*10)
|
||||||
i--
|
i--
|
||||||
val = q
|
val = v
|
||||||
}
|
}
|
||||||
// val < 10
|
// val < 10
|
||||||
buf[i] = byte('0' + val)
|
buf[i] = byte('0' + val)
|
||||||
|
|
|
@ -31,15 +31,14 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fxamacker/cbor/v2"
|
"github.com/fxamacker/cbor/v2"
|
||||||
|
"github.com/smallstep/certificates/authority/config"
|
||||||
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"go.step.sm/crypto/jose"
|
"go.step.sm/crypto/jose"
|
||||||
"go.step.sm/crypto/keyutil"
|
"go.step.sm/crypto/keyutil"
|
||||||
"go.step.sm/crypto/minica"
|
"go.step.sm/crypto/minica"
|
||||||
|
"go.step.sm/crypto/x509util"
|
||||||
"github.com/smallstep/certificates/authority/config"
|
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockClient struct {
|
type mockClient struct {
|
||||||
|
@ -4008,3 +4007,291 @@ func Test_deviceAttest01Validate(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
oidTPMManufacturer = asn1.ObjectIdentifier{2, 23, 133, 2, 1}
|
||||||
|
oidTPMModel = asn1.ObjectIdentifier{2, 23, 133, 2, 2}
|
||||||
|
oidTPMVersion = asn1.ObjectIdentifier{2, 23, 133, 2, 3}
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateValidAKCertificate(t *testing.T) *x509.Certificate {
|
||||||
|
t.Helper()
|
||||||
|
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
template := &x509.Certificate{
|
||||||
|
PublicKey: signer.Public(),
|
||||||
|
Version: 3,
|
||||||
|
IsCA: false,
|
||||||
|
UnknownExtKeyUsage: []asn1.ObjectIdentifier{oidTCGKpAIKCertificate},
|
||||||
|
}
|
||||||
|
asn1Value := []byte(fmt.Sprintf(`{"extraNames":[{"type": %q, "value": %q},{"type": %q, "value": %q},{"type": %q, "value": %q}]}`, oidTPMManufacturer, "1414747215", oidTPMModel, "SLB 9670 TPM2.0", oidTPMVersion, "7.55"))
|
||||||
|
sans := []x509util.SubjectAlternativeName{
|
||||||
|
{Type: x509util.DirectoryNameType,
|
||||||
|
ASN1Value: asn1Value},
|
||||||
|
}
|
||||||
|
ext, err := createSubjectAltNameExtension(nil, nil, nil, nil, sans, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
ext.Set(template)
|
||||||
|
ca, err := minica.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
cert, err := ca.Sign(template)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return cert
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateAKCertificate(t *testing.T) {
|
||||||
|
cert := generateValidAKCertificate(t)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
c *x509.Certificate
|
||||||
|
expErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ok",
|
||||||
|
c: cert,
|
||||||
|
expErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/version",
|
||||||
|
c: &x509.Certificate{
|
||||||
|
Version: 1,
|
||||||
|
},
|
||||||
|
expErr: errors.New("AK certificate has invalid version 1; only version 3 is allowed"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/subject",
|
||||||
|
c: &x509.Certificate{
|
||||||
|
Version: 3,
|
||||||
|
Subject: pkix.Name{CommonName: "fail!"},
|
||||||
|
},
|
||||||
|
expErr: errors.New(`AK certificate subject must be empty; got "CN=fail!"`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/isCA",
|
||||||
|
c: &x509.Certificate{
|
||||||
|
Version: 3,
|
||||||
|
IsCA: true,
|
||||||
|
},
|
||||||
|
expErr: errors.New("AK certificate must not be a CA"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fail/extendedKeyUsage",
|
||||||
|
c: &x509.Certificate{
|
||||||
|
Version: 3,
|
||||||
|
},
|
||||||
|
expErr: errors.New("AK certificate is missing Extended Key Usage extension"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := validateAKCertificate(tt.c)
|
||||||
|
if tt.expErr != nil {
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.EqualError(t, err, tt.expErr.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateAKCertificateSubjectAlternativeNames(t *testing.T) {
|
||||||
|
ok := generateValidAKCertificate(t)
|
||||||
|
t.Helper()
|
||||||
|
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
getBase := func() *x509.Certificate {
|
||||||
|
return &x509.Certificate{
|
||||||
|
PublicKey: signer.Public(),
|
||||||
|
Version: 3,
|
||||||
|
IsCA: false,
|
||||||
|
UnknownExtKeyUsage: []asn1.ObjectIdentifier{oidTCGKpAIKCertificate},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ca, err := minica.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
missingManufacturerASN1 := []byte(fmt.Sprintf(`{"extraNames":[{"type": %q, "value": %q},{"type": %q, "value": %q}]}`, oidTPMModel, "SLB 9670 TPM2.0", oidTPMVersion, "7.55"))
|
||||||
|
sans := []x509util.SubjectAlternativeName{
|
||||||
|
{Type: x509util.DirectoryNameType,
|
||||||
|
ASN1Value: missingManufacturerASN1},
|
||||||
|
}
|
||||||
|
ext, err := createSubjectAltNameExtension(nil, nil, nil, nil, sans, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
missingManufacturer := getBase()
|
||||||
|
ext.Set(missingManufacturer)
|
||||||
|
|
||||||
|
missingManufacturer, err = ca.Sign(missingManufacturer)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
missingModelASN1 := []byte(fmt.Sprintf(`{"extraNames":[{"type": %q, "value": %q},{"type": %q, "value": %q}]}`, oidTPMManufacturer, "1414747215", oidTPMVersion, "7.55"))
|
||||||
|
sans = []x509util.SubjectAlternativeName{
|
||||||
|
{Type: x509util.DirectoryNameType,
|
||||||
|
ASN1Value: missingModelASN1},
|
||||||
|
}
|
||||||
|
ext, err = createSubjectAltNameExtension(nil, nil, nil, nil, sans, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
missingModel := getBase()
|
||||||
|
ext.Set(missingModel)
|
||||||
|
|
||||||
|
missingModel, err = ca.Sign(missingModel)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
missingFirmwareVersionASN1 := []byte(fmt.Sprintf(`{"extraNames":[{"type": %q, "value": %q},{"type": %q, "value": %q}]}`, oidTPMManufacturer, "1414747215", oidTPMModel, "SLB 9670 TPM2.0"))
|
||||||
|
sans = []x509util.SubjectAlternativeName{
|
||||||
|
{Type: x509util.DirectoryNameType,
|
||||||
|
ASN1Value: missingFirmwareVersionASN1},
|
||||||
|
}
|
||||||
|
ext, err = createSubjectAltNameExtension(nil, nil, nil, nil, sans, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
missingFirmwareVersion := getBase()
|
||||||
|
ext.Set(missingFirmwareVersion)
|
||||||
|
|
||||||
|
missingFirmwareVersion, err = ca.Sign(missingFirmwareVersion)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
c *x509.Certificate
|
||||||
|
expErr error
|
||||||
|
}{
|
||||||
|
{"ok", ok, nil},
|
||||||
|
{"fail/missing-manufacturer", missingManufacturer, errors.New("missing TPM manufacturer")},
|
||||||
|
{"fail/missing-model", missingModel, errors.New("missing TPM model")},
|
||||||
|
{"fail/missing-firmware-version", missingFirmwareVersion, errors.New("missing TPM version")},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := validateAKCertificateSubjectAlternativeNames(tt.c)
|
||||||
|
if tt.expErr != nil {
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.EqualError(t, err, tt.expErr.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateAKCertificateExtendedKeyUsage(t *testing.T) {
|
||||||
|
ok := generateValidAKCertificate(t)
|
||||||
|
missingEKU := &x509.Certificate{}
|
||||||
|
t.Helper()
|
||||||
|
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
template := &x509.Certificate{
|
||||||
|
PublicKey: signer.Public(),
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
}
|
||||||
|
ca, err := minica.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
wrongEKU, err := ca.Sign(template)
|
||||||
|
require.NoError(t, err)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
c *x509.Certificate
|
||||||
|
expErr error
|
||||||
|
}{
|
||||||
|
{"ok", ok, nil},
|
||||||
|
{"fail/wrong-eku", wrongEKU, errors.New("AK certificate is missing Extended Key Usage value tcg-kp-AIKCertificate (2.23.133.8.3)")},
|
||||||
|
{"fail/missing-eku", missingEKU, errors.New("AK certificate is missing Extended Key Usage extension")},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := validateAKCertificateExtendedKeyUsage(tt.c)
|
||||||
|
if tt.expErr != nil {
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.EqualError(t, err, tt.expErr.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createSubjectAltNameExtension will construct an Extension containing all
|
||||||
|
// SubjectAlternativeNames held in a Certificate. It implements more types than
|
||||||
|
// the golang x509 library, so it is used whenever OtherName or RegisteredID
|
||||||
|
// type SANs are present in the certificate.
|
||||||
|
//
|
||||||
|
// See also https://datatracker.ietf.org/doc/html/rfc5280.html#section-4.2.1.6
|
||||||
|
//
|
||||||
|
// TODO(hs): this was copied from go.step.sm/crypto/x509util to make it easier
|
||||||
|
// to create the SAN extension for testing purposes. Should it be exposed instead?
|
||||||
|
func createSubjectAltNameExtension(dnsNames, emailAddresses x509util.MultiString, ipAddresses x509util.MultiIP, uris x509util.MultiURL, sans []x509util.SubjectAlternativeName, subjectIsEmpty bool) (x509util.Extension, error) {
|
||||||
|
var zero x509util.Extension
|
||||||
|
|
||||||
|
var rawValues []asn1.RawValue
|
||||||
|
for _, dnsName := range dnsNames {
|
||||||
|
rawValue, err := x509util.SubjectAlternativeName{
|
||||||
|
Type: x509util.DNSType, Value: dnsName,
|
||||||
|
}.RawValue()
|
||||||
|
if err != nil {
|
||||||
|
return zero, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rawValues = append(rawValues, rawValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, emailAddress := range emailAddresses {
|
||||||
|
rawValue, err := x509util.SubjectAlternativeName{
|
||||||
|
Type: x509util.EmailType, Value: emailAddress,
|
||||||
|
}.RawValue()
|
||||||
|
if err != nil {
|
||||||
|
return zero, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rawValues = append(rawValues, rawValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ipAddresses {
|
||||||
|
rawValue, err := x509util.SubjectAlternativeName{
|
||||||
|
Type: x509util.IPType, Value: ip.String(),
|
||||||
|
}.RawValue()
|
||||||
|
if err != nil {
|
||||||
|
return zero, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rawValues = append(rawValues, rawValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, uri := range uris {
|
||||||
|
rawValue, err := x509util.SubjectAlternativeName{
|
||||||
|
Type: x509util.URIType, Value: uri.String(),
|
||||||
|
}.RawValue()
|
||||||
|
if err != nil {
|
||||||
|
return zero, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rawValues = append(rawValues, rawValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, san := range sans {
|
||||||
|
rawValue, err := san.RawValue()
|
||||||
|
if err != nil {
|
||||||
|
return zero, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rawValues = append(rawValues, rawValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now marshal the rawValues into the ASN1 sequence, and create an Extension object to hold the extension
|
||||||
|
rawBytes, err := asn1.Marshal(rawValues)
|
||||||
|
if err != nil {
|
||||||
|
return zero, fmt.Errorf("error marshaling SubjectAlternativeName extension to ASN1: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return x509util.Extension{
|
||||||
|
ID: x509util.ObjectIdentifier(oidSubjectAlternativeName),
|
||||||
|
Critical: subjectIsEmpty,
|
||||||
|
Value: rawBytes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
859
acme/challenge_tpmsimulator_test.go
Normal file
859
acme/challenge_tpmsimulator_test.go
Normal file
|
@ -0,0 +1,859 @@
|
||||||
|
//go:build tpmsimulator
|
||||||
|
// +build tpmsimulator
|
||||||
|
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/fxamacker/cbor/v2"
|
||||||
|
"github.com/google/go-attestation/attest"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"go.step.sm/crypto/jose"
|
||||||
|
"go.step.sm/crypto/keyutil"
|
||||||
|
"go.step.sm/crypto/minica"
|
||||||
|
"go.step.sm/crypto/tpm"
|
||||||
|
"go.step.sm/crypto/tpm/simulator"
|
||||||
|
tpmstorage "go.step.sm/crypto/tpm/storage"
|
||||||
|
"go.step.sm/crypto/x509util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newSimulatedTPM(t *testing.T) *tpm.TPM {
|
||||||
|
t.Helper()
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
tpm, err := tpm.New(withSimulator(t), tpm.WithStore(tpmstorage.NewDirstore(tmpDir))) // TODO: provide in-memory storage implementation instead
|
||||||
|
require.NoError(t, err)
|
||||||
|
return tpm
|
||||||
|
}
|
||||||
|
|
||||||
|
func withSimulator(t *testing.T) tpm.NewTPMOption {
|
||||||
|
t.Helper()
|
||||||
|
var sim simulator.Simulator
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if sim == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := sim.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
sim = simulator.New()
|
||||||
|
err := sim.Open()
|
||||||
|
require.NoError(t, err)
|
||||||
|
return tpm.WithSimulator(sim)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateKeyID(t *testing.T, pub crypto.PublicKey) []byte {
|
||||||
|
t.Helper()
|
||||||
|
b, err := x509.MarshalPKIXPublicKey(pub)
|
||||||
|
require.NoError(t, err)
|
||||||
|
hash := sha256.Sum256(b)
|
||||||
|
return hash[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustAttestTPM(t *testing.T, keyAuthorization string, permanentIdentifiers []string) ([]byte, crypto.Signer, *x509.Certificate) {
|
||||||
|
t.Helper()
|
||||||
|
aca, err := minica.New(
|
||||||
|
minica.WithName("TPM Testing"),
|
||||||
|
minica.WithGetSignerFunc(
|
||||||
|
func() (crypto.Signer, error) {
|
||||||
|
return keyutil.GenerateSigner("RSA", "", 2048)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// prepare simulated TPM and create an AK
|
||||||
|
stpm := newSimulatedTPM(t)
|
||||||
|
eks, err := stpm.GetEKs(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
ak, err := stpm.CreateAK(context.Background(), "first-ak")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ak)
|
||||||
|
|
||||||
|
// extract the AK public key // TODO(hs): replace this when there's a simpler method to get the AK public key (e.g. ak.Public())
|
||||||
|
ap, err := ak.AttestationParameters(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
akp, err := attest.ParseAKPublic(attest.TPMVersion20, ap.Public)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create template and sign certificate for the AK public key
|
||||||
|
keyID := generateKeyID(t, eks[0].Public())
|
||||||
|
template := &x509.Certificate{
|
||||||
|
PublicKey: akp.Public,
|
||||||
|
IsCA: false,
|
||||||
|
UnknownExtKeyUsage: []asn1.ObjectIdentifier{oidTCGKpAIKCertificate},
|
||||||
|
}
|
||||||
|
sans := []x509util.SubjectAlternativeName{}
|
||||||
|
uris := []*url.URL{{Scheme: "urn", Opaque: "ek:sha256:" + base64.StdEncoding.EncodeToString(keyID)}}
|
||||||
|
for _, pi := range permanentIdentifiers {
|
||||||
|
sans = append(sans, x509util.SubjectAlternativeName{
|
||||||
|
Type: x509util.PermanentIdentifierType,
|
||||||
|
Value: pi,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
asn1Value := []byte(fmt.Sprintf(`{"extraNames":[{"type": %q, "value": %q},{"type": %q, "value": %q},{"type": %q, "value": %q}]}`, oidTPMManufacturer, "1414747215", oidTPMModel, "SLB 9670 TPM2.0", oidTPMVersion, "7.55"))
|
||||||
|
sans = append(sans, x509util.SubjectAlternativeName{
|
||||||
|
Type: x509util.DirectoryNameType,
|
||||||
|
ASN1Value: asn1Value,
|
||||||
|
})
|
||||||
|
ext, err := createSubjectAltNameExtension(nil, nil, nil, uris, sans, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
ext.Set(template)
|
||||||
|
akCert, err := aca.Sign(template)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, akCert)
|
||||||
|
|
||||||
|
// create a new key attested by the AK, while including
|
||||||
|
// the key authorization bytes as qualifying data.
|
||||||
|
keyAuthSum := sha256.Sum256([]byte(keyAuthorization))
|
||||||
|
config := tpm.AttestKeyConfig{
|
||||||
|
Algorithm: "RSA",
|
||||||
|
Size: 2048,
|
||||||
|
QualifyingData: keyAuthSum[:],
|
||||||
|
}
|
||||||
|
key, err := stpm.AttestKey(context.Background(), "first-ak", "first-key", config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, key)
|
||||||
|
require.Equal(t, "first-key", key.Name())
|
||||||
|
require.NotEqual(t, 0, len(key.Data()))
|
||||||
|
require.Equal(t, "first-ak", key.AttestedBy())
|
||||||
|
require.True(t, key.WasAttested())
|
||||||
|
require.True(t, key.WasAttestedBy(ak))
|
||||||
|
|
||||||
|
signer, err := key.Signer(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// prepare the attestation object with the AK certificate chain,
|
||||||
|
// the attested key, its metadata and the signature signed by the
|
||||||
|
// AK.
|
||||||
|
params, err := key.CertificationParameters(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
attObj, err := cbor.Marshal(struct {
|
||||||
|
Format string `json:"fmt"`
|
||||||
|
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
|
||||||
|
}{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// marshal the ACME payload
|
||||||
|
payload, err := json.Marshal(struct {
|
||||||
|
AttObj string `json:"attObj"`
|
||||||
|
}{
|
||||||
|
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return payload, signer, aca.Root
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_deviceAttest01ValidateWithTPMSimulator(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
ch *Challenge
|
||||||
|
db DB
|
||||||
|
jwk *jose.JSONWebKey
|
||||||
|
payload []byte
|
||||||
|
}
|
||||||
|
type test struct {
|
||||||
|
args args
|
||||||
|
wantErr *Error
|
||||||
|
}
|
||||||
|
tests := map[string]func(t *testing.T) test{
|
||||||
|
"ok/doTPMAttestationFormat-storeError": func(t *testing.T) test {
|
||||||
|
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
|
||||||
|
payload, _, root := mustAttestTPM(t, keyAuth, nil) // TODO: value(s) for AK cert?
|
||||||
|
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
|
||||||
|
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
|
||||||
|
|
||||||
|
// parse payload, set invalid "ver", remarshal
|
||||||
|
var p payloadType
|
||||||
|
err := json.Unmarshal(payload, &p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
attObj, err := base64.RawURLEncoding.DecodeString(p.AttObj)
|
||||||
|
require.NoError(t, err)
|
||||||
|
att := attestationObject{}
|
||||||
|
err = cbor.Unmarshal(attObj, &att)
|
||||||
|
require.NoError(t, err)
|
||||||
|
att.AttStatement["ver"] = "bogus"
|
||||||
|
attObj, err = cbor.Marshal(struct {
|
||||||
|
Format string `json:"fmt"`
|
||||||
|
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
|
||||||
|
}{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: att.AttStatement,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
payload, err = json.Marshal(struct {
|
||||||
|
AttObj string `json:"attObj"`
|
||||||
|
}{
|
||||||
|
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
return test{
|
||||||
|
args: args{
|
||||||
|
ctx: ctx,
|
||||||
|
jwk: jwk,
|
||||||
|
ch: &Challenge{
|
||||||
|
ID: "chID",
|
||||||
|
AuthorizationID: "azID",
|
||||||
|
Token: "token",
|
||||||
|
Type: "device-attest-01",
|
||||||
|
Status: StatusPending,
|
||||||
|
Value: "device.id.12345678",
|
||||||
|
},
|
||||||
|
payload: payload,
|
||||||
|
db: &MockDB{
|
||||||
|
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
|
||||||
|
assert.Equal(t, "azID", id)
|
||||||
|
return &Authorization{ID: "azID"}, nil
|
||||||
|
},
|
||||||
|
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
|
||||||
|
assert.Equal(t, "chID", updch.ID)
|
||||||
|
assert.Equal(t, "token", updch.Token)
|
||||||
|
assert.Equal(t, StatusInvalid, updch.Status)
|
||||||
|
assert.Equal(t, ChallengeType("device-attest-01"), updch.Type)
|
||||||
|
assert.Equal(t, "device.id.12345678", updch.Value)
|
||||||
|
|
||||||
|
err := NewError(ErrorBadAttestationStatementType, `version "bogus" is not supported`)
|
||||||
|
|
||||||
|
assert.EqualError(t, updch.Error.Err, err.Err.Error())
|
||||||
|
assert.Equal(t, err.Type, updch.Error.Type)
|
||||||
|
assert.Equal(t, err.Detail, updch.Error.Detail)
|
||||||
|
assert.Equal(t, err.Status, updch.Error.Status)
|
||||||
|
assert.Equal(t, err.Subproblems, updch.Error.Subproblems)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ok with invalid PermanentIdentifier SAN": func(t *testing.T) test {
|
||||||
|
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
|
||||||
|
payload, _, root := mustAttestTPM(t, keyAuth, []string{"device.id.12345678"}) // TODO: value(s) for AK cert?
|
||||||
|
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
|
||||||
|
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
|
||||||
|
return test{
|
||||||
|
args: args{
|
||||||
|
ctx: ctx,
|
||||||
|
jwk: jwk,
|
||||||
|
ch: &Challenge{
|
||||||
|
ID: "chID",
|
||||||
|
AuthorizationID: "azID",
|
||||||
|
Token: "token",
|
||||||
|
Type: "device-attest-01",
|
||||||
|
Status: StatusPending,
|
||||||
|
Value: "device.id.99999999",
|
||||||
|
},
|
||||||
|
payload: payload,
|
||||||
|
db: &MockDB{
|
||||||
|
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
|
||||||
|
assert.Equal(t, "azID", id)
|
||||||
|
return &Authorization{ID: "azID"}, nil
|
||||||
|
},
|
||||||
|
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
|
||||||
|
assert.Equal(t, "chID", updch.ID)
|
||||||
|
assert.Equal(t, "token", updch.Token)
|
||||||
|
assert.Equal(t, StatusInvalid, updch.Status)
|
||||||
|
assert.Equal(t, ChallengeType("device-attest-01"), updch.Type)
|
||||||
|
assert.Equal(t, "device.id.99999999", updch.Value)
|
||||||
|
|
||||||
|
err := NewError(ErrorRejectedIdentifierType, `permanent identifier does not match`).
|
||||||
|
AddSubproblems(NewSubproblemWithIdentifier(
|
||||||
|
ErrorMalformedType,
|
||||||
|
Identifier{Type: "permanent-identifier", Value: "device.id.99999999"},
|
||||||
|
`challenge identifier "device.id.99999999" doesn't match any of the attested hardware identifiers ["device.id.12345678"]`,
|
||||||
|
))
|
||||||
|
|
||||||
|
assert.EqualError(t, updch.Error.Err, err.Err.Error())
|
||||||
|
assert.Equal(t, err.Type, updch.Error.Type)
|
||||||
|
assert.Equal(t, err.Detail, updch.Error.Detail)
|
||||||
|
assert.Equal(t, err.Status, updch.Error.Status)
|
||||||
|
assert.Equal(t, err.Subproblems, updch.Error.Subproblems)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ok": func(t *testing.T) test {
|
||||||
|
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
|
||||||
|
payload, signer, root := mustAttestTPM(t, keyAuth, nil) // TODO: value(s) for AK cert?
|
||||||
|
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
|
||||||
|
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
|
||||||
|
return test{
|
||||||
|
args: args{
|
||||||
|
ctx: ctx,
|
||||||
|
jwk: jwk,
|
||||||
|
ch: &Challenge{
|
||||||
|
ID: "chID",
|
||||||
|
AuthorizationID: "azID",
|
||||||
|
Token: "token",
|
||||||
|
Type: "device-attest-01",
|
||||||
|
Status: StatusPending,
|
||||||
|
Value: "device.id.12345678",
|
||||||
|
},
|
||||||
|
payload: payload,
|
||||||
|
db: &MockDB{
|
||||||
|
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
|
||||||
|
assert.Equal(t, "azID", id)
|
||||||
|
return &Authorization{ID: "azID"}, nil
|
||||||
|
},
|
||||||
|
MockUpdateAuthorization: func(ctx context.Context, az *Authorization) error {
|
||||||
|
fingerprint, err := keyutil.Fingerprint(signer.Public())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "azID", az.ID)
|
||||||
|
assert.Equal(t, fingerprint, az.Fingerprint)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
|
||||||
|
assert.Equal(t, "chID", updch.ID)
|
||||||
|
assert.Equal(t, "token", updch.Token)
|
||||||
|
assert.Equal(t, StatusValid, updch.Status)
|
||||||
|
assert.Equal(t, ChallengeType("device-attest-01"), updch.Type)
|
||||||
|
assert.Equal(t, "device.id.12345678", updch.Value)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ok with PermanentIdentifier SAN": func(t *testing.T) test {
|
||||||
|
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
|
||||||
|
payload, signer, root := mustAttestTPM(t, keyAuth, []string{"device.id.12345678"}) // TODO: value(s) for AK cert?
|
||||||
|
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
|
||||||
|
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
|
||||||
|
return test{
|
||||||
|
args: args{
|
||||||
|
ctx: ctx,
|
||||||
|
jwk: jwk,
|
||||||
|
ch: &Challenge{
|
||||||
|
ID: "chID",
|
||||||
|
AuthorizationID: "azID",
|
||||||
|
Token: "token",
|
||||||
|
Type: "device-attest-01",
|
||||||
|
Status: StatusPending,
|
||||||
|
Value: "device.id.12345678",
|
||||||
|
},
|
||||||
|
payload: payload,
|
||||||
|
db: &MockDB{
|
||||||
|
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
|
||||||
|
assert.Equal(t, "azID", id)
|
||||||
|
return &Authorization{ID: "azID"}, nil
|
||||||
|
},
|
||||||
|
MockUpdateAuthorization: func(ctx context.Context, az *Authorization) error {
|
||||||
|
fingerprint, err := keyutil.Fingerprint(signer.Public())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "azID", az.ID)
|
||||||
|
assert.Equal(t, fingerprint, az.Fingerprint)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
|
||||||
|
assert.Equal(t, "chID", updch.ID)
|
||||||
|
assert.Equal(t, "token", updch.Token)
|
||||||
|
assert.Equal(t, StatusValid, updch.Status)
|
||||||
|
assert.Equal(t, ChallengeType("device-attest-01"), updch.Type)
|
||||||
|
assert.Equal(t, "device.id.12345678", updch.Value)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, run := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
tc := run(t)
|
||||||
|
|
||||||
|
if err := deviceAttest01Validate(tc.args.ctx, tc.args.ch, tc.args.db, tc.args.jwk, tc.args.payload); err != nil {
|
||||||
|
assert.Error(t, tc.wantErr)
|
||||||
|
assert.EqualError(t, err, tc.wantErr.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Nil(t, tc.wantErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBadAttestationStatementError(msg string) *Error {
|
||||||
|
return &Error{
|
||||||
|
Type: "urn:ietf:params:acme:error:badAttestationStatement",
|
||||||
|
Status: 400,
|
||||||
|
Err: errors.New(msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInternalServerError(msg string) *Error {
|
||||||
|
return &Error{
|
||||||
|
Type: "urn:ietf:params:acme:error:serverInternal",
|
||||||
|
Status: 500,
|
||||||
|
Err: errors.New(msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
oidPermanentIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3}
|
||||||
|
oidHardwareModuleNameIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 4}
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_doTPMAttestationFormat(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
aca, err := minica.New(
|
||||||
|
minica.WithName("TPM Testing"),
|
||||||
|
minica.WithGetSignerFunc(
|
||||||
|
func() (crypto.Signer, error) {
|
||||||
|
return keyutil.GenerateSigner("RSA", "", 2048)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
acaRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: aca.Root.Raw})
|
||||||
|
|
||||||
|
// prepare simulated TPM and create an AK
|
||||||
|
stpm := newSimulatedTPM(t)
|
||||||
|
eks, err := stpm.GetEKs(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
ak, err := stpm.CreateAK(context.Background(), "first-ak")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, ak)
|
||||||
|
|
||||||
|
// extract the AK public key // TODO(hs): replace this when there's a simpler method to get the AK public key (e.g. ak.Public())
|
||||||
|
ap, err := ak.AttestationParameters(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
akp, err := attest.ParseAKPublic(attest.TPMVersion20, ap.Public)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create template and sign certificate for the AK public key
|
||||||
|
keyID := generateKeyID(t, eks[0].Public())
|
||||||
|
template := &x509.Certificate{
|
||||||
|
PublicKey: akp.Public,
|
||||||
|
IsCA: false,
|
||||||
|
UnknownExtKeyUsage: []asn1.ObjectIdentifier{oidTCGKpAIKCertificate},
|
||||||
|
}
|
||||||
|
sans := []x509util.SubjectAlternativeName{}
|
||||||
|
uris := []*url.URL{{Scheme: "urn", Opaque: "ek:sha256:" + base64.StdEncoding.EncodeToString(keyID)}}
|
||||||
|
asn1Value := []byte(fmt.Sprintf(`{"extraNames":[{"type": %q, "value": %q},{"type": %q, "value": %q},{"type": %q, "value": %q}]}`, oidTPMManufacturer, "1414747215", oidTPMModel, "SLB 9670 TPM2.0", oidTPMVersion, "7.55"))
|
||||||
|
sans = append(sans, x509util.SubjectAlternativeName{
|
||||||
|
Type: x509util.DirectoryNameType,
|
||||||
|
ASN1Value: asn1Value,
|
||||||
|
})
|
||||||
|
ext, err := createSubjectAltNameExtension(nil, nil, nil, uris, sans, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
ext.Set(template)
|
||||||
|
akCert, err := aca.Sign(template)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, akCert)
|
||||||
|
|
||||||
|
invalidTemplate := &x509.Certificate{
|
||||||
|
PublicKey: akp.Public,
|
||||||
|
IsCA: false,
|
||||||
|
UnknownExtKeyUsage: []asn1.ObjectIdentifier{oidTCGKpAIKCertificate},
|
||||||
|
}
|
||||||
|
invalidAKCert, err := aca.Sign(invalidTemplate)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, invalidAKCert)
|
||||||
|
|
||||||
|
// generate a JWK and the key authorization value
|
||||||
|
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
keyAuthorization, err := KeyAuthorization("token", jwk)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create a new key attested by the AK, while including
|
||||||
|
// the key authorization bytes as qualifying data.
|
||||||
|
keyAuthSum := sha256.Sum256([]byte(keyAuthorization))
|
||||||
|
config := tpm.AttestKeyConfig{
|
||||||
|
Algorithm: "RSA",
|
||||||
|
Size: 2048,
|
||||||
|
QualifyingData: keyAuthSum[:],
|
||||||
|
}
|
||||||
|
key, err := stpm.AttestKey(context.Background(), "first-ak", "first-key", config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, key)
|
||||||
|
params, err := key.CertificationParameters(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
signer, err := key.Signer(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
fingerprint, err := keyutil.Fingerprint(signer.Public())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// attest another key and get its certification parameters
|
||||||
|
anotherKey, err := stpm.AttestKey(context.Background(), "first-ak", "another-key", config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, key)
|
||||||
|
anotherKeyParams, err := anotherKey.CertificationParameters(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
prov Provisioner
|
||||||
|
ch *Challenge
|
||||||
|
jwk *jose.JSONWebKey
|
||||||
|
att *attestationObject
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *tpmAttestationData
|
||||||
|
expErr *Error
|
||||||
|
}{
|
||||||
|
{"ok", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, nil},
|
||||||
|
{"fail ver not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("ver not present")},
|
||||||
|
{"fail ver type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": []interface{}{},
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("ver not present")},
|
||||||
|
{"fail bogus ver", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "bogus",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError(`version "bogus" is not supported`)},
|
||||||
|
{"fail x5c not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("x5c not present")},
|
||||||
|
{"fail x5c type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": [][]byte{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("x5c not present")},
|
||||||
|
{"fail x5c empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("x5c is empty")},
|
||||||
|
{"fail leaf type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "step",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{"leaf", aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("x5c is malformed")},
|
||||||
|
{"fail leaf parse", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "step",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw[:100], aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("x5c is malformed: x509: malformed certificate")},
|
||||||
|
{"fail intermediate type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "step",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, "intermediate"},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("x5c is malformed")},
|
||||||
|
{"fail intermediate parse", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "step",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw[:100]},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("x5c is malformed: x509: malformed certificate")},
|
||||||
|
{"fail roots", args{ctx, mustAttestationProvisioner(t, nil), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newInternalServerError("no root CA bundle available to verify the attestation certificate")},
|
||||||
|
{"fail verify", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "step",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("x5c is not valid: x509: certificate signed by unknown authority")},
|
||||||
|
{"fail validateAKCertificate", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{invalidAKCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("AK certificate is not valid: missing TPM manufacturer")},
|
||||||
|
{"fail pubArea not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("invalid pubArea in attestation statement")},
|
||||||
|
{"fail pubArea type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": []interface{}{},
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("invalid pubArea in attestation statement")},
|
||||||
|
{"fail pubArea empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": []byte{},
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("pubArea is empty")},
|
||||||
|
{"fail sig not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("invalid sig in attestation statement")},
|
||||||
|
{"fail sig type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": []interface{}{},
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("invalid sig in attestation statement")},
|
||||||
|
{"fail sig empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": []byte{},
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("sig is empty")},
|
||||||
|
{"fail certInfo not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("invalid certInfo in attestation statement")},
|
||||||
|
{"fail certInfo type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": []interface{}{},
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("invalid certInfo in attestation statement")},
|
||||||
|
{"fail certInfo empty", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": []byte{},
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("certInfo is empty")},
|
||||||
|
{"fail alg not present", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("invalid alg in attestation statement")},
|
||||||
|
{"fail alg type", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(0), // invalid alg
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("invalid alg 0 in attestation statement")},
|
||||||
|
{"fail attestation verification", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": anotherKeyParams.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("invalid certification parameters: certification refers to a different key")},
|
||||||
|
{"fail keyAuthorization", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "token"}, &jose.JSONWebKey{Key: []byte("not an asymmetric key")}, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), // RS256
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newInternalServerError("failed creating key auth digest: error generating JWK thumbprint: square/go-jose: unknown key type '[]uint8'")},
|
||||||
|
{"fail different keyAuthorization", args{ctx, mustAttestationProvisioner(t, acaRoot), &Challenge{Token: "aDifferentToken"}, jwk, &attestationObject{
|
||||||
|
Format: "tpm",
|
||||||
|
AttStatement: map[string]interface{}{
|
||||||
|
"ver": "2.0",
|
||||||
|
"x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw},
|
||||||
|
"alg": int64(-257), //
|
||||||
|
"sig": params.CreateSignature,
|
||||||
|
"certInfo": params.CreateAttestation,
|
||||||
|
"pubArea": params.Public,
|
||||||
|
},
|
||||||
|
}}, nil, newBadAttestationStatementError("key authorization does not match")},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := doTPMAttestationFormat(tt.args.ctx, tt.args.prov, tt.args.ch, tt.args.jwk, tt.args.att)
|
||||||
|
if tt.expErr != nil {
|
||||||
|
var ae *Error
|
||||||
|
if assert.True(t, errors.As(err, &ae)) {
|
||||||
|
assert.EqualError(t, err, tt.expErr.Error())
|
||||||
|
assert.Equal(t, ae.StatusCode(), tt.expErr.StatusCode())
|
||||||
|
assert.Equal(t, ae.Type, tt.expErr.Type)
|
||||||
|
}
|
||||||
|
assert.Nil(t, got)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.NotNil(t, got) {
|
||||||
|
assert.Equal(t, akCert, got.Certificate)
|
||||||
|
assert.Equal(t, [][]*x509.Certificate{
|
||||||
|
{
|
||||||
|
akCert, aca.Intermediate, aca.Root,
|
||||||
|
},
|
||||||
|
}, got.VerifiedChains)
|
||||||
|
assert.Equal(t, fingerprint, got.Fingerprint)
|
||||||
|
assert.Empty(t, got.PermanentIdentifiers) // currently expected to be always empty
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ func (c ACMEChallenge) Validate() error {
|
||||||
type ACMEAttestationFormat string
|
type ACMEAttestationFormat string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// APPLE is the format used to enable device-attest-01 on apple devices.
|
// APPLE is the format used to enable device-attest-01 on Apple devices.
|
||||||
APPLE ACMEAttestationFormat = "apple"
|
APPLE ACMEAttestationFormat = "apple"
|
||||||
|
|
||||||
// STEP is the format used to enable device-attest-01 on devices that
|
// STEP is the format used to enable device-attest-01 on devices that
|
||||||
|
@ -57,7 +57,7 @@ const (
|
||||||
// TODO(mariano): should we rename this to something else.
|
// TODO(mariano): should we rename this to something else.
|
||||||
STEP ACMEAttestationFormat = "step"
|
STEP ACMEAttestationFormat = "step"
|
||||||
|
|
||||||
// TPM is the format used to enable device-attest-01 on TPMs.
|
// TPM is the format used to enable device-attest-01 with TPMs.
|
||||||
TPM ACMEAttestationFormat = "tpm"
|
TPM ACMEAttestationFormat = "tpm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -184,7 +184,7 @@ func (p *ACME) Init(config Config) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse attestation roots.
|
// Parse attestation roots.
|
||||||
// The pool will be nil if the there are not roots.
|
// The pool will be nil if there are no roots.
|
||||||
if rest := p.AttestationRoots; len(rest) > 0 {
|
if rest := p.AttestationRoots; len(rest) > 0 {
|
||||||
var block *pem.Block
|
var block *pem.Block
|
||||||
var hasCert bool
|
var hasCert bool
|
||||||
|
|
42
go.mod
42
go.mod
|
@ -3,29 +3,20 @@ module github.com/smallstep/certificates
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.110.0 // indirect
|
|
||||||
cloud.google.com/go/longrunning v0.4.1
|
cloud.google.com/go/longrunning v0.4.1
|
||||||
cloud.google.com/go/security v1.13.0
|
cloud.google.com/go/security v1.13.0
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3
|
github.com/Masterminds/sprig/v3 v3.2.3
|
||||||
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
|
|
||||||
github.com/aws/aws-sdk-go v1.44.225 // indirect
|
|
||||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
|
||||||
github.com/fatih/color v1.9.0 // indirect
|
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0
|
github.com/fxamacker/cbor/v2 v2.4.0
|
||||||
github.com/go-chi/chi v4.1.2+incompatible
|
github.com/go-chi/chi v4.1.2+incompatible
|
||||||
github.com/go-kit/kit v0.10.0 // indirect
|
|
||||||
github.com/go-piv/piv-go v1.11.0 // indirect
|
|
||||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
|
github.com/google/go-attestation v0.4.4-0.20220404204839-8820d49b18d9
|
||||||
github.com/google/go-cmp v0.5.9
|
github.com/google/go-cmp v0.5.9
|
||||||
|
github.com/google/go-tpm v0.3.3
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/googleapis/gax-go/v2 v2.8.0
|
github.com/googleapis/gax-go/v2 v2.8.0
|
||||||
github.com/hashicorp/vault/api v1.9.0
|
github.com/hashicorp/vault/api v1.9.0
|
||||||
github.com/hashicorp/vault/api/auth/approle v0.4.0
|
github.com/hashicorp/vault/api/auth/approle v0.4.0
|
||||||
github.com/hashicorp/vault/api/auth/kubernetes v0.4.0
|
github.com/hashicorp/vault/api/auth/kubernetes v0.4.0
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
|
||||||
github.com/micromdm/scep/v2 v2.1.0
|
github.com/micromdm/scep/v2 v2.1.0
|
||||||
github.com/newrelic/go-agent/v3 v3.21.0
|
github.com/newrelic/go-agent/v3 v3.21.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
@ -38,20 +29,20 @@ require (
|
||||||
github.com/urfave/cli v1.22.12
|
github.com/urfave/cli v1.22.12
|
||||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
||||||
go.step.sm/cli-utils v0.7.5
|
go.step.sm/cli-utils v0.7.5
|
||||||
go.step.sm/crypto v0.28.0
|
go.step.sm/crypto v0.29.0
|
||||||
go.step.sm/linkedca v0.19.0
|
go.step.sm/linkedca v0.19.0
|
||||||
golang.org/x/crypto v0.7.0
|
golang.org/x/crypto v0.7.0
|
||||||
|
golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0
|
||||||
golang.org/x/net v0.8.0
|
golang.org/x/net v0.8.0
|
||||||
golang.org/x/sys v0.6.0 // indirect
|
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
|
||||||
google.golang.org/api v0.114.0
|
google.golang.org/api v0.114.0
|
||||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
|
|
||||||
google.golang.org/grpc v1.54.0
|
google.golang.org/grpc v1.54.0
|
||||||
google.golang.org/protobuf v1.30.0
|
google.golang.org/protobuf v1.30.0
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
cloud.google.com/go v0.110.0 // indirect
|
||||||
cloud.google.com/go/compute v1.18.0 // indirect
|
cloud.google.com/go/compute v1.18.0 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
cloud.google.com/go/iam v0.12.0 // indirect
|
cloud.google.com/go/iam v0.12.0 // indirect
|
||||||
|
@ -66,6 +57,8 @@ require (
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0 // indirect
|
github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0 // indirect
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||||
|
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
|
||||||
|
github.com/aws/aws-sdk-go v1.44.235 // indirect
|
||||||
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
|
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
github.com/cespare/xxhash v1.1.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
@ -74,14 +67,22 @@ require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgraph-io/badger v1.6.2 // indirect
|
github.com/dgraph-io/badger v1.6.2 // indirect
|
||||||
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
|
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
|
||||||
|
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
||||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
|
github.com/go-kit/kit v0.10.0 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
||||||
|
github.com/go-piv/piv-go v1.11.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
github.com/golang/glog v1.0.0 // indirect
|
github.com/golang/glog v1.0.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/google/btree v1.1.2 // indirect
|
||||||
|
github.com/google/certificate-transparency-go v1.1.4 // indirect
|
||||||
|
github.com/google/go-tpm-tools v0.3.10 // indirect
|
||||||
|
github.com/google/go-tspi v0.3.0 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
|
@ -106,16 +107,20 @@ require (
|
||||||
github.com/klauspost/compress v1.15.11 // indirect
|
github.com/klauspost/compress v1.15.11 // indirect
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
|
github.com/peterbourgon/diskv/v3 v3.0.1 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||||
|
github.com/schollz/jsonstore v1.1.0 // indirect
|
||||||
github.com/shopspring/decimal v1.2.0 // indirect
|
github.com/shopspring/decimal v1.2.0 // indirect
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
github.com/spf13/cast v1.4.1 // indirect
|
||||||
|
@ -124,9 +129,11 @@ require (
|
||||||
go.etcd.io/bbolt v1.3.7 // indirect
|
go.etcd.io/bbolt v1.3.7 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
golang.org/x/oauth2 v0.6.0 // indirect
|
golang.org/x/oauth2 v0.6.0 // indirect
|
||||||
|
golang.org/x/sys v0.6.0 // indirect
|
||||||
golang.org/x/text v0.8.0 // indirect
|
golang.org/x/text v0.8.0 // indirect
|
||||||
|
golang.org/x/time v0.1.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -138,3 +145,6 @@ require (
|
||||||
|
|
||||||
// use github.com/smallstep/pkcs7 fork with patches applied
|
// use github.com/smallstep/pkcs7 fork with patches applied
|
||||||
replace go.mozilla.org/pkcs7 => github.com/smallstep/pkcs7 v0.0.0-20230302202335-4c094085c948
|
replace go.mozilla.org/pkcs7 => github.com/smallstep/pkcs7 v0.0.0-20230302202335-4c094085c948
|
||||||
|
|
||||||
|
// use github.com/smallstep/go-attestation fork with patches for Windows AK support applied
|
||||||
|
replace github.com/google/go-attestation v0.4.4-0.20220404204839-8820d49b18d9 => github.com/smallstep/go-attestation v0.4.4-0.20230224121042-1bcb20a75add
|
||||||
|
|
Loading…
Reference in a new issue