forked from TrueCloudLab/certificates
commit
e3220232a0
19 changed files with 1903 additions and 60 deletions
|
@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||||
|
|
||||||
## [Unreleased - 0.17.5] - DATE
|
## [Unreleased - 0.17.5] - DATE
|
||||||
### Added
|
### Added
|
||||||
|
- Support for Azure Key Vault as a KMS.
|
||||||
|
- Adapt `pki` package to support key managers.
|
||||||
- gocritic linter
|
- gocritic linter
|
||||||
### Changed
|
### Changed
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
@ -15,13 +17,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
## [0.17.4] - 2021-09-28
|
## [0.17.4] - 2021-09-28
|
||||||
### Added
|
|
||||||
### Changed
|
|
||||||
### Deprecated
|
|
||||||
### Removed
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Support host-only or user-only SSH CA.
|
- Support host-only or user-only SSH CA.
|
||||||
### Security
|
|
||||||
|
|
||||||
## [0.17.3] - 2021-09-24
|
## [0.17.3] - 2021-09-24
|
||||||
### Added
|
### Added
|
||||||
|
@ -57,4 +54,3 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||||
- Update TLS cipher suites to include 1.3
|
- Update TLS cipher suites to include 1.3
|
||||||
### Security
|
### Security
|
||||||
- Fix key version when SHA512WithRSA is used. There was a typo creating RSA keys with SHA256 digests instead of SHA512.
|
- Fix key version when SHA512WithRSA is used. There was a typo creating RSA keys with SHA256 digests instead of SHA512.
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,9 @@ type GetCertificateAuthorityResponse struct {
|
||||||
RootCertificate *x509.Certificate
|
RootCertificate *x509.Certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateKeyRequest is the request used to generate a new key using a KMS.
|
||||||
|
type CreateKeyRequest = apiv1.CreateKeyRequest
|
||||||
|
|
||||||
// CreateCertificateAuthorityRequest is the request used to generate a root or
|
// CreateCertificateAuthorityRequest is the request used to generate a root or
|
||||||
// intermediate certificate.
|
// intermediate certificate.
|
||||||
type CreateCertificateAuthorityRequest struct {
|
type CreateCertificateAuthorityRequest struct {
|
||||||
|
@ -126,7 +129,7 @@ type CreateCertificateAuthorityRequest struct {
|
||||||
// CreateKey defines the KMS CreateKeyRequest to use when creating a new
|
// CreateKey defines the KMS CreateKeyRequest to use when creating a new
|
||||||
// CertificateAuthority. If CreateKey is nil, a default algorithm will be
|
// CertificateAuthority. If CreateKey is nil, a default algorithm will be
|
||||||
// used.
|
// used.
|
||||||
CreateKey *apiv1.CreateKeyRequest
|
CreateKey *CreateKeyRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateCertificateAuthorityResponse is the response for
|
// CreateCertificateAuthorityResponse is the response for
|
||||||
|
@ -136,6 +139,7 @@ type CreateCertificateAuthorityResponse struct {
|
||||||
Name string
|
Name string
|
||||||
Certificate *x509.Certificate
|
Certificate *x509.Certificate
|
||||||
CertificateChain []*x509.Certificate
|
CertificateChain []*x509.Certificate
|
||||||
|
KeyName string
|
||||||
PublicKey crypto.PublicKey
|
PublicKey crypto.PublicKey
|
||||||
PrivateKey crypto.PrivateKey
|
PrivateKey crypto.PrivateKey
|
||||||
Signer crypto.Signer
|
Signer crypto.Signer
|
||||||
|
|
|
@ -172,6 +172,7 @@ func (c *SoftCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthori
|
||||||
Name: cert.Subject.CommonName,
|
Name: cert.Subject.CommonName,
|
||||||
Certificate: cert,
|
Certificate: cert,
|
||||||
CertificateChain: chain,
|
CertificateChain: chain,
|
||||||
|
KeyName: key.Name,
|
||||||
PublicKey: key.PublicKey,
|
PublicKey: key.PublicKey,
|
||||||
PrivateKey: key.PrivateKey,
|
PrivateKey: key.PrivateKey,
|
||||||
Signer: signer,
|
Signer: signer,
|
||||||
|
|
|
@ -106,6 +106,7 @@ func (m *mockKeyManager) CreateKey(req *kmsapi.CreateKeyRequest) (*kmsapi.Create
|
||||||
signer = m.signer
|
signer = m.signer
|
||||||
}
|
}
|
||||||
return &kmsapi.CreateKeyResponse{
|
return &kmsapi.CreateKeyResponse{
|
||||||
|
Name: req.Name,
|
||||||
PrivateKey: signer,
|
PrivateKey: signer,
|
||||||
PublicKey: signer.Public(),
|
PublicKey: signer.Public(),
|
||||||
}, m.errCreateKey
|
}, m.errCreateKey
|
||||||
|
@ -516,6 +517,22 @@ func TestSoftCAS_CreateCertificateAuthority(t *testing.T) {
|
||||||
PrivateKey: saSigner,
|
PrivateKey: saSigner,
|
||||||
Signer: saSigner,
|
Signer: saSigner,
|
||||||
}, false},
|
}, false},
|
||||||
|
{"ok createKey", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{
|
||||||
|
Type: apiv1.RootCA,
|
||||||
|
Template: testRootTemplate,
|
||||||
|
Lifetime: 24 * time.Hour,
|
||||||
|
CreateKey: &kmsapi.CreateKeyRequest{
|
||||||
|
Name: "root_ca.crt",
|
||||||
|
SignatureAlgorithm: kmsapi.ECDSAWithSHA256,
|
||||||
|
},
|
||||||
|
}}, &apiv1.CreateCertificateAuthorityResponse{
|
||||||
|
Name: "Test Root CA",
|
||||||
|
Certificate: testSignedRootTemplate,
|
||||||
|
PublicKey: testSignedRootTemplate.PublicKey,
|
||||||
|
KeyName: "root_ca.crt",
|
||||||
|
PrivateKey: testSigner,
|
||||||
|
Signer: testSigner,
|
||||||
|
}, false},
|
||||||
{"fail template", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{
|
{"fail template", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{
|
||||||
Type: apiv1.RootCA,
|
Type: apiv1.RootCA,
|
||||||
Lifetime: 24 * time.Hour,
|
Lifetime: 24 * time.Hour,
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
|
|
||||||
// Enabled kms interfaces.
|
// Enabled kms interfaces.
|
||||||
_ "github.com/smallstep/certificates/kms/awskms"
|
_ "github.com/smallstep/certificates/kms/awskms"
|
||||||
|
_ "github.com/smallstep/certificates/kms/azurekms"
|
||||||
_ "github.com/smallstep/certificates/kms/cloudkms"
|
_ "github.com/smallstep/certificates/kms/cloudkms"
|
||||||
_ "github.com/smallstep/certificates/kms/softkms"
|
_ "github.com/smallstep/certificates/kms/softkms"
|
||||||
_ "github.com/smallstep/certificates/kms/sshagentkms"
|
_ "github.com/smallstep/certificates/kms/sshagentkms"
|
||||||
|
|
11
go.mod
11
go.mod
|
@ -1,9 +1,14 @@
|
||||||
module github.com/smallstep/certificates
|
module github.com/smallstep/certificates
|
||||||
|
|
||||||
go 1.14
|
go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.83.0
|
cloud.google.com/go v0.83.0
|
||||||
|
github.com/Azure/azure-sdk-for-go v58.0.0+incompatible
|
||||||
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.3.0
|
||||||
|
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||||
github.com/Masterminds/sprig/v3 v3.2.2
|
github.com/Masterminds/sprig/v3 v3.2.2
|
||||||
github.com/ThalesIgnite/crypto11 v1.2.4
|
github.com/ThalesIgnite/crypto11 v1.2.4
|
||||||
github.com/aws/aws-sdk-go v1.30.29
|
github.com/aws/aws-sdk-go v1.30.29
|
||||||
|
@ -11,7 +16,7 @@ require (
|
||||||
github.com/go-chi/chi v4.0.2+incompatible
|
github.com/go-chi/chi v4.0.2+incompatible
|
||||||
github.com/go-kit/kit v0.10.0 // indirect
|
github.com/go-kit/kit v0.10.0 // indirect
|
||||||
github.com/go-piv/piv-go v1.7.0
|
github.com/go-piv/piv-go v1.7.0
|
||||||
github.com/golang/mock v1.5.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5
|
github.com/googleapis/gax-go/v2 v2.0.5
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||||
|
@ -28,7 +33,7 @@ require (
|
||||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
||||||
go.step.sm/cli-utils v0.6.0
|
go.step.sm/cli-utils v0.6.0
|
||||||
go.step.sm/crypto v0.11.0
|
go.step.sm/crypto v0.11.0
|
||||||
go.step.sm/linkedca v0.5.0
|
go.step.sm/linkedca v0.7.0
|
||||||
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272
|
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272
|
||||||
golang.org/x/net v0.0.0-20210913180222-943fd674d43e
|
golang.org/x/net v0.0.0-20210913180222-943fd674d43e
|
||||||
google.golang.org/api v0.47.0
|
google.golang.org/api v0.47.0
|
||||||
|
|
40
go.sum
40
go.sum
|
@ -40,6 +40,31 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
|
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||||
|
github.com/Azure/azure-sdk-for-go v58.0.0+incompatible h1:Cw16jiP4dI+CK761aq44ol4RV5dUiIIXky1+EKpoiVM=
|
||||||
|
github.com/Azure/azure-sdk-for-go v58.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||||
|
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||||
|
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||||
|
github.com/Azure/go-autorest/autorest v0.11.17 h1:2zCdHwNgRH+St1J+ZMf66xI8aLr/5KMy+wWLH97zwYM=
|
||||||
|
github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.9.11 h1:L4/pmq7poLdsy41Bj1FayKvBhayuWRYkx9HU5i4Ybl0=
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
|
||||||
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 h1:TzPg6B6fTZ0G1zBf3T54aI7p3cAT6u//TOXGPmFMOXg=
|
||||||
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4=
|
||||||
|
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY=
|
||||||
|
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||||
|
github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
|
||||||
|
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||||
|
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
|
||||||
|
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
|
||||||
|
github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac=
|
||||||
|
github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
|
||||||
|
github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE=
|
||||||
|
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||||
|
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||||
|
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
|
@ -146,6 +171,9 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
||||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
|
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||||
|
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||||
|
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
@ -163,6 +191,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||||
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
@ -206,8 +236,9 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
||||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
|
|
||||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||||
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
@ -377,6 +408,7 @@ github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFW
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
|
@ -539,8 +571,8 @@ go.step.sm/cli-utils v0.6.0/go.mod h1:jklBMavFl2PbmGlyxgax08ZnB0uWpadjuOlSKKXz+0
|
||||||
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
||||||
go.step.sm/crypto v0.11.0 h1:VDpeVgEmqme/FK2w5QINxkOQ1FWOm/Wi2TwQXiacKr8=
|
go.step.sm/crypto v0.11.0 h1:VDpeVgEmqme/FK2w5QINxkOQ1FWOm/Wi2TwQXiacKr8=
|
||||||
go.step.sm/crypto v0.11.0/go.mod h1:5YzQ85BujYBu6NH18jw7nFjwuRnDch35nLzH0ES5sKg=
|
go.step.sm/crypto v0.11.0/go.mod h1:5YzQ85BujYBu6NH18jw7nFjwuRnDch35nLzH0ES5sKg=
|
||||||
go.step.sm/linkedca v0.5.0 h1:oZVRSpElM7lAL1XN2YkjdHwI/oIZ+1ULOnuqYPM6xjY=
|
go.step.sm/linkedca v0.7.0 h1:ydYigs0CgLFkPGjOO4KJcAcAWbuPP8ECF1IsyHdftYc=
|
||||||
go.step.sm/linkedca v0.5.0/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo=
|
go.step.sm/linkedca v0.7.0/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
@ -559,6 +591,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 h1:3erb+vDS8lU1sxfDHF4/hhWyaXnhIaO+7RgL4fDZORA=
|
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 h1:3erb+vDS8lU1sxfDHF4/hhWyaXnhIaO+7RgL4fDZORA=
|
||||||
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
|
|
@ -29,6 +29,12 @@ type CertificateManager interface {
|
||||||
StoreCertificate(req *StoreCertificateRequest) error
|
StoreCertificate(req *StoreCertificateRequest) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateName is an interface that KeyManager can implement to validate a
|
||||||
|
// given name or URI.
|
||||||
|
type NameValidator interface {
|
||||||
|
ValidateName(s string) error
|
||||||
|
}
|
||||||
|
|
||||||
// ErrNotImplemented is the type of error returned if an operation is not
|
// ErrNotImplemented is the type of error returned if an operation is not
|
||||||
// implemented.
|
// implemented.
|
||||||
type ErrNotImplemented struct {
|
type ErrNotImplemented struct {
|
||||||
|
@ -73,6 +79,8 @@ const (
|
||||||
YubiKey Type = "yubikey"
|
YubiKey Type = "yubikey"
|
||||||
// SSHAgentKMS is a KMS implementation using ssh-agent to access keys.
|
// SSHAgentKMS is a KMS implementation using ssh-agent to access keys.
|
||||||
SSHAgentKMS Type = "sshagentkms"
|
SSHAgentKMS Type = "sshagentkms"
|
||||||
|
// AzureKMS is a KMS implementation using Azure Key Vault.
|
||||||
|
AzureKMS Type = "azurekms"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options are the KMS options. They represent the kms object in the ca.json.
|
// Options are the KMS options. They represent the kms object in the ca.json.
|
||||||
|
@ -81,18 +89,18 @@ type Options struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
||||||
// Path to the credentials file used in CloudKMS and AmazonKMS.
|
// Path to the credentials file used in CloudKMS and AmazonKMS.
|
||||||
CredentialsFile string `json:"credentialsFile"`
|
CredentialsFile string `json:"credentialsFile,omitempty"`
|
||||||
|
|
||||||
// URI is based on the PKCS #11 URI Scheme defined in
|
// URI is based on the PKCS #11 URI Scheme defined in
|
||||||
// https://tools.ietf.org/html/rfc7512 and represents the configuration used
|
// https://tools.ietf.org/html/rfc7512 and represents the configuration used
|
||||||
// to connect to the KMS.
|
// to connect to the KMS.
|
||||||
//
|
//
|
||||||
// Used by: pkcs11
|
// Used by: pkcs11
|
||||||
URI string `json:"uri"`
|
URI string `json:"uri,omitempty"`
|
||||||
|
|
||||||
// Pin used to access the PKCS11 module. It can be defined in the URI using
|
// Pin used to access the PKCS11 module. It can be defined in the URI using
|
||||||
// the pin-value or pin-source properties.
|
// the pin-value or pin-source properties.
|
||||||
Pin string `json:"pin"`
|
Pin string `json:"pin,omitempty"`
|
||||||
|
|
||||||
// ManagementKey used in YubiKeys. Default management key is the hexadecimal
|
// ManagementKey used in YubiKeys. Default management key is the hexadecimal
|
||||||
// string 010203040506070801020304050607080102030405060708:
|
// string 010203040506070801020304050607080102030405060708:
|
||||||
|
@ -101,13 +109,13 @@ type Options struct {
|
||||||
// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
// }
|
// }
|
||||||
ManagementKey string `json:"managementKey"`
|
ManagementKey string `json:"managementKey,omitempty"`
|
||||||
|
|
||||||
// Region to use in AmazonKMS.
|
// Region to use in AmazonKMS.
|
||||||
Region string `json:"region"`
|
Region string `json:"region,omitempty"`
|
||||||
|
|
||||||
// Profile to use in AmazonKMS.
|
// Profile to use in AmazonKMS.
|
||||||
Profile string `json:"profile"`
|
Profile string `json:"profile,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate checks the fields in Options.
|
// Validate checks the fields in Options.
|
||||||
|
@ -118,8 +126,9 @@ func (o *Options) Validate() error {
|
||||||
|
|
||||||
switch Type(strings.ToLower(o.Type)) {
|
switch Type(strings.ToLower(o.Type)) {
|
||||||
case DefaultKMS, SoftKMS: // Go crypto based kms.
|
case DefaultKMS, SoftKMS: // Go crypto based kms.
|
||||||
case CloudKMS, AmazonKMS, SSHAgentKMS: // Cloud based kms.
|
case CloudKMS, AmazonKMS, AzureKMS: // Cloud based kms.
|
||||||
case YubiKey, PKCS11: // Hardware based kms.
|
case YubiKey, PKCS11: // Hardware based kms.
|
||||||
|
case SSHAgentKMS: // Others
|
||||||
default:
|
default:
|
||||||
return errors.Errorf("unsupported kms type %s", o.Type)
|
return errors.Errorf("unsupported kms type %s", o.Type)
|
||||||
}
|
}
|
||||||
|
|
80
kms/azurekms/internal/mock/key_vault_client.go
Normal file
80
kms/azurekms/internal/mock/key_vault_client.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/smallstep/certificates/kms/azurekms (interfaces: KeyVaultClient)
|
||||||
|
|
||||||
|
// Package mock is a generated GoMock package.
|
||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
keyvault "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
reflect "reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyVaultClient is a mock of KeyVaultClient interface
|
||||||
|
type KeyVaultClient struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *KeyVaultClientMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyVaultClientMockRecorder is the mock recorder for KeyVaultClient
|
||||||
|
type KeyVaultClientMockRecorder struct {
|
||||||
|
mock *KeyVaultClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeyVaultClient creates a new mock instance
|
||||||
|
func NewKeyVaultClient(ctrl *gomock.Controller) *KeyVaultClient {
|
||||||
|
mock := &KeyVaultClient{ctrl: ctrl}
|
||||||
|
mock.recorder = &KeyVaultClientMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *KeyVaultClient) EXPECT() *KeyVaultClientMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateKey mocks base method
|
||||||
|
func (m *KeyVaultClient) CreateKey(arg0 context.Context, arg1, arg2 string, arg3 keyvault.KeyCreateParameters) (keyvault.KeyBundle, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateKey", arg0, arg1, arg2, arg3)
|
||||||
|
ret0, _ := ret[0].(keyvault.KeyBundle)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateKey indicates an expected call of CreateKey
|
||||||
|
func (mr *KeyVaultClientMockRecorder) CreateKey(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateKey", reflect.TypeOf((*KeyVaultClient)(nil).CreateKey), arg0, arg1, arg2, arg3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKey mocks base method
|
||||||
|
func (m *KeyVaultClient) GetKey(arg0 context.Context, arg1, arg2, arg3 string) (keyvault.KeyBundle, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetKey", arg0, arg1, arg2, arg3)
|
||||||
|
ret0, _ := ret[0].(keyvault.KeyBundle)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKey indicates an expected call of GetKey
|
||||||
|
func (mr *KeyVaultClientMockRecorder) GetKey(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKey", reflect.TypeOf((*KeyVaultClient)(nil).GetKey), arg0, arg1, arg2, arg3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign mocks base method
|
||||||
|
func (m *KeyVaultClient) Sign(arg0 context.Context, arg1, arg2, arg3 string, arg4 keyvault.KeySignParameters) (keyvault.KeyOperationResult, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Sign", arg0, arg1, arg2, arg3, arg4)
|
||||||
|
ret0, _ := ret[0].(keyvault.KeyOperationResult)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign indicates an expected call of Sign
|
||||||
|
func (mr *KeyVaultClientMockRecorder) Sign(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sign", reflect.TypeOf((*KeyVaultClient)(nil).Sign), arg0, arg1, arg2, arg3, arg4)
|
||||||
|
}
|
275
kms/azurekms/key_vault.go
Normal file
275
kms/azurekms/key_vault.go
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
package azurekms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
|
||||||
|
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||||
|
"github.com/Azure/go-autorest/autorest/date"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/smallstep/certificates/kms/apiv1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
apiv1.Register(apiv1.AzureKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
|
||||||
|
return New(ctx, opts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scheme is the scheme used for the Azure Key Vault uris.
|
||||||
|
const Scheme = "azurekms"
|
||||||
|
|
||||||
|
// keyIDRegexp is the regular expression that Key Vault uses on the kid. We can
|
||||||
|
// extract the vault, name and version of the key.
|
||||||
|
var keyIDRegexp = regexp.MustCompile(`^https://([0-9a-zA-Z-]+)\.vault\.azure\.net/keys/([0-9a-zA-Z-]+)/([0-9a-zA-Z-]+)$`)
|
||||||
|
|
||||||
|
var (
|
||||||
|
valueTrue = true
|
||||||
|
value2048 int32 = 2048
|
||||||
|
value3072 int32 = 3072
|
||||||
|
value4096 int32 = 4096
|
||||||
|
)
|
||||||
|
|
||||||
|
var now = func() time.Time {
|
||||||
|
return time.Now().UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyType struct {
|
||||||
|
Kty keyvault.JSONWebKeyType
|
||||||
|
Curve keyvault.JSONWebKeyCurveName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k keyType) KeyType(pl apiv1.ProtectionLevel) keyvault.JSONWebKeyType {
|
||||||
|
switch k.Kty {
|
||||||
|
case keyvault.EC:
|
||||||
|
if pl == apiv1.HSM {
|
||||||
|
return keyvault.ECHSM
|
||||||
|
}
|
||||||
|
return k.Kty
|
||||||
|
case keyvault.RSA:
|
||||||
|
if pl == apiv1.HSM {
|
||||||
|
return keyvault.RSAHSM
|
||||||
|
}
|
||||||
|
return k.Kty
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]keyType{
|
||||||
|
apiv1.UnspecifiedSignAlgorithm: {
|
||||||
|
Kty: keyvault.EC,
|
||||||
|
Curve: keyvault.P256,
|
||||||
|
},
|
||||||
|
apiv1.SHA256WithRSA: {
|
||||||
|
Kty: keyvault.RSA,
|
||||||
|
},
|
||||||
|
apiv1.SHA384WithRSA: {
|
||||||
|
Kty: keyvault.RSA,
|
||||||
|
},
|
||||||
|
apiv1.SHA512WithRSA: {
|
||||||
|
Kty: keyvault.RSA,
|
||||||
|
},
|
||||||
|
apiv1.SHA256WithRSAPSS: {
|
||||||
|
Kty: keyvault.RSA,
|
||||||
|
},
|
||||||
|
apiv1.SHA384WithRSAPSS: {
|
||||||
|
Kty: keyvault.RSA,
|
||||||
|
},
|
||||||
|
apiv1.SHA512WithRSAPSS: {
|
||||||
|
Kty: keyvault.RSA,
|
||||||
|
},
|
||||||
|
apiv1.ECDSAWithSHA256: {
|
||||||
|
Kty: keyvault.EC,
|
||||||
|
Curve: keyvault.P256,
|
||||||
|
},
|
||||||
|
apiv1.ECDSAWithSHA384: {
|
||||||
|
Kty: keyvault.EC,
|
||||||
|
Curve: keyvault.P384,
|
||||||
|
},
|
||||||
|
apiv1.ECDSAWithSHA512: {
|
||||||
|
Kty: keyvault.EC,
|
||||||
|
Curve: keyvault.P521,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// vaultResource is the value the client will use as audience.
|
||||||
|
const vaultResource = "https://vault.azure.net"
|
||||||
|
|
||||||
|
// KeyVaultClient is the interface implemented by keyvault.BaseClient. It will
|
||||||
|
// be used for testing purposes.
|
||||||
|
type KeyVaultClient interface {
|
||||||
|
GetKey(ctx context.Context, vaultBaseURL string, keyName string, keyVersion string) (keyvault.KeyBundle, error)
|
||||||
|
CreateKey(ctx context.Context, vaultBaseURL string, keyName string, parameters keyvault.KeyCreateParameters) (keyvault.KeyBundle, error)
|
||||||
|
Sign(ctx context.Context, vaultBaseURL string, keyName string, keyVersion string, parameters keyvault.KeySignParameters) (keyvault.KeyOperationResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyVault implements a KMS using Azure Key Vault.
|
||||||
|
//
|
||||||
|
// The URI format used in Azure Key Vault is the following:
|
||||||
|
//
|
||||||
|
// - azurekms:name=key-name;vault=vault-name
|
||||||
|
// - azurekms:name=key-name;vault=vault-name?version=key-version
|
||||||
|
// - azurekms:name=key-name;vault=vault-name?hsm=true
|
||||||
|
//
|
||||||
|
// The scheme is "azurekms"; "name" is the key name; "vault" is the key vault
|
||||||
|
// name where the key is located; "version" is an optional parameter that
|
||||||
|
// defines the version of they key, if version is not given, the latest one will
|
||||||
|
// be used; "hsm" defines if an HSM want to be used for this key, this is
|
||||||
|
// specially useful when this is used from `step`.
|
||||||
|
//
|
||||||
|
// TODO(mariano): The implementation is using /services/keyvault/v7.1/keyvault
|
||||||
|
// package, at some point Azure might create a keyvault client with all the
|
||||||
|
// functionality in /sdk/keyvault, we should migrate to that once available.
|
||||||
|
type KeyVault struct {
|
||||||
|
baseClient KeyVaultClient
|
||||||
|
}
|
||||||
|
|
||||||
|
var createClient = func(ctx context.Context, opts apiv1.Options) (KeyVaultClient, error) {
|
||||||
|
// Attempt to authorize with the following methods:
|
||||||
|
// 1. Environment variables.
|
||||||
|
// - Client credentials
|
||||||
|
// - Client certificate
|
||||||
|
// - Username and password
|
||||||
|
// - MSI
|
||||||
|
// 2. Using Azure CLI 2.0 on local development.
|
||||||
|
authorizer, err := auth.NewAuthorizerFromEnvironmentWithResource(vaultResource)
|
||||||
|
if err != nil {
|
||||||
|
authorizer, err = auth.NewAuthorizerFromCLIWithResource(vaultResource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error getting authorizer for key vault")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
baseClient := keyvault.New()
|
||||||
|
baseClient.Authorizer = authorizer
|
||||||
|
return &baseClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New initializes a new KMS implemented using Azure Key Vault.
|
||||||
|
func New(ctx context.Context, opts apiv1.Options) (*KeyVault, error) {
|
||||||
|
baseClient, err := createClient(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &KeyVault{
|
||||||
|
baseClient: baseClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicKey loads a public key from Azure Key Vault by its resource name.
|
||||||
|
func (k *KeyVault) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
|
||||||
|
if req.Name == "" {
|
||||||
|
return nil, errors.New("getPublicKeyRequest 'name' cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
vault, name, version, _, err := parseKeyName(req.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := defaultContext()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := k.baseClient.GetKey(ctx, vaultBaseURL(vault), name, version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "keyVault GetKey failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertKey(resp.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateKey creates a asymmetric key in Azure Key Vault.
|
||||||
|
func (k *KeyVault) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
|
||||||
|
if req.Name == "" {
|
||||||
|
return nil, errors.New("createKeyRequest 'name' cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
vault, name, _, hsm, err := parseKeyName(req.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override protection level to HSM only if it's not specified, and is given
|
||||||
|
// in the uri.
|
||||||
|
protectionLevel := req.ProtectionLevel
|
||||||
|
if protectionLevel == apiv1.UnspecifiedProtectionLevel && hsm {
|
||||||
|
protectionLevel = apiv1.HSM
|
||||||
|
}
|
||||||
|
|
||||||
|
kt, ok := signatureAlgorithmMapping[req.SignatureAlgorithm]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("keyVault does not support signature algorithm '%s'", req.SignatureAlgorithm)
|
||||||
|
}
|
||||||
|
var keySize *int32
|
||||||
|
if kt.Kty == keyvault.RSA || kt.Kty == keyvault.RSAHSM {
|
||||||
|
switch req.Bits {
|
||||||
|
case 2048:
|
||||||
|
keySize = &value2048
|
||||||
|
case 0, 3072:
|
||||||
|
keySize = &value3072
|
||||||
|
case 4096:
|
||||||
|
keySize = &value4096
|
||||||
|
default:
|
||||||
|
return nil, errors.Errorf("keyVault does not support key size %d", req.Bits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
created := date.UnixTime(now())
|
||||||
|
|
||||||
|
ctx, cancel := defaultContext()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := k.baseClient.CreateKey(ctx, vaultBaseURL(vault), name, keyvault.KeyCreateParameters{
|
||||||
|
Kty: kt.KeyType(protectionLevel),
|
||||||
|
KeySize: keySize,
|
||||||
|
Curve: kt.Curve,
|
||||||
|
KeyOps: &[]keyvault.JSONWebKeyOperation{
|
||||||
|
keyvault.Sign, keyvault.Verify,
|
||||||
|
},
|
||||||
|
KeyAttributes: &keyvault.KeyAttributes{
|
||||||
|
Enabled: &valueTrue,
|
||||||
|
Created: &created,
|
||||||
|
NotBefore: &created,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "keyVault CreateKey failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, err := convertKey(resp.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyURI := getKeyName(vault, name, resp)
|
||||||
|
return &apiv1.CreateKeyResponse{
|
||||||
|
Name: keyURI,
|
||||||
|
PublicKey: publicKey,
|
||||||
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: keyURI,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSigner returns a crypto.Signer from a previously created asymmetric key.
|
||||||
|
func (k *KeyVault) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
|
||||||
|
if req.SigningKey == "" {
|
||||||
|
return nil, errors.New("createSignerRequest 'signingKey' cannot be empty")
|
||||||
|
}
|
||||||
|
return NewSigner(k.baseClient, req.SigningKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the client connection to the Azure Key Vault. This is a noop.
|
||||||
|
func (k *KeyVault) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateName validates that the given string is a valid URI.
|
||||||
|
func (k *KeyVault) ValidateName(s string) error {
|
||||||
|
_, _, _, _, err := parseKeyName(s)
|
||||||
|
return err
|
||||||
|
}
|
581
kms/azurekms/key_vault_test.go
Normal file
581
kms/azurekms/key_vault_test.go
Normal file
|
@ -0,0 +1,581 @@
|
||||||
|
//go:generate mockgen -package mock -mock_names=KeyVaultClient=KeyVaultClient -destination internal/mock/key_vault_client.go github.com/smallstep/certificates/kms/azurekms KeyVaultClient
|
||||||
|
package azurekms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
|
||||||
|
"github.com/Azure/go-autorest/autorest/date"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/smallstep/certificates/kms/apiv1"
|
||||||
|
"github.com/smallstep/certificates/kms/azurekms/internal/mock"
|
||||||
|
"go.step.sm/crypto/keyutil"
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errTest = fmt.Errorf("test error")
|
||||||
|
|
||||||
|
func mockNow(t *testing.T) time.Time {
|
||||||
|
old := now
|
||||||
|
t0 := time.Unix(1234567890, 123).UTC()
|
||||||
|
now = func() time.Time {
|
||||||
|
return t0
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
now = old
|
||||||
|
})
|
||||||
|
return t0
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockClient(t *testing.T) *mock.KeyVaultClient {
|
||||||
|
t.Helper()
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
ctrl.Finish()
|
||||||
|
})
|
||||||
|
return mock.NewKeyVaultClient(ctrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createJWK(t *testing.T, pub crypto.PublicKey) *keyvault.JSONWebKey {
|
||||||
|
t.Helper()
|
||||||
|
b, err := json.Marshal(&jose.JSONWebKey{
|
||||||
|
Key: pub,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
key := new(keyvault.JSONWebKey)
|
||||||
|
if err := json.Unmarshal(b, key); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_now(t *testing.T) {
|
||||||
|
t0 := now()
|
||||||
|
if loc := t0.Location(); loc != time.UTC {
|
||||||
|
t.Errorf("now() Location = %v, want %v", loc, time.UTC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
client := mockClient(t)
|
||||||
|
old := createClient
|
||||||
|
t.Cleanup(func() {
|
||||||
|
createClient = old
|
||||||
|
})
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
opts apiv1.Options
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setup func()
|
||||||
|
args args
|
||||||
|
want *KeyVault
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", func() {
|
||||||
|
createClient = func(ctx context.Context, opts apiv1.Options) (KeyVaultClient, error) {
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
}, args{context.Background(), apiv1.Options{}}, &KeyVault{
|
||||||
|
baseClient: client,
|
||||||
|
}, false},
|
||||||
|
{"fail", func() {
|
||||||
|
createClient = func(ctx context.Context, opts apiv1.Options) (KeyVaultClient, error) {
|
||||||
|
return nil, errTest
|
||||||
|
}
|
||||||
|
}, args{context.Background(), apiv1.Options{}}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.setup()
|
||||||
|
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 TestKeyVault_GetPublicKey(t *testing.T) {
|
||||||
|
key, err := keyutil.GenerateDefaultSigner()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pub := key.Public()
|
||||||
|
jwk := createJWK(t, pub)
|
||||||
|
|
||||||
|
client := mockClient(t)
|
||||||
|
client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", "").Return(keyvault.KeyBundle{
|
||||||
|
Key: jwk,
|
||||||
|
}, nil)
|
||||||
|
client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", "my-version").Return(keyvault.KeyBundle{
|
||||||
|
Key: jwk,
|
||||||
|
}, nil)
|
||||||
|
client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "not-found", "my-version").Return(keyvault.KeyBundle{}, errTest)
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
baseClient KeyVaultClient
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
req *apiv1.GetPublicKeyRequest
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want crypto.PublicKey
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", fields{client}, args{&apiv1.GetPublicKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key",
|
||||||
|
}}, pub, false},
|
||||||
|
{"ok with version", fields{client}, args{&apiv1.GetPublicKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key?version=my-version",
|
||||||
|
}}, pub, false},
|
||||||
|
{"fail GetKey", fields{client}, args{&apiv1.GetPublicKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=not-found?version=my-version",
|
||||||
|
}}, nil, true},
|
||||||
|
{"fail empty", fields{client}, args{&apiv1.GetPublicKeyRequest{
|
||||||
|
Name: "",
|
||||||
|
}}, nil, true},
|
||||||
|
{"fail vault", fields{client}, args{&apiv1.GetPublicKeyRequest{
|
||||||
|
Name: "azurekms:vault=;name=not-found?version=my-version",
|
||||||
|
}}, nil, true},
|
||||||
|
{"fail id", fields{client}, args{&apiv1.GetPublicKeyRequest{
|
||||||
|
Name: "azurekms:vault=;name=?version=my-version",
|
||||||
|
}}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
k := &KeyVault{
|
||||||
|
baseClient: tt.fields.baseClient,
|
||||||
|
}
|
||||||
|
got, err := k.GetPublicKey(tt.args.req)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("KeyVault.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("KeyVault.GetPublicKey() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyVault_CreateKey(t *testing.T) {
|
||||||
|
ecKey, err := keyutil.GenerateDefaultSigner()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rsaKey, err := keyutil.GenerateSigner("RSA", "", 2048)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ecPub := ecKey.Public()
|
||||||
|
rsaPub := rsaKey.Public()
|
||||||
|
ecJWK := createJWK(t, ecPub)
|
||||||
|
rsaJWK := createJWK(t, rsaPub)
|
||||||
|
|
||||||
|
t0 := date.UnixTime(mockNow(t))
|
||||||
|
client := mockClient(t)
|
||||||
|
|
||||||
|
expects := []struct {
|
||||||
|
Name string
|
||||||
|
Kty keyvault.JSONWebKeyType
|
||||||
|
KeySize *int32
|
||||||
|
Curve keyvault.JSONWebKeyCurveName
|
||||||
|
Key *keyvault.JSONWebKey
|
||||||
|
}{
|
||||||
|
{"P-256", keyvault.EC, nil, keyvault.P256, ecJWK},
|
||||||
|
{"P-256 HSM", keyvault.ECHSM, nil, keyvault.P256, ecJWK},
|
||||||
|
{"P-256 HSM (uri)", keyvault.ECHSM, nil, keyvault.P256, ecJWK},
|
||||||
|
{"P-256 Default", keyvault.EC, nil, keyvault.P256, ecJWK},
|
||||||
|
{"P-384", keyvault.EC, nil, keyvault.P384, ecJWK},
|
||||||
|
{"P-521", keyvault.EC, nil, keyvault.P521, ecJWK},
|
||||||
|
{"RSA 0", keyvault.RSA, &value3072, "", rsaJWK},
|
||||||
|
{"RSA 0 HSM", keyvault.RSAHSM, &value3072, "", rsaJWK},
|
||||||
|
{"RSA 0 HSM (uri)", keyvault.RSAHSM, &value3072, "", rsaJWK},
|
||||||
|
{"RSA 2048", keyvault.RSA, &value2048, "", rsaJWK},
|
||||||
|
{"RSA 3072", keyvault.RSA, &value3072, "", rsaJWK},
|
||||||
|
{"RSA 4096", keyvault.RSA, &value4096, "", rsaJWK},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range expects {
|
||||||
|
client.EXPECT().CreateKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", keyvault.KeyCreateParameters{
|
||||||
|
Kty: e.Kty,
|
||||||
|
KeySize: e.KeySize,
|
||||||
|
Curve: e.Curve,
|
||||||
|
KeyOps: &[]keyvault.JSONWebKeyOperation{
|
||||||
|
keyvault.Sign, keyvault.Verify,
|
||||||
|
},
|
||||||
|
KeyAttributes: &keyvault.KeyAttributes{
|
||||||
|
Enabled: &valueTrue,
|
||||||
|
Created: &t0,
|
||||||
|
NotBefore: &t0,
|
||||||
|
},
|
||||||
|
}).Return(keyvault.KeyBundle{
|
||||||
|
Key: e.Key,
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
client.EXPECT().CreateKey(gomock.Any(), "https://my-vault.vault.azure.net/", "not-found", gomock.Any()).Return(keyvault.KeyBundle{}, errTest)
|
||||||
|
client.EXPECT().CreateKey(gomock.Any(), "https://my-vault.vault.azure.net/", "not-found", gomock.Any()).Return(keyvault.KeyBundle{
|
||||||
|
Key: nil,
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
baseClient KeyVaultClient
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
req *apiv1.CreateKeyRequest
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want *apiv1.CreateKeyResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok P-256", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key",
|
||||||
|
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||||
|
ProtectionLevel: apiv1.Software,
|
||||||
|
}}, &apiv1.CreateKeyResponse{
|
||||||
|
Name: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
PublicKey: ecPub,
|
||||||
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
{"ok P-256 HSM", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key",
|
||||||
|
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||||
|
ProtectionLevel: apiv1.HSM,
|
||||||
|
}}, &apiv1.CreateKeyResponse{
|
||||||
|
Name: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
PublicKey: ecPub,
|
||||||
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
{"ok P-256 HSM (uri)", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key?hsm=true",
|
||||||
|
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||||
|
}}, &apiv1.CreateKeyResponse{
|
||||||
|
Name: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
PublicKey: ecPub,
|
||||||
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
{"ok P-256 Default", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key",
|
||||||
|
}}, &apiv1.CreateKeyResponse{
|
||||||
|
Name: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
PublicKey: ecPub,
|
||||||
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
{"ok P-384", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key",
|
||||||
|
SignatureAlgorithm: apiv1.ECDSAWithSHA384,
|
||||||
|
}}, &apiv1.CreateKeyResponse{
|
||||||
|
Name: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
PublicKey: ecPub,
|
||||||
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
{"ok P-521", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key",
|
||||||
|
SignatureAlgorithm: apiv1.ECDSAWithSHA512,
|
||||||
|
}}, &apiv1.CreateKeyResponse{
|
||||||
|
Name: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
PublicKey: ecPub,
|
||||||
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
{"ok RSA 0", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key",
|
||||||
|
Bits: 0,
|
||||||
|
SignatureAlgorithm: apiv1.SHA256WithRSA,
|
||||||
|
ProtectionLevel: apiv1.Software,
|
||||||
|
}}, &apiv1.CreateKeyResponse{
|
||||||
|
Name: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
PublicKey: rsaPub,
|
||||||
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
{"ok RSA 0 HSM", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key",
|
||||||
|
Bits: 0,
|
||||||
|
SignatureAlgorithm: apiv1.SHA256WithRSAPSS,
|
||||||
|
ProtectionLevel: apiv1.HSM,
|
||||||
|
}}, &apiv1.CreateKeyResponse{
|
||||||
|
Name: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
PublicKey: rsaPub,
|
||||||
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
{"ok RSA 0 HSM (uri)", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key;hsm=true",
|
||||||
|
Bits: 0,
|
||||||
|
SignatureAlgorithm: apiv1.SHA256WithRSAPSS,
|
||||||
|
}}, &apiv1.CreateKeyResponse{
|
||||||
|
Name: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
PublicKey: rsaPub,
|
||||||
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
{"ok RSA 2048", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key",
|
||||||
|
Bits: 2048,
|
||||||
|
SignatureAlgorithm: apiv1.SHA384WithRSA,
|
||||||
|
}}, &apiv1.CreateKeyResponse{
|
||||||
|
Name: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
PublicKey: rsaPub,
|
||||||
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
{"ok RSA 3072", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key",
|
||||||
|
Bits: 3072,
|
||||||
|
SignatureAlgorithm: apiv1.SHA512WithRSA,
|
||||||
|
}}, &apiv1.CreateKeyResponse{
|
||||||
|
Name: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
PublicKey: rsaPub,
|
||||||
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
{"ok RSA 4096", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=my-key",
|
||||||
|
Bits: 4096,
|
||||||
|
SignatureAlgorithm: apiv1.SHA512WithRSAPSS,
|
||||||
|
}}, &apiv1.CreateKeyResponse{
|
||||||
|
Name: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
PublicKey: rsaPub,
|
||||||
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:name=my-key;vault=my-vault",
|
||||||
|
},
|
||||||
|
}, false},
|
||||||
|
{"fail createKey", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=not-found",
|
||||||
|
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||||
|
}}, nil, true},
|
||||||
|
{"fail convertKey", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=not-found",
|
||||||
|
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||||
|
}}, nil, true},
|
||||||
|
{"fail name", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "",
|
||||||
|
}}, nil, true},
|
||||||
|
{"fail vault", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=;name=not-found?version=my-version",
|
||||||
|
}}, nil, true},
|
||||||
|
{"fail id", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=?version=my-version",
|
||||||
|
}}, nil, true},
|
||||||
|
{"fail SignatureAlgorithm", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=not-found",
|
||||||
|
SignatureAlgorithm: apiv1.PureEd25519,
|
||||||
|
}}, nil, true},
|
||||||
|
{"fail bit size", fields{client}, args{&apiv1.CreateKeyRequest{
|
||||||
|
Name: "azurekms:vault=my-vault;name=not-found",
|
||||||
|
SignatureAlgorithm: apiv1.SHA384WithRSAPSS,
|
||||||
|
Bits: 1024,
|
||||||
|
}}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
k := &KeyVault{
|
||||||
|
baseClient: tt.fields.baseClient,
|
||||||
|
}
|
||||||
|
got, err := k.CreateKey(tt.args.req)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("KeyVault.CreateKey() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("KeyVault.CreateKey() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyVault_CreateSigner(t *testing.T) {
|
||||||
|
key, err := keyutil.GenerateDefaultSigner()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pub := key.Public()
|
||||||
|
jwk := createJWK(t, pub)
|
||||||
|
|
||||||
|
client := mockClient(t)
|
||||||
|
client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", "").Return(keyvault.KeyBundle{
|
||||||
|
Key: jwk,
|
||||||
|
}, nil)
|
||||||
|
client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", "my-version").Return(keyvault.KeyBundle{
|
||||||
|
Key: jwk,
|
||||||
|
}, nil)
|
||||||
|
client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "not-found", "my-version").Return(keyvault.KeyBundle{}, errTest)
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
baseClient KeyVaultClient
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
req *apiv1.CreateSignerRequest
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want crypto.Signer
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", fields{client}, args{&apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:vault=my-vault;name=my-key",
|
||||||
|
}}, &Signer{
|
||||||
|
client: client,
|
||||||
|
vaultBaseURL: "https://my-vault.vault.azure.net/",
|
||||||
|
name: "my-key",
|
||||||
|
version: "",
|
||||||
|
publicKey: pub,
|
||||||
|
}, false},
|
||||||
|
{"ok with version", fields{client}, args{&apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:vault=my-vault;name=my-key;version=my-version",
|
||||||
|
}}, &Signer{
|
||||||
|
client: client,
|
||||||
|
vaultBaseURL: "https://my-vault.vault.azure.net/",
|
||||||
|
name: "my-key",
|
||||||
|
version: "my-version",
|
||||||
|
publicKey: pub,
|
||||||
|
}, false},
|
||||||
|
{"fail GetKey", fields{client}, args{&apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "azurekms:vault=my-vault;name=not-found;version=my-version",
|
||||||
|
}}, nil, true},
|
||||||
|
{"fail SigningKey", fields{client}, args{&apiv1.CreateSignerRequest{
|
||||||
|
SigningKey: "",
|
||||||
|
}}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
k := &KeyVault{
|
||||||
|
baseClient: tt.fields.baseClient,
|
||||||
|
}
|
||||||
|
got, err := k.CreateSigner(tt.args.req)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("KeyVault.CreateSigner() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("KeyVault.CreateSigner() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyVault_Close(t *testing.T) {
|
||||||
|
client := mockClient(t)
|
||||||
|
type fields struct {
|
||||||
|
baseClient KeyVaultClient
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", fields{client}, false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
k := &KeyVault{
|
||||||
|
baseClient: tt.fields.baseClient,
|
||||||
|
}
|
||||||
|
if err := k.Close(); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("KeyVault.Close() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_keyType_KeyType(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Kty keyvault.JSONWebKeyType
|
||||||
|
Curve keyvault.JSONWebKeyCurveName
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
pl apiv1.ProtectionLevel
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want keyvault.JSONWebKeyType
|
||||||
|
}{
|
||||||
|
{"ec", fields{keyvault.EC, keyvault.P256}, args{apiv1.UnspecifiedProtectionLevel}, keyvault.EC},
|
||||||
|
{"ec software", fields{keyvault.EC, keyvault.P384}, args{apiv1.Software}, keyvault.EC},
|
||||||
|
{"ec hsm", fields{keyvault.EC, keyvault.P521}, args{apiv1.HSM}, keyvault.ECHSM},
|
||||||
|
{"rsa", fields{keyvault.RSA, keyvault.P256}, args{apiv1.UnspecifiedProtectionLevel}, keyvault.RSA},
|
||||||
|
{"rsa software", fields{keyvault.RSA, ""}, args{apiv1.Software}, keyvault.RSA},
|
||||||
|
{"rsa hsm", fields{keyvault.RSA, ""}, args{apiv1.HSM}, keyvault.RSAHSM},
|
||||||
|
{"empty", fields{"FOO", ""}, args{apiv1.UnspecifiedProtectionLevel}, ""},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
k := keyType{
|
||||||
|
Kty: tt.fields.Kty,
|
||||||
|
Curve: tt.fields.Curve,
|
||||||
|
}
|
||||||
|
if got := k.KeyType(tt.args.pl); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("keyType.KeyType() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyVault_ValidateName(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
s string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", args{"azurekms:name=my-key;vault=my-vault"}, false},
|
||||||
|
{"ok hsm", args{"azurekms:name=my-key;vault=my-vault?hsm=true"}, false},
|
||||||
|
{"fail scheme", args{"azure:name=my-key;vault=my-vault"}, true},
|
||||||
|
{"fail parse uri", args{"azurekms:name=%ZZ;vault=my-vault"}, true},
|
||||||
|
{"fail no name", args{"azurekms:vault=my-vault"}, true},
|
||||||
|
{"fail no vault", args{"azurekms:name=my-key"}, true},
|
||||||
|
{"fail empty", args{""}, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
k := &KeyVault{}
|
||||||
|
if err := k.ValidateName(tt.args.s); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("KeyVault.ValidateName() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
160
kms/azurekms/signer.go
Normal file
160
kms/azurekms/signer.go
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
package azurekms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
|
"golang.org/x/crypto/cryptobyte/asn1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Signer implements a crypto.Signer using the AWS KMS.
|
||||||
|
type Signer struct {
|
||||||
|
client KeyVaultClient
|
||||||
|
vaultBaseURL string
|
||||||
|
name string
|
||||||
|
version string
|
||||||
|
publicKey crypto.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSigner creates a new signer using a key in the AWS KMS.
|
||||||
|
func NewSigner(client KeyVaultClient, signingKey string) (crypto.Signer, error) {
|
||||||
|
vault, name, version, _, err := parseKeyName(signingKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that the key exists.
|
||||||
|
signer := &Signer{
|
||||||
|
client: client,
|
||||||
|
vaultBaseURL: vaultBaseURL(vault),
|
||||||
|
name: name,
|
||||||
|
version: version,
|
||||||
|
}
|
||||||
|
if err := signer.preloadKey(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return signer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Signer) preloadKey() error {
|
||||||
|
ctx, cancel := defaultContext()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := s.client.GetKey(ctx, s.vaultBaseURL, s.name, s.version)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "keyVault GetKey failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.publicKey, err = convertKey(resp.Key)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public returns the public key of this signer or an error.
|
||||||
|
func (s *Signer) Public() crypto.PublicKey {
|
||||||
|
return s.publicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs digest with the private key stored in the AWS KMS.
|
||||||
|
func (s *Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||||
|
alg, err := getSigningAlgorithm(s.Public(), opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := defaultContext()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
b64 := base64.RawURLEncoding.EncodeToString(digest)
|
||||||
|
|
||||||
|
resp, err := s.client.Sign(ctx, s.vaultBaseURL, s.name, s.version, keyvault.KeySignParameters{
|
||||||
|
Algorithm: alg,
|
||||||
|
Value: &b64,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "keyVault Sign failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := base64.RawURLEncoding.DecodeString(*resp.Result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error decoding keyVault Sign result")
|
||||||
|
}
|
||||||
|
|
||||||
|
var octetSize int
|
||||||
|
switch alg {
|
||||||
|
case keyvault.ES256:
|
||||||
|
octetSize = 32 // 256-bit, concat(R,S) = 64 bytes
|
||||||
|
case keyvault.ES384:
|
||||||
|
octetSize = 48 // 384-bit, concat(R,S) = 96 bytes
|
||||||
|
case keyvault.ES512:
|
||||||
|
octetSize = 66 // 528-bit, concat(R,S) = 132 bytes
|
||||||
|
default:
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to asn1
|
||||||
|
if len(sig) != octetSize*2 {
|
||||||
|
return nil, errors.Errorf("keyVault Sign failed: unexpected signature length")
|
||||||
|
}
|
||||||
|
var b cryptobyte.Builder
|
||||||
|
b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
|
||||||
|
b.AddASN1BigInt(new(big.Int).SetBytes(sig[:octetSize])) // R
|
||||||
|
b.AddASN1BigInt(new(big.Int).SetBytes(sig[octetSize:])) // S
|
||||||
|
})
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSigningAlgorithm(key crypto.PublicKey, opts crypto.SignerOpts) (keyvault.JSONWebKeySignatureAlgorithm, error) {
|
||||||
|
switch key.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
hashFunc := opts.HashFunc()
|
||||||
|
pss, isPSS := opts.(*rsa.PSSOptions)
|
||||||
|
// Random salt lengths are not supported
|
||||||
|
if isPSS &&
|
||||||
|
pss.SaltLength != rsa.PSSSaltLengthAuto &&
|
||||||
|
pss.SaltLength != rsa.PSSSaltLengthEqualsHash &&
|
||||||
|
pss.SaltLength != hashFunc.Size() {
|
||||||
|
return "", errors.Errorf("unsupported RSA-PSS salt length %d", pss.SaltLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch h := hashFunc; h {
|
||||||
|
case crypto.SHA256:
|
||||||
|
if isPSS {
|
||||||
|
return keyvault.PS256, nil
|
||||||
|
}
|
||||||
|
return keyvault.RS256, nil
|
||||||
|
case crypto.SHA384:
|
||||||
|
if isPSS {
|
||||||
|
return keyvault.PS384, nil
|
||||||
|
}
|
||||||
|
return keyvault.RS384, nil
|
||||||
|
case crypto.SHA512:
|
||||||
|
if isPSS {
|
||||||
|
return keyvault.PS512, nil
|
||||||
|
}
|
||||||
|
return keyvault.RS512, nil
|
||||||
|
default:
|
||||||
|
return "", errors.Errorf("unsupported hash function %v", h)
|
||||||
|
}
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
switch h := opts.HashFunc(); h {
|
||||||
|
case crypto.SHA256:
|
||||||
|
return keyvault.ES256, nil
|
||||||
|
case crypto.SHA384:
|
||||||
|
return keyvault.ES384, nil
|
||||||
|
case crypto.SHA512:
|
||||||
|
return keyvault.ES512, nil
|
||||||
|
default:
|
||||||
|
return "", errors.Errorf("unsupported hash function %v", h)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return "", errors.Errorf("unsupported key type %T", key)
|
||||||
|
}
|
||||||
|
}
|
339
kms/azurekms/signer_test.go
Normal file
339
kms/azurekms/signer_test.go
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
package azurekms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"go.step.sm/crypto/keyutil"
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
|
"golang.org/x/crypto/cryptobyte/asn1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewSigner(t *testing.T) {
|
||||||
|
key, err := keyutil.GenerateDefaultSigner()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pub := key.Public()
|
||||||
|
jwk := createJWK(t, pub)
|
||||||
|
|
||||||
|
client := mockClient(t)
|
||||||
|
client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", "").Return(keyvault.KeyBundle{
|
||||||
|
Key: jwk,
|
||||||
|
}, nil)
|
||||||
|
client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", "my-version").Return(keyvault.KeyBundle{
|
||||||
|
Key: jwk,
|
||||||
|
}, nil)
|
||||||
|
client.EXPECT().GetKey(gomock.Any(), "https://my-vault.vault.azure.net/", "not-found", "my-version").Return(keyvault.KeyBundle{}, errTest)
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
client KeyVaultClient
|
||||||
|
signingKey string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want crypto.Signer
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", args{client, "azurekms:vault=my-vault;name=my-key"}, &Signer{
|
||||||
|
client: client,
|
||||||
|
vaultBaseURL: "https://my-vault.vault.azure.net/",
|
||||||
|
name: "my-key",
|
||||||
|
version: "",
|
||||||
|
publicKey: pub,
|
||||||
|
}, false},
|
||||||
|
{"ok with version", args{client, "azurekms:name=my-key;vault=my-vault?version=my-version"}, &Signer{
|
||||||
|
client: client,
|
||||||
|
vaultBaseURL: "https://my-vault.vault.azure.net/",
|
||||||
|
name: "my-key",
|
||||||
|
version: "my-version",
|
||||||
|
publicKey: pub,
|
||||||
|
}, false},
|
||||||
|
{"fail GetKey", args{client, "azurekms:name=not-found;vault=my-vault?version=my-version"}, nil, true},
|
||||||
|
{"fail vault", args{client, "azurekms:name=not-found;vault="}, nil, true},
|
||||||
|
{"fail id", args{client, "azurekms:name=;vault=my-vault?version=my-version"}, nil, true},
|
||||||
|
{"fail scheme", args{client, "kms:name=not-found;vault=my-vault?version=my-version"}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := NewSigner(tt.args.client, tt.args.signingKey)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("NewSigner() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("NewSigner() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSigner_Public(t *testing.T) {
|
||||||
|
key, err := keyutil.GenerateDefaultSigner()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pub := key.Public()
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
publicKey crypto.PublicKey
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want crypto.PublicKey
|
||||||
|
}{
|
||||||
|
{"ok", fields{pub}, pub},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := &Signer{
|
||||||
|
publicKey: tt.fields.publicKey,
|
||||||
|
}
|
||||||
|
if got := s.Public(); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Signer.Public() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSigner_Sign(t *testing.T) {
|
||||||
|
sign := func(kty, crv string, bits int, opts crypto.SignerOpts) (crypto.PublicKey, []byte, string, []byte) {
|
||||||
|
key, err := keyutil.GenerateSigner(kty, crv, bits)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
h := opts.HashFunc().New()
|
||||||
|
h.Write([]byte("random-data"))
|
||||||
|
sum := h.Sum(nil)
|
||||||
|
|
||||||
|
var sig, resultSig []byte
|
||||||
|
if priv, ok := key.(*ecdsa.PrivateKey); ok {
|
||||||
|
r, s, err := ecdsa.Sign(rand.Reader, priv, sum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
curveBits := priv.Params().BitSize
|
||||||
|
keyBytes := curveBits / 8
|
||||||
|
if curveBits%8 > 0 {
|
||||||
|
keyBytes++
|
||||||
|
}
|
||||||
|
rBytes := r.Bytes()
|
||||||
|
rBytesPadded := make([]byte, keyBytes)
|
||||||
|
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
|
||||||
|
|
||||||
|
sBytes := s.Bytes()
|
||||||
|
sBytesPadded := make([]byte, keyBytes)
|
||||||
|
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
|
||||||
|
// nolint:gocritic
|
||||||
|
resultSig = append(rBytesPadded, sBytesPadded...)
|
||||||
|
|
||||||
|
var b cryptobyte.Builder
|
||||||
|
b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
|
||||||
|
b.AddASN1BigInt(r)
|
||||||
|
b.AddASN1BigInt(s)
|
||||||
|
})
|
||||||
|
sig, err = b.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sig, err = key.Sign(rand.Reader, sum, opts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
resultSig = sig
|
||||||
|
}
|
||||||
|
|
||||||
|
return key.Public(), h.Sum(nil), base64.RawURLEncoding.EncodeToString(resultSig), sig
|
||||||
|
}
|
||||||
|
|
||||||
|
p256, p256Digest, p256ResultSig, p256Sig := sign("EC", "P-256", 0, crypto.SHA256)
|
||||||
|
p384, p384Digest, p386ResultSig, p384Sig := sign("EC", "P-384", 0, crypto.SHA384)
|
||||||
|
p521, p521Digest, p521ResultSig, p521Sig := sign("EC", "P-521", 0, crypto.SHA512)
|
||||||
|
rsaSHA256, rsaSHA256Digest, rsaSHA256ResultSig, rsaSHA256Sig := sign("RSA", "", 2048, crypto.SHA256)
|
||||||
|
rsaSHA384, rsaSHA384Digest, rsaSHA384ResultSig, rsaSHA384Sig := sign("RSA", "", 2048, crypto.SHA384)
|
||||||
|
rsaSHA512, rsaSHA512Digest, rsaSHA512ResultSig, rsaSHA512Sig := sign("RSA", "", 2048, crypto.SHA512)
|
||||||
|
rsaPSSSHA256, rsaPSSSHA256Digest, rsaPSSSHA256ResultSig, rsaPSSSHA256Sig := sign("RSA", "", 2048, &rsa.PSSOptions{
|
||||||
|
SaltLength: rsa.PSSSaltLengthAuto,
|
||||||
|
Hash: crypto.SHA256,
|
||||||
|
})
|
||||||
|
rsaPSSSHA384, rsaPSSSHA384Digest, rsaPSSSHA384ResultSig, rsaPSSSHA384Sig := sign("RSA", "", 2048, &rsa.PSSOptions{
|
||||||
|
SaltLength: rsa.PSSSaltLengthAuto,
|
||||||
|
Hash: crypto.SHA512,
|
||||||
|
})
|
||||||
|
rsaPSSSHA512, rsaPSSSHA512Digest, rsaPSSSHA512ResultSig, rsaPSSSHA512Sig := sign("RSA", "", 2048, &rsa.PSSOptions{
|
||||||
|
SaltLength: rsa.PSSSaltLengthAuto,
|
||||||
|
Hash: crypto.SHA512,
|
||||||
|
})
|
||||||
|
|
||||||
|
ed25519Key, err := keyutil.GenerateSigner("OKP", "Ed25519", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := mockClient(t)
|
||||||
|
expects := []struct {
|
||||||
|
name string
|
||||||
|
keyVersion string
|
||||||
|
alg keyvault.JSONWebKeySignatureAlgorithm
|
||||||
|
digest []byte
|
||||||
|
result keyvault.KeyOperationResult
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{"P-256", "", keyvault.ES256, p256Digest, keyvault.KeyOperationResult{
|
||||||
|
Result: &p256ResultSig,
|
||||||
|
}, nil},
|
||||||
|
{"P-384", "my-version", keyvault.ES384, p384Digest, keyvault.KeyOperationResult{
|
||||||
|
Result: &p386ResultSig,
|
||||||
|
}, nil},
|
||||||
|
{"P-521", "my-version", keyvault.ES512, p521Digest, keyvault.KeyOperationResult{
|
||||||
|
Result: &p521ResultSig,
|
||||||
|
}, nil},
|
||||||
|
{"RSA SHA256", "", keyvault.RS256, rsaSHA256Digest, keyvault.KeyOperationResult{
|
||||||
|
Result: &rsaSHA256ResultSig,
|
||||||
|
}, nil},
|
||||||
|
{"RSA SHA384", "", keyvault.RS384, rsaSHA384Digest, keyvault.KeyOperationResult{
|
||||||
|
Result: &rsaSHA384ResultSig,
|
||||||
|
}, nil},
|
||||||
|
{"RSA SHA512", "", keyvault.RS512, rsaSHA512Digest, keyvault.KeyOperationResult{
|
||||||
|
Result: &rsaSHA512ResultSig,
|
||||||
|
}, nil},
|
||||||
|
{"RSA-PSS SHA256", "", keyvault.PS256, rsaPSSSHA256Digest, keyvault.KeyOperationResult{
|
||||||
|
Result: &rsaPSSSHA256ResultSig,
|
||||||
|
}, nil},
|
||||||
|
{"RSA-PSS SHA384", "", keyvault.PS384, rsaPSSSHA384Digest, keyvault.KeyOperationResult{
|
||||||
|
Result: &rsaPSSSHA384ResultSig,
|
||||||
|
}, nil},
|
||||||
|
{"RSA-PSS SHA512", "", keyvault.PS512, rsaPSSSHA512Digest, keyvault.KeyOperationResult{
|
||||||
|
Result: &rsaPSSSHA512ResultSig,
|
||||||
|
}, nil},
|
||||||
|
// Errors
|
||||||
|
{"fail Sign", "", keyvault.RS256, rsaSHA256Digest, keyvault.KeyOperationResult{}, errTest},
|
||||||
|
{"fail sign length", "", keyvault.ES256, p256Digest, keyvault.KeyOperationResult{
|
||||||
|
Result: &rsaSHA256ResultSig,
|
||||||
|
}, nil},
|
||||||
|
{"fail base64", "", keyvault.ES256, p256Digest, keyvault.KeyOperationResult{
|
||||||
|
Result: func() *string {
|
||||||
|
v := "😎"
|
||||||
|
return &v
|
||||||
|
}(),
|
||||||
|
}, nil},
|
||||||
|
}
|
||||||
|
for _, e := range expects {
|
||||||
|
value := base64.RawURLEncoding.EncodeToString(e.digest)
|
||||||
|
client.EXPECT().Sign(gomock.Any(), "https://my-vault.vault.azure.net/", "my-key", e.keyVersion, keyvault.KeySignParameters{
|
||||||
|
Algorithm: e.alg,
|
||||||
|
Value: &value,
|
||||||
|
}).Return(e.result, e.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
client KeyVaultClient
|
||||||
|
vaultBaseURL string
|
||||||
|
name string
|
||||||
|
version string
|
||||||
|
publicKey crypto.PublicKey
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
rand io.Reader
|
||||||
|
digest []byte
|
||||||
|
opts crypto.SignerOpts
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want []byte
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok P-256", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", p256}, args{
|
||||||
|
rand.Reader, p256Digest, crypto.SHA256,
|
||||||
|
}, p256Sig, false},
|
||||||
|
{"ok P-384", fields{client, "https://my-vault.vault.azure.net/", "my-key", "my-version", p384}, args{
|
||||||
|
rand.Reader, p384Digest, crypto.SHA384,
|
||||||
|
}, p384Sig, false},
|
||||||
|
{"ok P-521", fields{client, "https://my-vault.vault.azure.net/", "my-key", "my-version", p521}, args{
|
||||||
|
rand.Reader, p521Digest, crypto.SHA512,
|
||||||
|
}, p521Sig, false},
|
||||||
|
{"ok RSA SHA256", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaSHA256}, args{
|
||||||
|
rand.Reader, rsaSHA256Digest, crypto.SHA256,
|
||||||
|
}, rsaSHA256Sig, false},
|
||||||
|
{"ok RSA SHA384", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaSHA384}, args{
|
||||||
|
rand.Reader, rsaSHA384Digest, crypto.SHA384,
|
||||||
|
}, rsaSHA384Sig, false},
|
||||||
|
{"ok RSA SHA512", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaSHA512}, args{
|
||||||
|
rand.Reader, rsaSHA512Digest, crypto.SHA512,
|
||||||
|
}, rsaSHA512Sig, false},
|
||||||
|
{"ok RSA-PSS SHA256", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaPSSSHA256}, args{
|
||||||
|
rand.Reader, rsaPSSSHA256Digest, &rsa.PSSOptions{
|
||||||
|
SaltLength: rsa.PSSSaltLengthAuto,
|
||||||
|
Hash: crypto.SHA256,
|
||||||
|
},
|
||||||
|
}, rsaPSSSHA256Sig, false},
|
||||||
|
{"ok RSA-PSS SHA384", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaPSSSHA384}, args{
|
||||||
|
rand.Reader, rsaPSSSHA384Digest, &rsa.PSSOptions{
|
||||||
|
SaltLength: rsa.PSSSaltLengthEqualsHash,
|
||||||
|
Hash: crypto.SHA384,
|
||||||
|
},
|
||||||
|
}, rsaPSSSHA384Sig, false},
|
||||||
|
{"ok RSA-PSS SHA512", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaPSSSHA512}, args{
|
||||||
|
rand.Reader, rsaPSSSHA512Digest, &rsa.PSSOptions{
|
||||||
|
SaltLength: 64,
|
||||||
|
Hash: crypto.SHA512,
|
||||||
|
},
|
||||||
|
}, rsaPSSSHA512Sig, false},
|
||||||
|
{"fail Sign", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaSHA256}, args{
|
||||||
|
rand.Reader, rsaSHA256Digest, crypto.SHA256,
|
||||||
|
}, nil, true},
|
||||||
|
{"fail sign length", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", p256}, args{
|
||||||
|
rand.Reader, p256Digest, crypto.SHA256,
|
||||||
|
}, nil, true},
|
||||||
|
{"fail base64", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", p256}, args{
|
||||||
|
rand.Reader, p256Digest, crypto.SHA256,
|
||||||
|
}, nil, true},
|
||||||
|
{"fail RSA-PSS salt length", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaPSSSHA256}, args{
|
||||||
|
rand.Reader, rsaPSSSHA256Digest, &rsa.PSSOptions{
|
||||||
|
SaltLength: 64,
|
||||||
|
Hash: crypto.SHA256,
|
||||||
|
},
|
||||||
|
}, nil, true},
|
||||||
|
{"fail RSA Hash", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", rsaSHA256}, args{
|
||||||
|
rand.Reader, rsaSHA256Digest, crypto.SHA1,
|
||||||
|
}, nil, true},
|
||||||
|
{"fail ECDSA Hash", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", p256}, args{
|
||||||
|
rand.Reader, p256Digest, crypto.MD5,
|
||||||
|
}, nil, true},
|
||||||
|
{"fail Ed25519", fields{client, "https://my-vault.vault.azure.net/", "my-key", "", ed25519Key}, args{
|
||||||
|
rand.Reader, []byte("message"), crypto.Hash(0),
|
||||||
|
}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
s := &Signer{
|
||||||
|
client: tt.fields.client,
|
||||||
|
vaultBaseURL: tt.fields.vaultBaseURL,
|
||||||
|
name: tt.fields.name,
|
||||||
|
version: tt.fields.version,
|
||||||
|
publicKey: tt.fields.publicKey,
|
||||||
|
}
|
||||||
|
got, err := s.Sign(tt.args.rand, tt.args.digest, tt.args.opts)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Signer.Sign() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Signer.Sign() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
88
kms/azurekms/utils.go
Normal file
88
kms/azurekms/utils.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package azurekms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/smallstep/certificates/kms/uri"
|
||||||
|
"go.step.sm/crypto/jose"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultContext returns the default context used in requests to azure.
|
||||||
|
func defaultContext() (context.Context, context.CancelFunc) {
|
||||||
|
return context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getKeyName returns the uri of the key vault key.
|
||||||
|
func getKeyName(vault, name string, bundle keyvault.KeyBundle) string {
|
||||||
|
if bundle.Key != nil && bundle.Key.Kid != nil {
|
||||||
|
sm := keyIDRegexp.FindAllStringSubmatch(*bundle.Key.Kid, 1)
|
||||||
|
if len(sm) == 1 && len(sm[0]) == 4 {
|
||||||
|
m := sm[0]
|
||||||
|
u := uri.New(Scheme, url.Values{
|
||||||
|
"vault": []string{m[1]},
|
||||||
|
"name": []string{m[2]},
|
||||||
|
})
|
||||||
|
u.RawQuery = url.Values{"version": []string{m[3]}}.Encode()
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to URI without id.
|
||||||
|
return uri.New(Scheme, url.Values{
|
||||||
|
"vault": []string{vault},
|
||||||
|
"name": []string{name},
|
||||||
|
}).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseKeyName returns the key vault, name and version from URIs like:
|
||||||
|
//
|
||||||
|
// - azurekms:vault=key-vault;name=key-name
|
||||||
|
// - azurekms:vault=key-vault;name=key-name?version=key-id
|
||||||
|
// - azurekms:vault=key-vault;name=key-name?version=key-id&hsm=true
|
||||||
|
//
|
||||||
|
// The key-id defines the version of the key, if it is not passed the latest
|
||||||
|
// version will be used.
|
||||||
|
//
|
||||||
|
// HSM can also be passed to define the protection level if this is not given in
|
||||||
|
// CreateQuery.
|
||||||
|
func parseKeyName(rawURI string) (vault, name, version string, hsm bool, err error) {
|
||||||
|
var u *uri.URI
|
||||||
|
|
||||||
|
u, err = uri.ParseWithScheme("azurekms", rawURI)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if name = u.Get("name"); name == "" {
|
||||||
|
err = errors.Errorf("key uri %s is not valid: name is missing", rawURI)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if vault = u.Get("vault"); vault == "" {
|
||||||
|
err = errors.Errorf("key uri %s is not valid: vault is missing", rawURI)
|
||||||
|
name = ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
version = u.Get("version")
|
||||||
|
hsm = u.GetBool("hsm")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func vaultBaseURL(vault string) string {
|
||||||
|
return "https://" + vault + ".vault.azure.net/"
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertKey(key *keyvault.JSONWebKey) (crypto.PublicKey, error) {
|
||||||
|
b, err := json.Marshal(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error marshaling key")
|
||||||
|
}
|
||||||
|
var jwk jose.JSONWebKey
|
||||||
|
if err := jwk.UnmarshalJSON(b); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error unmarshaling key")
|
||||||
|
}
|
||||||
|
return jwk.Key, nil
|
||||||
|
}
|
91
kms/azurekms/utils_test.go
Normal file
91
kms/azurekms/utils_test.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package azurekms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_getKeyName(t *testing.T) {
|
||||||
|
getBundle := func(kid string) keyvault.KeyBundle {
|
||||||
|
return keyvault.KeyBundle{
|
||||||
|
Key: &keyvault.JSONWebKey{
|
||||||
|
Kid: &kid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
vault string
|
||||||
|
name string
|
||||||
|
bundle keyvault.KeyBundle
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"ok", args{"my-vault", "my-key", getBundle("https://my-vault.vault.azure.net/keys/my-key/my-version")}, "azurekms:name=my-key;vault=my-vault?version=my-version"},
|
||||||
|
{"ok default", args{"my-vault", "my-key", getBundle("https://my-vault.foo.net/keys/my-key/my-version")}, "azurekms:name=my-key;vault=my-vault"},
|
||||||
|
{"ok too short", args{"my-vault", "my-key", getBundle("https://my-vault.vault.azure.net/keys/my-version")}, "azurekms:name=my-key;vault=my-vault"},
|
||||||
|
{"ok too long", args{"my-vault", "my-key", getBundle("https://my-vault.vault.azure.net/keys/my-key/my-version/sign")}, "azurekms:name=my-key;vault=my-vault"},
|
||||||
|
{"ok nil key", args{"my-vault", "my-key", keyvault.KeyBundle{}}, "azurekms:name=my-key;vault=my-vault"},
|
||||||
|
{"ok nil kid", args{"my-vault", "my-key", keyvault.KeyBundle{Key: &keyvault.JSONWebKey{}}}, "azurekms:name=my-key;vault=my-vault"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := getKeyName(tt.args.vault, tt.args.name, tt.args.bundle); got != tt.want {
|
||||||
|
t.Errorf("getKeyName() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseKeyName(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
rawURI string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantVault string
|
||||||
|
wantName string
|
||||||
|
wantVersion string
|
||||||
|
wantHsm bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", args{"azurekms:name=my-key;vault=my-vault?version=my-version"}, "my-vault", "my-key", "my-version", false, false},
|
||||||
|
{"ok opaque version", args{"azurekms:name=my-key;vault=my-vault;version=my-version"}, "my-vault", "my-key", "my-version", false, false},
|
||||||
|
{"ok no version", args{"azurekms:name=my-key;vault=my-vault"}, "my-vault", "my-key", "", false, false},
|
||||||
|
{"ok hsm", args{"azurekms:name=my-key;vault=my-vault?hsm=true"}, "my-vault", "my-key", "", true, false},
|
||||||
|
{"ok hsm false", args{"azurekms:name=my-key;vault=my-vault?hsm=false"}, "my-vault", "my-key", "", false, false},
|
||||||
|
{"fail scheme", args{"azure:name=my-key;vault=my-vault"}, "", "", "", false, true},
|
||||||
|
{"fail parse uri", args{"azurekms:name=%ZZ;vault=my-vault"}, "", "", "", false, true},
|
||||||
|
{"fail no name", args{"azurekms:vault=my-vault"}, "", "", "", false, true},
|
||||||
|
{"fail empty name", args{"azurekms:name=;vault=my-vault"}, "", "", "", false, true},
|
||||||
|
{"fail no vault", args{"azurekms:name=my-key"}, "", "", "", false, true},
|
||||||
|
{"fail empty vault", args{"azurekms:name=my-key;vault="}, "", "", "", false, true},
|
||||||
|
{"fail empty", args{""}, "", "", "", false, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
gotVault, gotName, gotVersion, gotHsm, err := parseKeyName(tt.args.rawURI)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("parseKeyName() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if gotVault != tt.wantVault {
|
||||||
|
t.Errorf("parseKeyName() gotVault = %v, want %v", gotVault, tt.wantVault)
|
||||||
|
}
|
||||||
|
if gotName != tt.wantName {
|
||||||
|
t.Errorf("parseKeyName() gotName = %v, want %v", gotName, tt.wantName)
|
||||||
|
}
|
||||||
|
if gotVersion != tt.wantVersion {
|
||||||
|
t.Errorf("parseKeyName() gotVersion = %v, want %v", gotVersion, tt.wantVersion)
|
||||||
|
}
|
||||||
|
if gotHsm != tt.wantHsm {
|
||||||
|
t.Errorf("parseKeyName() gotHsm = %v, want %v", gotHsm, tt.wantHsm)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/smallstep/certificates/kms/apiv1"
|
"github.com/smallstep/certificates/kms/apiv1"
|
||||||
|
|
||||||
// Enable default implementation
|
// Enable default implementation
|
||||||
_ "github.com/smallstep/certificates/kms/softkms"
|
"github.com/smallstep/certificates/kms/softkms"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KeyManager is the interface implemented by all the KMS.
|
// KeyManager is the interface implemented by all the KMS.
|
||||||
|
@ -18,6 +18,12 @@ type KeyManager = apiv1.KeyManager
|
||||||
// store x509.Certificates.
|
// store x509.Certificates.
|
||||||
type CertificateManager = apiv1.CertificateManager
|
type CertificateManager = apiv1.CertificateManager
|
||||||
|
|
||||||
|
// Options are the KMS options. They represent the kms object in the ca.json.
|
||||||
|
type Options = apiv1.Options
|
||||||
|
|
||||||
|
// Default is the implementation of the default KMS.
|
||||||
|
var Default = &softkms.SoftKMS{}
|
||||||
|
|
||||||
// New initializes a new KMS from the given type.
|
// New initializes a new KMS from the given type.
|
||||||
func New(ctx context.Context, opts apiv1.Options) (KeyManager, error) {
|
func New(ctx context.Context, opts apiv1.Options) (KeyManager, error) {
|
||||||
if err := opts.Validate(); err != nil {
|
if err := opts.Validate(); err != nil {
|
||||||
|
|
|
@ -95,6 +95,16 @@ func (u *URI) Get(key string) string {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBool returns true if a given key has the value "true". It returns false
|
||||||
|
// otherwise.
|
||||||
|
func (u *URI) GetBool(key string) bool {
|
||||||
|
v := u.Values.Get(key)
|
||||||
|
if v == "" {
|
||||||
|
v = u.URL.Query().Get(key)
|
||||||
|
}
|
||||||
|
return strings.EqualFold(v, "true")
|
||||||
|
}
|
||||||
|
|
||||||
// GetEncoded returns the first value in the uri with the given key, it will
|
// GetEncoded returns the first value in the uri with the given key, it will
|
||||||
// return empty nil if that field is not present or is empty. If the return
|
// return empty nil if that field is not present or is empty. If the return
|
||||||
// value is hex encoded it will decode it and return it.
|
// value is hex encoded it will decode it and return it.
|
||||||
|
|
|
@ -212,6 +212,40 @@ func TestURI_Get(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestURI_GetBool(t *testing.T) {
|
||||||
|
mustParse := func(s string) *URI {
|
||||||
|
u, err := Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
uri *URI
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"true", mustParse("azurekms:name=foo;vault=bar;hsm=true"), args{"hsm"}, true},
|
||||||
|
{"TRUE", mustParse("azurekms:name=foo;vault=bar;hsm=TRUE"), args{"hsm"}, true},
|
||||||
|
{"tRUe query", mustParse("azurekms:name=foo;vault=bar?hsm=tRUe"), args{"hsm"}, true},
|
||||||
|
{"false", mustParse("azurekms:name=foo;vault=bar;hsm=false"), args{"hsm"}, false},
|
||||||
|
{"false query", mustParse("azurekms:name=foo;vault=bar?hsm=false"), args{"hsm"}, false},
|
||||||
|
{"empty", mustParse("azurekms:name=foo;vault=bar;hsm=?bar=true"), args{"hsm"}, false},
|
||||||
|
{"missing", mustParse("azurekms:name=foo;vault=bar"), args{"hsm"}, false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.uri.GetBool(tt.args.key); got != tt.want {
|
||||||
|
t.Errorf("URI.GetBool() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestURI_GetEncoded(t *testing.T) {
|
func TestURI_GetEncoded(t *testing.T) {
|
||||||
mustParse := func(s string) *URI {
|
mustParse := func(s string) *URI {
|
||||||
u, err := Parse(s)
|
u, err := Parse(s)
|
||||||
|
|
142
pki/pki.go
142
pki/pki.go
|
@ -26,13 +26,14 @@ import (
|
||||||
"github.com/smallstep/certificates/cas"
|
"github.com/smallstep/certificates/cas"
|
||||||
"github.com/smallstep/certificates/cas/apiv1"
|
"github.com/smallstep/certificates/cas/apiv1"
|
||||||
"github.com/smallstep/certificates/db"
|
"github.com/smallstep/certificates/db"
|
||||||
|
"github.com/smallstep/certificates/kms"
|
||||||
|
kmsapi "github.com/smallstep/certificates/kms/apiv1"
|
||||||
"github.com/smallstep/nosql"
|
"github.com/smallstep/nosql"
|
||||||
"go.step.sm/cli-utils/config"
|
"go.step.sm/cli-utils/config"
|
||||||
"go.step.sm/cli-utils/errs"
|
"go.step.sm/cli-utils/errs"
|
||||||
"go.step.sm/cli-utils/fileutil"
|
"go.step.sm/cli-utils/fileutil"
|
||||||
"go.step.sm/cli-utils/ui"
|
"go.step.sm/cli-utils/ui"
|
||||||
"go.step.sm/crypto/jose"
|
"go.step.sm/crypto/jose"
|
||||||
"go.step.sm/crypto/keyutil"
|
|
||||||
"go.step.sm/crypto/pemutil"
|
"go.step.sm/crypto/pemutil"
|
||||||
"go.step.sm/linkedca"
|
"go.step.sm/linkedca"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
@ -176,6 +177,10 @@ type options struct {
|
||||||
noDB bool
|
noDB bool
|
||||||
isHelm bool
|
isHelm bool
|
||||||
deploymentType DeploymentType
|
deploymentType DeploymentType
|
||||||
|
rootKeyURI string
|
||||||
|
intermediateKeyURI string
|
||||||
|
hostKeyURI string
|
||||||
|
userKeyURI string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option is the type of a configuration option on the pki constructor.
|
// Option is the type of a configuration option on the pki constructor.
|
||||||
|
@ -258,6 +263,26 @@ func WithDeploymentType(dt DeploymentType) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithKMS enables the kms with the given name.
|
||||||
|
func WithKMS(name string) Option {
|
||||||
|
return func(p *PKI) {
|
||||||
|
typ := linkedca.KMS_Type_value[strings.ToUpper(name)]
|
||||||
|
p.Configuration.Kms = &linkedca.KMS{
|
||||||
|
Type: linkedca.KMS_Type(typ),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithKeyURIs defines the key uris for X.509 and SSH keys.
|
||||||
|
func WithKeyURIs(rootKey, intermediateKey, hostKey, userKey string) Option {
|
||||||
|
return func(p *PKI) {
|
||||||
|
p.options.rootKeyURI = rootKey
|
||||||
|
p.options.intermediateKeyURI = intermediateKey
|
||||||
|
p.options.hostKeyURI = hostKey
|
||||||
|
p.options.userKeyURI = userKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PKI represents the Public Key Infrastructure used by a certificate authority.
|
// PKI represents the Public Key Infrastructure used by a certificate authority.
|
||||||
type PKI struct {
|
type PKI struct {
|
||||||
linkedca.Configuration
|
linkedca.Configuration
|
||||||
|
@ -265,6 +290,7 @@ type PKI struct {
|
||||||
casOptions apiv1.Options
|
casOptions apiv1.Options
|
||||||
caService apiv1.CertificateAuthorityService
|
caService apiv1.CertificateAuthorityService
|
||||||
caCreator apiv1.CertificateAuthorityCreator
|
caCreator apiv1.CertificateAuthorityCreator
|
||||||
|
keyManager kmsapi.KeyManager
|
||||||
config string
|
config string
|
||||||
defaults string
|
defaults string
|
||||||
ottPublicKey *jose.JSONWebKey
|
ottPublicKey *jose.JSONWebKey
|
||||||
|
@ -303,8 +329,9 @@ func New(o apiv1.Options, opts ...Option) (*PKI, error) {
|
||||||
Files: make(map[string][]byte),
|
Files: make(map[string][]byte),
|
||||||
},
|
},
|
||||||
casOptions: o,
|
casOptions: o,
|
||||||
caCreator: caCreator,
|
|
||||||
caService: caService,
|
caService: caService,
|
||||||
|
caCreator: caCreator,
|
||||||
|
keyManager: o.KeyManager,
|
||||||
options: &options{
|
options: &options{
|
||||||
provisioner: "step-cli",
|
provisioner: "step-cli",
|
||||||
},
|
},
|
||||||
|
@ -313,6 +340,11 @@ func New(o apiv1.Options, opts ...Option) (*PKI, error) {
|
||||||
fn(p)
|
fn(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use default key manager
|
||||||
|
if p.keyManager == nil {
|
||||||
|
p.keyManager = kms.Default
|
||||||
|
}
|
||||||
|
|
||||||
// Use /home/step as the step path in helm configurations.
|
// Use /home/step as the step path in helm configurations.
|
||||||
// Use the current step path when creating pki in files.
|
// Use the current step path when creating pki in files.
|
||||||
var public, private, cfg string
|
var public, private, cfg string
|
||||||
|
@ -448,11 +480,18 @@ func (p *PKI) GenerateKeyPairs(pass []byte) error {
|
||||||
// GenerateRootCertificate generates a root certificate with the given name
|
// GenerateRootCertificate generates a root certificate with the given name
|
||||||
// and using the default key type.
|
// and using the default key type.
|
||||||
func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (*apiv1.CreateCertificateAuthorityResponse, error) {
|
func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (*apiv1.CreateCertificateAuthorityResponse, error) {
|
||||||
|
if uri := p.options.rootKeyURI; uri != "" {
|
||||||
|
p.RootKey[0] = uri
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{
|
resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{
|
||||||
Name: resource + "-Root-CA",
|
Name: resource + "-Root-CA",
|
||||||
Type: apiv1.RootCA,
|
Type: apiv1.RootCA,
|
||||||
Lifetime: 10 * 365 * 24 * time.Hour,
|
Lifetime: 10 * 365 * 24 * time.Hour,
|
||||||
CreateKey: nil, // use default
|
CreateKey: &apiv1.CreateKeyRequest{
|
||||||
|
Name: p.RootKey[0],
|
||||||
|
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
|
||||||
|
},
|
||||||
Template: &x509.Certificate{
|
Template: &x509.Certificate{
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
CommonName: name + " Root CA",
|
CommonName: name + " Root CA",
|
||||||
|
@ -469,6 +508,13 @@ func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace key name with the one from the key manager if available. On
|
||||||
|
// softcas this will be the original filename, on any other kms will be the
|
||||||
|
// uri to the key.
|
||||||
|
if resp.KeyName != "" {
|
||||||
|
p.RootKey[0] = resp.KeyName
|
||||||
|
}
|
||||||
|
|
||||||
// PrivateKey will only be set if we have access to it (SoftCAS).
|
// PrivateKey will only be set if we have access to it (SoftCAS).
|
||||||
if err := p.WriteRootCertificate(resp.Certificate, resp.PrivateKey, pass); err != nil {
|
if err := p.WriteRootCertificate(resp.Certificate, resp.PrivateKey, pass); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -495,11 +541,18 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{
|
||||||
// GenerateIntermediateCertificate generates an intermediate certificate with
|
// GenerateIntermediateCertificate generates an intermediate certificate with
|
||||||
// the given name and using the default key type.
|
// the given name and using the default key type.
|
||||||
func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent *apiv1.CreateCertificateAuthorityResponse, pass []byte) error {
|
func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent *apiv1.CreateCertificateAuthorityResponse, pass []byte) error {
|
||||||
|
if uri := p.options.intermediateKeyURI; uri != "" {
|
||||||
|
p.IntermediateKey = uri
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{
|
resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{
|
||||||
Name: resource + "-Intermediate-CA",
|
Name: resource + "-Intermediate-CA",
|
||||||
Type: apiv1.IntermediateCA,
|
Type: apiv1.IntermediateCA,
|
||||||
Lifetime: 10 * 365 * 24 * time.Hour,
|
Lifetime: 10 * 365 * 24 * time.Hour,
|
||||||
CreateKey: nil, // use default
|
CreateKey: &apiv1.CreateKeyRequest{
|
||||||
|
Name: p.IntermediateKey,
|
||||||
|
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
|
||||||
|
},
|
||||||
Template: &x509.Certificate{
|
Template: &x509.Certificate{
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
CommonName: name + " Intermediate CA",
|
CommonName: name + " Intermediate CA",
|
||||||
|
@ -519,7 +572,19 @@ func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent
|
||||||
|
|
||||||
p.casOptions.CertificateAuthority = resp.Name
|
p.casOptions.CertificateAuthority = resp.Name
|
||||||
p.Files[p.Intermediate] = encodeCertificate(resp.Certificate)
|
p.Files[p.Intermediate] = encodeCertificate(resp.Certificate)
|
||||||
|
|
||||||
|
// Replace the key name with the one from the key manager. On softcas this
|
||||||
|
// will be the original filename, on any other kms will be the uri to the
|
||||||
|
// key.
|
||||||
|
if resp.KeyName != "" {
|
||||||
|
p.IntermediateKey = resp.KeyName
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a kms is used it will not have the private key
|
||||||
|
if resp.PrivateKey != nil {
|
||||||
p.Files[p.IntermediateKey], err = encodePrivateKey(resp.PrivateKey, pass)
|
p.Files[p.IntermediateKey], err = encodePrivateKey(resp.PrivateKey, pass)
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -564,27 +629,67 @@ func (p *PKI) GetCertificateAuthority() error {
|
||||||
// GenerateSSHSigningKeys generates and encrypts a private key used for signing
|
// GenerateSSHSigningKeys generates and encrypts a private key used for signing
|
||||||
// SSH user certificates and a private key used for signing host certificates.
|
// SSH user certificates and a private key used for signing host certificates.
|
||||||
func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
|
func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
|
||||||
var pubNames = []string{p.Ssh.HostPublicKey, p.Ssh.UserPublicKey}
|
// Enable SSH
|
||||||
var privNames = []string{p.Ssh.HostKey, p.Ssh.UserKey}
|
p.options.enableSSH = true
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
pub, priv, err := keyutil.GenerateDefaultKeyPair()
|
// Create SSH key used to sign host certificates. Using
|
||||||
|
// kmsapi.UnspecifiedSignAlgorithm will default to the default algorithm.
|
||||||
|
name := p.Ssh.HostKey
|
||||||
|
if uri := p.options.hostKeyURI; uri != "" {
|
||||||
|
name = uri
|
||||||
|
}
|
||||||
|
resp, err := p.keyManager.CreateKey(&kmsapi.CreateKeyRequest{
|
||||||
|
Name: name,
|
||||||
|
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, ok := priv.(crypto.Signer); !ok {
|
sshKey, err := ssh.NewPublicKey(resp.PublicKey)
|
||||||
return errors.Errorf("key of type %T is not a crypto.Signer", priv)
|
|
||||||
}
|
|
||||||
sshKey, err := ssh.NewPublicKey(pub)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error converting public key")
|
return errors.Wrapf(err, "error converting public key")
|
||||||
}
|
}
|
||||||
p.Files[pubNames[i]] = ssh.MarshalAuthorizedKey(sshKey)
|
p.Files[p.Ssh.HostPublicKey] = ssh.MarshalAuthorizedKey(sshKey)
|
||||||
p.Files[privNames[i]], err = encodePrivateKey(priv, password)
|
|
||||||
|
// On softkms we will have the private key
|
||||||
|
if resp.PrivateKey != nil {
|
||||||
|
p.Files[p.Ssh.HostKey], err = encodePrivateKey(resp.PrivateKey, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
p.Ssh.HostKey = resp.Name
|
||||||
}
|
}
|
||||||
p.options.enableSSH = true
|
|
||||||
|
// Create SSH key used to sign user certificates. Using
|
||||||
|
// kmsapi.UnspecifiedSignAlgorithm will default to the default algorithm.
|
||||||
|
name = p.Ssh.UserKey
|
||||||
|
if uri := p.options.userKeyURI; uri != "" {
|
||||||
|
name = uri
|
||||||
|
}
|
||||||
|
resp, err = p.keyManager.CreateKey(&kmsapi.CreateKeyRequest{
|
||||||
|
Name: name,
|
||||||
|
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sshKey, err = ssh.NewPublicKey(resp.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error converting public key")
|
||||||
|
}
|
||||||
|
p.Files[p.Ssh.UserPublicKey] = ssh.MarshalAuthorizedKey(sshKey)
|
||||||
|
|
||||||
|
// On softkms we will have the private key
|
||||||
|
if resp.PrivateKey != nil {
|
||||||
|
p.Files[p.Ssh.UserKey], err = encodePrivateKey(resp.PrivateKey, password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.Ssh.UserKey = resp.Name
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -685,6 +790,13 @@ func (p *PKI) GenerateConfig(opt ...ConfigOption) (*authconfig.Config, error) {
|
||||||
cfg.AuthorityConfig.DeploymentType = LinkedDeployment.String()
|
cfg.AuthorityConfig.DeploymentType = LinkedDeployment.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable KMS if necessary
|
||||||
|
if p.Kms != nil {
|
||||||
|
cfg.KMS = &kmsapi.Options{
|
||||||
|
Type: strings.ToLower(p.Kms.Type.String()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// On standalone deployments add the provisioners to either the ca.json or
|
// On standalone deployments add the provisioners to either the ca.json or
|
||||||
// the database.
|
// the database.
|
||||||
var provisioners []provisioner.Interface
|
var provisioners []provisioner.Interface
|
||||||
|
|
Loading…
Reference in a new issue