Refactor signer code into command and package
This commit is contained in:
parent
3a2578ae55
commit
38566f35ef
13 changed files with 476 additions and 276 deletions
90
signer/command_processor.go
Normal file
90
signer/command_processor.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package signer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"git.cacert.org/cacert-gosigner/datastructures"
|
||||
"git.cacert.org/cacert-gosigner/shared"
|
||||
)
|
||||
|
||||
// The CommandProcessor takes parsed protocol data and executes the actual
|
||||
// functionality.
|
||||
type CommandProcessor struct {
|
||||
}
|
||||
|
||||
// Process the signer request
|
||||
func (p *CommandProcessor) Process(command datastructures.SignerRequest) (
|
||||
response *datastructures.SignerResponse,
|
||||
err error,
|
||||
) {
|
||||
log.Infof("analyze %+v", command)
|
||||
|
||||
switch command.Action {
|
||||
case shared.ActionNul:
|
||||
response, err = p.handleNulAction(command)
|
||||
return
|
||||
case shared.ActionSign:
|
||||
response, err = p.handleSignAction(command)
|
||||
return
|
||||
case shared.ActionRevoke:
|
||||
response, err = p.handleRevokeAction(command)
|
||||
return
|
||||
default:
|
||||
return nil, errors.New(fmt.Sprintf(
|
||||
"unsupported Action 0x%02x %s",
|
||||
int(command.Action),
|
||||
command.Action,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
func (*CommandProcessor) handleNulAction(command datastructures.SignerRequest) (
|
||||
*datastructures.SignerResponse,
|
||||
error,
|
||||
) {
|
||||
var timeSpec unix.Timespec
|
||||
err := unix.ClockGettime(unix.CLOCK_REALTIME, &timeSpec)
|
||||
if err != nil {
|
||||
log.Errorf("could not get system time: %v", err)
|
||||
}
|
||||
log.Debugf("current system time is %v", timeSpec)
|
||||
// TODO: calculate the actual system time from the payload
|
||||
_, _, e1 := unix.Syscall(
|
||||
unix.SYS_CLOCK_SETTIME,
|
||||
uintptr(unix.CLOCK_REALTIME),
|
||||
uintptr(unsafe.Pointer(&timeSpec)),
|
||||
0,
|
||||
)
|
||||
if e1 != 0 {
|
||||
log.Errorf("could not set system time: %v", e1)
|
||||
}
|
||||
|
||||
return &datastructures.SignerResponse{
|
||||
Version: command.Version, Action: command.Action,
|
||||
Content1: "", Content2: "", Content3: ""}, nil
|
||||
}
|
||||
|
||||
func (p *CommandProcessor) handleSignAction(
|
||||
command datastructures.SignerRequest,
|
||||
) (
|
||||
*datastructures.SignerResponse,
|
||||
error,
|
||||
) {
|
||||
log.Debugf("handle sign call: %v", command)
|
||||
return nil, errors.New("not implemented yet")
|
||||
}
|
||||
|
||||
func (p *CommandProcessor) handleRevokeAction(
|
||||
command datastructures.SignerRequest,
|
||||
) (
|
||||
*datastructures.SignerResponse,
|
||||
error,
|
||||
) {
|
||||
log.Debugf("handle revoke call: %v", command)
|
||||
return nil, errors.New("not implemented yet")
|
||||
}
|
205
signer/main.go
205
signer/main.go
|
@ -1,205 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.bug.st/serial"
|
||||
|
||||
"git.cacert.org/cacert-gosigner/datastructures"
|
||||
"git.cacert.org/cacert-gosigner/shared"
|
||||
)
|
||||
|
||||
var (
|
||||
address string
|
||||
baudrate int
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.StringVar(&address, "a", "/dev/ttyUSB0", "address")
|
||||
flag.IntVar(&baudrate, "b", 115200, "baud rate")
|
||||
flag.Parse()
|
||||
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
DisableColors: true,
|
||||
FullTimestamp: true,
|
||||
})
|
||||
|
||||
serialMode := &serial.Mode{
|
||||
BaudRate: baudrate,
|
||||
DataBits: 8,
|
||||
StopBits: serial.OneStopBit,
|
||||
Parity: serial.NoParity,
|
||||
}
|
||||
log.Infof("connecting to %s using %+v", address, serialMode)
|
||||
port, err := serial.Open(address, serialMode)
|
||||
if err != nil {
|
||||
log.Fatalf("could not open serial port: %v", err)
|
||||
}
|
||||
log.Info("connected")
|
||||
|
||||
count := 0
|
||||
|
||||
defer func() {
|
||||
err := port.Close()
|
||||
if err != nil {
|
||||
log.Fatalf("could not close port: %v", err)
|
||||
}
|
||||
log.Info("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)
|
||||
readWriteCloser := (io.ReadWriteCloser)(port)
|
||||
go Receive(&readWriteCloser, &commandChan, &errChan)
|
||||
|
||||
select {
|
||||
case command := <-commandChan:
|
||||
response, err := Process(command)
|
||||
if err != nil {
|
||||
log.Errorf("error processing command: %v", err)
|
||||
break readLoop
|
||||
}
|
||||
_ = SendResponse(&readWriteCloser, response)
|
||||
case <-timeout:
|
||||
log.Error("timeout in main loop")
|
||||
break readLoop
|
||||
case err := <-errChan:
|
||||
log.Errorf("error from io goroutine %v", err)
|
||||
}
|
||||
|
||||
count++
|
||||
log.Infof("%d requests processed. Waiting for next request ...", count)
|
||||
}
|
||||
}
|
||||
|
||||
// Send a response to the client
|
||||
func SendResponse(port *io.ReadWriteCloser, response *datastructures.SignerResponse) error {
|
||||
if _, err := (*port).Write([]byte{0x02}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ack, err := shared.ReceiveBytes(port, 1, 5*time.Second); 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 := shared.ReceiveBytes(port, 1, 5*time.Second); 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.Infof("analyze %+v", command)
|
||||
|
||||
switch command.Action {
|
||||
case datastructures.ActionNul:
|
||||
response, err = handleNulAction(command)
|
||||
return
|
||||
default:
|
||||
return nil, errors.New(fmt.Sprintf(
|
||||
"unsupported Action 0x%02x %s",
|
||||
int(command.Action),
|
||||
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 *io.ReadWriteCloser, commandChan *chan datastructures.SignerRequest, errorChan *chan error) {
|
||||
header, err := shared.ReceiveBytes(port, 1, 20*time.Second)
|
||||
if err != nil {
|
||||
*errorChan <- err
|
||||
return
|
||||
}
|
||||
if header[0] != shared.HandshakeByte {
|
||||
*errorChan <- fmt.Errorf("unexpected byte 0x%x expected 0x%x", header[0], shared.HandshakeByte)
|
||||
}
|
||||
|
||||
if _, err := (*port).Write([]byte{shared.AckByte}); err != nil {
|
||||
*errorChan <- errors.New("could not write ACK")
|
||||
return
|
||||
}
|
||||
|
||||
lengthBytes, err := shared.ReceiveBytes(port, 3, 2*time.Second)
|
||||
if err != nil {
|
||||
*errorChan <- err
|
||||
return
|
||||
}
|
||||
blockLength := datastructures.Decode24BitLength(lengthBytes)
|
||||
blockData, err := shared.ReceiveBytes(port, blockLength, 5*time.Second)
|
||||
if err != nil {
|
||||
*errorChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
checkSum, err := shared.ReceiveBytes(port, 1, 2*time.Second)
|
||||
if err != nil {
|
||||
*errorChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
command, err := datastructures.SignerRequestFromData(lengthBytes, blockData, checkSum[0])
|
||||
if err != nil {
|
||||
*errorChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
trailer, err := shared.ReceiveBytes(port, len(shared.MagicTrailer), 2*time.Second)
|
||||
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
|
||||
}
|
212
signer/port_handler.go
Normal file
212
signer/port_handler.go
Normal file
|
@ -0,0 +1,212 @@
|
|||
package signer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"git.cacert.org/cacert-gosigner/datastructures"
|
||||
"git.cacert.org/cacert-gosigner/shared"
|
||||
)
|
||||
|
||||
const (
|
||||
waitForHandShake = time.Second * 5
|
||||
waitForInitialByte = time.Second * 20
|
||||
waitForLengthBytes = time.Second * 2
|
||||
waitForChecksumByte = time.Second * 2
|
||||
waitForTrailerBytes = time.Second * 2
|
||||
waitForBlock = time.Minute * 1
|
||||
|
||||
maxTriesPerBlock = 10_000
|
||||
)
|
||||
|
||||
// The PortHandler takes care of reading and writing of raw bytes from/to a serial port
|
||||
type PortHandler struct {
|
||||
port io.ReadWriteCloser
|
||||
processor *CommandProcessor
|
||||
errors chan error
|
||||
commandChan chan *datastructures.SignerRequest
|
||||
}
|
||||
|
||||
func (p *PortHandler) MainLoop() {
|
||||
count := 0
|
||||
for {
|
||||
go p.Receive()
|
||||
|
||||
select {
|
||||
case command := <-p.commandChan:
|
||||
if command == nil {
|
||||
log.Infof("command channel has been closed. stopping execution.")
|
||||
return
|
||||
}
|
||||
log.Debugf("received command %v", command)
|
||||
response, err := p.processor.Process(*command)
|
||||
if err != nil {
|
||||
log.Errorf("error processing command: %v", err)
|
||||
close(p.commandChan)
|
||||
} else {
|
||||
_ = p.SendResponse(response)
|
||||
}
|
||||
case err := <-p.errors:
|
||||
log.Errorf("error from io goroutine %v", err)
|
||||
}
|
||||
|
||||
count++
|
||||
log.Infof("%d requests processed. Waiting for next request ...", count)
|
||||
}
|
||||
}
|
||||
|
||||
// Receive a request and generate a request data structure
|
||||
func (p *PortHandler) Receive() {
|
||||
header, err := shared.ReceiveBytes(p.port, 1, waitForInitialByte)
|
||||
if err != nil {
|
||||
p.errors <- err
|
||||
return
|
||||
}
|
||||
if header[0] != shared.HandshakeByte {
|
||||
p.errors <- fmt.Errorf(
|
||||
"unexpected byte 0x%x expected 0x%x",
|
||||
header[0],
|
||||
shared.HandshakeByte,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
tries := maxTriesPerBlock
|
||||
|
||||
var command *datastructures.SignerRequest
|
||||
for {
|
||||
if tries <= 0 {
|
||||
p.errors <- errors.New("tried reading block too often")
|
||||
break
|
||||
}
|
||||
tries--
|
||||
if err := shared.SendBytes(p.port, []byte{shared.AckByte}); err != nil {
|
||||
p.errors <- fmt.Errorf("could not write ACK byte: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
lengthBytes, err := shared.ReceiveBytes(p.port, shared.LengthFieldSize, waitForLengthBytes)
|
||||
if err != nil {
|
||||
p.errors <- fmt.Errorf("could not read lenght bytes: %v", err)
|
||||
break
|
||||
}
|
||||
blockLength := datastructures.Decode24BitLength(lengthBytes)
|
||||
blockData, err := shared.ReceiveBytes(p.port, blockLength, waitForBlock)
|
||||
if err != nil {
|
||||
p.errors <- fmt.Errorf("could not read data block: %v", err)
|
||||
if !p.requestResend() {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
checkSum, err := shared.ReceiveBytes(p.port, shared.CheckSumFieldSize, waitForChecksumByte)
|
||||
if err != nil {
|
||||
p.errors <- fmt.Errorf("could not read checksum byte: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
calculated := datastructures.CalculateXorCheckSum([][]byte{lengthBytes, blockData})
|
||||
if checkSum[0] != calculated {
|
||||
p.errors <- fmt.Errorf("CRC error. expected 0x%02x, got 0x%02x", calculated, checkSum[0])
|
||||
if !p.requestResend() {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
trailer, err := shared.ReceiveBytes(p.port, shared.TrailerFieldSize, waitForTrailerBytes)
|
||||
if err != nil {
|
||||
p.errors <- fmt.Errorf("could not read trailer bytes: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
if string(trailer) != shared.MagicTrailer {
|
||||
p.errors <- fmt.Errorf(
|
||||
"BROKEN block detected, expected trailer bytes '%s' not found, got '%s' instead",
|
||||
shared.MagicTrailer,
|
||||
trailer,
|
||||
)
|
||||
if !p.requestResend() {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
command, err = datastructures.SignerRequestFromData(blockData)
|
||||
if err != nil {
|
||||
p.errors <- fmt.Errorf("failed to parse block as signer request: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
if err := shared.SendBytes(p.port, []byte{shared.AckByte}); err != nil {
|
||||
p.errors <- fmt.Errorf("failed to send ACK byte: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
p.commandChan <- command
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PortHandler) requestResend() bool {
|
||||
if err := shared.SendBytes(p.port, []byte{shared.ResendByte}); err != nil {
|
||||
p.errors <- err
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Send a response to the client
|
||||
func (p *PortHandler) SendResponse(response *datastructures.SignerResponse) error {
|
||||
if err := shared.SendBytes(p.port, []byte{shared.HandshakeByte}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ack, err := shared.ReceiveBytes(p.port, 1, waitForHandShake); err != nil {
|
||||
return err
|
||||
} else if ack[0] != shared.AckByte {
|
||||
return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0]))
|
||||
}
|
||||
|
||||
tryAgain := true
|
||||
for tryAgain {
|
||||
data := response.Serialize()
|
||||
if err := shared.SendBytes(p.port, data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checksum := datastructures.CalculateXorCheckSum([][]byte{data})
|
||||
if err := shared.SendBytes(p.port, []byte{checksum}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := shared.SendBytes(p.port, []byte(shared.MagicTrailer)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ack, err := shared.ReceiveBytes(p.port, 1, waitForHandShake); err != nil {
|
||||
return err
|
||||
} else if ack[0] == shared.AckByte {
|
||||
tryAgain = false
|
||||
} else if ack[0] != 0x11 {
|
||||
return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0]))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSignerProcess(port io.ReadWriteCloser) *PortHandler {
|
||||
errorChan := make(chan error)
|
||||
return &PortHandler{
|
||||
port: port,
|
||||
errors: errorChan,
|
||||
commandChan: make(chan *datastructures.SignerRequest, 1),
|
||||
processor: &CommandProcessor{},
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue