From df92c82b3f2c51bd2bd73283e0a389acc0a98c7f Mon Sep 17 00:00:00 2001 From: Urko Date: Wed, 15 Feb 2023 11:10:23 +0100 Subject: [PATCH] feat: cert generation implementation --- internal/cert/cert.go | 191 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 internal/cert/cert.go diff --git a/internal/cert/cert.go b/internal/cert/cert.go new file mode 100644 index 0000000..5bf79ea --- /dev/null +++ b/internal/cert/cert.go @@ -0,0 +1,191 @@ +package cert + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "log" + "time" + + "gitlab.com/urkob/go-cert-gen/pkg/ca" + + "gitlab.com/urkob/go-cert-gen/pkg/client" +) + +// Creates a new 512bit private key. +func newPrivateKey() (*ecdsa.PrivateKey, error) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, fmt.Errorf("ecdsa: %s", err) + } + + return priv, nil +} + +// Parse a text PEM file into a x509 cert. +func parseCertificate(data []byte) (*x509.Certificate, error) { + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("parseCertificate (pem.Decode)") + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parseCertificate: %w", err) + } + return cert, nil +} + +// Encodes a private key into PEM. +func encodePrivateKey(priv *ecdsa.PrivateKey) ([]byte, error) { + out := &bytes.Buffer{} + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return nil, fmt.Errorf("marshal: %s", err) + } + pem.Encode(out, &pem.Block{ + Type: "PRIVATE KEY", + Bytes: privBytes, + }) + return out.Bytes(), nil +} + +// Create a self-signed certificate. +func newRootCA(config ca.CaConfig) ([]byte, []byte, error) { + priv, err := newPrivateKey() + if err != nil { + return nil, nil, fmt.Errorf("newPrivateKey: %s", err) + } + + template := x509.Certificate{ + SerialNumber: config.SerialNumber, + Subject: pkix.Name{ + Organization: []string{config.Subject.Organization}, + CommonName: config.Subject.CommonName, + }, + NotBefore: time.Now().Add(-time.Minute), + NotAfter: time.Now().Add(config.Duration), + IsCA: true, + KeyUsage: config.KeyUsage, + ExtKeyUsage: config.ExtKeyUsage, + BasicConstraintsValid: true, + } + der, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return nil, nil, fmt.Errorf("x509.CreateCertificate: %s", err) + } + + out := &bytes.Buffer{} + pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: der}) + caPEM := out.Bytes() + keyPEM, err := encodePrivateKey(priv) + if err != nil { + return nil, nil, fmt.Errorf("encodePrivateKey: %s", err) + } + + return caPEM, keyPEM, nil +} + +func newClientCert(config *client.ClientCertConfig, rootCA *x509.Certificate, rootKeyPEM []byte) ([]byte, []byte, error) { + template := &x509.Certificate{ + SerialNumber: config.Serial, + Subject: pkix.Name{ + Organization: []string{config.Subject.Organization}, + Country: []string{config.Subject.Country}, + Province: []string{config.Subject.Province}, + Locality: []string{config.Subject.Locality}, + StreetAddress: []string{config.Subject.StreetAddress}, + PostalCode: []string{config.Subject.PostalCode}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(config.Duration), + SubjectKeyId: config.SubjectKeyId, + ExtKeyUsage: config.ExtKeyUsage, + KeyUsage: config.KeyUsage, + } + + block, _ := pem.Decode(rootKeyPEM) + caPrivKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + log.Fatalf("x509.ParsePKCS8PrivateKey: %s", err) + } + + priv, err := newPrivateKey() + if err != nil { + return nil, nil, fmt.Errorf("newPrivateKey: %s", err) + } + + der, err := x509.CreateCertificate(rand.Reader, template, rootCA, &priv.PublicKey, caPrivKey) + if err != nil { + return nil, nil, fmt.Errorf("x509.CreateCertificate: %s", err) + } + + out := &bytes.Buffer{} + pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: der}) + certPEM := out.Bytes() + keyPEM, err := encodePrivateKey(priv) + if err != nil { + return nil, nil, fmt.Errorf("encodePrivateKey: %s", err) + } + + return certPEM, keyPEM, nil +} + +type rootCA struct { + caPEM []byte + keyPEM []byte +} + +type clientCert struct { + certPEM []byte + keyPEM []byte +} + +func (c *clientCert) Key() []byte { + return c.keyPEM +} + +func (c *clientCert) PEM() []byte { + return c.certPEM +} + +func (r *rootCA) WithClientCert(config *client.ClientCertConfig) (client.ClientCertIface, error) { + x509RootCA, err := parseCertificate(r.caPEM) + if err != nil { + return nil, fmt.Errorf("parseCertificate: %s", err) + } + + clientCertPEM, clientKeyPEM, err := newClientCert(config, x509RootCA, r.keyPEM) + if err != nil { + return nil, fmt.Errorf("newClientCert: %s", err) + } + + return &clientCert{ + certPEM: clientCertPEM, + keyPEM: clientKeyPEM, + }, nil +} + +func (r *rootCA) Key() []byte { + return r.keyPEM +} + +func (r *rootCA) PEM() []byte { + return r.caPEM +} + +func NewRootCA(config ca.CaConfig) (ca.RootCACertificateIface, error) { + caPEM, keyPEM, err := newRootCA(config) + if err != nil { + return nil, fmt.Errorf("newRootCA: %s", err) + } + + return &rootCA{ + caPEM: caPEM, + keyPEM: keyPEM, + }, nil +}