From 2a249d20deb77a6aea307fd0f8457a92cae005e9 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Feb 2021 00:32:21 +0100 Subject: [PATCH] Refactor initialization of SCEP authority --- authority/authority.go | 45 ++++++ authority/provisioner/scep.go | 4 +- cas/softcas/softcas_test.go | 4 + go.mod | 2 +- go.sum | 26 ++++ kms/apiv1/options.go | 1 + kms/apiv1/requests.go | 11 ++ kms/awskms/awskms.go | 6 + kms/cloudkms/cloudkms.go | 6 + kms/pkcs11/pkcs11.go | 5 + kms/softkms/softkms.go | 36 +++++ kms/sshagentkms/sshagentkms.go | 6 + kms/yubikey/yubikey.go | 6 + scep/api/api.go | 43 +++-- scep/authority.go | 277 ++++++++++++++++++++++++++++----- scep/scep.go | 54 +++++++ scep/service.go | 9 ++ 17 files changed, 476 insertions(+), 65 deletions(-) create mode 100644 scep/scep.go create mode 100644 scep/service.go diff --git a/authority/authority.go b/authority/authority.go index 5652c71a..e753aeae 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -12,6 +12,7 @@ import ( "github.com/smallstep/certificates/cas" "github.com/smallstep/certificates/linkedca" + "github.com/smallstep/certificates/scep" "github.com/pkg/errors" "github.com/smallstep/certificates/authority/admin" @@ -46,6 +47,9 @@ type Authority struct { federatedX509Certs []*x509.Certificate certificates *sync.Map + // SCEP CA + scepService *scep.Service + // SSH CA sshCAUserCertSignKey ssh.Signer sshCAHostCertSignKey ssh.Signer @@ -309,6 +313,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)) @@ -529,3 +565,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 +} diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 30b4a1b2..6cdfa69f 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -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 } diff --git a/cas/softcas/softcas_test.go b/cas/softcas/softcas_test.go index 0a50a990..092a0337 100644 --- a/cas/softcas/softcas_test.go +++ b/cas/softcas/softcas_test.go @@ -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 } diff --git a/go.mod b/go.mod index fe0c7f87..ea867f8f 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 77e0b0cb..934bcfb6 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,7 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 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/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= +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= @@ -116,10 +117,16 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 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 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= 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= @@ -185,6 +192,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 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -211,6 +221,12 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o 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= +<<<<<<< HEAD +======= +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= +>>>>>>> 7ad90d1 (Refactor initialization of SCEP authority) github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -232,6 +248,11 @@ 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= +<<<<<<< HEAD +======= +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= +>>>>>>> 7ad90d1 (Refactor initialization of SCEP authority) 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/micromdm/scep v1.0.0 h1:ai//kcZnxZPq1YE/MatiE2bIRD94KOAwZRpN1fhVQXY= @@ -248,6 +269,7 @@ github.com/newrelic/go-agent v2.15.0+incompatible h1:IB0Fy+dClpBq9aEoIrLyQXzU34J github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 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= @@ -314,6 +336,8 @@ github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= 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= @@ -371,6 +395,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 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= @@ -417,6 +442,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= 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= diff --git a/kms/apiv1/options.go b/kms/apiv1/options.go index 705f3633..0e6f32df 100644 --- a/kms/apiv1/options.go +++ b/kms/apiv1/options.go @@ -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 } diff --git a/kms/apiv1/requests.go b/kms/apiv1/requests.go index e58c4546..31097040 100644 --- a/kms/apiv1/requests.go +++ b/kms/apiv1/requests.go @@ -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 { diff --git a/kms/awskms/awskms.go b/kms/awskms/awskms.go index da392989..00d7d0b3 100644 --- a/kms/awskms/awskms.go +++ b/kms/awskms/awskms.go @@ -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 diff --git a/kms/cloudkms/cloudkms.go b/kms/cloudkms/cloudkms.go index cfbf8235..ace12a1a 100644 --- a/kms/cloudkms/cloudkms.go +++ b/kms/cloudkms/cloudkms.go @@ -3,6 +3,7 @@ package cloudkms import ( "context" "crypto" + "fmt" "log" "strings" "time" @@ -284,6 +285,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. diff --git a/kms/pkcs11/pkcs11.go b/kms/pkcs11/pkcs11.go index 47c298a5..8c1497e8 100644 --- a/kms/pkcs11/pkcs11.go +++ b/kms/pkcs11/pkcs11.go @@ -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") +} diff --git a/kms/softkms/softkms.go b/kms/softkms/softkms.go index 23b50849..a2f43c31 100644 --- a/kms/softkms/softkms.go +++ b/kms/softkms/softkms.go @@ -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") + } +} diff --git a/kms/sshagentkms/sshagentkms.go b/kms/sshagentkms/sshagentkms.go index b3627a08..9c8a7866 100644 --- a/kms/sshagentkms/sshagentkms.go +++ b/kms/sshagentkms/sshagentkms.go @@ -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") +} diff --git a/kms/yubikey/yubikey.go b/kms/yubikey/yubikey.go index 2dde244a..b78ff5e5 100644 --- a/kms/yubikey/yubikey.go +++ b/kms/yubikey/yubikey.go @@ -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") diff --git a/scep/api/api.go b/scep/api/api.go index 5a4cb5b2..ec959519 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -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 { - 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 + pkimsg := &scep.PKIMessage{ + TransactionID: msg.TransactionID, + MessageType: msg.MessageType, + SenderNonce: msg.SenderNonce, + Raw: msg.Raw, } - ca := certs[0] - if err := msg.DecryptPKIEnvelope(ca, key); err != nil { + if err := h.Auth.DecryptPKIEnvelope(pkimsg); 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 } diff --git a/scep/authority.go b/scep/authority.go index 27817b0f..244a6f92 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -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,68 +112,251 @@ 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) { +// LoadProvisionerByID calls out to the SignAuthority interface to load a +// provisioner by ID. +func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) { + return a.signAuth.LoadProvisionerByID(id) +} + +// 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") + } + + return []*x509.Certificate{a.intermediateCertificate}, nil +} + +// DecryptPKIEnvelope decrypts an enveloped message +func (a *Authority) DecryptPKIEnvelope(msg *PKIMessage) error { + + data := msg.Raw + + p7, err := pkcs7.Parse(data) + if err != nil { + return err + } + + 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 = µscep.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 + } + } - keyBytes, err := ioutil.ReadFile(path) + 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 } - block, _ := pem.Decode([]byte(keyBytes)) - if block == nil { - return nil, nil + 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 } - key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + e7, err := pkcs7.Encrypt(deg, msg.p7.Certificates) if err != nil { return nil, err } - return key, nil -} + // 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, + }, + }, + } -// LoadProvisionerByID calls out to the SignAuthority interface to load a -// provisioner by ID. -func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) { - return a.signAuth.LoadProvisionerByID(id) -} + signedData, err := pkcs7.NewSignedData(e7) + if err != nil { + return nil, err + } -// GetCACertificates returns the certificate (chain) for the CA -func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { + // 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) - if a.intermediateCertificate == nil { - return nil, errors.New("no intermediate certificate available in SCEP authority") + authCert := a.intermediateCertificate + + // sign the attributes + if err := signedData.AddSigner(authCert, a.service.Signer, config); err != nil { + return nil, err } - return []*x509.Certificate{a.intermediateCertificate}, nil -} + certRepBytes, err := signedData.Finish() + if err != nil { + return nil, err + } -// 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) { + cr := &CertRepMessage{ + PKIStatus: microscep.SUCCESS, + RecipientNonce: microscep.RecipientNonce(msg.SenderNonce), + Certificate: cert, + degenerate: deg, + } - if a.intermediateKey == nil { - return nil, errors.New("no intermediate key available in SCEP authority") + // create a CertRep message from the original + crepMsg := &PKIMessage{ + Raw: certRepBytes, + TransactionID: msg.TransactionID, + MessageType: microscep.CertRep, + CertRepMessage: cr, } - return a.intermediateKey, nil + 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 diff --git a/scep/scep.go b/scep/scep.go new file mode 100644 index 00000000..bc46cce7 --- /dev/null +++ b/scep/scep.go @@ -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 +} diff --git a/scep/service.go b/scep/service.go new file mode 100644 index 00000000..1d743dd6 --- /dev/null +++ b/scep/service.go @@ -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 +}