Skip to content

Commit 8090bce

Browse files
authored
feat(authsign): store additional metadata/fields in certdb (#1126)
This is a major change in that the included DB migrations *must* be run before the new version of `cfssl` is deployed. This allows for clients (i.e. https://github.com/cloudflare/certmgr) to send some additional optional fields to `/api/v1/cfssl/authsign` to be stored in `certdb`. It also starts saving SANs, common name, and NotBefore from the issued certificates so that they can be queried without having to parse the PEM.
1 parent 046b174 commit 8090bce

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+11041
-705
lines changed

certdb/certdb.go

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,62 @@
11
package certdb
22

33
import (
4+
"encoding/json"
45
"time"
6+
7+
"github.com/jmoiron/sqlx/types"
58
)
69

710
// CertificateRecord encodes a certificate and its metadata
811
// that will be recorded in a database.
912
type CertificateRecord struct {
10-
Serial string `db:"serial_number"`
11-
AKI string `db:"authority_key_identifier"`
12-
CALabel string `db:"ca_label"`
13-
Status string `db:"status"`
14-
Reason int `db:"reason"`
15-
Expiry time.Time `db:"expiry"`
16-
RevokedAt time.Time `db:"revoked_at"`
17-
PEM string `db:"pem"`
13+
Serial string `db:"serial_number"`
14+
AKI string `db:"authority_key_identifier"`
15+
CALabel string `db:"ca_label"`
16+
Status string `db:"status"`
17+
Reason int `db:"reason"`
18+
Expiry time.Time `db:"expiry"`
19+
RevokedAt time.Time `db:"revoked_at"`
20+
PEM string `db:"pem"`
21+
IssuedAt time.Time `db:"issued_at"`
22+
NotBefore time.Time `db:"not_before"`
23+
MetadataJSON types.JSONText `db:"metadata"`
24+
SANsJSON types.JSONText `db:"sans"`
25+
CommonName string `db:"common_name"`
26+
}
27+
28+
// SetMetadata sets the metadata json
29+
func (c *CertificateRecord) SetMetadata(meta map[string]interface{}) error {
30+
marshaled, err := json.Marshal(meta)
31+
if err != nil {
32+
return err
33+
}
34+
c.MetadataJSON = types.JSONText(marshaled)
35+
return nil
36+
}
37+
38+
// GetMetadata returns the json metadata
39+
func (c *CertificateRecord) GetMetadata() (map[string]interface{}, error) {
40+
var meta map[string]interface{}
41+
err := c.MetadataJSON.Unmarshal(&meta)
42+
return meta, err
43+
}
44+
45+
// SetSANs sets the list of sans
46+
func (c *CertificateRecord) SetSANs(meta []string) error {
47+
marshaled, err := json.Marshal(meta)
48+
if err != nil {
49+
return err
50+
}
51+
c.SANsJSON = types.JSONText(marshaled)
52+
return nil
53+
}
54+
55+
// GetSANs returns the json SANs
56+
func (c *CertificateRecord) GetSANs() ([]string, error) {
57+
var sans []string
58+
err := c.SANsJSON.Unmarshal(&sans)
59+
return sans, err
1860
}
1961

2062
// OCSPRecord encodes a OCSP response body and its metadata
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- +goose Up
2+
-- SQL in section 'Up' is executed when this migration is applied
3+
ALTER TABLE certificates
4+
ADD COLUMN issued_at timestamp DEFAULT '0000-00-00 00:00:00',
5+
ADD COLUMN not_before timestamp DEFAULT '0000-00-00 00:00:00',
6+
ADD COLUMN metadata JSON,
7+
ADD COLUMN sans JSON,
8+
ADD COLUMN common_name TEXT;
9+
-- +goose Down
10+
-- SQL section 'Down' is executed when this migration is rolled back
11+
ALTER TABLE certificates DROP COLUMN issued_at,
12+
DROP COLUMN not_before,
13+
DROP COLUMN metadata,
14+
DROP COLUMN sans,
15+
DROP COLUMN common_name;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- +goose Up
2+
-- SQL in section 'Up' is executed when this migration is applied
3+
ALTER TABLE certificates
4+
ADD COLUMN issued_at timestamptz,
5+
ADD COLUMN not_before timestamptz,
6+
ADD COLUMN metadata jsonb,
7+
ADD COLUMN sans jsonb,
8+
ADD COLUMN common_name TEXT;
9+
-- +goose Down
10+
-- SQL section 'Down' is executed when this migration is rolled back
11+
ALTER TABLE certificates DROP COLUMN issued_at,
12+
DROP COLUMN not_before,
13+
DROP COLUMN metadata,
14+
DROP COLUMN sans,
15+
DROP COLUMN common_name;

certdb/sql/database_accessor.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ func init() {
1919

2020
const (
2121
insertSQL = `
22-
INSERT INTO certificates (serial_number, authority_key_identifier, ca_label, status, reason, expiry, revoked_at, pem)
23-
VALUES (:serial_number, :authority_key_identifier, :ca_label, :status, :reason, :expiry, :revoked_at, :pem);`
22+
INSERT INTO certificates (serial_number, authority_key_identifier, ca_label, status, reason, expiry, revoked_at, pem,
23+
issued_at, not_before, metadata, sans, common_name)
24+
VALUES (:serial_number, :authority_key_identifier, :ca_label, :status, :reason, :expiry, :revoked_at, :pem,
25+
:issued_at, :not_before, :metadata, :sans, :common_name);`
2426

2527
selectSQL = `
2628
SELECT %s FROM certificates
@@ -100,14 +102,19 @@ func (d *Accessor) InsertCertificate(cr certdb.CertificateRecord) error {
100102
}
101103

102104
res, err := d.db.NamedExec(insertSQL, &certdb.CertificateRecord{
103-
Serial: cr.Serial,
104-
AKI: cr.AKI,
105-
CALabel: cr.CALabel,
106-
Status: cr.Status,
107-
Reason: cr.Reason,
108-
Expiry: cr.Expiry.UTC(),
109-
RevokedAt: cr.RevokedAt.UTC(),
110-
PEM: cr.PEM,
105+
Serial: cr.Serial,
106+
AKI: cr.AKI,
107+
CALabel: cr.CALabel,
108+
Status: cr.Status,
109+
Reason: cr.Reason,
110+
Expiry: cr.Expiry.UTC(),
111+
RevokedAt: cr.RevokedAt.UTC(),
112+
PEM: cr.PEM,
113+
IssuedAt: cr.IssuedAt.UTC(),
114+
NotBefore: cr.NotBefore.UTC(),
115+
MetadataJSON: cr.MetadataJSON,
116+
SANsJSON: cr.SANsJSON,
117+
CommonName: cr.CommonName,
111118
})
112119
if err != nil {
113120
return wrapSQLError(err)

certdb/sql/sql_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/cloudflare/cfssl/certdb/testdb"
1010

1111
"github.com/jmoiron/sqlx"
12+
"github.com/stretchr/testify/require"
1213
)
1314

1415
const (
@@ -70,7 +71,7 @@ func testInsertCertificateAndGetCertificate(ta TestAccessor, t *testing.T) {
7071
Reason: 0,
7172
Expiry: expiry,
7273
}
73-
74+
want.SetMetadata(map[string]interface{}{"k": "v"})
7475
if err := ta.Accessor.InsertCertificate(want); err != nil {
7576
t.Fatal(err)
7677
}
@@ -92,6 +93,9 @@ func testInsertCertificateAndGetCertificate(ta TestAccessor, t *testing.T) {
9293
want.PEM != got.PEM || !roughlySameTime(got.Expiry, expiry) {
9394
t.Errorf("want Certificate %+v, got %+v", want, got)
9495
}
96+
gotMeta, err := got.GetMetadata()
97+
require.NoError(t, err)
98+
require.Equal(t, map[string]interface{}{"k": "v"}, gotMeta)
9599

96100
unexpired, err := ta.Accessor.GetUnexpiredCertificates()
97101

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-- +goose Up
2+
-- SQL in section 'Up' is executed when this migration is applied
3+
4+
ALTER TABLE certificates ADD COLUMN "issued_at" timestamp;
5+
ALTER TABLE certificates ADD COLUMN "not_before" timestamp;
6+
ALTER TABLE certificates ADD COLUMN "metadata" text;
7+
ALTER TABLE certificates ADD COLUMN "sans" text;
8+
ALTER TABLE certificates ADD COLUMN "common_name" text;
9+
10+
-- +goose Down
11+
-- SQL section 'Down' is executed when this migration is rolled back
12+
13+
-- can't drop columns in sqlite
5 KB
Binary file not shown.

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,23 @@ require (
1010
github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41
1111
github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c
1212
github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7 // indirect
13-
github.com/go-sql-driver/mysql v1.3.0
14-
github.com/golang/protobuf v1.3.1 // indirect
13+
github.com/go-sql-driver/mysql v1.4.0
1514
github.com/google/certificate-transparency-go v1.0.21
1615
github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548
17-
github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade
16+
github.com/jmoiron/sqlx v1.2.0
1817
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49
1918
github.com/kisom/goutils v1.1.0
2019
github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28 // indirect
2120
github.com/lib/pq v1.3.0
2221
github.com/mattn/go-sqlite3 v1.10.0
2322
github.com/pkg/errors v0.8.0 // indirect
23+
github.com/stretchr/testify v1.3.0
2424
github.com/weppos/publicsuffix-go v0.5.0 // indirect
2525
github.com/ziutek/mymysql v1.5.4 // indirect
2626
github.com/zmap/zcrypto v0.0.0-20191112190257-7f2fe6faf8cf
2727
github.com/zmap/zlint/v2 v2.0.0
2828
golang.org/x/crypto v0.0.0-20200124225646-8b5121be2f68
2929
golang.org/x/lint v0.0.0-20190930215403-16217165b5de
3030
golang.org/x/net v0.0.0-20190620200207-3b0461eec859
31-
golang.org/x/text v0.3.2 // indirect
31+
google.golang.org/appengine v1.6.6 // indirect
3232
)

go.sum

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
2121
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2222
github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7 h1:ELaJ1cjF2nEJeIlHXahGme22yG7TK+3jB6IGCq0Cdrc=
2323
github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
24-
github.com/go-sql-driver/mysql v1.3.0 h1:pgwjLi/dvffoP9aabwkT3AKpXQM93QARkjFhDDqC1UE=
25-
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
24+
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
25+
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
2626
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
2727
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
2828
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
@@ -31,8 +31,8 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR
3131
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
3232
github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548 h1:dYTbLf4m0a5u0KLmPfB6mgxbcV7588bOCx79hxa5Sr4=
3333
github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo=
34-
github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade h1:ryslCsfLTV4Cm/9NXqCJirlbYodWqFiTH454IaSn/fY=
35-
github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
34+
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
35+
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
3636
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49 h1:o/c0aWEP/m6n61xlYW2QP4t9424qlJOsxugn5Zds2Rg=
3737
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
3838
github.com/kisom/goutils v1.1.0 h1:z4HEOgAnFq+e1+O4QdVsyDPatJDu5Ei/7w7DRbYjsIA=
@@ -45,8 +45,10 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
4545
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
4646
github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28 h1:mkl3tvPHIuPaWsLtmHTybJeoVEW7cbePK73Ir8VtruA=
4747
github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c=
48+
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
4849
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
4950
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
51+
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
5052
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
5153
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
5254
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
@@ -89,6 +91,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
8991
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
9092
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
9193
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
94+
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
9295
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
9396
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
9497
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -100,5 +103,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
100103
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
101104
golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ=
102105
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
106+
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
107+
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
103108
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
104109
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

signer/local/local.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"net/mail"
2222
"net/url"
2323
"os"
24+
"time"
2425

2526
"github.com/cloudflare/cfssl/certdb"
2627
"github.com/cloudflare/cfssl/config"
@@ -29,7 +30,7 @@ import (
2930
"github.com/cloudflare/cfssl/info"
3031
"github.com/cloudflare/cfssl/log"
3132
"github.com/cloudflare/cfssl/signer"
32-
"github.com/google/certificate-transparency-go"
33+
ct "github.com/google/certificate-transparency-go"
3334
"github.com/google/certificate-transparency-go/client"
3435
"github.com/google/certificate-transparency-go/jsonclient"
3536

@@ -509,15 +510,24 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
509510
Serial: certTBS.SerialNumber.String(),
510511
// this relies on the specific behavior of x509.CreateCertificate
511512
// which sets the AuthorityKeyId from the signer's SubjectKeyId
512-
AKI: hex.EncodeToString(parsedCert.AuthorityKeyId),
513-
CALabel: req.Label,
514-
Status: "good",
515-
Expiry: certTBS.NotAfter,
516-
PEM: string(signedCert),
513+
AKI: hex.EncodeToString(parsedCert.AuthorityKeyId),
514+
CALabel: req.Label,
515+
Status: "good",
516+
Expiry: certTBS.NotAfter,
517+
PEM: string(signedCert),
518+
IssuedAt: time.Now(),
519+
NotBefore: certTBS.NotBefore,
520+
CommonName: certTBS.Subject.CommonName,
517521
}
518522

519-
err = s.dbAccessor.InsertCertificate(certRecord)
520-
if err != nil {
523+
if err := certRecord.SetMetadata(req.Metadata); err != nil {
524+
return nil, err
525+
}
526+
if err := certRecord.SetSANs(certTBS.DNSNames); err != nil {
527+
return nil, err
528+
}
529+
530+
if err := s.dbAccessor.InsertCertificate(certRecord); err != nil {
521531
return nil, err
522532
}
523533
log.Debug("saved certificate with serial number ", certTBS.SerialNumber)

signer/signer.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ type SignRequest struct {
7070
// be passed to SignFromPrecert with the SCTs in order to create a
7171
// valid certificate.
7272
ReturnPrecert bool
73+
74+
// Arbitrary metadata to be stored in certdb.
75+
Metadata map[string]interface{} `json:"metadata"`
7376
}
7477

7578
// appendIf appends to a if s is not an empty string.
@@ -193,8 +196,8 @@ func ParseCertificateRequest(s Signer, p *config.SigningProfile, csrBytes []byte
193196
IPAddresses: csrv.IPAddresses,
194197
EmailAddresses: csrv.EmailAddresses,
195198
URIs: csrv.URIs,
196-
Extensions: csrv.Extensions,
197-
ExtraExtensions: []pkix.Extension{},
199+
Extensions: csrv.Extensions,
200+
ExtraExtensions: []pkix.Extension{},
198201
}
199202

200203
for _, val := range csrv.Extensions {
@@ -216,7 +219,7 @@ func ParseCertificateRequest(s Signer, p *config.SigningProfile, csrBytes []byte
216219
template.MaxPathLenZero = template.MaxPathLen == 0
217220
} else {
218221
// If the profile has 'copy_extensions' to true then lets add it
219-
if (p.CopyExtensions) {
222+
if p.CopyExtensions {
220223
template.ExtraExtensions = append(template.ExtraExtensions, val)
221224
}
222225
}

vendor/github.com/davecgh/go-spew/LICENSE

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)