From a89275a8e4eab83e33a78307d4592d9a85e100a8 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Wed, 31 Oct 2018 11:17:51 +0100 Subject: [PATCH] Initial signer rewrite in Go --- .gitignore | 2 + Gopkg.lock | 15 +++ Gopkg.toml | 34 +++++ client/main.go | 106 +++++++++++++++ datastructures/common.go | 28 ++++ datastructures/signerrequest.go | 80 +++++++++++ datastructures/signerresponse.go | 63 +++++++++ shared/shared.go | 4 + signer/main.go | 225 +++++++++++++++++++++++++++++++ 9 files changed, 557 insertions(+) create mode 100644 .gitignore create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml create mode 100644 client/main.go create mode 100644 datastructures/common.go create mode 100644 datastructures/signerrequest.go create mode 100644 datastructures/signerresponse.go create mode 100644 shared/shared.go create mode 100644 signer/main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ef0e14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor/ +.idea/ diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..832a69b --- /dev/null +++ b/Gopkg.lock @@ -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 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..a6f6d56 --- /dev/null +++ b/Gopkg.toml @@ -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 diff --git a/client/main.go b/client/main.go new file mode 100644 index 0000000..cdd0a14 --- /dev/null +++ b/client/main.go @@ -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 +} diff --git a/datastructures/common.go b/datastructures/common.go new file mode 100644 index 0000000..6f29667 --- /dev/null +++ b/datastructures/common.go @@ -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 +} diff --git a/datastructures/signerrequest.go b/datastructures/signerrequest.go new file mode 100644 index 0000000..6566043 --- /dev/null +++ b/datastructures/signerrequest.go @@ -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")} +} diff --git a/datastructures/signerresponse.go b/datastructures/signerresponse.go new file mode 100644 index 0000000..f6c36ba --- /dev/null +++ b/datastructures/signerresponse.go @@ -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{}) +} diff --git a/shared/shared.go b/shared/shared.go new file mode 100644 index 0000000..da345c8 --- /dev/null +++ b/shared/shared.go @@ -0,0 +1,4 @@ +package shared + +const MagicTrailer = "rie4Ech7" + diff --git a/signer/main.go b/signer/main.go new file mode 100644 index 0000000..2274e21 --- /dev/null +++ b/signer/main.go @@ -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 + } +}