package acme import ( "crypto/x509" "encoding/json" "encoding/pem" "testing" "time" "github.com/pkg/errors" "github.com/smallstep/assert" "github.com/smallstep/certificates/db" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/nosql" "github.com/smallstep/nosql/database" ) func defaultCertOps() (*CertOptions, error) { crt, err := pemutil.ReadCertificate("../authority/testdata/certs/foo.crt") if err != nil { return nil, err } inter, err := pemutil.ReadCertificate("../authority/testdata/certs/intermediate_ca.crt") if err != nil { return nil, err } root, err := pemutil.ReadCertificate("../authority/testdata/certs/root_ca.crt") if err != nil { return nil, err } return &CertOptions{ AccountID: "accID", OrderID: "ordID", Leaf: crt, Intermediates: []*x509.Certificate{inter, root}, }, nil } func newcert() (*certificate, error) { ops, err := defaultCertOps() if err != nil { return nil, err } mockdb := &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { return nil, true, nil }, } return newCert(mockdb, *ops) } func TestNewCert(t *testing.T) { type test struct { db nosql.DB ops CertOptions err *Error id *string } tests := map[string]func(t *testing.T) test{ "fail/cmpAndSwap-error": func(t *testing.T) test { ops, err := defaultCertOps() assert.FatalError(t, err) return test{ ops: *ops, db: &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { assert.Equals(t, bucket, certTable) assert.Equals(t, old, nil) return nil, false, errors.New("force") }, }, err: ServerInternalErr(errors.Errorf("error storing certificate: force")), } }, "fail/cmpAndSwap-false": func(t *testing.T) test { ops, err := defaultCertOps() assert.FatalError(t, err) return test{ ops: *ops, db: &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { assert.Equals(t, bucket, certTable) assert.Equals(t, old, nil) return nil, false, nil }, }, err: ServerInternalErr(errors.Errorf("error storing certificate; value has changed since last read")), } }, "ok": func(t *testing.T) test { ops, err := defaultCertOps() assert.FatalError(t, err) var _id string id := &_id return test{ ops: *ops, db: &db.MockNoSQLDB{ MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { assert.Equals(t, bucket, certTable) assert.Equals(t, old, nil) *id = string(key) return nil, true, nil }, }, id: id, } }, } for name, run := range tests { t.Run(name, func(t *testing.T) { tc := run(t) if cert, err := newCert(tc.db, tc.ops); err != nil { if assert.NotNil(t, tc.err) { ae, ok := err.(*Error) assert.True(t, ok) assert.HasPrefix(t, ae.Error(), tc.err.Error()) assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) assert.Equals(t, ae.Type, tc.err.Type) } } else { if assert.Nil(t, tc.err) { assert.Equals(t, cert.ID, *tc.id) assert.Equals(t, cert.AccountID, tc.ops.AccountID) assert.Equals(t, cert.OrderID, tc.ops.OrderID) leaf := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: tc.ops.Leaf.Raw, }) var intermediates []byte for _, cert := range tc.ops.Intermediates { intermediates = append(intermediates, pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: cert.Raw, })...) } assert.Equals(t, cert.Leaf, leaf) assert.Equals(t, cert.Intermediates, intermediates) assert.True(t, cert.Created.Before(time.Now().Add(time.Minute))) assert.True(t, cert.Created.After(time.Now().Add(-time.Minute))) } } }) } } func TestGetCert(t *testing.T) { type test struct { id string db nosql.DB cert *certificate err *Error } tests := map[string]func(t *testing.T) test{ "fail/not-found": func(t *testing.T) test { cert, err := newcert() assert.FatalError(t, err) return test{ cert: cert, id: cert.ID, db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, certTable) assert.Equals(t, key, []byte(cert.ID)) return nil, database.ErrNotFound }, }, err: MalformedErr(errors.Errorf("certificate %s not found: not found", cert.ID)), } }, "fail/db-error": func(t *testing.T) test { cert, err := newcert() assert.FatalError(t, err) return test{ cert: cert, id: cert.ID, db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, certTable) assert.Equals(t, key, []byte(cert.ID)) return nil, errors.New("force") }, }, err: ServerInternalErr(errors.New("error loading certificate: force")), } }, "fail/unmarshal-error": func(t *testing.T) test { cert, err := newcert() assert.FatalError(t, err) return test{ cert: cert, id: cert.ID, db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, certTable) assert.Equals(t, key, []byte(cert.ID)) return nil, nil }, }, err: ServerInternalErr(errors.New("error unmarshaling certificate: unexpected end of JSON input")), } }, "ok": func(t *testing.T) test { cert, err := newcert() assert.FatalError(t, err) b, err := json.Marshal(cert) assert.FatalError(t, err) return test{ cert: cert, id: cert.ID, db: &db.MockNoSQLDB{ MGet: func(bucket, key []byte) ([]byte, error) { assert.Equals(t, bucket, certTable) assert.Equals(t, key, []byte(cert.ID)) return b, nil }, }, } }, } for name, run := range tests { t.Run(name, func(t *testing.T) { tc := run(t) if cert, err := getCert(tc.db, tc.id); err != nil { if assert.NotNil(t, tc.err) { ae, ok := err.(*Error) assert.True(t, ok) assert.HasPrefix(t, ae.Error(), tc.err.Error()) assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) assert.Equals(t, ae.Type, tc.err.Type) } } else { if assert.Nil(t, tc.err) { assert.Equals(t, tc.cert.ID, cert.ID) assert.Equals(t, tc.cert.AccountID, cert.AccountID) assert.Equals(t, tc.cert.OrderID, cert.OrderID) assert.Equals(t, tc.cert.Created, cert.Created) assert.Equals(t, tc.cert.Leaf, cert.Leaf) assert.Equals(t, tc.cert.Intermediates, cert.Intermediates) } } }) } } func TestCertificateToACME(t *testing.T) { cert, err := newcert() assert.FatalError(t, err) acmeCert, err := cert.toACME(nil, nil) assert.FatalError(t, err) assert.Equals(t, append(cert.Leaf, cert.Intermediates...), acmeCert) }