consul/agent/connect/csr.go

146 lines
4.2 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
[COMPLIANCE] License changes (#18443) * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2023-08-11 13:12:13 +00:00
// SPDX-License-Identifier: BUSL-1.1
2018-05-09 16:15:29 +00:00
package connect
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
2018-05-09 16:15:29 +00:00
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
2018-05-09 16:15:29 +00:00
"encoding/pem"
"fmt"
"net"
2018-05-09 16:15:29 +00:00
"net/url"
"strings"
2018-05-09 16:15:29 +00:00
)
// SigAlgoForKey returns the preferred x509.SignatureAlgorithm for a given key
// based on it's type. If the key type is not supported we return
// ECDSAWithSHA256 on the basis that it will fail anyway and we've already type
// checked keys by the time we call this in general.
func SigAlgoForKey(key crypto.Signer) x509.SignatureAlgorithm {
if _, ok := key.(*rsa.PrivateKey); ok {
return x509.SHA256WithRSA
}
// We default to ECDSA but don't bother detecting invalid key types as we do
// that in lots of other places and it will fail anyway if we try to sign with
// an incompatible type.
return x509.ECDSAWithSHA256
}
// SigAlgoForKeyType returns the preferred x509.SignatureAlgorithm for a given
// key type string from configuration or an existing cert. If the key type is
// not supported we return ECDSAWithSHA256 on the basis that it will fail anyway
// and we've already type checked config by the time we call this in general.
func SigAlgoForKeyType(keyType string) x509.SignatureAlgorithm {
switch keyType {
case "rsa":
return x509.SHA256WithRSA
case "ec":
fallthrough
default:
return x509.ECDSAWithSHA256
}
}
// CreateCSR returns a CSR to sign the given service with SAN entries
// along with the PEM-encoded private key for this certificate.
func CreateCSR(uri CertURI, privateKey crypto.Signer,
dnsNames []string, ipAddresses []net.IP, extensions ...pkix.Extension) (string, error) {
// Drop everything after the ':' from the name when constructing the DNS SANs.
uniqueNames := make(map[string]struct{})
formattedDNSNames := make([]string, 0)
for _, host := range dnsNames {
hostSegments := strings.Split(host, ":")
if len(hostSegments) == 0 || hostSegments[0] == "" {
continue
}
formattedHost := hostSegments[0]
if _, ok := uniqueNames[formattedHost]; !ok {
formattedDNSNames = append(formattedDNSNames, formattedHost)
uniqueNames[formattedHost] = struct{}{}
}
}
2018-05-09 16:15:29 +00:00
template := &x509.CertificateRequest{
URIs: []*url.URL{uri.URI()},
SignatureAlgorithm: SigAlgoForKey(privateKey),
ExtraExtensions: extensions,
DNSNames: formattedDNSNames,
IPAddresses: ipAddresses,
2018-05-09 16:15:29 +00:00
}
HackSANExtensionForCSR(template)
2018-05-09 16:15:29 +00:00
// Create the CSR itself
var csrBuf bytes.Buffer
bs, err := x509.CreateCertificateRequest(rand.Reader, template, privateKey)
if err != nil {
return "", err
}
err = pem.Encode(&csrBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: bs})
if err != nil {
return "", err
}
return csrBuf.String(), nil
}
// CreateCSR returns a CA CSR to sign the given service along with the PEM-encoded
// private key for this certificate.
func CreateCACSR(uri CertURI, privateKey crypto.Signer) (string, error) {
ext, err := CreateCAExtension()
if err != nil {
return "", err
}
return CreateCSR(uri, privateKey, nil, nil, ext)
}
// CreateCAExtension creates a pkix.Extension for the x509 Basic Constraints
// IsCA field ()
func CreateCAExtension() (pkix.Extension, error) {
type basicConstraints struct {
IsCA bool `asn1:"optional"`
MaxPathLen int `asn1:"optional"`
}
basicCon := basicConstraints{IsCA: true, MaxPathLen: 0}
bitstr, err := asn1.Marshal(basicCon)
if err != nil {
return pkix.Extension{}, err
}
return pkix.Extension{
Id: []int{2, 5, 29, 19}, // from x509 package
Critical: true,
Value: bitstr,
}, nil
}
// InvalidCSRError returns an error with the given fmt.Sprintf-formatted message
// indicating certificate signing failed because the user supplied an invalid CSR.
//
// See: IsInvalidCSRError.
func InvalidCSRError(format string, args ...interface{}) error {
return invalidCSRError{fmt.Sprintf(format, args...)}
}
// IsInvalidCSRError returns whether the given error indicates that certificate
// signing failed because the user supplied an invalid CSR.
func IsInvalidCSRError(err error) bool {
_, ok := err.(invalidCSRError)
return ok
}
type invalidCSRError struct {
s string
}
func (e invalidCSRError) Error() string { return e.s }