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
|
@ -18,9 +18,9 @@ type SignerRequest struct {
|
||||||
MdAlgorithm shared.MessageDigestAlgorithmId
|
MdAlgorithm shared.MessageDigestAlgorithmId
|
||||||
Days uint16
|
Days uint16
|
||||||
Spkac uint8
|
Spkac uint8
|
||||||
Content1 string
|
Content1 []byte
|
||||||
Content2 string
|
Content2 []byte
|
||||||
Content3 string
|
Content3 []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func SignerRequestFromData(blockData []byte) (*SignerRequest, error) {
|
func SignerRequestFromData(blockData []byte) (*SignerRequest, error) {
|
||||||
|
@ -29,15 +29,15 @@ func SignerRequestFromData(blockData []byte) (*SignerRequest, error) {
|
||||||
|
|
||||||
contentBytes := blockData[3+headerLength:]
|
contentBytes := blockData[3+headerLength:]
|
||||||
content1Length := Decode24BitLength(contentBytes[0:3])
|
content1Length := Decode24BitLength(contentBytes[0:3])
|
||||||
content1 := string(contentBytes[3 : 3+content1Length])
|
content1 := contentBytes[3 : 3+content1Length]
|
||||||
|
|
||||||
content2Offset := 3 + content1Length
|
content2Offset := 3 + content1Length
|
||||||
content2Length := Decode24BitLength(contentBytes[content2Offset : content2Offset+3])
|
content2Length := Decode24BitLength(contentBytes[content2Offset : content2Offset+3])
|
||||||
content2 := string(contentBytes[3+content2Offset : 3+content2Offset+content2Length])
|
content2 := contentBytes[3+content2Offset : 3+content2Offset+content2Length]
|
||||||
|
|
||||||
content3Offset := 3 + content2Offset + content2Length
|
content3Offset := 3 + content2Offset + content2Length
|
||||||
content3Length := Decode24BitLength(contentBytes[content3Offset : content3Offset+3])
|
content3Length := Decode24BitLength(contentBytes[content3Offset : content3Offset+3])
|
||||||
content3 := string(contentBytes[3+content3Offset : 3+content3Offset+content3Length])
|
content3 := contentBytes[3+content3Offset : 3+content3Offset+content3Length]
|
||||||
|
|
||||||
return &SignerRequest{
|
return &SignerRequest{
|
||||||
Version: headerBytes[0],
|
Version: headerBytes[0],
|
||||||
|
@ -67,9 +67,9 @@ func (r *SignerRequest) Serialize() []byte {
|
||||||
byte(r.MdAlgorithm),
|
byte(r.MdAlgorithm),
|
||||||
},
|
},
|
||||||
parameter2Bytes, {r.Spkac}}, []byte{})
|
parameter2Bytes, {r.Spkac}}, []byte{})
|
||||||
content1Bytes := []byte(r.Content1)
|
content1Bytes := r.Content1
|
||||||
content2Bytes := []byte(r.Content2)
|
content2Bytes := r.Content2
|
||||||
content3Bytes := []byte(r.Content3)
|
content3Bytes := r.Content3
|
||||||
blockBytes := bytes.Join([][]byte{
|
blockBytes := bytes.Join([][]byte{
|
||||||
Encode24BitLength(headerBytes), headerBytes,
|
Encode24BitLength(headerBytes), headerBytes,
|
||||||
Encode24BitLength(content1Bytes), content1Bytes,
|
Encode24BitLength(content1Bytes), content1Bytes,
|
||||||
|
@ -100,6 +100,6 @@ func NewNulRequest() *SignerRequest {
|
||||||
return &SignerRequest{
|
return &SignerRequest{
|
||||||
Version: shared.ProtocolVersion,
|
Version: shared.ProtocolVersion,
|
||||||
Action: shared.ActionNul,
|
Action: shared.ActionNul,
|
||||||
Content1: time.Now().UTC().Format(signerTimeFormat),
|
Content1: []byte(time.Now().UTC().Format(signerTimeFormat)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,9 @@ type SignerResponse struct {
|
||||||
Action shared.Action
|
Action shared.Action
|
||||||
Reserved1 uint8
|
Reserved1 uint8
|
||||||
Reserved2 uint8
|
Reserved2 uint8
|
||||||
Content1 string
|
Content []byte
|
||||||
Content2 string
|
Argument1 []byte
|
||||||
Content3 string
|
Argument2 []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte) (*SignerResponse, error) {
|
func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte) (*SignerResponse, error) {
|
||||||
|
@ -29,14 +29,14 @@ func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte)
|
||||||
headerBytes := blockData[offset : offset+headerLength]
|
headerBytes := blockData[offset : offset+headerLength]
|
||||||
offset += headerLength
|
offset += headerLength
|
||||||
|
|
||||||
content := make([]string, 3)
|
content := make([][]byte, 3)
|
||||||
for offset < len(blockData) {
|
for offset < len(blockData) {
|
||||||
dataLength := Decode24BitLength(blockData[offset : offset+3])
|
dataLength := Decode24BitLength(blockData[offset : offset+3])
|
||||||
if len(blockData)-3 < dataLength {
|
if len(blockData)-3 < dataLength {
|
||||||
return nil, errors.New("structure cut off")
|
return nil, errors.New("structure cut off")
|
||||||
}
|
}
|
||||||
offset += 3
|
offset += 3
|
||||||
content = append(content, string(blockData[offset:offset+dataLength]))
|
content = append(content, blockData[offset:offset+dataLength])
|
||||||
offset += dataLength
|
offset += dataLength
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,32 +50,49 @@ func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte)
|
||||||
Action: shared.Action(headerBytes[1]),
|
Action: shared.Action(headerBytes[1]),
|
||||||
Reserved1: headerBytes[2],
|
Reserved1: headerBytes[2],
|
||||||
Reserved2: headerBytes[3],
|
Reserved2: headerBytes[3],
|
||||||
Content1: content[0],
|
Content: content[0],
|
||||||
Content2: content[1],
|
Argument1: content[1],
|
||||||
Content3: content[2],
|
Argument2: content[2],
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r SignerResponse) Serialize() []byte {
|
func (r SignerResponse) Serialize() []byte {
|
||||||
headerBytes := []byte{r.Version, byte(r.Action), r.Reserved1, r.Reserved2}
|
headerBytes := []byte{r.Version, byte(r.Action), r.Reserved1, r.Reserved2}
|
||||||
content1Bytes := []byte(r.Content1)
|
|
||||||
content2Bytes := []byte(r.Content2)
|
|
||||||
content3Bytes := []byte(r.Content3)
|
|
||||||
blockBytes := bytes.Join([][]byte{
|
blockBytes := bytes.Join([][]byte{
|
||||||
Encode24BitLength(headerBytes), headerBytes,
|
Encode24BitLength(headerBytes), headerBytes,
|
||||||
Encode24BitLength(content1Bytes), content1Bytes,
|
Encode24BitLength(r.Content), r.Content,
|
||||||
Encode24BitLength(content2Bytes), content2Bytes,
|
Encode24BitLength(r.Argument1), r.Argument1,
|
||||||
Encode24BitLength(content3Bytes), content3Bytes,
|
Encode24BitLength(r.Argument2), r.Argument2,
|
||||||
}, []byte{})
|
}, []byte{})
|
||||||
return bytes.Join([][]byte{Encode24BitLength(blockBytes), blockBytes}, []byte{})
|
return bytes.Join([][]byte{Encode24BitLength(blockBytes), blockBytes}, []byte{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNulResponse(version byte) *SignerResponse {
|
func NewNulResponse(version byte) *SignerResponse {
|
||||||
return &SignerResponse{
|
return &SignerResponse{
|
||||||
Version: version,
|
Version: version,
|
||||||
Action: shared.ActionNul,
|
Action: shared.ActionNul,
|
||||||
Content1: "",
|
Content: []byte{},
|
||||||
Content2: "",
|
Argument1: []byte{},
|
||||||
Content3: "",
|
Argument2: []byte{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRevokeResponse(version byte, content []byte) *SignerResponse {
|
||||||
|
return &SignerResponse{
|
||||||
|
Version: version,
|
||||||
|
Action: shared.ActionRevoke,
|
||||||
|
Content: content,
|
||||||
|
Argument1: []byte{},
|
||||||
|
Argument2: []byte{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSignResponse(version byte, content []byte) *SignerResponse {
|
||||||
|
return &SignerResponse{
|
||||||
|
Version: version,
|
||||||
|
Action: shared.ActionSign,
|
||||||
|
Content: content,
|
||||||
|
Argument1: []byte{},
|
||||||
|
Argument2: []byte{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
30
shared/io.go
30
shared/io.go
|
@ -1,6 +1,7 @@
|
||||||
package shared
|
package shared
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
@ -13,31 +14,36 @@ func ReceiveBytes(port io.Reader, count int, timeout time.Duration) ([]byte, err
|
||||||
readCh := make(chan []byte, 1)
|
readCh := make(chan []byte, 1)
|
||||||
errCh := make(chan error, 1)
|
errCh := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
sumRead := 0
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
for {
|
for remainder := count; remainder > 0; {
|
||||||
data := make([]byte, count)
|
data := make([]byte, remainder)
|
||||||
if readBytes, err := port.Read(data); err != nil {
|
if readBytes, err := port.Read(data); err != nil {
|
||||||
errCh <- err
|
errCh <- err
|
||||||
|
return
|
||||||
} else if readBytes > 0 {
|
} else if readBytes > 0 {
|
||||||
log.Tracef("%d bytes read", readBytes)
|
buffer.Write(data[0:readBytes])
|
||||||
sumRead += readBytes
|
remainder -= readBytes
|
||||||
readCh <- data[0:readBytes]
|
log.Tracef("%d bytes read, remaining %d", readBytes, remainder)
|
||||||
} else {
|
|
||||||
readCh <- make([]byte, 0)
|
|
||||||
}
|
|
||||||
if sumRead >= count {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
readCh <- buffer.Bytes()
|
||||||
|
close(readCh)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
select {
|
select {
|
||||||
case <-time.After(timeout):
|
case <-time.After(timeout):
|
||||||
return nil, fmt.Errorf("timeout passed %v", timeout)
|
return nil, fmt.Errorf("timeout passed %v", timeout)
|
||||||
case err := <-errCh:
|
case err := <-errCh:
|
||||||
return nil, err
|
return nil, err
|
||||||
case data := <-readCh:
|
case data := <-readCh:
|
||||||
return data, nil
|
log.Tracef("received %d bytes from channel", len(data))
|
||||||
|
if data == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buffer.Write(data)
|
||||||
}
|
}
|
||||||
|
return buffer.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendBytes(port io.Writer, data []byte) error {
|
func SendBytes(port io.Writer, data []byte) error {
|
||||||
|
|
|
@ -2,18 +2,15 @@ package signer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"os/exec"
|
||||||
"time"
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
@ -21,20 +18,14 @@ import (
|
||||||
|
|
||||||
"git.cacert.org/cacert-gosigner/datastructures"
|
"git.cacert.org/cacert-gosigner/datastructures"
|
||||||
"git.cacert.org/cacert-gosigner/shared"
|
"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 {
|
type CommandProcessorSettings struct {
|
||||||
CABaseDir string
|
CABaseDir string
|
||||||
XDeltaPath string
|
XDeltaPath string
|
||||||
OpenPGPUidEmail string
|
OpenPGPKeyRingDir string
|
||||||
|
OpenPGPUidEmail string
|
||||||
}
|
}
|
||||||
|
|
||||||
// The CommandProcessor takes parsed protocol data and executes the actual
|
// The CommandProcessor takes parsed protocol data and executes the actual
|
||||||
|
@ -102,7 +93,36 @@ func (p *CommandProcessor) handleSignAction(
|
||||||
error,
|
error,
|
||||||
) {
|
) {
|
||||||
log.Debugf("handle sign call: %v", command)
|
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(
|
func (p *CommandProcessor) handleRevokeAction(
|
||||||
|
@ -128,7 +148,11 @@ func (p *CommandProcessor) handleRevokeAction(
|
||||||
case CsX509:
|
case CsX509:
|
||||||
request := command.Content1
|
request := command.Content1
|
||||||
clientHash := command.Content3
|
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:
|
default:
|
||||||
return nil, fmt.Errorf("revoke not implemented for crypto system %s", idSystem.System)
|
return nil, fmt.Errorf("revoke not implemented for crypto system %s", idSystem.System)
|
||||||
}
|
}
|
||||||
|
@ -136,10 +160,9 @@ func (p *CommandProcessor) handleRevokeAction(
|
||||||
|
|
||||||
type IdSystemParameters struct {
|
type IdSystemParameters struct {
|
||||||
System *CryptoSystem
|
System *CryptoSystem
|
||||||
Root *RootCredentials
|
Root interface{}
|
||||||
RootId shared.CryptoSystemRootId
|
Profile interface{}
|
||||||
Profile string
|
MessageDigestAlgorithm interface{}
|
||||||
MessageDigestAlgorithm x509.SignatureAlgorithm
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *CommandProcessor) checkIdentitySystem(
|
func (p *CommandProcessor) checkIdentitySystem(
|
||||||
|
@ -158,7 +181,7 @@ func (p *CommandProcessor) checkIdentitySystem(
|
||||||
root, ok := p.CryptoSystems[systemId].Roots[rootId]
|
root, ok := p.CryptoSystems[systemId].Roots[rootId]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"unsupported root certificate %d for crypto system %s",
|
"unsupported root %d for crypto system %s",
|
||||||
rootId,
|
rootId,
|
||||||
cryptoSystem,
|
cryptoSystem,
|
||||||
)
|
)
|
||||||
|
@ -166,7 +189,7 @@ func (p *CommandProcessor) checkIdentitySystem(
|
||||||
profile, ok := p.CryptoSystems[systemId].Profiles[profileId]
|
profile, ok := p.CryptoSystems[systemId].Profiles[profileId]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"invalid CA profile %d for crypto system %s",
|
"invalid profile %d for crypto system %s",
|
||||||
profileId,
|
profileId,
|
||||||
cryptoSystem,
|
cryptoSystem,
|
||||||
)
|
)
|
||||||
|
@ -182,153 +205,111 @@ func (p *CommandProcessor) checkIdentitySystem(
|
||||||
return &IdSystemParameters{
|
return &IdSystemParameters{
|
||||||
System: cryptoSystem,
|
System: cryptoSystem,
|
||||||
Root: root,
|
Root: root,
|
||||||
RootId: rootId,
|
|
||||||
Profile: profile,
|
Profile: profile,
|
||||||
MessageDigestAlgorithm: mdAlgorithm,
|
MessageDigestAlgorithm: mdAlgorithm,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *CommandProcessor) revokeX509(
|
func (p *CommandProcessor) revokeX509(system *IdSystemParameters, request []byte, clientHash []byte) ([]byte, error) {
|
||||||
system *IdSystemParameters,
|
x509Root := system.Root.(*x509_ops.Root)
|
||||||
request string,
|
signatureAlgorithm := system.MessageDigestAlgorithm.(x509.SignatureAlgorithm)
|
||||||
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("revoke X.509 for root %s", x509Root)
|
||||||
log.Debugf("hash bytes from client %x", clientHashBytes)
|
log.Debugf("hash bytes from client %x", clientHash)
|
||||||
|
|
||||||
crlFileName := fmt.Sprintf("revoke-root%d.crl", system.RootId)
|
var (
|
||||||
stat, err := os.Stat(crlFileName)
|
err error
|
||||||
if err != nil {
|
crlBytes []byte
|
||||||
log.Debugf("CRL file %s does not exist", crlFileName)
|
newHash *[20]byte
|
||||||
} 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 {
|
if len(request) > 0 {
|
||||||
_, err = revokeCertificate(request, system.System, system.RootId)
|
_, err = x509Root.RevokeCertificate(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not revoke certificate / create CRL: %v", err)
|
return nil, fmt.Errorf("could not revoke certificate / create CRL: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var crlBytes []byte
|
crlBytes, newHash, err = x509Root.GenerateCrl(signatureAlgorithm)
|
||||||
crlBytes, err = generateCrl(system.MessageDigestAlgorithm, system.System, system.RootId)
|
|
||||||
if err != nil {
|
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{
|
var content []byte
|
||||||
Type: "X509 CRL",
|
oldCrlFile := x509Root.GetCrlFileName(string(clientHash))
|
||||||
Bytes: crlBytes,
|
newCrlFile := x509Root.GetCrlFileName(hex.EncodeToString(newHash[:]))
|
||||||
}))
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(crlFileName, crlBytes, 0644)
|
statOld, oldErr := os.Stat(oldCrlFile)
|
||||||
if err != nil {
|
statNew, newErr := os.Stat(newCrlFile)
|
||||||
return nil, fmt.Errorf("could not write new CRL to %s: %v", crlFileName, err)
|
|
||||||
|
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 {
|
func (p *CommandProcessor) buildXDelta(oldFile string, newFile string) ([]byte, error) {
|
||||||
path.Join(p.Settings.CABaseDir, "currentcrls", fmt.Sprintf("%d", id))
|
patchFile, err := ioutil.TempFile(os.TempDir(), "*.patch")
|
||||||
// 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 {
|
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() {
|
defer func() {
|
||||||
if err = system.bumpCRLNumber(rootId, nextCrlNumber); err != nil {
|
if err := os.Remove(patchFile.Name()); err != nil {
|
||||||
log.Errorf("could not bump CRL number: %v", err)
|
log.Warnf("could not remove temporary file %s: %v", patchFile.Name(), err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
if err = patchFile.Close(); err != nil {
|
||||||
return x509.CreateRevocationList(
|
return nil, fmt.Errorf("could not close temporary file: %v", err)
|
||||||
rand.Reader,
|
}
|
||||||
crlTemplate,
|
buf := bytes.NewBuffer([]byte{})
|
||||||
caCertificate,
|
cmd := exec.Command(p.Settings.XDeltaPath, "delta", oldFile, newFile, patchFile.Name())
|
||||||
caPrivateKey,
|
cmd.Stdout = buf
|
||||||
)
|
cmd.Stderr = buf
|
||||||
}
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
func revokeCertificate(request string, system *CryptoSystem, rootId shared.CryptoSystemRootId) (*pkix.RevokedCertificate, error) {
|
switch err.(type) {
|
||||||
pemBlock, _ := pem.Decode([]byte(request))
|
case *exec.ExitError:
|
||||||
if pemBlock.Type != "CERTIFICATE" {
|
if err.(*exec.ExitError).ExitCode() == 1 {
|
||||||
if pemBlock.Type != "CERTIFICATE" {
|
// xdelta delta exits with status code 1 if a delta has been found
|
||||||
log.Warnf(
|
break
|
||||||
"PEM structure is probably not a certificate. PEM block has type %s",
|
}
|
||||||
pemBlock.Type,
|
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)
|
return ioutil.ReadFile(patchFile.Name())
|
||||||
if err != nil {
|
}
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"could no parse certificate: %v",
|
func (p *CommandProcessor) signX509Certificate(system *IdSystemParameters, days uint16, spkac uint8, request []byte, san []byte, subject []byte) ([]byte, error) {
|
||||||
err,
|
return nil, errors.New("signX509Certificate is not implemented yet")
|
||||||
)
|
}
|
||||||
}
|
|
||||||
return system.recordRevocation(rootId, certificate)
|
func (p *CommandProcessor) signOpenpgpKey(system *IdSystemParameters, days uint16, pubKey []byte) ([]byte, error) {
|
||||||
|
return nil, errors.New("signOpenpgpKey is not implemented yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommandProcessorSettings() *CommandProcessorSettings {
|
func NewCommandProcessorSettings() *CommandProcessorSettings {
|
||||||
|
@ -336,9 +317,18 @@ func NewCommandProcessorSettings() *CommandProcessorSettings {
|
||||||
if !ok {
|
if !ok {
|
||||||
caBasedir = "."
|
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 {
|
if !ok {
|
||||||
gpgUidEmail = "gpg@cacert.org"
|
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",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
17
signer/common/helpers.go
Normal file
17
signer/common/helpers.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StringAsBigInt(data []byte) (*big.Int, error) {
|
||||||
|
dataString := strings.TrimSpace(string(data))
|
||||||
|
parseInt, err := strconv.ParseInt(dataString, 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse %s as big int: %v", dataString, err)
|
||||||
|
}
|
||||||
|
return big.NewInt(parseInt), nil
|
||||||
|
}
|
|
@ -1,251 +1,16 @@
|
||||||
package signer
|
package signer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"crypto"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"git.cacert.org/cacert-gosigner/shared"
|
"git.cacert.org/cacert-gosigner/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RootCredentials struct {
|
|
||||||
Name string
|
|
||||||
PrivateKeyFile string
|
|
||||||
PublicKeyFile string
|
|
||||||
CertificateFile string
|
|
||||||
DatabaseFile string
|
|
||||||
CRLNumber string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CryptoSystem struct {
|
type CryptoSystem struct {
|
||||||
Name string
|
Name string
|
||||||
Roots map[shared.CryptoSystemRootId]*RootCredentials
|
Roots map[shared.CryptoSystemRootId]interface{}
|
||||||
Profiles map[shared.CertificateProfileId]string
|
Profiles map[shared.CertificateProfileId]interface{}
|
||||||
DigestAlgorithms map[shared.MessageDigestAlgorithmId]x509.SignatureAlgorithm
|
DigestAlgorithms map[shared.MessageDigestAlgorithmId]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (system CryptoSystem) String() string {
|
func (system CryptoSystem) String() string {
|
||||||
return system.Name
|
return system.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (system CryptoSystem) loadCertificate(rootId shared.CryptoSystemRootId) (*x509.Certificate, error) {
|
|
||||||
certificateFile := system.Roots[rootId].CertificateFile
|
|
||||||
pemBytes, err := ioutil.ReadFile(certificateFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"could not load certificate %s: %v",
|
|
||||||
certificateFile,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
pemBlock, _ := pem.Decode(pemBytes)
|
|
||||||
if pemBlock.Type != "CERTIFICATE" {
|
|
||||||
log.Warnf(
|
|
||||||
"PEM in %s is probably not a certificate. PEM block has type %s",
|
|
||||||
certificateFile,
|
|
||||||
pemBlock.Type,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
certificate, err := x509.ParseCertificate(pemBlock.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"could no parse certificate from %s: %v",
|
|
||||||
certificateFile,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return certificate, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (system CryptoSystem) getPrivateKey(rootId shared.CryptoSystemRootId) (crypto.Signer, error) {
|
|
||||||
privateKeyFile := system.Roots[rootId].PrivateKeyFile
|
|
||||||
pemBytes, err := ioutil.ReadFile(privateKeyFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"could not load private key %s: %v",
|
|
||||||
privateKeyFile,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
pemBlock, _ := pem.Decode(pemBytes)
|
|
||||||
if pemBlock.Type != "PRIVATE KEY" {
|
|
||||||
log.Warnf(
|
|
||||||
"PEM in %s is probably not a private key. PEM block has type %s",
|
|
||||||
privateKeyFile,
|
|
||||||
pemBlock.Type,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
privateKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"could no parse private key from %s: %v",
|
|
||||||
privateKeyFile,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return privateKey.(*rsa.PrivateKey), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (system *CryptoSystem) getNextCRLNumber(rootId shared.CryptoSystemRootId) (*big.Int, error) {
|
|
||||||
crlNumberFile := system.Roots[rootId].CRLNumber
|
|
||||||
_, err := os.Stat(crlNumberFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("CRL number file %s does not exist: %v", crlNumberFile, err)
|
|
||||||
return big.NewInt(1), nil
|
|
||||||
}
|
|
||||||
data, err := ioutil.ReadFile(crlNumberFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not read CRL number file %s", crlNumberFile)
|
|
||||||
}
|
|
||||||
result, err := stringAsBigInt(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse content of %s as CRL number: %v", crlNumberFile, err)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (system *CryptoSystem) bumpCRLNumber(rootId shared.CryptoSystemRootId, current *big.Int) error {
|
|
||||||
serial := current.Int64() + 1
|
|
||||||
crlNumberFile := system.Roots[rootId].CRLNumber
|
|
||||||
outFile, err := ioutil.TempFile(path.Dir(crlNumberFile), "*.txt")
|
|
||||||
defer func() { _ = outFile.Close() }()
|
|
||||||
|
|
||||||
_, err = outFile.WriteString(fmt.Sprintf(
|
|
||||||
"%s\n",
|
|
||||||
strings.ToUpper(strconv.FormatInt(serial, 16)),
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not write new CRL number %d to %s: %v", serial, outFile.Name(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = outFile.Close(); err != nil {
|
|
||||||
return fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err)
|
|
||||||
}
|
|
||||||
if err = os.Rename(crlNumberFile, fmt.Sprintf("%s.old", crlNumberFile)); err != nil {
|
|
||||||
return fmt.Errorf("could not rename %s to %s.old: %v", crlNumberFile, crlNumberFile, err)
|
|
||||||
}
|
|
||||||
if err = os.Rename(outFile.Name(), crlNumberFile); err != nil {
|
|
||||||
return fmt.Errorf("could not rename %s to %s: %v", outFile.Name(), crlNumberFile, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringAsBigInt(data []byte) (*big.Int, error) {
|
|
||||||
dataString := strings.TrimSpace(string(data))
|
|
||||||
parseInt, err := strconv.ParseInt(dataString, 16, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse %s as big int: %v", dataString, err)
|
|
||||||
}
|
|
||||||
return big.NewInt(parseInt), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (system *CryptoSystem) loadRevokedCertificatesFromDatabase(rootId shared.CryptoSystemRootId) ([]pkix.RevokedCertificate, error) {
|
|
||||||
databaseFile := system.Roots[rootId].DatabaseFile
|
|
||||||
_, err := os.Stat(databaseFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("openssl certificate database file %s does not exist: %v", databaseFile, err)
|
|
||||||
return []pkix.RevokedCertificate{}, nil
|
|
||||||
}
|
|
||||||
file, err := os.Open(databaseFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", databaseFile, err)
|
|
||||||
}
|
|
||||||
defer func() { _ = file.Close() }()
|
|
||||||
|
|
||||||
result := make([]pkix.RevokedCertificate, 0)
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := strings.Split(scanner.Text(), "\t")
|
|
||||||
if line[0] == "R" {
|
|
||||||
serialNumber, err := stringAsBigInt([]byte(line[3]))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse serial number %s as big int: %v", line[3], err)
|
|
||||||
}
|
|
||||||
revokeTs, err := strconv.ParseInt(line[2][:len(line[2])-1], 10, 64)
|
|
||||||
result = append(result, pkix.RevokedCertificate{
|
|
||||||
SerialNumber: serialNumber,
|
|
||||||
RevocationTime: time.Unix(revokeTs, 0),
|
|
||||||
Extensions: nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (system *CryptoSystem) recordRevocation(rootId shared.CryptoSystemRootId, certificate *x509.Certificate) (*pkix.RevokedCertificate, error) {
|
|
||||||
databaseFile := system.Roots[rootId].DatabaseFile
|
|
||||||
_, err := os.Stat(databaseFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("openssl certificate database file %s does not exist: %v", databaseFile, err)
|
|
||||||
}
|
|
||||||
inFile, err := os.Open(databaseFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", databaseFile, err)
|
|
||||||
}
|
|
||||||
defer func() { _ = inFile.Close() }()
|
|
||||||
|
|
||||||
outFile, err := ioutil.TempFile(path.Dir(databaseFile), "*.txt")
|
|
||||||
defer func() { _ = outFile.Close() }()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(inFile)
|
|
||||||
writer := bufio.NewWriter(outFile)
|
|
||||||
|
|
||||||
found := false
|
|
||||||
revocationTime := time.Now()
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
parts := strings.Split(line, "\t")
|
|
||||||
serialNumber, err := stringAsBigInt([]byte(parts[3]))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse serial number %s as big int: %v", parts[3], err)
|
|
||||||
}
|
|
||||||
if serialNumber == certificate.SerialNumber {
|
|
||||||
line = strings.Join(
|
|
||||||
[]string{"R", parts[1], strconv.FormatInt(revocationTime.Unix(), 10) + "Z", parts[3], parts[4]},
|
|
||||||
"\t",
|
|
||||||
)
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
if _, err = writer.WriteString(fmt.Sprintf("%s\n", line)); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not write '%s' to %s: %v", line, outFile.Name(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = outFile.Close(); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err)
|
|
||||||
}
|
|
||||||
if err = inFile.Close(); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not close %s: %v", databaseFile, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.Rename(databaseFile, fmt.Sprintf("%s.old", databaseFile)); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not rename %s to %s.old: %v", databaseFile, databaseFile, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.Rename(outFile.Name(), databaseFile); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not rename temporary file %s to %s: %v", outFile.Name(), databaseFile, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
log.Warnf("entry not found in database")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &pkix.RevokedCertificate{
|
|
||||||
SerialNumber: certificate.SerialNumber,
|
|
||||||
RevocationTime: revocationTime,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
11
signer/openpgp_ops/operations.go
Normal file
11
signer/openpgp_ops/operations.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package openpgp_ops
|
||||||
|
|
||||||
|
type OpenPGPRoot struct {
|
||||||
|
Name string
|
||||||
|
SecretKeyRing string
|
||||||
|
Identifier string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpenPGPProfile struct {
|
||||||
|
Name string
|
||||||
|
}
|
|
@ -1,9 +1,14 @@
|
||||||
package signer
|
package signer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
"git.cacert.org/cacert-gosigner/shared"
|
"git.cacert.org/cacert-gosigner/shared"
|
||||||
|
"git.cacert.org/cacert-gosigner/signer/openpgp_ops"
|
||||||
|
"git.cacert.org/cacert-gosigner/signer/x509_ops"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -36,13 +41,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
X509MDDefault shared.MessageDigestAlgorithmId = 0
|
X509MDDefault shared.MessageDigestAlgorithmId = 0
|
||||||
X509MDMd5 shared.MessageDigestAlgorithmId = 1
|
X509MDMd5 shared.MessageDigestAlgorithmId = 1
|
||||||
X509MDSha1 shared.MessageDigestAlgorithmId = 2
|
X509MDSha1 shared.MessageDigestAlgorithmId = 2
|
||||||
X509MDRipeMD160 shared.MessageDigestAlgorithmId = 3
|
// X509MDRipeMD160 shared.MessageDigestAlgorithmId = 3 x509 package does not support RIPEMD160
|
||||||
X509MDSha256 shared.MessageDigestAlgorithmId = 8
|
X509MDSha256 shared.MessageDigestAlgorithmId = 8
|
||||||
X509MDSha384 shared.MessageDigestAlgorithmId = 9
|
X509MDSha384 shared.MessageDigestAlgorithmId = 9
|
||||||
X509MDSha512 shared.MessageDigestAlgorithmId = 10
|
X509MDSha512 shared.MessageDigestAlgorithmId = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -58,74 +63,67 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCommandProcessor() *CommandProcessor {
|
func NewCommandProcessor() *CommandProcessor {
|
||||||
cryptoSystems := make(map[shared.CryptoSystemId]*CryptoSystem)
|
settings := NewCommandProcessorSettings()
|
||||||
cryptoSystems[CsX509] = &CryptoSystem{
|
|
||||||
Name: "X.509",
|
cryptoSystems := map[shared.CryptoSystemId]*CryptoSystem{
|
||||||
Roots: map[shared.CryptoSystemRootId]*RootCredentials{
|
CsX509: {
|
||||||
X509RootDefault: {
|
Name: "X.509",
|
||||||
Name: "openssl",
|
Roots: map[shared.CryptoSystemRootId]interface{}{
|
||||||
PrivateKeyFile: "/srv/ca/CA/private/ca.key.pem",
|
X509RootDefault: x509_ops.NewRoot(settings.CABaseDir, "openssl", "CA", X509RootDefault),
|
||||||
CertificateFile: "/srv/ca/CA/ca.crt.pem",
|
X509RootClass3: x509_ops.NewRoot(settings.CABaseDir, "class3", "class3", X509RootClass3),
|
||||||
DatabaseFile: "/srv/ca/CA/index.txt",
|
X509RootClass3s: &x509_ops.Root{Name: "class3s"},
|
||||||
CRLNumber: "/srv/ca/CA/crlnumber",
|
X509Root3: &x509_ops.Root{Name: "root3"},
|
||||||
|
X509Root4: &x509_ops.Root{Name: "root4"},
|
||||||
|
X509Root5: &x509_ops.Root{Name: "root5"},
|
||||||
},
|
},
|
||||||
X509RootClass3: {
|
Profiles: map[shared.CertificateProfileId]interface{}{
|
||||||
Name: "class3",
|
X509ProfileClient: &x509_ops.Profile{Name: "client"},
|
||||||
PrivateKeyFile: "/srv/ca/class3/private/ca.key.pem",
|
X509ProfileClientOrg: &x509_ops.Profile{Name: "client-org"},
|
||||||
CertificateFile: "/srv/ca/class3/ca.crt.pem",
|
X509ProfileClientCodesign: &x509_ops.Profile{Name: "client-codesign"},
|
||||||
DatabaseFile: "/srv/ca/class3/index.txt",
|
X509ProfileClientMachine: &x509_ops.Profile{Name: "client-machine"},
|
||||||
CRLNumber: "/srv/ca/class3/crlnumber",
|
X509ProfileClientAds: &x509_ops.Profile{Name: "client-ads"},
|
||||||
|
X509ProfileServer: &x509_ops.Profile{Name: "server"},
|
||||||
|
X509ProfileServerOrg: &x509_ops.Profile{Name: "server-org"},
|
||||||
|
X509ProfileServerJabber: &x509_ops.Profile{Name: "server-jabber"},
|
||||||
|
X509ProfileOCSP: &x509_ops.Profile{Name: "ocsp"},
|
||||||
|
X509ProfileTimestamp: &x509_ops.Profile{Name: "timestamp"},
|
||||||
|
X509ProfileProxy: &x509_ops.Profile{Name: "proxy"},
|
||||||
|
X509ProfileSubCA: &x509_ops.Profile{Name: "subca"},
|
||||||
|
},
|
||||||
|
// constants for openssl invocations. Should be replaced with
|
||||||
|
// something more useful
|
||||||
|
DigestAlgorithms: map[shared.MessageDigestAlgorithmId]interface{}{
|
||||||
|
X509MDDefault: x509.SHA256WithRSA,
|
||||||
|
X509MDMd5: x509.MD5WithRSA,
|
||||||
|
X509MDSha1: x509.SHA1WithRSA,
|
||||||
|
X509MDSha256: x509.SHA256WithRSA,
|
||||||
|
X509MDSha384: x509.SHA384WithRSA,
|
||||||
|
X509MDSha512: x509.SHA512WithRSA,
|
||||||
},
|
},
|
||||||
X509RootClass3s: {Name: "class3s"},
|
|
||||||
X509Root3: {Name: "root3"},
|
|
||||||
X509Root4: {Name: "root4"},
|
|
||||||
X509Root5: {Name: "root5"},
|
|
||||||
},
|
},
|
||||||
Profiles: map[shared.CertificateProfileId]string{
|
CsOpenPGP: {
|
||||||
X509ProfileClient: "client",
|
Name: "OpenPGP",
|
||||||
X509ProfileClientOrg: "client-org",
|
Roots: map[shared.CryptoSystemRootId]interface{}{
|
||||||
X509ProfileClientCodesign: "client-codesign",
|
OpenPGPRoot0: &openpgp_ops.OpenPGPRoot{
|
||||||
X509ProfileClientMachine: "client-machine",
|
Name: "OpenPGP Root",
|
||||||
X509ProfileClientAds: "client-ads",
|
SecretKeyRing: path.Join(
|
||||||
X509ProfileServer: "server",
|
settings.OpenPGPKeyRingDir,
|
||||||
X509ProfileServerOrg: "server-org",
|
fmt.Sprintf("gpg_root_%d", OpenPGPRoot0),
|
||||||
X509ProfileServerJabber: "server-jabber",
|
"secring.gpg",
|
||||||
X509ProfileOCSP: "ocsp",
|
),
|
||||||
X509ProfileTimestamp: "timestamp",
|
Identifier: settings.OpenPGPUidEmail,
|
||||||
X509ProfileProxy: "proxy",
|
},
|
||||||
X509ProfileSubCA: "subca",
|
},
|
||||||
},
|
Profiles: map[shared.CertificateProfileId]interface{}{
|
||||||
// constants for openssl invocations. Should be replaced with
|
OpenPGPDefaultProfile: &openpgp_ops.OpenPGPProfile{Name: "default"},
|
||||||
// something more useful
|
},
|
||||||
DigestAlgorithms: map[shared.MessageDigestAlgorithmId]x509.SignatureAlgorithm{
|
// constants for gnupg cert-digest-algo parameter. Should be replaced with
|
||||||
X509MDDefault: x509.SHA256WithRSA,
|
// something more useful
|
||||||
X509MDMd5: x509.MD5WithRSA,
|
DigestAlgorithms: map[shared.MessageDigestAlgorithmId]interface{}{
|
||||||
X509MDSha1: x509.SHA1WithRSA,
|
OpenPGPDefaultMD: crypto.SHA256,
|
||||||
X509MDRipeMD160: x509.UnknownSignatureAlgorithm,
|
},
|
||||||
X509MDSha256: x509.SHA256WithRSA,
|
|
||||||
X509MDSha384: x509.SHA384WithRSA,
|
|
||||||
X509MDSha512: x509.SHA512WithRSA,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cryptoSystems[CsOpenPGP] = &CryptoSystem{
|
return &CommandProcessor{CryptoSystems: cryptoSystems, Settings: settings}
|
||||||
Name: "OpenPGP",
|
|
||||||
Roots: map[shared.CryptoSystemRootId]*RootCredentials{
|
|
||||||
OpenPGPRoot0: {
|
|
||||||
Name: "OpenPGP Root",
|
|
||||||
PrivateKeyFile: "secring0.gpg",
|
|
||||||
PublicKeyFile: "pubring0.gpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Profiles: map[shared.CertificateProfileId]string{
|
|
||||||
OpenPGPDefaultProfile: "default",
|
|
||||||
},
|
|
||||||
// constants for gnupg cert-digest-algo parameter. Should be replaced with
|
|
||||||
// something more useful
|
|
||||||
DigestAlgorithms: map[shared.MessageDigestAlgorithmId]x509.SignatureAlgorithm{
|
|
||||||
OpenPGPDefaultMD: x509.SHA256WithRSA,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CommandProcessor{CryptoSystems: cryptoSystems, Settings: NewCommandProcessorSettings()}
|
|
||||||
}
|
}
|
||||||
|
|
418
signer/x509_ops/operations.go
Normal file
418
signer/x509_ops/operations.go
Normal file
|
@ -0,0 +1,418 @@
|
||||||
|
package x509_ops
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"git.cacert.org/cacert-gosigner/shared"
|
||||||
|
"git.cacert.org/cacert-gosigner/signer/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
const crlLifetime = time.Hour * 24 * 7
|
||||||
|
|
||||||
|
type Root struct {
|
||||||
|
Name string
|
||||||
|
privateKeyFile string
|
||||||
|
certificateFile string
|
||||||
|
databaseFile string
|
||||||
|
crlNumberFile string
|
||||||
|
crlFileName string
|
||||||
|
crlHashDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) String() string {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type Profile struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) loadCertificate() (*x509.Certificate, error) {
|
||||||
|
certificateFile := x.certificateFile
|
||||||
|
pemBytes, err := ioutil.ReadFile(certificateFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"could not load certificate %s: %v",
|
||||||
|
certificateFile,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pemBlock, _ := pem.Decode(pemBytes)
|
||||||
|
if pemBlock.Type != "CERTIFICATE" {
|
||||||
|
log.Warnf(
|
||||||
|
"PEM in %s is probably not a certificate. PEM block has type %s",
|
||||||
|
certificateFile,
|
||||||
|
pemBlock.Type,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
certificate, err := x509.ParseCertificate(pemBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"could no parse certificate from %s: %v",
|
||||||
|
certificateFile,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return certificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) getPrivateKey() (crypto.Signer, error) {
|
||||||
|
privateKeyFile := x.privateKeyFile
|
||||||
|
pemBytes, err := ioutil.ReadFile(privateKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"could not load private key %s: %v",
|
||||||
|
privateKeyFile,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pemBlock, _ := pem.Decode(pemBytes)
|
||||||
|
if pemBlock.Type != "PRIVATE KEY" {
|
||||||
|
log.Warnf(
|
||||||
|
"PEM in %s is probably not a private key. PEM block has type %s",
|
||||||
|
privateKeyFile,
|
||||||
|
pemBlock.Type,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
privateKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"could no parse private key from %s: %v",
|
||||||
|
privateKeyFile,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return privateKey.(*rsa.PrivateKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) getNextCRLNumber() (*big.Int, error) {
|
||||||
|
crlNumberFile := x.crlNumberFile
|
||||||
|
_, err := os.Stat(crlNumberFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("CRL number file %s does not exist: %v", crlNumberFile, err)
|
||||||
|
return big.NewInt(1), nil
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadFile(crlNumberFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not read CRL number file %s", crlNumberFile)
|
||||||
|
}
|
||||||
|
result, err := common.StringAsBigInt(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse content of %s as CRL number: %v", crlNumberFile, err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) bumpCRLNumber(current *big.Int) error {
|
||||||
|
serial := current.Int64() + 1
|
||||||
|
crlNumberFile := x.crlNumberFile
|
||||||
|
outFile, err := ioutil.TempFile(path.Dir(crlNumberFile), "*.txt")
|
||||||
|
defer func() { _ = outFile.Close() }()
|
||||||
|
|
||||||
|
_, err = outFile.WriteString(fmt.Sprintf(
|
||||||
|
"%s\n",
|
||||||
|
strings.ToUpper(strconv.FormatInt(serial, 16)),
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not write new CRL number %d to %s: %v", serial, outFile.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = outFile.Close(); err != nil {
|
||||||
|
return fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err)
|
||||||
|
}
|
||||||
|
if err = os.Rename(crlNumberFile, fmt.Sprintf("%s.old", crlNumberFile)); err != nil {
|
||||||
|
return fmt.Errorf("could not rename %s to %s.old: %v", crlNumberFile, crlNumberFile, err)
|
||||||
|
}
|
||||||
|
if err = os.Rename(outFile.Name(), crlNumberFile); err != nil {
|
||||||
|
return fmt.Errorf("could not rename %s to %s: %v", outFile.Name(), crlNumberFile, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) loadRevokedCertificatesFromDatabase() ([]pkix.RevokedCertificate, error) {
|
||||||
|
databaseFile := x.databaseFile
|
||||||
|
_, err := os.Stat(databaseFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("openssl certificate database file %s does not exist: %v", databaseFile, err)
|
||||||
|
return []pkix.RevokedCertificate{}, nil
|
||||||
|
}
|
||||||
|
file, err := os.Open(databaseFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", databaseFile, err)
|
||||||
|
}
|
||||||
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
|
result := make([]pkix.RevokedCertificate, 0)
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.Split(scanner.Text(), "\t")
|
||||||
|
if line[0] == "R" {
|
||||||
|
serialNumber, err := common.StringAsBigInt([]byte(line[3]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse serial number %s as big int: %v", line[3], err)
|
||||||
|
}
|
||||||
|
revokeTs, err := strconv.ParseInt(line[2][:len(line[2])-1], 10, 64)
|
||||||
|
result = append(result, pkix.RevokedCertificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
RevocationTime: time.Unix(revokeTs, 0),
|
||||||
|
Extensions: nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) recordRevocation(certificate *x509.Certificate) (*pkix.RevokedCertificate, error) {
|
||||||
|
databaseFile := x.databaseFile
|
||||||
|
_, err := os.Stat(databaseFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("openssl certificate database file %s does not exist: %v", databaseFile, err)
|
||||||
|
}
|
||||||
|
inFile, err := os.Open(databaseFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", databaseFile, err)
|
||||||
|
}
|
||||||
|
defer func() { _ = inFile.Close() }()
|
||||||
|
|
||||||
|
outFile, err := ioutil.TempFile(path.Dir(databaseFile), "*.txt")
|
||||||
|
defer func() { _ = outFile.Close() }()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(inFile)
|
||||||
|
writer := bufio.NewWriter(outFile)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
revocationTime := time.Now()
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
parts := strings.Split(line, "\t")
|
||||||
|
serialNumber, err := common.StringAsBigInt([]byte(parts[3]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse serial number %s as big int: %v", parts[3], err)
|
||||||
|
}
|
||||||
|
if serialNumber == certificate.SerialNumber {
|
||||||
|
line = strings.Join(
|
||||||
|
[]string{"R", parts[1], strconv.FormatInt(revocationTime.Unix(), 10) + "Z", parts[3], parts[4]},
|
||||||
|
"\t",
|
||||||
|
)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
if _, err = writer.WriteString(fmt.Sprintf("%s\n", line)); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not write '%s' to %s: %v", line, outFile.Name(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = outFile.Close(); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err)
|
||||||
|
}
|
||||||
|
if err = inFile.Close(); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not close %s: %v", databaseFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.Rename(databaseFile, fmt.Sprintf("%s.old", databaseFile)); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not rename %s to %s.old: %v", databaseFile, databaseFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.Rename(outFile.Name(), databaseFile); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not rename temporary file %s to %s: %v", outFile.Name(), databaseFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
log.Warnf("entry not found in database")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pkix.RevokedCertificate{
|
||||||
|
SerialNumber: certificate.SerialNumber,
|
||||||
|
RevocationTime: revocationTime,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) RevokeCertificate(request []byte) (*pkix.RevokedCertificate, error) {
|
||||||
|
pemBlock, _ := pem.Decode(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 x.recordRevocation(certificate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) GenerateCrl(algorithm x509.SignatureAlgorithm) ([]byte, *[20]byte, error) {
|
||||||
|
caCertificate, err := x.loadCertificate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
caPrivateKey, err := x.getPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
certificatesToRevoke, err := x.loadRevokedCertificatesFromDatabase()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
nextCrlNumber, err := x.getNextCRLNumber()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 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 = x.bumpCRLNumber(nextCrlNumber); err != nil {
|
||||||
|
log.Errorf("could not bump CRL number: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
crlBytes, err := x509.CreateRevocationList(
|
||||||
|
rand.Reader,
|
||||||
|
crlTemplate,
|
||||||
|
caCertificate,
|
||||||
|
caPrivateKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("could not create new CRL: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ioutil.WriteFile(x.crlFileName, crlBytes, 0644); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("could not write new CRL to %s: %v", x.crlFileName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newCrlHash := sha1.Sum(crlBytes)
|
||||||
|
hashedCrlFileName := path.Join(
|
||||||
|
x.crlHashDir,
|
||||||
|
fmt.Sprintf("%s.crl", hex.EncodeToString(newCrlHash[:])),
|
||||||
|
)
|
||||||
|
if err = ioutil.WriteFile(hashedCrlFileName, crlBytes, 0644); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("could not write new CRL to %s: %v", hashedCrlFileName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return crlBytes, &newCrlHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) DeleteOldCRLs(keepHashes ...string) error {
|
||||||
|
log.Debugf("will look for CRLs in %s", x.crlHashDir)
|
||||||
|
|
||||||
|
found, err := filepath.Glob(path.Join(x.crlHashDir, "*.crl"))
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("could not match files: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
nextFound:
|
||||||
|
for _, filename := range found {
|
||||||
|
for _, keepHash := range keepHashes {
|
||||||
|
if x.GetCrlFileName(keepHash) == filename {
|
||||||
|
continue nextFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := os.Remove(filename); err != nil {
|
||||||
|
return fmt.Errorf("could not delete %s: %v", filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) GetCrlFileName(hash string) string {
|
||||||
|
return path.Join(x.crlHashDir, fmt.Sprintf("%s.crl", hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) checkPreconditions() {
|
||||||
|
results := []bool{
|
||||||
|
x.checkFile(x.privateKeyFile, "private key"),
|
||||||
|
x.checkFile(x.certificateFile, "certificate"),
|
||||||
|
x.checkFile(x.databaseFile, "database"),
|
||||||
|
x.checkFile(x.crlNumberFile, "CRL serial number"),
|
||||||
|
x.checkFile(x.crlFileName, "CRL file"),
|
||||||
|
x.checkDir(x.crlHashDir, "directory for hash indexed CRLs"),
|
||||||
|
}
|
||||||
|
for _, success := range results {
|
||||||
|
if !success {
|
||||||
|
log.Warnf("preconditions for %s failed, operations may fail too", x)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) checkFile(path, prefix string) bool {
|
||||||
|
ok := true
|
||||||
|
if s, e := os.Stat(path); e != nil {
|
||||||
|
log.Warnf("%s file %s of %s has issues: %v", prefix, path, x, e)
|
||||||
|
ok = false
|
||||||
|
} else if s.IsDir() {
|
||||||
|
log.Warnf("%s file %s of %s is a directory", prefix, path, x)
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Root) checkDir(path, prefix string) bool {
|
||||||
|
ok := true
|
||||||
|
if s, e := os.Stat(path); e != nil {
|
||||||
|
log.Warnf("%s %s of %s has issues: %v", prefix, path, x, e)
|
||||||
|
if err := os.MkdirAll(path, 0755); err != nil {
|
||||||
|
log.Warnf("could not create %s %s of %s: %v", prefix, path, x, err)
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
ok = false
|
||||||
|
} else if !s.IsDir() {
|
||||||
|
log.Warnf("%s %s of %s is not a directory", prefix, path, x)
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRoot(basedir, name, subdir string, id shared.CryptoSystemRootId) *Root {
|
||||||
|
root := &Root{
|
||||||
|
Name: name,
|
||||||
|
privateKeyFile: path.Join(basedir, subdir, "private", "ca.key.pem"),
|
||||||
|
certificateFile: path.Join(basedir, subdir, "ca.crt.pem"),
|
||||||
|
databaseFile: path.Join(basedir, subdir, "index.txt"),
|
||||||
|
crlNumberFile: path.Join(basedir, subdir, "crlnumber"),
|
||||||
|
crlFileName: fmt.Sprintf("revoke-root%d.crl", id),
|
||||||
|
crlHashDir: path.Join(
|
||||||
|
basedir,
|
||||||
|
"currentcrls",
|
||||||
|
fmt.Sprintf("%d", id),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
root.checkPreconditions()
|
||||||
|
return root
|
||||||
|
}
|
Loading…
Reference in a new issue