Merge branch 'master' into master
This commit is contained in:
commit
ad7e2cc5fe
|
|
@ -12,58 +12,63 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/auth",
|
||||
"Comment": "1.1.0-268-ge32101b",
|
||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
||||
"Comment": "1.1.0-345-g3cc473b",
|
||||
"Rev": "3cc473b970536c9c35099bc46be861cd33f8bda2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/certdb",
|
||||
"Comment": "1.1.0-345-g3cc473b",
|
||||
"Rev": "3cc473b970536c9c35099bc46be861cd33f8bda2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/config",
|
||||
"Comment": "1.1.0-268-ge32101b",
|
||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
||||
"Comment": "1.1.0-345-g3cc473b",
|
||||
"Rev": "3cc473b970536c9c35099bc46be861cd33f8bda2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs11key",
|
||||
"Comment": "1.1.0-268-ge32101b",
|
||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
||||
"Comment": "1.1.0-345-g3cc473b",
|
||||
"Rev": "3cc473b970536c9c35099bc46be861cd33f8bda2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs7",
|
||||
"Comment": "1.1.0-268-ge32101b",
|
||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
||||
"Comment": "1.1.0-345-g3cc473b",
|
||||
"Rev": "3cc473b970536c9c35099bc46be861cd33f8bda2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/csr",
|
||||
"Comment": "1.1.0-268-ge32101b",
|
||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
||||
"Comment": "1.1.0-345-g3cc473b",
|
||||
"Rev": "3cc473b970536c9c35099bc46be861cd33f8bda2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/errors",
|
||||
"Comment": "1.1.0-268-ge32101b",
|
||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
||||
"Comment": "1.1.0-345-g3cc473b",
|
||||
"Rev": "3cc473b970536c9c35099bc46be861cd33f8bda2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/helpers",
|
||||
"Comment": "1.1.0-268-ge32101b",
|
||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
||||
"Comment": "1.1.0-345-g3cc473b",
|
||||
"Rev": "3cc473b970536c9c35099bc46be861cd33f8bda2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/info",
|
||||
"Comment": "1.1.0-268-ge32101b",
|
||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
||||
"Comment": "1.1.0-345-g3cc473b",
|
||||
"Rev": "3cc473b970536c9c35099bc46be861cd33f8bda2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/log",
|
||||
"Comment": "1.1.0-268-ge32101b",
|
||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
||||
"Comment": "1.1.0-345-g3cc473b",
|
||||
"Rev": "3cc473b970536c9c35099bc46be861cd33f8bda2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/ocsp",
|
||||
"Comment": "1.1.0-268-ge32101b",
|
||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
||||
"Comment": "1.1.0-345-g3cc473b",
|
||||
"Rev": "3cc473b970536c9c35099bc46be861cd33f8bda2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/cloudflare/cfssl/signer",
|
||||
"Comment": "1.1.0-268-ge32101b",
|
||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
||||
"Comment": "1.1.0-345-g3cc473b",
|
||||
"Rev": "3cc473b970536c9c35099bc46be861cd33f8bda2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/codegangsta/cli",
|
||||
|
|
@ -134,11 +139,11 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/ocsp",
|
||||
"Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc"
|
||||
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/pkcs12",
|
||||
"Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc"
|
||||
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/context",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
Copyright (c) 2014 CloudFlare Inc.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
58
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/README.md
generated
vendored
Normal file
58
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# certdb usage
|
||||
|
||||
Using a database enables additional functionality for existing commands when a
|
||||
db config is provided:
|
||||
|
||||
- `sign` and `gencert` add a certificate to the certdb after signing it
|
||||
- `serve` enables database functionality for the sign and revoke endpoints
|
||||
|
||||
A database is required for the following:
|
||||
|
||||
- `revoke` marks certificates revoked in the database with an optional reason
|
||||
- `ocsprefresh` refreshes the table of cached OCSP responses
|
||||
- `ocspdump` outputs cached OCSP responses in a concatenated base64-encoded format
|
||||
|
||||
## Setup/Migration
|
||||
|
||||
This directory stores [goose](https://bitbucket.org/liamstask/goose/) db migration scripts for various DB backends.
|
||||
Currently supported:
|
||||
- SQLite in sqlite
|
||||
- PostgreSQL in pg
|
||||
|
||||
### Get goose
|
||||
|
||||
go get https://bitbucket.org/liamstask/goose/
|
||||
|
||||
### Use goose to start and terminate a SQLite DB
|
||||
To start a SQLite DB using goose:
|
||||
|
||||
goose -path $GOPATH/src/github.com/cloudflare/cfssl/certdb/sqlite up'
|
||||
|
||||
To tear down a SQLite DB using goose
|
||||
|
||||
goose -path $GOPATH/src/github.com/cloudflare/cfssl/certdb/sqlite down
|
||||
|
||||
### Use goose to start and terminate a PostgreSQL DB
|
||||
To start a PostgreSQL using goose:
|
||||
|
||||
goose -path $GOPATH/src/github.com/cloudflare/cfssl/certdb/pg up
|
||||
|
||||
To tear down a PostgreSQL DB using goose
|
||||
|
||||
goose -path $GOPATH/src/github.com/cloudflare/cfssl/certdb/pg down
|
||||
|
||||
Note: the administration of PostgreSQL DB is not included. We assume
|
||||
the databases being connected to are already created and access control
|
||||
are properly handled.
|
||||
|
||||
## CFSSL Configuration
|
||||
|
||||
Several cfssl commands take a -db-config flag. Create a file with a
|
||||
JSON dictionary:
|
||||
|
||||
{"driver":"sqlite3","data_source":"certs.db"}
|
||||
|
||||
or
|
||||
|
||||
{"driver":"postgres","data_source":"postgres://user:password@host/db"}
|
||||
|
||||
38
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/certdb.go
generated
vendored
Normal file
38
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/certdb.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package certdb
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// CertificateRecord encodes a certificate and its metadata
|
||||
// that will be recorded in a database.
|
||||
type CertificateRecord struct {
|
||||
Serial string `sql:"serial"`
|
||||
CALabel string `sql:"ca_label"`
|
||||
Status string `sql:"status"`
|
||||
Reason int `sql:"reason"`
|
||||
Expiry time.Time `sql:"expiry"`
|
||||
RevokedAt time.Time `sql:"revoked_at"`
|
||||
PEM string `sql:"pem"`
|
||||
}
|
||||
|
||||
// OCSPRecord encodes a OCSP response body and its metadata
|
||||
// that will be recorded in a database.
|
||||
type OCSPRecord struct {
|
||||
Serial string `sql:"serial"`
|
||||
Body string `sql:"body"`
|
||||
Expiry time.Time `sql:"expiry"`
|
||||
}
|
||||
|
||||
// Accessor abstracts the CRUD of certdb objects from a DB.
|
||||
type Accessor interface {
|
||||
InsertCertificate(cr *CertificateRecord) error
|
||||
GetCertificate(serial string) (*CertificateRecord, error)
|
||||
GetUnexpiredCertificates() ([]*CertificateRecord, error)
|
||||
RevokeCertificate(serial string, reasonCode int) error
|
||||
InsertOCSP(rr *OCSPRecord) error
|
||||
GetOCSP(serial string) (*OCSPRecord, error)
|
||||
GetUnexpiredOCSPs() ([]*OCSPRecord, error)
|
||||
UpdateOCSP(serial, body string, expiry time.Time) error
|
||||
UpsertOCSP(serial, body string, expiry time.Time) error
|
||||
}
|
||||
58
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/dbconf/db_config.go
generated
vendored
Normal file
58
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/dbconf/db_config.go
generated
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package dbconf
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
|
||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
|
||||
_ "github.com/lib/pq" // import just to initialize Postgres
|
||||
_ "github.com/mattn/go-sqlite3" // import just to initialize SQLite
|
||||
)
|
||||
|
||||
// DBConfig contains the database driver name and configuration to be passed to Open
|
||||
type DBConfig struct {
|
||||
DriverName string `json:"driver"`
|
||||
DataSourceName string `json:"data_source"`
|
||||
}
|
||||
|
||||
// LoadFile attempts to load the db configuration file stored at the path
|
||||
// and returns the configuration. On error, it returns nil.
|
||||
func LoadFile(path string) (cfg *DBConfig, err error) {
|
||||
log.Debugf("loading db configuration file from %s", path)
|
||||
if path == "" {
|
||||
return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("invalid path"))
|
||||
}
|
||||
|
||||
var body []byte
|
||||
body, err = ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("could not read configuration file"))
|
||||
}
|
||||
|
||||
cfg = &DBConfig{}
|
||||
err = json.Unmarshal(body, &cfg)
|
||||
if err != nil {
|
||||
return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
|
||||
errors.New("failed to unmarshal configuration: "+err.Error()))
|
||||
}
|
||||
|
||||
if cfg.DataSourceName == "" || cfg.DriverName == "" {
|
||||
return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("invalid db configuration"))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DBFromConfig opens a sql.DB from settings in a db config file
|
||||
func DBFromConfig(path string) (db *sql.DB, err error) {
|
||||
var dbCfg *DBConfig
|
||||
dbCfg, err = LoadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sql.Open(dbCfg.DriverName, dbCfg.DataSourceName)
|
||||
}
|
||||
15
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/pg/dbconf.yml
generated
vendored
Normal file
15
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/pg/dbconf.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
development:
|
||||
driver: postgres
|
||||
open: dbname=certdb_development sslmode=disable
|
||||
|
||||
test:
|
||||
driver: postgres
|
||||
open: dbname=certdb_test sslmode=disable
|
||||
|
||||
staging:
|
||||
driver: postgres
|
||||
open: dbname=certdb_staging sslmode=disable
|
||||
|
||||
production:
|
||||
driver: postgres
|
||||
open: dbname=certdb_production sslmode=disable
|
||||
18
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/pg/migrations/001_CreateCertificates.sql
generated
vendored
Normal file
18
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/pg/migrations/001_CreateCertificates.sql
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
|
||||
CREATE TABLE certificates (
|
||||
id serial,
|
||||
serial bytea NOT NULL PRIMARY KEY,
|
||||
ca_label bytea NOT NULL,
|
||||
status bytea NOT NULL,
|
||||
reason int,
|
||||
expiry timestamptz,
|
||||
revoked_at timestamptz,
|
||||
pem bytea NOT NULL
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
||||
DROP TABLE certificates;
|
||||
16
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/pg/migrations/002_CreateOCSPResponses.sql
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/pg/migrations/002_CreateOCSPResponses.sql
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
|
||||
CREATE TABLE ocsp_responses (
|
||||
serial bytea NOT NULL PRIMARY KEY,
|
||||
body bytea NOT NULL,
|
||||
expiry timestamptz,
|
||||
FOREIGN KEY(serial) REFERENCES certificates(serial)
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
||||
DROP TABLE ocsp_responses;
|
||||
|
||||
|
||||
327
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/sql/database_accessor.go
generated
vendored
Normal file
327
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/sql/database_accessor.go
generated
vendored
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
package sql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb"
|
||||
"time"
|
||||
|
||||
"github.com/kisielk/sqlstruct"
|
||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
insertSQL = `
|
||||
INSERT INTO certificates (serial, ca_label, status, reason, expiry, revoked_at, pem)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7);`
|
||||
|
||||
selectSQL = `
|
||||
SELECT %s FROM certificates
|
||||
WHERE (serial = $1);`
|
||||
|
||||
selectAllSQL = `
|
||||
SELECT %s FROM certificates;`
|
||||
|
||||
selectAllUnexpiredSQL = `
|
||||
SELECT %s FROM certificates
|
||||
WHERE CURRENT_TIMESTAMP < expiry;`
|
||||
|
||||
updateRevokeSQL = `
|
||||
UPDATE certificates
|
||||
SET status='revoked', revoked_at=CURRENT_TIMESTAMP, reason=$1
|
||||
WHERE (serial = $2);`
|
||||
|
||||
insertOCSPSQL = `
|
||||
INSERT INTO ocsp_responses (serial, body, expiry)
|
||||
VALUES ($1, $2, $3);`
|
||||
|
||||
updateOCSPSQL = `
|
||||
UPDATE ocsp_responses
|
||||
SET expiry=$3, body=$2
|
||||
WHERE (serial = $1);`
|
||||
|
||||
selectAllUnexpiredOCSPSQL = `
|
||||
SELECT %s FROM ocsp_responses
|
||||
WHERE CURRENT_TIMESTAMP < expiry;`
|
||||
|
||||
selectOCSPSQL = `
|
||||
SELECT %s FROM ocsp_responses
|
||||
WHERE (serial = $1);`
|
||||
)
|
||||
|
||||
// Accessor implements certdb.Accessor interface.
|
||||
type Accessor struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func wrapSQLError(err error) error {
|
||||
if err != nil {
|
||||
return cferr.Wrap(cferr.CertStoreError, cferr.Unknown, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Accessor) checkDB() error {
|
||||
if d.db == nil {
|
||||
return cferr.Wrap(cferr.CertStoreError, cferr.Unknown,
|
||||
errors.New("unknown db object, please check SetDB method"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewAccessor returns a new Accessor.
|
||||
func NewAccessor(db *sql.DB) *Accessor {
|
||||
return &Accessor{db: db}
|
||||
}
|
||||
|
||||
// SetDB changes the underlying sql.DB object Accessor is manipulating.
|
||||
func (d *Accessor) SetDB(db *sql.DB) {
|
||||
d.db = db
|
||||
return
|
||||
}
|
||||
|
||||
// InsertCertificate puts a certdb.CertificateRecord into db.
|
||||
func (d *Accessor) InsertCertificate(cr *certdb.CertificateRecord) error {
|
||||
err := d.checkDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := d.db.Exec(
|
||||
insertSQL,
|
||||
cr.Serial,
|
||||
cr.CALabel,
|
||||
cr.Status,
|
||||
cr.Reason,
|
||||
cr.Expiry.UTC(),
|
||||
cr.RevokedAt,
|
||||
cr.PEM,
|
||||
)
|
||||
if err != nil {
|
||||
return wrapSQLError(err)
|
||||
}
|
||||
|
||||
numRowsAffected, _ := res.RowsAffected()
|
||||
|
||||
if numRowsAffected == 0 {
|
||||
return cferr.Wrap(cferr.CertStoreError, cferr.InsertionFailed, fmt.Errorf("failed to insert the certificate record"))
|
||||
}
|
||||
|
||||
if numRowsAffected != 1 {
|
||||
return wrapSQLError(fmt.Errorf("%d rows are affected, should be 1 row", numRowsAffected))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCertificate gets a certdb.CertificateRecord indexed by serial.
|
||||
func (d *Accessor) GetCertificate(serial string) (*certdb.CertificateRecord, error) {
|
||||
err := d.checkDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cr := new(certdb.CertificateRecord)
|
||||
rows, err := d.db.Query(fmt.Sprintf(selectSQL, sqlstruct.Columns(*cr)), serial)
|
||||
if err != nil {
|
||||
return nil, wrapSQLError(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
return cr, wrapSQLError(sqlstruct.Scan(cr, rows))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetUnexpiredCertificates gets all unexpired certificate from db.
|
||||
func (d *Accessor) GetUnexpiredCertificates() (crs []*certdb.CertificateRecord, err error) {
|
||||
err = d.checkDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cr := new(certdb.CertificateRecord)
|
||||
rows, err := d.db.Query(fmt.Sprintf(selectAllUnexpiredSQL, sqlstruct.Columns(*cr)))
|
||||
if err != nil {
|
||||
return nil, wrapSQLError(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
err = sqlstruct.Scan(cr, rows)
|
||||
if err != nil {
|
||||
return nil, wrapSQLError(err)
|
||||
}
|
||||
crs = append(crs, cr)
|
||||
}
|
||||
|
||||
return crs, nil
|
||||
}
|
||||
|
||||
// RevokeCertificate updates a certificate with a given serial number and marks it revoked.
|
||||
func (d *Accessor) RevokeCertificate(serial string, reasonCode int) error {
|
||||
err := d.checkDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := d.db.Exec(updateRevokeSQL, reasonCode, serial)
|
||||
|
||||
if err != nil {
|
||||
return wrapSQLError(err)
|
||||
}
|
||||
|
||||
numRowsAffected, _ := result.RowsAffected()
|
||||
|
||||
if numRowsAffected == 0 {
|
||||
return cferr.Wrap(cferr.CertStoreError, cferr.RecordNotFound, fmt.Errorf("failed to revoke the certificate: certificate not found"))
|
||||
}
|
||||
|
||||
if numRowsAffected != 1 {
|
||||
return wrapSQLError(fmt.Errorf("%d rows are affected, should be 1 row", numRowsAffected))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertOCSP puts a new certdb.OCSPRecord into the db.
|
||||
func (d *Accessor) InsertOCSP(rr *certdb.OCSPRecord) error {
|
||||
err := d.checkDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := d.db.Exec(
|
||||
insertOCSPSQL,
|
||||
rr.Serial,
|
||||
rr.Body,
|
||||
rr.Expiry.UTC(),
|
||||
)
|
||||
if err != nil {
|
||||
return wrapSQLError(err)
|
||||
}
|
||||
|
||||
numRowsAffected, _ := res.RowsAffected()
|
||||
|
||||
if numRowsAffected == 0 {
|
||||
return cferr.Wrap(cferr.CertStoreError, cferr.InsertionFailed, fmt.Errorf("failed to insert the OCSP record"))
|
||||
}
|
||||
|
||||
if numRowsAffected != 1 {
|
||||
return wrapSQLError(fmt.Errorf("%d rows are affected, should be 1 row", numRowsAffected))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOCSP retrieves a certdb.OCSPRecord from db by serial.
|
||||
func (d *Accessor) GetOCSP(serial string) (rr *certdb.OCSPRecord, err error) {
|
||||
err = d.checkDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rr = new(certdb.OCSPRecord)
|
||||
rows, err := d.db.Query(fmt.Sprintf(selectOCSPSQL, sqlstruct.Columns(*rr)), serial)
|
||||
if err != nil {
|
||||
return nil, wrapSQLError(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
return rr, sqlstruct.Scan(rr, rows)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetUnexpiredOCSPs retrieves all unexpired certdb.OCSPRecord from db.
|
||||
func (d *Accessor) GetUnexpiredOCSPs() (rrs []*certdb.OCSPRecord, err error) {
|
||||
err = d.checkDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rr := new(certdb.OCSPRecord)
|
||||
rows, err := d.db.Query(fmt.Sprintf(selectAllUnexpiredOCSPSQL, sqlstruct.Columns(*rr)))
|
||||
if err != nil {
|
||||
return nil, wrapSQLError(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
err = sqlstruct.Scan(rr, rows)
|
||||
if err != nil {
|
||||
return nil, wrapSQLError(err)
|
||||
}
|
||||
rrs = append(rrs, rr)
|
||||
}
|
||||
|
||||
return rrs, nil
|
||||
}
|
||||
|
||||
// UpdateOCSP updates a ocsp response record with a given serial number.
|
||||
func (d *Accessor) UpdateOCSP(serial, body string, expiry time.Time) error {
|
||||
err := d.checkDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := d.db.Exec(updateOCSPSQL, serial, body, expiry)
|
||||
|
||||
if err != nil {
|
||||
return wrapSQLError(err)
|
||||
}
|
||||
|
||||
numRowsAffected, err := result.RowsAffected()
|
||||
|
||||
if numRowsAffected == 0 {
|
||||
return cferr.Wrap(cferr.CertStoreError, cferr.RecordNotFound, fmt.Errorf("failed to update the OCSP record"))
|
||||
}
|
||||
|
||||
if numRowsAffected != 1 {
|
||||
return wrapSQLError(fmt.Errorf("%d rows are affected, should be 1 row", numRowsAffected))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// UpsertOCSP update a ocsp response record with a given serial number,
|
||||
// or insert the record if it doesn't yet exist in the db
|
||||
// Implementation note:
|
||||
// We didn't implement 'upsert' with SQL statement and we lost race condition
|
||||
// prevention provided by underlying DBMS.
|
||||
// Reasoning:
|
||||
// 1. it's diffcult to support multiple DBMS backends in the same time, the
|
||||
// SQL syntax differs from one to another.
|
||||
// 2. we don't need a strict simultaneous consistency between OCSP and certificate
|
||||
// status. It's OK that a OCSP response still shows 'good' while the
|
||||
// corresponding certificate is being revoked seconds ago, as long as the OCSP
|
||||
// response catches up to be eventually consistent (within hours to days).
|
||||
// Write race condition between OCSP writers on OCSP table is not a problem,
|
||||
// since we don't have write race condition on Certificate table and OCSP
|
||||
// writers should periodically use Certificate table to update OCSP table
|
||||
// to catch up.
|
||||
func (d *Accessor) UpsertOCSP(serial, body string, expiry time.Time) error {
|
||||
err := d.checkDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := d.db.Exec(updateOCSPSQL, serial, body, expiry)
|
||||
|
||||
if err != nil {
|
||||
return wrapSQLError(err)
|
||||
}
|
||||
|
||||
numRowsAffected, err := result.RowsAffected()
|
||||
|
||||
if numRowsAffected == 0 {
|
||||
return d.InsertOCSP(&certdb.OCSPRecord{Serial: serial, Body: body, Expiry: expiry})
|
||||
}
|
||||
|
||||
if numRowsAffected != 1 {
|
||||
return wrapSQLError(fmt.Errorf("%d rows are affected, should be 1 row", numRowsAffected))
|
||||
}
|
||||
return err
|
||||
}
|
||||
15
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/sqlite/dbconf.yml
generated
vendored
Normal file
15
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/sqlite/dbconf.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
development:
|
||||
driver: sqlite3
|
||||
open: ./certstore_development.db
|
||||
|
||||
test:
|
||||
driver: sqlite3
|
||||
open: ./certstore_test.db
|
||||
|
||||
staging:
|
||||
driver: sqlite3
|
||||
open: ./certstore_staging.db
|
||||
|
||||
production:
|
||||
driver: sqlite3
|
||||
open: ./certstore_production.db
|
||||
19
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/sqlite/migrations/001_CreateCertificates.sql
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/sqlite/migrations/001_CreateCertificates.sql
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
|
||||
CREATE TABLE certificates (
|
||||
id serial,
|
||||
serial bytea NOT NULL PRIMARY KEY,
|
||||
ca_label bytea NOT NULL,
|
||||
status bytea NOT NULL,
|
||||
reason int,
|
||||
expiry timestamp,
|
||||
revoked_at timestamp,
|
||||
pem bytea NOT NULL
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
||||
DROP TABLE certificates;
|
||||
|
||||
15
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/sqlite/migrations/002_CreateOCSPResponses.sql
generated
vendored
Normal file
15
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/sqlite/migrations/002_CreateOCSPResponses.sql
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
|
||||
CREATE TABLE ocsp_responses (
|
||||
serial bytea NOT NULL PRIMARY KEY,
|
||||
body bytea NOT NULL,
|
||||
expiry timestamp,
|
||||
FOREIGN KEY(serial) REFERENCES certificates(serial)
|
||||
);
|
||||
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
||||
DROP TABLE ocsp_responses;
|
||||
|
||||
BIN
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/testdb/certstore_development.db
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/testdb/certstore_development.db
generated
vendored
Normal file
Binary file not shown.
68
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/testdb/testdb.go
generated
vendored
Normal file
68
Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb/testdb/testdb.go
generated
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package testdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
|
||||
_ "github.com/lib/pq" // register postgresql driver
|
||||
_ "github.com/mattn/go-sqlite3" // register sqlite3 driver
|
||||
)
|
||||
|
||||
const (
|
||||
pgTruncateTables = `
|
||||
CREATE OR REPLACE FUNCTION truncate_tables() RETURNS void AS $$
|
||||
DECLARE
|
||||
statements CURSOR FOR
|
||||
SELECT tablename FROM pg_tables
|
||||
WHERE tablename != 'goose_db_version'
|
||||
AND tableowner = session_user
|
||||
AND schemaname = 'public';
|
||||
BEGIN
|
||||
FOR stmt IN statements LOOP
|
||||
EXECUTE 'TRUNCATE TABLE ' || quote_ident(stmt.tablename) || ' CASCADE;';
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
SELECT truncate_tables();
|
||||
`
|
||||
|
||||
sqliteTruncateTables = `
|
||||
DELETE FROM certificates;
|
||||
DELETE FROM ocsp_responses;
|
||||
`
|
||||
)
|
||||
|
||||
// PostgreSQLDB returns a PostgreSQL db instance for certdb testing.
|
||||
func PostgreSQLDB() *sql.DB {
|
||||
connStr := "dbname=certdb_development sslmode=disable"
|
||||
|
||||
if dbURL := os.Getenv("DATABASE_URL"); dbURL != "" {
|
||||
connStr = dbURL
|
||||
}
|
||||
|
||||
db, err := sql.Open("postgres", connStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if _, err := db.Exec(pgTruncateTables); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
// SQLiteDB returns a SQLite db instance for certdb testing.
|
||||
func SQLiteDB(dbpath string) *sql.DB {
|
||||
db, err := sql.Open("sqlite3", dbpath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if _, err := db.Exec(sqliteTruncateTables); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ import (
|
|||
// mechanism.
|
||||
type CSRWhitelist struct {
|
||||
Subject, PublicKeyAlgorithm, PublicKey, SignatureAlgorithm bool
|
||||
DNSNames, IPAddresses bool
|
||||
DNSNames, IPAddresses, EmailAddresses bool
|
||||
}
|
||||
|
||||
// OID is our own version of asn1's ObjectIdentifier, so we can define a custom
|
||||
|
|
@ -78,6 +78,7 @@ type SigningProfile struct {
|
|||
AuthRemote AuthRemote `json:"auth_remote"`
|
||||
CTLogServers []string `json:"ct_log_servers"`
|
||||
AllowedExtensions []OID `json:"allowed_extensions"`
|
||||
CertStore string `json:"cert_store"`
|
||||
|
||||
Policies []CertificatePolicy
|
||||
Expiry time.Duration
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@
|
|||
// this system's use of PKCS #7 data. Version is an integer type, note that PKCS #7 is
|
||||
// recursive, this second layer of ContentInfo is similar ignored for our degenerate
|
||||
// usage. The ExtendedCertificatesAndCertificates type consists of a sequence of choices
|
||||
// between PKCS #6 extended certificates andx509 certificates. Any sequence consisting
|
||||
// of any number of extended certificates is not yet supported in this implementation
|
||||
// between PKCS #6 extended certificates and x509 certificates. Any sequence consisting
|
||||
// of any number of extended certificates is not yet supported in this implementation.
|
||||
//
|
||||
// The ContentType Data is simpy a raw octet string and is parsed directly into a Go []byte
|
||||
// The ContentType Data is simply a raw octet string and is parsed directly into a Go []byte slice.
|
||||
//
|
||||
// The ContentType encryptedData is the most complicated and its form can be gathered by
|
||||
// the go type below. It essentially contains a raw octet string of encrypted data and an
|
||||
// algorithm identifier for use in decrypting this data
|
||||
// algorithm identifier for use in decrypting this data.
|
||||
package pkcs7
|
||||
|
||||
import (
|
||||
|
|
@ -55,7 +55,7 @@ import (
|
|||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
)
|
||||
|
||||
// Types used for asn1 Unmarshaling
|
||||
// Types used for asn1 Unmarshaling.
|
||||
|
||||
type signedData struct {
|
||||
Version int
|
||||
|
|
@ -72,7 +72,7 @@ type initPKCS7 struct {
|
|||
Content asn1.RawValue `asn1:"tag:0,explicit,optional"`
|
||||
}
|
||||
|
||||
// Object identifiers strings of the three implemented PKCS7 types
|
||||
// Object identifier strings of the three implemented PKCS7 types.
|
||||
const (
|
||||
ObjIDData = "1.2.840.113549.1.7.1"
|
||||
ObjIDSignedData = "1.2.840.113549.1.7.2"
|
||||
|
|
@ -84,21 +84,21 @@ const (
|
|||
// the ContentInfo field, the other two being nil. SignedData
|
||||
// is the degenerate SignedData Content info without signature used
|
||||
// to hold certificates and crls. Data is raw bytes, and EncryptedData
|
||||
// is as defined in PKCS #7 standard
|
||||
// is as defined in PKCS #7 standard.
|
||||
type PKCS7 struct {
|
||||
Raw asn1.RawContent
|
||||
ContentInfo string
|
||||
Content Content
|
||||
}
|
||||
|
||||
// Content implements three of the six possible PKCS7 data types. Only one is non-nil
|
||||
// Content implements three of the six possible PKCS7 data types. Only one is non-nil.
|
||||
type Content struct {
|
||||
Data []byte
|
||||
SignedData SignedData
|
||||
EncryptedData EncryptedData
|
||||
}
|
||||
|
||||
// SignedData defines the typical carrier of certificates and crls
|
||||
// SignedData defines the typical carrier of certificates and crls.
|
||||
type SignedData struct {
|
||||
Raw asn1.RawContent
|
||||
Version int
|
||||
|
|
@ -106,19 +106,19 @@ type SignedData struct {
|
|||
Crl *pkix.CertificateList
|
||||
}
|
||||
|
||||
// Data contains raw bytes. Used as a subtype in PKCS12
|
||||
// Data contains raw bytes. Used as a subtype in PKCS12.
|
||||
type Data struct {
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
// EncryptedData contains encrypted data. Used as a subtype in PKCS12
|
||||
// EncryptedData contains encrypted data. Used as a subtype in PKCS12.
|
||||
type EncryptedData struct {
|
||||
Raw asn1.RawContent
|
||||
Version int
|
||||
EncryptedContentInfo EncryptedContentInfo
|
||||
}
|
||||
|
||||
// EncryptedContentInfo is a subtype of PKCS7EncryptedData
|
||||
// EncryptedContentInfo is a subtype of PKCS7EncryptedData.
|
||||
type EncryptedContentInfo struct {
|
||||
Raw asn1.RawContent
|
||||
ContentType asn1.ObjectIdentifier
|
||||
|
|
@ -127,7 +127,7 @@ type EncryptedContentInfo struct {
|
|||
}
|
||||
|
||||
// ParsePKCS7 attempts to parse the DER encoded bytes of a
|
||||
// PKCS7 structure
|
||||
// PKCS7 structure.
|
||||
func ParsePKCS7(raw []byte) (msg *PKCS7, err error) {
|
||||
|
||||
var pkcs7 initPKCS7
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"encoding/pem"
|
||||
"errors"
|
||||
"net"
|
||||
"net/mail"
|
||||
"strings"
|
||||
|
||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
|
|
@ -145,7 +146,7 @@ type CertificateRequest struct {
|
|||
// BasicKeyRequest.
|
||||
func New() *CertificateRequest {
|
||||
return &CertificateRequest{
|
||||
KeyRequest: &BasicKeyRequest{},
|
||||
KeyRequest: NewBasicKeyRequest(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,6 +222,8 @@ func ParseRequest(req *CertificateRequest) (csr, key []byte, err error) {
|
|||
for i := range req.Hosts {
|
||||
if ip := net.ParseIP(req.Hosts[i]); ip != nil {
|
||||
tpl.IPAddresses = append(tpl.IPAddresses, ip)
|
||||
} else if email, err := mail.ParseAddress(req.Hosts[i]); err == nil && email != nil {
|
||||
tpl.EmailAddresses = append(tpl.EmailAddresses, req.Hosts[i])
|
||||
} else {
|
||||
tpl.DNSNames = append(tpl.DNSNames, req.Hosts[i])
|
||||
}
|
||||
|
|
@ -271,6 +274,9 @@ func getHosts(cert *x509.Certificate) []string {
|
|||
for _, dns := range cert.DNSNames {
|
||||
hosts = append(hosts, dns)
|
||||
}
|
||||
for _, email := range cert.EmailAddresses {
|
||||
hosts = append(hosts, email)
|
||||
}
|
||||
|
||||
return hosts
|
||||
}
|
||||
|
|
@ -380,6 +386,8 @@ func Generate(priv crypto.Signer, req *CertificateRequest) (csr []byte, err erro
|
|||
for i := range req.Hosts {
|
||||
if ip := net.ParseIP(req.Hosts[i]); ip != nil {
|
||||
tpl.IPAddresses = append(tpl.IPAddresses, ip)
|
||||
} else if email, err := mail.ParseAddress(req.Hosts[i]); err == nil && email != nil {
|
||||
tpl.EmailAddresses = append(tpl.EmailAddresses, email.Address)
|
||||
} else {
|
||||
tpl.DNSNames = append(tpl.DNSNames, req.Hosts[i])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ The index of codes are listed below:
|
|||
5100: NoKeyUsages
|
||||
5200: InvalidPolicy
|
||||
5300: InvalidRequest
|
||||
5400: UnknownProfile
|
||||
6XXX: DialError
|
||||
|
||||
2. Type HttpError is intended for CF SSL API to consume. It contains a HTTP status code that will be read and returned
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ const (
|
|||
|
||||
// CTError indicates a problem with the certificate transparency process
|
||||
CTError // 10XXX
|
||||
|
||||
// CertStoreError indicates a problem with the certificate store
|
||||
CertStoreError // 11XXX
|
||||
)
|
||||
|
||||
// None is a non-specified error.
|
||||
|
|
@ -143,6 +146,9 @@ const (
|
|||
// InvalidRequest indicates a certificate request violated the
|
||||
// constraints of the policy being applied to the request.
|
||||
InvalidRequest // 53XX
|
||||
|
||||
// UnknownProfile indicates that the profile does not exist.
|
||||
UnknownProfile // 54XX
|
||||
)
|
||||
|
||||
// The following are API client related errors, and should be
|
||||
|
|
@ -185,6 +191,15 @@ const (
|
|||
PrecertSubmissionFailed = 100 * (iota + 1)
|
||||
)
|
||||
|
||||
// Certificate persistence related errors specified with CertStoreError
|
||||
const (
|
||||
// InsertionFailed occurs when a SQL insert query failes to complete.
|
||||
InsertionFailed = 100 * (iota + 1)
|
||||
// RecordNotFound occurs when a SQL query targeting on one unique
|
||||
// record failes to update the specified row in the table.
|
||||
RecordNotFound
|
||||
)
|
||||
|
||||
// The error interface implementation, which formats to a JSON object string.
|
||||
func (e *Error) Error() string {
|
||||
marshaled, err := json.Marshal(e)
|
||||
|
|
@ -296,6 +311,8 @@ func New(category Category, reason Reason) *Error {
|
|||
msg = "Invalid or unknown policy"
|
||||
case InvalidRequest:
|
||||
msg = "Policy violation request"
|
||||
case UnknownProfile:
|
||||
msg = "Unknown policy profile"
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported CFSSL error reason %d under category PolicyError.",
|
||||
reason))
|
||||
|
|
@ -348,6 +365,13 @@ func New(category Category, reason Reason) *Error {
|
|||
default:
|
||||
panic(fmt.Sprintf("Unsupported CF-SSL error reason %d under category CTError.", reason))
|
||||
}
|
||||
case CertStoreError:
|
||||
switch reason {
|
||||
case Unknown:
|
||||
msg = "Certificate store action failed due to unknown error"
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported CF-SSL error reason %d under category CertStoreError.", reason))
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported CFSSL error type: %d.",
|
||||
|
|
@ -383,7 +407,8 @@ func Wrap(category Category, reason Reason, err error) *Error {
|
|||
errorCode += unknownAuthority
|
||||
}
|
||||
}
|
||||
case PrivateKeyError, IntermediatesError, RootError, PolicyError, DialError, APIClientError, CSRError, CTError:
|
||||
case PrivateKeyError, IntermediatesError, RootError, PolicyError, DialError,
|
||||
APIClientError, CSRError, CTError, CertStoreError:
|
||||
// no-op, just use the error
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported CFSSL error type: %d.",
|
||||
|
|
|
|||
|
|
@ -421,6 +421,21 @@ func ParseCSR(in []byte) (csr *x509.CertificateRequest, rest []byte, err error)
|
|||
return csr, rest, nil
|
||||
}
|
||||
|
||||
// ParseCSRPEM parses a PEM-encoded certificiate signing request.
|
||||
// It does not check the signature. This is useful for dumping data from a CSR
|
||||
// locally.
|
||||
func ParseCSRPEM(csrPEM []byte) (*x509.CertificateRequest, error) {
|
||||
block, _ := pem.Decode([]byte(csrPEM))
|
||||
der := block.Bytes
|
||||
csrObject, err := x509.ParseCertificateRequest(der)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return csrObject, nil
|
||||
}
|
||||
|
||||
// SignerAlgo returns an X.509 signature algorithm corresponding to
|
||||
// the crypto.Hash provided from a crypto.Signer.
|
||||
func SignerAlgo(priv crypto.Signer, h crypto.Hash) x509.SignatureAlgorithm {
|
||||
|
|
|
|||
63
Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers/testsuite/testing_helpers.go
generated
vendored
63
Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers/testsuite/testing_helpers.go
generated
vendored
|
|
@ -4,6 +4,7 @@ package testsuite
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"testing"
|
||||
// "crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
|
@ -14,6 +15,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/csr"
|
||||
// "github.com/cloudflare/cfssl/helpers/testsuite/stoppable"
|
||||
)
|
||||
|
|
@ -359,3 +361,64 @@ func cleanCLIOutput(CLIOutput []byte, item string) (cleanedOutput []byte, err er
|
|||
|
||||
return []byte(outputString), nil
|
||||
}
|
||||
|
||||
// NewConfig returns a config object from the data passed.
|
||||
func NewConfig(t *testing.T, configBytes []byte) *config.Config {
|
||||
conf, err := config.LoadConfig([]byte(configBytes))
|
||||
if err != nil {
|
||||
t.Fatal("config loading error:", err)
|
||||
}
|
||||
if !conf.Valid() {
|
||||
t.Fatal("config is not valid")
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
// CSRTest holds information about CSR test files.
|
||||
type CSRTest struct {
|
||||
File string
|
||||
KeyAlgo string
|
||||
KeyLen int
|
||||
// Error checking function
|
||||
ErrorCallback func(*testing.T, error)
|
||||
}
|
||||
|
||||
// CSRTests define a set of CSR files for testing.
|
||||
var CSRTests = []CSRTest{
|
||||
{
|
||||
File: "../../signer/local/testdata/rsa2048.csr",
|
||||
KeyAlgo: "rsa",
|
||||
KeyLen: 2048,
|
||||
ErrorCallback: nil,
|
||||
},
|
||||
{
|
||||
File: "../../signer/local/testdata/rsa3072.csr",
|
||||
KeyAlgo: "rsa",
|
||||
KeyLen: 3072,
|
||||
ErrorCallback: nil,
|
||||
},
|
||||
{
|
||||
File: "../../signer/local/testdata/rsa4096.csr",
|
||||
KeyAlgo: "rsa",
|
||||
KeyLen: 4096,
|
||||
ErrorCallback: nil,
|
||||
},
|
||||
{
|
||||
File: "../../signer/local/testdata/ecdsa256.csr",
|
||||
KeyAlgo: "ecdsa",
|
||||
KeyLen: 256,
|
||||
ErrorCallback: nil,
|
||||
},
|
||||
{
|
||||
File: "../../signer/local/testdata/ecdsa384.csr",
|
||||
KeyAlgo: "ecdsa",
|
||||
KeyLen: 384,
|
||||
ErrorCallback: nil,
|
||||
},
|
||||
{
|
||||
File: "../../signer/local/testdata/ecdsa521.csr",
|
||||
KeyAlgo: "ecdsa",
|
||||
KeyLen: 521,
|
||||
ErrorCallback: nil,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@ import (
|
|||
"bytes"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
|
|
@ -20,6 +23,21 @@ import (
|
|||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
// revocationReasonCodes is a map between string reason codes
|
||||
// to integers as defined in RFC 5280
|
||||
var revocationReasonCodes = map[string]int{
|
||||
"unspecified": ocsp.Unspecified,
|
||||
"keycompromise": ocsp.KeyCompromise,
|
||||
"cacompromise": ocsp.CACompromise,
|
||||
"affiliationchanged": ocsp.AffiliationChanged,
|
||||
"superseded": ocsp.Superseded,
|
||||
"cessationofoperation": ocsp.CessationOfOperation,
|
||||
"certificatehold": ocsp.CertificateHold,
|
||||
"removefromcrl": ocsp.RemoveFromCRL,
|
||||
"privilegewithdrawn": ocsp.PrivilegeWithdrawn,
|
||||
"aacompromise": ocsp.AACompromise,
|
||||
}
|
||||
|
||||
// StatusCode is a map between string statuses sent by cli/api
|
||||
// to ocsp int statuses
|
||||
var StatusCode = map[string]int{
|
||||
|
|
@ -35,6 +53,7 @@ type SignRequest struct {
|
|||
Status string
|
||||
Reason int
|
||||
RevokedAt time.Time
|
||||
Extensions []pkix.Extension
|
||||
}
|
||||
|
||||
// Signer represents a general signer of OCSP responses. It is
|
||||
|
|
@ -56,6 +75,27 @@ type StandardSigner struct {
|
|||
interval time.Duration
|
||||
}
|
||||
|
||||
// ReasonStringToCode tries to convert a reason string to an integer code
|
||||
func ReasonStringToCode(reason string) (reasonCode int, err error) {
|
||||
// default to 0
|
||||
if reason == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
reasonCode, present := revocationReasonCodes[strings.ToLower(reason)]
|
||||
if !present {
|
||||
reasonCode, err = strconv.Atoi(reason)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if reasonCode >= ocsp.AACompromise || reasonCode <= ocsp.Unspecified {
|
||||
return 0, cferr.New(cferr.OCSPError, cferr.InvalidStatus)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NewSignerFromFile reads the issuer cert, the responder cert and the responder key
|
||||
// from PEM files, and takes an interval in seconds
|
||||
func NewSignerFromFile(issuerFile, responderFile, keyFile string, interval time.Duration) (Signer, error) {
|
||||
|
|
@ -138,11 +178,12 @@ func (s StandardSigner) Sign(req SignRequest) ([]byte, error) {
|
|||
}
|
||||
|
||||
template := ocsp.Response{
|
||||
Status: status,
|
||||
SerialNumber: req.Certificate.SerialNumber,
|
||||
ThisUpdate: thisUpdate,
|
||||
NextUpdate: nextUpdate,
|
||||
Certificate: certificate,
|
||||
Status: status,
|
||||
SerialNumber: req.Certificate.SerialNumber,
|
||||
ThisUpdate: thisUpdate,
|
||||
NextUpdate: nextUpdate,
|
||||
Certificate: certificate,
|
||||
ExtraExtensions: req.Extensions,
|
||||
}
|
||||
|
||||
if status == ocsp.Revoked {
|
||||
|
|
|
|||
|
|
@ -7,26 +7,25 @@ import (
|
|||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/mail"
|
||||
|
||||
"encoding/asn1"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/info"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/google/certificate-transparency/go"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/google/certificate-transparency/go/client"
|
||||
)
|
||||
|
|
@ -34,10 +33,11 @@ import (
|
|||
// Signer contains a signer that uses the standard library to
|
||||
// support both ECDSA and RSA CA keys.
|
||||
type Signer struct {
|
||||
ca *x509.Certificate
|
||||
priv crypto.Signer
|
||||
policy *config.Signing
|
||||
sigAlgo x509.SignatureAlgorithm
|
||||
ca *x509.Certificate
|
||||
priv crypto.Signer
|
||||
policy *config.Signing
|
||||
sigAlgo x509.SignatureAlgorithm
|
||||
dbAccessor certdb.Accessor
|
||||
}
|
||||
|
||||
// NewSigner creates a new Signer directly from a
|
||||
|
|
@ -102,12 +102,14 @@ func (s *Signer) sign(template *x509.Certificate, profile *config.SigningProfile
|
|||
return
|
||||
}
|
||||
template.DNSNames = nil
|
||||
template.EmailAddresses = nil
|
||||
s.ca = template
|
||||
initRoot = true
|
||||
template.MaxPathLen = signer.MaxPathLen
|
||||
} else if template.IsCA {
|
||||
template.MaxPathLen = 1
|
||||
template.DNSNames = nil
|
||||
template.EmailAddresses = nil
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, template, s.ca, template.PublicKey, s.priv)
|
||||
|
|
@ -228,6 +230,9 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
|
|||
if profile.CSRWhitelist.IPAddresses {
|
||||
safeTemplate.IPAddresses = csrTemplate.IPAddresses
|
||||
}
|
||||
if profile.CSRWhitelist.EmailAddresses {
|
||||
safeTemplate.EmailAddresses = csrTemplate.EmailAddresses
|
||||
}
|
||||
}
|
||||
|
||||
OverrideHosts(&safeTemplate, req.Hosts)
|
||||
|
|
@ -245,6 +250,11 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
|
|||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
||||
}
|
||||
}
|
||||
for _, name := range safeTemplate.EmailAddresses {
|
||||
if profile.NameWhitelist.Find([]byte(name)) == nil {
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if profile.ClientProvidesSerialNumbers {
|
||||
|
|
@ -254,11 +264,25 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
|
|||
}
|
||||
safeTemplate.SerialNumber = req.Serial
|
||||
} else {
|
||||
serialNumber, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
|
||||
// RFC 5280 4.1.2.2:
|
||||
// Certificate users MUST be able to handle serialNumber
|
||||
// values up to 20 octets. Conforming CAs MUST NOT use
|
||||
// serialNumber values longer than 20 octets.
|
||||
//
|
||||
// If CFSSL is providing the serial numbers, it makes
|
||||
// sense to use the max supported size.
|
||||
serialNumber := make([]byte, 20)
|
||||
_, err = io.ReadFull(rand.Reader, serialNumber)
|
||||
if err != nil {
|
||||
return nil, cferr.Wrap(cferr.CertificateError, cferr.Unknown, err)
|
||||
}
|
||||
safeTemplate.SerialNumber = serialNumber
|
||||
|
||||
// SetBytes interprets buf as the bytes of a big-endian
|
||||
// unsigned integer. The leading byte should be masked
|
||||
// off to ensure it isn't negative.
|
||||
serialNumber[0] &= 0x7F
|
||||
|
||||
safeTemplate.SerialNumber = new(big.Int).SetBytes(serialNumber)
|
||||
}
|
||||
|
||||
if len(req.Extensions) > 0 {
|
||||
|
|
@ -323,7 +347,29 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
|
|||
var SCTListExtension = pkix.Extension{Id: signer.SCTListOID, Critical: false, Value: serializedSCTList}
|
||||
certTBS.ExtraExtensions = append(certTBS.ExtraExtensions, SCTListExtension)
|
||||
}
|
||||
return s.sign(&certTBS, profile)
|
||||
var signedCert []byte
|
||||
signedCert, err = s.sign(&certTBS, profile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.dbAccessor != nil {
|
||||
var certRecord = &certdb.CertificateRecord{
|
||||
Serial: certTBS.SerialNumber.String(),
|
||||
CALabel: req.Label,
|
||||
Status: "good",
|
||||
Expiry: certTBS.NotAfter,
|
||||
PEM: string(signedCert),
|
||||
}
|
||||
|
||||
err = s.dbAccessor.InsertCertificate(certRecord)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug("saved certificate with serial number ", certTBS.SerialNumber)
|
||||
}
|
||||
|
||||
return signedCert, nil
|
||||
}
|
||||
|
||||
func serializeSCTList(sctList []ct.SignedCertificateTimestamp) ([]byte, error) {
|
||||
|
|
@ -380,6 +426,11 @@ func (s *Signer) SetPolicy(policy *config.Signing) {
|
|||
s.policy = policy
|
||||
}
|
||||
|
||||
// SetDBAccessor sets the signers' cert db accessor
|
||||
func (s *Signer) SetDBAccessor(dba certdb.Accessor) {
|
||||
s.dbAccessor = dba
|
||||
}
|
||||
|
||||
// Policy returns the signer's policy.
|
||||
func (s *Signer) Policy() *config.Signing {
|
||||
return s.policy
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"errors"
|
||||
|
||||
"github.com/cloudflare/cfssl/api/client"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/info"
|
||||
|
|
@ -106,6 +107,11 @@ func (s *Signer) SetPolicy(policy *config.Signing) {
|
|||
s.policy = policy
|
||||
}
|
||||
|
||||
// SetDBAccessor sets the signers' cert db accessor
|
||||
func (s *Signer) SetDBAccessor(dba certdb.Accessor) {
|
||||
// noop
|
||||
}
|
||||
|
||||
// Policy returns the signer's policy.
|
||||
func (s *Signer) Policy() *config.Signing {
|
||||
return s.policy
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/csr"
|
||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
|
|
@ -94,6 +95,7 @@ func SplitHosts(hostList string) []string {
|
|||
type Signer interface {
|
||||
Info(info.Req) (*info.Resp, error)
|
||||
Policy() *config.Signing
|
||||
SetDBAccessor(certdb.Accessor)
|
||||
SetPolicy(*config.Signing)
|
||||
SigAlgo() x509.SignatureAlgorithm
|
||||
Sign(req SignRequest) (cert []byte, err error)
|
||||
|
|
@ -172,6 +174,7 @@ func ParseCertificateRequest(s Signer, csrBytes []byte) (template *x509.Certific
|
|||
SignatureAlgorithm: s.SigAlgo(),
|
||||
DNSNames: csr.DNSNames,
|
||||
IPAddresses: csr.IPAddresses,
|
||||
EmailAddresses: csr.EmailAddresses,
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
|||
169
Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/universal/universal.go
generated
vendored
169
Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/universal/universal.go
generated
vendored
|
|
@ -2,14 +2,26 @@
|
|||
package universal
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/info"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/local"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/pkcs11"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/remote"
|
||||
)
|
||||
|
||||
// Signer represents a universal signer which is both local and remote
|
||||
// to fulfill the signer.Signer interface.
|
||||
type Signer struct {
|
||||
local signer.Signer
|
||||
remote signer.Signer
|
||||
policy *config.Signing
|
||||
}
|
||||
|
||||
// Root is used to define where the universal signer gets its public
|
||||
// certificate and private keys for signing.
|
||||
type Root struct {
|
||||
|
|
@ -67,6 +79,54 @@ var localSignerList = []localSignerCheck{
|
|||
fileBackedSigner,
|
||||
}
|
||||
|
||||
func newLocalSigner(root Root, policy *config.Signing) (s signer.Signer, err error) {
|
||||
// shouldProvide indicates whether the
|
||||
// function *should* have produced a key. If
|
||||
// it's true, we should use the signer and
|
||||
// error returned. Otherwise, keep looking for
|
||||
// signers.
|
||||
var shouldProvide bool
|
||||
|
||||
// localSignerList is defined in the
|
||||
// universal_signers*.go files. These activate
|
||||
// and deactivate signers based on build
|
||||
// flags; for example,
|
||||
// universal_signers_pkcs11.go contains a list
|
||||
// of valid signers when PKCS #11 is turned
|
||||
// on.
|
||||
for _, possibleSigner := range localSignerList {
|
||||
s, shouldProvide, err = possibleSigner(&root, policy)
|
||||
if shouldProvide {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
err = cferr.New(cferr.PrivateKeyError, cferr.Unknown)
|
||||
}
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
func newUniversalSigner(root Root, policy *config.Signing) (*Signer, error) {
|
||||
ls, err := newLocalSigner(root, policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rs, err := remote.NewSigner(policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &Signer{
|
||||
policy: policy,
|
||||
local: ls,
|
||||
remote: rs,
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
|
||||
// NewSigner generates a new certificate signer from a Root structure.
|
||||
// This is one of two standard signers: local or remote. If the root
|
||||
// structure specifies a force remote, then a remote signer is created,
|
||||
|
|
@ -91,40 +151,89 @@ func NewSigner(root Root, policy *config.Signing) (signer.Signer, error) {
|
|||
s, err = remote.NewSigner(policy)
|
||||
} else {
|
||||
if policy.NeedsLocalSigner() && policy.NeedsRemoteSigner() {
|
||||
// Currently we don't support a hybrid signer
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
||||
}
|
||||
|
||||
if policy.NeedsLocalSigner() {
|
||||
// shouldProvide indicates whether the
|
||||
// function *should* have produced a key. If
|
||||
// it's true, we should use the signer and
|
||||
// error returned. Otherwise, keep looking for
|
||||
// signers.
|
||||
var shouldProvide bool
|
||||
// localSignerList is defined in the
|
||||
// universal_signers*.go files. These activate
|
||||
// and deactivate signers based on build
|
||||
// flags; for example,
|
||||
// universal_signers_pkcs11.go contains a list
|
||||
// of valid signers when PKCS #11 is turned
|
||||
// on.
|
||||
for _, possibleSigner := range localSignerList {
|
||||
s, shouldProvide, err = possibleSigner(&root, policy)
|
||||
if shouldProvide {
|
||||
break
|
||||
}
|
||||
s, err = newUniversalSigner(root, policy)
|
||||
} else {
|
||||
if policy.NeedsLocalSigner() {
|
||||
s, err = newLocalSigner(root, policy)
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
err = cferr.New(cferr.PrivateKeyError, cferr.Unknown)
|
||||
if policy.NeedsRemoteSigner() {
|
||||
s, err = remote.NewSigner(policy)
|
||||
}
|
||||
}
|
||||
|
||||
if policy.NeedsRemoteSigner() {
|
||||
s, err = remote.NewSigner(policy)
|
||||
}
|
||||
}
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
// getMatchingProfile returns the SigningProfile that matches the profile passed.
|
||||
// if an empty profile string is passed it returns the default profile.
|
||||
func (s *Signer) getMatchingProfile(profile string) (*config.SigningProfile, error) {
|
||||
if profile == "" {
|
||||
return s.policy.Default, nil
|
||||
}
|
||||
|
||||
for p, signingProfile := range s.policy.Profiles {
|
||||
if p == profile {
|
||||
return signingProfile, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.UnknownProfile)
|
||||
}
|
||||
|
||||
// Sign sends a signature request to either the remote or local signer,
|
||||
// receiving a signed certificate or an error in response.
|
||||
func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
|
||||
profile, err := s.getMatchingProfile(req.Profile)
|
||||
if err != nil {
|
||||
return cert, err
|
||||
}
|
||||
|
||||
if profile.RemoteServer != "" {
|
||||
return s.remote.Sign(req)
|
||||
}
|
||||
return s.local.Sign(req)
|
||||
|
||||
}
|
||||
|
||||
// Info sends an info request to the remote or local CFSSL server
|
||||
// receiving an Resp struct or an error in response.
|
||||
func (s *Signer) Info(req info.Req) (resp *info.Resp, err error) {
|
||||
profile, err := s.getMatchingProfile(req.Profile)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if profile.RemoteServer != "" {
|
||||
return s.remote.Info(req)
|
||||
}
|
||||
return s.local.Info(req)
|
||||
|
||||
}
|
||||
|
||||
// SetDBAccessor sets the signer's cert db accessor.
|
||||
func (s *Signer) SetDBAccessor(dba certdb.Accessor) {
|
||||
s.local.SetDBAccessor(dba)
|
||||
s.remote.SetDBAccessor(dba)
|
||||
}
|
||||
|
||||
// SigAlgo returns the RSA signer's signature algorithm.
|
||||
func (s *Signer) SigAlgo() x509.SignatureAlgorithm {
|
||||
if s.local != nil {
|
||||
return s.local.SigAlgo()
|
||||
}
|
||||
|
||||
// currently remote.SigAlgo just returns
|
||||
// x509.UnknownSignatureAlgorithm.
|
||||
return s.remote.SigAlgo()
|
||||
}
|
||||
|
||||
// SetPolicy sets the signer's signature policy.
|
||||
func (s *Signer) SetPolicy(policy *config.Signing) {
|
||||
s.policy = policy
|
||||
}
|
||||
|
||||
// Policy returns the signer's policy.
|
||||
func (s *Signer) Policy() *config.Signing {
|
||||
return s.policy
|
||||
}
|
||||
|
|
|
|||
13
Godeps/_workspace/src/github.com/cloudflare/cfssl/whitelist/LICENSE
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/cloudflare/cfssl/whitelist/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
Copyright (c) 2014 Kyle Isom <kyle@gokyle.org>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
||||
|
|
@ -19,23 +19,60 @@ import (
|
|||
"encoding/asn1"
|
||||
"errors"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var idPKIXOCSPBasic = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 1})
|
||||
|
||||
// These are internal structures that reflect the ASN.1 structure of an OCSP
|
||||
// response. See RFC 2560, section 4.2.
|
||||
// ResponseStatus contains the result of an OCSP request. See
|
||||
// https://tools.ietf.org/html/rfc6960#section-2.3
|
||||
type ResponseStatus int
|
||||
|
||||
const (
|
||||
ocspSuccess = 0
|
||||
ocspMalformed = 1
|
||||
ocspInternalError = 2
|
||||
ocspTryLater = 3
|
||||
ocspSigRequired = 4
|
||||
ocspUnauthorized = 5
|
||||
Success ResponseStatus = 0
|
||||
Malformed ResponseStatus = 1
|
||||
InternalError ResponseStatus = 2
|
||||
TryLater ResponseStatus = 3
|
||||
// Status code four is ununsed in OCSP. See
|
||||
// https://tools.ietf.org/html/rfc6960#section-4.2.1
|
||||
SignatureRequired ResponseStatus = 5
|
||||
Unauthorized ResponseStatus = 6
|
||||
)
|
||||
|
||||
func (r ResponseStatus) String() string {
|
||||
switch r {
|
||||
case Success:
|
||||
return "success"
|
||||
case Malformed:
|
||||
return "malformed"
|
||||
case InternalError:
|
||||
return "internal error"
|
||||
case TryLater:
|
||||
return "try later"
|
||||
case SignatureRequired:
|
||||
return "signature required"
|
||||
case Unauthorized:
|
||||
return "unauthorized"
|
||||
default:
|
||||
return "unknown OCSP status: " + strconv.Itoa(int(r))
|
||||
}
|
||||
}
|
||||
|
||||
// ResponseError is an error that may be returned by ParseResponse to indicate
|
||||
// that the response itself is an error, not just that its indicating that a
|
||||
// certificate is revoked, unknown, etc.
|
||||
type ResponseError struct {
|
||||
Status ResponseStatus
|
||||
}
|
||||
|
||||
func (r ResponseError) Error() string {
|
||||
return "ocsp: error from server: " + r.Status.String()
|
||||
}
|
||||
|
||||
// These are internal structures that reflect the ASN.1 structure of an OCSP
|
||||
// response. See RFC 2560, section 4.2.
|
||||
|
||||
type certID struct {
|
||||
HashAlgorithm pkix.AlgorithmIdentifier
|
||||
NameHash []byte
|
||||
|
|
@ -60,7 +97,7 @@ type request struct {
|
|||
|
||||
type responseASN1 struct {
|
||||
Status asn1.Enumerated
|
||||
Response responseBytes `asn1:"explicit,tag:0"`
|
||||
Response responseBytes `asn1:"explicit,tag:0,optional"`
|
||||
}
|
||||
|
||||
type responseBytes struct {
|
||||
|
|
@ -85,12 +122,13 @@ type responseData struct {
|
|||
}
|
||||
|
||||
type singleResponse struct {
|
||||
CertID certID
|
||||
Good asn1.Flag `asn1:"tag:0,optional"`
|
||||
Revoked revokedInfo `asn1:"tag:1,optional"`
|
||||
Unknown asn1.Flag `asn1:"tag:2,optional"`
|
||||
ThisUpdate time.Time `asn1:"generalized"`
|
||||
NextUpdate time.Time `asn1:"generalized,explicit,tag:0,optional"`
|
||||
CertID certID
|
||||
Good asn1.Flag `asn1:"tag:0,optional"`
|
||||
Revoked revokedInfo `asn1:"tag:1,optional"`
|
||||
Unknown asn1.Flag `asn1:"tag:2,optional"`
|
||||
ThisUpdate time.Time `asn1:"generalized"`
|
||||
NextUpdate time.Time `asn1:"generalized,explicit,tag:0,optional"`
|
||||
SingleExtensions []pkix.Extension `asn1:"explicit,tag:1,optional"`
|
||||
}
|
||||
|
||||
type revokedInfo struct {
|
||||
|
|
@ -235,11 +273,13 @@ const (
|
|||
// Good means that the certificate is valid.
|
||||
Good = iota
|
||||
// Revoked means that the certificate has been deliberately revoked.
|
||||
Revoked = iota
|
||||
Revoked
|
||||
// Unknown means that the OCSP responder doesn't know about the certificate.
|
||||
Unknown = iota
|
||||
// ServerFailed means that the OCSP responder failed to process the request.
|
||||
ServerFailed = iota
|
||||
Unknown
|
||||
// ServerFailed is unused and was never used (see
|
||||
// https://go-review.googlesource.com/#/c/18944). ParseResponse will
|
||||
// return a ResponseError when an error response is parsed.
|
||||
ServerFailed
|
||||
)
|
||||
|
||||
// The enumerated reasons for revoking a certificate. See RFC 5280.
|
||||
|
|
@ -257,7 +297,7 @@ const (
|
|||
AACompromise = iota
|
||||
)
|
||||
|
||||
// Request represents an OCSP request. See RFC 2560.
|
||||
// Request represents an OCSP request. See RFC 6960.
|
||||
type Request struct {
|
||||
HashAlgorithm crypto.Hash
|
||||
IssuerNameHash []byte
|
||||
|
|
@ -265,9 +305,10 @@ type Request struct {
|
|||
SerialNumber *big.Int
|
||||
}
|
||||
|
||||
// Response represents an OCSP response. See RFC 2560.
|
||||
// Response represents an OCSP response containing a single SingleResponse. See
|
||||
// RFC 6960.
|
||||
type Response struct {
|
||||
// Status is one of {Good, Revoked, Unknown, ServerFailed}
|
||||
// Status is one of {Good, Revoked, Unknown}
|
||||
Status int
|
||||
SerialNumber *big.Int
|
||||
ProducedAt, ThisUpdate, NextUpdate, RevokedAt time.Time
|
||||
|
|
@ -278,6 +319,20 @@ type Response struct {
|
|||
TBSResponseData []byte
|
||||
Signature []byte
|
||||
SignatureAlgorithm x509.SignatureAlgorithm
|
||||
|
||||
// Extensions contains raw X.509 extensions from the singleExtensions field
|
||||
// of the OCSP response. When parsing certificates, this can be used to
|
||||
// extract non-critical extensions that are not parsed by this package. When
|
||||
// marshaling OCSP responses, the Extensions field is ignored, see
|
||||
// ExtraExtensions.
|
||||
Extensions []pkix.Extension
|
||||
|
||||
// ExtraExtensions contains extensions to be copied, raw, into any marshaled
|
||||
// OCSP response (in the singleExtensions field). Values override any
|
||||
// extensions that would otherwise be produced based on the other fields. The
|
||||
// ExtraExtensions field is not populated when parsing certificates, see
|
||||
// Extensions.
|
||||
ExtraExtensions []pkix.Extension
|
||||
}
|
||||
|
||||
// These are pre-serialized error responses for the various non-success codes
|
||||
|
|
@ -342,8 +397,10 @@ func ParseRequest(bytes []byte) (*Request, error) {
|
|||
// ParseResponse parses an OCSP response in DER form. It only supports
|
||||
// responses for a single certificate. If the response contains a certificate
|
||||
// then the signature over the response is checked. If issuer is not nil then
|
||||
// it will be used to validate the signature or embedded certificate. Invalid
|
||||
// signatures or parse failures will result in a ParseError.
|
||||
// it will be used to validate the signature or embedded certificate.
|
||||
//
|
||||
// Invalid signatures or parse failures will result in a ParseError. Error
|
||||
// responses will result in a ResponseError.
|
||||
func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) {
|
||||
var resp responseASN1
|
||||
rest, err := asn1.Unmarshal(bytes, &resp)
|
||||
|
|
@ -354,10 +411,8 @@ func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) {
|
|||
return nil, ParseError("trailing data in OCSP response")
|
||||
}
|
||||
|
||||
ret := new(Response)
|
||||
if resp.Status != ocspSuccess {
|
||||
ret.Status = ServerFailed
|
||||
return ret, nil
|
||||
if status := ResponseStatus(resp.Status); status != Success {
|
||||
return nil, ResponseError{status}
|
||||
}
|
||||
|
||||
if !resp.Response.ResponseType.Equal(idPKIXOCSPBasic) {
|
||||
|
|
@ -378,9 +433,11 @@ func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) {
|
|||
return nil, ParseError("OCSP response contains bad number of responses")
|
||||
}
|
||||
|
||||
ret.TBSResponseData = basicResp.TBSResponseData.Raw
|
||||
ret.Signature = basicResp.Signature.RightAlign()
|
||||
ret.SignatureAlgorithm = getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm)
|
||||
ret := &Response{
|
||||
TBSResponseData: basicResp.TBSResponseData.Raw,
|
||||
Signature: basicResp.Signature.RightAlign(),
|
||||
SignatureAlgorithm: getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm),
|
||||
}
|
||||
|
||||
if len(basicResp.Certificates) > 0 {
|
||||
ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes)
|
||||
|
|
@ -405,6 +462,13 @@ func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) {
|
|||
|
||||
r := basicResp.TBSResponseData.Responses[0]
|
||||
|
||||
for _, ext := range r.SingleExtensions {
|
||||
if ext.Critical {
|
||||
return nil, ParseError("unsupported critical extension")
|
||||
}
|
||||
}
|
||||
ret.Extensions = r.SingleExtensions
|
||||
|
||||
ret.SerialNumber = r.CertID.SerialNumber
|
||||
|
||||
switch {
|
||||
|
|
@ -534,8 +598,9 @@ func CreateResponse(issuer, responderCert *x509.Certificate, template Response,
|
|||
IssuerKeyHash: issuerKeyHash,
|
||||
SerialNumber: template.SerialNumber,
|
||||
},
|
||||
ThisUpdate: template.ThisUpdate.UTC(),
|
||||
NextUpdate: template.NextUpdate.UTC(),
|
||||
ThisUpdate: template.ThisUpdate.UTC(),
|
||||
NextUpdate: template.NextUpdate.UTC(),
|
||||
SingleExtensions: template.ExtraExtensions,
|
||||
}
|
||||
|
||||
switch template.Status {
|
||||
|
|
@ -599,7 +664,7 @@ func CreateResponse(issuer, responderCert *x509.Certificate, template Response,
|
|||
}
|
||||
|
||||
return asn1.Marshal(responseASN1{
|
||||
Status: ocspSuccess,
|
||||
Status: asn1.Enumerated(Success),
|
||||
Response: responseBytes{
|
||||
ResponseType: idPKIXOCSPBasic,
|
||||
Response: responseDER,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ type safeBag struct {
|
|||
|
||||
type pkcs12Attribute struct {
|
||||
Id asn1.ObjectIdentifier
|
||||
Value asn1.RawValue `ans1:"set"`
|
||||
Value asn1.RawValue `asn1:"set"`
|
||||
}
|
||||
|
||||
type encryptedPrivateKeyInfo struct {
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ func setup(t *testing.T) *testCtx {
|
|||
PublicKey: true,
|
||||
SignatureAlgorithm: true,
|
||||
},
|
||||
ClientProvidesSerialNumbers: true,
|
||||
},
|
||||
ecdsaProfileName: &cfsslConfig.SigningProfile{
|
||||
Usage: []string{"digital signature", "server auth"},
|
||||
|
|
@ -224,6 +225,7 @@ func setup(t *testing.T) *testCtx {
|
|||
PublicKey: true,
|
||||
SignatureAlgorithm: true,
|
||||
},
|
||||
ClientProvidesSerialNumbers: true,
|
||||
},
|
||||
},
|
||||
Default: &cfsslConfig.SigningProfile{
|
||||
|
|
|
|||
|
|
@ -82,10 +82,6 @@ func (src *DBSource) Response(req *ocsp.Request) ([]byte, bool) {
|
|||
src.log.Info(fmt.Sprintf("OCSP Response sent for CA=%s, Serial=%s", hex.EncodeToString(src.caKeyHash), serialString))
|
||||
}
|
||||
}()
|
||||
// Note: we first check for an OCSP response in the certificateStatus table (
|
||||
// the new method) if we don't find a response there we instead look in the
|
||||
// ocspResponses table (the old method) while transitioning between the two
|
||||
// tables.
|
||||
err := src.dbMap.SelectOne(
|
||||
&response,
|
||||
"SELECT ocspResponse FROM certificateStatus WHERE serial = :serial",
|
||||
|
|
@ -94,19 +90,6 @@ func (src *DBSource) Response(req *ocsp.Request) ([]byte, bool) {
|
|||
if err != nil && err != sql.ErrNoRows {
|
||||
src.log.Err(fmt.Sprintf("Failed to retrieve response from certificateStatus table: %s", err))
|
||||
}
|
||||
// TODO(#970): Delete this ocspResponses check once the table has been removed
|
||||
if len(response) == 0 {
|
||||
// Ignoring possible error, if response hasn't been filled, attempt to find
|
||||
// response in old table
|
||||
err = src.dbMap.SelectOne(
|
||||
&response,
|
||||
"SELECT response from ocspResponses WHERE serial = :serial ORDER BY id DESC LIMIT 1;",
|
||||
map[string]interface{}{"serial": serialString},
|
||||
)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
src.log.Err(fmt.Sprintf("Failed to retrieve response from ocspResponses table: %s", err))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,6 @@ func TestErrorLog(t *testing.T) {
|
|||
test.Assert(t, !found, "Somehow found OCSP response")
|
||||
|
||||
test.AssertEquals(t, len(mockLog.GetAllMatching("Failed to retrieve response from certificateStatus table")), 1)
|
||||
test.AssertEquals(t, len(mockLog.GetAllMatching("Failed to retrieve response from ocspResponses table")), 1)
|
||||
}
|
||||
|
||||
func mustRead(path string) []byte {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/info"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
|
||||
|
|
@ -311,6 +312,11 @@ func (bhs BadHSMSigner) SetPolicy(*config.Signing) {
|
|||
return
|
||||
}
|
||||
|
||||
// SetDBAccessor is a mock.
|
||||
func (bhs BadHSMSigner) SetDBAccessor(certdb.Accessor) {
|
||||
return
|
||||
}
|
||||
|
||||
// SigAlgo is a mock
|
||||
func (bhs BadHSMSigner) SigAlgo() x509.SignatureAlgorithm {
|
||||
return x509.UnknownSignatureAlgorithm
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
|
|||
SignatureAlgorithm: true,
|
||||
DNSNames: true,
|
||||
},
|
||||
ClientProvidesSerialNumbers: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue