2022-04-24 07:25:04 +00:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto"
|
|
|
|
"crypto/elliptic"
|
|
|
|
"crypto/x509"
|
|
|
|
"crypto/x509/pkix"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2022-11-20 17:59:37 +00:00
|
|
|
"path"
|
2022-04-19 14:48:32 +00:00
|
|
|
"strings"
|
2022-04-16 20:24:32 +00:00
|
|
|
"time"
|
2022-04-19 14:48:32 +00:00
|
|
|
|
|
|
|
"gopkg.in/yaml.v3"
|
2022-11-20 17:59:37 +00:00
|
|
|
|
2022-11-28 16:39:48 +00:00
|
|
|
"git.cacert.org/cacert-gosigner/internal/x509/openssl"
|
|
|
|
"git.cacert.org/cacert-gosigner/internal/x509/revoking"
|
|
|
|
"git.cacert.org/cacert-gosigner/internal/x509/signing"
|
2022-04-16 20:24:32 +00:00
|
|
|
)
|
|
|
|
|
2022-11-20 17:59:37 +00:00
|
|
|
const minRSABits = 2048
|
|
|
|
|
2022-08-03 12:38:36 +00:00
|
|
|
type Serial struct {
|
|
|
|
Device string
|
|
|
|
Baud int
|
|
|
|
Timeout time.Duration
|
|
|
|
}
|
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
type Settings struct {
|
2022-04-23 16:34:51 +00:00
|
|
|
Organization *pkix.Name
|
2022-04-19 14:48:32 +00:00
|
|
|
ValidityYears struct {
|
2022-08-03 14:01:06 +00:00
|
|
|
Root, Subordinate int
|
2022-04-23 16:34:51 +00:00
|
|
|
}
|
2022-04-19 14:48:32 +00:00
|
|
|
URLPatterns struct {
|
2022-04-23 16:34:51 +00:00
|
|
|
Ocsp, CRL, Issuer string
|
|
|
|
}
|
2022-08-03 12:38:36 +00:00
|
|
|
Serial *Serial
|
2022-04-23 16:34:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2022-08-03 14:01:06 +00:00
|
|
|
Root int `yaml:"root"`
|
|
|
|
Subordinate int `yaml:"subordinate"`
|
2022-04-23 16:34:51 +00:00
|
|
|
} `yaml:"validity-years"`
|
|
|
|
URLPatterns struct {
|
|
|
|
Ocsp string `yaml:"ocsp"`
|
|
|
|
CRL string `yaml:"crl"`
|
|
|
|
Issuer string `yaml:"issuer"`
|
|
|
|
} `yaml:"url-patterns"`
|
2022-08-03 12:38:36 +00:00
|
|
|
Serial struct {
|
|
|
|
Device string `yaml:"device"`
|
|
|
|
Baud int `yaml:"baud"`
|
|
|
|
TimeoutMillis int `yaml:"timeout-millis"`
|
|
|
|
} `yaml:"serial"`
|
2022-04-23 16:34:51 +00:00
|
|
|
}{}
|
|
|
|
|
|
|
|
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'"}
|
|
|
|
}
|
|
|
|
|
2022-08-03 14:01:06 +00:00
|
|
|
if data.ValidityYears.Root == 0 || data.ValidityYears.Subordinate == 0 {
|
|
|
|
return SettingsError{"you must specify validity years for 'root' and 'subordinate'"}
|
2022-04-23 16:34:51 +00:00
|
|
|
}
|
|
|
|
|
2022-08-03 14:01:06 +00:00
|
|
|
if data.ValidityYears.Root < data.ValidityYears.Subordinate {
|
2022-04-24 09:24:15 +00:00
|
|
|
return SettingsError{"validity of root CA certificates must be equal or greater than those of" +
|
2022-08-03 14:01:06 +00:00
|
|
|
" subordinate CA certificates"}
|
2022-04-23 16:34:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if data.URLPatterns.Ocsp == "" {
|
|
|
|
return SettingsError{"you must specify an 'ocsp' URL pattern"}
|
|
|
|
}
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-23 16:34:51 +00:00
|
|
|
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"}
|
|
|
|
}
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-23 16:34:51 +00:00
|
|
|
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"}
|
|
|
|
}
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-23 16:34:51 +00:00
|
|
|
if strings.Count(data.URLPatterns.Issuer, "%s") != 1 {
|
|
|
|
return SettingsError{"url-pattern 'issuer' must contain one '%s' placeholder"}
|
|
|
|
}
|
|
|
|
|
2022-08-03 12:38:36 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-04-23 16:34:51 +00:00
|
|
|
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
|
2022-08-03 14:01:06 +00:00
|
|
|
s.ValidityYears.Subordinate = data.ValidityYears.Subordinate
|
2022-04-23 16:34:51 +00:00
|
|
|
|
|
|
|
s.URLPatterns.Ocsp = data.URLPatterns.Ocsp
|
|
|
|
s.URLPatterns.CRL = data.URLPatterns.CRL
|
|
|
|
s.URLPatterns.Issuer = data.URLPatterns.Issuer
|
|
|
|
|
2022-08-03 12:38:36 +00:00
|
|
|
s.Serial = &Serial{
|
|
|
|
Device: data.Serial.Device,
|
|
|
|
Baud: data.Serial.Baud,
|
|
|
|
Timeout: time.Duration(data.Serial.TimeoutMillis) * time.Millisecond,
|
|
|
|
}
|
|
|
|
|
2022-04-23 16:34:51 +00:00
|
|
|
return nil
|
2022-04-19 14:48:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type KeyStorage struct {
|
|
|
|
Label string
|
|
|
|
Module string
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
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":
|
2022-04-23 16:34:51 +00:00
|
|
|
k.Module = SoftHsmModule
|
2022-04-19 14:48:32 +00:00
|
|
|
case "p11module":
|
|
|
|
if ks.Module == "" {
|
|
|
|
return errors.New("specify a 'module' field when using the 'p11module' type")
|
|
|
|
}
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
k.Module = ks.Module
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("unsupported KeyStorage type '%s'", ks.TokenType)
|
|
|
|
}
|
|
|
|
|
2022-04-23 16:34:51 +00:00
|
|
|
if ks.Label == "" {
|
|
|
|
return errors.New("element 'label' must be specified")
|
|
|
|
}
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
k.Label = ks.Label
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-20 17:59:37 +00:00
|
|
|
type CARepository interface {
|
|
|
|
revoking.Repository
|
|
|
|
signing.Repository
|
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
type SignerConfig struct {
|
2022-11-20 17:59:37 +00:00
|
|
|
global *Settings `yaml:"Settings"`
|
|
|
|
caMap map[string]*CaCertificateEntry `yaml:"CAs"`
|
|
|
|
keyStorage map[string]*KeyStorage `yaml:"KeyStorage"`
|
|
|
|
repositories map[string]CARepository
|
2022-04-19 14:48:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
return entry, nil
|
|
|
|
}
|
|
|
|
|
2022-04-23 16:34:51 +00:00
|
|
|
func (c *SignerConfig) CalculateValidity(cert *CaCertificateEntry, relativeTo time.Time) (time.Time, time.Time) {
|
2022-04-16 20:24:32 +00:00
|
|
|
var notBefore, notAfter time.Time
|
2022-04-23 16:34:51 +00:00
|
|
|
notBefore = relativeTo
|
2022-04-16 20:24:32 +00:00
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
if cert.IsRoot() {
|
|
|
|
notAfter = notBefore.AddDate(c.global.ValidityYears.Root, 0, 0)
|
2022-04-16 20:24:32 +00:00
|
|
|
} else {
|
2022-08-03 14:01:06 +00:00
|
|
|
notAfter = notBefore.AddDate(c.global.ValidityYears.Subordinate, 0, 0)
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
return notBefore, notAfter
|
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
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,
|
|
|
|
}
|
2022-04-16 20:24:32 +00:00
|
|
|
subject.CommonName = cert.CommonName
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
return subject
|
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
func (c *SignerConfig) CertificateFileName(label string) string {
|
|
|
|
return fmt.Sprintf("%s.crt", label)
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
func (c *SignerConfig) BuildIssuerURL(cert *CaCertificateEntry) string {
|
2022-04-23 16:34:51 +00:00
|
|
|
return fmt.Sprintf(c.global.URLPatterns.Issuer, cert.Parent)
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
func (c *SignerConfig) BuildOCSPURL(cert *CaCertificateEntry) string {
|
|
|
|
// in case the configuration specifies a placeholder
|
|
|
|
if strings.Count(c.global.URLPatterns.Ocsp, "%s") == 1 {
|
2022-04-23 16:34:51 +00:00
|
|
|
return fmt.Sprintf(c.global.URLPatterns.Ocsp, cert.Parent)
|
2022-04-19 14:48:32 +00:00
|
|
|
}
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
return c.global.URLPatterns.Ocsp
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
2022-11-30 19:12:26 +00:00
|
|
|
func (c *SignerConfig) BuildCRLUrl(label string) string {
|
|
|
|
return fmt.Sprintf(c.global.URLPatterns.CRL, label)
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
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)
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
if entry.IsRoot() {
|
|
|
|
return nil, fmt.Errorf("CA %s is a root CA and has no parent", label)
|
|
|
|
}
|
|
|
|
|
2022-08-03 07:59:26 +00:00
|
|
|
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
|
2022-04-19 14:48:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// RootCAs returns the labels of all configured root CAs
|
|
|
|
func (c *SignerConfig) RootCAs() []string {
|
|
|
|
roots := make([]string, 0)
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
for label, entry := range c.caMap {
|
|
|
|
if entry.IsRoot() {
|
|
|
|
roots = append(roots, label)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return roots
|
|
|
|
}
|
|
|
|
|
2022-08-03 14:01:06 +00:00
|
|
|
// SubordinateCAs returns the labels of all configured subordinate CAs
|
|
|
|
func (c *SignerConfig) SubordinateCAs() []string {
|
|
|
|
subordinates := make([]string, 0)
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
for label, entry := range c.caMap {
|
|
|
|
if !entry.IsRoot() {
|
2022-08-03 14:01:06 +00:00
|
|
|
subordinates = append(subordinates, label)
|
2022-04-19 14:48:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-03 14:01:06 +00:00
|
|
|
return subordinates
|
2022-04-19 14:48:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-24 12:05:46 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-08-03 12:38:36 +00:00
|
|
|
func (c *SignerConfig) GetSerial() *Serial {
|
|
|
|
return c.global.Serial
|
|
|
|
}
|
|
|
|
|
2022-11-20 17:59:37 +00:00
|
|
|
func (c *SignerConfig) Repository(name string) (CARepository, error) {
|
|
|
|
var (
|
|
|
|
repo CARepository
|
|
|
|
ok bool
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
repo, ok = c.repositories[name]
|
|
|
|
if !ok {
|
|
|
|
repo, err = openssl.NewFileRepository(path.Join("repos", name))
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("could not create repository for %s: %w", name, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.repositories[name] = repo
|
|
|
|
}
|
|
|
|
|
|
|
|
return repo, nil
|
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
// 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)
|
2022-04-16 20:24:32 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2022-04-19 14:48:32 +00:00
|
|
|
return nil, fmt.Errorf("could not parse YAML configuration: %w", err)
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-21 19:12:34 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
return &SignerConfig{
|
2022-11-20 17:59:37 +00:00
|
|
|
global: config.Global,
|
|
|
|
caMap: config.CAs,
|
|
|
|
keyStorage: config.KeyStorage,
|
|
|
|
repositories: make(map[string]CARepository),
|
2022-04-19 14:48:32 +00:00
|
|
|
}, nil
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type PrivateKeyInfo struct {
|
2022-11-20 17:59:37 +00:00
|
|
|
Algorithm x509.PublicKeyAlgorithm
|
|
|
|
EccCurve elliptic.Curve
|
|
|
|
RSABits int
|
|
|
|
CRLSignatureAlgorithm x509.SignatureAlgorithm
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
func (p *PrivateKeyInfo) UnmarshalYAML(value *yaml.Node) error {
|
2022-04-16 20:24:32 +00:00
|
|
|
internalStructure := struct {
|
2022-11-20 17:59:37 +00:00
|
|
|
Algorithm string `yaml:"algorithm"`
|
|
|
|
EccCurve string `yaml:"ecc-curve,omitempty"`
|
|
|
|
RSABits *int `yaml:"rsa-bits,omitempty"`
|
|
|
|
CRLSignatureAlgorithm string `yaml:"crl-signature-algorithm,omitempty"`
|
2022-04-16 20:24:32 +00:00
|
|
|
}{}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
err := value.Decode(&internalStructure)
|
2022-04-16 20:24:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not unmarshal private key info: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch internalStructure.Algorithm {
|
|
|
|
case "RSA":
|
|
|
|
p.Algorithm = x509.RSA
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
if internalStructure.RSABits == nil {
|
2022-04-21 19:12:34 +00:00
|
|
|
return errors.New("element 'rsa-bits' with RSA key length required for algorithm RSA")
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
p.RSABits = *internalStructure.RSABits
|
2022-11-20 17:59:37 +00:00
|
|
|
|
|
|
|
if p.RSABits < minRSABits {
|
|
|
|
return fmt.Errorf("RSA keys must have a length of at least %d bits", minRSABits)
|
|
|
|
}
|
|
|
|
|
|
|
|
p.CRLSignatureAlgorithm = determineRSASignatureAlgorithm(internalStructure.CRLSignatureAlgorithm)
|
2022-04-16 20:24:32 +00:00
|
|
|
case "EC":
|
|
|
|
p.Algorithm = x509.ECDSA
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-21 19:12:34 +00:00
|
|
|
if internalStructure.EccCurve == "" {
|
|
|
|
return errors.New("element 'ecc-curve' required for algorithm EC")
|
|
|
|
}
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
p.EccCurve, err = nameToCurve(internalStructure.EccCurve)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-20 17:59:37 +00:00
|
|
|
|
|
|
|
p.CRLSignatureAlgorithm = determineECDSASignatureAlgorithm(internalStructure.CRLSignatureAlgorithm)
|
2022-04-21 19:12:34 +00:00
|
|
|
case "":
|
|
|
|
return errors.New("element 'algorithm' must be specified as 'EC' or 'RSA'")
|
2022-04-16 20:24:32 +00:00
|
|
|
default:
|
2022-04-21 19:12:34 +00:00
|
|
|
return fmt.Errorf(
|
|
|
|
"unsupported key algorithm %s, use either 'EC' or 'RSA'",
|
|
|
|
internalStructure.Algorithm,
|
|
|
|
)
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-20 17:59:37 +00:00
|
|
|
func determineRSASignatureAlgorithm(algorithm string) x509.SignatureAlgorithm {
|
|
|
|
switch strings.ToLower(algorithm) {
|
|
|
|
case "sha1withrsa", "sha1":
|
|
|
|
return x509.SHA1WithRSA
|
|
|
|
case "sha384withrsa", "sha384":
|
|
|
|
return x509.SHA384WithRSA
|
|
|
|
case "sha512withrsa", "sha512":
|
|
|
|
return x509.SHA512WithRSA
|
|
|
|
default:
|
|
|
|
return x509.SHA256WithRSA
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func determineECDSASignatureAlgorithm(algorithm string) x509.SignatureAlgorithm {
|
|
|
|
switch strings.ToLower(algorithm) {
|
|
|
|
case "sha1withecdsa", "sha1":
|
|
|
|
return x509.ECDSAWithSHA1
|
|
|
|
case "sha384withecdsa", "sha384":
|
|
|
|
return x509.ECDSAWithSHA384
|
|
|
|
case "sha512withecdsa", "sha512":
|
|
|
|
return x509.ECDSAWithSHA512
|
|
|
|
default:
|
|
|
|
return x509.ECDSAWithSHA256
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
func (p *PrivateKeyInfo) MarshalYAML() (interface{}, error) {
|
2022-04-16 20:24:32 +00:00
|
|
|
internalStructure := struct {
|
2022-04-19 14:48:32 +00:00
|
|
|
Algorithm string `yaml:"algorithm"`
|
|
|
|
EccCurve string `yaml:"ecc-curve,omitempty"`
|
|
|
|
RSABits *int `yaml:"rsa-bits,omitempty"`
|
2022-04-16 20:24:32 +00:00
|
|
|
}{}
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
switch p.Algorithm {
|
|
|
|
case x509.RSA:
|
|
|
|
internalStructure.Algorithm = "RSA"
|
|
|
|
internalStructure.RSABits = &p.RSABits
|
|
|
|
case x509.ECDSA:
|
|
|
|
internalStructure.Algorithm = "EC"
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
curveName, err := curveToName(p.EccCurve)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-04-24 07:25:04 +00:00
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
internalStructure.EccCurve = curveName
|
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
return internalStructure, nil
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-11 12:32:05 +00:00
|
|
|
var validProfileUsages = map[string]signing.ProfileUsage{
|
|
|
|
"ocsp": signing.UsageOCSP,
|
|
|
|
|
|
|
|
"client": signing.UsageClient,
|
|
|
|
"code": signing.UsageCode,
|
|
|
|
"person": signing.UsagePerson,
|
|
|
|
"server": signing.UsageServer,
|
|
|
|
"server_client": signing.UsageServerClient,
|
|
|
|
|
|
|
|
"org_client": signing.UsageOrgClient,
|
|
|
|
"org_code": signing.UsageOrgCode,
|
|
|
|
"org_email": signing.UsageOrgEmail,
|
|
|
|
"org_person": signing.UsageOrgPerson,
|
|
|
|
"org_server": signing.UsageOrgServer,
|
|
|
|
"org_server_client": signing.UsageOrgServerClient,
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParseUsage(u string) (signing.ProfileUsage, error) {
|
|
|
|
usage, ok := validProfileUsages[u]
|
|
|
|
if !ok {
|
|
|
|
return signing.UsageInvalid, fmt.Errorf("unsupported profile usage: %s", u)
|
|
|
|
}
|
|
|
|
|
|
|
|
return usage, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type Profile struct {
|
|
|
|
Name string
|
|
|
|
UseFor signing.ProfileUsage
|
|
|
|
Years int
|
|
|
|
Months int
|
|
|
|
Days int
|
|
|
|
}
|
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
type CaCertificateEntry struct {
|
|
|
|
KeyInfo *PrivateKeyInfo
|
|
|
|
CommonName string
|
2022-04-19 14:48:32 +00:00
|
|
|
MaxPathLen int // maximum path length should be 0 for CAs that issue end entity certificates
|
|
|
|
ExtKeyUsage []x509.ExtKeyUsage
|
2022-04-16 20:24:32 +00:00
|
|
|
Certificate *x509.Certificate
|
|
|
|
KeyPair crypto.Signer
|
2022-04-23 16:34:51 +00:00
|
|
|
Parent string
|
2022-04-19 14:48:32 +00:00
|
|
|
Storage string
|
2022-12-11 12:32:05 +00:00
|
|
|
Profiles []Profile
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
func (c *CaCertificateEntry) UnmarshalYAML(value *yaml.Node) error {
|
2022-04-16 20:24:32 +00:00
|
|
|
var m struct {
|
2022-04-24 07:25:04 +00:00
|
|
|
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"`
|
2022-11-30 17:42:40 +00:00
|
|
|
Profiles []struct {
|
2022-12-11 12:32:05 +00:00
|
|
|
Name string `yaml:"name"`
|
|
|
|
UseFor string `yaml:"use-for"`
|
|
|
|
Validity struct {
|
|
|
|
Years int `yaml:"years"`
|
|
|
|
Months int `yaml:"months"`
|
|
|
|
Days int `yaml:"days"`
|
|
|
|
} `yaml:"validity"`
|
2022-11-30 17:42:40 +00:00
|
|
|
} `yaml:"profiles"`
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
err := value.Decode(&m)
|
2022-04-16 20:24:32 +00:00
|
|
|
if err != nil {
|
2022-04-24 07:25:04 +00:00
|
|
|
return fmt.Errorf("could not unmarshal CA certificate entry: %w", err)
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-21 19:12:34 +00:00
|
|
|
if m.KeyInfo == nil {
|
|
|
|
return errors.New("element 'key-info' must be set")
|
|
|
|
}
|
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
c.KeyInfo = m.KeyInfo
|
2022-04-21 19:12:34 +00:00
|
|
|
|
|
|
|
if m.CommonName == "" {
|
|
|
|
return errors.New("element 'common-name' must be set")
|
|
|
|
}
|
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
c.CommonName = m.CommonName
|
|
|
|
c.MaxPathLen = m.MaxPathLen
|
2022-04-19 14:48:32 +00:00
|
|
|
|
2022-04-23 16:34:51 +00:00
|
|
|
if m.Parent == "" && m.ExtKeyUsage != nil {
|
|
|
|
return errors.New("a root CA must not specify 'ext-key-usages'")
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Parent = m.Parent
|
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
if m.ExtKeyUsage != nil {
|
|
|
|
c.ExtKeyUsage, err = mapExtKeyUsageNames(m.ExtKeyUsage)
|
2022-04-23 16:34:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-04-16 20:24:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
if m.Storage != "" {
|
|
|
|
c.Storage = m.Storage
|
|
|
|
} else {
|
|
|
|
c.Storage = "default"
|
|
|
|
}
|
|
|
|
|
2022-11-30 17:42:40 +00:00
|
|
|
if m.Profiles != nil {
|
2022-12-11 12:32:05 +00:00
|
|
|
c.Profiles = make([]Profile, len(m.Profiles))
|
2022-11-30 17:42:40 +00:00
|
|
|
|
|
|
|
for i, prof := range m.Profiles {
|
2022-12-11 12:32:05 +00:00
|
|
|
usage, err := ParseUsage(prof.UseFor)
|
2022-11-30 17:42:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("config error: %w", err)
|
|
|
|
}
|
|
|
|
|
2022-12-11 12:32:05 +00:00
|
|
|
if prof.Validity.Years > 0 || prof.Validity.Months > 0 || prof.Validity.Days > 0 {
|
|
|
|
prof.Validity.Years, prof.Validity.Months, prof.Validity.Days = defaultValidity(usage)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Profiles[i] = Profile{
|
2022-11-30 17:42:40 +00:00
|
|
|
Name: prof.Name,
|
|
|
|
UseFor: usage,
|
2022-12-11 12:32:05 +00:00
|
|
|
Years: prof.Validity.Years,
|
|
|
|
Months: prof.Validity.Months,
|
|
|
|
Days: prof.Validity.Days,
|
2022-11-30 17:42:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-12-11 12:32:05 +00:00
|
|
|
func defaultValidity(useFor signing.ProfileUsage) (years int, months int, days int) {
|
|
|
|
switch useFor {
|
|
|
|
case signing.UsagePerson, signing.UsageOrgPerson, signing.UsageOrgEmail:
|
|
|
|
return 2, 0, 0
|
|
|
|
default:
|
|
|
|
return 1, 0, 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-19 14:48:32 +00:00
|
|
|
func (c *CaCertificateEntry) IsRoot() bool {
|
2022-04-23 16:34:51 +00:00
|
|
|
return c.Parent == ""
|
2022-04-19 14:48:32 +00:00
|
|
|
}
|
|
|
|
|
2022-04-16 20:24:32 +00:00
|
|
|
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
|
|
|
|
}
|