Refactor initialization of SCEP authority

This commit is contained in:
Herman Slatman 2021-02-26 00:32:21 +01:00
parent f871f8135c
commit 7ad90d10b3
No known key found for this signature in database
GPG key ID: F4D8A44EA0A75A4F
19 changed files with 476 additions and 72 deletions

View file

@ -11,6 +11,7 @@ import (
"time"
"github.com/smallstep/certificates/cas"
"github.com/smallstep/certificates/scep"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner"
@ -42,6 +43,9 @@ type Authority struct {
federatedX509Certs []*x509.Certificate
certificates *sync.Map
// SCEP CA
scepService *scep.Service
// SSH CA
sshCAUserCertSignKey ssh.Signer
sshCAHostCertSignKey ssh.Signer
@ -196,6 +200,38 @@ func (a *Authority) init() error {
}
}
// TODO: decide if this is a good approach for providing the SCEP functionality
if a.scepService == nil {
var options casapi.Options
if a.config.AuthorityConfig.Options != nil {
options = *a.config.AuthorityConfig.Options
}
// Read intermediate and create X509 signer for default CAS.
if options.Is(casapi.SoftCAS) {
options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert)
if err != nil {
return err
}
options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
options.Decrypter, err = a.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
}
a.scepService = &scep.Service{
Signer: options.Signer,
Decrypter: options.Decrypter,
}
}
// Read root certificates and store them in the certificates map.
if len(a.rootX509Certs) == 0 {
a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root))
@ -394,3 +430,12 @@ func (a *Authority) CloseForReload() {
log.Printf("error closing the key manager: %v", err)
}
}
// GetSCEPService returns the configured SCEP Service
// TODO: this function is intended to exist temporarily
// in order to make SCEP work more easily. It can be
// made more correct by using the right interfaces/abstractions
// after it works as expected.
func (a *Authority) GetSCEPService() scep.Service {
return *a.scepService
}

View file

@ -55,7 +55,7 @@ func (s *SCEP) DefaultTLSCertDuration() time.Duration {
return s.claimer.DefaultTLSCertDuration()
}
// Init initializes and validates the fields of a JWK type.
// Init initializes and validates the fields of a SCEP type.
func (s *SCEP) Init(config Config) (err error) {
switch {
@ -70,6 +70,8 @@ func (s *SCEP) Init(config Config) (err error) {
return err
}
// TODO: add other, SCEP specific, options?
return err
}

View file

@ -156,10 +156,11 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) {
// well as certificates via SCEP.
tlsConfig = nil
// TODO: get the SCEP service
scepPrefix := "scep"
scepAuthority, err := scep.New(auth, scep.AuthorityOptions{
IntermediateCertificatePath: config.IntermediateCert,
IntermediateKeyPath: config.IntermediateKey,
Service: auth.GetSCEPService(),
Backdate: *config.AuthorityConfig.Backdate,
DB: auth.GetDatabase().(nosql.DB),
DNS: dns,

View file

@ -24,7 +24,8 @@ type Options struct {
// Certificate and signer are the issuer certificate,along with any other bundled certificates to be returned in the chain for consumers, and signer used in SoftCAS.
// They are configured in ca.json crt and key properties.
CertificateChain []*x509.Certificate
Signer crypto.Signer `json:"-"`
Signer crypto.Signer `json:"-"`
Decrypter crypto.Decrypter `json:"-"`
// IsCreator is set to true when we're creating a certificate authority. Is
// used to skip some validations when initializing a CertificateAuthority.

View file

@ -110,6 +110,10 @@ func (m *mockKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (crypto.S
return signer, m.errCreatesigner
}
func (m *mockKeyManager) CreateDecrypter(req *kmsapi.CreateDecrypterRequest) (crypto.Decrypter, error) {
return nil, nil
}
func (m *mockKeyManager) Close() error {
return m.errClose
}

2
go.mod
View file

@ -13,7 +13,7 @@ require (
github.com/google/uuid v1.1.2
github.com/googleapis/gax-go/v2 v2.0.5
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/micromdm/scep v1.0.0
github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23
github.com/newrelic/go-agent v2.15.0+incompatible
github.com/pkg/errors v0.9.1
github.com/rs/xid v1.2.1

21
go.sum
View file

@ -56,6 +56,7 @@ github.com/ThalesIgnite/crypto11 v1.2.4/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aws/aws-sdk-go v1.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P0=
github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@ -103,10 +104,16 @@ github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxm
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.4.0 h1:KeVK+Emj3c3S4eRztFuzbFYb2BAgf2jmwDwyXEri7Lo=
github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-piv/piv-go v1.7.0 h1:rfjdFdASfGV5KLJhSjgpGJ5lzVZVtRWn8ovy/H9HQ/U=
github.com/go-piv/piv-go v1.7.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.6.0 h1:MmJCxYVKTJ0SplGKqFVX3SBnmaUhODHZrrFF6jMbpZk=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -177,6 +184,9 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.4.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd5rUMdNogn35MWXBX1UiBigrU8eTj8DoAC2c=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@ -202,6 +212,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGi
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
@ -223,8 +235,8 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/micromdm/scep v1.0.0 h1:ai//kcZnxZPq1YE/MatiE2bIRD94KOAwZRpN1fhVQXY=
github.com/micromdm/scep v1.0.0/go.mod h1:CID2SixSr5FvoauZdAFUSpQkn5MAuSy9oyURMGOJbag=
github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23 h1:QACkVsQ7Qx4PuPDFL2OFD5u4OnYT0TkReWk9IVqaGHA=
github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23/go.mod h1:a82oNZyGV8jj9Do7dh8EkA90+esBls0CZHR6B85Qda8=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
@ -236,6 +248,7 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
github.com/newrelic/go-agent v2.15.0+incompatible h1:IB0Fy+dClpBq9aEoIrLyQXzU34JyI1xVTanPLB/+jvU=
github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -291,6 +304,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -343,6 +358,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20170726083632-f5079bd7f6f7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -387,6 +403,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170728174421-0f826bdd13b5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View file

@ -13,6 +13,7 @@ type KeyManager interface {
GetPublicKey(req *GetPublicKeyRequest) (crypto.PublicKey, error)
CreateKey(req *CreateKeyRequest) (*CreateKeyResponse, error)
CreateSigner(req *CreateSignerRequest) (crypto.Signer, error)
CreateDecrypter(req *CreateDecrypterRequest) (crypto.Decrypter, error) // TODO: split into separate interface?
Close() error
}

View file

@ -133,6 +133,17 @@ type CreateSignerRequest struct {
Password []byte
}
// CreateDecrypterRequest is the parameter used in the kms.Decrypt method.
type CreateDecrypterRequest struct {
Decrypter crypto.Decrypter
DecryptionKey string
DecryptionKeyPEM []byte
TokenLabel string
PublicKey string
PublicKeyPEM []byte
Password []byte
}
// LoadCertificateRequest is the parameter used in the LoadCertificate method of
// a CertificateManager.
type LoadCertificateRequest struct {

View file

@ -3,6 +3,7 @@ package awskms
import (
"context"
"crypto"
"fmt"
"net/url"
"strings"
"time"
@ -221,6 +222,11 @@ func (k *KMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error
return NewSigner(k.service, req.SigningKey)
}
// CreateDecrypter creates a new crypto.decrypter backed by AWS KMS
func (k *KMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) {
return nil, fmt.Errorf("not implemented yet")
}
// Close closes the connection of the KMS client.
func (k *KMS) Close() error {
return nil

View file

@ -3,6 +3,7 @@ package cloudkms
import (
"context"
"crypto"
"fmt"
"log"
"strings"
"time"
@ -285,6 +286,11 @@ func (k *CloudKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKe
return pk, nil
}
// CreateDecrypter creates a new crypto.Decrypter backed by Google Cloud KMS
func (k *CloudKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) {
return nil, fmt.Errorf("not implemented yet")
}
// getPublicKeyWithRetries retries the request if the error is
// FailedPrecondition, caused because the key is in the PENDING_GENERATION
// status.

View file

@ -352,3 +352,8 @@ func findCertificate(ctx P11, rawuri string) (*x509.Certificate, error) {
}
return cert, nil
}
// CreateDecrypter creates a new crypto.Decrypter backed by PKCS11
func (k *PKCS11) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) {
return nil, fmt.Errorf("not implemented yet")
}

View file

@ -145,3 +145,39 @@ func (k *SoftKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey
return nil, errors.Errorf("unsupported public key type %T", v)
}
}
// CreateDecrypter creates a new crypto.Decrypter backed by disk/software
func (k *SoftKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) {
var opts []pemutil.Options
if req.Password != nil {
opts = append(opts, pemutil.WithPassword(req.Password))
}
switch {
case req.Decrypter != nil:
return req.Decrypter, nil
case len(req.DecryptionKeyPEM) != 0:
v, err := pemutil.ParseKey(req.DecryptionKeyPEM, opts...)
if err != nil {
return nil, err
}
decrypter, ok := v.(crypto.Decrypter)
if !ok {
return nil, errors.New("decryptorKeyPEM is not a crypto.Decrypter")
}
return decrypter, nil
case req.DecryptionKey != "":
v, err := pemutil.Read(req.DecryptionKey, opts...)
if err != nil {
return nil, err
}
decrypter, ok := v.(crypto.Decrypter)
if !ok {
return nil, errors.New("decryptionKey is not a crypto.Decrypter")
}
return decrypter, nil
default:
return nil, errors.New("failed to load softKMS: please define decryptionKeyPEM or decryptionKey")
}
}

View file

@ -7,6 +7,7 @@ import (
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"fmt"
"io"
"net"
"os"
@ -204,3 +205,8 @@ func (k *SSHAgentKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.Publi
return nil, errors.Errorf("unsupported public key type %T", v)
}
}
// CreateDecrypter creates a crypto.Decrypter backed by ssh-agent
func (k *SSHAgentKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) {
return nil, fmt.Errorf("not implemented yet")
}

View file

@ -7,6 +7,7 @@ import (
"crypto"
"crypto/x509"
"encoding/hex"
"fmt"
"net/url"
"strings"
@ -189,6 +190,11 @@ func (k *YubiKey) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, e
return signer, nil
}
// CreateDecrypter creates a new crypto.Decrypter backed by a YubiKey
func (k *YubiKey) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) {
return nil, fmt.Errorf("not implemented yet")
}
// Close releases the connection to the YubiKey.
func (k *YubiKey) Close() error {
return errors.Wrap(k.yk.Close(), "error closing yubikey")

View file

@ -28,6 +28,8 @@ const (
opnGetCACert = "GetCACert"
opnGetCACaps = "GetCACaps"
opnPKIOperation = "PKIOperation"
// TODO: add other (more optional) operations and handling
)
// SCEPRequest is a SCEP server request.
@ -98,6 +100,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
}
func (h *Handler) Post(w http.ResponseWriter, r *http.Request) {
scepRequest, err := decodeSCEPRequest(r)
if err != nil {
fmt.Println(err)
@ -252,27 +255,22 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque
return err
}
certs, err := h.Auth.GetCACertificates()
if err != nil {
pkimsg := &scep.PKIMessage{
TransactionID: msg.TransactionID,
MessageType: msg.MessageType,
SenderNonce: msg.SenderNonce,
Raw: msg.Raw,
}
if err := h.Auth.DecryptPKIEnvelope(pkimsg); err != nil {
return err
}
// TODO: instead of getting the key to decrypt, add a decrypt function to the auth; less leaky
key, err := h.Auth.GetSigningKey()
if err != nil {
return err
}
ca := certs[0]
if err := msg.DecryptPKIEnvelope(ca, key); err != nil {
return err
}
if msg.MessageType == microscep.PKCSReq {
if pkimsg.MessageType == microscep.PKCSReq {
// TODO: CSR validation, like challenge password
}
csr := msg.CSRReqMessage.CSR
csr := pkimsg.CSRReqMessage.CSR
id, err := createKeyIdentifier(csr.PublicKey)
if err != nil {
return err
@ -282,6 +280,7 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque
days := 40
// TODO: use information from provisioner, like claims
template := &x509.Certificate{
SerialNumber: serial,
Subject: csr.Subject,
@ -296,16 +295,16 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque
EmailAddresses: csr.EmailAddresses,
}
certRep, err := msg.SignCSR(ca, key, template)
certRep, err := h.Auth.SignCSR(pkimsg, template)
if err != nil {
return err
}
//cert := certRep.CertRepMessage.Certificate
//name := certName(cert)
// //cert := certRep.CertRepMessage.Certificate
// //name := certName(cert)
// TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not
// TODO: store the new cert for CN locally; should go into the DB
// // TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not
// // TODO: store the new cert for CN locally; should go into the DB
scepResponse.Data = certRep.Raw
@ -321,7 +320,7 @@ func certName(cert *x509.Certificate) string {
return string(cert.Signature)
}
// createKeyIdentifier create an identifier for public keys
// createKeyIdentifier creates an identifier for public keys
// according to the first method in RFC5280 section 4.2.1.2.
func createKeyIdentifier(pub crypto.PublicKey) ([]byte, error) {
@ -390,9 +389,9 @@ func ProvisionerFromContext(ctx context.Context) (scep.Provisioner, error) {
if val == nil {
return nil, errors.New("provisioner expected in request context")
}
pval, ok := val.(scep.Provisioner)
if !ok || pval == nil {
p, ok := val.(scep.Provisioner)
if !ok || p == nil {
return nil, errors.New("provisioner in context is not a SCEP provisioner")
}
return pval, nil
return p, nil
}

View file

@ -1,17 +1,26 @@
package scep
import (
"crypto/rsa"
"bytes"
"crypto/x509"
"encoding/pem"
"errors"
"io/ioutil"
"fmt"
"math/big"
"math/rand"
"github.com/smallstep/certificates/authority/provisioner"
database "github.com/smallstep/certificates/db"
"go.step.sm/crypto/pemutil"
"github.com/smallstep/nosql"
microx509util "github.com/micromdm/scep/crypto/x509util"
microscep "github.com/micromdm/scep/scep"
"github.com/smallstep/certificates/scep/pkcs7"
"go.step.sm/crypto/x509util"
)
// Interface is the SCEP authority interface.
@ -41,7 +50,10 @@ type Interface interface {
// GetLinkExplicit(linkType Link, provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string
GetCACertificates() ([]*x509.Certificate, error)
GetSigningKey() (*rsa.PrivateKey, error)
//GetSigningKey() (*rsa.PrivateKey, error)
DecryptPKIEnvelope(*PKIMessage) error
SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error)
}
// Authority is the layer that handles all SCEP interactions.
@ -54,17 +66,16 @@ type Authority struct {
// dir *directory
intermediateCertificate *x509.Certificate
intermediateKey *rsa.PrivateKey
//signer crypto.Signer
service Service
signAuth SignAuthority
}
// AuthorityOptions required to create a new SCEP Authority.
type AuthorityOptions struct {
IntermediateCertificatePath string
IntermediateKeyPath string
Service Service
// Backdate
Backdate provisioner.Duration
@ -79,7 +90,7 @@ type AuthorityOptions struct {
Prefix string
}
// SignAuthority is the interface implemented by a CA authority.
// SignAuthority is the interface for a signing authority
type SignAuthority interface {
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
LoadProvisionerByID(string) (provisioner.Interface, error)
@ -87,6 +98,7 @@ type SignAuthority interface {
// New returns a new Authority that implements the SCEP interface.
func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) {
if _, ok := ops.DB.(*database.SimpleDB); !ok {
// TODO: see ACME implementation
}
@ -100,42 +112,17 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) {
return nil, err
}
intermediateKey, err := readPrivateKey(ops.IntermediateKeyPath)
if err != nil {
return nil, err
}
return &Authority{
backdate: ops.Backdate,
db: ops.DB,
prefix: ops.Prefix,
dns: ops.DNS,
intermediateCertificate: certificateChain[0],
intermediateKey: intermediateKey,
service: ops.Service,
signAuth: signAuth,
}, nil
}
func readPrivateKey(path string) (*rsa.PrivateKey, error) {
keyBytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
block, _ := pem.Decode([]byte(keyBytes))
if block == nil {
return nil, nil
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return key, nil
}
// LoadProvisionerByID calls out to the SignAuthority interface to load a
// provisioner by ID.
func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) {
@ -145,6 +132,20 @@ func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error
// GetCACertificates returns the certificate (chain) for the CA
func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) {
// TODO: this should return: the "SCEP Server (RA)" certificate, the issuing CA up to and excl. the root
// Some clients do need the root certificate however; also see: https://github.com/openxpki/openxpki/issues/73
//
// This means we might need to think about if we should use the current intermediate CA
// certificate as the "SCEP Server (RA)" certificate. It might be better to have a distinct
// RA certificate, with a corresponding rsa.PrivateKey, just for SCEP usage, which is signed by
// the intermediate CA. Will need to look how we can provide this nicely within step-ca.
//
// This might also mean that we might want to use a distinct instance of KMS for doing the key operations,
// so that we can use RSA just for SCEP.
//
// Using an RA does not seem to exist in https://tools.ietf.org/html/rfc8894, but is mentioned in
// https://tools.ietf.org/id/draft-nourse-scep-21.html. Will continue using the CA directly for now.
if a.intermediateCertificate == nil {
return nil, errors.New("no intermediate certificate available in SCEP authority")
}
@ -152,16 +153,210 @@ func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) {
return []*x509.Certificate{a.intermediateCertificate}, nil
}
// GetSigningKey returns the RSA private key for the CA
// TODO: we likely should provide utility functions for decrypting and
// signing instead of providing the signing key directly
func (a *Authority) GetSigningKey() (*rsa.PrivateKey, error) {
// DecryptPKIEnvelope decrypts an enveloped message
func (a *Authority) DecryptPKIEnvelope(msg *PKIMessage) error {
if a.intermediateKey == nil {
return nil, errors.New("no intermediate key available in SCEP authority")
data := msg.Raw
p7, err := pkcs7.Parse(data)
if err != nil {
return err
}
return a.intermediateKey, nil
var tID microscep.TransactionID
if err := p7.UnmarshalSignedAttribute(oidSCEPtransactionID, &tID); err != nil {
return err
}
var msgType microscep.MessageType
if err := p7.UnmarshalSignedAttribute(oidSCEPmessageType, &msgType); err != nil {
return err
}
msg.p7 = p7
p7c, err := pkcs7.Parse(p7.Content)
if err != nil {
return err
}
envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.Decrypter)
if err != nil {
return err
}
msg.pkiEnvelope = envelope
switch msg.MessageType {
case microscep.CertRep:
certs, err := microscep.CACerts(msg.pkiEnvelope)
if err != nil {
return err
}
msg.CertRepMessage.Certificate = certs[0] // TODO: check correctness of this
return nil
case microscep.PKCSReq, microscep.UpdateReq, microscep.RenewalReq:
csr, err := x509.ParseCertificateRequest(msg.pkiEnvelope)
if err != nil {
return fmt.Errorf("parse CSR from pkiEnvelope")
}
// check for challengePassword
cp, err := microx509util.ParseChallengePassword(msg.pkiEnvelope)
if err != nil {
return fmt.Errorf("scep: parse challenge password in pkiEnvelope")
}
msg.CSRReqMessage = &microscep.CSRReqMessage{
RawDecrypted: msg.pkiEnvelope,
CSR: csr,
ChallengePassword: cp,
}
//msg.Certificate = p7.Certificates[0] // TODO: check if this is necessary to add (again)
return nil
case microscep.GetCRL, microscep.GetCert, microscep.CertPoll:
return fmt.Errorf("not implemented") //errNotImplemented
}
return nil
}
// SignCSR creates an x509.Certificate based on a template and Cert Authority credentials
// returns a new PKIMessage with CertRep data
//func (msg *PKIMessage) SignCSR(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, template *x509.Certificate) (*PKIMessage, error) {
func (a *Authority) SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) {
// check if CSRReqMessage has already been decrypted
if msg.CSRReqMessage.CSR == nil {
if err := a.DecryptPKIEnvelope(msg); err != nil {
return nil, err
}
}
csr := msg.CSRReqMessage.CSR
// Template data
data := x509util.NewTemplateData()
data.SetCommonName(csr.Subject.CommonName)
//data.Set(x509util.SANsKey, sans)
// templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data)
// if err != nil {
// return nil, ServerInternalErr(errors.Wrapf(err, "error creating template options from ACME provisioner"))
// }
// signOps = append(signOps, templateOptions)
// // Create and store a new certificate.
// certChain, err := auth.Sign(csr, provisioner.SignOptions{
// NotBefore: provisioner.NewTimeDuration(o.NotBefore),
// NotAfter: provisioner.NewTimeDuration(o.NotAfter),
// }, signOps...)
// if err != nil {
// return nil, ServerInternalErr(errors.Wrapf(err, "error generating certificate for order %s", o.ID))
// }
// TODO: proper options
signOps := provisioner.SignOptions{}
signOps2 := []provisioner.SignOption{}
certs, err := a.signAuth.Sign(csr, signOps, signOps2...)
if err != nil {
return nil, err
}
cert := certs[0]
// fmt.Println("CERT")
// fmt.Println(cert)
// fmt.Println(fmt.Sprintf("%T", cert))
// fmt.Println(cert.Issuer)
// fmt.Println(cert.Subject)
serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic?
cert.SerialNumber = serial
// create a degenerate cert structure
deg, err := DegenerateCertificates([]*x509.Certificate{cert})
if err != nil {
return nil, err
}
e7, err := pkcs7.Encrypt(deg, msg.p7.Certificates)
if err != nil {
return nil, err
}
// PKIMessageAttributes to be signed
config := pkcs7.SignerInfoConfig{
ExtraSignedAttributes: []pkcs7.Attribute{
{
Type: oidSCEPtransactionID,
Value: msg.TransactionID,
},
{
Type: oidSCEPpkiStatus,
Value: microscep.SUCCESS,
},
{
Type: oidSCEPmessageType,
Value: microscep.CertRep,
},
{
Type: oidSCEPrecipientNonce,
Value: msg.SenderNonce,
},
},
}
signedData, err := pkcs7.NewSignedData(e7)
if err != nil {
return nil, err
}
// add the certificate into the signed data type
// this cert must be added before the signedData because the recipient will expect it
// as the first certificate in the array
signedData.AddCertificate(cert)
authCert := a.intermediateCertificate
// sign the attributes
if err := signedData.AddSigner(authCert, a.service.Signer, config); err != nil {
return nil, err
}
certRepBytes, err := signedData.Finish()
if err != nil {
return nil, err
}
cr := &CertRepMessage{
PKIStatus: microscep.SUCCESS,
RecipientNonce: microscep.RecipientNonce(msg.SenderNonce),
Certificate: cert,
degenerate: deg,
}
// create a CertRep message from the original
crepMsg := &PKIMessage{
Raw: certRepBytes,
TransactionID: msg.TransactionID,
MessageType: microscep.CertRep,
CertRepMessage: cr,
}
return crepMsg, nil
}
// DegenerateCertificates creates degenerate certificates pkcs#7 type
func DegenerateCertificates(certs []*x509.Certificate) ([]byte, error) {
var buf bytes.Buffer
for _, cert := range certs {
buf.Write(cert.Raw)
}
degenerate, err := pkcs7.DegenerateCertificate(buf.Bytes())
if err != nil {
return nil, err
}
return degenerate, nil
}
// Interface guards

54
scep/scep.go Normal file
View file

@ -0,0 +1,54 @@
package scep
import (
"crypto/x509"
"encoding/asn1"
microscep "github.com/micromdm/scep/scep"
"github.com/smallstep/certificates/scep/pkcs7"
)
// SCEP OIDs
var (
oidSCEPmessageType = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 2}
oidSCEPpkiStatus = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 3}
oidSCEPfailInfo = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 4}
oidSCEPsenderNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 5}
oidSCEPrecipientNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 6}
oidSCEPtransactionID = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 7}
oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7}
)
// PKIMessage defines the possible SCEP message types
type PKIMessage struct {
microscep.TransactionID
microscep.MessageType
microscep.SenderNonce
*microscep.CSRReqMessage
*CertRepMessage
// DER Encoded PKIMessage
Raw []byte
// parsed
p7 *pkcs7.PKCS7
// decrypted enveloped content
pkiEnvelope []byte
// Used to sign message
Recipients []*x509.Certificate
}
// CertRepMessage is a type of PKIMessage
type CertRepMessage struct {
microscep.PKIStatus
microscep.RecipientNonce
microscep.FailInfo
Certificate *x509.Certificate
degenerate []byte
}

9
scep/service.go Normal file
View file

@ -0,0 +1,9 @@
package scep
import "crypto"
// Service is a (temporary?) wrapper for signer/decrypters
type Service struct {
Signer crypto.Signer
Decrypter crypto.Decrypter
}