Merge branch 'master' into herman/acme-da-roots

pull/1040/head
Herman Slatman 2 years ago
commit ce3215c702
No known key found for this signature in database
GPG Key ID: F4D8A44EA0A75A4F

@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
--- ---
## [Unreleased] ## [Unreleased]
### Added
- Added support for ACME device-attest-01 challenge.
## [0.22.1] - 2022-08-31 ## [0.22.1] - 2022-08-31
### Fixed ### Fixed

@ -35,7 +35,7 @@ To get up and running quickly, or as an alternative to running your own `step-ca
[![GitHub release](https://img.shields.io/github/release/smallstep/certificates.svg)](https://github.com/smallstep/certificates/releases/latest) [![GitHub release](https://img.shields.io/github/release/smallstep/certificates.svg)](https://github.com/smallstep/certificates/releases/latest)
[![Go Report Card](https://goreportcard.com/badge/github.com/smallstep/certificates)](https://goreportcard.com/report/github.com/smallstep/certificates) [![Go Report Card](https://goreportcard.com/badge/github.com/smallstep/certificates)](https://goreportcard.com/report/github.com/smallstep/certificates)
[![Build Status](https://travis-ci.com/smallstep/certificates.svg?branch=master)](https://travis-ci.com/smallstep/certificates) [![Build Status](https://github.com/smallstep/certificates/actions/workflows/test.yml/badge.svg)](https://github.com/smallstep/certificates)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![CLA assistant](https://cla-assistant.io/readme/badge/smallstep/certificates)](https://cla-assistant.io/smallstep/certificates) [![CLA assistant](https://cla-assistant.io/readme/badge/smallstep/certificates)](https://cla-assistant.io/smallstep/certificates)

@ -3,6 +3,7 @@ package api
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/x509"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@ -49,6 +50,10 @@ func (*fakeProvisioner) IsAttestationFormatEnabled(ctx context.Context, format p
return true return true
} }
func (*fakeProvisioner) GetAttestationRoots() (*x509.CertPool, bool) {
return nil, false
}
func (*fakeProvisioner) AuthorizeRevoke(ctx context.Context, token string) error { return nil } func (*fakeProvisioner) AuthorizeRevoke(ctx context.Context, token string) error { return nil }
func (*fakeProvisioner) GetID() string { return "" } func (*fakeProvisioner) GetID() string { return "" }
func (*fakeProvisioner) GetName() string { return "" } func (*fakeProvisioner) GetName() string { return "" }

@ -354,7 +354,7 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
switch att.Format { switch att.Format {
case "apple": case "apple":
data, err := doAppleAttestationFormat(ctx, ch, db, &att) data, err := doAppleAttestationFormat(ctx, prov, ch, &att)
if err != nil { if err != nil {
var acmeError *Error var acmeError *Error
if errors.As(err, &acmeError) { if errors.As(err, &acmeError) {
@ -382,7 +382,7 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "permanent identifier does not match")) return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "permanent identifier does not match"))
} }
case "step": case "step":
data, err := doStepAttestationFormat(ctx, ch, jwk, &att) data, err := doStepAttestationFormat(ctx, prov, ch, jwk, &att)
if err != nil { if err != nil {
var acmeError *Error var acmeError *Error
if errors.As(err, &acmeError) { if errors.As(err, &acmeError) {
@ -593,13 +593,17 @@ type appleAttestationData struct {
Certificate *x509.Certificate Certificate *x509.Certificate
} }
func doAppleAttestationFormat(ctx context.Context, ch *Challenge, db DB, att *AttestationObject) (*appleAttestationData, error) { func doAppleAttestationFormat(ctx context.Context, prov Provisioner, ch *Challenge, att *AttestationObject) (*appleAttestationData, error) {
root, err := pemutil.ParseCertificate([]byte(appleEnterpriseAttestationRootCA)) // Use configured or default attestation roots if none is configured.
if err != nil { roots, ok := prov.GetAttestationRoots()
return nil, WrapErrorISE(err, "error parsing apple enterprise ca") if !ok {
root, err := pemutil.ParseCertificate([]byte(appleEnterpriseAttestationRootCA))
if err != nil {
return nil, WrapErrorISE(err, "error parsing apple enterprise ca")
}
roots = x509.NewCertPool()
roots.AddCert(root)
} }
roots := x509.NewCertPool()
roots.AddCert(root)
x5c, ok := att.AttStatement["x5c"].([]interface{}) x5c, ok := att.AttStatement["x5c"].([]interface{})
if !ok { if !ok {
@ -690,13 +694,17 @@ type stepAttestationData struct {
SerialNumber string SerialNumber string
} }
func doStepAttestationFormat(ctx context.Context, ch *Challenge, jwk *jose.JSONWebKey, att *AttestationObject) (*stepAttestationData, error) { func doStepAttestationFormat(ctx context.Context, prov Provisioner, ch *Challenge, jwk *jose.JSONWebKey, att *AttestationObject) (*stepAttestationData, error) {
root, err := pemutil.ParseCertificate([]byte(yubicoPIVRootCA)) // Use configured or default attestation roots if none is configured.
if err != nil { roots, ok := prov.GetAttestationRoots()
return nil, WrapErrorISE(err, "error parsing root ca") if !ok {
root, err := pemutil.ParseCertificate([]byte(yubicoPIVRootCA))
if err != nil {
return nil, WrapErrorISE(err, "error parsing root ca")
}
roots = x509.NewCertPool()
roots.AddCert(root)
} }
roots := x509.NewCertPool()
roots.AddCert(root)
// Extract x5c and verify certificate // Extract x5c and verify certificate
x5c, ok := att.AttStatement["x5c"].([]interface{}) x5c, ok := att.AttStatement["x5c"].([]interface{})

@ -4,6 +4,8 @@ import (
"bytes" "bytes"
"context" "context"
"crypto" "crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha256" "crypto/sha256"
@ -13,6 +15,7 @@ import (
"encoding/asn1" "encoding/asn1"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -20,13 +23,18 @@ import (
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect"
"strings" "strings"
"testing" "testing"
"time" "time"
"go.step.sm/crypto/jose" "github.com/fxamacker/cbor/v2"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/minica"
) )
type mockClient struct { type mockClient struct {
@ -37,8 +45,25 @@ type mockClient struct {
func (m *mockClient) Get(url string) (*http.Response, error) { return m.get(url) } func (m *mockClient) Get(url string) (*http.Response, error) { return m.get(url) }
func (m *mockClient) LookupTxt(name string) ([]string, error) { return m.lookupTxt(name) } func (m *mockClient) LookupTxt(name string) ([]string, error) { return m.lookupTxt(name) }
func (m *mockClient) TLSDial(network, addr string, config *tls.Config) (*tls.Conn, error) { func (m *mockClient) TLSDial(network, addr string, tlsConfig *tls.Config) (*tls.Conn, error) {
return m.tlsDial(network, addr, config) return m.tlsDial(network, addr, tlsConfig)
}
func mustAttestationProvisioner(t *testing.T, roots []byte) Provisioner {
t.Helper()
prov := &provisioner.ACME{
Type: "ACME",
Name: "acme",
Challenges: []provisioner.ACMEChallenge{provisioner.DEVICE_ATTEST_01},
AttestationRoots: roots,
}
if err := prov.Init(provisioner.Config{
Claims: config.GlobalProvisionerClaims,
}); err != nil {
t.Fatal(err)
}
return prov
} }
func Test_storeError(t *testing.T) { func Test_storeError(t *testing.T) {
@ -2400,3 +2425,352 @@ func Test_http01ChallengeHost(t *testing.T) {
}) })
} }
} }
func Test_doAppleAttestationFormat(t *testing.T) {
ctx := context.Background()
ca, err := minica.New()
if err != nil {
t.Fatal(err)
}
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Root.Raw})
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
leaf, err := ca.Sign(&x509.Certificate{
Subject: pkix.Name{CommonName: "attestation cert"},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{
{Id: oidAppleSerialNumber, Value: []byte("serial-number")},
{Id: oidAppleUniqueDeviceIdentifier, Value: []byte("udid")},
{Id: oidAppleSecureEnclaveProcessorOSVersion, Value: []byte("16.0")},
{Id: oidAppleNonce, Value: []byte("nonce")},
},
})
if err != nil {
t.Fatal(err)
}
type args struct {
ctx context.Context
prov Provisioner
ch *Challenge
att *AttestationObject
}
tests := []struct {
name string
args args
want *appleAttestationData
wantErr bool
}{
{"ok", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &AttestationObject{
Format: "apple",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
},
}}, &appleAttestationData{
Nonce: []byte("nonce"),
SerialNumber: "serial-number",
UDID: "udid",
SEPVersion: "16.0",
Certificate: leaf,
}, false},
{"fail apple issuer", args{ctx, mustAttestationProvisioner(t, nil), &Challenge{}, &AttestationObject{
Format: "apple",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
},
}}, nil, true},
{"fail missing x5c", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &AttestationObject{
Format: "apple",
AttStatement: map[string]interface{}{
"foo": "bar",
},
}}, nil, true},
{"fail empty issuer", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &AttestationObject{
Format: "apple",
AttStatement: map[string]interface{}{
"x5c": []interface{}{},
},
}}, nil, true},
{"fail leaf type", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &AttestationObject{
Format: "apple",
AttStatement: map[string]interface{}{
"x5c": []interface{}{"leaf", ca.Intermediate.Raw},
},
}}, nil, true},
{"fail leaf parse", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &AttestationObject{
Format: "apple",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw[:100], ca.Intermediate.Raw},
},
}}, nil, true},
{"fail intermediate type", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &AttestationObject{
Format: "apple",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, "intermediate"},
},
}}, nil, true},
{"fail intermediate parse", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &AttestationObject{
Format: "apple",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw[:100]},
},
}}, nil, true},
{"fail verify", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &AttestationObject{
Format: "apple",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw},
},
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := doAppleAttestationFormat(tt.args.ctx, tt.args.prov, tt.args.ch, tt.args.att)
if (err != nil) != tt.wantErr {
t.Errorf("doAppleAttestationFormat() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("doAppleAttestationFormat() = %v, want %v", got, tt.want)
}
})
}
}
func Test_doStepAttestationFormat(t *testing.T) {
ctx := context.Background()
ca, err := minica.New()
if err != nil {
t.Fatal(err)
}
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Root.Raw})
makeLeaf := func(signer crypto.Signer, serialNumber []byte) *x509.Certificate {
leaf, err := ca.Sign(&x509.Certificate{
Subject: pkix.Name{CommonName: "attestation cert"},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{
{Id: oidYubicoSerialNumber, Value: serialNumber},
},
})
if err != nil {
t.Fatal(err)
}
return leaf
}
mustSigner := func(kty, crv string, size int) crypto.Signer {
s, err := keyutil.GenerateSigner(kty, crv, size)
if err != nil {
t.Fatal(err)
}
return s
}
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
serialNumber, err := asn1.Marshal(1234)
if err != nil {
t.Fatal(err)
}
leaf := makeLeaf(signer, serialNumber)
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
if err != nil {
t.Fatal(err)
}
keyAuth, err := KeyAuthorization("token", jwk)
if err != nil {
t.Fatal(err)
}
keyAuthSum := sha256.Sum256([]byte(keyAuth))
sig, err := signer.Sign(rand.Reader, keyAuthSum[:], crypto.SHA256)
if err != nil {
t.Fatal(err)
}
cborSig, err := cbor.Marshal(sig)
if err != nil {
t.Fatal(err)
}
otherSigner, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
otherSig, err := otherSigner.Sign(rand.Reader, keyAuthSum[:], crypto.SHA256)
if err != nil {
t.Fatal(err)
}
otherCBORSig, err := cbor.Marshal(otherSig)
if err != nil {
t.Fatal(err)
}
type args struct {
ctx context.Context
prov Provisioner
ch *Challenge
jwk *jose.JSONWebKey
att *AttestationObject
}
tests := []struct {
name string
args args
want *stepAttestationData
wantErr bool
}{
{"ok", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
}}, &stepAttestationData{
SerialNumber: "1234",
Certificate: leaf,
}, false},
{"fail yubico issuer", args{ctx, mustAttestationProvisioner(t, nil), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
}}, nil, true},
{"fail x5c type", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": [][]byte{leaf.Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
}}, nil, true},
{"fail x5c empty", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{},
"alg": -7,
"sig": cborSig,
},
}}, nil, true},
{"fail leaf type", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{"leaf", ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
}}, nil, true},
{"fail leaf parse", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw[:100], ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
}}, nil, true},
{"fail intermediate type", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, "intermediate"},
"alg": -7,
"sig": cborSig,
},
}}, nil, true},
{"fail intermediate parse", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw[:100]},
"alg": -7,
"sig": cborSig,
},
}}, nil, true},
{"fail verify", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw},
"alg": -7,
"sig": cborSig,
},
}}, nil, true},
{"fail sig type", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": string(cborSig),
},
}}, nil, true},
{"fail sig unmarshal", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": []byte("bad-sig"),
},
}}, nil, true},
{"fail keyAuthorization", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, &jose.JSONWebKey{Key: []byte("not an asymmetric key")}, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
}}, nil, true},
{"fail sig verify P-256", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": otherCBORSig,
},
}}, nil, true},
{"fail sig verify P-384", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{makeLeaf(mustSigner("EC", "P-384", 0), serialNumber).Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
}}, nil, true},
{"fail sig verify RSA", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{makeLeaf(mustSigner("RSA", "", 2048), serialNumber).Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
}}, nil, true},
{"fail sig verify Ed25519", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{makeLeaf(mustSigner("OKP", "Ed25519", 0), serialNumber).Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
}}, nil, true},
{"fail unmarshal serial number", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{makeLeaf(signer, []byte("bad-serial")).Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := doStepAttestationFormat(tt.args.ctx, tt.args.prov, tt.args.ch, tt.args.jwk, tt.args.att)
if (err != nil) != tt.wantErr {
t.Errorf("doStepAttestationFormat() error = %#v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("doStepAttestationFormat() = %v, want %v", got, tt.want)
}
})
}
}

@ -73,6 +73,7 @@ type Provisioner interface {
AuthorizeRevoke(ctx context.Context, token string) error AuthorizeRevoke(ctx context.Context, token string) error
IsChallengeEnabled(ctx context.Context, challenge provisioner.ACMEChallenge) bool IsChallengeEnabled(ctx context.Context, challenge provisioner.ACMEChallenge) bool
IsAttestationFormatEnabled(ctx context.Context, format provisioner.ACMEAttestationFormat) bool IsAttestationFormatEnabled(ctx context.Context, format provisioner.ACMEAttestationFormat) bool
GetAttestationRoots() (*x509.CertPool, bool)
GetID() string GetID() string
GetName() string GetName() string
DefaultTLSCertDuration() time.Duration DefaultTLSCertDuration() time.Duration
@ -113,6 +114,7 @@ type MockProvisioner struct {
MauthorizeRevoke func(ctx context.Context, token string) error MauthorizeRevoke func(ctx context.Context, token string) error
MisChallengeEnabled func(ctx context.Context, challenge provisioner.ACMEChallenge) bool MisChallengeEnabled func(ctx context.Context, challenge provisioner.ACMEChallenge) bool
MisAttFormatEnabled func(ctx context.Context, format provisioner.ACMEAttestationFormat) bool MisAttFormatEnabled func(ctx context.Context, format provisioner.ACMEAttestationFormat) bool
MgetAttestationRoots func() (*x509.CertPool, bool)
MdefaultTLSCertDuration func() time.Duration MdefaultTLSCertDuration func() time.Duration
MgetOptions func() *provisioner.Options MgetOptions func() *provisioner.Options
} }
@ -165,6 +167,13 @@ func (m *MockProvisioner) IsAttestationFormatEnabled(ctx context.Context, format
return m.Merr == nil return m.Merr == nil
} }
func (m *MockProvisioner) GetAttestationRoots() (*x509.CertPool, bool) {
if m.MgetAttestationRoots != nil {
return m.MgetAttestationRoots()
}
return m.Mret1.(*x509.CertPool), m.Mret1 != nil
}
// DefaultTLSCertDuration mock // DefaultTLSCertDuration mock
func (m *MockProvisioner) DefaultTLSCertDuration() time.Duration { func (m *MockProvisioner) DefaultTLSCertDuration() time.Duration {
if m.MdefaultTLSCertDuration != nil { if m.MdefaultTLSCertDuration != nil {

@ -77,7 +77,7 @@ func TestDB_getDBAuthz(t *testing.T) {
Token: "token", Token: "token",
CreatedAt: now, CreatedAt: now,
ExpiresAt: now.Add(5 * time.Minute), ExpiresAt: now.Add(5 * time.Minute),
Error: acme.NewErrorISE("force"), Error: acme.NewErrorISE("The server experienced an internal error"),
ChallengeIDs: []string{"foo", "bar"}, ChallengeIDs: []string{"foo", "bar"},
Wildcard: true, Wildcard: true,
} }
@ -254,7 +254,7 @@ func TestDB_GetAuthorization(t *testing.T) {
Token: "token", Token: "token",
CreatedAt: now, CreatedAt: now,
ExpiresAt: now.Add(5 * time.Minute), ExpiresAt: now.Add(5 * time.Minute),
Error: acme.NewErrorISE("force"), Error: acme.NewErrorISE("The server experienced an internal error"),
ChallengeIDs: []string{"foo", "bar"}, ChallengeIDs: []string{"foo", "bar"},
Wildcard: true, Wildcard: true,
} }
@ -532,7 +532,7 @@ func TestDB_UpdateAuthorization(t *testing.T) {
assert.Equals(t, dbNew.Wildcard, dbaz.Wildcard) assert.Equals(t, dbNew.Wildcard, dbaz.Wildcard)
assert.Equals(t, dbNew.CreatedAt, dbaz.CreatedAt) assert.Equals(t, dbNew.CreatedAt, dbaz.CreatedAt)
assert.Equals(t, dbNew.ExpiresAt, dbaz.ExpiresAt) assert.Equals(t, dbNew.ExpiresAt, dbaz.ExpiresAt)
assert.Equals(t, dbNew.Error.Error(), acme.NewError(acme.ErrorMalformedType, "malformed").Error()) assert.Equals(t, dbNew.Error.Error(), acme.NewError(acme.ErrorMalformedType, "The request message was malformed").Error())
return nil, false, errors.New("force") return nil, false, errors.New("force")
}, },
}, },
@ -582,7 +582,7 @@ func TestDB_UpdateAuthorization(t *testing.T) {
assert.Equals(t, dbNew.Wildcard, dbaz.Wildcard) assert.Equals(t, dbNew.Wildcard, dbaz.Wildcard)
assert.Equals(t, dbNew.CreatedAt, dbaz.CreatedAt) assert.Equals(t, dbNew.CreatedAt, dbaz.CreatedAt)
assert.Equals(t, dbNew.ExpiresAt, dbaz.ExpiresAt) assert.Equals(t, dbNew.ExpiresAt, dbaz.ExpiresAt)
assert.Equals(t, dbNew.Error.Error(), acme.NewError(acme.ErrorMalformedType, "malformed").Error()) assert.Equals(t, dbNew.Error.Error(), acme.NewError(acme.ErrorMalformedType, "The request message was malformed").Error())
return nu, true, nil return nu, true, nil
}, },
}, },

@ -72,7 +72,7 @@ func TestDB_getDBChallenge(t *testing.T) {
Value: "test.ca.smallstep.com", Value: "test.ca.smallstep.com",
CreatedAt: clock.Now(), CreatedAt: clock.Now(),
ValidatedAt: "foobar", ValidatedAt: "foobar",
Error: acme.NewErrorISE("force"), Error: acme.NewErrorISE("The server experienced an internal error"),
} }
b, err := json.Marshal(dbc) b, err := json.Marshal(dbc)
assert.FatalError(t, err) assert.FatalError(t, err)
@ -264,7 +264,7 @@ func TestDB_GetChallenge(t *testing.T) {
Value: "test.ca.smallstep.com", Value: "test.ca.smallstep.com",
CreatedAt: clock.Now(), CreatedAt: clock.Now(),
ValidatedAt: "foobar", ValidatedAt: "foobar",
Error: acme.NewErrorISE("force"), Error: acme.NewErrorISE("The server experienced an internal error"),
} }
b, err := json.Marshal(dbc) b, err := json.Marshal(dbc)
assert.FatalError(t, err) assert.FatalError(t, err)
@ -354,7 +354,7 @@ func TestDB_UpdateChallenge(t *testing.T) {
ID: chID, ID: chID,
Status: acme.StatusValid, Status: acme.StatusValid,
ValidatedAt: "foobar", ValidatedAt: "foobar",
Error: acme.NewError(acme.ErrorMalformedType, "malformed"), Error: acme.NewError(acme.ErrorMalformedType, "The request message was malformed"),
} }
return test{ return test{
ch: updCh, ch: updCh,
@ -428,7 +428,7 @@ func TestDB_UpdateChallenge(t *testing.T) {
assert.Equals(t, dbNew.CreatedAt, dbc.CreatedAt) assert.Equals(t, dbNew.CreatedAt, dbc.CreatedAt)
assert.Equals(t, dbNew.Status, acme.StatusValid) assert.Equals(t, dbNew.Status, acme.StatusValid)
assert.Equals(t, dbNew.ValidatedAt, "foobar") assert.Equals(t, dbNew.ValidatedAt, "foobar")
assert.Equals(t, dbNew.Error.Error(), acme.NewError(acme.ErrorMalformedType, "malformed").Error()) assert.Equals(t, dbNew.Error.Error(), acme.NewError(acme.ErrorMalformedType, "The request message was malformed").Error())
return nu, true, nil return nu, true, nil
}, },
}, },

@ -80,7 +80,7 @@ func TestDB_getDBOrder(t *testing.T) {
{Type: "dns", Value: "example.foo.com"}, {Type: "dns", Value: "example.foo.com"},
}, },
AuthorizationIDs: []string{"foo", "bar"}, AuthorizationIDs: []string{"foo", "bar"},
Error: acme.NewError(acme.ErrorMalformedType, "force"), Error: acme.NewError(acme.ErrorMalformedType, "The request message was malformed"),
} }
b, err := json.Marshal(dbo) b, err := json.Marshal(dbo)
assert.FatalError(t, err) assert.FatalError(t, err)
@ -185,7 +185,7 @@ func TestDB_GetOrder(t *testing.T) {
{Type: "dns", Value: "example.foo.com"}, {Type: "dns", Value: "example.foo.com"},
}, },
AuthorizationIDs: []string{"foo", "bar"}, AuthorizationIDs: []string{"foo", "bar"},
Error: acme.NewError(acme.ErrorMalformedType, "force"), Error: acme.NewError(acme.ErrorMalformedType, "The request message was malformed"),
} }
b, err := json.Marshal(dbo) b, err := json.Marshal(dbo)
assert.FatalError(t, err) assert.FatalError(t, err)
@ -284,7 +284,7 @@ func TestDB_UpdateOrder(t *testing.T) {
ID: orderID, ID: orderID,
Status: acme.StatusValid, Status: acme.StatusValid,
CertificateID: "certID", CertificateID: "certID",
Error: acme.NewError(acme.ErrorMalformedType, "force"), Error: acme.NewError(acme.ErrorMalformedType, "The request message was malformed"),
} }
return test{ return test{
o: o, o: o,
@ -324,7 +324,7 @@ func TestDB_UpdateOrder(t *testing.T) {
ID: orderID, ID: orderID,
Status: acme.StatusValid, Status: acme.StatusValid,
CertificateID: "certID", CertificateID: "certID",
Error: acme.NewError(acme.ErrorMalformedType, "force"), Error: acme.NewError(acme.ErrorMalformedType, "The request message was malformed"),
} }
return test{ return test{
o: o, o: o,
@ -372,7 +372,7 @@ func TestDB_UpdateOrder(t *testing.T) {
assert.Equals(t, tc.o.ID, dbo.ID) assert.Equals(t, tc.o.ID, dbo.ID)
assert.Equals(t, tc.o.CertificateID, "certID") assert.Equals(t, tc.o.CertificateID, "certID")
assert.Equals(t, tc.o.Status, acme.StatusValid) assert.Equals(t, tc.o.Status, acme.StatusValid)
assert.Equals(t, tc.o.Error.Error(), acme.NewError(acme.ErrorMalformedType, "force").Error()) assert.Equals(t, tc.o.Error.Error(), acme.NewError(acme.ErrorMalformedType, "The request message was malformed").Error())
} }
} }
}) })
@ -659,7 +659,7 @@ func TestDB_updateAddOrderIDs(t *testing.T) {
assert.Equals(t, newdbo.ID, "foo") assert.Equals(t, newdbo.ID, "foo")
assert.Equals(t, newdbo.Status, acme.StatusInvalid) assert.Equals(t, newdbo.Status, acme.StatusInvalid)
assert.Equals(t, newdbo.ExpiresAt, expiry) assert.Equals(t, newdbo.ExpiresAt, expiry)
assert.Equals(t, newdbo.Error.Error(), acme.NewError(acme.ErrorMalformedType, "order has expired").Error()) assert.Equals(t, newdbo.Error.Error(), acme.NewError(acme.ErrorMalformedType, "The request message was malformed").Error())
return nil, false, errors.New("force") return nil, false, errors.New("force")
}, },
}, },

@ -335,9 +335,12 @@ func (e *Error) StatusCode() int {
return e.Status return e.Status
} }
// Error allows AError to implement the error interface. // Error implements the error interface.
func (e *Error) Error() string { func (e *Error) Error() string {
return e.Detail if e.Err == nil {
return e.Detail
}
return e.Err.Error()
} }
// Cause returns the internal error and implements the Causer interface. // Cause returns the internal error and implements the Causer interface.

@ -157,6 +157,9 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
data := x509util.NewTemplateData() data := x509util.NewTemplateData()
data.SetCommonName(csr.Subject.CommonName) data.SetCommonName(csr.Subject.CommonName)
// Custom sign options passed to authority.Sign
var extraOptions []provisioner.SignOption
// TODO: support for multiple identifiers? // TODO: support for multiple identifiers?
var permanentIdentifier string var permanentIdentifier string
for i := range o.Identifiers { for i := range o.Identifiers {
@ -173,6 +176,9 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
Type: x509util.PermanentIdentifierType, Type: x509util.PermanentIdentifierType,
Value: permanentIdentifier, Value: permanentIdentifier,
}) })
extraOptions = append(extraOptions, provisioner.AttestationData{
PermanentIdentifier: permanentIdentifier,
})
} else { } else {
defaultTemplate = x509util.DefaultLeafTemplate defaultTemplate = x509util.DefaultLeafTemplate
sans, err := o.sans(csr) sans, err := o.sans(csr)
@ -193,7 +199,11 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
if err != nil { if err != nil {
return WrapErrorISE(err, "error creating template options from ACME provisioner") return WrapErrorISE(err, "error creating template options from ACME provisioner")
} }
// Build extra signing options.
signOps = append(signOps, templateOptions) signOps = append(signOps, templateOptions)
signOps = append(signOps, extraOptions...)
// Sign a new certificate. // Sign a new certificate.
certChain, err := auth.Sign(csr, provisioner.SignOptions{ certChain, err := auth.Sign(csr, provisioner.SignOptions{
NotBefore: provisioner.NewTimeDuration(o.NotBefore), NotBefore: provisioner.NewTimeDuration(o.NotBefore),

@ -434,7 +434,7 @@ func (a *Authority) AuthorizeRenewToken(ctx context.Context, ott string) (*x509.
audiences := a.config.GetAudiences().Renew audiences := a.config.GetAudiences().Renew
if !matchesAudience(claims.Audience, audiences) { if !matchesAudience(claims.Audience, audiences) {
return nil, errs.InternalServerErr(err, errs.WithMessage("error validating renew token: invalid audience claim (aud)")) return nil, errs.InternalServerErr(jose.ErrInvalidAudience, errs.WithMessage("error validating renew token: invalid audience claim (aud)"))
} }
// validate issuer: old versions used the provisioner name, new version uses // validate issuer: old versions used the provisioner name, new version uses

@ -96,14 +96,14 @@ type ACME struct {
// provisioner. If this value is not set the default apple, step and tpm // provisioner. If this value is not set the default apple, step and tpm
// will be used. // will be used.
AttestationFormats []ACMEAttestationFormat `json:"attestationFormats,omitempty"` AttestationFormats []ACMEAttestationFormat `json:"attestationFormats,omitempty"`
Claims *Claims `json:"claims,omitempty"` // AttestationRoots contains a bundle of root certificates in PEM format
Options *Options `json:"options,omitempty"` // that will be used to verify the attestation certificates. If provided,
// this bundle will be used even for well-known CAs like Apple and Yubico.
// TODO(hs): WIP configuration for ACME Device Attestation AttestationRoots []byte `json:"attestationRoots,omitempty"`
AttestationRoots []byte `json:"attestationRoots"` Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"`
attestationRootPool *x509.CertPool attestationRootPool *x509.CertPool
ctl *Controller
ctl *Controller
} }
// GetID returns the provisioner unique identifier. // GetID returns the provisioner unique identifier.
@ -192,6 +192,29 @@ func (p *ACME) Init(config Config) (err error) {
} }
} }
// Parse attestation roots.
// The pool will be nil if the there are not roots.
if rest := p.AttestationRoots; len(rest) > 0 {
var block *pem.Block
var hasCert bool
p.attestationRootPool = x509.NewCertPool()
for rest != nil {
block, rest = pem.Decode(rest)
if block == nil {
break
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return errors.New("error parsing attestationRoots: malformed certificate")
}
p.attestationRootPool.AddCert(cert)
hasCert = true
}
if !hasCert {
return errors.New("error parsing attestationRoots: no certificates found")
}
}
p.ctl, err = NewController(p, p.Claims, config, p.Options) p.ctl, err = NewController(p, p.Claims, config, p.Options)
return return
} }
@ -291,12 +314,6 @@ func (p *ACME) IsChallengeEnabled(ctx context.Context, challenge ACMEChallenge)
return false return false
} }
// TODO(hs): we may not want to expose the root pool like this;
// call into an interface function instead to authorize?
func (p *ACME) GetAttestationRoots() (*x509.CertPool, error) {
return p.attestationRootPool, nil
}
// IsAttestationFormatEnabled checks if the given attestation format is enabled. // IsAttestationFormatEnabled checks if the given attestation format is enabled.
// By default apple, step and tpm are enabled, to disable any of them the // By default apple, step and tpm are enabled, to disable any of them the
// AttestationFormat provisioner property should have at least one element. // AttestationFormat provisioner property should have at least one element.
@ -314,3 +331,9 @@ func (p *ACME) IsAttestationFormatEnabled(ctx context.Context, format ACMEAttest
} }
return false return false
} }
// GetAttestationRoots returns certificate pool with the configured attestation
// roots and reports if the pool contains at least one certificate.
func (p *ACME) GetAttestationRoots() (*x509.CertPool, bool) {
return p.attestationRootPool, p.attestationRootPool != nil
}

@ -1,11 +1,13 @@
package provisioner package provisioner
import ( import (
"bytes"
"context" "context"
"crypto/x509" "crypto/x509"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"os"
"testing" "testing"
"time" "time"
@ -77,6 +79,15 @@ func TestACME_Getters(t *testing.T) {
} }
func TestACME_Init(t *testing.T) { func TestACME_Init(t *testing.T) {
appleCA, err := os.ReadFile("testdata/certs/apple-att-ca.crt")
if err != nil {
t.Fatal(err)
}
yubicoCA, err := os.ReadFile("testdata/certs/yubico-piv-ca.crt")
if err != nil {
t.Fatal(err)
}
type ProvisionerValidateTest struct { type ProvisionerValidateTest struct {
p *ACME p *ACME
err error err error
@ -120,6 +131,18 @@ func TestACME_Init(t *testing.T) {
err: errors.New("acme attestation format \"zar\" is not supported"), err: errors.New("acme attestation format \"zar\" is not supported"),
} }
}, },
"fail-parse-attestation-roots": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "bar", AttestationRoots: []byte("-----BEGIN CERTIFICATE-----\nZm9v\n-----END CERTIFICATE-----")},
err: errors.New("error parsing attestationRoots: malformed certificate"),
}
},
"fail-empty-attestation-roots": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "bar", AttestationRoots: []byte("\n")},
err: errors.New("error parsing attestationRoots: no certificates found"),
}
},
"ok": func(t *testing.T) ProvisionerValidateTest { "ok": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{ return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "bar"}, p: &ACME{Name: "foo", Type: "bar"},
@ -132,6 +155,7 @@ func TestACME_Init(t *testing.T) {
Type: "bar", Type: "bar",
Challenges: []ACMEChallenge{DNS_01, DEVICE_ATTEST_01}, Challenges: []ACMEChallenge{DNS_01, DEVICE_ATTEST_01},
AttestationFormats: []ACMEAttestationFormat{APPLE, STEP}, AttestationFormats: []ACMEAttestationFormat{APPLE, STEP},
AttestationRoots: bytes.Join([][]byte{appleCA, yubicoCA}, []byte("\n")),
}, },
} }
}, },
@ -144,6 +168,7 @@ func TestACME_Init(t *testing.T) {
for name, get := range tests { for name, get := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc := get(t) tc := get(t)
t.Log(string(tc.p.AttestationRoots))
err := tc.p.Init(config) err := tc.p.Init(config)
if err != nil { if err != nil {
if assert.NotNil(t, tc.err) { if assert.NotNil(t, tc.err) {
@ -346,3 +371,58 @@ func TestACME_IsAttestationFormatEnabled(t *testing.T) {
}) })
} }
} }
func TestACME_GetAttestationRoots(t *testing.T) {
appleCA, err := os.ReadFile("testdata/certs/apple-att-ca.crt")
if err != nil {
t.Fatal(err)
}
yubicoCA, err := os.ReadFile("testdata/certs/yubico-piv-ca.crt")
if err != nil {
t.Fatal(err)
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(appleCA)
pool.AppendCertsFromPEM(yubicoCA)
type fields struct {
Type string
Name string
AttestationRoots []byte
}
tests := []struct {
name string
fields fields
want *x509.CertPool
want1 bool
}{
{"ok", fields{"ACME", "acme", bytes.Join([][]byte{appleCA, yubicoCA}, []byte("\n"))}, pool, true},
{"nil", fields{"ACME", "acme", nil}, nil, false},
{"empty", fields{"ACME", "acme", []byte{}}, nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &ACME{
Type: tt.fields.Type,
Name: tt.fields.Name,
AttestationRoots: tt.fields.AttestationRoots,
}
if err := p.Init(Config{
Claims: globalProvisionerClaims,
Audiences: testAudiences,
}); err != nil {
t.Fatal(err)
}
got, got1 := p.GetAttestationRoots()
if tt.want == nil && got != nil {
t.Errorf("ACME.GetAttestationRoots() got = %v, want %v", got, tt.want)
} else if !tt.want.Equal(got) {
t.Errorf("ACME.GetAttestationRoots() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("ACME.GetAttestationRoots() got1 = %v, want %v", got1, tt.want1)
}
})
}
}

@ -77,6 +77,12 @@ func (fn CertificateEnforcerFunc) Enforce(cert *x509.Certificate) error {
return fn(cert) return fn(cert)
} }
// AttestationData is a SignOption used to pass attestation information to the
// sign methods.
type AttestationData struct {
PermanentIdentifier string
}
// emailOnlyIdentity is a CertificateRequestValidator that checks that the only // emailOnlyIdentity is a CertificateRequestValidator that checks that the only
// SAN provided is the given email address. // SAN provided is the given email address.
type emailOnlyIdentity string type emailOnlyIdentity string

@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICJDCCAamgAwIBAgIUQsDCuyxyfFxeq/bxpm8frF15hzcwCgYIKoZIzj0EAwMw
UTEtMCsGA1UEAwwkQXBwbGUgRW50ZXJwcmlzZSBBdHRlc3RhdGlvbiBSb290IENB
MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzAeFw0yMjAyMTYxOTAx
MjRaFw00NzAyMjAwMDAwMDBaMFExLTArBgNVBAMMJEFwcGxlIEVudGVycHJpc2Ug
QXR0ZXN0YXRpb24gUm9vdCBDQTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UE
BhMCVVMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT6Jigq+Ps9Q4CoT8t8q+UnOe2p
oT9nRaUfGhBTbgvqSGXPjVkbYlIWYO+1zPk2Sz9hQ5ozzmLrPmTBgEWRcHjA2/y7
7GEicps9wn2tj+G89l3INNDKETdxSPPIZpPj8VmjQjBAMA8GA1UdEwEB/wQFMAMB
Af8wHQYDVR0OBBYEFPNqTQGd8muBpV5du+UIbVbi+d66MA4GA1UdDwEB/wQEAwIB
BjAKBggqhkjOPQQDAwNpADBmAjEA1xpWmTLSpr1VH4f8Ypk8f3jMUKYz4QPG8mL5
8m9sX/b2+eXpTv2pH4RZgJjucnbcAjEA4ZSB6S45FlPuS/u4pTnzoz632rA+xW/T
ZwFEh9bhKjJ+5VQ9/Do1os0u3LEkgN/r
-----END CERTIFICATE-----

@ -0,0 +1,19 @@
-----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-----

@ -94,6 +94,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
var prov provisioner.Interface var prov provisioner.Interface
var pInfo *casapi.ProvisionerInfo var pInfo *casapi.ProvisionerInfo
var attData provisioner.AttestationData
for _, op := range extraOpts { for _, op := range extraOpts {
switch k := op.(type) { switch k := op.(type) {
// Capture current provisioner // Capture current provisioner
@ -129,6 +130,11 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
case provisioner.CertificateEnforcer: case provisioner.CertificateEnforcer:
certEnforcers = append(certEnforcers, k) certEnforcers = append(certEnforcers, k)
// Extra information from ACME attestations.
case provisioner.AttestationData:
attData = k
// TODO(mariano,areed): remove me once attData is used.
_ = attData
default: default:
return nil, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]interface{}{k}, opts...)...) return nil, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]interface{}{k}, opts...)...)
} }

@ -14,6 +14,7 @@ import (
"io" "io"
"net" "net"
"os" "os"
"path/filepath"
"reflect" "reflect"
"testing" "testing"
"time" "time"
@ -402,6 +403,14 @@ func TestNew_real(t *testing.T) {
}) })
} }
failDefaultCredentials := true
if home, err := os.UserHomeDir(); err == nil {
file := filepath.Join(home, ".config", "gcloud", "application_default_credentials.json")
if _, err := os.Stat(file); err == nil {
failDefaultCredentials = false
}
}
type args struct { type args struct {
ctx context.Context ctx context.Context
opts apiv1.Options opts apiv1.Options
@ -412,7 +421,7 @@ func TestNew_real(t *testing.T) {
args args args args
wantErr bool wantErr bool
}{ }{
{"fail default credentials", true, args{context.Background(), apiv1.Options{CertificateAuthority: testAuthorityName}}, true}, {"fail default credentials", true, args{context.Background(), apiv1.Options{CertificateAuthority: testAuthorityName}}, failDefaultCredentials},
{"fail certificate authority", false, args{context.Background(), apiv1.Options{}}, true}, {"fail certificate authority", false, args{context.Background(), apiv1.Options{}}, true},
{"fail with credentials", false, args{context.Background(), apiv1.Options{ {"fail with credentials", false, args{context.Background(), apiv1.Options{
CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/missing.json", CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/missing.json",

@ -18,6 +18,7 @@ require (
github.com/go-piv/piv-go v1.10.0 // indirect github.com/go-piv/piv-go v1.10.0 // indirect
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.5.8 github.com/google/go-cmp v0.5.8
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.4.0 github.com/googleapis/gax-go/v2 v2.4.0
github.com/hashicorp/vault/api v1.3.1 github.com/hashicorp/vault/api v1.3.1
@ -31,6 +32,7 @@ require (
github.com/newrelic/go-agent/v3 v3.18.0 github.com/newrelic/go-agent/v3 v3.18.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rs/xid v1.2.1 github.com/rs/xid v1.2.1
github.com/ryboe/q v1.0.17
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/slackhq/nebula v1.5.2 github.com/slackhq/nebula v1.5.2
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262
@ -40,7 +42,7 @@ require (
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.4 go.step.sm/cli-utils v0.7.4
go.step.sm/crypto v0.19.0 go.step.sm/crypto v0.19.0
go.step.sm/linkedca v0.18.1-0.20220909212924-c69cf68797cb go.step.sm/linkedca v0.19.0-rc.1
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/net v0.0.0-20220607020251-c690dde0001d golang.org/x/net v0.0.0-20220607020251-c690dde0001d
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
@ -51,11 +53,6 @@ require (
gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/square/go-jose.v2 v2.6.0
) )
require (
github.com/google/go-tpm v0.3.3
github.com/ryboe/q v1.0.17
)
require ( require (
cloud.google.com/go/compute v1.6.1 // indirect cloud.google.com/go/compute v1.6.1 // indirect
cloud.google.com/go/iam v0.1.0 // indirect cloud.google.com/go/iam v0.1.0 // indirect

@ -675,8 +675,8 @@ go.step.sm/cli-utils v0.7.4/go.mod h1:taSsY8haLmXoXM3ZkywIyRmVij/4Aj0fQbNTlJvv71
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
go.step.sm/crypto v0.19.0 h1:WxjUDeTDpuPZ1IR3v6c4jc6WdlQlS5IYYQBhfnG5uW0= go.step.sm/crypto v0.19.0 h1:WxjUDeTDpuPZ1IR3v6c4jc6WdlQlS5IYYQBhfnG5uW0=
go.step.sm/crypto v0.19.0/go.mod h1:qZ+pNU1nV+THwP7TPTNCRMRr9xrRURhETTAK7U5psfw= go.step.sm/crypto v0.19.0/go.mod h1:qZ+pNU1nV+THwP7TPTNCRMRr9xrRURhETTAK7U5psfw=
go.step.sm/linkedca v0.18.1-0.20220909212924-c69cf68797cb h1:qCG7i7PAZcTDLqyFmOzBBl5tfyHI033U5jONS9DuN+8= go.step.sm/linkedca v0.19.0-rc.1 h1:8XcQvanelK1g0ijl5/itmmAIsqD2QSMHGqcWzJwwJCU=
go.step.sm/linkedca v0.18.1-0.20220909212924-c69cf68797cb/go.mod h1:qSuYlIIhvPmA2+DSSS03E2IXhbXWTLW61Xh9zDQJ3VM= go.step.sm/linkedca v0.19.0-rc.1/go.mod h1:G35baT7Qnh6VsRCjzSfi5xsYw0ERrU+I1aIuZswMBeA=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=

Loading…
Cancel
Save