Merge branch 'master' into delete_check_caa
This commit is contained in:
commit
fb657de5f1
|
|
@ -12,58 +12,63 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/cloudflare/cfssl/auth",
|
"ImportPath": "github.com/cloudflare/cfssl/auth",
|
||||||
"Comment": "1.1.0-268-ge32101b",
|
"Comment": "1.1.0-355-g3f3fa68",
|
||||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
"Rev": "3f3fa68e8d6ce6ceace60ea86461f8be41fa477b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/cloudflare/cfssl/certdb",
|
||||||
|
"Comment": "1.1.0-355-g3f3fa68",
|
||||||
|
"Rev": "3f3fa68e8d6ce6ceace60ea86461f8be41fa477b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/cloudflare/cfssl/config",
|
"ImportPath": "github.com/cloudflare/cfssl/config",
|
||||||
"Comment": "1.1.0-268-ge32101b",
|
"Comment": "1.1.0-355-g3f3fa68",
|
||||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
"Rev": "3f3fa68e8d6ce6ceace60ea86461f8be41fa477b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs11key",
|
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs11key",
|
||||||
"Comment": "1.1.0-268-ge32101b",
|
"Comment": "1.1.0-355-g3f3fa68",
|
||||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
"Rev": "3f3fa68e8d6ce6ceace60ea86461f8be41fa477b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs7",
|
"ImportPath": "github.com/cloudflare/cfssl/crypto/pkcs7",
|
||||||
"Comment": "1.1.0-268-ge32101b",
|
"Comment": "1.1.0-355-g3f3fa68",
|
||||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
"Rev": "3f3fa68e8d6ce6ceace60ea86461f8be41fa477b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/cloudflare/cfssl/csr",
|
"ImportPath": "github.com/cloudflare/cfssl/csr",
|
||||||
"Comment": "1.1.0-268-ge32101b",
|
"Comment": "1.1.0-355-g3f3fa68",
|
||||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
"Rev": "3f3fa68e8d6ce6ceace60ea86461f8be41fa477b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/cloudflare/cfssl/errors",
|
"ImportPath": "github.com/cloudflare/cfssl/errors",
|
||||||
"Comment": "1.1.0-268-ge32101b",
|
"Comment": "1.1.0-355-g3f3fa68",
|
||||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
"Rev": "3f3fa68e8d6ce6ceace60ea86461f8be41fa477b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/cloudflare/cfssl/helpers",
|
"ImportPath": "github.com/cloudflare/cfssl/helpers",
|
||||||
"Comment": "1.1.0-268-ge32101b",
|
"Comment": "1.1.0-355-g3f3fa68",
|
||||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
"Rev": "3f3fa68e8d6ce6ceace60ea86461f8be41fa477b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/cloudflare/cfssl/info",
|
"ImportPath": "github.com/cloudflare/cfssl/info",
|
||||||
"Comment": "1.1.0-268-ge32101b",
|
"Comment": "1.1.0-355-g3f3fa68",
|
||||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
"Rev": "3f3fa68e8d6ce6ceace60ea86461f8be41fa477b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/cloudflare/cfssl/log",
|
"ImportPath": "github.com/cloudflare/cfssl/log",
|
||||||
"Comment": "1.1.0-268-ge32101b",
|
"Comment": "1.1.0-355-g3f3fa68",
|
||||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
"Rev": "3f3fa68e8d6ce6ceace60ea86461f8be41fa477b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/cloudflare/cfssl/ocsp",
|
"ImportPath": "github.com/cloudflare/cfssl/ocsp",
|
||||||
"Comment": "1.1.0-268-ge32101b",
|
"Comment": "1.1.0-355-g3f3fa68",
|
||||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
"Rev": "3f3fa68e8d6ce6ceace60ea86461f8be41fa477b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/cloudflare/cfssl/signer",
|
"ImportPath": "github.com/cloudflare/cfssl/signer",
|
||||||
"Comment": "1.1.0-268-ge32101b",
|
"Comment": "1.1.0-355-g3f3fa68",
|
||||||
"Rev": "e32101b1ae6cb0e1f1653c8ebec92c0113908136"
|
"Rev": "3f3fa68e8d6ce6ceace60ea86461f8be41fa477b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/codegangsta/cli",
|
"ImportPath": "github.com/codegangsta/cli",
|
||||||
|
|
@ -114,7 +119,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/letsencrypt/net/publicsuffix",
|
"ImportPath": "github.com/letsencrypt/net/publicsuffix",
|
||||||
"Rev": "13c140962d9691f998ad9ca4f60e82815bc4685d"
|
"Rev": "7eb31e4ef6d2609d3ef5597cbc65cb68c8027f62"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/miekg/dns",
|
"ImportPath": "github.com/miekg/dns",
|
||||||
|
|
@ -134,11 +139,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/crypto/ocsp",
|
"ImportPath": "golang.org/x/crypto/ocsp",
|
||||||
"Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc"
|
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/crypto/pkcs12",
|
"ImportPath": "golang.org/x/crypto/pkcs12",
|
||||||
"Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc"
|
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/net/context",
|
"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.
|
// mechanism.
|
||||||
type CSRWhitelist struct {
|
type CSRWhitelist struct {
|
||||||
Subject, PublicKeyAlgorithm, PublicKey, SignatureAlgorithm bool
|
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
|
// 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"`
|
AuthRemote AuthRemote `json:"auth_remote"`
|
||||||
CTLogServers []string `json:"ct_log_servers"`
|
CTLogServers []string `json:"ct_log_servers"`
|
||||||
AllowedExtensions []OID `json:"allowed_extensions"`
|
AllowedExtensions []OID `json:"allowed_extensions"`
|
||||||
|
CertStore string `json:"cert_store"`
|
||||||
|
|
||||||
Policies []CertificatePolicy
|
Policies []CertificatePolicy
|
||||||
Expiry time.Duration
|
Expiry time.Duration
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,13 @@
|
||||||
// recursive, this second layer of ContentInfo is similar ignored for our degenerate
|
// recursive, this second layer of ContentInfo is similar ignored for our degenerate
|
||||||
// usage. The ExtendedCertificatesAndCertificates type consists of a sequence of choices
|
// usage. The ExtendedCertificatesAndCertificates type consists of a sequence of choices
|
||||||
// between PKCS #6 extended certificates and x509 certificates. Any sequence consisting
|
// between PKCS #6 extended certificates and x509 certificates. Any sequence consisting
|
||||||
// of any number of extended certificates is not yet supported in this implementation
|
// 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 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
|
// 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
|
package pkcs7
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -55,7 +55,7 @@ import (
|
||||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
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 {
|
type signedData struct {
|
||||||
Version int
|
Version int
|
||||||
|
|
@ -72,7 +72,7 @@ type initPKCS7 struct {
|
||||||
Content asn1.RawValue `asn1:"tag:0,explicit,optional"`
|
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 (
|
const (
|
||||||
ObjIDData = "1.2.840.113549.1.7.1"
|
ObjIDData = "1.2.840.113549.1.7.1"
|
||||||
ObjIDSignedData = "1.2.840.113549.1.7.2"
|
ObjIDSignedData = "1.2.840.113549.1.7.2"
|
||||||
|
|
@ -84,21 +84,21 @@ const (
|
||||||
// the ContentInfo field, the other two being nil. SignedData
|
// the ContentInfo field, the other two being nil. SignedData
|
||||||
// is the degenerate SignedData Content info without signature used
|
// is the degenerate SignedData Content info without signature used
|
||||||
// to hold certificates and crls. Data is raw bytes, and EncryptedData
|
// 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 {
|
type PKCS7 struct {
|
||||||
Raw asn1.RawContent
|
Raw asn1.RawContent
|
||||||
ContentInfo string
|
ContentInfo string
|
||||||
Content Content
|
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 {
|
type Content struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
SignedData SignedData
|
SignedData SignedData
|
||||||
EncryptedData EncryptedData
|
EncryptedData EncryptedData
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignedData defines the typical carrier of certificates and crls
|
// SignedData defines the typical carrier of certificates and crls.
|
||||||
type SignedData struct {
|
type SignedData struct {
|
||||||
Raw asn1.RawContent
|
Raw asn1.RawContent
|
||||||
Version int
|
Version int
|
||||||
|
|
@ -106,19 +106,19 @@ type SignedData struct {
|
||||||
Crl *pkix.CertificateList
|
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 {
|
type Data struct {
|
||||||
Bytes []byte
|
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 {
|
type EncryptedData struct {
|
||||||
Raw asn1.RawContent
|
Raw asn1.RawContent
|
||||||
Version int
|
Version int
|
||||||
EncryptedContentInfo EncryptedContentInfo
|
EncryptedContentInfo EncryptedContentInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptedContentInfo is a subtype of PKCS7EncryptedData
|
// EncryptedContentInfo is a subtype of PKCS7EncryptedData.
|
||||||
type EncryptedContentInfo struct {
|
type EncryptedContentInfo struct {
|
||||||
Raw asn1.RawContent
|
Raw asn1.RawContent
|
||||||
ContentType asn1.ObjectIdentifier
|
ContentType asn1.ObjectIdentifier
|
||||||
|
|
@ -127,7 +127,7 @@ type EncryptedContentInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsePKCS7 attempts to parse the DER encoded bytes of a
|
// ParsePKCS7 attempts to parse the DER encoded bytes of a
|
||||||
// PKCS7 structure
|
// PKCS7 structure.
|
||||||
func ParsePKCS7(raw []byte) (msg *PKCS7, err error) {
|
func ParsePKCS7(raw []byte) (msg *PKCS7, err error) {
|
||||||
|
|
||||||
var pkcs7 initPKCS7
|
var pkcs7 initPKCS7
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"net/mail"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||||
|
|
@ -145,7 +146,7 @@ type CertificateRequest struct {
|
||||||
// BasicKeyRequest.
|
// BasicKeyRequest.
|
||||||
func New() *CertificateRequest {
|
func New() *CertificateRequest {
|
||||||
return &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 {
|
for i := range req.Hosts {
|
||||||
if ip := net.ParseIP(req.Hosts[i]); ip != nil {
|
if ip := net.ParseIP(req.Hosts[i]); ip != nil {
|
||||||
tpl.IPAddresses = append(tpl.IPAddresses, ip)
|
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 {
|
} else {
|
||||||
tpl.DNSNames = append(tpl.DNSNames, req.Hosts[i])
|
tpl.DNSNames = append(tpl.DNSNames, req.Hosts[i])
|
||||||
}
|
}
|
||||||
|
|
@ -271,6 +274,9 @@ func getHosts(cert *x509.Certificate) []string {
|
||||||
for _, dns := range cert.DNSNames {
|
for _, dns := range cert.DNSNames {
|
||||||
hosts = append(hosts, dns)
|
hosts = append(hosts, dns)
|
||||||
}
|
}
|
||||||
|
for _, email := range cert.EmailAddresses {
|
||||||
|
hosts = append(hosts, email)
|
||||||
|
}
|
||||||
|
|
||||||
return hosts
|
return hosts
|
||||||
}
|
}
|
||||||
|
|
@ -380,6 +386,8 @@ func Generate(priv crypto.Signer, req *CertificateRequest) (csr []byte, err erro
|
||||||
for i := range req.Hosts {
|
for i := range req.Hosts {
|
||||||
if ip := net.ParseIP(req.Hosts[i]); ip != nil {
|
if ip := net.ParseIP(req.Hosts[i]); ip != nil {
|
||||||
tpl.IPAddresses = append(tpl.IPAddresses, ip)
|
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 {
|
} else {
|
||||||
tpl.DNSNames = append(tpl.DNSNames, req.Hosts[i])
|
tpl.DNSNames = append(tpl.DNSNames, req.Hosts[i])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ The index of codes are listed below:
|
||||||
5100: NoKeyUsages
|
5100: NoKeyUsages
|
||||||
5200: InvalidPolicy
|
5200: InvalidPolicy
|
||||||
5300: InvalidRequest
|
5300: InvalidRequest
|
||||||
|
5400: UnknownProfile
|
||||||
6XXX: DialError
|
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
|
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 indicates a problem with the certificate transparency process
|
||||||
CTError // 10XXX
|
CTError // 10XXX
|
||||||
|
|
||||||
|
// CertStoreError indicates a problem with the certificate store
|
||||||
|
CertStoreError // 11XXX
|
||||||
)
|
)
|
||||||
|
|
||||||
// None is a non-specified error.
|
// None is a non-specified error.
|
||||||
|
|
@ -143,6 +146,9 @@ const (
|
||||||
// InvalidRequest indicates a certificate request violated the
|
// InvalidRequest indicates a certificate request violated the
|
||||||
// constraints of the policy being applied to the request.
|
// constraints of the policy being applied to the request.
|
||||||
InvalidRequest // 53XX
|
InvalidRequest // 53XX
|
||||||
|
|
||||||
|
// UnknownProfile indicates that the profile does not exist.
|
||||||
|
UnknownProfile // 54XX
|
||||||
)
|
)
|
||||||
|
|
||||||
// The following are API client related errors, and should be
|
// The following are API client related errors, and should be
|
||||||
|
|
@ -185,6 +191,15 @@ const (
|
||||||
PrecertSubmissionFailed = 100 * (iota + 1)
|
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.
|
// The error interface implementation, which formats to a JSON object string.
|
||||||
func (e *Error) Error() string {
|
func (e *Error) Error() string {
|
||||||
marshaled, err := json.Marshal(e)
|
marshaled, err := json.Marshal(e)
|
||||||
|
|
@ -296,6 +311,8 @@ func New(category Category, reason Reason) *Error {
|
||||||
msg = "Invalid or unknown policy"
|
msg = "Invalid or unknown policy"
|
||||||
case InvalidRequest:
|
case InvalidRequest:
|
||||||
msg = "Policy violation request"
|
msg = "Policy violation request"
|
||||||
|
case UnknownProfile:
|
||||||
|
msg = "Unknown policy profile"
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("Unsupported CFSSL error reason %d under category PolicyError.",
|
panic(fmt.Sprintf("Unsupported CFSSL error reason %d under category PolicyError.",
|
||||||
reason))
|
reason))
|
||||||
|
|
@ -348,6 +365,13 @@ func New(category Category, reason Reason) *Error {
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("Unsupported CF-SSL error reason %d under category CTError.", reason))
|
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:
|
default:
|
||||||
panic(fmt.Sprintf("Unsupported CFSSL error type: %d.",
|
panic(fmt.Sprintf("Unsupported CFSSL error type: %d.",
|
||||||
|
|
@ -383,7 +407,8 @@ func Wrap(category Category, reason Reason, err error) *Error {
|
||||||
errorCode += unknownAuthority
|
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
|
// no-op, just use the error
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("Unsupported CFSSL error type: %d.",
|
panic(fmt.Sprintf("Unsupported CFSSL error type: %d.",
|
||||||
|
|
|
||||||
|
|
@ -327,7 +327,14 @@ func LoadPEMCertPool(certsFile string) (*x509.CertPool, error) {
|
||||||
// key. The private key may be either an unencrypted PKCS#8, PKCS#1,
|
// key. The private key may be either an unencrypted PKCS#8, PKCS#1,
|
||||||
// or elliptic private key.
|
// or elliptic private key.
|
||||||
func ParsePrivateKeyPEM(keyPEM []byte) (key crypto.Signer, err error) {
|
func ParsePrivateKeyPEM(keyPEM []byte) (key crypto.Signer, err error) {
|
||||||
keyDER, err := GetKeyDERFromPEM(keyPEM)
|
return ParsePrivateKeyPEMWithPassword(keyPEM, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePrivateKeyPEMWithPassword parses and returns a PEM-encoded private
|
||||||
|
// key. The private key may be a potentially encrypted PKCS#8, PKCS#1,
|
||||||
|
// or elliptic private key.
|
||||||
|
func ParsePrivateKeyPEMWithPassword(keyPEM []byte, password []byte) (key crypto.Signer, err error) {
|
||||||
|
keyDER, err := GetKeyDERFromPEM(keyPEM, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -336,11 +343,14 @@ func ParsePrivateKeyPEM(keyPEM []byte) (key crypto.Signer, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeyDERFromPEM parses a PEM-encoded private key and returns DER-format key bytes.
|
// GetKeyDERFromPEM parses a PEM-encoded private key and returns DER-format key bytes.
|
||||||
func GetKeyDERFromPEM(in []byte) ([]byte, error) {
|
func GetKeyDERFromPEM(in []byte, password []byte) ([]byte, error) {
|
||||||
keyDER, _ := pem.Decode(in)
|
keyDER, _ := pem.Decode(in)
|
||||||
if keyDER != nil {
|
if keyDER != nil {
|
||||||
if procType, ok := keyDER.Headers["Proc-Type"]; ok {
|
if procType, ok := keyDER.Headers["Proc-Type"]; ok {
|
||||||
if strings.Contains(procType, "ENCRYPTED") {
|
if strings.Contains(procType, "ENCRYPTED") {
|
||||||
|
if password != nil {
|
||||||
|
return x509.DecryptPEMBlock(keyDER, password)
|
||||||
|
}
|
||||||
return nil, cferr.New(cferr.PrivateKeyError, cferr.Encrypted)
|
return nil, cferr.New(cferr.PrivateKeyError, cferr.Encrypted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -421,6 +431,21 @@ func ParseCSR(in []byte) (csr *x509.CertificateRequest, rest []byte, err error)
|
||||||
return csr, rest, nil
|
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
|
// SignerAlgo returns an X.509 signature algorithm corresponding to
|
||||||
// the crypto.Hash provided from a crypto.Signer.
|
// the crypto.Hash provided from a crypto.Signer.
|
||||||
func SignerAlgo(priv crypto.Signer, h crypto.Hash) x509.SignatureAlgorithm {
|
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 (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"testing"
|
||||||
// "crypto/tls"
|
// "crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
@ -14,6 +15,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/csr"
|
||||||
// "github.com/cloudflare/cfssl/helpers/testsuite/stoppable"
|
// "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
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,43 +6,82 @@
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
golog "log"
|
"log"
|
||||||
|
"log/syslog"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The following constants represent logging levels in increasing levels of seriousness.
|
// The following constants represent logging levels in increasing levels of seriousness.
|
||||||
const (
|
const (
|
||||||
|
// LevelDebug is the log level for Debug statements.
|
||||||
LevelDebug = iota
|
LevelDebug = iota
|
||||||
|
// LevelInfo is the log level for Info statements.
|
||||||
LevelInfo
|
LevelInfo
|
||||||
|
// LevelWarning is the log level for Warning statements.
|
||||||
LevelWarning
|
LevelWarning
|
||||||
|
// LevelError is the log level for Error statements.
|
||||||
LevelError
|
LevelError
|
||||||
|
// LevelCritical is the log level for Critical statements.
|
||||||
LevelCritical
|
LevelCritical
|
||||||
|
// LevelFatal is the log level for Fatal statements.
|
||||||
LevelFatal
|
LevelFatal
|
||||||
)
|
)
|
||||||
|
|
||||||
var levelPrefix = [...]string{
|
var levelPrefix = [...]string{
|
||||||
LevelDebug: "[DEBUG] ",
|
LevelDebug: "DEBUG",
|
||||||
LevelInfo: "[INFO] ",
|
LevelInfo: "INFO",
|
||||||
LevelWarning: "[WARNING] ",
|
LevelWarning: "WARNING",
|
||||||
LevelError: "[ERROR] ",
|
LevelError: "ERROR",
|
||||||
LevelCritical: "[CRITICAL] ",
|
LevelCritical: "CRITICAL",
|
||||||
LevelFatal: "[FATAL] ",
|
LevelFatal: "FATAL",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
// Level stores the current logging level.
|
// Level stores the current logging level.
|
||||||
var Level = LevelDebug
|
Level = LevelInfo
|
||||||
|
// SysLogger is a syslog Writer to be used if not nil.
|
||||||
|
SysLogger *syslog.Writer
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.IntVar(&Level, "loglevel", LevelInfo, "Log level (0 = DEBUG, 5 = FATAL)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func print(l int, msg string) {
|
||||||
|
if l >= Level {
|
||||||
|
if SysLogger != nil {
|
||||||
|
var err error
|
||||||
|
switch l {
|
||||||
|
case LevelDebug:
|
||||||
|
err = SysLogger.Debug(msg)
|
||||||
|
case LevelInfo:
|
||||||
|
err = SysLogger.Info(msg)
|
||||||
|
case LevelWarning:
|
||||||
|
err = SysLogger.Warning(msg)
|
||||||
|
case LevelError:
|
||||||
|
err = SysLogger.Err(msg)
|
||||||
|
case LevelCritical:
|
||||||
|
err = SysLogger.Crit(msg)
|
||||||
|
case LevelFatal:
|
||||||
|
err = SysLogger.Emerg(msg)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to write syslog: %v for msg: %s\n", err, msg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("[%s] %s", levelPrefix[l], msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func outputf(l int, format string, v []interface{}) {
|
func outputf(l int, format string, v []interface{}) {
|
||||||
if l >= Level {
|
print(l, fmt.Sprintf(format, v...))
|
||||||
golog.Printf(fmt.Sprint(levelPrefix[l], format), v...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func output(l int, v []interface{}) {
|
func output(l int, v []interface{}) {
|
||||||
if l >= Level {
|
print(l, fmt.Sprint(v...))
|
||||||
golog.Print(levelPrefix[l], fmt.Sprint(v...))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatalf logs a formatted message at the "fatal" level and then exits. The
|
// Fatalf logs a formatted message at the "fatal" level and then exits. The
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,10 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
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"
|
"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
|
// StatusCode is a map between string statuses sent by cli/api
|
||||||
// to ocsp int statuses
|
// to ocsp int statuses
|
||||||
var StatusCode = map[string]int{
|
var StatusCode = map[string]int{
|
||||||
|
|
@ -35,6 +53,7 @@ type SignRequest struct {
|
||||||
Status string
|
Status string
|
||||||
Reason int
|
Reason int
|
||||||
RevokedAt time.Time
|
RevokedAt time.Time
|
||||||
|
Extensions []pkix.Extension
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signer represents a general signer of OCSP responses. It is
|
// Signer represents a general signer of OCSP responses. It is
|
||||||
|
|
@ -56,6 +75,27 @@ type StandardSigner struct {
|
||||||
interval time.Duration
|
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
|
// NewSignerFromFile reads the issuer cert, the responder cert and the responder key
|
||||||
// from PEM files, and takes an interval in seconds
|
// from PEM files, and takes an interval in seconds
|
||||||
func NewSignerFromFile(issuerFile, responderFile, keyFile string, interval time.Duration) (Signer, error) {
|
func NewSignerFromFile(issuerFile, responderFile, keyFile string, interval time.Duration) (Signer, error) {
|
||||||
|
|
@ -143,6 +183,7 @@ func (s StandardSigner) Sign(req SignRequest) ([]byte, error) {
|
||||||
ThisUpdate: thisUpdate,
|
ThisUpdate: thisUpdate,
|
||||||
NextUpdate: nextUpdate,
|
NextUpdate: nextUpdate,
|
||||||
Certificate: certificate,
|
Certificate: certificate,
|
||||||
|
ExtraExtensions: req.Extensions,
|
||||||
}
|
}
|
||||||
|
|
||||||
if status == ocsp.Revoked {
|
if status == ocsp.Revoked {
|
||||||
|
|
|
||||||
|
|
@ -7,26 +7,25 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
|
"os"
|
||||||
|
|
||||||
"encoding/asn1"
|
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/certdb"
|
||||||
"encoding/binary"
|
|
||||||
|
|
||||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
|
"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"
|
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/helpers"
|
||||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/info"
|
"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/log"
|
||||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
|
"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"
|
||||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/google/certificate-transparency/go/client"
|
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/google/certificate-transparency/go/client"
|
||||||
)
|
)
|
||||||
|
|
@ -38,6 +37,7 @@ type Signer struct {
|
||||||
priv crypto.Signer
|
priv crypto.Signer
|
||||||
policy *config.Signing
|
policy *config.Signing
|
||||||
sigAlgo x509.SignatureAlgorithm
|
sigAlgo x509.SignatureAlgorithm
|
||||||
|
dbAccessor certdb.Accessor
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSigner creates a new Signer directly from a
|
// NewSigner creates a new Signer directly from a
|
||||||
|
|
@ -80,7 +80,13 @@ func NewSignerFromFile(caFile, caKeyFile string, policy *config.Signing) (*Signe
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
priv, err := helpers.ParsePrivateKeyPEM(cakey)
|
strPassword := os.Getenv("CFSSL_CA_PK_PASSWORD")
|
||||||
|
password := []byte(strPassword)
|
||||||
|
if strPassword == "" {
|
||||||
|
password = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, err := helpers.ParsePrivateKeyPEMWithPassword(cakey, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Malformed private key %v", err)
|
log.Debug("Malformed private key %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -102,12 +108,14 @@ func (s *Signer) sign(template *x509.Certificate, profile *config.SigningProfile
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
template.DNSNames = nil
|
template.DNSNames = nil
|
||||||
|
template.EmailAddresses = nil
|
||||||
s.ca = template
|
s.ca = template
|
||||||
initRoot = true
|
initRoot = true
|
||||||
template.MaxPathLen = signer.MaxPathLen
|
template.MaxPathLen = signer.MaxPathLen
|
||||||
} else if template.IsCA {
|
} else if template.IsCA {
|
||||||
template.MaxPathLen = 1
|
template.MaxPathLen = 1
|
||||||
template.DNSNames = nil
|
template.DNSNames = nil
|
||||||
|
template.EmailAddresses = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
derBytes, err := x509.CreateCertificate(rand.Reader, template, s.ca, template.PublicKey, s.priv)
|
derBytes, err := x509.CreateCertificate(rand.Reader, template, s.ca, template.PublicKey, s.priv)
|
||||||
|
|
@ -154,7 +162,9 @@ func PopulateSubjectFromCSR(s *signer.Subject, req pkix.Name) pkix.Name {
|
||||||
replaceSliceIfEmpty(&name.Locality, &req.Locality)
|
replaceSliceIfEmpty(&name.Locality, &req.Locality)
|
||||||
replaceSliceIfEmpty(&name.Organization, &req.Organization)
|
replaceSliceIfEmpty(&name.Organization, &req.Organization)
|
||||||
replaceSliceIfEmpty(&name.OrganizationalUnit, &req.OrganizationalUnit)
|
replaceSliceIfEmpty(&name.OrganizationalUnit, &req.OrganizationalUnit)
|
||||||
|
if name.SerialNumber == "" {
|
||||||
|
name.SerialNumber = req.SerialNumber
|
||||||
|
}
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,6 +238,9 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
|
||||||
if profile.CSRWhitelist.IPAddresses {
|
if profile.CSRWhitelist.IPAddresses {
|
||||||
safeTemplate.IPAddresses = csrTemplate.IPAddresses
|
safeTemplate.IPAddresses = csrTemplate.IPAddresses
|
||||||
}
|
}
|
||||||
|
if profile.CSRWhitelist.EmailAddresses {
|
||||||
|
safeTemplate.EmailAddresses = csrTemplate.EmailAddresses
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OverrideHosts(&safeTemplate, req.Hosts)
|
OverrideHosts(&safeTemplate, req.Hosts)
|
||||||
|
|
@ -245,20 +258,38 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
|
||||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
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 {
|
if profile.ClientProvidesSerialNumbers {
|
||||||
if req.Serial == nil {
|
if req.Serial == nil {
|
||||||
fmt.Printf("xx %#v\n", profile)
|
|
||||||
return nil, cferr.New(cferr.CertificateError, cferr.MissingSerial)
|
return nil, cferr.New(cferr.CertificateError, cferr.MissingSerial)
|
||||||
}
|
}
|
||||||
safeTemplate.SerialNumber = req.Serial
|
safeTemplate.SerialNumber = req.Serial
|
||||||
} else {
|
} 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 {
|
if err != nil {
|
||||||
return nil, cferr.Wrap(cferr.CertificateError, cferr.Unknown, err)
|
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 {
|
if len(req.Extensions) > 0 {
|
||||||
|
|
@ -323,7 +354,29 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
|
||||||
var SCTListExtension = pkix.Extension{Id: signer.SCTListOID, Critical: false, Value: serializedSCTList}
|
var SCTListExtension = pkix.Extension{Id: signer.SCTListOID, Critical: false, Value: serializedSCTList}
|
||||||
certTBS.ExtraExtensions = append(certTBS.ExtraExtensions, SCTListExtension)
|
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) {
|
func serializeSCTList(sctList []ct.SignedCertificateTimestamp) ([]byte, error) {
|
||||||
|
|
@ -380,6 +433,11 @@ func (s *Signer) SetPolicy(policy *config.Signing) {
|
||||||
s.policy = policy
|
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.
|
// Policy returns the signer's policy.
|
||||||
func (s *Signer) Policy() *config.Signing {
|
func (s *Signer) Policy() *config.Signing {
|
||||||
return s.policy
|
return s.policy
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/cloudflare/cfssl/api/client"
|
"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"
|
"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"
|
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/info"
|
||||||
|
|
@ -106,6 +107,11 @@ func (s *Signer) SetPolicy(policy *config.Signing) {
|
||||||
s.policy = policy
|
s.policy = policy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDBAccessor sets the signers' cert db accessor
|
||||||
|
func (s *Signer) SetDBAccessor(dba certdb.Accessor) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
// Policy returns the signer's policy.
|
// Policy returns the signer's policy.
|
||||||
func (s *Signer) Policy() *config.Signing {
|
func (s *Signer) Policy() *config.Signing {
|
||||||
return s.policy
|
return s.policy
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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/config"
|
||||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/csr"
|
"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"
|
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
|
||||||
|
|
@ -30,6 +31,7 @@ var MaxPathLen = 2
|
||||||
type Subject struct {
|
type Subject struct {
|
||||||
CN string
|
CN string
|
||||||
Names []csr.Name `json:"names"`
|
Names []csr.Name `json:"names"`
|
||||||
|
SerialNumber string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extension represents a raw extension to be included in the certificate. The
|
// Extension represents a raw extension to be included in the certificate. The
|
||||||
|
|
@ -76,6 +78,7 @@ func (s *Subject) Name() pkix.Name {
|
||||||
appendIf(n.O, &name.Organization)
|
appendIf(n.O, &name.Organization)
|
||||||
appendIf(n.OU, &name.OrganizationalUnit)
|
appendIf(n.OU, &name.OrganizationalUnit)
|
||||||
}
|
}
|
||||||
|
name.SerialNumber = s.SerialNumber
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,6 +97,7 @@ func SplitHosts(hostList string) []string {
|
||||||
type Signer interface {
|
type Signer interface {
|
||||||
Info(info.Req) (*info.Resp, error)
|
Info(info.Req) (*info.Resp, error)
|
||||||
Policy() *config.Signing
|
Policy() *config.Signing
|
||||||
|
SetDBAccessor(certdb.Accessor)
|
||||||
SetPolicy(*config.Signing)
|
SetPolicy(*config.Signing)
|
||||||
SigAlgo() x509.SignatureAlgorithm
|
SigAlgo() x509.SignatureAlgorithm
|
||||||
Sign(req SignRequest) (cert []byte, err error)
|
Sign(req SignRequest) (cert []byte, err error)
|
||||||
|
|
@ -172,6 +176,7 @@ func ParseCertificateRequest(s Signer, csrBytes []byte) (template *x509.Certific
|
||||||
SignatureAlgorithm: s.SigAlgo(),
|
SignatureAlgorithm: s.SigAlgo(),
|
||||||
DNSNames: csr.DNSNames,
|
DNSNames: csr.DNSNames,
|
||||||
IPAddresses: csr.IPAddresses,
|
IPAddresses: csr.IPAddresses,
|
||||||
|
EmailAddresses: csr.EmailAddresses,
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
||||||
165
Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/universal/universal.go
generated
vendored
165
Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/universal/universal.go
generated
vendored
|
|
@ -2,14 +2,26 @@
|
||||||
package universal
|
package universal
|
||||||
|
|
||||||
import (
|
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"
|
"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"
|
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"
|
||||||
"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/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/pkcs11"
|
||||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/remote"
|
"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
|
// Root is used to define where the universal signer gets its public
|
||||||
// certificate and private keys for signing.
|
// certificate and private keys for signing.
|
||||||
type Root struct {
|
type Root struct {
|
||||||
|
|
@ -67,6 +79,54 @@ var localSignerList = []localSignerCheck{
|
||||||
fileBackedSigner,
|
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.
|
// NewSigner generates a new certificate signer from a Root structure.
|
||||||
// This is one of two standard signers: local or remote. If the root
|
// This is one of two standard signers: local or remote. If the root
|
||||||
// structure specifies a force remote, then a remote signer is created,
|
// 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)
|
s, err = remote.NewSigner(policy)
|
||||||
} else {
|
} else {
|
||||||
if policy.NeedsLocalSigner() && policy.NeedsRemoteSigner() {
|
if policy.NeedsLocalSigner() && policy.NeedsRemoteSigner() {
|
||||||
// Currently we don't support a hybrid signer
|
s, err = newUniversalSigner(root, policy)
|
||||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
if policy.NeedsLocalSigner() {
|
if policy.NeedsLocalSigner() {
|
||||||
// shouldProvide indicates whether the
|
s, err = newLocalSigner(root, policy)
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if policy.NeedsRemoteSigner() {
|
if policy.NeedsRemoteSigner() {
|
||||||
s, err = remote.NewSigner(policy)
|
s, err = remote.NewSigner(policy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return s, err
|
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.
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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"
|
"encoding/asn1"
|
||||||
"errors"
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var idPKIXOCSPBasic = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 1})
|
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
|
// ResponseStatus contains the result of an OCSP request. See
|
||||||
// response. See RFC 2560, section 4.2.
|
// https://tools.ietf.org/html/rfc6960#section-2.3
|
||||||
|
type ResponseStatus int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ocspSuccess = 0
|
Success ResponseStatus = 0
|
||||||
ocspMalformed = 1
|
Malformed ResponseStatus = 1
|
||||||
ocspInternalError = 2
|
InternalError ResponseStatus = 2
|
||||||
ocspTryLater = 3
|
TryLater ResponseStatus = 3
|
||||||
ocspSigRequired = 4
|
// Status code four is ununsed in OCSP. See
|
||||||
ocspUnauthorized = 5
|
// 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 {
|
type certID struct {
|
||||||
HashAlgorithm pkix.AlgorithmIdentifier
|
HashAlgorithm pkix.AlgorithmIdentifier
|
||||||
NameHash []byte
|
NameHash []byte
|
||||||
|
|
@ -60,7 +97,7 @@ type request struct {
|
||||||
|
|
||||||
type responseASN1 struct {
|
type responseASN1 struct {
|
||||||
Status asn1.Enumerated
|
Status asn1.Enumerated
|
||||||
Response responseBytes `asn1:"explicit,tag:0"`
|
Response responseBytes `asn1:"explicit,tag:0,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type responseBytes struct {
|
type responseBytes struct {
|
||||||
|
|
@ -91,6 +128,7 @@ type singleResponse struct {
|
||||||
Unknown asn1.Flag `asn1:"tag:2,optional"`
|
Unknown asn1.Flag `asn1:"tag:2,optional"`
|
||||||
ThisUpdate time.Time `asn1:"generalized"`
|
ThisUpdate time.Time `asn1:"generalized"`
|
||||||
NextUpdate time.Time `asn1:"generalized,explicit,tag:0,optional"`
|
NextUpdate time.Time `asn1:"generalized,explicit,tag:0,optional"`
|
||||||
|
SingleExtensions []pkix.Extension `asn1:"explicit,tag:1,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type revokedInfo struct {
|
type revokedInfo struct {
|
||||||
|
|
@ -235,11 +273,13 @@ const (
|
||||||
// Good means that the certificate is valid.
|
// Good means that the certificate is valid.
|
||||||
Good = iota
|
Good = iota
|
||||||
// Revoked means that the certificate has been deliberately revoked.
|
// 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 means that the OCSP responder doesn't know about the certificate.
|
||||||
Unknown = iota
|
Unknown
|
||||||
// ServerFailed means that the OCSP responder failed to process the request.
|
// ServerFailed is unused and was never used (see
|
||||||
ServerFailed = iota
|
// 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.
|
// The enumerated reasons for revoking a certificate. See RFC 5280.
|
||||||
|
|
@ -257,7 +297,7 @@ const (
|
||||||
AACompromise = iota
|
AACompromise = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
// Request represents an OCSP request. See RFC 2560.
|
// Request represents an OCSP request. See RFC 6960.
|
||||||
type Request struct {
|
type Request struct {
|
||||||
HashAlgorithm crypto.Hash
|
HashAlgorithm crypto.Hash
|
||||||
IssuerNameHash []byte
|
IssuerNameHash []byte
|
||||||
|
|
@ -265,9 +305,10 @@ type Request struct {
|
||||||
SerialNumber *big.Int
|
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 {
|
type Response struct {
|
||||||
// Status is one of {Good, Revoked, Unknown, ServerFailed}
|
// Status is one of {Good, Revoked, Unknown}
|
||||||
Status int
|
Status int
|
||||||
SerialNumber *big.Int
|
SerialNumber *big.Int
|
||||||
ProducedAt, ThisUpdate, NextUpdate, RevokedAt time.Time
|
ProducedAt, ThisUpdate, NextUpdate, RevokedAt time.Time
|
||||||
|
|
@ -278,6 +319,20 @@ type Response struct {
|
||||||
TBSResponseData []byte
|
TBSResponseData []byte
|
||||||
Signature []byte
|
Signature []byte
|
||||||
SignatureAlgorithm x509.SignatureAlgorithm
|
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
|
// 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
|
// ParseResponse parses an OCSP response in DER form. It only supports
|
||||||
// responses for a single certificate. If the response contains a certificate
|
// 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
|
// 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
|
// it will be used to validate the signature or embedded certificate.
|
||||||
// signatures or parse failures will result in a ParseError.
|
//
|
||||||
|
// 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) {
|
func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) {
|
||||||
var resp responseASN1
|
var resp responseASN1
|
||||||
rest, err := asn1.Unmarshal(bytes, &resp)
|
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")
|
return nil, ParseError("trailing data in OCSP response")
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := new(Response)
|
if status := ResponseStatus(resp.Status); status != Success {
|
||||||
if resp.Status != ocspSuccess {
|
return nil, ResponseError{status}
|
||||||
ret.Status = ServerFailed
|
|
||||||
return ret, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !resp.Response.ResponseType.Equal(idPKIXOCSPBasic) {
|
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")
|
return nil, ParseError("OCSP response contains bad number of responses")
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.TBSResponseData = basicResp.TBSResponseData.Raw
|
ret := &Response{
|
||||||
ret.Signature = basicResp.Signature.RightAlign()
|
TBSResponseData: basicResp.TBSResponseData.Raw,
|
||||||
ret.SignatureAlgorithm = getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm)
|
Signature: basicResp.Signature.RightAlign(),
|
||||||
|
SignatureAlgorithm: getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm),
|
||||||
|
}
|
||||||
|
|
||||||
if len(basicResp.Certificates) > 0 {
|
if len(basicResp.Certificates) > 0 {
|
||||||
ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes)
|
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]
|
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
|
ret.SerialNumber = r.CertID.SerialNumber
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
@ -536,6 +600,7 @@ func CreateResponse(issuer, responderCert *x509.Certificate, template Response,
|
||||||
},
|
},
|
||||||
ThisUpdate: template.ThisUpdate.UTC(),
|
ThisUpdate: template.ThisUpdate.UTC(),
|
||||||
NextUpdate: template.NextUpdate.UTC(),
|
NextUpdate: template.NextUpdate.UTC(),
|
||||||
|
SingleExtensions: template.ExtraExtensions,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch template.Status {
|
switch template.Status {
|
||||||
|
|
@ -599,7 +664,7 @@ func CreateResponse(issuer, responderCert *x509.Certificate, template Response,
|
||||||
}
|
}
|
||||||
|
|
||||||
return asn1.Marshal(responseASN1{
|
return asn1.Marshal(responseASN1{
|
||||||
Status: ocspSuccess,
|
Status: asn1.Enumerated(Success),
|
||||||
Response: responseBytes{
|
Response: responseBytes{
|
||||||
ResponseType: idPKIXOCSPBasic,
|
ResponseType: idPKIXOCSPBasic,
|
||||||
Response: responseDER,
|
Response: responseDER,
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ type safeBag struct {
|
||||||
|
|
||||||
type pkcs12Attribute struct {
|
type pkcs12Attribute struct {
|
||||||
Id asn1.ObjectIdentifier
|
Id asn1.ObjectIdentifier
|
||||||
Value asn1.RawValue `ans1:"set"`
|
Value asn1.RawValue `asn1:"set"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type encryptedPrivateKeyInfo struct {
|
type encryptedPrivateKeyInfo struct {
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -1,7 +1,7 @@
|
||||||
# This Makefile also tricks Travis into not running 'go get' for our
|
# This Makefile also tricks Travis into not running 'go get' for our
|
||||||
# build. See http://docs.travis-ci.com/user/languages/go/
|
# build. See http://docs.travis-ci.com/user/languages/go/
|
||||||
|
|
||||||
OBJDIR ?= ./bin
|
OBJDIR ?= $(shell pwd)/bin
|
||||||
DESTDIR ?= /usr/local/bin
|
DESTDIR ?= /usr/local/bin
|
||||||
ARCHIVEDIR ?= /tmp
|
ARCHIVEDIR ?= /tmp
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,29 @@ func (mock *MockDNSResolver) LookupCAA(_ context.Context, domain string) ([]*dns
|
||||||
secondRecord := record
|
secondRecord := record
|
||||||
secondRecord.Value = "letsencrypt.org"
|
secondRecord.Value = "letsencrypt.org"
|
||||||
results = append(results, &secondRecord)
|
results = append(results, &secondRecord)
|
||||||
|
case "unknown-critical.com":
|
||||||
|
record.Flag = 128
|
||||||
|
record.Tag = "foo"
|
||||||
|
record.Value = "bar"
|
||||||
|
results = append(results, &record)
|
||||||
|
case "unknown-critical2.com":
|
||||||
|
record.Flag = 1
|
||||||
|
record.Tag = "foo"
|
||||||
|
record.Value = "bar"
|
||||||
|
results = append(results, &record)
|
||||||
|
case "unknown-noncritical.com":
|
||||||
|
record.Flag = 0x7E // all bits we don't treat as meaning "critical"
|
||||||
|
record.Tag = "foo"
|
||||||
|
record.Value = "bar"
|
||||||
|
results = append(results, &record)
|
||||||
|
case "present-with-parameter.com":
|
||||||
|
record.Tag = "issue"
|
||||||
|
record.Value = " letsencrypt.org ;foo=bar;baz=bar"
|
||||||
|
results = append(results, &record)
|
||||||
|
case "unsatisfiable.com":
|
||||||
|
record.Tag = "issue"
|
||||||
|
record.Value = ";"
|
||||||
|
results = append(results, &record)
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,7 @@ func setup(t *testing.T) *testCtx {
|
||||||
PublicKey: true,
|
PublicKey: true,
|
||||||
SignatureAlgorithm: true,
|
SignatureAlgorithm: true,
|
||||||
},
|
},
|
||||||
|
ClientProvidesSerialNumbers: true,
|
||||||
},
|
},
|
||||||
ecdsaProfileName: &cfsslConfig.SigningProfile{
|
ecdsaProfileName: &cfsslConfig.SigningProfile{
|
||||||
Usage: []string{"digital signature", "server auth"},
|
Usage: []string{"digital signature", "server auth"},
|
||||||
|
|
@ -224,6 +225,7 @@ func setup(t *testing.T) *testCtx {
|
||||||
PublicKey: true,
|
PublicKey: true,
|
||||||
SignatureAlgorithm: true,
|
SignatureAlgorithm: true,
|
||||||
},
|
},
|
||||||
|
ClientProvidesSerialNumbers: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Default: &cfsslConfig.SigningProfile{
|
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))
|
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(
|
err := src.dbMap.SelectOne(
|
||||||
&response,
|
&response,
|
||||||
"SELECT ocspResponse FROM certificateStatus WHERE serial = :serial",
|
"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 {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
src.log.Err(fmt.Sprintf("Failed to retrieve response from certificateStatus table: %s", err))
|
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 {
|
if err != nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,6 @@ func TestErrorLog(t *testing.T) {
|
||||||
test.Assert(t, !found, "Somehow found OCSP response")
|
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 certificateStatus table")), 1)
|
||||||
test.AssertEquals(t, len(mockLog.GetAllMatching("Failed to retrieve response from ocspResponses table")), 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustRead(path string) []byte {
|
func mustRead(path string) []byte {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
"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/config"
|
||||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/info"
|
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/info"
|
||||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
|
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp"
|
||||||
|
|
@ -311,6 +312,11 @@ func (bhs BadHSMSigner) SetPolicy(*config.Signing) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDBAccessor is a mock.
|
||||||
|
func (bhs BadHSMSigner) SetDBAccessor(certdb.Accessor) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// SigAlgo is a mock
|
// SigAlgo is a mock
|
||||||
func (bhs BadHSMSigner) SigAlgo() x509.SignatureAlgorithm {
|
func (bhs BadHSMSigner) SigAlgo() x509.SignatureAlgorithm {
|
||||||
return x509.UnknownSignatureAlgorithm
|
return x509.UnknownSignatureAlgorithm
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
|
||||||
SignatureAlgorithm: true,
|
SignatureAlgorithm: true,
|
||||||
DNSNames: true,
|
DNSNames: true,
|
||||||
},
|
},
|
||||||
|
ClientProvidesSerialNumbers: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -568,9 +568,13 @@ type CAASet struct {
|
||||||
func (caaSet CAASet) criticalUnknown() bool {
|
func (caaSet CAASet) criticalUnknown() bool {
|
||||||
if len(caaSet.Unknown) > 0 {
|
if len(caaSet.Unknown) > 0 {
|
||||||
for _, caaRecord := range caaSet.Unknown {
|
for _, caaRecord := range caaSet.Unknown {
|
||||||
// Critical flag is 1, but according to RFC 6844 any flag other than
|
// The critical flag is the bit with significance 128. However, many CAA
|
||||||
// 0 should currently be interpreted as critical.
|
// record users have misinterpreted the RFC and concluded that the bit
|
||||||
if caaRecord.Flag > 0 {
|
// with significance 1 is the critical bit. This is sufficiently
|
||||||
|
// widespread that that bit must reasonably be considered an alias for
|
||||||
|
// the critical bit. The remaining bits are 0/ignore as proscribed by the
|
||||||
|
// RFC.
|
||||||
|
if (caaRecord.Flag & (128 | 1)) != 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -628,35 +632,69 @@ func (va *ValidationAuthorityImpl) checkCAARecords(ctx context.Context, identifi
|
||||||
hostname := strings.ToLower(identifier.Value)
|
hostname := strings.ToLower(identifier.Value)
|
||||||
caaSet, err := va.getCAASet(ctx, hostname)
|
caaSet, err := va.getCAASet(ctx, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if caaSet == nil {
|
if caaSet == nil {
|
||||||
// No CAA records found, can issue
|
// No CAA records found, can issue
|
||||||
present = false
|
va.stats.Inc("VA.CAA.None", 1, 1.0)
|
||||||
valid = true
|
return false, true, nil
|
||||||
return
|
|
||||||
} else if caaSet.criticalUnknown() {
|
|
||||||
present = true
|
|
||||||
valid = false
|
|
||||||
return
|
|
||||||
} else if len(caaSet.Issue) > 0 || len(caaSet.Issuewild) > 0 {
|
|
||||||
present = true
|
|
||||||
var checkSet []*dns.CAA
|
|
||||||
if strings.SplitN(hostname, ".", 2)[0] == "*" {
|
|
||||||
checkSet = caaSet.Issuewild
|
|
||||||
} else {
|
|
||||||
checkSet = caaSet.Issue
|
|
||||||
}
|
}
|
||||||
for _, caa := range checkSet {
|
|
||||||
if caa.Value == va.IssuerDomain {
|
// Record stats on directives not currently processed.
|
||||||
valid = true
|
if len(caaSet.Iodef) > 0 {
|
||||||
return
|
va.stats.Inc("VA.CAA.WithIodef", 1, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if caaSet.criticalUnknown() {
|
||||||
|
// Contains unknown critical directives.
|
||||||
|
va.stats.Inc("VA.CAA.UnknownCritical", 1, 1.0)
|
||||||
|
return true, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(caaSet.Unknown) > 0 {
|
||||||
|
va.stats.Inc("VA.CAA.WithUnknownNoncritical", 1, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(caaSet.Issue) == 0 {
|
||||||
|
// Although CAA records exist, none of them pertain to issuance in this case.
|
||||||
|
// (e.g. there is only an issuewild directive, but we are checking for a
|
||||||
|
// non-wildcard identifier, or there is only an iodef or non-critical unknown
|
||||||
|
// directive.)
|
||||||
|
va.stats.Inc("VA.CAA.NoneRelevant", 1, 1.0)
|
||||||
|
return true, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are CAA records pertaining to issuance in our case. Note that this
|
||||||
|
// includes the case of the unsatisfiable CAA record value ";", used to
|
||||||
|
// prevent issuance by any CA under any circumstance.
|
||||||
|
//
|
||||||
|
// Our CAA identity must be found in the chosen checkSet.
|
||||||
|
for _, caa := range caaSet.Issue {
|
||||||
|
if extractIssuerDomain(caa) == va.IssuerDomain {
|
||||||
|
va.stats.Inc("VA.CAA.Authorized", 1, 1.0)
|
||||||
|
return true, true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
valid = false
|
// The list of authorized issuers is non-empty, but we are not in it. Fail.
|
||||||
return
|
va.stats.Inc("VA.CAA.Unauthorized", 1, 1.0)
|
||||||
|
return true, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
// Given a CAA record, assume that the Value is in the issue/issuewild format,
|
||||||
|
// that is, a domain name with zero or more additional key-value parameters.
|
||||||
|
// Returns the domain name, which may be "" (unsatisfiable).
|
||||||
|
func extractIssuerDomain(caa *dns.CAA) string {
|
||||||
|
v := caa.Value
|
||||||
|
v = strings.Trim(v, " \t") // Value can start and end with whitespace.
|
||||||
|
idx := strings.IndexByte(v, ';')
|
||||||
|
if idx < 0 {
|
||||||
|
return v // no parameters; domain only
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently, ignore parameters. Unfortunately, the RFC makes no statement on
|
||||||
|
// whether any parameters are critical. Treat unknown parameters as
|
||||||
|
// non-critical.
|
||||||
|
return strings.Trim(v[0:idx], " \t")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -681,6 +681,15 @@ func TestCAAChecking(t *testing.T) {
|
||||||
CAATest{"present.com", true, true},
|
CAATest{"present.com", true, true},
|
||||||
// Good (multiple critical, one matching)
|
// Good (multiple critical, one matching)
|
||||||
CAATest{"multi-crit-present.com", true, true},
|
CAATest{"multi-crit-present.com", true, true},
|
||||||
|
// Bad (unknown critical)
|
||||||
|
CAATest{"unknown-critical.com", true, false},
|
||||||
|
CAATest{"unknown-critical2.com", true, false},
|
||||||
|
// Good (unknown noncritical, no issue/issuewild records)
|
||||||
|
CAATest{"unknown-noncritical.com", true, true},
|
||||||
|
// Good (issue record with unknown parameters)
|
||||||
|
CAATest{"present-with-parameter.com", true, true},
|
||||||
|
// Bad (unsatisfiable issue record)
|
||||||
|
CAATest{"unsatisfiable.com", true, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, _ := statsd.NewNoopClient()
|
stats, _ := statsd.NewNoopClient()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue