You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							199 lines
						
					
					
						
							5.7 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							199 lines
						
					
					
						
							5.7 KiB
						
					
					
				| // 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) | |
| )
 | |
| 
 |