Browse Source

Configure golangci-lint and fix warnings

master
Jan Dittberner 8 months ago
parent
commit
2e467b3d2e
  1. 39
      .golangci.yml
  2. 46
      client/config.go
  3. 5
      client/config_test.go
  4. 44
      client/main.go
  5. 1
      client/processing/process.go
  6. 211
      client/protocol/protocol.go
  7. 11
      cmd/signer/main.go
  8. 3
      datastructures/common.go
  9. 65
      datastructures/signerrequest.go
  10. 39
      datastructures/signerresponse.go
  11. 17
      shared/io.go
  12. 10
      shared/shared.go
  13. 163
      signer/command_processor.go
  14. 17
      signer/common/helpers.go
  15. 6
      signer/crypto_system.go
  16. 36
      signer/openpgpops/openpgpops.go
  17. 118
      signer/port_handler.go
  18. 226
      signer/protocol_elements.go
  19. 358
      signer/x509ops/x509ops.go
  20. 35
      signer/x509ops/x509ops_test.go

39
.golangci.yml

@ -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

46
client/config.go

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

5
client/config_test.go

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

44
client/main.go

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

1
client/processing/process.go

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

211
client/protocol/protocol.go

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

11
cmd/signer/main.go

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

3
datastructures/common.go

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

65
datastructures/signerrequest.go

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

39
datastructures/signerresponse.go

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

17
shared/io.go

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

10
shared/shared.go

@ -19,7 +19,7 @@ type Action byte
const (
ActionNul = Action(0)
ActionSign = Action(1)
ActionRevoke = Action(2)
ActionRevoke = Action(2) // nolint:gomnd
)
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

163
signer/command_processor.go

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

17
signer/common/helpers.go

@ -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
}

6
signer/crypto_system.go

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

36
signer/openpgp_ops/operations.go → signer/openpgpops/openpgpops.go

@ -1,8 +1,9 @@
package openpgp_ops
package openpgpops
import (
"bytes"
"crypto"
"errors"
"fmt"
"os"
"time"
@ -13,6 +14,8 @@ import (
"golang.org/x/crypto/openpgp/packet"
)
const hoursInADay = 24
type OpenPGPRoot struct {
Name string
SecretKeyRing string
@ -22,24 +25,27 @@ type OpenPGPRoot struct {
func (r *OpenPGPRoot) SignPublicKey(pubKey []byte, algorithm crypto.Hash, days uint16) ([]byte, error) {
signingKey, err := r.findSigningKey(r.Identifier)
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))
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{})
armorOutput, err := armor.Encode(output, "PGP PUBLIC KEY BLOCK", map[string]string{})
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 {
log.Tracef("found %+v", pe.PrimaryKey.KeyIdString())
for _, i := range pe.Identities {
expiry := calculateExpiry(i, days)
if !i.SelfSignature.KeyExpired(time.Now()) {
sig := &packet.Signature{
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{
DefaultHash: algorithm,
}); 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)
}