mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			238 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			238 lines
		
	
	
		
			8.2 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 caddypki
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/x509"
 | 
						|
	"encoding/json"
 | 
						|
	"encoding/pem"
 | 
						|
	"flag"
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
	"os"
 | 
						|
	"path"
 | 
						|
 | 
						|
	"github.com/caddyserver/caddy/v2"
 | 
						|
	caddycmd "github.com/caddyserver/caddy/v2/cmd"
 | 
						|
	"github.com/smallstep/truststore"
 | 
						|
)
 | 
						|
 | 
						|
func init() {
 | 
						|
	caddycmd.RegisterCommand(caddycmd.Command{
 | 
						|
		Name:  "trust",
 | 
						|
		Func:  cmdTrust,
 | 
						|
		Usage: "[--ca <id>] [--address <listen>] [--config <path> [--adapter <name>]]",
 | 
						|
		Short: "Installs a CA certificate into local trust stores",
 | 
						|
		Long: `
 | 
						|
Adds a root certificate into the local trust stores.
 | 
						|
 | 
						|
Caddy will attempt to install its root certificates into the local
 | 
						|
trust stores automatically when they are first generated, but it
 | 
						|
might fail if Caddy doesn't have the appropriate permissions to
 | 
						|
write to the trust store. This command is necessary to pre-install
 | 
						|
the certificates before using them, if the server process runs as an
 | 
						|
unprivileged user (such as via systemd).
 | 
						|
 | 
						|
By default, this command installs the root certificate for Caddy's
 | 
						|
default CA (i.e. 'local'). You may specify the ID of another CA
 | 
						|
with the --ca flag.
 | 
						|
 | 
						|
This command will attempt to connect to Caddy's admin API running at
 | 
						|
'` + caddy.DefaultAdminListen + `' to fetch the root certificate. You may
 | 
						|
explicitly specify the --address, or use the --config flag to load
 | 
						|
the admin address from your config, if not using the default.`,
 | 
						|
		Flags: func() *flag.FlagSet {
 | 
						|
			fs := flag.NewFlagSet("trust", flag.ExitOnError)
 | 
						|
			fs.String("ca", "", "The ID of the CA to trust (defaults to 'local')")
 | 
						|
			fs.String("address", "", "Address of the administration API listener (if --config is not used)")
 | 
						|
			fs.String("config", "", "Configuration file (if --address is not used)")
 | 
						|
			fs.String("adapter", "", "Name of config adapter to apply (if --config is used)")
 | 
						|
			return fs
 | 
						|
		}(),
 | 
						|
	})
 | 
						|
 | 
						|
	caddycmd.RegisterCommand(caddycmd.Command{
 | 
						|
		Name:  "untrust",
 | 
						|
		Func:  cmdUntrust,
 | 
						|
		Usage: "[--cert <path>] | [[--ca <id>] [--address <listen>] [--config <path> [--adapter <name>]]]",
 | 
						|
		Short: "Untrusts a locally-trusted CA certificate",
 | 
						|
		Long: `
 | 
						|
Untrusts a root certificate from the local trust store(s).
 | 
						|
 | 
						|
This command uninstalls trust; it does not necessarily delete the
 | 
						|
root certificate from trust stores entirely. Thus, repeatedly
 | 
						|
trusting and untrusting new certificates can fill up trust databases.
 | 
						|
 | 
						|
This command does not delete or modify certificate files from Caddy's
 | 
						|
configured storage.
 | 
						|
 | 
						|
This command can be used in one of two ways. Either by specifying
 | 
						|
which certificate to untrust by a direct path to the certificate
 | 
						|
file with the --cert flag, or by fetching the root certificate for
 | 
						|
the CA from the admin API (default behaviour).
 | 
						|
 | 
						|
If the admin API is used, then the CA defaults to 'local'. You may
 | 
						|
specify the ID of another CA with the --ca flag. By default, this
 | 
						|
will attempt to connect to the Caddy's admin API running at
 | 
						|
'` + caddy.DefaultAdminListen + `' to fetch the root certificate.
 | 
						|
You may explicitly specify the --address, or use the --config flag
 | 
						|
to load the admin address from your config, if not using the default.`,
 | 
						|
		Flags: func() *flag.FlagSet {
 | 
						|
			fs := flag.NewFlagSet("untrust", flag.ExitOnError)
 | 
						|
			fs.String("cert", "", "The path to the CA certificate to untrust")
 | 
						|
			fs.String("ca", "", "The ID of the CA to untrust (defaults to 'local')")
 | 
						|
			fs.String("address", "", "Address of the administration API listener (if --config is not used)")
 | 
						|
			fs.String("config", "", "Configuration file (if --address is not used)")
 | 
						|
			fs.String("adapter", "", "Name of config adapter to apply (if --config is used)")
 | 
						|
			return fs
 | 
						|
		}(),
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func cmdTrust(fl caddycmd.Flags) (int, error) {
 | 
						|
	caID := fl.String("ca")
 | 
						|
	addrFlag := fl.String("address")
 | 
						|
	configFlag := fl.String("config")
 | 
						|
	configAdapterFlag := fl.String("adapter")
 | 
						|
 | 
						|
	// Prepare the URI to the admin endpoint
 | 
						|
	if caID == "" {
 | 
						|
		caID = DefaultCAID
 | 
						|
	}
 | 
						|
 | 
						|
	// Determine where we're sending the request to get the CA info
 | 
						|
	adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, configFlag, configAdapterFlag)
 | 
						|
	if err != nil {
 | 
						|
		return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Fetch the root cert from the admin API
 | 
						|
	rootCert, err := rootCertFromAdmin(adminAddr, caID)
 | 
						|
	if err != nil {
 | 
						|
		return caddy.ExitCodeFailedStartup, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Set up the CA struct; we only need to fill in the root
 | 
						|
	// because we're only using it to make use of the installRoot()
 | 
						|
	// function. Also needs a logger for warnings, and a "cert path"
 | 
						|
	// for the root cert; since we're loading from the API and we
 | 
						|
	// don't know the actual storage path via this flow, we'll just
 | 
						|
	// pass through the admin API address instead.
 | 
						|
	ca := CA{
 | 
						|
		log:          caddy.Log(),
 | 
						|
		root:         rootCert,
 | 
						|
		rootCertPath: adminAddr + path.Join(adminPKIEndpointBase, "ca", caID),
 | 
						|
	}
 | 
						|
 | 
						|
	// Install the cert!
 | 
						|
	err = ca.installRoot()
 | 
						|
	if err != nil {
 | 
						|
		return caddy.ExitCodeFailedStartup, err
 | 
						|
	}
 | 
						|
 | 
						|
	return caddy.ExitCodeSuccess, nil
 | 
						|
}
 | 
						|
 | 
						|
func cmdUntrust(fl caddycmd.Flags) (int, error) {
 | 
						|
	certFile := fl.String("cert")
 | 
						|
	caID := fl.String("ca")
 | 
						|
	addrFlag := fl.String("address")
 | 
						|
	configFlag := fl.String("config")
 | 
						|
	configAdapterFlag := fl.String("adapter")
 | 
						|
 | 
						|
	if certFile != "" && (caID != "" || addrFlag != "" || configFlag != "") {
 | 
						|
		return caddy.ExitCodeFailedStartup, fmt.Errorf("conflicting command line arguments, cannot use --cert with other flags")
 | 
						|
	}
 | 
						|
 | 
						|
	// If a file was specified, try to uninstall the cert matching that file
 | 
						|
	if certFile != "" {
 | 
						|
		// Sanity check, make sure cert file exists first
 | 
						|
		_, err := os.Stat(certFile)
 | 
						|
		if err != nil {
 | 
						|
			return caddy.ExitCodeFailedStartup, fmt.Errorf("accessing certificate file: %v", err)
 | 
						|
		}
 | 
						|
 | 
						|
		// Uninstall the file!
 | 
						|
		err = truststore.UninstallFile(certFile,
 | 
						|
			truststore.WithDebug(),
 | 
						|
			truststore.WithFirefox(),
 | 
						|
			truststore.WithJava())
 | 
						|
		if err != nil {
 | 
						|
			return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to uninstall certificate file: %v", err)
 | 
						|
		}
 | 
						|
 | 
						|
		return caddy.ExitCodeSuccess, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Prepare the URI to the admin endpoint
 | 
						|
	if caID == "" {
 | 
						|
		caID = DefaultCAID
 | 
						|
	}
 | 
						|
 | 
						|
	// Determine where we're sending the request to get the CA info
 | 
						|
	adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, configFlag, configAdapterFlag)
 | 
						|
	if err != nil {
 | 
						|
		return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Fetch the root cert from the admin API
 | 
						|
	rootCert, err := rootCertFromAdmin(adminAddr, caID)
 | 
						|
	if err != nil {
 | 
						|
		return caddy.ExitCodeFailedStartup, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Uninstall the cert!
 | 
						|
	err = truststore.Uninstall(rootCert,
 | 
						|
		truststore.WithDebug(),
 | 
						|
		truststore.WithFirefox(),
 | 
						|
		truststore.WithJava())
 | 
						|
	if err != nil {
 | 
						|
		return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to uninstall certificate file: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return caddy.ExitCodeSuccess, nil
 | 
						|
}
 | 
						|
 | 
						|
// rootCertFromAdmin makes the API request to fetch the root certificate for the named CA via admin API.
 | 
						|
func rootCertFromAdmin(adminAddr string, caID string) (*x509.Certificate, error) {
 | 
						|
	uri := path.Join(adminPKIEndpointBase, "ca", caID)
 | 
						|
 | 
						|
	// Make the request to fetch the CA info
 | 
						|
	resp, err := caddycmd.AdminAPIRequest(adminAddr, http.MethodGet, uri, make(http.Header), nil)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("requesting CA info: %v", err)
 | 
						|
	}
 | 
						|
	defer resp.Body.Close()
 | 
						|
 | 
						|
	// Decode the response
 | 
						|
	caInfo := new(caInfo)
 | 
						|
	err = json.NewDecoder(resp.Body).Decode(caInfo)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to decode JSON response: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Decode the root cert
 | 
						|
	rootBlock, _ := pem.Decode([]byte(caInfo.RootCert))
 | 
						|
	if rootBlock == nil {
 | 
						|
		return nil, fmt.Errorf("failed to decode root certificate: %v", err)
 | 
						|
	}
 | 
						|
	rootCert, err := x509.ParseCertificate(rootBlock.Bytes)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to parse root certificate: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return rootCert, nil
 | 
						|
}
 |