github.com/avenga/couper@v1.12.2/server/tls_certificate.go (about)

     1  package server
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/ecdsa"
     6  	"crypto/elliptic"
     7  	"crypto/rand"
     8  	"crypto/tls"
     9  	"crypto/x509"
    10  	"crypto/x509/pkix"
    11  	"encoding/pem"
    12  	"log"
    13  	"math/big"
    14  	"net"
    15  	"strconv"
    16  	"sync/atomic"
    17  	"time"
    18  )
    19  
    20  type SelfSignedCertificate struct {
    21  	CA                            *tls.Certificate
    22  	CACertificate                 PEM
    23  	Server                        *tls.Certificate
    24  	ServerCertificate             PEM
    25  	ServerPrivateKey              []byte
    26  	Client                        *tls.Certificate
    27  	ClientCertificate             PEM
    28  	ClientPrivateKey              []byte
    29  	ClientIntermediateCertificate PEM
    30  	ClientIntermediate            *tls.Certificate
    31  }
    32  
    33  type PEM struct {
    34  	Certificate []byte
    35  	PrivateKey  []byte
    36  }
    37  
    38  var caCount uint32 = 1
    39  
    40  // NewCertificate creates a certificate with given hosts and duration.
    41  // If no hosts are provided all localhost variants will be used.
    42  func NewCertificate(duration time.Duration, hosts []string, notBefore *time.Time) (*SelfSignedCertificate, error) {
    43  	parentName := "rootCA_" + strconv.Itoa(int(atomic.LoadUint32(&caCount)))
    44  	defer atomic.AddUint32(&caCount, 1)
    45  
    46  	rootCA, rootPEM, err := newCertificateAuthority(parentName, "", nil, nil)
    47  	if err != nil {
    48  		log.Fatalf("Failed to create certificate: %s", err)
    49  	}
    50  
    51  	// server
    52  	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	template := defaultTemplate()
    58  
    59  	if notBefore == nil {
    60  		n := time.Now()
    61  		notBefore = &n
    62  	}
    63  	template.NotAfter = notBefore.Add(duration)
    64  
    65  	if len(hosts) == 0 {
    66  		hosts = []string{"127.0.0.1", "::1", "localhost", "0.0.0.0", "::0"}
    67  	}
    68  
    69  	for _, h := range hosts {
    70  		if ip := net.ParseIP(h); ip != nil {
    71  			template.IPAddresses = append(template.IPAddresses, ip)
    72  		} else {
    73  			template.DNSNames = append(template.DNSNames, h)
    74  		}
    75  	}
    76  
    77  	srvDER, err := x509.CreateCertificate(rand.Reader, &template, rootCA.Leaf, &privateKey.PublicKey, rootCA.PrivateKey)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	srvCrt, srvKeyBytes, srvPEM, err := newCertificateFromDER(srvDER, privateKey)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	// intermediate
    88  	intermediateName := "intermediateCA_" + strconv.Itoa(int(atomic.LoadUint32(&caCount)))
    89  	interCA, interPEM, err := newCertificateAuthority(intermediateName, parentName, rootCA.Leaf.PublicKey, rootCA.PrivateKey)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	// client
    95  	privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	template = defaultTemplate()
   101  	template.NotAfter = notBefore.Add(duration)
   102  	template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
   103  	template.MaxPathLenZero = true
   104  
   105  	clientDER, err := x509.CreateCertificate(rand.Reader, &template, interCA.Leaf, &privateKey.PublicKey, interCA.PrivateKey)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	clientCrt, ClientKeyBytes, clientPEM, err := newCertificateFromDER(clientDER, privateKey)
   111  
   112  	return &SelfSignedCertificate{
   113  		CA:                            rootCA,
   114  		CACertificate:                 *rootPEM,
   115  		Server:                        srvCrt,
   116  		ServerCertificate:             *srvPEM,
   117  		ServerPrivateKey:              srvKeyBytes,
   118  		Client:                        clientCrt,
   119  		ClientCertificate:             *clientPEM,
   120  		ClientPrivateKey:              ClientKeyBytes,
   121  		ClientIntermediate:            interCA,
   122  		ClientIntermediateCertificate: *interPEM,
   123  	}, err
   124  }
   125  
   126  func newCertificateAuthority(name, parentName string, publicKey any, privateKey crypto.PrivateKey) (*tls.Certificate, *PEM, error) {
   127  	pubKey := publicKey
   128  	pathLen := 2
   129  	if _, ok := privateKey.(*ecdsa.PrivateKey); !ok {
   130  		key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   131  		if err != nil {
   132  			return nil, nil, err
   133  		}
   134  		pubKey = &key.PublicKey
   135  		privateKey = key
   136  		pathLen = 1
   137  	}
   138  
   139  	template := defaultTemplate()
   140  	template.IsCA = true
   141  	template.KeyUsage |= x509.KeyUsageCertSign | x509.KeyUsageCRLSign
   142  	template.NotAfter = template.NotBefore.Add(time.Hour * 24)
   143  	template.Subject = pkix.Name{
   144  		CommonName:         name,
   145  		Country:            template.Subject.Country,
   146  		Organization:       template.Subject.Organization,
   147  		OrganizationalUnit: template.Subject.OrganizationalUnit,
   148  	}
   149  	template.BasicConstraintsValid = true
   150  	template.MaxPathLen = pathLen
   151  
   152  	if parentName != "" {
   153  		template.Issuer = template.Subject
   154  		template.Issuer.CommonName = parentName
   155  	}
   156  
   157  	caDER, err := x509.CreateCertificate(rand.Reader, &template, &template, pubKey, privateKey)
   158  	if err != nil {
   159  		return nil, nil, err
   160  	}
   161  	cert, _, certPEM, err := newCertificateFromDER(caDER, privateKey)
   162  	return cert, certPEM, err
   163  }
   164  
   165  func newCertificateFromDER(caDER []byte, key any) (*tls.Certificate, []byte, *PEM, error) {
   166  	caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caDER})
   167  
   168  	privBytes, err := x509.MarshalPKCS8PrivateKey(key)
   169  	if err != nil {
   170  		return nil, nil, nil, err
   171  	}
   172  	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
   173  
   174  	certificate, err := tls.X509KeyPair(caPEM, keyPEM)
   175  	if err != nil {
   176  		return nil, nil, nil, err
   177  	}
   178  	certificate.Leaf, err = x509.ParseCertificate(caDER)
   179  	return &certificate, privBytes, &PEM{caPEM, keyPEM}, err
   180  }
   181  
   182  func newSerialNumber() *big.Int {
   183  	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
   184  	i, _ := rand.Int(rand.Reader, serialNumberLimit)
   185  	return i
   186  }
   187  
   188  func defaultTemplate() x509.Certificate {
   189  	return x509.Certificate{
   190  		Subject: pkix.Name{
   191  			Country:            []string{"DE"},
   192  			Organization:       []string{"Couper"},
   193  			OrganizationalUnit: []string{"Development"},
   194  		},
   195  		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
   196  		Issuer:       pkix.Name{CommonName: "github/avenga/couper/server"},
   197  		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
   198  		NotBefore:    time.Now(),
   199  		SerialNumber: newSerialNumber(),
   200  	}
   201  }