1 package tlsx
2
3 import (
4 "crypto/ecdsa"
5 "crypto/rand"
6 "crypto/rsa"
7 "crypto/tls"
8 "crypto/x509"
9 "crypto/x509/pkix"
10 "encoding/base64"
11 "encoding/pem"
12 "fmt"
13 "math/big"
14 "os"
15 "time"
16
17 "github.com/pkg/errors"
18 )
19
20
21 var ErrNoCertificatesConfigured = errors.New("no tls configuration was found")
22
23
24 var ErrInvalidCertificateConfiguration = errors.New("tls configuration is invalid")
25
26
27 func HTTPSCertificate() ([]tls.Certificate, error) {
28 prefix := "HTTPS_TLS"
29 return Certificate(
30 os.Getenv(prefix+"_CERT"), os.Getenv(prefix+"_KEY"),
31 os.Getenv(prefix+"_CERT_PATH"), os.Getenv(prefix+"_KEY_PATH"),
32 )
33 }
34
35
36 func HTTPSCertificateHelpMessage() string {
37 return CertificateHelpMessage("HTTPS_TLS")
38 }
39
40
41 func CertificateHelpMessage(prefix string) string {
42 return `- ` + prefix + `_CERT_PATH: The path to the TLS certificate (pem encoded).
43 Example: ` + prefix + `_CERT_PATH=~/cert.pem
44
45 - ` + prefix + `_KEY_PATH: The path to the TLS private key (pem encoded).
46 Example: ` + prefix + `_KEY_PATH=~/key.pem
47
48 - ` + prefix + `_CERT: Base64 encoded (without padding) string of the TLS certificate (PEM encoded) to be used for HTTP over TLS (HTTPS).
49 Example: ` + prefix + `_CERT="-----BEGIN CERTIFICATE-----\nMIIDZTCCAk2gAwIBAgIEV5xOtDANBgkqhkiG9w0BAQ0FADA0MTIwMAYDVQQDDClP..."
50
51 - ` + prefix + `_KEY: Base64 encoded (without padding) string of the private key (PEM encoded) to be used for HTTP over TLS (HTTPS).
52 Example: ` + prefix + `_KEY="-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDg..."
53 `
54 }
55
56
57 func Certificate(
58 certString, keyString string,
59 certPath, keyPath string,
60 ) ([]tls.Certificate, error) {
61 if certString == "" && keyString == "" && certPath == "" && keyPath == "" {
62 return nil, errors.WithStack(ErrNoCertificatesConfigured)
63 } else if certString != "" && keyString != "" {
64 tlsCertBytes, err := base64.StdEncoding.DecodeString(certString)
65 if err != nil {
66 return nil, fmt.Errorf("unable to base64 decode the TLS certificate: %v", err)
67 }
68 tlsKeyBytes, err := base64.StdEncoding.DecodeString(keyString)
69 if err != nil {
70 return nil, fmt.Errorf("unable to base64 decode the TLS private key: %v", err)
71 }
72
73 cert, err := tls.X509KeyPair(tlsCertBytes, tlsKeyBytes)
74 if err != nil {
75 return nil, fmt.Errorf("unable to load X509 key pair: %v", err)
76 }
77 return []tls.Certificate{cert}, nil
78 }
79
80 if certPath != "" && keyPath != "" {
81 cert, err := tls.LoadX509KeyPair(certPath, keyPath)
82 if err != nil {
83 return nil, fmt.Errorf("unable to load X509 key pair from files: %v", err)
84 }
85 return []tls.Certificate{cert}, nil
86 }
87
88 return nil, errors.WithStack(ErrInvalidCertificateConfiguration)
89 }
90
91
92 func PublicKey(key interface{}) interface{} {
93 switch k := key.(type) {
94 case *rsa.PrivateKey:
95 return &k.PublicKey
96 case *ecdsa.PrivateKey:
97 return &k.PublicKey
98 default:
99 return nil
100 }
101 }
102
103
104 func CreateSelfSignedTLSCertificate(key interface{}) (*tls.Certificate, error) {
105 c, err := CreateSelfSignedCertificate(key)
106 if err != nil {
107 return nil, err
108 }
109
110 block, err := PEMBlockForKey(key)
111 if err != nil {
112 return nil, err
113 }
114
115 pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: c.Raw})
116 pemKey := pem.EncodeToMemory(block)
117 cert, err := tls.X509KeyPair(pemCert, pemKey)
118 if err != nil {
119 return nil, err
120 }
121
122 return &cert, nil
123 }
124
125
126 func CreateSelfSignedCertificate(key interface{}) (cert *x509.Certificate, err error) {
127 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
128 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
129 if err != nil {
130 return cert, errors.Errorf("failed to generate serial number: %s", err)
131 }
132
133 certificate := &x509.Certificate{
134 SerialNumber: serialNumber,
135 Subject: pkix.Name{
136 Organization: []string{"ORY GmbH"},
137 CommonName: "ORY",
138 },
139 Issuer: pkix.Name{
140 Organization: []string{"ORY GmbH"},
141 CommonName: "ORY",
142 },
143 NotBefore: time.Now().UTC(),
144 NotAfter: time.Now().UTC().Add(time.Hour * 24 * 31),
145 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
146 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
147 BasicConstraintsValid: true,
148 }
149
150 certificate.IsCA = true
151 certificate.KeyUsage |= x509.KeyUsageCertSign
152 certificate.DNSNames = append(certificate.DNSNames, "localhost")
153 der, err := x509.CreateCertificate(rand.Reader, certificate, certificate, PublicKey(key), key)
154 if err != nil {
155 return cert, errors.Errorf("failed to create certificate: %s", err)
156 }
157
158 cert, err = x509.ParseCertificate(der)
159 if err != nil {
160 return cert, errors.Errorf("failed to encode private key: %s", err)
161 }
162 return cert, nil
163 }
164
165
166 func PEMBlockForKey(key interface{}) (*pem.Block, error) {
167 switch k := key.(type) {
168 case *rsa.PrivateKey:
169 return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
170 case *ecdsa.PrivateKey:
171 b, err := x509.MarshalECPrivateKey(k)
172 if err != nil {
173 return nil, errors.WithStack(err)
174 }
175 return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
176 default:
177 return nil, errors.New("Invalid key type")
178 }
179 }
180
View as plain text