Merge branch 'master' into cert-limit
This commit is contained in:
commit
9026378031
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
# }
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue