cacert-gosigner/signer/command_processor.go

381 lines
10 KiB
Go

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/openpgp_ops"
"git.cacert.org/cacert-gosigner/signer/x509_ops"
)
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, 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: %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
if content, err := p.signX509Certificate(idSystem, command.Days, command.Spkac, request, san, subject); err != nil {
return nil, err
} else {
return datastructures.NewSignResponse(command.Version, content), nil
}
case CsOpenPGP:
pubKey := command.Content1
if content, err := p.signOpenpgpKey(idSystem, command.Days, pubKey); err != nil {
return nil, err
} else {
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
if content, err := p.revokeX509(idSystem, request, clientHash); err != nil {
return nil, err
} else {
return datastructures.NewRevokeResponse(command.Version, content), nil
}
default:
return nil, fmt.Errorf("revoke not implemented for crypto system %s", idSystem.System)
}
}
type IdSystemParameters struct {
System *CryptoSystem
Root interface{}
Profile interface{}
MessageDigestAlgorithm interface{}
}
func (s *IdSystemParameters) 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.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 %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 &IdSystemParameters{
System: cryptoSystem,
Root: root,
Profile: profile,
MessageDigestAlgorithm: mdAlgorithm,
}, nil
}
func (p *CommandProcessor) revokeX509(system *IdSystemParameters, request []byte, clientHash []byte) ([]byte, error) {
x509Root := system.Root.(*x509_ops.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: %v", err)
}
}
crlBytes, newHash, err = x509Root.GenerateCrl(signatureAlgorithm)
if err != nil {
return nil, fmt.Errorf("could not generate a new CRL for root %s: %v", 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: %v", err)
}
defer func() {
if err := os.Remove(patchFile.Name()); err != nil {
log.Warnf("could not remove temporary file %s: %v", patchFile.Name(), err)
}
}()
if err = patchFile.Close(); err != nil {
return nil, fmt.Errorf("could not close temporary file: %v", err)
}
buf := bytes.NewBuffer([]byte{})
cmd := exec.Command(p.Settings.XDeltaPath, "delta", oldFile, newFile, patchFile.Name())
cmd.Stdout = buf
cmd.Stderr = buf
err = cmd.Run()
if err != nil {
switch err.(type) {
case *exec.ExitError:
if err.(*exec.ExitError).ExitCode() == 1 {
// xdelta delta exits with status code 1 if a delta has been found
break
}
return nil, fmt.Errorf(
"xdelta command '%s' did not work correctly: %v\noutput was:\n%s",
strings.Join(cmd.Args, " "),
err,
buf.String(),
)
default:
return nil, fmt.Errorf(
"xdelta command '%s' did not work correctly: %v\noutput was:\n%s",
strings.Join(cmd.Args, " "),
err,
buf.String(),
)
}
}
return ioutil.ReadFile(patchFile.Name())
}
func (p *CommandProcessor) signX509Certificate(system *IdSystemParameters, days uint16, spkac uint8, request []byte, san []byte, subject []byte) ([]byte, error) {
x509Root := system.Root.(*x509_ops.Root)
signatureAlgorithm := system.MessageDigestAlgorithm.(x509.SignatureAlgorithm)
profile := system.Profile.(*x509_ops.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,
&x509_ops.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: %v", x509Root, profile, err)
}
return content, nil
}
func (p *CommandProcessor) signOpenpgpKey(system *IdSystemParameters, days uint16, pubKey []byte) ([]byte, error) {
openPgpRoot := system.Root.(*openpgp_ops.OpenPGPRoot)
signatureAlgorithm := system.MessageDigestAlgorithm.(crypto.Hash)
log.Debugf("sign openpgp for root %s", openPgpRoot)
content, err := openPgpRoot.SignPublicKey(pubKey, signatureAlgorithm, days)
if err != nil {
return nil, fmt.Errorf("could not sign openpgp public key with root %s: %v", 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",
}
}