mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-28 14:43:48 +03:00
200 lines
5.7 KiB
Go
200 lines
5.7 KiB
Go
|
// 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 (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"crypto"
|
||
|
"crypto/x509"
|
||
|
"encoding/pem"
|
||
|
"fmt"
|
||
|
"time"
|
||
|
|
||
|
"github.com/caddyserver/caddy/v2"
|
||
|
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||
|
"github.com/caddyserver/certmagic"
|
||
|
"github.com/smallstep/certificates/authority"
|
||
|
"github.com/smallstep/certificates/authority/provisioner"
|
||
|
"github.com/smallstep/cli/crypto/x509util"
|
||
|
)
|
||
|
|
||
|
func init() {
|
||
|
caddy.RegisterModule(InternalIssuer{})
|
||
|
}
|
||
|
|
||
|
// InternalIssuer is a certificate issuer that generates
|
||
|
// certificates internally using a locally-configured
|
||
|
// CA which can be customized using the `pki` app.
|
||
|
type InternalIssuer struct {
|
||
|
// The ID of the CA to use for signing. The default
|
||
|
// CA ID is "local". The CA can be configured with the
|
||
|
// `pki` app.
|
||
|
CA string `json:"ca,omitempty"`
|
||
|
|
||
|
// The validity period of certificates.
|
||
|
Lifetime caddy.Duration `json:"lifetime,omitempty"`
|
||
|
|
||
|
// If true, the root will be the issuer instead of
|
||
|
// the intermediate. This is NOT recommended and should
|
||
|
// only be used when devices/clients do not properly
|
||
|
// validate certificate chains.
|
||
|
SignWithRoot bool `json:"sign_with_root,omitempty"`
|
||
|
|
||
|
ca *caddypki.CA
|
||
|
}
|
||
|
|
||
|
// CaddyModule returns the Caddy module information.
|
||
|
func (InternalIssuer) CaddyModule() caddy.ModuleInfo {
|
||
|
return caddy.ModuleInfo{
|
||
|
ID: "tls.issuance.internal",
|
||
|
New: func() caddy.Module { return new(InternalIssuer) },
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Provision sets up the issuer.
|
||
|
func (li *InternalIssuer) Provision(ctx caddy.Context) error {
|
||
|
// get a reference to the configured CA
|
||
|
appModule, err := ctx.App("pki")
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
pkiApp := appModule.(*caddypki.PKI)
|
||
|
if li.CA == "" {
|
||
|
li.CA = defaultInternalCAName
|
||
|
}
|
||
|
ca, ok := pkiApp.CAs[li.CA]
|
||
|
if !ok {
|
||
|
return fmt.Errorf("no certificate authority configured with id: %s", li.CA)
|
||
|
}
|
||
|
li.ca = ca
|
||
|
|
||
|
// set any other default values
|
||
|
if li.Lifetime == 0 {
|
||
|
li.Lifetime = caddy.Duration(defaultInternalCertLifetime)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// IssuerKey returns the unique issuer key for the
|
||
|
// confgured CA endpoint.
|
||
|
func (li InternalIssuer) IssuerKey() string {
|
||
|
return li.ca.ID()
|
||
|
}
|
||
|
|
||
|
// Issue issues a certificate to satisfy the CSR.
|
||
|
func (li InternalIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
|
||
|
// prepare the signing authority
|
||
|
// TODO: eliminate placeholders / needless values
|
||
|
cfg := &authority.Config{
|
||
|
Address: "placeholder_Address:1",
|
||
|
Root: []string{"placeholder_Root"},
|
||
|
IntermediateCert: "placeholder_IntermediateCert",
|
||
|
IntermediateKey: "placeholder_IntermediateKey",
|
||
|
DNSNames: []string{"placeholder_DNSNames"},
|
||
|
AuthorityConfig: &authority.AuthConfig{
|
||
|
Provisioners: provisioner.List{},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// get the root certificate and the issuer cert+key
|
||
|
rootCert := li.ca.RootCertificate()
|
||
|
var issuerCert *x509.Certificate
|
||
|
var issuerKey interface{}
|
||
|
if li.SignWithRoot {
|
||
|
issuerCert = rootCert
|
||
|
var err error
|
||
|
issuerKey, err = li.ca.RootKey()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("loading signing key: %v", err)
|
||
|
}
|
||
|
} else {
|
||
|
issuerCert = li.ca.IntermediateCertificate()
|
||
|
issuerKey = li.ca.IntermediateKey()
|
||
|
}
|
||
|
|
||
|
auth, err := authority.New(cfg,
|
||
|
authority.WithX509Signer(issuerCert, issuerKey.(crypto.Signer)),
|
||
|
authority.WithX509RootCerts(rootCert),
|
||
|
)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("initializing certificate authority: %v", err)
|
||
|
}
|
||
|
|
||
|
// ensure issued certificate does not expire later than its issuer
|
||
|
lifetime := time.Duration(li.Lifetime)
|
||
|
if time.Now().Add(lifetime).After(issuerCert.NotAfter) {
|
||
|
// TODO: log this
|
||
|
lifetime = issuerCert.NotAfter.Sub(time.Now())
|
||
|
}
|
||
|
|
||
|
certChain, err := auth.Sign(csr, provisioner.Options{},
|
||
|
profileDefaultDuration(li.Lifetime),
|
||
|
)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
var buf bytes.Buffer
|
||
|
for _, cert := range certChain {
|
||
|
err := pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return &certmagic.IssuedCertificate{
|
||
|
Certificate: buf.Bytes(),
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
// TODO: borrowing from https://github.com/smallstep/certificates/blob/806abb6232a5691198b891d76b9898ea7f269da0/authority/provisioner/sign_options.go#L191-L211
|
||
|
// as per https://github.com/smallstep/certificates/issues/198.
|
||
|
// profileDefaultDuration is a wrapper against x509util.WithOption to conform
|
||
|
// the SignOption interface.
|
||
|
type profileDefaultDuration time.Duration
|
||
|
|
||
|
// TODO: is there a better way to set cert lifetimes than copying from the smallstep libs?
|
||
|
func (d profileDefaultDuration) Option(so provisioner.Options) x509util.WithOption {
|
||
|
var backdate time.Duration
|
||
|
notBefore := so.NotBefore.Time()
|
||
|
if notBefore.IsZero() {
|
||
|
notBefore = time.Now().Truncate(time.Second)
|
||
|
backdate = -1 * so.Backdate
|
||
|
}
|
||
|
notAfter := so.NotAfter.RelativeTime(notBefore)
|
||
|
return func(p x509util.Profile) error {
|
||
|
fn := x509util.WithNotBeforeAfterDuration(notBefore, notAfter, time.Duration(d))
|
||
|
if err := fn(p); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
crt := p.Subject()
|
||
|
crt.NotBefore = crt.NotBefore.Add(backdate)
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
defaultInternalCAName = "local"
|
||
|
defaultInternalCertLifetime = 12 * time.Hour
|
||
|
)
|
||
|
|
||
|
// Interface guards
|
||
|
var (
|
||
|
_ caddy.Provisioner = (*InternalIssuer)(nil)
|
||
|
_ certmagic.Issuer = (*InternalIssuer)(nil)
|
||
|
)
|