Protocol improvements

- add a client generated command ID for tracing commands and responses
- define protocol delimiter in protocol.CobsDelimiter
- apply code simplifications suggested by golangci-lint
- add Makefile
- add compile time build information for signer binary
- make sure that dependencies for msgpackgen survive go mod tidy
- extract MsgPackHandler into its own file
- add CRL number to fetch CRL response
- remove port.Flush() to avoid removing written data before it reaches the
  client
This commit is contained in:
Jan Dittberner 2022-11-28 17:10:46 +01:00
parent 8e443bd8b4
commit f0d456dd13
11 changed files with 482 additions and 643 deletions

26
Makefile Normal file
View file

@ -0,0 +1,26 @@
GOFILES := $(shell find -type f -name '*.go')
BUILD_TIME := $(shell date --rfc-3339=seconds)
COMMIT := $(shell git show-ref --head --abbrev=8 HEAD|cut -d ' ' -f 1)
VERSION := $(shell git describe --always --dirty)
all: pkg/messages/resolver.msgpackgen.go lint test clientsim signer
pkg/messages/resolver.msgpackgen.go: pkg/messages/messages.go
go generate $<
lint:
golangci-lint run
test:
go test -race ./...
clientsim: $(GOFILES)
go build -race ./cmd/clientsim
signer: $(GOFILES)
go build -race -ldflags="-X 'main.date=$(BUILD_TIME)' -X 'main.commit=$(COMMIT)' -X 'main.version=$(VERSION)'" ./cmd/signer
clean:
rm -f signer clientsim pkg/messages/resolver.msgpackgen.go
.PHONY: test lint all clean

View file

@ -37,7 +37,7 @@ import (
"git.cacert.org/cacert-gosigner/pkg/messages"
)
var cobsConfig = cobs.Config{SpecialByte: 0x00, Delimiter: true, EndingSave: true}
var cobsConfig = cobs.Config{SpecialByte: protocol.CobsDelimiter, Delimiter: true, EndingSave: true}
type protocolState int8
@ -82,17 +82,16 @@ func (g *TestCommandGenerator) CmdAnnouncement() ([]byte, error) {
g.lock.Lock()
defer g.lock.Unlock()
select {
case g.currentCommand = <-g.commands:
announceData, err := msgpack.Marshal(g.currentCommand.Announce)
if err != nil {
return nil, fmt.Errorf("could not marshal command annoucement: %w", err)
}
g.currentCommand = <-g.commands
g.logger.WithField("announcement", &g.currentCommand.Announce).Info("write command announcement")
return announceData, nil
announceData, err := msgpack.Marshal(g.currentCommand.Announce)
if err != nil {
return nil, fmt.Errorf("could not marshal command annoucement: %w", err)
}
g.logger.WithField("announcement", &g.currentCommand.Announce).Info("write command announcement")
return announceData, nil
}
func (g *TestCommandGenerator) CmdData() ([]byte, error) {
@ -138,13 +137,15 @@ func (g *TestCommandGenerator) HandleResponse(frame []byte) error {
return fmt.Errorf("unmarshal failed: %w", err)
}
g.currentResponse.Response = response
g.currentResponse.Response = &response
case messages.RespFetchCRL:
var response messages.FetchCRLResponse
if err := msgpack.Unmarshal(frame, &response); err != nil {
return fmt.Errorf("unmarshal failed: %w", err)
}
g.currentResponse.Response = &response
}
g.logger.WithField(
@ -159,14 +160,27 @@ func (g *TestCommandGenerator) HandleResponse(frame []byte) error {
}
func (g *TestCommandGenerator) GenerateCommands(ctx context.Context) error {
const healthInterval = 10 * time.Second
const (
healthInterval = 5 * time.Second
startPause = 3 * time.Second
)
var (
announce *messages.CommandAnnounce
err error
)
g.logger.Info("start generating commands")
time.Sleep(healthInterval)
time.Sleep(startPause)
announce, err = messages.BuildCommandAnnounce(messages.CmdFetchCRL)
if err != nil {
return fmt.Errorf("build command announce failed: %w", err)
}
g.commands <- &protocol.Command{
Announce: messages.BuildCommandAnnounce(messages.CmdFetchCRL),
Announce: announce,
Command: &messages.FetchCRLCommand{IssuerID: "sub-ecc_person_2022"},
}
@ -181,8 +195,13 @@ func (g *TestCommandGenerator) GenerateCommands(ctx context.Context) error {
return nil
case <-timer.C:
announce, err = messages.BuildCommandAnnounce(messages.CmdHealth)
if err != nil {
return fmt.Errorf("build command announce failed: %w", err)
}
g.commands <- &protocol.Command{
Announce: messages.BuildCommandAnnounce(messages.CmdHealth),
Announce: announce,
Command: &messages.HealthCommand{},
}
}
@ -203,7 +222,9 @@ func (c *clientSimulator) readFrames() error {
const readInterval = 50 * time.Millisecond
var frame []byte
buffer := &bytes.Buffer{}
delimiter := []byte{cobsConfig.SpecialByte}
for {
@ -231,6 +252,8 @@ func (c *clientSimulator) readFrames() error {
rest := buffer.Bytes()
if !bytes.Contains(rest, delimiter) {
c.logger.Tracef("read data does not contain the delimiter %x", delimiter)
continue
}
@ -254,6 +277,8 @@ func (c *clientSimulator) readFrames() error {
c.logger.Tracef("frame decoded to length %d", len(decoded))
c.framesIn <- decoded
c.logger.Tracef("%d bytes remaining", len(rest))
}
buffer.Truncate(0)
@ -266,9 +291,6 @@ func (c *clientSimulator) readFrames() error {
func (c *clientSimulator) writeFrame(frame []byte) error {
encoded := cobs.Encode(frame, cobsConfig)
c.lock.Lock()
defer c.lock.Unlock()
if _, err := io.Copy(os.Stdout, bytes.NewBuffer(encoded)); err != nil {
return fmt.Errorf("could not write data: %w", err)
}
@ -281,11 +303,15 @@ func (c *clientSimulator) readFromStdin() ([]byte, error) {
buf := make([]byte, bufferSize)
c.logger.Trace("waiting for input")
count, err := os.Stdin.Read(buf)
if err != nil {
return nil, fmt.Errorf("reading input failed: %w", err)
}
c.logger.Tracef("read %d bytes from stdin", count)
return buf[:count], nil
}
@ -308,25 +334,6 @@ func (c *clientSimulator) writeCmdAnnouncement() error {
return nil
}
func (c *clientSimulator) writeCommandAnnouncement() error {
frame, err := c.commandGenerator.CmdAnnouncement()
if err != nil {
return fmt.Errorf("could not get command announcement: %w", err)
}
c.logger.Trace("writing command announcement")
if err := c.writeFrame(frame); err != nil {
return err
}
if err := c.nextState(); err != nil {
return err
}
return nil
}
func (c *clientSimulator) writeCommand() error {
frame, err := c.commandGenerator.CmdData()
if err != nil {
@ -349,19 +356,18 @@ func (c *clientSimulator) writeCommand() error {
func (c *clientSimulator) handleResponseAnnounce() error {
c.logger.Trace("waiting for response announce")
select {
case frame := <-c.framesIn:
if frame == nil {
return nil
}
frame := <-c.framesIn
if err := c.commandGenerator.HandleResponseAnnounce(frame); err != nil {
return fmt.Errorf("response announce handling failed: %w", err)
}
if frame == nil {
return nil
}
if err := c.nextState(); err != nil {
return err
}
if err := c.commandGenerator.HandleResponseAnnounce(frame); err != nil {
return fmt.Errorf("response announce handling failed: %w", err)
}
if err := c.nextState(); err != nil {
return err
}
return nil
@ -370,19 +376,18 @@ func (c *clientSimulator) handleResponseAnnounce() error {
func (c *clientSimulator) handleResponseData() error {
c.logger.Trace("waiting for response data")
select {
case frame := <-c.framesIn:
if frame == nil {
return nil
}
frame := <-c.framesIn
if err := c.commandGenerator.HandleResponse(frame); err != nil {
return fmt.Errorf("response handler failed: %w", err)
}
if frame == nil {
return nil
}
if err := c.nextState(); err != nil {
return err
}
if err := c.commandGenerator.HandleResponse(frame); err != nil {
return fmt.Errorf("response handler failed: %w", err)
}
if err := c.nextState(); err != nil {
return err
}
return nil
@ -412,6 +417,7 @@ func (c *clientSimulator) Run(ctx context.Context) error {
if err != nil {
return fmt.Errorf("error from handler loop: %w", err)
}
return nil
default:
if err := c.handleProtocolState(); err != nil {
@ -424,6 +430,9 @@ func (c *clientSimulator) Run(ctx context.Context) error {
func (c *clientSimulator) handleProtocolState() error {
c.logger.Tracef("handling protocol state %s", c.protocolState)
c.lock.Lock()
defer c.lock.Unlock()
switch c.protocolState {
case cmdAnnounce:
if err := c.writeCmdAnnouncement(); err != nil {
@ -462,17 +471,17 @@ func (c *clientSimulator) nextState() error {
func main() {
logger := logrus.New()
logger.SetOutput(os.Stderr)
logger.SetLevel(logrus.TraceLevel)
logger.SetLevel(logrus.DebugLevel)
messages.RegisterGeneratedResolver()
sim := &clientSimulator{
commandGenerator: &TestCommandGenerator{
logger: logger,
commands: make(chan *protocol.Command, 0),
commands: make(chan *protocol.Command),
},
logger: logger,
framesIn: make(chan []byte, 0),
framesIn: make(chan []byte),
}
err := sim.Run(context.Background())

View file

@ -35,9 +35,9 @@ import (
)
var (
commit string
date string
version string
commit = "dev"
date = "unknown"
version = "unknown"
)
const (

3
go.mod
View file

@ -4,6 +4,8 @@ go 1.17
require (
github.com/ThalesIgnite/crypto11 v1.2.5
github.com/dave/jennifer v1.4.1
github.com/google/uuid v1.3.0
github.com/justincpresley/go-cobs v1.2.0
github.com/shamaton/msgpackgen v0.3.0
github.com/sirupsen/logrus v1.9.0
@ -14,7 +16,6 @@ require (
)
require (
github.com/dave/jennifer v1.4.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect

2
go.sum
View file

@ -5,6 +5,8 @@ github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpT
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/justincpresley/go-cobs v1.2.0 h1:dyWszWzXObEv8sxVMJTAIo9XT7HEM10vkAOZq2eVEsQ=
github.com/justincpresley/go-cobs v1.2.0/go.mod h1:L0d+EbGirv6IzsXNzwULduI2/z3ijkkAmsAuPMpLfqA=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=

View file

@ -24,8 +24,14 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"strings"
"time"
// required for msgpackgen
_ "github.com/dave/jennifer"
"github.com/google/uuid"
)
type CommandCode int8
@ -72,33 +78,48 @@ func (c ResponseCode) String() string {
type CommandAnnounce struct {
Code CommandCode `msgpack:"code"`
ID string `msgpack:"id"`
Created time.Time `msgpack:"created"`
}
func (r *CommandAnnounce) String() string {
return fmt.Sprintf("CommandAnnounce[code=%s, created=%s]", r.Code, r.Created)
return fmt.Sprintf("code=%s, id=%s, created=%s", r.Code, r.ID, r.Created.Format(time.RFC3339))
}
type ResponseAnnounce struct {
Code ResponseCode `msgpack:"code"`
Created time.Time `msgpack:"created"`
ID string `msgpack:"id"`
}
func (r *ResponseAnnounce) String() string {
return fmt.Sprintf("ResponseAnnounce[code=%s, created=%s]", r.Code, r.Created)
return fmt.Sprintf("code=%s, id=%s, created=%s", r.Code, r.ID, r.Created.Format(time.RFC3339))
}
type FetchCRLCommand struct {
IssuerID string `msgpack:"issuer_id"`
LastKnownHash struct {
Algorithm string `msgpack:"algorithm"`
Value string `msgpack:"value"`
} `msgpack:"last_known_hash"` // optional last known hash in format
IssuerID string `msgpack:"issuer_id"`
LastKnownID *big.Int `msgpack:"last_known_id"`
}
func (f *FetchCRLCommand) String() string {
builder := &strings.Builder{}
_, _ = fmt.Fprintf(builder, "issuerId='%s'", f.IssuerID)
if f.LastKnownID != nil {
_, _ = fmt.Fprintf(builder, ", lastKnownId=%s", f.LastKnownID.Text(16))
}
return builder.String()
}
type HealthCommand struct {
}
func (h *HealthCommand) String() string {
return ""
}
type HealthInfo struct {
Source string
Healthy bool
@ -138,9 +159,10 @@ func (h *HealthResponse) String() string {
}
type FetchCRLResponse struct {
IssuerID string `msgpack:"issuer_id"`
IsDelta bool `msgpack:"is_delta"`
CRLData []byte `msgpack:"crl_data"`
IssuerID string `msgpack:"issuer_id"`
IsDelta bool `msgpack:"is_delta"`
CRLData []byte `msgpack:"crl_data"`
CRLNumber *big.Int
}
func (r *FetchCRLResponse) String() string {
@ -151,12 +173,18 @@ func (r *FetchCRLResponse) String() string {
if r.IsDelta {
_, _ = fmt.Fprint(builder, ", delta CRL data not shown")
} else {
revlist, err := x509.ParseRevocationList(r.CRLData)
revocationList, err := x509.ParseRevocationList(r.CRLData)
if err != nil {
_, _ = fmt.Fprintf(builder, ", could not parse CRL: %s", err.Error())
} else {
_, _ = fmt.Fprintf(builder, ", CRL info: issuer=%s, number=%s, next update=%s, revoked certificates=%d",
revlist.Issuer, revlist.Number, revlist.NextUpdate, len(revlist.RevokedCertificates))
_, _ = fmt.Fprintf(
builder,
", CRL info: issuer=%s, number=%s, next update=%s, revoked certificates=%d",
revocationList.Issuer,
revocationList.Number,
revocationList.NextUpdate,
len(revocationList.RevokedCertificates),
)
_, _ = builder.WriteString(", CRL data:\n")
_ = pem.Encode(builder, &pem.Block{
Type: "CERTIFICATE REVOCATION LIST",
@ -172,10 +200,19 @@ type ErrorResponse struct {
Message string `msgpack:"message"`
}
func BuildCommandAnnounce(code CommandCode) *CommandAnnounce {
return &CommandAnnounce{Code: code, Created: time.Now().UTC()}
func (e *ErrorResponse) String() string {
return fmt.Sprintf("message=%s", e.Message)
}
func BuildResponseAnnounce(code ResponseCode) *ResponseAnnounce {
return &ResponseAnnounce{Code: code, Created: time.Now().UTC()}
func BuildCommandAnnounce(code CommandCode) (*CommandAnnounce, error) {
commandID, err := uuid.NewUUID()
if err != nil {
return nil, fmt.Errorf("could not build command id: %w", err)
}
return &CommandAnnounce{Code: code, ID: commandID.String(), Created: time.Now().UTC()}, nil
}
func BuildResponseAnnounce(code ResponseCode, commandID string) *ResponseAnnounce {
return &ResponseAnnounce{Code: code, ID: commandID, Created: time.Now().UTC()}
}

View file

@ -116,36 +116,6 @@ func ___encodeAsArray(i interface{}) ([]byte, error) {
return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthResponse", size, offset)
}
return b, err
case FetchCRLResponse:
encoder := enc.NewEncoder()
size, err := ___calcArraySizeFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder)
if err != nil {
return nil, err
}
encoder.MakeBytes(size)
b, offset, err := ___encodeArrayFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0)
if err != nil {
return nil, err
}
if size != offset {
return nil, fmt.Errorf("%s size / offset different %d : %d", "FetchCRLResponse", size, offset)
}
return b, err
case *FetchCRLResponse:
encoder := enc.NewEncoder()
size, err := ___calcArraySizeFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder)
if err != nil {
return nil, err
}
encoder.MakeBytes(size)
b, offset, err := ___encodeArrayFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0)
if err != nil {
return nil, err
}
if size != offset {
return nil, fmt.Errorf("%s size / offset different %d : %d", "FetchCRLResponse", size, offset)
}
return b, err
case ErrorResponse:
encoder := enc.NewEncoder()
size, err := ___calcArraySizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder)
@ -273,36 +243,6 @@ func ___encodeAsMap(i interface{}) ([]byte, error) {
return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthResponse", size, offset)
}
return b, err
case FetchCRLResponse:
encoder := enc.NewEncoder()
size, err := ___calcMapSizeFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder)
if err != nil {
return nil, err
}
encoder.MakeBytes(size)
b, offset, err := ___encodeMapFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0)
if err != nil {
return nil, err
}
if size != offset {
return nil, fmt.Errorf("%s size / offset different %d : %d", "FetchCRLResponse", size, offset)
}
return b, err
case *FetchCRLResponse:
encoder := enc.NewEncoder()
size, err := ___calcMapSizeFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder)
if err != nil {
return nil, err
}
encoder.MakeBytes(size)
b, offset, err := ___encodeMapFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0)
if err != nil {
return nil, err
}
if size != offset {
return nil, fmt.Errorf("%s size / offset different %d : %d", "FetchCRLResponse", size, offset)
}
return b, err
case ErrorResponse:
encoder := enc.NewEncoder()
size, err := ___calcMapSizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder)
@ -391,20 +331,6 @@ func ___decodeAsArray(data []byte, i interface{}) (bool, error) {
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
}
return true, err
case *FetchCRLResponse:
decoder := dec.NewDecoder(data)
offset, err := ___decodeArrayFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, decoder, 0)
if err == nil && offset != decoder.Len() {
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
}
return true, err
case **FetchCRLResponse:
decoder := dec.NewDecoder(data)
offset, err := ___decodeArrayFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, decoder, 0)
if err == nil && offset != decoder.Len() {
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
}
return true, err
case *ErrorResponse:
decoder := dec.NewDecoder(data)
offset, err := ___decodeArrayErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, decoder, 0)
@ -468,20 +394,6 @@ func ___decodeAsMap(data []byte, i interface{}) (bool, error) {
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
}
return true, err
case *FetchCRLResponse:
decoder := dec.NewDecoder(data)
offset, err := ___decodeMapFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, decoder, 0)
if err == nil && offset != decoder.Len() {
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
}
return true, err
case **FetchCRLResponse:
decoder := dec.NewDecoder(data)
offset, err := ___decodeMapFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, decoder, 0)
if err == nil && offset != decoder.Len() {
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
}
return true, err
case *ErrorResponse:
decoder := dec.NewDecoder(data)
offset, err := ___decodeMapErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, decoder, 0)
@ -1057,217 +969,6 @@ func ___decodeMapHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f
return offset, err
}
// calculate size from git.cacert.org/cacert-gosigner/pkg/messages.FetchCRLResponse
func ___calcArraySizeFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v FetchCRLResponse, encoder *enc.Encoder) (int, error) {
size := 0
size += encoder.CalcStructHeaderFix(3)
size += encoder.CalcString(v.IssuerID)
size += encoder.CalcBool(v.IsDelta)
if v.CRLData != nil {
s, err := encoder.CalcSliceLength(len(v.CRLData), true)
if err != nil {
return 0, err
}
size += s
for _, vv := range v.CRLData {
size += encoder.CalcByte(vv)
}
} else {
size += encoder.CalcNil()
}
return size, nil
}
// calculate size from git.cacert.org/cacert-gosigner/pkg/messages.FetchCRLResponse
func ___calcMapSizeFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v FetchCRLResponse, encoder *enc.Encoder) (int, error) {
size := 0
size += encoder.CalcStructHeaderFix(3)
size += encoder.CalcStringFix(9)
size += encoder.CalcString(v.IssuerID)
size += encoder.CalcStringFix(8)
size += encoder.CalcBool(v.IsDelta)
size += encoder.CalcStringFix(8)
if v.CRLData != nil {
s, err := encoder.CalcSliceLength(len(v.CRLData), true)
if err != nil {
return 0, err
}
size += s
for _, vv := range v.CRLData {
size += encoder.CalcByte(vv)
}
} else {
size += encoder.CalcNil()
}
return size, nil
}
// encode from git.cacert.org/cacert-gosigner/pkg/messages.FetchCRLResponse
func ___encodeArrayFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v FetchCRLResponse, encoder *enc.Encoder, offset int) ([]byte, int, error) {
var err error
offset = encoder.WriteStructHeaderFixAsArray(3, offset)
offset = encoder.WriteString(v.IssuerID, offset)
offset = encoder.WriteBool(v.IsDelta, offset)
if v.CRLData != nil {
offset = encoder.WriteSliceLength(len(v.CRLData), offset, true)
for _, vv := range v.CRLData {
offset = encoder.WriteByte(vv, offset)
}
} else {
offset = encoder.WriteNil(offset)
}
return encoder.EncodedBytes(), offset, err
}
// encode from git.cacert.org/cacert-gosigner/pkg/messages.FetchCRLResponse
func ___encodeMapFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v FetchCRLResponse, encoder *enc.Encoder, offset int) ([]byte, int, error) {
var err error
offset = encoder.WriteStructHeaderFixAsMap(3, offset)
offset = encoder.WriteStringFix("issuer_id", 9, offset)
offset = encoder.WriteString(v.IssuerID, offset)
offset = encoder.WriteStringFix("is_delta", 8, offset)
offset = encoder.WriteBool(v.IsDelta, offset)
offset = encoder.WriteStringFix("crl_data", 8, offset)
if v.CRLData != nil {
offset = encoder.WriteSliceLength(len(v.CRLData), offset, true)
for _, vv := range v.CRLData {
offset = encoder.WriteByte(vv, offset)
}
} else {
offset = encoder.WriteNil(offset)
}
return encoder.EncodedBytes(), offset, err
}
// decode to git.cacert.org/cacert-gosigner/pkg/messages.FetchCRLResponse
func ___decodeArrayFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *FetchCRLResponse, decoder *dec.Decoder, offset int) (int, error) {
offset, err := decoder.CheckStructHeader(3, offset)
if err != nil {
return 0, err
}
{
var vv string
vv, offset, err = decoder.AsString(offset)
if err != nil {
return 0, err
}
v.IssuerID = vv
}
{
var vv bool
vv, offset, err = decoder.AsBool(offset)
if err != nil {
return 0, err
}
v.IsDelta = vv
}
if !decoder.IsCodeNil(offset) {
var vv []byte
var vvl int
vvl, offset, err = decoder.SliceLength(offset)
if err != nil {
return 0, err
}
vv = make([]byte, vvl)
for vvi := range vv {
var vvv byte
vvv, offset, err = decoder.AsByte(offset)
if err != nil {
return 0, err
}
vv[vvi] = vvv
}
v.CRLData = vv
} else {
offset++
}
return offset, err
}
// decode to git.cacert.org/cacert-gosigner/pkg/messages.FetchCRLResponse
func ___decodeMapFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *FetchCRLResponse, decoder *dec.Decoder, offset int) (int, error) {
keys := [][]byte{
{uint8(0x69), uint8(0x73), uint8(0x73), uint8(0x75), uint8(0x65), uint8(0x72), uint8(0x5f), uint8(0x69), uint8(0x64)}, // issuer_id
{uint8(0x69), uint8(0x73), uint8(0x5f), uint8(0x64), uint8(0x65), uint8(0x6c), uint8(0x74), uint8(0x61)}, // is_delta
{uint8(0x63), uint8(0x72), uint8(0x6c), uint8(0x5f), uint8(0x64), uint8(0x61), uint8(0x74), uint8(0x61)}, // crl_data
}
offset, err := decoder.CheckStructHeader(3, offset)
if err != nil {
return 0, err
}
count := 0
for count < 3 {
var dataKey []byte
dataKey, offset, err = decoder.AsStringBytes(offset)
if err != nil {
return 0, err
}
fieldIndex := -1
for i, key := range keys {
if len(dataKey) != len(key) {
continue
}
fieldIndex = i
for dataKeyIndex := range dataKey {
if dataKey[dataKeyIndex] != key[dataKeyIndex] {
fieldIndex = -1
break
}
}
if fieldIndex >= 0 {
break
}
}
switch fieldIndex {
case 0:
{
var vv string
vv, offset, err = decoder.AsString(offset)
if err != nil {
return 0, err
}
v.IssuerID = vv
}
count++
case 1:
{
var vv bool
vv, offset, err = decoder.AsBool(offset)
if err != nil {
return 0, err
}
v.IsDelta = vv
}
count++
case 2:
if !decoder.IsCodeNil(offset) {
var vv []byte
var vvl int
vvl, offset, err = decoder.SliceLength(offset)
if err != nil {
return 0, err
}
vv = make([]byte, vvl)
for vvi := range vv {
var vvv byte
vvv, offset, err = decoder.AsByte(offset)
if err != nil {
return 0, err
}
vv[vvi] = vvv
}
v.CRLData = vv
} else {
offset++
}
count++
default:
return 0, fmt.Errorf("unknown key[%s] found", string(dataKey))
}
}
return offset, err
}
// calculate size from git.cacert.org/cacert-gosigner/pkg/messages.ErrorResponse
func ___calcArraySizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v ErrorResponse, encoder *enc.Encoder) (int, error) {
size := 0

282
pkg/protocol/msgpack.go Normal file
View file

@ -0,0 +1,282 @@
/*
Copyright 2022 CAcert Inc.
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package protocol
import (
"errors"
"fmt"
"sync"
"github.com/shamaton/msgpackgen/msgpack"
"github.com/sirupsen/logrus"
"git.cacert.org/cacert-gosigner/pkg/health"
"git.cacert.org/cacert-gosigner/pkg/messages"
"git.cacert.org/cacert-gosigner/pkg/x509/revoking"
)
// MsgPackHandler is a Handler implementation for the msgpack serialization format.
type MsgPackHandler struct {
logger *logrus.Logger
healthHandler *health.Handler
fetchCRLHandler *revoking.FetchCRLHandler
currentCommand *Command
currentResponse *Response
lock sync.Mutex
}
func (m *MsgPackHandler) HandleCommandAnnounce(frame []byte) error {
m.lock.Lock()
defer m.lock.Unlock()
var ann messages.CommandAnnounce
if err := msgpack.Unmarshal(frame, &ann); err != nil {
return fmt.Errorf("could not unmarshal command announcement: %w", err)
}
m.logger.WithField("announcement", &ann).Info("received command announcement")
m.currentCommand = &Command{Announce: &ann}
return nil
}
func (m *MsgPackHandler) HandleCommand(frame []byte) error {
m.lock.Lock()
defer m.lock.Unlock()
err := m.parseCommand(frame)
if err != nil {
m.currentResponse = m.buildErrorResponse(err.Error())
m.logCommandResponse()
return nil
}
err = m.handleCommand()
if err != nil {
m.logger.WithError(err).Error("command handling failed")
return err
}
m.logCommandResponse()
m.currentCommand = nil
return nil
}
func (m *MsgPackHandler) logCommandResponse() {
m.logger.WithField("command", m.currentCommand.Announce).Info("handled command")
m.logger.WithField(
"command",
m.currentCommand,
).WithField(
"response",
m.currentResponse,
).Debug("command and response")
}
func (m *MsgPackHandler) ResponseAnnounce() ([]byte, error) {
m.lock.Lock()
defer m.lock.Unlock()
announceData, err := msgpack.Marshal(m.currentResponse.Announce)
if err != nil {
return nil, fmt.Errorf("could not marshal response announcement: %w", err)
}
m.logger.WithField("announcement", m.currentResponse.Announce).Debug("write response announcement")
return announceData, nil
}
func (m *MsgPackHandler) ResponseData() ([]byte, error) {
m.lock.Lock()
defer m.lock.Unlock()
responseData, err := msgpack.Marshal(m.currentResponse.Response)
if err != nil {
return nil, fmt.Errorf("could not marshal response: %w", err)
}
m.logger.WithField("response", m.currentResponse.Response).Debug("write response")
return responseData, nil
}
func (m *MsgPackHandler) parseHealthCommand(frame []byte) error {
var command messages.HealthCommand
if err := msgpack.Unmarshal(frame, &command); err != nil {
m.logger.WithError(err).Error("unmarshal failed")
return errors.New("could not unmarshal health command")
}
m.currentCommand.Command = &command
return nil
}
func (m *MsgPackHandler) parseFetchCRLCommand(frame []byte) error {
var command messages.FetchCRLCommand
if err := msgpack.Unmarshal(frame, &command); err != nil {
m.logger.WithError(err).Error("unmarshal failed")
return errors.New("could not unmarshal fetch crl command")
}
m.currentCommand.Command = &command
return nil
}
func (m *MsgPackHandler) currentID() string {
return m.currentCommand.Announce.ID
}
func (m *MsgPackHandler) handleCommand() error {
var (
err error
responseData interface{}
responseCode messages.ResponseCode
)
switch m.currentCommand.Command.(type) {
case *messages.HealthCommand:
response, err := m.handleHealthCommand()
if err != nil {
return err
}
responseCode, responseData = messages.RespHealth, response
case *messages.FetchCRLCommand:
response, err := m.handleFetchCRLCommand()
if err != nil {
return err
}
responseCode, responseData = messages.RespFetchCRL, response
default:
return fmt.Errorf("unhandled command %s", m.currentCommand.Announce)
}
if err != nil {
return fmt.Errorf("error from command handler: %w", err)
}
m.currentResponse = &Response{
Announce: messages.BuildResponseAnnounce(responseCode, m.currentID()),
Response: responseData,
}
return nil
}
func (m *MsgPackHandler) buildErrorResponse(errMsg string) *Response {
return &Response{
Announce: messages.BuildResponseAnnounce(messages.RespError, m.currentID()),
Response: &messages.ErrorResponse{Message: errMsg},
}
}
func (m *MsgPackHandler) parseCommand(frame []byte) error {
switch m.currentCommand.Announce.Code {
case messages.CmdHealth:
return m.parseHealthCommand(frame)
case messages.CmdFetchCRL:
return m.parseFetchCRLCommand(frame)
default:
return fmt.Errorf("unhandled command code %s", m.currentCommand.Announce.Code)
}
}
func (m *MsgPackHandler) handleHealthCommand() (*messages.HealthResponse, error) {
res, err := m.healthHandler.CheckHealth()
if err != nil {
return nil, fmt.Errorf("could not check health: %w", err)
}
response := &messages.HealthResponse{
Version: res.Version,
Healthy: res.Healthy,
}
for _, info := range res.Info {
response.Info = append(response.Info, &messages.HealthInfo{
Source: info.Source,
Healthy: info.Healthy,
MoreInfo: info.MoreInfo,
})
}
return response, nil
}
func (m *MsgPackHandler) handleFetchCRLCommand() (*messages.FetchCRLResponse, error) {
fetchCRLPayload, ok := m.currentCommand.Command.(*messages.FetchCRLCommand)
if !ok {
return nil, fmt.Errorf("could not use payload as FetchCRLPayload")
}
res, err := m.fetchCRLHandler.FetchCRL(fetchCRLPayload.IssuerID)
if err != nil {
return nil, fmt.Errorf("could not fetch CRL: %w", err)
}
response := &messages.FetchCRLResponse{
IsDelta: false,
CRLNumber: res.Number,
CRLData: res.CRLData,
}
return response, nil
}
func New(logger *logrus.Logger, handlers ...RegisterHandler) (Handler, error) {
messages.RegisterGeneratedResolver()
h := &MsgPackHandler{
logger: logger,
}
for _, reg := range handlers {
reg(h)
}
return h, nil
}
type RegisterHandler func(handler *MsgPackHandler)
func RegisterHealthHandler(healthHandler *health.Handler) func(*MsgPackHandler) {
return func(h *MsgPackHandler) {
h.healthHandler = healthHandler
}
}
func RegisterFetchCRLHandler(fetchCRLHandler *revoking.FetchCRLHandler) func(handler *MsgPackHandler) {
return func(h *MsgPackHandler) {
h.fetchCRLHandler = fetchCRLHandler
}
}

View file

@ -19,250 +19,39 @@ limitations under the License.
package protocol
import (
"errors"
"fmt"
"sync"
"github.com/shamaton/msgpackgen/msgpack"
"github.com/sirupsen/logrus"
"git.cacert.org/cacert-gosigner/pkg/x509/revoking"
"git.cacert.org/cacert-gosigner/pkg/health"
"git.cacert.org/cacert-gosigner/pkg/messages"
)
const CobsDelimiter = 0x00
type Command struct {
Announce *messages.CommandAnnounce
Command interface{}
}
func (c *Command) String() string {
return fmt.Sprintf("Cmd[announce={%s}, data={%s}]", c.Announce, c.Command)
}
type Response struct {
Announce *messages.ResponseAnnounce
Response interface{}
}
func (r *Response) String() string {
return fmt.Sprintf("Response[Code=%s] created=%s data=%s", r.Announce.Code, r.Announce.Created, r.Response)
return fmt.Sprintf("Rsp[announce={%s}, data={%s}]", r.Announce, r.Response)
}
// Handler is responsible for parsing incoming frames and calling commands
type Handler interface {
// HandleCommandAnnounce handles the initial announcement of a command.
HandleCommandAnnounce([]byte) error
// HandleCommand handles the command data.
HandleCommand([]byte) error
// ResponseAnnounce generates the announcement for a response.
ResponseAnnounce() ([]byte, error)
// ResponseData generates the response data.
ResponseData() ([]byte, error)
}
type MsgPackHandler struct {
logger *logrus.Logger
healthHandler *health.Handler
fetchCRLHandler *revoking.FetchCRLHandler
currentCommand *Command
currentResponse *Response
lock sync.Mutex
}
func (m *MsgPackHandler) HandleCommandAnnounce(frame []byte) error {
m.lock.Lock()
defer m.lock.Unlock()
var ann messages.CommandAnnounce
if err := msgpack.Unmarshal(frame, &ann); err != nil {
return fmt.Errorf("could not unmarshal command announcement: %w", err)
}
m.logger.WithField("announcement", &ann).Info("received command announcement")
m.currentCommand = &Command{Announce: &ann}
return nil
}
func (m *MsgPackHandler) HandleCommand(frame []byte) error {
m.lock.Lock()
defer m.lock.Unlock()
var clientError error
switch m.currentCommand.Announce.Code {
case messages.CmdHealth:
// health has no payload, ignore the frame
response, err := m.handleCommand()
if err != nil {
m.logger.WithError(err).Error("health handling failed")
clientError = errors.New("could not handle request")
break
}
m.currentResponse = response
case messages.CmdFetchCRL:
var command messages.FetchCRLCommand
if err := msgpack.Unmarshal(frame, &command); err != nil {
m.logger.WithError(err).Error("unmarshal failed")
clientError = errors.New("could not unmarshal fetch crl command")
break
}
m.currentCommand.Command = command
response, err := m.handleCommand()
if err != nil {
m.logger.WithError(err).Error("fetch CRL handling failed")
clientError = errors.New("could not handle request")
break
}
m.currentResponse = response
}
if clientError != nil {
m.currentResponse = buildErrorResponse(clientError.Error())
}
m.logger.WithField(
"command",
m.currentCommand,
).WithField(
"response",
m.currentResponse,
).Info("handled command")
m.currentCommand = nil
return nil
}
func (m *MsgPackHandler) ResponseAnnounce() ([]byte, error) {
m.lock.Lock()
defer m.lock.Unlock()
announceData, err := msgpack.Marshal(m.currentResponse.Announce)
if err != nil {
return nil, fmt.Errorf("could not marshal response announcement: %w", err)
}
m.logger.WithField("announcement", &m.currentResponse.Announce).Info("write response announcement")
return announceData, nil
}
func (m *MsgPackHandler) ResponseData() ([]byte, error) {
m.lock.Lock()
defer m.lock.Unlock()
responseData, err := msgpack.Marshal(m.currentResponse.Response)
if err != nil {
return nil, fmt.Errorf("could not marshal response: %w", err)
}
m.logger.WithField("response", &m.currentResponse.Response).Info("write response")
return responseData, nil
}
func (m *MsgPackHandler) handleCommand() (*Response, error) {
var (
err error
responseData interface{}
responseCode messages.ResponseCode
)
switch m.currentCommand.Announce.Code {
case messages.CmdHealth:
var res *health.Result
res, err = m.healthHandler.CheckHealth()
if err != nil {
return nil, err
}
response := &messages.HealthResponse{
Version: res.Version,
Healthy: res.Healthy,
}
for _, info := range res.Info {
response.Info = append(response.Info, &messages.HealthInfo{
Source: info.Source,
Healthy: info.Healthy,
MoreInfo: info.MoreInfo,
})
}
responseCode, responseData = messages.RespHealth, response
case messages.CmdFetchCRL:
var res *revoking.Result
fetchCRLPayload, ok := m.currentCommand.Command.(messages.FetchCRLCommand)
if !ok {
return nil, fmt.Errorf("could not use payload as FetchCRLPayload")
}
res, err = m.fetchCRLHandler.FetchCRL(fetchCRLPayload.IssuerID)
if err != nil {
return nil, err
}
response := &messages.FetchCRLResponse{
IsDelta: false,
CRLData: res.Data,
}
responseCode, responseData = messages.RespFetchCRL, response
default:
return nil, fmt.Errorf("unhandled command %s", m.currentCommand.Announce)
}
if err != nil {
return nil, fmt.Errorf("error from command handler: %w", err)
}
return &Response{
Announce: messages.BuildResponseAnnounce(responseCode),
Response: responseData,
}, nil
}
func buildErrorResponse(errMsg string) *Response {
return &Response{
Announce: messages.BuildResponseAnnounce(messages.RespError),
Response: &messages.ErrorResponse{Message: errMsg},
}
}
func New(logger *logrus.Logger, handlers ...RegisterHandler) (Handler, error) {
messages.RegisterGeneratedResolver()
h := &MsgPackHandler{
logger: logger,
}
for _, reg := range handlers {
reg(h)
}
return h, nil
}
type RegisterHandler func(handler *MsgPackHandler)
func RegisterHealthHandler(healthHandler *health.Handler) func(*MsgPackHandler) {
return func(h *MsgPackHandler) {
h.healthHandler = healthHandler
}
}
func RegisterFetchCRLHandler(fetchCRLHandler *revoking.FetchCRLHandler) func(handler *MsgPackHandler) {
return func(h *MsgPackHandler) {
h.fetchCRLHandler = fetchCRLHandler
}
}

View file

@ -95,7 +95,7 @@ func (h *Handler) Close() error {
return nil
}
var cobsConfig = cobs.Config{SpecialByte: 0x00, Delimiter: true, EndingSave: true}
var cobsConfig = cobs.Config{SpecialByte: protocol.CobsDelimiter, Delimiter: true, EndingSave: true}
func (h *Handler) Run(ctx context.Context) error {
h.protocolState = cmdAnnounce
@ -133,7 +133,9 @@ func (h *Handler) readFrames() error {
)
var frame []byte
buffer := &bytes.Buffer{}
delimiter := []byte{cobsConfig.SpecialByte}
for {
@ -212,6 +214,9 @@ func (h *Handler) nextState() error {
func (h *Handler) handleProtocolState() error {
h.logger.Tracef("handling protocol state %s", h.protocolState)
h.lock.Lock()
defer h.lock.Unlock()
switch h.protocolState {
case cmdAnnounce:
if err := h.handleCmdAnnounce(); err != nil {
@ -237,9 +242,6 @@ func (h *Handler) handleProtocolState() error {
}
func (h *Handler) writeToPort(data []byte) error {
h.lock.Lock()
defer h.lock.Unlock()
reader := bytes.NewReader(data)
n, err := io.Copy(h.port, reader)
@ -249,10 +251,6 @@ func (h *Handler) writeToPort(data []byte) error {
h.logger.Tracef("wrote %d bytes", n)
if err := h.port.Flush(); err != nil {
return fmt.Errorf("could not flush data: %w", err)
}
return nil
}
@ -272,19 +270,17 @@ func (h *Handler) readFromPort() ([]byte, error) {
func (h *Handler) handleCmdAnnounce() error {
h.logger.Trace("waiting for command announce")
select {
case frame := <-h.framesIn:
if frame == nil {
return nil
}
frame := <-h.framesIn
if frame == nil {
return nil
}
if err := h.protocolHandler.HandleCommandAnnounce(frame); err != nil {
return fmt.Errorf("command announce handling failed: %w", err)
}
if err := h.protocolHandler.HandleCommandAnnounce(frame); err != nil {
return fmt.Errorf("command announce handling failed: %w", err)
}
if err := h.nextState(); err != nil {
return err
}
if err := h.nextState(); err != nil {
return err
}
return nil
@ -293,20 +289,18 @@ func (h *Handler) handleCmdAnnounce() error {
func (h *Handler) handleCmdData() error {
h.logger.Trace("waiting for command data")
select {
case frame := <-h.framesIn:
frame := <-h.framesIn
if frame == nil {
return nil
}
if frame == nil {
return nil
}
if err := h.protocolHandler.HandleCommand(frame); err != nil {
return fmt.Errorf("command handler failed: %w", err)
}
if err := h.protocolHandler.HandleCommand(frame); err != nil {
return fmt.Errorf("command handler failed: %w", err)
}
if err := h.nextState(); err != nil {
return err
}
if err := h.nextState(); err != nil {
return err
}
return nil
@ -318,8 +312,6 @@ func (h *Handler) handleRespAnnounce() error {
return fmt.Errorf("could not get response announcement: %w", err)
}
h.logger.Trace("writing response announce")
if err := h.writeFrame(frame); err != nil {
return err
}
@ -337,8 +329,6 @@ func (h *Handler) handleRespData() error {
return fmt.Errorf("could not get response data: %w", err)
}
h.logger.Trace("writing response data")
if err := h.writeFrame(frame); err != nil {
return err
}
@ -354,7 +344,7 @@ func New(cfg *config.Serial, logger *logrus.Logger, protocolHandler protocol.Han
h := &Handler{
protocolHandler: protocolHandler,
logger: logger,
framesIn: make(chan []byte, 0),
framesIn: make(chan []byte),
}
h.config = &serial.Config{Name: cfg.Device, Baud: cfg.Baud, ReadTimeout: cfg.Timeout}

View file

@ -108,7 +108,8 @@ func NewRevokeCertificate(serialNumber *big.Int, reason CRLReason) *RevokeCertif
}
type CRLInformation struct {
CRL []byte // DER encoded CRL
CRL []byte // DER encoded CRL
Number *big.Int
}
func (r *X509Revoking) Revoke(revokeCertificate *RevokeCertificate) (*pkix.RevokedCertificate, error) {
@ -146,7 +147,7 @@ func (r *X509Revoking) CreateCRL() (*CRLInformation, error) {
return nil, fmt.Errorf("could not sign revocation list: %w", err)
}
return &CRLInformation{CRL: list}, nil
return &CRLInformation{CRL: list, Number: nextNumber}, nil
}
func NewX509Revoking(
@ -159,7 +160,8 @@ func NewX509Revoking(
}
type Result struct {
Data []byte
CRLData []byte
Number *big.Int
}
type FetchCRLHandler struct {
@ -181,5 +183,5 @@ func (h *FetchCRLHandler) FetchCRL(issuerID string) (*Result, error) {
return nil, fmt.Errorf("could not create CRL for issuer ID %s: %w", issuerID, err)
}
return &Result{Data: currentCRL.CRL}, nil
return &Result{CRLData: currentCRL.CRL, Number: currentCRL.Number}, nil
}