Refactor code structure
Move X.509 and Openpgp operations into custom packages. Implement more robust input reading. Do not convert []byte to string unnecessarily. Finish implementation of X.509 CRL creation.
This commit is contained in:
parent
2de9771472
commit
9f0916b14a
9 changed files with 715 additions and 493 deletions
|
|
@ -2,18 +2,15 @@ package signer
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
|
@ -21,20 +18,14 @@ import (
|
|||
|
||||
"git.cacert.org/cacert-gosigner/datastructures"
|
||||
"git.cacert.org/cacert-gosigner/shared"
|
||||
"git.cacert.org/cacert-gosigner/signer/x509_ops"
|
||||
)
|
||||
|
||||
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
|
||||
CABaseDir string
|
||||
XDeltaPath string
|
||||
OpenPGPKeyRingDir string
|
||||
OpenPGPUidEmail string
|
||||
}
|
||||
|
||||
// The CommandProcessor takes parsed protocol data and executes the actual
|
||||
|
|
@ -102,7 +93,36 @@ func (p *CommandProcessor) handleSignAction(
|
|||
error,
|
||||
) {
|
||||
log.Debugf("handle sign call: %v", command)
|
||||
return nil, errors.New("not implemented yet")
|
||||
|
||||
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
|
||||
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(
|
||||
|
|
@ -128,7 +148,11 @@ func (p *CommandProcessor) handleRevokeAction(
|
|||
case CsX509:
|
||||
request := command.Content1
|
||||
clientHash := command.Content3
|
||||
return p.revokeX509(idSystem, request, clientHash)
|
||||
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)
|
||||
}
|
||||
|
|
@ -136,10 +160,9 @@ func (p *CommandProcessor) handleRevokeAction(
|
|||
|
||||
type IdSystemParameters struct {
|
||||
System *CryptoSystem
|
||||
Root *RootCredentials
|
||||
RootId shared.CryptoSystemRootId
|
||||
Profile string
|
||||
MessageDigestAlgorithm x509.SignatureAlgorithm
|
||||
Root interface{}
|
||||
Profile interface{}
|
||||
MessageDigestAlgorithm interface{}
|
||||
}
|
||||
|
||||
func (p *CommandProcessor) checkIdentitySystem(
|
||||
|
|
@ -158,7 +181,7 @@ func (p *CommandProcessor) checkIdentitySystem(
|
|||
root, ok := p.CryptoSystems[systemId].Roots[rootId]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"unsupported root certificate %d for crypto system %s",
|
||||
"unsupported root %d for crypto system %s",
|
||||
rootId,
|
||||
cryptoSystem,
|
||||
)
|
||||
|
|
@ -166,7 +189,7 @@ func (p *CommandProcessor) checkIdentitySystem(
|
|||
profile, ok := p.CryptoSystems[systemId].Profiles[profileId]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"invalid CA profile %d for crypto system %s",
|
||||
"invalid profile %d for crypto system %s",
|
||||
profileId,
|
||||
cryptoSystem,
|
||||
)
|
||||
|
|
@ -182,153 +205,111 @@ func (p *CommandProcessor) checkIdentitySystem(
|
|||
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)
|
||||
}
|
||||
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 (%d)", system.Root, system.RootId)
|
||||
log.Debugf("hash bytes from client %x", clientHashBytes)
|
||||
log.Debugf("revoke X.509 for root %s", x509Root)
|
||||
log.Debugf("hash bytes from client %x", clientHash)
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
var (
|
||||
err error
|
||||
crlBytes []byte
|
||||
newHash *[20]byte
|
||||
)
|
||||
|
||||
if len(request) > 0 {
|
||||
_, err = revokeCertificate(request, system.System, system.RootId)
|
||||
_, err = x509Root.RevokeCertificate(request)
|
||||
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)
|
||||
crlBytes, newHash, err = x509Root.GenerateCrl(signatureAlgorithm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not generate a new CRL for root %d: %v", system.RootId, err)
|
||||
return nil, fmt.Errorf("could not generate a new CRL for root %s: %v", x509Root, err)
|
||||
}
|
||||
log.Debugf("crlIsCurrent: %v, crlBytes: %d", crlIsCurrent, len(crlBytes))
|
||||
log.Debugf("crlBytes: %d", len(crlBytes))
|
||||
|
||||
log.Tracef("New CRL\n%s", pem.EncodeToMemory(&pem.Block{
|
||||
Type: "X509 CRL",
|
||||
Bytes: crlBytes,
|
||||
}))
|
||||
var content []byte
|
||||
oldCrlFile := x509Root.GetCrlFileName(string(clientHash))
|
||||
newCrlFile := x509Root.GetCrlFileName(hex.EncodeToString(newHash[:]))
|
||||
|
||||
err = ioutil.WriteFile(crlFileName, crlBytes, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not write new CRL to %s: %v", crlFileName, err)
|
||||
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("xdelte 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 nil, errors.New("not implemented yet")
|
||||
return content, nil
|
||||
}
|
||||
|
||||
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)
|
||||
func (p *CommandProcessor) buildXDelta(oldFile string, newFile string) ([]byte, error) {
|
||||
patchFile, err := ioutil.TempFile(os.TempDir(), "*.patch")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("could not create temporary file for patch: %v", 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)
|
||||
if err := os.Remove(patchFile.Name()); err != nil {
|
||||
log.Warnf("could not remove temporary file %s: %v", patchFile.Name(), 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,
|
||||
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(),
|
||||
)
|
||||
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)
|
||||
return ioutil.ReadFile(patchFile.Name())
|
||||
}
|
||||
|
||||
func (p *CommandProcessor) signX509Certificate(system *IdSystemParameters, days uint16, spkac uint8, request []byte, san []byte, subject []byte) ([]byte, error) {
|
||||
return nil, errors.New("signX509Certificate is not implemented yet")
|
||||
}
|
||||
|
||||
func (p *CommandProcessor) signOpenpgpKey(system *IdSystemParameters, days uint16, pubKey []byte) ([]byte, error) {
|
||||
return nil, errors.New("signOpenpgpKey is not implemented yet")
|
||||
}
|
||||
|
||||
func NewCommandProcessorSettings() *CommandProcessorSettings {
|
||||
|
|
@ -336,9 +317,18 @@ func NewCommandProcessorSettings() *CommandProcessorSettings {
|
|||
if !ok {
|
||||
caBasedir = "."
|
||||
}
|
||||
gpgUidEmail, ok := os.LookupEnv("SIGNER_GPG_KEYRING_DIR")
|
||||
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, OpenPGPUidEmail: gpgUidEmail, XDeltaPath: "/usr/bin/xdelta"}
|
||||
return &CommandProcessorSettings{
|
||||
CABaseDir: caBasedir,
|
||||
OpenPGPKeyRingDir: gpgKeyringDir,
|
||||
OpenPGPUidEmail: gpgUidEmail,
|
||||
XDeltaPath: "/usr/bin/xdelta",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue