package hsm import ( "context" "crypto" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/asn1" "encoding/pem" "errors" "fmt" "log" "math/big" "os" "syscall" "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) 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) 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 }