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 }