github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/helper/tlsutil/generate.go (about)

     1  package tlsutil
     2  
     3  import (
     4  	"bytes"
     5  	"crypto"
     6  	"crypto/ecdsa"
     7  	"crypto/elliptic"
     8  	"crypto/rand"
     9  	"crypto/rsa"
    10  	"crypto/sha256"
    11  	"crypto/x509"
    12  	"crypto/x509/pkix"
    13  	"encoding/pem"
    14  	"fmt"
    15  	"math/big"
    16  	"net"
    17  	"time"
    18  )
    19  
    20  // GenerateSerialNumber returns random bigint generated with crypto/rand
    21  func GenerateSerialNumber() (*big.Int, error) {
    22  	l := new(big.Int).Lsh(big.NewInt(1), 128)
    23  	s, err := rand.Int(rand.Reader, l)
    24  	if err != nil {
    25  		return nil, err
    26  	}
    27  	return s, nil
    28  }
    29  
    30  // GeneratePrivateKey generates a new ecdsa private key
    31  func GeneratePrivateKey() (crypto.Signer, string, error) {
    32  	curve := elliptic.P256()
    33  
    34  	pk, err := ecdsa.GenerateKey(curve, rand.Reader)
    35  	if err != nil {
    36  		return nil, "", fmt.Errorf("error generating ECDSA private key: %s", err)
    37  	}
    38  
    39  	bs, err := x509.MarshalECPrivateKey(pk)
    40  	if err != nil {
    41  		return nil, "", fmt.Errorf("error marshaling ECDSA private key: %s", err)
    42  	}
    43  
    44  	pemBlock, err := pemEncodeKey(bs, "EC PRIVATE KEY")
    45  	if err != nil {
    46  		return nil, "", err
    47  	}
    48  
    49  	return pk, pemBlock, nil
    50  }
    51  
    52  func pemEncodeKey(key []byte, blockType string) (string, error) {
    53  	var buf bytes.Buffer
    54  
    55  	if err := pem.Encode(&buf, &pem.Block{Type: blockType, Bytes: key}); err != nil {
    56  		return "", fmt.Errorf("error encoding private key: %s", err)
    57  	}
    58  	return buf.String(), nil
    59  }
    60  
    61  type CAOpts struct {
    62  	Signer              crypto.Signer
    63  	Serial              *big.Int
    64  	Days                int
    65  	PermittedDNSDomains []string
    66  	Domain              string
    67  	Name                string
    68  }
    69  
    70  type CertOpts struct {
    71  	Signer      crypto.Signer
    72  	CA          string
    73  	Serial      *big.Int
    74  	Name        string
    75  	Days        int
    76  	DNSNames    []string
    77  	IPAddresses []net.IP
    78  	ExtKeyUsage []x509.ExtKeyUsage
    79  }
    80  
    81  // GenerateCA generates a new CA for agent TLS (not to be confused with Connect TLS)
    82  func GenerateCA(opts CAOpts) (string, string, error) {
    83  	signer := opts.Signer
    84  	var pk string
    85  	if signer == nil {
    86  		var err error
    87  		signer, pk, err = GeneratePrivateKey()
    88  		if err != nil {
    89  			return "", "", err
    90  		}
    91  	}
    92  
    93  	id, err := keyID(signer.Public())
    94  	if err != nil {
    95  		return "", "", err
    96  	}
    97  
    98  	sn := opts.Serial
    99  	if sn == nil {
   100  		var err error
   101  		sn, err = GenerateSerialNumber()
   102  		if err != nil {
   103  			return "", "", err
   104  		}
   105  	}
   106  	name := opts.Name
   107  	if name == "" {
   108  		name = fmt.Sprintf("Nomad Agent CA %d", sn)
   109  	}
   110  
   111  	days := opts.Days
   112  	if opts.Days == 0 {
   113  		days = 365
   114  	}
   115  
   116  	// Create the CA cert
   117  	template := x509.Certificate{
   118  		SerialNumber: sn,
   119  		Subject: pkix.Name{
   120  			Country:       []string{"US"},
   121  			PostalCode:    []string{"94105"},
   122  			Province:      []string{"CA"},
   123  			Locality:      []string{"San Francisco"},
   124  			StreetAddress: []string{"101 Second Street"},
   125  			Organization:  []string{"HashiCorp Inc."},
   126  			CommonName:    name,
   127  		},
   128  		BasicConstraintsValid: true,
   129  		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
   130  		IsCA:                  true,
   131  		NotAfter:              time.Now().AddDate(0, 0, days),
   132  		NotBefore:             time.Now(),
   133  		AuthorityKeyId:        id,
   134  		SubjectKeyId:          id,
   135  	}
   136  
   137  	if len(opts.PermittedDNSDomains) > 0 {
   138  		template.PermittedDNSDomainsCritical = true
   139  		template.PermittedDNSDomains = opts.PermittedDNSDomains
   140  	}
   141  	bs, err := x509.CreateCertificate(
   142  		rand.Reader, &template, &template, signer.Public(), signer)
   143  	if err != nil {
   144  		return "", "", fmt.Errorf("error generating CA certificate: %s", err)
   145  	}
   146  
   147  	var buf bytes.Buffer
   148  	err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
   149  	if err != nil {
   150  		return "", "", fmt.Errorf("error encoding private key: %s", err)
   151  	}
   152  
   153  	return buf.String(), pk, nil
   154  }
   155  
   156  // GenerateCert generates a new certificate for agent TLS (not to be confused with Connect TLS)
   157  func GenerateCert(opts CertOpts) (string, string, error) {
   158  	parent, err := parseCert(opts.CA)
   159  	if err != nil {
   160  		return "", "", err
   161  	}
   162  
   163  	signee, pk, err := GeneratePrivateKey()
   164  	if err != nil {
   165  		return "", "", err
   166  	}
   167  
   168  	id, err := keyID(signee.Public())
   169  	if err != nil {
   170  		return "", "", err
   171  	}
   172  
   173  	sn := opts.Serial
   174  	if sn == nil {
   175  		var err error
   176  		sn, err = GenerateSerialNumber()
   177  		if err != nil {
   178  			return "", "", err
   179  		}
   180  	}
   181  
   182  	template := x509.Certificate{
   183  		SerialNumber:          sn,
   184  		Subject:               pkix.Name{CommonName: opts.Name},
   185  		BasicConstraintsValid: true,
   186  		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
   187  		ExtKeyUsage:           opts.ExtKeyUsage,
   188  		IsCA:                  false,
   189  		NotAfter:              time.Now().AddDate(0, 0, opts.Days),
   190  		NotBefore:             time.Now(),
   191  		SubjectKeyId:          id,
   192  		DNSNames:              opts.DNSNames,
   193  		IPAddresses:           opts.IPAddresses,
   194  	}
   195  
   196  	bs, err := x509.CreateCertificate(rand.Reader, &template, parent, signee.Public(), opts.Signer)
   197  	if err != nil {
   198  		return "", "", err
   199  	}
   200  
   201  	var buf bytes.Buffer
   202  	err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
   203  	if err != nil {
   204  		return "", "", fmt.Errorf("error encoding private key: %s", err)
   205  	}
   206  
   207  	return buf.String(), pk, nil
   208  }
   209  
   210  // KeyId returns a x509 KeyId from the given signing key.
   211  func keyID(raw interface{}) ([]byte, error) {
   212  	switch raw.(type) {
   213  	case *ecdsa.PublicKey:
   214  	case *rsa.PublicKey:
   215  	default:
   216  		return nil, fmt.Errorf("invalid key type: %T", raw)
   217  	}
   218  
   219  	// This is not standard; RFC allows any unique identifier as long as they
   220  	// match in subject/authority chains but suggests specific hashing of DER
   221  	// bytes of public key including DER tags.
   222  	bs, err := x509.MarshalPKIXPublicKey(raw)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	// String formatted
   228  	kID := sha256.Sum256(bs)
   229  	return kID[:], nil
   230  }
   231  
   232  // ParseCert parses the x509 certificate from a PEM-encoded value.
   233  func ParseCert(pemValue string) (*x509.Certificate, error) {
   234  	// The _ result below is not an error but the remaining PEM bytes.
   235  	block, _ := pem.Decode([]byte(pemValue))
   236  	if block == nil {
   237  		return nil, fmt.Errorf("no PEM-encoded data found")
   238  	}
   239  
   240  	if block.Type != "CERTIFICATE" {
   241  		return nil, fmt.Errorf("first PEM-block should be CERTIFICATE type")
   242  	}
   243  
   244  	return x509.ParseCertificate(block.Bytes)
   245  }
   246  
   247  func parseCert(pemValue string) (*x509.Certificate, error) {
   248  	// The _ result below is not an error but the remaining PEM bytes.
   249  	block, _ := pem.Decode([]byte(pemValue))
   250  	if block == nil {
   251  		return nil, fmt.Errorf("no PEM-encoded data found")
   252  	}
   253  
   254  	if block.Type != "CERTIFICATE" {
   255  		return nil, fmt.Errorf("first PEM-block should be CERTIFICATE type")
   256  	}
   257  
   258  	return x509.ParseCertificate(block.Bytes)
   259  }
   260  
   261  // ParseSigner parses a crypto.Signer from a PEM-encoded key. The private key
   262  // is expected to be the first block in the PEM value.
   263  func ParseSigner(pemValue string) (crypto.Signer, error) {
   264  	// The _ result below is not an error but the remaining PEM bytes.
   265  	block, _ := pem.Decode([]byte(pemValue))
   266  	if block == nil {
   267  		return nil, fmt.Errorf("no PEM-encoded data found")
   268  	}
   269  
   270  	switch block.Type {
   271  	case "EC PRIVATE KEY":
   272  		return x509.ParseECPrivateKey(block.Bytes)
   273  
   274  	case "RSA PRIVATE KEY":
   275  		return x509.ParsePKCS1PrivateKey(block.Bytes)
   276  
   277  	case "PRIVATE KEY":
   278  		signer, err := x509.ParsePKCS8PrivateKey(block.Bytes)
   279  		if err != nil {
   280  			return nil, err
   281  		}
   282  		pk, ok := signer.(crypto.Signer)
   283  		if !ok {
   284  			return nil, fmt.Errorf("private key is not a valid format")
   285  		}
   286  
   287  		return pk, nil
   288  
   289  	default:
   290  		return nil, fmt.Errorf("unknown PEM block type for signing key: %s", block.Type)
   291  	}
   292  }
   293  
   294  func Verify(caString, certString, dns string) error {
   295  	roots := x509.NewCertPool()
   296  	ok := roots.AppendCertsFromPEM([]byte(caString))
   297  	if !ok {
   298  		return fmt.Errorf("failed to parse root certificate")
   299  	}
   300  
   301  	cert, err := parseCert(certString)
   302  	if err != nil {
   303  		return fmt.Errorf("failed to parse certificate")
   304  	}
   305  
   306  	opts := x509.VerifyOptions{
   307  		DNSName: fmt.Sprint(dns),
   308  		Roots:   roots,
   309  	}
   310  
   311  	_, err = cert.Verify(opts)
   312  	return err
   313  }