cacert-gosigner/pkg/hsm/hsm.go

462 lines
11 KiB
Go

package hsm
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"log"
"math/big"
"os"
"syscall"
"time"
"github.com/ThalesIgnite/crypto11"
"git.cacert.org/cacert-gosigner/pkg/config"
)
var (
// 1.3.6.1.4.1.18506.2.3.1 Class3 Policy Version 1
oidCAcertClass3PolicyV1 = []int{1, 3, 6, 1, 4, 1, 18506, 2, 3, 1}
errKeyGenerationRefused = errors.New("not in setup mode, refusing key generation")
errCertificateGenerationRefused = errors.New("not in setup mode, refusing certificate generation")
)
func GetRootCACertificate(ctx context.Context, label string) (*x509.Certificate, error) {
var (
certificate *x509.Certificate
keyPair crypto.Signer
)
sc := GetSignerConfig(ctx)
caCert, err := sc.GetCADefinition(label)
if err != nil {
return nil, err
}
if !caCert.IsRoot() {
return nil, fmt.Errorf("CA definition %s is not a root CA definition", label)
}
certFile := sc.CertificateFileName(label)
certificate, err = loadCertificate(certFile)
if err != nil {
return nil, err
}
if certificate != nil && !IsSetupMode(ctx) {
caCert.Certificate = certificate
return certificate, nil
}
keyPair, err = getKeyPair(ctx, label, caCert.KeyInfo)
if err != nil {
return nil, err
}
if certificate != nil && certificateMatches(certificate, keyPair) {
caCert.Certificate, caCert.KeyPair = certificate, keyPair
return certificate, nil
}
if !IsSetupMode(ctx) {
return nil, errCertificateGenerationRefused
}
notBefore, notAfter := sc.CalculateValidity(caCert, time.Now())
subject := sc.CalculateSubject(caCert)
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, err
}
p11Context, err := GetP11Context(ctx, caCert)
if err != nil {
return nil, err
}
err = addCertificate(p11Context, label, certificate)
if err != nil {
return nil, err
}
caCert.Certificate, caCert.KeyPair = certificate, keyPair
return certificate, nil
}
func GetIntermediaryCACertificate(ctx context.Context, certLabel string) (*x509.Certificate, error) {
var (
certificate *x509.Certificate
keyPair crypto.Signer
)
sc := GetSignerConfig(ctx)
caCert, err := sc.GetCADefinition(certLabel)
if err != nil {
return nil, err
}
if caCert.IsRoot() {
return nil, fmt.Errorf(
"CA definition %s is a root CA definition, intermediary expected",
certLabel,
)
}
keyPair, err = getKeyPair(ctx, certLabel, caCert.KeyInfo)
if err != nil {
return nil, err
}
certFile := sc.CertificateFileName(certLabel)
certificate, err = loadCertificate(certFile)
if err != nil {
return nil, err
}
if certificate != nil && certificateMatches(certificate, keyPair) {
caCert.Certificate, caCert.KeyPair = certificate, keyPair
return certificate, nil
}
if !IsSetupMode(ctx) {
return nil, errCertificateGenerationRefused
}
notBefore, notAfter := sc.CalculateValidity(caCert, time.Now())
subject := sc.CalculateSubject(caCert)
certificate, err = generateIntermediaryCACertificate(
sc,
certLabel,
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{sc.BuildIssuerURL(caCert)},
OCSPServer: []string{sc.BuildOCSPURL(caCert)},
CRLDistributionPoints: []string{sc.BuildCRLUrl(caCert)},
PolicyIdentifiers: []asn1.ObjectIdentifier{
// use policy identifiers from http://wiki.cacert.org/OidAllocation
oidCAcertClass3PolicyV1,
},
},
)
if err != nil {
return nil, err
}
p11Context, err := GetP11Context(ctx, caCert)
if err != nil {
return nil, err
}
err = addCertificate(p11Context, certLabel, certificate)
if err != nil {
return nil, err
}
caCert.Certificate, caCert.KeyPair = certificate, keyPair
return certificate, nil
}
func generateIntermediaryCACertificate(config *config.SignerConfig, certLabel string, publicKey crypto.PublicKey, template *x509.Certificate) (*x509.Certificate, error) {
parent, err := config.GetParentCA(certLabel)
if err != nil {
return nil, err
}
serial, err := randomSerialNumber()
if err != nil {
return nil, err
}
template.SerialNumber = serial
template.SignatureAlgorithm, err = determineSignatureAlgorithm(parent.KeyPair)
if err != nil {
return nil, err
}
certBytes, err := x509.CreateCertificate(
rand.Reader,
template,
parent.Certificate,
publicKey,
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 := config.CertificateFileName(certLabel)
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(ctx context.Context, label string, keyInfo *config.PrivateKeyInfo) (crypto.Signer, error) {
sc := GetSignerConfig(ctx)
cert, err := sc.GetCADefinition(label)
if err != nil {
return nil, err
}
if cert.KeyPair != nil {
return cert.KeyPair, nil
}
p11Context, err := GetP11Context(ctx, cert)
if err != nil {
return nil, err
}
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
}
if !IsSetupMode(ctx) {
return nil, errKeyGenerationRefused
}
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
}