146 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			146 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Go
		
	
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"crypto/x509"
 | |
| 	"encoding/base64"
 | |
| 	"encoding/json"
 | |
| 	"flag"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/letsencrypt/boulder/cmd"
 | |
| 	"github.com/letsencrypt/boulder/core"
 | |
| 
 | |
| 	"github.com/letsencrypt/pkcs11key"
 | |
| 	"golang.org/x/crypto/ocsp"
 | |
| )
 | |
| 
 | |
| const usage = `
 | |
| name:
 | |
|   single-ocsp - Creates a single OCSP response
 | |
| 
 | |
| usage:
 | |
|   single-ocsp [args]
 | |
| 
 | |
| description:
 | |
|   According to the BRs, the OCSP responses for intermediate certificate must
 | |
|   be issued once per year.  So we need to issue OCSP responses for these
 | |
|   certificates, but it doesn't make sense to use all the infrastructure
 | |
|   that the "ocsp-updater" tool requires.  This tool allows an administrator
 | |
|   to manually generate an OCSP response for an intermediate certificate.
 | |
| 
 | |
|   This will write a base64-encoded DER format OCSP response to the file
 | |
|   specified with -out, ending in a newline. This output can be directly
 | |
|   appended to the flat file used by ocsp-responder for intermediate and
 | |
|   root OCSP responses.
 | |
| `
 | |
| 
 | |
| const pkcs11Usage = `
 | |
| PKCS#11 configuration (JSON), e.g.:
 | |
| 
 | |
| {
 | |
| 	"module": "/usr/local/lib/libpkcs11-proxy.so",
 | |
| 	"tokenLabel": "intermediate",
 | |
| 	"pin": "5678",
 | |
| 	"privateKeyLabel": "intermediate_key"
 | |
| }
 | |
| 
 | |
| Note: These values should *not* be the same as the ones in the CA's config JSON, which point at a differen HSM partition.
 | |
| `
 | |
| 
 | |
| func readFiles(issuerFileName, responderFileName, targetFileName, pkcs11FileName string) (issuer, responder, target *x509.Certificate, pkcs11Config pkcs11key.Config, err error) {
 | |
| 	// Issuer certificate
 | |
| 	issuer, err = core.LoadCert(issuerFileName)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Responder certificate
 | |
| 	responder, err = core.LoadCert(responderFileName)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Target certificate
 | |
| 	target, err = core.LoadCert(targetFileName)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// PKCS#11 config
 | |
| 	pkcs11Bytes, err := ioutil.ReadFile(pkcs11FileName)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	err = json.Unmarshal(pkcs11Bytes, &pkcs11Config)
 | |
| 	if pkcs11Config.Module == "" ||
 | |
| 		pkcs11Config.TokenLabel == "" ||
 | |
| 		pkcs11Config.PIN == "" ||
 | |
| 		pkcs11Config.PrivateKeyLabel == "" {
 | |
| 		err = fmt.Errorf("Missing a field in pkcs11Config %#v", pkcs11Config)
 | |
| 		return
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func main() {
 | |
| 	issuerFile := flag.String("issuer", "", "Issuer certificate (PEM)")
 | |
| 	responderFile := flag.String("responder", "", "OCSP responder certificate (DER)")
 | |
| 	targetFile := flag.String("target", "", "Certificate whose status is being reported (PEM)")
 | |
| 	pkcs11File := flag.String("pkcs11", "", pkcs11Usage)
 | |
| 	outFile := flag.String("out", "", "File to which the OCSP response will be written")
 | |
| 	thisUpdateString := flag.String("thisUpdate", "", "Time for ThisUpdate field, RFC3339 format (e.g. 2016-09-02T00:00:00Z)")
 | |
| 	nextUpdateString := flag.String("nextUpdate", "", "Time for NextUpdate field, RFC3339 format")
 | |
| 	status := flag.Int("status", 0, "Status for response (0 = good, 1 = revoked)")
 | |
| 	flag.Usage = func() {
 | |
| 		fmt.Fprint(os.Stderr, usage)
 | |
| 		flag.PrintDefaults()
 | |
| 	}
 | |
| 	flag.Parse()
 | |
| 
 | |
| 	if len(*outFile) == 0 {
 | |
| 		cmd.FailOnError(fmt.Errorf("No output file provided"), "")
 | |
| 	}
 | |
| 	thisUpdate, err := time.Parse(time.RFC3339, *thisUpdateString)
 | |
| 	cmd.FailOnError(err, "Parsing thisUpdate flag")
 | |
| 	nextUpdate, err := time.Parse(time.RFC3339, *nextUpdateString)
 | |
| 	cmd.FailOnError(err, "Parsing nextUpdate flag")
 | |
| 
 | |
| 	issuer, responder, target, pkcs11, err := readFiles(*issuerFile, *responderFile, *targetFile, *pkcs11File)
 | |
| 	cmd.FailOnError(err, "Failed to read files")
 | |
| 
 | |
| 	// Instantiate the private key from PKCS11
 | |
| 	priv, err := pkcs11key.New(pkcs11.Module, pkcs11.TokenLabel, pkcs11.PIN, pkcs11.PrivateKeyLabel)
 | |
| 	cmd.FailOnError(err, "Failed to load PKCS#11 key")
 | |
| 
 | |
| 	// Populate the remaining fields in the template
 | |
| 	template := ocsp.Response{
 | |
| 		SerialNumber: target.SerialNumber,
 | |
| 		Certificate:  responder,
 | |
| 		Status:       *status,
 | |
| 		ThisUpdate:   thisUpdate,
 | |
| 		NextUpdate:   nextUpdate,
 | |
| 	}
 | |
| 
 | |
| 	if !core.KeyDigestEquals(responder.PublicKey, priv.Public()) {
 | |
| 		cmd.FailOnError(fmt.Errorf("PKCS#11 pubkey does not match pubkey "+
 | |
| 			"in responder certificate"), "loading keys")
 | |
| 	}
 | |
| 
 | |
| 	// Sign the OCSP response
 | |
| 	responseBytes, err := ocsp.CreateResponse(issuer, responder, template, priv)
 | |
| 	cmd.FailOnError(err, "Failed to sign OCSP response")
 | |
| 
 | |
| 	_, err = ocsp.ParseResponse(responseBytes, nil)
 | |
| 	cmd.FailOnError(err, "Failed to parse signed response")
 | |
| 
 | |
| 	responseBytesBase64 := base64.StdEncoding.EncodeToString(responseBytes) + "\n"
 | |
| 
 | |
| 	// Write the OCSP response to stdout
 | |
| 	err = ioutil.WriteFile(*outFile, []byte(responseBytesBase64), 0666)
 | |
| 	cmd.FailOnError(err, "Failed to write output file")
 | |
| }
 |