192 lines
4.6 KiB
Go
192 lines
4.6 KiB
Go
|
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
|
||
|
}
|