Improve config handling and test coverage
This commit is contained in:
parent
7d415ff181
commit
42c7dc7170
6 changed files with 877 additions and 107 deletions
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue