Initial signer rewrite in Go

This commit is contained in:
Jan Dittberner 2018-10-31 11:17:51 +01:00
commit a89275a8e4
9 changed files with 557 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
vendor/
.idea/

15
Gopkg.lock generated Normal file
View file

@ -0,0 +1,15 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/goburrow/serial"
packages = ["."]
revision = "5efbe925ecf714f8ba147bf2226f2e7afc7111bc"
version = "v0.1.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "efa8bdd22b6fa2a5e6f86ba25724a05fca0ebabf4d436332aa90ded96fafa94d"
solver-name = "gps-cdcl"
solver-version = 1

34
Gopkg.toml Normal file
View file

@ -0,0 +1,34 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/goburrow/serial"
version = "0.1.0"
[prune]
go-tests = true
unused-packages = true

106
client/main.go Normal file
View file

@ -0,0 +1,106 @@
package main
import (
"flag"
"git.cacert.org/cacert-gosigner/datastructures"
"git.cacert.org/cacert-gosigner/shared"
"log"
"time"
"github.com/goburrow/serial"
)
var (
address string
baudrate int
databits int
stopbits int
parity string
message string
)
func main() {
flag.StringVar(&address, "a", "/dev/ttyUSB0", "address")
flag.IntVar(&baudrate, "b", 115200, "baud rate")
flag.IntVar(&databits, "d", 8, "data bits")
flag.IntVar(&stopbits, "s", 1, "stop bits")
flag.StringVar(&parity, "p", "N", "parity (N/E/O)")
flag.StringVar(&message, "m", "serial", "message")
flag.Parse()
config := serial.Config{
Address: address,
BaudRate: baudrate,
DataBits: databits,
StopBits: stopbits,
Parity: parity,
Timeout: 30 * time.Second,
}
log.Printf("connecting %+v", config)
port, err := serial.Open(&config)
if err != nil {
log.Fatal(err)
}
log.Println("connected")
defer func() {
err := port.Close()
if err != nil {
log.Fatal(err)
}
log.Println("closed")
}()
if _, err = port.Write([]byte{0x02}); err != nil {
log.Fatal(err)
}
resp := make([]byte, 1)
if _, err = port.Read(resp); err != nil {
log.Fatal(err)
}
request := datastructures.NewNulRequest()
timeout := time.After(2700 * time.Millisecond)
errorChannel := make(chan error, 1)
responseChannel := make(chan *datastructures.SignerResponse, 1)
go func() {
if response, err := SendRequest(&port, request); err != nil {
errorChannel <- err
} else {
responseChannel <- response
}
}()
select {
case <-timeout:
log.Fatal("timeout")
case err := <-errorChannel:
log.Fatal(err)
case response := <- responseChannel:
if err := Process(response); err != nil {
log.Fatal(err)
}
}
requestBytes := request.Serialize()
if _, err = port.Write(requestBytes); err != nil {
log.Fatal(err)
}
if _, err = port.Write([]byte{datastructures.CalculateXorCheckSum([][]byte{requestBytes})}); err != nil {
log.Fatal(err)
}
if _, err = port.Write([]byte(shared.MagicTrailer)); err != nil {
log.Fatal(err)
}
}
func Process(response *datastructures.SignerResponse) error {
return nil
}
func SendRequest(port *serial.Port, request *datastructures.SignerRequest) (*datastructures.SignerResponse, error) {
return nil, nil
}

28
datastructures/common.go Normal file
View file

@ -0,0 +1,28 @@
package datastructures
import "encoding/binary"
type Action uint8
const ActionNul = Action(0)
func encode24BitLength(data []byte) []byte {
lengthBytes := make([]byte, 4)
binary.BigEndian.PutUint32(lengthBytes, uint32(len(data)))
return lengthBytes[1:]
}
// calculate length from 24 bits of data in network byte order
func decode24BitLength(bytes []byte) int {
return int(binary.BigEndian.Uint32([]byte{0x0, bytes[0], bytes[1], bytes[2]}))
}
func CalculateXorCheckSum(byteBlocks [][]byte) byte {
var result byte = 0x0
for _, byteBlock := range byteBlocks {
for _, b := range byteBlock {
result ^= b
}
}
return result
}

View file

@ -0,0 +1,80 @@
package datastructures
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"time"
)
type SignerRequest struct {
Version uint8
Action Action
System uint8
Root uint8
Configuration uint8
Parameter1 uint8
Parameter2 uint16
Parameter3 uint8
Content1 string
Content2 string
Content3 string
}
func SignerRequestFromData(lengthBytes []byte, blockData []byte, checkSum byte) (*SignerRequest, error) {
headerLength := decode24BitLength(blockData[0:3])
headerBytes := blockData[3 : 3+headerLength]
contentBytes := blockData[3+headerLength:]
content1Length := decode24BitLength(contentBytes[0:3])
content1 := string(contentBytes[3 : 3+content1Length])
content2Offset := 3 + content1Length
content2Length := decode24BitLength(contentBytes[content2Offset : content2Offset+3])
content2 := string(contentBytes[3+content2Offset : 3+content2Offset+content2Length])
content3Offset := 3 + content2Offset + content2Length
content3Length := decode24BitLength(contentBytes[content3Offset : content3Offset+3])
content3 := string(contentBytes[3+content3Offset : 3+content3Offset+content3Length])
calculated := CalculateXorCheckSum([][]byte{lengthBytes, blockData})
if checkSum != calculated {
return nil, errors.New(fmt.Sprintf("invalid checksum expected 0x%x got 0x%x", calculated, checkSum))
}
return &SignerRequest{
Version: headerBytes[0],
Action: Action(headerBytes[1]),
System: headerBytes[2],
Root: headerBytes[3],
Configuration: headerBytes[4],
Parameter1: headerBytes[5],
Parameter2: binary.BigEndian.Uint16([]byte{headerBytes[6], headerBytes[7]}),
Parameter3: headerBytes[8],
Content1: content1,
Content2: content2,
Content3: content3,
}, nil
}
func (r SignerRequest) Serialize() []byte {
parameter2Bytes := make([]byte, 2)
binary.BigEndian.PutUint16(parameter2Bytes, r.Parameter2)
headerBytes := bytes.Join([][]byte{
{r.Version, byte(r.Action), r.System, r.Root, r.Configuration, r.Parameter1},
parameter2Bytes, {r.Parameter3}}, []byte{})
content1Bytes := []byte(r.Content1)
content2Bytes := []byte(r.Content2)
content3Bytes := []byte(r.Content3)
blockBytes := bytes.Join([][]byte{
encode24BitLength(headerBytes), headerBytes,
encode24BitLength(content1Bytes), content1Bytes,
encode24BitLength(content2Bytes), content2Bytes,
encode24BitLength(content3Bytes), content3Bytes,
}, []byte{})
return bytes.Join([][]byte{encode24BitLength(blockBytes), blockBytes}, []byte{})
}
func NewNulRequest() *SignerRequest {
return &SignerRequest{Version: 1, Action: ActionNul, Content1: time.Now().UTC().Format("010203042006.05")}
}

View file

@ -0,0 +1,63 @@
package datastructures
import (
"bytes"
"errors"
"fmt"
)
type SignerResponse struct {
Version uint8
Action Action
Reserved1 uint8
Reserved2 uint8
Content1 string
Content2 string
Content3 string
}
func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte) (*SignerResponse, error) {
headerLength := decode24BitLength(lengthBytes)
headerBytes := blockData[3 : 3+headerLength]
contentBytes := blockData[3+headerLength:]
content1Length := decode24BitLength(contentBytes[0:3])
content1 := string(contentBytes[3 : 3+content1Length])
content2Offset := 3 + content1Length
content2Length := decode24BitLength(contentBytes[content2Offset : content2Offset+3])
content2 := string(contentBytes[3+content2Offset : 3+content2Offset+content2Length])
content3Offset := 3 + content2Offset + content2Length
content3Length := decode24BitLength(contentBytes[content3Offset : content3Offset+3])
content3 := string(contentBytes[3+content3Offset : 3+content3Offset+content3Length])
calculated := CalculateXorCheckSum([][]byte{lengthBytes, blockData})
if checkSum != calculated {
return nil, errors.New(fmt.Sprintf("invalid checksum expected 0x%x got 0x%x", calculated, checkSum))
}
return &SignerResponse{
Version: headerBytes[0],
Action: Action(headerBytes[1]),
Reserved1: headerBytes[2],
Reserved2: headerBytes[3],
Content1: content1,
Content2: content2,
Content3: content3,
}, nil
}
func (r SignerResponse) Serialize() []byte {
headerBytes := []byte{r.Version, byte(r.Action), r.Reserved1, r.Reserved2}
content1Bytes := []byte(r.Content1)
content2Bytes := []byte(r.Content2)
content3Bytes := []byte(r.Content3)
blockBytes := bytes.Join([][]byte{
encode24BitLength(headerBytes), headerBytes,
encode24BitLength(content1Bytes), content1Bytes,
encode24BitLength(content2Bytes), content2Bytes,
encode24BitLength(content3Bytes), content3Bytes,
}, []byte{})
return bytes.Join([][]byte{encode24BitLength(blockBytes), blockBytes}, []byte{})
}

4
shared/shared.go Normal file
View file

@ -0,0 +1,4 @@
package shared
const MagicTrailer = "rie4Ech7"

225
signer/main.go Normal file
View file

@ -0,0 +1,225 @@
package main
import (
"encoding/binary"
"errors"
"flag"
"fmt"
"git.cacert.org/cacert-gosigner/datastructures"
"git.cacert.org/cacert-gosigner/shared"
"io"
"log"
"time"
"github.com/goburrow/serial"
)
var (
address string
baudrate int
databits int
stopbits int
parity string
)
func main() {
flag.StringVar(&address, "a", "/dev/ttyUSB0", "address")
flag.IntVar(&baudrate, "b", 115200, "baud rate")
flag.IntVar(&databits, "d", 8, "data bits")
flag.IntVar(&stopbits, "s", 1, "stop bits")
flag.StringVar(&parity, "p", "N", "parity (N/E/O)")
flag.Parse()
config := serial.Config{
Address: address,
BaudRate: baudrate,
DataBits: databits,
StopBits: stopbits,
Parity: parity,
Timeout: 5 * time.Minute,
}
log.Printf("connecting %+v", config)
port, err := serial.Open(&config)
if err != nil {
log.Fatal(err)
}
log.Println("connected")
count := 0
defer func() {
err := port.Close()
if err != nil {
log.Fatal(err)
}
log.Println("closed")
}()
readLoop:
for {
// timeout if no command has been received within 15 seconds
timeout := time.After(15 * time.Second)
commandChan := make(chan datastructures.SignerRequest, 1)
errChan := make(chan error, 1)
go Receive(&port, &commandChan, &errChan)
select {
case command := <-commandChan:
response, err := Process(command)
if err != nil {
log.Printf("ERROR %v\n", err)
} else {
SendResponse(&port, response)
}
case <-timeout:
log.Println("timeout in main loop")
break readLoop
case err := <-errChan:
log.Printf("ERROR %v\n", err)
}
count++
log.Printf("INFO %d requests processed. Waiting for next request ...\n", count)
}
}
// Send a response to the client
func SendResponse(port *serial.Port, response *datastructures.SignerResponse) error {
if _, err := (*port).Write([]byte{0x02}); err != nil {
return err
}
if ack, err := receiveBytes(port, 1, 5); err != nil {
return err
} else if ack[0] != 0x10 {
return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0]))
}
tryAgain := true
for ; tryAgain; {
data := response.Serialize()
if _, err := (*port).Write(data); err != nil {
return err
}
checksum := datastructures.CalculateXorCheckSum([][]byte{data})
if _, err := (*port).Write([]byte{checksum}); err != nil {
return err
}
if _, err := (*port).Write([]byte(shared.MagicTrailer)); err != nil {
return err
}
if ack, err := receiveBytes(port, 1, 5); err != nil {
return err
} else if ack[0] == 0x10 {
tryAgain = false
} else if ack[0] != 0x11 {
return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0]))
}
}
return nil
}
// Process the signer request
func Process(command datastructures.SignerRequest) (response *datastructures.SignerResponse, err error) {
log.Printf("INFO analyze %+v\n", command)
switch command.Action {
case datastructures.ActionNul:
response, err = handleNulAction(command)
return
default:
return nil, errors.New(fmt.Sprintf("unsupported Action 0x%02x", command.Action))
}
}
func handleNulAction(command datastructures.SignerRequest) (*datastructures.SignerResponse, error) {
// todo: generate script to set system time from command time data in command.content1
return &datastructures.SignerResponse{
Version: command.Version, Action: command.Action,
Content1: "", Content2: "", Content3: ""}, nil
}
// Receive a request and generate a request data structure
func Receive(port *serial.Port, commandChan *chan datastructures.SignerRequest, errorChan *chan error) {
header, err := receiveBytes(port, 1, 20)
if err != nil {
*errorChan <- err
return
}
if header[0] != 0x02 {
*errorChan <- errors.New(fmt.Sprintf("unexpected byte 0x%x expected 0x02", header))
}
if _, err := (*port).Write([]byte{0x10}); err != nil {
*errorChan <- errors.New("could not write ACK")
return
}
lengthBytes, err := receiveBytes(port, 3, 2)
if err != nil {
*errorChan <- err
return
}
blockLength := binary.BigEndian.Uint32([]byte{0x0, lengthBytes[0], lengthBytes[1], lengthBytes[2]})
blockData, err := receiveBytes(port, int(blockLength), 5)
if err != nil {
*errorChan <- err
return
}
checkSum, err := receiveBytes(port, 1, 2)
if err != nil {
*errorChan <- err
return
}
command, err := datastructures.SignerRequestFromData(lengthBytes, blockData, checkSum[0])
if err != nil {
*errorChan <- err
}
trailer, err := receiveBytes(port, len(shared.MagicTrailer), 2)
if err != nil {
*errorChan <- err
return
}
if string(trailer) != shared.MagicTrailer {
*errorChan <- errors.New("expected trailer bytes not found")
return
}
if _, err := (*port).Write([]byte{0x10}); err != nil {
*errorChan <- err
return
}
*commandChan <- *command
}
// receive the requested number of bytes from serial port and stop after the given timeout in seconds
func receiveBytes(port *serial.Port, count int, timeout time.Duration) ([]byte, error) {
timeoutCh := time.After(timeout * time.Second)
readCh := make(chan []byte, 1)
errCh := make(chan error, 1)
go func() {
data := make([]byte, count)
if _, err := io.ReadAtLeast(*port, data, count); err != nil {
errCh <- err
} else {
readCh <- data
}
}()
select {
case <-timeoutCh:
return nil, errors.New("timeout")
case err := <-errCh:
return nil, err
case data := <-readCh:
return data, nil
}
}