package signer import ( "bytes" "crypto" "crypto/x509" "encoding/hex" "encoding/pem" "errors" "fmt" "io/ioutil" "os" "os/exec" "strings" "unsafe" log "github.com/sirupsen/logrus" "golang.org/x/sys/unix" "git.cacert.org/cacert-gosigner/datastructures" "git.cacert.org/cacert-gosigner/shared" "git.cacert.org/cacert-gosigner/signer/openpgpops" "git.cacert.org/cacert-gosigner/signer/x509ops" ) type CommandProcessorSettings struct { CABaseDir string XDeltaPath string OpenPGPKeyRingDir 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) ( *datastructures.SignerResponse, error, ) { 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: return p.handleNulAction(command) case shared.ActionSign: return p.handleSignAction(command) case shared.ActionRevoke: return p.handleRevokeAction(command) default: return nil, fmt.Errorf( "unsupported Action 0x%02x %s", int(command.Action), command.Action, ) } } func (*CommandProcessor) handleNulAction(command *datastructures.SignerRequest) ( *datastructures.SignerResponse, error, ) { var timeSpec unix.Timespec err := unix.ClockGettime(unix.CLOCK_REALTIME, &timeSpec) if err != nil { log.Errorf("could not get system time: %v", err) } log.Debugf("current system time is %v", timeSpec) // TODO: calculate the actual system time from the payload _, _, e1 := unix.Syscall( unix.SYS_CLOCK_SETTIME, uintptr(unix.CLOCK_REALTIME), uintptr(unsafe.Pointer(&timeSpec)), 0, ) if e1 != 0 { log.Errorf("could not set system time: %v", e1) } return datastructures.NewNulResponse(command.Version), nil } func (p *CommandProcessor) handleSignAction( command *datastructures.SignerRequest, ) ( *datastructures.SignerResponse, error, ) { log.Debugf("handle sign call: %s", command) idSystem, err := p.checkIdentitySystem( command.System, command.Root, command.Profile, command.MdAlgorithm) if err != nil { return nil, err } log.Debugf("identified id system: %s", idSystem) switch command.System { case CsX509: request := command.Content1 san := command.Content2 subject := command.Content3 content, err := p.signX509Certificate(idSystem, command.Days, command.Spkac, request, san, subject) if err != nil { return nil, fmt.Errorf("could not sign X.509 certificate: %w", err) } return datastructures.NewSignResponse(command.Version, content), nil case CsOpenPGP: pubKey := command.Content1 content, err := p.signOpenpgpKey(idSystem, command.Days, pubKey) if err != nil { return nil, fmt.Errorf("could not sign OpenPGP key: %w", err) } return datastructures.NewSignResponse(command.Version, content), nil default: return nil, fmt.Errorf("sign not implemented for crypto system %s", idSystem.System) } } func (p *CommandProcessor) handleRevokeAction( 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 content, err := p.revokeX509(idSystem, request, clientHash) if err != nil { return nil, fmt.Errorf("could not revoke X.509 certificate: %w", err) } return datastructures.NewRevokeResponse(command.Version, content), nil default: return nil, fmt.Errorf("revoke not implemented for crypto system %s", idSystem.System) } } type IDSystemParams struct { System *CryptoSystem Root interface{} Profile interface{} MessageDigestAlgorithm interface{} } func (s *IDSystemParams) String() string { return fmt.Sprintf("%s r:%s p:%s m:%s", s.System, s.Root, s.Profile, s.MessageDigestAlgorithm) } func (p *CommandProcessor) checkIdentitySystem( systemID shared.CryptoSystemID, rootID shared.CryptoSystemRootID, profileID shared.CertificateProfileID, algorithmID shared.SignatureAlgorithmID, ) (*IDSystemParams, 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 %d for crypto system %s", rootID, cryptoSystem, ) } profile, ok := p.CryptoSystems[systemID].Profiles[profileID] if !ok { return nil, fmt.Errorf( "invalid 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 &IDSystemParams{ System: cryptoSystem, Root: root, Profile: profile, MessageDigestAlgorithm: mdAlgorithm, }, nil } func (p *CommandProcessor) revokeX509(system *IDSystemParams, request []byte, clientHash []byte) ([]byte, error) { x509Root := system.Root.(*x509ops.Root) signatureAlgorithm := system.MessageDigestAlgorithm.(x509.SignatureAlgorithm) log.Debugf("revoke X.509 for root %s", x509Root) log.Debugf("hash bytes from client %x", clientHash) var ( err error crlBytes []byte newHash *[20]byte ) if len(request) > 0 { _, err = x509Root.RevokeCertificate(request) if err != nil { return nil, fmt.Errorf("could not revoke certificate / create CRL: %w", err) } } crlBytes, newHash, err = x509Root.GenerateCrl(signatureAlgorithm) if err != nil { return nil, fmt.Errorf("could not generate a new CRL for root %s: %w", x509Root, err) } log.Debugf("crlBytes: %d", len(crlBytes)) var content []byte oldCrlFile := x509Root.GetCrlFileName(string(clientHash)) newCrlFile := x509Root.GetCrlFileName(hex.EncodeToString(newHash[:])) statOld, oldErr := os.Stat(oldCrlFile) statNew, newErr := os.Stat(newCrlFile) if oldErr == nil && newErr == nil && !statOld.IsDir() && !statNew.IsDir() { content, err = p.buildXDelta(oldCrlFile, newCrlFile) if err != nil { log.Warnf("could not generate xdelta: %v", err) } log.Tracef("xdelta produced %d bytes", len(content)) } if content == nil { content = pem.EncodeToMemory(&pem.Block{ Type: "X509 CRL", Bytes: crlBytes, }) log.Tracef("sending %d PEM CRL bytes", len(content)) } return content, nil } func (p *CommandProcessor) buildXDelta(oldFile string, newFile string) ([]byte, error) { patchFile, err := ioutil.TempFile(os.TempDir(), "*.patch") if err != nil { return nil, fmt.Errorf("could not create temporary file for patch: %w", err) } patchName := patchFile.Name() defer func() { if err := os.Remove(patchName); err != nil { log.Warnf("could not remove temporary file %s: %v", patchName, err) } }() if err = patchFile.Close(); err != nil { return nil, fmt.Errorf("could not close temporary file: %w", err) } buf := bytes.NewBuffer([]byte{}) // #nosec G204 no parameters are based on user input cmd := exec.Command(p.Settings.XDeltaPath, "delta", oldFile, newFile, patchName) cmd.Stdout = buf cmd.Stderr = buf err = cmd.Run() if err != nil { var e *exec.ExitError if !errors.As(err, &e) || e.ExitCode() != 1 { // xdelta delta exits with status code 1 if a delta has been found return nil, fmt.Errorf( "xdelta command '%s' did not work correctly: %w\noutput was:\n%s", strings.Join(cmd.Args, " "), err, buf.String(), ) } } return ioutil.ReadFile(patchName) } func (p *CommandProcessor) signX509Certificate( system *IDSystemParams, days uint16, spkac uint8, request []byte, san []byte, subject []byte, ) ([]byte, error) { x509Root := system.Root.(*x509ops.Root) signatureAlgorithm := system.MessageDigestAlgorithm.(x509.SignatureAlgorithm) profile := system.Profile.(*x509ops.Profile) log.Debugf( "sign X.509 certificate for root %s using profile %s and signature algorithm %s", x509Root, profile, signatureAlgorithm, ) log.Debugf( "client wants %d days spkac is %v, requested subject '%s' and subjectAlternativeNames '%s'", days, spkac == 1, subject, san, ) log.Tracef("the following CSR should be signed\n%s", request) content, err := x509Root.SignCertificate( profile, signatureAlgorithm, &x509ops.SigningRequestParameters{ Request: request, Subject: subject, SubjectAlternativeNames: san, Days: days, IsSpkac: spkac == 1, }, ) if err != nil { return nil, fmt.Errorf("could not sign X.509 CSR with root %s and profile %s: %w", x509Root, profile, err) } return content, nil } func (p *CommandProcessor) signOpenpgpKey(system *IDSystemParams, days uint16, pubKey []byte) ([]byte, error) { openPgpRoot := system.Root.(*openpgpops.OpenPGPRoot) signatureAlgorithm := system.MessageDigestAlgorithm.(crypto.Hash) log.Debugf("sign openpgpops for root %s", openPgpRoot) content, err := openPgpRoot.SignPublicKey(pubKey, signatureAlgorithm, days) if err != nil { return nil, fmt.Errorf("could not sign openpgpops public key with root %s: %w", openPgpRoot, err) } return content, nil } func NewCommandProcessorSettings() *CommandProcessorSettings { caBasedir, ok := os.LookupEnv("SIGNER_BASEDIR") if !ok { caBasedir = "." } gpgKeyringDir, ok := os.LookupEnv("SIGNER_GPG_KEYRING_DIR") if !ok { gpgKeyringDir = "." } gpgUIDEmail, ok := os.LookupEnv("SIGNER_GPG_ID") if !ok { gpgUIDEmail = "gpg@cacert.org" } return &CommandProcessorSettings{ CABaseDir: caBasedir, OpenPGPKeyRingDir: gpgKeyringDir, OpenPGPUidEmail: gpgUIDEmail, XDeltaPath: "/usr/bin/xdelta", } }