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" "golang.org/x/sys/unix" "git.cacert.org/cacert-gosigner/datastructures" "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) ( *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, errors.New(fmt.Sprintf( "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: %v", command) return nil, errors.New("not implemented yet") } 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 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"} }