Add support for PKCS #11 KMS.
The implementation works with YubiHSM2. Unit tests are still pending. Fixes #301
This commit is contained in:
parent
c61222de1d
commit
8dca652bc7
10 changed files with 602 additions and 13 deletions
11
Makefile
11
Makefile
|
@ -6,6 +6,8 @@ AWSKMS_BINNAME?=step-awskms-init
|
|||
AWSKMS_PKG?=github.com/smallstep/certificates/cmd/step-awskms-init
|
||||
YUBIKEY_BINNAME?=step-yubikey-init
|
||||
YUBIKEY_PKG?=github.com/smallstep/certificates/cmd/step-yubikey-init
|
||||
PKCS11_BINNAME?=step-pkcs11-init
|
||||
PKCS11_PKG?=github.com/smallstep/certificates/cmd/step-pkcs11-init
|
||||
|
||||
# Set V to 1 for verbose output from the Makefile
|
||||
Q=$(if $V,,@)
|
||||
|
@ -76,7 +78,7 @@ GOFLAGS := CGO_ENABLED=0
|
|||
download:
|
||||
$Q go mod download
|
||||
|
||||
build: $(PREFIX)bin/$(BINNAME) $(PREFIX)bin/$(CLOUDKMS_BINNAME) $(PREFIX)bin/$(AWSKMS_BINNAME) $(PREFIX)bin/$(YUBIKEY_BINNAME)
|
||||
build: $(PREFIX)bin/$(BINNAME) $(PREFIX)bin/$(CLOUDKMS_BINNAME) $(PREFIX)bin/$(AWSKMS_BINNAME) $(PREFIX)bin/$(YUBIKEY_BINNAME) $(PREFIX)bin/$(PKCS11_BINNAME)
|
||||
@echo "Build Complete!"
|
||||
|
||||
$(PREFIX)bin/$(BINNAME): download $(call rwildcard,*.go)
|
||||
|
@ -95,6 +97,10 @@ $(PREFIX)bin/$(YUBIKEY_BINNAME): download $(call rwildcard,*.go)
|
|||
$Q mkdir -p $(@D)
|
||||
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(YUBIKEY_BINNAME) $(LDFLAGS) $(YUBIKEY_PKG)
|
||||
|
||||
$(PREFIX)bin/$(PKCS11_BINNAME): download $(call rwildcard,*.go)
|
||||
$Q mkdir -p $(@D)
|
||||
$Q $(GOOS_OVERRIDE) $(GOFLAGS) go build -v -o $(PREFIX)bin/$(PKCS11_BINNAME) $(LDFLAGS) $(PKCS11_PKG)
|
||||
|
||||
# Target to force a build of step-ca without running tests
|
||||
simple: build
|
||||
|
||||
|
@ -171,6 +177,9 @@ endif
|
|||
ifneq ($(YUBIKEY_BINNAME),"")
|
||||
$Q rm -f bin/$(YUBIKEY_BINNAME)
|
||||
endif
|
||||
ifneq ($(PKCS11_BINNAME),"")
|
||||
$Q rm -f bin/$(PKCS11_BINNAME)
|
||||
endif
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
_ "github.com/smallstep/certificates/kms/sshagentkms"
|
||||
|
||||
// Experimental kms interfaces.
|
||||
_ "github.com/smallstep/certificates/kms/pkcs11"
|
||||
_ "github.com/smallstep/certificates/kms/yubikey"
|
||||
|
||||
// Enabled cas interfaces.
|
||||
|
|
430
cmd/step-pkcs11-init/main.go
Normal file
430
cmd/step-pkcs11-init/main.go
Normal file
|
@ -0,0 +1,430 @@
|
|||
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
|
||||
EnableSSH bool
|
||||
Force bool
|
||||
}
|
||||
|
||||
// Validate checks the flags in the config.
|
||||
func (c *Config) Validate() error {
|
||||
switch {
|
||||
case c.KMS == "":
|
||||
return errors.New("one of 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"
|
||||
}
|
||||
default:
|
||||
|
||||
}
|
||||
|
||||
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.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)
|
||||
}
|
||||
|
||||
// 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 != "" {
|
||||
checkObject(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 != "" {
|
||||
if err := deleter.DeleteCertificate(u); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, u := range keyUris {
|
||||
if u != "" {
|
||||
if err := deleter.DeleteKey(u); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := createPKI(k, c); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = k.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
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 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 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`.")
|
||||
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 {
|
||||
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 {
|
||||
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[:]
|
||||
}
|
2
go.mod
2
go.mod
|
@ -5,6 +5,7 @@ go 1.14
|
|||
require (
|
||||
cloud.google.com/go v0.70.0
|
||||
github.com/Masterminds/sprig/v3 v3.1.0
|
||||
github.com/ThalesIgnite/crypto11 v1.2.3
|
||||
github.com/aws/aws-sdk-go v1.30.29
|
||||
github.com/go-chi/chi v4.0.2+incompatible
|
||||
github.com/go-piv/piv-go v1.7.0
|
||||
|
@ -33,3 +34,4 @@ require (
|
|||
// replace github.com/smallstep/nosql => ../nosql
|
||||
// replace go.step.sm/crypto => ../crypto
|
||||
// replace github.com/smallstep/nosql => ../nosql
|
||||
replace github.com/ThalesIgnite/crypto11 => github.com/maraino/crypto11 v1.2.4-0.20210127032225-7ed5319b45a1
|
||||
|
|
7
go.sum
7
go.sum
|
@ -214,6 +214,8 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm
|
|||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo=
|
||||
github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
|
||||
github.com/maraino/crypto11 v1.2.4-0.20210127032225-7ed5319b45a1 h1:aj2ASiF6u9p576GdvGsRW5SiwiE/Hp5BeU2c/ldlYTI=
|
||||
github.com/maraino/crypto11 v1.2.4-0.20210127032225-7ed5319b45a1/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
|
@ -222,6 +224,8 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
|
|||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ=
|
||||
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
|
@ -273,9 +277,12 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
|
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
|
||||
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
|
|
|
@ -64,10 +64,15 @@ type Options struct {
|
|||
// Path to the credentials file used in CloudKMS and AmazonKMS.
|
||||
CredentialsFile string `json:"credentialsFile"`
|
||||
|
||||
// Path to the module used with PKCS11 KMS.
|
||||
Module string `json:"module"`
|
||||
// URI is based on the PKCS #11 URI Scheme defined in
|
||||
// https://tools.ietf.org/html/rfc7512 and represents the configuration used
|
||||
// to connect to the KMS.
|
||||
//
|
||||
// Used by: pkcs11
|
||||
URI string `json:"uri"`
|
||||
|
||||
// Pin used to access the PKCS11 module.
|
||||
// Pin used to access the PKCS11 module. It can be defined in the URI using
|
||||
// the pin-value or pin-source properties.
|
||||
Pin string `json:"pin"`
|
||||
|
||||
// ManagementKey used in YubiKeys. Default management key is the hexadecimal
|
||||
|
@ -93,10 +98,9 @@ func (o *Options) Validate() error {
|
|||
}
|
||||
|
||||
switch Type(strings.ToLower(o.Type)) {
|
||||
case DefaultKMS, SoftKMS, CloudKMS, AmazonKMS, SSHAgentKMS:
|
||||
case YubiKey:
|
||||
case PKCS11:
|
||||
return ErrNotImplemented{"support for PKCS11 is not yet implemented"}
|
||||
case DefaultKMS, SoftKMS: // Go crypto based kms.
|
||||
case CloudKMS, AmazonKMS, SSHAgentKMS: // Cloud based kms.
|
||||
case YubiKey, PKCS11: // Hardware based kms.
|
||||
default:
|
||||
return errors.Errorf("unsupported kms type %s", o.Type)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ func TestOptions_Validate(t *testing.T) {
|
|||
{"cloudkms", &Options{Type: "cloudkms"}, false},
|
||||
{"awskms", &Options{Type: "awskms"}, false},
|
||||
{"sshagentkms", &Options{Type: "sshagentkms"}, false},
|
||||
{"pkcs11", &Options{Type: "pkcs11"}, true},
|
||||
{"pkcs11", &Options{Type: "pkcs11"}, false},
|
||||
{"unsupported", &Options{Type: "unsupported"}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
|
|
@ -98,9 +98,16 @@ type GetPublicKeyRequest struct {
|
|||
|
||||
// CreateKeyRequest is the parameter used in the kms.CreateKey method.
|
||||
type CreateKeyRequest struct {
|
||||
Name string
|
||||
// Name represents the key name or label used to identify a key.
|
||||
//
|
||||
// Used by: awskms, cloudkms, pkcs11, yubikey.
|
||||
Name string
|
||||
|
||||
// SignatureAlgorithm represents the type of key to create.
|
||||
SignatureAlgorithm SignatureAlgorithm
|
||||
Bits int
|
||||
|
||||
// Bits is the number of bits on RSA keys.
|
||||
Bits int
|
||||
|
||||
// ProtectionLevel specifies how cryptographic operations are performed.
|
||||
// Used by: cloudkms
|
||||
|
|
130
kms/uri/uri.go
130
kms/uri/uri.go
|
@ -1,8 +1,12 @@
|
|||
package uri
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -82,5 +86,129 @@ func ParseWithScheme(scheme, rawuri string) (*URI, error) {
|
|||
// Get returns the first value in the uri with the give n key, it will return
|
||||
// empty string if that field is not present.
|
||||
func (u *URI) Get(key string) string {
|
||||
return u.Values.Get(key)
|
||||
v := u.Values.Get(key)
|
||||
if v == "" {
|
||||
v = u.URL.Query().Get(key)
|
||||
}
|
||||
return StringDecode(v)
|
||||
}
|
||||
|
||||
// GetHex returns the first value in the uri with the give n key, it will return
|
||||
// empty nil if that field is not present.
|
||||
func (u *URI) GetHex(key string) ([]byte, error) {
|
||||
v := u.Values.Get(key)
|
||||
if v == "" {
|
||||
v = u.URL.Query().Get(key)
|
||||
}
|
||||
return HexDecode(v)
|
||||
}
|
||||
|
||||
// Pin returns the pin encoded in the url. It will read the pin from the
|
||||
// pin-value or the pin-source attributes.
|
||||
func (u *URI) Pin() string {
|
||||
if value := u.Get("pin-value"); value != "" {
|
||||
return StringDecode(value)
|
||||
}
|
||||
if path := u.Get("pin-source"); path != "" {
|
||||
if b, err := readFile(path); err == nil {
|
||||
return string(bytes.TrimRightFunc(b, unicode.IsSpace))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func readFile(path string) ([]byte, error) {
|
||||
u, err := url.Parse(path)
|
||||
if err == nil && (u.Scheme == "" || u.Scheme == "file") && u.Path != "" {
|
||||
path = u.Path
|
||||
}
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading %s", path)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// PercentEncode encodes the given bytes using the percent encoding described in
|
||||
// RFC3986 (https://tools.ietf.org/html/rfc3986).
|
||||
func PercentEncode(b []byte) string {
|
||||
buf := new(strings.Builder)
|
||||
for _, v := range b {
|
||||
buf.WriteString("%" + hex.EncodeToString([]byte{v}))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// PercentDecode decodes the given string using the percent encoding described
|
||||
// in RFC3986 (https://tools.ietf.org/html/rfc3986).
|
||||
func PercentDecode(s string) ([]byte, error) {
|
||||
if len(s)%3 != 0 {
|
||||
return nil, errors.Errorf("error parsing %s: wrong length", s)
|
||||
}
|
||||
|
||||
var first string
|
||||
buf := new(bytes.Buffer)
|
||||
for i, r := range s {
|
||||
mod := i % 3
|
||||
rr := string(r)
|
||||
switch mod {
|
||||
case 0:
|
||||
if r != '%' {
|
||||
return nil, errors.Errorf("error parsing %s: expected %% and found %s in position %d", s, rr, i)
|
||||
}
|
||||
case 1:
|
||||
if !isHex(r) {
|
||||
return nil, errors.Errorf("error parsing %s: %s in position %d is not an hexadecimal number", s, rr, i)
|
||||
}
|
||||
first = string(r)
|
||||
case 2:
|
||||
if !isHex(r) {
|
||||
return nil, errors.Errorf("error parsing %s: %s in position %d is not an hexadecimal number", s, rr, i)
|
||||
}
|
||||
b, err := hex.DecodeString(first + rr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error parsing %s", s)
|
||||
}
|
||||
buf.Write(b)
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// StringDecode returns the string given, but it will use Percent-Encoding if
|
||||
// the string is percent encoded.
|
||||
func StringDecode(s string) string {
|
||||
if strings.HasPrefix(s, "%") {
|
||||
if b, err := PercentDecode(s); err == nil {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// HexDecode deocdes the string s using Percent-Encoding or regular hex
|
||||
// encoding.
|
||||
func HexDecode(s string) ([]byte, error) {
|
||||
if strings.HasPrefix(s, "%") {
|
||||
return PercentDecode(s)
|
||||
}
|
||||
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error parsing %s", s)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func isHex(r rune) bool {
|
||||
switch {
|
||||
case r >= '0' && r <= '9':
|
||||
return true
|
||||
case r >= 'a' && r <= 'f':
|
||||
return true
|
||||
case r >= 'A' && r <= 'F':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -189,8 +189,9 @@ func TestURI_Get(t *testing.T) {
|
|||
{"ok", mustParse("yubikey:slot-id=9a"), args{"slot-id"}, "9a"},
|
||||
{"ok first", mustParse("yubikey:slot-id=9a;slot-id=9b"), args{"slot-id"}, "9a"},
|
||||
{"ok multiple", mustParse("yubikey:slot-id=9a;foo=bar"), args{"foo"}, "bar"},
|
||||
{"ok in query", mustParse("yubikey:slot-id=9a?foo=bar"), args{"foo"}, "bar"},
|
||||
{"fail missing", mustParse("yubikey:slot-id=9a"), args{"foo"}, ""},
|
||||
{"fail in query", mustParse("yubikey:slot-id=9a?foo=bar"), args{"foo"}, ""},
|
||||
{"fail missing query", mustParse("yubikey:slot-id=9a?bar=zar"), args{"foo"}, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
Loading…
Reference in a new issue