Configure golangci-lint and fix warnings

This commit is contained in:
Jan Dittberner 2021-01-09 11:24:40 +01:00
parent ecd1846975
commit 2e467b3d2e
20 changed files with 915 additions and 559 deletions

39
.golangci.yml Normal file
View file

@ -0,0 +1,39 @@
---
output:
sort-results: true
linter-settings:
goimports:
local-prefixes: git.cacert.org/cacert-gosigner
misspell:
locale: US
ignore-words:
- CAcert
linters:
disable-all: false
enable:
- errorlint
- gocognit
- goconst
- gocritic
- gofmt
- goheader
- goimports
- golint
- gomnd
- gosec
- interfacer
- lll
- makezero
- misspell
- nakedret
- nestif
- nlreturn
- nolintlint
- predeclared
- rowserrcheck
- scopelint
- sqlclosecheck
- wrapcheck
- wsl

View file

@ -2,10 +2,17 @@ package main
import ( import (
"fmt" "fmt"
"github.com/sirupsen/logrus" "io/ioutil"
log "github.com/sirupsen/logrus"
"go.bug.st/serial" "go.bug.st/serial"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"io/ioutil" )
const (
dataBits = 8
defaultBaudRate = 115200
defaultBufferSize = 2048
) )
type ClientConfig struct { type ClientConfig struct {
@ -21,8 +28,8 @@ type ClientConfig struct {
var defaultConfig = ClientConfig{ var defaultConfig = ClientConfig{
SerialAddress: "/dev/ttyUSB0", SerialAddress: "/dev/ttyUSB0",
BaudRate: 115200, BaudRate: defaultBaudRate,
BufferSize: 2048, BufferSize: defaultBufferSize,
Paranoid: false, Paranoid: false,
Debug: false, Debug: false,
OpenSSLBinary: "/usr/bin/openssl", OpenSSLBinary: "/usr/bin/openssl",
@ -30,31 +37,36 @@ var defaultConfig = ClientConfig{
MySQLDSN: "<username>:<password>@/database?parseTime=true", MySQLDSN: "<username>:<password>@/database?parseTime=true",
} }
func generateExampleConfig(configFile string) (config *ClientConfig, err error) { func generateExampleConfig(configFile string) (*ClientConfig, error) {
config = &defaultConfig config := &defaultConfig
configBytes, err := yaml.Marshal(config) configBytes, err := yaml.Marshal(config)
if err != nil { if err != nil {
logrus.Errorf("could not generate configuration data") return nil, fmt.Errorf("could not generate configuration data: %w", err)
return
} }
logrus.Infof("example data for %s:\n\n---\n%s\n", configFile, configBytes) log.Infof("example data for %s:\n\n---\n%s\n", configFile, configBytes)
return
return config, nil
} }
func readConfig(configFile string) (config *ClientConfig, err error) { func readConfig(configFile string) (config *ClientConfig, err error) {
source, err := ioutil.ReadFile(configFile) source, err := ioutil.ReadFile(configFile)
if err != nil { if err != nil {
logrus.Errorf("opening configuration file failed: %v", err) log.Errorf("opening configuration file failed: %v", err)
if exampleConfig, err := generateExampleConfig(configFile); err != nil {
exampleConfig, err := generateExampleConfig(configFile)
if err != nil {
return nil, err return nil, err
} else { }
logrus.Info("starting with default config")
log.Info("starting with default config")
return exampleConfig, nil return exampleConfig, nil
} }
}
if err := yaml.Unmarshal(source, &config); err != nil { if err := yaml.Unmarshal(source, &config); err != nil {
return nil, fmt.Errorf("loading configuration file failed: %v", err) return nil, fmt.Errorf("loading configuration file failed: %w", err)
} }
return config, nil return config, nil
@ -63,7 +75,7 @@ func readConfig(configFile string) (config *ClientConfig, err error) {
func fillSerialMode(clientConfig *ClientConfig) *serial.Mode { func fillSerialMode(clientConfig *ClientConfig) *serial.Mode {
return &serial.Mode{ return &serial.Mode{
BaudRate: clientConfig.BaudRate, BaudRate: clientConfig.BaudRate,
DataBits: 8, DataBits: dataBits,
StopBits: serial.OneStopBit, StopBits: serial.OneStopBit,
Parity: serial.NoParity, Parity: serial.NoParity,
} }

View file

@ -2,14 +2,15 @@ package main
import ( import (
"bytes" "bytes"
"github.com/sirupsen/logrus"
"strings" "strings"
"testing" "testing"
log "github.com/sirupsen/logrus"
) )
func TestGenerateExampleConfig(t *testing.T) { func TestGenerateExampleConfig(t *testing.T) {
testOutput := bytes.Buffer{} testOutput := bytes.Buffer{}
logrus.SetOutput(&testOutput) log.SetOutput(&testOutput)
config, err := generateExampleConfig("test.yaml") config, err := generateExampleConfig("test.yaml")

View file

@ -16,29 +16,37 @@ import (
"git.cacert.org/cacert-gosigner/datastructures" "git.cacert.org/cacert-gosigner/datastructures"
) )
const mainLoopSleep = 2700
func main() { func main() {
var configFile string var configFile string
flag.StringVar(&configFile, "c", "client.yaml", "client configuration file in YAML format") flag.StringVar(&configFile, "c", "client.yaml", "client configuration file in YAML format")
flag.Parse() flag.Parse()
var clientConfig *ClientConfig var (
var serialConfig *serial.Mode clientConfig *ClientConfig
var err error serialConfig *serial.Mode
err error
)
if clientConfig, err = readConfig(configFile); err != nil { if clientConfig, err = readConfig(configFile); err != nil {
log.Panic(err) log.Panic(err)
} }
serialConfig = fillSerialMode(clientConfig) serialConfig = fillSerialMode(clientConfig)
if clientConfig.Debug { if clientConfig.Debug {
log.SetLevel(log.TraceLevel) log.SetLevel(log.TraceLevel)
} }
log.Infof("connecting to %s using %+v", clientConfig.SerialAddress, serialConfig) log.Infof("connecting to %s using %+v", clientConfig.SerialAddress, serialConfig)
port, err := serial.Open(clientConfig.SerialAddress, serialConfig) port, err := serial.Open(clientConfig.SerialAddress, serialConfig)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Debug("serial port connected") log.Debug("serial port connected")
requestChannel := protocol.NewSignerProtocolRequestChannel() requestChannel := protocol.NewSignerProtocolRequestChannel()
@ -48,6 +56,7 @@ func main() {
if clientConfig.BufferSize != 0 { if clientConfig.BufferSize != 0 {
clientProtocolConfig.BufferSize = int(clientConfig.BufferSize) clientProtocolConfig.BufferSize = int(clientConfig.BufferSize)
} }
protocolHandler := protocol.NewProtocolHandler( protocolHandler := protocol.NewProtocolHandler(
requestChannel, &responseChannel, port, clientProtocolConfig, requestChannel, &responseChannel, port, clientProtocolConfig,
) )
@ -55,14 +64,17 @@ func main() {
cancelChannel := make(chan os.Signal, 1) cancelChannel := make(chan os.Signal, 1)
signal.Notify(cancelChannel, syscall.SIGTERM, syscall.SIGINT) signal.Notify(cancelChannel, syscall.SIGTERM, syscall.SIGINT)
const goRoutines = 2
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(2) wg.Add(goRoutines)
go func() { go func() {
if err := protocolHandler.HandleSignerProtocol(); err != nil { if err := protocolHandler.HandleSignerProtocol(); err != nil {
log.Errorf("terminating because of %v", err) log.Errorf("terminating because of %v", err)
close(cancelChannel) close(cancelChannel)
} }
wg.Done() wg.Done()
}() }()
@ -75,20 +87,26 @@ func main() {
if sig != nil { if sig != nil {
log.Infof("caught %+v", sig) log.Infof("caught %+v", sig)
} }
if err := protocolHandler.Close(); err != nil { if err := protocolHandler.Close(); err != nil {
log.Error(err) log.Error(err)
} else { } else {
log.Infof("protocol handler closed") log.Infof("protocol handler closed")
} }
if err := port.Close(); err != nil { if err := port.Close(); err != nil {
log.Error(err) log.Error(err)
} else { } else {
log.Infof("serial port closed") log.Infof("serial port closed")
} }
wg.Wait() wg.Wait()
} }
func runMainLoop(requestChannel *protocol.SignerProtocolRequestChannel, responseChannel *chan *datastructures.SignerResponse) { func runMainLoop(
requestChannel *protocol.SignerProtocolRequestChannel,
responseChannel *chan *datastructures.SignerResponse,
) {
crlCheck := 0 crlCheck := 0
log.Debug("starting main loop") log.Debug("starting main loop")
@ -99,30 +117,28 @@ func runMainLoop(requestChannel *protocol.SignerProtocolRequestChannel, response
log.Error(err) log.Error(err)
} }
} }
log.Trace("processing goroutine terminated") log.Trace("processing goroutine terminated")
}() }()
for { for {
log.Debug("handling GPG database ...") log.Debug("handling GPG database ...") // HandleGPG(&requestChannel)
// HandleGPG(&requestChannel) log.Debug("issuing certificates ...") // HandleCertificates(&requestChannel)
log.Debug("issuing certificates ...") log.Debug("revoking certificates ...") // RevokeCertificates(&requestChannel)
// HandleCertificates(&requestChannel)
log.Debug("revoking certificates ...")
// RevokeCertificates(&requestChannel)
crlCheck++ crlCheck++
if crlCheck%100 == 0 { if crlCheck%100 == 0 {
log.Debug("refresh CRLs ...") log.Debug("refresh CRLs ...") // RefreshCRLs(&requestChannel)
// RefreshCRLs(&requestChannel)
} }
if requestChannel.IsClosed() { if requestChannel.IsClosed() {
return return
} }
log.Debug("send NUL request to keep connection open") log.Debug("send NUL request to keep connection open")
requestChannel.C <- datastructures.NewNulRequest() requestChannel.C <- datastructures.NewNulRequest()
log.Debug("sleep for 2.7 seconds") log.Debug("sleep for 2.7 seconds")
time.Sleep(2700 * time.Millisecond) time.Sleep(mainLoopSleep * time.Millisecond)
} }
} }

View file

@ -16,6 +16,7 @@ func Process(response *datastructures.SignerResponse) (err error) {
switch response.Action { switch response.Action {
case shared.ActionNul: case shared.ActionNul:
logrus.Trace("received response for NUL request") logrus.Trace("received response for NUL request")
return return
default: default:
return fmt.Errorf("unsupported action in response 0x%x", response.Action) return fmt.Errorf("unsupported action in response 0x%x", response.Action)

View file

@ -2,6 +2,7 @@ package protocol
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io" "io"
"sync" "sync"
@ -13,17 +14,24 @@ import (
"git.cacert.org/cacert-gosigner/shared" "git.cacert.org/cacert-gosigner/shared"
) )
const (
waitForHeader = 20
waitForData = 5
waitForHandShake = 120
bufferSize = 2048
)
type SignerProtocolHandler interface { type SignerProtocolHandler interface {
io.Closer io.Closer
HandleSignerProtocol() error HandleSignerProtocol() error
} }
type signerProtocolConfig struct { type SignerProtocolConfig struct {
BufferSize int BufferSize int
} }
func NewSignerProtocolConfig() *signerProtocolConfig { func NewSignerProtocolConfig() *SignerProtocolConfig {
return &signerProtocolConfig{BufferSize: 2048} return &SignerProtocolConfig{BufferSize: bufferSize}
} }
type SignerProtocolRequestChannel struct { type SignerProtocolRequestChannel struct {
@ -39,6 +47,7 @@ func NewSignerProtocolRequestChannel() *SignerProtocolRequestChannel {
func (rc *SignerProtocolRequestChannel) SafeClose() { func (rc *SignerProtocolRequestChannel) SafeClose() {
rc.mutex.Lock() rc.mutex.Lock()
defer rc.mutex.Unlock() defer rc.mutex.Unlock()
if !rc.closed { if !rc.closed {
close(rc.C) close(rc.C)
rc.closed = true rc.closed = true
@ -48,6 +57,7 @@ func (rc *SignerProtocolRequestChannel) SafeClose() {
func (rc *SignerProtocolRequestChannel) IsClosed() bool { func (rc *SignerProtocolRequestChannel) IsClosed() bool {
rc.mutex.Lock() rc.mutex.Lock()
defer rc.mutex.Unlock() defer rc.mutex.Unlock()
return rc.closed return rc.closed
} }
@ -55,7 +65,7 @@ type protocolHandler struct {
requestChannel *SignerProtocolRequestChannel requestChannel *SignerProtocolRequestChannel
responseChannel *chan *datastructures.SignerResponse responseChannel *chan *datastructures.SignerResponse
serialConnection io.ReadWriteCloser serialConnection io.ReadWriteCloser
config *signerProtocolConfig config *SignerProtocolConfig
} }
type UnExpectedAcknowledgeByte struct { type UnExpectedAcknowledgeByte struct {
@ -75,6 +85,7 @@ func (e UnExpectedAcknowledgeByte) Error() string {
func (ph *protocolHandler) Close() error { func (ph *protocolHandler) Close() error {
close(*ph.responseChannel) close(*ph.responseChannel)
ph.requestChannel.SafeClose() ph.requestChannel.SafeClose()
return nil return nil
} }
@ -82,7 +93,7 @@ func NewProtocolHandler(
requests *SignerProtocolRequestChannel, requests *SignerProtocolRequestChannel,
response *chan *datastructures.SignerResponse, response *chan *datastructures.SignerResponse,
serialConnection io.ReadWriteCloser, serialConnection io.ReadWriteCloser,
config *signerProtocolConfig, config *SignerProtocolConfig,
) SignerProtocolHandler { ) SignerProtocolHandler {
return &protocolHandler{ return &protocolHandler{
requestChannel: requests, requestChannel: requests,
@ -93,85 +104,115 @@ func NewProtocolHandler(
} }
func (ph *protocolHandler) HandleSignerProtocol() error { func (ph *protocolHandler) HandleSignerProtocol() error {
for { for request := range ph.requestChannel.C {
select {
case request := <-ph.requestChannel.C:
log.Tracef("handle request %+v", request) log.Tracef("handle request %+v", request)
var err error
var lengthBytes, responseBytes *[]byte var (
var checksum byte err error
lengthBytes, responseBytes *[]byte
checksum byte
)
if err = ph.sendHandshake(); err != nil { if err = ph.sendHandshake(); err != nil {
switch err.(type) { var e *UnExpectedHandshakeByte
case UnExpectedAcknowledgeByte: if errors.As(err, &e) {
log.Errorf("unexpected handshake byte: 0x%x", err.(UnExpectedAcknowledgeByte).ResponseByte) log.Errorf("unexpected handshake byte: 0x%x", e.ResponseByte)
// TODO drain input
} }
return err return err
} }
requestBytes := request.Serialize() requestBytes := request.Serialize()
if err = ph.sendRequest(&requestBytes); err != nil { if err = ph.sendRequest(&requestBytes); err != nil {
return err return err
} }
if err = ph.waitForResponseHandshake(); err != nil { if err = ph.waitForResponseHandshake(); err != nil {
return err return err
} }
if lengthBytes, responseBytes, checksum, err = ph.readResponse(); err != nil { if lengthBytes, responseBytes, checksum, err = ph.readResponse(); err != nil {
return err return err
} }
response, err := datastructures.SignerResponseFromData(*lengthBytes, *responseBytes, checksum) response, err := datastructures.SignerResponseFromData(*lengthBytes, *responseBytes, checksum)
if err != nil { if err != nil {
return err return fmt.Errorf("could not create response: %w", err)
} }
*ph.responseChannel <- response *ph.responseChannel <- response
} }
}
return nil
} }
func (ph *protocolHandler) sendHandshake() (err error) { func (ph *protocolHandler) sendHandshake() error {
var bytesWritten, bytesRead int var (
if bytesWritten, err = ph.serialConnection.Write([]byte{shared.HandshakeByte}); err != nil { bytesWritten, bytesRead int
return err error
} else { )
data := make([]byte, 0)
bytesWritten, err = ph.serialConnection.Write([]byte{shared.HandshakeByte})
if err != nil {
return fmt.Errorf("could not send handshake byte: %w", err)
}
log.Tracef("wrote %d bytes of handshake info", bytesWritten) log.Tracef("wrote %d bytes of handshake info", bytesWritten)
bytesRead, err = ph.serialConnection.Read(data)
if err != nil {
return fmt.Errorf("could not receieve ACK byte: %w", err)
} }
data := make([]byte, 1)
if bytesRead, err = ph.serialConnection.Read(data); err != nil {
return
}
log.Tracef("%d bytes read", bytesRead) log.Tracef("%d bytes read", bytesRead)
if bytesRead != 1 || data[0] != shared.AckByte { if bytesRead != 1 || data[0] != shared.AckByte {
log.Warnf("received invalid handshake byte 0x%x", data[0]) log.Warnf("received invalid handshake byte 0x%x", data[0])
return UnExpectedAcknowledgeByte{data[0]} return UnExpectedAcknowledgeByte{data[0]}
} }
return
return nil
} }
func (ph *protocolHandler) sendRequest(requestBytes *[]byte) error { func (ph *protocolHandler) sendRequest(requestBytes *[]byte) error {
var (
n int
err error
)
for { for {
if length, err := ph.serialConnection.Write(*requestBytes); err != nil { n, err = ph.serialConnection.Write(*requestBytes)
return err
} else {
log.Tracef("wrote %d request bytes", length)
}
if length, err := ph.serialConnection.Write([]byte{
datastructures.CalculateXorCheckSum([][]byte{*requestBytes}),
}); err != nil {
return err
} else {
log.Tracef("wrote %d checksum bytes", length)
}
if length, err := ph.serialConnection.Write([]byte(shared.MagicTrailer)); err != nil {
return err
} else {
log.Tracef("wrote %d trailer bytes", length)
}
header, err := shared.ReceiveBytes(ph.serialConnection, 1, 20*time.Second)
if err != nil { if err != nil {
return err return fmt.Errorf("could not send request bytes: %w", err)
} }
log.Tracef("wrote %d request bytes", n)
n, err = ph.serialConnection.Write([]byte{
datastructures.CalculateXorCheckSum([][]byte{*requestBytes}),
})
if err != nil {
return fmt.Errorf("could not send checksum byte: %w", err)
}
log.Tracef("wrote %d checksum bytes", n)
n, err = ph.serialConnection.Write([]byte(shared.MagicTrailer))
if err != nil {
return fmt.Errorf("could not send trailer bytes: %w", err)
}
log.Tracef("wrote %d trailer bytes", n)
header, err := shared.ReceiveBytes(ph.serialConnection, 1, waitForHeader*time.Second)
if err != nil {
return fmt.Errorf("could not read header bytes: %w", err)
}
switch header[0] { switch header[0] {
case shared.AckByte: case shared.AckByte:
return nil return nil
@ -182,62 +223,78 @@ func (ph *protocolHandler) sendRequest(requestBytes *[]byte) error {
} }
} }
func (ph *protocolHandler) waitForResponseHandshake() (err error) { func (ph *protocolHandler) waitForResponseHandshake() error {
data, err := shared.ReceiveBytes(ph.serialConnection, 1, 120*time.Second) data, err := shared.ReceiveBytes(ph.serialConnection, 1, waitForHandShake*time.Second)
if err != nil { if err != nil {
return err return fmt.Errorf("could not receive handshake byte: %w", err)
}
if len(data) != 1 || data[0] != shared.HandshakeByte {
log.Warnf("received invalid handshake byte 0x%x", data[0])
return UnExpectedHandshakeByte{data[0]}
}
if err = shared.SendBytes(ph.serialConnection, []byte{shared.AckByte}); err != nil {
return
} }
return if len(data) != 1 || data[0] != shared.HandshakeByte {
log.Warnf("received invalid handshake byte 0x%x", data[0])
return UnExpectedHandshakeByte{data[0]}
}
if err = shared.SendBytes(ph.serialConnection, []byte{shared.AckByte}); err != nil {
return fmt.Errorf("could not send ACK: %w", err)
}
return nil
} }
func (ph *protocolHandler) readResponse() (*[]byte, *[]byte, byte, error) { func (ph *protocolHandler) readResponse() (*[]byte, *[]byte, byte, error) {
dataLength := -1 dataLength := -1
var lengthBuffer = bytes.NewBuffer(make([]byte, 0))
var byteBuffer = bytes.NewBuffer(make([]byte, 0)) var (
lengthBuffer = bytes.NewBuffer(make([]byte, 0))
byteBuffer = bytes.NewBuffer(make([]byte, 0))
)
for { for {
readBuffer, err := shared.ReceiveBytes(ph.serialConnection, ph.config.BufferSize, 5*time.Second) readBuffer, err := shared.ReceiveBytes(ph.serialConnection, ph.config.BufferSize, waitForData*time.Second)
if err != nil { if err != nil {
return nil, nil, 0, err return nil, nil, 0, fmt.Errorf("could not receive response: %w", err)
} }
bytesRead := len(readBuffer) bytesRead := len(readBuffer)
if bytesRead > 0 { if bytesRead > 0 {
byteBuffer.Write(readBuffer[0:bytesRead]) byteBuffer.Write(readBuffer[0:bytesRead])
for _, b := range readBuffer { for _, b := range readBuffer {
if lengthBuffer.Len() < 3 { if lengthBuffer.Len() < shared.LengthFieldSize {
lengthBuffer.WriteByte(b) lengthBuffer.WriteByte(b)
} else { } else {
break break
} }
} }
} }
if dataLength < 0 && lengthBuffer.Len() == 3 {
if dataLength < 0 && lengthBuffer.Len() == shared.LengthFieldSize {
dataLength = datastructures.Decode24BitLength(lengthBuffer.Bytes()) dataLength = datastructures.Decode24BitLength(lengthBuffer.Bytes())
log.Tracef("expect to read %d data bytes", dataLength) log.Tracef("expect to read %d data bytes", dataLength)
} }
if dataLength == byteBuffer.Len()-4-len(shared.MagicTrailer) {
trailerOffset := shared.LengthFieldSize + shared.CheckSumFieldSize
if dataLength == byteBuffer.Len()-trailerOffset-len(shared.MagicTrailer) {
allBytes := byteBuffer.Bytes() allBytes := byteBuffer.Bytes()
trailer := string(allBytes[4+dataLength:])
trailer := string(allBytes[trailerOffset+dataLength:])
if trailer != shared.MagicTrailer { if trailer != shared.MagicTrailer {
return nil, nil, 0, fmt.Errorf("invalid trailer bytes: %v", trailer) return nil, nil, 0, fmt.Errorf("invalid trailer bytes: %v", trailer)
} }
lengthBytes := allBytes[0:3]
dataBytes := allBytes[3 : 3+dataLength] lengthBytes := allBytes[0:shared.LengthFieldSize]
checkSum := allBytes[3+dataLength] dataBytes := allBytes[shared.LengthFieldSize : shared.LengthFieldSize+dataLength]
checkSum := allBytes[shared.LengthFieldSize+dataLength]
calculatedChecksum := datastructures.CalculateXorCheckSum([][]byte{lengthBytes, dataBytes}) calculatedChecksum := datastructures.CalculateXorCheckSum([][]byte{lengthBytes, dataBytes})
if calculatedChecksum != checkSum { if calculatedChecksum != checkSum {
return nil, nil, 0, fmt.Errorf("calculated checksum mismatch 0x%x vs 0x%x", calculatedChecksum, checkSum) return nil, nil, 0, fmt.Errorf("calculated checksum mismatch 0x%x vs 0x%x", calculatedChecksum, checkSum)
} }
if err := shared.SendBytes(ph.serialConnection, []byte{shared.AckByte}); err != nil { if err := shared.SendBytes(ph.serialConnection, []byte{shared.AckByte}); err != nil {
return nil, nil, 0, err return nil, nil, 0, fmt.Errorf("could not send ACK byte: %w", err)
} }
return &lengthBytes, &dataBytes, checkSum, nil return &lengthBytes, &dataBytes, checkSum, nil

View file

@ -14,11 +14,12 @@ import (
func main() { func main() {
var ( var (
address string address string
baudRate int baudRate, dataBits int
) )
flag.StringVar(&address, "a", "/dev/ttyUSB0", "address") flag.StringVar(&address, "a", "/dev/ttyUSB0", "address")
flag.IntVar(&baudRate, "b", 115200, "baud rate") flag.IntVar(&baudRate, "b", 115200, "baud rate")
flag.IntVar(&dataBits, "d", 8, "data bits")
flag.Parse() flag.Parse()
log.SetFormatter(&log.TextFormatter{ log.SetFormatter(&log.TextFormatter{
@ -29,15 +30,18 @@ func main() {
serialMode := &serial.Mode{ serialMode := &serial.Mode{
BaudRate: baudRate, BaudRate: baudRate,
DataBits: 8, DataBits: dataBits,
StopBits: serial.OneStopBit, StopBits: serial.OneStopBit,
Parity: serial.NoParity, Parity: serial.NoParity,
} }
log.Infof("connecting to %s using %+v", address, serialMode) log.Infof("connecting to %s using %+v", address, serialMode)
port, err := serial.Open(address, serialMode) port, err := serial.Open(address, serialMode)
if err != nil { if err != nil {
log.Fatalf("could not open serial port: %v", err) log.Fatalf("could not open serial port: %v", err)
} }
log.Info("connected") log.Info("connected")
defer func() { defer func() {
@ -45,6 +49,7 @@ func main() {
if err != nil { if err != nil {
log.Fatalf("could not close port: %v", err) log.Fatalf("could not close port: %v", err)
} }
log.Info("serial port closed") log.Info("serial port closed")
}() }()

View file

@ -7,6 +7,7 @@ const signerTimeFormat = "010203042006.05"
func Encode24BitLength(data []byte) []byte { func Encode24BitLength(data []byte) []byte {
lengthBytes := make([]byte, 4) lengthBytes := make([]byte, 4)
binary.BigEndian.PutUint32(lengthBytes, uint32(len(data))) binary.BigEndian.PutUint32(lengthBytes, uint32(len(data)))
return lengthBytes[1:] return lengthBytes[1:]
} }
@ -17,10 +18,12 @@ func Decode24BitLength(bytes []byte) int {
func CalculateXorCheckSum(byteBlocks [][]byte) byte { func CalculateXorCheckSum(byteBlocks [][]byte) byte {
var result byte = 0x0 var result byte = 0x0
for _, byteBlock := range byteBlocks { for _, byteBlock := range byteBlocks {
for _, b := range byteBlock { for _, b := range byteBlock {
result ^= b result ^= b
} }
} }
return result return result
} }

View file

@ -9,13 +9,21 @@ import (
"git.cacert.org/cacert-gosigner/shared" "git.cacert.org/cacert-gosigner/shared"
) )
const (
headerPosSystem = 2
headerPosRoot = 3
headerPosProfile = 4
headerPosSignatureAlgorithm = 5
headerPosDay = 6
)
type SignerRequest struct { type SignerRequest struct {
Version uint8 Version uint8
Action shared.Action Action shared.Action
System shared.CryptoSystemId System shared.CryptoSystemID
Root shared.CryptoSystemRootId Root shared.CryptoSystemRootID
Profile shared.CertificateProfileId Profile shared.CertificateProfileID
MdAlgorithm shared.MessageDigestAlgorithmId MdAlgorithm shared.SignatureAlgorithmID
Days uint16 Days uint16
Spkac uint8 Spkac uint8
Content1 []byte Content1 []byte
@ -24,33 +32,33 @@ type SignerRequest struct {
} }
func SignerRequestFromData(blockData []byte) (*SignerRequest, error) { func SignerRequestFromData(blockData []byte) (*SignerRequest, error) {
headerLength := Decode24BitLength(blockData[0:3]) headerLength := Decode24BitLength(blockData[0:shared.LengthFieldSize])
headerBytes := blockData[3 : 3+headerLength] headerBytes := blockData[shared.LengthFieldSize : shared.LengthFieldSize+headerLength]
contentBytes := blockData[3+headerLength:] contentBytes := blockData[shared.LengthFieldSize+headerLength:]
content1Length := Decode24BitLength(contentBytes[0:3]) contentLen := Decode24BitLength(contentBytes[0:shared.LengthFieldSize])
content1 := contentBytes[3 : 3+content1Length] content := contentBytes[shared.LengthFieldSize : shared.LengthFieldSize+contentLen]
content2Offset := 3 + content1Length argument1Offset := shared.LengthFieldSize + contentLen
content2Length := Decode24BitLength(contentBytes[content2Offset : content2Offset+3]) argument1Len := Decode24BitLength(contentBytes[argument1Offset : argument1Offset+shared.LengthFieldSize])
content2 := contentBytes[3+content2Offset : 3+content2Offset+content2Length] argument1 := contentBytes[shared.LengthFieldSize+argument1Offset : shared.LengthFieldSize+argument1Offset+argument1Len]
content3Offset := 3 + content2Offset + content2Length argument2Offset := shared.LengthFieldSize + argument1Offset + argument1Len
content3Length := Decode24BitLength(contentBytes[content3Offset : content3Offset+3]) argument2Len := Decode24BitLength(contentBytes[argument2Offset : argument2Offset+shared.LengthFieldSize])
content3 := contentBytes[3+content3Offset : 3+content3Offset+content3Length] argument2 := contentBytes[shared.LengthFieldSize+argument2Offset : shared.LengthFieldSize+argument2Offset+argument2Len]
return &SignerRequest{ return &SignerRequest{
Version: headerBytes[0], Version: headerBytes[headerPosVersion],
Action: shared.Action(headerBytes[1]), Action: shared.Action(headerBytes[headerPosAction]),
System: shared.CryptoSystemId(headerBytes[2]), System: shared.CryptoSystemID(headerBytes[headerPosSystem]),
Root: shared.CryptoSystemRootId(headerBytes[3]), Root: shared.CryptoSystemRootID(headerBytes[headerPosRoot]),
Profile: shared.CertificateProfileId(headerBytes[4]), Profile: shared.CertificateProfileID(headerBytes[headerPosProfile]),
MdAlgorithm: shared.MessageDigestAlgorithmId(headerBytes[5]), MdAlgorithm: shared.SignatureAlgorithmID(headerBytes[headerPosSignatureAlgorithm]),
Days: binary.BigEndian.Uint16([]byte{headerBytes[6], headerBytes[7]}), Days: binary.BigEndian.Uint16(headerBytes[headerPosDay : headerPosDay+1]),
Spkac: headerBytes[8], Spkac: headerBytes[8],
Content1: content1, Content1: content,
Content2: content2, Content2: argument1,
Content3: content3, Content3: argument2,
}, nil }, nil
} }
@ -76,6 +84,7 @@ func (r *SignerRequest) Serialize() []byte {
Encode24BitLength(content2Bytes), content2Bytes, Encode24BitLength(content2Bytes), content2Bytes,
Encode24BitLength(content3Bytes), content3Bytes, Encode24BitLength(content3Bytes), content3Bytes,
}, []byte{}) }, []byte{})
return bytes.Join([][]byte{Encode24BitLength(blockBytes), blockBytes}, []byte{}) return bytes.Join([][]byte{Encode24BitLength(blockBytes), blockBytes}, []byte{})
} }
@ -97,9 +106,11 @@ func (r *SignerRequest) String() string {
} }
func shorten(original []byte) []byte { func shorten(original []byte) []byte {
if len(original) > 20 { const maxLength = 20
return original[:20] if len(original) > maxLength {
return original[:maxLength]
} }
return original return original
} }

View file

@ -8,6 +8,16 @@ import (
"git.cacert.org/cacert-gosigner/shared" "git.cacert.org/cacert-gosigner/shared"
) )
const (
headerPosVersion = 0
headerPosAction = 1
headerPosReserved1 = 2
headerPosReserved2 = 3
blockPosContent = 0
blockPosArgument1 = 1
blockPosArgument2 = 2
)
type SignerResponse struct { type SignerResponse struct {
Version uint8 Version uint8
Action shared.Action Action shared.Action
@ -19,23 +29,25 @@ type SignerResponse struct {
} }
func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte) (*SignerResponse, error) { func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte) (*SignerResponse, error) {
if len(blockData) < 3 { if len(blockData) < shared.LengthFieldSize {
return nil, errors.New("begin of structure corrupt") return nil, errors.New("begin of structure corrupt")
} }
offset := 0 offset := 0
headerLength := Decode24BitLength(blockData[offset : offset+3]) headerLength := Decode24BitLength(blockData[offset : offset+shared.LengthFieldSize])
offset += 3 offset += shared.LengthFieldSize
headerBytes := blockData[offset : offset+headerLength] headerBytes := blockData[offset : offset+headerLength]
offset += headerLength offset += headerLength
content := make([][]byte, 3) content := make([][]byte, 0)
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)-shared.LengthFieldSize < dataLength {
return nil, errors.New("structure cut off") return nil, errors.New("structure cut off")
} }
offset += 3
offset += shared.LengthFieldSize
content = append(content, blockData[offset:offset+dataLength]) content = append(content, blockData[offset:offset+dataLength])
offset += dataLength offset += dataLength
} }
@ -46,13 +58,13 @@ func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte)
} }
return &SignerResponse{ return &SignerResponse{
Version: headerBytes[0], Version: headerBytes[headerPosVersion],
Action: shared.Action(headerBytes[1]), Action: shared.Action(headerBytes[headerPosAction]),
Reserved1: headerBytes[2], Reserved1: headerBytes[headerPosReserved1],
Reserved2: headerBytes[3], Reserved2: headerBytes[headerPosReserved2],
Content: content[0], Content: content[blockPosContent],
Argument1: content[1], Argument1: content[blockPosArgument1],
Argument2: content[2], Argument2: content[blockPosArgument2],
}, nil }, nil
} }
@ -64,6 +76,7 @@ func (r SignerResponse) Serialize() []byte {
Encode24BitLength(r.Argument1), r.Argument1, Encode24BitLength(r.Argument1), r.Argument1,
Encode24BitLength(r.Argument2), r.Argument2, Encode24BitLength(r.Argument2), r.Argument2,
}, []byte{}) }, []byte{})
return bytes.Join([][]byte{Encode24BitLength(blockBytes), blockBytes}, []byte{}) return bytes.Join([][]byte{Encode24BitLength(blockBytes), blockBytes}, []byte{})
} }

View file

@ -13,12 +13,15 @@ import (
func ReceiveBytes(port io.Reader, count int, timeout time.Duration) ([]byte, error) { func ReceiveBytes(port io.Reader, count int, timeout time.Duration) ([]byte, error) {
readCh := make(chan []byte, 1) readCh := make(chan []byte, 1)
errCh := make(chan error, 1) errCh := make(chan error, 1)
go func() { go func() {
buffer := bytes.NewBuffer([]byte{}) buffer := bytes.NewBuffer([]byte{})
for remainder := count; remainder > 0; { for remainder := count; remainder > 0; {
data := make([]byte, remainder) 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 return
} else if readBytes > 0 { } else if readBytes > 0 {
buffer.Write(data[0:readBytes]) buffer.Write(data[0:readBytes])
@ -26,6 +29,7 @@ func ReceiveBytes(port io.Reader, count int, timeout time.Duration) ([]byte, err
log.Tracef("%d bytes read, remaining %d", readBytes, remainder) log.Tracef("%d bytes read, remaining %d", readBytes, remainder)
} }
} }
readCh <- buffer.Bytes() readCh <- buffer.Bytes()
close(readCh) close(readCh)
}() }()
@ -38,19 +42,24 @@ func ReceiveBytes(port io.Reader, count int, timeout time.Duration) ([]byte, err
return nil, err return nil, err
case data := <-readCh: case data := <-readCh:
log.Tracef("received %d bytes from channel", len(data)) log.Tracef("received %d bytes from channel", len(data))
if data == nil { if data == nil {
break break
} }
buffer.Write(data) buffer.Write(data)
} }
return buffer.Bytes(), nil return buffer.Bytes(), nil
} }
func SendBytes(port io.Writer, data []byte) error { func SendBytes(port io.Writer, data []byte) error {
if bytesWritten, err := port.Write(data); err != nil { n, err := port.Write(data)
return err if err != nil {
} else { return fmt.Errorf("could not send bytes: %w", err)
log.Tracef("wrote %d bytes", bytesWritten)
} }
log.Tracef("wrote %d bytes", n)
return nil return nil
} }

View file

@ -19,7 +19,7 @@ type Action byte
const ( const (
ActionNul = Action(0) ActionNul = Action(0)
ActionSign = Action(1) ActionSign = Action(1)
ActionRevoke = Action(2) ActionRevoke = Action(2) // nolint:gomnd
) )
func (a Action) String() string { func (a Action) String() string {
@ -35,10 +35,10 @@ func (a Action) String() string {
} }
} }
type CryptoSystemRootId byte type CryptoSystemRootID byte
type CertificateProfileId byte type CertificateProfileID byte
type MessageDigestAlgorithmId byte type SignatureAlgorithmID byte
type CryptoSystemId byte type CryptoSystemID byte

View file

@ -19,8 +19,8 @@ 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/openpgp_ops" "git.cacert.org/cacert-gosigner/signer/openpgpops"
"git.cacert.org/cacert-gosigner/signer/x509_ops" "git.cacert.org/cacert-gosigner/signer/x509ops"
) )
type CommandProcessorSettings struct { type CommandProcessorSettings struct {
@ -34,7 +34,7 @@ type CommandProcessorSettings struct {
// functionality. // functionality.
type CommandProcessor struct { type CommandProcessor struct {
Settings *CommandProcessorSettings Settings *CommandProcessorSettings
CryptoSystems map[shared.CryptoSystemId]*CryptoSystem CryptoSystems map[shared.CryptoSystemID]*CryptoSystem
} }
// Process the signer request // Process the signer request
@ -56,11 +56,11 @@ func (p *CommandProcessor) Process(command *datastructures.SignerRequest) (
case shared.ActionRevoke: case shared.ActionRevoke:
return p.handleRevokeAction(command) return p.handleRevokeAction(command)
default: default:
return nil, errors.New(fmt.Sprintf( return nil, fmt.Errorf(
"unsupported Action 0x%02x %s", "unsupported Action 0x%02x %s",
int(command.Action), int(command.Action),
command.Action, command.Action,
)) )
} }
} }
@ -69,10 +69,12 @@ func (*CommandProcessor) handleNulAction(command *datastructures.SignerRequest)
error, error,
) { ) {
var timeSpec unix.Timespec var timeSpec unix.Timespec
err := unix.ClockGettime(unix.CLOCK_REALTIME, &timeSpec) err := unix.ClockGettime(unix.CLOCK_REALTIME, &timeSpec)
if err != nil { if err != nil {
log.Errorf("could not get system time: %v", err) log.Errorf("could not get system time: %v", err)
} }
log.Debugf("current system time is %v", timeSpec) log.Debugf("current system time is %v", timeSpec)
// TODO: calculate the actual system time from the payload // TODO: calculate the actual system time from the payload
_, _, e1 := unix.Syscall( _, _, e1 := unix.Syscall(
@ -101,6 +103,7 @@ func (p *CommandProcessor) handleSignAction(
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debugf("identified id system: %s", idSystem) log.Debugf("identified id system: %s", idSystem)
switch command.System { switch command.System {
@ -109,19 +112,21 @@ func (p *CommandProcessor) handleSignAction(
san := command.Content2 san := command.Content2
subject := command.Content3 subject := command.Content3
if content, err := p.signX509Certificate(idSystem, command.Days, command.Spkac, request, san, subject); err != nil { content, err := p.signX509Certificate(idSystem, command.Days, command.Spkac, request, san, subject)
return nil, err if err != nil {
} else { return nil, fmt.Errorf("could not sign X.509 certificate: %w", err)
return datastructures.NewSignResponse(command.Version, content), nil
} }
return datastructures.NewSignResponse(command.Version, content), nil
case CsOpenPGP: case CsOpenPGP:
pubKey := command.Content1 pubKey := command.Content1
if content, err := p.signOpenpgpKey(idSystem, command.Days, pubKey); err != nil { content, err := p.signOpenpgpKey(idSystem, command.Days, pubKey)
return nil, err if err != nil {
} else { return nil, fmt.Errorf("could not sign OpenPGP key: %w", err)
return datastructures.NewSignResponse(command.Version, content), nil
} }
return datastructures.NewSignResponse(command.Version, content), nil
default: default:
return nil, fmt.Errorf("sign not implemented for crypto system %s", idSystem.System) return nil, fmt.Errorf("sign not implemented for crypto system %s", idSystem.System)
} }
@ -144,71 +149,78 @@ func (p *CommandProcessor) handleRevokeAction(
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debugf("identified id system: %+v", idSystem) log.Debugf("identified id system: %+v", idSystem)
switch command.System { switch command.System {
case CsX509: case CsX509:
request := command.Content1 request := command.Content1
clientHash := command.Content3 clientHash := command.Content3
if content, err := p.revokeX509(idSystem, request, clientHash); err != nil {
return nil, err content, err := p.revokeX509(idSystem, request, clientHash)
} else { if err != nil {
return datastructures.NewRevokeResponse(command.Version, content), nil return nil, fmt.Errorf("could not revoke X.509 certificate: %w", err)
} }
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)
} }
} }
type IdSystemParameters struct { type IDSystemParams struct {
System *CryptoSystem System *CryptoSystem
Root interface{} Root interface{}
Profile interface{} Profile interface{}
MessageDigestAlgorithm interface{} MessageDigestAlgorithm interface{}
} }
func (s *IdSystemParameters) String() string { func (s *IDSystemParams) String() string {
return fmt.Sprintf("%s r:%s p:%s m:%s", s.System, s.Root, s.Profile, s.MessageDigestAlgorithm) return fmt.Sprintf("%s r:%s p:%s m:%s", s.System, s.Root, s.Profile, s.MessageDigestAlgorithm)
} }
func (p *CommandProcessor) checkIdentitySystem( func (p *CommandProcessor) checkIdentitySystem(
systemId shared.CryptoSystemId, systemID shared.CryptoSystemID,
rootId shared.CryptoSystemRootId, rootID shared.CryptoSystemRootID,
profileId shared.CertificateProfileId, profileID shared.CertificateProfileID,
algorithmId shared.MessageDigestAlgorithmId, algorithmID shared.SignatureAlgorithmID,
) (*IdSystemParameters, error) { ) (*IDSystemParams, error) {
cryptoSystem, ok := p.CryptoSystems[systemId] cryptoSystem, ok := p.CryptoSystems[systemID]
if !ok { if !ok {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"unsupported crypto system %d", "unsupported crypto system %d",
systemId, systemID,
) )
} }
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 %d for crypto system %s", "unsupported root %d for crypto system %s",
rootId, rootID,
cryptoSystem, cryptoSystem,
) )
} }
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 profile %d for crypto system %s", "invalid profile %d for crypto system %s",
profileId, profileID,
cryptoSystem, cryptoSystem,
) )
} }
mdAlgorithm, ok := p.CryptoSystems[systemId].DigestAlgorithms[algorithmId]
mdAlgorithm, ok := p.CryptoSystems[systemID].DigestAlgorithms[algorithmID]
if !ok { if !ok {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"unsupported digest algorithm %d for crypto system %s", "unsupported digest algorithm %d for crypto system %s",
algorithmId, algorithmID,
cryptoSystem, cryptoSystem,
) )
} }
return &IdSystemParameters{
return &IDSystemParams{
System: cryptoSystem, System: cryptoSystem,
Root: root, Root: root,
Profile: profile, Profile: profile,
@ -216,8 +228,8 @@ func (p *CommandProcessor) checkIdentitySystem(
}, nil }, nil
} }
func (p *CommandProcessor) revokeX509(system *IdSystemParameters, request []byte, clientHash []byte) ([]byte, error) { func (p *CommandProcessor) revokeX509(system *IDSystemParams, request []byte, clientHash []byte) ([]byte, error) {
x509Root := system.Root.(*x509_ops.Root) x509Root := system.Root.(*x509ops.Root)
signatureAlgorithm := system.MessageDigestAlgorithm.(x509.SignatureAlgorithm) signatureAlgorithm := system.MessageDigestAlgorithm.(x509.SignatureAlgorithm)
log.Debugf("revoke X.509 for root %s", x509Root) log.Debugf("revoke X.509 for root %s", x509Root)
@ -232,17 +244,19 @@ func (p *CommandProcessor) revokeX509(system *IdSystemParameters, request []byte
if len(request) > 0 { if len(request) > 0 {
_, err = x509Root.RevokeCertificate(request) _, 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: %w", err)
} }
} }
crlBytes, newHash, err = x509Root.GenerateCrl(signatureAlgorithm) crlBytes, newHash, err = x509Root.GenerateCrl(signatureAlgorithm)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not generate a new CRL for root %s: %v", x509Root, err) return nil, fmt.Errorf("could not generate a new CRL for root %s: %w", x509Root, err)
} }
log.Debugf("crlBytes: %d", len(crlBytes)) log.Debugf("crlBytes: %d", len(crlBytes))
var content []byte var content []byte
oldCrlFile := x509Root.GetCrlFileName(string(clientHash)) oldCrlFile := x509Root.GetCrlFileName(string(clientHash))
newCrlFile := x509Root.GetCrlFileName(hex.EncodeToString(newHash[:])) newCrlFile := x509Root.GetCrlFileName(hex.EncodeToString(newHash[:]))
@ -254,8 +268,10 @@ func (p *CommandProcessor) revokeX509(system *IdSystemParameters, request []byte
if err != nil { if err != nil {
log.Warnf("could not generate xdelta: %v", err) log.Warnf("could not generate xdelta: %v", err)
} }
log.Tracef("xdelta produced %d bytes", len(content)) log.Tracef("xdelta produced %d bytes", len(content))
} }
if content == nil { if content == nil {
content = pem.EncodeToMemory(&pem.Block{ content = pem.EncodeToMemory(&pem.Block{
Type: "X509 CRL", Type: "X509 CRL",
@ -270,50 +286,56 @@ func (p *CommandProcessor) revokeX509(system *IdSystemParameters, request []byte
func (p *CommandProcessor) buildXDelta(oldFile string, newFile string) ([]byte, error) { func (p *CommandProcessor) buildXDelta(oldFile string, newFile string) ([]byte, error) {
patchFile, err := ioutil.TempFile(os.TempDir(), "*.patch") patchFile, err := ioutil.TempFile(os.TempDir(), "*.patch")
if err != nil { if err != nil {
return nil, fmt.Errorf("could not create temporary file for patch: %v", err) return nil, fmt.Errorf("could not create temporary file for patch: %w", err)
} }
patchName := patchFile.Name()
defer func() { defer func() {
if err := os.Remove(patchFile.Name()); err != nil { if err := os.Remove(patchName); err != nil {
log.Warnf("could not remove temporary file %s: %v", patchFile.Name(), err) log.Warnf("could not remove temporary file %s: %v", patchName, err)
} }
}() }()
if err = patchFile.Close(); err != nil { if err = patchFile.Close(); err != nil {
return nil, fmt.Errorf("could not close temporary file: %v", err) return nil, fmt.Errorf("could not close temporary file: %w", err)
} }
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := exec.Command(p.Settings.XDeltaPath, "delta", oldFile, newFile, patchFile.Name())
// #nosec G204 no parameters are based on user input
cmd := exec.Command(p.Settings.XDeltaPath, "delta", oldFile, newFile, patchName)
cmd.Stdout = buf cmd.Stdout = buf
cmd.Stderr = buf cmd.Stderr = buf
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
switch err.(type) { var e *exec.ExitError
case *exec.ExitError: if !errors.As(err, &e) || e.ExitCode() != 1 {
if err.(*exec.ExitError).ExitCode() == 1 {
// xdelta delta exits with status code 1 if a delta has been found // xdelta delta exits with status code 1 if a delta has been found
break
}
return nil, fmt.Errorf( return nil, fmt.Errorf(
"xdelta command '%s' did not work correctly: %v\noutput was:\n%s", "xdelta command '%s' did not work correctly: %w\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, " "), strings.Join(cmd.Args, " "),
err, err,
buf.String(), buf.String(),
) )
} }
} }
return ioutil.ReadFile(patchFile.Name())
return ioutil.ReadFile(patchName)
} }
func (p *CommandProcessor) signX509Certificate(system *IdSystemParameters, days uint16, spkac uint8, request []byte, san []byte, subject []byte) ([]byte, error) { func (p *CommandProcessor) signX509Certificate(
x509Root := system.Root.(*x509_ops.Root) system *IDSystemParams,
days uint16,
spkac uint8,
request []byte,
san []byte,
subject []byte,
) ([]byte, error) {
x509Root := system.Root.(*x509ops.Root)
signatureAlgorithm := system.MessageDigestAlgorithm.(x509.SignatureAlgorithm) signatureAlgorithm := system.MessageDigestAlgorithm.(x509.SignatureAlgorithm)
profile := system.Profile.(*x509_ops.Profile) profile := system.Profile.(*x509ops.Profile)
log.Debugf( log.Debugf(
"sign X.509 certificate for root %s using profile %s and signature algorithm %s", "sign X.509 certificate for root %s using profile %s and signature algorithm %s",
@ -330,7 +352,7 @@ func (p *CommandProcessor) signX509Certificate(system *IdSystemParameters, days
content, err := x509Root.SignCertificate( content, err := x509Root.SignCertificate(
profile, profile,
signatureAlgorithm, signatureAlgorithm,
&x509_ops.SigningRequestParameters{ &x509ops.SigningRequestParameters{
Request: request, Request: request,
Subject: subject, Subject: subject,
SubjectAlternativeNames: san, SubjectAlternativeNames: san,
@ -339,21 +361,21 @@ func (p *CommandProcessor) signX509Certificate(system *IdSystemParameters, days
}, },
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("could not sign X.509 CSR with root %s and profile %s: %v", x509Root, profile, err) return nil, fmt.Errorf("could not sign X.509 CSR with root %s and profile %s: %w", x509Root, profile, err)
} }
return content, nil return content, nil
} }
func (p *CommandProcessor) signOpenpgpKey(system *IdSystemParameters, days uint16, pubKey []byte) ([]byte, error) { func (p *CommandProcessor) signOpenpgpKey(system *IDSystemParams, days uint16, pubKey []byte) ([]byte, error) {
openPgpRoot := system.Root.(*openpgp_ops.OpenPGPRoot) openPgpRoot := system.Root.(*openpgpops.OpenPGPRoot)
signatureAlgorithm := system.MessageDigestAlgorithm.(crypto.Hash) signatureAlgorithm := system.MessageDigestAlgorithm.(crypto.Hash)
log.Debugf("sign openpgp for root %s", openPgpRoot) log.Debugf("sign openpgpops for root %s", openPgpRoot)
content, err := openPgpRoot.SignPublicKey(pubKey, signatureAlgorithm, days) content, err := openPgpRoot.SignPublicKey(pubKey, signatureAlgorithm, days)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not sign openpgp public key with root %s: %v", openPgpRoot, err) return nil, fmt.Errorf("could not sign openpgpops public key with root %s: %w", openPgpRoot, err)
} }
return content, nil return content, nil
@ -364,18 +386,21 @@ func NewCommandProcessorSettings() *CommandProcessorSettings {
if !ok { if !ok {
caBasedir = "." caBasedir = "."
} }
gpgKeyringDir, ok := os.LookupEnv("SIGNER_GPG_KEYRING_DIR") gpgKeyringDir, ok := os.LookupEnv("SIGNER_GPG_KEYRING_DIR")
if !ok { if !ok {
gpgKeyringDir = "." gpgKeyringDir = "."
} }
gpgUidEmail, ok := os.LookupEnv("SIGNER_GPG_ID")
gpgUIDEmail, ok := os.LookupEnv("SIGNER_GPG_ID")
if !ok { if !ok {
gpgUidEmail = "gpg@cacert.org" gpgUIDEmail = "gpg@cacert.org"
} }
return &CommandProcessorSettings{ return &CommandProcessorSettings{
CABaseDir: caBasedir, CABaseDir: caBasedir,
OpenPGPKeyRingDir: gpgKeyringDir, OpenPGPKeyRingDir: gpgKeyringDir,
OpenPGPUidEmail: gpgUidEmail, OpenPGPUidEmail: gpgUIDEmail,
XDeltaPath: "/usr/bin/xdelta", XDeltaPath: "/usr/bin/xdelta",
} }
} }

View file

@ -1,17 +0,0 @@
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
}

View file

@ -6,9 +6,9 @@ import (
type CryptoSystem struct { type CryptoSystem struct {
Name string Name string
Roots map[shared.CryptoSystemRootId]interface{} Roots map[shared.CryptoSystemRootID]interface{}
Profiles map[shared.CertificateProfileId]interface{} Profiles map[shared.CertificateProfileID]interface{}
DigestAlgorithms map[shared.MessageDigestAlgorithmId]interface{} DigestAlgorithms map[shared.SignatureAlgorithmID]interface{}
} }
func (system CryptoSystem) String() string { func (system CryptoSystem) String() string {

View file

@ -1,8 +1,9 @@
package openpgp_ops package openpgpops
import ( import (
"bytes" "bytes"
"crypto" "crypto"
"errors"
"fmt" "fmt"
"os" "os"
"time" "time"
@ -13,6 +14,8 @@ import (
"golang.org/x/crypto/openpgp/packet" "golang.org/x/crypto/openpgp/packet"
) )
const hoursInADay = 24
type OpenPGPRoot struct { type OpenPGPRoot struct {
Name string Name string
SecretKeyRing string SecretKeyRing string
@ -22,24 +25,27 @@ type OpenPGPRoot struct {
func (r *OpenPGPRoot) SignPublicKey(pubKey []byte, algorithm crypto.Hash, days uint16) ([]byte, error) { func (r *OpenPGPRoot) SignPublicKey(pubKey []byte, algorithm crypto.Hash, days uint16) ([]byte, error) {
signingKey, err := r.findSigningKey(r.Identifier) signingKey, err := r.findSigningKey(r.Identifier)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not find a signing key matching %s: %v", r.Identifier, err) return nil, fmt.Errorf("could not find a signing key matching %s: %w", r.Identifier, err)
} }
pubKeyRing, err := openpgp.ReadKeyRing(bytes.NewReader(pubKey)) pubKeyRing, err := openpgp.ReadKeyRing(bytes.NewReader(pubKey))
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read openpgp keyring: %v", err) return nil, fmt.Errorf("could not read openpgpops keyring: %w", err)
} }
output := bytes.NewBuffer([]byte{}) output := bytes.NewBuffer([]byte{})
armorOutput, err := armor.Encode(output, "PGP PUBLIC KEY BLOCK", map[string]string{}) armorOutput, err := armor.Encode(output, "PGP PUBLIC KEY BLOCK", map[string]string{})
if err != nil { if err != nil {
return nil, fmt.Errorf("could not create ASCII armor wrapper for openpgp output: %v", err) return nil, fmt.Errorf("could not create ASCII armor wrapper for openpgpops output: %w", err)
} }
for _, pe := range pubKeyRing { for _, pe := range pubKeyRing {
log.Tracef("found %+v", pe.PrimaryKey.KeyIdString()) log.Tracef("found %+v", pe.PrimaryKey.KeyIdString())
for _, i := range pe.Identities { for _, i := range pe.Identities {
expiry := calculateExpiry(i, days) expiry := calculateExpiry(i, days)
if !i.SelfSignature.KeyExpired(time.Now()) { if !i.SelfSignature.KeyExpired(time.Now()) {
sig := &packet.Signature{ sig := &packet.Signature{
SigType: packet.SigTypeGenericCert, SigType: packet.SigTypeGenericCert,
@ -52,14 +58,16 @@ func (r *OpenPGPRoot) SignPublicKey(pubKey []byte, algorithm crypto.Hash, days u
if err := sig.SignUserId(i.Name, pe.PrimaryKey, signingKey.PrivateKey, &packet.Config{ if err := sig.SignUserId(i.Name, pe.PrimaryKey, signingKey.PrivateKey, &packet.Config{
DefaultHash: algorithm, DefaultHash: algorithm,
}); err != nil { }); err != nil {
return nil, fmt.Errorf("could not sign identity %s: %v", i.Name, err) return nil, fmt.Errorf("could not sign identity %s: %w", i.Name, err)
} }
i.Signatures = append(i.Signatures, sig) i.Signatures = append(i.Signatures, sig)
} }
} }
if err = pe.Serialize(armorOutput); err != nil { if err = pe.Serialize(armorOutput); err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"could not write signed public key %s to output: %v", "could not write signed public key %s to output: %w",
pe.PrimaryKey.KeyIdString(), pe.PrimaryKey.KeyIdString(),
err, err,
) )
@ -67,7 +75,7 @@ func (r *OpenPGPRoot) SignPublicKey(pubKey []byte, algorithm crypto.Hash, days u
} }
if err = armorOutput.Close(); err != nil { if err = armorOutput.Close(); err != nil {
return nil, fmt.Errorf("could not close output stream: %v", err) return nil, fmt.Errorf("could not close output stream: %w", err)
} }
log.Tracef("signed public key\n%s", output.String()) log.Tracef("signed public key\n%s", output.String())
@ -77,35 +85,41 @@ func (r *OpenPGPRoot) SignPublicKey(pubKey []byte, algorithm crypto.Hash, days u
func calculateExpiry(i *openpgp.Identity, days uint16) *uint32 { func calculateExpiry(i *openpgp.Identity, days uint16) *uint32 {
maxExpiry := time.Second * time.Duration(*i.SelfSignature.KeyLifetimeSecs) maxExpiry := time.Second * time.Duration(*i.SelfSignature.KeyLifetimeSecs)
calcExpiry := time.Hour * 24 * time.Duration(days) calcExpiry := time.Hour * hoursInADay * time.Duration(days)
if calcExpiry > maxExpiry { if calcExpiry > maxExpiry {
calcExpiry = maxExpiry calcExpiry = maxExpiry
} }
expirySeconds := uint32(calcExpiry.Seconds()) expirySeconds := uint32(calcExpiry.Seconds())
return &expirySeconds return &expirySeconds
} }
func (r *OpenPGPRoot) findSigningKey(identifier string) (*openpgp.Entity, error) { func (r *OpenPGPRoot) findSigningKey(identifier string) (*openpgp.Entity, error) {
keyring, err := os.Open(r.SecretKeyRing) keyring, err := os.Open(r.SecretKeyRing)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not open secret keyring: %v", err) return nil, fmt.Errorf("could not open secret keyring: %w", err)
} }
defer func() { _ = keyring.Close() }() defer func() { _ = keyring.Close() }()
el, err := openpgp.ReadKeyRing(keyring) el, err := openpgp.ReadKeyRing(keyring)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read keyring: %v", err) return nil, fmt.Errorf("could not read keyring: %w", err)
} }
for _, e := range el { for _, e := range el {
log.Tracef("found %s", e.PrimaryKey.KeyIdString()) log.Tracef("found %s", e.PrimaryKey.KeyIdString())
for _, i := range e.Identities { for _, i := range e.Identities {
if i.UserId.Email == identifier && len(e.Revocations) == 0 && !i.SelfSignature.KeyExpired(time.Now()) { if i.UserId.Email == identifier && len(e.Revocations) == 0 && !i.SelfSignature.KeyExpired(time.Now()) {
return e, nil return e, nil
} }
} }
} }
return nil, fmt.Errorf("no matching key found")
return nil, errors.New("no matching key found")
} }
type OpenPGPProfile struct { type OpenPGPProfile struct {

View file

@ -33,6 +33,7 @@ type PortHandler struct {
func (p *PortHandler) MainLoop() { func (p *PortHandler) MainLoop() {
count := 0 count := 0
for { for {
go p.Receive() go p.Receive()
@ -40,9 +41,12 @@ func (p *PortHandler) MainLoop() {
case command := <-p.commandChan: case command := <-p.commandChan:
if command == nil { if command == nil {
log.Infof("command channel has been closed. stopping execution.") log.Infof("command channel has been closed. stopping execution.")
return return
} }
log.Debugf("received command %v", command) log.Debugf("received command %v", command)
response, err := p.processor.Process(command) response, err := p.processor.Process(command)
if err != nil { if err != nil {
log.Errorf("error processing command: %v", err) log.Errorf("error processing command: %v", err)
@ -61,68 +65,62 @@ func (p *PortHandler) MainLoop() {
// Receive a request and generate a request data structure // Receive a request and generate a request data structure
func (p *PortHandler) Receive() { func (p *PortHandler) Receive() {
header, err := shared.ReceiveBytes(p.port, 1, waitForInitialByte) if err := p.receiveHandshake(); err != nil {
if err != nil {
p.errors <- err p.errors <- err
return
}
if header[0] != shared.HandshakeByte {
p.errors <- fmt.Errorf(
"unexpected byte 0x%x expected 0x%x",
header[0],
shared.HandshakeByte,
)
return
}
tries := maxTriesPerBlock return
}
var command *datastructures.SignerRequest var command *datastructures.SignerRequest
for {
if tries <= 0 { for tries := maxTriesPerBlock; tries > 0; tries-- {
p.errors <- errors.New("tried reading block too often")
break
}
tries--
if err := shared.SendBytes(p.port, []byte{shared.AckByte}); err != nil { if err := shared.SendBytes(p.port, []byte{shared.AckByte}); err != nil {
p.errors <- fmt.Errorf("could not write ACK byte: %v", err) p.errors <- fmt.Errorf("could not write ACK byte: %w", err)
break
return
} }
lengthBytes, err := shared.ReceiveBytes(p.port, shared.LengthFieldSize, waitForLengthBytes) lengthBytes, err := shared.ReceiveBytes(p.port, shared.LengthFieldSize, waitForLengthBytes)
if err != nil { if err != nil {
p.errors <- fmt.Errorf("could not read lenght bytes: %v", err) p.errors <- fmt.Errorf("could not read length bytes: %w", err)
break
return
} }
blockLength := datastructures.Decode24BitLength(lengthBytes) blockLength := datastructures.Decode24BitLength(lengthBytes)
blockData, err := shared.ReceiveBytes(p.port, blockLength, waitForBlock) blockData, err := shared.ReceiveBytes(p.port, blockLength, waitForBlock)
if err != nil { if err != nil {
p.errors <- fmt.Errorf("could not read data block: %v", err) p.errors <- fmt.Errorf("could not read data block: %w", err)
if !p.requestResend() { if !p.requestResend() {
break return
} }
continue continue
} }
checkSum, err := shared.ReceiveBytes(p.port, shared.CheckSumFieldSize, waitForChecksumByte) checkSum, err := shared.ReceiveBytes(p.port, shared.CheckSumFieldSize, waitForChecksumByte)
if err != nil { if err != nil {
p.errors <- fmt.Errorf("could not read checksum byte: %v", err) p.errors <- fmt.Errorf("could not read checksum byte: %w", err)
break
return
} }
calculated := datastructures.CalculateXorCheckSum([][]byte{lengthBytes, blockData}) calculated := datastructures.CalculateXorCheckSum([][]byte{lengthBytes, blockData})
if checkSum[0] != calculated { if checkSum[0] != calculated {
p.errors <- fmt.Errorf("CRC error. expected 0x%02x, got 0x%02x", calculated, checkSum[0]) p.errors <- fmt.Errorf("CRC error. expected 0x%02x, got 0x%02x", calculated, checkSum[0])
if !p.requestResend() { if !p.requestResend() {
break return
} }
continue continue
} }
trailer, err := shared.ReceiveBytes(p.port, shared.TrailerFieldSize, waitForTrailerBytes) trailer, err := shared.ReceiveBytes(p.port, shared.TrailerFieldSize, waitForTrailerBytes)
if err != nil { if err != nil {
p.errors <- fmt.Errorf("could not read trailer bytes: %v", err) p.errors <- fmt.Errorf("could not read trailer bytes: %w", err)
break
return
} }
if string(trailer) != shared.MagicTrailer { if string(trailer) != shared.MagicTrailer {
@ -131,70 +129,99 @@ func (p *PortHandler) Receive() {
shared.MagicTrailer, shared.MagicTrailer,
trailer, trailer,
) )
if !p.requestResend() { if !p.requestResend() {
break break
} }
continue continue
} }
command, err = datastructures.SignerRequestFromData(blockData) command, err = datastructures.SignerRequestFromData(blockData)
if err != nil { if err != nil {
p.errors <- fmt.Errorf("failed to parse block as signer request: %v", err) p.errors <- fmt.Errorf("failed to parse block as signer request: %w", err)
break
return
} }
if err := shared.SendBytes(p.port, []byte{shared.AckByte}); err != nil { if err := shared.SendBytes(p.port, []byte{shared.AckByte}); err != nil {
p.errors <- fmt.Errorf("failed to send ACK byte: %v", err) p.errors <- fmt.Errorf("failed to send ACK byte: %w", err)
break
return
} }
p.commandChan <- command p.commandChan <- command
break
return
} }
p.errors <- errors.New("tried reading block too often")
}
func (p *PortHandler) receiveHandshake() error {
header, err := shared.ReceiveBytes(p.port, 1, waitForInitialByte)
if err != nil {
return fmt.Errorf("could not receive initial byte: %w", err)
}
if header[0] != shared.HandshakeByte {
return fmt.Errorf(
"unexpected byte 0x%x expected 0x%x",
header[0],
shared.HandshakeByte,
)
}
return nil
} }
func (p *PortHandler) requestResend() bool { func (p *PortHandler) requestResend() bool {
if err := shared.SendBytes(p.port, []byte{shared.ResendByte}); err != nil { if err := shared.SendBytes(p.port, []byte{shared.ResendByte}); err != nil {
p.errors <- err p.errors <- err
return false return false
} }
return true return true
} }
// Send a response to the client // Send a response to the client
func (p *PortHandler) SendResponse(response *datastructures.SignerResponse) error { func (p *PortHandler) SendResponse(response *datastructures.SignerResponse) error {
if err := shared.SendBytes(p.port, []byte{shared.HandshakeByte}); err != nil { if err := shared.SendBytes(p.port, []byte{shared.HandshakeByte}); err != nil {
return err return fmt.Errorf("could not send handshake: %w", err)
} }
if ack, err := shared.ReceiveBytes(p.port, 1, waitForHandShake); err != nil { if ack, err := shared.ReceiveBytes(p.port, 1, waitForHandShake); err != nil {
return err return fmt.Errorf("could not receive ACK: %w", err)
} else if ack[0] != shared.AckByte { } else if ack[0] != shared.AckByte {
return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0])) return fmt.Errorf("invalid ack byte 0x%02x", ack[0])
} }
tryAgain := true tryAgain := true
for tryAgain { for tryAgain {
data := response.Serialize() data := response.Serialize()
if err := shared.SendBytes(p.port, data); err != nil { if err := shared.SendBytes(p.port, data); err != nil {
return err return fmt.Errorf("could not send data block: %w", err)
} }
checksum := datastructures.CalculateXorCheckSum([][]byte{data}) checksum := datastructures.CalculateXorCheckSum([][]byte{data})
if err := shared.SendBytes(p.port, []byte{checksum}); err != nil { if err := shared.SendBytes(p.port, []byte{checksum}); err != nil {
return err return fmt.Errorf("could not send checksum: %w", err)
} }
if err := shared.SendBytes(p.port, []byte(shared.MagicTrailer)); err != nil { if err := shared.SendBytes(p.port, []byte(shared.MagicTrailer)); err != nil {
return err return fmt.Errorf("could not send trailer: %w", err)
} }
if ack, err := shared.ReceiveBytes(p.port, 1, waitForHandShake); err != nil { ack, err := shared.ReceiveBytes(p.port, 1, waitForHandShake)
return err if err != nil {
} else if ack[0] == shared.AckByte { return fmt.Errorf("could not receive ACK: %w", err)
}
if ack[0] == shared.AckByte {
tryAgain = false tryAgain = false
} else if ack[0] != 0x11 { } else {
return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0])) return fmt.Errorf("invalid ack byte 0x%02x", ack[0])
} }
} }
@ -203,6 +230,7 @@ func (p *PortHandler) SendResponse(response *datastructures.SignerResponse) erro
func NewSignerProcess(port io.ReadWriteCloser) *PortHandler { func NewSignerProcess(port io.ReadWriteCloser) *PortHandler {
errorChan := make(chan error) errorChan := make(chan error)
return &PortHandler{ return &PortHandler{
port: port, port: port,
errors: errorChan, errors: errorChan,

View file

@ -7,65 +7,65 @@ import (
"path" "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/openpgpops"
"git.cacert.org/cacert-gosigner/signer/x509_ops" "git.cacert.org/cacert-gosigner/signer/x509ops"
) )
const ( const (
CsX509 shared.CryptoSystemId = 1 CsX509 shared.CryptoSystemID = 1
CsOpenPGP shared.CryptoSystemId = 2 CsOpenPGP shared.CryptoSystemID = 2
) )
const ( const (
X509RootDefault shared.CryptoSystemRootId = 0 X509RootDefault shared.CryptoSystemRootID = 0
X509RootClass3 shared.CryptoSystemRootId = 1 X509RootClass3 shared.CryptoSystemRootID = 1
// The following roots existed in the old server.pl but had // The following roots existed in the old server.pl but had
// no profile configurations and were thus unusable // no profile configurations and were thus unusable
// //
// X509RootClass3s shared.CryptoSystemRootId = 2 // X509RootClass3s shared.CryptoSystemRootID = 2
// X509Root3 shared.CryptoSystemRootId = 3 // X509Root3 shared.CryptoSystemRootID = 3
// X509Root4 shared.CryptoSystemRootId = 4 // X509Root4 shared.CryptoSystemRootID = 4
// X509Root5 shared.CryptoSystemRootId = 5 // X509Root5 shared.CryptoSystemRootID = 5
) )
const ( const (
X509ProfileClient shared.CertificateProfileId = 0 X509ProfileClient shared.CertificateProfileID = 0
X509ProfileClientOrg shared.CertificateProfileId = 1 X509ProfileClientOrg shared.CertificateProfileID = 1
X509ProfileClientCodesign shared.CertificateProfileId = 2 X509ProfileClientCodesign shared.CertificateProfileID = 2
X509ProfileServer shared.CertificateProfileId = 5 X509ProfileServer shared.CertificateProfileID = 5
X509ProfileServerOrg shared.CertificateProfileId = 6 X509ProfileServerOrg shared.CertificateProfileID = 6
X509ProfileOCSP shared.CertificateProfileId = 8 X509ProfileOCSP shared.CertificateProfileID = 8
X509ProfileTimestamp shared.CertificateProfileId = 9 X509ProfileTimestamp shared.CertificateProfileID = 9
// the following profiles where valid options in the original signer code but had no configurations // the following profiles where valid options in the original signer code but had no configurations
// //
// X509ProfileClientMachine shared.CertificateProfileId = 3 // no configuration on original signer // X509ProfileClientMachine shared.CertificateProfileID = 3 // no configuration on original signer
// X509ProfileClientAds shared.CertificateProfileId = 4 // no configuration on original signer // X509ProfileClientAds shared.CertificateProfileID = 4 // no configuration on original signer
// X509ProfileServerJabber shared.CertificateProfileId = 7 // no configuration on original signer // X509ProfileServerJabber shared.CertificateProfileID = 7 // no configuration on original signer
// X509ProfileProxy shared.CertificateProfileId = 10 // no configuration on original signer // X509ProfileProxy shared.CertificateProfileID = 10 // no configuration on original signer
// X509ProfileSubCA shared.CertificateProfileId = 11 // no configuration on original signer // X509ProfileSubCA shared.CertificateProfileID = 11 // no configuration on original signer
) )
const ( const (
X509MDDefault shared.MessageDigestAlgorithmId = 0 X509MDDefault shared.SignatureAlgorithmID = 0
X509MDMd5 shared.MessageDigestAlgorithmId = 1 X509MDMd5 shared.SignatureAlgorithmID = 1
X509MDSha1 shared.MessageDigestAlgorithmId = 2 X509MDSha1 shared.SignatureAlgorithmID = 2
// X509MDRipeMD160 shared.MessageDigestAlgorithmId = 3 x509 package does not support RIPEMD160 // X509MDRipeMD160 shared.SignatureAlgorithmID = 3 x509ops package does not support RIPEMD160
X509MDSha256 shared.MessageDigestAlgorithmId = 8 X509MDSha256 shared.SignatureAlgorithmID = 8
X509MDSha384 shared.MessageDigestAlgorithmId = 9 X509MDSha384 shared.SignatureAlgorithmID = 9
X509MDSha512 shared.MessageDigestAlgorithmId = 10 X509MDSha512 shared.SignatureAlgorithmID = 10
) )
const ( const (
OpenPGPRoot0 shared.CryptoSystemRootId = 0 OpenPGPRoot0 shared.CryptoSystemRootID = 0
) )
const ( const (
OpenPGPDefaultProfile shared.CertificateProfileId = 0 OpenPGPDefaultProfile shared.CertificateProfileID = 0
) )
const ( const (
OpenPGPDefaultMD shared.MessageDigestAlgorithmId = 0 OpenPGPDefaultMD shared.SignatureAlgorithmID = 0
) )
func NewCommandProcessor() *CommandProcessor { func NewCommandProcessor() *CommandProcessor {
@ -76,9 +76,9 @@ func NewCommandProcessor() *CommandProcessor {
ExtKeyUsage: []x509.ExtKeyUsage{ ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageEmailProtection, x509.ExtKeyUsageEmailProtection,
x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageClientAuth,
// x509.ExtKeyUsageMicrosoftServerGatedCrypto, // x509ops.ExtKeyUsageMicrosoftServerGatedCrypto,
// 1.3.6.1.4.1.311.10.3.4 msEFS not supported by golang.org/crypto // 1.3.6.1.4.1.311.10.3.4 msEFS not supported by golang.org/crypto
// x509.ExtKeyUsageNetscapeServerGatedCrypto, // x509ops.ExtKeyUsageNetscapeServerGatedCrypto,
}, },
} }
codeSignPrototype := &x509.Certificate{ codeSignPrototype := &x509.Certificate{
@ -88,10 +88,10 @@ func NewCommandProcessor() *CommandProcessor {
x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageCodeSigning, x509.ExtKeyUsageCodeSigning,
// 1.3.6.1.4.1.311.2.1.21 msCodeInd not supported by golang.org/crypto // 1.3.6.1.4.1.311.2.1.21 msCodeInd not supported by golang.org/crypto
// x509.ExtKeyUsageMicrosoftCommercialCodeSigning, // x509ops.ExtKeyUsageMicrosoftCommercialCodeSigning,
// x509.ExtKeyUsageMicrosoftServerGatedCrypto, // x509ops.ExtKeyUsageMicrosoftServerGatedCrypto,
// 1.3.6.1.4.1.311.10.3.4 msEFS not supported by golang.org/crypto // 1.3.6.1.4.1.311.10.3.4 msEFS not supported by golang.org/crypto
// x509.ExtKeyUsageNetscapeServerGatedCrypto, // x509ops.ExtKeyUsageNetscapeServerGatedCrypto,
}, },
} }
serverPrototype := &x509.Certificate{ serverPrototype := &x509.Certificate{
@ -99,8 +99,8 @@ func NewCommandProcessor() *CommandProcessor {
ExtKeyUsage: []x509.ExtKeyUsage{ ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageServerAuth,
// x509.ExtKeyUsageMicrosoftServerGatedCrypto, // x509ops.ExtKeyUsageMicrosoftServerGatedCrypto,
// x509.ExtKeyUsageNetscapeServerGatedCrypto, // x509ops.ExtKeyUsageNetscapeServerGatedCrypto,
}, },
} }
ocspPrototype := &x509.Certificate{ ocspPrototype := &x509.Certificate{
@ -108,8 +108,8 @@ func NewCommandProcessor() *CommandProcessor {
ExtKeyUsage: []x509.ExtKeyUsage{ ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageOCSPSigning, x509.ExtKeyUsageOCSPSigning,
// x509.ExtKeyUsageMicrosoftServerGatedCrypto, // x509ops.ExtKeyUsageMicrosoftServerGatedCrypto,
// x509.ExtKeyUsageNetscapeServerGatedCrypto, // x509ops.ExtKeyUsageNetscapeServerGatedCrypto,
}, },
} }
timestampPrototype := &x509.Certificate{ timestampPrototype := &x509.Certificate{
@ -117,15 +117,15 @@ func NewCommandProcessor() *CommandProcessor {
ExtKeyUsage: []x509.ExtKeyUsage{ ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageOCSPSigning, x509.ExtKeyUsageOCSPSigning,
// x509.ExtKeyUsageMicrosoftServerGatedCrypto, // x509ops.ExtKeyUsageMicrosoftServerGatedCrypto,
// x509.ExtKeyUsageNetscapeServerGatedCrypto, // x509ops.ExtKeyUsageNetscapeServerGatedCrypto,
}, },
} }
cryptoSystems := map[shared.CryptoSystemId]*CryptoSystem{ cryptoSystems := map[shared.CryptoSystemID]*CryptoSystem{
CsX509: { CsX509: {
Name: "X.509", Name: "X.509",
Roots: map[shared.CryptoSystemRootId]interface{}{ Roots: map[shared.CryptoSystemRootID]interface{}{
X509RootDefault: x509_ops.NewRoot( X509RootDefault: x509ops.NewRoot(
settings.CABaseDir, settings.CABaseDir,
"openssl", "openssl",
"CA", "CA",
@ -135,7 +135,7 @@ func NewCommandProcessor() *CommandProcessor {
// TODO: parse OCSP endpoints from configuration // TODO: parse OCSP endpoints from configuration
[]string{"http://ocsp.cacert.localhost"}, []string{"http://ocsp.cacert.localhost"},
), ),
X509RootClass3: x509_ops.NewRoot( X509RootClass3: x509ops.NewRoot(
settings.CABaseDir, settings.CABaseDir,
"class3", "class3",
"class3", "class3",
@ -148,99 +148,99 @@ func NewCommandProcessor() *CommandProcessor {
// The following roots existed in the old server.pl but had // The following roots existed in the old server.pl but had
// no profile configurations and were thus unusable // no profile configurations and were thus unusable
// //
// X509RootClass3s: &x509_ops.Root{Name: "class3s"}, // no profile configs // X509RootClass3s: &x509ops.Root{Name: "class3s"}, // no profile configs
// X509Root3: &x509_ops.Root{Name: "root3"}, // X509Root3: &x509ops.Root{Name: "root3"},
// X509Root4: &x509_ops.Root{Name: "root4"}, // X509Root4: &x509ops.Root{Name: "root4"},
// X509Root5: &x509_ops.Root{Name: "root5"}, // X509Root5: &x509ops.Root{Name: "root5"},
}, },
Profiles: map[shared.CertificateProfileId]interface{}{ Profiles: map[shared.CertificateProfileID]interface{}{
X509ProfileClient: x509_ops.NewProfile( X509ProfileClient: x509ops.NewProfile(
"client", "client",
clientPrototype, clientPrototype,
[]x509_ops.SubjectDnField{ []x509ops.SubjectDnField{
x509_ops.SubjectDnFieldCommonName, x509ops.SubjectDnFieldCommonName,
x509_ops.SubjectDnFieldEmailAddress, x509ops.SubjectDnFieldEmailAddress,
}, },
nil, nil,
true, true,
), ),
X509ProfileClientOrg: x509_ops.NewProfile("client-org", clientPrototype, X509ProfileClientOrg: x509ops.NewProfile("client-org", clientPrototype,
[]x509_ops.SubjectDnField{ []x509ops.SubjectDnField{
x509_ops.SubjectDnFieldCountryName, x509ops.SubjectDnFieldCountryName,
x509_ops.SubjectDnFieldStateOrProvinceName, x509ops.SubjectDnFieldStateOrProvinceName,
x509_ops.SubjectDnFieldLocalityName, x509ops.SubjectDnFieldLocalityName,
x509_ops.SubjectDnFieldOrganizationName, x509ops.SubjectDnFieldOrganizationName,
x509_ops.SubjectDnFieldOrganizationalUnitName, x509ops.SubjectDnFieldOrganizationalUnitName,
x509_ops.SubjectDnFieldCommonName, x509ops.SubjectDnFieldCommonName,
x509_ops.SubjectDnFieldEmailAddress, x509ops.SubjectDnFieldEmailAddress,
}, },
nil, nil,
true, true,
), ),
X509ProfileClientCodesign: x509_ops.NewProfile("client-codesign", codeSignPrototype, X509ProfileClientCodesign: x509ops.NewProfile("client-codesign", codeSignPrototype,
[]x509_ops.SubjectDnField{ []x509ops.SubjectDnField{
x509_ops.SubjectDnFieldCountryName, x509ops.SubjectDnFieldCountryName,
x509_ops.SubjectDnFieldStateOrProvinceName, x509ops.SubjectDnFieldStateOrProvinceName,
x509_ops.SubjectDnFieldLocalityName, x509ops.SubjectDnFieldLocalityName,
x509_ops.SubjectDnFieldCommonName, x509ops.SubjectDnFieldCommonName,
x509_ops.SubjectDnFieldEmailAddress, x509ops.SubjectDnFieldEmailAddress,
}, },
nil, nil,
true, true,
), ),
// X509ProfileClientMachine: &x509_ops.Profile{Name: "client-machine"}, // X509ProfileClientMachine: &x509ops.Profile{Name: "client-machine"},
// X509ProfileClientAds: &x509_ops.Profile{Name: "client-ads"}, // X509ProfileClientAds: &x509ops.Profile{Name: "client-ads"},
X509ProfileServer: x509_ops.NewProfile("server", serverPrototype, X509ProfileServer: x509ops.NewProfile("server", serverPrototype,
[]x509_ops.SubjectDnField{ []x509ops.SubjectDnField{
x509_ops.SubjectDnFieldCommonName, x509ops.SubjectDnFieldCommonName,
}, },
[]x509_ops.AltNameType{x509_ops.NameTypeDNS, x509_ops.NameTypeXmppJid}, []x509ops.AltNameType{x509ops.NameTypeDNS, x509ops.NameTypeXMPPJid},
false, false,
), ),
X509ProfileServerOrg: x509_ops.NewProfile("server-org", serverPrototype, X509ProfileServerOrg: x509ops.NewProfile("server-org", serverPrototype,
[]x509_ops.SubjectDnField{ []x509ops.SubjectDnField{
x509_ops.SubjectDnFieldCountryName, x509ops.SubjectDnFieldCountryName,
x509_ops.SubjectDnFieldStateOrProvinceName, x509ops.SubjectDnFieldStateOrProvinceName,
x509_ops.SubjectDnFieldLocalityName, x509ops.SubjectDnFieldLocalityName,
x509_ops.SubjectDnFieldOrganizationName, x509ops.SubjectDnFieldOrganizationName,
x509_ops.SubjectDnFieldOrganizationalUnitName, x509ops.SubjectDnFieldOrganizationalUnitName,
x509_ops.SubjectDnFieldCommonName, x509ops.SubjectDnFieldCommonName,
}, },
[]x509_ops.AltNameType{x509_ops.NameTypeDNS, x509_ops.NameTypeXmppJid}, []x509ops.AltNameType{x509ops.NameTypeDNS, x509ops.NameTypeXMPPJid},
false, false,
), ),
// X509ProfileServerJabber: &x509_ops.Profile{Name: "server-jabber"}, // X509ProfileServerJabber: &x509ops.Profile{Name: "server-jabber"},
X509ProfileOCSP: x509_ops.NewProfile("ocsp", ocspPrototype, X509ProfileOCSP: x509ops.NewProfile("ocsp", ocspPrototype,
[]x509_ops.SubjectDnField{ []x509ops.SubjectDnField{
x509_ops.SubjectDnFieldCountryName, x509ops.SubjectDnFieldCountryName,
x509_ops.SubjectDnFieldStateOrProvinceName, x509ops.SubjectDnFieldStateOrProvinceName,
x509_ops.SubjectDnFieldLocalityName, x509ops.SubjectDnFieldLocalityName,
x509_ops.SubjectDnFieldOrganizationName, x509ops.SubjectDnFieldOrganizationName,
x509_ops.SubjectDnFieldOrganizationalUnitName, x509ops.SubjectDnFieldOrganizationalUnitName,
x509_ops.SubjectDnFieldCommonName, x509ops.SubjectDnFieldCommonName,
x509_ops.SubjectDnFieldEmailAddress, x509ops.SubjectDnFieldEmailAddress,
}, },
nil, nil,
false, false,
), ),
X509ProfileTimestamp: x509_ops.NewProfile("timestamp", timestampPrototype, X509ProfileTimestamp: x509ops.NewProfile("timestamp", timestampPrototype,
[]x509_ops.SubjectDnField{ []x509ops.SubjectDnField{
x509_ops.SubjectDnFieldCountryName, x509ops.SubjectDnFieldCountryName,
x509_ops.SubjectDnFieldStateOrProvinceName, x509ops.SubjectDnFieldStateOrProvinceName,
x509_ops.SubjectDnFieldLocalityName, x509ops.SubjectDnFieldLocalityName,
x509_ops.SubjectDnFieldOrganizationName, x509ops.SubjectDnFieldOrganizationName,
x509_ops.SubjectDnFieldOrganizationalUnitName, x509ops.SubjectDnFieldOrganizationalUnitName,
x509_ops.SubjectDnFieldCommonName, x509ops.SubjectDnFieldCommonName,
}, },
nil, nil,
true, true,
), ),
// X509ProfileProxy: &x509_ops.Profile{Name: "proxy"}, // X509ProfileProxy: &x509ops.Profile{Name: "proxy"},
// X509ProfileSubCA: &x509_ops.Profile{Name: "subca"}, // X509ProfileSubCA: &x509ops.Profile{Name: "subca"},
}, },
// constants for openssl invocations. Should be replaced with // constants for openssl invocations. Should be replaced with
// something more useful // something more useful
DigestAlgorithms: map[shared.MessageDigestAlgorithmId]interface{}{ DigestAlgorithms: map[shared.SignatureAlgorithmID]interface{}{
X509MDDefault: x509.SHA256WithRSA, X509MDDefault: x509.SHA256WithRSA,
X509MDMd5: x509.MD5WithRSA, X509MDMd5: x509.MD5WithRSA,
X509MDSha1: x509.SHA1WithRSA, X509MDSha1: x509.SHA1WithRSA,
@ -251,8 +251,8 @@ func NewCommandProcessor() *CommandProcessor {
}, },
CsOpenPGP: { CsOpenPGP: {
Name: "OpenPGP", Name: "OpenPGP",
Roots: map[shared.CryptoSystemRootId]interface{}{ Roots: map[shared.CryptoSystemRootID]interface{}{
OpenPGPRoot0: &openpgp_ops.OpenPGPRoot{ OpenPGPRoot0: &openpgpops.OpenPGPRoot{
Name: "OpenPGP Root", Name: "OpenPGP Root",
SecretKeyRing: path.Join( SecretKeyRing: path.Join(
settings.OpenPGPKeyRingDir, settings.OpenPGPKeyRingDir,
@ -262,12 +262,12 @@ func NewCommandProcessor() *CommandProcessor {
Identifier: settings.OpenPGPUidEmail, Identifier: settings.OpenPGPUidEmail,
}, },
}, },
Profiles: map[shared.CertificateProfileId]interface{}{ Profiles: map[shared.CertificateProfileID]interface{}{
OpenPGPDefaultProfile: &openpgp_ops.OpenPGPProfile{Name: "default"}, OpenPGPDefaultProfile: &openpgpops.OpenPGPProfile{Name: "default"},
}, },
// constants for gnupg cert-digest-algo parameter. Should be replaced with // constants for gnupg cert-digest-algo parameter. Should be replaced with
// something more useful // something more useful
DigestAlgorithms: map[shared.MessageDigestAlgorithmId]interface{}{ DigestAlgorithms: map[shared.SignatureAlgorithmID]interface{}{
OpenPGPDefaultMD: crypto.SHA256, OpenPGPDefaultMD: crypto.SHA256,
}, },
}, },

View file

@ -1,4 +1,4 @@
package x509_ops package x509ops
import ( import (
"bufio" "bufio"
@ -6,7 +6,7 @@ import (
"crypto" "crypto"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha1" "crypto/sha1" // #nosec G505 needed for protocol version 1
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/base64" "encoding/base64"
@ -27,11 +27,16 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"git.cacert.org/cacert-gosigner/shared" "git.cacert.org/cacert-gosigner/shared"
"git.cacert.org/cacert-gosigner/signer/common"
) )
const crlLifetime = time.Hour * 24 * 7 const crlLifetime = time.Hour * 24 * 7
const (
pemTypeCertificate = "CERTIFICATE"
pemTypeCertificateRequest = "CERTIFICATE REQUEST"
pemTypePrivateKey = "PRIVATE KEY"
)
var ( var (
oidPkcs9EmailAddress = []int{1, 2, 840, 113549, 1, 9, 1} oidPkcs9EmailAddress = []int{1, 2, 840, 113549, 1, 9, 1}
) )
@ -57,27 +62,30 @@ func loadCertificate(certificateFile string) (*x509.Certificate, error) {
pemBytes, err := ioutil.ReadFile(certificateFile) pemBytes, err := ioutil.ReadFile(certificateFile)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"could not load certificate %s: %v", "could not load certificate %s: %w",
certificateFile, certificateFile,
err, err,
) )
} }
pemBlock, _ := pem.Decode(pemBytes) pemBlock, _ := pem.Decode(pemBytes)
if pemBlock.Type != "CERTIFICATE" { if pemBlock.Type != pemTypeCertificate {
log.Warnf( log.Warnf(
"PEM in %s is probably not a certificate. PEM block has type %s", "PEM in %s is probably not a certificate. PEM block has type %s",
certificateFile, certificateFile,
pemBlock.Type, pemBlock.Type,
) )
} }
certificate, err := x509.ParseCertificate(pemBlock.Bytes) certificate, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"could no parse certificate from %s: %v", "could no parse certificate from %s: %w",
certificateFile, certificateFile,
err, err,
) )
} }
return certificate, nil return certificate, nil
} }
@ -85,30 +93,34 @@ func loadPrivateKey(filename string) (crypto.Signer, error) {
pemBytes, err := ioutil.ReadFile(filename) pemBytes, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"could not load private key %s: %v", "could not load private key %s: %w",
filename, filename,
err, err,
) )
} }
pemBlock, _ := pem.Decode(pemBytes) pemBlock, _ := pem.Decode(pemBytes)
if pemBlock == nil { if pemBlock == nil {
return nil, fmt.Errorf("no PEM data found in %s", filename) return nil, fmt.Errorf("no PEM data found in %s", filename)
} }
if pemBlock.Type != "PRIVATE KEY" {
if pemBlock.Type != pemTypePrivateKey {
log.Warnf( log.Warnf(
"PEM in %s is probably not a private key. PEM block has type %s", "PEM in %s is probably not a private key. PEM block has type %s",
filename, filename,
pemBlock.Type, pemBlock.Type,
) )
} }
privateKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes) privateKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"could no parse private key from %s: %v", "could no parse private key from %s: %w",
filename, filename,
err, err,
) )
} }
return privateKey.(*rsa.PrivateKey), nil return privateKey.(*rsa.PrivateKey), nil
} }
@ -116,19 +128,24 @@ func (x *Root) getNextSerialNumber() (*big.Int, error) {
// TODO: decide whether we should use 64 bit random serial numbers as // TODO: decide whether we should use 64 bit random serial numbers as
// recommended by CAB forum baseline requirements // recommended by CAB forum baseline requirements
serialNumberFile := x.serialNumberFile serialNumberFile := x.serialNumberFile
_, err := os.Stat(serialNumberFile) _, err := os.Stat(serialNumberFile)
if err != nil { if err != nil {
log.Warnf("serial number file %s does not exist: %v", x.serialNumberFile, err) log.Warnf("serial number file %s does not exist: %v", x.serialNumberFile, err)
return big.NewInt(1), nil return big.NewInt(1), nil
} }
data, err := ioutil.ReadFile(x.serialNumberFile) data, err := ioutil.ReadFile(x.serialNumberFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read serial number file %s: %v", x.serialNumberFile, err) return nil, fmt.Errorf("could not read serial number file %s: %w", x.serialNumberFile, err)
} }
result, err := common.StringAsBigInt(data)
result, err := stringAsBigInt(data)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse content of %s as serial number: %v", x.serialNumberFile, err) return nil, fmt.Errorf("could not parse content of %s as serial number: %w", x.serialNumberFile, err)
} }
return result, err return result, err
} }
@ -136,26 +153,32 @@ func (x *Root) getNextCRLNumber() (*big.Int, error) {
_, err := os.Stat(x.crlNumberFile) _, err := os.Stat(x.crlNumberFile)
if err != nil { if err != nil {
log.Warnf("CRL number file %s does not exist: %v", x.crlNumberFile, err) log.Warnf("CRL number file %s does not exist: %v", x.crlNumberFile, err)
return big.NewInt(1), nil return big.NewInt(1), nil
} }
data, err := ioutil.ReadFile(x.crlNumberFile) data, err := ioutil.ReadFile(x.crlNumberFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read CRL number file %s: %v", x.crlNumberFile, err) return nil, fmt.Errorf("could not read CRL number file %s: %w", x.crlNumberFile, err)
} }
result, err := common.StringAsBigInt(data)
result, err := stringAsBigInt(data)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse content of %s as CRL number: %v", x.crlNumberFile, err) return nil, fmt.Errorf("could not parse content of %s as CRL number: %w", x.crlNumberFile, err)
} }
return result, nil return result, nil
} }
func (x *Root) bumpCRLNumber(current *big.Int) error { func (x *Root) bumpCRLNumber(current *big.Int) error {
serial := current.Int64() + 1 serial := current.Int64() + 1
crlNumberFile := x.crlNumberFile crlNumberFile := x.crlNumberFile
outFile, err := ioutil.TempFile(path.Dir(crlNumberFile), "*.txt") outFile, err := ioutil.TempFile(path.Dir(crlNumberFile), "*.txt")
if err != nil { if err != nil {
return fmt.Errorf("could not create temporary crl number file: %v", err) return fmt.Errorf("could not create temporary crl number file: %w", err)
} }
defer func() { _ = outFile.Close() }() defer func() { _ = outFile.Close() }()
_, err = outFile.WriteString(fmt.Sprintf( _, err = outFile.WriteString(fmt.Sprintf(
@ -163,27 +186,32 @@ func (x *Root) bumpCRLNumber(current *big.Int) error {
strings.ToUpper(strconv.FormatInt(serial, 16)), strings.ToUpper(strconv.FormatInt(serial, 16)),
)) ))
if err != nil { if err != nil {
return fmt.Errorf("could not write new CRL number %d to %s: %v", serial, outFile.Name(), err) return fmt.Errorf("could not write new CRL number %d to %s: %w", serial, outFile.Name(), err)
} }
if err = outFile.Close(); err != nil { if err = outFile.Close(); err != nil {
return fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err) return fmt.Errorf("could not close temporary file %s: %w", outFile.Name(), err)
} }
if err = os.Rename(crlNumberFile, fmt.Sprintf("%s.old", crlNumberFile)); err != nil { 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) return fmt.Errorf("could not rename %s to %s.old: %w", crlNumberFile, crlNumberFile, err)
} }
if err = os.Rename(outFile.Name(), crlNumberFile); err != nil { if err = os.Rename(outFile.Name(), crlNumberFile); err != nil {
return fmt.Errorf("could not rename %s to %s: %v", outFile.Name(), crlNumberFile, err) return fmt.Errorf("could not rename %s to %s: %w", outFile.Name(), crlNumberFile, err)
} }
return nil return nil
} }
func (x *Root) bumpSerialNumber(current *big.Int) error { func (x *Root) bumpSerialNumber(current *big.Int) error {
serial := current.Int64() + 1 serial := current.Int64() + 1
outFile, err := ioutil.TempFile(path.Dir(x.serialNumberFile), "*.txt") outFile, err := ioutil.TempFile(path.Dir(x.serialNumberFile), "*.txt")
if err != nil { if err != nil {
return fmt.Errorf("could not open temporary serial number file: %v", err) return fmt.Errorf("could not open temporary serial number file: %w", err)
} }
defer func() { _ = outFile.Close() }() defer func() { _ = outFile.Close() }()
_, err = outFile.WriteString(fmt.Sprintf( _, err = outFile.WriteString(fmt.Sprintf(
@ -191,49 +219,59 @@ func (x *Root) bumpSerialNumber(current *big.Int) error {
strings.ToUpper(strconv.FormatInt(serial, 16)), strings.ToUpper(strconv.FormatInt(serial, 16)),
)) ))
if err != nil { if err != nil {
return fmt.Errorf("could not write new serial number %d to %s: %v", serial, outFile.Name(), err) return fmt.Errorf("could not write new serial number %d to %s: %w", serial, outFile.Name(), err)
} }
if err = outFile.Close(); err != nil { if err = outFile.Close(); err != nil {
return fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err) return fmt.Errorf("could not close temporary file %s: %w", outFile.Name(), err)
} }
if _, err = os.Stat(x.serialNumberFile); err == nil { if _, err = os.Stat(x.serialNumberFile); err == nil {
if err = os.Rename(x.serialNumberFile, fmt.Sprintf("%s.old", x.serialNumberFile)); err != nil { if err = os.Rename(x.serialNumberFile, fmt.Sprintf("%s.old", x.serialNumberFile)); err != nil {
return fmt.Errorf("could not rename %s to %s.old: %v", x.serialNumberFile, x.serialNumberFile, err) return fmt.Errorf("could not rename %s to %s.old: %w", x.serialNumberFile, x.serialNumberFile, err)
} }
} }
if err = os.Rename(outFile.Name(), x.serialNumberFile); err != nil { if err = os.Rename(outFile.Name(), x.serialNumberFile); err != nil {
return fmt.Errorf("could not rename %s to %s: %v", outFile.Name(), x.serialNumberFile, err) return fmt.Errorf("could not rename %s to %s: %w", outFile.Name(), x.serialNumberFile, err)
} }
return nil return nil
} }
func (x *Root) loadRevokedCertificatesFromDatabase() ([]pkix.RevokedCertificate, error) { func (x *Root) loadRevokedCertificatesFromDatabase() ([]pkix.RevokedCertificate, error) {
databaseFile := x.databaseFile databaseFile := x.databaseFile
_, err := os.Stat(databaseFile) _, err := os.Stat(databaseFile)
if err != nil { if err != nil {
log.Warnf("openssl certificate database file %s does not exist: %v", databaseFile, err) log.Warnf("openssl certificate database file %s does not exist: %v", databaseFile, err)
return []pkix.RevokedCertificate{}, nil return []pkix.RevokedCertificate{}, nil
} }
file, err := os.Open(databaseFile) file, err := os.Open(databaseFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", databaseFile, err) return nil, fmt.Errorf("could not open openssl certificate database file %s: %w", databaseFile, err)
} }
defer func() { _ = file.Close() }() defer func() { _ = file.Close() }()
result := make([]pkix.RevokedCertificate, 0) result := make([]pkix.RevokedCertificate, 0)
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
for scanner.Scan() { for scanner.Scan() {
line := strings.Split(scanner.Text(), "\t") line := strings.Split(scanner.Text(), "\t")
if line[0] == "R" { if line[0] == "R" {
serialNumber, err := common.StringAsBigInt([]byte(line[3])) serialNumber, err := stringAsBigInt([]byte(line[3]))
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse serial number %s as big int: %v", line[3], err) return nil, fmt.Errorf("could not parse serial number %s as big int: %w", line[3], err)
} }
revokeTs, err := strconv.ParseInt(line[2][:len(line[2])-1], 10, 64) revokeTs, err := strconv.ParseInt(line[2][:len(line[2])-1], 10, 64)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse serial number: %v", err) return nil, fmt.Errorf("could not parse serial number: %w", err)
} }
result = append(result, pkix.RevokedCertificate{ result = append(result, pkix.RevokedCertificate{
SerialNumber: serialNumber, SerialNumber: serialNumber,
RevocationTime: time.Unix(revokeTs, 0), RevocationTime: time.Unix(revokeTs, 0),
@ -241,24 +279,29 @@ func (x *Root) loadRevokedCertificatesFromDatabase() ([]pkix.RevokedCertificate,
}) })
} }
} }
return result, nil return result, nil
} }
func (x *Root) recordRevocation(certificate *x509.Certificate) (*pkix.RevokedCertificate, error) { func (x *Root) recordRevocation(certificate *x509.Certificate) (*pkix.RevokedCertificate, error) {
_, err := os.Stat(x.databaseFile) _, err := os.Stat(x.databaseFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("openssl certificate database file %s does not exist: %v", x.databaseFile, err) return nil, fmt.Errorf("openssl certificate database file %s does not exist: %w", x.databaseFile, err)
} }
inFile, err := os.Open(x.databaseFile) inFile, err := os.Open(x.databaseFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", x.databaseFile, err) return nil, fmt.Errorf("could not open openssl certificate database file %s: %w", x.databaseFile, err)
} }
defer func() { _ = inFile.Close() }() defer func() { _ = inFile.Close() }()
outFile, err := ioutil.TempFile(path.Dir(x.databaseFile), "*.txt") outFile, err := ioutil.TempFile(path.Dir(x.databaseFile), "*.txt")
defer func() { _ = outFile.Close() }() defer func() { _ = outFile.Close() }()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not open temporary database file: %v", err) return nil, fmt.Errorf("could not open temporary database file: %w", err)
} }
scanner := bufio.NewScanner(inFile) scanner := bufio.NewScanner(inFile)
@ -270,10 +313,12 @@ func (x *Root) recordRevocation(certificate *x509.Certificate) (*pkix.RevokedCer
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
parts := strings.Split(line, "\t") parts := strings.Split(line, "\t")
serialNumber, err := common.StringAsBigInt([]byte(parts[3]))
serialNumber, err := stringAsBigInt([]byte(parts[3]))
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse serial number %s as big int: %v", parts[3], err) return nil, fmt.Errorf("could not parse serial number %s as big int: %w", parts[3], err)
} }
if serialNumber == certificate.SerialNumber { if serialNumber == certificate.SerialNumber {
line = strings.Join( line = strings.Join(
[]string{"R", parts[1], strconv.FormatInt(revocationTime.Unix(), 10) + "Z", parts[3], parts[4]}, []string{"R", parts[1], strconv.FormatInt(revocationTime.Unix(), 10) + "Z", parts[3], parts[4]},
@ -281,24 +326,26 @@ func (x *Root) recordRevocation(certificate *x509.Certificate) (*pkix.RevokedCer
) )
found = true found = true
} }
if _, err = writer.WriteString(fmt.Sprintf("%s\n", line)); err != nil { 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) return nil, fmt.Errorf("could not write '%s' to %s: %w", line, outFile.Name(), err)
} }
} }
if err = outFile.Close(); err != nil { if err = outFile.Close(); err != nil {
return nil, fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err) return nil, fmt.Errorf("could not close temporary file %s: %w", outFile.Name(), err)
} }
if err = inFile.Close(); err != nil { if err = inFile.Close(); err != nil {
return nil, fmt.Errorf("could not close %s: %v", x.databaseFile, err) return nil, fmt.Errorf("could not close %s: %w", x.databaseFile, err)
} }
if err = os.Rename(x.databaseFile, fmt.Sprintf("%s.old", x.databaseFile)); err != nil { if err = os.Rename(x.databaseFile, fmt.Sprintf("%s.old", x.databaseFile)); err != nil {
return nil, fmt.Errorf("could not rename %s to %s.old: %v", x.databaseFile, x.databaseFile, err) return nil, fmt.Errorf("could not rename %s to %s.old: %w", x.databaseFile, x.databaseFile, err)
} }
if err = os.Rename(outFile.Name(), x.databaseFile); err != nil { if err = os.Rename(outFile.Name(), x.databaseFile); err != nil {
return nil, fmt.Errorf("could not rename temporary file %s to %s: %v", outFile.Name(), x.databaseFile, err) return nil, fmt.Errorf("could not rename temporary file %s to %s: %w", outFile.Name(), x.databaseFile, err)
} }
if !found { if !found {
@ -313,22 +360,22 @@ func (x *Root) recordRevocation(certificate *x509.Certificate) (*pkix.RevokedCer
func (x *Root) RevokeCertificate(request []byte) (*pkix.RevokedCertificate, error) { func (x *Root) RevokeCertificate(request []byte) (*pkix.RevokedCertificate, error) {
pemBlock, _ := pem.Decode(request) pemBlock, _ := pem.Decode(request)
if pemBlock.Type != "CERTIFICATE" { if pemBlock.Type != pemTypeCertificate {
if pemBlock.Type != "CERTIFICATE" {
log.Warnf( log.Warnf(
"PEM structure is probably not a certificate. PEM block has type %s", "PEM structure is probably not a certificate. PEM block has type %s",
pemBlock.Type, pemBlock.Type,
) )
log.Trace(request) log.Trace(request)
} }
}
certificate, err := x509.ParseCertificate(pemBlock.Bytes) certificate, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"could no parse certificate: %v", "could no parse certificate: %w",
err, err,
) )
} }
return x.recordRevocation(certificate) return x.recordRevocation(certificate)
} }
@ -337,10 +384,12 @@ func (x *Root) GenerateCrl(algorithm x509.SignatureAlgorithm) ([]byte, *[20]byte
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
nextCrlNumber, err := x.getNextCRLNumber() nextCrlNumber, err := x.getNextCRLNumber()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
crlTemplate := &x509.RevocationList{ crlTemplate := &x509.RevocationList{
SignatureAlgorithm: algorithm, SignatureAlgorithm: algorithm,
RevokedCertificates: certificatesToRevoke, RevokedCertificates: certificatesToRevoke,
@ -363,20 +412,23 @@ func (x *Root) GenerateCrl(algorithm x509.SignatureAlgorithm) ([]byte, *[20]byte
x.privateKey, x.privateKey,
) )
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("could not create new CRL: %v", err) return nil, nil, fmt.Errorf("could not create new CRL: %w", err)
} }
if err = ioutil.WriteFile(x.crlFileName, crlBytes, 0644); err != nil { if err = ioutil.WriteFile(x.crlFileName, crlBytes, 0600); err != nil {
return nil, nil, fmt.Errorf("could not write new CRL to %s: %v", x.crlFileName, err) return nil, nil, fmt.Errorf("could not write new CRL to %s: %w", x.crlFileName, err)
} }
// sha1 is implied by protocol version 1
// #nosec G401
newCrlHash := sha1.Sum(crlBytes) newCrlHash := sha1.Sum(crlBytes)
hashedCrlFileName := path.Join( hashedCrlFileName := path.Join(
x.crlHashDir, x.crlHashDir,
fmt.Sprintf("%s.crl", hex.EncodeToString(newCrlHash[:])), 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) if err = ioutil.WriteFile(hashedCrlFileName, crlBytes, 0600); err != nil {
return nil, nil, fmt.Errorf("could not write new CRL to %s: %w", hashedCrlFileName, err)
} }
return crlBytes, &newCrlHash, nil return crlBytes, &newCrlHash, nil
@ -388,6 +440,7 @@ func (x *Root) DeleteOldCRLs(keepHashes ...string) error {
found, err := filepath.Glob(path.Join(x.crlHashDir, "*.crl")) found, err := filepath.Glob(path.Join(x.crlHashDir, "*.crl"))
if err != nil { if err != nil {
log.Warnf("could not match files: %v", err) log.Warnf("could not match files: %v", err)
return nil return nil
} }
@ -399,7 +452,7 @@ nextFound:
} }
} }
if err := os.Remove(filename); err != nil { if err := os.Remove(filename); err != nil {
return fmt.Errorf("could not delete %s: %v", filename, err) return fmt.Errorf("could not delete %s: %w", filename, err)
} }
} }
@ -421,6 +474,7 @@ func (x *Root) checkPreconditions() {
for _, success := range results { for _, success := range results {
if !success { if !success {
log.Warnf("preconditions for %s failed, operations may fail too", x) log.Warnf("preconditions for %s failed, operations may fail too", x)
break break
} }
} }
@ -428,26 +482,34 @@ func (x *Root) checkPreconditions() {
func (x *Root) checkFile(path, prefix string) bool { func (x *Root) checkFile(path, prefix string) bool {
ok := true ok := true
if s, e := os.Stat(path); e != nil { if s, e := os.Stat(path); e != nil {
log.Warnf("%s file %s of %s has issues: %v", prefix, path, x, e) log.Warnf("%s file %s of %s has issues: %v", prefix, path, x, e)
ok = false ok = false
} else if s.IsDir() { } else if s.IsDir() {
log.Warnf("%s file %s of %s is a directory", prefix, path, x) log.Warnf("%s file %s of %s is a directory", prefix, path, x)
ok = false ok = false
} }
return ok return ok
} }
func (x *Root) checkDir(path, prefix string) bool { func (x *Root) checkDir(path, prefix string) bool {
ok := true ok := true
if s, e := os.Stat(path); e != nil { if s, e := os.Stat(path); e != nil {
log.Warnf("%s %s of %s has issues: %v", prefix, path, x, e) log.Warnf("%s %s of %s has issues: %v", prefix, path, x, e)
if err := os.MkdirAll(path, 0755); err != nil { if err := os.MkdirAll(path, 0755); err != nil {
log.Warnf("could not create %s %s of %s: %v", prefix, path, x, err) log.Warnf("could not create %s %s of %s: %v", prefix, path, x, err)
} }
ok = false ok = false
} else if !s.IsDir() { } else if !s.IsDir() {
log.Warnf("%s %s of %s is not a directory", prefix, path, x) log.Warnf("%s %s of %s is not a directory", prefix, path, x)
ok = false ok = false
} }
@ -468,35 +530,45 @@ func (x *Root) SignCertificate(
params *SigningRequestParameters, params *SigningRequestParameters,
) ([]byte, error) { ) ([]byte, error) {
var publicKey interface{} var publicKey interface{}
// nolint:nestif
if params.IsSpkac { if params.IsSpkac {
var err error var err error
const spkacPrefix = "SPKAC=" const spkacPrefix = "SPKAC="
if !bytes.Equal([]byte(spkacPrefix), params.Request[:len(spkacPrefix)]) { if !bytes.Equal([]byte(spkacPrefix), params.Request[:len(spkacPrefix)]) {
return nil, fmt.Errorf("request does not contain a valid SPKAC string") return nil, fmt.Errorf("request does not contain a valid SPKAC string")
} }
derBytes, err := base64.StdEncoding.DecodeString(string(params.Request[len(spkacPrefix):])) derBytes, err := base64.StdEncoding.DecodeString(string(params.Request[len(spkacPrefix):]))
if err != nil { if err != nil {
return nil, fmt.Errorf("could not decode SPKAC bytes: %v", err) return nil, fmt.Errorf("could not decode SPKAC bytes: %w", err)
} }
publicKey, err = pkac.ParseSPKAC(derBytes) publicKey, err = pkac.ParseSPKAC(derBytes)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse SPKAC: %v", err) return nil, fmt.Errorf("could not parse SPKAC: %w", err)
} }
} else { } else {
csrBlock, _ := pem.Decode(params.Request) csrBlock, _ := pem.Decode(params.Request)
if csrBlock.Type != "CERTIFICATE REQUEST" { if csrBlock.Type != pemTypeCertificateRequest {
return nil, fmt.Errorf("unexpected PEM block '%s' instead of 'CERTIFICATE REQUEST'", csrBlock.Type) return nil, fmt.Errorf(
"unexpected PEM block '%s' instead of '%s'",
csrBlock.Type,
pemTypeCertificateRequest,
)
} }
csr, err := x509.ParseCertificateRequest(csrBlock.Bytes) csr, err := x509.ParseCertificateRequest(csrBlock.Bytes)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse CSR: %v", err) return nil, fmt.Errorf("could not parse CSR: %w", err)
} }
publicKey = csr.PublicKey publicKey = csr.PublicKey
} }
nextSerialNumber, err := x.getNextSerialNumber() nextSerialNumber, err := x.getNextSerialNumber()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get next serial number: %v", err) return nil, fmt.Errorf("could not get next serial number: %w", err)
} }
// copy profile // copy profile
@ -516,14 +588,15 @@ func (x *Root) SignCertificate(
// check subject // check subject
subject, err := profile.parseSubject(params.Subject) subject, err := profile.parseSubject(params.Subject)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse subject: %v", err) return nil, fmt.Errorf("could not parse subject: %w", err)
} }
certificate.Subject = *subject certificate.Subject = *subject
// check altNames // check altNames
err = profile.parseAltNames(certificate, params.SubjectAlternativeNames) err = profile.parseAltNames(certificate, params.SubjectAlternativeNames)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse subject alternative names: %v", err) return nil, fmt.Errorf("could not parse subject alternative names: %w", err)
} }
moveEmailsFromSubjectToAlternativeNames(certificate) moveEmailsFromSubjectToAlternativeNames(certificate)
@ -532,24 +605,24 @@ func (x *Root) SignCertificate(
certBytes, err := x509.CreateCertificate(rand.Reader, certificate, x.certificate, publicKey, x.privateKey) certBytes, err := x509.CreateCertificate(rand.Reader, certificate, x.certificate, publicKey, x.privateKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not sign certificate: %v", err) return nil, fmt.Errorf("could not sign certificate: %w", err)
} }
parsedCertificate, err := x509.ParseCertificate(certBytes) parsedCertificate, err := x509.ParseCertificate(certBytes)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse signed certificate: %v", err) return nil, fmt.Errorf("could not parse signed certificate: %w", err)
} }
if err = x.bumpSerialNumber(nextSerialNumber); err != nil { if err = x.bumpSerialNumber(nextSerialNumber); err != nil {
log.Errorf("could not bump serial number: %v", err) return nil, fmt.Errorf("could not bump serial number: %w", err)
} }
err = x.recordIssuedCertificate(parsedCertificate) err = x.recordIssuedCertificate(parsedCertificate)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not record signed certificate in database: %v", err) return nil, fmt.Errorf("could not record signed certificate in database: %w", err)
} }
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) pemBytes := pem.EncodeToMemory(&pem.Block{Type: pemTypeCertificate, Bytes: certBytes})
log.Tracef("signed new certificate\n%s", pemBytes) log.Tracef("signed new certificate\n%s", pemBytes)
return pemBytes, nil return pemBytes, nil
@ -559,36 +632,46 @@ func (x *Root) SignCertificate(
// otherwise which is not compliant to RFC-5280 // otherwise which is not compliant to RFC-5280
func moveEmailsFromSubjectToAlternativeNames(certificate *x509.Certificate) { func moveEmailsFromSubjectToAlternativeNames(certificate *x509.Certificate) {
extraNames := make([]pkix.AttributeTypeAndValue, 0) extraNames := make([]pkix.AttributeTypeAndValue, 0)
for _, p := range certificate.Subject.ExtraNames { for _, p := range certificate.Subject.ExtraNames {
if p.Type.Equal(oidPkcs9EmailAddress) { if p.Type.Equal(oidPkcs9EmailAddress) {
email := p.Value.(string) email := p.Value.(string)
if certificate.EmailAddresses == nil { if certificate.EmailAddresses == nil {
certificate.EmailAddresses = []string{email} certificate.EmailAddresses = []string{email}
continue continue
} }
for _, e := range certificate.EmailAddresses { for _, e := range certificate.EmailAddresses {
if e == p.Value { if e == p.Value {
continue continue
} }
} }
certificate.EmailAddresses = append(certificate.EmailAddresses, email) certificate.EmailAddresses = append(certificate.EmailAddresses, email)
} else { } else {
extraNames = append(extraNames, p) extraNames = append(extraNames, p)
} }
} }
certificate.Subject.ExtraNames = extraNames certificate.Subject.ExtraNames = extraNames
} }
func (x *Root) recordIssuedCertificate(certificate *x509.Certificate) error { func (x *Root) recordIssuedCertificate(certificate *x509.Certificate) error {
log.Tracef("recording %+v", certificate) log.Tracef("recording %+v", certificate)
tempFile, err := ioutil.TempFile(path.Dir(x.databaseFile), "*.txt") tempFile, err := ioutil.TempFile(path.Dir(x.databaseFile), "*.txt")
if err != nil { if err != nil {
return fmt.Errorf("could not create temporary file: %v", err) return fmt.Errorf("could not create temporary file: %w", err)
} }
defer func() { _ = tempFile.Close() }() defer func() { _ = tempFile.Close() }()
tempName := tempFile.Name() tempName := tempFile.Name()
dbExists := false dbExists := false
_, err = os.Stat(x.databaseFile) _, err = os.Stat(x.databaseFile)
if err != nil { if err != nil {
log.Warnf("openssl certificate database file %s does not exist: %v", x.databaseFile, err) log.Warnf("openssl certificate database file %s does not exist: %v", x.databaseFile, err)
@ -597,76 +680,105 @@ func (x *Root) recordIssuedCertificate(certificate *x509.Certificate) error {
inFile, err := os.Open(x.databaseFile) inFile, err := os.Open(x.databaseFile)
defer func() { _ = inFile.Close() }() defer func() { _ = inFile.Close() }()
if err != nil { if err != nil {
return fmt.Errorf("could not open openssl certificate database file %s: %v", x.databaseFile, err) return fmt.Errorf("could not open openssl certificate database file %s: %w", x.databaseFile, err)
} }
_, err = io.Copy(tempFile, inFile) _, err = io.Copy(tempFile, inFile)
if err != nil { if err != nil {
return fmt.Errorf("could not copy %s to temporary file %s: %v", x.databaseFile, tempName, err) return fmt.Errorf("could not copy %s to temporary file %s: %w", x.databaseFile, tempName, err)
} }
if err = inFile.Close(); err != nil { if err = inFile.Close(); err != nil {
return fmt.Errorf("could not close %s: %v", x.databaseFile, err) return fmt.Errorf("could not close %s: %w", x.databaseFile, err)
} }
} }
if err = tempFile.Close(); err != nil { if err = tempFile.Close(); err != nil {
return fmt.Errorf("could not close temporary file %s: %v", tempName, err) return fmt.Errorf("could not close temporary file %s: %w", tempName, err)
} }
outFile, err := os.OpenFile(tempName, os.O_APPEND|os.O_WRONLY, 0644) outFile, err := os.OpenFile(tempName, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil { if err != nil {
return fmt.Errorf("could not open temporary file for writing %s: %v", tempName, err) return fmt.Errorf("could not open temporary file for writing %s: %w", tempName, err)
} }
defer func() { _ = outFile.Close() }() defer func() { _ = outFile.Close() }()
line := strings.Join([]string{"V", strconv.FormatInt(certificate.NotBefore.Unix(), 10) + "Z", "", strings.ToUpper(certificate.SerialNumber.Text(16)), "unknown", opensslFormatDN(certificate.Subject)}, "\t")
line := strings.Join(
// nolint:gomnd
[]string{
"V",
strconv.FormatInt(certificate.NotBefore.Unix(), 10) + "Z",
"",
strings.ToUpper(certificate.SerialNumber.Text(16)),
"unknown",
opensslFormatDN(certificate.Subject),
},
"\t",
)
_, err = fmt.Fprintln(outFile, line) _, err = fmt.Fprintln(outFile, line)
if err != nil { if err != nil {
return fmt.Errorf("could not write '%s' to %s: %v", line, tempName, err) return fmt.Errorf("could not write '%s' to %s: %w", line, tempName, err)
} }
if err = outFile.Close(); err != nil { if err = outFile.Close(); err != nil {
return fmt.Errorf("could not close temporary file %s: %v", tempName, err) return fmt.Errorf("could not close temporary file %s: %w", tempName, err)
} }
if dbExists { if dbExists {
if err = os.Rename(x.databaseFile, fmt.Sprintf("%s.old", x.databaseFile)); err != nil { if err = os.Rename(x.databaseFile, fmt.Sprintf("%s.old", x.databaseFile)); err != nil {
return fmt.Errorf("could not rename %s to %s.old: %v", x.databaseFile, x.databaseFile, err) return fmt.Errorf("could not rename %s to %s.old: %w", x.databaseFile, x.databaseFile, err)
} }
} }
if err = os.Rename(tempName, x.databaseFile); err != nil { if err = os.Rename(tempName, x.databaseFile); err != nil {
return fmt.Errorf("could not rename temporary file %s to %s: %v", tempName, x.databaseFile, err) return fmt.Errorf("could not rename temporary file %s to %s: %w", tempName, x.databaseFile, err)
} }
return nil return nil
} }
func opensslFormatDN(subject pkix.Name) string { func opensslFormatDN(subject pkix.Name) string {
const (
oidSuffixCommonName = 3
oidSuffixCountryName = 6
oidSuffixLocalityName = 7
oidSuffixProvinceName = 8
oidSuffixOrganization = 10
oidSuffixOrganizationalUnit = 11
)
var buf strings.Builder var buf strings.Builder
for _, rdn := range subject.ToRDNSequence() { for _, rdn := range subject.ToRDNSequence() {
if len(rdn) == 0 { if len(rdn) == 0 {
continue continue
} }
for _, atv := range rdn { for _, atv := range rdn {
value, ok := atv.Value.(string) value, ok := atv.Value.(string)
if !ok { if !ok {
continue continue
} }
t := atv.Type t := atv.Type
if len(t) == 4 && t[:3].Equal([]int{2, 5, 4}) { if len(t) == 4 && t[:3].Equal([]int{2, 5, 4}) {
switch t[3] { switch t[3] {
case 3: case oidSuffixCommonName:
buf.WriteString("/CN=") buf.WriteString("/CN=")
buf.WriteString(value) buf.WriteString(value)
case 6: case oidSuffixCountryName:
buf.WriteString("/C=") buf.WriteString("/C=")
buf.WriteString(value) buf.WriteString(value)
case 7: case oidSuffixLocalityName:
buf.WriteString("/L=") buf.WriteString("/L=")
buf.WriteString(value) buf.WriteString(value)
case 8: case oidSuffixProvinceName:
buf.WriteString("/ST=") buf.WriteString("/ST=")
buf.WriteString(value) buf.WriteString(value)
case 10: case oidSuffixOrganization:
buf.WriteString("/O=") buf.WriteString("/O=")
buf.WriteString(value) buf.WriteString(value)
case 11: case oidSuffixOrganizationalUnit:
buf.WriteString("/OU=") buf.WriteString("/OU=")
buf.WriteString(value) buf.WriteString(value)
} }
@ -676,22 +788,25 @@ func opensslFormatDN(subject pkix.Name) string {
} }
} }
} }
return buf.String() return buf.String()
} }
func NewRoot( func NewRoot(
basedir, name, subdir string, basedir, name, subdir string,
id shared.CryptoSystemRootId, id shared.CryptoSystemRootID,
crlDistributionPoints, ocspServers []string, crlDistributionPoints, ocspServers []string,
) *Root { ) *Root {
key, err := loadPrivateKey(path.Join(basedir, subdir, "private", "ca.key.pem")) key, err := loadPrivateKey(path.Join(basedir, subdir, "private", "ca.key.pem"))
if err != nil { if err != nil {
log.Fatalf("could not load private key: %v", err) log.Fatalf("could not load private key: %v", err)
} }
cert, err := loadCertificate(path.Join(basedir, subdir, "ca.crt.pem")) cert, err := loadCertificate(path.Join(basedir, subdir, "ca.crt.pem"))
if err != nil { if err != nil {
log.Fatalf("could not load CA certificate: %v", err) log.Fatalf("could not load CA certificate: %v", err)
} }
root := &Root{ root := &Root{
Name: name, Name: name,
privateKey: key, privateKey: key,
@ -709,6 +824,7 @@ func NewRoot(
ocspServers: ocspServers, ocspServers: ocspServers,
} }
root.checkPreconditions() root.checkPreconditions()
return root return root
} }
@ -716,7 +832,7 @@ type AltNameType string
const ( const (
NameTypeDNS AltNameType = "DNS" NameTypeDNS AltNameType = "DNS"
NameTypeXmppJid AltNameType = "otherName:1.3.6.1.5.5.7.8.5;UTF8" // from RFC 3920, 6120 NameTypeXMPPJid AltNameType = "otherName:1.3.6.1.5.5.7.8.5;UTF8" // from RFC 3920, 6120
) )
type SubjectDnField string type SubjectDnField string
@ -744,73 +860,55 @@ func (p *Profile) String() string {
} }
func (p *Profile) parseSubject(subject []byte) (*pkix.Name, error) { func (p *Profile) parseSubject(subject []byte) (*pkix.Name, error) {
parts := strings.Split(string(subject), "/") parts := strings.Split(string(subject), "/")
subjectDN := &pkix.Name{} subjectDN := &pkix.Name{}
for _, part := range parts { for _, part := range parts {
if len(strings.TrimSpace(part)) == 0 { if len(strings.TrimSpace(part)) == 0 {
continue continue
} }
handled := false handled := false
item := strings.SplitN(part, "=", 2) item := strings.SplitN(part, "=", 2)
for _, f := range p.subjectDNFields { for _, f := range p.subjectDNFields {
if !strings.EqualFold(item[0], string(f)) { if !strings.EqualFold(item[0], string(f)) {
continue continue
} }
value := item[1] value := item[1]
handled = true handled = true
switch f { switch f {
case SubjectDnFieldCountryName: case SubjectDnFieldCountryName:
if subjectDN.Country == nil {
subjectDN.Country = []string{value}
} else {
subjectDN.Country = append(subjectDN.Country, value) subjectDN.Country = append(subjectDN.Country, value)
}
case SubjectDnFieldStateOrProvinceName: case SubjectDnFieldStateOrProvinceName:
if subjectDN.Province == nil {
subjectDN.Province = []string{value}
} else {
subjectDN.Province = append(subjectDN.Province, value) subjectDN.Province = append(subjectDN.Province, value)
}
case SubjectDnFieldLocalityName: case SubjectDnFieldLocalityName:
if subjectDN.Locality == nil {
subjectDN.Locality = []string{value}
} else {
subjectDN.Locality = append(subjectDN.Locality, value) subjectDN.Locality = append(subjectDN.Locality, value)
}
case SubjectDnFieldOrganizationName: case SubjectDnFieldOrganizationName:
if subjectDN.Organization == nil {
subjectDN.Organization = []string{value}
} else {
subjectDN.Organization = append(subjectDN.Organization, value) subjectDN.Organization = append(subjectDN.Organization, value)
}
case SubjectDnFieldOrganizationalUnitName: case SubjectDnFieldOrganizationalUnitName:
if subjectDN.OrganizationalUnit == nil {
subjectDN.OrganizationalUnit = []string{value}
} else {
subjectDN.OrganizationalUnit = append(subjectDN.OrganizationalUnit, value) subjectDN.OrganizationalUnit = append(subjectDN.OrganizationalUnit, value)
}
case SubjectDnFieldCommonName: case SubjectDnFieldCommonName:
subjectDN.CommonName = value subjectDN.CommonName = value
case SubjectDnFieldEmailAddress: case SubjectDnFieldEmailAddress:
emailIA5 := pkix.AttributeTypeAndValue{ subjectDN.ExtraNames = append(subjectDN.ExtraNames, pkix.AttributeTypeAndValue{
Type: oidPkcs9EmailAddress, Type: oidPkcs9EmailAddress,
Value: value, Value: value,
} })
if subjectDN.ExtraNames == nil {
subjectDN.ExtraNames = []pkix.AttributeTypeAndValue{emailIA5}
} else {
subjectDN.ExtraNames = append(subjectDN.ExtraNames, emailIA5)
}
default: default:
log.Warnf("unhandled subject DN type %s", f) log.Warnf("unhandled subject DN type %s", f)
} }
} }
if !handled { if !handled {
return nil, fmt.Errorf("skipped part %s because it is not supported by profile %s", part, p) return nil, fmt.Errorf("skipped part %s because it is not supported by profile %s", part, p)
} }
} }
log.Debugf("created subject DN %s", subjectDN) log.Debugf("created subject DN %s", subjectDN)
return subjectDN, nil return subjectDN, nil
} }
@ -820,19 +918,24 @@ func (p *Profile) parseAltNames(template *x509.Certificate, altNames []byte) err
if len(strings.TrimSpace(part)) == 0 { if len(strings.TrimSpace(part)) == 0 {
continue continue
} }
handled := false handled := false
item := strings.SplitN(part, ":", 3) item := strings.SplitN(part, ":", 3)
if item[0] == "otherName" { if item[0] == "otherName" {
item = []string{strings.Join(item[:2], ":"), item[2]} item = []string{strings.Join(item[:2], ":"), item[2]}
} else { } else {
item = []string{item[0], strings.Join(item[1:], ":")} item = []string{item[0], strings.Join(item[1:], ":")}
} }
for _, f := range p.altNameTypes { for _, f := range p.altNameTypes {
if item[0] != string(f) { if item[0] != string(f) {
continue continue
} }
value := item[1] value := item[1]
handled = true handled = true
switch f { switch f {
case NameTypeDNS: case NameTypeDNS:
if template.DNSNames == nil { if template.DNSNames == nil {
@ -840,17 +943,19 @@ func (p *Profile) parseAltNames(template *x509.Certificate, altNames []byte) err
} else { } else {
template.DNSNames = append(template.DNSNames, value) template.DNSNames = append(template.DNSNames, value)
} }
case NameTypeXmppJid: case NameTypeXMPPJid:
// x509.Certificate has no support for otherName alternative names // x509ops.Certificate has no support for otherName alternative names
log.Warnf("skipping %s because it cannot be supported", part) log.Warnf("skipping %s because it cannot be supported", part)
default: default:
log.Warnf("unhandled alternative name type %s", f) log.Warnf("unhandled alternative name type %s", f)
} }
} }
if !handled { if !handled {
return fmt.Errorf("skipped alternative name %s because it is not supported by profile %s", part, p) return fmt.Errorf("skipped alternative name %s because it is not supported by profile %s", part, p)
} }
} }
return nil return nil
} }
@ -869,3 +974,14 @@ func NewProfile(
copyEmail: copyEmail, copyEmail: copyEmail,
} }
} }
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: %w", dataString, err)
}
return big.NewInt(parseInt), nil
}

View file

@ -1,4 +1,4 @@
package x509_ops package x509ops
import ( import (
"crypto" "crypto"
@ -33,6 +33,7 @@ func TestRoot_SignClientCertificateWithCSR(t *testing.T) {
subjectDNFields: []SubjectDnField{SubjectDnFieldCommonName, SubjectDnFieldEmailAddress}, subjectDNFields: []SubjectDnField{SubjectDnFieldCommonName, SubjectDnFieldEmailAddress},
copyEmail: true, copyEmail: true,
} }
certificate, err := root.SignCertificate(clientProfile, x509.SHA256WithRSA, &SigningRequestParameters{ certificate, err := root.SignCertificate(clientProfile, x509.SHA256WithRSA, &SigningRequestParameters{
Request: decodeToBytes(t, testRequest), Request: decodeToBytes(t, testRequest),
Subject: []byte(testClientSubject), Subject: []byte(testClientSubject),
@ -42,17 +43,21 @@ func TestRoot_SignClientCertificateWithCSR(t *testing.T) {
}) })
if err != nil { if err != nil {
t.Errorf("error signing certificate: %v", err) t.Errorf("error signing certificate: %v", err)
return return
} }
certificateDer, _ := pem.Decode(certificate) certificateDer, _ := pem.Decode(certificate)
if certificateDer.Type != "CERTIFICATE" { if certificateDer.Type != pemTypeCertificate {
t.Errorf("invalid PEM type '%s' instead of 'CERTIFICATE'", certificateDer.Type) t.Errorf("invalid PEM type '%s' instead of '%s'", certificateDer.Type, pemTypeCertificate)
return return
} }
_, err = x509.ParseCertificate(certificateDer.Bytes) _, err = x509.ParseCertificate(certificateDer.Bytes)
if err != nil { if err != nil {
t.Errorf("could not parse generated certificate: %v", err) t.Errorf("could not parse generated certificate: %v", err)
return return
} }
} }
@ -90,17 +95,21 @@ func TestRoot_SignClientCertificateWithSPKAC(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("error signing certificate: %v", err) t.Errorf("error signing certificate: %v", err)
return return
} }
certificateDer, _ := pem.Decode(certificate) certificateDer, _ := pem.Decode(certificate)
if certificateDer.Type != "CERTIFICATE" { if certificateDer.Type != pemTypeCertificate {
t.Errorf("invalid PEM type '%s' instead of 'CERTIFICATE'", certificateDer.Type) t.Errorf("invalid PEM type '%s' instead of '%s'", certificateDer.Type, pemTypeCertificate)
return return
} }
_, err = x509.ParseCertificate(certificateDer.Bytes) _, err = x509.ParseCertificate(certificateDer.Bytes)
if err != nil { if err != nil {
t.Errorf("could not parse generated certificate: %v", err) t.Errorf("could not parse generated certificate: %v", err)
return return
} }
} }
@ -134,9 +143,10 @@ func TestRoot_SignServerCertificateWithCSR(t *testing.T) {
SubjectDnFieldOrganizationalUnitName, SubjectDnFieldOrganizationalUnitName,
SubjectDnFieldCommonName, SubjectDnFieldCommonName,
}, },
altNameTypes: []AltNameType{NameTypeDNS, NameTypeXmppJid}, altNameTypes: []AltNameType{NameTypeDNS, NameTypeXMPPJid},
copyEmail: false, copyEmail: false,
} }
certificate, err := root.SignCertificate(clientProfile, x509.SHA256WithRSA, &SigningRequestParameters{ certificate, err := root.SignCertificate(clientProfile, x509.SHA256WithRSA, &SigningRequestParameters{
Request: decodeToBytes(t, testRequest), Request: decodeToBytes(t, testRequest),
Subject: []byte(testServerSubject), Subject: []byte(testServerSubject),
@ -146,36 +156,44 @@ func TestRoot_SignServerCertificateWithCSR(t *testing.T) {
}) })
if err != nil { if err != nil {
t.Errorf("error signing certificate: %v", err) t.Errorf("error signing certificate: %v", err)
return return
} }
certificateDer, _ := pem.Decode(certificate) certificateDer, _ := pem.Decode(certificate)
if certificateDer.Type != "CERTIFICATE" { if certificateDer.Type != "CERTIFICATE" {
t.Errorf("invalid PEM type '%s' instead of 'CERTIFICATE'", certificateDer.Type) t.Errorf("invalid PEM type '%s' instead of 'CERTIFICATE'", certificateDer.Type)
return return
} }
_, err = x509.ParseCertificate(certificateDer.Bytes) _, err = x509.ParseCertificate(certificateDer.Bytes)
if err != nil { if err != nil {
t.Errorf("could not parse generated certificate: %v", err) t.Errorf("could not parse generated certificate: %v", err)
return return
} }
} }
func loadTestKey(t *testing.T) crypto.Signer { func loadTestKey(t *testing.T) crypto.Signer {
testKeyBytes := decodeToBytes(t, testCAKey) testKeyBytes := decodeToBytes(t, testCAKey)
key, err := x509.ParsePKCS1PrivateKey(testKeyBytes) key, err := x509.ParsePKCS1PrivateKey(testKeyBytes)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
return key return key
} }
func loadTestCACertificate(t *testing.T) *x509.Certificate { func loadTestCACertificate(t *testing.T) *x509.Certificate {
testCertBytes := decodeToBytes(t, testCACertificate) testCertBytes := decodeToBytes(t, testCACertificate)
cert, err := x509.ParseCertificate(testCertBytes) cert, err := x509.ParseCertificate(testCertBytes)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
return cert return cert
} }
@ -184,11 +202,14 @@ func decodeToBytes(t *testing.T, request string) []byte {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
return decodeString return decodeString
} }
// nolint:lll
const testRequest = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ1ZEQ0NBVHdDQVFBd0R6RU5NQXNHQTFVRUF3d0VWR1Z6ZERDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRApnZ0VQQURDQ0FRb0NnZ0VCQU9wQ1JmMVZWQjVwK3RscVFjM25qekYyZVAydG40bGZ2NVlJU3RFMktMSmxJRDRECi9YZTRVUGdodlNuMUN3R1VzeCtQcFBDaTdZVFdQNXRKSWYwbmNLMm02YVZIWUtqcDN2K3NvMnRENkJ4V0lMSFAKS0tQSm5qbnBjU0l1Q3hKUytRU2xyMHh0aEJYYzZ2UzVWRE5Ib285VXJWRUYzSVlTd3VDTklqTWpMR25kYmpCagprNm5TUk5JZWVmeVBaVGI4MHFsVTJFZ3hJMFdFYTA1dm5sQTY5L2tQZGhmTjVRRHBHQ1NxU25GdXo1cGFmRXVIClZMOE1aQXVtVDJySkVkYnorcHRPRjBqMWZSWEQ4b1RKZ0ppQmszbGR6YlBqeFpndW5LSTl3NVcrSWdEWmxsNm8KRzM2TUN6WHNYREdkb25NbCt0K1JIbEdocjN0VDQrKzBobXVYL2pzQ0F3RUFBYUFBTUEwR0NTcUdTSWIzRFFFQgpDd1VBQTRJQkFRRFBlYnhmR3pRaWRud1pxUGdQYUliRmxNM0xsWXpROEJFbFUrYXV5bC90VjhJRC85a0dkekNxClN4N0QyRWVNVC83QmZ6L29XUjRMczVUeFpzdjR0N3RiSEUrSStFdEcwS3FnUDhNTTJPWStGckJBMXVnY3JJa2YKNmVpbXFEVkFtUFBNMHhCNUg3aFdNY1BMVUhzbW1GNlV4ajNsVXphOVQ5OXpxTWppMXlyYlpIc1pkMEM0RFd6RQo1YWtZU1hTTGNuK1F3R25LY1pvV1QwczNWZU5pMHNUK3BTNEVkdk1SbzV6Q3JUMW1SbFlYQkNqU0tpQzZEVjNpCnhyaDI2WWJqMjRKSys5dlNUR3N4RFlpMXUzOG04a1AxRVR2L0lCVnRDSVpKVmJ2eXhWbUpuemV2QnJONHpxdncKV1QvQi9jOGdrK0FQR1BKM3ZaZDUxNVhvM2QzVld4NkwKLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tCg==" const testRequest = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ1ZEQ0NBVHdDQVFBd0R6RU5NQXNHQTFVRUF3d0VWR1Z6ZERDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRApnZ0VQQURDQ0FRb0NnZ0VCQU9wQ1JmMVZWQjVwK3RscVFjM25qekYyZVAydG40bGZ2NVlJU3RFMktMSmxJRDRECi9YZTRVUGdodlNuMUN3R1VzeCtQcFBDaTdZVFdQNXRKSWYwbmNLMm02YVZIWUtqcDN2K3NvMnRENkJ4V0lMSFAKS0tQSm5qbnBjU0l1Q3hKUytRU2xyMHh0aEJYYzZ2UzVWRE5Ib285VXJWRUYzSVlTd3VDTklqTWpMR25kYmpCagprNm5TUk5JZWVmeVBaVGI4MHFsVTJFZ3hJMFdFYTA1dm5sQTY5L2tQZGhmTjVRRHBHQ1NxU25GdXo1cGFmRXVIClZMOE1aQXVtVDJySkVkYnorcHRPRjBqMWZSWEQ4b1RKZ0ppQmszbGR6YlBqeFpndW5LSTl3NVcrSWdEWmxsNm8KRzM2TUN6WHNYREdkb25NbCt0K1JIbEdocjN0VDQrKzBobXVYL2pzQ0F3RUFBYUFBTUEwR0NTcUdTSWIzRFFFQgpDd1VBQTRJQkFRRFBlYnhmR3pRaWRud1pxUGdQYUliRmxNM0xsWXpROEJFbFUrYXV5bC90VjhJRC85a0dkekNxClN4N0QyRWVNVC83QmZ6L29XUjRMczVUeFpzdjR0N3RiSEUrSStFdEcwS3FnUDhNTTJPWStGckJBMXVnY3JJa2YKNmVpbXFEVkFtUFBNMHhCNUg3aFdNY1BMVUhzbW1GNlV4ajNsVXphOVQ5OXpxTWppMXlyYlpIc1pkMEM0RFd6RQo1YWtZU1hTTGNuK1F3R25LY1pvV1QwczNWZU5pMHNUK3BTNEVkdk1SbzV6Q3JUMW1SbFlYQkNqU0tpQzZEVjNpCnhyaDI2WWJqMjRKSys5dlNUR3N4RFlpMXUzOG04a1AxRVR2L0lCVnRDSVpKVmJ2eXhWbUpuemV2QnJONHpxdncKV1QvQi9jOGdrK0FQR1BKM3ZaZDUxNVhvM2QzVld4NkwKLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tCg=="
// nolint:lll
const testSpkac = "U1BLQUM9TUlJQ1FEQ0NBU2d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRHFRa1g5VlZRZWFmclpha0hONTQ4eGRuajlyWitKWDcrV0NFclJOaWl5WlNBK0EvMTN1RkQ0SWIwcDlRc0JsTE1majZUd291MkUxaitiU1NIOUozQ3RwdW1sUjJDbzZkNy9yS05yUStnY1ZpQ3h6eWlqeVo0NTZYRWlMZ3NTVXZrRXBhOU1iWVFWM09yMHVWUXpSNktQVksxUkJkeUdFc0xnalNJekl5eHAzVzR3WTVPcDBrVFNIbm44ajJVMi9OS3BWTmhJTVNORmhHdE9iNTVRT3ZmNUQzWVh6ZVVBNlJna3FrcHhicythV254TGgxUy9ER1FMcGs5cXlSSFc4L3FiVGhkSTlYMFZ3L0tFeVlDWWdaTjVYYzJ6NDhXWUxweWlQY09WdmlJQTJaWmVxQnQrakFzMTdGd3huYUp6SmZyZmtSNVJvYTk3VStQdnRJWnJsLzQ3QWdNQkFBRVdBREFOQmdrcWhraUc5dzBCQVFRRkFBT0NBUUVBaDdqWmxaYXpPOXdHRkl3Mll1SVN5WjVYb2JjU0pSS2dMbG52UDh5cUkyVkxTdVBmdkNXOGJxKzNMWnNWcFZ6U0dhbDgwUTk5empOVy9lRm0xTElMRXZNZ0FCeEFtdk9UNlNKZURyajc4WkQyL0haazdmdHVLWU1VbkZTOWhzUWlkWmoveUttbDd4Nm9hQnlpalg5UVU0eTZYcCs3NzlhREFSenZOWTR5RGlRZmNuaFBVN1dablJyTUJZOHBSTVJVVjNKc2MvZXBQclZjOVhoaVE0KzBRNHFVQW1VNlVHNWNQeXFROVJJK2ZIL3VMbm8vZkNHUGZFVjM1NDV3WG5NeXNubC9HYlNLa0FHdVFmcm5ZT2dReFV4dUx1aHBoazVIaGtlMzNUUy9FYVlGc2JTZEhzRktoaCt4Uks3NW9wWExJTkpzNTFpYVdySUFTVmZvRTI0a3FRPT0K" const testSpkac = "U1BLQUM9TUlJQ1FEQ0NBU2d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRHFRa1g5VlZRZWFmclpha0hONTQ4eGRuajlyWitKWDcrV0NFclJOaWl5WlNBK0EvMTN1RkQ0SWIwcDlRc0JsTE1majZUd291MkUxaitiU1NIOUozQ3RwdW1sUjJDbzZkNy9yS05yUStnY1ZpQ3h6eWlqeVo0NTZYRWlMZ3NTVXZrRXBhOU1iWVFWM09yMHVWUXpSNktQVksxUkJkeUdFc0xnalNJekl5eHAzVzR3WTVPcDBrVFNIbm44ajJVMi9OS3BWTmhJTVNORmhHdE9iNTVRT3ZmNUQzWVh6ZVVBNlJna3FrcHhicythV254TGgxUy9ER1FMcGs5cXlSSFc4L3FiVGhkSTlYMFZ3L0tFeVlDWWdaTjVYYzJ6NDhXWUxweWlQY09WdmlJQTJaWmVxQnQrakFzMTdGd3huYUp6SmZyZmtSNVJvYTk3VStQdnRJWnJsLzQ3QWdNQkFBRVdBREFOQmdrcWhraUc5dzBCQVFRRkFBT0NBUUVBaDdqWmxaYXpPOXdHRkl3Mll1SVN5WjVYb2JjU0pSS2dMbG52UDh5cUkyVkxTdVBmdkNXOGJxKzNMWnNWcFZ6U0dhbDgwUTk5empOVy9lRm0xTElMRXZNZ0FCeEFtdk9UNlNKZURyajc4WkQyL0haazdmdHVLWU1VbkZTOWhzUWlkWmoveUttbDd4Nm9hQnlpalg5UVU0eTZYcCs3NzlhREFSenZOWTR5RGlRZmNuaFBVN1dablJyTUJZOHBSTVJVVjNKc2MvZXBQclZjOVhoaVE0KzBRNHFVQW1VNlVHNWNQeXFROVJJK2ZIL3VMbm8vZkNHUGZFVjM1NDV3WG5NeXNubC9HYlNLa0FHdVFmcm5ZT2dReFV4dUx1aHBoazVIaGtlMzNUUy9FYVlGc2JTZEhzRktoaCt4Uks3NW9wWExJTkpzNTFpYVdySUFTVmZvRTI0a3FRPT0K"
const testClientSubject = "/CN=Test/emailAddress=test@example.org" const testClientSubject = "/CN=Test/emailAddress=test@example.org"
@ -199,6 +220,8 @@ const testServerSubject = "/CN=www.example.org"
const testServerSan = "DNS:www.example.org,otherName:1.3.6.1.5.5.7.8.5;UTF8:www.example.org" const testServerSan = "DNS:www.example.org,otherName:1.3.6.1.5.5.7.8.5;UTF8:www.example.org"
// nolint:lll
const testCAKey = "MIIG5AIBAAKCAYEAqMWP2Ec/DxJ5n5bCv0e6oTBoGvsplq1qHVtwPDEL/bwlhUbTigGPQGoUK+Y3eS2T5FY/qpdfqjUBi/FnPiKNwEnmkYuSakSSS6GyrgsP876z1xXth50CIkUnAPR1YJ0bYmhUpEitRJgoWa5my3bS+LuNt1gVHD+zyCOlbfNJZTILnQHFLtzi/wPivlTWpUDJzHWvvo+Ki4e29qWRMaAatiXLUq/wW06fsRSa9plkhNv7jlg9hq8Y2SEie0mRvuyFgIKvkBmcT3X5yPhCWZPomsqQnEJXKnxno0SkrM+XoWWBeusYPsZkfXknGwy/wvoMbVT5MfqyMYY1CTw8/zaSDoC/sj8XmAL44t+EsZ+JEUYgSVW6Y3L2KieuqCibg4B+G8qI4AQm2cjXanjX1kWTUCCtGO6ylxRKNq0zCWhflE2i2s/+4v+RuHQ1laYnfl3vIQZHz0/gtQIlR2AqXc2ODRoTO8d80dXZjwImnrjHQ/yHx4LErHMprNjQb0BprtCDAgMBAAECggGAEF7hfhQjHL4pB/7isxUtGDeO0ZctSI1XrrNQ5rXHOPyIEy50lH1kPNZNUJjLJrjyEIMBN/Xo9KShmsZ2wkMtxsokUFfegupV2no7z8AI8xa7cRCScsYbD+HvT5tmy1FR97CxDSJzlCTCPTi6hd/nxPLEY1Vq7suLD83NXSXtJ6C8GaWzT8FjT2M8GkQ2cd8f8/IycuSPhstKRxB2Tf7+uE5gM4wXX3P374BVK7hjVLPV6c/LYAYZ/e3F33maZo+glyRP0DIWUtVQHhn7ZxlhatLYPrzwoM9MBFVjX4KHjRBWpZ/eSRKmDske0KnQ8nKPo2MsXxm6aKRNr4XJRC0FXqvz1CEa22ANOtCyfNmHHH6PC7R9rCCt8TZFAPDyyVq31KJe89cwdPngPBOIZdnW9U5pmG0aQrwU9ubafX5Yf+uDP0N9rEPEw5sU0QZHMai5751jQFIpej/6IA3mv0rscxP5Bjc8gGJynhj4BvpHWHZzdRQZquQG1CPKDm5yItuBAoHBANhF+ukAUkIvWMuD9WXSGcUuDsfPOSvx5Fx2riQYhbDFhb2W5zZ75Pi4E2OnQ8RDxCbUJ/iubLwm69LixW5e2+hvR8+AFV3bBgXQUl4uJHKCChz94JZHNgaUae1jjqWNxINLWNAIGUzR9ABslWRiE15InjOjMLf0is0IqryPUX1JtHLM9HnDtR5RbuZUhEUxp3a/msJJeM1sVbOMELolmC0O6ChhMM2mj7mSkexSE7XZwJLn+pIP5GZBhdi0Jz++wQKBwQDHxd55McC7gEIOPjy4SUHhE/JnJd0MRr6R2kHiZs7yMy8WBjDp+Ez2JiHHj00HY9hTRswO45ZtfaMFzfAjLJ7Dja7Pvbh48QkZJV+bul1pLdsLnzHtxqDaZSZluGBBUMoh+PE7WauGgflxtWrH0QX1kv+E0Z70F1fgsJkJ7L9j+M9TkKJxOtqS0BVo13Ko2LHN+6hFOeE5J7ItvdapWPXBGUySw9ELmLd38br0wTdzpWD6OupcM8M8Qb+vncEc5EMCgcEA1Vby07VFb5RU+y0IfZBra16rpd58fyT2J1/LGEA4YM/3xbV+DvjYPaEXP05YQtq2O7c8Vst454FdT4HzT5SzSO284KtwaE0N+94r4kuSGIK+hyrIyHUmjgcJFusGY7kdCIbi7ROQIX9aOrDiDUvR30ezBy0Leer4oJjUE30s3XI/Vp9m6lZr66RYyUzFzZvVngYUG2NujvU29Q5N0dIT8x6pVGvLQJH1ZRF4cK3mU5Shqki7nCmhHF22MrZDoVYBAoHBALCmIC5sty9Vn5N2pzyR0sZTXBKnoYo8eEECjSXEoRP7/JPuD4ykenFikJYk+gkh2eTxgnlb9+WDpgb47nI7/3uOKlkaOyf+g3wP1zYeGoFqAfqJ352Q+SWFMenamorHBKX7ulwv04OSJN/OesiL5UgcnwN0VKkkhxlxLzJefXLKTZJoH6weTa5qf7QAZyw0yS0KbeYg4y4mEuFtr4Z52n3QgCx7KLunY/yU7SuGOyFwyIscU6YKQ4Zh4T1KMrv4fwKBwExZH2XIvvu7setxg6IkNMnNJdeJ6mefk3kxdZX+ZprO3cyh60bv0lSjqrKADQuy2MknQKjx0NvI4vbzmhUUb18Koy66oh4r7M5iSKofWs3rybfeGjF4StETSW7fS1nLGlicYqIbX6TT4Hhg91RwT33vrEvvlBQFowV8cR5OmGq6aW6H6bh3UkzcxV2HI/QvwW2mvRvDQycnjfGjuYbVwi6tn2O2wet0Dka7y/AZfp9OBLJRBZJNoIViTn4Lx9FHlQ==" const testCAKey = "MIIG5AIBAAKCAYEAqMWP2Ec/DxJ5n5bCv0e6oTBoGvsplq1qHVtwPDEL/bwlhUbTigGPQGoUK+Y3eS2T5FY/qpdfqjUBi/FnPiKNwEnmkYuSakSSS6GyrgsP876z1xXth50CIkUnAPR1YJ0bYmhUpEitRJgoWa5my3bS+LuNt1gVHD+zyCOlbfNJZTILnQHFLtzi/wPivlTWpUDJzHWvvo+Ki4e29qWRMaAatiXLUq/wW06fsRSa9plkhNv7jlg9hq8Y2SEie0mRvuyFgIKvkBmcT3X5yPhCWZPomsqQnEJXKnxno0SkrM+XoWWBeusYPsZkfXknGwy/wvoMbVT5MfqyMYY1CTw8/zaSDoC/sj8XmAL44t+EsZ+JEUYgSVW6Y3L2KieuqCibg4B+G8qI4AQm2cjXanjX1kWTUCCtGO6ylxRKNq0zCWhflE2i2s/+4v+RuHQ1laYnfl3vIQZHz0/gtQIlR2AqXc2ODRoTO8d80dXZjwImnrjHQ/yHx4LErHMprNjQb0BprtCDAgMBAAECggGAEF7hfhQjHL4pB/7isxUtGDeO0ZctSI1XrrNQ5rXHOPyIEy50lH1kPNZNUJjLJrjyEIMBN/Xo9KShmsZ2wkMtxsokUFfegupV2no7z8AI8xa7cRCScsYbD+HvT5tmy1FR97CxDSJzlCTCPTi6hd/nxPLEY1Vq7suLD83NXSXtJ6C8GaWzT8FjT2M8GkQ2cd8f8/IycuSPhstKRxB2Tf7+uE5gM4wXX3P374BVK7hjVLPV6c/LYAYZ/e3F33maZo+glyRP0DIWUtVQHhn7ZxlhatLYPrzwoM9MBFVjX4KHjRBWpZ/eSRKmDske0KnQ8nKPo2MsXxm6aKRNr4XJRC0FXqvz1CEa22ANOtCyfNmHHH6PC7R9rCCt8TZFAPDyyVq31KJe89cwdPngPBOIZdnW9U5pmG0aQrwU9ubafX5Yf+uDP0N9rEPEw5sU0QZHMai5751jQFIpej/6IA3mv0rscxP5Bjc8gGJynhj4BvpHWHZzdRQZquQG1CPKDm5yItuBAoHBANhF+ukAUkIvWMuD9WXSGcUuDsfPOSvx5Fx2riQYhbDFhb2W5zZ75Pi4E2OnQ8RDxCbUJ/iubLwm69LixW5e2+hvR8+AFV3bBgXQUl4uJHKCChz94JZHNgaUae1jjqWNxINLWNAIGUzR9ABslWRiE15InjOjMLf0is0IqryPUX1JtHLM9HnDtR5RbuZUhEUxp3a/msJJeM1sVbOMELolmC0O6ChhMM2mj7mSkexSE7XZwJLn+pIP5GZBhdi0Jz++wQKBwQDHxd55McC7gEIOPjy4SUHhE/JnJd0MRr6R2kHiZs7yMy8WBjDp+Ez2JiHHj00HY9hTRswO45ZtfaMFzfAjLJ7Dja7Pvbh48QkZJV+bul1pLdsLnzHtxqDaZSZluGBBUMoh+PE7WauGgflxtWrH0QX1kv+E0Z70F1fgsJkJ7L9j+M9TkKJxOtqS0BVo13Ko2LHN+6hFOeE5J7ItvdapWPXBGUySw9ELmLd38br0wTdzpWD6OupcM8M8Qb+vncEc5EMCgcEA1Vby07VFb5RU+y0IfZBra16rpd58fyT2J1/LGEA4YM/3xbV+DvjYPaEXP05YQtq2O7c8Vst454FdT4HzT5SzSO284KtwaE0N+94r4kuSGIK+hyrIyHUmjgcJFusGY7kdCIbi7ROQIX9aOrDiDUvR30ezBy0Leer4oJjUE30s3XI/Vp9m6lZr66RYyUzFzZvVngYUG2NujvU29Q5N0dIT8x6pVGvLQJH1ZRF4cK3mU5Shqki7nCmhHF22MrZDoVYBAoHBALCmIC5sty9Vn5N2pzyR0sZTXBKnoYo8eEECjSXEoRP7/JPuD4ykenFikJYk+gkh2eTxgnlb9+WDpgb47nI7/3uOKlkaOyf+g3wP1zYeGoFqAfqJ352Q+SWFMenamorHBKX7ulwv04OSJN/OesiL5UgcnwN0VKkkhxlxLzJefXLKTZJoH6weTa5qf7QAZyw0yS0KbeYg4y4mEuFtr4Z52n3QgCx7KLunY/yU7SuGOyFwyIscU6YKQ4Zh4T1KMrv4fwKBwExZH2XIvvu7setxg6IkNMnNJdeJ6mefk3kxdZX+ZprO3cyh60bv0lSjqrKADQuy2MknQKjx0NvI4vbzmhUUb18Koy66oh4r7M5iSKofWs3rybfeGjF4StETSW7fS1nLGlicYqIbX6TT4Hhg91RwT33vrEvvlBQFowV8cR5OmGq6aW6H6bh3UkzcxV2HI/QvwW2mvRvDQycnjfGjuYbVwi6tn2O2wet0Dka7y/AZfp9OBLJRBZJNoIViTn4Lx9FHlQ=="
// nolint:lll
const testCACertificate = "MIIFmDCCBACgAwIBAgIUOlITUGXFrKZesL4LlawzVxLTXFYwDQYJKoZIhvcNAQELBQAwNzELMAkGA1UEBhMCQVUxFDASBgNVBAoMC0NBY2VydCBJbmMuMRIwEAYDVQQDDAlUZXN0IFJvb3QwHhcNMjEwMTA4MTMwNDM5WhcNMjYwMTA3MTMwNDM5WjA9MQswCQYDVQQGEwJBVTEUMBIGA1UECgwLQ0FjZXJ0IEluYy4xGDAWBgNVBAMMD0NsYXNzIDMgVGVzdCBDQTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKjFj9hHPw8SeZ+Wwr9HuqEwaBr7KZatah1bcDwxC/28JYVG04oBj0BqFCvmN3ktk+RWP6qXX6o1AYvxZz4ijcBJ5pGLkmpEkkuhsq4LD/O+s9cV7YedAiJFJwD0dWCdG2JoVKRIrUSYKFmuZst20vi7jbdYFRw/s8gjpW3zSWUyC50BxS7c4v8D4r5U1qVAycx1r76PiouHtvalkTGgGrYly1Kv8FtOn7EUmvaZZITb+45YPYavGNkhIntJkb7shYCCr5AZnE91+cj4QlmT6JrKkJxCVyp8Z6NEpKzPl6FlgXrrGD7GZH15JxsMv8L6DG1U+TH6sjGGNQk8PP82kg6Av7I/F5gC+OLfhLGfiRFGIElVumNy9ionrqgom4OAfhvKiOAEJtnI12p419ZFk1AgrRjuspcUSjatMwloX5RNotrP/uL/kbh0NZWmJ35d7yEGR89P4LUCJUdgKl3Njg0aEzvHfNHV2Y8CJp64x0P8h8eCxKxzKazY0G9Aaa7QgwIDAQABo4IBlDCCAZAwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBRI3ALoQ7dcRqu0gHtxaLVAv2DcOzAfBgNVHSMEGDAWgBTrwahlUMEoV1OavhMbkLcSar8PIzB3BggrBgEFBQcBAQRrMGkwNwYIKwYBBQUHMAKGK2h0dHA6Ly90ZXN0LmNhY2VydC5sb2NhbGhvc3QvY2Evcm9vdC9jYS5jcnQwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLnRlc3QuY2FjZXJ0LmxvY2FsaG9zdC8wPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC50ZXN0LmNhY2VydC5sb2NhbGhvc3QvY2xhc3MzLmNybDBUBgNVHSAETTBLMEkGCCsGAQUFBwIBMD0wOwYIKwYBBQUHAgEWL2h0dHA6Ly90ZXN0LmNhY2VydC5sb2NhbGhvc3QvY2EvY2xhc3MzL2Nwcy5odG1sMA0GCSqGSIb3DQEBCwUAA4IBgQAcqK68GOxTfM9zSRbHWHchsbiyKcbxPo42se9dm/nLHT/N2XEW9Ycj5dZD8+XgoW8dVPS3uVZGj57Pr8ix3OXhMKGqcdO2QRAQaoyjw7t9dCkaJ8b7h39sY/5pFSSIdYAyyb9uPgJ1FPLueOqm3bZHVFcbiiA8/miiwGWPVEfK7zdEmFKMAkY2wYtWBeovKNVnCbuQ1Pd8CxvkCs5R9KnMfbU7bgJK8zkhlHwdtalmg2IS4yMuvYeL9S3QwL7fYcCjjTLCKwkj3frsnkRC5pGPHQ6/iVVbdsqAI70A1Uqcl15Jcpzg0Nc2EABjhbWO7gLpHpzMI5Alt+Tr+oWhe2M7wnBhuojgwASA10CnXT27GYXziIzr8d3P+T0PVLD2WcvQeEUJoQySw6W8CIkaZEZG6YBWjrAkGcO6JB+YJ5UiJOCHA6W4pmwNkGR2oh6JMQCUikaFVywb1HMIGOINOBHymj4KkuywC2w6SXMD4OqJcsCmHSNcqjFvcT/22kYCtDE=" const testCACertificate = "MIIFmDCCBACgAwIBAgIUOlITUGXFrKZesL4LlawzVxLTXFYwDQYJKoZIhvcNAQELBQAwNzELMAkGA1UEBhMCQVUxFDASBgNVBAoMC0NBY2VydCBJbmMuMRIwEAYDVQQDDAlUZXN0IFJvb3QwHhcNMjEwMTA4MTMwNDM5WhcNMjYwMTA3MTMwNDM5WjA9MQswCQYDVQQGEwJBVTEUMBIGA1UECgwLQ0FjZXJ0IEluYy4xGDAWBgNVBAMMD0NsYXNzIDMgVGVzdCBDQTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKjFj9hHPw8SeZ+Wwr9HuqEwaBr7KZatah1bcDwxC/28JYVG04oBj0BqFCvmN3ktk+RWP6qXX6o1AYvxZz4ijcBJ5pGLkmpEkkuhsq4LD/O+s9cV7YedAiJFJwD0dWCdG2JoVKRIrUSYKFmuZst20vi7jbdYFRw/s8gjpW3zSWUyC50BxS7c4v8D4r5U1qVAycx1r76PiouHtvalkTGgGrYly1Kv8FtOn7EUmvaZZITb+45YPYavGNkhIntJkb7shYCCr5AZnE91+cj4QlmT6JrKkJxCVyp8Z6NEpKzPl6FlgXrrGD7GZH15JxsMv8L6DG1U+TH6sjGGNQk8PP82kg6Av7I/F5gC+OLfhLGfiRFGIElVumNy9ionrqgom4OAfhvKiOAEJtnI12p419ZFk1AgrRjuspcUSjatMwloX5RNotrP/uL/kbh0NZWmJ35d7yEGR89P4LUCJUdgKl3Njg0aEzvHfNHV2Y8CJp64x0P8h8eCxKxzKazY0G9Aaa7QgwIDAQABo4IBlDCCAZAwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBRI3ALoQ7dcRqu0gHtxaLVAv2DcOzAfBgNVHSMEGDAWgBTrwahlUMEoV1OavhMbkLcSar8PIzB3BggrBgEFBQcBAQRrMGkwNwYIKwYBBQUHMAKGK2h0dHA6Ly90ZXN0LmNhY2VydC5sb2NhbGhvc3QvY2Evcm9vdC9jYS5jcnQwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLnRlc3QuY2FjZXJ0LmxvY2FsaG9zdC8wPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC50ZXN0LmNhY2VydC5sb2NhbGhvc3QvY2xhc3MzLmNybDBUBgNVHSAETTBLMEkGCCsGAQUFBwIBMD0wOwYIKwYBBQUHAgEWL2h0dHA6Ly90ZXN0LmNhY2VydC5sb2NhbGhvc3QvY2EvY2xhc3MzL2Nwcy5odG1sMA0GCSqGSIb3DQEBCwUAA4IBgQAcqK68GOxTfM9zSRbHWHchsbiyKcbxPo42se9dm/nLHT/N2XEW9Ycj5dZD8+XgoW8dVPS3uVZGj57Pr8ix3OXhMKGqcdO2QRAQaoyjw7t9dCkaJ8b7h39sY/5pFSSIdYAyyb9uPgJ1FPLueOqm3bZHVFcbiiA8/miiwGWPVEfK7zdEmFKMAkY2wYtWBeovKNVnCbuQ1Pd8CxvkCs5R9KnMfbU7bgJK8zkhlHwdtalmg2IS4yMuvYeL9S3QwL7fYcCjjTLCKwkj3frsnkRC5pGPHQ6/iVVbdsqAI70A1Uqcl15Jcpzg0Nc2EABjhbWO7gLpHpzMI5Alt+Tr+oWhe2M7wnBhuojgwASA10CnXT27GYXziIzr8d3P+T0PVLD2WcvQeEUJoQySw6W8CIkaZEZG6YBWjrAkGcO6JB+YJ5UiJOCHA6W4pmwNkGR2oh6JMQCUikaFVywb1HMIGOINOBHymj4KkuywC2w6SXMD4OqJcsCmHSNcqjFvcT/22kYCtDE="