Add a PKCS#11 key generation tool (#3163)

Tested against master SoftHSMv2 and relevant hardware.

Fixes #3125.
This commit is contained in:
Roland Bracewell Shoemaker 2017-10-30 16:09:28 -07:00 committed by Jacob Hoffman-Andrews
parent 0882b86e6c
commit 29c95f0aed
6 changed files with 908 additions and 0 deletions

219
cmd/gen-key/ecdsa.go Normal file
View File

@ -0,0 +1,219 @@
package main
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"encoding/asn1"
"errors"
"fmt"
"log"
"math/big"
"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(),
}
// curveToOIDDER maps the name of the curves to their DER encoded OIDs
var curveToOIDDER = map[string][]byte{
elliptic.P224().Params().Name: []byte{6, 5, 43, 129, 4, 0, 33},
elliptic.P256().Params().Name: []byte{6, 8, 42, 134, 72, 206, 61, 3, 1, 7},
elliptic.P384().Params().Name: []byte{6, 5, 43, 129, 4, 0, 34},
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 hashToString = map[crypto.Hash]string{
crypto.SHA256: "SHA-256",
crypto.SHA384: "SHA-384",
crypto.SHA512: "SHA-512",
}
// 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. compatMode is used to determine which
// mechanism and attribute types should be used, for devices that implement
// a pre-2.11 version of the PKCS#11 specification compatMode should be true.
func ecArgs(label string, curve *elliptic.CurveParams, compatMode bool) generateArgs {
encodedCurve := curveToOIDDER[curve.Name]
log.Printf("\tEncoded curve parameters for %s: %X\n", curve.Params().Name, encodedCurve)
var genMech, paramType uint
if compatMode {
genMech = pkcs11.CKM_ECDSA_KEY_PAIR_GEN
paramType = pkcs11.CKA_ECDSA_PARAMS
} else {
genMech = pkcs11.CKM_EC_KEY_PAIR_GEN
paramType = pkcs11.CKA_EC_PARAMS
}
return generateArgs{
mechanism: []*pkcs11.Mechanism{
pkcs11.NewMechanism(genMech, nil),
},
publicAttrs: []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
pkcs11.NewAttribute(pkcs11.CKA_VERIFY, true),
pkcs11.NewAttribute(paramType, encodedCurve),
},
privateAttrs: []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
// Prevent attributes being retrieved
pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true),
// Prevent the key being extracted from the device
pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, false),
// Allow the key to sign data
pkcs11.NewAttribute(pkcs11.CKA_SIGN, true),
},
}
}
// ecPub extracts the generated public key, specified by the provided object
// handle, and constructs an ecdsa.PublicKey. It also checks that the key is of
// the correct curve type. For devices that implement a pre-2.11 version of the
// PKCS#11 specification compatMode should be true.
func ecPub(ctx PKCtx, session pkcs11.SessionHandle, object pkcs11.ObjectHandle, expectedCurve *elliptic.CurveParams, compatMode bool) (*ecdsa.PublicKey, error) {
var paramType uint
if compatMode {
paramType = pkcs11.CKA_ECDSA_PARAMS
} else {
paramType = pkcs11.CKA_EC_PARAMS
}
// Retrieve the curve and public point for the generated public key
attrs, err := ctx.GetAttributeValue(session, object, []*pkcs11.Attribute{
pkcs11.NewAttribute(paramType, nil),
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, nil),
})
if err != nil {
return nil, fmt.Errorf("Failed to retrieve key attributes: %s", err)
}
pubKey := &ecdsa.PublicKey{}
gotCurve, gotPoint := false, false
for _, a := range attrs {
switch a.Type {
case paramType:
rCurve, present := oidDERToCurve[fmt.Sprintf("%X", a.Value)]
if !present {
return nil, errors.New("Unknown curve OID value returned")
}
pubKey.Curve = rCurve
if pubKey.Curve != expectedCurve {
return nil, errors.New("Returned EC parameters doesn't match expected curve")
}
gotCurve = true
case pkcs11.CKA_EC_POINT:
x, y := elliptic.Unmarshal(expectedCurve, a.Value)
if x == nil {
// http://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/os/pkcs11-curr-v2.40-os.html#_ftn1
// PKCS#11 v2.20 specified that the CKA_EC_POINT was to be stored in a DER-encoded
// OCTET STRING.
var point asn1.RawValue
_, err = asn1.Unmarshal(a.Value, &point)
if err != nil {
return nil, fmt.Errorf("Failed to unmarshal returned CKA_EC_POINT: %s", err)
}
if len(point.Bytes) == 0 {
return nil, errors.New("Invalid CKA_EC_POINT value returned, OCTET string is empty")
}
x, y = elliptic.Unmarshal(expectedCurve, point.Bytes)
if x == nil {
fmt.Println(point.Bytes)
return nil, errors.New("Invalid CKA_EC_POINT value returned, point is malformed")
}
}
pubKey.X, pubKey.Y = x, y
gotPoint = true
log.Printf("\tX: %X\n", pubKey.X.Bytes())
log.Printf("\tY: %X\n", pubKey.Y.Bytes())
}
}
if !gotPoint || !gotCurve {
return nil, errors.New("Couldn't retrieve EC point and EC parameters")
}
return pubKey, nil
}
// ecVerify verifies that the extracted public key corresponds with the generated
// private key on the device, specified by the provided object handle, by signing
// a nonce generated on the device and verifying the returned signature using the
// public key.
func ecVerify(ctx PKCtx, session pkcs11.SessionHandle, object pkcs11.ObjectHandle, pub *ecdsa.PublicKey) error {
err := ctx.SignInit(session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_ECDSA, nil)}, object)
if err != nil {
return fmt.Errorf("failed to initialize signing operation: %s", err)
}
nonce, err := getRandomBytes(ctx, session)
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.Write(nonce)
hash := hashFunc.Sum(nil)
log.Printf("\tMessage %s hash: %X\n", hashToString[curveToHash[pub.Curve.Params()]], hash)
signature, err := ctx.Sign(session, hash)
if err != nil {
return fmt.Errorf("failed to sign data: %s", err)
}
log.Printf("\tMessage signature: %X\n", signature)
r := big.NewInt(0).SetBytes(signature[:len(signature)/2])
s := big.NewInt(0).SetBytes(signature[len(signature)/2:])
if !ecdsa.Verify(pub, hash[:], r, s) {
return errors.New("failed to verify ECDSA signature over test data")
}
log.Println("\tSignature verified")
return nil
}
// ecGenerate is used to generate and verify a ECDSA key pair of the type
// specified by curveStr and with the provided label. For devices that implement
// a pre-2.11 version of the PKCS#11 specification compatMode should be true.
// It returns the public part of the generated key pair as a ecdsa.PublicKey.
func ecGenerate(ctx PKCtx, session pkcs11.SessionHandle, label, curveStr string, compatMode bool) (*ecdsa.PublicKey, error) {
curve, present := stringToCurve[curveStr]
if !present {
return nil, fmt.Errorf("curve %q not supported", curveStr)
}
log.Printf("Generating ECDSA key with curve %s\n", curveStr)
args := ecArgs(label, curve, compatMode)
pub, priv, err := ctx.GenerateKeyPair(session, args.mechanism, args.publicAttrs, args.privateAttrs)
if err != nil {
return nil, err
}
log.Println("Key generated")
log.Println("Extracting public key")
pk, err := ecPub(ctx, session, pub, curve, compatMode)
if err != nil {
return nil, err
}
log.Println("Extracted public key")
log.Println("Verifying public key")
err = ecVerify(ctx, session, priv, pk)
if err != nil {
return nil, err
}
log.Println("Key verified")
return pk, nil
}

211
cmd/gen-key/ecdsa_test.go Normal file
View File

@ -0,0 +1,211 @@
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"errors"
"testing"
"github.com/letsencrypt/boulder/test"
"github.com/miekg/pkcs11"
)
func TestECPub(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 := ecPub(ctx, 0, 0, nil, false)
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, false)
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(), false)
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(), false)
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(), false)
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(), false)
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(), false)
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(), false)
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(), false)
test.AssertNotError(t, err, "ecPub failed with valid attributes (non-traditional encoding)")
}
func TestECVerify(t *testing.T) {
ctx := mockCtx{}
// test SignInit failing
ctx.SignInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error {
return errors.New("yup")
}
err := ecVerify(ctx, 0, 0, nil)
test.AssertError(t, err, "ecVerify didn't fail on SignInit error")
// test GenerateRandom failing
ctx.SignInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error {
return nil
}
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 Sign failing
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return []byte{1, 2, 3}, 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().Params(), 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
}
return append(r.Bytes(), s.Bytes()...), nil
}
err = ecVerify(ctx, 0, 0, &tk.PublicKey)
test.AssertNotError(t, err, "ecVerify failed with a valid signature")
}
func TestECGenerate(t *testing.T) {
ctx := mockCtx{}
// Test ecGenerate fails with unknown curve
_, err := ecGenerate(ctx, 0, "", "bad-curve", false)
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", false)
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", false)
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.SignInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error {
return errors.New("yup")
}
_, err = ecGenerate(ctx, 0, "", "P-256", false)
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", false)
test.AssertNotError(t, err, "ecGenerate didn't succeed when everything worked as expected")
// Test ecGenerate doesn't fail when everything works with compatibility mode
ctx.GetAttributeValueFunc = func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_ECDSA_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
}
_, err = ecGenerate(ctx, 0, "", "P-256", true)
test.AssertNotError(t, err, "ecGenerate didn't succeed when everything worked as expected")
}

143
cmd/gen-key/main.go Normal file
View File

@ -0,0 +1,143 @@
// 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
// (or CKM_ECDSA_KEY_PAIR_GEN for pre-PKCS#11 v2.11 devices).
// 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"
"errors"
"flag"
"fmt"
"log"
"os"
"github.com/miekg/pkcs11"
)
type generateArgs struct {
mechanism []*pkcs11.Mechanism
privateAttrs []*pkcs11.Attribute
publicAttrs []*pkcs11.Attribute
}
type PKCtx interface {
GenerateKeyPair(pkcs11.SessionHandle, []*pkcs11.Mechanism, []*pkcs11.Attribute, []*pkcs11.Attribute) (pkcs11.ObjectHandle, pkcs11.ObjectHandle, error)
GetAttributeValue(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error)
SignInit(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error
Sign(pkcs11.SessionHandle, []byte) ([]byte, error)
GenerateRandom(pkcs11.SessionHandle, int) ([]byte, error)
}
func getRandomBytes(ctx PKCtx, session pkcs11.SessionHandle) ([]byte, error) {
r, err := ctx.GenerateRandom(session, 4)
if err != nil {
return nil, err
}
return r, nil
}
func initialize(module string, slot uint, pin string) (PKCtx, pkcs11.SessionHandle, error) {
ctx := pkcs11.New(module)
if ctx == nil {
return nil, 0, errors.New("failed to load module")
}
err := ctx.Initialize()
if err != nil {
return nil, 0, fmt.Errorf("couldn't initialize context: %s", err)
}
session, err := ctx.OpenSession(slot, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
if err != nil {
return nil, 0, fmt.Errorf("couldn't open session: %s", err)
}
err = ctx.Login(session, pkcs11.CKU_USER, pin)
if err != nil {
return nil, 0, fmt.Errorf("couldn't login: %s", err)
}
return ctx, session, 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")
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")
compatMode := flag.Bool("compat-mode", false, "Use pre PKCS#11 v2.11 style ECDSA parameters. Only used if --type=ECDSA")
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 *pin == "" {
log.Fatal("--pin is required")
}
if *label == "" {
log.Fatal("--label is required")
}
ctx, session, err := initialize(*module, *slot, *pin)
if err != nil {
log.Fatalf("Failed to setup session and PKCS#11 context: %s", err)
}
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, *compatMode)
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)
}
err = pem.Encode(os.Stdout, &pem.Block{Type: "PUBLIC KEY", Bytes: der})
if err != nil {
log.Fatalf("Failed to encode public key as PEM object: %s", err)
}
}

28
cmd/gen-key/main_test.go Normal file
View File

@ -0,0 +1,28 @@
package main
import "github.com/miekg/pkcs11"
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)
SignInitFunc func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error
SignFunc func(pkcs11.SessionHandle, []byte) ([]byte, error)
GenerateRandomFunc func(pkcs11.SessionHandle, int) ([]byte, error)
}
func (mc mockCtx) GenerateKeyPair(s pkcs11.SessionHandle, m []*pkcs11.Mechanism, a1 []*pkcs11.Attribute, a2 []*pkcs11.Attribute) (pkcs11.ObjectHandle, pkcs11.ObjectHandle, error) {
return mc.GenerateKeyPairFunc(s, m, a1, a2)
}
func (mc mockCtx) GetAttributeValue(s pkcs11.SessionHandle, o pkcs11.ObjectHandle, a []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
return mc.GetAttributeValueFunc(s, o, a)
}
func (mc mockCtx) SignInit(s pkcs11.SessionHandle, m []*pkcs11.Mechanism, o pkcs11.ObjectHandle) error {
return mc.SignInitFunc(s, m, o)
}
func (mc mockCtx) Sign(s pkcs11.SessionHandle, m []byte) ([]byte, error) {
return mc.SignFunc(s, m)
}
func (mc mockCtx) GenerateRandom(s pkcs11.SessionHandle, c int) ([]byte, error) {
return mc.GenerateRandomFunc(s, c)
}

152
cmd/gen-key/rsa.go Normal file
View File

@ -0,0 +1,152 @@
package main
import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"errors"
"fmt"
"log"
"math/big"
"github.com/miekg/pkcs11"
)
// rsaArgs constructs the private and public key template attributes sent to the
// device and specifies which mechanism should be used. modulusLen specifies the
// length of the modulus to be generated on the device in bits and exponent
// specifies the public exponent that should be used.
func rsaArgs(label string, modulusLen, exponent uint) generateArgs {
// Encode as unpadded big endian encoded byte slice
expSlice := big.NewInt(int64(exponent)).Bytes()
log.Printf("\tEncoded public exponent (%d) as: %0X\n", exponent, expSlice)
return generateArgs{
mechanism: []*pkcs11.Mechanism{
pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_KEY_PAIR_GEN, nil),
},
publicAttrs: []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
// Allow the key to verify signatures
pkcs11.NewAttribute(pkcs11.CKA_VERIFY, true),
// Set requested modulus length
pkcs11.NewAttribute(pkcs11.CKA_MODULUS_BITS, modulusLen),
// Set requested public exponent
pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, expSlice),
},
privateAttrs: []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
// Prevent attributes being retrieved
pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true),
// Prevent the key being extracted from the device
pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, false),
// Allow the key to create signatures
pkcs11.NewAttribute(pkcs11.CKA_SIGN, true),
},
}
}
// rsaPub extracts the generated public key, specified by the provided object
// handle, and constructs a rsa.PublicKey. It also checks that the key has the
// correct length modulus and that the public exponent is what was requested in
// the public key template.
func rsaPub(ctx PKCtx, session pkcs11.SessionHandle, object pkcs11.ObjectHandle, modulusLen, exponent uint) (*rsa.PublicKey, error) {
// Retrieve the public exponent and modulus for the generated public key
attrs, err := ctx.GetAttributeValue(session, object, []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, nil),
pkcs11.NewAttribute(pkcs11.CKA_MODULUS, nil),
})
if err != nil {
return nil, fmt.Errorf("Failed to retrieve key attributes: %s", err)
}
// Attempt to build the public key from the retrieved attributes
pubKey := &rsa.PublicKey{}
gotMod, gotExp := false, false
for _, a := range attrs {
switch a.Type {
case pkcs11.CKA_PUBLIC_EXPONENT:
pubKey.E = int(big.NewInt(0).SetBytes(a.Value).Int64())
// Check the provided public exponent was used
if pubKey.E != int(exponent) {
return nil, errors.New("Returned CKA_PUBLIC_EXPONENT doesn't match expected exponent")
}
gotExp = true
log.Printf("\tPublic exponent: %d\n", pubKey.E)
case pkcs11.CKA_MODULUS:
pubKey.N = big.NewInt(0).SetBytes(a.Value)
// Check the right length modulus was generated on the device
if pubKey.N.BitLen() != int(modulusLen) {
return nil, errors.New("Returned CKA_MODULUS isn't of the expected bit length")
}
gotMod = true
log.Printf("\tModulus: (%d bits) %X\n", pubKey.N.BitLen(), pubKey.N.Bytes())
}
}
// Fail if we are missing either the public exponent or modulus
if !gotExp || !gotMod {
return nil, errors.New("Couldn't retrieve modulus and exponent")
}
return pubKey, nil
}
// rsaVerify verifies that the extracted public key corresponds with the generated
// private key on the device, specified by the provided object handle, by signing
// a nonce generated on the device and verifying the returned signature using the
// public key.
func rsaVerify(ctx PKCtx, session pkcs11.SessionHandle, object pkcs11.ObjectHandle, pub *rsa.PublicKey) error {
// Initialize a signing operation
err := ctx.SignInit(session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS, nil)}, object)
if err != nil {
return fmt.Errorf("Failed to initialize signing operation: %s", err)
}
// PKCS#11 requires a hash identifier prefix to the message in order to determine which hash was used.
// This prefix indicates SHA-256.
input := []byte{0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}
nonce, err := getRandomBytes(ctx, session)
if err != nil {
return fmt.Errorf("Failed to retrieve nonce: %s", err)
}
log.Printf("\tConstructed nonce: %d (%X)\n", big.NewInt(0).SetBytes(nonce), nonce)
hash := sha256.Sum256(nonce)
log.Printf("\tMessage SHA-256 hash: %X\n", hash)
input = append(input, hash[:]...)
log.Println("\tSigning message")
signature, err := ctx.Sign(session, input)
if err != nil {
return fmt.Errorf("Failed to sign data: %s", err)
}
log.Printf("\tMessage signature: %X\n", signature)
err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash[:], signature)
if err != nil {
return fmt.Errorf("Failed to verify signature: %s", err)
}
log.Println("\tSignature verified")
return nil
}
// 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 PKCtx, session pkcs11.SessionHandle, label string, modulusLen, pubExponent uint) (*rsa.PublicKey, error) {
log.Printf("Generating RSA key with %d bit modulus and public exponent %d\n", modulusLen, pubExponent)
args := rsaArgs(label, modulusLen, pubExponent)
pub, priv, err := ctx.GenerateKeyPair(session, args.mechanism, args.publicAttrs, args.privateAttrs)
if err != nil {
return 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
}
log.Println("Extracted public key")
log.Println("Verifying public key")
err = rsaVerify(ctx, session, priv, pk)
if err != nil {
return nil, err
}
return pk, nil
}

155
cmd/gen-key/rsa_test.go Normal file
View File

@ -0,0 +1,155 @@
package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"errors"
"testing"
"github.com/letsencrypt/boulder/test"
"github.com/miekg/pkcs11"
)
func TestRSAPub(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 := 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}),
}, nil
}
_, err = rsaPub(ctx, 0, 0, 0, 0)
test.AssertError(t, err, "rsaPub didn't fail with non-matching exp")
// 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, 16, 65537)
test.AssertError(t, err, "rsaPub didn't fail with non-matching modulus size")
// 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 = rsaPub(ctx, 0, 0, 8, 65537)
test.AssertNotError(t, err, "rsaPub failed with valid attributes")
}
func TestRSAVerify(t *testing.T) {
ctx := mockCtx{}
// test SignInit failing
ctx.SignInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error {
return errors.New("yup")
}
err := rsaVerify(ctx, 0, 0, nil)
test.AssertError(t, err, "rsaVerify didn't fail on SignInit error")
// test GenerateRandom failing
ctx.SignInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error {
return nil
}
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return nil, errors.New("yup")
}
err = rsaVerify(ctx, 0, 0, nil)
test.AssertError(t, err, "rsaVerify didn't fail on GenerateRandom error")
// test Sign failing
ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) {
return []byte{1, 2, 3}, nil
}
ctx.SignFunc = func(pkcs11.SessionHandle, []byte) ([]byte, error) {
return nil, errors.New("yup")
}
err = rsaVerify(ctx, 0, 0, nil)
test.AssertError(t, err, "rsaVerify 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 := rsa.GenerateKey(rand.Reader, 1024)
test.AssertNotError(t, err, "rsa.GenerateKey failed")
err = rsaVerify(ctx, 0, 0, &tk.PublicKey)
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[:])
}
err = rsaVerify(ctx, 0, 0, &tk.PublicKey)
test.AssertNotError(t, err, "rsaVerify failed with a valid signature")
}
func TestRSAGenerate(t *testing.T) {
ctx := mockCtx{}
// 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)
test.AssertError(t, err, "rsaGenerate didn't fail on GenerateKeyPair error")
// Test rsaGenerate fails when rsaPub 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 = 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}),
}, nil
}
ctx.SignInitFunc = func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error {
return errors.New("yup")
}
_, 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
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{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
}
_, err = rsaGenerate(ctx, 0, "", 1024, 65537)
test.AssertNotError(t, err, "rsaGenerate didn't succeed when everything worked as expected")
}