Merge pull request #1265 from smallstep/check-csr-acme-da

Verify CSR key fingerprint with attestation certificate key
This commit is contained in:
Mariano Cano 2023-02-14 09:34:48 -08:00 committed by GitHub
commit c2c246b062
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 793 additions and 450 deletions

View file

@ -11,6 +11,7 @@ type Authorization struct {
ID string `json:"-"` ID string `json:"-"`
AccountID string `json:"-"` AccountID string `json:"-"`
Token string `json:"-"` Token string `json:"-"`
Fingerprint string `json:"-"`
Identifier Identifier `json:"identifier"` Identifier Identifier `json:"identifier"`
Status Status `json:"status"` Status Status `json:"status"`
Challenges []*Challenge `json:"challenges"` Challenges []*Challenge `json:"challenges"`

View file

@ -27,6 +27,7 @@ import (
"github.com/fxamacker/cbor/v2" "github.com/fxamacker/cbor/v2"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
@ -347,6 +348,13 @@ type attestationObject struct {
// TODO(bweeks): move attestation verification to a shared package. // TODO(bweeks): move attestation verification to a shared package.
func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, payload []byte) error { func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, payload []byte) error {
// Load authorization to store the key fingerprint.
az, err := db.GetAuthorization(ctx, ch.AuthorizationID)
if err != nil {
return WrapErrorISE(err, "error loading authorization")
}
// Parse payload.
var p payloadType var p payloadType
if err := json.Unmarshal(payload, &p); err != nil { if err := json.Unmarshal(payload, &p); err != nil {
return WrapErrorISE(err, "error unmarshalling JSON") return WrapErrorISE(err, "error unmarshalling JSON")
@ -385,7 +393,6 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
} }
return WrapErrorISE(err, "error validating attestation") return WrapErrorISE(err, "error validating attestation")
} }
// Validate nonce with SHA-256 of the token. // Validate nonce with SHA-256 of the token.
if len(data.Nonce) != 0 { if len(data.Nonce) != 0 {
sum := sha256.Sum256([]byte(ch.Token)) sum := sha256.Sum256([]byte(ch.Token))
@ -401,6 +408,9 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
if data.UDID != ch.Value && data.SerialNumber != ch.Value { if data.UDID != ch.Value && data.SerialNumber != ch.Value {
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "permanent identifier does not match")) return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "permanent identifier does not match"))
} }
// Update attestation key fingerprint to compare against the CSR
az.Fingerprint = data.Fingerprint
case "step": case "step":
data, err := doStepAttestationFormat(ctx, prov, ch, jwk, &att) data, err := doStepAttestationFormat(ctx, prov, ch, jwk, &att)
if err != nil { if err != nil {
@ -426,6 +436,9 @@ 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
default: default:
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "unexpected attestation object format")) return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "unexpected attestation object format"))
} }
@ -435,6 +448,15 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
ch.Error = nil ch.Error = nil
ch.ValidatedAt = clock.Now().Format(time.RFC3339) ch.ValidatedAt = clock.Now().Format(time.RFC3339)
// Store the fingerprint in the authorization.
//
// TODO: add method to update authorization and challenge atomically.
if az.Fingerprint != "" {
if err := db.UpdateAuthorization(ctx, az); err != nil {
return WrapErrorISE(err, "error updating authorization")
}
}
if err := db.UpdateChallenge(ctx, ch); err != nil { if err := db.UpdateChallenge(ctx, ch); err != nil {
return WrapErrorISE(err, "error updating challenge") return WrapErrorISE(err, "error updating challenge")
} }
@ -471,6 +493,7 @@ type appleAttestationData struct {
UDID string UDID string
SEPVersion string SEPVersion string
Certificate *x509.Certificate Certificate *x509.Certificate
Fingerprint string
} }
func doAppleAttestationFormat(ctx context.Context, prov Provisioner, ch *Challenge, att *attestationObject) (*appleAttestationData, error) { func doAppleAttestationFormat(ctx context.Context, prov Provisioner, ch *Challenge, att *attestationObject) (*appleAttestationData, error) {
@ -527,6 +550,9 @@ func doAppleAttestationFormat(ctx context.Context, prov Provisioner, ch *Challen
data := &appleAttestationData{ data := &appleAttestationData{
Certificate: leaf, Certificate: leaf,
} }
if data.Fingerprint, err = keyutil.Fingerprint(leaf.PublicKey); err != nil {
return nil, WrapErrorISE(err, "error calculating key fingerprint")
}
for _, ext := range leaf.Extensions { for _, ext := range leaf.Extensions {
switch { switch {
case ext.Id.Equal(oidAppleSerialNumber): case ext.Id.Equal(oidAppleSerialNumber):
@ -572,6 +598,7 @@ var oidYubicoSerialNumber = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 41482, 3, 7}
type stepAttestationData struct { type stepAttestationData struct {
Certificate *x509.Certificate Certificate *x509.Certificate
SerialNumber string SerialNumber string
Fingerprint string
} }
func doStepAttestationFormat(ctx context.Context, prov Provisioner, 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) {
@ -667,6 +694,9 @@ func doStepAttestationFormat(ctx context.Context, prov Provisioner, ch *Challeng
data := &stepAttestationData{ data := &stepAttestationData{
Certificate: leaf, Certificate: leaf,
} }
if data.Fingerprint, err = keyutil.Fingerprint(leaf.PublicKey); err != nil {
return nil, WrapErrorISE(err, "error calculating key fingerprint")
}
for _, ext := range leaf.Extensions { for _, ext := range leaf.Extensions {
if !ext.Id.Equal(oidYubicoSerialNumber) { if !ext.Id.Equal(oidYubicoSerialNumber) {
continue continue

View file

@ -54,6 +54,13 @@ func (m *mockClient) TLSDial(network, addr string, tlsConfig *tls.Config) (*tls.
return m.tlsDial(network, addr, tlsConfig) return m.tlsDial(network, addr, tlsConfig)
} }
func fatalError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatal(err)
}
}
func mustNonAttestationProvisioner(t *testing.T) Provisioner { func mustNonAttestationProvisioner(t *testing.T) Provisioner {
t.Helper() t.Helper()
@ -88,6 +95,108 @@ func mustAttestationProvisioner(t *testing.T, roots []byte) Provisioner {
return prov return prov
} }
func mustAccountAndKeyAuthorization(t *testing.T, token string) (*jose.JSONWebKey, string) {
t.Helper()
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
fatalError(t, err)
keyAuth, err := KeyAuthorization(token, jwk)
fatalError(t, err)
return jwk, keyAuth
}
func mustAttestApple(t *testing.T, nonce string) ([]byte, *x509.Certificate, *x509.Certificate) {
t.Helper()
ca, err := minica.New()
fatalError(t, err)
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
fatalError(t, err)
nonceSum := sha256.Sum256([]byte(nonce))
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: nonceSum[:]},
},
})
fatalError(t, err)
attObj, err := cbor.Marshal(struct {
Format string `json:"fmt"`
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
}{
Format: "apple",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
},
})
fatalError(t, err)
payload, err := json.Marshal(struct {
AttObj string `json:"attObj"`
}{
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
})
fatalError(t, err)
return payload, leaf, ca.Root
}
func mustAttestYubikey(t *testing.T, nonce, keyAuthorization string, serial int) ([]byte, *x509.Certificate, *x509.Certificate) {
ca, err := minica.New()
fatalError(t, err)
keyAuthSum := sha256.Sum256([]byte(keyAuthorization))
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
fatalError(t, err)
sig, err := signer.Sign(rand.Reader, keyAuthSum[:], crypto.SHA256)
fatalError(t, err)
cborSig, err := cbor.Marshal(sig)
fatalError(t, err)
serialNumber, err := asn1.Marshal(serial)
fatalError(t, err)
leaf, err := ca.Sign(&x509.Certificate{
Subject: pkix.Name{CommonName: "attestation cert"},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{
{Id: oidYubicoSerialNumber, Value: serialNumber},
},
})
fatalError(t, err)
attObj, err := cbor.Marshal(struct {
Format string `json:"fmt"`
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
}{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
})
fatalError(t, err)
payload, err := json.Marshal(struct {
AttObj string `json:"attObj"`
}{
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
})
fatalError(t, err)
return payload, leaf, ca.Root
}
func Test_storeError(t *testing.T) { func Test_storeError(t *testing.T) {
type test struct { type test struct {
ch *Challenge ch *Challenge
@ -661,13 +770,6 @@ func TestChallenge_Validate(t *testing.T) {
} }
}, },
"fail/device-attest-01": func(t *testing.T) test { "fail/device-attest-01": func(t *testing.T) test {
ch := &Challenge{
ID: "chID",
Token: "token",
Type: "device-attest-01",
Status: StatusPending,
Value: "12345678",
}
payload, err := json.Marshal(struct { payload, err := json.Marshal(struct {
Error string `json:"error"` Error string `json:"error"`
}{ }{
@ -675,9 +777,20 @@ func TestChallenge_Validate(t *testing.T) {
}) })
assert.NoError(t, err) assert.NoError(t, err)
return test{ return test{
ch: ch, ch: &Challenge{
ID: "chID",
AuthorizationID: "azID",
Token: "token",
Type: "device-attest-01",
Status: StatusPending,
Value: "12345678",
},
payload: payload, payload: payload,
db: &MockDB{ 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 { MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID) assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token) assert.Equal(t, "token", updch.Token)
@ -699,76 +812,39 @@ func TestChallenge_Validate(t *testing.T) {
} }
}, },
"ok/device-attest-01": func(t *testing.T) test { "ok/device-attest-01": func(t *testing.T) test {
ctx := context.Background() jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
ca, err := minica.New() payload, leaf, root := mustAttestYubikey(t, "nonce", keyAuth, 1234)
assert.NoError(t, err)
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Root.Raw})
ctx = NewProvisionerContext(ctx, mustAttestationProvisioner(t, caRoot))
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
}
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
assert.NoError(t, err) ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
serialNumber, err := asn1.Marshal(1234)
assert.NoError(t, err)
leaf := makeLeaf(signer, serialNumber)
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) return test{
assert.NoError(t, err) ch: &Challenge{
token := "token"
keyAuth, err := KeyAuthorization(token, jwk)
assert.NoError(t, err)
keyAuthSum := sha256.Sum256([]byte(keyAuth))
sig, err := signer.Sign(rand.Reader, keyAuthSum[:], crypto.SHA256)
assert.NoError(t, err)
cborSig, err := cbor.Marshal(sig)
assert.NoError(t, err)
ch := &Challenge{
ID: "chID", ID: "chID",
Token: token, AuthorizationID: "azID",
Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
Value: "1234", Value: "1234",
}
attObj, err := cbor.Marshal(struct {
Format string `json:"fmt"`
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
}{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
}, },
})
assert.NoError(t, err)
payload, err := json.Marshal(struct {
AttObj string `json:"attObj"`
}{
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
})
assert.NoError(t, err)
return test{
ch: ch,
payload: payload, payload: payload,
ctx: ctx, ctx: ctx,
jwk: jwk, jwk: jwk,
db: &MockDB{ 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(leaf.PublicKey)
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 { MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID) assert.Equal(t, "chID", updch.ID)
assert.Equal(t, token, updch.Token) assert.Equal(t, "token", updch.Token)
assert.Equal(t, StatusValid, updch.Status) assert.Equal(t, StatusValid, updch.Status)
assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type)
assert.Equal(t, "1234", updch.Value) assert.Equal(t, "1234", updch.Value)
@ -2745,6 +2821,10 @@ func Test_doAppleAttestationFormat(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
fingerprint, err := keyutil.Fingerprint(signer.Public())
if err != nil {
t.Fatal(err)
}
type args struct { type args struct {
ctx context.Context ctx context.Context
@ -2769,6 +2849,7 @@ func Test_doAppleAttestationFormat(t *testing.T) {
UDID: "udid", UDID: "udid",
SEPVersion: "16.0", SEPVersion: "16.0",
Certificate: leaf, Certificate: leaf,
Fingerprint: fingerprint,
}, false}, }, false},
{"fail apple issuer", args{ctx, mustAttestationProvisioner(t, nil), &Challenge{}, &attestationObject{ {"fail apple issuer", args{ctx, mustAttestationProvisioner(t, nil), &Challenge{}, &attestationObject{
Format: "apple", Format: "apple",
@ -2871,6 +2952,10 @@ func Test_doStepAttestationFormat(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
leaf := makeLeaf(signer, serialNumber) leaf := makeLeaf(signer, serialNumber)
fingerprint, err := keyutil.Fingerprint(signer.Public())
if err != nil {
t.Fatal(err)
}
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
if err != nil { if err != nil {
@ -2926,6 +3011,7 @@ func Test_doStepAttestationFormat(t *testing.T) {
}}, &stepAttestationData{ }}, &stepAttestationData{
SerialNumber: "1234", SerialNumber: "1234",
Certificate: leaf, Certificate: leaf,
Fingerprint: fingerprint,
}, false}, }, false},
{"fail yubico issuer", args{ctx, mustAttestationProvisioner(t, nil), &Challenge{Token: "token"}, jwk, &attestationObject{ {"fail yubico issuer", args{ctx, mustAttestationProvisioner(t, nil), &Challenge{Token: "token"}, jwk, &attestationObject{
Format: "step", Format: "step",
@ -3196,16 +3282,44 @@ func Test_deviceAttest01Validate(t *testing.T) {
wantErr *Error wantErr *Error
} }
tests := map[string]func(t *testing.T) test{ tests := map[string]func(t *testing.T) test{
"fail/getAuthorization": func(t *testing.T) test {
return test{
args: args{
ch: &Challenge{
ID: "chID",
AuthorizationID: "azID",
Token: "token",
Type: "device-attest-01",
Status: StatusPending,
Value: "12345678",
},
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return nil, errors.New("not found")
},
},
payload: []byte(invalidPayload),
},
wantErr: NewErrorISE("error loading authorization: not found"),
}
},
"fail/json.Unmarshal": func(t *testing.T) test { "fail/json.Unmarshal": func(t *testing.T) test {
return test{ return test{
args: args{ args: args{
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "token", Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
Value: "12345678", Value: "12345678",
}, },
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
assert.Equal(t, "azID", id)
return &Authorization{ID: "azID"}, nil
},
},
payload: []byte(invalidPayload), payload: []byte(invalidPayload),
}, },
wantErr: NewErrorISE("error unmarshalling JSON: invalid character '!' looking for beginning of value"), wantErr: NewErrorISE("error unmarshalling JSON: invalid character '!' looking for beginning of value"),
@ -3217,6 +3331,7 @@ func Test_deviceAttest01Validate(t *testing.T) {
args: args{ args: args{
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "token", Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
@ -3224,6 +3339,10 @@ func Test_deviceAttest01Validate(t *testing.T) {
}, },
payload: errorPayload, payload: errorPayload,
db: &MockDB{ 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 { MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID) assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token) assert.Equal(t, "token", updch.Token)
@ -3251,6 +3370,7 @@ func Test_deviceAttest01Validate(t *testing.T) {
args: args{ args: args{
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "token", Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
@ -3258,6 +3378,10 @@ func Test_deviceAttest01Validate(t *testing.T) {
}, },
payload: errorPayload, payload: errorPayload,
db: &MockDB{ 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 { MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID) assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token) assert.Equal(t, "token", updch.Token)
@ -3285,11 +3409,18 @@ func Test_deviceAttest01Validate(t *testing.T) {
args: args{ args: args{
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "token", Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
Value: "12345678", Value: "12345678",
}, },
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
assert.Equal(t, "azID", id)
return &Authorization{ID: "azID"}, nil
},
},
payload: errorBase64Payload, payload: errorBase64Payload,
}, },
wantErr: NewErrorISE("error base64 decoding attObj: illegal base64 data at input byte 0"), wantErr: NewErrorISE("error base64 decoding attObj: illegal base64 data at input byte 0"),
@ -3300,71 +3431,35 @@ func Test_deviceAttest01Validate(t *testing.T) {
args: args{ args: args{
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "token", Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
Value: "12345678", Value: "12345678",
}, },
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
assert.Equal(t, "azID", id)
return &Authorization{ID: "azID"}, nil
},
},
payload: errorCBORPayload, payload: errorCBORPayload,
}, },
wantErr: NewErrorISE("error unmarshalling CBOR: cbor: cannot unmarshal positive integer into Go value of type acme.attestationObject"), wantErr: NewErrorISE("error unmarshalling CBOR: cbor: cannot unmarshal positive integer into Go value of type acme.attestationObject"),
} }
}, },
"ok/prov.IsAttestationFormatEnabled": func(t *testing.T) test { "ok/prov.IsAttestationFormatEnabled": func(t *testing.T) test {
ca, err := minica.New() jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
require.NoError(t, err) payload, _, _ := mustAttestYubikey(t, "nonce", keyAuth, 12345678)
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
}
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
serialNumber, err := asn1.Marshal(1234)
require.NoError(t, err)
leaf := makeLeaf(signer, serialNumber)
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
require.NoError(t, err)
token := "token"
keyAuth, err := KeyAuthorization(token, jwk)
require.NoError(t, err)
keyAuthSum := sha256.Sum256([]byte(keyAuth))
sig, err := signer.Sign(rand.Reader, keyAuthSum[:], crypto.SHA256)
require.NoError(t, err)
cborSig, err := cbor.Marshal(sig)
require.NoError(t, err)
ctx := NewProvisionerContext(context.Background(), mustNonAttestationProvisioner(t)) ctx := NewProvisionerContext(context.Background(), mustNonAttestationProvisioner(t))
attObj, err := cbor.Marshal(struct {
Format string `json:"fmt"`
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
}{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
})
require.NoError(t, err)
payload, err := json.Marshal(struct {
AttObj string `json:"attObj"`
}{
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
})
require.NoError(t, err)
return test{ return test{
args: args{ args: args{
ctx: ctx, ctx: ctx,
jwk: jwk,
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "token", Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
@ -3372,6 +3467,10 @@ func Test_deviceAttest01Validate(t *testing.T) {
}, },
payload: payload, payload: payload,
db: &MockDB{ 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 { MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID) assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token) assert.Equal(t, "token", updch.Token)
@ -3415,6 +3514,7 @@ func Test_deviceAttest01Validate(t *testing.T) {
ctx: ctx, ctx: ctx,
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "token", Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
@ -3422,6 +3522,10 @@ func Test_deviceAttest01Validate(t *testing.T) {
}, },
payload: payload, payload: payload,
db: &MockDB{ 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 { MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID) assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token) assert.Equal(t, "token", updch.Token)
@ -3445,57 +3549,36 @@ func Test_deviceAttest01Validate(t *testing.T) {
} }
}, },
"ok/doAppleAttestationFormat-non-matching-nonce": func(t *testing.T) test { "ok/doAppleAttestationFormat-non-matching-nonce": func(t *testing.T) test {
ca, err := minica.New() jwk, _ := mustAccountAndKeyAuthorization(t, "token")
require.NoError(t, err) payload, _, root := mustAttestApple(t, "bad-nonce")
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err) caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Root.Raw})
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")},
},
})
require.NoError(t, err)
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot)) ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
attObj, err := cbor.Marshal(struct {
Format string `json:"fmt"`
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
}{
Format: "apple",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
},
})
require.NoError(t, err)
payload, err := json.Marshal(struct {
AttObj string `json:"attObj"`
}{
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
})
require.NoError(t, err)
return test{ return test{
args: args{ args: args{
ctx: ctx, ctx: ctx,
jwk: jwk,
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "token", Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
Value: "12345678", Value: "serial-number",
}, },
payload: payload, payload: payload,
db: &MockDB{ 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 { MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID) assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token) assert.Equal(t, "token", updch.Token)
assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, StatusInvalid, updch.Status)
assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type)
assert.Equal(t, "12345678", updch.Value) assert.Equal(t, "serial-number", updch.Value)
err := NewError(ErrorBadAttestationStatementType, "challenge token does not match") err := NewError(ErrorBadAttestationStatementType, "challenge token does not match")
@ -3513,45 +3596,18 @@ func Test_deviceAttest01Validate(t *testing.T) {
} }
}, },
"ok/doAppleAttestationFormat-non-matching-challenge-value": func(t *testing.T) test { "ok/doAppleAttestationFormat-non-matching-challenge-value": func(t *testing.T) test {
ca, err := minica.New() jwk, _ := mustAccountAndKeyAuthorization(t, "token")
require.NoError(t, err) payload, _, root := mustAttestApple(t, "nonce")
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err) caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Root.Raw})
nonce := sha256.Sum256([]byte("nonce"))
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: nonce[:]},
},
})
require.NoError(t, err)
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot)) ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
attObj, err := cbor.Marshal(struct {
Format string `json:"fmt"`
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
}{
Format: "apple",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
},
})
require.NoError(t, err)
payload, err := json.Marshal(struct {
AttObj string `json:"attObj"`
}{
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
})
require.NoError(t, err)
return test{ return test{
args: args{ args: args{
ctx: ctx, ctx: ctx,
jwk: jwk,
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "nonce", Token: "nonce",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
@ -3559,6 +3615,10 @@ func Test_deviceAttest01Validate(t *testing.T) {
}, },
payload: payload, payload: payload,
db: &MockDB{ 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 { MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID) assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "nonce", updch.Token) assert.Equal(t, "nonce", updch.Token)
@ -3620,6 +3680,7 @@ func Test_deviceAttest01Validate(t *testing.T) {
ctx: ctx, ctx: ctx,
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "token", Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
@ -3627,6 +3688,10 @@ func Test_deviceAttest01Validate(t *testing.T) {
}, },
payload: payload, payload: payload,
db: &MockDB{ 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 { MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID) assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token) assert.Equal(t, "token", updch.Token)
@ -3650,62 +3715,19 @@ func Test_deviceAttest01Validate(t *testing.T) {
} }
}, },
"ok/doStepAttestationFormat-non-matching-identifier": func(t *testing.T) test { "ok/doStepAttestationFormat-non-matching-identifier": func(t *testing.T) test {
ca, err := minica.New() jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
require.NoError(t, err) payload, leaf, root := mustAttestYubikey(t, "nonce", keyAuth, 87654321)
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Root.Raw})
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
require.NoError(t, err)
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
require.NoError(t, err)
token := "token"
keyAuth, err := KeyAuthorization(token, jwk)
require.NoError(t, err)
keyAuthSum := sha256.Sum256([]byte(keyAuth))
sig, err := signer.Sign(rand.Reader, keyAuthSum[:], crypto.SHA256)
require.NoError(t, err)
cborSig, err := cbor.Marshal(sig)
require.NoError(t, err)
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot)) ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
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
}
require.NoError(t, err)
serialNumber, err := asn1.Marshal(87654321)
require.NoError(t, err)
leaf := makeLeaf(signer, serialNumber)
attObj, err := cbor.Marshal(struct {
Format string `json:"fmt"`
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
}{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
})
require.NoError(t, err)
payload, err := json.Marshal(struct {
AttObj string `json:"attObj"`
}{
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
})
require.NoError(t, err)
return test{ return test{
args: args{ args: args{
ctx: ctx, ctx: ctx,
jwk: jwk,
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "token", Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
@ -3713,6 +3735,17 @@ func Test_deviceAttest01Validate(t *testing.T) {
}, },
payload: payload, payload: payload,
db: &MockDB{ 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(leaf.PublicKey)
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 { MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID) assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token) assert.Equal(t, "token", updch.Token)
@ -3736,7 +3769,6 @@ func Test_deviceAttest01Validate(t *testing.T) {
return nil return nil
}, },
}, },
jwk: jwk,
}, },
wantErr: nil, wantErr: nil,
} }
@ -3797,6 +3829,7 @@ func Test_deviceAttest01Validate(t *testing.T) {
ctx: ctx, ctx: ctx,
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "token", Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
@ -3804,6 +3837,10 @@ func Test_deviceAttest01Validate(t *testing.T) {
}, },
payload: payload, payload: payload,
db: &MockDB{ 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 { MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID) assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token) assert.Equal(t, "token", updch.Token)
@ -3827,63 +3864,20 @@ func Test_deviceAttest01Validate(t *testing.T) {
wantErr: nil, wantErr: nil,
} }
}, },
"fail/db.UpdateChallenge": func(t *testing.T) test { "fail/db.UpdateAuthorization": func(t *testing.T) test {
ca, err := minica.New() jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
require.NoError(t, err) payload, leaf, root := mustAttestYubikey(t, "nonce", keyAuth, 12345678)
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Root.Raw})
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
require.NoError(t, err)
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
require.NoError(t, err)
token := "token"
keyAuth, err := KeyAuthorization(token, jwk)
require.NoError(t, err)
keyAuthSum := sha256.Sum256([]byte(keyAuth))
sig, err := signer.Sign(rand.Reader, keyAuthSum[:], crypto.SHA256)
require.NoError(t, err)
cborSig, err := cbor.Marshal(sig)
require.NoError(t, err)
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot)) ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
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
}
require.NoError(t, err)
serialNumber, err := asn1.Marshal(12345678)
require.NoError(t, err)
leaf := makeLeaf(signer, serialNumber)
attObj, err := cbor.Marshal(struct {
Format string `json:"fmt"`
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
}{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
})
require.NoError(t, err)
payload, err := json.Marshal(struct {
AttObj string `json:"attObj"`
}{
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
})
require.NoError(t, err)
return test{ return test{
args: args{ args: args{
ctx: ctx, ctx: ctx,
jwk: jwk,
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "token", Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
@ -3891,6 +3885,54 @@ func Test_deviceAttest01Validate(t *testing.T) {
}, },
payload: payload, payload: payload,
db: &MockDB{ 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(leaf.PublicKey)
assert.NoError(t, err)
assert.Equal(t, "azID", az.ID)
assert.Equal(t, fingerprint, az.Fingerprint)
return errors.New("force")
},
},
},
wantErr: NewError(ErrorServerInternalType, "error updating authorization: force"),
}
},
"fail/db.UpdateChallenge": func(t *testing.T) test {
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
payload, leaf, root := mustAttestYubikey(t, "nonce", keyAuth, 12345678)
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: "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(leaf.PublicKey)
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 { MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID) assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token) assert.Equal(t, "token", updch.Token)
@ -3901,68 +3943,24 @@ func Test_deviceAttest01Validate(t *testing.T) {
return errors.New("force") return errors.New("force")
}, },
}, },
jwk: jwk,
}, },
wantErr: NewError(ErrorServerInternalType, "error updating challenge: force"), wantErr: NewError(ErrorServerInternalType, "error updating challenge: force"),
} }
}, },
"ok": func(t *testing.T) test { "ok": func(t *testing.T) test {
ca, err := minica.New() jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
require.NoError(t, err) payload, leaf, root := mustAttestYubikey(t, "nonce", keyAuth, 12345678)
caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Root.Raw})
signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw})
require.NoError(t, err)
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
require.NoError(t, err)
token := "token"
keyAuth, err := KeyAuthorization(token, jwk)
require.NoError(t, err)
keyAuthSum := sha256.Sum256([]byte(keyAuth))
sig, err := signer.Sign(rand.Reader, keyAuthSum[:], crypto.SHA256)
require.NoError(t, err)
cborSig, err := cbor.Marshal(sig)
require.NoError(t, err)
ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot)) ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot))
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
}
require.NoError(t, err)
serialNumber, err := asn1.Marshal(12345678)
require.NoError(t, err)
leaf := makeLeaf(signer, serialNumber)
attObj, err := cbor.Marshal(struct {
Format string `json:"fmt"`
AttStatement map[string]interface{} `json:"attStmt,omitempty"`
}{
Format: "step",
AttStatement: map[string]interface{}{
"x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw},
"alg": -7,
"sig": cborSig,
},
})
require.NoError(t, err)
payload, err := json.Marshal(struct {
AttObj string `json:"attObj"`
}{
AttObj: base64.RawURLEncoding.EncodeToString(attObj),
})
require.NoError(t, err)
return test{ return test{
args: args{ args: args{
ctx: ctx, ctx: ctx,
jwk: jwk,
ch: &Challenge{ ch: &Challenge{
ID: "chID", ID: "chID",
AuthorizationID: "azID",
Token: "token", Token: "token",
Type: "device-attest-01", Type: "device-attest-01",
Status: StatusPending, Status: StatusPending,
@ -3970,6 +3968,17 @@ func Test_deviceAttest01Validate(t *testing.T) {
}, },
payload: payload, payload: payload,
db: &MockDB{ 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(leaf.PublicKey)
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 { MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID) assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token) assert.Equal(t, "token", updch.Token)
@ -3980,7 +3989,6 @@ func Test_deviceAttest01Validate(t *testing.T) {
return nil return nil
}, },
}, },
jwk: jwk,
}, },
wantErr: nil, wantErr: nil,
} }

View file

@ -17,6 +17,7 @@ type dbAuthz struct {
Identifier acme.Identifier `json:"identifier"` Identifier acme.Identifier `json:"identifier"`
Status acme.Status `json:"status"` Status acme.Status `json:"status"`
Token string `json:"token"` Token string `json:"token"`
Fingerprint string `json:"fingerprint,omitempty"`
ChallengeIDs []string `json:"challengeIDs"` ChallengeIDs []string `json:"challengeIDs"`
Wildcard bool `json:"wildcard"` Wildcard bool `json:"wildcard"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
@ -69,6 +70,7 @@ func (db *DB) GetAuthorization(ctx context.Context, id string) (*acme.Authorizat
Wildcard: dbaz.Wildcard, Wildcard: dbaz.Wildcard,
ExpiresAt: dbaz.ExpiresAt, ExpiresAt: dbaz.ExpiresAt,
Token: dbaz.Token, Token: dbaz.Token,
Fingerprint: dbaz.Fingerprint,
Error: dbaz.Error, Error: dbaz.Error,
}, nil }, nil
} }
@ -97,6 +99,7 @@ func (db *DB) CreateAuthorization(ctx context.Context, az *acme.Authorization) e
Identifier: az.Identifier, Identifier: az.Identifier,
ChallengeIDs: chIDs, ChallengeIDs: chIDs,
Token: az.Token, Token: az.Token,
Fingerprint: az.Fingerprint,
Wildcard: az.Wildcard, Wildcard: az.Wildcard,
} }
@ -111,8 +114,8 @@ func (db *DB) UpdateAuthorization(ctx context.Context, az *acme.Authorization) e
} }
nu := old.clone() nu := old.clone()
nu.Status = az.Status nu.Status = az.Status
nu.Fingerprint = az.Fingerprint
nu.Error = az.Error nu.Error = az.Error
return db.save(ctx, old.ID, nu, old, "authz", authzTable) return db.save(ctx, old.ID, nu, old, "authz", authzTable)
} }
@ -144,6 +147,7 @@ func (db *DB) GetAuthorizationsByAccountID(ctx context.Context, accountID string
Wildcard: dbaz.Wildcard, Wildcard: dbaz.Wildcard,
ExpiresAt: dbaz.ExpiresAt, ExpiresAt: dbaz.ExpiresAt,
Token: dbaz.Token, Token: dbaz.Token,
Fingerprint: dbaz.Fingerprint,
Error: dbaz.Error, Error: dbaz.Error,
}) })
} }

View file

@ -473,6 +473,7 @@ func TestDB_UpdateAuthorization(t *testing.T) {
ExpiresAt: now.Add(5 * time.Minute), ExpiresAt: now.Add(5 * time.Minute),
ChallengeIDs: []string{"foo", "bar"}, ChallengeIDs: []string{"foo", "bar"},
Wildcard: true, Wildcard: true,
Fingerprint: "fingerprint",
} }
b, err := json.Marshal(dbaz) b, err := json.Marshal(dbaz)
assert.FatalError(t, err) assert.FatalError(t, err)
@ -552,6 +553,7 @@ func TestDB_UpdateAuthorization(t *testing.T) {
Token: dbaz.Token, Token: dbaz.Token,
Wildcard: dbaz.Wildcard, Wildcard: dbaz.Wildcard,
ExpiresAt: dbaz.ExpiresAt, ExpiresAt: dbaz.ExpiresAt,
Fingerprint: "fingerprint",
Error: acme.NewError(acme.ErrorMalformedType, "malformed"), Error: acme.NewError(acme.ErrorMalformedType, "malformed"),
} }
return test{ return test{
@ -582,6 +584,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.Fingerprint, dbaz.Fingerprint)
assert.Equals(t, dbNew.Error.Error(), acme.NewError(acme.ErrorMalformedType, "The request message was 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
}, },

View file

@ -3,6 +3,7 @@ package acme
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/subtle"
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"net" "net"
@ -11,6 +12,7 @@ import (
"time" "time"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
) )
@ -125,6 +127,27 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
return nil return nil
} }
// getKeyFingerprint returns a fingerprint from the list of authorizations. This
// fingerprint is used on the device-attest-01 flow to verify the attestation
// certificate public key with the CSR public key.
//
// There's no point on reading all the authorizations as there will be only one
// for a permanent identifier.
func (o *Order) getAuthorizationFingerprint(ctx context.Context, db DB) (string, error) {
for _, azID := range o.AuthorizationIDs {
az, err := db.GetAuthorization(ctx, azID)
if err != nil {
return "", WrapErrorISE(err, "error getting authorization %q", azID)
}
// There's no point on reading all the authorizations as there will
// be only one for a permanent identifier.
if az.Fingerprint != "" {
return az.Fingerprint, nil
}
}
return "", nil
}
// Finalize signs a certificate if the necessary conditions for Order completion // Finalize signs a certificate if the necessary conditions for Order completion
// have been met. // have been met.
// //
@ -150,6 +173,24 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
return NewErrorISE("unexpected status %s for order %s", o.Status, o.ID) return NewErrorISE("unexpected status %s for order %s", o.Status, o.ID)
} }
// Get key fingerprint if any. And then compare it with the CSR fingerprint.
//
// In device-attest-01 challenges we should check that the keys in the CSR
// and the attestation certificate are the same.
fingerprint, err := o.getAuthorizationFingerprint(ctx, db)
if err != nil {
return err
}
if fingerprint != "" {
fp, err := keyutil.Fingerprint(csr.PublicKey)
if err != nil {
return WrapErrorISE(err, "error calculating key fingerprint")
}
if subtle.ConstantTimeCompare([]byte(fingerprint), []byte(fp)) == 0 {
return NewError(ErrorUnauthorizedType, "order %s csr does not match the attested key", o.ID)
}
}
// canonicalize the CSR to allow for comparison // canonicalize the CSR to allow for comparison
csr = canonicalize(csr) csr = canonicalize(csr)

View file

@ -2,6 +2,7 @@ package acme
import ( import (
"context" "context"
"crypto"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/asn1" "encoding/asn1"
@ -18,6 +19,7 @@ import (
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
) )
@ -308,6 +310,14 @@ func (m *mockSignAuth) Revoke(context.Context, *authority.RevokeOptions) error {
} }
func TestOrder_Finalize(t *testing.T) { func TestOrder_Finalize(t *testing.T) {
mustSigner := func(kty, crv string, size int) crypto.Signer {
s, err := keyutil.GenerateSigner(kty, crv, size)
if err != nil {
t.Fatal(err)
}
return s
}
type test struct { type test struct {
o *Order o *Order
err *Error err *Error
@ -400,10 +410,18 @@ func TestOrder_Finalize(t *testing.T) {
{Type: "permanent-identifier", Value: "a-permanent-identifier"}, {Type: "permanent-identifier", Value: "a-permanent-identifier"},
}, },
} }
signer := mustSigner("EC", "P-256", 0)
fingerprint, err := keyutil.Fingerprint(signer.Public())
if err != nil {
t.Fatal(err)
}
csr := &x509.CertificateRequest{ csr := &x509.CertificateRequest{
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: "a-different-identifier", CommonName: "a-different-identifier",
}, },
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{ ExtraExtensions: []pkix.Extension{
{ {
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3}, Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
@ -414,6 +432,29 @@ func TestOrder_Finalize(t *testing.T) {
return test{ return test{
o: o, o: o,
csr: csr, csr: csr,
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
switch id {
case "a":
return &Authorization{
ID: id,
Status: StatusValid,
}, nil
case "b":
return &Authorization{
ID: id,
Fingerprint: fingerprint,
Status: StatusValid,
}, nil
default:
assert.FatalError(t, errors.Errorf("unexpected authorization %s", id))
return nil, errors.New("force")
}
},
MockUpdateOrder: func(ctx context.Context, o *Order) error {
return nil
},
},
err: &Error{ err: &Error{
Type: "urn:ietf:params:acme:error:badCSR", Type: "urn:ietf:params:acme:error:badCSR",
Detail: "The CSR is unacceptable", Detail: "The CSR is unacceptable",
@ -452,6 +493,11 @@ func TestOrder_Finalize(t *testing.T) {
return nil, errors.New("force") return nil, errors.New("force")
}, },
}, },
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
},
err: NewErrorISE("error retrieving authorization options from ACME provisioner: force"), err: NewErrorISE("error retrieving authorization options from ACME provisioner: force"),
} }
}, },
@ -491,6 +537,11 @@ func TestOrder_Finalize(t *testing.T) {
} }
}, },
}, },
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
},
err: NewErrorISE("error creating template options from ACME provisioner: error unmarshaling template data: invalid character 'o' in literal false (expecting 'a')"), err: NewErrorISE("error creating template options from ACME provisioner: error unmarshaling template data: invalid character 'o' in literal false (expecting 'a')"),
} }
}, },
@ -532,6 +583,11 @@ func TestOrder_Finalize(t *testing.T) {
return nil, errors.New("force") return nil, errors.New("force")
}, },
}, },
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
},
err: NewErrorISE("error signing certificate for order oID: force"), err: NewErrorISE("error signing certificate for order oID: force"),
} }
}, },
@ -578,6 +634,9 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
db: &MockDB{ db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error { MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
assert.Equals(t, cert.AccountID, o.AccountID) assert.Equals(t, cert.AccountID, o.AccountID)
assert.Equals(t, cert.OrderID, o.ID) assert.Equals(t, cert.OrderID, o.ID)
@ -632,6 +691,9 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
db: &MockDB{ db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error { MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID" cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID) assert.Equals(t, cert.AccountID, o.AccountID)
@ -654,7 +716,7 @@ func TestOrder_Finalize(t *testing.T) {
err: NewErrorISE("error updating order oID: force"), err: NewErrorISE("error updating order oID: force"),
} }
}, },
"ok/permanent-identifier": func(t *testing.T) test { "fail/csr-fingerprint": func(t *testing.T) test {
now := clock.Now() now := clock.Now()
o := &Order{ o := &Order{
ID: "oID", ID: "oID",
@ -666,10 +728,14 @@ func TestOrder_Finalize(t *testing.T) {
{Type: "permanent-identifier", Value: "a-permanent-identifier"}, {Type: "permanent-identifier", Value: "a-permanent-identifier"},
}, },
} }
signer := mustSigner("EC", "P-256", 0)
csr := &x509.CertificateRequest{ csr := &x509.CertificateRequest{
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: "a-permanent-identifier", CommonName: "a-permanent-identifier",
}, },
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{ ExtraExtensions: []pkix.Extension{
{ {
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3}, Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
@ -680,6 +746,7 @@ func TestOrder_Finalize(t *testing.T) {
leaf := &x509.Certificate{ leaf := &x509.Certificate{
Subject: pkix.Name{CommonName: "a-permanent-identifier"}, Subject: pkix.Name{CommonName: "a-permanent-identifier"},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{ ExtraExtensions: []pkix.Extension{
{ {
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3}, Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
@ -709,6 +776,117 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
db: &MockDB{ db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{
ID: id,
Fingerprint: "other-fingerprint",
Status: StatusValid,
}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID)
assert.Equals(t, cert.OrderID, o.ID)
assert.Equals(t, cert.Leaf, leaf)
assert.Equals(t, cert.Intermediates, []*x509.Certificate{inter, root})
return nil
},
MockUpdateOrder: func(ctx context.Context, updo *Order) error {
assert.Equals(t, updo.CertificateID, "certID")
assert.Equals(t, updo.Status, StatusValid)
assert.Equals(t, updo.ID, o.ID)
assert.Equals(t, updo.AccountID, o.AccountID)
assert.Equals(t, updo.ExpiresAt, o.ExpiresAt)
assert.Equals(t, updo.AuthorizationIDs, o.AuthorizationIDs)
assert.Equals(t, updo.Identifiers, o.Identifiers)
return nil
},
},
err: NewError(ErrorUnauthorizedType, "order oID csr does not match the attested key"),
}
},
"ok/permanent-identifier": func(t *testing.T) test {
now := clock.Now()
o := &Order{
ID: "oID",
AccountID: "accID",
Status: StatusReady,
ExpiresAt: now.Add(5 * time.Minute),
AuthorizationIDs: []string{"a", "b"},
Identifiers: []Identifier{
{Type: "permanent-identifier", Value: "a-permanent-identifier"},
},
}
signer := mustSigner("EC", "P-256", 0)
fingerprint, err := keyutil.Fingerprint(signer.Public())
if err != nil {
t.Fatal(err)
}
csr := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: "a-permanent-identifier",
},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
Value: []byte("a-permanent-identifier"),
},
},
}
leaf := &x509.Certificate{
Subject: pkix.Name{CommonName: "a-permanent-identifier"},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
Value: []byte("a-permanent-identifier"),
},
},
}
inter := &x509.Certificate{Subject: pkix.Name{CommonName: "inter"}}
root := &x509.Certificate{Subject: pkix.Name{CommonName: "root"}}
return test{
o: o,
csr: csr,
prov: &MockProvisioner{
MauthorizeSign: func(ctx context.Context, token string) ([]provisioner.SignOption, error) {
assert.Equals(t, token, "")
return nil, nil
},
MgetOptions: func() *provisioner.Options {
return nil
},
},
ca: &mockSignAuth{
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, _csr, csr)
return []*x509.Certificate{leaf, inter, root}, nil
},
},
db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
switch id {
case "a":
return &Authorization{
ID: id,
Status: StatusValid,
}, nil
case "b":
return &Authorization{
ID: id,
Fingerprint: fingerprint,
Status: StatusValid,
}, nil
default:
assert.FatalError(t, errors.Errorf("unexpected authorization %s", id))
return nil, errors.New("force")
}
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error { MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID" cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID) assert.Equals(t, cert.AccountID, o.AccountID)
@ -743,11 +921,19 @@ func TestOrder_Finalize(t *testing.T) {
{Type: "permanent-identifier", Value: "a-permanent-identifier"}, {Type: "permanent-identifier", Value: "a-permanent-identifier"},
}, },
} }
signer := mustSigner("EC", "P-256", 0)
fingerprint, err := keyutil.Fingerprint(signer.Public())
if err != nil {
t.Fatal(err)
}
csr := &x509.CertificateRequest{ csr := &x509.CertificateRequest{
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: "a-permanent-identifier", CommonName: "a-permanent-identifier",
}, },
DNSNames: []string{"foo.internal"}, DNSNames: []string{"foo.internal"},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{ ExtraExtensions: []pkix.Extension{
{ {
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3}, Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
@ -758,6 +944,7 @@ func TestOrder_Finalize(t *testing.T) {
leaf := &x509.Certificate{ leaf := &x509.Certificate{
Subject: pkix.Name{CommonName: "a-permanent-identifier"}, Subject: pkix.Name{CommonName: "a-permanent-identifier"},
PublicKey: signer.Public(),
ExtraExtensions: []pkix.Extension{ ExtraExtensions: []pkix.Extension{
{ {
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3}, Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 8, 3},
@ -792,6 +979,13 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
db: &MockDB{ db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{
ID: id,
Fingerprint: fingerprint,
Status: StatusValid,
}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error { MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID" cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID) assert.Equals(t, cert.AccountID, o.AccountID)
@ -856,6 +1050,9 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
db: &MockDB{ db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error { MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID" cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID) assert.Equals(t, cert.AccountID, o.AccountID)
@ -917,6 +1114,9 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
db: &MockDB{ db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error { MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID" cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID) assert.Equals(t, cert.AccountID, o.AccountID)
@ -981,6 +1181,9 @@ func TestOrder_Finalize(t *testing.T) {
}, },
}, },
db: &MockDB{ db: &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error { MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
cert.ID = "certID" cert.ID = "certID"
assert.Equals(t, cert.AccountID, o.AccountID) assert.Equals(t, cert.AccountID, o.AccountID)
@ -1688,3 +1891,55 @@ func TestOrder_sans(t *testing.T) {
}) })
} }
} }
func TestOrder_getAuthorizationFingerprint(t *testing.T) {
ctx := context.Background()
type fields struct {
AuthorizationIDs []string
}
type args struct {
ctx context.Context
db DB
}
tests := []struct {
name string
fields fields
args args
want string
wantErr bool
}{
{"ok", fields{[]string{"az1", "az2"}}, args{ctx, &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return &Authorization{ID: id, Status: StatusValid}, nil
},
}}, "", false},
{"ok fingerprint", fields{[]string{"az1", "az2"}}, args{ctx, &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
if id == "az1" {
return &Authorization{ID: id, Status: StatusValid}, nil
}
return &Authorization{ID: id, Fingerprint: "fingerprint", Status: StatusValid}, nil
},
}}, "fingerprint", false},
{"fail", fields{[]string{"az1", "az2"}}, args{ctx, &MockDB{
MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) {
return nil, errors.New("force")
},
}}, "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &Order{
AuthorizationIDs: tt.fields.AuthorizationIDs,
}
got, err := o.getAuthorizationFingerprint(tt.args.ctx, tt.args.db)
if (err != nil) != tt.wantErr {
t.Errorf("Order.getAuthorizationFingerprint() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Order.getAuthorizationFingerprint() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -52,6 +52,7 @@ var (
func init() { func init() {
step.Set("Smallstep CA", Version, BuildTime) step.Set("Smallstep CA", Version, BuildTime)
authority.GlobalVersion.Version = Version authority.GlobalVersion.Version = Version
//nolint:staticcheck // deprecated in Go 1.20
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
// Add support for asking passwords // Add support for asking passwords
pemutil.PromptPassword = func(msg string) ([]byte, error) { pemutil.PromptPassword = func(msg string) ([]byte, error) {

4
go.mod
View file

@ -12,7 +12,7 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
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/ThalesIgnite/crypto11 v1.2.5 // indirect
github.com/aws/aws-sdk-go v1.44.185 // indirect github.com/aws/aws-sdk-go v1.44.195 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/fatih/color v1.9.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
@ -43,7 +43,7 @@ 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.23.2 go.step.sm/crypto v0.25.0
go.step.sm/linkedca v0.19.0 go.step.sm/linkedca v0.19.0
golang.org/x/crypto v0.5.0 golang.org/x/crypto v0.5.0
golang.org/x/net v0.5.0 golang.org/x/net v0.5.0

8
go.sum
View file

@ -84,8 +84,8 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.44.185 h1:stasiou+Ucx2A0RyXRyPph4sLCBxVQK7DPPK8tNcl5g= github.com/aws/aws-sdk-go v1.44.195 h1:d5xFL0N83Fpsq2LFiHgtBUHknCRUPGHdOlCWt/jtOJs=
github.com/aws/aws-sdk-go v1.44.185/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go v1.44.195/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@ -690,8 +690,8 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
go.step.sm/cli-utils v0.7.5 h1:jyp6X8k8mN1B0uWJydTid0C++8tQhm2kaaAdXKQQzdk= go.step.sm/cli-utils v0.7.5 h1:jyp6X8k8mN1B0uWJydTid0C++8tQhm2kaaAdXKQQzdk=
go.step.sm/cli-utils v0.7.5/go.mod h1:taSsY8haLmXoXM3ZkywIyRmVij/4Aj0fQbNTlJvv71I= go.step.sm/cli-utils v0.7.5/go.mod h1:taSsY8haLmXoXM3ZkywIyRmVij/4Aj0fQbNTlJvv71I=
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.23.2 h1:XGmQH9Pkpxop47cjYlUhF10L5roPCbu1BCZXopbeW8I= go.step.sm/crypto v0.25.0 h1:a+7sKyozZH9B30s0dHluygxreUxI1NtCBEmuNXx7a4k=
go.step.sm/crypto v0.23.2/go.mod h1:/IXGz8al8k7u7OV0RTWIi8TRVqO2FMyZVpedV+6Da6U= go.step.sm/crypto v0.25.0/go.mod h1:kr1rzO6SzeQnLm6Zu6lNtksHZLiFe9k8LolSJNhoc94=
go.step.sm/linkedca v0.19.0 h1:xuagkR35wrJI2gnu6FAM+q3VmjwsHScvGcJsfZ0GdsI= go.step.sm/linkedca v0.19.0 h1:xuagkR35wrJI2gnu6FAM+q3VmjwsHScvGcJsfZ0GdsI=
go.step.sm/linkedca v0.19.0/go.mod h1:b7vWPrHfYLEOTSUZitFEcztVCpTc+ileIN85CwEAluM= go.step.sm/linkedca v0.19.0/go.mod h1:b7vWPrHfYLEOTSUZitFEcztVCpTc+ileIN85CwEAluM=
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=