mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -05:00 
			
		
		
		
	* pki: Initial commit of embedded ACME server (#3021) * reverseproxy: Support auto-managed TLS client certificates (#3021) * A little cleanup after today's review session
		
			
				
	
	
		
			180 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			180 lines
		
	
	
		
			5.0 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/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/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 = caddypki.DefaultCAID
 | 
						|
	}
 | 
						|
	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
 | 
						|
	authCfg := caddypki.AuthorityConfig{
 | 
						|
		SignWithRoot: li.SignWithRoot,
 | 
						|
	}
 | 
						|
	auth, err := li.ca.NewAuthority(authCfg)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// get the cert (public key) that will be used for signing
 | 
						|
	var issuerCert *x509.Certificate
 | 
						|
	if li.SignWithRoot {
 | 
						|
		issuerCert = li.ca.RootCertificate()
 | 
						|
	} else {
 | 
						|
		issuerCert = li.ca.IntermediateCertificate()
 | 
						|
	}
 | 
						|
 | 
						|
	// 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
 | 
						|
}
 | 
						|
 | 
						|
// profileDefaultDuration is a wrapper against x509util.WithOption to conform
 | 
						|
// the SignOption interface.
 | 
						|
//
 | 
						|
// This type is borrowed from the smallstep libraries:
 | 
						|
// https://github.com/smallstep/certificates/blob/806abb6232a5691198b891d76b9898ea7f269da0/authority/provisioner/sign_options.go#L191-L211
 | 
						|
// as per https://github.com/smallstep/certificates/issues/198.
 | 
						|
//
 | 
						|
// TODO: In the future, this approach to custom cert lifetimes may not be necessary
 | 
						|
type profileDefaultDuration time.Duration
 | 
						|
 | 
						|
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 (
 | 
						|
	defaultInternalCertLifetime = 12 * time.Hour
 | 
						|
)
 | 
						|
 | 
						|
// Interface guards
 | 
						|
var (
 | 
						|
	_ caddy.Provisioner = (*InternalIssuer)(nil)
 | 
						|
	_ certmagic.Issuer  = (*InternalIssuer)(nil)
 | 
						|
)
 |