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 }