Verify CSR key fingerprint with attestation certificate key
This commit makes sure that the attestation certificate key matches the key used on the CSR on an ACME device attestation flow.
This commit is contained in:
parent
ec3be2359a
commit
6ba20209c2
9 changed files with 792 additions and 450 deletions
|
@ -16,6 +16,7 @@ type Authorization struct {
|
||||||
Challenges []*Challenge `json:"challenges"`
|
Challenges []*Challenge `json:"challenges"`
|
||||||
Wildcard bool `json:"wildcard"`
|
Wildcard bool `json:"wildcard"`
|
||||||
ExpiresAt time.Time `json:"expires"`
|
ExpiresAt time.Time `json:"expires"`
|
||||||
|
Fingerprint string `json:"fingerprint,omitempty"`
|
||||||
Error *Error `json:"error,omitempty"`
|
Error *Error `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -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
8
go.sum
|
@ -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=
|
||||||
|
|
Loading…
Reference in a new issue