Implement configuration and CA hierarchy setup
This commit implements a mechanism to load CA configuration dynamically from JSON files. Missing keys and certificates can be generated in a PKCS#11 HSM or Smartcard. Certificates are stored as PEM encoded .crt files in the filesystem. The default PKCS#11 module (softhsm2) is now loaded from a platform specific path using go:build comments.
This commit is contained in:
parent
9befa5bea6
commit
de997913cf
12 changed files with 923 additions and 96 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,4 +1,6 @@
|
||||||
|
*.crt
|
||||||
*.pem
|
*.pem
|
||||||
*.pub
|
*.pub
|
||||||
.idea/
|
.idea/
|
||||||
|
ca-hierarchy.json
|
||||||
dist/
|
dist/
|
5
cmd/signer/amd64.go
Normal file
5
cmd/signer/amd64.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build linux && amd64
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
const defaultPkcs11Module = "/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so"
|
5
cmd/signer/arm64.go
Normal file
5
cmd/signer/arm64.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build linux && arm64
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
const defaultPkcs11Module = "/usr/lib/aarch64-linux-gnu/softhsm/libsofthsm2.so"
|
5
cmd/signer/armhf.go
Normal file
5
cmd/signer/armhf.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build linux && arm
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
const defaultPkcs11Module = "/usr/lib/arm-linux-gnueabihf/softhsm/libsofthsm2.so"
|
|
@ -1,4 +1,99 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"git.cacert.org/cacert-gosigner/pkg/config"
|
||||||
|
"github.com/ThalesIgnite/crypto11"
|
||||||
|
"golang.org/x/term"
|
||||||
|
|
||||||
|
"git.cacert.org/cacert-gosigner/pkg/hsm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
commit string
|
||||||
|
date string
|
||||||
|
version string
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultTokenLabel = "localhsm"
|
||||||
|
defaultSignerConfigFile = "ca-hierarchy.json"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
p11Config := &crypto11.Config{}
|
||||||
|
var (
|
||||||
|
showVersion bool
|
||||||
|
signerConfigFile string
|
||||||
|
)
|
||||||
|
|
||||||
|
log.Printf("cacert-gosigner %s (%s) - built %s\n", version, commit, date)
|
||||||
|
|
||||||
|
flag.StringVar(&p11Config.Path, "module", defaultPkcs11Module, "PKCS#11 module")
|
||||||
|
flag.StringVar(&p11Config.TokenLabel, "token", defaultTokenLabel, "PKCS#11 token label")
|
||||||
|
flag.StringVar(&signerConfigFile, "caconfig", defaultSignerConfigFile, "signer configuration file")
|
||||||
|
flag.BoolVar(&showVersion, "version", false, "show version")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if showVersion {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("using PKCS#11 module %s", p11Config.Path)
|
||||||
|
log.Printf("looking for token with label %s", p11Config.TokenLabel)
|
||||||
|
|
||||||
|
configFile, err := os.Open(signerConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not open singer configuration file %s: %v", signerConfigFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
caConfig, err := config.LoadConfiguration(configFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not load CA hierarchy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
getPin(p11Config)
|
||||||
|
|
||||||
|
p11Context, err := crypto11.Configure(p11Config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not configure PKCS#11 library: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func(p11Context *crypto11.Context) {
|
||||||
|
err := p11Context.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("could not close PKCS#11 library context: %v", err)
|
||||||
|
}
|
||||||
|
}(p11Context)
|
||||||
|
|
||||||
|
err = hsm.EnsureCAKeysAndCertificates(p11Context, caConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not ensure CA keys and certificates exist: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPin(p11Config *crypto11.Config) {
|
||||||
|
pin, found := os.LookupEnv("TOKEN_PIN")
|
||||||
|
if !found {
|
||||||
|
log.Printf("environment variable TOKEN_PIN has not been set")
|
||||||
|
if !term.IsTerminal(syscall.Stdin) {
|
||||||
|
log.Fatal("stdin is not a terminal")
|
||||||
|
}
|
||||||
|
fmt.Print("Enter PIN: ")
|
||||||
|
bytePin, err := term.ReadPassword(syscall.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not read PIN")
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
pin = string(bytePin)
|
||||||
|
}
|
||||||
|
p11Config.Pin = strings.TrimSpace(pin)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ThalesIgnite/crypto11"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultPkcs11Module = "/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so"
|
|
||||||
|
|
||||||
func TestStart(t *testing.T) {
|
|
||||||
pkcs11Module, found := os.LookupEnv("PKCS11_LIB")
|
|
||||||
if !found {
|
|
||||||
pkcs11Module = defaultPkcs11Module
|
|
||||||
}
|
|
||||||
p11Context, err := crypto11.Configure(&crypto11.Config{
|
|
||||||
Path: pkcs11Module,
|
|
||||||
TokenLabel: "localhsm",
|
|
||||||
Pin: "123456",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not configure PKCS#11 library: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func(p11Context *crypto11.Context) {
|
|
||||||
err := p11Context.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not close PKCS#11 library context: %v", err)
|
|
||||||
}
|
|
||||||
}(p11Context)
|
|
||||||
|
|
||||||
pair, err := p11Context.FindKeyPair(nil, []byte("rootkey2022"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not find requested key pair: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
serial, err := randomSerialNumber()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
notBefore := time.Now()
|
|
||||||
notAfter := notBefore.AddDate(20, 0, 0)
|
|
||||||
|
|
||||||
certTemplate := &x509.Certificate{
|
|
||||||
SerialNumber: serial,
|
|
||||||
Subject: pkix.Name{
|
|
||||||
Country: []string{"CH"},
|
|
||||||
Organization: []string{"CAcert Inc."},
|
|
||||||
Locality: []string{"Genève"},
|
|
||||||
StreetAddress: []string{"Clos Belmont 2"},
|
|
||||||
PostalCode: []string{"1208"},
|
|
||||||
CommonName: "CAcert ECC Root 2022",
|
|
||||||
},
|
|
||||||
NotBefore: notBefore,
|
|
||||||
NotAfter: notAfter,
|
|
||||||
MaxPathLen: 0,
|
|
||||||
MaxPathLenZero: true,
|
|
||||||
BasicConstraintsValid: true,
|
|
||||||
KeyUsage: x509.KeyUsageCRLSign | x509.KeyUsageCertSign,
|
|
||||||
IsCA: true,
|
|
||||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
|
||||||
}
|
|
||||||
|
|
||||||
certificate, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, pair.Public(), pair)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not create root certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certBlock := &pem.Block{
|
|
||||||
Type: "CERTIFICATE",
|
|
||||||
Bytes: certificate,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.WriteFile("/tmp/test.pem", pem.EncodeToMemory(certBlock), 0o600)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not write certificate: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func randomSerialNumber() (*big.Int, error) {
|
|
||||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
||||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not generate serial number: %w", err)
|
|
||||||
}
|
|
||||||
return serialNumber, nil
|
|
||||||
}
|
|
2
go.mod
2
go.mod
|
@ -5,6 +5,7 @@ go 1.18
|
||||||
require (
|
require (
|
||||||
github.com/ThalesIgnite/crypto11 v1.2.5
|
github.com/ThalesIgnite/crypto11 v1.2.5
|
||||||
github.com/stretchr/testify v1.7.1
|
github.com/stretchr/testify v1.7.1
|
||||||
|
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -13,5 +14,6 @@ require (
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/thales-e-security/pool v0.0.2 // indirect
|
github.com/thales-e-security/pool v0.0.2 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
)
|
)
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -17,6 +17,10 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
|
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
|
||||||
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
|
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
|
||||||
|
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
240
pkg/config/config.go
Normal file
240
pkg/config/config.go
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Settings struct {
|
||||||
|
Organization pkix.Name `json:"organization"`
|
||||||
|
RootYears int `json:"root-years"`
|
||||||
|
IntermediaryYears int `json:"intermediary-years"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Settings) 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)
|
||||||
|
} else {
|
||||||
|
notAfter = notBefore.AddDate(s.IntermediaryYears, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return notBefore, notAfter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Settings) CalculateSubject(cert *CaCertificateEntry) pkix.Name {
|
||||||
|
subject := s.Organization
|
||||||
|
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 (s *Settings) BuildOCSPURL(_ *CaCertificateEntry) string {
|
||||||
|
return "http://ocsp.example.org/"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Settings) BuildCRLUrl(parentCA *CaCertificateEntry) string {
|
||||||
|
return fmt.Sprintf("http://crl.cacert.org/%s.crl", parentCA.Label)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignerConfig struct {
|
||||||
|
Global *Settings `json:"Settings"`
|
||||||
|
CAs []*CaCertificateEntry `json:"CAs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &SignerConfig{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse JSON configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrivateKeyInfo struct {
|
||||||
|
Algorithm x509.PublicKeyAlgorithm
|
||||||
|
EccCurve elliptic.Curve
|
||||||
|
RSABits int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PrivateKeyInfo) UnmarshalJSON(data []byte) error {
|
||||||
|
internalStructure := struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Algorithm string `json:"algorithm"`
|
||||||
|
EccCurve string `json:"ecc-curve,omitempty"`
|
||||||
|
RSABits *int `json:"rsa-bits,omitempty"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
err := json.Unmarshal(data, &internalStructure)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not unmarshal private key info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch internalStructure.Algorithm {
|
||||||
|
case "RSA":
|
||||||
|
p.Algorithm = x509.RSA
|
||||||
|
if internalStructure.RSABits == nil {
|
||||||
|
return errors.New("RSA key length not specified")
|
||||||
|
}
|
||||||
|
p.RSABits = *internalStructure.RSABits
|
||||||
|
case "EC":
|
||||||
|
p.Algorithm = x509.ECDSA
|
||||||
|
p.EccCurve, err = nameToCurve(internalStructure.EccCurve)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported key algorithm %s", internalStructure.Algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PrivateKeyInfo) MarshalJSON() ([]byte, error) {
|
||||||
|
internalStructure := struct {
|
||||||
|
Algorithm string `json:"algorithm"`
|
||||||
|
EccCurve string `json:"ecc-curve,omitempty"`
|
||||||
|
RSABits *int `json:"rsa-bits,omitempty"`
|
||||||
|
}{}
|
||||||
|
switch p.Algorithm {
|
||||||
|
case x509.RSA:
|
||||||
|
internalStructure.Algorithm = "RSA"
|
||||||
|
internalStructure.RSABits = &p.RSABits
|
||||||
|
case x509.ECDSA:
|
||||||
|
internalStructure.Algorithm = "EC"
|
||||||
|
curveName, err := curveToName(p.EccCurve)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
internalStructure.EccCurve = curveName
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(internalStructure)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not marshal private key info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func curveToName(curve elliptic.Curve) (string, error) {
|
||||||
|
switch curve {
|
||||||
|
case elliptic.P224():
|
||||||
|
return "P-224", nil
|
||||||
|
case elliptic.P256():
|
||||||
|
return "P-256", nil
|
||||||
|
case elliptic.P384():
|
||||||
|
return "P-384", nil
|
||||||
|
case elliptic.P521():
|
||||||
|
return "P-521", nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported EC curve %s", curve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nameToCurve maps a curve name to an elliptic curve
|
||||||
|
func nameToCurve(name string) (elliptic.Curve, error) {
|
||||||
|
switch name {
|
||||||
|
case "P-224", "secp224r1":
|
||||||
|
return elliptic.P224(), nil
|
||||||
|
case "P-256", "secp256r1":
|
||||||
|
return elliptic.P256(), nil
|
||||||
|
case "P-384", "secp384r1":
|
||||||
|
return elliptic.P384(), nil
|
||||||
|
case "P-521", "secp521r1":
|
||||||
|
return elliptic.P521(), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported EC curve %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CaCertificateEntry struct {
|
||||||
|
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"`
|
||||||
|
Certificate *x509.Certificate
|
||||||
|
KeyPair crypto.Signer
|
||||||
|
Parent *CaCertificateEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CaCertificateEntry) CertificateFileName() string {
|
||||||
|
return c.Label + ".crt"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CaCertificateEntry) UnmarshalJSON(data []byte) error {
|
||||||
|
var m struct {
|
||||||
|
Label string
|
||||||
|
KeyInfo *PrivateKeyInfo `json:"key-info"`
|
||||||
|
CommonName string `json:"common-name"`
|
||||||
|
SubCAs []*CaCertificateEntry `json:"sub-cas,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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(data, &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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
136
pkg/config/config_test.go
Normal file
136
pkg/config/config_test.go
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrivateKeyInfo_MarshalJSON(t *testing.T) {
|
||||||
|
testData := []struct {
|
||||||
|
name string
|
||||||
|
pkInfo *PrivateKeyInfo
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"RSA",
|
||||||
|
&PrivateKeyInfo{
|
||||||
|
Algorithm: x509.RSA,
|
||||||
|
RSABits: 3072,
|
||||||
|
},
|
||||||
|
`{"algorithm":"RSA","rsa-bits":3072}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ECDSA",
|
||||||
|
&PrivateKeyInfo{
|
||||||
|
Algorithm: x509.ECDSA,
|
||||||
|
EccCurve: elliptic.P224(),
|
||||||
|
},
|
||||||
|
`{"algorithm":"EC","ecc-curve":"P-224"}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range testData {
|
||||||
|
t.Run(item.name, func(t *testing.T) {
|
||||||
|
data, err := json.Marshal(item.pkInfo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, item.expected, string(data))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivateKeyInfo_UnmarshalJSON(t *testing.T) {
|
||||||
|
testData := []struct {
|
||||||
|
name string
|
||||||
|
json string
|
||||||
|
expected *PrivateKeyInfo
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"RSA",
|
||||||
|
`{"label":"mykey","algorithm":"RSA","rsa-bits":2048}`,
|
||||||
|
&PrivateKeyInfo{
|
||||||
|
Algorithm: x509.RSA,
|
||||||
|
RSABits: 2048,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ECDSA",
|
||||||
|
`{"label":"mykey","algorithm":"EC","ecc-curve":"P-521"}`,
|
||||||
|
&PrivateKeyInfo{
|
||||||
|
Algorithm: x509.ECDSA,
|
||||||
|
EccCurve: elliptic.P521(),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no-algorithm",
|
||||||
|
`{"label":"mykey"}`,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"RSA-no-rsa-bits",
|
||||||
|
`{"label":"mykey","algorithm":"RSA"}`,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ECDSA-no-curve",
|
||||||
|
`{"label":"mykey","algorithm":"EC"}`,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range testData {
|
||||||
|
t.Run(item.name, func(t *testing.T) {
|
||||||
|
pkInfo := &PrivateKeyInfo{}
|
||||||
|
err := json.Unmarshal([]byte(item.json), pkInfo)
|
||||||
|
if err != nil {
|
||||||
|
if !item.expectErr {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !item.expectErr {
|
||||||
|
assert.Equal(t, item.expected, pkInfo)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCaCertificateEntry_UnmarshalJSON(t *testing.T) {
|
||||||
|
data := `{
|
||||||
|
"label":"root",
|
||||||
|
"key-info": {
|
||||||
|
"algorithm":"EC",
|
||||||
|
"ecc-curve":"P-521"
|
||||||
|
},
|
||||||
|
"certificate-file":"test.crt",
|
||||||
|
"common-name":"My Little Test Root CA"
|
||||||
|
}`
|
||||||
|
|
||||||
|
entry := CaCertificateEntry{}
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(data), &entry)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, CaCertificateEntry{
|
||||||
|
Label: "root",
|
||||||
|
KeyInfo: &PrivateKeyInfo{
|
||||||
|
Algorithm: x509.ECDSA,
|
||||||
|
EccCurve: elliptic.P521(),
|
||||||
|
},
|
||||||
|
CommonName: "My Little Test Root CA",
|
||||||
|
}, entry)
|
||||||
|
}
|
367
pkg/hsm/hsm.go
Normal file
367
pkg/hsm/hsm.go
Normal file
|
@ -0,0 +1,367 @@
|
||||||
|
package hsm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.cacert.org/cacert-gosigner/pkg/config"
|
||||||
|
"github.com/ThalesIgnite/crypto11"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetRootCACertificate(p11Context *crypto11.Context, settings *config.Settings, caCert *config.CaCertificateEntry) (*x509.Certificate, crypto.Signer, error) {
|
||||||
|
keyPair, err := getKeyPair(p11Context, caCert.Label, caCert.KeyInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
certFile := caCert.CertificateFileName()
|
||||||
|
|
||||||
|
certificate, err := loadCertificate(certFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if certificate != nil && certificateMatches(certificate, keyPair) {
|
||||||
|
return certificate, keyPair, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
notBefore := time.Now()
|
||||||
|
notAfter := notBefore.AddDate(settings.RootYears, 0, 0)
|
||||||
|
|
||||||
|
subject := settings.Organization
|
||||||
|
subject.CommonName = caCert.CommonName
|
||||||
|
|
||||||
|
certificate, err = generateRootCACertificate(
|
||||||
|
certFile,
|
||||||
|
keyPair,
|
||||||
|
&x509.Certificate{
|
||||||
|
Subject: subject,
|
||||||
|
NotBefore: notBefore,
|
||||||
|
NotAfter: notAfter,
|
||||||
|
MaxPathLen: 0,
|
||||||
|
MaxPathLenZero: false,
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
KeyUsage: x509.KeyUsageCRLSign | x509.KeyUsageCertSign,
|
||||||
|
IsCA: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = addCertificate(p11Context, caCert.Label, certificate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificate, keyPair, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetIntermediaryCACertificate(
|
||||||
|
p11Context *crypto11.Context,
|
||||||
|
settings *config.Settings,
|
||||||
|
caCert *config.CaCertificateEntry,
|
||||||
|
) (*x509.Certificate, crypto.Signer, error) {
|
||||||
|
keyPair, err := getKeyPair(p11Context, caCert.Label, caCert.KeyInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
certFile := caCert.CertificateFileName()
|
||||||
|
|
||||||
|
certificate, err := loadCertificate(certFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if certificate != nil && certificateMatches(certificate, keyPair) {
|
||||||
|
return certificate, keyPair, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
notBefore, notAfter := settings.CalculateValidity(caCert)
|
||||||
|
subject := settings.CalculateSubject(caCert)
|
||||||
|
|
||||||
|
certificate, err = generateIntermediaryCACertificate(
|
||||||
|
caCert,
|
||||||
|
keyPair.Public(),
|
||||||
|
&x509.Certificate{
|
||||||
|
Subject: subject,
|
||||||
|
NotBefore: notBefore,
|
||||||
|
NotAfter: notAfter,
|
||||||
|
MaxPathLen: caCert.MaxPathLen,
|
||||||
|
MaxPathLenZero: true,
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IsCA: true,
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||||
|
ExtKeyUsage: caCert.ExtKeyUsage,
|
||||||
|
IssuingCertificateURL: []string{settings.BuildIssuerURL(caCert.Parent)},
|
||||||
|
OCSPServer: []string{settings.BuildOCSPURL(caCert.Parent)},
|
||||||
|
CRLDistributionPoints: []string{settings.BuildCRLUrl(caCert.Parent)},
|
||||||
|
PolicyIdentifiers: []asn1.ObjectIdentifier{
|
||||||
|
// use policy identifiers from http://wiki.cacert.org/OidAllocation
|
||||||
|
{1, 3, 6, 1, 4, 1, 18506, 2, 3, 1}, // 1.3.6.1.4.1.18506.2.3.1 Class3 Policy Version 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = addCertificate(p11Context, caCert.Label, certificate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificate, keyPair, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateIntermediaryCACertificate(caCert *config.CaCertificateEntry, publicKey crypto.PublicKey, template *x509.Certificate) (*x509.Certificate, error) {
|
||||||
|
serial, err := randomSerialNumber()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
template.SerialNumber = serial
|
||||||
|
template.SignatureAlgorithm, err = determineSignatureAlgorithm(caCert.Parent.KeyPair)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
certBytes, err := x509.CreateCertificate(
|
||||||
|
rand.Reader,
|
||||||
|
template,
|
||||||
|
caCert.Parent.Certificate,
|
||||||
|
publicKey,
|
||||||
|
caCert.Parent.KeyPair,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not create intermediary CA certificate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certBlock := &pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: certBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
certFile := caCert.CertificateFileName()
|
||||||
|
|
||||||
|
err = os.WriteFile(certFile, pem.EncodeToMemory(certBlock), 0o600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not write certificate to %s: %w", certFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate, err := x509.ParseCertificate(certBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse generated certificate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addCertificate(p11Context *crypto11.Context, label string, certificate *x509.Certificate) error {
|
||||||
|
objectId, err := randomObjectId()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p11Context.ImportCertificateWithLabel(objectId, []byte(label), certificate)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not import certificate into token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKeyPair(p11Context *crypto11.Context, label string, keyInfo *config.PrivateKeyInfo) (crypto.Signer, error) {
|
||||||
|
keyPair, err := p11Context.FindKeyPair(nil, []byte(label))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not find requested key pair: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyPair != nil {
|
||||||
|
return keyPair, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch keyInfo.Algorithm {
|
||||||
|
case x509.RSA:
|
||||||
|
keyPair, err = generateRSAKeyPair(p11Context, label, keyInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not generate RSA key pair: %w", err)
|
||||||
|
}
|
||||||
|
case x509.ECDSA:
|
||||||
|
keyPair, err = generateECDSAKeyPair(p11Context, label, keyInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not generate ECDSA key pair: %w", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("could not generate private key with label %s with unsupported key algorithm %s", label, keyInfo.Algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyPair, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateECDSAKeyPair(p11Context *crypto11.Context, label string, keyInfo *config.PrivateKeyInfo) (crypto11.Signer, error) {
|
||||||
|
newObjectId, err := randomObjectId()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p11Context.GenerateECDSAKeyPairWithLabel(newObjectId, []byte(label), keyInfo.EccCurve)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRSAKeyPair(p11Context *crypto11.Context, label string, keyInfo *config.PrivateKeyInfo) (crypto11.Signer, error) {
|
||||||
|
newObjectId, err := randomObjectId()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p11Context.GenerateRSAKeyPairWithLabel(newObjectId, []byte(label), keyInfo.RSABits)
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomObjectId() ([]byte, error) {
|
||||||
|
result := make([]byte, 20)
|
||||||
|
_, err := rand.Read(result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not create new random object id: %w", err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRootCACertificate(certFile string, keyPair crypto.Signer, template *x509.Certificate) (*x509.Certificate, error) {
|
||||||
|
serial, err := randomSerialNumber()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
template.SerialNumber = serial
|
||||||
|
template.SignatureAlgorithm, err = determineSignatureAlgorithm(keyPair)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
certBytes, err := x509.CreateCertificate(
|
||||||
|
rand.Reader,
|
||||||
|
template,
|
||||||
|
template,
|
||||||
|
keyPair.Public(),
|
||||||
|
keyPair,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not create root certificate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certBlock := &pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: certBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(certFile, pem.EncodeToMemory(certBlock), 0o600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not write certificate to %s: %w", certFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate, err := x509.ParseCertificate(certBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse generated certificate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func determineSignatureAlgorithm(keyPair crypto.Signer) (x509.SignatureAlgorithm, error) {
|
||||||
|
switch keyPair.Public().(type) {
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
return x509.ECDSAWithSHA256, nil
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
return x509.SHA256WithRSA, nil
|
||||||
|
default:
|
||||||
|
return x509.UnknownSignatureAlgorithm,
|
||||||
|
fmt.Errorf("could not determine signature algorithm for key of type %T", keyPair)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func certificateMatches(certificate *x509.Certificate, key crypto.Signer) bool {
|
||||||
|
switch v := certificate.PublicKey.(type) {
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
if pub, ok := key.Public().(*ecdsa.PublicKey); ok {
|
||||||
|
if v.Equal(pub) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
if pub, ok := key.Public().(*rsa.PublicKey); ok {
|
||||||
|
if v.Equal(pub) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Printf("unsupported public key %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf(
|
||||||
|
"public key from certificate does not match private key: %s != %s",
|
||||||
|
certificate.PublicKey,
|
||||||
|
key.Public(),
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadCertificate(certFile string) (*x509.Certificate, error) {
|
||||||
|
certFileInfo, err := os.Stat(certFile)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, syscall.ENOENT) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("could not get info for %s: %w", certFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !certFileInfo.Mode().IsRegular() {
|
||||||
|
return nil, fmt.Errorf("certificate file %s is not a regular file", certFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
certData, err := os.ReadFile(certFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not read %s: %w", certFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pemData, _ := pem.Decode(certData)
|
||||||
|
if pemData == nil {
|
||||||
|
return nil, fmt.Errorf("no PEM data in %s", certFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pemData.Type != "CERTIFICATE" {
|
||||||
|
return nil, fmt.Errorf("no certificate found in %s", certFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate, err := x509.ParseCertificate(pemData.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse certificate from %s: %w", certFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomSerialNumber() (*big.Int, error) {
|
||||||
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not generate serial number: %w", err)
|
||||||
|
}
|
||||||
|
return serialNumber, nil
|
||||||
|
}
|
62
pkg/hsm/setup.go
Normal file
62
pkg/hsm/setup.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package hsm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.cacert.org/cacert-gosigner/pkg/config"
|
||||||
|
"github.com/ThalesIgnite/crypto11"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EnsureCAKeysAndCertificates(p11Context *crypto11.Context, conf *config.SignerConfig) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for _, root := range conf.CAs {
|
||||||
|
root.Certificate, root.KeyPair, err = GetRootCACertificate(p11Context, conf.Global, root)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("got root CA certificate:\n Subject %s\n Issuer %s\n Valid from %s until %s\n Serial %s",
|
||||||
|
root.Certificate.Subject,
|
||||||
|
root.Certificate.Issuer,
|
||||||
|
root.Certificate.NotBefore,
|
||||||
|
root.Certificate.NotAfter,
|
||||||
|
root.Certificate.SerialNumber)
|
||||||
|
|
||||||
|
for _, intermediary := range root.SubCAs {
|
||||||
|
err := setupIntermediaries(p11Context, conf.Global, intermediary, root)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupIntermediaries(p11Context *crypto11.Context, settings *config.Settings, intermediary, parent *config.CaCertificateEntry) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
intermediary.Parent = parent
|
||||||
|
|
||||||
|
intermediary.Certificate, intermediary.KeyPair, err = GetIntermediaryCACertificate(p11Context, settings, intermediary)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("got intermediary CA certificate:\n Subject %s\n Issuer %s\n Valid from %s until %s\n Serial %s",
|
||||||
|
intermediary.Certificate.Subject,
|
||||||
|
intermediary.Certificate.Issuer,
|
||||||
|
intermediary.Certificate.NotBefore,
|
||||||
|
intermediary.Certificate.NotAfter,
|
||||||
|
intermediary.Certificate.SerialNumber)
|
||||||
|
|
||||||
|
for _, sub := range intermediary.SubCAs {
|
||||||
|
err := setupIntermediaries(p11Context, settings, sub, intermediary)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue