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 }