1package ca
2
3import (
4	"crypto/ecdsa"
5	"crypto/elliptic"
6	"crypto/rand"
7	"crypto/x509"
8	"crypto/x509/pkix"
9	"encoding/pem"
10	"math/big"
11	"os"
12	"time"
13
14	"github.com/grafana/dskit/runutil"
15)
16
17type CA struct {
18	key    *ecdsa.PrivateKey
19	cert   *x509.Certificate
20	serial *big.Int
21}
22
23func New(name string) *CA {
24	key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
25	if err != nil {
26		panic(err)
27	}
28
29	return &CA{
30		key: key,
31		cert: &x509.Certificate{
32			SerialNumber: big.NewInt(1),
33			Subject: pkix.Name{
34				Organization: []string{name},
35			},
36			NotBefore: time.Now(),
37			NotAfter:  time.Now().Add(time.Hour * 24 * 180),
38
39			KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
40			ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
41			BasicConstraintsValid: true,
42			IsCA:                  true,
43		},
44		serial: big.NewInt(2),
45	}
46
47}
48
49func writeExclusivePEMFile(path, marker string, mode os.FileMode, data []byte) (err error) {
50	f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode)
51	if err != nil {
52		return err
53	}
54	defer runutil.CloseWithErrCapture(&err, f, "write pem file")
55
56	if err := pem.Encode(f, &pem.Block{Type: marker, Bytes: data}); err != nil {
57		return err
58	}
59	return nil
60}
61
62func (ca *CA) WriteCACertificate(path string) error {
63	derBytes, err := x509.CreateCertificate(rand.Reader, ca.cert, ca.cert, ca.key.Public(), ca.key)
64	if err != nil {
65		return err
66	}
67
68	return writeExclusivePEMFile(path, "CERTIFICATE", 0644, derBytes)
69}
70
71func (ca *CA) WriteCertificate(template *x509.Certificate, certPath string, keyPath string) error {
72	key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
73	if err != nil {
74		return err
75	}
76
77	keyBytes, err := x509.MarshalECPrivateKey(key)
78	if err != nil {
79		return err
80	}
81
82	if err := writeExclusivePEMFile(keyPath, "PRIVATE KEY", 0600, keyBytes); err != nil {
83		return err
84	}
85
86	template.IsCA = false
87	template.NotBefore = time.Now()
88	if template.NotAfter.IsZero() {
89		template.NotAfter = time.Now().Add(time.Hour * 24 * 180)
90	}
91	template.SerialNumber = ca.serial.Add(ca.serial, big.NewInt(1))
92
93	derBytes, err := x509.CreateCertificate(rand.Reader, template, ca.cert, key.Public(), ca.key)
94	if err != nil {
95		return err
96	}
97
98	return writeExclusivePEMFile(certPath, "CERTIFICATE", 0644, derBytes)
99}
100