Ceremony monolith (#4678)

Merges gen-ca and gen-key into a single tool that can be used to complete a key/certificate generation ceremony. The driving idea here is that instead of having to write out multiple long commands in a specific order in order to complete a ceremony a configuration file is fed to a single binary. This config file contains all of the information needed to complete the ceremony, and can be easily tested outside of the secure environment before hand without fear of later typing a command/flag incorrectly etc.

The tooling works against the test hardware I have (there are minimal changes to the actual PKCS#11 code behind the scenes). Specific attention should be given to the documentation, and the general UX of the tool.

Fixes #4639 and fixes #4667.
This commit is contained in:
Roland Bracewell Shoemaker 2020-03-27 13:54:56 -07:00 committed by GitHub
parent 3a1a08a10b
commit 81bb4047d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2074 additions and 1005 deletions

182
cmd/ceremony/README.md Normal file
View File

@ -0,0 +1,182 @@
# `ceremony`
```
ceremony --config path/to/config.yml
```
`ceremony` is a tool designed for Certificate Authority specific key and certificate ceremonies. The main design principle is that unlike most ceremony tooling there is a single user input, a configuration file, which is required to complete a root, intermediate, or key ceremony. The goal is to make ceremonies as simple as possible and allow for simple verification of a single file, instead of verification of a large number of independent commands.
`ceremony` operates in one of three modes
* `root` - generates a signing key on HSM and creates a self-signed root certificate that uses the generated key, outputting a PEM public key, and a PEM certificate
* `intermediate` - creates a intermediate certificate and signs it using a signing key already on a HSM, outputting a PEM certificate
* `key` - generates a signing key on HSM, outputting a PEM public key
These modes are set in the `ceremony-type` field of the configuration file.
## Configuration format
`ceremony` uses YAML for its configuration file, mainly as it allows for commenting. Each ceremony type has a different set of configuration fields.
### Root ceremony
- `ceremony-type`: string describing the ceremony type, `root`.
- `pkcs11`: object containing PKCS#11 related fields.
| Field | Description |
| --- | --- |
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
| `store-key-in-slot` | Specifies which HSM object slot the generated signing key should be stored in. |
| `store-key-with-label` | Specifies the HSM object label for the generated signing key. |
- `key`: object containing key generation related fields.
| Field | Description |
| --- | --- |
| `key-type` | Specifies the type of key to be generated, either `rsa` or `ecdsa`. If `rsa` the generated key will have an exponent of 65537 and a modulus length specified by `rsa-mod-length`. If `ecdsa` the curve is specified by `ecdsa-curve`. |
| `ecdsa-curve` | Specifies the ECDSA curve to use when generating key, either `P-224`, `P-256`, `P-384`, or `P-521`. |
| `rsa-mod-length` | Specifies the length of the RSA modulus, either `2048` or `4096`.
- `outputs`: object containing paths to write outputs.
| Field | Description |
| --- | --- |
| `public-key-path` | Path to store generated PEM public key. |
| `certificate-path` | Path to store signed PEM certificate. |
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#Certificate-profile-format).
Example:
```yaml
ceremony-type: root
pkcs11:
module: /usr/lib/opensc-pkcs11.so
store-key-in-slot: 0
store-key-with-label: root signing key
key:
type: ecdsa
ecdsa-curve: P-384
outputs:
public-key-path: /home/user/root-signing-pub.pem
certificate-path: /home/user/root-cert.pem
certificate-profile:
signature-algorithm: ECDSAWithSHA384
common-name: CA intermediate
organization: good guys
country: US
not-before: 2020-01-01 12:00:00
not-after: 2040-01-01 12:00:00
key-usages:
- Cert Sign
- CRL Sign
```
This config generates a ECDSA P-384 key in the HSM with the object label `root signing key` and uses this key to sign a self-signed certificate. The public key for the key generated is written to `/home/user/root-signing-pub.pem` and the certificate is written to `/home/user/root-cert.pem`.
### Intermediate ceremony
- `ceremony-type`: string describing the ceremony type, `intermediate`.
- `pkcs11`: object containing PKCS#11 related fields.
| Field | Description |
| --- | --- |
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
| `signing-key-label` | Specifies the HSM object label for the signing key. |
| `signing-key-id` | Specifies the HSM object ID for the signing key. |
- `inputs`: object containing paths for inputs
| Field | Description |
| --- | --- |
| `public-key-path` | Path to PEM subject public key for certificate. |
| `issuer-path` | Path to PEM issuer certificate. |
- `outputs`: object containing paths to write outputs.
| Field | Description |
| --- | --- |
| `certificate-path` | Path to store signed PEM certificate. |
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#Certificate-profile-format).
Example:
```yaml
ceremony-type: intermediate
pkcs11:
module: /usr/lib/opensc-pkcs11.so
signing-key-slot: 0
signing-key-label: root signing key
signing-key-id: ffff
inputs:
public-key-path: /home/user/intermediate-signing-pub.pem
issuer-path: /home/user/root-cert.pem
outputs:
certificate-path: /home/user/intermediate-cert.pem
certificate-profile:
signature-algorithm: ECDSAWithSHA384
common-name: CA root
organization: good guys
country: US
not-before: 2020-01-01 12:00:00
not-after: 2040-01-01 12:00:00
ocsp-url: http://good-guys.com/ocsp
crl-url: http://good-guys.com/crl
issuer-url: http://good-guys.com/root
policy-oids:
- 1.2.3
- 5.4.3.2.1
key-usages:
- Digital Signature
- Cert Sign
- CRL Sign
```
This config generates an intermediate certificate signed by a key in the HSM, identified by the object label `root signing key` and the object ID `ffff`. The subject key used is taken from `/home/user/intermediate-signing-pub.pem` and the issuer is `/home/user/root-cert.pem`, the resulting certificate is written to `/home/user/intermediate-cert.pem`.
### Key ceremony
- `ceremony-type`: string describing the ceremony type, `key`.
- `pkcs11`: object containing PKCS#11 related fields.
| Field | Description |
| --- | --- |
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
| `store-key-in-slot` | Specifies which HSM object slot the generated signing key should be stored in. |
| `store-key-with-label` | Specifies the HSM object label for the generated signing key. |
- `key`: object containing key generation related fields.
| Field | Description |
| --- | --- |
| `key-type` | Specifies the type of key to be generated, either `rsa` or `ecdsa`. If `rsa` the generated key will have an exponent of 65537 and a modulus length specified by `rsa-mod-length`. If `ecdsa` the curve is specified by `ecdsa-curve`. |
| `ecdsa-curve` | Specifies the ECDSA curve to use when generating key, either `P-224`, `P-256`, `P-384`, or `P-521`. |
| `rsa-mod-length` | Specifies the length of the RSA modulus, either `2048` or `4096`.
- `outputs`: object containing paths to write outputs.
| Field | Description |
| --- | --- |
| `public-key-path` | Path to store generated PEM public key. |
Example:
```yaml
ceremony-type: key
pkcs11:
module: /usr/lib/opensc-pkcs11.so
store-key-in-slot: 0
store-key-with-label: intermediate signing key
key:
key-type: ecdsa
ecdsa-curve: P-384
outputs:
public-key-path: /home/user/intermediate-signing-pub.pem
```
This config generates an ECDSA P-384 key in the HSM with the object label `intermediate signing key`. The public key is written to `/home/user/intermediate-signing-pub.pem`.
### Certificate profile format
The certificate profile defines a restricted set of fields that are used to generate root and intermediate certificates.
| Field | Description |
| --- | --- |
| `signature-algorithm` | Specifies the signing algorithm to use, one of `SHA256WithRSA`, `SHA384WithRSA`, `SHA512WithRSA`, `ECDSAWithSHA256`, `ECDSAWithSHA384`, `ECDSAWithSHA512` |
| `common-name` | Specifies the subject commonName |
| `organization` | Specifies the subject organization |
| `country` | Specifies the subject country |
| `not-before` | Specifies the certificate notBefore date, in the format `2006-01-02 15:04:05`. The time will be interpreted as UTC. |
| `not-after` | Specifies the certificate notAfter date, in the format `2006-01-02 15:04:05`. The time will be interpreted as UTC. |
| `ocsp-url` | Specifies the AIA OCSP responder URL |
| `crl-url` | Specifies the cRLDistributionPoints URL |
| `issuer-url` | Specifies the AIA caIssuer URL |
| `policy-oids` | Specifies the contents of the certificatePolicies extension |
| `key-usages` | Specifies list of key usage bits should be set, list can contain `Digital Signature`, `CRL Sign`, and `Cert Sign` |

View File

@ -7,26 +7,213 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"math/big"
"os"
"strconv"
"strings"
"time"
"github.com/miekg/pkcs11"
"github.com/letsencrypt/boulder/pkcs11helpers"
"github.com/miekg/pkcs11"
)
// certProfile contains the information required to generate a certificate
type certProfile struct {
// SignatureAlgorithm should contain one of the allowed signature algorithms
// in AllowedSigAlgs
SignatureAlgorithm string `yaml:"signature-algorithm"`
// CommonName should contain the requested subject common name
CommonName string `yaml:"common-name"`
// Organization should contain the requested subject organization
Organization string `yaml:"organization"`
// Country should contain the requested subject country code
Country string `yaml:"country"`
// NotBefore should contain the requested NotBefore date for the
// certificate in the format "2006-01-02 15:04:05". Dates will
// always be UTC.
NotBefore string `yaml:"not-before"`
// NotAfter should contain the requested NotAfter date for the
// certificate in the format "2006-01-02 15:04:05". Dates will
// always be UTC.
NotAfter string `yaml:"not-after"`
// OCSPURL should contain the URL at which a OCSP responder that
// can respond to OCSP requests for this certificate operates
OCSPURL string `yaml:"ocsp-url"`
// CRLURL should contain the URL at which CRLs for this certificate
// can be found
CRLURL string `yaml:"crl-url"`
// IssuerURL should contain the URL at which the issuing certificate
// can be found, this is only required if generating an intermediate
// certificate
IssuerURL string `yaml:"issuer-url"`
// PolicyOIDs should contain any OIDs to be inserted in a certificate
// policies extension. These should be formatted in the standard OID
// string format (i.e. "1.2.3")
PolicyOIDs []string `yaml:"policy-oids"`
// KeyUsages should contain the set of key usage bits to set
KeyUsages []string `yaml:"key-usages"`
}
// AllowedSigAlgs contains the allowed signature algorithms
var AllowedSigAlgs = map[string]x509.SignatureAlgorithm{
"SHA256WithRSA": x509.SHA256WithRSA,
"SHA384WithRSA": x509.SHA384WithRSA,
"SHA512WithRSA": x509.SHA512WithRSA,
"ECDSAWithSHA256": x509.ECDSAWithSHA256,
"ECDSAWithSHA384": x509.ECDSAWithSHA384,
"ECDSAWithSHA512": x509.ECDSAWithSHA512,
}
func (profile *certProfile) verifyProfile(root bool) error {
if profile.NotBefore == "" {
return errors.New("not-before is required")
}
if profile.NotAfter == "" {
return errors.New("not-after is required")
}
if profile.SignatureAlgorithm == "" {
return errors.New("signature-algorithm is required")
}
if profile.CommonName == "" {
return errors.New("common-name is required")
}
if profile.Organization == "" {
return errors.New("organization is required")
}
if profile.Country == "" {
return errors.New("country is required")
}
if !root && profile.OCSPURL == "" {
return errors.New("ocsp-url is required for intermediates")
}
if !root && profile.CRLURL == "" {
return errors.New("crl-url is required for intermediates")
}
if !root && profile.IssuerURL == "" {
return errors.New("issuer-url is required for intermediates")
}
return nil
}
func parseOID(oidStr string) (asn1.ObjectIdentifier, error) {
var oid asn1.ObjectIdentifier
for _, a := range strings.Split(oidStr, ".") {
i, err := strconv.Atoi(a)
if err != nil {
return nil, err
}
oid = append(oid, i)
}
return oid, nil
}
var stringToKeyUsage = map[string]x509.KeyUsage{
"Digital Signature": x509.KeyUsageDigitalSignature,
"CRL Sign": x509.KeyUsageCRLSign,
"Cert Sign": x509.KeyUsageCertSign,
}
// makeTemplate generates the certificate template for use in x509.CreateCertificate
func makeTemplate(randReader io.Reader, profile *certProfile, pubKey []byte) (*x509.Certificate, error) {
dateLayout := "2006-01-02 15:04:05"
notBefore, err := time.Parse(dateLayout, profile.NotBefore)
if err != nil {
return nil, err
}
notAfter, err := time.Parse(dateLayout, profile.NotAfter)
if err != nil {
return nil, err
}
var ocspServer []string
if profile.OCSPURL != "" {
ocspServer = []string{profile.OCSPURL}
}
var crlDistributionPoints []string
if profile.CRLURL != "" {
crlDistributionPoints = []string{profile.CRLURL}
}
var issuingCertificateURL []string
if profile.IssuerURL != "" {
issuingCertificateURL = []string{profile.IssuerURL}
}
var policyOIDs []asn1.ObjectIdentifier
for _, oidStr := range profile.PolicyOIDs {
oid, err := parseOID(oidStr)
if err != nil {
return nil, err
}
policyOIDs = append(policyOIDs, oid)
}
sigAlg, ok := AllowedSigAlgs[profile.SignatureAlgorithm]
if !ok {
return nil, fmt.Errorf("unsupported signature algorithm %q", profile.SignatureAlgorithm)
}
subjectKeyID := sha256.Sum256(pubKey)
serial := make([]byte, 16)
_, err = randReader.Read(serial)
if err != nil {
return nil, fmt.Errorf("failed to generate serial number: %s", err)
}
var ku x509.KeyUsage
if len(profile.KeyUsages) == 0 {
return nil, errors.New("key usages must be set")
}
for _, kuStr := range profile.KeyUsages {
kuBit, ok := stringToKeyUsage[kuStr]
if !ok {
return nil, fmt.Errorf("unknown key usage %q", kuStr)
}
ku |= kuBit
}
cert := &x509.Certificate{
SignatureAlgorithm: sigAlg,
SerialNumber: big.NewInt(0).SetBytes(serial),
BasicConstraintsValid: true,
IsCA: true,
Subject: pkix.Name{
CommonName: profile.CommonName,
Organization: []string{profile.Organization},
Country: []string{profile.Country},
},
NotBefore: notBefore,
NotAfter: notAfter,
OCSPServer: ocspServer,
CRLDistributionPoints: crlDistributionPoints,
IssuingCertificateURL: issuingCertificateURL,
PolicyIdentifiers: policyOIDs,
KeyUsage: ku,
SubjectKeyId: subjectKeyID[:],
}
return cert, nil
}
// failReader exists to be passed to x509.CreateCertificate which requires
// a source of randomness for signing methods that require a source of
// randomness. Since HSM based signing will generate its own randomness
// we don't need a real reader. Instead of passing a nil reader we use one
// that always returns errors in case the internal usage of this reader
// changes.
type failReader struct{}
func (fr *failReader) Read([]byte) (int, error) {
return 0, errors.New("Empty reader used by x509.CreateCertificate")
}
// x509Signer is a convenience wrapper used for converting between the
// PKCS#11 ECDSA signature format and the RFC 5480 one which is required
// for X.509 certificates
@ -68,40 +255,15 @@ func (p *x509Signer) Public() crypto.PublicKey {
return p.pub
}
// findObject looks up a PKCS#11 object handle based on the provided template.
// In the case where zero or more than one objects are found to match the
// template an error is returned.
func findObject(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, tmpl []*pkcs11.Attribute) (pkcs11.ObjectHandle, error) {
if err := ctx.FindObjectsInit(session, tmpl); err != nil {
return 0, err
}
handles, more, err := ctx.FindObjects(session, 1)
if err != nil {
return 0, err
}
if len(handles) == 0 {
return 0, errors.New("no objects found matching provided template")
}
if more {
return 0, errors.New("more than one object matches provided template")
}
if err := ctx.FindObjectsFinal(session); err != nil {
return 0, err
}
return handles[0], nil
}
// getKey constructs a x509Signer for the private key object associated with the
// given label and ID
func getKey(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, label string, idStr string) (*x509Signer, error) {
id, err := hex.DecodeString(idStr)
if err != nil {
return nil, err
}
// newSigner constructs a x509Signer for the private key object associated with the
// given label and ID. Unlike letsencrypt/pkcs11key this method doesn't rely on
// having the actual public key object in order to retrieve the private key
// handle. This is because we already have the key pair object ID, and as such
// do not need to query the HSM to retrieve it.
func newSigner(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, label string, id []byte) (crypto.Signer, error) {
// Retrieve the private key handle that will later be used for the certificate
// signing operation
privateHandle, err := findObject(ctx, session, []*pkcs11.Attribute{
privateHandle, err := pkcs11helpers.FindObject(ctx, session, []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
pkcs11.NewAttribute(pkcs11.CKA_ID, id),
@ -121,7 +283,7 @@ func getKey(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, label string,
// Retrieve the public key handle with the same CKA_ID as the private key
// and construct a {rsa,ecdsa}.PublicKey for use in x509.CreateCertificate
pubHandle, err := findObject(ctx, session, []*pkcs11.Attribute{
pubHandle, err := pkcs11helpers.FindObject(ctx, session, []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
pkcs11.NewAttribute(pkcs11.CKA_ID, id),
@ -159,302 +321,3 @@ func getKey(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, label string,
pub: pub,
}, nil
}
// AllowedSigAlgs contains the allowed signature algorithms
var AllowedSigAlgs = map[string]x509.SignatureAlgorithm{
"SHA256WithRSA": x509.SHA256WithRSA,
"SHA384WithRSA": x509.SHA384WithRSA,
"SHA512WithRSA": x509.SHA512WithRSA,
"ECDSAWithSHA256": x509.ECDSAWithSHA256,
"ECDSAWithSHA384": x509.ECDSAWithSHA384,
"ECDSAWithSHA512": x509.ECDSAWithSHA512,
}
// CertProfile contains the information required to generate a certificate
// for signing
type CertProfile struct {
// SignatureAlgorithm should contain one of the allowed signature algorithms
// in AllowedSigAlgs
SignatureAlgorithm string
// CommonName should contain the requested subject common name
CommonName string
// Organization should contain the requested subject organization
Organization string
// Country should contain the requested subject country code
Country string
// NotBefore should contain the requested NotBefore date for the
// certificate in the format "2006-01-02 15:04:05". Dates will
// always be UTC.
NotBefore string
// NotAfter should contain the requested NotAfter date for the
// certificate in the format "2006-01-02 15:04:05". Dates will
// always be UTC.
NotAfter string
// OCSPURL should contain the URL at which a OCSP responder that
// can respond to OCSP requests for this certificate operates
OCSPURL string
// CRLURL should contain the URL at which CRLs for this certificate
// can be found
CRLURL string
// IssuerURL should contain the URL at which the issuing certificate
// can be found, this is only required if generating an intermediate
// certificate
IssuerURL string
// PolicyOIDs should contain any OIDs to be inserted in a certificate
// policies extension. These should be formatted in the standard OID
// string format (i.e. "1.2.3")
PolicyOIDs []string
}
func parseOID(oidStr string) (asn1.ObjectIdentifier, error) {
var oid asn1.ObjectIdentifier
for _, a := range strings.Split(oidStr, ".") {
i, err := strconv.Atoi(a)
if err != nil {
return nil, err
}
oid = append(oid, i)
}
return oid, nil
}
func verifyProfile(profile CertProfile, root bool) error {
if profile.NotBefore == "" {
return errors.New("NotBefore in profile is required")
}
if profile.NotAfter == "" {
return errors.New("NotAfter in profile is required")
}
if profile.SignatureAlgorithm == "" {
return errors.New("SignatureAlgorithm in profile is required")
}
if profile.CommonName == "" {
return errors.New("CommonName in profile is required")
}
if profile.Organization == "" {
return errors.New("Organization in profile is required")
}
if profile.Country == "" {
return errors.New("Country in profile is required")
}
if !root && profile.OCSPURL == "" {
return errors.New("OCSPURL in profile is required for intermediates")
}
if !root && profile.CRLURL == "" {
return errors.New("CRLURL in profile is required for intermediates")
}
if !root && profile.IssuerURL == "" {
return errors.New("IssuerURL in profile is required for intermediates")
}
return nil
}
// makeTemplate generates the certificate template for use in x509.CreateCertificate
func makeTemplate(ctx pkcs11helpers.PKCtx, profile *CertProfile, pubKey []byte, session pkcs11.SessionHandle) (*x509.Certificate, error) {
dateLayout := "2006-01-02 15:04:05"
notBefore, err := time.Parse(dateLayout, profile.NotBefore)
if err != nil {
return nil, err
}
notAfter, err := time.Parse(dateLayout, profile.NotAfter)
if err != nil {
return nil, err
}
var ocspServer []string
if profile.OCSPURL != "" {
ocspServer = []string{profile.OCSPURL}
}
var crlDistributionPoints []string
if profile.CRLURL != "" {
crlDistributionPoints = []string{profile.CRLURL}
}
var issuingCertificateURL []string
if profile.IssuerURL != "" {
issuingCertificateURL = []string{profile.IssuerURL}
}
var policyOIDs []asn1.ObjectIdentifier
for _, oidStr := range profile.PolicyOIDs {
oid, err := parseOID(oidStr)
if err != nil {
return nil, err
}
policyOIDs = append(policyOIDs, oid)
}
sigAlg, ok := AllowedSigAlgs[profile.SignatureAlgorithm]
if !ok {
return nil, fmt.Errorf("unsupported signature algorithm %q", profile.SignatureAlgorithm)
}
subjectKeyID := sha256.Sum256(pubKey)
serial, err := ctx.GenerateRandom(session, 16)
if err != nil {
return nil, fmt.Errorf("failed to generate serial number: %s", err)
}
cert := &x509.Certificate{
SignatureAlgorithm: sigAlg,
SerialNumber: big.NewInt(0).SetBytes(serial),
BasicConstraintsValid: true,
IsCA: true,
Subject: pkix.Name{
CommonName: profile.CommonName,
Organization: []string{profile.Organization},
Country: []string{profile.Country},
},
NotBefore: notBefore,
NotAfter: notAfter,
OCSPServer: ocspServer,
CRLDistributionPoints: crlDistributionPoints,
IssuingCertificateURL: issuingCertificateURL,
PolicyIdentifiers: policyOIDs,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
SubjectKeyId: subjectKeyID[:],
}
return cert, nil
}
type failReader struct{}
func (fr *failReader) Read([]byte) (int, error) {
return 0, errors.New("Empty reader used by x509.CreateCertificate")
}
func main() {
module := flag.String("module", "", "PKCS#11 module to use")
slot := flag.Uint("slot", 0, "ID of PKCS#11 slot containing token with signing key.")
pin := flag.String("pin", "", "PKCS#11 token PIN. If empty, will assume PED based login.")
label := flag.String("label", "", "PKCS#11 key label")
id := flag.String("id", "", "PKCS#11 hex key ID (simplified format, i.e. ffff")
profilePath := flag.String("profile", "", "Path to file containing certificate profile in JSON format. See https://godoc.org/github.com/letsencrypt/boulder/cmd/gen-ca#CertProfile for details.")
pubKeyPath := flag.String("publicKey", "", "Path to file containing the subject public key in PEM format")
issuerPath := flag.String("issuer", "", "Path to issuer cert if generating an intermediate")
outputPath := flag.String("output", "", "Path to store generated PEM certificate")
flag.Parse()
if *module == "" {
log.Fatal("--module is required")
}
if *label == "" {
log.Fatal("--label is required")
}
if *id == "" {
log.Fatal("--id is required")
}
if *profilePath == "" {
log.Fatal("--profile is required")
}
if *pubKeyPath == "" {
log.Fatal("--publicKey is required")
}
if *outputPath == "" {
log.Fatal("--output is required")
}
ctx, session, err := pkcs11helpers.Initialize(*module, *slot, *pin)
if err != nil {
log.Fatalf("Failed to setup session and PKCS#11 context: %s", err)
}
log.Println("Opened PKCS#11 session")
privKey, err := getKey(ctx, session, *label, *id)
if err != nil {
log.Fatalf("Failed to retrieve private key handle: %s", err)
}
log.Println("Retrieved private key handle")
profileBytes, err := ioutil.ReadFile(*profilePath)
if err != nil {
log.Fatalf("Failed to read certificate profile %q: %s", *profilePath, err)
}
var profile CertProfile
err = json.Unmarshal(profileBytes, &profile)
if err != nil {
log.Fatalf("Failed to parse certificate profile: %s", err)
}
if err = verifyProfile(profile, *issuerPath == ""); err != nil {
log.Fatalf("Invalid certificate profile: %s", err)
}
pubPEMBytes, err := ioutil.ReadFile(*pubKeyPath)
if err != nil {
log.Fatalf("Failed to read public key %q: %s", *pubKeyPath, err)
}
pubPEM, _ := pem.Decode(pubPEMBytes)
if pubPEM == nil {
log.Fatal("Failed to parse public key")
}
pub, err := x509.ParsePKIXPublicKey(pubPEM.Bytes)
if err != nil {
log.Fatalf("Failed to parse public key: %s", err)
}
certTemplate, err := makeTemplate(ctx, &profile, pubPEM.Bytes, session)
if err != nil {
log.Fatalf("Failed to construct certificate template from profile: %s", err)
}
log.Println("Generated certificate template from profile")
var issuer *x509.Certificate
if *issuerPath != "" {
// If generating an intermediate, load the parent issuer and
// set the Authority Key Identifier in the template.
issuerPEMBytes, err := ioutil.ReadFile(*issuerPath)
if err != nil {
log.Fatalf("Failed to read issuer certificate %q: %s", *issuerPath, err)
}
issuerPEM, _ := pem.Decode(issuerPEMBytes)
if issuerPEM == nil {
log.Fatal("Failed to parse issuer certificate PEM")
}
issuer, err = x509.ParseCertificate(issuerPEM.Bytes)
if err != nil {
log.Fatalf("Failed to parse issuer certificate: %s", err)
}
certTemplate.AuthorityKeyId = issuer.SubjectKeyId
} else {
issuer = certTemplate
}
// x509.CreateCertificate uses a io.Reader here for signing methods that require
// a source of randomness. Since PKCS#11 based signing generates needed randomness
// at the HSM we don't need to pass a real reader. Instead of passing a nil reader
// we use one that always returns errors in case the internal usage of this reader
// changes.
certBytes, err := x509.CreateCertificate(&failReader{}, certTemplate, issuer, pub, privKey)
if err != nil {
log.Fatalf("Failed to create certificate: %s", err)
}
log.Printf("Signed certificate: %x\n", certBytes)
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
log.Fatalf("Failed to parse signed certificate: %s", err)
}
// If generating a root then the signing key is the public key
// in the cert itself, so set the parent to itself
var parent *x509.Certificate
if *issuerPath == "" {
parent = cert
} else {
parent = issuer
}
if err := cert.CheckSignatureFrom(parent); err != nil {
log.Fatalf("Failed to verify certificate signature: %s", err)
}
log.Println("Verified certificate signature")
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
log.Printf("Certificate PEM:\n%s", pemBytes)
if err := ioutil.WriteFile(*outputPath, pemBytes, os.ModePerm); err != nil {
log.Fatalf("Failed to write certificate to %q: %s", *outputPath, err)
}
log.Printf("Certificate written to %q\n", *outputPath)
}

View File

@ -7,6 +7,7 @@ import (
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"errors"
"math/big"
@ -65,74 +66,212 @@ func TestX509Signer(t *testing.T) {
test.AssertEquals(t, signer.Public(), tk.Public())
}
func TestFindObject(t *testing.T) {
func TestParseOID(t *testing.T) {
_, err := parseOID("")
test.AssertError(t, err, "parseOID accepted an empty OID")
_, err = parseOID("a.b.c")
test.AssertError(t, err, "parseOID accepted an OID containing non-ints")
oid, err := parseOID("1.2.3")
test.AssertNotError(t, err, "parseOID failed with a valid OID")
test.Assert(t, oid.Equal(asn1.ObjectIdentifier{1, 2, 3}), "parseOID returned incorrect OID")
}
func TestMakeTemplate(t *testing.T) {
ctx := pkcs11helpers.MockCtx{}
profile := &certProfile{}
randReader := newRandReader(&ctx, 0)
// test findObject fails when FindObjectsInit fails
ctx.FindObjectsInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Attribute) error {
return errors.New("broken")
}
_, err := findObject(ctx, 0, nil)
test.AssertError(t, err, "findObject didn't fail when FindObjectsInit failed")
profile.NotBefore = "1234"
_, err := makeTemplate(randReader, profile, nil)
test.AssertError(t, err, "makeTemplate didn't fail with invalid not before")
// test findObject fails when FindObjects fails
ctx.FindObjectsInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Attribute) error {
return nil
}
ctx.FindObjectsFunc = func(pkcs11.SessionHandle, int) ([]pkcs11.ObjectHandle, bool, error) {
return nil, false, errors.New("broken")
}
_, err = findObject(ctx, 0, nil)
test.AssertError(t, err, "findObject didn't fail when FindObjects failed")
profile.NotBefore = "2018-05-18 11:31:00"
profile.NotAfter = "1234"
_, err = makeTemplate(randReader, profile, nil)
test.AssertError(t, err, "makeTemplate didn't fail with invalid not after")
// test findObject fails when no handles are returned
ctx.FindObjectsFunc = func(pkcs11.SessionHandle, int) ([]pkcs11.ObjectHandle, bool, error) {
return []pkcs11.ObjectHandle{}, false, nil
}
_, err = findObject(ctx, 0, nil)
test.AssertError(t, err, "findObject didn't fail when FindObjects returns no handles")
profile.NotAfter = "2018-05-18 11:31:00"
profile.PolicyOIDs = []string{""}
_, err = makeTemplate(randReader, profile, nil)
test.AssertError(t, err, "makeTemplate didn't fail with invalid policy OID")
// test findObject fails when multiple handles are returned
ctx.FindObjectsFunc = func(pkcs11.SessionHandle, int) ([]pkcs11.ObjectHandle, bool, error) {
return []pkcs11.ObjectHandle{1}, true, nil
}
_, err = findObject(ctx, 0, nil)
test.AssertError(t, err, "findObject didn't fail when FindObjects returns multiple handles")
profile.PolicyOIDs = []string{"1.2.3"}
profile.SignatureAlgorithm = "nope"
_, err = makeTemplate(randReader, profile, nil)
test.AssertError(t, err, "makeTemplate didn't fail with invalid signature algorithm")
// test findObject fails when FindObjectsFinal fails
ctx.FindObjectsFunc = func(pkcs11.SessionHandle, int) ([]pkcs11.ObjectHandle, bool, error) {
return []pkcs11.ObjectHandle{1}, false, nil
profile.SignatureAlgorithm = "SHA256WithRSA"
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return nil, errors.New("bad")
}
ctx.FindObjectsFinalFunc = func(pkcs11.SessionHandle) error {
return errors.New("broken")
}
_, err = findObject(ctx, 0, nil)
test.AssertError(t, err, "findObject didn't fail when FindObjectsFinal fails")
_, err = makeTemplate(randReader, profile, nil)
test.AssertError(t, err, "makeTemplate didn't fail when GenerateRandom failed")
// test findObject works
ctx.FindObjectsFinalFunc = func(pkcs11.SessionHandle) error {
return nil
ctx.GenerateRandomFunc = func(_ pkcs11.SessionHandle, length int) ([]byte, error) {
r := make([]byte, length)
_, err := rand.Read(r)
return r, err
}
_, err = makeTemplate(randReader, profile, nil)
test.AssertError(t, err, "makeTemplate didn't fail with empty key usages")
profile.KeyUsages = []string{"asd"}
_, err = makeTemplate(randReader, profile, nil)
test.AssertError(t, err, "makeTemplate didn't fail with invalid key usages")
profile.KeyUsages = []string{"Digital Signature", "CRL Sign"}
profile.CommonName = "common name"
profile.Organization = "organization"
profile.Country = "country"
profile.OCSPURL = "ocsp"
profile.CRLURL = "crl"
profile.IssuerURL = "issuer"
cert, err := makeTemplate(randReader, profile, nil)
test.AssertNotError(t, err, "makeTemplate failed when everything worked as expected")
test.AssertEquals(t, cert.Subject.CommonName, profile.CommonName)
test.AssertEquals(t, len(cert.Subject.Organization), 1)
test.AssertEquals(t, cert.Subject.Organization[0], profile.Organization)
test.AssertEquals(t, len(cert.Subject.Country), 1)
test.AssertEquals(t, cert.Subject.Country[0], profile.Country)
test.AssertEquals(t, len(cert.OCSPServer), 1)
test.AssertEquals(t, cert.OCSPServer[0], profile.OCSPURL)
test.AssertEquals(t, len(cert.CRLDistributionPoints), 1)
test.AssertEquals(t, cert.CRLDistributionPoints[0], profile.CRLURL)
test.AssertEquals(t, len(cert.IssuingCertificateURL), 1)
test.AssertEquals(t, cert.IssuingCertificateURL[0], profile.IssuerURL)
test.AssertEquals(t, cert.KeyUsage, x509.KeyUsageDigitalSignature|x509.KeyUsageCRLSign)
}
func TestVerifyProfile(t *testing.T) {
for _, tc := range []struct {
profile certProfile
root bool
expectedErr string
}{
{
profile: certProfile{},
root: false,
expectedErr: "not-before is required",
},
{
profile: certProfile{
NotBefore: "a",
},
root: false,
expectedErr: "not-after is required",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
},
root: false,
expectedErr: "signature-algorithm is required",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
},
root: false,
expectedErr: "common-name is required",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
},
root: false,
expectedErr: "organization is required",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
},
root: false,
expectedErr: "country is required",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
},
root: false,
expectedErr: "ocsp-url is required for intermediates",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
OCSPURL: "g",
},
root: false,
expectedErr: "crl-url is required for intermediates",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
OCSPURL: "g",
CRLURL: "h",
},
root: false,
expectedErr: "issuer-url is required for intermediates",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
},
root: true,
},
} {
err := tc.profile.verifyProfile(tc.root)
if err != nil {
if tc.expectedErr != err.Error() {
t.Fatalf("Expected %q, got %q", tc.expectedErr, err.Error())
}
} else if tc.expectedErr != "" {
t.Fatalf("verifyProfile didn't fail, expected %q", tc.expectedErr)
}
}
handle, err := findObject(ctx, 0, nil)
test.AssertNotError(t, err, "findObject failed when everything worked as expected")
test.AssertEquals(t, handle, pkcs11.ObjectHandle(1))
}
func TestGetKey(t *testing.T) {
ctx := pkcs11helpers.MockCtx{}
// test getKey fails with invalid key ID
_, err := getKey(ctx, 0, "label", "not hex")
test.AssertError(t, err, "getKey didn't fail with invalid key ID")
// test getKey fails when findObject for private key handle fails
// test newSigner fails when pkcs11helpers.FindObject for private key handle fails
ctx.FindObjectsInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Attribute) error {
return errors.New("broken")
}
_, err = getKey(ctx, 0, "label", "ffff")
test.AssertError(t, err, "getKey didn't fail when findObject for private key handle failed")
_, err := newSigner(ctx, 0, "label", []byte{255, 255})
test.AssertError(t, err, "newSigner didn't fail when pkcs11helpers.FindObject for private key handle failed")
// test getKey fails when GetAttributeValue fails
// test newSigner fails when GetAttributeValue fails
ctx.FindObjectsInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Attribute) error {
return nil
}
@ -145,17 +284,17 @@ func TestGetKey(t *testing.T) {
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return nil, errors.New("broken")
}
_, err = getKey(ctx, 0, "label", "ffff")
test.AssertError(t, err, "getKey didn't fail when GetAttributeValue for private key type failed")
_, err = newSigner(ctx, 0, "label", []byte{255, 255})
test.AssertError(t, err, "newSigner didn't fail when GetAttributeValue for private key type failed")
// test getKey fails when GetAttributeValue returns no attributes
// test newSigner fails when GetAttributeValue returns no attributes
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return nil, nil
}
_, err = getKey(ctx, 0, "label", "ffff")
test.AssertError(t, err, "getKey didn't fail when GetAttributeValue for private key type returned no attributes")
_, err = newSigner(ctx, 0, "label", []byte{255, 255})
test.AssertError(t, err, "newSigner didn't fail when GetAttributeValue for private key type returned no attributes")
// test getKey fails when findObject for public key handle fails
// test newSigner fails when pkcs11helpers.FindObject for public key handle fails
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_EC)}, nil
}
@ -165,34 +304,34 @@ func TestGetKey(t *testing.T) {
}
return nil
}
_, err = getKey(ctx, 0, "label", "ffff")
test.AssertError(t, err, "getKey didn't fail when findObject for public key handle failed")
_, err = newSigner(ctx, 0, "label", []byte{255, 255})
test.AssertError(t, err, "newSigner didn't fail when pkcs11helpers.FindObject for public key handle failed")
// test getKey fails when findObject for private key returns unknown CKA_KEY_TYPE
// test newSigner fails when pkcs11helpers.FindObject for private key returns unknown CKA_KEY_TYPE
ctx.FindObjectsInitFunc = func(_ pkcs11.SessionHandle, tmpl []*pkcs11.Attribute) error {
return nil
}
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, []byte{2, 0, 0, 0, 0, 0, 0, 0})}, nil
}
_, err = getKey(ctx, 0, "label", "ffff")
test.AssertError(t, err, "getKey didn't fail when GetAttributeValue for private key returned unknown key type")
_, err = newSigner(ctx, 0, "label", []byte{255, 255})
test.AssertError(t, err, "newSigner didn't fail when GetAttributeValue for private key returned unknown key type")
// test getKey fails when GetRSAPublicKey fails
// test newSigner fails when GetRSAPublicKey fails
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, []byte{0, 0, 0, 0, 0, 0, 0, 0})}, nil
}
_, err = getKey(ctx, 0, "label", "ffff")
test.AssertError(t, err, "getKey didn't fail when GetRSAPublicKey fails")
_, err = newSigner(ctx, 0, "label", []byte{255, 255})
test.AssertError(t, err, "newSigner didn't fail when GetRSAPublicKey fails")
// test getKey fails when GetECDSAPublicKey fails
// test newSigner fails when GetECDSAPublicKey fails
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, []byte{3, 0, 0, 0, 0, 0, 0, 0})}, nil
}
_, err = getKey(ctx, 0, "label", "ffff")
test.AssertError(t, err, "getKey didn't fail when GetECDSAPublicKey fails")
_, err = newSigner(ctx, 0, "label", []byte{255, 255})
test.AssertError(t, err, "newSigner didn't fail when GetECDSAPublicKey fails")
// test getKey works when everything... works
// test newSigner works when everything... works
ctx.GetAttributeValueFunc = func(_ pkcs11.SessionHandle, _ pkcs11.ObjectHandle, attrs []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
var returns []*pkcs11.Attribute
for _, attr := range attrs {
@ -209,190 +348,6 @@ func TestGetKey(t *testing.T) {
}
return returns, nil
}
_, err = getKey(ctx, 0, "label", "ffff")
test.AssertNotError(t, err, "getKey failed when everything worked properly")
}
func TestParseOID(t *testing.T) {
_, err := parseOID("")
test.AssertError(t, err, "parseOID accepted an empty OID")
_, err = parseOID("a.b.c")
test.AssertError(t, err, "parseOID accepted an OID containing non-ints")
oid, err := parseOID("1.2.3")
test.AssertNotError(t, err, "parseOID failed with a valid OID")
test.Assert(t, oid.Equal(asn1.ObjectIdentifier{1, 2, 3}), "parseOID returned incorrect OID")
}
func TestMakeTemplate(t *testing.T) {
ctx := pkcs11helpers.MockCtx{}
profile := &CertProfile{}
profile.NotBefore = "1234"
_, err := makeTemplate(ctx, profile, nil, 0)
test.AssertError(t, err, "makeTemplate didn't fail with invalid not before")
profile.NotBefore = "2018-05-18 11:31:00"
profile.NotAfter = "1234"
_, err = makeTemplate(ctx, profile, nil, 0)
test.AssertError(t, err, "makeTemplate didn't fail with invalid not after")
profile.NotAfter = "2018-05-18 11:31:00"
profile.PolicyOIDs = []string{""}
_, err = makeTemplate(ctx, profile, nil, 0)
test.AssertError(t, err, "makeTemplate didn't fail with invalid policy OID")
profile.PolicyOIDs = []string{"1.2.3"}
profile.SignatureAlgorithm = "nope"
_, err = makeTemplate(ctx, profile, nil, 0)
test.AssertError(t, err, "makeTemplate didn't fail with invalid signature algorithm")
profile.SignatureAlgorithm = "SHA256WithRSA"
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return nil, errors.New("bad")
}
_, err = makeTemplate(ctx, profile, nil, 0)
test.AssertError(t, err, "makeTemplate didn't fail when GenerateRandom failed")
ctx.GenerateRandomFunc = func(_ pkcs11.SessionHandle, length int) ([]byte, error) {
r := make([]byte, length)
_, err := rand.Read(r)
return r, err
}
profile.CommonName = "common name"
profile.Organization = "organization"
profile.Country = "country"
profile.OCSPURL = "ocsp"
profile.CRLURL = "crl"
profile.IssuerURL = "issuer"
cert, err := makeTemplate(ctx, profile, nil, 0)
test.AssertNotError(t, err, "makeTemplate failed when everything worked as expected")
test.AssertEquals(t, cert.Subject.CommonName, profile.CommonName)
test.AssertEquals(t, len(cert.Subject.Organization), 1)
test.AssertEquals(t, cert.Subject.Organization[0], profile.Organization)
test.AssertEquals(t, len(cert.Subject.Country), 1)
test.AssertEquals(t, cert.Subject.Country[0], profile.Country)
test.AssertEquals(t, len(cert.OCSPServer), 1)
test.AssertEquals(t, cert.OCSPServer[0], profile.OCSPURL)
test.AssertEquals(t, len(cert.CRLDistributionPoints), 1)
test.AssertEquals(t, cert.CRLDistributionPoints[0], profile.CRLURL)
test.AssertEquals(t, len(cert.IssuingCertificateURL), 1)
test.AssertEquals(t, cert.IssuingCertificateURL[0], profile.IssuerURL)
}
func TestVerifyProfile(t *testing.T) {
for _, tc := range []struct {
profile CertProfile
root bool
expectedErr string
}{
{
profile: CertProfile{},
root: false,
expectedErr: "NotBefore in profile is required",
},
{
profile: CertProfile{
NotBefore: "a",
},
root: false,
expectedErr: "NotAfter in profile is required",
},
{
profile: CertProfile{
NotBefore: "a",
NotAfter: "b",
},
root: false,
expectedErr: "SignatureAlgorithm in profile is required",
},
{
profile: CertProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
},
root: false,
expectedErr: "CommonName in profile is required",
},
{
profile: CertProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
},
root: false,
expectedErr: "Organization in profile is required",
},
{
profile: CertProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
},
root: false,
expectedErr: "Country in profile is required",
},
{
profile: CertProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
},
root: false,
expectedErr: "OCSPURL in profile is required for intermediates",
},
{
profile: CertProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
OCSPURL: "g",
},
root: false,
expectedErr: "CRLURL in profile is required for intermediates",
},
{
profile: CertProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
OCSPURL: "g",
CRLURL: "h",
},
root: false,
expectedErr: "IssuerURL in profile is required for intermediates",
},
{
profile: CertProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
},
root: true,
},
} {
err := verifyProfile(tc.profile, tc.root)
if err != nil {
if tc.expectedErr != err.Error() {
t.Fatalf("Expected %q, got %q", tc.expectedErr, err.Error())
}
} else if tc.expectedErr != "" {
t.Fatalf("verifyProfile didn't fail, expected %q", tc.expectedErr)
}
}
_, err = newSigner(ctx, 0, "label", []byte{255, 255})
test.AssertNotError(t, err, "newSigner failed when everything worked properly")
}

View File

@ -4,7 +4,6 @@ import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"errors"
"fmt"
"log"
@ -14,11 +13,11 @@ import (
"github.com/miekg/pkcs11"
)
var stringToCurve = map[string]*elliptic.CurveParams{
elliptic.P224().Params().Name: elliptic.P224().Params(),
elliptic.P256().Params().Name: elliptic.P256().Params(),
elliptic.P384().Params().Name: elliptic.P384().Params(),
elliptic.P521().Params().Name: elliptic.P521().Params(),
var stringToCurve = map[string]elliptic.Curve{
elliptic.P224().Params().Name: elliptic.P224(),
elliptic.P256().Params().Name: elliptic.P256(),
elliptic.P384().Params().Name: elliptic.P384(),
elliptic.P521().Params().Name: elliptic.P521(),
}
// curveToOIDDER maps the name of the curves to their DER encoded OIDs
@ -29,20 +28,11 @@ var curveToOIDDER = map[string][]byte{
elliptic.P521().Params().Name: []byte{6, 5, 43, 129, 4, 0, 35},
}
// oidDERToCurve maps the hex of the DER encoding of the various curve OIDs to
// the relevant curve parameters
var oidDERToCurve = map[string]*elliptic.CurveParams{
"06052B81040021": elliptic.P224().Params(),
"06082A8648CE3D030107": elliptic.P256().Params(),
"06052B81040022": elliptic.P384().Params(),
"06052B81040023": elliptic.P521().Params(),
}
var curveToHash = map[*elliptic.CurveParams]crypto.Hash{
elliptic.P224().Params(): crypto.SHA256,
elliptic.P256().Params(): crypto.SHA256,
elliptic.P384().Params(): crypto.SHA384,
elliptic.P521().Params(): crypto.SHA512,
var curveToHash = map[elliptic.Curve]crypto.Hash{
elliptic.P224(): crypto.SHA256,
elliptic.P256(): crypto.SHA256,
elliptic.P384(): crypto.SHA384,
elliptic.P521(): crypto.SHA512,
}
var hashToString = map[crypto.Hash]string{
@ -54,8 +44,8 @@ var hashToString = map[crypto.Hash]string{
// ecArgs constructs the private and public key template attributes sent to the
// device and specifies which mechanism should be used. curve determines which
// type of key should be generated.
func ecArgs(label string, curve *elliptic.CurveParams, keyID []byte) generateArgs {
encodedCurve := curveToOIDDER[curve.Name]
func ecArgs(label string, curve elliptic.Curve, keyID []byte) generateArgs {
encodedCurve := curveToOIDDER[curve.Params().Name]
log.Printf("\tEncoded curve parameters for %s: %X\n", curve.Params().Name, encodedCurve)
return generateArgs{
mechanism: []*pkcs11.Mechanism{
@ -89,7 +79,7 @@ func ecPub(
ctx pkcs11helpers.PKCtx,
session pkcs11.SessionHandle,
object pkcs11.ObjectHandle,
expectedCurve *elliptic.CurveParams,
expectedCurve elliptic.Curve,
) (*ecdsa.PublicKey, error) {
pubKey, err := pkcs11helpers.GetECDSAPublicKey(ctx, session, object)
if err != nil {
@ -108,16 +98,17 @@ func ecPub(
// a nonce generated on the device and verifying the returned signature using the
// public key.
func ecVerify(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, object pkcs11.ObjectHandle, pub *ecdsa.PublicKey) error {
nonce, err := getRandomBytes(ctx, session)
nonce := make([]byte, 4)
_, err := newRandReader(ctx, session).Read(nonce)
if err != nil {
return fmt.Errorf("failed to construct nonce: %s", err)
}
log.Printf("\tConstructed nonce: %d (%X)\n", big.NewInt(0).SetBytes(nonce), nonce)
hashFunc := curveToHash[pub.Curve.Params()].New()
hashFunc := curveToHash[pub.Curve].New()
hashFunc.Write(nonce)
digest := hashFunc.Sum(nil)
log.Printf("\tMessage %s hash: %X\n", hashToString[curveToHash[pub.Curve.Params()]], digest)
signature, err := pkcs11helpers.Sign(ctx, session, object, pkcs11helpers.ECDSAKey, digest, curveToHash[pub.Curve.Params()])
log.Printf("\tMessage %s hash: %X\n", hashToString[curveToHash[pub.Curve]], digest)
signature, err := pkcs11helpers.Sign(ctx, session, object, pkcs11helpers.ECDSAKey, digest, curveToHash[pub.Curve])
if err != nil {
return err
}
@ -133,35 +124,36 @@ func ecVerify(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, object pkcs
// ecGenerate is used to generate and verify a ECDSA key pair of the type
// specified by curveStr and with the provided label. It returns the public
// part of the generated key pair as a ecdsa.PublicKey.
func ecGenerate(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, label, curveStr string) (*ecdsa.PublicKey, error) {
// part of the generated key pair as a ecdsa.PublicKey and the random key ID
// that the HSM uses to identify the key pair.
func ecGenerate(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, label, curveStr string) (*ecdsa.PublicKey, []byte, error) {
curve, present := stringToCurve[curveStr]
if !present {
return nil, fmt.Errorf("curve %q not supported", curveStr)
return nil, nil, fmt.Errorf("curve %q not supported", curveStr)
}
keyID := make([]byte, 4)
_, err := rand.Read(keyID)
_, err := newRandReader(ctx, session).Read(keyID)
if err != nil {
return nil, err
return nil, nil, err
}
log.Printf("Generating ECDSA key with curve %s and ID %x\n", curveStr, keyID)
args := ecArgs(label, curve, keyID)
pub, priv, err := ctx.GenerateKeyPair(session, args.mechanism, args.publicAttrs, args.privateAttrs)
if err != nil {
return nil, err
return nil, nil, err
}
log.Println("Key generated")
log.Println("Extracting public key")
pk, err := ecPub(ctx, session, pub, curve)
if err != nil {
return nil, err
return nil, nil, err
}
log.Println("Extracted public key")
log.Println("Verifying public key")
err = ecVerify(ctx, session, priv, pk)
if err != nil {
return nil, err
return nil, nil, err
}
log.Println("Key verified")
return pk, nil
return pk, keyID, nil
}

160
cmd/ceremony/ecdsa_test.go Normal file
View File

@ -0,0 +1,160 @@
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"errors"
"testing"
"github.com/letsencrypt/boulder/pkcs11helpers"
"github.com/letsencrypt/boulder/test"
"github.com/miekg/pkcs11"
)
func TestECPub(t *testing.T) {
ctx := pkcs11helpers.MockCtx{}
// test we fail when pkcs11helpers.GetECDSAPublicKey fails
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return nil, errors.New("bad!")
}
_, err := ecPub(ctx, 0, 0, elliptic.P256())
test.AssertError(t, err, "ecPub didn't fail with non-matching curve")
test.AssertEquals(t, err.Error(), "Failed to retrieve key attributes: bad!")
// test we fail to construct key with non-matching curve
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 5, 43, 129, 4, 0, 33}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{4, 217, 225, 246, 210, 153, 134, 246, 104, 95, 79, 122, 206, 135, 241, 37, 114, 199, 87, 56, 167, 83, 56, 136, 174, 6, 145, 97, 239, 221, 49, 67, 148, 13, 126, 65, 90, 208, 195, 193, 171, 105, 40, 98, 132, 124, 30, 189, 215, 197, 178, 226, 166, 238, 240, 57, 215}),
}, nil
}
_, err = ecPub(ctx, 0, 0, elliptic.P256())
test.AssertError(t, err, "ecPub didn't fail with non-matching curve")
}
func TestECVerify(t *testing.T) {
ctx := pkcs11helpers.MockCtx{}
// test GenerateRandom failing
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return nil, errors.New("yup")
}
err := ecVerify(ctx, 0, 0, nil)
test.AssertError(t, err, "ecVerify didn't fail on GenerateRandom error")
// test SignInit failing
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return []byte{1, 2, 3, 4}, nil
}
ctx.SignInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error {
return errors.New("yup")
}
err = ecVerify(ctx, 0, 0, &ecdsa.PublicKey{Curve: elliptic.P256()})
test.AssertError(t, err, "ecVerify didn't fail on SignInit error")
// test Sign failing
ctx.SignInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error {
return nil
}
ctx.SignFunc = func(pkcs11.SessionHandle, []byte) ([]byte, error) {
return nil, errors.New("yup")
}
err = ecVerify(ctx, 0, 0, &ecdsa.PublicKey{Curve: elliptic.P256()})
test.AssertError(t, err, "ecVerify didn't fail on Sign error")
// test signature verification failing
ctx.SignFunc = func(pkcs11.SessionHandle, []byte) ([]byte, error) {
return []byte{1, 2, 3}, nil
}
tk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
test.AssertNotError(t, err, "ecdsa.GenerateKey failed")
err = ecVerify(ctx, 0, 0, &tk.PublicKey)
test.AssertError(t, err, "ecVerify didn't fail on signature verification error")
// test we don't fail with valid signature
ctx.SignFunc = func(_ pkcs11.SessionHandle, msg []byte) ([]byte, error) {
return ecPKCS11Sign(tk, msg)
}
err = ecVerify(ctx, 0, 0, &tk.PublicKey)
test.AssertNotError(t, err, "ecVerify failed with a valid signature")
}
func TestECGenerate(t *testing.T) {
ctx := pkcs11helpers.MockCtx{}
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return []byte{1, 2, 3}, nil
}
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
test.AssertNotError(t, err, "Failed to generate a ECDSA test key")
// Test ecGenerate fails with unknown curve
_, _, err = ecGenerate(ctx, 0, "", "bad-curve")
test.AssertError(t, err, "ecGenerate accepted unknown curve")
// Test ecGenerate fails when GenerateKeyPair fails
ctx.GenerateKeyPairFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, []*pkcs11.Attribute, []*pkcs11.Attribute) (pkcs11.ObjectHandle, pkcs11.ObjectHandle, error) {
return 0, 0, errors.New("bad")
}
_, _, err = ecGenerate(ctx, 0, "", "P-256")
test.AssertError(t, err, "ecGenerate didn't fail on GenerateKeyPair error")
// Test ecGenerate fails when ecPub fails
ctx.GenerateKeyPairFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, []*pkcs11.Attribute, []*pkcs11.Attribute) (pkcs11.ObjectHandle, pkcs11.ObjectHandle, error) {
return 0, 0, nil
}
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return nil, errors.New("bad")
}
_, _, err = ecGenerate(ctx, 0, "", "P-256")
test.AssertError(t, err, "ecGenerate didn't fail on ecPub error")
// Test ecGenerate fails when ecVerify fails
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 8, 42, 134, 72, 206, 61, 3, 1, 7}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, elliptic.Marshal(elliptic.P256(), priv.X, priv.Y)),
}, nil
}
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return nil, errors.New("yup")
}
_, _, err = ecGenerate(ctx, 0, "", "P-256")
test.AssertError(t, err, "ecGenerate didn't fail on ecVerify error")
// Test ecGenerate doesn't fail when everything works
ctx.SignInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error {
return nil
}
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return []byte{1, 2, 3}, nil
}
ctx.SignFunc = func(_ pkcs11.SessionHandle, msg []byte) ([]byte, error) {
return ecPKCS11Sign(priv, msg)
}
_, _, err = ecGenerate(ctx, 0, "", "P-256")
test.AssertNotError(t, err, "ecGenerate didn't succeed when everything worked as expected")
}
func ecPKCS11Sign(priv *ecdsa.PrivateKey, msg []byte) ([]byte, error) {
r, s, err := ecdsa.Sign(rand.Reader, priv, msg[:])
if err != nil {
return nil, err
}
rBytes := r.Bytes()
sBytes := s.Bytes()
// http://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/os/pkcs11-curr-v2.40-os.html
// Section 2.3.1: EC Signatures
// "If r and s have different octet length, the shorter of both must be padded with
// leading zero octets such that both have the same octet length."
switch {
case len(rBytes) < len(sBytes):
padding := make([]byte, len(sBytes)-len(rBytes))
rBytes = append(padding, rBytes...)
case len(rBytes) > len(sBytes):
padding := make([]byte, len(rBytes)-len(sBytes))
sBytes = append(padding, sBytes...)
}
return append(rBytes, sBytes...), nil
}

85
cmd/ceremony/key.go Normal file
View File

@ -0,0 +1,85 @@
package main
import (
"crypto"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
"github.com/letsencrypt/boulder/pkcs11helpers"
"github.com/miekg/pkcs11"
)
type hsmRandReader struct {
ctx pkcs11helpers.PKCtx
session pkcs11.SessionHandle
}
func newRandReader(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle) *hsmRandReader {
return &hsmRandReader{
ctx: ctx,
session: session,
}
}
func (hrr hsmRandReader) Read(p []byte) (n int, err error) {
r, err := hrr.ctx.GenerateRandom(hrr.session, len(p))
if err != nil {
return 0, err
}
copy(p[:], r)
return len(r), nil
}
type generateArgs struct {
mechanism []*pkcs11.Mechanism
privateAttrs []*pkcs11.Attribute
publicAttrs []*pkcs11.Attribute
}
const (
rsaExp = 65537
)
// keyInfo is a struct used to pass around information about the public key
// associated with the generated private key. der contains the DER encoding
// of the SubjectPublicKeyInfo structure for the public key. id contains the
// HSM key pair object ID.
type keyInfo struct {
key crypto.PublicKey
der []byte
id []byte
}
func generateKey(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, label string, outputPath string, config keyGenConfig) (*keyInfo, error) {
var pubKey crypto.PublicKey
var keyID []byte
var err error
switch config.Type {
case "rsa":
pubKey, keyID, err = rsaGenerate(ctx, session, label, config.RSAModLength, rsaExp)
if err != nil {
return nil, fmt.Errorf("failed to generate RSA key pair: %s", err)
}
case "ecdsa":
pubKey, keyID, err = ecGenerate(ctx, session, label, config.ECDSACurve)
if err != nil {
return nil, fmt.Errorf("failed to generate ECDSA key pair: %s", err)
}
}
der, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return nil, fmt.Errorf("Failed to marshal public key: %s", err)
}
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der})
log.Printf("Public key PEM:\n%s\n", pemBytes)
if err := ioutil.WriteFile(outputPath, pemBytes, 0644); err != nil {
return nil, fmt.Errorf("Failed to write public key to %q: %s", outputPath, err)
}
log.Printf("Public key written to %q\n", outputPath)
return &keyInfo{key: pubKey, der: der, id: keyID}, nil
}

87
cmd/ceremony/key_test.go Normal file
View File

@ -0,0 +1,87 @@
package main
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"math/big"
"os"
"path"
"testing"
"github.com/letsencrypt/boulder/pkcs11helpers"
"github.com/letsencrypt/boulder/test"
"github.com/miekg/pkcs11"
)
func TestGenerateKey(t *testing.T) {
tmp, err := ioutil.TempDir("", "ceremony-testing")
test.AssertNotError(t, err, "Failed to create temporary directory")
defer os.RemoveAll(tmp)
// RSA
ctx := pkcs11helpers.MockCtx{}
rsaPriv, err := rsa.GenerateKey(rand.Reader, 1024)
test.AssertNotError(t, err, "Failed to generate a test RSA key")
ctx.GenerateKeyPairFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, []*pkcs11.Attribute, []*pkcs11.Attribute) (pkcs11.ObjectHandle, pkcs11.ObjectHandle, error) {
return 0, 0, nil
}
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, big.NewInt(int64(rsaPriv.E)).Bytes()),
pkcs11.NewAttribute(pkcs11.CKA_MODULUS, rsaPriv.N.Bytes()),
}, nil
}
ctx.SignInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error {
return nil
}
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return []byte{1, 2, 3}, nil
}
ctx.SignFunc = func(_ pkcs11.SessionHandle, msg []byte) ([]byte, error) {
// Chop of the hash identifier and feed back into rsa.SignPKCS1v15
return rsa.SignPKCS1v15(rand.Reader, rsaPriv, crypto.SHA256, msg[19:])
}
keyPath := path.Join(tmp, "test-rsa-key.pem")
keyInfo, err := generateKey(ctx, 0, "", keyPath, keyGenConfig{
Type: "rsa",
RSAModLength: 1024,
})
test.AssertNotError(t, err, "Failed to generate RSA key")
diskKeyBytes, err := ioutil.ReadFile(keyPath)
test.AssertNotError(t, err, "Failed to load key from disk")
block, _ := pem.Decode(diskKeyBytes)
diskKey, err := x509.ParsePKIXPublicKey(block.Bytes)
test.AssertNotError(t, err, "Failed to parse disk key")
test.AssertDeepEquals(t, diskKey, keyInfo.key)
// EC
ecPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
test.AssertNotError(t, err, "Failed to generate a ECDSA test key")
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 8, 42, 134, 72, 206, 61, 3, 1, 7}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, elliptic.Marshal(elliptic.P256(), ecPriv.X, ecPriv.Y)),
}, nil
}
ctx.SignFunc = func(_ pkcs11.SessionHandle, msg []byte) ([]byte, error) {
return ecPKCS11Sign(ecPriv, msg)
}
keyPath = path.Join(tmp, "test-ecdsa-key.pem")
keyInfo, err = generateKey(ctx, 0, "", keyPath, keyGenConfig{
Type: "ecdsa",
ECDSACurve: "P-256",
})
test.AssertNotError(t, err, "Failed to generate ECDSA key")
diskKeyBytes, err = ioutil.ReadFile(keyPath)
test.AssertNotError(t, err, "Failed to load key from disk")
block, _ = pem.Decode(diskKeyBytes)
diskKey, err = x509.ParsePKIXPublicKey(block.Bytes)
test.AssertNotError(t, err, "Failed to parse disk key")
test.AssertDeepEquals(t, diskKey, keyInfo.key)
}

370
cmd/ceremony/main.go Normal file
View File

@ -0,0 +1,370 @@
package main
import (
"crypto"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"github.com/letsencrypt/boulder/pkcs11helpers"
"gopkg.in/yaml.v2"
)
type keyGenConfig struct {
Type string `yaml:"type"`
RSAModLength uint `yaml:"rsa-mod-length"`
ECDSACurve string `yaml:"ecdsa-curve"`
}
var allowedCurves = map[string]bool{
"P-224": true,
"P-256": true,
"P-384": true,
"P-521": true,
}
func (kgc keyGenConfig) validate() error {
if kgc.Type == "" {
return errors.New("key.type is required")
}
if kgc.Type != "rsa" && kgc.Type != "ecdsa" {
return errors.New("key.type can only be 'rsa' or 'ecdsa'")
}
if kgc.Type == "rsa" && (kgc.RSAModLength != 2048 && kgc.RSAModLength != 4096) {
return errors.New("key.rsa-mod-length can only be 2048 or 4096")
}
if kgc.Type == "rsa" && kgc.ECDSACurve != "" {
return errors.New("if key.type = 'rsa' then key.ecdsa-curve is not used")
}
if kgc.Type == "ecdsa" && !allowedCurves[kgc.ECDSACurve] {
return errors.New("key.ecdsa-curve can only be 'P-224', 'P-256', 'P-384', or 'P-521'")
}
if kgc.Type == "ecdsa" && kgc.RSAModLength != 0 {
return errors.New("if key.type = 'ecdsa' then key.rsa-mod-length is not used")
}
return nil
}
type rootConfig struct {
CeremonyType string `yaml:"ceremony-type"`
PKCS11 struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
StoreSlot uint `yaml:"store-key-in-slot"`
StoreLabel string `yaml:"store-key-with-label"`
} `yaml:"pkcs11"`
Key keyGenConfig `yaml:"key"`
Outputs struct {
PublicKeyPath string `yaml:"public-key-path"`
CertificatePath string `yaml:"certificate-path"`
} `yaml:"outputs"`
CertProfile certProfile `yaml:"certificate-profile"`
}
func (rc rootConfig) validate() error {
// PKCS11 fields
if rc.PKCS11.Module == "" {
return errors.New("pkcs11.module is required")
}
// key-slot cannot be tested because 0 is a valid slot
if rc.PKCS11.StoreLabel == "" {
return errors.New("pkcs11.store-key-with-label is required")
}
// Key gen fields
if err := rc.Key.validate(); err != nil {
return err
}
// Output fields
if rc.Outputs.PublicKeyPath == "" {
return errors.New("outputs.public-key-path is required")
}
if rc.Outputs.CertificatePath == "" {
return errors.New("outputs.certificate-path is required")
}
// Certificate profile
if err := rc.CertProfile.verifyProfile(true); err != nil {
return err
}
return nil
}
type intermediateConfig struct {
CeremonyType string `yaml:"ceremony-type"`
PKCS11 struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
SigningSlot uint `yaml:"signing-key-slot"`
SigningLabel string `yaml:"signing-key-label"`
SigningKeyID string `yaml:"signing-key-id"`
} `yaml:"pkcs11"`
Inputs struct {
PublicKeyPath string `yaml:"public-key-path"`
IssuerCertificatePath string `yaml:"issuer-certificate-path"`
} `yaml:"inputs"`
Outputs struct {
CertificatePath string `yaml:"certificate-path"`
} `yaml:"outputs"`
CertProfile certProfile `yaml:"certificate-profile"`
}
func (ic intermediateConfig) validate() error {
// PKCS11 fields
if ic.PKCS11.Module == "" {
return errors.New("pkcs11.module is required")
}
// key-slot cannot be tested because 0 is a valid slot
if ic.PKCS11.SigningLabel == "" {
return errors.New("pkcs11.signing-key-label is required")
}
if ic.PKCS11.SigningKeyID == "" {
return errors.New("pkcs11.signing-key-id is required")
}
// Input fields
if ic.Inputs.PublicKeyPath == "" {
return errors.New("inputs.public-key-path is required")
}
if ic.Inputs.IssuerCertificatePath == "" {
return errors.New("inputs.issuer-certificate is required")
}
// Output fields
if ic.Outputs.CertificatePath == "" {
return errors.New("outputs.certificate-path is required")
}
// Certificate profile
if err := ic.CertProfile.verifyProfile(false); err != nil {
return err
}
return nil
}
type keyConfig struct {
CeremonyType string `yaml:"ceremony-type"`
PKCS11 struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
StoreSlot uint `yaml:"store-key-in-slot"`
StoreLabel string `yaml:"store-key-with-label"`
} `yaml:"pkcs11"`
Key keyGenConfig `yaml:"key"`
Outputs struct {
PublicKeyPath string `yaml:"public-key-path"`
} `yaml:"outputs"`
}
func (kc keyConfig) validate() error {
// PKCS11 fields
if kc.PKCS11.Module == "" {
return errors.New("pkcs11.module is required")
}
// key-slot cannot be tested because 0 is a valid slot
if kc.PKCS11.StoreLabel == "" {
return errors.New("pkcs11.store-key-with-label is required")
}
// Key gen fields
if err := kc.Key.validate(); err != nil {
return err
}
// Output fields
if kc.Outputs.PublicKeyPath == "" {
return errors.New("outputs.public-key-path is required")
}
return nil
}
func signAndWriteCert(tbs, issuer *x509.Certificate, subjectPubKey crypto.PublicKey, signer crypto.Signer, certPath string) error {
// x509.CreateCertificate uses a io.Reader here for signing methods that require
// a source of randomness. Since PKCS#11 based signing generates needed randomness
// at the HSM we don't need to pass a real reader. Instead of passing a nil reader
// we use one that always returns errors in case the internal usage of this reader
// changes.
certBytes, err := x509.CreateCertificate(&failReader{}, tbs, issuer, subjectPubKey, signer)
if err != nil {
return fmt.Errorf("failed to create certificate: %s", err)
}
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
log.Printf("Signed certificate PEM:\n%s", pemBytes)
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return fmt.Errorf("failed to parse signed certificate: %s", err)
}
if tbs == issuer {
// If cert is self-signed we need to populate the issuer subject key to
// verify the signature
issuer.PublicKey = cert.PublicKey
issuer.PublicKeyAlgorithm = cert.PublicKeyAlgorithm
}
if err := cert.CheckSignatureFrom(issuer); err != nil {
return fmt.Errorf("failed to verify certificate signature: %s", err)
}
log.Printf("Certificate PEM:\n%s", pemBytes)
if err := ioutil.WriteFile(certPath, pemBytes, 0644); err != nil {
return fmt.Errorf("failed to write certificate to %q: %s", certPath, err)
}
log.Printf("Certificate written to %q\n", certPath)
return nil
}
func rootCeremony(configBytes []byte) error {
var config rootConfig
err := yaml.UnmarshalStrict(configBytes, &config)
if err != nil {
return fmt.Errorf("failed to parse config: %s", err)
}
ctx, session, err := pkcs11helpers.Initialize(config.PKCS11.Module, config.PKCS11.StoreSlot, config.PKCS11.PIN)
if err != nil {
return fmt.Errorf("failed to setup session and PKCS#11 context for slot %d: %s", config.PKCS11.StoreSlot, err)
}
log.Printf("Opened PKCS#11 session for slot %d\n", config.PKCS11.StoreSlot)
keyInfo, err := generateKey(ctx, session, config.PKCS11.StoreLabel, config.Outputs.PublicKeyPath, config.Key)
if err != nil {
return err
}
signer, err := newSigner(ctx, session, config.PKCS11.StoreLabel, keyInfo.id)
if err != nil {
return fmt.Errorf("failed to retrieve signer: %s", err)
}
template, err := makeTemplate(newRandReader(ctx, session), &config.CertProfile, keyInfo.der)
if err != nil {
return fmt.Errorf("failed to create certificate profile: %s", err)
}
err = signAndWriteCert(template, template, keyInfo.key, signer, config.Outputs.CertificatePath)
if err != nil {
return err
}
return nil
}
func intermediateCeremony(configBytes []byte) error {
var config intermediateConfig
err := yaml.UnmarshalStrict(configBytes, &config)
if err != nil {
return fmt.Errorf("failed to parse config: %s", err)
}
ctx, session, err := pkcs11helpers.Initialize(config.PKCS11.Module, config.PKCS11.SigningSlot, config.PKCS11.PIN)
if err != nil {
return fmt.Errorf("failed to setup session and PKCS#11 context for slot %d: %s", config.PKCS11.SigningSlot, err)
}
log.Printf("Opened PKCS#11 session for slot %d\n", config.PKCS11.SigningSlot)
keyID, err := hex.DecodeString(config.PKCS11.SigningKeyID)
if err != nil {
return fmt.Errorf("failed to decode key-id: %s", err)
}
signer, err := newSigner(ctx, session, config.PKCS11.SigningLabel, keyID)
if err != nil {
return fmt.Errorf("failed to retrieve private key handle: %s", err)
}
log.Println("Retrieved private key handle")
pubPEMBytes, err := ioutil.ReadFile(config.Inputs.PublicKeyPath)
if err != nil {
return fmt.Errorf("failed to read public key %q: %s", config.Inputs.PublicKeyPath, err)
}
pubPEM, _ := pem.Decode(pubPEMBytes)
if pubPEM == nil {
return fmt.Errorf("failed to parse public key")
}
pub, err := x509.ParsePKIXPublicKey(pubPEM.Bytes)
if err != nil {
return fmt.Errorf("failed to parse public key: %s", err)
}
issuerPEMBytes, err := ioutil.ReadFile(config.Inputs.IssuerCertificatePath)
if err != nil {
return fmt.Errorf("failed to read issuer certificate %q: %s", config.Inputs.IssuerCertificatePath, err)
}
issuerPEM, _ := pem.Decode(issuerPEMBytes)
if issuerPEM == nil {
return fmt.Errorf("failed to parse issuer certificate PEM")
}
issuer, err := x509.ParseCertificate(issuerPEM.Bytes)
if err != nil {
return fmt.Errorf("failed to parse issuer certificate: %s", err)
}
template, err := makeTemplate(newRandReader(ctx, session), &config.CertProfile, pubPEM.Bytes)
if err != nil {
return fmt.Errorf("failed to create certificate profile: %s", err)
}
template.AuthorityKeyId = issuer.SubjectKeyId
err = signAndWriteCert(template, issuer, pub, signer, config.Outputs.CertificatePath)
if err != nil {
return err
}
return nil
}
func keyCeremony(configBytes []byte) error {
var config keyConfig
err := yaml.UnmarshalStrict(configBytes, &config)
if err != nil {
return fmt.Errorf("failed to parse config: %s", err)
}
ctx, session, err := pkcs11helpers.Initialize(config.PKCS11.Module, config.PKCS11.StoreSlot, config.PKCS11.PIN)
if err != nil {
return fmt.Errorf("failed to setup session and PKCS#11 context for slot %d: %s", config.PKCS11.StoreSlot, err)
}
log.Printf("Opened PKCS#11 session for slot %d\n", config.PKCS11.StoreSlot)
if _, err = generateKey(ctx, session, config.PKCS11.StoreLabel, config.Outputs.PublicKeyPath, config.Key); err != nil {
return err
}
return nil
}
func main() {
configPath := flag.String("config", "", "Path to ceremony configuration file")
flag.Parse()
if *configPath == "" {
log.Fatal("--config is required")
}
configBytes, err := ioutil.ReadFile(*configPath)
if err != nil {
log.Fatalf("Failed to read config file: %s", err)
}
var ct struct {
CeremonyType string `yaml:"ceremony-type"`
}
err = yaml.Unmarshal(configBytes, &ct)
if err != nil {
log.Fatalf("Failed to parse config: %s", err)
}
switch ct.CeremonyType {
case "root":
err = rootCeremony(configBytes)
if err != nil {
log.Fatalf("root ceremony failed: %s", err)
}
case "intermediate":
err = intermediateCeremony(configBytes)
if err != nil {
log.Fatalf("intermediate ceremony failed: %s", err)
}
case "key":
err = keyCeremony(configBytes)
if err != nil {
log.Fatalf("key ceremony failed: %s", err)
}
}
}

520
cmd/ceremony/main_test.go Normal file
View File

@ -0,0 +1,520 @@
package main
import "testing"
func TestKeyGenConfigValidate(t *testing.T) {
cases := []struct {
name string
config keyGenConfig
expectedError string
}{
{
name: "no key.type",
config: keyGenConfig{},
expectedError: "key.type is required",
},
{
name: "bad key.type",
config: keyGenConfig{
Type: "doop",
},
expectedError: "key.type can only be 'rsa' or 'ecdsa'",
},
{
name: "bad key.rsa-mod-length",
config: keyGenConfig{
Type: "rsa",
RSAModLength: 1337,
},
expectedError: "key.rsa-mod-length can only be 2048 or 4096",
},
{
name: "key.type is rsa but key.ecdsa-curve is present",
config: keyGenConfig{
Type: "rsa",
RSAModLength: 2048,
ECDSACurve: "bad",
},
expectedError: "if key.type = 'rsa' then key.ecdsa-curve is not used",
},
{
name: "bad key.ecdsa-curve",
config: keyGenConfig{
Type: "ecdsa",
ECDSACurve: "bad",
},
expectedError: "key.ecdsa-curve can only be 'P-224', 'P-256', 'P-384', or 'P-521'",
},
{
name: "key.type is ecdsa but key.rsa-mod-length is present",
config: keyGenConfig{
Type: "ecdsa",
RSAModLength: 2048,
ECDSACurve: "P-256",
},
expectedError: "if key.type = 'ecdsa' then key.rsa-mod-length is not used",
},
{
name: "good rsa config",
config: keyGenConfig{
Type: "rsa",
RSAModLength: 2048,
},
},
{
name: "good ecdsa config",
config: keyGenConfig{
Type: "ecdsa",
ECDSACurve: "P-256",
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := tc.config.validate()
if err != nil && err.Error() != tc.expectedError {
t.Fatalf("Unexpected error, wanted: %q, got: %q", tc.expectedError, err)
} else if err == nil && tc.expectedError != "" {
t.Fatalf("validate didn't fail, wanted: %q", err)
}
})
}
}
func TestRootConfigValidate(t *testing.T) {
cases := []struct {
name string
config rootConfig
expectedError string
}{
{
name: "no pkcs11.module",
config: rootConfig{},
expectedError: "pkcs11.module is required",
},
{
name: "no pkcs11.store-key-with-label",
config: rootConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
StoreSlot uint `yaml:"store-key-in-slot"`
StoreLabel string `yaml:"store-key-with-label"`
}{
Module: "module",
},
},
expectedError: "pkcs11.store-key-with-label is required",
},
{
name: "bad key fields",
config: rootConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
StoreSlot uint `yaml:"store-key-in-slot"`
StoreLabel string `yaml:"store-key-with-label"`
}{
Module: "module",
StoreLabel: "label",
},
},
expectedError: "key.type is required",
},
{
name: "no outputs.public-key-path",
config: rootConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
StoreSlot uint `yaml:"store-key-in-slot"`
StoreLabel string `yaml:"store-key-with-label"`
}{
Module: "module",
StoreLabel: "label",
},
Key: keyGenConfig{
Type: "rsa",
RSAModLength: 2048,
},
},
expectedError: "outputs.public-key-path is required",
},
{
name: "no outputs.certificate-path",
config: rootConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
StoreSlot uint `yaml:"store-key-in-slot"`
StoreLabel string `yaml:"store-key-with-label"`
}{
Module: "module",
StoreLabel: "label",
},
Key: keyGenConfig{
Type: "rsa",
RSAModLength: 2048,
},
Outputs: struct {
PublicKeyPath string `yaml:"public-key-path"`
CertificatePath string `yaml:"certificate-path"`
}{
PublicKeyPath: "path",
},
},
expectedError: "outputs.certificate-path is required",
},
{
name: "bad certificate-profile",
config: rootConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
StoreSlot uint `yaml:"store-key-in-slot"`
StoreLabel string `yaml:"store-key-with-label"`
}{
Module: "module",
StoreLabel: "label",
},
Key: keyGenConfig{
Type: "rsa",
RSAModLength: 2048,
},
Outputs: struct {
PublicKeyPath string `yaml:"public-key-path"`
CertificatePath string `yaml:"certificate-path"`
}{
PublicKeyPath: "path",
CertificatePath: "path",
},
},
expectedError: "not-before is required",
},
{
name: "good config",
config: rootConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
StoreSlot uint `yaml:"store-key-in-slot"`
StoreLabel string `yaml:"store-key-with-label"`
}{
Module: "module",
StoreLabel: "label",
},
Key: keyGenConfig{
Type: "rsa",
RSAModLength: 2048,
},
Outputs: struct {
PublicKeyPath string `yaml:"public-key-path"`
CertificatePath string `yaml:"certificate-path"`
}{
PublicKeyPath: "path",
CertificatePath: "path",
},
CertProfile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := tc.config.validate()
if err != nil && err.Error() != tc.expectedError {
t.Fatalf("Unexpected error, wanted: %q, got: %q", tc.expectedError, err)
} else if err == nil && tc.expectedError != "" {
t.Fatalf("validate didn't fail, wanted: %q", err)
}
})
}
}
func TestIntermediateConfigValidate(t *testing.T) {
cases := []struct {
name string
config intermediateConfig
expectedError string
}{
{
name: "no pkcs11.module",
config: intermediateConfig{},
expectedError: "pkcs11.module is required",
},
{
name: "no pkcs11.signing-key-label",
config: intermediateConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
SigningSlot uint `yaml:"signing-key-slot"`
SigningLabel string `yaml:"signing-key-label"`
SigningKeyID string `yaml:"signing-key-id"`
}{
Module: "module",
},
},
expectedError: "pkcs11.signing-key-label is required",
},
{
name: "no pkcs11.key-id",
config: intermediateConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
SigningSlot uint `yaml:"signing-key-slot"`
SigningLabel string `yaml:"signing-key-label"`
SigningKeyID string `yaml:"signing-key-id"`
}{
Module: "module",
SigningLabel: "label",
},
},
expectedError: "pkcs11.signing-key-id is required",
},
{
name: "no inputs.public-key-path",
config: intermediateConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
SigningSlot uint `yaml:"signing-key-slot"`
SigningLabel string `yaml:"signing-key-label"`
SigningKeyID string `yaml:"signing-key-id"`
}{
Module: "module",
SigningLabel: "label",
SigningKeyID: "id",
},
},
expectedError: "inputs.public-key-path is required",
},
{
name: "no inputs.issuer-certificate-path",
config: intermediateConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
SigningSlot uint `yaml:"signing-key-slot"`
SigningLabel string `yaml:"signing-key-label"`
SigningKeyID string `yaml:"signing-key-id"`
}{
Module: "module",
SigningLabel: "label",
SigningKeyID: "id",
},
Inputs: struct {
PublicKeyPath string `yaml:"public-key-path"`
IssuerCertificatePath string `yaml:"issuer-certificate-path"`
}{
PublicKeyPath: "path",
},
},
expectedError: "inputs.issuer-certificate is required",
},
{
name: "no outputs.certificate-path",
config: intermediateConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
SigningSlot uint `yaml:"signing-key-slot"`
SigningLabel string `yaml:"signing-key-label"`
SigningKeyID string `yaml:"signing-key-id"`
}{
Module: "module",
SigningLabel: "label",
SigningKeyID: "id",
},
Inputs: struct {
PublicKeyPath string `yaml:"public-key-path"`
IssuerCertificatePath string `yaml:"issuer-certificate-path"`
}{
PublicKeyPath: "path",
IssuerCertificatePath: "path",
},
},
expectedError: "outputs.certificate-path is required",
},
{
name: "bad certificate-profile",
config: intermediateConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
SigningSlot uint `yaml:"signing-key-slot"`
SigningLabel string `yaml:"signing-key-label"`
SigningKeyID string `yaml:"signing-key-id"`
}{
Module: "module",
SigningLabel: "label",
SigningKeyID: "id",
},
Inputs: struct {
PublicKeyPath string `yaml:"public-key-path"`
IssuerCertificatePath string `yaml:"issuer-certificate-path"`
}{
PublicKeyPath: "path",
IssuerCertificatePath: "path",
},
Outputs: struct {
CertificatePath string `yaml:"certificate-path"`
}{
CertificatePath: "path",
},
},
expectedError: "not-before is required",
},
{
name: "good config",
config: intermediateConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
SigningSlot uint `yaml:"signing-key-slot"`
SigningLabel string `yaml:"signing-key-label"`
SigningKeyID string `yaml:"signing-key-id"`
}{
Module: "module",
SigningLabel: "label",
SigningKeyID: "id",
},
Inputs: struct {
PublicKeyPath string `yaml:"public-key-path"`
IssuerCertificatePath string `yaml:"issuer-certificate-path"`
}{
PublicKeyPath: "path",
IssuerCertificatePath: "path",
},
Outputs: struct {
CertificatePath string `yaml:"certificate-path"`
}{
CertificatePath: "path",
},
CertProfile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
OCSPURL: "g",
CRLURL: "h",
IssuerURL: "i",
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := tc.config.validate()
if err != nil && err.Error() != tc.expectedError {
t.Fatalf("Unexpected error, wanted: %q, got: %q", tc.expectedError, err)
} else if err == nil && tc.expectedError != "" {
t.Fatalf("validate didn't fail, wanted: %q", err)
}
})
}
}
func TestKeyConfigValidate(t *testing.T) {
cases := []struct {
name string
config keyConfig
expectedError string
}{
{
name: "no pkcs11.module",
config: keyConfig{},
expectedError: "pkcs11.module is required",
},
{
name: "no pkcs11.store-key-with-label",
config: keyConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
StoreSlot uint `yaml:"store-key-in-slot"`
StoreLabel string `yaml:"store-key-with-label"`
}{
Module: "module",
},
},
expectedError: "pkcs11.store-key-with-label is required",
},
{
name: "bad key fields",
config: keyConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
StoreSlot uint `yaml:"store-key-in-slot"`
StoreLabel string `yaml:"store-key-with-label"`
}{
Module: "module",
StoreLabel: "label",
},
},
expectedError: "key.type is required",
},
{
name: "no outputs.public-key-path",
config: keyConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
StoreSlot uint `yaml:"store-key-in-slot"`
StoreLabel string `yaml:"store-key-with-label"`
}{
Module: "module",
StoreLabel: "label",
},
Key: keyGenConfig{
Type: "rsa",
RSAModLength: 2048,
},
},
expectedError: "outputs.public-key-path is required",
},
{
name: "good config",
config: keyConfig{
PKCS11: struct {
Module string `yaml:"module"`
PIN string `yaml:"pin"`
StoreSlot uint `yaml:"store-key-in-slot"`
StoreLabel string `yaml:"store-key-with-label"`
}{
Module: "module",
StoreLabel: "label",
},
Key: keyGenConfig{
Type: "rsa",
RSAModLength: 2048,
},
Outputs: struct {
PublicKeyPath string `yaml:"public-key-path"`
}{
PublicKeyPath: "path",
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := tc.config.validate()
if err != nil && err.Error() != tc.expectedError {
t.Fatalf("Unexpected error, wanted: %q, got: %q", tc.expectedError, err)
} else if err == nil && tc.expectedError != "" {
t.Fatalf("validate didn't fail, wanted: %q", err)
}
})
}
}

View File

@ -2,7 +2,6 @@ package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"errors"
@ -76,7 +75,8 @@ func rsaPub(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, object pkcs11
// a nonce generated on the device and verifying the returned signature using the
// public key.
func rsaVerify(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, object pkcs11.ObjectHandle, pub *rsa.PublicKey) error {
nonce, err := getRandomBytes(ctx, session)
nonce := make([]byte, 4)
_, err := newRandReader(ctx, session).Read(nonce)
if err != nil {
return fmt.Errorf("Failed to retrieve nonce: %s", err)
}
@ -98,30 +98,31 @@ func rsaVerify(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, object pkc
// rsaGenerate is used to generate and verify a RSA key pair of the size
// specified by modulusLen and with the exponent specified by pubExponent.
// It returns the public part of the generated key pair as a rsa.PublicKey.
func rsaGenerate(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, label string, modulusLen, pubExponent uint) (*rsa.PublicKey, error) {
// It returns the public part of the generated key pair as a rsa.PublicKey
// and the random key ID that the HSM uses to identify the key pair.
func rsaGenerate(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle, label string, modulusLen, pubExponent uint) (*rsa.PublicKey, []byte, error) {
keyID := make([]byte, 4)
_, err := rand.Read(keyID)
_, err := newRandReader(ctx, session).Read(keyID)
if err != nil {
return nil, err
return nil, nil, err
}
log.Printf("Generating RSA key with %d bit modulus and public exponent %d and ID %x\n", modulusLen, pubExponent, keyID)
args := rsaArgs(label, modulusLen, pubExponent, keyID)
pub, priv, err := ctx.GenerateKeyPair(session, args.mechanism, args.publicAttrs, args.privateAttrs)
if err != nil {
return nil, err
return nil, nil, err
}
log.Println("Key generated")
log.Println("Extracting public key")
pk, err := rsaPub(ctx, session, pub, modulusLen, pubExponent)
if err != nil {
return nil, err
return nil, nil, err
}
log.Println("Extracted public key")
log.Println("Verifying public key")
err = rsaVerify(ctx, session, priv, pk)
if err != nil {
return nil, err
return nil, nil, err
}
return pk, nil
return pk, keyID, nil
}

View File

@ -4,8 +4,8 @@ import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"errors"
"math/big"
"testing"
"github.com/letsencrypt/boulder/pkcs11helpers"
@ -16,30 +16,17 @@ import (
func TestRSAPub(t *testing.T) {
ctx := pkcs11helpers.MockCtx{}
// test attribute retrieval failing
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return nil, errors.New("yup")
}
_, err := rsaPub(ctx, 0, 0, 0, 0)
test.AssertError(t, err, "rsaPub didn't fail on GetAttributeValue error")
// test we fail to construct key with missing modulus and exp
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{}, nil
}
_, err = rsaPub(ctx, 0, 0, 0, 0)
test.AssertError(t, err, "rsaPub didn't fail with empty attribute list")
// test we fail to construct key with non-matching exp
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, []byte{1, 0, 1}),
pkcs11.NewAttribute(pkcs11.CKA_MODULUS, []byte{255}),
}, nil
}
_, err = rsaPub(ctx, 0, 0, 0, 0)
_, err := rsaPub(ctx, 0, 0, 0, 255)
test.AssertError(t, err, "rsaPub didn't fail with non-matching exp")
// test we fail to construct key with non-matching exp
// test we fail to construct key with non-matching modulus
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, []byte{1, 0, 1}),
@ -85,7 +72,7 @@ func TestRSAVerify(t *testing.T) {
return nil
}
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return []byte{1, 2, 3}, nil
return []byte{1, 2, 3, 4}, nil
}
ctx.SignFunc = func(pkcs11.SessionHandle, []byte) ([]byte, error) {
return nil, errors.New("yup")
@ -103,9 +90,9 @@ func TestRSAVerify(t *testing.T) {
test.AssertError(t, err, "rsaVerify didn't fail on signature verification error")
// test we don't fail with valid signature
ctx.SignFunc = func(pkcs11.SessionHandle, []byte) ([]byte, error) {
hash := sha256.Sum256([]byte{1, 2, 3})
return rsa.SignPKCS1v15(rand.Reader, tk, crypto.SHA256, hash[:])
ctx.SignFunc = func(_ pkcs11.SessionHandle, msg []byte) ([]byte, error) {
// Chop of the hash identifier and feed back into rsa.SignPKCS1v15
return rsa.SignPKCS1v15(rand.Reader, tk, crypto.SHA256, msg[19:])
}
err = rsaVerify(ctx, 0, 0, &tk.PublicKey)
test.AssertNotError(t, err, "rsaVerify failed with a valid signature")
@ -113,12 +100,18 @@ func TestRSAVerify(t *testing.T) {
func TestRSAGenerate(t *testing.T) {
ctx := pkcs11helpers.MockCtx{}
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return []byte{1, 2, 3}, nil
}
priv, err := rsa.GenerateKey(rand.Reader, 1024)
test.AssertNotError(t, err, "Failed to generate a RSA test key")
// Test rsaGenerate fails when GenerateKeyPair fails
ctx.GenerateKeyPairFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, []*pkcs11.Attribute, []*pkcs11.Attribute) (pkcs11.ObjectHandle, pkcs11.ObjectHandle, error) {
return 0, 0, errors.New("bad")
}
_, err := rsaGenerate(ctx, 0, "", 1024, 65537)
_, _, err = rsaGenerate(ctx, 0, "", 1024, 65537)
test.AssertError(t, err, "rsaGenerate didn't fail on GenerateKeyPair error")
// Test rsaGenerate fails when rsaPub fails
@ -128,20 +121,20 @@ func TestRSAGenerate(t *testing.T) {
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return nil, errors.New("bad")
}
_, err = rsaGenerate(ctx, 0, "", 1024, 65537)
_, _, err = rsaGenerate(ctx, 0, "", 1024, 65537)
test.AssertError(t, err, "rsaGenerate didn't fail on rsaPub error")
// Test rsaGenerate fails when rsaVerify fails
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, []byte{1, 0, 1}),
pkcs11.NewAttribute(pkcs11.CKA_MODULUS, []byte{217, 226, 207, 73, 127, 217, 136, 48, 203, 2, 12, 223, 251, 130, 143, 118, 13, 186, 82, 183, 220, 178, 158, 204, 19, 255, 121, 75, 243, 84, 118, 40, 128, 29, 11, 245, 43, 246, 217, 244, 166, 208, 36, 59, 69, 34, 142, 40, 22, 230, 195, 193, 111, 202, 186, 174, 233, 175, 140, 74, 19, 135, 191, 82, 27, 41, 123, 157, 174, 219, 38, 71, 19, 138, 28, 41, 48, 52, 142, 234, 196, 242, 51, 90, 204, 10, 235, 88, 150, 156, 89, 156, 199, 152, 173, 251, 88, 67, 138, 147, 86, 190, 236, 107, 190, 169, 53, 160, 219, 71, 147, 247, 230, 24, 188, 44, 61, 92, 106, 254, 125, 145, 233, 211, 76, 13, 159, 167}),
pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, big.NewInt(int64(priv.E)).Bytes()),
pkcs11.NewAttribute(pkcs11.CKA_MODULUS, priv.N.Bytes()),
}, nil
}
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return nil, errors.New("yup")
}
_, err = rsaGenerate(ctx, 0, "", 1024, 65537)
_, _, err = rsaGenerate(ctx, 0, "", 1024, 65537)
test.AssertError(t, err, "rsaGenerate didn't fail on rsaVerify error")
// Test rsaGenerate doesn't fail when everything works
@ -151,9 +144,10 @@ func TestRSAGenerate(t *testing.T) {
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return []byte{1, 2, 3}, nil
}
ctx.SignFunc = func(pkcs11.SessionHandle, []byte) ([]byte, error) {
return []byte{182, 42, 17, 237, 215, 151, 23, 254, 234, 219, 10, 119, 178, 76, 204, 254, 235, 67, 135, 83, 97, 134, 117, 38, 68, 115, 190, 250, 69, 200, 138, 225, 5, 188, 175, 45, 32, 179, 239, 145, 13, 168, 119, 75, 11, 171, 161, 220, 39, 185, 249, 87, 226, 132, 237, 82, 246, 187, 26, 232, 69, 86, 29, 12, 233, 8, 252, 59, 24, 194, 173, 74, 191, 101, 249, 108, 195, 240, 100, 28, 241, 70, 78, 236, 9, 136, 130, 218, 245, 195, 128, 80, 253, 42, 82, 99, 200, 115, 14, 75, 218, 176, 94, 98, 7, 226, 110, 24, 187, 108, 42, 144, 238, 244, 114, 153, 125, 3, 248, 129, 159, 51, 91, 26, 177, 118, 250, 79}, nil
ctx.SignFunc = func(_ pkcs11.SessionHandle, msg []byte) ([]byte, error) {
// Chop of the hash identifier and feed back into rsa.SignPKCS1v15
return rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA256, msg[19:])
}
_, err = rsaGenerate(ctx, 0, "", 1024, 65537)
_, _, err = rsaGenerate(ctx, 0, "", 1024, 65537)
test.AssertNotError(t, err, "rsaGenerate didn't succeed when everything worked as expected")
}

View File

@ -1,216 +0,0 @@
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"errors"
"testing"
"github.com/letsencrypt/boulder/pkcs11helpers"
"github.com/letsencrypt/boulder/test"
"github.com/miekg/pkcs11"
)
func TestECPub(t *testing.T) {
ctx := pkcs11helpers.MockCtx{}
// test attribute retrieval failing
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return nil, errors.New("yup")
}
_, err := ecPub(ctx, 0, 0, nil)
test.AssertError(t, err, "ecPub didn't fail on GetAttributeValue error")
// test we fail to construct key with missing params and point
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{}, nil
}
_, err = ecPub(ctx, 0, 0, nil)
test.AssertError(t, err, "ecPub didn't fail with empty attribute list")
// test we fail to construct key with unknown curve
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{1, 2, 3}),
}, nil
}
_, err = ecPub(ctx, 0, 0, elliptic.P224().Params())
test.AssertError(t, err, "ecPub didn't fail with unknown curve")
// test we fail to construct key with non-matching curve
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 8, 42, 134, 72, 206, 61, 3, 1, 7}),
}, nil
}
_, err = ecPub(ctx, 0, 0, elliptic.P224().Params())
test.AssertError(t, err, "ecPub didn't fail with non-matching curve")
// test we fail to construct key with invalid EC point (invalid encoding)
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 8, 42, 134, 72, 206, 61, 3, 1, 7}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{255}),
}, nil
}
_, err = ecPub(ctx, 0, 0, elliptic.P256().Params())
test.AssertError(t, err, "ecPub didn't fail with invalid EC point (invalid encoding)")
// test we fail to construct key with invalid EC point (empty octet string)
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 8, 42, 134, 72, 206, 61, 3, 1, 7}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{4, 0}),
}, nil
}
_, err = ecPub(ctx, 0, 0, elliptic.P256().Params())
test.AssertError(t, err, "ecPub didn't fail with invalid EC point (octet string, invalid contents)")
// test we fail to construct key with invalid EC point (empty octet string)
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 8, 42, 134, 72, 206, 61, 3, 1, 7}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{4, 4, 4, 1, 2, 3}),
}, nil
}
_, err = ecPub(ctx, 0, 0, elliptic.P256().Params())
test.AssertError(t, err, "ecPub didn't fail with invalid EC point (empty octet string)")
// test we don't fail with the correct attributes (traditional encoding)
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 5, 43, 129, 4, 0, 33}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{4, 217, 225, 246, 210, 153, 134, 246, 104, 95, 79, 122, 206, 135, 241, 37, 114, 199, 87, 56, 167, 83, 56, 136, 174, 6, 145, 97, 239, 221, 49, 67, 148, 13, 126, 65, 90, 208, 195, 193, 171, 105, 40, 98, 132, 124, 30, 189, 215, 197, 178, 226, 166, 238, 240, 57, 215}),
}, nil
}
_, err = ecPub(ctx, 0, 0, elliptic.P224().Params())
test.AssertNotError(t, err, "ecPub failed with valid attributes (traditional encoding)")
// test we don't fail with the correct attributes (non-traditional encoding)
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 5, 43, 129, 4, 0, 33}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{4, 57, 4, 217, 225, 246, 210, 153, 134, 246, 104, 95, 79, 122, 206, 135, 241, 37, 114, 199, 87, 56, 167, 83, 56, 136, 174, 6, 145, 97, 239, 221, 49, 67, 148, 13, 126, 65, 90, 208, 195, 193, 171, 105, 40, 98, 132, 124, 30, 189, 215, 197, 178, 226, 166, 238, 240, 57, 215}),
}, nil
}
_, err = ecPub(ctx, 0, 0, elliptic.P224().Params())
test.AssertNotError(t, err, "ecPub failed with valid attributes (non-traditional encoding)")
}
func TestECVerify(t *testing.T) {
ctx := pkcs11helpers.MockCtx{}
// test GenerateRandom failing
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return nil, errors.New("yup")
}
err := ecVerify(ctx, 0, 0, nil)
test.AssertError(t, err, "ecVerify didn't fail on GenerateRandom error")
// test SignInit failing
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return []byte{1, 2, 3}, nil
}
ctx.SignInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error {
return errors.New("yup")
}
err = ecVerify(ctx, 0, 0, &ecdsa.PublicKey{Curve: elliptic.P256().Params()})
test.AssertError(t, err, "ecVerify didn't fail on SignInit error")
// test Sign failing
ctx.SignInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error {
return nil
}
ctx.SignFunc = func(pkcs11.SessionHandle, []byte) ([]byte, error) {
return nil, errors.New("yup")
}
err = ecVerify(ctx, 0, 0, &ecdsa.PublicKey{Curve: elliptic.P256().Params()})
test.AssertError(t, err, "ecVerify didn't fail on Sign error")
// test signature verification failing
ctx.SignFunc = func(pkcs11.SessionHandle, []byte) ([]byte, error) {
return []byte{1, 2, 3}, nil
}
tk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
test.AssertNotError(t, err, "ecdsa.GenerateKey failed")
err = ecVerify(ctx, 0, 0, &tk.PublicKey)
test.AssertError(t, err, "ecVerify didn't fail on signature verification error")
// test we don't fail with valid signature
ctx.SignFunc = func(pkcs11.SessionHandle, []byte) ([]byte, error) {
hash := sha256.Sum256([]byte{1, 2, 3})
r, s, err := ecdsa.Sign(rand.Reader, tk, hash[:])
if err != nil {
return nil, err
}
rBytes := r.Bytes()
sBytes := s.Bytes()
// http://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/os/pkcs11-curr-v2.40-os.html
// Section 2.3.1: EC Signatures
// "If r and s have different octet length, the shorter of both must be padded with
// leading zero octets such that both have the same octet length."
switch {
case len(rBytes) < len(sBytes):
padding := make([]byte, len(sBytes)-len(rBytes))
rBytes = append(padding, rBytes...)
case len(rBytes) > len(sBytes):
padding := make([]byte, len(rBytes)-len(sBytes))
sBytes = append(padding, sBytes...)
}
return append(rBytes, sBytes...), nil
}
err = ecVerify(ctx, 0, 0, &tk.PublicKey)
test.AssertNotError(t, err, "ecVerify failed with a valid signature")
}
func TestECGenerate(t *testing.T) {
ctx := pkcs11helpers.MockCtx{}
// Test ecGenerate fails with unknown curve
_, err := ecGenerate(ctx, 0, "", "bad-curve")
test.AssertError(t, err, "ecGenerate accepted unknown curve")
// Test ecGenerate fails when GenerateKeyPair fails
ctx.GenerateKeyPairFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, []*pkcs11.Attribute, []*pkcs11.Attribute) (pkcs11.ObjectHandle, pkcs11.ObjectHandle, error) {
return 0, 0, errors.New("bad")
}
_, err = ecGenerate(ctx, 0, "", "P-256")
test.AssertError(t, err, "ecGenerate didn't fail on GenerateKeyPair error")
// Test ecGenerate fails when ecPub fails
ctx.GenerateKeyPairFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, []*pkcs11.Attribute, []*pkcs11.Attribute) (pkcs11.ObjectHandle, pkcs11.ObjectHandle, error) {
return 0, 0, nil
}
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return nil, errors.New("bad")
}
_, err = ecGenerate(ctx, 0, "", "P-256")
test.AssertError(t, err, "ecGenerate didn't fail on ecPub error")
// Test ecGenerate fails when ecVerify fails
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 8, 42, 134, 72, 206, 61, 3, 1, 7}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{4, 71, 137, 101, 56, 44, 59, 172, 148, 152, 118, 61, 183, 215, 242, 168, 62, 77, 94, 246, 212, 164, 96, 210, 134, 87, 169, 142, 226, 189, 118, 137, 203, 117, 55, 2, 215, 177, 159, 42, 196, 33, 91, 92, 251, 98, 53, 137, 221, 167, 148, 25, 209, 1, 5, 90, 52, 43, 18, 7, 30, 33, 142, 228, 235}),
}, nil
}
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return nil, errors.New("yup")
}
_, err = ecGenerate(ctx, 0, "", "P-256")
test.AssertError(t, err, "ecGenerate didn't fail on ecVerify error")
// Test ecGenerate doesn't fail when everything works
ctx.SignInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error {
return nil
}
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return []byte{1, 2, 3}, nil
}
ctx.SignFunc = func(pkcs11.SessionHandle, []byte) ([]byte, error) {
return []byte{82, 33, 179, 118, 118, 141, 38, 154, 5, 20, 207, 140, 127, 221, 237, 139, 222, 74, 189, 107, 84, 133, 127, 80, 226, 169, 25, 110, 141, 226, 196, 69, 202, 51, 204, 77, 22, 198, 104, 91, 74, 120, 221, 156, 122, 11, 43, 54, 106, 10, 165, 202, 229, 71, 44, 18, 113, 236, 213, 47, 208, 239, 198, 33}, nil
}
_, err = ecGenerate(ctx, 0, "", "P-256")
test.AssertNotError(t, err, "ecGenerate didn't succeed when everything worked as expected")
}

View File

@ -1,114 +0,0 @@
// gen-key is a tool for generating RSA or ECDSA keys on a HSM using PKCS#11.
// After generating the key pair it attempts to extract and construct the public
// key and verifies a test message that was signed using the generated private
// key. Any action it takes should be thoroughly logged and documented.
//
// When generating a key this tool follows the following steps:
// 1. Constructs templates for the private and public keys consisting
// of the appropriate PKCS#11 attributes.
// 2. Executes a PKCS#11 GenerateKeyPair operation with the constructed
// templates and either CKM_RSA_PKCS_KEY_PAIR_GEN or CKM_EC_KEY_PAIR_GEN.
// 3. Extracts the public key components from the returned public key object
// handle and construct a Golang public key object from them.
// 4. Generates 4 bytes of random data from the HSM using a PKCS#11 GenerateRandom
// operation.
// 5. Signs the random data with the private key object handle using a PKCS#11
// SignInit/Sign operation.
// 6. Verifies the returned signature of the random data with the constructed
// public key.
// 7. Marshals the public key into a PEM public key object and print it to STDOUT.
//
package main
import (
"crypto/x509"
"encoding/pem"
"flag"
"io/ioutil"
"log"
"os"
"github.com/letsencrypt/boulder/pkcs11helpers"
"github.com/miekg/pkcs11"
)
type generateArgs struct {
mechanism []*pkcs11.Mechanism
privateAttrs []*pkcs11.Attribute
publicAttrs []*pkcs11.Attribute
}
func getRandomBytes(ctx pkcs11helpers.PKCtx, session pkcs11.SessionHandle) ([]byte, error) {
r, err := ctx.GenerateRandom(session, 4)
if err != nil {
return nil, err
}
return r, nil
}
func main() {
module := flag.String("module", "", "PKCS#11 module to use")
keyType := flag.String("type", "", "Type of key to generate (RSA or ECDSA)")
slot := flag.Uint("slot", 0, "Slot to generate key in")
pin := flag.String("pin", "", "PIN for slot if not using PED to login")
label := flag.String("label", "", "Key label")
rsaModLen := flag.Uint("modulus-bits", 0, "Size of RSA modulus in bits. Only used if --type=RSA")
rsaExp := flag.Uint("public-exponent", 65537, "Public RSA exponent. Only used if --type=RSA")
ecdsaCurve := flag.String("curve", "", "Type of ECDSA curve to use (P-224, P-256, P-384, P-521). Only used if --type=ECDSA")
outputPath := flag.String("output", "", "Path to store generated PEM public key")
flag.Parse()
if *module == "" {
log.Fatal("--module is required")
}
if *keyType == "" {
log.Fatal("--type is required")
}
if *keyType != "RSA" && *keyType != "ECDSA" {
log.Fatal("--type may only be RSA or ECDSA")
}
if *label == "" {
log.Fatal("--label is required")
}
if *outputPath == "" {
log.Fatal("--output is required")
}
ctx, session, err := pkcs11helpers.Initialize(*module, *slot, *pin)
if err != nil {
log.Fatalf("Failed to setup session and PKCS#11 context: %s", err)
}
log.Println("Opened PKCS#11 session")
var pubKey interface{}
switch *keyType {
case "RSA":
if *rsaModLen == 0 {
log.Fatal("--modulus-bits is required")
}
pubKey, err = rsaGenerate(ctx, session, *label, *rsaModLen, *rsaExp)
if err != nil {
log.Fatalf("Failed to generate RSA key pair: %s", err)
}
case "ECDSA":
if *ecdsaCurve == "" {
log.Fatal("--ecdsaCurve is required")
}
pubKey, err = ecGenerate(ctx, session, *label, *ecdsaCurve)
if err != nil {
log.Fatalf("Failed to generate ECDSA key pair: %s", err)
}
}
der, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
log.Fatalf("Failed to marshal public key: %s", err)
}
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der})
log.Printf("Public key PEM:\n%s\n", pemBytes)
if err := ioutil.WriteFile(*outputPath, pemBytes, os.ModePerm); err != nil {
log.Fatalf("Failed to write public key to %q: %s", *outputPath, err)
}
log.Printf("Public key written to %q\n", *outputPath)
}

View File

@ -79,11 +79,11 @@ func GetRSAPublicKey(ctx PKCtx, session pkcs11.SessionHandle, object pkcs11.Obje
// oidDERToCurve maps the hex of the DER encoding of the various curve OIDs to
// the relevant curve parameters
var oidDERToCurve = map[string]*elliptic.CurveParams{
"06052B81040021": elliptic.P224().Params(),
"06082A8648CE3D030107": elliptic.P256().Params(),
"06052B81040022": elliptic.P384().Params(),
"06052B81040023": elliptic.P521().Params(),
var oidDERToCurve = map[string]elliptic.Curve{
"06052B81040021": elliptic.P224(),
"06082A8648CE3D030107": elliptic.P256(),
"06052B81040022": elliptic.P384(),
"06052B81040023": elliptic.P521(),
}
func GetECDSAPublicKey(ctx PKCtx, session pkcs11.SessionHandle, object pkcs11.ObjectHandle) (*ecdsa.PublicKey, error) {
@ -182,6 +182,29 @@ func Sign(ctx PKCtx, session pkcs11.SessionHandle, object pkcs11.ObjectHandle, k
return signature, nil
}
// FindObject looks up a PKCS#11 object handle based on the provided template.
// In the case where zero or more than one objects are found to match the
// template an error is returned.
func FindObject(ctx PKCtx, session pkcs11.SessionHandle, tmpl []*pkcs11.Attribute) (pkcs11.ObjectHandle, error) {
if err := ctx.FindObjectsInit(session, tmpl); err != nil {
return 0, err
}
handles, more, err := ctx.FindObjects(session, 1)
if err != nil {
return 0, err
}
if len(handles) == 0 {
return 0, errors.New("no objects found matching provided template")
}
if more {
return 0, errors.New("more than one object matches provided template")
}
if err := ctx.FindObjectsFinal(session); err != nil {
return 0, err
}
return handles[0], nil
}
type MockCtx struct {
GenerateKeyPairFunc func(pkcs11.SessionHandle, []*pkcs11.Mechanism, []*pkcs11.Attribute, []*pkcs11.Attribute) (pkcs11.ObjectHandle, pkcs11.ObjectHandle, error)
GetAttributeValueFunc func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error)

View File

@ -0,0 +1,167 @@
package pkcs11helpers
import (
"errors"
"testing"
"github.com/letsencrypt/boulder/test"
"github.com/miekg/pkcs11"
)
func TestGetECDSAPublicKey(t *testing.T) {
ctx := MockCtx{}
// test attribute retrieval failing
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return nil, errors.New("yup")
}
_, err := GetECDSAPublicKey(ctx, 0, 0)
test.AssertError(t, err, "ecPub didn't fail on GetAttributeValue error")
// test we fail to construct key with missing params and point
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{}, nil
}
_, err = GetECDSAPublicKey(ctx, 0, 0)
test.AssertError(t, err, "ecPub didn't fail with empty attribute list")
// test we fail to construct key with unknown curve
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{1, 2, 3}),
}, nil
}
_, err = GetECDSAPublicKey(ctx, 0, 0)
test.AssertError(t, err, "ecPub didn't fail with unknown curve")
// test we fail to construct key with invalid EC point (invalid encoding)
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 8, 42, 134, 72, 206, 61, 3, 1, 7}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{255}),
}, nil
}
_, err = GetECDSAPublicKey(ctx, 0, 0)
test.AssertError(t, err, "ecPub didn't fail with invalid EC point (invalid encoding)")
// test we fail to construct key with invalid EC point (empty octet string)
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 8, 42, 134, 72, 206, 61, 3, 1, 7}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{4, 0}),
}, nil
}
_, err = GetECDSAPublicKey(ctx, 0, 0)
test.AssertError(t, err, "ecPub didn't fail with invalid EC point (empty octet string)")
// test we fail to construct key with invalid EC point (octet string, invalid contents)
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 8, 42, 134, 72, 206, 61, 3, 1, 7}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{4, 4, 4, 1, 2, 3}),
}, nil
}
_, err = GetECDSAPublicKey(ctx, 0, 0)
test.AssertError(t, err, "ecPub didn't fail with invalid EC point (octet string, invalid contents)")
// test we don't fail with the correct attributes (traditional encoding)
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 5, 43, 129, 4, 0, 33}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{4, 217, 225, 246, 210, 153, 134, 246, 104, 95, 79, 122, 206, 135, 241, 37, 114, 199, 87, 56, 167, 83, 56, 136, 174, 6, 145, 97, 239, 221, 49, 67, 148, 13, 126, 65, 90, 208, 195, 193, 171, 105, 40, 98, 132, 124, 30, 189, 215, 197, 178, 226, 166, 238, 240, 57, 215}),
}, nil
}
_, err = GetECDSAPublicKey(ctx, 0, 0)
test.AssertNotError(t, err, "ecPub failed with valid attributes (traditional encoding)")
// test we don't fail with the correct attributes (non-traditional encoding)
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{6, 5, 43, 129, 4, 0, 33}),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{4, 57, 4, 217, 225, 246, 210, 153, 134, 246, 104, 95, 79, 122, 206, 135, 241, 37, 114, 199, 87, 56, 167, 83, 56, 136, 174, 6, 145, 97, 239, 221, 49, 67, 148, 13, 126, 65, 90, 208, 195, 193, 171, 105, 40, 98, 132, 124, 30, 189, 215, 197, 178, 226, 166, 238, 240, 57, 215}),
}, nil
}
_, err = GetECDSAPublicKey(ctx, 0, 0)
test.AssertNotError(t, err, "ecPub failed with valid attributes (non-traditional encoding)")
}
func TestRSAPublicKey(t *testing.T) {
ctx := MockCtx{}
// test attribute retrieval failing
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return nil, errors.New("yup")
}
_, err := GetRSAPublicKey(ctx, 0, 0)
test.AssertError(t, err, "rsaPub didn't fail on GetAttributeValue error")
// test we fail to construct key with missing modulus and exp
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{}, nil
}
_, err = GetRSAPublicKey(ctx, 0, 0)
test.AssertError(t, err, "rsaPub didn't fail with empty attribute list")
// test we don't fail with the correct attributes
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, []byte{1, 0, 1}),
pkcs11.NewAttribute(pkcs11.CKA_MODULUS, []byte{255}),
}, nil
}
_, err = GetRSAPublicKey(ctx, 0, 0)
test.AssertNotError(t, err, "rsaPub failed with valid attributes")
}
func TestFindObject(t *testing.T) {
ctx := MockCtx{}
// test FindObject fails when FindObjectsInit fails
ctx.FindObjectsInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Attribute) error {
return errors.New("broken")
}
_, err := FindObject(ctx, 0, nil)
test.AssertError(t, err, "FindObject didn't fail when FindObjectsInit failed")
// test FindObject fails when FindObjects fails
ctx.FindObjectsInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Attribute) error {
return nil
}
ctx.FindObjectsFunc = func(pkcs11.SessionHandle, int) ([]pkcs11.ObjectHandle, bool, error) {
return nil, false, errors.New("broken")
}
_, err = FindObject(ctx, 0, nil)
test.AssertError(t, err, "FindObject didn't fail when FindObjects failed")
// test FindObject fails when no handles are returned
ctx.FindObjectsFunc = func(pkcs11.SessionHandle, int) ([]pkcs11.ObjectHandle, bool, error) {
return []pkcs11.ObjectHandle{}, false, nil
}
_, err = FindObject(ctx, 0, nil)
test.AssertError(t, err, "FindObject didn't fail when FindObjects returns no handles")
// test FindObject fails when multiple handles are returned
ctx.FindObjectsFunc = func(pkcs11.SessionHandle, int) ([]pkcs11.ObjectHandle, bool, error) {
return []pkcs11.ObjectHandle{1}, true, nil
}
_, err = FindObject(ctx, 0, nil)
test.AssertError(t, err, "FindObject didn't fail when FindObjects returns multiple handles")
// test FindObject fails when FindObjectsFinal fails
ctx.FindObjectsFunc = func(pkcs11.SessionHandle, int) ([]pkcs11.ObjectHandle, bool, error) {
return []pkcs11.ObjectHandle{1}, false, nil
}
ctx.FindObjectsFinalFunc = func(pkcs11.SessionHandle) error {
return errors.New("broken")
}
_, err = FindObject(ctx, 0, nil)
test.AssertError(t, err, "FindObject didn't fail when FindObjectsFinal fails")
// test FindObject works
ctx.FindObjectsFinalFunc = func(pkcs11.SessionHandle) error {
return nil
}
handle, err := FindObject(ctx, 0, nil)
test.AssertNotError(t, err, "FindObject failed when everything worked as expected")
test.AssertEquals(t, handle, pkcs11.ObjectHandle(1))
}