cacert-gosigner/pkg/config/config.go
Jan Dittberner 47d5b2afff Improve configuration, implement setup mode
- implement a dedicated setup mode for creating CA certificates that is
  triggered by the '-setup' command line flag
- switch to YAML configuration for comment support and more human
  readable syntax. Format documentation is in docs/config.sample.yaml
- move HSM related code to pkg/hsm
- improve consistency checks in pkg/config
2022-04-19 16:48:32 +02:00

353 lines
8.7 KiB
Go

package config
import (
"crypto"
"crypto/elliptic"
"crypto/x509"
"crypto/x509/pkix"
"errors"
"fmt"
"io"
"strings"
"time"
"gopkg.in/yaml.v3"
)
type Settings struct {
Organization struct {
Country []string `yaml:"country"`
Organization []string `yaml:"organization"`
Locality []string `yaml:"locality"`
StreetAddress []string `yaml:"street-address"`
PostalCode []string `yaml:"postal-code"`
} `yaml:"organization"`
ValidityYears struct {
Root int `yaml:"root"`
Intermediary int `yaml:"intermediary"`
} `yaml:"validity-years"`
URLPatterns struct {
Ocsp string `yaml:"ocsp"`
CRL string `yaml:"crl"`
Issuer string `yaml:"issuer"`
} `yaml:"url-patterns"`
}
type KeyStorage struct {
Label string
Module string
}
func (k *KeyStorage) UnmarshalYAML(n *yaml.Node) error {
ks := struct {
TokenType string `yaml:"type"`
Label string `yaml:"label"`
Module string `yaml:"module"`
}{}
err := n.Decode(&ks)
if err != nil {
return fmt.Errorf("could not decode YAML: %w", err)
}
switch ks.TokenType {
case "softhsm":
k.Module = softHsmModule
case "p11module":
if ks.Module == "" {
return errors.New("specify a 'module' field when using the 'p11module' type")
}
k.Module = ks.Module
default:
return fmt.Errorf("unsupported KeyStorage type '%s'", ks.TokenType)
}
k.Label = ks.Label
return nil
}
type SignerConfig struct {
global *Settings `yaml:"Settings"`
caMap map[string]*CaCertificateEntry `yaml:"CAs"`
KeyStorage map[string]*KeyStorage `yaml:"KeyStorage"`
}
func (c *SignerConfig) GetCADefinition(label string) (*CaCertificateEntry, error) {
entry, ok := c.caMap[label]
if !ok {
return nil, fmt.Errorf("no CA definition found for label %s", label)
}
return entry, nil
}
func (c *SignerConfig) CalculateValidity(cert *CaCertificateEntry) (time.Time, time.Time) {
var notBefore, notAfter time.Time
notBefore = time.Now()
if cert.IsRoot() {
notAfter = notBefore.AddDate(c.global.ValidityYears.Root, 0, 0)
} else {
notAfter = notBefore.AddDate(c.global.ValidityYears.Intermediary, 0, 0)
}
return notBefore, notAfter
}
func (c *SignerConfig) CalculateSubject(cert *CaCertificateEntry) pkix.Name {
subject := pkix.Name{
Country: c.global.Organization.Country,
Organization: c.global.Organization.Organization,
Locality: c.global.Organization.Locality,
StreetAddress: c.global.Organization.StreetAddress,
PostalCode: c.global.Organization.PostalCode,
}
subject.CommonName = cert.CommonName
return subject
}
func (c *SignerConfig) CertificateFileName(label string) string {
return fmt.Sprintf("%s.crt", label)
}
func (c *SignerConfig) BuildIssuerURL(cert *CaCertificateEntry) string {
return fmt.Sprintf(c.global.URLPatterns.Issuer, cert.parent)
}
func (c *SignerConfig) BuildOCSPURL(cert *CaCertificateEntry) string {
// in case the configuration specifies a placeholder
if strings.Count(c.global.URLPatterns.Ocsp, "%s") == 1 {
return fmt.Sprintf(c.global.URLPatterns.Ocsp, cert.parent)
}
return c.global.URLPatterns.Ocsp
}
func (c *SignerConfig) BuildCRLUrl(cert *CaCertificateEntry) string {
return fmt.Sprintf(c.global.URLPatterns.CRL, cert.parent)
}
func (c *SignerConfig) GetParentCA(label string) (*CaCertificateEntry, error) {
entry, ok := c.caMap[label]
if !ok {
return nil, fmt.Errorf("no CA definition for %s", label)
}
if entry.IsRoot() {
return nil, fmt.Errorf("CA %s is a root CA and has no parent", label)
}
return c.caMap[entry.parent], nil
}
// RootCAs returns the labels of all configured root CAs
func (c *SignerConfig) RootCAs() []string {
roots := make([]string, 0)
for label, entry := range c.caMap {
if entry.IsRoot() {
roots = append(roots, label)
}
}
return roots
}
// IntermediaryCAs returns the labels of all configured intermediary CAs
func (c *SignerConfig) IntermediaryCAs() []string {
intermediaries := make([]string, 0)
for label, entry := range c.caMap {
if !entry.IsRoot() {
intermediaries = append(intermediaries, label)
}
}
return intermediaries
}
// LoadConfiguration reads YAML configuration from the given reader as a SignerConfig structure
func LoadConfiguration(r io.Reader) (*SignerConfig, error) {
config := struct {
Global *Settings `yaml:"Settings"`
CAs map[string]*CaCertificateEntry `yaml:"CAs"`
KeyStorage map[string]*KeyStorage `yaml:"KeyStorage"`
}{}
decoder := yaml.NewDecoder(r)
err := decoder.Decode(&config)
if err != nil {
return nil, fmt.Errorf("could not parse YAML configuration: %w", err)
}
return &SignerConfig{
global: config.Global,
caMap: config.CAs,
KeyStorage: config.KeyStorage,
}, nil
}
type PrivateKeyInfo struct {
Algorithm x509.PublicKeyAlgorithm
EccCurve elliptic.Curve
RSABits int
}
func (p *PrivateKeyInfo) UnmarshalYAML(value *yaml.Node) error {
internalStructure := struct {
Label string `yaml:"label"`
Algorithm string `yaml:"algorithm"`
EccCurve string `yaml:"ecc-curve,omitempty"`
RSABits *int `yaml:"rsa-bits,omitempty"`
}{}
err := value.Decode(&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) MarshalYAML() (interface{}, error) {
internalStructure := struct {
Algorithm string `yaml:"algorithm"`
EccCurve string `yaml:"ecc-curve,omitempty"`
RSABits *int `yaml:"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
}
return internalStructure, 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 {
KeyInfo *PrivateKeyInfo
CommonName string
MaxPathLen int // maximum path length should be 0 for CAs that issue end entity certificates
ExtKeyUsage []x509.ExtKeyUsage
Certificate *x509.Certificate
KeyPair crypto.Signer
parent string
Storage string
}
func (c *CaCertificateEntry) UnmarshalYAML(value *yaml.Node) error {
var m struct {
KeyInfo *PrivateKeyInfo `yaml:"key-info"`
CommonName string `yaml:"common-name"`
MaxPathLen int `yaml:"max-path-len,omitempty"` // maximum path length should be 0 for CAs that issue end entity certificates
ExtKeyUsage []string `yaml:"ext-key-usages,omitempty"`
Parent string `yaml:"parent"`
Storage string `yaml:"storage"`
}
err := value.Decode(&m)
if err != nil {
return err
}
c.KeyInfo = m.KeyInfo
c.CommonName = m.CommonName
c.MaxPathLen = m.MaxPathLen
if m.ExtKeyUsage != nil {
c.ExtKeyUsage, err = mapExtKeyUsageNames(m.ExtKeyUsage)
}
c.parent = m.Parent
if m.Storage != "" {
c.Storage = m.Storage
} else {
c.Storage = "default"
}
if err != nil {
return err
}
return nil
}
func (c *CaCertificateEntry) IsRoot() bool {
return c.parent == ""
}
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
}