mirror of
https://github.com/caddyserver/caddy.git
synced 2025-12-08 06:09:53 +00:00
caddypki: Add support for multiple intermediates in signing chain
This commit is contained in:
parent
786d537877
commit
c2a2626f86
9 changed files with 570 additions and 64 deletions
2
go.mod
2
go.mod
|
|
@ -35,6 +35,7 @@ require (
|
|||
go.opentelemetry.io/contrib/propagators/autoprop v0.63.0
|
||||
go.opentelemetry.io/otel v1.38.0
|
||||
go.opentelemetry.io/otel/sdk v1.38.0
|
||||
go.step.sm/crypto v0.72.0
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
go.uber.org/zap v1.27.1
|
||||
go.uber.org/zap/exp v0.3.0
|
||||
|
|
@ -166,7 +167,6 @@ require (
|
|||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
go.step.sm/crypto v0.74.0
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/sys v0.38.0
|
||||
|
|
|
|||
64
go.sum
64
go.sum
|
|
@ -10,8 +10,8 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB
|
|||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
|
||||
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
|
||||
cloud.google.com/go/kms v1.23.2 h1:4IYDQL5hG4L+HzJBhzejUySoUOheh3Lk5YT4PCyyW6k=
|
||||
cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g=
|
||||
cloud.google.com/go/kms v1.23.1 h1:Mesyv84WoP3tPjUC0O5LRqPWICO0ufdpWf9jtBCEz64=
|
||||
cloud.google.com/go/kms v1.23.1/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g=
|
||||
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
|
||||
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
|
|
@ -50,34 +50,34 @@ github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9
|
|||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.5 h1:e/SXuia3rkFtapghJROrydtQpfQaaUgd1cUvyO1mp2w=
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.5/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.16 h1:E4Tz+tJiPc7kGnXwIfCyUj6xHJNpENlY11oKpRTgsjc=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.16/go.mod h1:2S9hBElpCyGMifv14WxQ7EfPumgoeCPZUpuPX8VtW34=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.20 h1:KFndAnHd9NUuzikHjQ8D5CfFVO+bgELkmcGY8yAw98Q=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.20/go.mod h1:9mCi28a+fmBHSQ0UM79omkz6JtN+PEsvLrnG36uoUv0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12 h1:VO3FIM2TDbm0kqp6sFNR0PbioXJb/HzCDW6NtIZpIWE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12/go.mod h1:6C39gB8kg82tx3r72muZSrNhHia9rjGkX7ORaS2GKNE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12 h1:p/9flfXdoAnwJnuW9xHEAFY22R3A6skYkW19JFF9F+8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12/go.mod h1:ZTLHakoVCTtW8AaLGSwJ3LXqHD9uQKnOcv1TrpO6u2k=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12 h1:2lTWFvRcnWFFLzHWmtddu5MTchc5Oj2OOey++99tPZ0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12/go.mod h1:hI92pK+ho8HVcWMHKHrK3Uml4pfG7wvL86FzO0LVtQQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 h1:xtuxji5CS0JknaXoACOunXOYOQzgfTvGAc9s2QdCJA4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2/go.mod h1:zxwi0DIR0rcRcgdbl7E2MSOvxDyyXGBlScvBkARFaLQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12 h1:MM8imH7NZ0ovIVX7D2RxfMDv7Jt9OiUXkcQ+GqywA7M=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12/go.mod h1:gf4OGwdNkbEsb7elw2Sy76odfhwNktWII3WgvQgQQ6w=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.47.0 h1:A97YCVyGz19rRs3+dWf3GpMPflCswgETA9r6/Q0JNSY=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.47.0/go.mod h1:ZJ1ghBt9gQM8JoNscUua1siIgao8w74o3kvdWUU6N/Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.0 h1:xHXvxst78wBpJFgDW07xllOx0IAzbryrSdM4nMVQ4Dw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.0/go.mod h1:/e8m+AO6HNPPqMyfKRtzZ9+mBF5/x1Wk8QiDva4m07I=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4 h1:tBw2Qhf0kj4ZwtsVpDiVRU3zKLvjvjgIjHMKirxXg8M=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4/go.mod h1:Deq4B7sRM6Awq/xyOBlxBdgW8/Z926KYNNaGMW2lrkA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 h1:C+BRMnasSYFcgDw8o9H5hzehKzXyAb9GY5v/8bP9DUY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.39.0/go.mod h1:4EjU+4mIx6+JqKQkruye+CaigV7alL3thVPfDd9VlMs=
|
||||
github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M=
|
||||
github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I=
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.12 h1:pYM1Qgy0dKZLHX2cXslNacbcEFMkDMl+Bcj5ROuS6p8=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.12/go.mod h1:/MM0dyD7KSDPR+39p9ZNVKaHDLb9qnfDurvVS2KAhN8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.16 h1:4JHirI4zp958zC026Sm+V4pSDwW4pwLefKrc0bF2lwI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.16/go.mod h1:qQMtGx9OSw7ty1yLclzLxXCRbrkjWAM7JnObZjmCB7I=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wgeqPL5DRFu5bQL9BGPQ8Y=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.45.6 h1:Br3kil4j7RPW+7LoLVkYt8SuhIWlg6ylmbmzXJ7PgXY=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.45.6/go.mod h1:FKXkHzw1fJZtg1P1qoAIiwen5thz/cDRTTDCIu8ljxc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 h1:A1oRkiSQOWstGh61y4Wc/yQ04sqrQZr1Si/oAXj20/s=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.6/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8=
|
||||
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
|
||||
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=
|
||||
|
|
@ -428,8 +428,8 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr
|
|||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||
go.step.sm/crypto v0.74.0 h1:/APBEv45yYR4qQFg47HA8w1nesIGcxh44pGyQNw6JRA=
|
||||
go.step.sm/crypto v0.74.0/go.mod h1:UoXqCAJjjRgzPte0Llaqen7O9P7XjPmgjgTHQGkKCDk=
|
||||
go.step.sm/crypto v0.72.0 h1:cwkxbmnN8jj8YWmoXdoGhaac81d2SwXguwmHN9KJxHw=
|
||||
go.step.sm/crypto v0.72.0/go.mod h1:EAy7MSOXxCvCaDAKJqz0bLdTSDdhpEM9xqye8XsfrM4=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
|
|
|
|||
|
|
@ -222,11 +222,16 @@ func rootAndIntermediatePEM(ca *CA) (root, inter []byte, err error) {
|
|||
if err != nil {
|
||||
return root, inter, err
|
||||
}
|
||||
inter, err = pemEncodeCert(ca.IntermediateCertificate().Raw)
|
||||
if err != nil {
|
||||
return root, inter, err
|
||||
|
||||
for _, interCert := range ca.IntermediateCertificateChain() {
|
||||
pemBytes, err := pemEncodeCert(interCert.Raw)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
inter = append(inter, pemBytes...)
|
||||
}
|
||||
return root, inter, err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// caInfo is the response structure for the CA info API endpoint.
|
||||
|
|
|
|||
|
|
@ -75,10 +75,11 @@ type CA struct {
|
|||
// and module provisioning.
|
||||
ID string `json:"-"`
|
||||
|
||||
storage certmagic.Storage
|
||||
root, inter *x509.Certificate
|
||||
interKey any // TODO: should we just store these as crypto.Signer?
|
||||
mu *sync.RWMutex
|
||||
storage certmagic.Storage
|
||||
root *x509.Certificate
|
||||
interChain []*x509.Certificate
|
||||
interKey any // TODO: should we just store these as crypto.Signer?
|
||||
mu *sync.RWMutex
|
||||
|
||||
rootCertPath string // mainly used for logging purposes if trusting
|
||||
log *zap.Logger
|
||||
|
|
@ -127,14 +128,16 @@ func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error {
|
|||
}
|
||||
|
||||
// load the certs and key that will be used for signing
|
||||
var rootCert, interCert *x509.Certificate
|
||||
var rootCert *x509.Certificate
|
||||
var rootCertChain, interCertChain []*x509.Certificate
|
||||
var rootKey, interKey crypto.Signer
|
||||
var err error
|
||||
if ca.Root != nil {
|
||||
if ca.Root.Format == "" || ca.Root.Format == "pem_file" {
|
||||
ca.rootCertPath = ca.Root.Certificate
|
||||
}
|
||||
rootCert, rootKey, err = ca.Root.Load()
|
||||
rootCertChain, rootKey, err = ca.Root.Load()
|
||||
rootCert = rootCertChain[0]
|
||||
} else {
|
||||
ca.rootCertPath = "storage:" + ca.storageKeyRootCert()
|
||||
rootCert, rootKey, err = ca.loadOrGenRoot()
|
||||
|
|
@ -147,16 +150,16 @@ func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error {
|
|||
return fmt.Errorf("intermediate certificate lifetime must be less than actual root certificate lifetime (%s)", actualRootLifetime)
|
||||
}
|
||||
if ca.Intermediate != nil {
|
||||
interCert, interKey, err = ca.Intermediate.Load()
|
||||
interCertChain, interKey, err = ca.Intermediate.Load()
|
||||
} else {
|
||||
interCert, interKey, err = ca.loadOrGenIntermediate(rootCert, rootKey)
|
||||
interCertChain, interKey, err = ca.loadOrGenIntermediate(rootCert, rootKey)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ca.mu.Lock()
|
||||
ca.root, ca.inter, ca.interKey = rootCert, interCert, interKey
|
||||
ca.root, ca.interChain, ca.interKey = rootCert, interCertChain, interKey
|
||||
ca.mu.Unlock()
|
||||
|
||||
return nil
|
||||
|
|
@ -182,7 +185,15 @@ func (ca CA) RootKey() (any, error) {
|
|||
func (ca CA) IntermediateCertificate() *x509.Certificate {
|
||||
ca.mu.RLock()
|
||||
defer ca.mu.RUnlock()
|
||||
return ca.inter
|
||||
return ca.interChain[0]
|
||||
}
|
||||
|
||||
// IntermediateCertificateChain returns the CA's intermediate
|
||||
// certificate chain.
|
||||
func (ca CA) IntermediateCertificateChain() []*x509.Certificate {
|
||||
ca.mu.RLock()
|
||||
defer ca.mu.RUnlock()
|
||||
return ca.interChain
|
||||
}
|
||||
|
||||
// IntermediateKey returns the CA's intermediate private key.
|
||||
|
|
@ -220,13 +231,14 @@ func (ca *CA) NewAuthority(authorityConfig AuthorityConfig) (*authority.Authorit
|
|||
// sure it's always fresh, because the intermediate may
|
||||
// renew while Caddy is running (medium lifetime)
|
||||
signerOption = authority.WithX509SignerFunc(func() ([]*x509.Certificate, crypto.Signer, error) {
|
||||
issuerCert := ca.IntermediateCertificate()
|
||||
issuerChain := ca.IntermediateCertificateChain()
|
||||
issuerCert := issuerChain[0]
|
||||
issuerKey := ca.IntermediateKey().(crypto.Signer)
|
||||
ca.log.Debug("using intermediate signer",
|
||||
zap.String("serial", issuerCert.SerialNumber.String()),
|
||||
zap.String("not_before", issuerCert.NotBefore.String()),
|
||||
zap.String("not_after", issuerCert.NotAfter.String()))
|
||||
return []*x509.Certificate{issuerCert}, issuerKey, nil
|
||||
return issuerChain, issuerKey, nil
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +264,11 @@ func (ca *CA) NewAuthority(authorityConfig AuthorityConfig) (*authority.Authorit
|
|||
|
||||
func (ca CA) loadOrGenRoot() (rootCert *x509.Certificate, rootKey crypto.Signer, err error) {
|
||||
if ca.Root != nil {
|
||||
return ca.Root.Load()
|
||||
rootChain, rootSigner, err := ca.Root.Load()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return rootChain[0], rootSigner, nil
|
||||
}
|
||||
rootCertPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyRootCert())
|
||||
if err != nil {
|
||||
|
|
@ -314,7 +330,8 @@ func (ca CA) genRoot() (rootCert *x509.Certificate, rootKey crypto.Signer, err e
|
|||
return rootCert, rootKey, nil
|
||||
}
|
||||
|
||||
func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCert *x509.Certificate, interKey crypto.Signer, err error) {
|
||||
func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCertChain []*x509.Certificate, interKey crypto.Signer, err error) {
|
||||
var interCert *x509.Certificate
|
||||
interCertPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyIntermediateCert())
|
||||
if err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
|
|
@ -326,10 +343,12 @@ func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Si
|
|||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("generating new intermediate cert: %v", err)
|
||||
}
|
||||
|
||||
interCertChain = append(interCertChain, interCert)
|
||||
}
|
||||
|
||||
if interCert == nil {
|
||||
interCert, err = pemDecodeSingleCert(interCertPEM)
|
||||
if len(interCertChain) == 0 {
|
||||
interCertChain, err = pemDecodeCertificateChain(interCertPEM)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("decoding intermediate certificate PEM: %v", err)
|
||||
}
|
||||
|
|
@ -346,7 +365,7 @@ func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Si
|
|||
}
|
||||
}
|
||||
|
||||
return interCert, interKey, nil
|
||||
return interCertChain, interKey, nil
|
||||
}
|
||||
|
||||
func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCert *x509.Certificate, interKey crypto.Signer, err error) {
|
||||
|
|
|
|||
|
|
@ -17,12 +17,17 @@ package caddypki
|
|||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
func pemDecodeSingleCert(pemDER []byte) (*x509.Certificate, error) {
|
||||
|
|
@ -39,6 +44,15 @@ func pemDecodeSingleCert(pemDER []byte) (*x509.Certificate, error) {
|
|||
return x509.ParseCertificate(pemBlock.Bytes)
|
||||
}
|
||||
|
||||
func pemDecodeCertificateChain(pemDER []byte) ([]*x509.Certificate, error) {
|
||||
chain, err := pemutil.ParseCertificateBundle(pemDER)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing certificate chain: %w", err)
|
||||
}
|
||||
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
func pemEncodeCert(der []byte) ([]byte, error) {
|
||||
return pemEncode("CERTIFICATE", der)
|
||||
}
|
||||
|
|
@ -71,14 +85,14 @@ type KeyPair struct {
|
|||
}
|
||||
|
||||
// Load loads the certificate and key.
|
||||
func (kp KeyPair) Load() (*x509.Certificate, crypto.Signer, error) {
|
||||
func (kp KeyPair) Load() ([]*x509.Certificate, crypto.Signer, error) {
|
||||
switch kp.Format {
|
||||
case "", "pem_file":
|
||||
certData, err := os.ReadFile(kp.Certificate)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cert, err := pemDecodeSingleCert(certData)
|
||||
chain, err := pemDecodeCertificateChain(certData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
@ -93,11 +107,49 @@ func (kp KeyPair) Load() (*x509.Certificate, crypto.Signer, error) {
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := verifyKeysMatch(chain[0], key); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cert, key, nil
|
||||
return chain, key, nil
|
||||
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported format: %s", kp.Format)
|
||||
}
|
||||
}
|
||||
|
||||
// verifyKeysMatch verifies that the public key in the [x509.Certificate] matches
|
||||
// the public key of the [crypto.Signer].
|
||||
func verifyKeysMatch(crt *x509.Certificate, signer crypto.Signer) error {
|
||||
switch pub := crt.PublicKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
pk, ok := signer.Public().(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("private key type %T does not match issuer public key type %T", signer.Public(), pub)
|
||||
}
|
||||
if !pub.Equal(pk) {
|
||||
return errors.New("private key does not match issuer public key")
|
||||
}
|
||||
case *ecdsa.PublicKey:
|
||||
pk, ok := signer.Public().(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("private key type %T does not match issuer public key type %T", signer.Public(), pub)
|
||||
}
|
||||
if !pub.Equal(pk) {
|
||||
return errors.New("private key does not match issuer public key")
|
||||
}
|
||||
case ed25519.PublicKey:
|
||||
pk, ok := signer.Public().(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("private key type %T does not match issuer public key type %T", signer.Public(), pub)
|
||||
}
|
||||
if !pub.Equal(pk) {
|
||||
return errors.New("private key does not match issuer public key")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported key type: %T", pub)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
170
modules/caddypki/crypto_test.go
Normal file
170
modules/caddypki/crypto_test.go
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package caddypki
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
func TestKeyPair_Load(t *testing.T) {
|
||||
rootSigner, err := keyutil.GenerateDefaultSigner()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed creating signer: %v", err)
|
||||
}
|
||||
|
||||
tmpl := &x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "test-root"},
|
||||
IsCA: true,
|
||||
MaxPathLen: 3,
|
||||
}
|
||||
rootBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, rootSigner.Public(), rootSigner)
|
||||
if err != nil {
|
||||
t.Fatalf("Creating root certificate failed: %v", err)
|
||||
}
|
||||
|
||||
root, err := x509.ParseCertificate(rootBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("Parsing root certificate failed: %v", err)
|
||||
}
|
||||
|
||||
intermediateSigner, err := keyutil.GenerateDefaultSigner()
|
||||
if err != nil {
|
||||
t.Fatalf("Creating intermedaite signer failed: %v", err)
|
||||
}
|
||||
|
||||
intermediateBytes, err := x509.CreateCertificate(rand.Reader, &x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "test-first-intermediate"},
|
||||
IsCA: true,
|
||||
MaxPathLen: 2,
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
}, root, intermediateSigner.Public(), rootSigner)
|
||||
if err != nil {
|
||||
t.Fatalf("Creating intermediate certificate failed: %v", err)
|
||||
}
|
||||
|
||||
intermediate, err := x509.ParseCertificate(intermediateBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("Parsing intermediate certificate failed: %v", err)
|
||||
}
|
||||
|
||||
var chainContents []byte
|
||||
chain := []*x509.Certificate{intermediate, root}
|
||||
for _, cert := range chain {
|
||||
b, err := pemutil.Serialize(cert)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed serializing intermediate certificate: %v", err)
|
||||
}
|
||||
chainContents = append(chainContents, pem.EncodeToMemory(b)...)
|
||||
}
|
||||
|
||||
dir := t.TempDir()
|
||||
rootCertFile := filepath.Join(dir, "root.pem")
|
||||
if _, err = pemutil.Serialize(root, pemutil.WithFilename(rootCertFile)); err != nil {
|
||||
t.Fatalf("Failed serializing root certificate: %v", err)
|
||||
}
|
||||
rootKeyFile := filepath.Join(dir, "root.key")
|
||||
if _, err = pemutil.Serialize(rootSigner, pemutil.WithFilename(rootKeyFile)); err != nil {
|
||||
t.Fatalf("Failed serializing root key: %v", err)
|
||||
}
|
||||
intermediateCertFile := filepath.Join(dir, "intermediate.pem")
|
||||
if _, err = pemutil.Serialize(intermediate, pemutil.WithFilename(intermediateCertFile)); err != nil {
|
||||
t.Fatalf("Failed serializing intermediate certificate: %v", err)
|
||||
}
|
||||
intermediateKeyFile := filepath.Join(dir, "intermediate.key")
|
||||
if _, err = pemutil.Serialize(intermediateSigner, pemutil.WithFilename(intermediateKeyFile)); err != nil {
|
||||
t.Fatalf("Failed serializing intermediate key: %v", err)
|
||||
}
|
||||
chainFile := filepath.Join(dir, "chain.pem")
|
||||
if err := os.WriteFile(chainFile, chainContents, 0644); err != nil {
|
||||
t.Fatalf("Failed writing intermediate chain: %v", err)
|
||||
}
|
||||
|
||||
t.Run("ok/single-certificate-without-signer", func(t *testing.T) {
|
||||
kp := KeyPair{
|
||||
Certificate: rootCertFile,
|
||||
}
|
||||
chain, signer, err := kp.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading KeyPair: %v", err)
|
||||
}
|
||||
if len(chain) != 1 {
|
||||
t.Errorf("Expected 1 certificate in chain; got %d", len(chain))
|
||||
}
|
||||
if signer != nil {
|
||||
t.Error("Expected no signer to be returned")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ok/single-certificate-with-signer", func(t *testing.T) {
|
||||
kp := KeyPair{
|
||||
Certificate: rootCertFile,
|
||||
PrivateKey: rootKeyFile,
|
||||
}
|
||||
chain, signer, err := kp.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading KeyPair: %v", err)
|
||||
}
|
||||
if len(chain) != 1 {
|
||||
t.Errorf("Expected 1 certificate in chain; got %d", len(chain))
|
||||
}
|
||||
if signer == nil {
|
||||
t.Error("Expected signer to be returned")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ok/multiple-certificates-with-signer", func(t *testing.T) {
|
||||
kp := KeyPair{
|
||||
Certificate: chainFile,
|
||||
PrivateKey: intermediateKeyFile,
|
||||
}
|
||||
chain, signer, err := kp.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading KeyPair: %v", err)
|
||||
}
|
||||
if len(chain) != 2 {
|
||||
t.Errorf("Expected 1 certificate in chain; got %d", len(chain))
|
||||
}
|
||||
if signer == nil {
|
||||
t.Error("Expected signer to be returned")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fail/non-matching-public-key", func(t *testing.T) {
|
||||
kp := KeyPair{
|
||||
Certificate: intermediateCertFile,
|
||||
PrivateKey: rootKeyFile,
|
||||
}
|
||||
chain, signer, err := kp.Load()
|
||||
if err == nil {
|
||||
t.Error("Expected loading KeyPair to return an error")
|
||||
}
|
||||
if chain != nil {
|
||||
t.Error("Expected no chain to be returned")
|
||||
}
|
||||
if signer != nil {
|
||||
t.Error("Expected no signer to be returned")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -66,16 +66,16 @@ func (p *PKI) renewCertsForCA(ca *CA) error {
|
|||
if needsRenewal(ca.root) {
|
||||
// TODO: implement root renewal (use same key)
|
||||
log.Warn("root certificate expiring soon (FIXME: ROOT RENEWAL NOT YET IMPLEMENTED)",
|
||||
zap.Duration("time_remaining", time.Until(ca.inter.NotAfter)),
|
||||
zap.Duration("time_remaining", time.Until(ca.interChain[0].NotAfter)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// only maintain the intermediate if it's not manually provided in the config
|
||||
if ca.Intermediate == nil {
|
||||
if needsRenewal(ca.inter) {
|
||||
if needsRenewal(ca.interChain[0]) {
|
||||
log.Info("intermediate expires soon; renewing",
|
||||
zap.Duration("time_remaining", time.Until(ca.inter.NotAfter)),
|
||||
zap.Duration("time_remaining", time.Until(ca.interChain[0].NotAfter)),
|
||||
)
|
||||
|
||||
rootCert, rootKey, err := ca.loadOrGenRoot()
|
||||
|
|
@ -86,10 +86,10 @@ func (p *PKI) renewCertsForCA(ca *CA) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("generating new certificate: %v", err)
|
||||
}
|
||||
ca.inter, ca.interKey = interCert, interKey
|
||||
ca.interChain, ca.interKey = []*x509.Certificate{interCert}, interKey
|
||||
|
||||
log.Info("renewed intermediate",
|
||||
zap.Time("new_expiration", ca.inter.NotAfter),
|
||||
zap.Time("new_expiration", ca.interChain[0].NotAfter),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ func (PKIIntermediateCAPool) CaddyModule() caddy.ModuleInfo {
|
|||
}
|
||||
}
|
||||
|
||||
// Loads the PKI app and load the intermediate certificates into the certificate pool
|
||||
// Loads the PKI app and loads the intermediate certificates into the certificate pool
|
||||
func (p *PKIIntermediateCAPool) Provision(ctx caddy.Context) error {
|
||||
pkiApp, err := ctx.AppIfConfigured("pki")
|
||||
if err != nil {
|
||||
|
|
@ -274,7 +274,9 @@ func (p *PKIIntermediateCAPool) Provision(ctx caddy.Context) error {
|
|||
|
||||
caPool := x509.NewCertPool()
|
||||
for _, ca := range p.ca {
|
||||
caPool.AddCert(ca.IntermediateCertificate())
|
||||
for _, c := range ca.IntermediateCertificateChain() {
|
||||
caPool.AddCert(c)
|
||||
}
|
||||
}
|
||||
p.pool = caPool
|
||||
return nil
|
||||
|
|
|
|||
258
modules/caddytls/internalissuer_test.go
Normal file
258
modules/caddytls/internalissuer_test.go
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package caddytls
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
func TestInternalIssuer_Issue(t *testing.T) {
|
||||
rootSigner, err := keyutil.GenerateDefaultSigner()
|
||||
if err != nil {
|
||||
t.Fatalf("Creating root signer failed: %v", err)
|
||||
}
|
||||
|
||||
tmpl := &x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "test-root"},
|
||||
IsCA: true,
|
||||
MaxPathLen: 3,
|
||||
}
|
||||
rootBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, rootSigner.Public(), rootSigner)
|
||||
if err != nil {
|
||||
t.Fatalf("Creating root certificate failed: %v", err)
|
||||
}
|
||||
|
||||
root, err := x509.ParseCertificate(rootBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("Parsing root certificate failed: %v", err)
|
||||
}
|
||||
|
||||
firstIntermediateSigner, err := keyutil.GenerateDefaultSigner()
|
||||
if err != nil {
|
||||
t.Fatalf("Creating intermedaite signer failed: %v", err)
|
||||
}
|
||||
|
||||
firstIntermediateBytes, err := x509.CreateCertificate(rand.Reader, &x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "test-first-intermediate"},
|
||||
IsCA: true,
|
||||
MaxPathLen: 2,
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
}, root, firstIntermediateSigner.Public(), rootSigner)
|
||||
if err != nil {
|
||||
t.Fatalf("Creating intermediate certificate failed: %v", err)
|
||||
}
|
||||
|
||||
firstIntermediate, err := x509.ParseCertificate(firstIntermediateBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("Parsing intermediate certificate failed: %v", err)
|
||||
}
|
||||
|
||||
secondIntermediateSigner, err := keyutil.GenerateDefaultSigner()
|
||||
if err != nil {
|
||||
t.Fatalf("Creating second intermedaite signer failed: %v", err)
|
||||
}
|
||||
|
||||
secondIntermediateBytes, err := x509.CreateCertificate(rand.Reader, &x509.Certificate{
|
||||
Subject: pkix.Name{CommonName: "test-second-intermediate"},
|
||||
IsCA: true,
|
||||
MaxPathLen: 2,
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
}, firstIntermediate, secondIntermediateSigner.Public(), firstIntermediateSigner)
|
||||
if err != nil {
|
||||
t.Fatalf("Creating second intermediate certificate failed: %v", err)
|
||||
}
|
||||
|
||||
secondIntermediate, err := x509.ParseCertificate(secondIntermediateBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("Parsing second intermediate certificate failed: %v", err)
|
||||
}
|
||||
|
||||
dir := t.TempDir()
|
||||
storageDir := filepath.Join(dir, "certmagic")
|
||||
rootCertFile := filepath.Join(dir, "root.pem")
|
||||
if _, err = pemutil.Serialize(root, pemutil.WithFilename(rootCertFile)); err != nil {
|
||||
t.Fatalf("Failed serializing root certificate: %v", err)
|
||||
}
|
||||
intermediateCertFile := filepath.Join(dir, "intermediate.pem")
|
||||
if _, err = pemutil.Serialize(firstIntermediate, pemutil.WithFilename(intermediateCertFile)); err != nil {
|
||||
t.Fatalf("Failed serializing intermediate certificate: %v", err)
|
||||
}
|
||||
intermediateKeyFile := filepath.Join(dir, "intermediate.key")
|
||||
if _, err = pemutil.Serialize(firstIntermediateSigner, pemutil.WithFilename(intermediateKeyFile)); err != nil {
|
||||
t.Fatalf("Failed serializing intermediate key: %v", err)
|
||||
}
|
||||
|
||||
var intermediateChainContents []byte
|
||||
intermediateChain := []*x509.Certificate{secondIntermediate, firstIntermediate}
|
||||
for _, cert := range intermediateChain {
|
||||
b, err := pemutil.Serialize(cert)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed serializing intermediate certificate: %v", err)
|
||||
}
|
||||
intermediateChainContents = append(intermediateChainContents, pem.EncodeToMemory(b)...)
|
||||
}
|
||||
intermediateChainFile := filepath.Join(dir, "intermediates.pem")
|
||||
if err := os.WriteFile(intermediateChainFile, intermediateChainContents, 0644); err != nil {
|
||||
t.Fatalf("Failed writing intermediate chain: %v", err)
|
||||
}
|
||||
intermediateChainKeyFile := filepath.Join(dir, "intermediates.key")
|
||||
if _, err = pemutil.Serialize(secondIntermediateSigner, pemutil.WithFilename(intermediateChainKeyFile)); err != nil {
|
||||
t.Fatalf("Failed serializing intermediate key: %v", err)
|
||||
}
|
||||
|
||||
signer, err := keyutil.GenerateDefaultSigner()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed creating signer: %v", err)
|
||||
}
|
||||
|
||||
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{
|
||||
Subject: pkix.Name{CommonName: "test"},
|
||||
}, signer)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed creating CSR: %v", err)
|
||||
}
|
||||
|
||||
csr, err := x509.ParseCertificateRequest(csrBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed parsing CSR: %v", err)
|
||||
}
|
||||
|
||||
t.Run("generated-with-defaults", func(t *testing.T) {
|
||||
caddyCtx, cancel := caddy.NewContext(caddy.Context{Context: t.Context()})
|
||||
t.Cleanup(cancel)
|
||||
logger := zap.NewNop()
|
||||
|
||||
ca := &caddypki.CA{
|
||||
StorageRaw: []byte(fmt.Sprintf(`{"module": "file_system", "root": %q}`, storageDir)),
|
||||
}
|
||||
if err := ca.Provision(caddyCtx, "local-test-generated", logger); err != nil {
|
||||
t.Fatalf("Failed provisioning CA: %v", err)
|
||||
}
|
||||
|
||||
iss := InternalIssuer{
|
||||
SignWithRoot: false,
|
||||
ca: ca,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
c, err := iss.Issue(t.Context(), csr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed issuing certificate: %v", err)
|
||||
}
|
||||
|
||||
chain, err := pemutil.ParseCertificateBundle(c.Certificate)
|
||||
if err != nil {
|
||||
t.Errorf("Failed issuing certificate: %v", err)
|
||||
}
|
||||
if len(chain) != 2 {
|
||||
t.Errorf("Expected 2 certificates in chain; got %d", len(chain))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("single-intermediate-from-disk", func(t *testing.T) {
|
||||
caddyCtx, cancel := caddy.NewContext(caddy.Context{Context: t.Context()})
|
||||
t.Cleanup(cancel)
|
||||
logger := zap.NewNop()
|
||||
|
||||
ca := &caddypki.CA{
|
||||
Root: &caddypki.KeyPair{
|
||||
Certificate: rootCertFile,
|
||||
},
|
||||
Intermediate: &caddypki.KeyPair{
|
||||
Certificate: intermediateCertFile,
|
||||
PrivateKey: intermediateKeyFile,
|
||||
},
|
||||
StorageRaw: []byte(fmt.Sprintf(`{"module": "file_system", "root": %q}`, storageDir)),
|
||||
}
|
||||
|
||||
if err := ca.Provision(caddyCtx, "local-test-single-intermediate", logger); err != nil {
|
||||
t.Fatalf("Failed provisioning CA: %v", err)
|
||||
}
|
||||
|
||||
iss := InternalIssuer{
|
||||
ca: ca,
|
||||
SignWithRoot: false,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
c, err := iss.Issue(t.Context(), csr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed issuing certificate: %v", err)
|
||||
}
|
||||
|
||||
chain, err := pemutil.ParseCertificateBundle(c.Certificate)
|
||||
if err != nil {
|
||||
t.Errorf("Failed issuing certificate: %v", err)
|
||||
}
|
||||
if len(chain) != 2 {
|
||||
t.Errorf("Expected 2 certificates in chain; got %d", len(chain))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple-intermediates-from-disk", func(t *testing.T) {
|
||||
caddyCtx, cancel := caddy.NewContext(caddy.Context{Context: t.Context()})
|
||||
t.Cleanup(cancel)
|
||||
logger := zap.NewNop()
|
||||
|
||||
ca := &caddypki.CA{
|
||||
Root: &caddypki.KeyPair{
|
||||
Certificate: rootCertFile,
|
||||
},
|
||||
Intermediate: &caddypki.KeyPair{
|
||||
Certificate: intermediateChainFile,
|
||||
PrivateKey: intermediateChainKeyFile,
|
||||
},
|
||||
StorageRaw: []byte(fmt.Sprintf(`{"module": "file_system", "root": %q}`, storageDir)),
|
||||
}
|
||||
|
||||
if err := ca.Provision(caddyCtx, "local-test", zap.NewNop()); err != nil {
|
||||
t.Fatalf("Failed provisioning CA: %v", err)
|
||||
}
|
||||
|
||||
iss := InternalIssuer{
|
||||
ca: ca,
|
||||
SignWithRoot: false,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
c, err := iss.Issue(t.Context(), csr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed issuing certificate: %v", err)
|
||||
}
|
||||
|
||||
chain, err := pemutil.ParseCertificateBundle(c.Certificate)
|
||||
if err != nil {
|
||||
t.Errorf("Failed issuing certificate: %v", err)
|
||||
}
|
||||
if len(chain) != 3 {
|
||||
t.Errorf("Expected 3 certificates in chain; got %d", len(chain))
|
||||
}
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue