From 4014aacc7e6756a732881fdd0a0a6527419c8ae7 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Thu, 23 Mar 2017 16:07:49 -0400 Subject: [PATCH 01/21] add db backed ocspserve command functionality --- cli/ocspserve/ocspserve.go | 20 ++++++++++++++++++-- ocsp/responder.go | 15 +++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/cli/ocspserve/ocspserve.go b/cli/ocspserve/ocspserve.go index 9f8519fc9..af67a3fed 100644 --- a/cli/ocspserve/ocspserve.go +++ b/cli/ocspserve/ocspserve.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "github.com/cloudflare/cfssl/cli" "github.com/cloudflare/cfssl/log" @@ -35,9 +36,24 @@ func ocspServerMain(args []string, c cli.Config) error { return errors.New("no response file provided, please set the -responses flag") } - src, err := ocsp.NewSourceFromFile(c.Responses) + // Parse connection string representing source of OCSP responses + u, err := url.Parse(c.Responses) if err != nil { - return errors.New("unable to read response file") + return errors.New("unrecognized connection string format") + } + switch u.Scheme { + case "file": + src, err := ocsp.NewSourceFromFile(u.Path) + if err != nil { + return errors.New("unable to read response file") + } + case "sqlite3": + src, err := ocsp.NewSqliteSource(u.Path) + if err != nil { + return errors.New("unable to read Sqlite connection string") + } + default: + return errors.New("TODO") } log.Info("Registering OCSP responder handler") diff --git a/ocsp/responder.go b/ocsp/responder.go index 27f8a6f91..0898d3c8e 100644 --- a/ocsp/responder.go +++ b/ocsp/responder.go @@ -19,8 +19,10 @@ import ( "time" "github.com/cloudflare/cfssl/certdb" + "github.com/cloudflare/cfssl/certdb/sql" "github.com/cloudflare/cfssl/log" "github.com/jmhodges/clock" + "github.com/jmoiron/sqlx" "golang.org/x/crypto/ocsp" ) @@ -63,6 +65,19 @@ func NewDBSource(dbAccessor certdb.Accessor) Source { } } +// NewSqliteSource creates a new DBSource object with a Sqlite3 dbAccessor from +// a given Sqlite connection string. +// ?? Not sure where this function should live...this may not be the right place ?? +func NewSqliteSource(dbpath string) (Source, error) { + db, err := sqlx.Open("sqlite3", dbpath) + if err != nil { + return nil, err + } + accessor := sql.NewAccessor(db) + src := NewDBSource(accessor) + return src, nil +} + // Response implements cfssl.ocsp.responder.Source, which returns the // OCSP response in the Database for the given request with the expiration // date furthest in the future. Response also returns a bool that is false From b6ad9a781d9a723d4780bc932a6b46a87526c8a7 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Thu, 23 Mar 2017 17:34:39 -0400 Subject: [PATCH 02/21] add test for checking sqlite db connection --- cli/ocspserve/ocspserve.go | 16 ++++++++-------- helpers/helpers.go | 20 ++++++++++++++++++++ helpers/helpers_test.go | 13 +++++++++++++ ocsp/responder_test.go | 8 ++++++++ 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/cli/ocspserve/ocspserve.go b/cli/ocspserve/ocspserve.go index af67a3fed..ad7bcac17 100644 --- a/cli/ocspserve/ocspserve.go +++ b/cli/ocspserve/ocspserve.go @@ -8,6 +8,7 @@ import ( "net/url" "github.com/cloudflare/cfssl/cli" + "github.com/cloudflare/cfssl/helpers" "github.com/cloudflare/cfssl/log" "github.com/cloudflare/cfssl/ocsp" ) @@ -36,24 +37,23 @@ func ocspServerMain(args []string, c cli.Config) error { return errors.New("no response file provided, please set the -responses flag") } - // Parse connection string representing source of OCSP responses - u, err := url.Parse(c.Responses) + typ, path, err := helpers.ParseConnectionStr(c.Responses) if err != nil { - return errors.New("unrecognized connection string format") + return errors.New("unable to parse responses connection string") } - switch u.Scheme { + switch typ { case "file": - src, err := ocsp.NewSourceFromFile(u.Path) + src, err := ocsp.NewSourceFromFile(path) if err != nil { return errors.New("unable to read response file") } - case "sqlite3": - src, err := ocsp.NewSqliteSource(u.Path) + case "sqlite": + src, err := ocsp.NewSqliteSource(path) if err != nil { return errors.New("unable to read Sqlite connection string") } default: - return errors.New("TODO") + return errors.New("TODO not sure what to do in this case") } log.Info("Registering OCSP responder handler") diff --git a/helpers/helpers.go b/helpers/helpers.go index 8a70c045d..32efd678e 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -15,6 +15,7 @@ import ( "errors" "io/ioutil" "math/big" + "net/url" "strings" "time" @@ -518,3 +519,22 @@ func CreateTLSConfig(remoteCAs *x509.CertPool, cert *tls.Certificate) *tls.Confi RootCAs: remoteCAs, } } + +// ParseConnectionStr parses a string representing the source of OCSP responses +// which can either be a file or the path to a DB (Sqlite, MySQL or PostgreSQL). +// It returns the type of the connection string (e.g. "File" or "MySQL" etc.) as +// well as the path to the source. +func ParseConnectionStr(conn string) (string, string, error) { + u, err := url.Parse(conn) + if err != nil { + return "", "", err + } + switch u.Scheme { + case "", "file": + return "file", u.Path, nil + case "sqlite3": + return "sqlite", u.Path, nil + default: + return "DB", u.Path, nil + } +} diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go index f7697c8ae..14df9ceab 100644 --- a/helpers/helpers_test.go +++ b/helpers/helpers_test.go @@ -498,3 +498,16 @@ func TestLoadPEMCertPool(t *testing.T) { t.Fatal("cert pool not created") } } + +func TestParseConnectionStr(t *testing.T) { + filePath := "/path/to/file.txt" + typ, path, err := ParseConnectionStr(filePath) + if typ != "file" || path != "/path/to/file.txt" || err != nil { + t.Fatal("Incorrect parsing of file path") + } + sqliteStr := "sqlite3:///path/to/db/file.db" + typ, path, err = ParseConnectionStr(sqliteStr) + if typ != "sqlite" || path != "/path/to/db/file.db" || err != nil { + t.Fatal("Incorrect parsing of sqlite connection string") + } +} diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index 62e34ec75..b937fb5b5 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -310,3 +310,11 @@ func TestSqliteRealResponse(t *testing.T) { t.Errorf("Error parsing response: %v", err) } } + +func TestNewSqliteSource(t *testing.T) { + dbpath := "sqlite_test.db" + src, err := NewSqliteSource(dbpath) + if err != nil { + t.Errorf("Error connection to Sqlite DB:", err) + } +} From 33c73712a61c0e7ffb7da42e89722ef4201b3bac Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Mon, 17 Apr 2017 13:52:15 -0400 Subject: [PATCH 03/21] fix unused var --- ocsp/responder_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index b937fb5b5..9ab6acd6d 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -313,7 +313,7 @@ func TestSqliteRealResponse(t *testing.T) { func TestNewSqliteSource(t *testing.T) { dbpath := "sqlite_test.db" - src, err := NewSqliteSource(dbpath) + _, err := NewSqliteSource(dbpath) if err != nil { t.Errorf("Error connection to Sqlite DB:", err) } From 3e00985fd265c76e3ba417ab77e8c58c2d66418f Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Mon, 17 Apr 2017 14:24:28 -0400 Subject: [PATCH 04/21] fix db filepath --- ocsp/responder_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index 9ab6acd6d..13d15105d 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -312,7 +312,7 @@ func TestSqliteRealResponse(t *testing.T) { } func TestNewSqliteSource(t *testing.T) { - dbpath := "sqlite_test.db" + dbpath := "testdata/sqlite_test.db" _, err := NewSqliteSource(dbpath) if err != nil { t.Errorf("Error connection to Sqlite DB:", err) From 856d7b2d5fcc4990d21c5d87e7be7c0b6ee770db Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Thu, 20 Apr 2017 14:32:55 -0400 Subject: [PATCH 05/21] add mysql and postgresql support --- cli/ocspserve/ocspserve.go | 13 ++++++++++++- helpers/helpers.go | 4 ++++ helpers/helpers_test.go | 10 ++++++++++ ocsp/responder.go | 12 +++++++----- ocsp/responder_test.go | 4 ++-- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/cli/ocspserve/ocspserve.go b/cli/ocspserve/ocspserve.go index ad7bcac17..7d865d7d7 100644 --- a/cli/ocspserve/ocspserve.go +++ b/cli/ocspserve/ocspserve.go @@ -52,8 +52,19 @@ func ocspServerMain(args []string, c cli.Config) error { if err != nil { return errors.New("unable to read Sqlite connection string") } + case "mysql": + src, err := ocsp.NewMySQLSource(path) + if err != nil { + return errors.New("unable to read MySQL connection string") + } + case "postgres": + src, err := ocsp.NewPostgresSource(path) + if err != nil { + return errors.New("unable to read PostgreSQL connection string") + } default: - return errors.New("TODO not sure what to do in this case") + // TODO: I can probably to something more intelligent here + return errors.New("not sure what to do in this case") } log.Info("Registering OCSP responder handler") diff --git a/helpers/helpers.go b/helpers/helpers.go index 32efd678e..749e5a7a5 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -534,6 +534,10 @@ func ParseConnectionStr(conn string) (string, string, error) { return "file", u.Path, nil case "sqlite3": return "sqlite", u.Path, nil + case "mysql": + return "mysql", conn[8:], nil + case "postgresql": + return "postgres", "dbname=" + u.Path[1:] + " sslmode=disable", nil default: return "DB", u.Path, nil } diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go index 14df9ceab..6dbee102b 100644 --- a/helpers/helpers_test.go +++ b/helpers/helpers_test.go @@ -510,4 +510,14 @@ func TestParseConnectionStr(t *testing.T) { if typ != "sqlite" || path != "/path/to/db/file.db" || err != nil { t.Fatal("Incorrect parsing of sqlite connection string") } + mysqlStr := "mysql://root@tcp(localhost:3306)/certdb_development?parseTime=true" + typ, path, err = ParseConnectionStr(mysqlStr) + if typ != "mysql" || path != "root@tcp(localhost:3306)/certdb_development?parseTime=true" || err != nil { + t.Fatal("Incorrect parsing of MySQL connection string") + } + postgresStr := "postgresql://root@tcp(localhost:3306)/certdb_development?parseTime=true" + typ, path, err = ParseConnectionStr(postgresStr) + if typ != "postgres" || path != "dbname=certdb_development sslmode=disable" || err != nil { + t.Fatal("Incorrect parsing of PostgreSQL connection string") + } } diff --git a/ocsp/responder.go b/ocsp/responder.go index 0898d3c8e..cc23191ed 100644 --- a/ocsp/responder.go +++ b/ocsp/responder.go @@ -65,11 +65,13 @@ func NewDBSource(dbAccessor certdb.Accessor) Source { } } -// NewSqliteSource creates a new DBSource object with a Sqlite3 dbAccessor from -// a given Sqlite connection string. -// ?? Not sure where this function should live...this may not be the right place ?? -func NewSqliteSource(dbpath string) (Source, error) { - db, err := sqlx.Open("sqlite3", dbpath) +// NewSourceFromConnStr creates a new DBSource object with an associated +// dbAccessor. They type of the DB connection is specificied by the typ +// argument, and this function currently supports Sqlite, MySQL and PostgreSQL. +// The dbpath argument is a connection string aiding in connecting to the +// associated DB type. +func NewSourceFromConnStr(typ, dbpath string) (Source, error) { + db, err := sqlx.Open(typ, dbpath) if err != nil { return nil, err } diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index 13d15105d..00d95ef88 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -313,8 +313,8 @@ func TestSqliteRealResponse(t *testing.T) { func TestNewSqliteSource(t *testing.T) { dbpath := "testdata/sqlite_test.db" - _, err := NewSqliteSource(dbpath) + _, err := NewSourceFromConnStr("sqlite3", dbpath) if err != nil { - t.Errorf("Error connection to Sqlite DB:", err) + t.Errorf("Error connecting to Sqlite DB:", err) } } From a3414b42ca28b653831de3245e7072335c775e07 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Fri, 21 Apr 2017 14:25:51 -0400 Subject: [PATCH 06/21] add test for mysql and postgres DB connections --- ocsp/responder_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index 00d95ef88..3a048495b 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -311,6 +311,9 @@ func TestSqliteRealResponse(t *testing.T) { } } +// Manually run the query "SELECT max(version_id) FROM goose_db_version;" +// on testdata/sqlite_test.db after running this test to verify that the +// DB was properly connected to. func TestNewSqliteSource(t *testing.T) { dbpath := "testdata/sqlite_test.db" _, err := NewSourceFromConnStr("sqlite3", dbpath) @@ -318,3 +321,21 @@ func TestNewSqliteSource(t *testing.T) { t.Errorf("Error connecting to Sqlite DB:", err) } } + +func TestNewMySQLSource(t *testing.T) { + dbpath := "root@tcp(localhost:3306)/certdb_development?parseTime=true" + // Error should be thrown here if DB cannot be connected to. + _, err := NewSourceFromConnStr("mysql", dbpath) + if err != nil { + t.Errorf("Error connecting to MySQL DB:", err) + } +} + +func TestNewPostgresSource(t *testing.T) { + dbpath := "dbname=certdb_development sslmode=disable" + // Error should be thrown here if DB cannot be connected to. + _, err := NewSourceFromConnStr("postgres", dbpath) + if err != nil { + t.Errorf("Error connecting to PostgreSQL DB:", err) + } +} From 0f4ae5e235937a5cd9eb841868c74d8703ff9e24 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Sun, 23 Apr 2017 16:05:44 -0400 Subject: [PATCH 07/21] add tests for MySQL and PostgreSQL ocsp responses --- cli/ocspserve/ocspserve.go | 10 ++-- ocsp/responder_test.go | 98 +++++++++++++++++++++++++++++++------- 2 files changed, 87 insertions(+), 21 deletions(-) diff --git a/cli/ocspserve/ocspserve.go b/cli/ocspserve/ocspserve.go index 7d865d7d7..c6d2284e2 100644 --- a/cli/ocspserve/ocspserve.go +++ b/cli/ocspserve/ocspserve.go @@ -48,23 +48,23 @@ func ocspServerMain(args []string, c cli.Config) error { return errors.New("unable to read response file") } case "sqlite": - src, err := ocsp.NewSqliteSource(path) + src, err := ocsp.NewSourceFromConnStr("sqlite", path) if err != nil { return errors.New("unable to read Sqlite connection string") } case "mysql": - src, err := ocsp.NewMySQLSource(path) + src, err := ocsp.NewSourceFromConnStr("mysql", path) if err != nil { return errors.New("unable to read MySQL connection string") } case "postgres": - src, err := ocsp.NewPostgresSource(path) + src, err := ocsp.NewSourceFromConnStr("postgres", path) if err != nil { return errors.New("unable to read PostgreSQL connection string") } default: - // TODO: I can probably to something more intelligent here - return errors.New("not sure what to do in this case") + // TODO: not sure if I should do something more intelligent in this case + return errors.New("unrecognized connection string format") } log.Info("Registering OCSP responder handler") diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index 3a048495b..ec5e87ac2 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -166,7 +166,7 @@ func TestNewSourceFromFile(t *testing.T) { } } -func TestSqliteTrivial(t *testing.T) { +func TestResponseTrivial(t *testing.T) { // First, read and parse certificate and issuer files needed to make // an OCSP request. certFile := "testdata/sqlite_ca.pem" @@ -198,9 +198,18 @@ func TestSqliteTrivial(t *testing.T) { t.Errorf("Error parsing OCSP request: %s", err) } + // Create SQLite DB and accossiated accessor. sqliteDBfile := "testdata/sqlite_test.db" - db := testdb.SQLiteDB(sqliteDBfile) - accessor := sql.NewAccessor(db) + sqlitedb := testdb.SQLiteDB(sqliteDBfile) + sqliteAccessor := sql.NewAccessor(sqlitedb) + + // Create MySQL DB and accossiated accessor. + mysqldb := testdb.MySQLDB() + mysqlAccessor := sql.NewAccessor(mysqldb) + + // Create PostgreSQL DB and accossiated accessor. + postgresdb := testdb.PostgreSQLDB() + postgresAccessor := sql.NewAccessor(postgresdb) // Populate the DB with the OCSPRecord, and check // that Response() handles the request appropiately. @@ -210,28 +219,65 @@ func TestSqliteTrivial(t *testing.T) { Expiry: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), Serial: req.SerialNumber.String(), } - err = accessor.InsertOCSP(ocsp) + err = sqliteAccessor.InsertOCSP(ocsp) if err != nil { - t.Errorf("Error inserting OCSP record into DB: %s", err) + t.Errorf("Error inserting OCSP record into SQLite DB: %s", err) + } + + err = mysqlAccessor.InsertOCSP(ocsp) + if err != nil { + t.Errorf("Error inserting OCSP record into MySQL DB: %s", err) + } + + err = postgresAccessor.InsertOCSP(ocsp) + if err != nil { + t.Errorf("Error inserting OCSP record into PostgreSQL DB: %s", err) } // Use the created Accessor to create a new DBSource. - src := NewDBSource(accessor) + sqliteSrc := NewDBSource(sqliteAccessor) + mysqlSrc := NewDBSource(mysqlAccessor) + postgresSrc := NewDBSource(postgresAccessor) // Call Response() method on constructed request and check the output. - response, present := src.Response(req) + response, present := sqliteSrc.Response(req) if !present { - t.Error("No response present for given request") + t.Error("No response present in SQLite DB for given request") } if string(response) != "Test OCSP" { t.Error("Incorrect response received from Sqlite DB") } + + response, present = mysqlSrc.Response(req) + if !present { + t.Error("No response present in MySQL DB for given request") + } + if string(response) != "Test OCSP" { + t.Error("Incorrect response received from MySQL DB") + } + + response, present = postgresSrc.Response(req) + if !present { + t.Error("No response present in PostgreSQL DB for given request") + } + if string(response) != "Test OCSP" { + t.Error("Incorrect response received from PostgreSQL DB") + } } -func TestSqliteRealResponse(t *testing.T) { +func TestRealResponse(t *testing.T) { + // Create SQLite DB and accossiated accessor. sqliteDBfile := "testdata/sqlite_test.db" - db := testdb.SQLiteDB(sqliteDBfile) - accessor := sql.NewAccessor(db) + sqlitedb := testdb.SQLiteDB(sqliteDBfile) + sqliteAccessor := sql.NewAccessor(sqlitedb) + + // Create MySQL DB and accossiated accessor. + mysqldb := testdb.MySQLDB() + mysqlAccessor := sql.NewAccessor(mysqldb) + + // Create PostgreSQL DB and accossiated accessor. + postgresdb := testdb.PostgreSQLDB() + postgresAccessor := sql.NewAccessor(postgresdb) certFile := "testdata/cert.pem" issuerFile := "testdata/ca.pem" @@ -297,17 +343,37 @@ func TestSqliteRealResponse(t *testing.T) { } // Use the created Accessor to create new DBSource. - src := NewDBSource(accessor) + sqliteSrc := NewDBSource(sqliteAccessor) + mysqlSrc := NewDBSource(mysqlAccessor) + postgresSrc := NewDBSource(postgresAccessor) // Call Response() method on constructed request and check the output. - response, present := src.Response(req) + // Then, attempt to parse the returned response and make sure it is well formed. + response, present := sqliteSrc.Response(req) + if !present { + t.Error("No response present in SQLite DB for given request") + } + _, err = goocsp.ParseResponse(response, issuer) + if err != nil { + t.Errorf("Error parsing SQLite response: %v", err) + } + + response, present = mysqlSrc.Response(req) + if !present { + t.Error("No response present in MySQL DB for given request") + } + _, err = goocsp.ParseResponse(response, issuer) + if err != nil { + t.Errorf("Error parsing MySQL response: %v", err) + } + + response, present = postgresSrc.Response(req) if !present { - t.Error("No response present for given request") + t.Error("No response present in PostgreSQL for given request") } - // Attempt to parse the returned response and make sure it is well formed. _, err = goocsp.ParseResponse(response, issuer) if err != nil { - t.Errorf("Error parsing response: %v", err) + t.Errorf("Error parsing PostgreSQL response: %v", err) } } From ae9007433a574b58187e0744529ca1bc2310eff2 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Sun, 23 Apr 2017 16:12:37 -0400 Subject: [PATCH 08/21] fix variable naming issue --- ocsp/responder_test.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index ec5e87ac2..69c6cb40c 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -337,9 +337,20 @@ func TestRealResponse(t *testing.T) { Expiry: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), Serial: req.SerialNumber.String(), } - err = accessor.InsertOCSP(ocsp) + + err = sqliteAccessor.InsertOCSP(ocsp) + if err != nil { + t.Errorf("Error inserting OCSP record into SQLite DB: %s", err) + } + + err = mysqlAccessor.InsertOCSP(ocsp) if err != nil { - t.Errorf("Error inserting OCSP record into DB: %s", err) + t.Errorf("Error inserting OCSP record into MySQL DB: %s", err) + } + + err = postgresAccessor.InsertOCSP(ocsp) + if err != nil { + t.Errorf("Error inserting OCSP record into PostgreSQL DB: %s", err) } // Use the created Accessor to create new DBSource. From 9adff05d33fb816a0cdf0ee09ed8708dc26c97b6 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Sun, 30 Apr 2017 16:31:22 -0400 Subject: [PATCH 09/21] add mysql ocsp responder tests --- ocsp/responder_test.go | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index 69c6cb40c..b61584a77 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -3,9 +3,11 @@ package ocsp import ( "encoding/hex" "io/ioutil" + "math/big" "net/http" "net/http/httptest" "net/url" + "strconv" "testing" "time" @@ -197,6 +199,9 @@ func TestResponseTrivial(t *testing.T) { if err != nil { t.Errorf("Error parsing OCSP request: %s", err) } + // Truncate the Serial Number so it can fit into the DB tables. + truncSN, err := strconv.Atoi(req.SerialNumber.String()[:20]) + req.SerialNumber = big.NewInt(int64(truncSN)) // Create SQLite DB and accossiated accessor. sqliteDBfile := "testdata/sqlite_test.db" @@ -208,8 +213,8 @@ func TestResponseTrivial(t *testing.T) { mysqlAccessor := sql.NewAccessor(mysqldb) // Create PostgreSQL DB and accossiated accessor. - postgresdb := testdb.PostgreSQLDB() - postgresAccessor := sql.NewAccessor(postgresdb) + /*postgresdb := testdb.PostgreSQLDB() + postgresAccessor := sql.NewAccessor(postgresdb)*/ // Populate the DB with the OCSPRecord, and check // that Response() handles the request appropiately. @@ -229,15 +234,15 @@ func TestResponseTrivial(t *testing.T) { t.Errorf("Error inserting OCSP record into MySQL DB: %s", err) } - err = postgresAccessor.InsertOCSP(ocsp) + /*err = postgresAccessor.InsertOCSP(ocsp) if err != nil { t.Errorf("Error inserting OCSP record into PostgreSQL DB: %s", err) - } + }*/ // Use the created Accessor to create a new DBSource. sqliteSrc := NewDBSource(sqliteAccessor) mysqlSrc := NewDBSource(mysqlAccessor) - postgresSrc := NewDBSource(postgresAccessor) + //postgresSrc := NewDBSource(postgresAccessor) // Call Response() method on constructed request and check the output. response, present := sqliteSrc.Response(req) @@ -256,13 +261,13 @@ func TestResponseTrivial(t *testing.T) { t.Error("Incorrect response received from MySQL DB") } - response, present = postgresSrc.Response(req) + /*response, present = postgresSrc.Response(req) if !present { t.Error("No response present in PostgreSQL DB for given request") } if string(response) != "Test OCSP" { t.Error("Incorrect response received from PostgreSQL DB") - } + }*/ } func TestRealResponse(t *testing.T) { @@ -276,8 +281,8 @@ func TestRealResponse(t *testing.T) { mysqlAccessor := sql.NewAccessor(mysqldb) // Create PostgreSQL DB and accossiated accessor. - postgresdb := testdb.PostgreSQLDB() - postgresAccessor := sql.NewAccessor(postgresdb) + /*postgresdb := testdb.PostgreSQLDB() + postgresAccessor := sql.NewAccessor(postgresdb)*/ certFile := "testdata/cert.pem" issuerFile := "testdata/ca.pem" @@ -348,15 +353,15 @@ func TestRealResponse(t *testing.T) { t.Errorf("Error inserting OCSP record into MySQL DB: %s", err) } - err = postgresAccessor.InsertOCSP(ocsp) + /*err = postgresAccessor.InsertOCSP(ocsp) if err != nil { t.Errorf("Error inserting OCSP record into PostgreSQL DB: %s", err) - } + }*/ // Use the created Accessor to create new DBSource. sqliteSrc := NewDBSource(sqliteAccessor) mysqlSrc := NewDBSource(mysqlAccessor) - postgresSrc := NewDBSource(postgresAccessor) + //postgresSrc := NewDBSource(postgresAccessor) // Call Response() method on constructed request and check the output. // Then, attempt to parse the returned response and make sure it is well formed. @@ -378,14 +383,14 @@ func TestRealResponse(t *testing.T) { t.Errorf("Error parsing MySQL response: %v", err) } - response, present = postgresSrc.Response(req) + /*response, present = postgresSrc.Response(req) if !present { t.Error("No response present in PostgreSQL for given request") } _, err = goocsp.ParseResponse(response, issuer) if err != nil { t.Errorf("Error parsing PostgreSQL response: %v", err) - } + }*/ } // Manually run the query "SELECT max(version_id) FROM goose_db_version;" From 6c2d7dcdfa5093385d560f5e55fc6409ddda6394 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Mon, 1 May 2017 16:11:44 -0400 Subject: [PATCH 10/21] fix postgres tests --- ocsp/responder_test.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index b61584a77..6fa351e33 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -213,8 +213,8 @@ func TestResponseTrivial(t *testing.T) { mysqlAccessor := sql.NewAccessor(mysqldb) // Create PostgreSQL DB and accossiated accessor. - /*postgresdb := testdb.PostgreSQLDB() - postgresAccessor := sql.NewAccessor(postgresdb)*/ + postgresdb := testdb.PostgreSQLDB() + postgresAccessor := sql.NewAccessor(postgresdb) // Populate the DB with the OCSPRecord, and check // that Response() handles the request appropiately. @@ -234,15 +234,15 @@ func TestResponseTrivial(t *testing.T) { t.Errorf("Error inserting OCSP record into MySQL DB: %s", err) } - /*err = postgresAccessor.InsertOCSP(ocsp) + err = postgresAccessor.InsertOCSP(ocsp) if err != nil { t.Errorf("Error inserting OCSP record into PostgreSQL DB: %s", err) - }*/ + } // Use the created Accessor to create a new DBSource. sqliteSrc := NewDBSource(sqliteAccessor) mysqlSrc := NewDBSource(mysqlAccessor) - //postgresSrc := NewDBSource(postgresAccessor) + postgresSrc := NewDBSource(postgresAccessor) // Call Response() method on constructed request and check the output. response, present := sqliteSrc.Response(req) @@ -261,13 +261,13 @@ func TestResponseTrivial(t *testing.T) { t.Error("Incorrect response received from MySQL DB") } - /*response, present = postgresSrc.Response(req) + response, present = postgresSrc.Response(req) if !present { t.Error("No response present in PostgreSQL DB for given request") } if string(response) != "Test OCSP" { t.Error("Incorrect response received from PostgreSQL DB") - }*/ + } } func TestRealResponse(t *testing.T) { @@ -281,8 +281,8 @@ func TestRealResponse(t *testing.T) { mysqlAccessor := sql.NewAccessor(mysqldb) // Create PostgreSQL DB and accossiated accessor. - /*postgresdb := testdb.PostgreSQLDB() - postgresAccessor := sql.NewAccessor(postgresdb)*/ + postgresdb := testdb.PostgreSQLDB() + postgresAccessor := sql.NewAccessor(postgresdb) certFile := "testdata/cert.pem" issuerFile := "testdata/ca.pem" @@ -353,15 +353,15 @@ func TestRealResponse(t *testing.T) { t.Errorf("Error inserting OCSP record into MySQL DB: %s", err) } - /*err = postgresAccessor.InsertOCSP(ocsp) + err = postgresAccessor.InsertOCSP(ocsp) if err != nil { t.Errorf("Error inserting OCSP record into PostgreSQL DB: %s", err) - }*/ + } // Use the created Accessor to create new DBSource. sqliteSrc := NewDBSource(sqliteAccessor) mysqlSrc := NewDBSource(mysqlAccessor) - //postgresSrc := NewDBSource(postgresAccessor) + postgresSrc := NewDBSource(postgresAccessor) // Call Response() method on constructed request and check the output. // Then, attempt to parse the returned response and make sure it is well formed. @@ -383,14 +383,14 @@ func TestRealResponse(t *testing.T) { t.Errorf("Error parsing MySQL response: %v", err) } - /*response, present = postgresSrc.Response(req) + response, present = postgresSrc.Response(req) if !present { t.Error("No response present in PostgreSQL for given request") } _, err = goocsp.ParseResponse(response, issuer) if err != nil { t.Errorf("Error parsing PostgreSQL response: %v", err) - }*/ + } } // Manually run the query "SELECT max(version_id) FROM goose_db_version;" From 32a6397312f943215dfda6e26ad3ed18defd4534 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Thu, 23 Mar 2017 16:07:49 -0400 Subject: [PATCH 11/21] add db backed ocspserve command functionality --- cli/ocspserve/ocspserve.go | 20 ++++++++++++++++++-- ocsp/responder.go | 15 +++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/cli/ocspserve/ocspserve.go b/cli/ocspserve/ocspserve.go index 9f8519fc9..af67a3fed 100644 --- a/cli/ocspserve/ocspserve.go +++ b/cli/ocspserve/ocspserve.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "github.com/cloudflare/cfssl/cli" "github.com/cloudflare/cfssl/log" @@ -35,9 +36,24 @@ func ocspServerMain(args []string, c cli.Config) error { return errors.New("no response file provided, please set the -responses flag") } - src, err := ocsp.NewSourceFromFile(c.Responses) + // Parse connection string representing source of OCSP responses + u, err := url.Parse(c.Responses) if err != nil { - return errors.New("unable to read response file") + return errors.New("unrecognized connection string format") + } + switch u.Scheme { + case "file": + src, err := ocsp.NewSourceFromFile(u.Path) + if err != nil { + return errors.New("unable to read response file") + } + case "sqlite3": + src, err := ocsp.NewSqliteSource(u.Path) + if err != nil { + return errors.New("unable to read Sqlite connection string") + } + default: + return errors.New("TODO") } log.Info("Registering OCSP responder handler") diff --git a/ocsp/responder.go b/ocsp/responder.go index 27f8a6f91..0898d3c8e 100644 --- a/ocsp/responder.go +++ b/ocsp/responder.go @@ -19,8 +19,10 @@ import ( "time" "github.com/cloudflare/cfssl/certdb" + "github.com/cloudflare/cfssl/certdb/sql" "github.com/cloudflare/cfssl/log" "github.com/jmhodges/clock" + "github.com/jmoiron/sqlx" "golang.org/x/crypto/ocsp" ) @@ -63,6 +65,19 @@ func NewDBSource(dbAccessor certdb.Accessor) Source { } } +// NewSqliteSource creates a new DBSource object with a Sqlite3 dbAccessor from +// a given Sqlite connection string. +// ?? Not sure where this function should live...this may not be the right place ?? +func NewSqliteSource(dbpath string) (Source, error) { + db, err := sqlx.Open("sqlite3", dbpath) + if err != nil { + return nil, err + } + accessor := sql.NewAccessor(db) + src := NewDBSource(accessor) + return src, nil +} + // Response implements cfssl.ocsp.responder.Source, which returns the // OCSP response in the Database for the given request with the expiration // date furthest in the future. Response also returns a bool that is false From 9839aaf4e1aa22fdec5249a83abeb2f9e99babb2 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Thu, 23 Mar 2017 17:34:39 -0400 Subject: [PATCH 12/21] add test for checking sqlite db connection --- cli/ocspserve/ocspserve.go | 16 ++++++++-------- helpers/helpers.go | 24 ++++++++++++++++++++---- helpers/helpers_test.go | 11 +++++++++++ ocsp/responder_test.go | 8 ++++++++ 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/cli/ocspserve/ocspserve.go b/cli/ocspserve/ocspserve.go index af67a3fed..ad7bcac17 100644 --- a/cli/ocspserve/ocspserve.go +++ b/cli/ocspserve/ocspserve.go @@ -8,6 +8,7 @@ import ( "net/url" "github.com/cloudflare/cfssl/cli" + "github.com/cloudflare/cfssl/helpers" "github.com/cloudflare/cfssl/log" "github.com/cloudflare/cfssl/ocsp" ) @@ -36,24 +37,23 @@ func ocspServerMain(args []string, c cli.Config) error { return errors.New("no response file provided, please set the -responses flag") } - // Parse connection string representing source of OCSP responses - u, err := url.Parse(c.Responses) + typ, path, err := helpers.ParseConnectionStr(c.Responses) if err != nil { - return errors.New("unrecognized connection string format") + return errors.New("unable to parse responses connection string") } - switch u.Scheme { + switch typ { case "file": - src, err := ocsp.NewSourceFromFile(u.Path) + src, err := ocsp.NewSourceFromFile(path) if err != nil { return errors.New("unable to read response file") } - case "sqlite3": - src, err := ocsp.NewSqliteSource(u.Path) + case "sqlite": + src, err := ocsp.NewSqliteSource(path) if err != nil { return errors.New("unable to read Sqlite connection string") } default: - return errors.New("TODO") + return errors.New("TODO not sure what to do in this case") } log.Info("Registering OCSP responder handler") diff --git a/helpers/helpers.go b/helpers/helpers.go index 3b3349ac2..73b101acc 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -19,11 +19,8 @@ import ( "io" "io/ioutil" "math/big" + "net/url" "os" - - "github.com/google/certificate-transparency/go" - "golang.org/x/crypto/ocsp" - "strings" "time" @@ -31,6 +28,8 @@ import ( cferr "github.com/cloudflare/cfssl/errors" "github.com/cloudflare/cfssl/helpers/derhelpers" "github.com/cloudflare/cfssl/log" + "github.com/google/certificate-transparency/go" + "golang.org/x/crypto/ocsp" "golang.org/x/crypto/pkcs12" ) @@ -644,5 +643,22 @@ func ReadBytes(valFile string) ([]byte, error) { default: return nil, fmt.Errorf("multiple prefixes: %s", strings.Join(splitVal[:len(splitVal)-1], ", ")) + +// ParseConnectionStr parses a string representing the source of OCSP responses +// which can either be a file or the path to a DB (Sqlite, MySQL or PostgreSQL). +// It returns the type of the connection string (e.g. "File" or "MySQL" etc.) as +// well as the path to the source. +func ParseConnectionStr(conn string) (string, string, error) { + u, err := url.Parse(conn) + if err != nil { + return "", "", err + } + switch u.Scheme { + case "", "file": + return "file", u.Path, nil + case "sqlite3": + return "sqlite", u.Path, nil + default: + return "DB", u.Path, nil } } diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go index c851700d9..c1531a7c7 100644 --- a/helpers/helpers_test.go +++ b/helpers/helpers_test.go @@ -623,5 +623,16 @@ func TestSCTListFromOCSPResponse(t *testing.T) { } if !sctEquals(zeroSCT, lst[0]) { t.Fatal("SCTs don't match") + +func TestParseConnectionStr(t *testing.T) { + filePath := "/path/to/file.txt" + typ, path, err := ParseConnectionStr(filePath) + if typ != "file" || path != "/path/to/file.txt" || err != nil { + t.Fatal("Incorrect parsing of file path") + } + sqliteStr := "sqlite3:///path/to/db/file.db" + typ, path, err = ParseConnectionStr(sqliteStr) + if typ != "sqlite" || path != "/path/to/db/file.db" || err != nil { + t.Fatal("Incorrect parsing of sqlite connection string") } } diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index 62e34ec75..b937fb5b5 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -310,3 +310,11 @@ func TestSqliteRealResponse(t *testing.T) { t.Errorf("Error parsing response: %v", err) } } + +func TestNewSqliteSource(t *testing.T) { + dbpath := "sqlite_test.db" + src, err := NewSqliteSource(dbpath) + if err != nil { + t.Errorf("Error connection to Sqlite DB:", err) + } +} From 75df0304a2a18bff1f95836ee5c1cdd962d0de95 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Mon, 1 May 2017 18:55:08 -0400 Subject: [PATCH 13/21] fix incorrect merge issues --- helpers/helpers.go | 2 ++ helpers/helpers_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/helpers/helpers.go b/helpers/helpers.go index 92afbeb29..c377404b3 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -643,6 +643,8 @@ func ReadBytes(valFile string) ([]byte, error) { default: return nil, fmt.Errorf("multiple prefixes: %s", strings.Join(splitVal[:len(splitVal)-1], ", ")) + } +} // ParseConnectionStr parses a string representing the source of OCSP responses // which can either be a file or the path to a DB (Sqlite, MySQL or PostgreSQL). diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go index 07e88f39c..e1c5b64d4 100644 --- a/helpers/helpers_test.go +++ b/helpers/helpers_test.go @@ -623,6 +623,8 @@ func TestSCTListFromOCSPResponse(t *testing.T) { } if !sctEquals(zeroSCT, lst[0]) { t.Fatal("SCTs don't match") + } +} func TestParseConnectionStr(t *testing.T) { filePath := "/path/to/file.txt" From 338b5ef5f2fe0ff5c1e3bedc739c2b9dec59fa95 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Mon, 1 May 2017 19:03:43 -0400 Subject: [PATCH 14/21] fix error message issues --- ocsp/responder_test.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index a7ec81b93..882c179c5 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -400,7 +400,7 @@ func TestNewSqliteSource(t *testing.T) { dbpath := "testdata/sqlite_test.db" _, err := NewSourceFromConnStr("sqlite3", dbpath) if err != nil { - t.Errorf("Error connecting to Sqlite DB:", err) + t.Errorf("Error connecting to Sqlite DB: %v", err) } } @@ -409,7 +409,7 @@ func TestNewMySQLSource(t *testing.T) { // Error should be thrown here if DB cannot be connected to. _, err := NewSourceFromConnStr("mysql", dbpath) if err != nil { - t.Errorf("Error connecting to MySQL DB:", err) + t.Errorf("Error connecting to MySQL DB: %v", err) } } @@ -418,14 +418,6 @@ func TestNewPostgresSource(t *testing.T) { // Error should be thrown here if DB cannot be connected to. _, err := NewSourceFromConnStr("postgres", dbpath) if err != nil { - t.Errorf("Error connecting to PostgreSQL DB:", err) - } -} - -func TestNewSqliteSource(t *testing.T) { - dbpath := "sqlite_test.db" - src, err := NewSqliteSource(dbpath) - if err != nil { - t.Errorf("Error connection to Sqlite DB:", err) + t.Errorf("Error connecting to PostgreSQL DB: %v", err) } } From e442dfa12ecf7f46d59911efe22438602af925e8 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Mon, 1 May 2017 19:26:53 -0400 Subject: [PATCH 15/21] fix package import issues and var declaration --- cli/ocspserve/ocspserve.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/ocspserve/ocspserve.go b/cli/ocspserve/ocspserve.go index de7716c3d..a2cbc9107 100644 --- a/cli/ocspserve/ocspserve.go +++ b/cli/ocspserve/ocspserve.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/http" - "net/url" "github.com/cloudflare/cfssl/cli" "github.com/cloudflare/cfssl/helpers" @@ -28,6 +27,7 @@ var ocspServerFlags = []string{"address", "port", "responses"} // ocspServerMain is the command line entry point to the OCSP responder. // It sets up a new HTTP server that responds to OCSP requests. func ocspServerMain(args []string, c cli.Config) error { + var src ocsp.Source // serve doesn't support arguments. if len(args) > 0 { return errors.New("argument is provided but not defined; please refer to the usage by flag -h") @@ -43,22 +43,22 @@ func ocspServerMain(args []string, c cli.Config) error { } switch typ { case "file": - src, err := ocsp.NewSourceFromFile(path) + src, err = ocsp.NewSourceFromFile(path) if err != nil { return errors.New("unable to read response file") } case "sqlite": - src, err := ocsp.NewSourceFromConnStr("sqlite", path) + src, err = ocsp.NewSourceFromConnStr("sqlite", path) if err != nil { return errors.New("unable to read Sqlite connection string") } case "mysql": - src, err := ocsp.NewSourceFromConnStr("mysql", path) + src, err = ocsp.NewSourceFromConnStr("mysql", path) if err != nil { return errors.New("unable to read MySQL connection string") } case "postgres": - src, err := ocsp.NewSourceFromConnStr("postgres", path) + src, err = ocsp.NewSourceFromConnStr("postgres", path) if err != nil { return errors.New("unable to read PostgreSQL connection string") } From b0fb7301d21460861e107a029ac232b19e99b6ea Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Mon, 1 May 2017 19:40:14 -0400 Subject: [PATCH 16/21] fix unused var --- ocsp/responder_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index 882c179c5..262391d82 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -200,7 +200,7 @@ func TestResponseTrivial(t *testing.T) { t.Errorf("Error parsing OCSP request: %s", err) } // Truncate the Serial Number so it can fit into the DB tables. - truncSN, err := strconv.Atoi(req.SerialNumber.String()[:20]) + truncSN, _ := strconv.Atoi(req.SerialNumber.String()[:20]) req.SerialNumber = big.NewInt(int64(truncSN)) // Create SQLite DB and accossiated accessor. From 986b4a3dbf53826f5786f7ff1a71b95e71ea8968 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Tue, 2 May 2017 12:30:13 -0400 Subject: [PATCH 17/21] fix issues with postgres foreign key constraints --- ocsp/responder_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index 262391d82..7c78bb2ad 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -234,6 +234,25 @@ func TestResponseTrivial(t *testing.T) { t.Errorf("Error inserting OCSP record into MySQL DB: %s", err) } + // Need to create and insert Certificate record into PostgreSQL + // before inserting OCSP record due to foreign key constraints + // of the Postgres tables. + cert_rec := certdb.CertificateRecord{ + Serial: req.SerialNumber.String(), + AKI: hex.EncodeToString(req.IssuerKeyHash), + CALabel: "Example Certificate", + Status: "Good", + Reason: 1, + Expiry: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), + RevokedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), + PEM: "PEM", + } + + err = postgresAccessor.InsertCertificate(cert_rec) + if err != nil { + t.Errorf("Error inserting Certificate record into PostgreSQL DB: %s", err) + } + err = postgresAccessor.InsertOCSP(ocsp) if err != nil { t.Errorf("Error inserting OCSP record into PostgreSQL DB: %s", err) @@ -353,6 +372,25 @@ func TestRealResponse(t *testing.T) { t.Errorf("Error inserting OCSP record into MySQL DB: %s", err) } + // Need to create and insert Certificate record into PostgreSQL + // before inserting OCSP record due to foreign key constraints + // of the Postgres tables. + cert_rec := certdb.CertificateRecord{ + Serial: req.SerialNumber.String(), + AKI: hex.EncodeToString(req.IssuerKeyHash), + CALabel: "Example Certificate", + Status: "Good", + Reason: 1, + Expiry: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), + RevokedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), + PEM: "PEM", + } + + err = postgresAccessor.InsertCertificate(cert_rec) + if err != nil { + t.Errorf("Error inserting Certificate record into PostgreSQL DB: %s", err) + } + err = postgresAccessor.InsertOCSP(ocsp) if err != nil { t.Errorf("Error inserting OCSP record into PostgreSQL DB: %s", err) From 8215b9a89f57e51f46e60444b6baa8f2ec99de0e Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Tue, 2 May 2017 12:52:00 -0400 Subject: [PATCH 18/21] fix variable naming convention issue --- ocsp/responder_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index 7c78bb2ad..35d591103 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -237,7 +237,7 @@ func TestResponseTrivial(t *testing.T) { // Need to create and insert Certificate record into PostgreSQL // before inserting OCSP record due to foreign key constraints // of the Postgres tables. - cert_rec := certdb.CertificateRecord{ + certRec := certdb.CertificateRecord{ Serial: req.SerialNumber.String(), AKI: hex.EncodeToString(req.IssuerKeyHash), CALabel: "Example Certificate", @@ -248,7 +248,7 @@ func TestResponseTrivial(t *testing.T) { PEM: "PEM", } - err = postgresAccessor.InsertCertificate(cert_rec) + err = postgresAccessor.InsertCertificate(certRec) if err != nil { t.Errorf("Error inserting Certificate record into PostgreSQL DB: %s", err) } @@ -375,7 +375,7 @@ func TestRealResponse(t *testing.T) { // Need to create and insert Certificate record into PostgreSQL // before inserting OCSP record due to foreign key constraints // of the Postgres tables. - cert_rec := certdb.CertificateRecord{ + certRec := certdb.CertificateRecord{ Serial: req.SerialNumber.String(), AKI: hex.EncodeToString(req.IssuerKeyHash), CALabel: "Example Certificate", @@ -386,7 +386,7 @@ func TestRealResponse(t *testing.T) { PEM: "PEM", } - err = postgresAccessor.InsertCertificate(cert_rec) + err = postgresAccessor.InsertCertificate(certRec) if err != nil { t.Errorf("Error inserting Certificate record into PostgreSQL DB: %s", err) } From 1e577fe5562e9db6eb0eb984b384eadd3cac1ea9 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Wed, 3 May 2017 20:41:50 -0400 Subject: [PATCH 19/21] fix func naming --- cli/ocspserve/ocspserve.go | 8 ++++---- helpers/helpers.go | 2 +- helpers/helpers_test.go | 10 +++++----- ocsp/responder.go | 2 +- ocsp/responder_test.go | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cli/ocspserve/ocspserve.go b/cli/ocspserve/ocspserve.go index a2cbc9107..31d0300d7 100644 --- a/cli/ocspserve/ocspserve.go +++ b/cli/ocspserve/ocspserve.go @@ -37,7 +37,7 @@ func ocspServerMain(args []string, c cli.Config) error { return errors.New("no response file provided, please set the -responses flag") } - typ, path, err := helpers.ParseConnectionStr(c.Responses) + typ, path, err := helpers.ParseConnString(c.Responses) if err != nil { return errors.New("unable to parse responses connection string") } @@ -48,17 +48,17 @@ func ocspServerMain(args []string, c cli.Config) error { return errors.New("unable to read response file") } case "sqlite": - src, err = ocsp.NewSourceFromConnStr("sqlite", path) + src, err = ocsp.NewSourceFromConnString("sqlite", path) if err != nil { return errors.New("unable to read Sqlite connection string") } case "mysql": - src, err = ocsp.NewSourceFromConnStr("mysql", path) + src, err = ocsp.NewSourceFromConnString("mysql", path) if err != nil { return errors.New("unable to read MySQL connection string") } case "postgres": - src, err = ocsp.NewSourceFromConnStr("postgres", path) + src, err = ocsp.NewSourceFromConnString("postgres", path) if err != nil { return errors.New("unable to read PostgreSQL connection string") } diff --git a/helpers/helpers.go b/helpers/helpers.go index c377404b3..9c479259d 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -650,7 +650,7 @@ func ReadBytes(valFile string) ([]byte, error) { // which can either be a file or the path to a DB (Sqlite, MySQL or PostgreSQL). // It returns the type of the connection string (e.g. "File" or "MySQL" etc.) as // well as the path to the source. -func ParseConnectionStr(conn string) (string, string, error) { +func ParseConnString(conn string) (string, string, error) { u, err := url.Parse(conn) if err != nil { return "", "", err diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go index e1c5b64d4..8d7daac0e 100644 --- a/helpers/helpers_test.go +++ b/helpers/helpers_test.go @@ -626,24 +626,24 @@ func TestSCTListFromOCSPResponse(t *testing.T) { } } -func TestParseConnectionStr(t *testing.T) { +func TestParseConnString(t *testing.T) { filePath := "/path/to/file.txt" - typ, path, err := ParseConnectionStr(filePath) + typ, path, err := ParseConnString(filePath) if typ != "file" || path != "/path/to/file.txt" || err != nil { t.Fatal("Incorrect parsing of file path") } sqliteStr := "sqlite3:///path/to/db/file.db" - typ, path, err = ParseConnectionStr(sqliteStr) + typ, path, err = ParseConnString(sqliteStr) if typ != "sqlite" || path != "/path/to/db/file.db" || err != nil { t.Fatal("Incorrect parsing of sqlite connection string") } mysqlStr := "mysql://root@tcp(localhost:3306)/certdb_development?parseTime=true" - typ, path, err = ParseConnectionStr(mysqlStr) + typ, path, err = ParseConnString(mysqlStr) if typ != "mysql" || path != "root@tcp(localhost:3306)/certdb_development?parseTime=true" || err != nil { t.Fatal("Incorrect parsing of MySQL connection string") } postgresStr := "postgresql://root@tcp(localhost:3306)/certdb_development?parseTime=true" - typ, path, err = ParseConnectionStr(postgresStr) + typ, path, err = ParseConnString(postgresStr) if typ != "postgres" || path != "dbname=certdb_development sslmode=disable" || err != nil { t.Fatal("Incorrect parsing of PostgreSQL connection string") } diff --git a/ocsp/responder.go b/ocsp/responder.go index cc23191ed..fdefb48c8 100644 --- a/ocsp/responder.go +++ b/ocsp/responder.go @@ -70,7 +70,7 @@ func NewDBSource(dbAccessor certdb.Accessor) Source { // argument, and this function currently supports Sqlite, MySQL and PostgreSQL. // The dbpath argument is a connection string aiding in connecting to the // associated DB type. -func NewSourceFromConnStr(typ, dbpath string) (Source, error) { +func NewSourceFromConnString(typ, dbpath string) (Source, error) { db, err := sqlx.Open(typ, dbpath) if err != nil { return nil, err diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index 35d591103..f83534e41 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -436,7 +436,7 @@ func TestRealResponse(t *testing.T) { // DB was properly connected to. func TestNewSqliteSource(t *testing.T) { dbpath := "testdata/sqlite_test.db" - _, err := NewSourceFromConnStr("sqlite3", dbpath) + _, err := NewSourceFromConnString("sqlite3", dbpath) if err != nil { t.Errorf("Error connecting to Sqlite DB: %v", err) } @@ -445,7 +445,7 @@ func TestNewSqliteSource(t *testing.T) { func TestNewMySQLSource(t *testing.T) { dbpath := "root@tcp(localhost:3306)/certdb_development?parseTime=true" // Error should be thrown here if DB cannot be connected to. - _, err := NewSourceFromConnStr("mysql", dbpath) + _, err := NewSourceFromConnString("mysql", dbpath) if err != nil { t.Errorf("Error connecting to MySQL DB: %v", err) } @@ -454,7 +454,7 @@ func TestNewMySQLSource(t *testing.T) { func TestNewPostgresSource(t *testing.T) { dbpath := "dbname=certdb_development sslmode=disable" // Error should be thrown here if DB cannot be connected to. - _, err := NewSourceFromConnStr("postgres", dbpath) + _, err := NewSourceFromConnString("postgres", dbpath) if err != nil { t.Errorf("Error connecting to PostgreSQL DB: %v", err) } From ab7769df22889b1f406e71ff0666719d75021c00 Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Wed, 3 May 2017 20:48:01 -0400 Subject: [PATCH 20/21] fix function header conventions --- helpers/helpers.go | 2 +- ocsp/responder.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/helpers/helpers.go b/helpers/helpers.go index 9c479259d..17845eccc 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -646,7 +646,7 @@ func ReadBytes(valFile string) ([]byte, error) { } } -// ParseConnectionStr parses a string representing the source of OCSP responses +// ParseConnString parses a string representing the source of OCSP responses // which can either be a file or the path to a DB (Sqlite, MySQL or PostgreSQL). // It returns the type of the connection string (e.g. "File" or "MySQL" etc.) as // well as the path to the source. diff --git a/ocsp/responder.go b/ocsp/responder.go index fdefb48c8..8a65986df 100644 --- a/ocsp/responder.go +++ b/ocsp/responder.go @@ -65,7 +65,7 @@ func NewDBSource(dbAccessor certdb.Accessor) Source { } } -// NewSourceFromConnStr creates a new DBSource object with an associated +// NewSourceFromConnString creates a new DBSource object with an associated // dbAccessor. They type of the DB connection is specificied by the typ // argument, and this function currently supports Sqlite, MySQL and PostgreSQL. // The dbpath argument is a connection string aiding in connecting to the From 4dea321988ca0e93c1d5fb590ed7ce81afe75d3e Mon Sep 17 00:00:00 2001 From: Brad Tiller Date: Wed, 10 May 2017 10:35:53 -0400 Subject: [PATCH 21/21] squash last 21 commits --- api/client/client.go | 25 ++- api/client/group.go | 9 +- api/crl/crl.go | 5 +- .../migrations/001_CreateCertificates.sql | 2 +- certdb/ocspstapling/ocspstapling.go | 118 +++++++++++ certdb/ocspstapling/ocspstapling_test.go | 158 +++++++++++++++ certdb/testdb/certstore_development.db | Bin 13312 -> 13312 bytes cli/config.go | 4 +- cli/crl/crl.go | 5 +- cli/gencert/gencert_test.go | 124 ++++++++++++ cli/ocspserve/ocspserve.go | 30 ++- helpers/helpers.go | 152 +++++++++++++- helpers/helpers_test.go | 150 ++++++++++++++ initca/initca.go | 10 +- ocsp/ocsp.go | 2 +- ocsp/responder.go | 17 ++ ocsp/responder_test.go | 186 ++++++++++++++++-- revoke/revoke.go | 18 +- signer/local/local.go | 24 +-- 19 files changed, 966 insertions(+), 73 deletions(-) create mode 100644 certdb/ocspstapling/ocspstapling.go create mode 100644 certdb/ocspstapling/ocspstapling_test.go diff --git a/api/client/client.go b/api/client/client.go index 7b3a39ea1..c34db5ffb 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -24,9 +24,10 @@ import ( // A server points to a single remote CFSSL instance. type server struct { - URL string - TLSConfig *tls.Config - reqModifier func(*http.Request, []byte) + URL string + TLSConfig *tls.Config + reqModifier func(*http.Request, []byte) + RequestTimeout time.Duration } // A Remote points to at least one (but possibly multiple) remote @@ -40,6 +41,7 @@ type Remote interface { Info(jsonData []byte) (*info.Resp, error) Hosts() []string SetReqModifier(func(*http.Request, []byte)) + SetRequestTimeout(d time.Duration) } // NewServer sets up a new server target. The address should be of @@ -62,9 +64,10 @@ func NewServerTLS(addr string, tlsConfig *tls.Config) Remote { } else { u, err := normalizeURL(addrs[0]) if err != nil { + log.Errorf("bad url: %v", err) return nil } - srv, _ := newServer(u, tlsConfig) + srv := newServer(u, tlsConfig) if srv != nil { remote = srv } @@ -80,9 +83,16 @@ func (srv *server) SetReqModifier(mod func(*http.Request, []byte)) { srv.reqModifier = mod } -func newServer(u *url.URL, tlsConfig *tls.Config) (*server, error) { +func (srv *server) SetRequestTimeout(timeout time.Duration) { + srv.RequestTimeout = timeout +} + +func newServer(u *url.URL, tlsConfig *tls.Config) *server { URL := u.String() - return &server{URL, tlsConfig, nil}, nil + return &server{ + URL: URL, + TLSConfig: tlsConfig, + } } func (srv *server) getURL(endpoint string) string { @@ -104,6 +114,9 @@ func (srv *server) post(url string, jsonData []byte) (*api.Response, error) { if srv.TLSConfig != nil { client.Transport = srv.createTLSTransport() } + if srv.RequestTimeout != 0 { + client.Timeout = srv.RequestTimeout + } req, err := http.NewRequest("POST", url, bytes.NewReader(jsonData)) if err != nil { err = fmt.Errorf("failed POST to %s: %v", url, err) diff --git a/api/client/group.go b/api/client/group.go index ac2d22b21..6beaadcbd 100644 --- a/api/client/group.go +++ b/api/client/group.go @@ -5,6 +5,7 @@ import ( "errors" "net/http" "strings" + "time" "github.com/cloudflare/cfssl/auth" "github.com/cloudflare/cfssl/info" @@ -49,7 +50,7 @@ func NewGroup(remotes []string, tlsConfig *tls.Config, strategy Strategy) (Remot if err != nil { return nil, err } - servers[i], _ = newServer(u, tlsConfig) + servers[i] = newServer(u, tlsConfig) } switch strategy { @@ -73,6 +74,12 @@ func (g *orderedListGroup) Hosts() []string { return hosts } +func (g *orderedListGroup) SetRequestTimeout(timeout time.Duration) { + for _, srv := range g.remotes { + srv.SetRequestTimeout(timeout) + } +} + func newOrdererdListGroup(remotes []*server) (Remote, error) { return &orderedListGroup{ remotes: remotes, diff --git a/api/crl/crl.go b/api/crl/crl.go index ea3184caf..9e84c12e8 100644 --- a/api/crl/crl.go +++ b/api/crl/crl.go @@ -4,7 +4,6 @@ package crl import ( "crypto" "crypto/x509" - "io/ioutil" "net/http" "os" "time" @@ -27,12 +26,12 @@ type Handler struct { // NewHandler returns a new http.Handler that handles a revoke request. func NewHandler(dbAccessor certdb.Accessor, caPath string, caKeyPath string) (http.Handler, error) { - ca, err := ioutil.ReadFile(caPath) + ca, err := helpers.ReadBytes(caPath) if err != nil { return nil, err } - caKey, err := ioutil.ReadFile(caKeyPath) + caKey, err := helpers.ReadBytes(caKeyPath) if err != nil { return nil, errors.Wrap(errors.PrivateKeyError, errors.ReadFailed, err) } diff --git a/certdb/mysql/migrations/001_CreateCertificates.sql b/certdb/mysql/migrations/001_CreateCertificates.sql index 1b2bb80f7..f242bc62d 100644 --- a/certdb/mysql/migrations/001_CreateCertificates.sql +++ b/certdb/mysql/migrations/001_CreateCertificates.sql @@ -14,7 +14,7 @@ CREATE TABLE certificates ( ); CREATE TABLE ocsp_responses ( - serial_number varbinary(20) NOT NULL, + serial_number varbinary(128) NOT NULL, authority_key_identifier varbinary(128) NOT NULL, body varbinary(4096) NOT NULL, expiry timestamp DEFAULT '0000-00-00 00:00:00', diff --git a/certdb/ocspstapling/ocspstapling.go b/certdb/ocspstapling/ocspstapling.go new file mode 100644 index 000000000..0ad71e4a2 --- /dev/null +++ b/certdb/ocspstapling/ocspstapling.go @@ -0,0 +1,118 @@ +// Package ocspstapling implements OCSP stapling of Signed Certificate +// Timestamps (SCTs) into OCSP responses in a database. See RFC 6962. +package ocspstapling + +import ( + "crypto" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/base64" + "errors" + + "github.com/cloudflare/cfssl/certdb" + cferr "github.com/cloudflare/cfssl/errors" + "github.com/cloudflare/cfssl/helpers" + "github.com/google/certificate-transparency/go" + "golang.org/x/crypto/ocsp" +) + +// sctExtOid is the OID of the OCSP Stapling SCT extension (see section 3.3. of RFC 6962). +var sctExtOid = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 5} + +// StapleSCTList inserts a list of Signed Certificate Timestamps into all OCSP +// responses in a database wrapped by a given certdb.Accessor. +// +// NOTE: This function is patterned after the exported Sign method in +// https://github.com/cloudflare/cfssl/blob/master/signer/local/local.go +func StapleSCTList(acc certdb.Accessor, serial, aki string, scts []ct.SignedCertificateTimestamp, + responderCert, issuer *x509.Certificate, priv crypto.Signer) error { + ocspRecs, err := acc.GetOCSP(serial, aki) + if err != nil { + return err + } + + if len(ocspRecs) == 0 { + return cferr.Wrap(cferr.CertStoreError, cferr.RecordNotFound, errors.New("empty OCSPRecord")) + } + + // This loop adds the SCTs to each OCSP response in ocspRecs. + for _, rec := range ocspRecs { + der, err := base64.StdEncoding.DecodeString(rec.Body) + if err != nil { + return cferr.Wrap(cferr.CertificateError, cferr.DecodeFailed, + errors.New("failed to decode Base64-encoded OCSP response")) + } + + response, err := ocsp.ParseResponse(der, nil) + if err != nil { + return cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, + errors.New("failed to parse DER-encoded OCSP response")) + } + + serializedSCTList, err := helpers.SerializeSCTList(scts) + if err != nil { + return cferr.Wrap(cferr.CTError, cferr.Unknown, + errors.New("failed to serialize SCT list")) + } + + serializedSCTList, err = asn1.Marshal(serializedSCTList) + if err != nil { + return cferr.Wrap(cferr.CTError, cferr.Unknown, + errors.New("failed to serialize SCT list")) + } + + sctExtension := pkix.Extension{ + Id: sctExtOid, + Critical: false, + Value: serializedSCTList, + } + + // This loop finds the SCTListExtension in the ocsp response. + var idxExt int + for _, ext := range response.Extensions { + if ext.Id.Equal(sctExtOid) { + break + } + idxExt++ + } + + newExtensions := make([]pkix.Extension, len(response.Extensions)) + copy(newExtensions, response.Extensions) + if idxExt >= len(response.Extensions) { + // No SCT extension was found. + newExtensions = append(newExtensions, sctExtension) + } else { + newExtensions[idxExt] = sctExtension + } + + // Here we write the updated extensions to replace the old + // response extensions when re-marshalling. + newSN := *response.SerialNumber + template := ocsp.Response{ + Status: response.Status, + SerialNumber: &newSN, + ThisUpdate: response.ThisUpdate, + NextUpdate: response.NextUpdate, + Certificate: response.Certificate, + ExtraExtensions: newExtensions, + IssuerHash: response.IssuerHash, + } + + // Finally, we re-sign the response to generate the new + // DER-encoded response. + der, err = ocsp.CreateResponse(issuer, responderCert, template, priv) + if err != nil { + return cferr.Wrap(cferr.CTError, cferr.Unknown, + errors.New("failed to sign new OCSP response")) + } + + body := base64.StdEncoding.EncodeToString(der) + err = acc.UpdateOCSP(serial, aki, body, rec.Expiry) + if err != nil { + return err + } + } + + return nil +} diff --git a/certdb/ocspstapling/ocspstapling_test.go b/certdb/ocspstapling/ocspstapling_test.go new file mode 100644 index 000000000..59f229388 --- /dev/null +++ b/certdb/ocspstapling/ocspstapling_test.go @@ -0,0 +1,158 @@ +package ocspstapling + +import ( + "bytes" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "math/big" + "os" + "testing" + + "github.com/cloudflare/cfssl/certdb" + "github.com/cloudflare/cfssl/certdb/sql" + "github.com/cloudflare/cfssl/certdb/testdb" + "github.com/cloudflare/cfssl/helpers" + "github.com/google/certificate-transparency/go" + "golang.org/x/crypto/ocsp" +) + +func TestStapleSCTList(t *testing.T) { + // issuer is a CA certificate. + issuer, issuerPrivKey, err := makeCert(nil) + if err != nil { + t.Fatal(err) + } + + // responderCert is a certificate for which to make an OCSP response. + responderCert, _, err := makeCert(issuer) + if err != nil { + t.Fatal(err) + } + + template := ocsp.Response{ + SerialNumber: responderCert.SerialNumber, + IssuerHash: crypto.SHA256, + Status: ocsp.Good, + } + + // respDER is an OCSP response to be added to the database. + respDER, err := ocsp.CreateResponse(issuer, responderCert, template, issuerPrivKey) + if err != nil { + t.Fatal(err) + } + + // testDB is an empty DB of OCSP responses. + gopath := os.Getenv("GOPATH") + dbPath := gopath + "/src/github.com/cloudflare/cfssl/certdb/testdb/certstore_development.db" + testDB := sql.NewAccessor(testdb.SQLiteDB(dbPath)) + + // Next, we store the OCSP response in the DB. + respSN := responderCert.SerialNumber.Text(16) + testDB.InsertOCSP(certdb.OCSPRecord{ + Serial: respSN, + Body: base64.StdEncoding.EncodeToString(respDER), + AKI: "Cornell CS 5152", + }) + + var zeroSCT ct.SignedCertificateTimestamp + err = StapleSCTList(testDB, respSN, "Cornell CS 5152", []ct.SignedCertificateTimestamp{zeroSCT}, + responderCert, issuer, issuerPrivKey) + if err != nil { + t.Fatal(err) + } + + // Lastly, we verify that the SCT was inserted. + recs, err := testDB.GetOCSP(respSN, "Cornell CS 5152") + if err != nil { + t.Fatal(err) + } + if len(recs) == 0 { + t.Fatal("SCT could not be retrieved from DB:", zeroSCT) + } + + respDER, err = base64.StdEncoding.DecodeString(recs[0].Body) + if err != nil { + t.Fatal(err) + } + + response, err := ocsp.ParseResponse(respDER, issuer) + if err != nil { + t.Fatal(err) + } + + scts, err := helpers.SCTListFromOCSPResponse(response) + if err != nil { + t.Fatal(err) + } + if len(scts) == 0 { + t.Fatal("No SCTs in OCSP response:", response) + } + + // Here, we check the equivalence of the SCT we inserted with the SCT + // returned by SCTListFromOCSPResponse. + + // sctEquals returns true if all fields of both SCTs are equivalent. + sctEquals := func(sctA, sctB ct.SignedCertificateTimestamp) bool { + if sctA.SCTVersion == sctB.SCTVersion && + sctA.LogID == sctB.LogID && + sctA.Timestamp == sctB.Timestamp && + bytes.Equal(sctA.Extensions, sctB.Extensions) && + sctA.Signature.Algorithm == sctB.Signature.Algorithm && + bytes.Equal(sctA.Signature.Signature, sctA.Signature.Signature) { + return true + } + return false + } + + if !sctEquals(scts[0], zeroSCT) { + t.Fatal("SCTs do not match:", "\nGot --", scts[0], "\nExpected --", zeroSCT) + } +} + +// serialCounter stores the next serial number to be issued by nextSN. +var serialCounter int64 + +// nextSN returns a new big.Int for creating x509 certificates. +func nextSN() *big.Int { + i := big.NewInt(serialCounter) + serialCounter++ + return i +} + +// makeCert returns a new x509 certificate with the given issuer certificate. +// If issuer is nil, the certificate is self-signed. +func makeCert(issuer *x509.Certificate) (*x509.Certificate, crypto.Signer, error) { + // Create a new private key + privKey, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + return nil, nil, err + } + + template := x509.Certificate{ + SerialNumber: nextSN(), + Subject: pkix.Name{ + Organization: []string{"Cornell CS 5152"}, + }, + AuthorityKeyId: []byte{42, 42, 42, 42}, + } + + if issuer == nil { // the cert is self-signed + issuer = &template + } + + der, err := x509.CreateCertificate(rand.Reader, &template, issuer, privKey.Public(), privKey) + if err != nil { + return nil, nil, err + } + + cert, err := x509.ParseCertificate(der) + if err != nil { + return nil, nil, err + } + + return cert, privKey, nil +} diff --git a/certdb/testdb/certstore_development.db b/certdb/testdb/certstore_development.db index 874ae08f1b660bede9c59cc33d021171abf62c0a..b13591d658088db140dc2bb601b6b0595a5e27ce 100644 GIT binary patch delta 636 zcmXYv%Wl&^6o#ERK$?;TqI6S`SR$D!jla9wmRpHhb{;NSSIVBJDDb_TfTZ!4)-}C6&pF%E+Y{gzjpMYzhIEYzfz4c?d&Yg1QNj zgf2Q`L_8BNofFbtwN71A3Yw5+RVcXUcu6xu0a%0OOrM83%Nw1uPB<9$s|8C?OCI%Y zU^cTYb38OQWMW>(e}B!wn~ zIgB8+i>LqsuTnYpdRMLDU;-*ie<_Ok{OW``*jowhWu6q2K(S^eyJ{F2KZA8KfOE^7 zLQV5S&(X#MZF-ukRU7pl)GrGqvMzP}nOJeNb4sp!K_w1V@uD*>Pj(3ck|L}4!0R=xZ zFn?w~%e)CFxP*D~CgC711{Ma8!Ww2BpqLzUy&p5Hx~zV1T4HvpLUC$QW@3(lF^ChN zn4Jk0Ov+EGR47U<%g;_tF)}bT(={;CH8fK&G_o=>vNAQ%voy6ZF*YzYGSW4$ure@k zV*;8dk7AxtT4H+*BO6<6UvRKc!=06%mi{}Jn3SAqY@D1n`GK^wN1~yjrKLd=^L#Ew zh6PQ`Gl0w~Y@FI`jI6Be28@g>P0X!8p+ crL-q2*u+gT>>|8OAZ0+z1EM#J7`$Ny0NKb>;{X5v diff --git a/cli/config.go b/cli/config.go index 53f142494..de7b45162 100644 --- a/cli/config.go +++ b/cli/config.go @@ -75,8 +75,8 @@ func registerFlags(c *Config, f *flag.FlagSet) { f.StringVar(&c.Hostname, "hostname", "", "Hostname for the cert, could be a comma-separated hostname list") f.StringVar(&c.CertFile, "cert", "", "Client certificate that contains the public key") f.StringVar(&c.CSRFile, "csr", "", "Certificate signature request file for new public key") - f.StringVar(&c.CAFile, "ca", "", "CA used to sign the new certificate") - f.StringVar(&c.CAKeyFile, "ca-key", "", "CA private key") + f.StringVar(&c.CAFile, "ca", "", "CA used to sign the new certificate -- accepts '[file:]fname' or 'env:varname'") + f.StringVar(&c.CAKeyFile, "ca-key", "", "CA private key -- accepts '[file:]fname' or 'env:varname'") f.StringVar(&c.TLSCertFile, "tls-cert", "", "Other endpoint CA to set up TLS protocol") f.StringVar(&c.TLSKeyFile, "tls-key", "", "Other endpoint CA private key") f.StringVar(&c.MutualTLSCAFile, "mutual-tls-ca", "", "Mutual TLS - require clients be signed by this CA ") diff --git a/cli/crl/crl.go b/cli/crl/crl.go index 711a7265e..06bfcb2fa 100644 --- a/cli/crl/crl.go +++ b/cli/crl/crl.go @@ -2,7 +2,6 @@ package crl import ( - "io/ioutil" "os" "github.com/cloudflare/cfssl/certdb/dbconf" @@ -50,12 +49,12 @@ func generateCRL(c cli.Config) (crlBytes []byte, err error) { dbAccessor := certsql.NewAccessor(db) log.Debug("loading CA: ", c.CAFile) - ca, err := ioutil.ReadFile(c.CAFile) + ca, err := helpers.ReadBytes(c.CAFile) if err != nil { return nil, err } log.Debug("loading CA key: ", c.CAKeyFile) - cakey, err := ioutil.ReadFile(c.CAKeyFile) + cakey, err := helpers.ReadBytes(c.CAKeyFile) if err != nil { return nil, cferr.Wrap(cferr.CertificateError, cferr.ReadFailed, err) } diff --git a/cli/gencert/gencert_test.go b/cli/gencert/gencert_test.go index 2be327f5a..020b641c7 100644 --- a/cli/gencert/gencert_test.go +++ b/cli/gencert/gencert_test.go @@ -1,6 +1,8 @@ package gencert import ( + "io/ioutil" + "os" "testing" "github.com/cloudflare/cfssl/cli" @@ -49,6 +51,128 @@ func TestGencertMain(t *testing.T) { } } +func TestGencertFile(t *testing.T) { + c := cli.Config{ + IsCA: true, + CAKeyFile: "file:../testdata/ca-key.pem", + } + + err := gencertMain([]string{"../testdata/csr.json"}, c) + if err != nil { + t.Fatal(err) + } + + c = cli.Config{ + CAFile: "file:../testdata/ca.pem", + CAKeyFile: "file:../testdata/ca-key.pem", + } + + err = gencertMain([]string{"../testdata/csr.json"}, c) + if err != nil { + t.Fatal(err) + } + + c = cli.Config{ + RenewCA: true, + CAFile: "file:../testdata/ca.pem", + CAKeyFile: "file:../testdata/ca-key.pem", + } + err = gencertMain([]string{}, c) + + if err != nil { + t.Fatal(err) + } +} + +func TestGencertEnv(t *testing.T) { + tempCaCert, _ := ioutil.ReadFile("../testdata/ca.pem") + tempCaKey, _ := ioutil.ReadFile("../testdata/ca-key.pem") + os.Setenv("ca", string(tempCaCert)) + os.Setenv("ca_key", string(tempCaKey)) + + c := cli.Config{ + IsCA: true, + CAKeyFile: "env:ca_key", + } + + err := gencertMain([]string{"../testdata/csr.json"}, c) + if err != nil { + t.Fatal(err) + } + + c = cli.Config{ + CAFile: "env:ca", + CAKeyFile: "env:ca_key", + } + + err = gencertMain([]string{"../testdata/csr.json"}, c) + if err != nil { + t.Fatal(err) + } + + c = cli.Config{ + RenewCA: true, + CAFile: "env:ca", + CAKeyFile: "env:ca_key", + } + err = gencertMain([]string{}, c) + + if err != nil { + t.Fatal(err) + } +} + +func TestBadGencertEnv(t *testing.T) { + tempCaCert, _ := ioutil.ReadFile("../testdata/ca.pem") + tempCaKey, _ := ioutil.ReadFile("../testdata/ca-key.pem") + os.Setenv("ca", string(tempCaCert)) + os.Setenv("ca_key", string(tempCaKey)) + + c := cli.Config{ + RenewCA: true, + CAFile: "ca", + CAKeyFile: "env:ca_key", + } + err := gencertMain([]string{}, c) + + if err == nil { + t.Fatal("No prefix provided, should report an error") + } + + c = cli.Config{ + RenewCA: true, + CAFile: "env:ca", + CAKeyFile: "ca_key", + } + err = gencertMain([]string{}, c) + + if err == nil { + t.Fatal("No prefix provided, should report an error") + } + + c = cli.Config{ + RenewCA: true, + CAFile: "env:ca", + CAKeyFile: "en:ca_key", + } + err = gencertMain([]string{}, c) + + if err == nil { + t.Fatal("Unsupported prefix, should report error") + } + + c = cli.Config{ + RenewCA: true, + CAFile: "env:ca", + CAKeyFile: "env:file:ca_key", + } + err = gencertMain([]string{}, c) + + if err == nil { + t.Fatal("Multiple prefixes, should report error") + } +} + func TestBadGencertMain(t *testing.T) { err := gencertMain([]string{"../testdata/csr.json"}, cli.Config{}) if err != nil { diff --git a/cli/ocspserve/ocspserve.go b/cli/ocspserve/ocspserve.go index 9f8519fc9..31d0300d7 100644 --- a/cli/ocspserve/ocspserve.go +++ b/cli/ocspserve/ocspserve.go @@ -7,6 +7,7 @@ import ( "net/http" "github.com/cloudflare/cfssl/cli" + "github.com/cloudflare/cfssl/helpers" "github.com/cloudflare/cfssl/log" "github.com/cloudflare/cfssl/ocsp" ) @@ -26,6 +27,7 @@ var ocspServerFlags = []string{"address", "port", "responses"} // ocspServerMain is the command line entry point to the OCSP responder. // It sets up a new HTTP server that responds to OCSP requests. func ocspServerMain(args []string, c cli.Config) error { + var src ocsp.Source // serve doesn't support arguments. if len(args) > 0 { return errors.New("argument is provided but not defined; please refer to the usage by flag -h") @@ -35,9 +37,33 @@ func ocspServerMain(args []string, c cli.Config) error { return errors.New("no response file provided, please set the -responses flag") } - src, err := ocsp.NewSourceFromFile(c.Responses) + typ, path, err := helpers.ParseConnString(c.Responses) if err != nil { - return errors.New("unable to read response file") + return errors.New("unable to parse responses connection string") + } + switch typ { + case "file": + src, err = ocsp.NewSourceFromFile(path) + if err != nil { + return errors.New("unable to read response file") + } + case "sqlite": + src, err = ocsp.NewSourceFromConnString("sqlite", path) + if err != nil { + return errors.New("unable to read Sqlite connection string") + } + case "mysql": + src, err = ocsp.NewSourceFromConnString("mysql", path) + if err != nil { + return errors.New("unable to read MySQL connection string") + } + case "postgres": + src, err = ocsp.NewSourceFromConnString("postgres", path) + if err != nil { + return errors.New("unable to read PostgreSQL connection string") + } + default: + return errors.New("unrecognized connection string format") } log.Info("Registering OCSP responder handler") diff --git a/helpers/helpers.go b/helpers/helpers.go index 8a70c045d..17845eccc 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -10,12 +10,17 @@ import ( "crypto/rsa" "crypto/tls" "crypto/x509" + "crypto/x509/pkix" "encoding/asn1" + "encoding/binary" "encoding/pem" "errors" + "fmt" + "io" "io/ioutil" "math/big" - + "net/url" + "os" "strings" "time" @@ -23,6 +28,8 @@ import ( cferr "github.com/cloudflare/cfssl/errors" "github.com/cloudflare/cfssl/helpers/derhelpers" "github.com/cloudflare/cfssl/log" + "github.com/google/certificate-transparency/go" + "golang.org/x/crypto/ocsp" "golang.org/x/crypto/pkcs12" ) @@ -518,3 +525,146 @@ func CreateTLSConfig(remoteCAs *x509.CertPool, cert *tls.Certificate) *tls.Confi RootCAs: remoteCAs, } } + +// SerializeSCTList serializes a list of SCTs. +func SerializeSCTList(sctList []ct.SignedCertificateTimestamp) ([]byte, error) { + var buf bytes.Buffer + for _, sct := range sctList { + sct, err := ct.SerializeSCT(sct) + if err != nil { + return nil, err + } + binary.Write(&buf, binary.BigEndian, uint16(len(sct))) + buf.Write(sct) + } + + var sctListLengthField = make([]byte, 2) + binary.BigEndian.PutUint16(sctListLengthField, uint16(buf.Len())) + return bytes.Join([][]byte{sctListLengthField, buf.Bytes()}, nil), nil +} + +// DeserializeSCTList deserializes a list of SCTs. +func DeserializeSCTList(serializedSCTList []byte) (*[]ct.SignedCertificateTimestamp, error) { + sctList := new([]ct.SignedCertificateTimestamp) + sctReader := bytes.NewBuffer(serializedSCTList) + + var sctListLen uint16 + err := binary.Read(sctReader, binary.BigEndian, &sctListLen) + if err != nil { + if err == io.EOF { + return sctList, cferr.Wrap(cferr.CTError, cferr.Unknown, + errors.New("serialized SCT list could not be read")) + } + return sctList, cferr.Wrap(cferr.CTError, cferr.Unknown, err) + } + if sctReader.Len() != int(sctListLen) { + return sctList, errors.New("SCT length field and SCT length don't match") + } + + for err != io.EOF { + var sctLen uint16 + err = binary.Read(sctReader, binary.BigEndian, &sctLen) + if err != nil { + if err == io.EOF { + return sctList, nil + } + return sctList, cferr.Wrap(cferr.CTError, cferr.Unknown, err) + } + + if sctReader.Len() < int(sctLen) { + return sctList, errors.New("SCT length field and SCT length don't match") + } + + serializedSCT := sctReader.Next(int(sctLen)) + sct, err := ct.DeserializeSCT(bytes.NewReader(serializedSCT)) + if err != nil { + return sctList, cferr.Wrap(cferr.CTError, cferr.Unknown, err) + } + + temp := append(*sctList, *sct) + sctList = &temp + } + + return sctList, cferr.Wrap(cferr.CTError, cferr.Unknown, err) +} + +// SCTListFromOCSPResponse extracts the SCTList from an ocsp.Response, +// returning an empty list if the SCT extension was not found or could not be +// unmarshalled. +func SCTListFromOCSPResponse(response *ocsp.Response) ([]ct.SignedCertificateTimestamp, error) { + // This loop finds the SCTListExtension in the OCSP response. + var SCTListExtension, ext pkix.Extension + for _, ext = range response.Extensions { + // sctExtOid is the ObjectIdentifier of a Signed Certificate Timestamp. + sctExtOid := asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 5} + if ext.Id.Equal(sctExtOid) { + SCTListExtension = ext + break + } + } + + // This code block extracts the sctList from the SCT extension. + var emptySCTList []ct.SignedCertificateTimestamp + sctList := &emptySCTList + var err error + if numBytes := len(SCTListExtension.Value); numBytes != 0 { + serializedSCTList := new([]byte) + rest := make([]byte, numBytes) + copy(rest, SCTListExtension.Value) + for len(rest) != 0 { + rest, err = asn1.Unmarshal(rest, serializedSCTList) + if err != nil { + return nil, cferr.Wrap(cferr.CTError, cferr.Unknown, err) + } + } + sctList, err = DeserializeSCTList(*serializedSCTList) + } + return *sctList, err +} + +// ReadBytes reads a []byte either from a file or an environment variable. +// If valFile has a prefix of 'env:', the []byte is read from the environment +// using the subsequent name. If the prefix is 'file:' the []byte is read from +// the subsequent file. If no prefix is provided, valFile is assumed to be a +// file path. +func ReadBytes(valFile string) ([]byte, error) { + switch splitVal := strings.SplitN(valFile, ":", 2); len(splitVal) { + case 1: + return ioutil.ReadFile(valFile) + case 2: + switch splitVal[0] { + case "env": + return []byte(os.Getenv(splitVal[1])), nil + case "file": + return ioutil.ReadFile(splitVal[1]) + default: + return nil, fmt.Errorf("unknown prefix: %s", splitVal[0]) + } + default: + return nil, fmt.Errorf("multiple prefixes: %s", + strings.Join(splitVal[:len(splitVal)-1], ", ")) + } +} + +// ParseConnString parses a string representing the source of OCSP responses +// which can either be a file or the path to a DB (Sqlite, MySQL or PostgreSQL). +// It returns the type of the connection string (e.g. "File" or "MySQL" etc.) as +// well as the path to the source. +func ParseConnString(conn string) (string, string, error) { + u, err := url.Parse(conn) + if err != nil { + return "", "", err + } + switch u.Scheme { + case "", "file": + return "file", u.Path, nil + case "sqlite3": + return "sqlite", u.Path, nil + case "mysql": + return "mysql", conn[8:], nil + case "postgresql": + return "postgres", "dbname=" + u.Path[1:] + " sslmode=disable", nil + default: + return "DB", u.Path, nil + } +} diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go index f7697c8ae..8d7daac0e 100644 --- a/helpers/helpers_test.go +++ b/helpers/helpers_test.go @@ -1,16 +1,23 @@ package helpers import ( + "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" "encoding/pem" "io/ioutil" "math" "testing" "time" + + "golang.org/x/crypto/ocsp" + + "github.com/google/certificate-transparency/go" ) const ( @@ -498,3 +505,146 @@ func TestLoadPEMCertPool(t *testing.T) { t.Fatal("cert pool not created") } } + +// sctEquals returns true if all fields of both SCTs are equivalent. +func sctEquals(sctA, sctB ct.SignedCertificateTimestamp) bool { + if sctA.SCTVersion == sctB.SCTVersion && + sctA.LogID == sctB.LogID && + sctA.Timestamp == sctB.Timestamp && + bytes.Equal(sctA.Extensions, sctB.Extensions) && + sctA.Signature.Algorithm == sctB.Signature.Algorithm && + bytes.Equal(sctA.Signature.Signature, sctA.Signature.Signature) { + return true + } + return false +} + +// NOTE: TestDeserializeSCTList tests both DeserializeSCTList and +// SerializeSCTList. +func TestDeserializeSCTList(t *testing.T) { + // Here we make sure that empty SCT lists return an error + emptyLists := [][]byte{nil, {}} + for _, emptyList := range emptyLists { + _, err := DeserializeSCTList(emptyList) + if err == nil { + t.Fatalf("DeserializeSCTList(%v) should raise an error\n", emptyList) + } + } + + // Here we make sure that an SCT list with a zero SCT is deserialized + // correctly + var zeroSCT ct.SignedCertificateTimestamp + serializedSCT, err := SerializeSCTList([]ct.SignedCertificateTimestamp{zeroSCT}) + if err != nil { + t.Fatal(err) + } + deserializedSCTList, err := DeserializeSCTList(serializedSCT) + if err != nil { + t.Fatal(err) + } + if !sctEquals(zeroSCT, (*deserializedSCTList)[0]) { + t.Fatal("SCTs don't match") + } + + // Here we verify that an error is raised when the SCT list length + // field is greater than its actual length + serializedSCT, err = SerializeSCTList([]ct.SignedCertificateTimestamp{zeroSCT}) + if err != nil { + t.Fatal(err) + } + serializedSCT[0] = 15 + _, err = DeserializeSCTList(serializedSCT) + if err == nil { + t.Fatalf("DeserializeSCTList should raise an error when " + + "the SCT list length field and the list length don't match\n") + } + + // Here we verify that an error is raised when the SCT list length + // field is less than its actual length + serializedSCT[0] = 0 + serializedSCT[1] = 0 + _, err = DeserializeSCTList(serializedSCT) + if err == nil { + t.Fatalf("DeserializeSCTList should raise an error when " + + "the SCT list length field and the list length don't match\n") + } + + // Here we verify that an error is raised when the SCT length field is + // greater than its actual length + serializedSCT[0] = 0 + serializedSCT[1] = 49 + serializedSCT[2] = 1 + _, err = DeserializeSCTList(serializedSCT) + if err == nil { + t.Fatalf("DeserializeSCTList should raise an error when " + + "the SCT length field and the SCT length don't match\n") + } + + // Here we verify that an error is raised when the SCT length field is + // less than its actual length + serializedSCT[2] = 0 + serializedSCT[3] = 0 + _, err = DeserializeSCTList(serializedSCT) + if err == nil { + t.Fatalf("DeserializeSCTList should raise an error when " + + "the SCT length field and the SCT length don't match\n") + } +} + +func TestSCTListFromOCSPResponse(t *testing.T) { + var response ocsp.Response + lst, err := SCTListFromOCSPResponse(&response) + if err != nil { + t.Fatal(err) + } + if len(lst) != 0 { + t.Fatal("SCTListFromOCSPResponse should return an empty SCT list for an empty extension") + } + + var zeroSCT ct.SignedCertificateTimestamp + serializedSCTList, err := SerializeSCTList([]ct.SignedCertificateTimestamp{zeroSCT}) + if err != nil { + t.Fatal("failed to serialize SCT list") + } + serializedSCTList, err = asn1.Marshal(serializedSCTList) + if err != nil { + t.Fatal("failed to serialize SCT list") + } + // The value of Id below is the object identifier of the OCSP Stapling + // SCT extension (see section 3.3. of RFC 6962). + response.Extensions = []pkix.Extension{{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 5}, + Critical: false, + Value: serializedSCTList, + }} + lst, err = SCTListFromOCSPResponse(&response) + if err != nil { + t.Fatal(err) + } + if !sctEquals(zeroSCT, lst[0]) { + t.Fatal("SCTs don't match") + } +} + +func TestParseConnString(t *testing.T) { + filePath := "/path/to/file.txt" + typ, path, err := ParseConnString(filePath) + if typ != "file" || path != "/path/to/file.txt" || err != nil { + t.Fatal("Incorrect parsing of file path") + } + sqliteStr := "sqlite3:///path/to/db/file.db" + typ, path, err = ParseConnString(sqliteStr) + if typ != "sqlite" || path != "/path/to/db/file.db" || err != nil { + t.Fatal("Incorrect parsing of sqlite connection string") + } + mysqlStr := "mysql://root@tcp(localhost:3306)/certdb_development?parseTime=true" + typ, path, err = ParseConnString(mysqlStr) + if typ != "mysql" || path != "root@tcp(localhost:3306)/certdb_development?parseTime=true" || err != nil { + t.Fatal("Incorrect parsing of MySQL connection string") + } + postgresStr := "postgresql://root@tcp(localhost:3306)/certdb_development?parseTime=true" + typ, path, err = ParseConnString(postgresStr) + if typ != "postgres" || path != "dbname=certdb_development sslmode=disable" || err != nil { + t.Fatal("Incorrect parsing of PostgreSQL connection string") + } +} diff --git a/initca/initca.go b/initca/initca.go index c9ab3bbc3..559190655 100644 --- a/initca/initca.go +++ b/initca/initca.go @@ -8,7 +8,6 @@ import ( "crypto/rsa" "crypto/x509" "errors" - "io/ioutil" "time" "github.com/cloudflare/cfssl/config" @@ -54,7 +53,7 @@ func New(req *csr.CertificateRequest) (cert, csrPEM, key []byte, err error) { } policy.Default.CAConstraint.MaxPathLen = req.CA.PathLength - if req.CA.PathLength != 0 && req.CA.PathLenZero == true { + if req.CA.PathLength != 0 && req.CA.PathLenZero { log.Infof("ignore invalid 'pathlenzero' value") } else { policy.Default.CAConstraint.MaxPathLenZero = req.CA.PathLenZero @@ -90,7 +89,7 @@ func New(req *csr.CertificateRequest) (cert, csrPEM, key []byte, err error) { // NewFromPEM creates a new root certificate from the key file passed in. func NewFromPEM(req *csr.CertificateRequest, keyFile string) (cert, csrPEM []byte, err error) { - privData, err := ioutil.ReadFile(keyFile) + privData, err := helpers.ReadBytes(keyFile) if err != nil { return nil, nil, err } @@ -109,7 +108,7 @@ func NewFromPEM(req *csr.CertificateRequest, keyFile string) (cert, csrPEM []byt // is valid for a year from Jan 01 2015 to Jan 01 2016, the renewed certificate // will be valid from now and expire in one year as well. func RenewFromPEM(caFile, keyFile string) ([]byte, error) { - caBytes, err := ioutil.ReadFile(caFile) + caBytes, err := helpers.ReadBytes(caFile) if err != nil { return nil, err } @@ -119,7 +118,7 @@ func RenewFromPEM(caFile, keyFile string) ([]byte, error) { return nil, err } - keyBytes, err := ioutil.ReadFile(keyFile) + keyBytes, err := helpers.ReadBytes(keyFile) if err != nil { return nil, err } @@ -130,7 +129,6 @@ func RenewFromPEM(caFile, keyFile string) ([]byte, error) { } return RenewFromSigner(ca, key) - } // NewFromSigner creates a new root certificate from a crypto.Signer. diff --git a/ocsp/ocsp.go b/ocsp/ocsp.go index 1e7c7eb04..65bd19ea0 100644 --- a/ocsp/ocsp.go +++ b/ocsp/ocsp.go @@ -104,7 +104,7 @@ func ReasonStringToCode(reason string) (reasonCode int, err error) { // from PEM files, and takes an interval in seconds func NewSignerFromFile(issuerFile, responderFile, keyFile string, interval time.Duration) (Signer, error) { log.Debug("Loading issuer cert: ", issuerFile) - issuerBytes, err := ioutil.ReadFile(issuerFile) + issuerBytes, err := helpers.ReadBytes(issuerFile) if err != nil { return nil, err } diff --git a/ocsp/responder.go b/ocsp/responder.go index 27f8a6f91..8a65986df 100644 --- a/ocsp/responder.go +++ b/ocsp/responder.go @@ -19,8 +19,10 @@ import ( "time" "github.com/cloudflare/cfssl/certdb" + "github.com/cloudflare/cfssl/certdb/sql" "github.com/cloudflare/cfssl/log" "github.com/jmhodges/clock" + "github.com/jmoiron/sqlx" "golang.org/x/crypto/ocsp" ) @@ -63,6 +65,21 @@ func NewDBSource(dbAccessor certdb.Accessor) Source { } } +// NewSourceFromConnString creates a new DBSource object with an associated +// dbAccessor. They type of the DB connection is specificied by the typ +// argument, and this function currently supports Sqlite, MySQL and PostgreSQL. +// The dbpath argument is a connection string aiding in connecting to the +// associated DB type. +func NewSourceFromConnString(typ, dbpath string) (Source, error) { + db, err := sqlx.Open(typ, dbpath) + if err != nil { + return nil, err + } + accessor := sql.NewAccessor(db) + src := NewDBSource(accessor) + return src, nil +} + // Response implements cfssl.ocsp.responder.Source, which returns the // OCSP response in the Database for the given request with the expiration // date furthest in the future. Response also returns a bool that is false diff --git a/ocsp/responder_test.go b/ocsp/responder_test.go index 5987adcb2..f83534e41 100644 --- a/ocsp/responder_test.go +++ b/ocsp/responder_test.go @@ -3,9 +3,11 @@ package ocsp import ( "encoding/hex" "io/ioutil" + "math/big" "net/http" "net/http/httptest" "net/url" + "strconv" "testing" "time" @@ -166,7 +168,7 @@ func TestNewSourceFromFile(t *testing.T) { } } -func TestSqliteTrivial(t *testing.T) { +func TestResponseTrivial(t *testing.T) { // First, read and parse certificate and issuer files needed to make // an OCSP request. certFile := "testdata/sqlite_ca.pem" @@ -197,10 +199,22 @@ func TestSqliteTrivial(t *testing.T) { if err != nil { t.Errorf("Error parsing OCSP request: %s", err) } + // Truncate the Serial Number so it can fit into the DB tables. + truncSN, _ := strconv.Atoi(req.SerialNumber.String()[:20]) + req.SerialNumber = big.NewInt(int64(truncSN)) + // Create SQLite DB and accossiated accessor. sqliteDBfile := "testdata/sqlite_test.db" - db := testdb.SQLiteDB(sqliteDBfile) - accessor := sql.NewAccessor(db) + sqlitedb := testdb.SQLiteDB(sqliteDBfile) + sqliteAccessor := sql.NewAccessor(sqlitedb) + + // Create MySQL DB and accossiated accessor. + mysqldb := testdb.MySQLDB() + mysqlAccessor := sql.NewAccessor(mysqldb) + + // Create PostgreSQL DB and accossiated accessor. + postgresdb := testdb.PostgreSQLDB() + postgresAccessor := sql.NewAccessor(postgresdb) // Populate the DB with the OCSPRecord, and check // that Response() handles the request appropiately. @@ -210,28 +224,84 @@ func TestSqliteTrivial(t *testing.T) { Expiry: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), Serial: req.SerialNumber.String(), } - err = accessor.InsertOCSP(ocsp) + err = sqliteAccessor.InsertOCSP(ocsp) + if err != nil { + t.Errorf("Error inserting OCSP record into SQLite DB: %s", err) + } + + err = mysqlAccessor.InsertOCSP(ocsp) + if err != nil { + t.Errorf("Error inserting OCSP record into MySQL DB: %s", err) + } + + // Need to create and insert Certificate record into PostgreSQL + // before inserting OCSP record due to foreign key constraints + // of the Postgres tables. + certRec := certdb.CertificateRecord{ + Serial: req.SerialNumber.String(), + AKI: hex.EncodeToString(req.IssuerKeyHash), + CALabel: "Example Certificate", + Status: "Good", + Reason: 1, + Expiry: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), + RevokedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), + PEM: "PEM", + } + + err = postgresAccessor.InsertCertificate(certRec) + if err != nil { + t.Errorf("Error inserting Certificate record into PostgreSQL DB: %s", err) + } + + err = postgresAccessor.InsertOCSP(ocsp) if err != nil { - t.Errorf("Error inserting OCSP record into DB: %s", err) + t.Errorf("Error inserting OCSP record into PostgreSQL DB: %s", err) } // Use the created Accessor to create a new DBSource. - src := NewDBSource(accessor) + sqliteSrc := NewDBSource(sqliteAccessor) + mysqlSrc := NewDBSource(mysqlAccessor) + postgresSrc := NewDBSource(postgresAccessor) // Call Response() method on constructed request and check the output. - response, present := src.Response(req) + response, present := sqliteSrc.Response(req) if !present { - t.Error("No response present for given request") + t.Error("No response present in SQLite DB for given request") } if string(response) != "Test OCSP" { t.Error("Incorrect response received from Sqlite DB") } + + response, present = mysqlSrc.Response(req) + if !present { + t.Error("No response present in MySQL DB for given request") + } + if string(response) != "Test OCSP" { + t.Error("Incorrect response received from MySQL DB") + } + + response, present = postgresSrc.Response(req) + if !present { + t.Error("No response present in PostgreSQL DB for given request") + } + if string(response) != "Test OCSP" { + t.Error("Incorrect response received from PostgreSQL DB") + } } -func TestSqliteRealResponse(t *testing.T) { +func TestRealResponse(t *testing.T) { + // Create SQLite DB and accossiated accessor. sqliteDBfile := "testdata/sqlite_test.db" - db := testdb.SQLiteDB(sqliteDBfile) - accessor := sql.NewAccessor(db) + sqlitedb := testdb.SQLiteDB(sqliteDBfile) + sqliteAccessor := sql.NewAccessor(sqlitedb) + + // Create MySQL DB and accossiated accessor. + mysqldb := testdb.MySQLDB() + mysqlAccessor := sql.NewAccessor(mysqldb) + + // Create PostgreSQL DB and accossiated accessor. + postgresdb := testdb.PostgreSQLDB() + postgresAccessor := sql.NewAccessor(postgresdb) certFile := "testdata/cert.pem" issuerFile := "testdata/ca.pem" @@ -291,19 +361,101 @@ func TestSqliteRealResponse(t *testing.T) { Expiry: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), Serial: req.SerialNumber.String(), } - accessor.InsertOCSP(ocsp) + + err = sqliteAccessor.InsertOCSP(ocsp) + if err != nil { + t.Errorf("Error inserting OCSP record into SQLite DB: %s", err) + } + + err = mysqlAccessor.InsertOCSP(ocsp) + if err != nil { + t.Errorf("Error inserting OCSP record into MySQL DB: %s", err) + } + + // Need to create and insert Certificate record into PostgreSQL + // before inserting OCSP record due to foreign key constraints + // of the Postgres tables. + certRec := certdb.CertificateRecord{ + Serial: req.SerialNumber.String(), + AKI: hex.EncodeToString(req.IssuerKeyHash), + CALabel: "Example Certificate", + Status: "Good", + Reason: 1, + Expiry: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), + RevokedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), + PEM: "PEM", + } + + err = postgresAccessor.InsertCertificate(certRec) + if err != nil { + t.Errorf("Error inserting Certificate record into PostgreSQL DB: %s", err) + } + + err = postgresAccessor.InsertOCSP(ocsp) + if err != nil { + t.Errorf("Error inserting OCSP record into PostgreSQL DB: %s", err) + } // Use the created Accessor to create new DBSource. - src := NewDBSource(accessor) + sqliteSrc := NewDBSource(sqliteAccessor) + mysqlSrc := NewDBSource(mysqlAccessor) + postgresSrc := NewDBSource(postgresAccessor) // Call Response() method on constructed request and check the output. - response, present := src.Response(req) + // Then, attempt to parse the returned response and make sure it is well formed. + response, present := sqliteSrc.Response(req) + if !present { + t.Error("No response present in SQLite DB for given request") + } + _, err = goocsp.ParseResponse(response, issuer) + if err != nil { + t.Errorf("Error parsing SQLite response: %v", err) + } + + response, present = mysqlSrc.Response(req) if !present { - t.Error("No response present for given request") + t.Error("No response present in MySQL DB for given request") } - // Attempt to parse the returned response and make sure it is well formed. _, err = goocsp.ParseResponse(response, issuer) if err != nil { - t.Errorf("Error parsing response: %v", err) + t.Errorf("Error parsing MySQL response: %v", err) + } + + response, present = postgresSrc.Response(req) + if !present { + t.Error("No response present in PostgreSQL for given request") + } + _, err = goocsp.ParseResponse(response, issuer) + if err != nil { + t.Errorf("Error parsing PostgreSQL response: %v", err) + } +} + +// Manually run the query "SELECT max(version_id) FROM goose_db_version;" +// on testdata/sqlite_test.db after running this test to verify that the +// DB was properly connected to. +func TestNewSqliteSource(t *testing.T) { + dbpath := "testdata/sqlite_test.db" + _, err := NewSourceFromConnString("sqlite3", dbpath) + if err != nil { + t.Errorf("Error connecting to Sqlite DB: %v", err) + } +} + +func TestNewMySQLSource(t *testing.T) { + dbpath := "root@tcp(localhost:3306)/certdb_development?parseTime=true" + // Error should be thrown here if DB cannot be connected to. + _, err := NewSourceFromConnString("mysql", dbpath) + if err != nil { + t.Errorf("Error connecting to MySQL DB: %v", err) + } +} + +func TestNewPostgresSource(t *testing.T) { + dbpath := "dbname=certdb_development sslmode=disable" + // Error should be thrown here if DB cannot be connected to. + _, err := NewSourceFromConnString("postgres", dbpath) + if err != nil { + t.Errorf("Error connecting to PostgreSQL DB: %v", err) } } diff --git a/revoke/revoke.go b/revoke/revoke.go index 4dc34fed7..a74170db9 100644 --- a/revoke/revoke.go +++ b/revoke/revoke.go @@ -79,17 +79,17 @@ func revCheck(cert *x509.Certificate) (revoked, ok bool) { log.Info("certificate is revoked via CRL") return true, true } + } - if revoked, ok := certIsRevokedOCSP(cert, HardFail); !ok { - log.Warning("error checking revocation via OCSP") - if HardFail { - return true, false - } - return false, false - } else if revoked { - log.Info("certificate is revoked via OCSP") - return true, true + if revoked, ok := certIsRevokedOCSP(cert, HardFail); !ok { + log.Warning("error checking revocation via OCSP") + if HardFail { + return true, false } + return false, false + } else if revoked { + log.Info("certificate is revoked via OCSP") + return true, true } return false, true diff --git a/signer/local/local.go b/signer/local/local.go index d6eb2e899..f50d93d13 100644 --- a/signer/local/local.go +++ b/signer/local/local.go @@ -8,12 +8,10 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" - "encoding/binary" "encoding/hex" "encoding/pem" "errors" "io" - "io/ioutil" "math/big" "net" "net/http" @@ -68,12 +66,12 @@ func NewSigner(priv crypto.Signer, cert *x509.Certificate, sigAlgo x509.Signatur // and a caKey file, both PEM encoded. func NewSignerFromFile(caFile, caKeyFile string, policy *config.Signing) (*Signer, error) { log.Debug("Loading CA: ", caFile) - ca, err := ioutil.ReadFile(caFile) + ca, err := helpers.ReadBytes(caFile) if err != nil { return nil, err } log.Debug("Loading CA key: ", caKeyFile) - cakey, err := ioutil.ReadFile(caKeyFile) + cakey, err := helpers.ReadBytes(caKeyFile) if err != nil { return nil, cferr.Wrap(cferr.CertificateError, cferr.ReadFailed, err) } @@ -369,7 +367,7 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) { } var serializedSCTList []byte - serializedSCTList, err = serializeSCTList(sctList) + serializedSCTList, err = helpers.SerializeSCTList(sctList) if err != nil { return nil, cferr.Wrap(cferr.CTError, cferr.Unknown, err) } @@ -411,22 +409,6 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) { return signedCert, nil } -func serializeSCTList(sctList []ct.SignedCertificateTimestamp) ([]byte, error) { - var buf bytes.Buffer - for _, sct := range sctList { - sct, err := ct.SerializeSCT(sct) - if err != nil { - return nil, err - } - binary.Write(&buf, binary.BigEndian, uint16(len(sct))) - buf.Write(sct) - } - - var sctListLengthField = make([]byte, 2) - binary.BigEndian.PutUint16(sctListLengthField, uint16(buf.Len())) - return bytes.Join([][]byte{sctListLengthField, buf.Bytes()}, nil), nil -} - // Info return a populated info.Resp struct or an error. func (s *Signer) Info(req info.Req) (resp *info.Resp, err error) { cert, err := s.Certificate(req.Label, req.Profile)