Merge branch 'master' into cert-limit

This commit is contained in:
Jeff Hodges 2015-09-25 11:27:33 -07:00
commit 9026378031
9 changed files with 117 additions and 32 deletions

View File

@ -11,6 +11,7 @@ import (
"encoding/hex"
"fmt"
"net/http"
"net/url"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
@ -85,6 +86,32 @@ func (src *DBSource) Response(req *ocsp.Request) (response []byte, present bool)
return
}
func makeDBSource(dbConnect, issuerCert string, sqlDebug bool) (cfocsp.Source, error) {
var noSource cfocsp.Source
// Configure DB
dbMap, err := sa.NewDbMap(dbConnect)
if err != nil {
return noSource, fmt.Errorf("Could not connect to database: %s", err)
}
sa.SetSQLDebug(dbMap, sqlDebug)
// Load the CA's key so we can store its SubjectKey in the DB
caCertDER, err := cmd.LoadCert(issuerCert)
if err != nil {
return noSource, fmt.Errorf("Could not read issuer cert %s: %s", issuerCert, err)
}
caCert, err := x509.ParseCertificate(caCertDER)
if err != nil {
return noSource, fmt.Errorf("Could not parse issuer cert %s: %s", issuerCert, err)
}
if len(caCert.SubjectKeyId) == 0 {
return noSource, fmt.Errorf("Empty subjectKeyID")
}
// Construct source from DB
return NewSourceFromDatabase(dbMap, caCert.SubjectKeyId)
}
func main() {
app := cmd.NewAppShell("boulder-ocsp-responder", "Handles OCSP requests")
app.Action = func(c cmd.Config) {
@ -106,25 +133,26 @@ func main() {
auditlogger.Info(app.VersionString())
// Configure DB
dbMap, err := sa.NewDbMap(c.OCSPResponder.DBConnect)
cmd.FailOnError(err, "Could not connect to database")
sa.SetSQLDebug(dbMap, c.SQL.SQLDebug)
config := c.OCSPResponder
var source cfocsp.Source
url, err := url.Parse(config.Source)
cmd.FailOnError(err, fmt.Sprintf("Source was not a URL: %s", config.Source))
// Load the CA's key so we can store its SubjectKey in the DB
caCertDER, err := cmd.LoadCert(c.Common.IssuerCert)
cmd.FailOnError(err, fmt.Sprintf("Couldn't read issuer cert [%s]", c.Common.IssuerCert))
caCert, err := x509.ParseCertificate(caCertDER)
cmd.FailOnError(err, fmt.Sprintf("Couldn't parse cert read from [%s]", c.Common.IssuerCert))
if len(caCert.SubjectKeyId) == 0 {
cmd.FailOnError(fmt.Errorf("Empty subjectKeyID"), "Unable to use CA certificate")
if url.Scheme == "mysql+tcp" {
auditlogger.Info(fmt.Sprintf("Loading OCSP Database for CA Cert: %s", c.Common.IssuerCert))
source, err = makeDBSource(config.Source, c.Common.IssuerCert, c.SQL.SQLDebug)
cmd.FailOnError(err, "Couldn't load OCSP DB")
} else if url.Scheme == "file" {
filename := url.Path
// Go interprets cwd-relative file urls (file:test/foo.txt) as having the
// relative part of the path in the 'Opaque' field.
if filename == "" {
filename = url.Opaque
}
source, err = cfocsp.NewSourceFromFile(filename)
cmd.FailOnError(err, fmt.Sprintf("Couldn't read file: %s", url.Path))
}
// Construct source from DB
auditlogger.Info(fmt.Sprintf("Loading OCSP Database for CA Cert ID: %s", hex.EncodeToString(caCert.SubjectKeyId)))
src, err := NewSourceFromDatabase(dbMap, caCert.SubjectKeyId)
cmd.FailOnError(err, "Could not connect to OCSP database")
stopTimeout, err := time.ParseDuration(c.OCSPResponder.ShutdownStopTimeout)
cmd.FailOnError(err, "Couldn't parse shutdown stop timeout")
killTimeout, err := time.ParseDuration(c.OCSPResponder.ShutdownKillTimeout)
@ -132,7 +160,7 @@ func main() {
// Configure HTTP
m := http.NewServeMux()
m.Handle(c.OCSPResponder.Path, cfocsp.Responder{Source: src})
m.Handle(c.OCSPResponder.Path, cfocsp.Responder{Source: source})
httpMonitor := metrics.NewHTTPMonitor(stats, m, "OCSP")
srv := &http.Server{

View File

@ -156,7 +156,11 @@ type Config struct {
}
OCSPResponder struct {
DBConnect string
// Source indicates the source of pre-signed OCSP responses to be used. It
// can be a DBConnect string or a file URL. The file URL style is used
// when responding from a static file for intermediates and roots.
Source string
Path string
ListenAddress string

View File

@ -33,11 +33,11 @@ def die(status):
exit_status = status
sys.exit(exit_status)
def get_ocsp(certFile):
def get_ocsp(certFile, url):
openssl_ocsp_cmd = ("""
openssl x509 -in %s -out %s.pem -inform der -outform pem;
openssl ocsp -no_nonce -issuer ../test-ca.pem -CAfile ../test-ca.pem -cert %s.pem -url http://localhost:4002
""" % (certFile, certFile, certFile))
openssl ocsp -no_nonce -issuer ../test-ca.pem -CAfile ../test-ca.pem -cert %s.pem -url %s
""" % (certFile, certFile, certFile, url))
try:
print openssl_ocsp_cmd
output = subprocess.check_output(openssl_ocsp_cmd, shell=True)
@ -49,14 +49,14 @@ def get_ocsp(certFile):
print output
return output
def verify_ocsp_good(certFile):
output = get_ocsp(certFile)
def verify_ocsp_good(certFile, url):
output = get_ocsp(certFile, url)
if not re.search(": good", output):
print "Expected OCSP response 'good', got something else."
die(ExitStatus.OCSPFailure)
def verify_ocsp_revoked(certFile):
output = get_ocsp(certFile)
def verify_ocsp_revoked(certFile, url):
output = get_ocsp(certFile, url)
if not re.search(": revoked", output):
print "Expected OCSP response 'revoked', got something else."
die(ExitStatus.OCSPFailure)
@ -85,7 +85,12 @@ def run_node_test():
print("\nIssuing failed")
die(ExitStatus.NodeFailure)
verify_ocsp_good(certFile)
ee_ocsp_url = "http://localhost:4002"
issuer_ocsp_url = "http://localhost:4003"
verify_ocsp_good(certFile, ee_ocsp_url)
# Also verify that the static OCSP responder, which answers with a
# pre-signed, long-lived response for the CA cert, also works.
verify_ocsp_good("../test-ca.der", issuer_ocsp_url)
if subprocess.Popen('''
node revoke.js %s %s http://localhost:4000/acme/revoke-cert
@ -93,7 +98,7 @@ def run_node_test():
print("\nRevoking failed")
die(ExitStatus.NodeFailure)
verify_ocsp_revoked(certFile)
verify_ocsp_revoked(certFile, ee_ocsp_url)
return 0

View File

@ -146,7 +146,7 @@
},
"ocspResponder": {
"dbConnect": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration",
"source": "mysql+tcp://boulder@localhost:3306/boulder_sa_integration",
"path": "/",
"listenAddress": "localhost:4002",
"shutdownStopTimeout": "10s",

View File

@ -0,0 +1,16 @@
{
"ocspResponder": {
"source": "file:test/issuer-ocsp-responses.txt",
"path": "/",
"listenAddress": "localhost:4003",
"shutdownStopTimeout": "10s",
"shutdownKillTimeout": "1m",
"debugAddr": "localhost:8010"
},
"common": {
"issuerCert": "test/test-ca.pem"
},
"sql": {
"sqlDebug": true
}
}

View File

@ -0,0 +1,22 @@
# issuer-ocsp-responses contains two OCSP responses from test-ca.pem, signed by
# test-ca.pem. One is signed 2014, good til 2015, and the other is signed 2015,
# good til 2030. Only the latter should be served. Note that order is important.
# If there are two entries for the same serial number in the file, the one that
# occurs last in the file will take priority.
#
# The response were generated with:
# openssl x509 -outform der -in test/test-ca.pem -out test/test-ca.der
# single-ocsp --issuer test/test-ca.der --responder test/test-ca.der \
# --target test/test-ca.der --template template.json --pkcs11 ~/pkcs11.json \
# --out ocsp.rsp
# base64 ocsp.rsp | tr -d '\n' > issuer-ocsp-responses.txt
# Note that you must setup SoftHSM locally, import the private key from
# test-ca.key, and put appropriate settings in pkcs11.json for this to work.
# Format is one base64-encoded DER entry per line.
#
# template.json looks like this:
# {
# "Status": 0,
# "ThisUpdate": "2014-09-23T00:00:00Z",
# "NextUpdate": "2015-08-23T00:00:00Z"
# }

View File

@ -0,0 +1,2 @@
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

View File

@ -26,9 +26,9 @@ class ToSServerThread(threading.Thread):
sys.exit(1)
config = os.environ.get('BOULDER_CONFIG')
if config is None:
config = 'test/boulder-config.json'
default_config = os.environ.get('BOULDER_CONFIG')
if default_config is None:
default_config = 'test/boulder-config.json'
processes = []
@ -51,7 +51,7 @@ def install(progs, race_detection):
print('installed %s with pid %d' % (cmd, p.pid))
return True
def run(path, race_detection):
def run(path, race_detection, config=default_config):
binary = os.path.basename(path)
# Note: Must use exec here so that killing this process kills the command.
cmd = """GORACE="halt_on_error=1" exec %s --config %s""" % (binary, config)
@ -94,6 +94,14 @@ def start(race_detection):
# Don't keep building stuff if a server has already died.
return False
# Additionally run the issuer-ocsp-responder, which is not amenable to the
# above `run` pattern because it uses a different config file.
try:
processes.append(run('ocsp-responder', race_detection, 'test/issuer-ocsp-responder.json'))
except Exception as e:
print(e)
return False
# Wait until all servers are up before returning to caller. This means
# checking each server's debug port until it's available.
# seconds.

BIN
test/test-ca.der Normal file

Binary file not shown.