cacert-gosigner/pkg/config/config.go
Jan Dittberner de997913cf Implement configuration and CA hierarchy setup
This commit implements a mechanism to load CA configuration dynamically from
JSON files. Missing keys and certificates can be generated in a PKCS#11 HSM
or Smartcard. Certificates are stored as PEM encoded .crt files in the
filesystem.

The default PKCS#11 module (softhsm2) is now loaded from a platform specific
path using go:build comments.
2022-04-16 22:24:32 +02:00

240 lines
6 KiB
Go

package config
import (
"crypto"
"crypto/elliptic"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"errors"
"fmt"
"io"
"time"
)
type Settings struct {
Organization pkix.Name `json:"organization"`
RootYears int `json:"root-years"`
IntermediaryYears int `json:"intermediary-years"`
}
func (s *Settings) CalculateValidity(cert *CaCertificateEntry) (time.Time, time.Time) {
var notBefore, notAfter time.Time
notBefore = time.Now()
if cert.Parent == nil {
notAfter = notBefore.AddDate(s.RootYears, 0, 0)
} else {
notAfter = notBefore.AddDate(s.IntermediaryYears, 0, 0)
}
return notBefore, notAfter
}
func (s *Settings) CalculateSubject(cert *CaCertificateEntry) pkix.Name {
subject := s.Organization
subject.CommonName = cert.CommonName
return subject
}
func (s *Settings) BuildIssuerURL(parentCA *CaCertificateEntry) string {
return fmt.Sprintf("http://www.example.org/%s.crt", parentCA.Label)
}
func (s *Settings) BuildOCSPURL(_ *CaCertificateEntry) string {
return "http://ocsp.example.org/"
}
func (s *Settings) BuildCRLUrl(parentCA *CaCertificateEntry) string {
return fmt.Sprintf("http://crl.cacert.org/%s.crl", parentCA.Label)
}
type SignerConfig struct {
Global *Settings `json:"Settings"`
CAs []*CaCertificateEntry `json:"CAs"`
}
// LoadConfiguration reads JSON configuration from the given reader as a SignerConfig structure
func LoadConfiguration(r io.Reader) (*SignerConfig, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("could not load configuration: %w", err)
}
config := &SignerConfig{}
err = json.Unmarshal(data, config)
if err != nil {
return nil, fmt.Errorf("could not parse JSON configuration: %w", err)
}
return config, nil
}
type PrivateKeyInfo struct {
Algorithm x509.PublicKeyAlgorithm
EccCurve elliptic.Curve
RSABits int
}
func (p *PrivateKeyInfo) UnmarshalJSON(data []byte) error {
internalStructure := struct {
Label string `json:"label"`
Algorithm string `json:"algorithm"`
EccCurve string `json:"ecc-curve,omitempty"`
RSABits *int `json:"rsa-bits,omitempty"`
}{}
err := json.Unmarshal(data, &internalStructure)
if err != nil {
return fmt.Errorf("could not unmarshal private key info: %w", err)
}
switch internalStructure.Algorithm {
case "RSA":
p.Algorithm = x509.RSA
if internalStructure.RSABits == nil {
return errors.New("RSA key length not specified")
}
p.RSABits = *internalStructure.RSABits
case "EC":
p.Algorithm = x509.ECDSA
p.EccCurve, err = nameToCurve(internalStructure.EccCurve)
if err != nil {
return err
}
default:
return fmt.Errorf("unsupported key algorithm %s", internalStructure.Algorithm)
}
return nil
}
func (p *PrivateKeyInfo) MarshalJSON() ([]byte, error) {
internalStructure := struct {
Algorithm string `json:"algorithm"`
EccCurve string `json:"ecc-curve,omitempty"`
RSABits *int `json:"rsa-bits,omitempty"`
}{}
switch p.Algorithm {
case x509.RSA:
internalStructure.Algorithm = "RSA"
internalStructure.RSABits = &p.RSABits
case x509.ECDSA:
internalStructure.Algorithm = "EC"
curveName, err := curveToName(p.EccCurve)
if err != nil {
return nil, err
}
internalStructure.EccCurve = curveName
}
data, err := json.Marshal(internalStructure)
if err != nil {
return nil, fmt.Errorf("could not marshal private key info: %w", err)
}
return data, nil
}
func curveToName(curve elliptic.Curve) (string, error) {
switch curve {
case elliptic.P224():
return "P-224", nil
case elliptic.P256():
return "P-256", nil
case elliptic.P384():
return "P-384", nil
case elliptic.P521():
return "P-521", nil
default:
return "", fmt.Errorf("unsupported EC curve %s", curve)
}
}
// nameToCurve maps a curve name to an elliptic curve
func nameToCurve(name string) (elliptic.Curve, error) {
switch name {
case "P-224", "secp224r1":
return elliptic.P224(), nil
case "P-256", "secp256r1":
return elliptic.P256(), nil
case "P-384", "secp384r1":
return elliptic.P384(), nil
case "P-521", "secp521r1":
return elliptic.P521(), nil
default:
return nil, fmt.Errorf("unsupported EC curve %s", name)
}
}
type CaCertificateEntry struct {
Label string
KeyInfo *PrivateKeyInfo
CommonName string
SubCAs []*CaCertificateEntry
MaxPathLen int `json:"max-path-len"` // maximum path length should be 0 for CAs that issue end entity certificates
ExtKeyUsage []x509.ExtKeyUsage `json:"ext-key-usage"`
Certificate *x509.Certificate
KeyPair crypto.Signer
Parent *CaCertificateEntry
}
func (c *CaCertificateEntry) CertificateFileName() string {
return c.Label + ".crt"
}
func (c *CaCertificateEntry) UnmarshalJSON(data []byte) error {
var m struct {
Label string
KeyInfo *PrivateKeyInfo `json:"key-info"`
CommonName string `json:"common-name"`
SubCAs []*CaCertificateEntry `json:"sub-cas,omitempty"`
MaxPathLen int `json:"max-path-len,omitempty"` // maximum path length should be 0 for CAs that issue end entity certificates
ExtKeyUsage []string `json:"ext-key-usage,omitempty"`
}
err := json.Unmarshal(data, &m)
if err != nil {
return err
}
c.Label = m.Label
c.KeyInfo = m.KeyInfo
c.CommonName = m.CommonName
c.SubCAs = m.SubCAs
c.MaxPathLen = m.MaxPathLen
if m.ExtKeyUsage != nil {
c.ExtKeyUsage, err = mapExtKeyUsageNames(m.ExtKeyUsage)
}
if err != nil {
return err
}
return nil
}
func mapExtKeyUsageNames(usages []string) ([]x509.ExtKeyUsage, error) {
extKeyUsages := make([]x509.ExtKeyUsage, len(usages))
for idx, usage := range usages {
switch usage {
case "client":
extKeyUsages[idx] = x509.ExtKeyUsageClientAuth
case "code":
extKeyUsages[idx] = x509.ExtKeyUsageCodeSigning
case "email":
extKeyUsages[idx] = x509.ExtKeyUsageEmailProtection
case "server":
extKeyUsages[idx] = x509.ExtKeyUsageServerAuth
case "ocsp":
extKeyUsages[idx] = x509.ExtKeyUsageOCSPSigning
default:
return nil, fmt.Errorf("unsupported extended key usage %s", usage)
}
}
return extKeyUsages, nil
}