Skip to content

Commit 87fee12

Browse files
authored
Improve single-ocsp command (letsencrypt#2181)
Output base64-encoded DER, as expected by ocsp-responder. Use flags instead of template for Status, ThisUpdate, NextUpdate. Provide better help. Remove old test (wasn't run automatically). Add it to integration test, and use its output for integration test of issuer ocsp-responder. Add another slot to boulder-tools HSM image, to store root key.
1 parent 104f409 commit 87fee12

21 files changed

+133
-330
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# To minimize the fetching of various layers this image and tag should
22
# be used as the base of the bhsm container in boulder/docker-compose.yml
3-
FROM letsencrypt/boulder-tools:2016-08-03
3+
FROM letsencrypt/boulder-tools:2016-09-09
44

55
# Boulder exposes its web application at port TCP 4000
66
EXPOSE 4000 4002 4003 8053 8055

cmd/ocsp-responder/main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,12 @@ func main() {
154154
configFile := flag.String("config", "", "File path to the configuration file for this service")
155155
flag.Parse()
156156
if *configFile == "" {
157-
flag.Usage()
157+
fmt.Fprintf(os.Stderr, `Usage of %s:
158+
Config JSON should contain either a DBConnectFile or a Source value containing a file: URL.
159+
If Source is a file: URL, the file should contain a list of OCSP responses in base64-encoded DER,
160+
as generated by Boulder's single-ocsp command.
161+
`, os.Args[0])
162+
flag.PrintDefaults()
158163
os.Exit(1)
159164
}
160165

cmd/single-ocsp/main.go

Lines changed: 64 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,21 @@ package main
22

33
import (
44
"crypto/x509"
5+
"encoding/base64"
56
"encoding/json"
67
"flag"
78
"fmt"
89
"io/ioutil"
10+
"os"
11+
"time"
912

1013
"github.com/letsencrypt/boulder/cmd"
14+
"github.com/letsencrypt/boulder/core"
1115

1216
"github.com/letsencrypt/pkcs11key"
1317
"golang.org/x/crypto/ocsp"
1418
)
1519

16-
// PKCS11Config defines how to load a module for an HSM.
17-
// XXX(rlb@ipv.sx) Copied from certificate-authority.go
18-
type PKCS11Config struct {
19-
Module string
20-
TokenLabel string
21-
PrivateKeyLabel string
22-
PIN string
23-
}
24-
2520
const usage = `
2621
name:
2722
single-ocsp - Creates a single OCSP response
@@ -31,82 +26,45 @@ usage:
3126
3227
description:
3328
According to the BRs, the OCSP responses for intermediate certificate must
34-
be issued once per year. So there's a need to issue OCSP responses for
35-
these certificates, but it doesn't make sense to use all the infrastructure
29+
be issued once per year. So we need to issue OCSP responses for these
30+
certificates, but it doesn't make sense to use all the infrastructure
3631
that the "ocsp-updater" tool requires. This tool allows an administrator
3732
to manually generate an OCSP response for an intermediate certificate.
38-
`
3933
40-
const templateUsage = `
41-
OCSP template file (JSON), e.g.:
42-
43-
{
44-
"Status": 0, // Good
45-
"ThisUpdate": "2015-08-26T00:00:00Z",
46-
"NextUpdate": "2016-08-26T00:00:00Z"
47-
}
48-
49-
{
50-
"Status": 1, // Revoked
51-
"ThisUpdate": "2015-08-26T00:00:00Z",
52-
"NextUpdate": "2016-08-26T00:00:00Z",
53-
"RevokedAt": "2015-08-20T00:00:00Z",
54-
"RevocationReason": 1 // Key compromise
55-
}
34+
This will write a base64-encoded DER format OCSP response to the file
35+
specified with -out, ending in a newline. This output can be directly
36+
appended to the flat file used by ocsp-responder for intermediate and
37+
root OCSP responses.
5638
`
5739

5840
const pkcs11Usage = `
5941
PKCS#11 configuration (JSON), e.g.:
6042
6143
{
62-
"Module": "/Library/OpenSC/lib/opensc-pkcs11.so",
63-
"Token": "Yubico Yubikey NEO CCID",
64-
"Label": "PIV AUTH key",
65-
"PIN": "123456"
44+
"module": "/usr/local/lib/libpkcs11-proxy.so",
45+
"tokenLabel": "intermediate",
46+
"pin": "5678",
47+
"privateKeyLabel": "intermediate_key"
6648
}
49+
50+
Note: These values should *not* be the same as the ones in the CA's config JSON, which point at a differen HSM partition.
6751
`
6852

69-
func readFiles(issuerFileName, responderFileName, targetFileName, templateFileName, pkcs11FileName string) (issuer, responder, target *x509.Certificate, template ocsp.Response, pkcs11 PKCS11Config, err error) {
53+
func readFiles(issuerFileName, responderFileName, targetFileName, pkcs11FileName string) (issuer, responder, target *x509.Certificate, pkcs11Config pkcs11key.Config, err error) {
7054
// Issuer certificate
71-
issuerBytes, err := ioutil.ReadFile(issuerFileName)
72-
if err != nil {
73-
return
74-
}
75-
76-
issuer, err = x509.ParseCertificate(issuerBytes)
55+
issuer, err = core.LoadCert(issuerFileName)
7756
if err != nil {
7857
return
7958
}
8059

8160
// Responder certificate
82-
responderBytes, err := ioutil.ReadFile(responderFileName)
83-
if err != nil {
84-
return
85-
}
86-
87-
responder, err = x509.ParseCertificate(responderBytes)
61+
responder, err = core.LoadCert(responderFileName)
8862
if err != nil {
8963
return
9064
}
9165

9266
// Target certificate
93-
targetBytes, err := ioutil.ReadFile(targetFileName)
94-
if err != nil {
95-
return
96-
}
97-
98-
target, err = x509.ParseCertificate(targetBytes)
99-
if err != nil {
100-
return
101-
}
102-
103-
// OCSP template
104-
templateBytes, err := ioutil.ReadFile(templateFileName)
105-
if err != nil {
106-
return
107-
}
108-
109-
err = json.Unmarshal(templateBytes, &template)
67+
target, err = core.LoadCert(targetFileName)
11068
if err != nil {
11169
return
11270
}
@@ -117,38 +75,71 @@ func readFiles(issuerFileName, responderFileName, targetFileName, templateFileNa
11775
return
11876
}
11977

120-
err = json.Unmarshal(pkcs11Bytes, &pkcs11)
78+
err = json.Unmarshal(pkcs11Bytes, &pkcs11Config)
79+
if pkcs11Config.Module == "" ||
80+
pkcs11Config.TokenLabel == "" ||
81+
pkcs11Config.PIN == "" ||
82+
pkcs11Config.PrivateKeyLabel == "" {
83+
err = fmt.Errorf("Missing a field in pkcs11Config %#v", pkcs11Config)
84+
return
85+
}
12186
return
12287
}
12388

12489
func main() {
125-
issuerFile := flag.String("issuer", "", "Issuer certificate (DER)")
90+
issuerFile := flag.String("issuer", "", "Issuer certificate (PEM)")
12691
responderFile := flag.String("responder", "", "OCSP responder certificate (DER)")
127-
targetFile := flag.String("target", "", "Certificate whose status is being reported (DER)")
128-
templateFile := flag.String("template", "", templateUsage)
92+
targetFile := flag.String("target", "", "Certificate whose status is being reported (PEM)")
12993
pkcs11File := flag.String("pkcs11", "", pkcs11Usage)
13094
outFile := flag.String("out", "", "File to which the OCSP response will be written")
95+
thisUpdateString := flag.String("thisUpdate", "", "Time for ThisUpdate field, RFC3339 format (e.g. 2016-09-02T00:00:00Z)")
96+
nextUpdateString := flag.String("nextUpdate", "", "Time for NextUpdate field, RFC3339 format")
97+
status := flag.Int("status", 0, "Status for response (0 = good, 1 = revoked)")
98+
flag.Usage = func() {
99+
fmt.Fprint(os.Stderr, usage)
100+
flag.PrintDefaults()
101+
}
131102
flag.Parse()
132103

133-
issuer, responder, target, template, pkcs11, err := readFiles(*issuerFile, *responderFile, *targetFile, *templateFile, *pkcs11File)
104+
if len(*outFile) == 0 {
105+
cmd.FailOnError(fmt.Errorf("No output file provided"), "")
106+
}
107+
thisUpdate, err := time.Parse(time.RFC3339, *thisUpdateString)
108+
cmd.FailOnError(err, "Parsing thisUpdate flag")
109+
nextUpdate, err := time.Parse(time.RFC3339, *nextUpdateString)
110+
cmd.FailOnError(err, "Parsing nextUpdate flag")
111+
112+
issuer, responder, target, pkcs11, err := readFiles(*issuerFile, *responderFile, *targetFile, *pkcs11File)
134113
cmd.FailOnError(err, "Failed to read files")
135114

136115
// Instantiate the private key from PKCS11
137116
priv, err := pkcs11key.New(pkcs11.Module, pkcs11.TokenLabel, pkcs11.PIN, pkcs11.PrivateKeyLabel)
138117
cmd.FailOnError(err, "Failed to load PKCS#11 key")
139118

140119
// Populate the remaining fields in the template
141-
template.SerialNumber = target.SerialNumber
142-
template.Certificate = responder
120+
template := ocsp.Response{
121+
SerialNumber: target.SerialNumber,
122+
Certificate: responder,
123+
Status: *status,
124+
ThisUpdate: thisUpdate,
125+
NextUpdate: nextUpdate,
126+
}
127+
128+
if !core.KeyDigestEquals(responder.PublicKey, priv.Public()) {
129+
cmd.FailOnError(fmt.Errorf("PKCS#11 pubkey does not match pubkey "+
130+
"in responder certificate"), "loading keys")
131+
}
143132

144133
// Sign the OCSP response
145134
responseBytes, err := ocsp.CreateResponse(issuer, responder, template, priv)
146135
cmd.FailOnError(err, "Failed to sign OCSP response")
147136

137+
_, err = ocsp.ParseResponse(responseBytes, nil)
138+
cmd.FailOnError(err, "Failed to parse signed response")
139+
140+
responseBytesBase64 := base64.StdEncoding.EncodeToString(responseBytes) + "\n"
141+
148142
// Write the OCSP response to stdout
149-
if len(*outFile) == 0 {
150-
cmd.FailOnError(fmt.Errorf(""), "No output file provided")
151-
}
152-
err = ioutil.WriteFile(*outFile, responseBytes, 0666)
143+
err = ioutil.WriteFile(*outFile, []byte(responseBytesBase64), 0666)
153144
cmd.FailOnError(err, "Failed to write output file")
154145
}

cmd/single-ocsp/test/Makefile

Lines changed: 0 additions & 37 deletions
This file was deleted.

cmd/single-ocsp/test/README

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)