mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 02:27:19 -04:00 
			
		
		
		
	* pki: Initial commit of PKI app (WIP) (see #2502 and #3021) * pki: Ability to use root/intermediates, and sign with root * pki: Fix benign misnamings left over from copy+paste * pki: Only install root if not already trusted * Make HTTPS port the default; all names use auto-HTTPS; bug fixes * Fix build - what happened to our CI tests?? * Fix go.mod
		
			
				
	
	
		
			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)
 | |
| )
 |