Jan Dittberner
7852c4d3df
This commit adds explicit input and output channel type information to make the channel's intent visible to developers.
367 lines
8.6 KiB
Go
367 lines
8.6 KiB
Go
/*
|
|
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 (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/shamaton/msgpackgen/msgpack"
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"git.cacert.org/cacert-gosigner/pkg/protocol"
|
|
|
|
"git.cacert.org/cacert-gosigner/pkg/messages"
|
|
)
|
|
|
|
type TestCommandGenerator struct {
|
|
logger *logrus.Logger
|
|
commands chan *protocol.Command
|
|
}
|
|
|
|
func (g *TestCommandGenerator) GenerateCommands(ctx context.Context) error {
|
|
// write some leading garbage to test signer robustness
|
|
_, _ = io.CopyN(os.Stdout, rand.Reader, 50) //nolint:gomnd
|
|
|
|
g.commands <- &protocol.Command{
|
|
Announce: messages.BuildCommandAnnounce(messages.CmdHealth),
|
|
Command: &messages.HealthCommand{},
|
|
}
|
|
|
|
const (
|
|
healthInterval = 5 * time.Second
|
|
crlInterval = 15 * time.Minute
|
|
startPause = 3 * time.Second
|
|
)
|
|
|
|
g.logger.Info("start generating commands")
|
|
|
|
time.Sleep(startPause)
|
|
|
|
g.commands <- &protocol.Command{
|
|
Announce: messages.BuildCommandAnnounce(messages.CmdFetchCRL),
|
|
Command: &messages.FetchCRLCommand{IssuerID: "sub-ecc_person_2022"},
|
|
}
|
|
|
|
healthTimer := time.NewTimer(healthInterval)
|
|
crlTimer := time.NewTimer(crlInterval)
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
_ = healthTimer.Stop()
|
|
|
|
g.logger.Info("stopped health check loop")
|
|
|
|
_ = crlTimer.Stop()
|
|
|
|
g.logger.Info("stopped CRL fetch loop")
|
|
|
|
return nil
|
|
case <-healthTimer.C:
|
|
g.commands <- &protocol.Command{
|
|
Announce: messages.BuildCommandAnnounce(messages.CmdHealth),
|
|
Command: &messages.HealthCommand{},
|
|
}
|
|
|
|
healthTimer.Reset(healthInterval)
|
|
case <-crlTimer.C:
|
|
g.commands <- &protocol.Command{
|
|
Announce: messages.BuildCommandAnnounce(messages.CmdFetchCRL),
|
|
Command: &messages.FetchCRLCommand{IssuerID: "sub-ecc_person_2022"},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type clientSimulator struct {
|
|
clientHandler protocol.ClientHandler
|
|
framesIn chan []byte
|
|
framesOut chan []byte
|
|
framer protocol.Framer
|
|
commandGenerator *TestCommandGenerator
|
|
logger *logrus.Logger
|
|
}
|
|
|
|
const (
|
|
responseAnnounceTimeout = 30 * time.Second
|
|
responseDataTimeout = 2 * time.Second
|
|
)
|
|
|
|
func (c *clientSimulator) Run(ctx context.Context) error {
|
|
framerErrors := make(chan error)
|
|
protocolErrors := make(chan error)
|
|
generatorErrors := make(chan error)
|
|
|
|
go func() {
|
|
err := c.framer.ReadFrames(ctx, os.Stdin, c.framesIn)
|
|
|
|
framerErrors <- err
|
|
}()
|
|
|
|
go func() {
|
|
err := c.framer.WriteFrames(ctx, os.Stdout, c.framesOut)
|
|
|
|
framerErrors <- err
|
|
}()
|
|
|
|
go func() {
|
|
clientProtocol := protocol.NewClient(c.clientHandler, c.commandGenerator.commands, c.framesIn, c.framesOut, c.logger)
|
|
|
|
err := clientProtocol.Handle(ctx)
|
|
|
|
protocolErrors <- err
|
|
}()
|
|
|
|
go func() {
|
|
err := c.commandGenerator.GenerateCommands(ctx)
|
|
|
|
generatorErrors <- err
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case err := <-framerErrors:
|
|
if err != nil {
|
|
return fmt.Errorf("error from framer: %w", err)
|
|
}
|
|
|
|
return nil
|
|
case err := <-generatorErrors:
|
|
if err != nil {
|
|
return fmt.Errorf("error from command generator: %w", err)
|
|
}
|
|
|
|
return nil
|
|
case err := <-protocolErrors:
|
|
if err != nil {
|
|
return fmt.Errorf("error from protocol handler: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
type ClientHandler struct {
|
|
logger *logrus.Logger
|
|
commands chan *protocol.Command
|
|
caList []string
|
|
}
|
|
|
|
func (c *ClientHandler) Send(ctx context.Context, command *protocol.Command, out chan<- []byte) error {
|
|
var (
|
|
frame []byte
|
|
err error
|
|
)
|
|
|
|
frame, err = msgpack.Marshal(command.Announce)
|
|
if err != nil {
|
|
return fmt.Errorf("could not marshal command annoucement: %w", err)
|
|
}
|
|
|
|
c.logger.WithField("announcement", command.Announce).Info("write command announcement")
|
|
|
|
c.logger.Trace("writing command announcement")
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case out <- frame:
|
|
break
|
|
}
|
|
|
|
frame, err = msgpack.Marshal(command.Command)
|
|
if err != nil {
|
|
return fmt.Errorf("could not marshal command data: %w", err)
|
|
}
|
|
|
|
c.logger.WithField("command", command.Command).Info("write command data")
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case out <- frame:
|
|
break
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *ClientHandler) ResponseAnnounce(ctx context.Context, in <-chan []byte) (*protocol.Response, error) {
|
|
response := &protocol.Response{}
|
|
|
|
var announce messages.ResponseAnnounce
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, nil
|
|
case frame := <-in:
|
|
if err := msgpack.Unmarshal(frame, &announce); err != nil {
|
|
return nil, fmt.Errorf("could not unmarshal response announcement: %w", err)
|
|
}
|
|
|
|
response.Announce = &announce
|
|
|
|
c.logger.WithField("announcement", response.Announce).Debug("received response announcement")
|
|
|
|
return response, nil
|
|
case <-time.After(responseAnnounceTimeout):
|
|
return nil, protocol.ErrResponseAnnounceTimeoutExpired
|
|
}
|
|
}
|
|
|
|
func (c *ClientHandler) ResponseData(ctx context.Context, in <-chan []byte, response *protocol.Response) error {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case frame := <-in:
|
|
switch response.Announce.Code {
|
|
case messages.RespHealth:
|
|
var resp messages.HealthResponse
|
|
if err := msgpack.Unmarshal(frame, &resp); err != nil {
|
|
return fmt.Errorf("could not unmarshal health response data: %w", err)
|
|
}
|
|
|
|
c.updateCAs(ctx, c.commands, resp.Info)
|
|
|
|
response.Response = &resp
|
|
case messages.RespCAInfo:
|
|
var resp messages.CAInfoResponse
|
|
if err := msgpack.Unmarshal(frame, &resp); err != nil {
|
|
return fmt.Errorf("could not unmarshal CA info response data: %w", err)
|
|
}
|
|
|
|
certificate, err := x509.ParseCertificate(resp.Certificate)
|
|
if err != nil {
|
|
return fmt.Errorf("could not parse certificate data: %w", err)
|
|
}
|
|
|
|
c.logger.Infof(
|
|
"certificate for %s: subject=%s, issuer=%s, serial=0x%x, valid from %s to %s",
|
|
resp.Name,
|
|
certificate.Subject,
|
|
certificate.Issuer,
|
|
certificate.SerialNumber,
|
|
certificate.NotBefore,
|
|
certificate.NotAfter)
|
|
|
|
response.Response = &resp
|
|
case messages.RespFetchCRL:
|
|
var resp messages.FetchCRLResponse
|
|
if err := msgpack.Unmarshal(frame, &resp); err != nil {
|
|
return fmt.Errorf("could not unmarshal fetch CRL response data: %w", err)
|
|
}
|
|
|
|
response.Response = &resp
|
|
default:
|
|
return fmt.Errorf("unhandled response code %s", response.Announce.Code)
|
|
}
|
|
case <-time.After(responseDataTimeout):
|
|
return protocol.ErrResponseDataTimeoutExpired
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *ClientHandler) HandleResponse(_ context.Context, response *protocol.Response) error {
|
|
c.logger.WithField("response", response.Announce).Info("handled response")
|
|
c.logger.WithField("response", response).Debug("full response")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *ClientHandler) updateCAs(ctx context.Context, out chan *protocol.Command, info []*messages.HealthInfo) {
|
|
caList := make([]string, 0)
|
|
|
|
for _, i := range info {
|
|
if i.Source == "HSM" {
|
|
c.logger.Debugf("info from HSM: %s", i)
|
|
|
|
for caName := range i.MoreInfo {
|
|
caList = append(caList, caName)
|
|
}
|
|
}
|
|
}
|
|
|
|
sort.Strings(caList)
|
|
|
|
if len(caList) != len(c.caList) {
|
|
c.caList = caList
|
|
|
|
for _, ca := range c.caList {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case out <- &protocol.Command{
|
|
Announce: messages.BuildCommandAnnounce(messages.CmdCAInfo),
|
|
Command: &messages.CAInfoCommand{Name: ca},
|
|
}:
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func newClientHandler(logger *logrus.Logger, commands chan *protocol.Command) *ClientHandler {
|
|
return &ClientHandler{logger: logger, commands: commands}
|
|
}
|
|
|
|
func main() {
|
|
logger := logrus.New()
|
|
logger.SetOutput(os.Stderr)
|
|
logger.SetLevel(logrus.DebugLevel)
|
|
|
|
messages.RegisterGeneratedResolver()
|
|
|
|
cobsFramer, err := protocol.NewCOBSFramer(logger)
|
|
if err != nil {
|
|
logger.WithError(err).Fatal("could not create COBS framer")
|
|
}
|
|
|
|
commandBufferSize := 50
|
|
|
|
commandCh := make(chan *protocol.Command, commandBufferSize)
|
|
|
|
sim := &clientSimulator{
|
|
commandGenerator: &TestCommandGenerator{
|
|
logger: logger,
|
|
commands: commandCh,
|
|
},
|
|
logger: logger,
|
|
framesIn: make(chan []byte),
|
|
framesOut: make(chan []byte),
|
|
framer: cobsFramer,
|
|
clientHandler: newClientHandler(logger, commandCh),
|
|
}
|
|
|
|
err = sim.Run(context.Background())
|
|
if err != nil {
|
|
logger.WithError(err).Error("simulator returned an error")
|
|
}
|
|
}
|