344 lines
8.7 KiB
Go
344 lines
8.7 KiB
Go
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"}
|
|
}
|