251 lines
7.4 KiB
Go
251 lines
7.4 KiB
Go
package signer
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math/big"
|
|
"os"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"git.cacert.org/cacert-gosigner/shared"
|
|
)
|
|
|
|
type RootCredentials struct {
|
|
Name string
|
|
PrivateKeyFile string
|
|
PublicKeyFile string
|
|
CertificateFile string
|
|
DatabaseFile string
|
|
CRLNumber string
|
|
}
|
|
|
|
type CryptoSystem struct {
|
|
Name string
|
|
Roots map[shared.CryptoSystemRootId]*RootCredentials
|
|
Profiles map[shared.CertificateProfileId]string
|
|
DigestAlgorithms map[shared.MessageDigestAlgorithmId]x509.SignatureAlgorithm
|
|
}
|
|
|
|
func (system CryptoSystem) String() string {
|
|
return system.Name
|
|
}
|
|
|
|
func (system CryptoSystem) loadCertificate(rootId shared.CryptoSystemRootId) (*x509.Certificate, error) {
|
|
certificateFile := system.Roots[rootId].CertificateFile
|
|
pemBytes, err := ioutil.ReadFile(certificateFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"could not load certificate %s: %v",
|
|
certificateFile,
|
|
err,
|
|
)
|
|
}
|
|
pemBlock, _ := pem.Decode(pemBytes)
|
|
if pemBlock.Type != "CERTIFICATE" {
|
|
log.Warnf(
|
|
"PEM in %s is probably not a certificate. PEM block has type %s",
|
|
certificateFile,
|
|
pemBlock.Type,
|
|
)
|
|
}
|
|
certificate, err := x509.ParseCertificate(pemBlock.Bytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"could no parse certificate from %s: %v",
|
|
certificateFile,
|
|
err,
|
|
)
|
|
}
|
|
return certificate, nil
|
|
}
|
|
|
|
func (system CryptoSystem) getPrivateKey(rootId shared.CryptoSystemRootId) (crypto.Signer, error) {
|
|
privateKeyFile := system.Roots[rootId].PrivateKeyFile
|
|
pemBytes, err := ioutil.ReadFile(privateKeyFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"could not load private key %s: %v",
|
|
privateKeyFile,
|
|
err,
|
|
)
|
|
}
|
|
pemBlock, _ := pem.Decode(pemBytes)
|
|
if pemBlock.Type != "PRIVATE KEY" {
|
|
log.Warnf(
|
|
"PEM in %s is probably not a private key. PEM block has type %s",
|
|
privateKeyFile,
|
|
pemBlock.Type,
|
|
)
|
|
}
|
|
privateKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"could no parse private key from %s: %v",
|
|
privateKeyFile,
|
|
err,
|
|
)
|
|
}
|
|
return privateKey.(*rsa.PrivateKey), nil
|
|
}
|
|
|
|
func (system *CryptoSystem) getNextCRLNumber(rootId shared.CryptoSystemRootId) (*big.Int, error) {
|
|
crlNumberFile := system.Roots[rootId].CRLNumber
|
|
_, err := os.Stat(crlNumberFile)
|
|
if err != nil {
|
|
log.Warnf("CRL number file %s does not exist: %v", crlNumberFile, err)
|
|
return big.NewInt(1), nil
|
|
}
|
|
data, err := ioutil.ReadFile(crlNumberFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not read CRL number file %s", crlNumberFile)
|
|
}
|
|
result, err := stringAsBigInt(data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not parse content of %s as CRL number: %v", crlNumberFile, err)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (system *CryptoSystem) bumpCRLNumber(rootId shared.CryptoSystemRootId, current *big.Int) error {
|
|
serial := current.Int64() + 1
|
|
crlNumberFile := system.Roots[rootId].CRLNumber
|
|
outFile, err := ioutil.TempFile(path.Dir(crlNumberFile), "*.txt")
|
|
defer func() { _ = outFile.Close() }()
|
|
|
|
_, err = outFile.WriteString(fmt.Sprintf(
|
|
"%s\n",
|
|
strings.ToUpper(strconv.FormatInt(serial, 16)),
|
|
))
|
|
if err != nil {
|
|
return fmt.Errorf("could not write new CRL number %d to %s: %v", serial, outFile.Name(), err)
|
|
}
|
|
|
|
if err = outFile.Close(); err != nil {
|
|
return fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err)
|
|
}
|
|
if err = os.Rename(crlNumberFile, fmt.Sprintf("%s.old", crlNumberFile)); err != nil {
|
|
return fmt.Errorf("could not rename %s to %s.old: %v", crlNumberFile, crlNumberFile, err)
|
|
}
|
|
if err = os.Rename(outFile.Name(), crlNumberFile); err != nil {
|
|
return fmt.Errorf("could not rename %s to %s: %v", outFile.Name(), crlNumberFile, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func stringAsBigInt(data []byte) (*big.Int, error) {
|
|
dataString := strings.TrimSpace(string(data))
|
|
parseInt, err := strconv.ParseInt(dataString, 16, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not parse %s as big int: %v", dataString, err)
|
|
}
|
|
return big.NewInt(parseInt), nil
|
|
}
|
|
|
|
func (system *CryptoSystem) loadRevokedCertificatesFromDatabase(rootId shared.CryptoSystemRootId) ([]pkix.RevokedCertificate, error) {
|
|
databaseFile := system.Roots[rootId].DatabaseFile
|
|
_, err := os.Stat(databaseFile)
|
|
if err != nil {
|
|
log.Warnf("openssl certificate database file %s does not exist: %v", databaseFile, err)
|
|
return []pkix.RevokedCertificate{}, nil
|
|
}
|
|
file, err := os.Open(databaseFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", databaseFile, err)
|
|
}
|
|
defer func() { _ = file.Close() }()
|
|
|
|
result := make([]pkix.RevokedCertificate, 0)
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := strings.Split(scanner.Text(), "\t")
|
|
if line[0] == "R" {
|
|
serialNumber, err := stringAsBigInt([]byte(line[3]))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not parse serial number %s as big int: %v", line[3], err)
|
|
}
|
|
revokeTs, err := strconv.ParseInt(line[2][:len(line[2])-1], 10, 64)
|
|
result = append(result, pkix.RevokedCertificate{
|
|
SerialNumber: serialNumber,
|
|
RevocationTime: time.Unix(revokeTs, 0),
|
|
Extensions: nil,
|
|
})
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (system *CryptoSystem) recordRevocation(rootId shared.CryptoSystemRootId, certificate *x509.Certificate) (*pkix.RevokedCertificate, error) {
|
|
databaseFile := system.Roots[rootId].DatabaseFile
|
|
_, err := os.Stat(databaseFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("openssl certificate database file %s does not exist: %v", databaseFile, err)
|
|
}
|
|
inFile, err := os.Open(databaseFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", databaseFile, err)
|
|
}
|
|
defer func() { _ = inFile.Close() }()
|
|
|
|
outFile, err := ioutil.TempFile(path.Dir(databaseFile), "*.txt")
|
|
defer func() { _ = outFile.Close() }()
|
|
|
|
scanner := bufio.NewScanner(inFile)
|
|
writer := bufio.NewWriter(outFile)
|
|
|
|
found := false
|
|
revocationTime := time.Now()
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
parts := strings.Split(line, "\t")
|
|
serialNumber, err := stringAsBigInt([]byte(parts[3]))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not parse serial number %s as big int: %v", parts[3], err)
|
|
}
|
|
if serialNumber == certificate.SerialNumber {
|
|
line = strings.Join(
|
|
[]string{"R", parts[1], strconv.FormatInt(revocationTime.Unix(), 10) + "Z", parts[3], parts[4]},
|
|
"\t",
|
|
)
|
|
found = true
|
|
}
|
|
if _, err = writer.WriteString(fmt.Sprintf("%s\n", line)); err != nil {
|
|
return nil, fmt.Errorf("could not write '%s' to %s: %v", line, outFile.Name(), err)
|
|
}
|
|
}
|
|
|
|
if err = outFile.Close(); err != nil {
|
|
return nil, fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err)
|
|
}
|
|
if err = inFile.Close(); err != nil {
|
|
return nil, fmt.Errorf("could not close %s: %v", databaseFile, err)
|
|
}
|
|
|
|
if err = os.Rename(databaseFile, fmt.Sprintf("%s.old", databaseFile)); err != nil {
|
|
return nil, fmt.Errorf("could not rename %s to %s.old: %v", databaseFile, databaseFile, err)
|
|
}
|
|
|
|
if err = os.Rename(outFile.Name(), databaseFile); err != nil {
|
|
return nil, fmt.Errorf("could not rename temporary file %s to %s: %v", outFile.Name(), databaseFile, err)
|
|
}
|
|
|
|
if !found {
|
|
log.Warnf("entry not found in database")
|
|
}
|
|
|
|
return &pkix.RevokedCertificate{
|
|
SerialNumber: certificate.SerialNumber,
|
|
RevocationTime: revocationTime,
|
|
}, nil
|
|
}
|