From 2de97714728c1ade5d8901acf410691b48fd84cf Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 4 Jan 2021 20:39:35 +0100 Subject: [PATCH] Start implementation of revoke action --- datastructures/common.go | 4 +- datastructures/signerrequest.go | 89 ++++++---- datastructures/signerresponse.go | 20 ++- go.mod | 1 + shared/shared.go | 10 +- signer/command_processor.go | 286 +++++++++++++++++++++++++++++-- signer/crypto_system.go | 251 +++++++++++++++++++++++++++ signer/port_handler.go | 4 +- signer/protocol_elements.go | 131 ++++++++++++++ 9 files changed, 739 insertions(+), 57 deletions(-) create mode 100644 signer/crypto_system.go create mode 100644 signer/protocol_elements.go diff --git a/datastructures/common.go b/datastructures/common.go index 4ca9031..52743ee 100644 --- a/datastructures/common.go +++ b/datastructures/common.go @@ -2,7 +2,9 @@ package datastructures import "encoding/binary" -func encode24BitLength(data []byte) []byte { +const signerTimeFormat = "010203042006.05" + +func Encode24BitLength(data []byte) []byte { lengthBytes := make([]byte, 4) binary.BigEndian.PutUint32(lengthBytes, uint32(len(data))) return lengthBytes[1:] diff --git a/datastructures/signerrequest.go b/datastructures/signerrequest.go index 89670fa..d1035bc 100644 --- a/datastructures/signerrequest.go +++ b/datastructures/signerrequest.go @@ -3,23 +3,24 @@ package datastructures import ( "bytes" "encoding/binary" + "fmt" "time" "git.cacert.org/cacert-gosigner/shared" ) type SignerRequest struct { - Version uint8 - Action shared.Action - System uint8 - Root uint8 - Configuration uint8 - Parameter1 uint8 - Parameter2 uint16 - Parameter3 uint8 - Content1 string - Content2 string - Content3 string + Version uint8 + Action shared.Action + System shared.CryptoSystemId + Root shared.CryptoSystemRootId + Profile shared.CertificateProfileId + MdAlgorithm shared.MessageDigestAlgorithmId + Days uint16 + Spkac uint8 + Content1 string + Content2 string + Content3 string } func SignerRequestFromData(blockData []byte) (*SignerRequest, error) { @@ -39,42 +40,66 @@ func SignerRequestFromData(blockData []byte) (*SignerRequest, error) { content3 := string(contentBytes[3+content3Offset : 3+content3Offset+content3Length]) return &SignerRequest{ - Version: headerBytes[0], - Action: shared.Action(headerBytes[1]), - System: headerBytes[2], - Root: headerBytes[3], - Configuration: headerBytes[4], - Parameter1: headerBytes[5], - Parameter2: binary.BigEndian.Uint16([]byte{headerBytes[6], headerBytes[7]}), - Parameter3: headerBytes[8], - Content1: content1, - Content2: content2, - Content3: content3, + Version: headerBytes[0], + Action: shared.Action(headerBytes[1]), + System: shared.CryptoSystemId(headerBytes[2]), + Root: shared.CryptoSystemRootId(headerBytes[3]), + Profile: shared.CertificateProfileId(headerBytes[4]), + MdAlgorithm: shared.MessageDigestAlgorithmId(headerBytes[5]), + Days: binary.BigEndian.Uint16([]byte{headerBytes[6], headerBytes[7]}), + Spkac: headerBytes[8], + Content1: content1, + Content2: content2, + Content3: content3, }, nil } -func (r SignerRequest) Serialize() []byte { +func (r *SignerRequest) Serialize() []byte { parameter2Bytes := make([]byte, 2) - binary.BigEndian.PutUint16(parameter2Bytes, r.Parameter2) + binary.BigEndian.PutUint16(parameter2Bytes, r.Days) headerBytes := bytes.Join([][]byte{ - {r.Version, byte(r.Action), r.System, r.Root, r.Configuration, r.Parameter1}, - parameter2Bytes, {r.Parameter3}}, []byte{}) + { + r.Version, + byte(r.Action), + byte(r.System), + byte(r.Root), + byte(r.Profile), + byte(r.MdAlgorithm), + }, + parameter2Bytes, {r.Spkac}}, []byte{}) content1Bytes := []byte(r.Content1) content2Bytes := []byte(r.Content2) content3Bytes := []byte(r.Content3) blockBytes := bytes.Join([][]byte{ - encode24BitLength(headerBytes), headerBytes, - encode24BitLength(content1Bytes), content1Bytes, - encode24BitLength(content2Bytes), content2Bytes, - encode24BitLength(content3Bytes), content3Bytes, + Encode24BitLength(headerBytes), headerBytes, + Encode24BitLength(content1Bytes), content1Bytes, + Encode24BitLength(content2Bytes), content2Bytes, + Encode24BitLength(content3Bytes), content3Bytes, }, []byte{}) - return bytes.Join([][]byte{encode24BitLength(blockBytes), blockBytes}, []byte{}) + return bytes.Join([][]byte{Encode24BitLength(blockBytes), blockBytes}, []byte{}) +} + +func (r *SignerRequest) String() string { + return fmt.Sprintf( + "v:%d %s s:%d r:%d p:%d md:%d days:%d spkac:%d '%s' '%s' '%s'", + r.Version, + r.Action, + r.System, + r.Root, + r.Profile, + r.MdAlgorithm, + r.Days, + r.Spkac, + r.Content1, + r.Content2, + r.Content3, + ) } func NewNulRequest() *SignerRequest { return &SignerRequest{ Version: shared.ProtocolVersion, Action: shared.ActionNul, - Content1: time.Now().UTC().Format("010203042006.05"), + Content1: time.Now().UTC().Format(signerTimeFormat), } } diff --git a/datastructures/signerresponse.go b/datastructures/signerresponse.go index 7c7685f..9bfea36 100644 --- a/datastructures/signerresponse.go +++ b/datastructures/signerresponse.go @@ -62,10 +62,20 @@ func (r SignerResponse) Serialize() []byte { content2Bytes := []byte(r.Content2) content3Bytes := []byte(r.Content3) blockBytes := bytes.Join([][]byte{ - encode24BitLength(headerBytes), headerBytes, - encode24BitLength(content1Bytes), content1Bytes, - encode24BitLength(content2Bytes), content2Bytes, - encode24BitLength(content3Bytes), content3Bytes, + Encode24BitLength(headerBytes), headerBytes, + Encode24BitLength(content1Bytes), content1Bytes, + Encode24BitLength(content2Bytes), content2Bytes, + Encode24BitLength(content3Bytes), content3Bytes, }, []byte{}) - return bytes.Join([][]byte{encode24BitLength(blockBytes), blockBytes}, []byte{}) + return bytes.Join([][]byte{Encode24BitLength(blockBytes), blockBytes}, []byte{}) +} + +func NewNulResponse(version byte) *SignerResponse { + return &SignerResponse{ + Version: version, + Action: shared.ActionNul, + Content1: "", + Content2: "", + Content3: "", + } } diff --git a/go.mod b/go.mod index 84509e6..2615fea 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,6 @@ go 1.15 require ( github.com/sirupsen/logrus v1.7.0 go.bug.st/serial v1.1.1 + golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/shared/shared.go b/shared/shared.go index 1965b8d..637223c 100644 --- a/shared/shared.go +++ b/shared/shared.go @@ -14,7 +14,7 @@ const ( TrailerFieldSize = len(MagicTrailer) ) -type Action uint8 +type Action byte const ( ActionNul = Action(0) @@ -34,3 +34,11 @@ func (a Action) String() string { return "unknown" } } + +type CryptoSystemRootId byte + +type CertificateProfileId byte + +type MessageDigestAlgorithmId byte + +type CryptoSystemId byte diff --git a/signer/command_processor.go b/signer/command_processor.go index 00bc763..97f2d47 100644 --- a/signer/command_processor.go +++ b/signer/command_processor.go @@ -1,8 +1,19 @@ package signer import ( + "bytes" + "crypto/rand" + "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/hex" + "encoding/pem" "errors" "fmt" + "io/ioutil" + "os" + "path" + "time" "unsafe" log "github.com/sirupsen/logrus" @@ -12,28 +23,45 @@ import ( "git.cacert.org/cacert-gosigner/shared" ) +const crlLifetime = time.Hour * 24 * 7 + +var emptyDataSha1 [20]byte + +func init() { + emptyDataSha1 = sha1.Sum([]byte{}) +} + +type CommandProcessorSettings struct { + CABaseDir string + XDeltaPath string + OpenPGPUidEmail string +} + // The CommandProcessor takes parsed protocol data and executes the actual // functionality. type CommandProcessor struct { + Settings *CommandProcessorSettings + CryptoSystems map[shared.CryptoSystemId]*CryptoSystem } // Process the signer request -func (p *CommandProcessor) Process(command datastructures.SignerRequest) ( - response *datastructures.SignerResponse, - err error, +func (p *CommandProcessor) Process(command *datastructures.SignerRequest) ( + *datastructures.SignerResponse, + error, ) { - log.Infof("analyze %+v", command) + log.Infof("process %s", command) + + if command.Version != shared.ProtocolVersion { + return nil, fmt.Errorf("unsupported protocol version %d", command.Version) + } switch command.Action { case shared.ActionNul: - response, err = p.handleNulAction(command) - return + return p.handleNulAction(command) case shared.ActionSign: - response, err = p.handleSignAction(command) - return + return p.handleSignAction(command) case shared.ActionRevoke: - response, err = p.handleRevokeAction(command) - return + return p.handleRevokeAction(command) default: return nil, errors.New(fmt.Sprintf( "unsupported Action 0x%02x %s", @@ -43,7 +71,7 @@ func (p *CommandProcessor) Process(command datastructures.SignerRequest) ( } } -func (*CommandProcessor) handleNulAction(command datastructures.SignerRequest) ( +func (*CommandProcessor) handleNulAction(command *datastructures.SignerRequest) ( *datastructures.SignerResponse, error, ) { @@ -64,13 +92,11 @@ func (*CommandProcessor) handleNulAction(command datastructures.SignerRequest) ( log.Errorf("could not set system time: %v", e1) } - return &datastructures.SignerResponse{ - Version: command.Version, Action: command.Action, - Content1: "", Content2: "", Content3: ""}, nil + return datastructures.NewNulResponse(command.Version), nil } func (p *CommandProcessor) handleSignAction( - command datastructures.SignerRequest, + command *datastructures.SignerRequest, ) ( *datastructures.SignerResponse, error, @@ -80,11 +106,239 @@ func (p *CommandProcessor) handleSignAction( } func (p *CommandProcessor) handleRevokeAction( - command datastructures.SignerRequest, + command *datastructures.SignerRequest, ) ( *datastructures.SignerResponse, error, ) { log.Debugf("handle revoke call: %v", command) + + idSystem, err := p.checkIdentitySystem( + command.System, + command.Root, + command.Profile, + command.MdAlgorithm, + ) + if err != nil { + return nil, err + } + log.Debugf("identified id system: %+v", idSystem) + + switch command.System { + case CsX509: + request := command.Content1 + clientHash := command.Content3 + return p.revokeX509(idSystem, request, clientHash) + default: + return nil, fmt.Errorf("revoke not implemented for crypto system %s", idSystem.System) + } +} + +type IdSystemParameters struct { + System *CryptoSystem + Root *RootCredentials + RootId shared.CryptoSystemRootId + Profile string + MessageDigestAlgorithm x509.SignatureAlgorithm +} + +func (p *CommandProcessor) checkIdentitySystem( + systemId shared.CryptoSystemId, + rootId shared.CryptoSystemRootId, + profileId shared.CertificateProfileId, + algorithmId shared.MessageDigestAlgorithmId, +) (*IdSystemParameters, error) { + cryptoSystem, ok := p.CryptoSystems[systemId] + if !ok { + return nil, fmt.Errorf( + "unsupported crypto system %d", + systemId, + ) + } + root, ok := p.CryptoSystems[systemId].Roots[rootId] + if !ok { + return nil, fmt.Errorf( + "unsupported root certificate %d for crypto system %s", + rootId, + cryptoSystem, + ) + } + profile, ok := p.CryptoSystems[systemId].Profiles[profileId] + if !ok { + return nil, fmt.Errorf( + "invalid CA profile %d for crypto system %s", + profileId, + cryptoSystem, + ) + } + mdAlgorithm, ok := p.CryptoSystems[systemId].DigestAlgorithms[algorithmId] + if !ok { + return nil, fmt.Errorf( + "unsupported digest algorithm %d for crypto system %s", + algorithmId, + cryptoSystem, + ) + } + return &IdSystemParameters{ + System: cryptoSystem, + Root: root, + RootId: rootId, + Profile: profile, + MessageDigestAlgorithm: mdAlgorithm, + }, nil +} + +func (p *CommandProcessor) revokeX509( + system *IdSystemParameters, + request string, + clientHash string, +) (*datastructures.SignerResponse, error) { + clientHashBytes, err := hex.DecodeString(clientHash) + if err != nil { + return nil, fmt.Errorf("could not parse '%s' as hex bytes", clientHash) + } + + log.Debugf("revoke X.509 for root %s (%d)", system.Root, system.RootId) + log.Debugf("hash bytes from client %x", clientHashBytes) + + crlFileName := fmt.Sprintf("revoke-root%d.crl", system.RootId) + stat, err := os.Stat(crlFileName) + if err != nil { + log.Debugf("CRL file %s does not exist", crlFileName) + } else if stat.IsDir() { + return nil, fmt.Errorf("CRL filename %s points to a directory", crlFileName) + } + + currentHash := emptyDataSha1 + if stat != nil { + crlData, err := ioutil.ReadFile(crlFileName) + if err != nil { + log.Warnf("could not read current CRL, assuming empty CRL") + } + currentHash = sha1.Sum(crlData) + } + log.Debugf("hash bytes on signer %x", currentHash) + + crlIsCurrent := false + if bytes.Equal(clientHashBytes, currentHash[:]) { + log.Debug("client CRL hash and current CRL hash on signer match") + err := p.deleteOldCrl(system.RootId, clientHash) + if err != nil { + log.Warnf("could not delete old CRLs: %v", err) + } + if !bytes.Equal(clientHashBytes, emptyDataSha1[:]) { + crlIsCurrent = true + } else { + log.Debugf("client has an empty CRL, the signer too") + } + } + + if len(request) > 0 { + _, err = revokeCertificate(request, system.System, system.RootId) + if err != nil { + return nil, fmt.Errorf("could not revoke certificate / create CRL: %v", err) + } + } + + var crlBytes []byte + crlBytes, err = generateCrl(system.MessageDigestAlgorithm, system.System, system.RootId) + if err != nil { + return nil, fmt.Errorf("could not generate a new CRL for root %d: %v", system.RootId, err) + } + log.Debugf("crlIsCurrent: %v, crlBytes: %d", crlIsCurrent, len(crlBytes)) + + log.Tracef("New CRL\n%s", pem.EncodeToMemory(&pem.Block{ + Type: "X509 CRL", + Bytes: crlBytes, + })) + + err = ioutil.WriteFile(crlFileName, crlBytes, 0644) + if err != nil { + return nil, fmt.Errorf("could not write new CRL to %s: %v", crlFileName, err) + } + return nil, errors.New("not implemented yet") } + +func (p *CommandProcessor) deleteOldCrl(id shared.CryptoSystemRootId, hash string) error { + path.Join(p.Settings.CABaseDir, "currentcrls", fmt.Sprintf("%d", id)) + // TODO: implement + return errors.New("not implemented yet") +} + +func generateCrl( + algorithm x509.SignatureAlgorithm, + system *CryptoSystem, + rootId shared.CryptoSystemRootId, +) ([]byte, error) { + caCertificate, err := system.loadCertificate(rootId) + if err != nil { + return nil, err + } + caPrivateKey, err := system.getPrivateKey(rootId) + if err != nil { + return nil, err + } + certificatesToRevoke, err := system.loadRevokedCertificatesFromDatabase(rootId) + if err != nil { + return nil, err + } + nextCrlNumber, err := system.getNextCRLNumber(rootId) + if err != nil { + return nil, err + } + crlTemplate := &x509.RevocationList{ + SignatureAlgorithm: algorithm, + RevokedCertificates: certificatesToRevoke, + Number: nextCrlNumber, + ThisUpdate: time.Now(), + NextUpdate: time.Now().Add(crlLifetime), + ExtraExtensions: nil, + } + + defer func() { + if err = system.bumpCRLNumber(rootId, nextCrlNumber); err != nil { + log.Errorf("could not bump CRL number: %v", err) + } + }() + + return x509.CreateRevocationList( + rand.Reader, + crlTemplate, + caCertificate, + caPrivateKey, + ) +} + +func revokeCertificate(request string, system *CryptoSystem, rootId shared.CryptoSystemRootId) (*pkix.RevokedCertificate, error) { + pemBlock, _ := pem.Decode([]byte(request)) + if pemBlock.Type != "CERTIFICATE" { + if pemBlock.Type != "CERTIFICATE" { + log.Warnf( + "PEM structure is probably not a certificate. PEM block has type %s", + pemBlock.Type, + ) + log.Trace(request) + } + } + certificate, err := x509.ParseCertificate(pemBlock.Bytes) + if err != nil { + return nil, fmt.Errorf( + "could no parse certificate: %v", + err, + ) + } + return system.recordRevocation(rootId, certificate) +} + +func NewCommandProcessorSettings() *CommandProcessorSettings { + caBasedir, ok := os.LookupEnv("SIGNER_BASEDIR") + if !ok { + caBasedir = "." + } + gpgUidEmail, ok := os.LookupEnv("SIGNER_GPG_KEYRING_DIR") + if !ok { + gpgUidEmail = "gpg@cacert.org" + } + return &CommandProcessorSettings{CABaseDir: caBasedir, OpenPGPUidEmail: gpgUidEmail, XDeltaPath: "/usr/bin/xdelta"} +} diff --git a/signer/crypto_system.go b/signer/crypto_system.go new file mode 100644 index 0000000..c33f779 --- /dev/null +++ b/signer/crypto_system.go @@ -0,0 +1,251 @@ +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 +} diff --git a/signer/port_handler.go b/signer/port_handler.go index 05750e4..9c56e59 100644 --- a/signer/port_handler.go +++ b/signer/port_handler.go @@ -43,7 +43,7 @@ func (p *PortHandler) MainLoop() { return } log.Debugf("received command %v", command) - response, err := p.processor.Process(*command) + response, err := p.processor.Process(command) if err != nil { log.Errorf("error processing command: %v", err) close(p.commandChan) @@ -207,6 +207,6 @@ func NewSignerProcess(port io.ReadWriteCloser) *PortHandler { port: port, errors: errorChan, commandChan: make(chan *datastructures.SignerRequest, 1), - processor: &CommandProcessor{}, + processor: NewCommandProcessor(), } } diff --git a/signer/protocol_elements.go b/signer/protocol_elements.go new file mode 100644 index 0000000..71491b8 --- /dev/null +++ b/signer/protocol_elements.go @@ -0,0 +1,131 @@ +package signer + +import ( + "crypto/x509" + + "git.cacert.org/cacert-gosigner/shared" +) + +const ( + CsX509 shared.CryptoSystemId = 1 + CsOpenPGP shared.CryptoSystemId = 2 +) + +const ( + X509RootDefault shared.CryptoSystemRootId = 0 + X509RootClass3 shared.CryptoSystemRootId = 1 + X509RootClass3s shared.CryptoSystemRootId = 2 + X509Root3 shared.CryptoSystemRootId = 3 + X509Root4 shared.CryptoSystemRootId = 4 + X509Root5 shared.CryptoSystemRootId = 5 +) + +const ( + X509ProfileClient shared.CertificateProfileId = 0 + X509ProfileClientOrg shared.CertificateProfileId = 1 + X509ProfileClientCodesign shared.CertificateProfileId = 2 + X509ProfileClientMachine shared.CertificateProfileId = 3 + X509ProfileClientAds shared.CertificateProfileId = 4 + X509ProfileServer shared.CertificateProfileId = 5 + X509ProfileServerOrg shared.CertificateProfileId = 6 + X509ProfileServerJabber shared.CertificateProfileId = 7 + X509ProfileOCSP shared.CertificateProfileId = 8 + X509ProfileTimestamp shared.CertificateProfileId = 9 + X509ProfileProxy shared.CertificateProfileId = 10 + X509ProfileSubCA shared.CertificateProfileId = 11 +) + +const ( + X509MDDefault shared.MessageDigestAlgorithmId = 0 + X509MDMd5 shared.MessageDigestAlgorithmId = 1 + X509MDSha1 shared.MessageDigestAlgorithmId = 2 + X509MDRipeMD160 shared.MessageDigestAlgorithmId = 3 + X509MDSha256 shared.MessageDigestAlgorithmId = 8 + X509MDSha384 shared.MessageDigestAlgorithmId = 9 + X509MDSha512 shared.MessageDigestAlgorithmId = 10 +) + +const ( + OpenPGPRoot0 shared.CryptoSystemRootId = 0 +) + +const ( + OpenPGPDefaultProfile shared.CertificateProfileId = 0 +) + +const ( + OpenPGPDefaultMD shared.MessageDigestAlgorithmId = 0 +) + +func NewCommandProcessor() *CommandProcessor { + cryptoSystems := make(map[shared.CryptoSystemId]*CryptoSystem) + cryptoSystems[CsX509] = &CryptoSystem{ + Name: "X.509", + Roots: map[shared.CryptoSystemRootId]*RootCredentials{ + X509RootDefault: { + Name: "openssl", + PrivateKeyFile: "/srv/ca/CA/private/ca.key.pem", + CertificateFile: "/srv/ca/CA/ca.crt.pem", + DatabaseFile: "/srv/ca/CA/index.txt", + CRLNumber: "/srv/ca/CA/crlnumber", + }, + X509RootClass3: { + Name: "class3", + PrivateKeyFile: "/srv/ca/class3/private/ca.key.pem", + CertificateFile: "/srv/ca/class3/ca.crt.pem", + DatabaseFile: "/srv/ca/class3/index.txt", + CRLNumber: "/srv/ca/class3/crlnumber", + }, + X509RootClass3s: {Name: "class3s"}, + X509Root3: {Name: "root3"}, + X509Root4: {Name: "root4"}, + X509Root5: {Name: "root5"}, + }, + Profiles: map[shared.CertificateProfileId]string{ + X509ProfileClient: "client", + X509ProfileClientOrg: "client-org", + X509ProfileClientCodesign: "client-codesign", + X509ProfileClientMachine: "client-machine", + X509ProfileClientAds: "client-ads", + X509ProfileServer: "server", + X509ProfileServerOrg: "server-org", + X509ProfileServerJabber: "server-jabber", + X509ProfileOCSP: "ocsp", + X509ProfileTimestamp: "timestamp", + X509ProfileProxy: "proxy", + X509ProfileSubCA: "subca", + }, + // constants for openssl invocations. Should be replaced with + // something more useful + DigestAlgorithms: map[shared.MessageDigestAlgorithmId]x509.SignatureAlgorithm{ + X509MDDefault: x509.SHA256WithRSA, + X509MDMd5: x509.MD5WithRSA, + X509MDSha1: x509.SHA1WithRSA, + X509MDRipeMD160: x509.UnknownSignatureAlgorithm, + X509MDSha256: x509.SHA256WithRSA, + X509MDSha384: x509.SHA384WithRSA, + X509MDSha512: x509.SHA512WithRSA, + }, + } + + cryptoSystems[CsOpenPGP] = &CryptoSystem{ + Name: "OpenPGP", + Roots: map[shared.CryptoSystemRootId]*RootCredentials{ + OpenPGPRoot0: { + Name: "OpenPGP Root", + PrivateKeyFile: "secring0.gpg", + PublicKeyFile: "pubring0.gpg", + }, + }, + Profiles: map[shared.CertificateProfileId]string{ + OpenPGPDefaultProfile: "default", + }, + // constants for gnupg cert-digest-algo parameter. Should be replaced with + // something more useful + DigestAlgorithms: map[shared.MessageDigestAlgorithmId]x509.SignatureAlgorithm{ + OpenPGPDefaultMD: x509.SHA256WithRSA, + }, + } + + return &CommandProcessor{CryptoSystems: cryptoSystems, Settings: NewCommandProcessorSettings()} +}