@ -19,9 +19,15 @@ package client
import (
"context"
"crypto/x509"
"fmt"
"math/big"
"os"
"path"
"sync"
"time"
"github.com/balacode/go-delta"
"github.com/sirupsen/logrus"
"github.com/tarm/serial"
@ -30,15 +36,38 @@ import (
"git.cacert.org/cacert-gosignerclient/internal/config"
)
const CallBackBufferSize = 50
const (
worldReadableDirPerm = 0 o755
worldReadableFilePerm = 0 o644
)
type Profile struct {
Name string
UseFor string
}
type SignerInfo struct {
SignerHealth bool
SignerVersion string
CACertificates [ ] string
UsableProfiles map [ string ] [ ] Profile
}
type Client struct {
port * serial . Port
logger * logrus . Logger
framer protocol . Framer
in chan [ ] byte
out chan [ ] byte
commands chan * protocol . Command
handler protocol . ClientHandler
config * config . ClientConfig
port * serial . Port
logger * logrus . Logger
framer protocol . Framer
in chan [ ] byte
out chan [ ] byte
commands chan * protocol . Command
handler protocol . ClientHandler
config * config . ClientConfig
signerInfo * SignerInfo
callback chan interface { }
sync . Mutex
lastKnownCRLS map [ string ] * big . Int
}
func ( c * Client ) Run ( ctx context . Context ) error {
@ -97,6 +126,11 @@ func (c *Client) setupConnection(serialConfig *serial.Config) error {
c . port = s
err = c . port . Flush ( )
if err != nil {
c . logger . WithError ( err ) . Warn ( "could not flush buffers of port: %w" , err )
}
return nil
}
@ -116,11 +150,40 @@ func (c *Client) Close() error {
func ( c * Client ) commandLoop ( ctx context . Context ) {
healthTimer := time . NewTimer ( c . config . HealthStart )
fetchCRLTimer := time . NewTimer ( c . config . FetchCRLStart )
for {
select {
case <- ctx . Done ( ) :
return
case callbackData := <- c . callback :
err := c . handleCallback ( callbackData )
if err != nil {
c . logger . WithError ( err ) . Error ( "callback handling failed" )
}
case <- fetchCRLTimer . C :
for _ , crlInfo := range c . buildCRLInfo ( ) {
announce , err := messages . BuildCommandAnnounce ( messages . CmdFetchCRL )
if err != nil {
c . logger . WithError ( err ) . Error ( "could not build fetch CRL command announce" )
}
var lastKnown [ ] byte
if crlInfo . LastKnown != nil {
lastKnown = crlInfo . LastKnown . Bytes ( )
}
c . commands <- & protocol . Command {
Announce : announce ,
Command : & messages . FetchCRLCommand {
IssuerID : crlInfo . Name ,
LastKnownID : lastKnown ,
} ,
}
}
fetchCRLTimer . Reset ( c . config . FetchCRLInterval )
case <- healthTimer . C :
announce , err := messages . BuildCommandAnnounce ( messages . CmdHealth )
if err != nil {
@ -137,20 +200,200 @@ func (c *Client) commandLoop(ctx context.Context) {
}
}
func ( c * Client ) handleCallback ( data interface { } ) error {
switch d := data . ( type ) {
case SignerInfo :
c . updateSignerInfo ( d )
case * messages . FetchCRLResponse :
c . updateCRL ( d )
default :
return fmt . Errorf ( "unknown callback data of type %T" , data )
}
return nil
}
func ( c * Client ) updateSignerInfo ( signerInfo SignerInfo ) {
c . Lock ( )
defer c . Unlock ( )
c . logger . Debug ( "update signer info" )
c . signerInfo = & signerInfo
}
type CRLInfo struct {
Name string
LastKnown * big . Int
}
func ( c * Client ) buildCRLInfo ( ) [ ] CRLInfo {
c . Lock ( )
defer c . Unlock ( )
if c . signerInfo == nil {
c . logger . Warn ( "no signer info available" )
return nil
}
infos := make ( [ ] CRLInfo , len ( c . signerInfo . CACertificates ) )
for i , caName := range c . signerInfo . CACertificates {
lastKnown := c . lastKnownCRL ( caName )
infos [ i ] = CRLInfo { Name : caName , LastKnown : lastKnown }
}
return infos
}
func ( c * Client ) lastKnownCRL ( caName string ) * big . Int {
crlFileName := c . buildCRLFileName ( caName )
_ , err := os . Stat ( crlFileName )
if err != nil {
c . logger . WithField ( "crl" , crlFileName ) . Debug ( "CRL file does not exist" )
delete ( c . lastKnownCRLS , caName )
return nil
}
lastKnown , ok := c . lastKnownCRLS [ caName ]
if ! ok {
derData , err := os . ReadFile ( crlFileName )
if err != nil {
c . logger . WithError ( err ) . WithField ( "crl" , crlFileName ) . Error ( "could not read CRL data" )
return nil
}
crl , err := x509 . ParseRevocationList ( derData )
if err != nil {
c . logger . WithError ( err ) . WithField ( "crl" , crlFileName ) . Error ( "could not parse CRL data" )
return nil
}
lastKnown = crl . Number
c . lastKnownCRLS [ caName ] = lastKnown
}
return lastKnown
}
func ( c * Client ) buildCRLFileName ( caName string ) string {
return path . Join ( c . config . CRLDirectory , fmt . Sprintf ( "%s.crl" , caName ) )
}
func ( c * Client ) updateCRL ( d * messages . FetchCRLResponse ) {
var (
der [ ] byte
crlNumber * big . Int
)
if d . UnChanged {
c . logger . WithField ( "issuer" , d . IssuerID ) . Debug ( "CRL did not change" )
return
}
if ! d . IsDelta {
der = d . CRLData
list , err := x509 . ParseRevocationList ( der )
if err != nil {
c . logger . WithError ( err ) . Error ( "CRL from signer could not be parsed" )
return
}
crlNumber = list . Number
} else {
crlFileName := c . buildCRLFileName ( d . IssuerID )
der , err := c . patchCRL ( crlFileName , d . CRLData )
if err != nil {
c . logger . WithError ( err ) . Error ( "CRL patching failed" )
delete ( c . lastKnownCRLS , d . IssuerID )
return
}
list , err := x509 . ParseRevocationList ( der )
if err != nil {
c . logger . WithError ( err ) . Error ( "could not parse patched CRL" )
delete ( c . lastKnownCRLS , d . IssuerID )
return
}
crlNumber = list . Number
}
if err := c . writeCRL ( d . IssuerID , der ) ; err != nil {
c . logger . WithError ( err ) . Error ( "could not store CRL" )
delete ( c . lastKnownCRLS , d . IssuerID )
return
}
c . lastKnownCRLS [ d . IssuerID ] = crlNumber
}
func ( c * Client ) writeCRL ( caName string , crlBytes [ ] byte ) error {
if err := os . MkdirAll ( c . config . CRLDirectory , worldReadableDirPerm ) ; err != nil {
return fmt . Errorf ( "could not create CRL directory %s: %w" , c . config . CRLDirectory , err )
}
if err := os . WriteFile ( c . buildCRLFileName ( caName ) , crlBytes , worldReadableFilePerm ) ; err != nil {
c . logger . WithError ( err ) . Error ( "could not write CRL file" )
}
return nil
}
func ( c * Client ) patchCRL ( crlFileName string , diff [ ] byte ) ( [ ] byte , error ) {
original , err := os . ReadFile ( crlFileName )
if err != nil {
return nil , fmt . Errorf ( "could not read existing CRL %s: %w" , crlFileName , err )
}
patch , err := delta . Load ( diff )
if err != nil {
return nil , fmt . Errorf ( "could not parse CRL delta: %w" , err )
}
der , err := patch . Apply ( original )
if err != nil {
return nil , fmt . Errorf ( "could not apply CRL delta: %w" , err )
}
return der , nil
}
func New (
cfg * config . ClientConfig ,
logger * logrus . Logger ,
handler protocol . ClientHandler ,
commands chan * protocol . Command ,
callback chan interface { } ,
) ( * Client , error ) {
client := & Client {
logger : logger ,
framer : protocol . NewCOBSFramer ( logger ) ,
in : make ( chan [ ] byte ) ,
out : make ( chan [ ] byte ) ,
commands : commands ,
handler : handler ,
config : cfg ,
logger : logger ,
framer : protocol . NewCOBSFramer ( logger ) ,
in : make ( chan [ ] byte ) ,
out : make ( chan [ ] byte ) ,
commands : commands ,
handler : handler ,
config : cfg ,
callback : callback ,
lastKnownCRLS : make ( map [ string ] * big . Int ) ,
}
err := client . setupConnection ( & serial . Config {