/* Copyright 2022 CAcert Inc. SPDX-License-Identifier: Apache-2.0 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package config import ( "crypto" "crypto/elliptic" "crypto/x509" "crypto/x509/pkix" "errors" "fmt" "io" "strings" "time" "gopkg.in/yaml.v3" ) type Serial struct { Device string Baud int Timeout time.Duration } type Settings struct { Organization *pkix.Name ValidityYears struct { Root, Intermediary int } URLPatterns struct { Ocsp, CRL, Issuer string } Serial *Serial } type SettingsError struct { msg string } func (se SettingsError) Error() string { return fmt.Sprintf("invalid Settings %s", se.msg) } func (s *Settings) UnmarshalYAML(n *yaml.Node) error { data := 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"` Serial struct { Device string `yaml:"device"` Baud int `yaml:"baud"` TimeoutMillis int `yaml:"timeout-millis"` } `yaml:"serial"` }{} err := n.Decode(&data) if err != nil { return fmt.Errorf("could not decode YAML: %w", err) } if data.Organization.Organization == nil { return SettingsError{"you need to specify 'organization'"} } if data.ValidityYears.Root == 0 || data.ValidityYears.Intermediary == 0 { return SettingsError{"you must specify validity years for 'root' and 'intermediary'"} } if data.ValidityYears.Root < data.ValidityYears.Intermediary { return SettingsError{"validity of root CA certificates must be equal or greater than those of" + " intermediary CA certificates"} } if data.URLPatterns.Ocsp == "" { return SettingsError{"you must specify an 'ocsp' URL pattern"} } if strings.Count(data.URLPatterns.Ocsp, "%s") > 1 { return SettingsError{"url-pattern 'ocsp' must contain zero or one '%s' placeholder"} } if data.URLPatterns.CRL == "" { return SettingsError{"you must specify an 'crl' URL pattern"} } if strings.Count(data.URLPatterns.CRL, "%s") != 1 { return SettingsError{"url-pattern 'crl' must contain one '%s' placeholder"} } if data.URLPatterns.Issuer == "" { return SettingsError{"you must specify an 'issuer' URL pattern"} } if strings.Count(data.URLPatterns.Issuer, "%s") != 1 { return SettingsError{"url-pattern 'issuer' must contain one '%s' placeholder"} } if data.Serial.Device == "" { return SettingsError{"you must specify a serial 'device'"} } if data.Serial.Baud == 0 { data.Serial.Baud = 115200 } if data.Serial.TimeoutMillis == 0 { data.Serial.TimeoutMillis = 5000 } s.Organization = &pkix.Name{} s.Organization.Organization = data.Organization.Organization s.Organization.Country = data.Organization.Country s.Organization.Locality = data.Organization.Locality s.Organization.StreetAddress = data.Organization.StreetAddress s.Organization.PostalCode = data.Organization.PostalCode s.ValidityYears.Root = data.ValidityYears.Root s.ValidityYears.Intermediary = data.ValidityYears.Intermediary s.URLPatterns.Ocsp = data.URLPatterns.Ocsp s.URLPatterns.CRL = data.URLPatterns.CRL s.URLPatterns.Issuer = data.URLPatterns.Issuer s.Serial = &Serial{ Device: data.Serial.Device, Baud: data.Serial.Baud, Timeout: time.Duration(data.Serial.TimeoutMillis) * time.Millisecond, } return nil } 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) } if ks.Label == "" { return errors.New("element 'label' must be specified") } 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, relativeTo time.Time) (time.Time, time.Time) { var notBefore, notAfter time.Time notBefore = relativeTo 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) } if entry.Parent == "" { return nil, fmt.Errorf("parent for %s is empty", label) } parent, ok := c.caMap[entry.Parent] if !ok { return nil, fmt.Errorf("parent %s for %s not found in signer config", entry.Parent, label) } return 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 } func (c *SignerConfig) GetKeyStorage(label string) (*KeyStorage, error) { keyStorage, ok := c.keyStorage[label] if !ok { return nil, fmt.Errorf("could not find storage definition with label %s", label) } return keyStorage, nil } func (c *SignerConfig) GetSerial() *Serial { return c.global.Serial } // 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) } if config.Global == nil { return nil, errors.New("configuration entry 'Settings' is missing or empty") } if config.CAs == nil { return nil, errors.New("configuration entry 'CAs' is missing or empty") } if config.KeyStorage == nil { return nil, errors.New("configuration entry 'KeyStorage' is missing or empty") } 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 { 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("element 'rsa-bits' with RSA key length required for algorithm RSA") } p.RSABits = *internalStructure.RSABits case "EC": p.Algorithm = x509.ECDSA if internalStructure.EccCurve == "" { return errors.New("element 'ecc-curve' required for algorithm EC") } p.EccCurve, err = nameToCurve(internalStructure.EccCurve) if err != nil { return err } case "": return errors.New("element 'algorithm' must be specified as 'EC' or 'RSA'") default: return fmt.Errorf( "unsupported key algorithm %s, use either 'EC' or 'RSA'", 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"` // maximum path length should be 0 for CAs that issue end entity certificates MaxPathLen int `yaml:"max-path-len,omitempty"` ExtKeyUsage []string `yaml:"ext-key-usages,omitempty"` Parent string `yaml:"parent"` Storage string `yaml:"storage"` } err := value.Decode(&m) if err != nil { return fmt.Errorf("could not unmarshal CA certificate entry: %w", err) } if m.KeyInfo == nil { return errors.New("element 'key-info' must be set") } c.KeyInfo = m.KeyInfo if m.CommonName == "" { return errors.New("element 'common-name' must be set") } c.CommonName = m.CommonName c.MaxPathLen = m.MaxPathLen if m.Parent == "" && m.ExtKeyUsage != nil { return errors.New("a root CA must not specify 'ext-key-usages'") } c.Parent = m.Parent if m.ExtKeyUsage != nil { c.ExtKeyUsage, err = mapExtKeyUsageNames(m.ExtKeyUsage) if err != nil { return err } } if m.Storage != "" { c.Storage = m.Storage } else { c.Storage = "default" } 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 }