Merge branch 'master' into hs/scep-master
commit
9e43dc85d8
@ -0,0 +1,455 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/certificates/kms/uri"
|
||||
"go.step.sm/cli-utils/fileutil"
|
||||
"go.step.sm/cli-utils/ui"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
|
||||
// Enable pkcs11.
|
||||
_ "github.com/smallstep/certificates/kms/pkcs11"
|
||||
)
|
||||
|
||||
// Config is a mapping of the cli flags.
|
||||
type Config struct {
|
||||
KMS string
|
||||
RootOnly bool
|
||||
RootObject string
|
||||
RootKeyObject string
|
||||
CrtObject string
|
||||
CrtKeyObject string
|
||||
SSHHostKeyObject string
|
||||
SSHUserKeyObject string
|
||||
RootFile string
|
||||
KeyFile string
|
||||
Pin string
|
||||
NoCerts bool
|
||||
EnableSSH bool
|
||||
Force bool
|
||||
}
|
||||
|
||||
// Validate checks the flags in the config.
|
||||
func (c *Config) Validate() error {
|
||||
switch {
|
||||
case c.KMS == "":
|
||||
return errors.New("flag `--kms` is required")
|
||||
case c.RootFile != "" && c.KeyFile == "":
|
||||
return errors.New("flag `--root` requires flag `--key`")
|
||||
case c.KeyFile != "" && c.RootFile == "":
|
||||
return errors.New("flag `--key` requires flag `--root`")
|
||||
case c.RootOnly && c.RootFile != "":
|
||||
return errors.New("flag `--root-only` is incompatible with flag `--root`")
|
||||
case c.RootFile == "" && c.RootObject == "":
|
||||
return errors.New("one of flag `--root` or `--root-cert` is required")
|
||||
case c.RootFile == "" && c.RootKeyObject == "":
|
||||
return errors.New("one of flag `--root` or `--root-key` is required")
|
||||
default:
|
||||
if c.RootFile != "" {
|
||||
c.RootObject = ""
|
||||
c.RootKeyObject = ""
|
||||
}
|
||||
if c.RootOnly {
|
||||
c.CrtObject = ""
|
||||
c.CrtKeyObject = ""
|
||||
}
|
||||
if !c.EnableSSH {
|
||||
c.SSHHostKeyObject = ""
|
||||
c.SSHUserKeyObject = ""
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var kmsuri string
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
kmsuri = "pkcs11:module-path=/usr/local/lib/pkcs11/yubihsm_pkcs11.dylib;token=YubiHSM"
|
||||
case "linux":
|
||||
kmsuri = "pkcs11:module-path=/usr/lib/x86_64-linux-gnu/pkcs11/yubihsm_pkcs11.so;token=YubiHSM"
|
||||
case "windows":
|
||||
if home, err := os.UserHomeDir(); err == nil {
|
||||
kmsuri = "pkcs11:module-path=" + home + "\\yubihsm2-sdk\\bin\\yubihsm_pkcs11.dll" + ";token=YubiHSM"
|
||||
}
|
||||
}
|
||||
|
||||
var c Config
|
||||
flag.StringVar(&c.KMS, "kms", kmsuri, "PKCS #11 URI with the module-path and token to connect to the module.")
|
||||
flag.StringVar(&c.RootObject, "root-cert", "pkcs11:id=7330;object=root-cert", "PKCS #11 URI with object id and label to store the root certificate.")
|
||||
flag.StringVar(&c.RootKeyObject, "root-key", "pkcs11:id=7330;object=root-key", "PKCS #11 URI with object id and label to store the root key.")
|
||||
flag.StringVar(&c.CrtObject, "crt-cert", "pkcs11:id=7331;object=intermediate-cert", "PKCS #11 URI with object id and label to store the intermediate certificate.")
|
||||
flag.StringVar(&c.CrtKeyObject, "crt-key", "pkcs11:id=7331;object=intermediate-key", "PKCS #11 URI with object id and label to store the intermediate certificate.")
|
||||
flag.StringVar(&c.SSHHostKeyObject, "ssh-host-key", "pkcs11:id=7332;object=ssh-host-key", "PKCS #11 URI with object id and label to store the key used to sign SSH host certificates.")
|
||||
flag.StringVar(&c.SSHUserKeyObject, "ssh-user-key", "pkcs11:id=7333;object=ssh-user-key", "PKCS #11 URI with object id and label to store the key used to sign SSH user certificates.")
|
||||
flag.BoolVar(&c.RootOnly, "root-only", false, "Store only only the root certificate and sign and intermediate.")
|
||||
flag.StringVar(&c.RootFile, "root", "", "Path to the root certificate to use.")
|
||||
flag.StringVar(&c.KeyFile, "key", "", "Path to the root key to use.")
|
||||
flag.BoolVar(&c.EnableSSH, "ssh", false, "Enable the creation of ssh keys.")
|
||||
flag.BoolVar(&c.NoCerts, "no-certs", false, "Do not store certificates in the module.")
|
||||
flag.BoolVar(&c.Force, "force", false, "Force the delete of previous keys.")
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
if err := c.Validate(); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
u, err := uri.ParseWithScheme("pkcs11", c.KMS)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if u.Pin() == "" {
|
||||
pin, err := ui.PromptPassword("What is the PKCS#11 PIN?")
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
c.Pin = string(pin)
|
||||
}
|
||||
|
||||
k, err := kms.New(context.Background(), apiv1.Options{
|
||||
Type: string(apiv1.PKCS11),
|
||||
URI: c.KMS,
|
||||
Pin: c.Pin,
|
||||
})
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = k.Close()
|
||||
}()
|
||||
|
||||
// Check if the slots are empty, fail if they are not
|
||||
certUris := []string{
|
||||
c.RootObject, c.CrtObject,
|
||||
}
|
||||
keyUris := []string{
|
||||
c.RootKeyObject, c.CrtKeyObject,
|
||||
c.SSHHostKeyObject, c.SSHUserKeyObject,
|
||||
}
|
||||
if !c.Force {
|
||||
for _, u := range certUris {
|
||||
if u != "" && !c.NoCerts {
|
||||
checkObject(k, u)
|
||||
checkCertificate(k, u)
|
||||
}
|
||||
}
|
||||
for _, u := range keyUris {
|
||||
if u != "" {
|
||||
checkObject(k, u)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deleter, ok := k.(interface {
|
||||
DeleteKey(uri string) error
|
||||
DeleteCertificate(uri string) error
|
||||
})
|
||||
if ok {
|
||||
for _, u := range certUris {
|
||||
if u != "" && !c.NoCerts {
|
||||
// Some HSMs like Nitrokey will overwrite the key with the
|
||||
// certificate label.
|
||||
if err := deleter.DeleteKey(u); err != nil {
|
||||
fatalClose(err, k)
|
||||
}
|
||||
if err := deleter.DeleteCertificate(u); err != nil {
|
||||
fatalClose(err, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, u := range keyUris {
|
||||
if u != "" {
|
||||
if err := deleter.DeleteKey(u); err != nil {
|
||||
fatalClose(err, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := createPKI(k, c); err != nil {
|
||||
fatalClose(err, k)
|
||||
}
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
if os.Getenv("STEPDEBUG") == "1" {
|
||||
fmt.Fprintf(os.Stderr, "%+v\n", err)
|
||||
} else {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func fatalClose(err error, k kms.KeyManager) {
|
||||
_ = k.Close()
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "Usage: step-pkcs11-init")
|
||||
fmt.Fprintln(os.Stderr, `
|
||||
The step-pkcs11-init command initializes a public key infrastructure (PKI)
|
||||
to be used by step-ca.
|
||||
|
||||
This tool is experimental and in the future it will be integrated in step cli.
|
||||
|
||||
OPTIONS`)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintln(os.Stderr, `
|
||||
COPYRIGHT
|
||||
|
||||
(c) 2018-2021 Smallstep Labs, Inc.`)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func checkCertificate(k kms.KeyManager, rawuri string) {
|
||||
if cm, ok := k.(kms.CertificateManager); ok {
|
||||
if _, err := cm.LoadCertificate(&apiv1.LoadCertificateRequest{
|
||||
Name: rawuri,
|
||||
}); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "⚠️ Your PKCS #11 module already has a certificate on %s.\n", rawuri)
|
||||
fmt.Fprintln(os.Stderr, " If you want to delete it and start fresh, use `--force`.")
|
||||
_ = k.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkObject(k kms.KeyManager, rawuri string) {
|
||||
if _, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{
|
||||
Name: rawuri,
|
||||
}); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "⚠️ Your PKCS #11 module already has a key on %s.\n", rawuri)
|
||||
fmt.Fprintln(os.Stderr, " If you want to delete it and start fresh, use `--force`.")
|
||||
_ = k.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func createPKI(k kms.KeyManager, c Config) error {
|
||||
var err error
|
||||
ui.Println("Creating PKI ...")
|
||||
now := time.Now()
|
||||
|
||||
// Root Certificate
|
||||
var signer crypto.Signer
|
||||
var root *x509.Certificate
|
||||
if c.RootFile != "" && c.KeyFile != "" {
|
||||
root, err = pemutil.ReadCertificate(c.RootFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := pemutil.Read(c.KeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if signer, ok = key.(crypto.Signer); !ok {
|
||||
return errors.Errorf("key type '%T' does not implement a signer", key)
|
||||
}
|
||||
} else {
|
||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: c.RootKeyObject,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signer, err = k.CreateSigner(&resp.CreateSignerRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template := &x509.Certificate{
|
||||
IsCA: true,
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
MaxPathLen: 1,
|
||||
MaxPathLenZero: false,
|
||||
Issuer: pkix.Name{CommonName: "PKCS #11 Smallstep Root"},
|
||||
Subject: pkix.Name{CommonName: "PKCS #11 Smallstep Root"},
|
||||
SerialNumber: mustSerialNumber(),
|
||||
SubjectKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
AuthorityKeyId: mustSubjectKeyID(resp.PublicKey),
|
||||
}
|
||||
|
||||
b, err := x509.CreateCertificate(rand.Reader, template, template, resp.PublicKey, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
root, err = x509.ParseCertificate(b)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing root certificate")
|
||||
}
|
||||
|
||||
if cm, ok := k.(kms.CertificateManager); ok && !c.NoCerts {
|
||||
if err = cm.StoreCertificate(&apiv1.StoreCertificateRequest{
|
||||
Name: c.RootObject,
|
||||
Certificate: root,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = fileutil.WriteFile("root_ca.crt", pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: b,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui.PrintSelected("Root Key", resp.Name)
|
||||
ui.PrintSelected("Root Certificate", "root_ca.crt")
|
||||
}
|
||||
|
||||
// Intermediate Certificate
|
||||
var keyName string
|
||||
var publicKey crypto.PublicKey
|
||||
if c.RootOnly {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating intermediate key")
|
||||
}
|
||||
|
||||
pass, err := ui.PromptPasswordGenerate("What do you want your password to be? [leave empty and we'll generate one]",
|
||||
ui.WithRichPrompt())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = pemutil.Serialize(priv, pemutil.WithPassword(pass), pemutil.ToFile("intermediate_ca_key", 0600))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
publicKey = priv.Public()
|
||||
} else {
|
||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: c.CrtKeyObject,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
publicKey = resp.PublicKey
|
||||
keyName = resp.Name
|
||||
}
|
||||
|
||||
template := &x509.Certificate{
|
||||
IsCA: true,
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour * 24 * 365 * 10),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
MaxPathLen: 0,
|
||||
MaxPathLenZero: true,
|
||||
Issuer: root.Subject,
|
||||
Subject: pkix.Name{CommonName: "YubiKey Smallstep Intermediate"},
|
||||
SerialNumber: mustSerialNumber(),
|
||||
SubjectKeyId: mustSubjectKeyID(publicKey),
|
||||
}
|
||||
|
||||
b, err := x509.CreateCertificate(rand.Reader, template, root, publicKey, signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
intermediate, err := x509.ParseCertificate(b)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing intermediate certificate")
|
||||
}
|
||||
|
||||
if cm, ok := k.(kms.CertificateManager); ok && !c.NoCerts {
|
||||
if err = cm.StoreCertificate(&apiv1.StoreCertificateRequest{
|
||||
Name: c.CrtObject,
|
||||
Certificate: intermediate,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = fileutil.WriteFile("intermediate_ca.crt", pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: b,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.RootOnly {
|
||||
ui.PrintSelected("Intermediate Key", "intermediate_ca_key")
|
||||
} else {
|
||||
ui.PrintSelected("Intermediate Key", keyName)
|
||||
}
|
||||
|
||||
ui.PrintSelected("Intermediate Certificate", "intermediate_ca.crt")
|
||||
|
||||
if c.SSHHostKeyObject != "" {
|
||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: c.SSHHostKeyObject,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ui.PrintSelected("SSH Host Key", resp.Name)
|
||||
}
|
||||
|
||||
if c.SSHUserKeyObject != "" {
|
||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: c.SSHUserKeyObject,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ui.PrintSelected("SSH User Key", resp.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustSerialNumber() *big.Int {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
sn, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sn
|
||||
}
|
||||
|
||||
func mustSubjectKeyID(key crypto.PublicKey) []byte {
|
||||
b, err := x509.MarshalPKIXPublicKey(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
hash := sha1.Sum(b)
|
||||
return hash[:]
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
// +build cgo
|
||||
|
||||
package pkcs11
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"testing"
|
||||
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
)
|
||||
|
||||
func benchmarkSign(b *testing.B, signer crypto.Signer, opts crypto.SignerOpts) {
|
||||
hash := opts.HashFunc()
|
||||
h := hash.New()
|
||||
h.Write([]byte("buggy-coheir-RUBRIC-rabbet-liberal-eaglet-khartoum-stagger"))
|
||||
digest := h.Sum(nil)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
signer.Sign(rand.Reader, digest, opts)
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkSignRSA(b *testing.B) {
|
||||
k := setupPKCS11(b)
|
||||
signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{
|
||||
SigningKey: "pkcs11:id=7371;object=rsa-key",
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatalf("PKCS11.CreateSigner() error = %v", err)
|
||||
}
|
||||
benchmarkSign(b, signer, crypto.SHA256)
|
||||
}
|
||||
|
||||
func BenchmarkSignRSAPSS(b *testing.B) {
|
||||
k := setupPKCS11(b)
|
||||
signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{
|
||||
SigningKey: "pkcs11:id=7372;object=rsa-pss-key",
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatalf("PKCS11.CreateSigner() error = %v", err)
|
||||
}
|
||||
benchmarkSign(b, signer, &rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthEqualsHash,
|
||||
Hash: crypto.SHA256,
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSignP256(b *testing.B) {
|
||||
k := setupPKCS11(b)
|
||||
signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{
|
||||
SigningKey: "pkcs11:id=7373;object=ecdsa-p256-key",
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatalf("PKCS11.CreateSigner() error = %v", err)
|
||||
}
|
||||
benchmarkSign(b, signer, crypto.SHA256)
|
||||
}
|
||||
|
||||
func BenchmarkSignP384(b *testing.B) {
|
||||
k := setupPKCS11(b)
|
||||
signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{
|
||||
SigningKey: "pkcs11:id=7374;object=ecdsa-p384-key",
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatalf("PKCS11.CreateSigner() error = %v", err)
|
||||
}
|
||||
benchmarkSign(b, signer, crypto.SHA384)
|
||||
}
|
||||
|
||||
func BenchmarkSignP521(b *testing.B) {
|
||||
k := setupPKCS11(b)
|
||||
signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{
|
||||
SigningKey: "pkcs11:id=7375;object=ecdsa-p521-key",
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatalf("PKCS11.CreateSigner() error = %v", err)
|
||||
}
|
||||
benchmarkSign(b, signer, crypto.SHA512)
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
// +build opensc
|
||||
|
||||
package pkcs11
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/ThalesIgnite/crypto11"
|
||||
)
|
||||
|
||||
var softHSM2Once sync.Once
|
||||
|
||||
// mustPKCS11 configures a *PKCS11 KMS to be used with OpenSC, using for example
|
||||
// a Nitrokey HSM. To initialize these tests we should run:
|
||||
// sc-hsm-tool --initialize --so-pin 3537363231383830 --pin 123456
|
||||
// Or:
|
||||
// pkcs11-tool --module /usr/local/lib/opensc-pkcs11.so \
|
||||
// --init-token --init-pin \
|
||||
// --so-pin=3537363231383830 --new-pin=123456 --pin=123456 \
|
||||
// --label="pkcs11-test"
|
||||
func mustPKCS11(t TBTesting) *PKCS11 {
|
||||
t.Helper()
|
||||
testModule = "OpenSC"
|
||||
if runtime.GOARCH != "amd64" {
|
||||
t.Fatalf("opensc test skipped on %s:%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
var path string
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
path = "/usr/local/lib/opensc-pkcs11.so"
|
||||
case "linux":
|
||||
path = "/usr/local/lib/opensc-pkcs11.so"
|
||||
default:
|
||||
t.Skipf("opensc test skipped on %s", runtime.GOOS)
|
||||
return nil
|
||||
}
|
||||
var zero int
|
||||
p11, err := crypto11.Configure(&crypto11.Config{
|
||||
Path: path,
|
||||
SlotNumber: &zero,
|
||||
Pin: "123456",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to configure opensc on %s: %v", runtime.GOOS, err)
|
||||
}
|
||||
|
||||
k := &PKCS11{
|
||||
p11: p11,
|
||||
}
|
||||
|
||||
// Setup
|
||||
softHSM2Once.Do(func() {
|
||||
teardown(t, k)
|
||||
setup(t, k)
|
||||
})
|
||||
|
||||
return k
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
// +build cgo,!softhsm2,!yubihsm2,!opensc
|
||||
|
||||
package pkcs11
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/ThalesIgnite/crypto11"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func mustPKCS11(t TBTesting) *PKCS11 {
|
||||
t.Helper()
|
||||
testModule = "Golang crypto"
|
||||
k := &PKCS11{
|
||||
p11: &stubPKCS11{
|
||||
signerIndex: make(map[keyType]int),
|
||||
certIndex: make(map[keyType]int),
|
||||
},
|
||||
}
|
||||
for i := range testCerts {
|
||||
testCerts[i].Certificates = nil
|
||||
}
|
||||
teardown(t, k)
|
||||
setup(t, k)
|
||||
return k
|
||||
}
|
||||
|
||||
type keyType struct {
|
||||
id string
|
||||
label string
|
||||
serial string
|
||||
}
|
||||
|
||||
func newKey(id, label []byte, serial *big.Int) keyType {
|
||||
var serialString string
|
||||
if serial != nil {
|
||||
serialString = serial.String()
|
||||
}
|
||||
return keyType{
|
||||
id: string(id),
|
||||
label: string(label),
|
||||
serial: serialString,
|
||||
}
|
||||
}
|
||||
|
||||
type stubPKCS11 struct {
|
||||
signers []crypto11.Signer
|
||||
certs []*x509.Certificate
|
||||
signerIndex map[keyType]int
|
||||
certIndex map[keyType]int
|
||||
}
|
||||
|
||||
func (s *stubPKCS11) FindKeyPair(id, label []byte) (crypto11.Signer, error) {
|
||||
if id == nil && label == nil {
|
||||
return nil, errors.New("id and label cannot both be nil")
|
||||
}
|
||||
if i, ok := s.signerIndex[newKey(id, label, nil)]; ok {
|
||||
return s.signers[i], nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *stubPKCS11) FindCertificate(id, label []byte, serial *big.Int) (*x509.Certificate, error) {
|
||||
if id == nil && label == nil && serial == nil {
|
||||
return nil, errors.New("id, label and serial cannot both be nil")
|
||||
}
|
||||
if i, ok := s.certIndex[newKey(id, label, serial)]; ok {
|
||||
return s.certs[i], nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *stubPKCS11) ImportCertificateWithLabel(id, label []byte, cert *x509.Certificate) error {
|
||||
switch {
|
||||
case id == nil && label == nil:
|
||||
return errors.New("id and label cannot both be nil")
|
||||
case cert == nil:
|
||||
return errors.New("certificate cannot be nil")
|
||||
}
|
||||
|
||||
i := len(s.certs)
|
||||
s.certs = append(s.certs, cert)
|
||||
s.certIndex[newKey(id, label, cert.SerialNumber)] = i
|
||||
s.certIndex[newKey(id, nil, nil)] = i
|
||||
s.certIndex[newKey(nil, label, nil)] = i
|
||||
s.certIndex[newKey(nil, nil, cert.SerialNumber)] = i
|
||||
s.certIndex[newKey(id, label, nil)] = i
|
||||
s.certIndex[newKey(id, nil, cert.SerialNumber)] = i
|
||||
s.certIndex[newKey(nil, label, cert.SerialNumber)] = i
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubPKCS11) DeleteCertificate(id, label []byte, serial *big.Int) error {
|
||||
if id == nil && label == nil && serial == nil {
|
||||
return errors.New("id, label and serial cannot both be nil")
|
||||
}
|
||||
if i, ok := s.certIndex[newKey(id, label, serial)]; ok {
|
||||
s.certs[i] = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubPKCS11) GenerateRSAKeyPairWithLabel(id, label []byte, bits int) (crypto11.SignerDecrypter, error) {
|
||||
if id == nil && label == nil {
|
||||
return nil, errors.New("id and label cannot both be nil")
|
||||
}
|
||||
p, err := rsa.GenerateKey(rand.Reader, bits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k := &privateKey{
|
||||
Signer: p,
|
||||
index: len(s.signers),
|
||||
stub: s,
|
||||
}
|
||||
s.signers = append(s.signers, k)
|
||||
s.signerIndex[newKey(id, label, nil)] = k.index
|
||||
s.signerIndex[newKey(id, nil, nil)] = k.index
|
||||
s.signerIndex[newKey(nil, label, nil)] = k.index
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func (s *stubPKCS11) GenerateECDSAKeyPairWithLabel(id, label []byte, curve elliptic.Curve) (crypto11.Signer, error) {
|
||||
if id == nil && label == nil {
|
||||
return nil, errors.New("id and label cannot both be nil")
|
||||
}
|
||||
p, err := ecdsa.GenerateKey(curve, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k := &privateKey{
|
||||
Signer: p,
|
||||
index: len(s.signers),
|
||||
stub: s,
|
||||
}
|
||||
s.signers = append(s.signers, k)
|
||||
s.signerIndex[newKey(id, label, nil)] = k.index
|
||||
s.signerIndex[newKey(id, nil, nil)] = k.index
|
||||
s.signerIndex[newKey(nil, label, nil)] = k.index
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func (s *stubPKCS11) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type privateKey struct {
|
||||
crypto.Signer
|
||||
index int
|
||||
stub *stubPKCS11
|
||||
}
|
||||
|
||||
func (s *privateKey) Delete() error {
|
||||
s.stub.signers[s.index] = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *privateKey) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error) {
|
||||
k, ok := s.Signer.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("key is not an rsa key")
|
||||
}
|
||||
return k.Decrypt(rand, msg, opts)
|
||||
}
|
@ -0,0 +1,354 @@
|
||||
// +build cgo
|
||||
|
||||
package pkcs11
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/elliptic"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/ThalesIgnite/crypto11"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/certificates/kms/uri"
|
||||
)
|
||||
|
||||
// Scheme is the scheme used in uris.
|
||||
const Scheme = "pkcs11"
|
||||
|
||||
// DefaultRSASize is the number of bits of a new RSA key if no size has been
|
||||
// specified.
|
||||
const DefaultRSASize = 3072
|
||||
|
||||
// P11 defines the methods on crypto11.Context that this package will use. This
|
||||
// interface will be used for unit testing.
|
||||
type P11 interface {
|
||||
FindKeyPair(id, label []byte) (crypto11.Signer, error)
|
||||
FindCertificate(id, label []byte, serial *big.Int) (*x509.Certificate, error)
|
||||
ImportCertificateWithLabel(id, label []byte, cert *x509.Certificate) error
|
||||
DeleteCertificate(id, label []byte, serial *big.Int) error
|
||||
GenerateRSAKeyPairWithLabel(id, label []byte, bits int) (crypto11.SignerDecrypter, error)
|
||||
GenerateECDSAKeyPairWithLabel(id, label []byte, curve elliptic.Curve) (crypto11.Signer, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
var p11Configure = func(config *crypto11.Config) (P11, error) {
|
||||
return crypto11.Configure(config)
|
||||
}
|
||||
|
||||
// PKCS11 is the implementation of a KMS using the PKCS #11 standard.
|
||||
type PKCS11 struct {
|
||||
p11 P11
|
||||
closed sync.Once
|
||||
}
|
||||
|
||||
// New returns a new PKCS11 KMS.
|
||||
func New(ctx context.Context, opts apiv1.Options) (*PKCS11, error) {
|
||||
var config crypto11.Config
|
||||
if opts.URI != "" {
|
||||
u, err := uri.ParseWithScheme(Scheme, opts.URI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.Pin = u.Pin()
|
||||
config.Path = u.Get("module-path")
|
||||
config.TokenLabel = u.Get("token")
|
||||
config.TokenSerial = u.Get("serial")
|
||||
if v := u.Get("slot-id"); v != "" {
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "kms uri 'slot-id' is not valid")
|
||||
}
|
||||
config.SlotNumber = &n
|
||||
}
|
||||
}
|
||||
if config.Pin == "" && opts.Pin != "" {
|
||||
config.Pin = opts.Pin
|
||||
}
|
||||
|
||||
switch {
|
||||
case config.Path == "":
|
||||
return nil, errors.New("kms uri 'module-path' are required")
|
||||
case config.TokenLabel == "" && config.TokenSerial == "" && config.SlotNumber == nil:
|
||||
return nil, errors.New("kms uri 'token', 'serial' or 'slot-id' are required")
|
||||
case config.Pin == "":
|
||||
return nil, errors.New("kms 'pin' cannot be empty")
|
||||
case config.TokenLabel != "" && config.TokenSerial != "":
|
||||
return nil, errors.New("kms uri 'token' and 'serial' are mutually exclusive")
|
||||
case config.TokenLabel != "" && config.SlotNumber != nil:
|
||||
return nil, errors.New("kms uri 'token' and 'slot-id' are mutually exclusive")
|
||||
case config.TokenSerial != "" && config.SlotNumber != nil:
|
||||
return nil, errors.New("kms uri 'serial' and 'slot-id' are mutually exclusive")
|
||||
}
|
||||
|
||||
p11, err := p11Configure(&config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error initializing PKCS#11")
|
||||
}
|
||||
|
||||
return &PKCS11{
|
||||
p11: p11,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
apiv1.Register(apiv1.PKCS11, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
|
||||
return New(ctx, opts)
|
||||
})
|
||||
}
|
||||
|
||||
// GetPublicKey returns the public key ....
|
||||
func (k *PKCS11) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
|
||||
if req.Name == "" {
|
||||
return nil, errors.New("getPublicKeyRequest 'name' cannot be empty")
|
||||
}
|
||||
|
||||
signer, err := findSigner(k.p11, req.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getPublicKey failed")
|
||||
}
|
||||
|
||||
return signer.Public(), nil
|
||||
}
|
||||
|
||||
// CreateKey generates a new key in the PKCS#11 module and returns the public key.
|
||||
func (k *PKCS11) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
|
||||
switch {
|
||||
case req.Name == "":
|
||||
return nil, errors.New("createKeyRequest 'name' cannot be empty")
|
||||
case req.Bits < 0:
|
||||
return nil, errors.New("createKeyRequest 'bits' cannot be negative")
|
||||
}
|
||||
|
||||
signer, err := generateKey(k.p11, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "createKey failed")
|
||||
}
|
||||
|
||||
return &apiv1.CreateKeyResponse{
|
||||
Name: req.Name,
|
||||
PublicKey: signer.Public(),
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: req.Name,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateSigner creates a signer using the key present in the PKCS#11 MODULE signature
|
||||
// slot.
|
||||
func (k *PKCS11) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
|
||||
switch {
|
||||
case req.SigningKey == "":
|
||||
return nil, errors.New("createSignerRequest 'signingKey' cannot be empty")
|
||||
}
|
||||
|
||||
signer, err := findSigner(k.p11, req.SigningKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "createSigner failed")
|
||||
}
|
||||
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
// LoadCertificate implements kms.CertificateManager and loads a certificate
|
||||
// from the YubiKey.
|
||||
func (k *PKCS11) LoadCertificate(req *apiv1.LoadCertificateRequest) (*x509.Certificate, error) {
|
||||
if req.Name == "" {
|
||||
return nil, errors.New("loadCertificateRequest 'name' cannot be nil")
|
||||
}
|
||||
cert, err := findCertificate(k.p11, req.Name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "loadCertificate failed")
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// StoreCertificate implements kms.CertificateManager and stores a certificate
|
||||
// in the YubiKey.
|
||||
func (k *PKCS11) StoreCertificate(req *apiv1.StoreCertificateRequest) error {
|
||||
switch {
|
||||
case req.Name == "":
|
||||
return errors.New("storeCertificateRequest 'name' cannot be empty")
|
||||
case req.Certificate == nil:
|
||||
return errors.New("storeCertificateRequest 'Certificate' cannot be nil")
|
||||
}
|
||||
|
||||
id, object, err := parseObject(req.Name)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "storeCertificate failed")
|
||||
}
|
||||
|
||||
cert, err := k.p11.FindCertificate(id, object, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "storeCertificate failed")
|
||||
}
|
||||
if cert != nil {
|
||||
return errors.Wrap(apiv1.ErrAlreadyExists{
|
||||
Message: req.Name + " already exists",
|
||||
}, "storeCertificate failed")
|
||||
}
|
||||
|
||||
if err := k.p11.ImportCertificateWithLabel(id, object, req.Certificate); err != nil {
|
||||
return errors.Wrap(err, "storeCertificate failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteKey is a utility function to delete a key given an uri.
|
||||
func (k *PKCS11) DeleteKey(uri string) error {
|
||||
id, object, err := parseObject(uri)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "deleteKey failed")
|
||||
}
|
||||
signer, err := k.p11.FindKeyPair(id, object)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "deleteKey failed")
|
||||
}
|
||||
if signer == nil {
|
||||
return nil
|
||||
}
|
||||
if err := signer.Delete(); err != nil {
|
||||
return errors.Wrap(err, "deleteKey failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteCertificate is a utility function to delete a certificate given an uri.
|
||||
func (k *PKCS11) DeleteCertificate(uri string) error {
|
||||
id, object, err := parseObject(uri)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "deleteCertificate failed")
|
||||
}
|
||||
if err := k.p11.DeleteCertificate(id, object, nil); err != nil {
|
||||
return errors.Wrap(err, "deleteCertificate failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close releases the connection to the PKCS#11 module.
|
||||
func (k *PKCS11) Close() (err error) {
|
||||
k.closed.Do(func() {
|
||||
err = errors.Wrap(k.p11.Close(), "error closing pkcs#11 context")
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func toByte(s string) []byte {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
return []byte(s)
|
||||
}
|
||||
|
||||
func parseObject(rawuri string) ([]byte, []byte, error) {
|
||||
u, err := uri.ParseWithScheme(Scheme, rawuri)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
id := u.GetEncoded("id")
|
||||
object := u.Get("object")
|
||||
if len(id) == 0 && object == "" {
|
||||
return nil, nil, errors.Errorf("key with uri %s is not valid, id or object are required", rawuri)
|
||||
}
|
||||
|
||||
return id, toByte(object), nil
|
||||
}
|
||||
|
||||
func generateKey(ctx P11, req *apiv1.CreateKeyRequest) (crypto11.Signer, error) {
|
||||
id, object, err := parseObject(req.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signer, err := ctx.FindKeyPair(id, object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if signer != nil {
|
||||
return nil, apiv1.ErrAlreadyExists{
|
||||
Message: req.Name + " already exists",
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce the use of both id and labels. This is not strictly necessary in
|
||||
// PKCS #11, but it's a good practice.
|
||||
if len(id) == 0 || len(object) == 0 {
|
||||
return nil, errors.Errorf("key with uri %s is not valid, id and object are required", req.Name)
|
||||
}
|
||||
|
||||
bits := req.Bits
|
||||
if bits == 0 {
|
||||
bits = DefaultRSASize
|
||||
}
|
||||
|
||||
switch req.SignatureAlgorithm {
|
||||
case apiv1.UnspecifiedSignAlgorithm:
|
||||
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P256())
|
||||
case apiv1.SHA256WithRSA, apiv1.SHA384WithRSA, apiv1.SHA512WithRSA:
|
||||
return ctx.GenerateRSAKeyPairWithLabel(id, object, bits)
|
||||
case apiv1.SHA256WithRSAPSS, apiv1.SHA384WithRSAPSS, apiv1.SHA512WithRSAPSS:
|
||||
return ctx.GenerateRSAKeyPairWithLabel(id, object, bits)
|
||||
case apiv1.ECDSAWithSHA256:
|
||||
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P256())
|
||||
case apiv1.ECDSAWithSHA384:
|
||||
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P384())
|
||||
case apiv1.ECDSAWithSHA512:
|
||||
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P521())
|
||||
case apiv1.PureEd25519:
|
||||
return nil, fmt.Errorf("signature algorithm %s is not supported", req.SignatureAlgorithm)
|
||||
default:
|
||||
return nil, fmt.Errorf("signature algorithm %s is not supported", req.SignatureAlgorithm)
|
||||
}
|
||||
}
|
||||
|
||||
func findSigner(ctx P11, rawuri string) (crypto11.Signer, error) {
|
||||
id, object, err := parseObject(rawuri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signer, err := ctx.FindKeyPair(id, object)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error finding key with uri %s", rawuri)
|
||||
}
|
||||
if signer == nil {
|
||||
return nil, errors.Errorf("key with uri %s not found", rawuri)
|
||||
}
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
func findCertificate(ctx P11, rawuri string) (*x509.Certificate, error) {
|
||||
u, err := uri.ParseWithScheme(Scheme, rawuri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, object, serial := u.GetEncoded("id"), u.Get("object"), u.Get("serial")
|
||||
if len(id) == 0 && object == "" && serial == "" {
|
||||
return nil, errors.Errorf("key with uri %s is not valid, id, object or serial are required", rawuri)
|
||||
}
|
||||
|
||||
var serialNumber *big.Int
|
||||
if serial != "" {
|
||||
b, err := hex.DecodeString(serial)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("key with uri %s is not valid, failed to decode serial", rawuri)
|
||||
}
|
||||
serialNumber = new(big.Int).SetBytes(b)
|
||||
}
|
||||
|
||||
cert, err := ctx.FindCertificate(id, toByte(object), serialNumber)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error finding certificate with uri %s", rawuri)
|
||||
}
|
||||
if cert == nil {
|
||||
return nil, errors.Errorf("certificate with uri %s not found", rawuri)
|
||||
}
|
||||
return cert, nil
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
// +build !cgo
|
||||
|
||||
package pkcs11
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
)
|
||||
|
||||
var errUnsupported error
|
||||
|
||||
func init() {
|
||||
name := filepath.Base(os.Args[0])
|
||||
errUnsupported = errors.Errorf("unsupported kms type 'pkcs11': %s is compiled without cgo support", name)
|
||||
|
||||
apiv1.Register(apiv1.PKCS11, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
|
||||
return nil, errUnsupported
|
||||
})
|
||||
}
|
||||
|
||||
// PKCS11 is the implementation of a KMS using the PKCS #11 standard.
|
||||
type PKCS11 struct{}
|
||||
|
||||
// New implements the kms.KeyManager interface and without CGO will always
|
||||
// return an error.
|
||||
func New(ctx context.Context, opts apiv1.Options) (*PKCS11, error) {
|
||||
return nil, errUnsupported
|
||||
}
|
||||
|
||||
// GetPublicKey implements the kms.KeyManager interface and without CGO will always
|
||||
// return an error.
|
||||
func (*PKCS11) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
|
||||
return nil, errUnsupported
|
||||
}
|
||||
|
||||
// CreateKey implements the kms.KeyManager interface and without CGO will always
|
||||
// return an error.
|
||||
func (*PKCS11) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
|
||||
return nil, errUnsupported
|
||||
}
|
||||
|
||||
// CreateSigner implements the kms.KeyManager interface and without CGO will always
|
||||
// return an error.
|
||||
func (*PKCS11) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
|
||||
return nil, errUnsupported
|
||||
}
|
||||
|
||||
// Close implements the kms.KeyManager interface and without CGO will always
|
||||
// return an error.
|
||||
func (*PKCS11) Close() error {
|
||||
return errUnsupported
|
||||
}
|
@ -0,0 +1,729 @@
|
||||
// +build cgo
|
||||
|
||||
package pkcs11
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ThalesIgnite/crypto11"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
"golang.org/x/crypto/cryptobyte/asn1"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
tmp := p11Configure
|
||||
t.Cleanup(func() {
|
||||
p11Configure = tmp
|
||||
})
|
||||
|
||||
k := mustPKCS11(t)
|
||||
t.Cleanup(func() {
|
||||
k.Close()
|
||||
})
|
||||
p11Configure = func(config *crypto11.Config) (P11, error) {
|
||||
if strings.Contains(config.Path, "fail") {
|
||||
return nil, errors.New("an error")
|
||||
}
|
||||
return k.p11, nil
|
||||
}
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
opts apiv1.Options
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *PKCS11
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test?pin-value=password",
|
||||
}}, k, false},
|
||||
{"ok with serial", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;serial=0123456789?pin-value=password",
|
||||
}}, k, false},
|
||||
{"ok with slot-id", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;slot-id=0?pin-value=password",
|
||||
}}, k, false},
|
||||
{"ok with pin", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test",
|
||||
Pin: "passowrd",
|
||||
}}, k, false},
|
||||
{"fail missing module", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "pkcs11:token=pkcs11-test",
|
||||
Pin: "passowrd",
|
||||
}}, nil, true},
|
||||
{"fail missing pin", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test",
|
||||
}}, nil, true},
|
||||
{"fail missing token/serial/slot-id", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so",
|
||||
Pin: "passowrd",
|
||||
}}, nil, true},
|
||||
{"fail token+serial+slot-id", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test;serial=0123456789;slot-id=0",
|
||||
Pin: "passowrd",
|
||||
}}, nil, true},
|
||||
{"fail token+serial", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test;serial=0123456789",
|
||||
Pin: "passowrd",
|
||||
}}, nil, true},
|
||||
{"fail token+slot-id", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test;slot-id=0",
|
||||
Pin: "passowrd",
|
||||
}}, nil, true},
|
||||
{"fail serial+slot-id", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;serial=0123456789;slot-id=0",
|
||||
Pin: "passowrd",
|
||||
}}, nil, true},
|
||||
{"fail slot-id", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;slot-id=x?pin-value=password",
|
||||
}}, nil, true},
|
||||
{"fail scheme", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "foo:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test?pin-value=password",
|
||||
}}, nil, true},
|
||||
{"fail configure", args{context.Background(), apiv1.Options{
|
||||
Type: "pkcs11",
|
||||
URI: "pkcs11:module-path=/usr/local/lib/fail.so;token=pkcs11-test?pin-value=password",
|
||||
}}, nil, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := New(tt.args.ctx, tt.args.opts)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("New() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPKCS11_GetPublicKey(t *testing.T) {
|
||||
k := setupPKCS11(t)
|
||||
type args struct {
|
||||
req *apiv1.GetPublicKeyRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want crypto.PublicKey
|
||||
wantErr bool
|
||||
}{
|
||||
{"RSA", args{&apiv1.GetPublicKeyRequest{
|
||||
Name: "pkcs11:id=7371;object=rsa-key",
|
||||
}}, &rsa.PublicKey{}, false},
|
||||
{"RSA by id", args{&apiv1.GetPublicKeyRequest{
|
||||
Name: "pkcs11:id=7371",
|
||||
}}, &rsa.PublicKey{}, false},
|
||||
{"RSA by label", args{&apiv1.GetPublicKeyRequest{
|
||||
Name: "pkcs11:object=rsa-key",
|
||||
}}, &rsa.PublicKey{}, false},
|
||||
{"ECDSA", args{&apiv1.GetPublicKeyRequest{
|
||||
Name: "pkcs11:id=7373;object=ecdsa-p256-key",
|
||||
}}, &ecdsa.PublicKey{}, false},
|
||||
{"ECDSA by id", args{&apiv1.GetPublicKeyRequest{
|
||||
Name: "pkcs11:id=7373",
|
||||
}}, &ecdsa.PublicKey{}, false},
|
||||
{"ECDSA by label", args{&apiv1.GetPublicKeyRequest{
|
||||
Name: "pkcs11:object=ecdsa-p256-key",
|
||||
}}, &ecdsa.PublicKey{}, false},
|
||||
{"fail name", args{&apiv1.GetPublicKeyRequest{
|
||||
Name: "",
|
||||
}}, nil, true},
|
||||
{"fail uri", args{&apiv1.GetPublicKeyRequest{
|
||||
Name: "https:id=9999;object=https",
|
||||
}}, nil, true},
|
||||
{"fail missing", args{&apiv1.GetPublicKeyRequest{
|
||||
Name: "pkcs11:id=9999;object=rsa-key",
|
||||
}}, nil, true},
|
||||
{"fail FindKeyPair", args{&apiv1.GetPublicKeyRequest{
|
||||
Name: "pkcs11:foo=bar",
|
||||
}}, nil, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := k.GetPublicKey(tt.args.req)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("PKCS11.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if reflect.TypeOf(got) != reflect.TypeOf(tt.want) {
|
||||
t.Errorf("PKCS11.GetPublicKey() = %T, want %T", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPKCS11_CreateKey(t *testing.T) {
|
||||
k := setupPKCS11(t)
|
||||
|
||||
// Make sure to delete the created key
|
||||
k.DeleteKey(testObject)
|
||||
|
||||
type args struct {
|
||||
req *apiv1.CreateKeyRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *apiv1.CreateKeyResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{"default", args{&apiv1.CreateKeyRequest{
|
||||
Name: testObject,
|
||||
}}, &apiv1.CreateKeyResponse{
|
||||
Name: testObject,
|
||||
PublicKey: &ecdsa.PublicKey{},
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: testObject,
|
||||
},
|
||||
}, false},
|
||||
{"RSA SHA256WithRSA", args{&apiv1.CreateKeyRequest{
|
||||
Name: testObject,
|
||||
SignatureAlgorithm: apiv1.SHA256WithRSA,
|
||||
}}, &apiv1.CreateKeyResponse{
|
||||
Name: testObject,
|
||||
PublicKey: &rsa.PublicKey{},
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: testObject,
|
||||
},
|
||||
}, false},
|
||||
{"RSA SHA384WithRSA", args{&apiv1.CreateKeyRequest{
|
||||
Name: testObject,
|
||||
SignatureAlgorithm: apiv1.SHA384WithRSA,
|
||||
}}, &apiv1.CreateKeyResponse{
|
||||
Name: testObject,
|
||||
PublicKey: &rsa.PublicKey{},
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: testObject,
|
||||
},
|
||||
}, false},
|
||||
{"RSA SHA512WithRSA", args{&apiv1.CreateKeyRequest{
|
||||
Name: testObject,
|
||||
SignatureAlgorithm: apiv1.SHA512WithRSA,
|
||||
}}, &apiv1.CreateKeyResponse{
|
||||
Name: testObject,
|
||||
PublicKey: &rsa.PublicKey{},
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: testObject,
|
||||
},
|
||||
}, false},
|
||||
{"RSA SHA256WithRSAPSS", args{&apiv1.CreateKeyRequest{
|
||||
Name: testObject,
|
||||
SignatureAlgorithm: apiv1.SHA256WithRSAPSS,
|
||||
}}, &apiv1.CreateKeyResponse{
|
||||
Name: testObject,
|
||||
PublicKey: &rsa.PublicKey{},
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: testObject,
|
||||
},
|
||||
}, false},
|
||||
{"RSA SHA384WithRSAPSS", args{&apiv1.CreateKeyRequest{
|
||||
Name: testObject,
|
||||
SignatureAlgorithm: apiv1.SHA384WithRSAPSS,
|
||||
}}, &apiv1.CreateKeyResponse{
|
||||
Name: testObject,
|
||||
PublicKey: &rsa.PublicKey{},
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: testObject,
|
||||
},
|
||||
}, false},
|
||||
{"RSA SHA512WithRSAPSS", args{&apiv1.CreateKeyRequest{
|
||||
Name: testObject,
|
||||
SignatureAlgorithm: apiv1.SHA512WithRSAPSS,
|
||||
}}, &apiv1.CreateKeyResponse{
|
||||
Name: testObject,
|
||||
PublicKey: &rsa.PublicKey{},
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: testObject,
|
||||
},
|
||||
}, false},
|
||||
{"RSA 2048", args{&apiv1.CreateKeyRequest{
|
||||
Name: testObject,
|
||||
SignatureAlgorithm: apiv1.SHA256WithRSA,
|
||||
Bits: 2048,
|
||||
}}, &apiv1.CreateKeyResponse{
|
||||
Name: testObject,
|
||||
PublicKey: &rsa.PublicKey{},
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: testObject,
|
||||
},
|
||||
}, false},
|
||||
{"RSA 4096", args{&apiv1.CreateKeyRequest{
|
||||
Name: testObject,
|
||||
SignatureAlgorithm: apiv1.SHA256WithRSA,
|
||||
Bits: 4096,
|
||||
}}, &apiv1.CreateKeyResponse{
|
||||
Name: testObject,
|
||||
PublicKey: &rsa.PublicKey{},
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: testObject,
|
||||
},
|
||||
}, false},
|
||||
{"ECDSA P256", args{&apiv1.CreateKeyRequest{
|
||||
Name: testObject,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
}}, &apiv1.CreateKeyResponse{
|
||||
Name: testObject,
|
||||
PublicKey: &ecdsa.PublicKey{},
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: testObject,
|
||||
},
|
||||
}, false},
|
||||
{"ECDSA P384", args{&apiv1.CreateKeyRequest{
|
||||
Name: testObject,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA384,
|
||||
}}, &apiv1.CreateKeyResponse{
|
||||
Name: testObject,
|
||||
PublicKey: &ecdsa.PublicKey{},
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: testObject,
|
||||
},
|
||||
}, false},
|
||||
{"ECDSA P521", args{&apiv1.CreateKeyRequest{
|
||||
Name: testObject,
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA512,
|
||||
}}, &apiv1.CreateKeyResponse{
|
||||
Name: testObject,
|
||||
PublicKey: &ecdsa.PublicKey{},
|
||||
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||
SigningKey: testObject,
|
||||
},
|
||||
}, false},
|
||||
{"fail name", args{&apiv1.CreateKeyRequest{
|
||||
Name: "",
|
||||
}}, nil, true},
|
||||
{"fail no id", args{&apiv1.CreateKeyRequest{
|
||||
Name: "pkcs11:object=create-key",
|
||||
}}, nil, true},
|
||||
{"fail no object", args{&apiv1.CreateKeyRequest{
|
||||
Name: "pkcs11:id=9999",
|
||||
}}, nil, true},
|
||||
{"fail schema", args{&apiv1.CreateKeyRequest{
|
||||
Name: "pkcs12:id=9999;object=create-key",
|
||||
}}, nil, true},
|
||||
{"fail bits", args{&apiv1.CreateKeyRequest{
|
||||
Name: "pkcs11:id=9999;object=create-key",
|
||||
Bits: -1,
|
||||
SignatureAlgorithm: apiv1.SHA256WithRSAPSS,
|
||||
}}, nil, true},
|
||||
{"fail ed25519", args{&apiv1.CreateKeyRequest{
|
||||
Name: "pkcs11:id=9999;object=create-key",
|
||||
SignatureAlgorithm: apiv1.PureEd25519,
|
||||
}}, nil, true},
|
||||
{"fail unknown", args{&apiv1.CreateKeyRequest{
|
||||
Name: "pkcs11:id=9999;object=create-key",
|
||||
SignatureAlgorithm: apiv1.SignatureAlgorithm(100),
|
||||
}}, nil, true},
|
||||
{"fail FindKeyPair", args{&apiv1.CreateKeyRequest{
|
||||
Name: "pkcs11:foo=bar",
|
||||
SignatureAlgorithm: apiv1.SHA256WithRSAPSS,
|
||||
}}, nil, true},
|
||||
{"fail already exists", args{&apiv1.CreateKeyRequest{
|
||||
Name: "pkcs11:id=7373;object=ecdsa-p256-key",
|
||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||
}}, nil, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := k.CreateKey(tt.args.req)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("PKCS11.CreateKey() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != nil {
|
||||
got.PublicKey = tt.want.PublicKey
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("PKCS11.CreateKey() = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got != nil {
|
||||
if err := k.DeleteKey(got.Name); err != nil {
|
||||
t.Errorf("PKCS11.DeleteKey() error = %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPKCS11_CreateSigner(t *testing.T) {
|
||||
k := setupPKCS11(t)
|
||||
data := []byte("buggy-coheir-RUBRIC-rabbet-liberal-eaglet-khartoum-stagger")
|
||||
|
||||
// VerifyASN1 verifies the ASN.1 encoded signature, sig, of hash using the
|
||||
// public key, pub. Its return value records whether the signature is valid.
|
||||
verifyASN1 := func(pub *ecdsa.PublicKey, hash, sig []byte) bool {
|
||||
var (
|
||||
r, s = &big.Int{}, &big.Int{}
|
||||
inner cryptobyte.String
|
||||
)
|
||||
input := cryptobyte.String(sig)
|
||||
if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
|
||||
!input.Empty() ||
|
||||
!inner.ReadASN1Integer(r) ||
|
||||
!inner.ReadASN1Integer(s) ||
|
||||
!inner.Empty() {
|
||||
return false
|
||||
}
|
||||
return ecdsa.Verify(pub, hash, r, s)
|
||||
}
|
||||
|
||||
type args struct {
|
||||
req *apiv1.CreateSignerRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
algorithm apiv1.SignatureAlgorithm
|
||||
signerOpts crypto.SignerOpts
|
||||
wantErr bool
|
||||
}{
|
||||
// SoftHSM2
|
||||
{"RSA", args{&apiv1.CreateSignerRequest{
|
||||
SigningKey: "pkcs11:id=7371;object=rsa-key",
|
||||
}}, apiv1.SHA256WithRSA, crypto.SHA256, false},
|
||||
{"RSA PSS", args{&apiv1.CreateSignerRequest{
|
||||
SigningKey: "pkcs11:id=7372;object=rsa-pss-key",
|
||||
}}, apiv1.SHA256WithRSAPSS, &rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthEqualsHash,
|
||||
Hash: crypto.SHA256,
|
||||
}, false},
|
||||
{"ECDSA P256", args{&apiv1.CreateSignerRequest{
|
||||
SigningKey: "pkcs11:id=7373;object=ecdsa-p256-key",
|
||||
}}, apiv1.ECDSAWithSHA256, crypto.SHA256, false},
|
||||
{"ECDSA P384", args{&apiv1.CreateSignerRequest{
|
||||
SigningKey: "pkcs11:id=7374;object=ecdsa-p384-key",
|
||||
}}, apiv1.ECDSAWithSHA384, crypto.SHA384, false},
|
||||
{"ECDSA P521", args{&apiv1.CreateSignerRequest{
|
||||
SigningKey: "pkcs11:id=7375;object=ecdsa-p521-key",
|
||||
}}, apiv1.ECDSAWithSHA512, crypto.SHA512, false},
|
||||
{"fail SigningKey", args{&apiv1.CreateSignerRequest{
|
||||
SigningKey: "",
|
||||
}}, 0, nil, true},
|
||||
{"fail uri", args{&apiv1.CreateSignerRequest{
|
||||
SigningKey: "https:id=7375;object=ecdsa-p521-key",
|
||||
}}, 0, nil, true},
|
||||
{"fail FindKeyPair", args{&apiv1.CreateSignerRequest{
|
||||
SigningKey: "pkcs11:foo=bar",
|
||||
}}, 0, nil, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := k.CreateSigner(tt.args.req)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("PKCS11.CreateSigner() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if got != nil {
|
||||
hash := tt.signerOpts.HashFunc()
|
||||
h := hash.New()
|
||||
h.Write(data)
|
||||
digest := h.Sum(nil)
|
||||
sig, err := got.Sign(rand.Reader, digest, tt.signerOpts)
|
||||
if err != nil {
|
||||
t.Errorf("cyrpto.Signer.Sign() error = %v", err)
|
||||
}
|
||||
|
||||
switch tt.algorithm {
|
||||
case apiv1.SHA256WithRSA, apiv1.SHA384WithRSA, apiv1.SHA512WithRSA:
|
||||
pub := got.Public().(*rsa.PublicKey)
|
||||
if err := rsa.VerifyPKCS1v15(pub, hash, digest, sig); err != nil {
|
||||
t.Errorf("rsa.VerifyPKCS1v15() error = %v", err)
|
||||
}
|
||||
case apiv1.UnspecifiedSignAlgorithm, apiv1.SHA256WithRSAPSS, apiv1.SHA384WithRSAPSS, apiv1.SHA512WithRSAPSS:
|
||||
pub := got.Public().(*rsa.PublicKey)
|
||||
if err := rsa.VerifyPSS(pub, hash, digest, sig, tt.signerOpts.(*rsa.PSSOptions)); err != nil {
|
||||
t.Errorf("rsa.VerifyPSS() error = %v", err)
|
||||
}
|
||||
case apiv1.ECDSAWithSHA256, apiv1.ECDSAWithSHA384, apiv1.ECDSAWithSHA512:
|
||||
pub := got.Public().(*ecdsa.PublicKey)
|
||||
if !verifyASN1(pub, digest, sig) {
|
||||
t.Error("ecdsa.VerifyASN1() failed")
|
||||
}
|
||||
default:
|
||||
t.Errorf("signature algorithm %s is not supported", tt.algorithm)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPKCS11_LoadCertificate(t *testing.T) {
|
||||
k := setupPKCS11(t)
|
||||
|
||||
getCertFn := func(i, j int) func() *x509.Certificate {
|
||||
return func() *x509.Certificate {
|
||||
return testCerts[i].Certificates[j]
|
||||
}
|
||||
}
|
||||
|
||||
type args struct {
|
||||
req *apiv1.LoadCertificateRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantFn func() *x509.Certificate
|
||||
wantErr bool
|
||||
}{
|
||||
{"load", args{&apiv1.LoadCertificateRequest{
|
||||
Name: "pkcs11:id=7376;object=test-root",
|
||||
}}, getCertFn(0, 0), false},
|
||||
{"load by id", args{&apiv1.LoadCertificateRequest{
|
||||
Name: "pkcs11:id=7376",
|
||||
}}, getCertFn(0, 0), false},
|
||||
{"load by label", args{&apiv1.LoadCertificateRequest{
|
||||
Name: "pkcs11:object=test-root",
|
||||
}}, getCertFn(0, 0), false},
|
||||
{"load by serial", args{&apiv1.LoadCertificateRequest{
|
||||
Name: "pkcs11:serial=64",
|
||||
}}, getCertFn(0, 0), false},
|
||||
{"fail missing", args{&apiv1.LoadCertificateRequest{
|
||||
Name: "pkcs11:id=9999;object=test-root",
|
||||
}}, nil, true},
|
||||
{"fail name", args{&apiv1.LoadCertificateRequest{
|
||||
Name: "",
|
||||
}}, nil, true},
|
||||
{"fail scheme", args{&apiv1.LoadCertificateRequest{
|
||||
Name: "foo:id=7376;object=test-root",
|
||||
}}, nil, true},
|
||||
{"fail serial", args{&apiv1.LoadCertificateRequest{
|
||||
Name: "pkcs11:serial=foo",
|
||||
}}, nil, true},
|
||||
{"fail FindCertificate", args{&apiv1.LoadCertificateRequest{
|
||||
Name: "pkcs11:foo=bar",
|
||||
}}, nil, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := k.LoadCertificate(tt.args.req)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("PKCS11.LoadCertificate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
var want *x509.Certificate
|
||||
if tt.wantFn != nil {
|
||||
want = tt.wantFn()
|
||||
got.Raw, got.RawIssuer, got.RawSubject, got.RawTBSCertificate, got.RawSubjectPublicKeyInfo = nil, nil, nil, nil, nil
|
||||
want.Raw, want.RawIssuer, want.RawSubject, want.RawTBSCertificate, want.RawSubjectPublicKeyInfo = nil, nil, nil, nil, nil
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("PKCS11.LoadCertificate() = %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPKCS11_StoreCertificate(t *testing.T) {
|
||||
k := setupPKCS11(t)
|
||||
|
||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("ed25519.GenerateKey() error = %v", err)
|
||||
}
|
||||
|
||||
cert, err := generateCertificate(pub, priv)
|
||||
if err != nil {
|
||||
t.Fatalf("x509.CreateCertificate() error = %v", err)
|
||||
}
|
||||
|
||||
// Make sure to delete the created certificate
|
||||
t.Cleanup(func() {
|
||||
k.DeleteCertificate(testObject)
|
||||
})
|
||||
|
||||
type args struct {
|
||||
req *apiv1.StoreCertificateRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", args{&apiv1.StoreCertificateRequest{
|
||||
Name: testObject,
|
||||
Certificate: cert,
|
||||
}}, false},
|
||||
{"fail already exists", args{&apiv1.StoreCertificateRequest{
|
||||
Name: testObject,
|
||||
Certificate: cert,
|
||||
}}, true},
|
||||
{"fail name", args{&apiv1.StoreCertificateRequest{
|
||||
Name: "",
|
||||
Certificate: cert,
|
||||
}}, true},
|
||||
{"fail certificate", args{&apiv1.StoreCertificateRequest{
|
||||
Name: testObject,
|
||||
Certificate: nil,
|
||||
}}, true},
|
||||
{"fail uri", args{&apiv1.StoreCertificateRequest{
|
||||
Name: "http:id=7770;object=create-cert",
|
||||
Certificate: cert,
|
||||
}}, true},
|
||||
{"fail ImportCertificateWithLabel", args{&apiv1.StoreCertificateRequest{
|
||||
Name: "pkcs11:foo=bar",
|
||||
Certificate: cert,
|
||||
}}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := k.StoreCertificate(tt.args.req); (err != nil) != tt.wantErr {
|
||||
t.Errorf("PKCS11.StoreCertificate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if !tt.wantErr {
|
||||
got, err := k.LoadCertificate(&apiv1.LoadCertificateRequest{
|
||||
Name: tt.args.req.Name,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("PKCS11.LoadCertificate() error = %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, cert) {
|
||||
t.Errorf("PKCS11.LoadCertificate() = %v, want %v", got, cert)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPKCS11_DeleteKey(t *testing.T) {
|
||||
k := setupPKCS11(t)
|
||||
|
||||
type args struct {
|
||||
uri string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"delete", args{testObject}, false},
|
||||
{"delete by id", args{testObjectByID}, false},
|
||||
{"delete by label", args{testObjectByLabel}, false},
|
||||
{"delete missing", args{"pkcs11:id=9999;object=missing-key"}, false},
|
||||
{"fail name", args{""}, true},
|
||||
{"fail FindKeyPair", args{"pkcs11:foo=bar"}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if _, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: testObject,
|
||||
}); err != nil {
|
||||
t.Fatalf("PKCS1.CreateKey() error = %v", err)
|
||||
}
|
||||
if err := k.DeleteKey(tt.args.uri); (err != nil) != tt.wantErr {
|
||||
t.Errorf("PKCS11.DeleteKey() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if _, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{
|
||||
Name: tt.args.uri,
|
||||
}); err == nil {
|
||||
t.Error("PKCS11.GetPublicKey() public key found and not expected")
|
||||
}
|
||||
// Make sure to delete the created one.
|
||||
if err := k.DeleteKey(testObject); err != nil {
|
||||
t.Errorf("PKCS11.DeleteKey() error = %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPKCS11_DeleteCertificate(t *testing.T) {
|
||||
k := setupPKCS11(t)
|
||||
|
||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("ed25519.GenerateKey() error = %v", err)
|
||||
}
|
||||
|
||||
cert, err := generateCertificate(pub, priv)
|
||||
if err != nil {
|
||||
t.Fatalf("x509.CreateCertificate() error = %v", err)
|
||||
}
|
||||
|
||||
type args struct {
|
||||
uri string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"delete", args{testObject}, false},
|
||||
{"delete by id", args{testObjectByID}, false},
|
||||
{"delete by label", args{testObjectByLabel}, false},
|
||||
{"delete missing", args{"pkcs11:id=9999;object=missing-key"}, false},
|
||||
{"fail name", args{""}, true},
|
||||
{"fail DeleteCertificate", args{"pkcs11:foo=bar"}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := k.StoreCertificate(&apiv1.StoreCertificateRequest{
|
||||
Name: testObject,
|
||||
Certificate: cert,
|
||||
}); err != nil {
|
||||
t.Fatalf("PKCS11.StoreCertificate() error = %v", err)
|
||||
}
|
||||
if err := k.DeleteCertificate(tt.args.uri); (err != nil) != tt.wantErr {
|
||||
t.Errorf("PKCS11.DeleteCertificate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if _, err := k.LoadCertificate(&apiv1.LoadCertificateRequest{
|
||||
Name: tt.args.uri,
|
||||
}); err == nil {
|
||||
t.Error("PKCS11.LoadCertificate() certificate found and not expected")
|
||||
}
|
||||
// Make sure to delete the created one.
|
||||
if err := k.DeleteCertificate(testObject); err != nil {
|
||||
t.Errorf("PKCS11.DeleteCertificate() error = %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPKCS11_Close(t *testing.T) {
|
||||
k := mustPKCS11(t)
|
||||
tests := []struct {
|
||||
name string
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", false},
|
||||
{"second", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := k.Close(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("PKCS11.Close() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
// +build cgo
|
||||
|
||||
package pkcs11
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
)
|
||||
|
||||
var (
|
||||
testModule = ""
|
||||
testObject = "pkcs11:id=7370;object=test-name"
|
||||
testObjectByID = "pkcs11:id=7370"
|
||||
testObjectByLabel = "pkcs11:object=test-name"
|
||||
testKeys = []struct {
|
||||
Name string
|
||||
SignatureAlgorithm apiv1.SignatureAlgorithm
|
||||
Bits int
|
||||
}{
|
||||
{"pkcs11:id=7371;object=rsa-key", apiv1.SHA256WithRSA, 2048},
|
||||
{"pkcs11:id=7372;object=rsa-pss-key", apiv1.SHA256WithRSAPSS, DefaultRSASize},
|
||||
{"pkcs11:id=7373;object=ecdsa-p256-key", apiv1.ECDSAWithSHA256, 0},
|
||||
{"pkcs11:id=7374;object=ecdsa-p384-key", apiv1.ECDSAWithSHA384, 0},
|
||||
{"pkcs11:id=7375;object=ecdsa-p521-key", apiv1.ECDSAWithSHA512, 0},
|
||||
}
|
||||
|
||||
testCerts = []struct {
|
||||
Name string
|
||||
Key string
|
||||
Certificates []*x509.Certificate
|
||||
}{
|
||||
{"pkcs11:id=7376;object=test-root", "pkcs11:id=7373;object=ecdsa-p256-key", nil},
|
||||
}
|
||||
)
|
||||
|
||||
type TBTesting interface {
|
||||
Helper()
|
||||
Cleanup(f func())
|
||||
Log(args ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
Skipf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
func generateCertificate(pub crypto.PublicKey, signer crypto.Signer) (*x509.Certificate, error) {
|
||||
now := time.Now()
|
||||
template := &x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "Test Root Certificate"},
|
||||
Issuer: pkix.Name{CommonName: "Test Root Certificate"},
|
||||
IsCA: true,
|
||||
MaxPathLen: 1,
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(time.Hour),
|
||||
SerialNumber: big.NewInt(100),
|
||||
}
|
||||
|
||||
b, err := x509.CreateCertificate(rand.Reader, template, template, pub, signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(b)
|
||||
}
|
||||
|
||||
func setup(t TBTesting, k *PKCS11) {
|
||||
t.Log("Running using", testModule)
|
||||
for _, tk := range testKeys {
|
||||
_, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||
Name: tk.Name,
|
||||
SignatureAlgorithm: tk.SignatureAlgorithm,
|
||||
Bits: tk.Bits,
|
||||
})
|
||||
if err != nil && !errors.Is(errors.Cause(err), apiv1.ErrAlreadyExists{
|
||||
Message: tk.Name + " already exists",
|
||||
}) {
|
||||
t.Errorf("PKCS11.GetPublicKey() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for i, c := range testCerts {
|
||||
signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{
|
||||
SigningKey: c.Key,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("PKCS11.CreateSigner() error = %v", err)
|
||||
continue
|
||||
}
|
||||
cert, err := generateCertificate(signer.Public(), signer)
|
||||
if err != nil {
|
||||
t.Errorf("x509.CreateCertificate() error = %v", err)
|
||||
continue
|
||||
}
|
||||
if err := k.StoreCertificate(&apiv1.StoreCertificateRequest{
|
||||
Name: c.Name,
|
||||
Certificate: cert,
|
||||
}); err != nil && !errors.Is(errors.Cause(err), apiv1.ErrAlreadyExists{
|
||||
Message: c.Name + " already exists",
|
||||
}) {
|
||||
t.Errorf("PKCS1.StoreCertificate() error = %+v", err)
|
||||
continue
|
||||
}
|
||||
testCerts[i].Certificates = append(testCerts[i].Certificates, cert)
|
||||
}
|
||||
}
|
||||
|
||||
func teardown(t TBTesting, k *PKCS11) {
|
||||
testObjects := []string{testObject, testObjectByID, testObjectByLabel}
|
||||
for _, name := range testObjects {
|
||||
if err := k.DeleteKey(name); err != nil {
|
||||
t.Errorf("PKCS11.DeleteKey() error = %v", err)
|
||||
}
|
||||
if err := k.DeleteCertificate(name); err != nil {
|
||||
t.Errorf("PKCS11.DeleteCertificate() error = %v", err)
|
||||
}
|
||||
}
|
||||
for _, tk := range testKeys {
|
||||
if err := k.DeleteKey(tk.Name); err != nil {
|
||||
t.Errorf("PKCS11.DeleteKey() error = %v", err)
|
||||
}
|
||||
}
|
||||
for _, tc := range testCerts {
|
||||
if err := k.DeleteCertificate(tc.Name); err != nil {
|
||||
t.Errorf("PKCS11.DeleteCertificate() error = %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setupPKCS11(t TBTesting) *PKCS11 {
|
||||
t.Helper()
|
||||
k := mustPKCS11(t)
|
||||
t.Cleanup(func() {
|
||||
k.Close()
|
||||
})
|
||||
return k
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
// +build cgo,softhsm2
|
||||
|
||||
package pkcs11
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/ThalesIgnite/crypto11"
|
||||
)
|
||||
|
||||
var softHSM2Once sync.Once
|
||||
|
||||
// mustPKCS11 configures a *PKCS11 KMS to be used with SoftHSM2. To initialize
|
||||
// these tests, we should run:
|
||||
// softhsm2-util --init-token --free \
|
||||
// --token pkcs11-test --label pkcs11-test \
|
||||
// --so-pin password --pin password
|
||||
//
|
||||
// To delete we should run:
|
||||
// softhsm2-util --delete-token --token pkcs11-test
|
||||
func mustPKCS11(t TBTesting) *PKCS11 {
|
||||
t.Helper()
|
||||
testModule = "SoftHSM2"
|
||||
if runtime.GOARCH != "amd64" {
|
||||
t.Fatalf("softHSM2 test skipped on %s:%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
var path string
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
path = "/usr/local/lib/softhsm/libsofthsm2.so"
|
||||
case "linux":
|
||||
path = "/usr/lib/softhsm/libsofthsm2.so"
|
||||
default:
|
||||
t.Skipf("softHSM2 test skipped on %s", runtime.GOOS)
|
||||
return nil
|
||||
}
|
||||
p11, err := crypto11.Configure(&crypto11.Config{
|
||||
Path: path,
|
||||
TokenLabel: "pkcs11-test",
|
||||
Pin: "password",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to configure softHSM2 on %s: %v", runtime.GOOS, err)
|
||||
}
|
||||
|
||||
k := &PKCS11{
|
||||
p11: p11,
|
||||
}
|
||||
|
||||
// Setup
|
||||
softHSM2Once.Do(func() {
|
||||
teardown(t, k)
|
||||
setup(t, k)
|
||||
})
|
||||
|
||||
return k
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
// +build cgo,yubihsm2
|
||||
|
||||
package pkcs11
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/ThalesIgnite/crypto11"
|
||||
)
|
||||
|
||||
var yubiHSM2Once sync.Once
|
||||
|
||||
// mustPKCS11 configures a *PKCS11 KMS to be used with YubiHSM2. To initialize
|
||||
// these tests, we should run:
|
||||
// yubihsm-connector -d
|
||||
func mustPKCS11(t TBTesting) *PKCS11 {
|
||||
t.Helper()
|
||||
testModule = "YubiHSM2"
|
||||
if runtime.GOARCH != "amd64" {
|
||||
t.Skipf("yubiHSM2 test skipped on %s:%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
var path string
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
path = "/usr/local/lib/pkcs11/yubihsm_pkcs11.dylib"
|
||||
case "linux":
|
||||
path = "/usr/lib/x86_64-linux-gnu/pkcs11/yubihsm_pkcs11.so"
|
||||
default:
|
||||
t.Skipf("yubiHSM2 test skipped on %s", runtime.GOOS)
|
||||
return nil
|
||||
}
|
||||
p11, err := crypto11.Configure(&crypto11.Config{
|
||||
Path: path,
|
||||
TokenLabel: "YubiHSM",
|
||||
Pin: "0001password",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to configure YubiHSM2 on %s: %v", runtime.GOOS, err)
|
||||
}
|
||||
|
||||
k := &PKCS11{
|
||||
p11: p11,
|
||||
}
|
||||
|
||||
// Setup
|
||||
yubiHSM2Once.Do(func() {
|
||||
teardown(t, k)
|
||||
setup(t, k)
|
||||
})
|
||||
|
||||
return k
|
||||
}
|
@ -0,0 +1 @@
|
||||
trim-this-pin
|
@ -0,0 +1,5 @@
|
||||
### Systemd unit files for `step-ca`
|
||||
|
||||
For documentation on `step-ca.service`, see [Running `step-ca` As A Daemon](https://smallstep.com/docs/step-ca/certificate-authority-server-production#running-step-ca-as-a-daemon).
|
||||
|
||||
For documentation on `cert-renewer@.*`, see [Automating Certificate Renewal](https://smallstep.com/docs/step-ca/certificate-authority-server-production#automate-x509-certificate-lifecycle-management)
|
@ -0,0 +1,31 @@
|
||||
[Unit]
|
||||
Description=Certificate renewer for %I
|
||||
After=network-online.target
|
||||
Documentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production
|
||||
StartLimitIntervalSec=0
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=root
|
||||
|
||||
Environment=STEPPATH=/etc/step-ca \
|
||||
CERT_LOCATION=/etc/step/certs/%i.crt \
|
||||
KEY_LOCATION=/etc/step/certs/%i.key
|
||||
|
||||
; ExecStartPre checks if the certificate is ready for renewal,
|
||||
; based on the exit status of the command.
|
||||
; (In systemd 243 and above, you can use ExecCondition= here.)
|
||||
ExecStartPre=/usr/bin/bash -c \
|
||||
'step certificate inspect $CERT_LOCATION --format json --roots "$STEPPATH/certs/root_ca.crt" | \
|
||||
jq -e "(((.validity.start | fromdate) + \
|
||||
((.validity.end | fromdate) - (.validity.start | fromdate)) * 0.66) \
|
||||
- now) <= 0" > /dev/null'
|
||||
|
||||
; ExecStart renews the certificate, if ExecStartPre was successful.
|
||||
ExecStart=/usr/bin/step ca renew --force $CERT_LOCATION $KEY_LOCATION
|
||||
|
||||
; Try to reload or restart the systemd service that relies on this cert-renewer
|
||||
ExecStartPost=/usr/bin/bash -c 'systemctl --quiet is-enabled %i && systemctl try-reload-or-restart %i'
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=Certificate renewal timer for %I
|
||||
Documentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production
|
||||
|
||||
[Timer]
|
||||
Persistent=true
|
||||
|
||||
; Run the timer unit every 5 minutes.
|
||||
OnCalendar=*:1/5
|
||||
|
||||
; Always run the timer on time.
|
||||
AccuracySec=1us
|
||||
|
||||
; Add jitter to prevent a "thundering hurd" of simultaneous certificate renewals.
|
||||
RandomizedDelaySec=5m
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
@ -0,0 +1,56 @@
|
||||
[Unit]
|
||||
Description=step-ca service
|
||||
Documentation=https://smallstep.com/docs/step-ca
|
||||
Documentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
StartLimitIntervalSec=30
|
||||
StartLimitBurst=3
|
||||
ConditionFileNotEmpty=/etc/step-ca/config/ca.json
|
||||
ConditionFileNotEmpty=/etc/step-ca/password.txt
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=step
|
||||
Group=step
|
||||
Environment=STEPPATH=/etc/step-ca
|
||||
WorkingDirectory=/etc/step-ca
|
||||
ExecStart=/usr/local/bin/step-ca config/ca.json --password-file password.txt
|
||||
ExecReload=/bin/kill --signal HUP $MAINPID
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=30
|
||||
StartLimitInterval=30
|
||||
StartLimitBurst=3
|
||||
|
||||
; Process capabilities & privileges
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
SecureBits=keep-caps
|
||||
NoNewPrivileges=yes
|
||||
|
||||
; Sandboxing
|
||||
; This sandboxing works with YubiKey PIV (via pcscd HTTP API), but it is likely
|
||||
; too restrictive for PKCS#11 HSMs.
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
RestrictNamespaces=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
PrivateTmp=true
|
||||
ProtectClock=true
|
||||
ProtectControlGroups=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectKernelModules=true
|
||||
LockPersonality=true
|
||||
RestrictSUIDSGID=true
|
||||
RemoveIPC=true
|
||||
RestrictRealtime=true
|
||||
PrivateDevices=true
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallArchitectures=native
|
||||
MemoryDenyWriteExecute=true
|
||||
ReadWriteDirectories=/etc/step-ca/db
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
Loading…
Reference in New Issue