Major refactoring.
+ Many lib functions have been split out into separate packages. + Adding cert/key generation tooling. + Add new time.Duration parser.
This commit is contained in:
211
certlib/certgen/config.go
Normal file
211
certlib/certgen/config.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package certgen
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.wntrmute.dev/kyle/goutils/lib"
|
||||
)
|
||||
|
||||
type KeySpec struct {
|
||||
Algorithm string `yaml:"algorithm"`
|
||||
Size int `yaml:"size"`
|
||||
}
|
||||
|
||||
func (ks KeySpec) Generate() (crypto.PublicKey, crypto.PrivateKey, error) {
|
||||
switch strings.ToLower(ks.Algorithm) {
|
||||
case "rsa":
|
||||
return GenerateKey(x509.RSA, ks.Size)
|
||||
case "ecdsa":
|
||||
return GenerateKey(x509.ECDSA, ks.Size)
|
||||
case "ed25519":
|
||||
return GenerateKey(x509.Ed25519, 0)
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unknown key algorithm: %s", ks.Algorithm)
|
||||
}
|
||||
}
|
||||
|
||||
func (ks KeySpec) SigningAlgorithm() (x509.SignatureAlgorithm, error) {
|
||||
switch strings.ToLower(ks.Algorithm) {
|
||||
case "rsa":
|
||||
return x509.SHA512WithRSAPSS, nil
|
||||
case "ecdsa":
|
||||
return x509.ECDSAWithSHA512, nil
|
||||
case "ed25519":
|
||||
return x509.PureEd25519, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown key algorithm: %s", ks.Algorithm)
|
||||
}
|
||||
}
|
||||
|
||||
type Subject struct {
|
||||
CommonName string `yaml:"common_name"`
|
||||
Country string `yaml:"country"`
|
||||
Locality string `yaml:"locality"`
|
||||
Province string `yaml:"province"`
|
||||
Organization string `yaml:"organization"`
|
||||
OrganizationalUnit string `yaml:"organizational_unit"`
|
||||
Email string `yaml:"email"`
|
||||
DNSNames []string `yaml:"dns"`
|
||||
IPAddresses []string `yaml:"ips"`
|
||||
}
|
||||
|
||||
type CertificateRequest struct {
|
||||
KeySpec KeySpec `yaml:"key"`
|
||||
Subject Subject `yaml:"subject"`
|
||||
Profile Profile `yaml:"profile"`
|
||||
}
|
||||
|
||||
func (cs CertificateRequest) Generate() (crypto.PrivateKey, *x509.CertificateRequest, error) {
|
||||
pub, priv, err := cs.KeySpec.Generate()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
subject := pkix.Name{}
|
||||
subject.CommonName = cs.Subject.CommonName
|
||||
subject.Country = []string{cs.Subject.Country}
|
||||
subject.Locality = []string{cs.Subject.Locality}
|
||||
subject.Province = []string{cs.Subject.Province}
|
||||
subject.Organization = []string{cs.Subject.Organization}
|
||||
subject.OrganizationalUnit = []string{cs.Subject.OrganizationalUnit}
|
||||
|
||||
ipAddresses := make([]net.IP, 0, len(cs.Subject.IPAddresses))
|
||||
for i, ip := range cs.Subject.IPAddresses {
|
||||
ipAddresses = append(ipAddresses, net.ParseIP(ip))
|
||||
if ipAddresses[i] == nil {
|
||||
return nil, nil, fmt.Errorf("invalid IP address: %s", ip)
|
||||
}
|
||||
}
|
||||
|
||||
req := &x509.CertificateRequest{
|
||||
PublicKeyAlgorithm: 0,
|
||||
PublicKey: pub,
|
||||
Subject: subject,
|
||||
DNSNames: cs.Subject.DNSNames,
|
||||
IPAddresses: ipAddresses,
|
||||
}
|
||||
|
||||
reqBytes, err := x509.CreateCertificateRequest(rand.Reader, req, priv)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create certificate request: %w", err)
|
||||
}
|
||||
|
||||
req, err = x509.ParseCertificateRequest(reqBytes)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse certificate request: %w", err)
|
||||
}
|
||||
|
||||
return priv, req, nil
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
IsCA bool `yaml:"is_ca"`
|
||||
PathLen int `yaml:"path_len"`
|
||||
KeyUse string `yaml:"key_uses"`
|
||||
ExtKeyUsages []string `yaml:"ext_key_usages"`
|
||||
Expiry string `yaml:"expiry"`
|
||||
}
|
||||
|
||||
func (p Profile) templateFromRequest(req *x509.CertificateRequest) (*x509.Certificate, error) {
|
||||
serial, err := SerialNumber()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate serial number: %w", err)
|
||||
}
|
||||
|
||||
expiry, err := lib.ParseDuration(p.Expiry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing expiry: %w", err)
|
||||
}
|
||||
|
||||
certTemplate := &x509.Certificate{
|
||||
SignatureAlgorithm: req.SignatureAlgorithm,
|
||||
PublicKeyAlgorithm: req.PublicKeyAlgorithm,
|
||||
PublicKey: req.PublicKey,
|
||||
SerialNumber: serial,
|
||||
Subject: req.Subject,
|
||||
NotBefore: time.Now().Add(-1 * time.Hour),
|
||||
NotAfter: time.Now().Add(expiry),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: p.IsCA,
|
||||
MaxPathLen: p.PathLen,
|
||||
DNSNames: req.DNSNames,
|
||||
IPAddresses: req.IPAddresses,
|
||||
}
|
||||
|
||||
var ok bool
|
||||
certTemplate.KeyUsage, ok = keyUsageStrings[p.KeyUse]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid key usage: %s", p.KeyUse)
|
||||
}
|
||||
|
||||
var eku x509.ExtKeyUsage
|
||||
for _, extKeyUsage := range p.ExtKeyUsages {
|
||||
eku, ok = extKeyUsageStrings[extKeyUsage]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid extended key usage: %s", extKeyUsage)
|
||||
}
|
||||
certTemplate.ExtKeyUsage = append(certTemplate.ExtKeyUsage, eku)
|
||||
}
|
||||
|
||||
return certTemplate, nil
|
||||
}
|
||||
|
||||
func (p Profile) SignRequest(parent *x509.Certificate, req *x509.CertificateRequest, priv crypto.PrivateKey) (*x509.Certificate, error) {
|
||||
tpl, err := p.templateFromRequest(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create certificate template: %w", err)
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, tpl, parent, req.PublicKey, priv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create certificate: %w", err)
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse certificate: %w", err)
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func (p Profile) SelfSign(req *x509.CertificateRequest, priv crypto.PrivateKey) (*x509.Certificate, error) {
|
||||
certTemplate, err := p.templateFromRequest(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create certificate template: %w", err)
|
||||
}
|
||||
|
||||
return p.SignRequest(certTemplate, req, priv)
|
||||
}
|
||||
|
||||
func SerialNumber() (*big.Int, error) {
|
||||
serialNumberBytes := make([]byte, 20)
|
||||
_, err := rand.Read(serialNumberBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate serial number: %w", err)
|
||||
}
|
||||
return new(big.Int).SetBytes(serialNumberBytes), nil
|
||||
}
|
||||
|
||||
// GenerateSelfSigned generates a self-signed certificate using the given certificate request.
|
||||
func GenerateSelfSigned(creq *CertificateRequest) (*x509.Certificate, crypto.PrivateKey, error) {
|
||||
priv, req, err := creq.Generate()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to generate certificate request: %w", err)
|
||||
}
|
||||
|
||||
cert, err := creq.Profile.SelfSign(req, priv)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to self-sign certificate: %w", err)
|
||||
}
|
||||
|
||||
return cert, priv, nil
|
||||
}
|
||||
Reference in New Issue
Block a user