@ -5,71 +5,183 @@ import (
"crypto/elliptic"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"time"
"gopkg.in/yaml.v3"
)
type Settings struct {
Organization pkix . Name ` json:"organization" `
RootYears int ` json:"root-years" `
IntermediaryYears int ` json:"intermediary-years" `
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 ( s * Settings ) CalculateValidity ( cert * CaCertificateEntry ) ( time . Time , time . Time ) {
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 . Parent == nil {
notAfter = notBefore . AddDate ( s . RootYears , 0 , 0 )
if cert . IsRoot ( ) {
notAfter = notBefore . AddDate ( c. global . ValidityYears . Root , 0 , 0 )
} else {
notAfter = notBefore . AddDate ( s . IntermediaryYears , 0 , 0 )
notAfter = notBefore . AddDate ( c. global . ValidityYear s. Intermediary , 0 , 0 )
}
return notBefore , notAfter
}
func ( s * Settings ) CalculateSubject ( cert * CaCertificateEntry ) pkix . Name {
subject := s . Organization
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 ( s * Settings ) BuildIssuerURL ( parentCA * CaCertificateEntry ) string {
return fmt . Sprintf ( "http://www.example.org/%s.crt" , parentCA . Label )
func ( c * SignerConfig ) CertificateFileName ( label string ) string {
return fmt . Sprintf ( " %s.crt", l abel)
}
func ( s * Settings ) BuildOCSPURL ( _ * CaCertificateEntry ) string {
return "http://ocsp.example.org/"
func ( c * SignerConfig ) BuildIssuerURL ( cert * CaCertificateEntry ) string {
return fmt . Sprintf ( c . global . URLPatterns . Issuer , cert . parent )
}
func ( s * Settings ) BuildCRLUrl ( parentCA * CaCertificateEntry ) string {
return fmt . Sprintf ( "http://crl.cacert.org/%s.crl" , parentCA . Label )
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
}
type SignerConfig struct {
Global * Settings ` json:"Settings" `
CAs [ ] * CaCertificateEntry ` json:"CAs" `
func ( c * SignerConfig ) BuildCRLUrl ( cert * CaCertificateEntry ) string {
return fmt . Sprintf ( c . global . URLPatterns . CRL , cert . parent )
}
// 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 )
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 )
}
config := & SignerConfig { }
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 )
err = json . Unmarshal ( data , config )
if err != nil {
return nil , fmt . Errorf ( "could not parse JSON configuration: %w" , err )
return nil , fmt . Errorf ( "could not parse YAML configuration: %w", err )
}
return config , nil
return & SignerConfig {
global : config . Global ,
caMap : config . CAs ,
KeyStorage : config . KeyStorage ,
} , nil
}
type PrivateKeyInfo struct {
@ -78,15 +190,15 @@ type PrivateKeyInfo struct {
RSABits int
}
func ( p * PrivateKeyInfo ) Unmarshal JSON( data [ ] byt e) error {
func ( p * PrivateKeyInfo ) Unmarshal YAML( value * yaml . Nod e) error {
internalStructure := struct {
Label string ` json :"label"`
Algorithm string ` json :"algorithm"`
EccCurve string ` json :"ecc-curve,omitempty"`
RSABits * int ` json :"rsa-bits,omitempty"`
Label string ` yaml :"label"`
Algorithm string ` yaml :"algorithm"`
EccCurve string ` yaml :"ecc-curve,omitempty"`
RSABits * int ` yaml :"rsa-bits,omitempty"`
} { }
err := json. Unmarshal ( data , & internalStructure )
err := value. Decode ( & internalStructure )
if err != nil {
return fmt . Errorf ( "could not unmarshal private key info: %w" , err )
}
@ -111,11 +223,11 @@ func (p *PrivateKeyInfo) UnmarshalJSON(data []byte) error {
return nil
}
func ( p * PrivateKeyInfo ) Marshal JSON( ) ( [ ] byte , error ) {
func ( p * PrivateKeyInfo ) Marshal YAML( ) ( interface { } , error ) {
internalStructure := struct {
Algorithm string ` json :"algorithm"`
EccCurve string ` json :"ecc-curve,omitempty"`
RSABits * int ` json :"rsa-bits,omitempty"`
Algorithm string ` yaml :"algorithm"`
EccCurve string ` yaml :"ecc-curve,omitempty"`
RSABits * int ` yaml :"rsa-bits,omitempty"`
} { }
switch p . Algorithm {
case x509 . RSA :
@ -130,12 +242,7 @@ func (p *PrivateKeyInfo) MarshalJSON() ([]byte, error) {
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
return internalStructure , nil
}
func curveToName ( curve elliptic . Curve ) ( string , error ) {
@ -170,45 +277,47 @@ func nameToCurve(name string) (elliptic.Curve, error) {
}
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" `
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 * CaCertificateEntry
}
func ( c * CaCertificateEntry ) CertificateFileName ( ) string {
return c . Label + ".crt"
parent string
Storage string
}
func ( c * CaCertificateEntry ) Unmarshal JSON( data [ ] byt e) error {
func ( c * CaCertificateEntry ) UnmarshalYAML ( value * yaml . Node ) error {
var m struct {
Label string
KeyInfo * PrivateKeyInfo ` json:"key-info "`
CommonName string ` json:"common-name" `
SubCAs [ ] * CaCertificateEntry ` json:"sub-ca s,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 "`
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-usage s,omitempty"`
Parent string ` yaml:"parent" `
Storage string ` yaml:"storage "`
}
err := json. Unmarshal ( data , & m )
err := value. Decode ( & 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 )
}
c . parent = m . Parent
if m . Storage != "" {
c . Storage = m . Storage
} else {
c . Storage = "default"
}
if err != nil {
return err
}
@ -216,6 +325,10 @@ func (c *CaCertificateEntry) UnmarshalJSON(data []byte) error {
return nil
}
func ( c * CaCertificateEntry ) IsRoot ( ) bool {
return c . parent == ""
}
func mapExtKeyUsageNames ( usages [ ] string ) ( [ ] x509 . ExtKeyUsage , error ) {
extKeyUsages := make ( [ ] x509 . ExtKeyUsage , len ( usages ) )