mirror of https://github.com/kubernetes/kops.git
				
				
				
			Move cert issuance code to pki module
This commit is contained in:
		
							parent
							
								
									2bfdf8a9c3
								
							
						
					
					
						commit
						c142483cfa
					
				| 
						 | 
				
			
			@ -35,7 +35,6 @@ go_library(
 | 
			
		|||
        "import.go",
 | 
			
		||||
        "import_cluster.go",
 | 
			
		||||
        "main.go",
 | 
			
		||||
        "pkix.go",
 | 
			
		||||
        "replace.go",
 | 
			
		||||
        "rollingupdate.go",
 | 
			
		||||
        "rollingupdatecluster.go",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,7 @@ import (
 | 
			
		|||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"k8s.io/kops/pkg/apis/kops"
 | 
			
		||||
	"k8s.io/kops/pkg/pki"
 | 
			
		||||
	"k8s.io/kops/upup/pkg/fi"
 | 
			
		||||
	"k8s.io/kubectl/pkg/util/i18n"
 | 
			
		||||
	"k8s.io/kubectl/pkg/util/templates"
 | 
			
		||||
| 
						 | 
				
			
			@ -176,8 +177,8 @@ func describeKeypair(keyStore fi.CAStore, item *fi.KeystoreItem, w *bytes.Buffer
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	if cert != nil {
 | 
			
		||||
		fmt.Fprintf(w, "Subject:\t%s\n", pkixNameToString(&cert.Certificate.Subject))
 | 
			
		||||
		fmt.Fprintf(w, "Issuer:\t%s\n", pkixNameToString(&cert.Certificate.Issuer))
 | 
			
		||||
		fmt.Fprintf(w, "Subject:\t%s\n", pki.PkixNameToString(&cert.Certificate.Subject))
 | 
			
		||||
		fmt.Fprintf(w, "Issuer:\t%s\n", pki.PkixNameToString(&cert.Certificate.Issuer))
 | 
			
		||||
		fmt.Fprintf(w, "AlternateNames:\t%s\n", strings.Join(alternateNames, ", "))
 | 
			
		||||
		fmt.Fprintf(w, "CA:\t%v\n", cert.IsCA)
 | 
			
		||||
		fmt.Fprintf(w, "NotAfter:\t%s\n", cert.Certificate.NotAfter)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,58 +0,0 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2019 The Kubernetes 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 main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/x509/pkix"
 | 
			
		||||
	"fmt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func pkixNameToString(name *pkix.Name) string {
 | 
			
		||||
	seq := name.ToRDNSequence()
 | 
			
		||||
	var s bytes.Buffer
 | 
			
		||||
	for _, rdnSet := range seq {
 | 
			
		||||
		for _, rdn := range rdnSet {
 | 
			
		||||
			if s.Len() != 0 {
 | 
			
		||||
				s.WriteString(",")
 | 
			
		||||
			}
 | 
			
		||||
			key := ""
 | 
			
		||||
			t := rdn.Type
 | 
			
		||||
			if len(t) == 4 && t[0] == 2 && t[1] == 5 && t[2] == 4 {
 | 
			
		||||
				switch t[3] {
 | 
			
		||||
				case 3:
 | 
			
		||||
					key = "cn"
 | 
			
		||||
				case 5:
 | 
			
		||||
					key = "serial"
 | 
			
		||||
				case 6:
 | 
			
		||||
					key = "c"
 | 
			
		||||
				case 7:
 | 
			
		||||
					key = "l"
 | 
			
		||||
				case 10:
 | 
			
		||||
					key = "o"
 | 
			
		||||
				case 11:
 | 
			
		||||
					key = "ou"
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if key == "" {
 | 
			
		||||
				key = t.String()
 | 
			
		||||
			}
 | 
			
		||||
			s.WriteString(fmt.Sprintf("%v=%v", key, rdn.Value))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return s.String()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,8 +3,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 | 
			
		|||
go_library(
 | 
			
		||||
    name = "go_default_library",
 | 
			
		||||
    srcs = [
 | 
			
		||||
        "cert_utils.go",
 | 
			
		||||
        "certificate.go",
 | 
			
		||||
        "csr.go",
 | 
			
		||||
        "issue.go",
 | 
			
		||||
        "privatekey.go",
 | 
			
		||||
        "sshkey.go",
 | 
			
		||||
    ],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,19 +14,20 @@ See the License for the specific language governing permissions and
 | 
			
		|||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package fitasks
 | 
			
		||||
package pki
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"crypto/x509/pkix"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func pkixNameToString(name *pkix.Name) string {
 | 
			
		||||
func PkixNameToString(name *pkix.Name) string {
 | 
			
		||||
	seq := name.ToRDNSequence()
 | 
			
		||||
	var s bytes.Buffer
 | 
			
		||||
	for _, rdnSet := range seq {
 | 
			
		||||
| 
						 | 
				
			
			@ -61,32 +62,6 @@ func pkixNameToString(name *pkix.Name) string {
 | 
			
		|||
	return s.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parsePkixName(s string) (*pkix.Name, error) {
 | 
			
		||||
	name := new(pkix.Name)
 | 
			
		||||
 | 
			
		||||
	tokens := strings.Split(s, ",")
 | 
			
		||||
	for _, token := range tokens {
 | 
			
		||||
		token = strings.TrimSpace(token)
 | 
			
		||||
		kv := strings.SplitN(token, "=", 2)
 | 
			
		||||
		if len(kv) != 2 {
 | 
			
		||||
			return nil, fmt.Errorf("unrecognized token (expected k=v): %q", token)
 | 
			
		||||
		}
 | 
			
		||||
		k := strings.ToLower(kv[0])
 | 
			
		||||
		v := kv[1]
 | 
			
		||||
 | 
			
		||||
		switch k {
 | 
			
		||||
		case "cn":
 | 
			
		||||
			name.CommonName = v
 | 
			
		||||
		case "o":
 | 
			
		||||
			name.Organization = append(name.Organization, v)
 | 
			
		||||
		default:
 | 
			
		||||
			return nil, fmt.Errorf("unrecognized key %q in token %q", k, token)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return name, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var keyUsageStrings = map[x509.KeyUsage]string{
 | 
			
		||||
	x509.KeyUsageDigitalSignature:  "KeyUsageDigitalSignature",
 | 
			
		||||
	x509.KeyUsageContentCommitment: "KeyUsageContentCommitment",
 | 
			
		||||
| 
						 | 
				
			
			@ -153,3 +128,29 @@ func parseExtKeyUsage(s string) (x509.ExtKeyUsage, bool) {
 | 
			
		|||
	}
 | 
			
		||||
	return 0, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BuildTypeDescription extracts the type based on the certificate extensions
 | 
			
		||||
func BuildTypeDescription(cert *x509.Certificate) string {
 | 
			
		||||
	var options []string
 | 
			
		||||
 | 
			
		||||
	if cert.IsCA {
 | 
			
		||||
		options = append(options, "CA")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	options = append(options, keyUsageToString(cert.KeyUsage)...)
 | 
			
		||||
 | 
			
		||||
	for _, extKeyUsage := range cert.ExtKeyUsage {
 | 
			
		||||
		options = append(options, extKeyUsageToString(extKeyUsage))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Strings(options)
 | 
			
		||||
	s := strings.Join(options, ",")
 | 
			
		||||
 | 
			
		||||
	for k, v := range wellKnownCertificateTypes {
 | 
			
		||||
		if v == s {
 | 
			
		||||
			s = k
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,144 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2020 The Kubernetes 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 pki
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"crypto/x509/pkix"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var wellKnownCertificateTypes = map[string]string{
 | 
			
		||||
	"ca":           "CA,KeyUsageCRLSign,KeyUsageCertSign",
 | 
			
		||||
	"client":       "ExtKeyUsageClientAuth,KeyUsageDigitalSignature",
 | 
			
		||||
	"clientServer": "ExtKeyUsageClientAuth,ExtKeyUsageServerAuth,KeyUsageDigitalSignature,KeyUsageKeyEncipherment",
 | 
			
		||||
	"server":       "ExtKeyUsageServerAuth,KeyUsageDigitalSignature,KeyUsageKeyEncipherment",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type IssueCertRequest struct {
 | 
			
		||||
	// Signer is the keypair to use to sign.
 | 
			
		||||
	Signer string
 | 
			
		||||
	// Type is the type of certificate i.e. CA, server, client etc.
 | 
			
		||||
	Type string
 | 
			
		||||
	// Subject is the certificate subject.
 | 
			
		||||
	Subject pkix.Name
 | 
			
		||||
	// AlternateNames is a list of alternative names for this certificate.
 | 
			
		||||
	AlternateNames []string
 | 
			
		||||
 | 
			
		||||
	// PrivateKey is the private key for this certificate. If nil, a new private key will be generated.
 | 
			
		||||
	PrivateKey *PrivateKey
 | 
			
		||||
 | 
			
		||||
	// Serial is the certificate serial number. If nil, a random number will be generated.
 | 
			
		||||
	Serial *big.Int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Keystore interface {
 | 
			
		||||
	// FindKeypair finds a cert & private key, returning nil where either is not found
 | 
			
		||||
	// (if the certificate is found but not keypair, that is not an error: only the cert will be returned).
 | 
			
		||||
	// This func returns a cert, private key and a bool.  The bool value is whether the keypair is stored
 | 
			
		||||
	// in a legacy format. This bool is used by a keypair
 | 
			
		||||
	// task to convert a Legacy Keypair to the new Keypair API format.
 | 
			
		||||
	FindKeypair(name string) (*Certificate, *PrivateKey, bool, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IssueCert(request *IssueCertRequest, keystore Keystore) (*Certificate, *PrivateKey, error) {
 | 
			
		||||
	certificateType := request.Type
 | 
			
		||||
	if expanded, found := wellKnownCertificateTypes[certificateType]; found {
 | 
			
		||||
		certificateType = expanded
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template := &x509.Certificate{
 | 
			
		||||
		BasicConstraintsValid: true,
 | 
			
		||||
		IsCA:                  false,
 | 
			
		||||
		SerialNumber:          request.Serial,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tokens := strings.Split(certificateType, ",")
 | 
			
		||||
	for _, t := range tokens {
 | 
			
		||||
		if strings.HasPrefix(t, "KeyUsage") {
 | 
			
		||||
			ku, found := parseKeyUsage(t)
 | 
			
		||||
			if !found {
 | 
			
		||||
				return nil, nil, fmt.Errorf("unrecognized certificate option: %v", t)
 | 
			
		||||
			}
 | 
			
		||||
			template.KeyUsage |= ku
 | 
			
		||||
		} else if strings.HasPrefix(t, "ExtKeyUsage") {
 | 
			
		||||
			ku, found := parseExtKeyUsage(t)
 | 
			
		||||
			if !found {
 | 
			
		||||
				return nil, nil, fmt.Errorf("unrecognized certificate option: %v", t)
 | 
			
		||||
			}
 | 
			
		||||
			template.ExtKeyUsage = append(template.ExtKeyUsage, ku)
 | 
			
		||||
		} else if t == "CA" {
 | 
			
		||||
			template.IsCA = true
 | 
			
		||||
		} else {
 | 
			
		||||
			return nil, nil, fmt.Errorf("unrecognized certificate option: %q", t)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template.Subject = request.Subject
 | 
			
		||||
 | 
			
		||||
	var alternateNames []string
 | 
			
		||||
	alternateNames = append(alternateNames, request.AlternateNames...)
 | 
			
		||||
 | 
			
		||||
	for _, san := range alternateNames {
 | 
			
		||||
		san = strings.TrimSpace(san)
 | 
			
		||||
		if san == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if ip := net.ParseIP(san); ip != nil {
 | 
			
		||||
			template.IPAddresses = append(template.IPAddresses, ip)
 | 
			
		||||
		} else {
 | 
			
		||||
			template.DNSNames = append(template.DNSNames, san)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var caCertificate *x509.Certificate
 | 
			
		||||
	var caPrivateKey *PrivateKey
 | 
			
		||||
	if !template.IsCA {
 | 
			
		||||
		var err error
 | 
			
		||||
		var caCert *Certificate
 | 
			
		||||
		caCert, caPrivateKey, _, err = keystore.FindKeypair(request.Signer)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
		}
 | 
			
		||||
		if caPrivateKey == nil {
 | 
			
		||||
			return nil, nil, fmt.Errorf("ca key for %q was not found; cannot issue certificates", request.Signer)
 | 
			
		||||
		}
 | 
			
		||||
		if caCert == nil {
 | 
			
		||||
			return nil, nil, fmt.Errorf("ca certificate for %q was not found; cannot issue certificates", request.Signer)
 | 
			
		||||
		}
 | 
			
		||||
		caCertificate = caCert.Certificate
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	privateKey := request.PrivateKey
 | 
			
		||||
	if privateKey == nil {
 | 
			
		||||
		var err error
 | 
			
		||||
		privateKey, err = GeneratePrivateKey()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	certificate, err := SignNewCertificate(privateKey, template, caCertificate, caPrivateKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return certificate, privateKey, err
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,7 +3,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 | 
			
		|||
go_library(
 | 
			
		||||
    name = "go_default_library",
 | 
			
		||||
    srcs = [
 | 
			
		||||
        "cert_utils.go",
 | 
			
		||||
        "keypair.go",
 | 
			
		||||
        "keypair_fitask.go",
 | 
			
		||||
        "managedfile.go",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,11 +17,8 @@ limitations under the License.
 | 
			
		|||
package fitasks
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"crypto/x509/pkix"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"net"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
| 
						 | 
				
			
			@ -31,13 +28,6 @@ import (
 | 
			
		|||
	"k8s.io/kops/upup/pkg/fi"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var wellKnownCertificateTypes = map[string]string{
 | 
			
		||||
	"ca":           "CA,KeyUsageCRLSign,KeyUsageCertSign",
 | 
			
		||||
	"client":       "ExtKeyUsageClientAuth,KeyUsageDigitalSignature",
 | 
			
		||||
	"clientServer": "ExtKeyUsageClientAuth,ExtKeyUsageServerAuth,KeyUsageDigitalSignature,KeyUsageKeyEncipherment",
 | 
			
		||||
	"server":       "ExtKeyUsageServerAuth,KeyUsageDigitalSignature,KeyUsageKeyEncipherment",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//go:generate fitask -type=Keypair
 | 
			
		||||
type Keypair struct {
 | 
			
		||||
	// Name is the name of the keypair
 | 
			
		||||
| 
						 | 
				
			
			@ -100,12 +90,12 @@ func (e *Keypair) Find(c *fi.Context) (*Keypair, error) {
 | 
			
		|||
	actual := &Keypair{
 | 
			
		||||
		Name:           &name,
 | 
			
		||||
		AlternateNames: alternateNames,
 | 
			
		||||
		Subject:        pkixNameToString(&cert.Subject),
 | 
			
		||||
		Type:           buildTypeDescription(cert.Certificate),
 | 
			
		||||
		Subject:        pki.PkixNameToString(&cert.Subject),
 | 
			
		||||
		Type:           pki.BuildTypeDescription(cert.Certificate),
 | 
			
		||||
		LegacyFormat:   legacyFormat,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	actual.Signer = &Keypair{Subject: pkixNameToString(&cert.Certificate.Issuer)}
 | 
			
		||||
	actual.Signer = &Keypair{Subject: pki.PkixNameToString(&cert.Certificate.Issuer)}
 | 
			
		||||
 | 
			
		||||
	// Avoid spurious changes
 | 
			
		||||
	actual.Lifecycle = e.Lifecycle
 | 
			
		||||
| 
						 | 
				
			
			@ -227,7 +217,7 @@ func (_ *Keypair) Render(c *fi.Context, a, e, changes *Keypair) error {
 | 
			
		|||
			return fmt.Errorf("subject name was empty for SSL keypair %q", *e.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		req := issueCertRequest{
 | 
			
		||||
		req := pki.IssueCertRequest{
 | 
			
		||||
			Signer:         signer,
 | 
			
		||||
			Type:           e.Type,
 | 
			
		||||
			Subject:        *subjectPkix,
 | 
			
		||||
| 
						 | 
				
			
			@ -235,7 +225,7 @@ func (_ *Keypair) Render(c *fi.Context, a, e, changes *Keypair) error {
 | 
			
		|||
			PrivateKey:     privateKey,
 | 
			
		||||
			Serial:         serial,
 | 
			
		||||
		}
 | 
			
		||||
		cert, privateKey, err := issueCert(&req, c.Keystore)
 | 
			
		||||
		cert, privateKey, err := pki.IssueCert(&req, c.Keystore)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -273,130 +263,28 @@ func (_ *Keypair) Render(c *fi.Context, a, e, changes *Keypair) error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type issueCertRequest struct {
 | 
			
		||||
	// Signer is the keypair to use to sign.
 | 
			
		||||
	Signer string
 | 
			
		||||
	// Type is the type of certificate i.e. CA, server, client etc.
 | 
			
		||||
	Type string
 | 
			
		||||
	// Subject is the certificate subject.
 | 
			
		||||
	Subject pkix.Name
 | 
			
		||||
	// AlternateNames is a list of alternative names for this certificate.
 | 
			
		||||
	AlternateNames []string
 | 
			
		||||
func parsePkixName(s string) (*pkix.Name, error) {
 | 
			
		||||
	name := new(pkix.Name)
 | 
			
		||||
 | 
			
		||||
	// PrivateKey is the private key for this certificate. If nil, a new private key will be generated.
 | 
			
		||||
	PrivateKey *pki.PrivateKey
 | 
			
		||||
	tokens := strings.Split(s, ",")
 | 
			
		||||
	for _, token := range tokens {
 | 
			
		||||
		token = strings.TrimSpace(token)
 | 
			
		||||
		kv := strings.SplitN(token, "=", 2)
 | 
			
		||||
		if len(kv) != 2 {
 | 
			
		||||
			return nil, fmt.Errorf("unrecognized token (expected k=v): %q", token)
 | 
			
		||||
		}
 | 
			
		||||
		k := strings.ToLower(kv[0])
 | 
			
		||||
		v := kv[1]
 | 
			
		||||
 | 
			
		||||
	// Serial is the certificate serial number. If nil, a random number will be generated.
 | 
			
		||||
	Serial *big.Int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func issueCert(request *issueCertRequest, keystore fi.Keystore) (*pki.Certificate, *pki.PrivateKey, error) {
 | 
			
		||||
	certificateType := request.Type
 | 
			
		||||
	if expanded, found := wellKnownCertificateTypes[certificateType]; found {
 | 
			
		||||
		certificateType = expanded
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template := &x509.Certificate{
 | 
			
		||||
		BasicConstraintsValid: true,
 | 
			
		||||
		IsCA:                  false,
 | 
			
		||||
		SerialNumber:          request.Serial,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tokens := strings.Split(certificateType, ",")
 | 
			
		||||
	for _, t := range tokens {
 | 
			
		||||
		if strings.HasPrefix(t, "KeyUsage") {
 | 
			
		||||
			ku, found := parseKeyUsage(t)
 | 
			
		||||
			if !found {
 | 
			
		||||
				return nil, nil, fmt.Errorf("unrecognized certificate option: %v", t)
 | 
			
		||||
			}
 | 
			
		||||
			template.KeyUsage |= ku
 | 
			
		||||
		} else if strings.HasPrefix(t, "ExtKeyUsage") {
 | 
			
		||||
			ku, found := parseExtKeyUsage(t)
 | 
			
		||||
			if !found {
 | 
			
		||||
				return nil, nil, fmt.Errorf("unrecognized certificate option: %v", t)
 | 
			
		||||
			}
 | 
			
		||||
			template.ExtKeyUsage = append(template.ExtKeyUsage, ku)
 | 
			
		||||
		} else if t == "CA" {
 | 
			
		||||
			template.IsCA = true
 | 
			
		||||
		} else {
 | 
			
		||||
			return nil, nil, fmt.Errorf("unrecognized certificate option: %q", t)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template.Subject = request.Subject
 | 
			
		||||
 | 
			
		||||
	var alternateNames []string
 | 
			
		||||
	alternateNames = append(alternateNames, request.AlternateNames...)
 | 
			
		||||
 | 
			
		||||
	for _, san := range alternateNames {
 | 
			
		||||
		san = strings.TrimSpace(san)
 | 
			
		||||
		if san == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if ip := net.ParseIP(san); ip != nil {
 | 
			
		||||
			template.IPAddresses = append(template.IPAddresses, ip)
 | 
			
		||||
		} else {
 | 
			
		||||
			template.DNSNames = append(template.DNSNames, san)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var caCertificate *x509.Certificate
 | 
			
		||||
	var caPrivateKey *pki.PrivateKey
 | 
			
		||||
	if !template.IsCA {
 | 
			
		||||
		var err error
 | 
			
		||||
		var caCert *pki.Certificate
 | 
			
		||||
		caCert, caPrivateKey, _, err = keystore.FindKeypair(request.Signer)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
		}
 | 
			
		||||
		if caPrivateKey == nil {
 | 
			
		||||
			return nil, nil, fmt.Errorf("ca key for %q was not found; cannot issue certificates", request.Signer)
 | 
			
		||||
		}
 | 
			
		||||
		if caCert == nil {
 | 
			
		||||
			return nil, nil, fmt.Errorf("ca certificate for %q was not found; cannot issue certificates", request.Signer)
 | 
			
		||||
		}
 | 
			
		||||
		caCertificate = caCert.Certificate
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	privateKey := request.PrivateKey
 | 
			
		||||
	if privateKey == nil {
 | 
			
		||||
		var err error
 | 
			
		||||
		privateKey, err = pki.GeneratePrivateKey()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	certificate, err := pki.SignNewCertificate(privateKey, template, caCertificate, caPrivateKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return certificate, privateKey, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// buildTypeDescription extracts the type based on the certificate extensions
 | 
			
		||||
func buildTypeDescription(cert *x509.Certificate) string {
 | 
			
		||||
	var options []string
 | 
			
		||||
 | 
			
		||||
	if cert.IsCA {
 | 
			
		||||
		options = append(options, "CA")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	options = append(options, keyUsageToString(cert.KeyUsage)...)
 | 
			
		||||
 | 
			
		||||
	for _, extKeyUsage := range cert.ExtKeyUsage {
 | 
			
		||||
		options = append(options, extKeyUsageToString(extKeyUsage))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Strings(options)
 | 
			
		||||
	s := strings.Join(options, ",")
 | 
			
		||||
 | 
			
		||||
	for k, v := range wellKnownCertificateTypes {
 | 
			
		||||
		if v == s {
 | 
			
		||||
			s = k
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return s
 | 
			
		||||
		switch k {
 | 
			
		||||
		case "cn":
 | 
			
		||||
			name.CommonName = v
 | 
			
		||||
		case "o":
 | 
			
		||||
			name.Organization = append(name.Organization, v)
 | 
			
		||||
		default:
 | 
			
		||||
			return nil, fmt.Errorf("unrecognized key %q in token %q", k, token)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return name, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue