Improve config handling and test coverage

This commit is contained in:
Jan Dittberner 2022-04-23 18:34:51 +02:00 committed by Jan Dittberner
parent 7d415ff181
commit 42c7dc7170
6 changed files with 877 additions and 107 deletions

View file

@ -2,4 +2,4 @@
package config
const softHsmModule = "/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so"
const SoftHsmModule = "/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so"

View file

@ -2,4 +2,4 @@
package config
const softHsmModule = "/usr/lib/aarch64-linux-gnu/softhsm/libsofthsm2.so"
const SoftHsmModule = "/usr/lib/aarch64-linux-gnu/softhsm/libsofthsm2.so"

View file

@ -2,4 +2,4 @@
package config
const softHsmModule = "/usr/lib/arm-linux-gnueabihf/softhsm/libsofthsm2.so"
const SoftHsmModule = "/usr/lib/arm-linux-gnueabihf/softhsm/libsofthsm2.so"

View file

@ -15,22 +15,96 @@ import (
)
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"`
Organization *pkix.Name
ValidityYears struct {
Root int `yaml:"root"`
Intermediary int `yaml:"intermediary"`
} `yaml:"validity-years"`
Root, Intermediary int
}
URLPatterns struct {
Ocsp string `yaml:"ocsp"`
CRL string `yaml:"crl"`
Issuer string `yaml:"issuer"`
} `yaml:"url-patterns"`
Ocsp, CRL, Issuer string
}
}
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"`
}{}
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 if 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"}
}
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
return nil
}
type KeyStorage struct {
@ -52,7 +126,7 @@ func (k *KeyStorage) UnmarshalYAML(n *yaml.Node) error {
switch ks.TokenType {
case "softhsm":
k.Module = softHsmModule
k.Module = SoftHsmModule
case "p11module":
if ks.Module == "" {
return errors.New("specify a 'module' field when using the 'p11module' type")
@ -62,6 +136,9 @@ func (k *KeyStorage) UnmarshalYAML(n *yaml.Node) error {
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
@ -81,9 +158,9 @@ func (c *SignerConfig) GetCADefinition(label string) (*CaCertificateEntry, error
return entry, nil
}
func (c *SignerConfig) CalculateValidity(cert *CaCertificateEntry) (time.Time, time.Time) {
func (c *SignerConfig) CalculateValidity(cert *CaCertificateEntry, relativeTo time.Time) (time.Time, time.Time) {
var notBefore, notAfter time.Time
notBefore = time.Now()
notBefore = relativeTo
if cert.IsRoot() {
notAfter = notBefore.AddDate(c.global.ValidityYears.Root, 0, 0)
@ -110,19 +187,19 @@ func (c *SignerConfig) CertificateFileName(label string) string {
}
func (c *SignerConfig) BuildIssuerURL(cert *CaCertificateEntry) string {
return fmt.Sprintf(c.global.URLPatterns.Issuer, cert.parent)
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 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)
return fmt.Sprintf(c.global.URLPatterns.CRL, cert.Parent)
}
func (c *SignerConfig) GetParentCA(label string) (*CaCertificateEntry, error) {
@ -135,7 +212,7 @@ func (c *SignerConfig) GetParentCA(label string) (*CaCertificateEntry, error) {
return nil, fmt.Errorf("CA %s is a root CA and has no parent", label)
}
return c.caMap[entry.parent], nil
return c.caMap[entry.Parent], nil
}
// RootCAs returns the labels of all configured root CAs
@ -302,7 +379,7 @@ type CaCertificateEntry struct {
ExtKeyUsage []x509.ExtKeyUsage
Certificate *x509.Certificate
KeyPair crypto.Signer
parent string
Parent string
Storage string
}
@ -334,11 +411,18 @@ func (c *CaCertificateEntry) UnmarshalYAML(value *yaml.Node) error {
c.CommonName = m.CommonName
c.MaxPathLen = m.MaxPathLen
if m.ExtKeyUsage != nil {
c.ExtKeyUsage, err = mapExtKeyUsageNames(m.ExtKeyUsage)
if m.Parent == "" && m.ExtKeyUsage != nil {
return errors.New("a root CA must not specify 'ext-key-usages'")
}
c.parent = m.Parent
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
@ -346,15 +430,11 @@ func (c *CaCertificateEntry) UnmarshalYAML(value *yaml.Node) error {
c.Storage = "default"
}
if err != nil {
return err
}
return nil
}
func (c *CaCertificateEntry) IsRoot() bool {
return c.parent == ""
return c.Parent == ""
}
func mapExtKeyUsageNames(usages []string) ([]x509.ExtKeyUsage, error) {

View file

@ -1,47 +1,124 @@
package config
package config_test
import (
"crypto/elliptic"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"git.cacert.org/cacert-gosigner/pkg/config"
)
type TestCurve struct {
}
func (t TestCurve) Params() *elliptic.CurveParams {
panic("not needed")
}
func (t TestCurve) IsOnCurve(_, _ *big.Int) bool {
panic("not needed")
}
func (t TestCurve) Add(_, _, _, _ *big.Int) (x, y *big.Int) {
panic("not needed")
}
func (t TestCurve) Double(_, _ *big.Int) (x, y *big.Int) {
panic("not needed")
}
func (t TestCurve) ScalarMult(_, _ *big.Int, _ []byte) (x, y *big.Int) {
panic("not needed")
}
func (t TestCurve) ScalarBaseMult(_ []byte) (x, y *big.Int) {
panic("not needed")
}
func TestPrivateKeyInfo_MarshalYAML(t *testing.T) {
testCurve := TestCurve{}
testData := []struct {
name string
pkInfo *PrivateKeyInfo
expected string
name string
pkInfo *config.PrivateKeyInfo
expected string
expectErr bool
}{
{
"RSA",
&PrivateKeyInfo{
name: "RSA",
pkInfo: &config.PrivateKeyInfo{
Algorithm: x509.RSA,
RSABits: 3072,
},
`algorithm: RSA
expected: `algorithm: RSA
rsa-bits: 3072
`,
},
{
"ECDSA",
&PrivateKeyInfo{
name: "ECDSA P-224",
pkInfo: &config.PrivateKeyInfo{
Algorithm: x509.ECDSA,
EccCurve: elliptic.P224(),
},
`algorithm: EC
expected: `algorithm: EC
ecc-curve: P-224
`,
},
{
name: "ECDSA P-256",
pkInfo: &config.PrivateKeyInfo{
Algorithm: x509.ECDSA,
EccCurve: elliptic.P256(),
},
expected: `algorithm: EC
ecc-curve: P-256
`,
},
{
name: "ECDSA P-384",
pkInfo: &config.PrivateKeyInfo{
Algorithm: x509.ECDSA,
EccCurve: elliptic.P384(),
},
expected: `algorithm: EC
ecc-curve: P-384
`,
},
{
name: "ECDSA P-521",
pkInfo: &config.PrivateKeyInfo{
Algorithm: x509.ECDSA,
EccCurve: elliptic.P521(),
},
expected: `algorithm: EC
ecc-curve: P-521
`,
},
{
name: "ECDSA unsupported curve",
pkInfo: &config.PrivateKeyInfo{
Algorithm: x509.ECDSA,
EccCurve: testCurve,
},
expectErr: true,
},
}
for _, item := range testData {
t.Run(item.name, func(t *testing.T) {
data, err := yaml.Marshal(item.pkInfo)
if item.expectErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.YAMLEq(t, item.expected, string(data))
@ -53,56 +130,99 @@ func TestPrivateKeyInfo_UnmarshalYAML(t *testing.T) {
testData := []struct {
name string
yaml string
expected *PrivateKeyInfo
expected *config.PrivateKeyInfo
expectErr bool
}{
{
"RSA",
`label: "mykey"
name: "malformed",
yaml: `noyaml`,
expectErr: true,
},
{
name: "RSA",
yaml: `label: "mykey"
algorithm: "RSA"
rsa-bits: 2048`,
&PrivateKeyInfo{
expected: &config.PrivateKeyInfo{
Algorithm: x509.RSA,
RSABits: 2048,
},
false,
},
{
"ECDSA",
`label: "mykey"
name: "ECDSA P-224",
yaml: `label: "mykey"
algorithm: "EC"
ecc-curve: "P-224"`,
expected: &config.PrivateKeyInfo{
Algorithm: x509.ECDSA,
EccCurve: elliptic.P224(),
},
},
{
name: "ECDSA P-256",
yaml: `label: "mykey"
algorithm: "EC"
ecc-curve: "P-256"`,
expected: &config.PrivateKeyInfo{
Algorithm: x509.ECDSA,
EccCurve: elliptic.P256(),
},
},
{
name: "ECDSA P-384",
yaml: `label: "mykey"
algorithm: "EC"
ecc-curve: "P-384"`,
expected: &config.PrivateKeyInfo{
Algorithm: x509.ECDSA,
EccCurve: elliptic.P384(),
},
},
{
name: "ECDSA P-521",
yaml: `label: "mykey"
algorithm: "EC"
ecc-curve: "P-521"`,
&PrivateKeyInfo{
expected: &config.PrivateKeyInfo{
Algorithm: x509.ECDSA,
EccCurve: elliptic.P521(),
},
false,
},
{
"no-algorithm",
`label: "mykey"`,
nil,
true,
name: "no-algorithm",
yaml: `label: "mykey"`,
expectErr: true,
},
{
"RSA-no-rsa-bits",
`label: "mykey"
name: "RSA-no-rsa-bits",
yaml: `label: "mykey"
algorithm: "RSA"`,
nil,
true,
expectErr: true,
},
{
"ECDSA-no-curve",
`label: "mykey"
name: "ECDSA-no-curve",
yaml: `label: "mykey"
algorithm: "EC"`,
nil,
true,
expectErr: true,
},
{
name: "ECDSA-invalid-curve",
yaml: `label: "mykey"
algorithm: "EC"
ecc-curve: "fancy-but-wrong"`,
expectErr: true,
},
{
name: "wrong-key-algorithm",
yaml: `label: "mykey"
algorithm: "ChaCha"`,
expectErr: true,
},
}
for _, item := range testData {
t.Run(item.name, func(t *testing.T) {
pkInfo := &PrivateKeyInfo{}
pkInfo := &config.PrivateKeyInfo{}
err := yaml.Unmarshal([]byte(item.yaml), pkInfo)
if err != nil && !item.expectErr {
require.NoError(t, err)
@ -120,47 +240,172 @@ algorithm: "EC"`,
}
func TestCaCertificateEntry_UnmarshalYAML(t *testing.T) {
data := `{
"key-info": {
"algorithm":"EC",
"ecc-curve":"P-521"
},
"certificate-file":"test.crt",
"common-name":"My Little Test Root CA"
}`
entry := CaCertificateEntry{}
err := yaml.Unmarshal([]byte(data), &entry)
require.NoError(t, err)
assert.Equal(t, CaCertificateEntry{
KeyInfo: &PrivateKeyInfo{
Algorithm: x509.ECDSA,
EccCurve: elliptic.P521(),
testData := []struct {
name, yaml string
expected config.CaCertificateEntry
expectErr bool
}{
{
name: "no yaml",
yaml: "no yaml",
expectErr: true,
},
CommonName: "My Little Test Root CA",
Storage: "default",
}, entry)
{
name: "no common name",
yaml: `
key-info:
algorithm: EC
ecc-curve: P-521
`,
expectErr: true,
},
{
name: "no key info",
yaml: `
common-name: My Little Test Root CA
`,
expectErr: true,
},
{
name: "EC P-521",
yaml: `
key-info:
algorithm: EC
ecc-curve: P-521
common-name: My Little Test Root CA
ext-key-usages:
- client
- server
`,
expectErr: true,
},
{
name: "EC root P-521",
yaml: `
key-info:
algorithm: EC
ecc-curve: P-521
common-name: My Little Test Root CA
storage: root
`,
expected: config.CaCertificateEntry{
KeyInfo: &config.PrivateKeyInfo{
Algorithm: x509.ECDSA,
EccCurve: elliptic.P521(),
},
CommonName: "My Little Test Root CA",
Storage: "root",
},
},
{
name: "EC sub P-256 client server",
yaml: `
key-info:
algorithm: EC
ecc-curve: P-256
common-name: My Little Test Sub CA
parent: root
ext-key-usages:
- client
- server
`,
expected: config.CaCertificateEntry{
KeyInfo: &config.PrivateKeyInfo{
Algorithm: x509.ECDSA,
EccCurve: elliptic.P256(),
},
Parent: "root",
CommonName: "My Little Test Sub CA",
Storage: "default",
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
},
},
},
{
name: "EC sub P-256 person",
yaml: `
key-info:
algorithm: EC
ecc-curve: P-256
common-name: My Little Test Sub CA
parent: root
ext-key-usages:
- client
- code
- email
- ocsp
`,
expected: config.CaCertificateEntry{
KeyInfo: &config.PrivateKeyInfo{
Algorithm: x509.ECDSA,
EccCurve: elliptic.P256(),
},
CommonName: "My Little Test Sub CA",
Storage: "default",
Parent: "root",
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageCodeSigning,
x509.ExtKeyUsageEmailProtection,
x509.ExtKeyUsageOCSPSigning,
},
},
},
{
name: "invalid ext-key-usages",
yaml: `
key-info:
algorithm: EC
ecc-curve: P-256
common-name: My Little Test Sub CA
parent: root
ext-key-usages:
- wrong_one
`,
expectErr: true,
},
}
for _, item := range testData {
t.Run(item.name, func(t *testing.T) {
entry := config.CaCertificateEntry{}
err := yaml.Unmarshal([]byte(item.yaml), &entry)
if item.expectErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, item.expected, entry)
})
}
}
func TestLoadConfiguration(t *testing.T) {
testData := []struct {
name, yaml string
err bool
testData := map[string]struct {
yaml string
errMsg string
}{
{
name: "Good",
"Good": {
yaml: `---
Settings:
organization:
organization: ["Acme CAs Ltd."]
validity-years:
root: 20
intermediary: 5
url-patterns:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s
issuer: http://%s.cas.example.org/
KeyStorage:
default:
type: softhsm
label: ca-storage
CAs:
root:
common-name: "Root CA"
@ -180,22 +425,75 @@ CAs:
ecc-curve: P-256
parent: root
`,
err: false,
},
{
name: "Bad",
yaml: `noyamlforyou: ]`,
err: true,
"Bad": {
yaml: `no yaml for you: ]`,
errMsg: "could not parse YAML configuration",
},
"NoGlobal": {
yaml: `---
KeyStorage:
default:
label: default
type: softhsm
CAs:
root:
common-name: "Root CA"
key-info:
algorithm: EC
ecc-curve: P-384
`,
errMsg: "configuration entry 'Settings' is missing or empty",
},
"NoKeyStorage": {
yaml: `
Settings:
organization:
organization: ["Acme CAs Ltd."]
validity-years:
root: 20
intermediary: 5
url-patterns:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s.crl
issuer: http://%s.cas.example.org/
CAs:
root:
common-name: "Root CA"
key-info:
algorithm: EC
ecc-curve: P-384
`,
errMsg: "configuration entry 'KeyStorage' is missing or empty",
},
"NoCAs": {
yaml: `
Settings:
organization:
organization: ["Acme CAs Ltd."]
validity-years:
root: 20
intermediary: 5
url-patterns:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s.crl
issuer: http://%s.cas.example.org/
KeyStorage:
default:
label: default
type: softhsm
`,
errMsg: "configuration entry 'CAs' is missing or empty",
},
}
for _, item := range testData {
t.Run(item.name, func(t *testing.T) {
for name, item := range testData {
t.Run(name, func(t *testing.T) {
r := strings.NewReader(item.yaml)
sc, err := LoadConfiguration(r)
sc, err := config.LoadConfiguration(r)
if item.err {
assert.Error(t, err)
if item.errMsg != "" {
assert.ErrorContains(t, err, item.errMsg)
assert.Nil(t, sc)
} else {
assert.NoError(t, err)
@ -205,17 +503,21 @@ CAs:
}
}
func TestSignerConfig_RootCAs(t *testing.T) {
yamlData := `---
const sampleConfig = `---
Settings:
organization:
organization: ["Acme CAs Ltd."]
validity-years:
root: 20
intermediary: 5
url-patterns:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s.crl
issuer: http://%s.cas.example.org/
KeyStorage:
default:
type: softhsm
label: ca-storage
CAs:
root:
common-name: "Root CA"
@ -235,12 +537,399 @@ CAs:
ecc-curve: P-256
parent: root
`
r := strings.NewReader(yamlData)
sc, err := LoadConfiguration(r)
func loadSignerConfig(t *testing.T) *config.SignerConfig {
r := strings.NewReader(sampleConfig)
sc, err := config.LoadConfiguration(r)
require.NoError(t, err)
require.NotNil(t, sc)
return sc
}
func TestSignerConfig_RootCAs(t *testing.T) {
sc := loadSignerConfig(t)
roots := sc.RootCAs()
assert.Equal(t, roots, []string{"root"})
}
func TestSignerConfig_IntermediaryCAs(t *testing.T) {
sc := loadSignerConfig(t)
cAs := sc.IntermediaryCAs()
assert.ElementsMatch(t, cAs, []string{"sub1", "sub2"})
}
func TestSignerConfig_GetParentCA(t *testing.T) {
testData := map[string]struct{ errMsg string }{
"root": {
errMsg: "CA root is a root CA and has no parent",
},
"unknown": {
errMsg: "no CA definition for unknown",
},
"sub1": {},
}
sc := loadSignerConfig(t)
for name, item := range testData {
t.Run(name, func(t *testing.T) {
ca, err := sc.GetParentCA(name)
if item.errMsg != "" {
assert.ErrorContains(t, err, item.errMsg)
} else {
require.NoError(t, err)
assert.Equal(t, "Root CA", ca.CommonName)
}
})
}
}
func TestSignerConfig_CertificateFileName(t *testing.T) {
sc := loadSignerConfig(t)
assert.Equal(t, "root.crt", sc.CertificateFileName("root"))
}
func TestSignerConfig_BuildCRLUrl(t *testing.T) {
sc := loadSignerConfig(t)
ca, err := sc.GetCADefinition("sub1")
require.NoError(t, err)
url := sc.BuildCRLUrl(ca)
assert.Equal(t, "http://crl.example.org/root.crl", url)
}
func TestSignerConfig_BuildIssuerUrl(t *testing.T) {
sc := loadSignerConfig(t)
ca, err := sc.GetCADefinition("sub1")
require.NoError(t, err)
url := sc.BuildIssuerURL(ca)
assert.Equal(t, "http://root.cas.example.org/", url)
}
func TestSignerConfig_BuildOCSPURL(t *testing.T) {
sc := loadSignerConfig(t)
ca, err := sc.GetCADefinition("sub1")
require.NoError(t, err)
url := sc.BuildOCSPURL(ca)
assert.Equal(t, "http://ocsp.example.org/", url)
}
func TestSignerConfig_BuildOCSPURL2(t *testing.T) {
r := strings.NewReader(`---
Settings:
organization:
organization: ["Acme CAs Ltd."]
validity-years:
root: 20
intermediary: 5
url-patterns:
ocsp: http://ocsp.example.org/%s
crl: http://crl.example.org/%s.crl
issuer: http://%s.cas.example.org/
KeyStorage:
default:
type: softhsm
label: ca-storage
CAs:
root:
common-name: "Root CA"
key-info:
algorithm: EC
ecc-curve: P-384
sub1:
common-name: "Sub CA 1"
key-info:
algorithm: EC
ecc-curve: P-256
parent: root
sub2:
common-name: "Sub CA 2"
key-info:
algorithm: EC
ecc-curve: P-256
parent: root
`)
sc, err := config.LoadConfiguration(r)
require.NoError(t, err)
require.NotNil(t, sc)
ca, err := sc.GetCADefinition("sub1")
require.NoError(t, err)
url := sc.BuildOCSPURL(ca)
assert.Equal(t, "http://ocsp.example.org/root", url)
}
func TestSettings_UnmarshalYAML(t *testing.T) {
testData := map[string]struct {
yaml string
expected config.Settings
errMsg string
}{
"no yaml": {
yaml: "no yaml",
errMsg: "could not decode YAML",
},
"missing organization": {
yaml: `
validity-years:
root: 10
intermediary: 5
url-patterns:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s.crl
issuer: http://issuer.example.org/%s.crt
`,
errMsg: "invalid Settings you need to specify 'organization'",
},
"missing validity-years": {
yaml: `
organization:
organization: ["Acme CAs Ltd."]
url-patterns:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s.crl
issuer: http://issuer.example.org/%s.crt
`,
errMsg: "invalid Settings you must specify validity years for 'root' and 'intermediary'",
},
"missing url-patterns": {
yaml: `
organization:
organization: ["Acme CAs Ltd."]
validity-years:
root: 10
intermediary: 5
`,
errMsg: "invalid Settings",
},
"invalid validity-years": {
yaml: `
organization:
organization: ["Acme CAs Ltd."]
validity-years:
root: 5
intermediary: 10
url-patterns:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s.crl
issuer: http://issuer.example.org/%s.crt
`,
errMsg: "invalid Settings validity of root CA certificates must be equal or greater than those if intermediary CA certificates",
},
"no OCSP pattern": {
yaml: `
organization:
organization: ["Acme CAs Ltd."]
validity-years:
root: 10
intermediary: 5
url-patterns:
crl: http://crl.example.org/%s.crl
issuer: http://issuer.example.org/%s.crt
`,
errMsg: "invalid Settings you must specify an 'ocsp' URL pattern",
},
"invalid OCSP pattern": {
yaml: `
organization:
organization: ["Acme CAs Ltd."]
validity-years:
root: 10
intermediary: 5
url-patterns:
ocsp: http://ocsp.example.org/%s_%s
crl: http://crl.example.org/%s.crl
issuer: http://issuer.example.org/%s.crt
`,
errMsg: "invalid Settings url-pattern 'ocsp' must contain zero or one '%s' placeholder",
},
"no CRL pattern": {
yaml: `
organization:
organization: ["Acme CAs Ltd."]
validity-years:
root: 10
intermediary: 5
url-patterns:
ocsp: http://ocsp.example.org/
issuer: http://issuer.example.org/%s.crt
`,
errMsg: "invalid Settings you must specify an 'crl' URL pattern",
},
"invalid CRL pattern": {
yaml: `
organization:
organization: ["Acme CAs Ltd."]
validity-years:
root: 10
intermediary: 5
url-patterns:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/
issuer: http://issuer.example.org/%s.crt
`,
errMsg: "invalid Settings url-pattern 'crl' must contain one '%s' placeholder",
},
"no issuer pattern": {
yaml: `
organization:
organization: ["Acme CAs Ltd."]
validity-years:
root: 10
intermediary: 5
url-patterns:
crl: http://crl.example.org/%s.crl
ocsp: http://ocsp.example.org/
`,
errMsg: "invalid Settings you must specify an 'issuer' URL pattern",
},
"invalid issuer pattern": {
yaml: `
organization:
organization: ["Acme CAs Ltd."]
validity-years:
root: 10
intermediary: 5
url-patterns:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s.crl
issuer: http://issuer.example.org/
`,
errMsg: "invalid Settings url-pattern 'issuer' must contain one '%s' placeholder",
},
}
for name, item := range testData {
t.Run(name, func(t *testing.T) {
var s config.Settings
err := yaml.Unmarshal([]byte(item.yaml), &s)
if item.errMsg != "" {
assert.ErrorContains(t, err, item.errMsg)
} else {
require.NoError(t, err)
assert.NotNil(t, s)
assert.Equal(t, item.expected, s)
}
})
}
}
func TestKeyStorage_UnmarshalYAML(t *testing.T) {
testData := map[string]struct {
yaml string
expected config.KeyStorage
errMsg string
}{
"no yaml": {
yaml: `no yaml`,
errMsg: "could not decode YAML:",
},
"softhsm": {
yaml: `
label: softhsm
type: softhsm`,
expected: config.KeyStorage{
Label: "softhsm", Module: config.SoftHsmModule,
},
},
"custom": {
yaml: `
label: custom
type: p11module
module: /usr/lib/mytokenp11.so`,
expected: config.KeyStorage{
Label: "custom", Module: "/usr/lib/mytokenp11.so",
},
},
"missing module": {
yaml: `
label: custom
type: p11module`,
errMsg: "specify a 'module' field when using the 'p11module' type",
},
"unsupported type": {
yaml: `
label: something
type: something`,
errMsg: "unsupported KeyStorage type 'something'",
},
"missing label": {
yaml: `
type: softhsm`,
errMsg: "element 'label' must be specified",
},
}
for name, item := range testData {
t.Run(name, func(t *testing.T) {
var ks config.KeyStorage
err := yaml.Unmarshal([]byte(item.yaml), &ks)
if item.errMsg != "" {
assert.ErrorContains(t, err, item.errMsg)
} else {
assert.NoError(t, err)
assert.Equal(t, item.expected, ks)
}
})
}
}
func TestSignerConfig_GetCADefinition(t *testing.T) {
sc := loadSignerConfig(t)
def, err := sc.GetCADefinition("unknown")
assert.ErrorContains(t, err, "no CA definition found for label unknown")
assert.Nil(t, def)
}
func TestSignerConfig_CalculateValidity(t *testing.T) {
sc := loadSignerConfig(t)
for _, ca := range []string{"root", "sub1"} {
t.Run(ca, func(t *testing.T) {
def, err := sc.GetCADefinition(ca)
require.NoError(t, err)
now := time.Now()
notBefore, notAfter := sc.CalculateValidity(def, now)
assert.True(t, now.Equal(notBefore))
assert.True(t, now.Before(notAfter))
})
}
}
func TestSignerConfig_CalculateSubject(t *testing.T) {
testData := map[string]struct {
cn string
}{
"root": {cn: "Root CA"},
"sub1": {cn: "Sub CA 1"},
}
sc := loadSignerConfig(t)
for name, item := range testData {
t.Run(name, func(t *testing.T) {
def, err := sc.GetCADefinition(name)
require.NoError(t, err)
subject := sc.CalculateSubject(def)
assert.Equal(t, pkix.Name{Organization: []string{"Acme CAs Ltd."}, CommonName: item.cn}, subject)
})
}
}

View file

@ -15,6 +15,7 @@ import (
"math/big"
"os"
"syscall"
"time"
"github.com/ThalesIgnite/crypto11"
@ -74,7 +75,7 @@ func GetRootCACertificate(ctx context.Context, label string) (*x509.Certificate,
return nil, errCertificateGenerationRefused
}
notBefore, notAfter := sc.CalculateValidity(caCert)
notBefore, notAfter := sc.CalculateValidity(caCert, time.Now())
subject := sc.CalculateSubject(caCert)
certificate, err = generateRootCACertificate(
@ -153,7 +154,7 @@ func GetIntermediaryCACertificate(ctx context.Context, certLabel string) (*x509.
return nil, errCertificateGenerationRefused
}
notBefore, notAfter := sc.CalculateValidity(caCert)
notBefore, notAfter := sc.CalculateValidity(caCert, time.Now())
subject := sc.CalculateSubject(caCert)
certificate, err = generateIntermediaryCACertificate(