Implement serial link and protocol handling infrastructure

This commit adds basic serial link and protocol support. None of the commands
from the docs/design.md document is implemented yet.

The following new packages have been added:

- seriallink containing the serial link handler including COBS decoding and
  encoding
- protocol containing the protocol handler including msgpack unmarshalling
  and marshaling
- health containing a rudimentary health check implementation
- messages containing command and response types and generated msgpack
  marshaling code

A client simulation command has been added in cmd/clientsim.

README.md got instructions how to run the client simulator. The
docs/config.sample.yaml contains a new section for the serial connection
parameters.
This commit is contained in:
Jan Dittberner 2022-08-03 14:38:36 +02:00 committed by Jan Dittberner
parent c2b987fd31
commit 3107ad8abb
16 changed files with 1058 additions and 7 deletions

3
.gitignore vendored
View file

@ -2,6 +2,9 @@
*.pem
*.pub
.idea/
/clientsim
/signer
/testPty
ca-hierarchy.json
config.yaml
dist/

View file

@ -2,7 +2,7 @@
## Setup HSM keys and certificates
```
```shell
sudo apt install softhsm2
umask 077
mkdir -p ~/.config/softhsm2/tokens
@ -17,7 +17,24 @@ go run ./cmd/signer -setup
## Run the signer
```
```shell
export PKCS11_PIN_LOCALHSM=123456
go run ./cmd/signer
```
## Run the client simulator with socat
You may run the client simulator that sends commands via `stdout` and reads responses on `stdin` via `socat` to
simulate traffic on an emulated serial device:
```shell
sudo apt install socat
```
```shell
go build ./cmd/clientsim
socat -d -d -v pty,rawer,link=$(pwd)/testPty EXEC:./clientsim,pty,rawer
```
You will need to configure `$(pwd)/testPty` as `serial`/`device` in your `config.yaml` to let the signer command find
the emulated serial device.

144
cmd/clientsim/main.go Normal file
View file

@ -0,0 +1,144 @@
/*
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.
*/
// client simulator
package main
import (
"fmt"
"log"
"os"
"sync"
"time"
"github.com/justincpresley/go-cobs"
"github.com/shamaton/msgpackgen/msgpack"
"git.cacert.org/cacert-gosigner/pkg/messages"
)
const cobsDelimiter = 0x00
var cobsConfig = cobs.Config{SpecialByte: cobsDelimiter, Delimiter: true, EndingSave: true}
func main() {
const (
bufferSize = 1024 * 1024
readInterval = 50 * time.Millisecond
)
errors := make(chan error)
errorLog := log.New(os.Stderr, "", log.LstdFlags)
wg := sync.WaitGroup{}
wg.Add(1)
done := make(chan struct{})
frame := make(chan []byte)
go func(done chan struct{}) {
buf := make([]byte, bufferSize)
for {
select {
case <-done:
wg.Done()
return
default:
count, err := os.Stdin.Read(buf)
if err != nil {
errors <- err
wg.Done()
return
}
if count == 0 {
time.Sleep(readInterval)
continue
}
data := buf[:count]
err = cobs.Verify(data, cobsConfig)
if err != nil {
errors <- err
wg.Done()
return
}
frame <- cobs.Decode(data, cobsConfig)
}
}
}(done)
err := writeTestCommands(frame, errorLog)
if err != nil {
errorLog.Printf("could not write test commands")
}
err = <-errors
if err != nil {
errorLog.Printf("error: %v", err)
}
wg.Wait()
}
func writeTestCommands(responses chan []byte, errorLog *log.Logger) error {
messages.RegisterGeneratedResolver()
commands := []messages.Command{
{
Code: messages.CmdHealth,
TimeStamp: time.Now().UTC(),
},
}
for _, command := range commands {
commandBytes, err := msgpack.Marshal(command)
if err != nil {
return fmt.Errorf("could not marshal command bytes: %w", err)
}
_, err = os.Stdout.Write(cobs.Encode(commandBytes, cobsConfig))
if err != nil {
return fmt.Errorf("write failed: %w", err)
}
responseBytes := <-responses
var response messages.Response
err = msgpack.Unmarshal(responseBytes, &response)
if err != nil {
return fmt.Errorf("could not unmarshal msgpack data: %w", err)
}
errorLog.Printf("received response: %+v", response)
}
return nil
}

View file

@ -23,7 +23,10 @@ import (
"os"
"git.cacert.org/cacert-gosigner/pkg/config"
"git.cacert.org/cacert-gosigner/pkg/health"
"git.cacert.org/cacert-gosigner/pkg/hsm"
"git.cacert.org/cacert-gosigner/pkg/protocol"
"git.cacert.org/cacert-gosigner/pkg/seriallink"
)
var (
@ -97,5 +100,23 @@ func main() {
return
}
healthHandler := health.New(version)
proto, err := protocol.New(infoLog, errorLog, protocol.RegisterHealthHandler(healthHandler))
if err != nil {
errorLog.Fatalf("could not setup protocol handler: %v", err)
}
serialHandler, err := seriallink.New(caConfig.GetSerial(), proto)
if err != nil {
errorLog.Fatalf("could not setup serial link handler: %v", err)
}
defer func() { _ = serialHandler.Close() }()
if err = serialHandler.Run(); err != nil {
errorLog.Fatalf("error in serial handler: %v", err)
}
infoLog.Print("setup complete, starting signer operation")
}

View file

@ -24,6 +24,12 @@ Settings:
ocsp: "http://ocsp.cacert.org/"
crl: "http://crl.cacert.org/%s.crl"
issuer: "http://www.cacert.org/certs/%s.crt"
# Settings for the serial link
# baud and timeout-millis are optional
serial:
device: "/dev/ttyUSB0"
baud: 115200
timeout-millis: 5000
# KeyStorage defines PKCS#11 tokens, a token named 'default' must be present
KeyStorage:

8
go.mod
View file

@ -4,9 +4,12 @@ go 1.17
require (
github.com/ThalesIgnite/crypto11 v1.2.5
github.com/stretchr/testify v1.7.1
github.com/justincpresley/go-cobs v1.2.0
github.com/shamaton/msgpackgen v0.3.0
github.com/stretchr/testify v1.8.0
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gopkg.in/yaml.v3 v3.0.1
)
require (
@ -14,6 +17,7 @@ require (
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shamaton/msgpack/v2 v2.1.0 // indirect
github.com/thales-e-security/pool v0.0.2 // indirect
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
)

17
go.sum
View file

@ -1,8 +1,11 @@
github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E=
github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE=
github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA=
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/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=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
@ -11,10 +14,18 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shamaton/msgpack/v2 v2.1.0 h1:9jJ2eGZw2Wa9KExPX3KaDDckVjgr4zhXGFCfWagUWqg=
github.com/shamaton/msgpack/v2 v2.1.0/go.mod h1:aTUEmh31ziGX1Ml7wMPLVY0f4vT3CRsCvZRoSCs+VGg=
github.com/shamaton/msgpackgen v0.3.0 h1:q6o7prOEJFdF9BAPgkOtfzJbs55pQi7g44RUnEVUxtM=
github.com/shamaton/msgpackgen v0.3.0/go.mod h1:fd99fDDuxuTiWzkHC59uEGzrt/WDu+ltGZTbEWwVXIc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
@ -24,5 +35,5 @@ golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuX
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -31,6 +31,12 @@ import (
"gopkg.in/yaml.v3"
)
type Serial struct {
Device string
Baud int
Timeout time.Duration
}
type Settings struct {
Organization *pkix.Name
ValidityYears struct {
@ -39,6 +45,7 @@ type Settings struct {
URLPatterns struct {
Ocsp, CRL, Issuer string
}
Serial *Serial
}
type SettingsError struct {
@ -67,6 +74,11 @@ func (s *Settings) UnmarshalYAML(n *yaml.Node) error {
CRL string `yaml:"crl"`
Issuer string `yaml:"issuer"`
} `yaml:"url-patterns"`
Serial struct {
Device string `yaml:"device"`
Baud int `yaml:"baud"`
TimeoutMillis int `yaml:"timeout-millis"`
} `yaml:"serial"`
}{}
err := n.Decode(&data)
@ -111,6 +123,18 @@ func (s *Settings) UnmarshalYAML(n *yaml.Node) error {
return SettingsError{"url-pattern 'issuer' must contain one '%s' placeholder"}
}
if data.Serial.Device == "" {
return SettingsError{"you must specify a serial 'device'"}
}
if data.Serial.Baud == 0 {
data.Serial.Baud = 115200
}
if data.Serial.TimeoutMillis == 0 {
data.Serial.TimeoutMillis = 5000
}
s.Organization = &pkix.Name{}
s.Organization.Organization = data.Organization.Organization
s.Organization.Country = data.Organization.Country
@ -125,6 +149,12 @@ func (s *Settings) UnmarshalYAML(n *yaml.Node) error {
s.URLPatterns.CRL = data.URLPatterns.CRL
s.URLPatterns.Issuer = data.URLPatterns.Issuer
s.Serial = &Serial{
Device: data.Serial.Device,
Baud: data.Serial.Baud,
Timeout: time.Duration(data.Serial.TimeoutMillis) * time.Millisecond,
}
return nil
}
@ -287,6 +317,10 @@ func (c *SignerConfig) GetKeyStorage(label string) (*KeyStorage, error) {
return keyStorage, nil
}
func (c *SignerConfig) GetSerial() *Serial {
return c.global.Serial
}
// LoadConfiguration reads YAML configuration from the given reader as a SignerConfig structure
func LoadConfiguration(r io.Reader) (*SignerConfig, error) {
config := struct {

View file

@ -421,6 +421,8 @@ Settings:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s
issuer: http://%s.cas.example.org/
serial:
device: /dev/ttyUSB0
KeyStorage:
default:
type: softhsm
@ -476,6 +478,8 @@ Settings:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s.crl
issuer: http://%s.cas.example.org/
serial:
device: /dev/ttyUSB0
CAs:
root:
common-name: "Root CA"
@ -497,6 +501,8 @@ Settings:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s.crl
issuer: http://%s.cas.example.org/
serial:
device: /dev/ttyUSB0
KeyStorage:
default:
label: default
@ -533,6 +539,8 @@ Settings:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s.crl
issuer: http://%s.cas.example.org/
serial:
device: /dev/ttyUSB0
KeyStorage:
default:
type: softhsm
@ -656,6 +664,8 @@ Settings:
ocsp: http://ocsp.example.org/%s
crl: http://crl.example.org/%s.crl
issuer: http://%s.cas.example.org/
serial:
device: /dev/ttyUSB0
KeyStorage:
default:
type: softhsm

27
pkg/health/health.go Normal file
View file

@ -0,0 +1,27 @@
/*
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 health implements Health checks
package health
type Handler struct {
Version string
}
func New(version string) *Handler {
return &Handler{Version: version}
}

View file

@ -183,6 +183,8 @@ Settings:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s.crl
issuer: http://%s.cas.example.org/
serial:
device: /dev/ttyUSB0
CAs:
root:
common-name: "Acme CAs root"

View file

@ -157,6 +157,8 @@ Settings:
ocsp: http://ocsp.example.org/
crl: http://crl.example.org/%s.crl
issuer: http://%s.cas.example.org/
serial:
device: /dev/ttyUSB0
CAs:
root:
common-name: "Acme CAs root"

68
pkg/messages/messages.go Normal file
View file

@ -0,0 +1,68 @@
/*
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.
*/
//go:generate go run github.com/shamaton/msgpackgen
// Package messages contains structure definitions for protocol messages
package messages
import (
"fmt"
"time"
)
type CommandCode int
const (
CmdHealth CommandCode = iota
)
func (c CommandCode) String() string {
switch c {
case CmdHealth:
return "HEALTH"
default:
return fmt.Sprintf("unknown (%d)", int(c))
}
}
type Command struct {
Code CommandCode `msgpack:"code"`
TimeStamp time.Time `msgpack:"created"`
Payload interface{} `msgpack:"payload"` // optional payload
}
type ResponseCode int
const (
RspError ResponseCode = -1
RspHealth ResponseCode = iota
)
type Response struct {
Code ResponseCode `msgpack:"code"`
TimeStamp time.Time `msgpack:"created"`
Payload interface{} `msgpack:"payload"`
}
type HealthResponse struct {
Version string `msgpack:"version"`
}
type ErrorResponse struct {
Message string `msgpack:"message"`
}

View file

@ -0,0 +1,439 @@
// Code generated by msgpackgen. DO NOT EDIT.
package messages
import (
"fmt"
msgpack "github.com/shamaton/msgpackgen/msgpack"
dec "github.com/shamaton/msgpackgen/msgpack/dec"
enc "github.com/shamaton/msgpackgen/msgpack/enc"
)
// RegisterGeneratedResolver registers generated resolver.
func RegisterGeneratedResolver() {
msgpack.SetResolver(___encodeAsMap, ___encodeAsArray, ___decodeAsMap, ___decodeAsArray)
}
// encode
func ___encode(i interface{}) ([]byte, error) {
if msgpack.StructAsArray() {
return ___encodeAsArray(i)
} else {
return ___encodeAsMap(i)
}
}
// encodeAsArray
func ___encodeAsArray(i interface{}) ([]byte, error) {
switch v := i.(type) {
case HealthResponse:
encoder := enc.NewEncoder()
size, err := ___calcArraySizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder)
if err != nil {
return nil, err
}
encoder.MakeBytes(size)
b, offset, err := ___encodeArrayHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0)
if err != nil {
return nil, err
}
if size != offset {
return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthResponse", size, offset)
}
return b, err
case *HealthResponse:
encoder := enc.NewEncoder()
size, err := ___calcArraySizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder)
if err != nil {
return nil, err
}
encoder.MakeBytes(size)
b, offset, err := ___encodeArrayHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0)
if err != nil {
return nil, err
}
if size != offset {
return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthResponse", size, offset)
}
return b, err
case ErrorResponse:
encoder := enc.NewEncoder()
size, err := ___calcArraySizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder)
if err != nil {
return nil, err
}
encoder.MakeBytes(size)
b, offset, err := ___encodeArrayErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0)
if err != nil {
return nil, err
}
if size != offset {
return nil, fmt.Errorf("%s size / offset different %d : %d", "ErrorResponse", size, offset)
}
return b, err
case *ErrorResponse:
encoder := enc.NewEncoder()
size, err := ___calcArraySizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder)
if err != nil {
return nil, err
}
encoder.MakeBytes(size)
b, offset, err := ___encodeArrayErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0)
if err != nil {
return nil, err
}
if size != offset {
return nil, fmt.Errorf("%s size / offset different %d : %d", "ErrorResponse", size, offset)
}
return b, err
}
return nil, nil
}
// encodeAsMap
func ___encodeAsMap(i interface{}) ([]byte, error) {
switch v := i.(type) {
case HealthResponse:
encoder := enc.NewEncoder()
size, err := ___calcMapSizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder)
if err != nil {
return nil, err
}
encoder.MakeBytes(size)
b, offset, err := ___encodeMapHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0)
if err != nil {
return nil, err
}
if size != offset {
return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthResponse", size, offset)
}
return b, err
case *HealthResponse:
encoder := enc.NewEncoder()
size, err := ___calcMapSizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder)
if err != nil {
return nil, err
}
encoder.MakeBytes(size)
b, offset, err := ___encodeMapHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0)
if err != nil {
return nil, err
}
if size != offset {
return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthResponse", size, offset)
}
return b, err
case ErrorResponse:
encoder := enc.NewEncoder()
size, err := ___calcMapSizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder)
if err != nil {
return nil, err
}
encoder.MakeBytes(size)
b, offset, err := ___encodeMapErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0)
if err != nil {
return nil, err
}
if size != offset {
return nil, fmt.Errorf("%s size / offset different %d : %d", "ErrorResponse", size, offset)
}
return b, err
case *ErrorResponse:
encoder := enc.NewEncoder()
size, err := ___calcMapSizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder)
if err != nil {
return nil, err
}
encoder.MakeBytes(size)
b, offset, err := ___encodeMapErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0)
if err != nil {
return nil, err
}
if size != offset {
return nil, fmt.Errorf("%s size / offset different %d : %d", "ErrorResponse", size, offset)
}
return b, err
}
return nil, nil
}
// decode
func ___decode(data []byte, i interface{}) (bool, error) {
if msgpack.StructAsArray() {
return ___decodeAsArray(data, i)
} else {
return ___decodeAsMap(data, i)
}
}
// decodeAsArray
func ___decodeAsArray(data []byte, i interface{}) (bool, error) {
switch v := i.(type) {
case *HealthResponse:
decoder := dec.NewDecoder(data)
offset, err := ___decodeArrayHealthResponse_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 **HealthResponse:
decoder := dec.NewDecoder(data)
offset, err := ___decodeArrayHealthResponse_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)
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)
if err == nil && offset != decoder.Len() {
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
}
return true, err
}
return false, nil
}
// decodeAsMap
func ___decodeAsMap(data []byte, i interface{}) (bool, error) {
switch v := i.(type) {
case *HealthResponse:
decoder := dec.NewDecoder(data)
offset, err := ___decodeMapHealthResponse_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 **HealthResponse:
decoder := dec.NewDecoder(data)
offset, err := ___decodeMapHealthResponse_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)
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)
if err == nil && offset != decoder.Len() {
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
}
return true, err
}
return false, nil
}
// calculate size from git.cacert.org/cacert-gosigner/pkg/messages.HealthResponse
func ___calcArraySizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v HealthResponse, encoder *enc.Encoder) (int, error) {
size := 0
size += encoder.CalcStructHeaderFix(1)
size += encoder.CalcString(v.Version)
return size, nil
}
// calculate size from git.cacert.org/cacert-gosigner/pkg/messages.HealthResponse
func ___calcMapSizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v HealthResponse, encoder *enc.Encoder) (int, error) {
size := 0
size += encoder.CalcStructHeaderFix(1)
size += encoder.CalcStringFix(7)
size += encoder.CalcString(v.Version)
return size, nil
}
// encode from git.cacert.org/cacert-gosigner/pkg/messages.HealthResponse
func ___encodeArrayHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v HealthResponse, encoder *enc.Encoder, offset int) ([]byte, int, error) {
var err error
offset = encoder.WriteStructHeaderFixAsArray(1, offset)
offset = encoder.WriteString(v.Version, offset)
return encoder.EncodedBytes(), offset, err
}
// encode from git.cacert.org/cacert-gosigner/pkg/messages.HealthResponse
func ___encodeMapHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v HealthResponse, encoder *enc.Encoder, offset int) ([]byte, int, error) {
var err error
offset = encoder.WriteStructHeaderFixAsMap(1, offset)
offset = encoder.WriteStringFix("version", 7, offset)
offset = encoder.WriteString(v.Version, offset)
return encoder.EncodedBytes(), offset, err
}
// decode to git.cacert.org/cacert-gosigner/pkg/messages.HealthResponse
func ___decodeArrayHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *HealthResponse, decoder *dec.Decoder, offset int) (int, error) {
offset, err := decoder.CheckStructHeader(1, offset)
if err != nil {
return 0, err
}
{
var vv string
vv, offset, err = decoder.AsString(offset)
if err != nil {
return 0, err
}
v.Version = vv
}
return offset, err
}
// decode to git.cacert.org/cacert-gosigner/pkg/messages.HealthResponse
func ___decodeMapHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *HealthResponse, decoder *dec.Decoder, offset int) (int, error) {
keys := [][]byte{
{uint8(0x76), uint8(0x65), uint8(0x72), uint8(0x73), uint8(0x69), uint8(0x6f), uint8(0x6e)}, // version
}
offset, err := decoder.CheckStructHeader(1, offset)
if err != nil {
return 0, err
}
count := 0
for count < 1 {
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.Version = vv
}
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
size += encoder.CalcStructHeaderFix(1)
size += encoder.CalcString(v.Message)
return size, nil
}
// calculate size from git.cacert.org/cacert-gosigner/pkg/messages.ErrorResponse
func ___calcMapSizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v ErrorResponse, encoder *enc.Encoder) (int, error) {
size := 0
size += encoder.CalcStructHeaderFix(1)
size += encoder.CalcStringFix(7)
size += encoder.CalcString(v.Message)
return size, nil
}
// encode from git.cacert.org/cacert-gosigner/pkg/messages.ErrorResponse
func ___encodeArrayErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v ErrorResponse, encoder *enc.Encoder, offset int) ([]byte, int, error) {
var err error
offset = encoder.WriteStructHeaderFixAsArray(1, offset)
offset = encoder.WriteString(v.Message, offset)
return encoder.EncodedBytes(), offset, err
}
// encode from git.cacert.org/cacert-gosigner/pkg/messages.ErrorResponse
func ___encodeMapErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v ErrorResponse, encoder *enc.Encoder, offset int) ([]byte, int, error) {
var err error
offset = encoder.WriteStructHeaderFixAsMap(1, offset)
offset = encoder.WriteStringFix("message", 7, offset)
offset = encoder.WriteString(v.Message, offset)
return encoder.EncodedBytes(), offset, err
}
// decode to git.cacert.org/cacert-gosigner/pkg/messages.ErrorResponse
func ___decodeArrayErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *ErrorResponse, decoder *dec.Decoder, offset int) (int, error) {
offset, err := decoder.CheckStructHeader(1, offset)
if err != nil {
return 0, err
}
{
var vv string
vv, offset, err = decoder.AsString(offset)
if err != nil {
return 0, err
}
v.Message = vv
}
return offset, err
}
// decode to git.cacert.org/cacert-gosigner/pkg/messages.ErrorResponse
func ___decodeMapErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *ErrorResponse, decoder *dec.Decoder, offset int) (int, error) {
keys := [][]byte{
{uint8(0x6d), uint8(0x65), uint8(0x73), uint8(0x73), uint8(0x61), uint8(0x67), uint8(0x65)}, // message
}
offset, err := decoder.CheckStructHeader(1, offset)
if err != nil {
return 0, err
}
count := 0
for count < 1 {
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.Message = vv
}
count++
default:
return 0, fmt.Errorf("unknown key[%s] found", string(dataKey))
}
}
return offset, err
}

129
pkg/protocol/protocol.go Normal file
View file

@ -0,0 +1,129 @@
/*
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 handles the protocol message marshaling and unmarshalling.
package protocol
import (
"fmt"
"log"
"time"
"github.com/shamaton/msgpackgen/msgpack"
"git.cacert.org/cacert-gosigner/pkg/health"
"git.cacert.org/cacert-gosigner/pkg/messages"
)
// Handler is responsible for parsing incoming frames and calling commands
type Handler interface {
HandleFrame([]byte) ([]byte, error)
}
type MsgPackHandler struct {
infoLog, errorLog *log.Logger
healthHandler *health.Handler
}
func (m *MsgPackHandler) HandleFrame(frame []byte) ([]byte, error) {
var command messages.Command
err := msgpack.Unmarshal(frame, &command)
if err != nil {
m.errorLog.Printf("unmarshal failed: %v", err)
errorResponse, innerErr := buildErrorResponse("do not understand")
if innerErr != nil {
return nil, innerErr
}
return errorResponse, nil
}
m.infoLog.Printf("Received %s command sent at %s", command.Code, command.TimeStamp)
response, err := m.handleCommand(&command)
if err != nil {
m.errorLog.Printf("command failed: %v", err)
errorResponse, innerErr := buildErrorResponse("command failed")
if innerErr != nil {
return nil, innerErr
}
return errorResponse, nil
}
responseData, err := msgpack.Marshal(response)
if err != nil {
return nil, fmt.Errorf("could not marshal response: %w", err)
}
return responseData, nil
}
func (m *MsgPackHandler) handleCommand(command *messages.Command) (*messages.Response, error) {
var (
payload interface{}
responseCode messages.ResponseCode
)
switch command.Code {
case messages.CmdHealth:
responseCode, payload = messages.RspHealth, messages.HealthResponse{Version: m.healthHandler.Version}
default:
return nil, fmt.Errorf("unhandled command %s", command)
}
return &messages.Response{TimeStamp: time.Now().UTC(), Code: responseCode, Payload: payload}, nil
}
func buildErrorResponse(errMsg string) ([]byte, error) {
marshal, err := msgpack.Marshal(&messages.Response{
Code: messages.RspError,
TimeStamp: time.Now().UTC(),
Payload: messages.ErrorResponse{Message: errMsg},
})
if err != nil {
return nil, fmt.Errorf("could not marshal error response: %w", err)
}
return marshal, nil
}
func New(infoLog *log.Logger, errorLog *log.Logger, handlers ...RegisterHandler) (Handler, error) {
messages.RegisterGeneratedResolver()
h := &MsgPackHandler{
infoLog: infoLog,
errorLog: errorLog,
}
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
}
}

View file

@ -0,0 +1,134 @@
/*
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 seriallink provides a handler for the serial connection of the signer machine.
package seriallink
import (
"fmt"
"time"
"github.com/justincpresley/go-cobs"
"github.com/tarm/serial"
"git.cacert.org/cacert-gosigner/pkg/config"
"git.cacert.org/cacert-gosigner/pkg/protocol"
)
type Handler struct {
protocolHandler protocol.Handler
config *serial.Config
port *serial.Port
}
func (h *Handler) setupConnection() error {
s, err := serial.OpenPort(h.config)
if err != nil {
return fmt.Errorf("could not open serial port: %w", err)
}
h.port = s
return nil
}
func (h *Handler) Close() error {
err := h.port.Close()
if err != nil {
return fmt.Errorf("could not close serial port: %w", err)
}
return nil
}
const cobsDelimiter = 0x00
func (h *Handler) Run() error {
const (
bufferSize = 1024 * 1024
readInterval = 50 * time.Millisecond
)
errors := make(chan error)
cobsConfig := cobs.Config{SpecialByte: cobsDelimiter, Delimiter: true, EndingSave: true}
go func() {
buf := make([]byte, bufferSize)
for {
count, err := h.port.Read(buf)
if err != nil {
errors <- err
return
}
if count == 0 {
time.Sleep(readInterval)
continue
}
err = cobs.Verify(buf[:count], cobsConfig)
if err != nil {
errors <- err
return
}
// perform COBS decoding
decoded := cobs.Decode(buf[:count], cobsConfig)
msg, err := h.protocolHandler.HandleFrame(decoded)
if err != nil {
errors <- err
return
}
// perform COBS encoding
encoded := cobs.Encode(msg, cobsConfig)
_, err = h.port.Write(encoded)
if err != nil {
errors <- err
return
}
}
}()
err := <-errors
if err != nil {
return fmt.Errorf("error from handler loop: %w", err)
}
return nil
}
func New(cfg *config.Serial, protocolHandler protocol.Handler) (*Handler, error) {
h := &Handler{protocolHandler: protocolHandler}
h.config = &serial.Config{Name: cfg.Device, Baud: cfg.Baud, ReadTimeout: cfg.Timeout}
err := h.setupConnection()
if err != nil {
return nil, err
}
return h, nil
}