diff --git a/.gitignore b/.gitignore index 1eda5c5..0ac7fd6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,11 @@ *.crt *.pem *.pub -.idea/ +/.idea/ +/ca-hierarchy.json /clientsim +/config.yaml +/dist/ +/repos/ /signer /testPty -ca-hierarchy.json -config.yaml -dist/ \ No newline at end of file diff --git a/cmd/clientsim/main.go b/cmd/clientsim/main.go index 324d377..5a01f13 100644 --- a/cmd/clientsim/main.go +++ b/cmd/clientsim/main.go @@ -20,6 +20,7 @@ limitations under the License. package main import ( + "bytes" "context" "fmt" "os" @@ -30,6 +31,8 @@ import ( "github.com/shamaton/msgpackgen/msgpack" "github.com/sirupsen/logrus" + "git.cacert.org/cacert-gosigner/pkg/protocol" + "git.cacert.org/cacert-gosigner/pkg/messages" ) @@ -39,7 +42,7 @@ var cobsConfig = cobs.Config{SpecialByte: cobsDelimiter, Delimiter: true, Ending func main() { logger := logrus.New() - logger.SetOutput(os.Stdout) + logger.SetOutput(os.Stderr) logger.SetLevel(logrus.InfoLevel) sim := &clientSimulator{ @@ -48,14 +51,14 @@ func main() { err := sim.Run() if err != nil { - logger.Errorf("simulator returned an error: %v", err) + logger.WithError(err).Error("simulator returned an error") } } type clientSimulator struct { logger *logrus.Logger - commands chan messages.Command - responses chan []byte + commands chan *protocol.Command + responses chan [][]byte } func (c *clientSimulator) writeTestCommands(ctx context.Context) error { @@ -63,6 +66,13 @@ func (c *clientSimulator) writeTestCommands(ctx context.Context) error { const healthInterval = 10 * time.Second + time.Sleep(healthInterval) + + c.commands <- &protocol.Command{ + Announce: messages.BuildCommandAnnounce(messages.CmdFetchCRL), + Command: &messages.FetchCRLCommand{IssuerID: "sub-ecc_person_2022"}, + } + timer := time.NewTimer(healthInterval) for { @@ -70,15 +80,17 @@ func (c *clientSimulator) writeTestCommands(ctx context.Context) error { case <-ctx.Done(): _ = timer.Stop() + c.logger.Info("stopping health check loop") + return nil case <-timer.C: - c.commands <- messages.Command{ - Code: messages.CmdHealth, - TimeStamp: time.Now().UTC(), + c.commands <- &protocol.Command{ + Announce: messages.BuildCommandAnnounce(messages.CmdHealth), + Command: &messages.HealthCommand{}, } - - timer.Reset(healthInterval) } + + timer.Reset(healthInterval) } } @@ -90,6 +102,18 @@ func (c *clientSimulator) handleInput(ctx context.Context) error { buf := make([]byte, bufferSize) + type protocolState int8 + + const ( + stAnn protocolState = iota + stResp + ) + + state := stAnn + + var announce []byte + +reading: for { select { case <-ctx.Done(): @@ -108,12 +132,26 @@ func (c *clientSimulator) handleInput(ctx context.Context) error { data := buf[:count] - err = cobs.Verify(data, cobsConfig) - if err != nil { - return fmt.Errorf("frame verification failed: %w", err) - } + for _, frame := range bytes.SplitAfter(data, []byte{cobsConfig.SpecialByte}) { + if len(frame) == 0 { + continue reading + } - c.responses <- cobs.Decode(data, cobsConfig) + err = cobs.Verify(frame, cobsConfig) + if err != nil { + return fmt.Errorf("frame verification failed: %w", err) + } + + if state == stAnn { + announce = cobs.Decode(frame, cobsConfig) + + state = stResp + } else { + c.responses <- [][]byte{announce, cobs.Decode(frame, cobsConfig)} + + state = stAnn + } + } } } } @@ -122,75 +160,152 @@ func (c *clientSimulator) handleCommands(ctx context.Context) error { for { select { case command := <-c.commands: - commandBytes, err := msgpack.Marshal(command) - if err != nil { - return fmt.Errorf("could not marshal command bytes: %w", err) + if err := writeCommandAnnouncement(command); err != nil { + return err } - _, err = os.Stdout.Write(cobs.Encode(commandBytes, cobsConfig)) - if err != nil { - return fmt.Errorf("write failed: %w", err) + if err := writeCommand(command); err != nil { + return err } - responseBytes := <-c.responses + respData := <-c.responses - var response messages.Response + c.logger.WithField("respdata", respData).Trace("read response data") - err = msgpack.Unmarshal(responseBytes, &response) - if err != nil { - return fmt.Errorf("could not unmarshal msgpack data: %w", err) + response := &protocol.Response{} + + if err := msgpack.Unmarshal(respData[0], &response.Announce); err != nil { + return fmt.Errorf("could not unmarshal response announcement: %w", err) + } + + if err := c.handleResponse(response, respData[1]); err != nil { + return err } - c.logger.Errorf("received response: %+v", response) case <-ctx.Done(): return nil } } } +func (c *clientSimulator) handleResponse(response *protocol.Response, respBytes []byte) error { + switch response.Announce.Code { + case messages.RespHealth: + data := messages.HealthResponse{} + + if err := msgpack.Unmarshal(respBytes, &data); err != nil { + return fmt.Errorf("could not unmarshal health data: %w", err) + } + + c.logger.WithField( + "announce", + response.Announce, + ).WithField( + "data", + &data, + ).Infof("received response") + case messages.RespFetchCRL: + data := messages.FetchCRLResponse{} + + if err := msgpack.Unmarshal(respBytes, &data); err != nil { + return fmt.Errorf("could not unmarshal fetch CRL data: %w", err) + } + + c.logger.WithField( + "announce", + response.Announce, + ).WithField( + "data", + &data, + ).Infof("received response") + case messages.RespError: + data := messages.ErrorResponse{} + + if err := msgpack.Unmarshal(respBytes, &data); err != nil { + return fmt.Errorf("could not unmarshal error data: %w", err) + } + + c.logger.WithField( + "announce", + response.Announce, + ).WithField( + "data", + &data, + ).Infof("received response") + default: + if err := msgpack.Unmarshal(respBytes, &response.Response); err != nil { + return fmt.Errorf("could not unmarshal response: %w", err) + } + + c.logger.WithField("response", response).Infof("received response") + } + + return nil +} + +func writeCommandAnnouncement(command *protocol.Command) error { + cmdAnnounceBytes, err := msgpack.Marshal(&command.Announce) + if err != nil { + return fmt.Errorf("could not marshal command announcement bytes: %w", err) + } + + if _, err = os.Stdout.Write(cobs.Encode(cmdAnnounceBytes, cobsConfig)); err != nil { + return fmt.Errorf("command announcement write failed: %w", err) + } + + return nil +} + +func writeCommand(command *protocol.Command) error { + cmdBytes, err := msgpack.Marshal(&command.Command) + if err != nil { + return fmt.Errorf("could not marshal command bytes: %w", err) + } + + if _, err = os.Stdout.Write(cobs.Encode(cmdBytes, cobsConfig)); err != nil { + return fmt.Errorf("command write failed: %w", err) + } + + return nil +} + func (c *clientSimulator) Run() error { ctx, cancel := context.WithCancel(context.Background()) - c.commands = make(chan messages.Command) - c.responses = make(chan []byte) + c.commands = make(chan *protocol.Command) + c.responses = make(chan [][]byte) wg := sync.WaitGroup{} wg.Add(2) - var inputError, commandError error - - go func(inputErr error) { - _ = c.handleInput(ctx) + go func() { + if err := c.handleInput(ctx); err != nil { + c.logger.WithError(err).Error("input handling failed") + } cancel() wg.Done() - }(inputError) + }() - go func(commandErr error) { - _ = c.handleCommands(ctx) + go func() { + if err := c.handleCommands(ctx); err != nil { + c.logger.WithError(err).Error("command handling failed") + } cancel() wg.Done() - }(commandError) + }() var result error if err := c.writeTestCommands(ctx); err != nil { - c.logger.Errorf("test commands failed: %v", err) + c.logger.WithError(err).Error("test commands failed") } cancel() wg.Wait() - if inputError != nil { - c.logger.Errorf("reading input failed: %v", inputError) - } - - if commandError != nil { - c.logger.Errorf("sending commands failed: %v", commandError) - } - return result } diff --git a/cmd/signer/main.go b/cmd/signer/main.go index f49c0a4..e0911b8 100644 --- a/cmd/signer/main.go +++ b/cmd/signer/main.go @@ -19,10 +19,13 @@ package main import ( "flag" + "fmt" "os" "github.com/sirupsen/logrus" + "git.cacert.org/cacert-gosigner/pkg/x509/revoking" + "git.cacert.org/cacert-gosigner/pkg/config" "git.cacert.org/cacert-gosigner/pkg/health" "git.cacert.org/cacert-gosigner/pkg/hsm" @@ -51,7 +54,7 @@ func main() { logger.SetOutput(os.Stdout) logger.SetLevel(logrus.InfoLevel) - logger.Infof("cacert-gosigner %s (%s) - built %s\n", version, commit, date) + logger.Infof("cacert-gosigner %s (%s) - built %s", version, commit, date) flag.StringVar(&signerConfigFile, "config", defaultSignerConfigFile, "signer configuration file") flag.BoolVar(&showVersion, "version", false, "show version") @@ -67,12 +70,12 @@ func main() { parsedLevel, err := logrus.ParseLevel(logLevel) if err != nil { - logger.Fatalf("could not parse log level: %v", err) + logger.WithError(err).Fatal("could not parse log level") } logger.SetLevel(parsedLevel) - caConfig := LoadConfig(signerConfigFile, logger) + caConfig := loadConfig(signerConfigFile, logger) access := initializeHSM(caConfig, setupMode, verbose, logger) @@ -82,23 +85,77 @@ func main() { healthHandler := health.New(version, access) - proto, err := protocol.New(logger, protocol.RegisterHealthHandler(healthHandler)) + revokingRepositories, err := configureRepositories(caConfig) + if err != nil { + logger.WithError(err).Fatal("could not setup revoking repositories") + } + + fetchCRLHandler := revoking.NewFetchCRLHandler(revokingRepositories) + + proto, err := protocol.New( + logger, + protocol.RegisterHealthHandler(healthHandler), + protocol.RegisterFetchCRLHandler(fetchCRLHandler), + ) if err != nil { - logger.Fatalf("could not setup protocol handler: %v", err) + logger.WithError(err).Fatal("could not setup protocol handler") } serialHandler, err := seriallink.New(caConfig.GetSerial(), proto) if err != nil { - logger.Fatalf("could not setup serial link handler: %v", err) + logger.WithError(err).Fatal("could not setup serial link handler") } defer func() { _ = serialHandler.Close() }() if err = serialHandler.Run(); err != nil { - logger.Fatalf("error in serial handler: %v", err) + logger.WithError(err).Fatal("error in serial handler") + } + + logger.Info("setup complete, starting signer operation") +} + +func configureRepositories( + caConfig *config.SignerConfig, +) (map[string]*revoking.X509Revoking, error) { + var err error + + result := make(map[string]*revoking.X509Revoking) + + for _, name := range caConfig.RootCAs() { + result[fmt.Sprintf("root-%s", name)], err = buildX509Revoking(caConfig, name) + if err != nil { + return nil, err + } + } + + for _, name := range caConfig.SubordinateCAs() { + result[fmt.Sprintf("sub-%s", name)], err = buildX509Revoking(caConfig, name) + if err != nil { + return nil, err + } + } + + return result, nil +} + +func buildX509Revoking(caConfig *config.SignerConfig, name string) (*revoking.X509Revoking, error) { + caDef, err := caConfig.GetCADefinition(name) + if err != nil { + return nil, fmt.Errorf("could not get CA definition for %s: %w", name, err) + } + + repo, err := caConfig.Repository(name) + if err != nil { + return nil, fmt.Errorf("could not get repository for %s: %w", name, err) } - logger.Infof("setup complete, starting signer operation") + return revoking.NewX509Revoking( + repo, + caDef.KeyInfo.CRLSignatureAlgorithm, + caDef.Certificate, + caDef.KeyPair, + ), nil } func initializeHSM(caConfig *config.SignerConfig, setupMode, verbose bool, logger *logrus.Logger) *hsm.Access { @@ -118,21 +175,21 @@ func initializeHSM(caConfig *config.SignerConfig, setupMode, verbose bool, logge access, err := hsm.NewAccess(logger, opts...) if err != nil { - logger.Fatalf("could not setup HSM access: %v", err) + logger.WithError(err).Fatal("could not setup HSM access") } err = access.EnsureCAKeysAndCertificates() if err != nil { - logger.Fatalf("could not ensure CA keys and certificates exist: %v", err) + logger.WithError(err).Fatal("could not ensure CA keys and certificates exist") } return access } -func LoadConfig(signerConfigFile string, logger *logrus.Logger) *config.SignerConfig { +func loadConfig(signerConfigFile string, logger *logrus.Logger) *config.SignerConfig { configFile, err := os.Open(signerConfigFile) if err != nil { - logger.Fatalf("could not open signer configuration file %s: %v", signerConfigFile, err) + logger.WithError(err).Fatalf("could not open signer configuration file: %s", signerConfigFile) } caConfig, err := config.LoadConfiguration(configFile) diff --git a/go.mod b/go.mod index 0696769..cb8e819 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/ThalesIgnite/crypto11 v1.2.5 github.com/justincpresley/go-cobs v1.2.0 github.com/shamaton/msgpackgen v0.3.0 + github.com/sirupsen/logrus v1.9.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 @@ -19,7 +20,6 @@ require ( 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/sirupsen/logrus v1.9.0 // indirect github.com/thales-e-security/pool v0.0.2 // indirect golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect ) diff --git a/go.sum b/go.sum index 0157490..481e99e 100644 --- a/go.sum +++ b/go.sum @@ -32,7 +32,6 @@ github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUY 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= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/config/config.go b/pkg/config/config.go index a09647f..d82f9bb 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -25,12 +25,19 @@ import ( "errors" "fmt" "io" + "path" "strings" "time" "gopkg.in/yaml.v3" + + "git.cacert.org/cacert-gosigner/pkg/x509/openssl" + "git.cacert.org/cacert-gosigner/pkg/x509/revoking" + "git.cacert.org/cacert-gosigner/pkg/x509/signing" ) +const minRSABits = 2048 + type Serial struct { Device string Baud int @@ -197,10 +204,16 @@ func (k *KeyStorage) UnmarshalYAML(n *yaml.Node) error { return nil } +type CARepository interface { + revoking.Repository + signing.Repository +} + type SignerConfig struct { - global *Settings `yaml:"Settings"` - caMap map[string]*CaCertificateEntry `yaml:"CAs"` - keyStorage map[string]*KeyStorage `yaml:"KeyStorage"` + global *Settings `yaml:"Settings"` + caMap map[string]*CaCertificateEntry `yaml:"CAs"` + keyStorage map[string]*KeyStorage `yaml:"KeyStorage"` + repositories map[string]CARepository } func (c *SignerConfig) GetCADefinition(label string) (*CaCertificateEntry, error) { @@ -321,6 +334,26 @@ func (c *SignerConfig) GetSerial() *Serial { return c.global.Serial } +func (c *SignerConfig) Repository(name string) (CARepository, error) { + var ( + repo CARepository + ok bool + err error + ) + + repo, ok = c.repositories[name] + if !ok { + repo, err = openssl.NewFileRepository(path.Join("repos", name)) + if err != nil { + return nil, fmt.Errorf("could not create repository for %s: %w", name, err) + } + + c.repositories[name] = repo + } + + return repo, nil +} + // LoadConfiguration reads YAML configuration from the given reader as a SignerConfig structure func LoadConfiguration(r io.Reader) (*SignerConfig, error) { config := struct { @@ -349,23 +382,26 @@ func LoadConfiguration(r io.Reader) (*SignerConfig, error) { } return &SignerConfig{ - global: config.Global, - caMap: config.CAs, - keyStorage: config.KeyStorage, + global: config.Global, + caMap: config.CAs, + keyStorage: config.KeyStorage, + repositories: make(map[string]CARepository), }, nil } type PrivateKeyInfo struct { - Algorithm x509.PublicKeyAlgorithm - EccCurve elliptic.Curve - RSABits int + Algorithm x509.PublicKeyAlgorithm + EccCurve elliptic.Curve + RSABits int + CRLSignatureAlgorithm x509.SignatureAlgorithm } func (p *PrivateKeyInfo) UnmarshalYAML(value *yaml.Node) error { internalStructure := struct { - Algorithm string `yaml:"algorithm"` - EccCurve string `yaml:"ecc-curve,omitempty"` - RSABits *int `yaml:"rsa-bits,omitempty"` + Algorithm string `yaml:"algorithm"` + EccCurve string `yaml:"ecc-curve,omitempty"` + RSABits *int `yaml:"rsa-bits,omitempty"` + CRLSignatureAlgorithm string `yaml:"crl-signature-algorithm,omitempty"` }{} err := value.Decode(&internalStructure) @@ -382,6 +418,12 @@ func (p *PrivateKeyInfo) UnmarshalYAML(value *yaml.Node) error { } p.RSABits = *internalStructure.RSABits + + if p.RSABits < minRSABits { + return fmt.Errorf("RSA keys must have a length of at least %d bits", minRSABits) + } + + p.CRLSignatureAlgorithm = determineRSASignatureAlgorithm(internalStructure.CRLSignatureAlgorithm) case "EC": p.Algorithm = x509.ECDSA @@ -393,6 +435,8 @@ func (p *PrivateKeyInfo) UnmarshalYAML(value *yaml.Node) error { if err != nil { return err } + + p.CRLSignatureAlgorithm = determineECDSASignatureAlgorithm(internalStructure.CRLSignatureAlgorithm) case "": return errors.New("element 'algorithm' must be specified as 'EC' or 'RSA'") default: @@ -405,6 +449,32 @@ func (p *PrivateKeyInfo) UnmarshalYAML(value *yaml.Node) error { return nil } +func determineRSASignatureAlgorithm(algorithm string) x509.SignatureAlgorithm { + switch strings.ToLower(algorithm) { + case "sha1withrsa", "sha1": + return x509.SHA1WithRSA + case "sha384withrsa", "sha384": + return x509.SHA384WithRSA + case "sha512withrsa", "sha512": + return x509.SHA512WithRSA + default: + return x509.SHA256WithRSA + } +} + +func determineECDSASignatureAlgorithm(algorithm string) x509.SignatureAlgorithm { + switch strings.ToLower(algorithm) { + case "sha1withecdsa", "sha1": + return x509.ECDSAWithSHA1 + case "sha384withecdsa", "sha384": + return x509.ECDSAWithSHA384 + case "sha512withecdsa", "sha512": + return x509.ECDSAWithSHA512 + default: + return x509.ECDSAWithSHA256 + } +} + func (p *PrivateKeyInfo) MarshalYAML() (interface{}, error) { internalStructure := struct { Algorithm string `yaml:"algorithm"` diff --git a/pkg/hsm/hsm.go b/pkg/hsm/hsm.go index 56aefca..76359ee 100644 --- a/pkg/hsm/hsm.go +++ b/pkg/hsm/hsm.go @@ -79,6 +79,8 @@ func (a *Access) Healthy() (*health.Info, error) { healthy = false moreInfo[infoKey] = checkFailed + + continue } moreInfo[infoKey] = fmt.Sprintf("ok, valid until %s", cert.NotAfter.UTC().Format(time.RFC3339)) @@ -92,6 +94,8 @@ func (a *Access) Healthy() (*health.Info, error) { healthy = false moreInfo[infoKey] = checkFailed + + continue } def, err := a.signerConfig.GetCADefinition(ca) @@ -99,6 +103,8 @@ func (a *Access) Healthy() (*health.Info, error) { healthy = false moreInfo[infoKey] = checkFailed + + continue } _, err = a.getKeyPair(ca, def.KeyInfo) @@ -106,6 +112,8 @@ func (a *Access) Healthy() (*health.Info, error) { healthy = false moreInfo[infoKey] = checkFailed + + continue } moreInfo[infoKey] = fmt.Sprintf("ok, valid until %s", cert.NotAfter.UTC().Format(time.RFC3339)) @@ -307,6 +315,8 @@ func (a *Access) GetSubordinateCACertificate(certLabel string) (*x509.Certificat return nil, err } + caCert.Certificate, caCert.KeyPair = certificate, keyPair + return certificate, nil } diff --git a/pkg/messages/messages.go b/pkg/messages/messages.go index a495b3c..2580b56 100644 --- a/pkg/messages/messages.go +++ b/pkg/messages/messages.go @@ -21,58 +21,82 @@ limitations under the License. package messages import ( + "crypto/x509" + "encoding/pem" "fmt" "strings" "time" ) -type CommandCode int +type CommandCode int8 const ( CmdHealth CommandCode = iota + CmdFetchCRL ) +var commandNames = map[CommandCode]string{ + CmdHealth: "HEALTH", + CmdFetchCRL: "FETCH URL", +} + func (c CommandCode) String() string { - switch c { - case CmdHealth: - return "HEALTH" - default: - return fmt.Sprintf("unknown (%d)", int(c)) + if name, ok := commandNames[c]; ok { + return name } -} -type Command struct { - Code CommandCode `msgpack:"code"` - TimeStamp time.Time `msgpack:"created"` - Payload interface{} `msgpack:"payload"` // optional payload + return fmt.Sprintf("unknown %d", c) } -type ResponseCode int +type ResponseCode int8 const ( - RspError ResponseCode = -1 - RspHealth ResponseCode = iota + RespError ResponseCode = -1 + RespHealth ResponseCode = iota + RespFetchCRL ) -func (r ResponseCode) String() string { - switch r { - case RspError: - return "ERROR" - case RspHealth: - return "HEALTH" - default: - return fmt.Sprintf("unknown (%d)", int(r)) +var responseNames = map[ResponseCode]string{ + RespError: "ERROR", + RespHealth: "HEALTH", + RespFetchCRL: "FETCH CRL", +} + +func (c ResponseCode) String() string { + if name, ok := responseNames[c]; ok { + return name } + + return fmt.Sprintf("unknown %d", c) +} + +type CommandAnnounce struct { + Code CommandCode `msgpack:"code"` + Created time.Time `msgpack:"created"` +} + +func (r *CommandAnnounce) String() string { + return fmt.Sprintf("CommandAnnounce[code=%s, created=%s]", r.Code, r.Created) +} + +type ResponseAnnounce struct { + Code ResponseCode `msgpack:"code"` + Created time.Time `msgpack:"created"` +} + +func (r *ResponseAnnounce) String() string { + return fmt.Sprintf("ResponseAnnounce[code=%s, created=%s]", r.Code, r.Created) } -type Response struct { - Code ResponseCode `msgpack:"code"` - TimeStamp time.Time `msgpack:"created"` - Payload interface{} `msgpack:"payload"` +type FetchCRLCommand struct { + IssuerID string `msgpack:"issuer_id"` + LastKnownHash struct { + Algorithm string `msgpack:"algorithm"` + Value string `msgpack:"value"` + } `msgpack:"last_known_hash"` // optional last known hash in format } -func (r *Response) String() string { - return fmt.Sprintf("[%s] at %s: %+v", r.Code, r.TimeStamp, r.Payload) +type HealthCommand struct { } type HealthInfo struct { @@ -97,10 +121,61 @@ func (i *HealthInfo) String() string { type HealthResponse struct { Version string `msgpack:"version"` - Healthy bool - Info []HealthInfo + Healthy bool `msgpack:"healthy"` + Info []*HealthInfo +} + +func (h *HealthResponse) String() string { + builder := &strings.Builder{} + + _, _ = fmt.Fprintf(builder, "signer version=%s, healthy=%v, health data:\n", h.Version, h.Healthy) + + for _, info := range h.Info { + _, _ = fmt.Fprintf(builder, " - %s", info) + } + + return builder.String() +} + +type FetchCRLResponse struct { + IssuerID string `msgpack:"issuer_id"` + IsDelta bool `msgpack:"is_delta"` + CRLData []byte `msgpack:"crl_data"` +} + +func (r *FetchCRLResponse) String() string { + builder := &strings.Builder{} + + _, _ = fmt.Fprintf(builder, "issuer id=%s, delta CRL data=%v", r.IssuerID, r.IsDelta) + + if r.IsDelta { + _, _ = fmt.Fprint(builder, ", delta CRL data not shown") + } else { + revlist, err := x509.ParseRevocationList(r.CRLData) + if err != nil { + _, _ = fmt.Fprintf(builder, ", could not parse CRL: %s", err.Error()) + } else { + _, _ = fmt.Fprintf(builder, ", CRL info: issuer=%s, number=%s, next update=%s, revoked certificates=%d", + revlist.Issuer, revlist.Number, revlist.NextUpdate, len(revlist.RevokedCertificates)) + _, _ = builder.WriteString(", CRL data:\n") + _ = pem.Encode(builder, &pem.Block{ + Type: "CERTIFICATE REVOCATION LIST", + Bytes: r.CRLData, + }) + } + } + + return builder.String() } type ErrorResponse struct { Message string `msgpack:"message"` } + +func BuildCommandAnnounce(code CommandCode) *CommandAnnounce { + return &CommandAnnounce{Code: code, Created: time.Now().UTC()} +} + +func BuildResponseAnnounce(code ResponseCode) *ResponseAnnounce { + return &ResponseAnnounce{Code: code, Created: time.Now().UTC()} +} diff --git a/pkg/messages/resolver.msgpackgen.go b/pkg/messages/resolver.msgpackgen.go index a999ce5..091da6c 100644 --- a/pkg/messages/resolver.msgpackgen.go +++ b/pkg/messages/resolver.msgpackgen.go @@ -26,6 +26,36 @@ func ___encode(i interface{}) ([]byte, error) { // encodeAsArray func ___encodeAsArray(i interface{}) ([]byte, error) { switch v := i.(type) { + case HealthCommand: + encoder := enc.NewEncoder() + size, err := ___calcArraySizeHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder) + if err != nil { + return nil, err + } + encoder.MakeBytes(size) + b, offset, err := ___encodeArrayHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0) + if err != nil { + return nil, err + } + if size != offset { + return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthCommand", size, offset) + } + return b, err + case *HealthCommand: + encoder := enc.NewEncoder() + size, err := ___calcArraySizeHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder) + if err != nil { + return nil, err + } + encoder.MakeBytes(size) + b, offset, err := ___encodeArrayHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0) + if err != nil { + return nil, err + } + if size != offset { + return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthCommand", size, offset) + } + return b, err case HealthInfo: encoder := enc.NewEncoder() size, err := ___calcArraySizeHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder) @@ -86,6 +116,36 @@ func ___encodeAsArray(i interface{}) ([]byte, error) { return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthResponse", size, offset) } return b, err + case FetchCRLResponse: + encoder := enc.NewEncoder() + size, err := ___calcArraySizeFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder) + if err != nil { + return nil, err + } + encoder.MakeBytes(size) + b, offset, err := ___encodeArrayFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0) + if err != nil { + return nil, err + } + if size != offset { + return nil, fmt.Errorf("%s size / offset different %d : %d", "FetchCRLResponse", size, offset) + } + return b, err + case *FetchCRLResponse: + encoder := enc.NewEncoder() + size, err := ___calcArraySizeFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder) + if err != nil { + return nil, err + } + encoder.MakeBytes(size) + b, offset, err := ___encodeArrayFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0) + if err != nil { + return nil, err + } + if size != offset { + return nil, fmt.Errorf("%s size / offset different %d : %d", "FetchCRLResponse", size, offset) + } + return b, err case ErrorResponse: encoder := enc.NewEncoder() size, err := ___calcArraySizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder) @@ -123,6 +183,36 @@ func ___encodeAsArray(i interface{}) ([]byte, error) { // encodeAsMap func ___encodeAsMap(i interface{}) ([]byte, error) { switch v := i.(type) { + case HealthCommand: + encoder := enc.NewEncoder() + size, err := ___calcMapSizeHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder) + if err != nil { + return nil, err + } + encoder.MakeBytes(size) + b, offset, err := ___encodeMapHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0) + if err != nil { + return nil, err + } + if size != offset { + return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthCommand", size, offset) + } + return b, err + case *HealthCommand: + encoder := enc.NewEncoder() + size, err := ___calcMapSizeHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder) + if err != nil { + return nil, err + } + encoder.MakeBytes(size) + b, offset, err := ___encodeMapHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0) + if err != nil { + return nil, err + } + if size != offset { + return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthCommand", size, offset) + } + return b, err case HealthInfo: encoder := enc.NewEncoder() size, err := ___calcMapSizeHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder) @@ -183,6 +273,36 @@ func ___encodeAsMap(i interface{}) ([]byte, error) { return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthResponse", size, offset) } return b, err + case FetchCRLResponse: + encoder := enc.NewEncoder() + size, err := ___calcMapSizeFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder) + if err != nil { + return nil, err + } + encoder.MakeBytes(size) + b, offset, err := ___encodeMapFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0) + if err != nil { + return nil, err + } + if size != offset { + return nil, fmt.Errorf("%s size / offset different %d : %d", "FetchCRLResponse", size, offset) + } + return b, err + case *FetchCRLResponse: + encoder := enc.NewEncoder() + size, err := ___calcMapSizeFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder) + if err != nil { + return nil, err + } + encoder.MakeBytes(size) + b, offset, err := ___encodeMapFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0) + if err != nil { + return nil, err + } + if size != offset { + return nil, fmt.Errorf("%s size / offset different %d : %d", "FetchCRLResponse", size, offset) + } + return b, err case ErrorResponse: encoder := enc.NewEncoder() size, err := ___calcMapSizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder) @@ -229,6 +349,20 @@ func ___decode(data []byte, i interface{}) (bool, error) { // decodeAsArray func ___decodeAsArray(data []byte, i interface{}) (bool, error) { switch v := i.(type) { + case *HealthCommand: + decoder := dec.NewDecoder(data) + offset, err := ___decodeArrayHealthCommand_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 **HealthCommand: + decoder := dec.NewDecoder(data) + offset, err := ___decodeArrayHealthCommand_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 *HealthInfo: decoder := dec.NewDecoder(data) offset, err := ___decodeArrayHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, decoder, 0) @@ -257,6 +391,20 @@ func ___decodeAsArray(data []byte, i interface{}) (bool, error) { return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len()) } return true, err + case *FetchCRLResponse: + decoder := dec.NewDecoder(data) + offset, err := ___decodeArrayFetchCRLResponse_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 **FetchCRLResponse: + decoder := dec.NewDecoder(data) + offset, err := ___decodeArrayFetchCRLResponse_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) @@ -278,6 +426,20 @@ func ___decodeAsArray(data []byte, i interface{}) (bool, error) { // decodeAsMap func ___decodeAsMap(data []byte, i interface{}) (bool, error) { switch v := i.(type) { + case *HealthCommand: + decoder := dec.NewDecoder(data) + offset, err := ___decodeMapHealthCommand_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 **HealthCommand: + decoder := dec.NewDecoder(data) + offset, err := ___decodeMapHealthCommand_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 *HealthInfo: decoder := dec.NewDecoder(data) offset, err := ___decodeMapHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, decoder, 0) @@ -306,6 +468,20 @@ func ___decodeAsMap(data []byte, i interface{}) (bool, error) { return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len()) } return true, err + case *FetchCRLResponse: + decoder := dec.NewDecoder(data) + offset, err := ___decodeMapFetchCRLResponse_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 **FetchCRLResponse: + decoder := dec.NewDecoder(data) + offset, err := ___decodeMapFetchCRLResponse_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) @@ -324,6 +500,81 @@ func ___decodeAsMap(data []byte, i interface{}) (bool, error) { return false, nil } +// calculate size from git.cacert.org/cacert-gosigner/pkg/messages.HealthCommand +func ___calcArraySizeHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v HealthCommand, encoder *enc.Encoder) (int, error) { + size := 0 + size += encoder.CalcStructHeaderFix(0) + return size, nil +} + +// calculate size from git.cacert.org/cacert-gosigner/pkg/messages.HealthCommand +func ___calcMapSizeHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v HealthCommand, encoder *enc.Encoder) (int, error) { + size := 0 + size += encoder.CalcStructHeaderFix(0) + return size, nil +} + +// encode from git.cacert.org/cacert-gosigner/pkg/messages.HealthCommand +func ___encodeArrayHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v HealthCommand, encoder *enc.Encoder, offset int) ([]byte, int, error) { + var err error + offset = encoder.WriteStructHeaderFixAsArray(0, offset) + return encoder.EncodedBytes(), offset, err +} + +// encode from git.cacert.org/cacert-gosigner/pkg/messages.HealthCommand +func ___encodeMapHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v HealthCommand, encoder *enc.Encoder, offset int) ([]byte, int, error) { + var err error + offset = encoder.WriteStructHeaderFixAsMap(0, offset) + return encoder.EncodedBytes(), offset, err +} + +// decode to git.cacert.org/cacert-gosigner/pkg/messages.HealthCommand +func ___decodeArrayHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *HealthCommand, decoder *dec.Decoder, offset int) (int, error) { + offset, err := decoder.CheckStructHeader(0, offset) + if err != nil { + return 0, err + } + return offset, err +} + +// decode to git.cacert.org/cacert-gosigner/pkg/messages.HealthCommand +func ___decodeMapHealthCommand_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *HealthCommand, decoder *dec.Decoder, offset int) (int, error) { + keys := [][]byte{} + offset, err := decoder.CheckStructHeader(0, offset) + if err != nil { + return 0, err + } + count := 0 + for count < 0 { + 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 { + default: + return 0, fmt.Errorf("unknown key[%s] found", string(dataKey)) + } + } + return offset, err +} + // calculate size from git.cacert.org/cacert-gosigner/pkg/messages.HealthInfo func ___calcArraySizeHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v HealthInfo, encoder *enc.Encoder) (int, error) { size := 0 @@ -562,11 +813,16 @@ func ___calcArraySizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81 } size += s for _, vv := range v.Info { - size_vv, err := ___calcArraySizeHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(vv, encoder) - if err != nil { - return 0, err + if vv != nil { + vvp := *vv + size_vvp, err := ___calcArraySizeHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(vvp, encoder) + if err != nil { + return 0, err + } + size += size_vvp + } else { + size += encoder.CalcNil() } - size += size_vv } } else { size += encoder.CalcNil() @@ -590,11 +846,16 @@ func ___calcMapSizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a8137 } size += s for _, vv := range v.Info { - size_vv, err := ___calcMapSizeHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(vv, encoder) - if err != nil { - return 0, err + if vv != nil { + vvp := *vv + size_vvp, err := ___calcMapSizeHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(vvp, encoder) + if err != nil { + return 0, err + } + size += size_vvp + } else { + size += encoder.CalcNil() } - size += size_vv } } else { size += encoder.CalcNil() @@ -611,9 +872,14 @@ func ___encodeArrayHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a8137 if v.Info != nil { offset = encoder.WriteSliceLength(len(v.Info), offset, false) for _, vv := range v.Info { - _, offset, err = ___encodeArrayHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(vv, encoder, offset) - if err != nil { - return nil, 0, err + if vv != nil { + vvp := *vv + _, offset, err = ___encodeArrayHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(vvp, encoder, offset) + if err != nil { + return nil, 0, err + } + } else { + offset = encoder.WriteNil(offset) } } } else { @@ -628,15 +894,20 @@ func ___encodeMapHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f offset = encoder.WriteStructHeaderFixAsMap(3, offset) offset = encoder.WriteStringFix("version", 7, offset) offset = encoder.WriteString(v.Version, offset) - offset = encoder.WriteStringFix("Healthy", 7, offset) + offset = encoder.WriteStringFix("healthy", 7, offset) offset = encoder.WriteBool(v.Healthy, offset) offset = encoder.WriteStringFix("Info", 4, offset) if v.Info != nil { offset = encoder.WriteSliceLength(len(v.Info), offset, false) for _, vv := range v.Info { - _, offset, err = ___encodeMapHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(vv, encoder, offset) - if err != nil { - return nil, 0, err + if vv != nil { + vvp := *vv + _, offset, err = ___encodeMapHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(vvp, encoder, offset) + if err != nil { + return nil, 0, err + } + } else { + offset = encoder.WriteNil(offset) } } } else { @@ -668,18 +939,24 @@ func ___decodeArrayHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a8137 v.Healthy = vv } if !decoder.IsCodeNil(offset) { - var vv []HealthInfo + var vv []*HealthInfo var vvl int vvl, offset, err = decoder.SliceLength(offset) if err != nil { return 0, err } - vv = make([]HealthInfo, vvl) + vv = make([]*HealthInfo, vvl) for vvi := range vv { - var vvv HealthInfo - offset, err = ___decodeArrayHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(&vvv, decoder, offset) - if err != nil { - return 0, err + var vvv *HealthInfo + if !decoder.IsCodeNil(offset) { + var vvvp HealthInfo + offset, err = ___decodeArrayHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(&vvvp, decoder, offset) + if err != nil { + return 0, err + } + vvv = &vvvp + } else { + offset++ } vv[vvi] = vvv } @@ -694,7 +971,7 @@ func ___decodeArrayHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a8137 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 - {uint8(0x48), uint8(0x65), uint8(0x61), uint8(0x6c), uint8(0x74), uint8(0x68), uint8(0x79)}, // Healthy + {uint8(0x68), uint8(0x65), uint8(0x61), uint8(0x6c), uint8(0x74), uint8(0x68), uint8(0x79)}, // healthy {uint8(0x49), uint8(0x6e), uint8(0x66), uint8(0x6f)}, // Info } offset, err := decoder.CheckStructHeader(3, offset) @@ -747,22 +1024,239 @@ func ___decodeMapHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f count++ case 2: if !decoder.IsCodeNil(offset) { - var vv []HealthInfo + var vv []*HealthInfo var vvl int vvl, offset, err = decoder.SliceLength(offset) if err != nil { return 0, err } - vv = make([]HealthInfo, vvl) + vv = make([]*HealthInfo, vvl) for vvi := range vv { - var vvv HealthInfo - offset, err = ___decodeMapHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(&vvv, decoder, offset) + var vvv *HealthInfo + if !decoder.IsCodeNil(offset) { + var vvvp HealthInfo + offset, err = ___decodeMapHealthInfo_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(&vvvp, decoder, offset) + if err != nil { + return 0, err + } + vvv = &vvvp + } else { + offset++ + } + vv[vvi] = vvv + } + v.Info = vv + } else { + offset++ + } + 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.FetchCRLResponse +func ___calcArraySizeFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v FetchCRLResponse, encoder *enc.Encoder) (int, error) { + size := 0 + size += encoder.CalcStructHeaderFix(3) + size += encoder.CalcString(v.IssuerID) + size += encoder.CalcBool(v.IsDelta) + if v.CRLData != nil { + s, err := encoder.CalcSliceLength(len(v.CRLData), true) + if err != nil { + return 0, err + } + size += s + for _, vv := range v.CRLData { + size += encoder.CalcByte(vv) + } + } else { + size += encoder.CalcNil() + } + return size, nil +} + +// calculate size from git.cacert.org/cacert-gosigner/pkg/messages.FetchCRLResponse +func ___calcMapSizeFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v FetchCRLResponse, encoder *enc.Encoder) (int, error) { + size := 0 + size += encoder.CalcStructHeaderFix(3) + size += encoder.CalcStringFix(9) + size += encoder.CalcString(v.IssuerID) + size += encoder.CalcStringFix(8) + size += encoder.CalcBool(v.IsDelta) + size += encoder.CalcStringFix(8) + if v.CRLData != nil { + s, err := encoder.CalcSliceLength(len(v.CRLData), true) + if err != nil { + return 0, err + } + size += s + for _, vv := range v.CRLData { + size += encoder.CalcByte(vv) + } + } else { + size += encoder.CalcNil() + } + return size, nil +} + +// encode from git.cacert.org/cacert-gosigner/pkg/messages.FetchCRLResponse +func ___encodeArrayFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v FetchCRLResponse, encoder *enc.Encoder, offset int) ([]byte, int, error) { + var err error + offset = encoder.WriteStructHeaderFixAsArray(3, offset) + offset = encoder.WriteString(v.IssuerID, offset) + offset = encoder.WriteBool(v.IsDelta, offset) + if v.CRLData != nil { + offset = encoder.WriteSliceLength(len(v.CRLData), offset, true) + for _, vv := range v.CRLData { + offset = encoder.WriteByte(vv, offset) + } + } else { + offset = encoder.WriteNil(offset) + } + return encoder.EncodedBytes(), offset, err +} + +// encode from git.cacert.org/cacert-gosigner/pkg/messages.FetchCRLResponse +func ___encodeMapFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v FetchCRLResponse, encoder *enc.Encoder, offset int) ([]byte, int, error) { + var err error + offset = encoder.WriteStructHeaderFixAsMap(3, offset) + offset = encoder.WriteStringFix("issuer_id", 9, offset) + offset = encoder.WriteString(v.IssuerID, offset) + offset = encoder.WriteStringFix("is_delta", 8, offset) + offset = encoder.WriteBool(v.IsDelta, offset) + offset = encoder.WriteStringFix("crl_data", 8, offset) + if v.CRLData != nil { + offset = encoder.WriteSliceLength(len(v.CRLData), offset, true) + for _, vv := range v.CRLData { + offset = encoder.WriteByte(vv, offset) + } + } else { + offset = encoder.WriteNil(offset) + } + return encoder.EncodedBytes(), offset, err +} + +// decode to git.cacert.org/cacert-gosigner/pkg/messages.FetchCRLResponse +func ___decodeArrayFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *FetchCRLResponse, decoder *dec.Decoder, offset int) (int, error) { + offset, err := decoder.CheckStructHeader(3, offset) + if err != nil { + return 0, err + } + { + var vv string + vv, offset, err = decoder.AsString(offset) + if err != nil { + return 0, err + } + v.IssuerID = vv + } + { + var vv bool + vv, offset, err = decoder.AsBool(offset) + if err != nil { + return 0, err + } + v.IsDelta = vv + } + if !decoder.IsCodeNil(offset) { + var vv []byte + var vvl int + vvl, offset, err = decoder.SliceLength(offset) + if err != nil { + return 0, err + } + vv = make([]byte, vvl) + for vvi := range vv { + var vvv byte + vvv, offset, err = decoder.AsByte(offset) + if err != nil { + return 0, err + } + vv[vvi] = vvv + } + v.CRLData = vv + } else { + offset++ + } + return offset, err +} + +// decode to git.cacert.org/cacert-gosigner/pkg/messages.FetchCRLResponse +func ___decodeMapFetchCRLResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *FetchCRLResponse, decoder *dec.Decoder, offset int) (int, error) { + keys := [][]byte{ + {uint8(0x69), uint8(0x73), uint8(0x73), uint8(0x75), uint8(0x65), uint8(0x72), uint8(0x5f), uint8(0x69), uint8(0x64)}, // issuer_id + {uint8(0x69), uint8(0x73), uint8(0x5f), uint8(0x64), uint8(0x65), uint8(0x6c), uint8(0x74), uint8(0x61)}, // is_delta + {uint8(0x63), uint8(0x72), uint8(0x6c), uint8(0x5f), uint8(0x64), uint8(0x61), uint8(0x74), uint8(0x61)}, // crl_data + } + offset, err := decoder.CheckStructHeader(3, offset) + if err != nil { + return 0, err + } + count := 0 + for count < 3 { + 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.IssuerID = vv + } + count++ + case 1: + { + var vv bool + vv, offset, err = decoder.AsBool(offset) + if err != nil { + return 0, err + } + v.IsDelta = vv + } + count++ + case 2: + if !decoder.IsCodeNil(offset) { + var vv []byte + var vvl int + vvl, offset, err = decoder.SliceLength(offset) + if err != nil { + return 0, err + } + vv = make([]byte, vvl) + for vvi := range vv { + var vvv byte + vvv, offset, err = decoder.AsByte(offset) if err != nil { return 0, err } vv[vvi] = vvv } - v.Info = vv + v.CRLData = vv } else { offset++ } diff --git a/pkg/protocol/protocol.go b/pkg/protocol/protocol.go index 55cd036..0914405 100644 --- a/pkg/protocol/protocol.go +++ b/pkg/protocol/protocol.go @@ -19,71 +19,116 @@ limitations under the License. package protocol import ( + "errors" "fmt" - "time" "github.com/shamaton/msgpackgen/msgpack" "github.com/sirupsen/logrus" + "git.cacert.org/cacert-gosigner/pkg/x509/revoking" + "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) + HandleCommandAnnounce([]byte) (*messages.CommandAnnounce, error) + HandleCommand(*messages.CommandAnnounce, []byte) ([]byte, []byte, error) } type MsgPackHandler struct { - logger *logrus.Logger - healthHandler *health.Handler + logger *logrus.Logger + healthHandler *health.Handler + fetchCRLHandler *revoking.FetchCRLHandler } -func (m *MsgPackHandler) HandleFrame(frame []byte) ([]byte, error) { - var command messages.Command +func (m *MsgPackHandler) HandleCommandAnnounce(frame []byte) (*messages.CommandAnnounce, error) { + var ann messages.CommandAnnounce - err := msgpack.Unmarshal(frame, &command) - if err != nil { - m.logger.Errorf("unmarshal failed: %v", err) + if err := msgpack.Unmarshal(frame, &ann); err != nil { + return nil, fmt.Errorf("could not unmarshal command announcement: %w", err) + } + + m.logger.Infof("received command announcement %+v", ann) + + return &ann, nil +} + +func (m *MsgPackHandler) HandleCommand(announce *messages.CommandAnnounce, frame []byte) ([]byte, []byte, error) { + var ( + response *Response + clientError, err error + ) + + switch announce.Code { + case messages.CmdHealth: + // health has no payload, ignore the frame + response, err = m.handleCommand(&Command{Announce: announce, Command: nil}) + if err != nil { + m.logger.WithError(err).Error("health handling failed") - errorResponse, innerErr := buildErrorResponse("do not understand") - if innerErr != nil { - return nil, innerErr + clientError = errors.New("could not handle request") } + case messages.CmdFetchCRL: + var command messages.FetchCRLCommand - return errorResponse, nil - } + err = msgpack.Unmarshal(frame, &command) + if err != nil { + m.logger.WithError(err).Error("unmarshal failed") - m.logger.Infof("Received %s command sent at %s", command.Code, command.TimeStamp) + clientError = errors.New("could not unmarshal fetch crl command") - response, err := m.handleCommand(&command) - if err != nil { - m.logger.Errorf("command failed: %v", err) + break + } - errorResponse, innerErr := buildErrorResponse("command failed") - if innerErr != nil { - return nil, innerErr + response, err = m.handleCommand(&Command{Announce: announce, Command: command}) + if err != nil { + m.logger.WithError(err).Error("fetch CRL handling failed") + + clientError = errors.New("could not handle request") } + } + + if clientError != nil { + response = buildErrorResponse(clientError.Error()) + } - return errorResponse, nil + announceData, err := msgpack.Marshal(response.Announce) + if err != nil { + return nil, nil, fmt.Errorf("could not marshal response announcement: %w", err) } - responseData, err := msgpack.Marshal(response) + responseData, err := msgpack.Marshal(response.Response) if err != nil { - return nil, fmt.Errorf("could not marshal response: %w", err) + return nil, nil, fmt.Errorf("could not marshal response: %w", err) } - return responseData, nil + return announceData, responseData, nil +} + +type Command struct { + Announce *messages.CommandAnnounce + Command interface{} } -func (m *MsgPackHandler) handleCommand(command *messages.Command) (*messages.Response, error) { +type Response struct { + Announce *messages.ResponseAnnounce + Response interface{} +} + +func (r *Response) String() string { + return fmt.Sprintf("Response[Code=%s] created=%s data=%s", r.Announce.Code, r.Announce.Created, r.Response) +} + +func (m *MsgPackHandler) handleCommand(command *Command) (*Response, error) { var ( err error - payload interface{} + responseData interface{} responseCode messages.ResponseCode ) - switch command.Code { + switch command.Announce.Code { case messages.CmdHealth: var res *health.Result @@ -98,36 +143,52 @@ func (m *MsgPackHandler) handleCommand(command *messages.Command) (*messages.Res } for _, info := range res.Info { - response.Info = append(response.Info, messages.HealthInfo{ + response.Info = append(response.Info, &messages.HealthInfo{ Source: info.Source, Healthy: info.Healthy, MoreInfo: info.MoreInfo, }) } - responseCode, payload = messages.RspHealth, response + responseCode, responseData = messages.RespHealth, response + case messages.CmdFetchCRL: + var res *revoking.Result + + fetchCRLPayload, ok := command.Command.(messages.FetchCRLCommand) + if !ok { + return nil, fmt.Errorf("could not use payload as FetchCRLPayload") + } + + res, err = m.fetchCRLHandler.FetchCRL(fetchCRLPayload.IssuerID) + if err != nil { + break + } + + response := messages.FetchCRLResponse{ + IsDelta: false, + CRLData: res.Data, + } + + responseCode, responseData = messages.RespFetchCRL, response default: - return nil, fmt.Errorf("unhandled command %s", command) + return nil, fmt.Errorf("unhandled command %v", command) } if err != nil { return nil, fmt.Errorf("error from command handler: %w", err) } - return &messages.Response{TimeStamp: time.Now().UTC(), Code: responseCode, Payload: payload}, nil + return &Response{ + Announce: messages.BuildResponseAnnounce(responseCode), + Response: responseData, + }, 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) +func buildErrorResponse(errMsg string) *Response { + return &Response{ + Announce: messages.BuildResponseAnnounce(messages.RespError), + Response: messages.ErrorResponse{Message: errMsg}, } - - return marshal, nil } func New(logger *logrus.Logger, handlers ...RegisterHandler) (Handler, error) { @@ -151,3 +212,9 @@ func RegisterHealthHandler(healthHandler *health.Handler) func(*MsgPackHandler) h.healthHandler = healthHandler } } + +func RegisterFetchCRLHandler(fetchCRLHandler *revoking.FetchCRLHandler) func(handler *MsgPackHandler) { + return func(h *MsgPackHandler) { + h.fetchCRLHandler = fetchCRLHandler + } +} diff --git a/pkg/seriallink/seriallink.go b/pkg/seriallink/seriallink.go index 0dfc1ca..1e6c402 100644 --- a/pkg/seriallink/seriallink.go +++ b/pkg/seriallink/seriallink.go @@ -19,6 +19,7 @@ limitations under the License. package seriallink import ( + "bytes" "fmt" "time" @@ -29,8 +30,17 @@ import ( "git.cacert.org/cacert-gosigner/pkg/protocol" ) +type protocolState int8 + +const ( + stAnnounce protocolState = iota + stCommand +) + type Handler struct { protocolHandler protocol.Handler + protocolState protocolState + currentCommand *protocol.Command config *serial.Config port *serial.Port } @@ -57,6 +67,8 @@ func (h *Handler) Close() error { const cobsDelimiter = 0x00 +var cobsConfig = cobs.Config{SpecialByte: cobsDelimiter, Delimiter: true, EndingSave: true} + func (h *Handler) Run() error { const ( bufferSize = 1024 * 1024 @@ -65,7 +77,7 @@ func (h *Handler) Run() error { errors := make(chan error) - cobsConfig := cobs.Config{SpecialByte: cobsDelimiter, Delimiter: true, EndingSave: true} + h.protocolState = stAnnounce go func() { buf := make([]byte, bufferSize) @@ -84,43 +96,114 @@ func (h *Handler) Run() error { continue } - err = cobs.Verify(buf[:count], cobsConfig) - if err != nil { + frames := bytes.SplitAfter(buf[:count], []byte{cobsDelimiter}) + + if err := h.handleFrames(frames); err != nil { errors <- err return } + } + }() - // perform COBS decoding - decoded := cobs.Decode(buf[:count], cobsConfig) + err := <-errors + if err != nil { + return fmt.Errorf("error from handler loop: %w", err) + } - msg, err := h.protocolHandler.HandleFrame(decoded) - if err != nil { - errors <- err + return nil +} - return - } +func (h *Handler) handleFrames(frames [][]byte) error { + for _, frame := range frames { + if len(frame) == 0 { + return nil + } - // perform COBS encoding - encoded := cobs.Encode(msg, cobsConfig) + if err := cobs.Verify(frame, cobsConfig); err != nil { + return fmt.Errorf("could not verify COBS frame: %w", err) + } - _, err = h.port.Write(encoded) - if err != nil { - errors <- err + // perform COBS decoding + decoded := cobs.Decode(frame, cobsConfig) - return + if h.protocolState == stAnnounce { + if err := h.handleCommandAnnounce(decoded); err != nil { + return err } } - }() - err := <-errors + if h.protocolState == stCommand { + if err := h.handleCommandData(decoded); err != nil { + return err + } + } + + if err := h.nextState(); err != nil { + return err + } + } + + return nil +} + +func (h *Handler) handleCommandData(decoded []byte) error { + respAnn, msg, err := h.protocolHandler.HandleCommand(h.currentCommand.Announce, decoded) if err != nil { - return fmt.Errorf("error from handler loop: %w", err) + return fmt.Errorf("command handler for %s failed: %w", h.currentCommand.Announce.Code, err) + } + + if err := h.writeResponse(respAnn, msg, cobsConfig); err != nil { + return err + } + + return nil +} + +func (h *Handler) handleCommandAnnounce(decoded []byte) error { + announce, err := h.protocolHandler.HandleCommandAnnounce(decoded) + if err != nil { + return fmt.Errorf("command announce handling failed: %w", err) + } + + h.currentCommand = &protocol.Command{Announce: announce} + + return nil +} + +func (h *Handler) writeResponse(ann, msg []byte, cobsConfig cobs.Config) error { + encoded := cobs.Encode(ann, cobsConfig) + + if _, err := h.port.Write(encoded); err != nil { + return fmt.Errorf("could not write response announcement: %w", err) + } + + encoded = cobs.Encode(msg, cobsConfig) + + if _, err := h.port.Write(encoded); err != nil { + return fmt.Errorf("could not write response: %w", err) } return nil } +func (h *Handler) nextState() error { + var next protocolState + + switch h.protocolState { + case stAnnounce: + next = stCommand + case stCommand: + next = stAnnounce + default: + return fmt.Errorf("illegal protocol state %d", int(h.protocolState)) + } + + h.protocolState = next + + 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} diff --git a/pkg/x509/openssl/repository.go b/pkg/x509/openssl/repository.go index 45788d4..077ff05 100644 --- a/pkg/x509/openssl/repository.go +++ b/pkg/x509/openssl/repository.go @@ -92,9 +92,45 @@ func (ie *indexEntry) String() string { // A reference for the file format can be found at // https://pki-tutorial.readthedocs.io/en/latest/cadb.html. type Repository struct { - indexFileName string - lock sync.Locker - entries []indexEntry + indexFileName string + crlNumberFileName string + lock sync.Locker + entries []indexEntry +} + +func (r *Repository) NextCRLNumber() (*big.Int, error) { + r.lock.Lock() + defer r.lock.Unlock() + + number := big.NewInt(0) + + data, err := os.ReadFile(r.crlNumberFileName) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf( + "could not read next CRL number from file %s: %w", + r.crlNumberFileName, + err, + ) + } + } else { + if _, ok := number.SetString(string(data), 16); !ok { + return nil, fmt.Errorf("could not parse %s as CRL serial number", data) + } + } + + number.Add(number, big.NewInt(1)) + + err = os.WriteFile(r.crlNumberFileName, []byte(number.Text(16)), 0600) //nolint:gomnd + if err != nil { + return nil, fmt.Errorf( + "could not write next CRL serial number to file %s: %w", + r.crlNumberFileName, + err, + ) + } + + return number, nil } func (ie *indexEntry) markRevoked(revocationTime time.Time, reason revoking.CRLReason) { @@ -378,13 +414,21 @@ func (r *Repository) newIndexEntryFromLine(text string) (*indexEntry, error) { } func NewFileRepository(baseDirectory string) (*Repository, error) { - err := os.Chdir(baseDirectory) + _, err := os.ReadDir(baseDirectory) if err != nil { - return nil, fmt.Errorf("could not change to base directory %s: %w", baseDirectory, err) + if !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("could not change to base directory %s: %w", baseDirectory, err) + } + + err = os.MkdirAll(baseDirectory, 0750) //nolint:gomnd + if err != nil { + return nil, fmt.Errorf("could not create base directory %s: %w", baseDirectory, err) + } } return &Repository{ - indexFileName: path.Join(baseDirectory, "index.txt"), - lock: &sync.Mutex{}, + indexFileName: path.Join(baseDirectory, "index.txt"), + crlNumberFileName: path.Join(baseDirectory, "crlnumber"), + lock: &sync.Mutex{}, }, nil } diff --git a/pkg/x509/revoking/revoking.go b/pkg/x509/revoking/revoking.go index e7880f1..79923a2 100644 --- a/pkg/x509/revoking/revoking.go +++ b/pkg/x509/revoking/revoking.go @@ -32,6 +32,8 @@ import ( var OidCRLReason = asn1.ObjectIdentifier{2, 5, 29, 21} +const defaultCRLValidity = 24 * time.Hour + type CRLReason int // CRL reason codes as defined in RFC 5280 section 5.3.1 @@ -138,6 +140,7 @@ func (r *X509Revoking) CreateCRL() (*CRLInformation, error) { SignatureAlgorithm: r.crlAlgorithm, RevokedCertificates: revoked, Number: nextNumber, + NextUpdate: time.Now().UTC().Add(defaultCRLValidity), }, r.crlIssuer, r.signer) if err != nil { return nil, fmt.Errorf("could not sign revocation list: %w", err) @@ -154,3 +157,29 @@ func NewX509Revoking( ) *X509Revoking { return &X509Revoking{repository: repo, crlAlgorithm: crlAlgorithm, crlIssuer: issuer, signer: signer} } + +type Result struct { + Data []byte +} + +type FetchCRLHandler struct { + repositories map[string]*X509Revoking +} + +func NewFetchCRLHandler(repositories map[string]*X509Revoking) *FetchCRLHandler { + return &FetchCRLHandler{repositories: repositories} +} + +func (h *FetchCRLHandler) FetchCRL(issuerID string) (*Result, error) { + repo, ok := h.repositories[issuerID] + if !ok { + return nil, fmt.Errorf("unknown issuer ID %s", issuerID) + } + + currentCRL, err := repo.CreateCRL() + if err != nil { + return nil, fmt.Errorf("could not create CRL for issuer ID %s: %w", issuerID, err) + } + + return &Result{Data: currentCRL.CRL}, nil +}