diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..34989ae --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,39 @@ +--- +output: + sort-results: true + +linter-settings: + goimports: + local-prefixes: git.cacert.org/cacert-gosigner + misspell: + locale: US + ignore-words: + - CAcert + +linters: + disable-all: false + enable: + - errorlint + - gocognit + - goconst + - gocritic + - gofmt + - goheader + - goimports + - golint + - gomnd + - gosec + - interfacer + - lll + - makezero + - misspell + - nakedret + - nestif + - nlreturn + - nolintlint + - predeclared + - rowserrcheck + - scopelint + - sqlclosecheck + - wrapcheck + - wsl diff --git a/client/config.go b/client/config.go index d8b19da..aea452b 100644 --- a/client/config.go +++ b/client/config.go @@ -2,10 +2,17 @@ package main import ( "fmt" - "github.com/sirupsen/logrus" + "io/ioutil" + + log "github.com/sirupsen/logrus" "go.bug.st/serial" "gopkg.in/yaml.v2" - "io/ioutil" +) + +const ( + dataBits = 8 + defaultBaudRate = 115200 + defaultBufferSize = 2048 ) type ClientConfig struct { @@ -21,8 +28,8 @@ type ClientConfig struct { var defaultConfig = ClientConfig{ SerialAddress: "/dev/ttyUSB0", - BaudRate: 115200, - BufferSize: 2048, + BaudRate: defaultBaudRate, + BufferSize: defaultBufferSize, Paranoid: false, Debug: false, OpenSSLBinary: "/usr/bin/openssl", @@ -30,31 +37,36 @@ var defaultConfig = ClientConfig{ MySQLDSN: ":@/database?parseTime=true", } -func generateExampleConfig(configFile string) (config *ClientConfig, err error) { - config = &defaultConfig +func generateExampleConfig(configFile string) (*ClientConfig, error) { + config := &defaultConfig + configBytes, err := yaml.Marshal(config) if err != nil { - logrus.Errorf("could not generate configuration data") - return + return nil, fmt.Errorf("could not generate configuration data: %w", err) } - logrus.Infof("example data for %s:\n\n---\n%s\n", configFile, configBytes) - return + log.Infof("example data for %s:\n\n---\n%s\n", configFile, configBytes) + + return config, nil } func readConfig(configFile string) (config *ClientConfig, err error) { source, err := ioutil.ReadFile(configFile) if err != nil { - logrus.Errorf("opening configuration file failed: %v", err) - if exampleConfig, err := generateExampleConfig(configFile); err != nil { + log.Errorf("opening configuration file failed: %v", err) + + exampleConfig, err := generateExampleConfig(configFile) + if err != nil { return nil, err - } else { - logrus.Info("starting with default config") - return exampleConfig, nil } + + log.Info("starting with default config") + + return exampleConfig, nil } + if err := yaml.Unmarshal(source, &config); err != nil { - return nil, fmt.Errorf("loading configuration file failed: %v", err) + return nil, fmt.Errorf("loading configuration file failed: %w", err) } return config, nil @@ -63,7 +75,7 @@ func readConfig(configFile string) (config *ClientConfig, err error) { func fillSerialMode(clientConfig *ClientConfig) *serial.Mode { return &serial.Mode{ BaudRate: clientConfig.BaudRate, - DataBits: 8, + DataBits: dataBits, StopBits: serial.OneStopBit, Parity: serial.NoParity, } diff --git a/client/config_test.go b/client/config_test.go index cc70ae1..01861e1 100644 --- a/client/config_test.go +++ b/client/config_test.go @@ -2,14 +2,15 @@ package main import ( "bytes" - "github.com/sirupsen/logrus" "strings" "testing" + + log "github.com/sirupsen/logrus" ) func TestGenerateExampleConfig(t *testing.T) { testOutput := bytes.Buffer{} - logrus.SetOutput(&testOutput) + log.SetOutput(&testOutput) config, err := generateExampleConfig("test.yaml") diff --git a/client/main.go b/client/main.go index 2692696..f1e36bc 100644 --- a/client/main.go +++ b/client/main.go @@ -16,29 +16,37 @@ import ( "git.cacert.org/cacert-gosigner/datastructures" ) +const mainLoopSleep = 2700 + func main() { var configFile string flag.StringVar(&configFile, "c", "client.yaml", "client configuration file in YAML format") flag.Parse() - var clientConfig *ClientConfig - var serialConfig *serial.Mode - var err error + var ( + clientConfig *ClientConfig + serialConfig *serial.Mode + err error + ) if clientConfig, err = readConfig(configFile); err != nil { log.Panic(err) } + serialConfig = fillSerialMode(clientConfig) + if clientConfig.Debug { log.SetLevel(log.TraceLevel) } log.Infof("connecting to %s using %+v", clientConfig.SerialAddress, serialConfig) + port, err := serial.Open(clientConfig.SerialAddress, serialConfig) if err != nil { log.Fatal(err) } + log.Debug("serial port connected") requestChannel := protocol.NewSignerProtocolRequestChannel() @@ -48,6 +56,7 @@ func main() { if clientConfig.BufferSize != 0 { clientProtocolConfig.BufferSize = int(clientConfig.BufferSize) } + protocolHandler := protocol.NewProtocolHandler( requestChannel, &responseChannel, port, clientProtocolConfig, ) @@ -55,14 +64,17 @@ func main() { cancelChannel := make(chan os.Signal, 1) signal.Notify(cancelChannel, syscall.SIGTERM, syscall.SIGINT) + const goRoutines = 2 + wg := sync.WaitGroup{} - wg.Add(2) + wg.Add(goRoutines) go func() { if err := protocolHandler.HandleSignerProtocol(); err != nil { log.Errorf("terminating because of %v", err) close(cancelChannel) } + wg.Done() }() @@ -75,20 +87,26 @@ func main() { if sig != nil { log.Infof("caught %+v", sig) } + if err := protocolHandler.Close(); err != nil { log.Error(err) } else { log.Infof("protocol handler closed") } + if err := port.Close(); err != nil { log.Error(err) } else { log.Infof("serial port closed") } + wg.Wait() } -func runMainLoop(requestChannel *protocol.SignerProtocolRequestChannel, responseChannel *chan *datastructures.SignerResponse) { +func runMainLoop( + requestChannel *protocol.SignerProtocolRequestChannel, + responseChannel *chan *datastructures.SignerResponse, +) { crlCheck := 0 log.Debug("starting main loop") @@ -99,30 +117,28 @@ func runMainLoop(requestChannel *protocol.SignerProtocolRequestChannel, response log.Error(err) } } + log.Trace("processing goroutine terminated") }() for { - log.Debug("handling GPG database ...") - // HandleGPG(&requestChannel) - log.Debug("issuing certificates ...") - // HandleCertificates(&requestChannel) - log.Debug("revoking certificates ...") - // RevokeCertificates(&requestChannel) + log.Debug("handling GPG database ...") // HandleGPG(&requestChannel) + log.Debug("issuing certificates ...") // HandleCertificates(&requestChannel) + log.Debug("revoking certificates ...") // RevokeCertificates(&requestChannel) crlCheck++ if crlCheck%100 == 0 { - log.Debug("refresh CRLs ...") - // RefreshCRLs(&requestChannel) + log.Debug("refresh CRLs ...") // RefreshCRLs(&requestChannel) } if requestChannel.IsClosed() { return } + log.Debug("send NUL request to keep connection open") requestChannel.C <- datastructures.NewNulRequest() log.Debug("sleep for 2.7 seconds") - time.Sleep(2700 * time.Millisecond) + time.Sleep(mainLoopSleep * time.Millisecond) } } diff --git a/client/processing/process.go b/client/processing/process.go index 4617b6c..f36a896 100644 --- a/client/processing/process.go +++ b/client/processing/process.go @@ -16,6 +16,7 @@ func Process(response *datastructures.SignerResponse) (err error) { switch response.Action { case shared.ActionNul: logrus.Trace("received response for NUL request") + return default: return fmt.Errorf("unsupported action in response 0x%x", response.Action) diff --git a/client/protocol/protocol.go b/client/protocol/protocol.go index 5cf2df2..f61e409 100644 --- a/client/protocol/protocol.go +++ b/client/protocol/protocol.go @@ -2,6 +2,7 @@ package protocol import ( "bytes" + "errors" "fmt" "io" "sync" @@ -13,17 +14,24 @@ import ( "git.cacert.org/cacert-gosigner/shared" ) +const ( + waitForHeader = 20 + waitForData = 5 + waitForHandShake = 120 + bufferSize = 2048 +) + type SignerProtocolHandler interface { io.Closer HandleSignerProtocol() error } -type signerProtocolConfig struct { +type SignerProtocolConfig struct { BufferSize int } -func NewSignerProtocolConfig() *signerProtocolConfig { - return &signerProtocolConfig{BufferSize: 2048} +func NewSignerProtocolConfig() *SignerProtocolConfig { + return &SignerProtocolConfig{BufferSize: bufferSize} } type SignerProtocolRequestChannel struct { @@ -39,6 +47,7 @@ func NewSignerProtocolRequestChannel() *SignerProtocolRequestChannel { func (rc *SignerProtocolRequestChannel) SafeClose() { rc.mutex.Lock() defer rc.mutex.Unlock() + if !rc.closed { close(rc.C) rc.closed = true @@ -48,6 +57,7 @@ func (rc *SignerProtocolRequestChannel) SafeClose() { func (rc *SignerProtocolRequestChannel) IsClosed() bool { rc.mutex.Lock() defer rc.mutex.Unlock() + return rc.closed } @@ -55,7 +65,7 @@ type protocolHandler struct { requestChannel *SignerProtocolRequestChannel responseChannel *chan *datastructures.SignerResponse serialConnection io.ReadWriteCloser - config *signerProtocolConfig + config *SignerProtocolConfig } type UnExpectedAcknowledgeByte struct { @@ -75,6 +85,7 @@ func (e UnExpectedAcknowledgeByte) Error() string { func (ph *protocolHandler) Close() error { close(*ph.responseChannel) ph.requestChannel.SafeClose() + return nil } @@ -82,7 +93,7 @@ func NewProtocolHandler( requests *SignerProtocolRequestChannel, response *chan *datastructures.SignerResponse, serialConnection io.ReadWriteCloser, - config *signerProtocolConfig, + config *SignerProtocolConfig, ) SignerProtocolHandler { return &protocolHandler{ requestChannel: requests, @@ -93,85 +104,115 @@ func NewProtocolHandler( } func (ph *protocolHandler) HandleSignerProtocol() error { - for { - select { - case request := <-ph.requestChannel.C: - log.Tracef("handle request %+v", request) - var err error - var lengthBytes, responseBytes *[]byte - var checksum byte + for request := range ph.requestChannel.C { + log.Tracef("handle request %+v", request) - if err = ph.sendHandshake(); err != nil { - switch err.(type) { - case UnExpectedAcknowledgeByte: - log.Errorf("unexpected handshake byte: 0x%x", err.(UnExpectedAcknowledgeByte).ResponseByte) - // TODO drain input - } - return err + var ( + err error + lengthBytes, responseBytes *[]byte + checksum byte + ) + + if err = ph.sendHandshake(); err != nil { + var e *UnExpectedHandshakeByte + if errors.As(err, &e) { + log.Errorf("unexpected handshake byte: 0x%x", e.ResponseByte) } - requestBytes := request.Serialize() - if err = ph.sendRequest(&requestBytes); err != nil { - return err - } - if err = ph.waitForResponseHandshake(); err != nil { - return err - } - if lengthBytes, responseBytes, checksum, err = ph.readResponse(); err != nil { - return err - } - response, err := datastructures.SignerResponseFromData(*lengthBytes, *responseBytes, checksum) - if err != nil { - return err - } - *ph.responseChannel <- response + + return err } + + requestBytes := request.Serialize() + + if err = ph.sendRequest(&requestBytes); err != nil { + return err + } + + if err = ph.waitForResponseHandshake(); err != nil { + return err + } + + if lengthBytes, responseBytes, checksum, err = ph.readResponse(); err != nil { + return err + } + + response, err := datastructures.SignerResponseFromData(*lengthBytes, *responseBytes, checksum) + if err != nil { + return fmt.Errorf("could not create response: %w", err) + } + + *ph.responseChannel <- response } + + return nil } -func (ph *protocolHandler) sendHandshake() (err error) { - var bytesWritten, bytesRead int - if bytesWritten, err = ph.serialConnection.Write([]byte{shared.HandshakeByte}); err != nil { - return - } else { - log.Tracef("wrote %d bytes of handshake info", bytesWritten) +func (ph *protocolHandler) sendHandshake() error { + var ( + bytesWritten, bytesRead int + err error + ) + + data := make([]byte, 0) + + bytesWritten, err = ph.serialConnection.Write([]byte{shared.HandshakeByte}) + if err != nil { + return fmt.Errorf("could not send handshake byte: %w", err) } - data := make([]byte, 1) - if bytesRead, err = ph.serialConnection.Read(data); err != nil { - return + + log.Tracef("wrote %d bytes of handshake info", bytesWritten) + + bytesRead, err = ph.serialConnection.Read(data) + if err != nil { + return fmt.Errorf("could not receieve ACK byte: %w", err) } + log.Tracef("%d bytes read", bytesRead) + if bytesRead != 1 || data[0] != shared.AckByte { log.Warnf("received invalid handshake byte 0x%x", data[0]) + return UnExpectedAcknowledgeByte{data[0]} } - return + + return nil } func (ph *protocolHandler) sendRequest(requestBytes *[]byte) error { + var ( + n int + err error + ) + for { - if length, err := ph.serialConnection.Write(*requestBytes); err != nil { - return err - } else { - log.Tracef("wrote %d request bytes", length) - } - - if length, err := ph.serialConnection.Write([]byte{ - datastructures.CalculateXorCheckSum([][]byte{*requestBytes}), - }); err != nil { - return err - } else { - log.Tracef("wrote %d checksum bytes", length) - } - - if length, err := ph.serialConnection.Write([]byte(shared.MagicTrailer)); err != nil { - return err - } else { - log.Tracef("wrote %d trailer bytes", length) - } - header, err := shared.ReceiveBytes(ph.serialConnection, 1, 20*time.Second) + n, err = ph.serialConnection.Write(*requestBytes) if err != nil { - return err + return fmt.Errorf("could not send request bytes: %w", err) } + + log.Tracef("wrote %d request bytes", n) + + n, err = ph.serialConnection.Write([]byte{ + datastructures.CalculateXorCheckSum([][]byte{*requestBytes}), + }) + if err != nil { + return fmt.Errorf("could not send checksum byte: %w", err) + } + + log.Tracef("wrote %d checksum bytes", n) + + n, err = ph.serialConnection.Write([]byte(shared.MagicTrailer)) + if err != nil { + return fmt.Errorf("could not send trailer bytes: %w", err) + } + + log.Tracef("wrote %d trailer bytes", n) + + header, err := shared.ReceiveBytes(ph.serialConnection, 1, waitForHeader*time.Second) + if err != nil { + return fmt.Errorf("could not read header bytes: %w", err) + } + switch header[0] { case shared.AckByte: return nil @@ -182,62 +223,78 @@ func (ph *protocolHandler) sendRequest(requestBytes *[]byte) error { } } -func (ph *protocolHandler) waitForResponseHandshake() (err error) { - data, err := shared.ReceiveBytes(ph.serialConnection, 1, 120*time.Second) +func (ph *protocolHandler) waitForResponseHandshake() error { + data, err := shared.ReceiveBytes(ph.serialConnection, 1, waitForHandShake*time.Second) if err != nil { - return err - } - if len(data) != 1 || data[0] != shared.HandshakeByte { - log.Warnf("received invalid handshake byte 0x%x", data[0]) - return UnExpectedHandshakeByte{data[0]} - } - if err = shared.SendBytes(ph.serialConnection, []byte{shared.AckByte}); err != nil { - return + return fmt.Errorf("could not receive handshake byte: %w", err) } - return + if len(data) != 1 || data[0] != shared.HandshakeByte { + log.Warnf("received invalid handshake byte 0x%x", data[0]) + + return UnExpectedHandshakeByte{data[0]} + } + + if err = shared.SendBytes(ph.serialConnection, []byte{shared.AckByte}); err != nil { + return fmt.Errorf("could not send ACK: %w", err) + } + + return nil } func (ph *protocolHandler) readResponse() (*[]byte, *[]byte, byte, error) { dataLength := -1 - var lengthBuffer = bytes.NewBuffer(make([]byte, 0)) - var byteBuffer = bytes.NewBuffer(make([]byte, 0)) + + var ( + lengthBuffer = bytes.NewBuffer(make([]byte, 0)) + byteBuffer = bytes.NewBuffer(make([]byte, 0)) + ) for { - readBuffer, err := shared.ReceiveBytes(ph.serialConnection, ph.config.BufferSize, 5*time.Second) + readBuffer, err := shared.ReceiveBytes(ph.serialConnection, ph.config.BufferSize, waitForData*time.Second) if err != nil { - return nil, nil, 0, err + return nil, nil, 0, fmt.Errorf("could not receive response: %w", err) } + bytesRead := len(readBuffer) if bytesRead > 0 { byteBuffer.Write(readBuffer[0:bytesRead]) + for _, b := range readBuffer { - if lengthBuffer.Len() < 3 { + if lengthBuffer.Len() < shared.LengthFieldSize { lengthBuffer.WriteByte(b) } else { break } } } - if dataLength < 0 && lengthBuffer.Len() == 3 { + + if dataLength < 0 && lengthBuffer.Len() == shared.LengthFieldSize { dataLength = datastructures.Decode24BitLength(lengthBuffer.Bytes()) log.Tracef("expect to read %d data bytes", dataLength) } - if dataLength == byteBuffer.Len()-4-len(shared.MagicTrailer) { + + trailerOffset := shared.LengthFieldSize + shared.CheckSumFieldSize + + if dataLength == byteBuffer.Len()-trailerOffset-len(shared.MagicTrailer) { allBytes := byteBuffer.Bytes() - trailer := string(allBytes[4+dataLength:]) + + trailer := string(allBytes[trailerOffset+dataLength:]) if trailer != shared.MagicTrailer { return nil, nil, 0, fmt.Errorf("invalid trailer bytes: %v", trailer) } - lengthBytes := allBytes[0:3] - dataBytes := allBytes[3 : 3+dataLength] - checkSum := allBytes[3+dataLength] + + lengthBytes := allBytes[0:shared.LengthFieldSize] + dataBytes := allBytes[shared.LengthFieldSize : shared.LengthFieldSize+dataLength] + checkSum := allBytes[shared.LengthFieldSize+dataLength] + calculatedChecksum := datastructures.CalculateXorCheckSum([][]byte{lengthBytes, dataBytes}) if calculatedChecksum != checkSum { return nil, nil, 0, fmt.Errorf("calculated checksum mismatch 0x%x vs 0x%x", calculatedChecksum, checkSum) } + if err := shared.SendBytes(ph.serialConnection, []byte{shared.AckByte}); err != nil { - return nil, nil, 0, err + return nil, nil, 0, fmt.Errorf("could not send ACK byte: %w", err) } return &lengthBytes, &dataBytes, checkSum, nil diff --git a/cmd/signer/main.go b/cmd/signer/main.go index dcf94b3..9df8c0f 100644 --- a/cmd/signer/main.go +++ b/cmd/signer/main.go @@ -13,12 +13,13 @@ import ( func main() { var ( - address string - baudRate int + address string + baudRate, dataBits int ) flag.StringVar(&address, "a", "/dev/ttyUSB0", "address") flag.IntVar(&baudRate, "b", 115200, "baud rate") + flag.IntVar(&dataBits, "d", 8, "data bits") flag.Parse() log.SetFormatter(&log.TextFormatter{ @@ -29,15 +30,18 @@ func main() { serialMode := &serial.Mode{ BaudRate: baudRate, - DataBits: 8, + DataBits: dataBits, 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") defer func() { @@ -45,6 +49,7 @@ func main() { if err != nil { log.Fatalf("could not close port: %v", err) } + log.Info("serial port closed") }() diff --git a/datastructures/common.go b/datastructures/common.go index 52743ee..1f2579e 100644 --- a/datastructures/common.go +++ b/datastructures/common.go @@ -7,6 +7,7 @@ const signerTimeFormat = "010203042006.05" func Encode24BitLength(data []byte) []byte { lengthBytes := make([]byte, 4) binary.BigEndian.PutUint32(lengthBytes, uint32(len(data))) + return lengthBytes[1:] } @@ -17,10 +18,12 @@ func Decode24BitLength(bytes []byte) int { func CalculateXorCheckSum(byteBlocks [][]byte) byte { var result byte = 0x0 + for _, byteBlock := range byteBlocks { for _, b := range byteBlock { result ^= b } } + return result } diff --git a/datastructures/signerrequest.go b/datastructures/signerrequest.go index 1c7a119..4331e7f 100644 --- a/datastructures/signerrequest.go +++ b/datastructures/signerrequest.go @@ -9,13 +9,21 @@ import ( "git.cacert.org/cacert-gosigner/shared" ) +const ( + headerPosSystem = 2 + headerPosRoot = 3 + headerPosProfile = 4 + headerPosSignatureAlgorithm = 5 + headerPosDay = 6 +) + type SignerRequest struct { Version uint8 Action shared.Action - System shared.CryptoSystemId - Root shared.CryptoSystemRootId - Profile shared.CertificateProfileId - MdAlgorithm shared.MessageDigestAlgorithmId + System shared.CryptoSystemID + Root shared.CryptoSystemRootID + Profile shared.CertificateProfileID + MdAlgorithm shared.SignatureAlgorithmID Days uint16 Spkac uint8 Content1 []byte @@ -24,33 +32,33 @@ type SignerRequest struct { } func SignerRequestFromData(blockData []byte) (*SignerRequest, error) { - headerLength := Decode24BitLength(blockData[0:3]) - headerBytes := blockData[3 : 3+headerLength] + headerLength := Decode24BitLength(blockData[0:shared.LengthFieldSize]) + headerBytes := blockData[shared.LengthFieldSize : shared.LengthFieldSize+headerLength] - contentBytes := blockData[3+headerLength:] - content1Length := Decode24BitLength(contentBytes[0:3]) - content1 := contentBytes[3 : 3+content1Length] + contentBytes := blockData[shared.LengthFieldSize+headerLength:] + contentLen := Decode24BitLength(contentBytes[0:shared.LengthFieldSize]) + content := contentBytes[shared.LengthFieldSize : shared.LengthFieldSize+contentLen] - content2Offset := 3 + content1Length - content2Length := Decode24BitLength(contentBytes[content2Offset : content2Offset+3]) - content2 := contentBytes[3+content2Offset : 3+content2Offset+content2Length] + argument1Offset := shared.LengthFieldSize + contentLen + argument1Len := Decode24BitLength(contentBytes[argument1Offset : argument1Offset+shared.LengthFieldSize]) + argument1 := contentBytes[shared.LengthFieldSize+argument1Offset : shared.LengthFieldSize+argument1Offset+argument1Len] - content3Offset := 3 + content2Offset + content2Length - content3Length := Decode24BitLength(contentBytes[content3Offset : content3Offset+3]) - content3 := contentBytes[3+content3Offset : 3+content3Offset+content3Length] + argument2Offset := shared.LengthFieldSize + argument1Offset + argument1Len + argument2Len := Decode24BitLength(contentBytes[argument2Offset : argument2Offset+shared.LengthFieldSize]) + argument2 := contentBytes[shared.LengthFieldSize+argument2Offset : shared.LengthFieldSize+argument2Offset+argument2Len] return &SignerRequest{ - Version: headerBytes[0], - Action: shared.Action(headerBytes[1]), - System: shared.CryptoSystemId(headerBytes[2]), - Root: shared.CryptoSystemRootId(headerBytes[3]), - Profile: shared.CertificateProfileId(headerBytes[4]), - MdAlgorithm: shared.MessageDigestAlgorithmId(headerBytes[5]), - Days: binary.BigEndian.Uint16([]byte{headerBytes[6], headerBytes[7]}), + Version: headerBytes[headerPosVersion], + Action: shared.Action(headerBytes[headerPosAction]), + System: shared.CryptoSystemID(headerBytes[headerPosSystem]), + Root: shared.CryptoSystemRootID(headerBytes[headerPosRoot]), + Profile: shared.CertificateProfileID(headerBytes[headerPosProfile]), + MdAlgorithm: shared.SignatureAlgorithmID(headerBytes[headerPosSignatureAlgorithm]), + Days: binary.BigEndian.Uint16(headerBytes[headerPosDay : headerPosDay+1]), Spkac: headerBytes[8], - Content1: content1, - Content2: content2, - Content3: content3, + Content1: content, + Content2: argument1, + Content3: argument2, }, nil } @@ -76,6 +84,7 @@ func (r *SignerRequest) Serialize() []byte { Encode24BitLength(content2Bytes), content2Bytes, Encode24BitLength(content3Bytes), content3Bytes, }, []byte{}) + return bytes.Join([][]byte{Encode24BitLength(blockBytes), blockBytes}, []byte{}) } @@ -97,9 +106,11 @@ func (r *SignerRequest) String() string { } func shorten(original []byte) []byte { - if len(original) > 20 { - return original[:20] + const maxLength = 20 + if len(original) > maxLength { + return original[:maxLength] } + return original } diff --git a/datastructures/signerresponse.go b/datastructures/signerresponse.go index c233d67..ca690ca 100644 --- a/datastructures/signerresponse.go +++ b/datastructures/signerresponse.go @@ -8,6 +8,16 @@ import ( "git.cacert.org/cacert-gosigner/shared" ) +const ( + headerPosVersion = 0 + headerPosAction = 1 + headerPosReserved1 = 2 + headerPosReserved2 = 3 + blockPosContent = 0 + blockPosArgument1 = 1 + blockPosArgument2 = 2 +) + type SignerResponse struct { Version uint8 Action shared.Action @@ -19,23 +29,25 @@ type SignerResponse struct { } func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte) (*SignerResponse, error) { - if len(blockData) < 3 { + if len(blockData) < shared.LengthFieldSize { return nil, errors.New("begin of structure corrupt") } offset := 0 - headerLength := Decode24BitLength(blockData[offset : offset+3]) - offset += 3 + headerLength := Decode24BitLength(blockData[offset : offset+shared.LengthFieldSize]) + offset += shared.LengthFieldSize headerBytes := blockData[offset : offset+headerLength] offset += headerLength - content := make([][]byte, 3) + content := make([][]byte, 0) + for offset < len(blockData) { dataLength := Decode24BitLength(blockData[offset : offset+3]) - if len(blockData)-3 < dataLength { + if len(blockData)-shared.LengthFieldSize < dataLength { return nil, errors.New("structure cut off") } - offset += 3 + + offset += shared.LengthFieldSize content = append(content, blockData[offset:offset+dataLength]) offset += dataLength } @@ -46,13 +58,13 @@ func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte) } return &SignerResponse{ - Version: headerBytes[0], - Action: shared.Action(headerBytes[1]), - Reserved1: headerBytes[2], - Reserved2: headerBytes[3], - Content: content[0], - Argument1: content[1], - Argument2: content[2], + Version: headerBytes[headerPosVersion], + Action: shared.Action(headerBytes[headerPosAction]), + Reserved1: headerBytes[headerPosReserved1], + Reserved2: headerBytes[headerPosReserved2], + Content: content[blockPosContent], + Argument1: content[blockPosArgument1], + Argument2: content[blockPosArgument2], }, nil } @@ -64,6 +76,7 @@ func (r SignerResponse) Serialize() []byte { Encode24BitLength(r.Argument1), r.Argument1, Encode24BitLength(r.Argument2), r.Argument2, }, []byte{}) + return bytes.Join([][]byte{Encode24BitLength(blockBytes), blockBytes}, []byte{}) } diff --git a/shared/io.go b/shared/io.go index 8ae89d7..9fece70 100644 --- a/shared/io.go +++ b/shared/io.go @@ -13,12 +13,15 @@ import ( func ReceiveBytes(port io.Reader, count int, timeout time.Duration) ([]byte, error) { readCh := make(chan []byte, 1) errCh := make(chan error, 1) + go func() { buffer := bytes.NewBuffer([]byte{}) + for remainder := count; remainder > 0; { data := make([]byte, remainder) if readBytes, err := port.Read(data); err != nil { errCh <- err + return } else if readBytes > 0 { buffer.Write(data[0:readBytes]) @@ -26,6 +29,7 @@ func ReceiveBytes(port io.Reader, count int, timeout time.Duration) ([]byte, err log.Tracef("%d bytes read, remaining %d", readBytes, remainder) } } + readCh <- buffer.Bytes() close(readCh) }() @@ -38,19 +42,24 @@ func ReceiveBytes(port io.Reader, count int, timeout time.Duration) ([]byte, err return nil, err case data := <-readCh: log.Tracef("received %d bytes from channel", len(data)) + if data == nil { break } + buffer.Write(data) } + return buffer.Bytes(), nil } func SendBytes(port io.Writer, data []byte) error { - if bytesWritten, err := port.Write(data); err != nil { - return err - } else { - log.Tracef("wrote %d bytes", bytesWritten) + n, err := port.Write(data) + if err != nil { + return fmt.Errorf("could not send bytes: %w", err) } + + log.Tracef("wrote %d bytes", n) + return nil } diff --git a/shared/shared.go b/shared/shared.go index 637223c..e69acaa 100644 --- a/shared/shared.go +++ b/shared/shared.go @@ -19,7 +19,7 @@ type Action byte const ( ActionNul = Action(0) ActionSign = Action(1) - ActionRevoke = Action(2) + ActionRevoke = Action(2) // nolint:gomnd ) func (a Action) String() string { @@ -35,10 +35,10 @@ func (a Action) String() string { } } -type CryptoSystemRootId byte +type CryptoSystemRootID byte -type CertificateProfileId byte +type CertificateProfileID byte -type MessageDigestAlgorithmId byte +type SignatureAlgorithmID byte -type CryptoSystemId byte +type CryptoSystemID byte diff --git a/signer/command_processor.go b/signer/command_processor.go index bd099e1..3cbdf20 100644 --- a/signer/command_processor.go +++ b/signer/command_processor.go @@ -19,8 +19,8 @@ import ( "git.cacert.org/cacert-gosigner/datastructures" "git.cacert.org/cacert-gosigner/shared" - "git.cacert.org/cacert-gosigner/signer/openpgp_ops" - "git.cacert.org/cacert-gosigner/signer/x509_ops" + "git.cacert.org/cacert-gosigner/signer/openpgpops" + "git.cacert.org/cacert-gosigner/signer/x509ops" ) type CommandProcessorSettings struct { @@ -34,7 +34,7 @@ type CommandProcessorSettings struct { // functionality. type CommandProcessor struct { Settings *CommandProcessorSettings - CryptoSystems map[shared.CryptoSystemId]*CryptoSystem + CryptoSystems map[shared.CryptoSystemID]*CryptoSystem } // Process the signer request @@ -56,11 +56,11 @@ func (p *CommandProcessor) Process(command *datastructures.SignerRequest) ( case shared.ActionRevoke: return p.handleRevokeAction(command) default: - return nil, errors.New(fmt.Sprintf( + return nil, fmt.Errorf( "unsupported Action 0x%02x %s", int(command.Action), command.Action, - )) + ) } } @@ -69,10 +69,12 @@ func (*CommandProcessor) handleNulAction(command *datastructures.SignerRequest) 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( @@ -101,6 +103,7 @@ func (p *CommandProcessor) handleSignAction( if err != nil { return nil, err } + log.Debugf("identified id system: %s", idSystem) switch command.System { @@ -109,19 +112,21 @@ func (p *CommandProcessor) handleSignAction( san := command.Content2 subject := command.Content3 - if content, err := p.signX509Certificate(idSystem, command.Days, command.Spkac, request, san, subject); err != nil { - return nil, err - } else { - return datastructures.NewSignResponse(command.Version, content), nil + content, err := p.signX509Certificate(idSystem, command.Days, command.Spkac, request, san, subject) + if err != nil { + return nil, fmt.Errorf("could not sign X.509 certificate: %w", err) } + + return datastructures.NewSignResponse(command.Version, content), nil case CsOpenPGP: pubKey := command.Content1 - if content, err := p.signOpenpgpKey(idSystem, command.Days, pubKey); err != nil { - return nil, err - } else { - return datastructures.NewSignResponse(command.Version, content), nil + content, err := p.signOpenpgpKey(idSystem, command.Days, pubKey) + if err != nil { + return nil, fmt.Errorf("could not sign OpenPGP key: %w", err) } + + return datastructures.NewSignResponse(command.Version, content), nil default: return nil, fmt.Errorf("sign not implemented for crypto system %s", idSystem.System) } @@ -144,71 +149,78 @@ func (p *CommandProcessor) handleRevokeAction( if err != nil { return nil, err } + log.Debugf("identified id system: %+v", idSystem) switch command.System { case CsX509: request := command.Content1 clientHash := command.Content3 - if content, err := p.revokeX509(idSystem, request, clientHash); err != nil { - return nil, err - } else { - return datastructures.NewRevokeResponse(command.Version, content), nil + + content, err := p.revokeX509(idSystem, request, clientHash) + if err != nil { + return nil, fmt.Errorf("could not revoke X.509 certificate: %w", err) } + + return datastructures.NewRevokeResponse(command.Version, content), nil default: return nil, fmt.Errorf("revoke not implemented for crypto system %s", idSystem.System) } } -type IdSystemParameters struct { +type IDSystemParams struct { System *CryptoSystem Root interface{} Profile interface{} MessageDigestAlgorithm interface{} } -func (s *IdSystemParameters) String() string { +func (s *IDSystemParams) String() string { return fmt.Sprintf("%s r:%s p:%s m:%s", s.System, s.Root, s.Profile, s.MessageDigestAlgorithm) } func (p *CommandProcessor) checkIdentitySystem( - systemId shared.CryptoSystemId, - rootId shared.CryptoSystemRootId, - profileId shared.CertificateProfileId, - algorithmId shared.MessageDigestAlgorithmId, -) (*IdSystemParameters, error) { - cryptoSystem, ok := p.CryptoSystems[systemId] + systemID shared.CryptoSystemID, + rootID shared.CryptoSystemRootID, + profileID shared.CertificateProfileID, + algorithmID shared.SignatureAlgorithmID, +) (*IDSystemParams, error) { + cryptoSystem, ok := p.CryptoSystems[systemID] if !ok { return nil, fmt.Errorf( "unsupported crypto system %d", - systemId, + systemID, ) } - root, ok := p.CryptoSystems[systemId].Roots[rootId] + + root, ok := p.CryptoSystems[systemID].Roots[rootID] if !ok { return nil, fmt.Errorf( "unsupported root %d for crypto system %s", - rootId, + rootID, cryptoSystem, ) } - profile, ok := p.CryptoSystems[systemId].Profiles[profileId] + + profile, ok := p.CryptoSystems[systemID].Profiles[profileID] if !ok { return nil, fmt.Errorf( "invalid profile %d for crypto system %s", - profileId, + profileID, cryptoSystem, ) } - mdAlgorithm, ok := p.CryptoSystems[systemId].DigestAlgorithms[algorithmId] + + mdAlgorithm, ok := p.CryptoSystems[systemID].DigestAlgorithms[algorithmID] if !ok { return nil, fmt.Errorf( "unsupported digest algorithm %d for crypto system %s", - algorithmId, + algorithmID, cryptoSystem, ) } - return &IdSystemParameters{ + + return &IDSystemParams{ System: cryptoSystem, Root: root, Profile: profile, @@ -216,8 +228,8 @@ func (p *CommandProcessor) checkIdentitySystem( }, nil } -func (p *CommandProcessor) revokeX509(system *IdSystemParameters, request []byte, clientHash []byte) ([]byte, error) { - x509Root := system.Root.(*x509_ops.Root) +func (p *CommandProcessor) revokeX509(system *IDSystemParams, request []byte, clientHash []byte) ([]byte, error) { + x509Root := system.Root.(*x509ops.Root) signatureAlgorithm := system.MessageDigestAlgorithm.(x509.SignatureAlgorithm) log.Debugf("revoke X.509 for root %s", x509Root) @@ -232,17 +244,19 @@ func (p *CommandProcessor) revokeX509(system *IdSystemParameters, request []byte if len(request) > 0 { _, err = x509Root.RevokeCertificate(request) if err != nil { - return nil, fmt.Errorf("could not revoke certificate / create CRL: %v", err) + return nil, fmt.Errorf("could not revoke certificate / create CRL: %w", err) } } crlBytes, newHash, err = x509Root.GenerateCrl(signatureAlgorithm) if err != nil { - return nil, fmt.Errorf("could not generate a new CRL for root %s: %v", x509Root, err) + return nil, fmt.Errorf("could not generate a new CRL for root %s: %w", x509Root, err) } + log.Debugf("crlBytes: %d", len(crlBytes)) var content []byte + oldCrlFile := x509Root.GetCrlFileName(string(clientHash)) newCrlFile := x509Root.GetCrlFileName(hex.EncodeToString(newHash[:])) @@ -254,8 +268,10 @@ func (p *CommandProcessor) revokeX509(system *IdSystemParameters, request []byte if err != nil { log.Warnf("could not generate xdelta: %v", err) } + log.Tracef("xdelta produced %d bytes", len(content)) } + if content == nil { content = pem.EncodeToMemory(&pem.Block{ Type: "X509 CRL", @@ -270,50 +286,56 @@ func (p *CommandProcessor) revokeX509(system *IdSystemParameters, request []byte func (p *CommandProcessor) buildXDelta(oldFile string, newFile string) ([]byte, error) { patchFile, err := ioutil.TempFile(os.TempDir(), "*.patch") if err != nil { - return nil, fmt.Errorf("could not create temporary file for patch: %v", err) + return nil, fmt.Errorf("could not create temporary file for patch: %w", err) } + + patchName := patchFile.Name() + defer func() { - if err := os.Remove(patchFile.Name()); err != nil { - log.Warnf("could not remove temporary file %s: %v", patchFile.Name(), err) + if err := os.Remove(patchName); err != nil { + log.Warnf("could not remove temporary file %s: %v", patchName, err) } }() + if err = patchFile.Close(); err != nil { - return nil, fmt.Errorf("could not close temporary file: %v", err) + return nil, fmt.Errorf("could not close temporary file: %w", err) } + buf := bytes.NewBuffer([]byte{}) - cmd := exec.Command(p.Settings.XDeltaPath, "delta", oldFile, newFile, patchFile.Name()) + + // #nosec G204 no parameters are based on user input + cmd := exec.Command(p.Settings.XDeltaPath, "delta", oldFile, newFile, patchName) cmd.Stdout = buf cmd.Stderr = buf + err = cmd.Run() if err != nil { - switch err.(type) { - case *exec.ExitError: - if err.(*exec.ExitError).ExitCode() == 1 { - // xdelta delta exits with status code 1 if a delta has been found - break - } + var e *exec.ExitError + if !errors.As(err, &e) || e.ExitCode() != 1 { + // xdelta delta exits with status code 1 if a delta has been found return nil, fmt.Errorf( - "xdelta command '%s' did not work correctly: %v\noutput was:\n%s", - strings.Join(cmd.Args, " "), - err, - buf.String(), - ) - default: - return nil, fmt.Errorf( - "xdelta command '%s' did not work correctly: %v\noutput was:\n%s", + "xdelta command '%s' did not work correctly: %w\noutput was:\n%s", strings.Join(cmd.Args, " "), err, buf.String(), ) } } - return ioutil.ReadFile(patchFile.Name()) + + return ioutil.ReadFile(patchName) } -func (p *CommandProcessor) signX509Certificate(system *IdSystemParameters, days uint16, spkac uint8, request []byte, san []byte, subject []byte) ([]byte, error) { - x509Root := system.Root.(*x509_ops.Root) +func (p *CommandProcessor) signX509Certificate( + system *IDSystemParams, + days uint16, + spkac uint8, + request []byte, + san []byte, + subject []byte, +) ([]byte, error) { + x509Root := system.Root.(*x509ops.Root) signatureAlgorithm := system.MessageDigestAlgorithm.(x509.SignatureAlgorithm) - profile := system.Profile.(*x509_ops.Profile) + profile := system.Profile.(*x509ops.Profile) log.Debugf( "sign X.509 certificate for root %s using profile %s and signature algorithm %s", @@ -330,7 +352,7 @@ func (p *CommandProcessor) signX509Certificate(system *IdSystemParameters, days content, err := x509Root.SignCertificate( profile, signatureAlgorithm, - &x509_ops.SigningRequestParameters{ + &x509ops.SigningRequestParameters{ Request: request, Subject: subject, SubjectAlternativeNames: san, @@ -339,21 +361,21 @@ func (p *CommandProcessor) signX509Certificate(system *IdSystemParameters, days }, ) if err != nil { - return nil, fmt.Errorf("could not sign X.509 CSR with root %s and profile %s: %v", x509Root, profile, err) + return nil, fmt.Errorf("could not sign X.509 CSR with root %s and profile %s: %w", x509Root, profile, err) } return content, nil } -func (p *CommandProcessor) signOpenpgpKey(system *IdSystemParameters, days uint16, pubKey []byte) ([]byte, error) { - openPgpRoot := system.Root.(*openpgp_ops.OpenPGPRoot) +func (p *CommandProcessor) signOpenpgpKey(system *IDSystemParams, days uint16, pubKey []byte) ([]byte, error) { + openPgpRoot := system.Root.(*openpgpops.OpenPGPRoot) signatureAlgorithm := system.MessageDigestAlgorithm.(crypto.Hash) - log.Debugf("sign openpgp for root %s", openPgpRoot) + log.Debugf("sign openpgpops for root %s", openPgpRoot) content, err := openPgpRoot.SignPublicKey(pubKey, signatureAlgorithm, days) if err != nil { - return nil, fmt.Errorf("could not sign openpgp public key with root %s: %v", openPgpRoot, err) + return nil, fmt.Errorf("could not sign openpgpops public key with root %s: %w", openPgpRoot, err) } return content, nil @@ -364,18 +386,21 @@ func NewCommandProcessorSettings() *CommandProcessorSettings { if !ok { caBasedir = "." } + gpgKeyringDir, ok := os.LookupEnv("SIGNER_GPG_KEYRING_DIR") if !ok { gpgKeyringDir = "." } - gpgUidEmail, ok := os.LookupEnv("SIGNER_GPG_ID") + + gpgUIDEmail, ok := os.LookupEnv("SIGNER_GPG_ID") if !ok { - gpgUidEmail = "gpg@cacert.org" + gpgUIDEmail = "gpg@cacert.org" } + return &CommandProcessorSettings{ CABaseDir: caBasedir, OpenPGPKeyRingDir: gpgKeyringDir, - OpenPGPUidEmail: gpgUidEmail, + OpenPGPUidEmail: gpgUIDEmail, XDeltaPath: "/usr/bin/xdelta", } } diff --git a/signer/common/helpers.go b/signer/common/helpers.go deleted file mode 100644 index 78c520f..0000000 --- a/signer/common/helpers.go +++ /dev/null @@ -1,17 +0,0 @@ -package common - -import ( - "fmt" - "math/big" - "strconv" - "strings" -) - -func StringAsBigInt(data []byte) (*big.Int, error) { - dataString := strings.TrimSpace(string(data)) - parseInt, err := strconv.ParseInt(dataString, 16, 64) - if err != nil { - return nil, fmt.Errorf("could not parse %s as big int: %v", dataString, err) - } - return big.NewInt(parseInt), nil -} diff --git a/signer/crypto_system.go b/signer/crypto_system.go index 23d1220..a4da3bc 100644 --- a/signer/crypto_system.go +++ b/signer/crypto_system.go @@ -6,9 +6,9 @@ import ( type CryptoSystem struct { Name string - Roots map[shared.CryptoSystemRootId]interface{} - Profiles map[shared.CertificateProfileId]interface{} - DigestAlgorithms map[shared.MessageDigestAlgorithmId]interface{} + Roots map[shared.CryptoSystemRootID]interface{} + Profiles map[shared.CertificateProfileID]interface{} + DigestAlgorithms map[shared.SignatureAlgorithmID]interface{} } func (system CryptoSystem) String() string { diff --git a/signer/openpgp_ops/operations.go b/signer/openpgpops/openpgpops.go similarity index 81% rename from signer/openpgp_ops/operations.go rename to signer/openpgpops/openpgpops.go index e7f2ce6..44cb3c1 100644 --- a/signer/openpgp_ops/operations.go +++ b/signer/openpgpops/openpgpops.go @@ -1,8 +1,9 @@ -package openpgp_ops +package openpgpops import ( "bytes" "crypto" + "errors" "fmt" "os" "time" @@ -13,6 +14,8 @@ import ( "golang.org/x/crypto/openpgp/packet" ) +const hoursInADay = 24 + type OpenPGPRoot struct { Name string SecretKeyRing string @@ -22,24 +25,27 @@ type OpenPGPRoot struct { func (r *OpenPGPRoot) SignPublicKey(pubKey []byte, algorithm crypto.Hash, days uint16) ([]byte, error) { signingKey, err := r.findSigningKey(r.Identifier) if err != nil { - return nil, fmt.Errorf("could not find a signing key matching %s: %v", r.Identifier, err) + return nil, fmt.Errorf("could not find a signing key matching %s: %w", r.Identifier, err) } pubKeyRing, err := openpgp.ReadKeyRing(bytes.NewReader(pubKey)) if err != nil { - return nil, fmt.Errorf("could not read openpgp keyring: %v", err) + return nil, fmt.Errorf("could not read openpgpops keyring: %w", err) } output := bytes.NewBuffer([]byte{}) + armorOutput, err := armor.Encode(output, "PGP PUBLIC KEY BLOCK", map[string]string{}) if err != nil { - return nil, fmt.Errorf("could not create ASCII armor wrapper for openpgp output: %v", err) + return nil, fmt.Errorf("could not create ASCII armor wrapper for openpgpops output: %w", err) } for _, pe := range pubKeyRing { log.Tracef("found %+v", pe.PrimaryKey.KeyIdString()) + for _, i := range pe.Identities { expiry := calculateExpiry(i, days) + if !i.SelfSignature.KeyExpired(time.Now()) { sig := &packet.Signature{ SigType: packet.SigTypeGenericCert, @@ -52,14 +58,16 @@ func (r *OpenPGPRoot) SignPublicKey(pubKey []byte, algorithm crypto.Hash, days u if err := sig.SignUserId(i.Name, pe.PrimaryKey, signingKey.PrivateKey, &packet.Config{ DefaultHash: algorithm, }); err != nil { - return nil, fmt.Errorf("could not sign identity %s: %v", i.Name, err) + return nil, fmt.Errorf("could not sign identity %s: %w", i.Name, err) } + i.Signatures = append(i.Signatures, sig) } } + if err = pe.Serialize(armorOutput); err != nil { return nil, fmt.Errorf( - "could not write signed public key %s to output: %v", + "could not write signed public key %s to output: %w", pe.PrimaryKey.KeyIdString(), err, ) @@ -67,7 +75,7 @@ func (r *OpenPGPRoot) SignPublicKey(pubKey []byte, algorithm crypto.Hash, days u } if err = armorOutput.Close(); err != nil { - return nil, fmt.Errorf("could not close output stream: %v", err) + return nil, fmt.Errorf("could not close output stream: %w", err) } log.Tracef("signed public key\n%s", output.String()) @@ -77,35 +85,41 @@ func (r *OpenPGPRoot) SignPublicKey(pubKey []byte, algorithm crypto.Hash, days u func calculateExpiry(i *openpgp.Identity, days uint16) *uint32 { maxExpiry := time.Second * time.Duration(*i.SelfSignature.KeyLifetimeSecs) - calcExpiry := time.Hour * 24 * time.Duration(days) + calcExpiry := time.Hour * hoursInADay * time.Duration(days) if calcExpiry > maxExpiry { calcExpiry = maxExpiry } + expirySeconds := uint32(calcExpiry.Seconds()) + return &expirySeconds } func (r *OpenPGPRoot) findSigningKey(identifier string) (*openpgp.Entity, error) { keyring, err := os.Open(r.SecretKeyRing) if err != nil { - return nil, fmt.Errorf("could not open secret keyring: %v", err) + return nil, fmt.Errorf("could not open secret keyring: %w", err) } + defer func() { _ = keyring.Close() }() el, err := openpgp.ReadKeyRing(keyring) if err != nil { - return nil, fmt.Errorf("could not read keyring: %v", err) + return nil, fmt.Errorf("could not read keyring: %w", err) } + for _, e := range el { log.Tracef("found %s", e.PrimaryKey.KeyIdString()) + for _, i := range e.Identities { if i.UserId.Email == identifier && len(e.Revocations) == 0 && !i.SelfSignature.KeyExpired(time.Now()) { return e, nil } } } - return nil, fmt.Errorf("no matching key found") + + return nil, errors.New("no matching key found") } type OpenPGPProfile struct { diff --git a/signer/port_handler.go b/signer/port_handler.go index 9c56e59..8c5028e 100644 --- a/signer/port_handler.go +++ b/signer/port_handler.go @@ -33,6 +33,7 @@ type PortHandler struct { func (p *PortHandler) MainLoop() { count := 0 + for { go p.Receive() @@ -40,9 +41,12 @@ func (p *PortHandler) MainLoop() { 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) @@ -61,68 +65,62 @@ func (p *PortHandler) MainLoop() { // Receive a request and generate a request data structure func (p *PortHandler) Receive() { - header, err := shared.ReceiveBytes(p.port, 1, waitForInitialByte) - if err != nil { + if err := p.receiveHandshake(); 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 + return + } var command *datastructures.SignerRequest - for { - if tries <= 0 { - p.errors <- errors.New("tried reading block too often") - break - } - tries-- + + for tries := maxTriesPerBlock; tries > 0; tries-- { if err := shared.SendBytes(p.port, []byte{shared.AckByte}); err != nil { - p.errors <- fmt.Errorf("could not write ACK byte: %v", err) - break + p.errors <- fmt.Errorf("could not write ACK byte: %w", err) + + return } lengthBytes, err := shared.ReceiveBytes(p.port, shared.LengthFieldSize, waitForLengthBytes) if err != nil { - p.errors <- fmt.Errorf("could not read lenght bytes: %v", err) - break + p.errors <- fmt.Errorf("could not read length bytes: %w", err) + + return } + 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) + p.errors <- fmt.Errorf("could not read data block: %w", err) if !p.requestResend() { - break + return } + 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 + p.errors <- fmt.Errorf("could not read checksum byte: %w", err) + + return } 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 + return } + 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 + p.errors <- fmt.Errorf("could not read trailer bytes: %w", err) + + return } if string(trailer) != shared.MagicTrailer { @@ -131,70 +129,99 @@ func (p *PortHandler) Receive() { 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 + p.errors <- fmt.Errorf("failed to parse block as signer request: %w", err) + + return } if err := shared.SendBytes(p.port, []byte{shared.AckByte}); err != nil { - p.errors <- fmt.Errorf("failed to send ACK byte: %v", err) - break + p.errors <- fmt.Errorf("failed to send ACK byte: %w", err) + + return } p.commandChan <- command - break + + return } + + p.errors <- errors.New("tried reading block too often") +} + +func (p *PortHandler) receiveHandshake() error { + header, err := shared.ReceiveBytes(p.port, 1, waitForInitialByte) + if err != nil { + return fmt.Errorf("could not receive initial byte: %w", err) + } + + if header[0] != shared.HandshakeByte { + return fmt.Errorf( + "unexpected byte 0x%x expected 0x%x", + header[0], + shared.HandshakeByte, + ) + } + + return nil } 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 + return fmt.Errorf("could not send handshake: %w", err) } if ack, err := shared.ReceiveBytes(p.port, 1, waitForHandShake); err != nil { - return err + return fmt.Errorf("could not receive ACK: %w", err) } else if ack[0] != shared.AckByte { - return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0])) + return fmt.Errorf("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 + return fmt.Errorf("could not send data block: %w", err) } checksum := datastructures.CalculateXorCheckSum([][]byte{data}) if err := shared.SendBytes(p.port, []byte{checksum}); err != nil { - return err + return fmt.Errorf("could not send checksum: %w", err) } if err := shared.SendBytes(p.port, []byte(shared.MagicTrailer)); err != nil { - return err + return fmt.Errorf("could not send trailer: %w", err) } - if ack, err := shared.ReceiveBytes(p.port, 1, waitForHandShake); err != nil { - return err - } else if ack[0] == shared.AckByte { + ack, err := shared.ReceiveBytes(p.port, 1, waitForHandShake) + if err != nil { + return fmt.Errorf("could not receive ACK: %w", err) + } + + if ack[0] == shared.AckByte { tryAgain = false - } else if ack[0] != 0x11 { - return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0])) + } else { + return fmt.Errorf("invalid ack byte 0x%02x", ack[0]) } } @@ -203,6 +230,7 @@ func (p *PortHandler) SendResponse(response *datastructures.SignerResponse) erro func NewSignerProcess(port io.ReadWriteCloser) *PortHandler { errorChan := make(chan error) + return &PortHandler{ port: port, errors: errorChan, diff --git a/signer/protocol_elements.go b/signer/protocol_elements.go index eca525a..1ad6fbc 100644 --- a/signer/protocol_elements.go +++ b/signer/protocol_elements.go @@ -7,65 +7,65 @@ import ( "path" "git.cacert.org/cacert-gosigner/shared" - "git.cacert.org/cacert-gosigner/signer/openpgp_ops" - "git.cacert.org/cacert-gosigner/signer/x509_ops" + "git.cacert.org/cacert-gosigner/signer/openpgpops" + "git.cacert.org/cacert-gosigner/signer/x509ops" ) const ( - CsX509 shared.CryptoSystemId = 1 - CsOpenPGP shared.CryptoSystemId = 2 + CsX509 shared.CryptoSystemID = 1 + CsOpenPGP shared.CryptoSystemID = 2 ) const ( - X509RootDefault shared.CryptoSystemRootId = 0 - X509RootClass3 shared.CryptoSystemRootId = 1 + X509RootDefault shared.CryptoSystemRootID = 0 + X509RootClass3 shared.CryptoSystemRootID = 1 // The following roots existed in the old server.pl but had // no profile configurations and were thus unusable // - // X509RootClass3s shared.CryptoSystemRootId = 2 - // X509Root3 shared.CryptoSystemRootId = 3 - // X509Root4 shared.CryptoSystemRootId = 4 - // X509Root5 shared.CryptoSystemRootId = 5 + // X509RootClass3s shared.CryptoSystemRootID = 2 + // X509Root3 shared.CryptoSystemRootID = 3 + // X509Root4 shared.CryptoSystemRootID = 4 + // X509Root5 shared.CryptoSystemRootID = 5 ) const ( - X509ProfileClient shared.CertificateProfileId = 0 - X509ProfileClientOrg shared.CertificateProfileId = 1 - X509ProfileClientCodesign shared.CertificateProfileId = 2 - X509ProfileServer shared.CertificateProfileId = 5 - X509ProfileServerOrg shared.CertificateProfileId = 6 - X509ProfileOCSP shared.CertificateProfileId = 8 - X509ProfileTimestamp shared.CertificateProfileId = 9 + X509ProfileClient shared.CertificateProfileID = 0 + X509ProfileClientOrg shared.CertificateProfileID = 1 + X509ProfileClientCodesign shared.CertificateProfileID = 2 + X509ProfileServer shared.CertificateProfileID = 5 + X509ProfileServerOrg shared.CertificateProfileID = 6 + X509ProfileOCSP shared.CertificateProfileID = 8 + X509ProfileTimestamp shared.CertificateProfileID = 9 // the following profiles where valid options in the original signer code but had no configurations // - // X509ProfileClientMachine shared.CertificateProfileId = 3 // no configuration on original signer - // X509ProfileClientAds shared.CertificateProfileId = 4 // no configuration on original signer - // X509ProfileServerJabber shared.CertificateProfileId = 7 // no configuration on original signer - // X509ProfileProxy shared.CertificateProfileId = 10 // no configuration on original signer - // X509ProfileSubCA shared.CertificateProfileId = 11 // no configuration on original signer + // X509ProfileClientMachine shared.CertificateProfileID = 3 // no configuration on original signer + // X509ProfileClientAds shared.CertificateProfileID = 4 // no configuration on original signer + // X509ProfileServerJabber shared.CertificateProfileID = 7 // no configuration on original signer + // X509ProfileProxy shared.CertificateProfileID = 10 // no configuration on original signer + // X509ProfileSubCA shared.CertificateProfileID = 11 // no configuration on original signer ) const ( - X509MDDefault shared.MessageDigestAlgorithmId = 0 - X509MDMd5 shared.MessageDigestAlgorithmId = 1 - X509MDSha1 shared.MessageDigestAlgorithmId = 2 - // X509MDRipeMD160 shared.MessageDigestAlgorithmId = 3 x509 package does not support RIPEMD160 - X509MDSha256 shared.MessageDigestAlgorithmId = 8 - X509MDSha384 shared.MessageDigestAlgorithmId = 9 - X509MDSha512 shared.MessageDigestAlgorithmId = 10 + X509MDDefault shared.SignatureAlgorithmID = 0 + X509MDMd5 shared.SignatureAlgorithmID = 1 + X509MDSha1 shared.SignatureAlgorithmID = 2 + // X509MDRipeMD160 shared.SignatureAlgorithmID = 3 x509ops package does not support RIPEMD160 + X509MDSha256 shared.SignatureAlgorithmID = 8 + X509MDSha384 shared.SignatureAlgorithmID = 9 + X509MDSha512 shared.SignatureAlgorithmID = 10 ) const ( - OpenPGPRoot0 shared.CryptoSystemRootId = 0 + OpenPGPRoot0 shared.CryptoSystemRootID = 0 ) const ( - OpenPGPDefaultProfile shared.CertificateProfileId = 0 + OpenPGPDefaultProfile shared.CertificateProfileID = 0 ) const ( - OpenPGPDefaultMD shared.MessageDigestAlgorithmId = 0 + OpenPGPDefaultMD shared.SignatureAlgorithmID = 0 ) func NewCommandProcessor() *CommandProcessor { @@ -76,9 +76,9 @@ func NewCommandProcessor() *CommandProcessor { ExtKeyUsage: []x509.ExtKeyUsage{ x509.ExtKeyUsageEmailProtection, x509.ExtKeyUsageClientAuth, - // x509.ExtKeyUsageMicrosoftServerGatedCrypto, + // x509ops.ExtKeyUsageMicrosoftServerGatedCrypto, // 1.3.6.1.4.1.311.10.3.4 msEFS not supported by golang.org/crypto - // x509.ExtKeyUsageNetscapeServerGatedCrypto, + // x509ops.ExtKeyUsageNetscapeServerGatedCrypto, }, } codeSignPrototype := &x509.Certificate{ @@ -88,10 +88,10 @@ func NewCommandProcessor() *CommandProcessor { x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageCodeSigning, // 1.3.6.1.4.1.311.2.1.21 msCodeInd not supported by golang.org/crypto - // x509.ExtKeyUsageMicrosoftCommercialCodeSigning, - // x509.ExtKeyUsageMicrosoftServerGatedCrypto, + // x509ops.ExtKeyUsageMicrosoftCommercialCodeSigning, + // x509ops.ExtKeyUsageMicrosoftServerGatedCrypto, // 1.3.6.1.4.1.311.10.3.4 msEFS not supported by golang.org/crypto - // x509.ExtKeyUsageNetscapeServerGatedCrypto, + // x509ops.ExtKeyUsageNetscapeServerGatedCrypto, }, } serverPrototype := &x509.Certificate{ @@ -99,8 +99,8 @@ func NewCommandProcessor() *CommandProcessor { ExtKeyUsage: []x509.ExtKeyUsage{ x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth, - // x509.ExtKeyUsageMicrosoftServerGatedCrypto, - // x509.ExtKeyUsageNetscapeServerGatedCrypto, + // x509ops.ExtKeyUsageMicrosoftServerGatedCrypto, + // x509ops.ExtKeyUsageNetscapeServerGatedCrypto, }, } ocspPrototype := &x509.Certificate{ @@ -108,8 +108,8 @@ func NewCommandProcessor() *CommandProcessor { ExtKeyUsage: []x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageOCSPSigning, - // x509.ExtKeyUsageMicrosoftServerGatedCrypto, - // x509.ExtKeyUsageNetscapeServerGatedCrypto, + // x509ops.ExtKeyUsageMicrosoftServerGatedCrypto, + // x509ops.ExtKeyUsageNetscapeServerGatedCrypto, }, } timestampPrototype := &x509.Certificate{ @@ -117,15 +117,15 @@ func NewCommandProcessor() *CommandProcessor { ExtKeyUsage: []x509.ExtKeyUsage{ x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageOCSPSigning, - // x509.ExtKeyUsageMicrosoftServerGatedCrypto, - // x509.ExtKeyUsageNetscapeServerGatedCrypto, + // x509ops.ExtKeyUsageMicrosoftServerGatedCrypto, + // x509ops.ExtKeyUsageNetscapeServerGatedCrypto, }, } - cryptoSystems := map[shared.CryptoSystemId]*CryptoSystem{ + cryptoSystems := map[shared.CryptoSystemID]*CryptoSystem{ CsX509: { Name: "X.509", - Roots: map[shared.CryptoSystemRootId]interface{}{ - X509RootDefault: x509_ops.NewRoot( + Roots: map[shared.CryptoSystemRootID]interface{}{ + X509RootDefault: x509ops.NewRoot( settings.CABaseDir, "openssl", "CA", @@ -135,7 +135,7 @@ func NewCommandProcessor() *CommandProcessor { // TODO: parse OCSP endpoints from configuration []string{"http://ocsp.cacert.localhost"}, ), - X509RootClass3: x509_ops.NewRoot( + X509RootClass3: x509ops.NewRoot( settings.CABaseDir, "class3", "class3", @@ -148,99 +148,99 @@ func NewCommandProcessor() *CommandProcessor { // The following roots existed in the old server.pl but had // no profile configurations and were thus unusable // - // X509RootClass3s: &x509_ops.Root{Name: "class3s"}, // no profile configs - // X509Root3: &x509_ops.Root{Name: "root3"}, - // X509Root4: &x509_ops.Root{Name: "root4"}, - // X509Root5: &x509_ops.Root{Name: "root5"}, + // X509RootClass3s: &x509ops.Root{Name: "class3s"}, // no profile configs + // X509Root3: &x509ops.Root{Name: "root3"}, + // X509Root4: &x509ops.Root{Name: "root4"}, + // X509Root5: &x509ops.Root{Name: "root5"}, }, - Profiles: map[shared.CertificateProfileId]interface{}{ - X509ProfileClient: x509_ops.NewProfile( + Profiles: map[shared.CertificateProfileID]interface{}{ + X509ProfileClient: x509ops.NewProfile( "client", clientPrototype, - []x509_ops.SubjectDnField{ - x509_ops.SubjectDnFieldCommonName, - x509_ops.SubjectDnFieldEmailAddress, + []x509ops.SubjectDnField{ + x509ops.SubjectDnFieldCommonName, + x509ops.SubjectDnFieldEmailAddress, }, nil, true, ), - X509ProfileClientOrg: x509_ops.NewProfile("client-org", clientPrototype, - []x509_ops.SubjectDnField{ - x509_ops.SubjectDnFieldCountryName, - x509_ops.SubjectDnFieldStateOrProvinceName, - x509_ops.SubjectDnFieldLocalityName, - x509_ops.SubjectDnFieldOrganizationName, - x509_ops.SubjectDnFieldOrganizationalUnitName, - x509_ops.SubjectDnFieldCommonName, - x509_ops.SubjectDnFieldEmailAddress, + X509ProfileClientOrg: x509ops.NewProfile("client-org", clientPrototype, + []x509ops.SubjectDnField{ + x509ops.SubjectDnFieldCountryName, + x509ops.SubjectDnFieldStateOrProvinceName, + x509ops.SubjectDnFieldLocalityName, + x509ops.SubjectDnFieldOrganizationName, + x509ops.SubjectDnFieldOrganizationalUnitName, + x509ops.SubjectDnFieldCommonName, + x509ops.SubjectDnFieldEmailAddress, }, nil, true, ), - X509ProfileClientCodesign: x509_ops.NewProfile("client-codesign", codeSignPrototype, - []x509_ops.SubjectDnField{ - x509_ops.SubjectDnFieldCountryName, - x509_ops.SubjectDnFieldStateOrProvinceName, - x509_ops.SubjectDnFieldLocalityName, - x509_ops.SubjectDnFieldCommonName, - x509_ops.SubjectDnFieldEmailAddress, + X509ProfileClientCodesign: x509ops.NewProfile("client-codesign", codeSignPrototype, + []x509ops.SubjectDnField{ + x509ops.SubjectDnFieldCountryName, + x509ops.SubjectDnFieldStateOrProvinceName, + x509ops.SubjectDnFieldLocalityName, + x509ops.SubjectDnFieldCommonName, + x509ops.SubjectDnFieldEmailAddress, }, nil, true, ), - // X509ProfileClientMachine: &x509_ops.Profile{Name: "client-machine"}, - // X509ProfileClientAds: &x509_ops.Profile{Name: "client-ads"}, - X509ProfileServer: x509_ops.NewProfile("server", serverPrototype, - []x509_ops.SubjectDnField{ - x509_ops.SubjectDnFieldCommonName, + // X509ProfileClientMachine: &x509ops.Profile{Name: "client-machine"}, + // X509ProfileClientAds: &x509ops.Profile{Name: "client-ads"}, + X509ProfileServer: x509ops.NewProfile("server", serverPrototype, + []x509ops.SubjectDnField{ + x509ops.SubjectDnFieldCommonName, }, - []x509_ops.AltNameType{x509_ops.NameTypeDNS, x509_ops.NameTypeXmppJid}, + []x509ops.AltNameType{x509ops.NameTypeDNS, x509ops.NameTypeXMPPJid}, false, ), - X509ProfileServerOrg: x509_ops.NewProfile("server-org", serverPrototype, - []x509_ops.SubjectDnField{ - x509_ops.SubjectDnFieldCountryName, - x509_ops.SubjectDnFieldStateOrProvinceName, - x509_ops.SubjectDnFieldLocalityName, - x509_ops.SubjectDnFieldOrganizationName, - x509_ops.SubjectDnFieldOrganizationalUnitName, - x509_ops.SubjectDnFieldCommonName, + X509ProfileServerOrg: x509ops.NewProfile("server-org", serverPrototype, + []x509ops.SubjectDnField{ + x509ops.SubjectDnFieldCountryName, + x509ops.SubjectDnFieldStateOrProvinceName, + x509ops.SubjectDnFieldLocalityName, + x509ops.SubjectDnFieldOrganizationName, + x509ops.SubjectDnFieldOrganizationalUnitName, + x509ops.SubjectDnFieldCommonName, }, - []x509_ops.AltNameType{x509_ops.NameTypeDNS, x509_ops.NameTypeXmppJid}, + []x509ops.AltNameType{x509ops.NameTypeDNS, x509ops.NameTypeXMPPJid}, false, ), - // X509ProfileServerJabber: &x509_ops.Profile{Name: "server-jabber"}, - X509ProfileOCSP: x509_ops.NewProfile("ocsp", ocspPrototype, - []x509_ops.SubjectDnField{ - x509_ops.SubjectDnFieldCountryName, - x509_ops.SubjectDnFieldStateOrProvinceName, - x509_ops.SubjectDnFieldLocalityName, - x509_ops.SubjectDnFieldOrganizationName, - x509_ops.SubjectDnFieldOrganizationalUnitName, - x509_ops.SubjectDnFieldCommonName, - x509_ops.SubjectDnFieldEmailAddress, + // X509ProfileServerJabber: &x509ops.Profile{Name: "server-jabber"}, + X509ProfileOCSP: x509ops.NewProfile("ocsp", ocspPrototype, + []x509ops.SubjectDnField{ + x509ops.SubjectDnFieldCountryName, + x509ops.SubjectDnFieldStateOrProvinceName, + x509ops.SubjectDnFieldLocalityName, + x509ops.SubjectDnFieldOrganizationName, + x509ops.SubjectDnFieldOrganizationalUnitName, + x509ops.SubjectDnFieldCommonName, + x509ops.SubjectDnFieldEmailAddress, }, nil, false, ), - X509ProfileTimestamp: x509_ops.NewProfile("timestamp", timestampPrototype, - []x509_ops.SubjectDnField{ - x509_ops.SubjectDnFieldCountryName, - x509_ops.SubjectDnFieldStateOrProvinceName, - x509_ops.SubjectDnFieldLocalityName, - x509_ops.SubjectDnFieldOrganizationName, - x509_ops.SubjectDnFieldOrganizationalUnitName, - x509_ops.SubjectDnFieldCommonName, + X509ProfileTimestamp: x509ops.NewProfile("timestamp", timestampPrototype, + []x509ops.SubjectDnField{ + x509ops.SubjectDnFieldCountryName, + x509ops.SubjectDnFieldStateOrProvinceName, + x509ops.SubjectDnFieldLocalityName, + x509ops.SubjectDnFieldOrganizationName, + x509ops.SubjectDnFieldOrganizationalUnitName, + x509ops.SubjectDnFieldCommonName, }, nil, true, ), - // X509ProfileProxy: &x509_ops.Profile{Name: "proxy"}, - // X509ProfileSubCA: &x509_ops.Profile{Name: "subca"}, + // X509ProfileProxy: &x509ops.Profile{Name: "proxy"}, + // X509ProfileSubCA: &x509ops.Profile{Name: "subca"}, }, // constants for openssl invocations. Should be replaced with // something more useful - DigestAlgorithms: map[shared.MessageDigestAlgorithmId]interface{}{ + DigestAlgorithms: map[shared.SignatureAlgorithmID]interface{}{ X509MDDefault: x509.SHA256WithRSA, X509MDMd5: x509.MD5WithRSA, X509MDSha1: x509.SHA1WithRSA, @@ -251,8 +251,8 @@ func NewCommandProcessor() *CommandProcessor { }, CsOpenPGP: { Name: "OpenPGP", - Roots: map[shared.CryptoSystemRootId]interface{}{ - OpenPGPRoot0: &openpgp_ops.OpenPGPRoot{ + Roots: map[shared.CryptoSystemRootID]interface{}{ + OpenPGPRoot0: &openpgpops.OpenPGPRoot{ Name: "OpenPGP Root", SecretKeyRing: path.Join( settings.OpenPGPKeyRingDir, @@ -262,12 +262,12 @@ func NewCommandProcessor() *CommandProcessor { Identifier: settings.OpenPGPUidEmail, }, }, - Profiles: map[shared.CertificateProfileId]interface{}{ - OpenPGPDefaultProfile: &openpgp_ops.OpenPGPProfile{Name: "default"}, + Profiles: map[shared.CertificateProfileID]interface{}{ + OpenPGPDefaultProfile: &openpgpops.OpenPGPProfile{Name: "default"}, }, // constants for gnupg cert-digest-algo parameter. Should be replaced with // something more useful - DigestAlgorithms: map[shared.MessageDigestAlgorithmId]interface{}{ + DigestAlgorithms: map[shared.SignatureAlgorithmID]interface{}{ OpenPGPDefaultMD: crypto.SHA256, }, }, diff --git a/signer/x509_ops/operations.go b/signer/x509ops/x509ops.go similarity index 78% rename from signer/x509_ops/operations.go rename to signer/x509ops/x509ops.go index 7452163..747ee88 100644 --- a/signer/x509_ops/operations.go +++ b/signer/x509ops/x509ops.go @@ -1,4 +1,4 @@ -package x509_ops +package x509ops import ( "bufio" @@ -6,7 +6,7 @@ import ( "crypto" "crypto/rand" "crypto/rsa" - "crypto/sha1" + "crypto/sha1" // #nosec G505 needed for protocol version 1 "crypto/x509" "crypto/x509/pkix" "encoding/base64" @@ -27,11 +27,16 @@ import ( log "github.com/sirupsen/logrus" "git.cacert.org/cacert-gosigner/shared" - "git.cacert.org/cacert-gosigner/signer/common" ) const crlLifetime = time.Hour * 24 * 7 +const ( + pemTypeCertificate = "CERTIFICATE" + pemTypeCertificateRequest = "CERTIFICATE REQUEST" + pemTypePrivateKey = "PRIVATE KEY" +) + var ( oidPkcs9EmailAddress = []int{1, 2, 840, 113549, 1, 9, 1} ) @@ -57,27 +62,30 @@ func loadCertificate(certificateFile string) (*x509.Certificate, error) { pemBytes, err := ioutil.ReadFile(certificateFile) if err != nil { return nil, fmt.Errorf( - "could not load certificate %s: %v", + "could not load certificate %s: %w", certificateFile, err, ) } + pemBlock, _ := pem.Decode(pemBytes) - if pemBlock.Type != "CERTIFICATE" { + if pemBlock.Type != pemTypeCertificate { log.Warnf( "PEM in %s is probably not a certificate. PEM block has type %s", certificateFile, pemBlock.Type, ) } + certificate, err := x509.ParseCertificate(pemBlock.Bytes) if err != nil { return nil, fmt.Errorf( - "could no parse certificate from %s: %v", + "could no parse certificate from %s: %w", certificateFile, err, ) } + return certificate, nil } @@ -85,30 +93,34 @@ func loadPrivateKey(filename string) (crypto.Signer, error) { pemBytes, err := ioutil.ReadFile(filename) if err != nil { return nil, fmt.Errorf( - "could not load private key %s: %v", + "could not load private key %s: %w", filename, err, ) } + pemBlock, _ := pem.Decode(pemBytes) if pemBlock == nil { return nil, fmt.Errorf("no PEM data found in %s", filename) } - if pemBlock.Type != "PRIVATE KEY" { + + if pemBlock.Type != pemTypePrivateKey { log.Warnf( "PEM in %s is probably not a private key. PEM block has type %s", filename, pemBlock.Type, ) } + privateKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes) if err != nil { return nil, fmt.Errorf( - "could no parse private key from %s: %v", + "could no parse private key from %s: %w", filename, err, ) } + return privateKey.(*rsa.PrivateKey), nil } @@ -116,19 +128,24 @@ func (x *Root) getNextSerialNumber() (*big.Int, error) { // TODO: decide whether we should use 64 bit random serial numbers as // recommended by CAB forum baseline requirements serialNumberFile := x.serialNumberFile + _, err := os.Stat(serialNumberFile) if err != nil { log.Warnf("serial number file %s does not exist: %v", x.serialNumberFile, err) + return big.NewInt(1), nil } + data, err := ioutil.ReadFile(x.serialNumberFile) if err != nil { - return nil, fmt.Errorf("could not read serial number file %s: %v", x.serialNumberFile, err) + return nil, fmt.Errorf("could not read serial number file %s: %w", x.serialNumberFile, err) } - result, err := common.StringAsBigInt(data) + + result, err := stringAsBigInt(data) if err != nil { - return nil, fmt.Errorf("could not parse content of %s as serial number: %v", x.serialNumberFile, err) + return nil, fmt.Errorf("could not parse content of %s as serial number: %w", x.serialNumberFile, err) } + return result, err } @@ -136,26 +153,32 @@ func (x *Root) getNextCRLNumber() (*big.Int, error) { _, err := os.Stat(x.crlNumberFile) if err != nil { log.Warnf("CRL number file %s does not exist: %v", x.crlNumberFile, err) + return big.NewInt(1), nil } + data, err := ioutil.ReadFile(x.crlNumberFile) if err != nil { - return nil, fmt.Errorf("could not read CRL number file %s: %v", x.crlNumberFile, err) + return nil, fmt.Errorf("could not read CRL number file %s: %w", x.crlNumberFile, err) } - result, err := common.StringAsBigInt(data) + + result, err := stringAsBigInt(data) if err != nil { - return nil, fmt.Errorf("could not parse content of %s as CRL number: %v", x.crlNumberFile, err) + return nil, fmt.Errorf("could not parse content of %s as CRL number: %w", x.crlNumberFile, err) } + return result, nil } func (x *Root) bumpCRLNumber(current *big.Int) error { serial := current.Int64() + 1 crlNumberFile := x.crlNumberFile + outFile, err := ioutil.TempFile(path.Dir(crlNumberFile), "*.txt") if err != nil { - return fmt.Errorf("could not create temporary crl number file: %v", err) + return fmt.Errorf("could not create temporary crl number file: %w", err) } + defer func() { _ = outFile.Close() }() _, err = outFile.WriteString(fmt.Sprintf( @@ -163,27 +186,32 @@ func (x *Root) bumpCRLNumber(current *big.Int) error { strings.ToUpper(strconv.FormatInt(serial, 16)), )) if err != nil { - return fmt.Errorf("could not write new CRL number %d to %s: %v", serial, outFile.Name(), err) + return fmt.Errorf("could not write new CRL number %d to %s: %w", serial, outFile.Name(), err) } if err = outFile.Close(); err != nil { - return fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err) + return fmt.Errorf("could not close temporary file %s: %w", outFile.Name(), err) } + if err = os.Rename(crlNumberFile, fmt.Sprintf("%s.old", crlNumberFile)); err != nil { - return fmt.Errorf("could not rename %s to %s.old: %v", crlNumberFile, crlNumberFile, err) + return fmt.Errorf("could not rename %s to %s.old: %w", crlNumberFile, crlNumberFile, err) } + if err = os.Rename(outFile.Name(), crlNumberFile); err != nil { - return fmt.Errorf("could not rename %s to %s: %v", outFile.Name(), crlNumberFile, err) + return fmt.Errorf("could not rename %s to %s: %w", outFile.Name(), crlNumberFile, err) } + return nil } func (x *Root) bumpSerialNumber(current *big.Int) error { serial := current.Int64() + 1 + outFile, err := ioutil.TempFile(path.Dir(x.serialNumberFile), "*.txt") if err != nil { - return fmt.Errorf("could not open temporary serial number file: %v", err) + return fmt.Errorf("could not open temporary serial number file: %w", err) } + defer func() { _ = outFile.Close() }() _, err = outFile.WriteString(fmt.Sprintf( @@ -191,49 +219,59 @@ func (x *Root) bumpSerialNumber(current *big.Int) error { strings.ToUpper(strconv.FormatInt(serial, 16)), )) if err != nil { - return fmt.Errorf("could not write new serial number %d to %s: %v", serial, outFile.Name(), err) + return fmt.Errorf("could not write new serial number %d to %s: %w", serial, outFile.Name(), err) } if err = outFile.Close(); err != nil { - return fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err) + return fmt.Errorf("could not close temporary file %s: %w", outFile.Name(), err) } + if _, err = os.Stat(x.serialNumberFile); err == nil { if err = os.Rename(x.serialNumberFile, fmt.Sprintf("%s.old", x.serialNumberFile)); err != nil { - return fmt.Errorf("could not rename %s to %s.old: %v", x.serialNumberFile, x.serialNumberFile, err) + return fmt.Errorf("could not rename %s to %s.old: %w", x.serialNumberFile, x.serialNumberFile, err) } } + if err = os.Rename(outFile.Name(), x.serialNumberFile); err != nil { - return fmt.Errorf("could not rename %s to %s: %v", outFile.Name(), x.serialNumberFile, err) + return fmt.Errorf("could not rename %s to %s: %w", outFile.Name(), x.serialNumberFile, err) } + return nil } func (x *Root) loadRevokedCertificatesFromDatabase() ([]pkix.RevokedCertificate, error) { databaseFile := x.databaseFile + _, err := os.Stat(databaseFile) if err != nil { log.Warnf("openssl certificate database file %s does not exist: %v", databaseFile, err) + return []pkix.RevokedCertificate{}, nil } + file, err := os.Open(databaseFile) if err != nil { - return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", databaseFile, err) + return nil, fmt.Errorf("could not open openssl certificate database file %s: %w", databaseFile, err) } + defer func() { _ = file.Close() }() result := make([]pkix.RevokedCertificate, 0) + scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.Split(scanner.Text(), "\t") if line[0] == "R" { - serialNumber, err := common.StringAsBigInt([]byte(line[3])) + serialNumber, err := stringAsBigInt([]byte(line[3])) if err != nil { - return nil, fmt.Errorf("could not parse serial number %s as big int: %v", line[3], err) + return nil, fmt.Errorf("could not parse serial number %s as big int: %w", line[3], err) } + revokeTs, err := strconv.ParseInt(line[2][:len(line[2])-1], 10, 64) if err != nil { - return nil, fmt.Errorf("could not parse serial number: %v", err) + return nil, fmt.Errorf("could not parse serial number: %w", err) } + result = append(result, pkix.RevokedCertificate{ SerialNumber: serialNumber, RevocationTime: time.Unix(revokeTs, 0), @@ -241,24 +279,29 @@ func (x *Root) loadRevokedCertificatesFromDatabase() ([]pkix.RevokedCertificate, }) } } + return result, nil } func (x *Root) recordRevocation(certificate *x509.Certificate) (*pkix.RevokedCertificate, error) { _, err := os.Stat(x.databaseFile) if err != nil { - return nil, fmt.Errorf("openssl certificate database file %s does not exist: %v", x.databaseFile, err) + return nil, fmt.Errorf("openssl certificate database file %s does not exist: %w", x.databaseFile, err) } + inFile, err := os.Open(x.databaseFile) if err != nil { - return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", x.databaseFile, err) + return nil, fmt.Errorf("could not open openssl certificate database file %s: %w", x.databaseFile, err) } + defer func() { _ = inFile.Close() }() outFile, err := ioutil.TempFile(path.Dir(x.databaseFile), "*.txt") + defer func() { _ = outFile.Close() }() + if err != nil { - return nil, fmt.Errorf("could not open temporary database file: %v", err) + return nil, fmt.Errorf("could not open temporary database file: %w", err) } scanner := bufio.NewScanner(inFile) @@ -270,10 +313,12 @@ func (x *Root) recordRevocation(certificate *x509.Certificate) (*pkix.RevokedCer for scanner.Scan() { line := scanner.Text() parts := strings.Split(line, "\t") - serialNumber, err := common.StringAsBigInt([]byte(parts[3])) + + serialNumber, err := stringAsBigInt([]byte(parts[3])) if err != nil { - return nil, fmt.Errorf("could not parse serial number %s as big int: %v", parts[3], err) + return nil, fmt.Errorf("could not parse serial number %s as big int: %w", parts[3], err) } + if serialNumber == certificate.SerialNumber { line = strings.Join( []string{"R", parts[1], strconv.FormatInt(revocationTime.Unix(), 10) + "Z", parts[3], parts[4]}, @@ -281,24 +326,26 @@ func (x *Root) recordRevocation(certificate *x509.Certificate) (*pkix.RevokedCer ) found = true } + if _, err = writer.WriteString(fmt.Sprintf("%s\n", line)); err != nil { - return nil, fmt.Errorf("could not write '%s' to %s: %v", line, outFile.Name(), err) + return nil, fmt.Errorf("could not write '%s' to %s: %w", line, outFile.Name(), err) } } if err = outFile.Close(); err != nil { - return nil, fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err) + return nil, fmt.Errorf("could not close temporary file %s: %w", outFile.Name(), err) } + if err = inFile.Close(); err != nil { - return nil, fmt.Errorf("could not close %s: %v", x.databaseFile, err) + return nil, fmt.Errorf("could not close %s: %w", x.databaseFile, err) } if err = os.Rename(x.databaseFile, fmt.Sprintf("%s.old", x.databaseFile)); err != nil { - return nil, fmt.Errorf("could not rename %s to %s.old: %v", x.databaseFile, x.databaseFile, err) + return nil, fmt.Errorf("could not rename %s to %s.old: %w", x.databaseFile, x.databaseFile, err) } if err = os.Rename(outFile.Name(), x.databaseFile); err != nil { - return nil, fmt.Errorf("could not rename temporary file %s to %s: %v", outFile.Name(), x.databaseFile, err) + return nil, fmt.Errorf("could not rename temporary file %s to %s: %w", outFile.Name(), x.databaseFile, err) } if !found { @@ -313,22 +360,22 @@ func (x *Root) recordRevocation(certificate *x509.Certificate) (*pkix.RevokedCer func (x *Root) RevokeCertificate(request []byte) (*pkix.RevokedCertificate, error) { pemBlock, _ := pem.Decode(request) - if pemBlock.Type != "CERTIFICATE" { - if pemBlock.Type != "CERTIFICATE" { - log.Warnf( - "PEM structure is probably not a certificate. PEM block has type %s", - pemBlock.Type, - ) - log.Trace(request) - } + if pemBlock.Type != pemTypeCertificate { + log.Warnf( + "PEM structure is probably not a certificate. PEM block has type %s", + pemBlock.Type, + ) + log.Trace(request) } + certificate, err := x509.ParseCertificate(pemBlock.Bytes) if err != nil { return nil, fmt.Errorf( - "could no parse certificate: %v", + "could no parse certificate: %w", err, ) } + return x.recordRevocation(certificate) } @@ -337,10 +384,12 @@ func (x *Root) GenerateCrl(algorithm x509.SignatureAlgorithm) ([]byte, *[20]byte if err != nil { return nil, nil, err } + nextCrlNumber, err := x.getNextCRLNumber() if err != nil { return nil, nil, err } + crlTemplate := &x509.RevocationList{ SignatureAlgorithm: algorithm, RevokedCertificates: certificatesToRevoke, @@ -363,20 +412,23 @@ func (x *Root) GenerateCrl(algorithm x509.SignatureAlgorithm) ([]byte, *[20]byte x.privateKey, ) if err != nil { - return nil, nil, fmt.Errorf("could not create new CRL: %v", err) + return nil, nil, fmt.Errorf("could not create new CRL: %w", err) } - if err = ioutil.WriteFile(x.crlFileName, crlBytes, 0644); err != nil { - return nil, nil, fmt.Errorf("could not write new CRL to %s: %v", x.crlFileName, err) + if err = ioutil.WriteFile(x.crlFileName, crlBytes, 0600); err != nil { + return nil, nil, fmt.Errorf("could not write new CRL to %s: %w", x.crlFileName, err) } + // sha1 is implied by protocol version 1 + // #nosec G401 newCrlHash := sha1.Sum(crlBytes) hashedCrlFileName := path.Join( x.crlHashDir, fmt.Sprintf("%s.crl", hex.EncodeToString(newCrlHash[:])), ) - if err = ioutil.WriteFile(hashedCrlFileName, crlBytes, 0644); err != nil { - return nil, nil, fmt.Errorf("could not write new CRL to %s: %v", hashedCrlFileName, err) + + if err = ioutil.WriteFile(hashedCrlFileName, crlBytes, 0600); err != nil { + return nil, nil, fmt.Errorf("could not write new CRL to %s: %w", hashedCrlFileName, err) } return crlBytes, &newCrlHash, nil @@ -388,6 +440,7 @@ func (x *Root) DeleteOldCRLs(keepHashes ...string) error { found, err := filepath.Glob(path.Join(x.crlHashDir, "*.crl")) if err != nil { log.Warnf("could not match files: %v", err) + return nil } @@ -399,7 +452,7 @@ nextFound: } } if err := os.Remove(filename); err != nil { - return fmt.Errorf("could not delete %s: %v", filename, err) + return fmt.Errorf("could not delete %s: %w", filename, err) } } @@ -421,6 +474,7 @@ func (x *Root) checkPreconditions() { for _, success := range results { if !success { log.Warnf("preconditions for %s failed, operations may fail too", x) + break } } @@ -428,26 +482,34 @@ func (x *Root) checkPreconditions() { func (x *Root) checkFile(path, prefix string) bool { ok := true + if s, e := os.Stat(path); e != nil { log.Warnf("%s file %s of %s has issues: %v", prefix, path, x, e) + ok = false } else if s.IsDir() { log.Warnf("%s file %s of %s is a directory", prefix, path, x) + ok = false } + return ok } func (x *Root) checkDir(path, prefix string) bool { ok := true + if s, e := os.Stat(path); e != nil { log.Warnf("%s %s of %s has issues: %v", prefix, path, x, e) + if err := os.MkdirAll(path, 0755); err != nil { log.Warnf("could not create %s %s of %s: %v", prefix, path, x, err) } + ok = false } else if !s.IsDir() { log.Warnf("%s %s of %s is not a directory", prefix, path, x) + ok = false } @@ -468,35 +530,45 @@ func (x *Root) SignCertificate( params *SigningRequestParameters, ) ([]byte, error) { var publicKey interface{} + + // nolint:nestif if params.IsSpkac { var err error + const spkacPrefix = "SPKAC=" + if !bytes.Equal([]byte(spkacPrefix), params.Request[:len(spkacPrefix)]) { return nil, fmt.Errorf("request does not contain a valid SPKAC string") } + derBytes, err := base64.StdEncoding.DecodeString(string(params.Request[len(spkacPrefix):])) if err != nil { - return nil, fmt.Errorf("could not decode SPKAC bytes: %v", err) + return nil, fmt.Errorf("could not decode SPKAC bytes: %w", err) } + publicKey, err = pkac.ParseSPKAC(derBytes) if err != nil { - return nil, fmt.Errorf("could not parse SPKAC: %v", err) + return nil, fmt.Errorf("could not parse SPKAC: %w", err) } } else { csrBlock, _ := pem.Decode(params.Request) - if csrBlock.Type != "CERTIFICATE REQUEST" { - return nil, fmt.Errorf("unexpected PEM block '%s' instead of 'CERTIFICATE REQUEST'", csrBlock.Type) + if csrBlock.Type != pemTypeCertificateRequest { + return nil, fmt.Errorf( + "unexpected PEM block '%s' instead of '%s'", + csrBlock.Type, + pemTypeCertificateRequest, + ) } csr, err := x509.ParseCertificateRequest(csrBlock.Bytes) if err != nil { - return nil, fmt.Errorf("could not parse CSR: %v", err) + return nil, fmt.Errorf("could not parse CSR: %w", err) } publicKey = csr.PublicKey } nextSerialNumber, err := x.getNextSerialNumber() if err != nil { - return nil, fmt.Errorf("could not get next serial number: %v", err) + return nil, fmt.Errorf("could not get next serial number: %w", err) } // copy profile @@ -516,14 +588,15 @@ func (x *Root) SignCertificate( // check subject subject, err := profile.parseSubject(params.Subject) if err != nil { - return nil, fmt.Errorf("could not parse subject: %v", err) + return nil, fmt.Errorf("could not parse subject: %w", err) } + certificate.Subject = *subject // check altNames err = profile.parseAltNames(certificate, params.SubjectAlternativeNames) if err != nil { - return nil, fmt.Errorf("could not parse subject alternative names: %v", err) + return nil, fmt.Errorf("could not parse subject alternative names: %w", err) } moveEmailsFromSubjectToAlternativeNames(certificate) @@ -532,24 +605,24 @@ func (x *Root) SignCertificate( certBytes, err := x509.CreateCertificate(rand.Reader, certificate, x.certificate, publicKey, x.privateKey) if err != nil { - return nil, fmt.Errorf("could not sign certificate: %v", err) + return nil, fmt.Errorf("could not sign certificate: %w", err) } parsedCertificate, err := x509.ParseCertificate(certBytes) if err != nil { - return nil, fmt.Errorf("could not parse signed certificate: %v", err) + return nil, fmt.Errorf("could not parse signed certificate: %w", err) } if err = x.bumpSerialNumber(nextSerialNumber); err != nil { - log.Errorf("could not bump serial number: %v", err) + return nil, fmt.Errorf("could not bump serial number: %w", err) } err = x.recordIssuedCertificate(parsedCertificate) if err != nil { - return nil, fmt.Errorf("could not record signed certificate in database: %v", err) + return nil, fmt.Errorf("could not record signed certificate in database: %w", err) } - pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) + pemBytes := pem.EncodeToMemory(&pem.Block{Type: pemTypeCertificate, Bytes: certBytes}) log.Tracef("signed new certificate\n%s", pemBytes) return pemBytes, nil @@ -559,36 +632,46 @@ func (x *Root) SignCertificate( // otherwise which is not compliant to RFC-5280 func moveEmailsFromSubjectToAlternativeNames(certificate *x509.Certificate) { extraNames := make([]pkix.AttributeTypeAndValue, 0) + for _, p := range certificate.Subject.ExtraNames { if p.Type.Equal(oidPkcs9EmailAddress) { email := p.Value.(string) + if certificate.EmailAddresses == nil { certificate.EmailAddresses = []string{email} + continue } + for _, e := range certificate.EmailAddresses { if e == p.Value { continue } } + certificate.EmailAddresses = append(certificate.EmailAddresses, email) } else { extraNames = append(extraNames, p) } } + certificate.Subject.ExtraNames = extraNames } func (x *Root) recordIssuedCertificate(certificate *x509.Certificate) error { log.Tracef("recording %+v", certificate) + tempFile, err := ioutil.TempFile(path.Dir(x.databaseFile), "*.txt") if err != nil { - return fmt.Errorf("could not create temporary file: %v", err) + return fmt.Errorf("could not create temporary file: %w", err) } + defer func() { _ = tempFile.Close() }() + tempName := tempFile.Name() dbExists := false + _, err = os.Stat(x.databaseFile) if err != nil { log.Warnf("openssl certificate database file %s does not exist: %v", x.databaseFile, err) @@ -597,76 +680,105 @@ func (x *Root) recordIssuedCertificate(certificate *x509.Certificate) error { inFile, err := os.Open(x.databaseFile) defer func() { _ = inFile.Close() }() if err != nil { - return fmt.Errorf("could not open openssl certificate database file %s: %v", x.databaseFile, err) + return fmt.Errorf("could not open openssl certificate database file %s: %w", x.databaseFile, err) } _, err = io.Copy(tempFile, inFile) if err != nil { - return fmt.Errorf("could not copy %s to temporary file %s: %v", x.databaseFile, tempName, err) + return fmt.Errorf("could not copy %s to temporary file %s: %w", x.databaseFile, tempName, err) } if err = inFile.Close(); err != nil { - return fmt.Errorf("could not close %s: %v", x.databaseFile, err) + return fmt.Errorf("could not close %s: %w", x.databaseFile, err) } } + if err = tempFile.Close(); err != nil { - return fmt.Errorf("could not close temporary file %s: %v", tempName, err) + return fmt.Errorf("could not close temporary file %s: %w", tempName, err) } + outFile, err := os.OpenFile(tempName, os.O_APPEND|os.O_WRONLY, 0644) if err != nil { - return fmt.Errorf("could not open temporary file for writing %s: %v", tempName, err) + return fmt.Errorf("could not open temporary file for writing %s: %w", tempName, err) } + defer func() { _ = outFile.Close() }() - line := strings.Join([]string{"V", strconv.FormatInt(certificate.NotBefore.Unix(), 10) + "Z", "", strings.ToUpper(certificate.SerialNumber.Text(16)), "unknown", opensslFormatDN(certificate.Subject)}, "\t") + + line := strings.Join( + // nolint:gomnd + []string{ + "V", + strconv.FormatInt(certificate.NotBefore.Unix(), 10) + "Z", + "", + strings.ToUpper(certificate.SerialNumber.Text(16)), + "unknown", + opensslFormatDN(certificate.Subject), + }, + "\t", + ) + _, err = fmt.Fprintln(outFile, line) if err != nil { - return fmt.Errorf("could not write '%s' to %s: %v", line, tempName, err) + return fmt.Errorf("could not write '%s' to %s: %w", line, tempName, err) } + if err = outFile.Close(); err != nil { - return fmt.Errorf("could not close temporary file %s: %v", tempName, err) + return fmt.Errorf("could not close temporary file %s: %w", tempName, err) } if dbExists { if err = os.Rename(x.databaseFile, fmt.Sprintf("%s.old", x.databaseFile)); err != nil { - return fmt.Errorf("could not rename %s to %s.old: %v", x.databaseFile, x.databaseFile, err) + return fmt.Errorf("could not rename %s to %s.old: %w", x.databaseFile, x.databaseFile, err) } } if err = os.Rename(tempName, x.databaseFile); err != nil { - return fmt.Errorf("could not rename temporary file %s to %s: %v", tempName, x.databaseFile, err) + return fmt.Errorf("could not rename temporary file %s to %s: %w", tempName, x.databaseFile, err) } return nil } func opensslFormatDN(subject pkix.Name) string { + const ( + oidSuffixCommonName = 3 + oidSuffixCountryName = 6 + oidSuffixLocalityName = 7 + oidSuffixProvinceName = 8 + oidSuffixOrganization = 10 + oidSuffixOrganizationalUnit = 11 + ) + var buf strings.Builder + for _, rdn := range subject.ToRDNSequence() { if len(rdn) == 0 { continue } + for _, atv := range rdn { value, ok := atv.Value.(string) if !ok { continue } + t := atv.Type if len(t) == 4 && t[:3].Equal([]int{2, 5, 4}) { switch t[3] { - case 3: + case oidSuffixCommonName: buf.WriteString("/CN=") buf.WriteString(value) - case 6: + case oidSuffixCountryName: buf.WriteString("/C=") buf.WriteString(value) - case 7: + case oidSuffixLocalityName: buf.WriteString("/L=") buf.WriteString(value) - case 8: + case oidSuffixProvinceName: buf.WriteString("/ST=") buf.WriteString(value) - case 10: + case oidSuffixOrganization: buf.WriteString("/O=") buf.WriteString(value) - case 11: + case oidSuffixOrganizationalUnit: buf.WriteString("/OU=") buf.WriteString(value) } @@ -676,22 +788,25 @@ func opensslFormatDN(subject pkix.Name) string { } } } + return buf.String() } func NewRoot( basedir, name, subdir string, - id shared.CryptoSystemRootId, + id shared.CryptoSystemRootID, crlDistributionPoints, ocspServers []string, ) *Root { key, err := loadPrivateKey(path.Join(basedir, subdir, "private", "ca.key.pem")) if err != nil { log.Fatalf("could not load private key: %v", err) } + cert, err := loadCertificate(path.Join(basedir, subdir, "ca.crt.pem")) if err != nil { log.Fatalf("could not load CA certificate: %v", err) } + root := &Root{ Name: name, privateKey: key, @@ -709,6 +824,7 @@ func NewRoot( ocspServers: ocspServers, } root.checkPreconditions() + return root } @@ -716,7 +832,7 @@ type AltNameType string const ( NameTypeDNS AltNameType = "DNS" - NameTypeXmppJid AltNameType = "otherName:1.3.6.1.5.5.7.8.5;UTF8" // from RFC 3920, 6120 + NameTypeXMPPJid AltNameType = "otherName:1.3.6.1.5.5.7.8.5;UTF8" // from RFC 3920, 6120 ) type SubjectDnField string @@ -744,73 +860,55 @@ func (p *Profile) String() string { } func (p *Profile) parseSubject(subject []byte) (*pkix.Name, error) { - parts := strings.Split(string(subject), "/") subjectDN := &pkix.Name{} + for _, part := range parts { if len(strings.TrimSpace(part)) == 0 { continue } + handled := false item := strings.SplitN(part, "=", 2) + for _, f := range p.subjectDNFields { if !strings.EqualFold(item[0], string(f)) { continue } + value := item[1] handled = true + switch f { case SubjectDnFieldCountryName: - if subjectDN.Country == nil { - subjectDN.Country = []string{value} - } else { - subjectDN.Country = append(subjectDN.Country, value) - } + subjectDN.Country = append(subjectDN.Country, value) case SubjectDnFieldStateOrProvinceName: - if subjectDN.Province == nil { - subjectDN.Province = []string{value} - } else { - subjectDN.Province = append(subjectDN.Province, value) - } + subjectDN.Province = append(subjectDN.Province, value) case SubjectDnFieldLocalityName: - if subjectDN.Locality == nil { - subjectDN.Locality = []string{value} - } else { - subjectDN.Locality = append(subjectDN.Locality, value) - } + subjectDN.Locality = append(subjectDN.Locality, value) case SubjectDnFieldOrganizationName: - if subjectDN.Organization == nil { - subjectDN.Organization = []string{value} - } else { - subjectDN.Organization = append(subjectDN.Organization, value) - } + subjectDN.Organization = append(subjectDN.Organization, value) case SubjectDnFieldOrganizationalUnitName: - if subjectDN.OrganizationalUnit == nil { - subjectDN.OrganizationalUnit = []string{value} - } else { - subjectDN.OrganizationalUnit = append(subjectDN.OrganizationalUnit, value) - } + subjectDN.OrganizationalUnit = append(subjectDN.OrganizationalUnit, value) case SubjectDnFieldCommonName: subjectDN.CommonName = value case SubjectDnFieldEmailAddress: - emailIA5 := pkix.AttributeTypeAndValue{ + subjectDN.ExtraNames = append(subjectDN.ExtraNames, pkix.AttributeTypeAndValue{ Type: oidPkcs9EmailAddress, Value: value, - } - if subjectDN.ExtraNames == nil { - subjectDN.ExtraNames = []pkix.AttributeTypeAndValue{emailIA5} - } else { - subjectDN.ExtraNames = append(subjectDN.ExtraNames, emailIA5) - } + }) default: log.Warnf("unhandled subject DN type %s", f) } } + if !handled { return nil, fmt.Errorf("skipped part %s because it is not supported by profile %s", part, p) } } + log.Debugf("created subject DN %s", subjectDN) + return subjectDN, nil } @@ -820,19 +918,24 @@ func (p *Profile) parseAltNames(template *x509.Certificate, altNames []byte) err if len(strings.TrimSpace(part)) == 0 { continue } + handled := false item := strings.SplitN(part, ":", 3) + if item[0] == "otherName" { item = []string{strings.Join(item[:2], ":"), item[2]} } else { item = []string{item[0], strings.Join(item[1:], ":")} } + for _, f := range p.altNameTypes { if item[0] != string(f) { continue } + value := item[1] handled = true + switch f { case NameTypeDNS: if template.DNSNames == nil { @@ -840,17 +943,19 @@ func (p *Profile) parseAltNames(template *x509.Certificate, altNames []byte) err } else { template.DNSNames = append(template.DNSNames, value) } - case NameTypeXmppJid: - // x509.Certificate has no support for otherName alternative names + case NameTypeXMPPJid: + // x509ops.Certificate has no support for otherName alternative names log.Warnf("skipping %s because it cannot be supported", part) default: log.Warnf("unhandled alternative name type %s", f) } } + if !handled { return fmt.Errorf("skipped alternative name %s because it is not supported by profile %s", part, p) } } + return nil } @@ -869,3 +974,14 @@ func NewProfile( copyEmail: copyEmail, } } + +func stringAsBigInt(data []byte) (*big.Int, error) { + dataString := strings.TrimSpace(string(data)) + + parseInt, err := strconv.ParseInt(dataString, 16, 64) + if err != nil { + return nil, fmt.Errorf("could not parse %s as big int: %w", dataString, err) + } + + return big.NewInt(parseInt), nil +} diff --git a/signer/x509_ops/operations_test.go b/signer/x509ops/x509ops_test.go similarity index 96% rename from signer/x509_ops/operations_test.go rename to signer/x509ops/x509ops_test.go index a2b27ca..651c0b2 100644 --- a/signer/x509_ops/operations_test.go +++ b/signer/x509ops/x509ops_test.go @@ -1,4 +1,4 @@ -package x509_ops +package x509ops import ( "crypto" @@ -33,6 +33,7 @@ func TestRoot_SignClientCertificateWithCSR(t *testing.T) { subjectDNFields: []SubjectDnField{SubjectDnFieldCommonName, SubjectDnFieldEmailAddress}, copyEmail: true, } + certificate, err := root.SignCertificate(clientProfile, x509.SHA256WithRSA, &SigningRequestParameters{ Request: decodeToBytes(t, testRequest), Subject: []byte(testClientSubject), @@ -42,17 +43,21 @@ func TestRoot_SignClientCertificateWithCSR(t *testing.T) { }) if err != nil { t.Errorf("error signing certificate: %v", err) + return } certificateDer, _ := pem.Decode(certificate) - if certificateDer.Type != "CERTIFICATE" { - t.Errorf("invalid PEM type '%s' instead of 'CERTIFICATE'", certificateDer.Type) + if certificateDer.Type != pemTypeCertificate { + t.Errorf("invalid PEM type '%s' instead of '%s'", certificateDer.Type, pemTypeCertificate) + return } + _, err = x509.ParseCertificate(certificateDer.Bytes) if err != nil { t.Errorf("could not parse generated certificate: %v", err) + return } } @@ -90,17 +95,21 @@ func TestRoot_SignClientCertificateWithSPKAC(t *testing.T) { if err != nil { t.Errorf("error signing certificate: %v", err) + return } certificateDer, _ := pem.Decode(certificate) - if certificateDer.Type != "CERTIFICATE" { - t.Errorf("invalid PEM type '%s' instead of 'CERTIFICATE'", certificateDer.Type) + if certificateDer.Type != pemTypeCertificate { + t.Errorf("invalid PEM type '%s' instead of '%s'", certificateDer.Type, pemTypeCertificate) + return } + _, err = x509.ParseCertificate(certificateDer.Bytes) if err != nil { t.Errorf("could not parse generated certificate: %v", err) + return } } @@ -134,9 +143,10 @@ func TestRoot_SignServerCertificateWithCSR(t *testing.T) { SubjectDnFieldOrganizationalUnitName, SubjectDnFieldCommonName, }, - altNameTypes: []AltNameType{NameTypeDNS, NameTypeXmppJid}, + altNameTypes: []AltNameType{NameTypeDNS, NameTypeXMPPJid}, copyEmail: false, } + certificate, err := root.SignCertificate(clientProfile, x509.SHA256WithRSA, &SigningRequestParameters{ Request: decodeToBytes(t, testRequest), Subject: []byte(testServerSubject), @@ -146,36 +156,44 @@ func TestRoot_SignServerCertificateWithCSR(t *testing.T) { }) if err != nil { t.Errorf("error signing certificate: %v", err) + return } certificateDer, _ := pem.Decode(certificate) if certificateDer.Type != "CERTIFICATE" { t.Errorf("invalid PEM type '%s' instead of 'CERTIFICATE'", certificateDer.Type) + return } + _, err = x509.ParseCertificate(certificateDer.Bytes) if err != nil { t.Errorf("could not parse generated certificate: %v", err) + return } } func loadTestKey(t *testing.T) crypto.Signer { testKeyBytes := decodeToBytes(t, testCAKey) + key, err := x509.ParsePKCS1PrivateKey(testKeyBytes) if err != nil { t.Fatal(err) } + return key } func loadTestCACertificate(t *testing.T) *x509.Certificate { testCertBytes := decodeToBytes(t, testCACertificate) + cert, err := x509.ParseCertificate(testCertBytes) if err != nil { t.Fatal(err) } + return cert } @@ -184,11 +202,14 @@ func decodeToBytes(t *testing.T, request string) []byte { if err != nil { t.Fatal(err) } + return decodeString } +// nolint:lll const testRequest = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ1ZEQ0NBVHdDQVFBd0R6RU5NQXNHQTFVRUF3d0VWR1Z6ZERDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRApnZ0VQQURDQ0FRb0NnZ0VCQU9wQ1JmMVZWQjVwK3RscVFjM25qekYyZVAydG40bGZ2NVlJU3RFMktMSmxJRDRECi9YZTRVUGdodlNuMUN3R1VzeCtQcFBDaTdZVFdQNXRKSWYwbmNLMm02YVZIWUtqcDN2K3NvMnRENkJ4V0lMSFAKS0tQSm5qbnBjU0l1Q3hKUytRU2xyMHh0aEJYYzZ2UzVWRE5Ib285VXJWRUYzSVlTd3VDTklqTWpMR25kYmpCagprNm5TUk5JZWVmeVBaVGI4MHFsVTJFZ3hJMFdFYTA1dm5sQTY5L2tQZGhmTjVRRHBHQ1NxU25GdXo1cGFmRXVIClZMOE1aQXVtVDJySkVkYnorcHRPRjBqMWZSWEQ4b1RKZ0ppQmszbGR6YlBqeFpndW5LSTl3NVcrSWdEWmxsNm8KRzM2TUN6WHNYREdkb25NbCt0K1JIbEdocjN0VDQrKzBobXVYL2pzQ0F3RUFBYUFBTUEwR0NTcUdTSWIzRFFFQgpDd1VBQTRJQkFRRFBlYnhmR3pRaWRud1pxUGdQYUliRmxNM0xsWXpROEJFbFUrYXV5bC90VjhJRC85a0dkekNxClN4N0QyRWVNVC83QmZ6L29XUjRMczVUeFpzdjR0N3RiSEUrSStFdEcwS3FnUDhNTTJPWStGckJBMXVnY3JJa2YKNmVpbXFEVkFtUFBNMHhCNUg3aFdNY1BMVUhzbW1GNlV4ajNsVXphOVQ5OXpxTWppMXlyYlpIc1pkMEM0RFd6RQo1YWtZU1hTTGNuK1F3R25LY1pvV1QwczNWZU5pMHNUK3BTNEVkdk1SbzV6Q3JUMW1SbFlYQkNqU0tpQzZEVjNpCnhyaDI2WWJqMjRKSys5dlNUR3N4RFlpMXUzOG04a1AxRVR2L0lCVnRDSVpKVmJ2eXhWbUpuemV2QnJONHpxdncKV1QvQi9jOGdrK0FQR1BKM3ZaZDUxNVhvM2QzVld4NkwKLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tCg==" +// nolint:lll const testSpkac = "U1BLQUM9TUlJQ1FEQ0NBU2d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRHFRa1g5VlZRZWFmclpha0hONTQ4eGRuajlyWitKWDcrV0NFclJOaWl5WlNBK0EvMTN1RkQ0SWIwcDlRc0JsTE1majZUd291MkUxaitiU1NIOUozQ3RwdW1sUjJDbzZkNy9yS05yUStnY1ZpQ3h6eWlqeVo0NTZYRWlMZ3NTVXZrRXBhOU1iWVFWM09yMHVWUXpSNktQVksxUkJkeUdFc0xnalNJekl5eHAzVzR3WTVPcDBrVFNIbm44ajJVMi9OS3BWTmhJTVNORmhHdE9iNTVRT3ZmNUQzWVh6ZVVBNlJna3FrcHhicythV254TGgxUy9ER1FMcGs5cXlSSFc4L3FiVGhkSTlYMFZ3L0tFeVlDWWdaTjVYYzJ6NDhXWUxweWlQY09WdmlJQTJaWmVxQnQrakFzMTdGd3huYUp6SmZyZmtSNVJvYTk3VStQdnRJWnJsLzQ3QWdNQkFBRVdBREFOQmdrcWhraUc5dzBCQVFRRkFBT0NBUUVBaDdqWmxaYXpPOXdHRkl3Mll1SVN5WjVYb2JjU0pSS2dMbG52UDh5cUkyVkxTdVBmdkNXOGJxKzNMWnNWcFZ6U0dhbDgwUTk5empOVy9lRm0xTElMRXZNZ0FCeEFtdk9UNlNKZURyajc4WkQyL0haazdmdHVLWU1VbkZTOWhzUWlkWmoveUttbDd4Nm9hQnlpalg5UVU0eTZYcCs3NzlhREFSenZOWTR5RGlRZmNuaFBVN1dablJyTUJZOHBSTVJVVjNKc2MvZXBQclZjOVhoaVE0KzBRNHFVQW1VNlVHNWNQeXFROVJJK2ZIL3VMbm8vZkNHUGZFVjM1NDV3WG5NeXNubC9HYlNLa0FHdVFmcm5ZT2dReFV4dUx1aHBoazVIaGtlMzNUUy9FYVlGc2JTZEhzRktoaCt4Uks3NW9wWExJTkpzNTFpYVdySUFTVmZvRTI0a3FRPT0K" const testClientSubject = "/CN=Test/emailAddress=test@example.org" @@ -199,6 +220,8 @@ const testServerSubject = "/CN=www.example.org" const testServerSan = "DNS:www.example.org,otherName:1.3.6.1.5.5.7.8.5;UTF8:www.example.org" +// nolint:lll const testCAKey = "MIIG5AIBAAKCAYEAqMWP2Ec/DxJ5n5bCv0e6oTBoGvsplq1qHVtwPDEL/bwlhUbTigGPQGoUK+Y3eS2T5FY/qpdfqjUBi/FnPiKNwEnmkYuSakSSS6GyrgsP876z1xXth50CIkUnAPR1YJ0bYmhUpEitRJgoWa5my3bS+LuNt1gVHD+zyCOlbfNJZTILnQHFLtzi/wPivlTWpUDJzHWvvo+Ki4e29qWRMaAatiXLUq/wW06fsRSa9plkhNv7jlg9hq8Y2SEie0mRvuyFgIKvkBmcT3X5yPhCWZPomsqQnEJXKnxno0SkrM+XoWWBeusYPsZkfXknGwy/wvoMbVT5MfqyMYY1CTw8/zaSDoC/sj8XmAL44t+EsZ+JEUYgSVW6Y3L2KieuqCibg4B+G8qI4AQm2cjXanjX1kWTUCCtGO6ylxRKNq0zCWhflE2i2s/+4v+RuHQ1laYnfl3vIQZHz0/gtQIlR2AqXc2ODRoTO8d80dXZjwImnrjHQ/yHx4LErHMprNjQb0BprtCDAgMBAAECggGAEF7hfhQjHL4pB/7isxUtGDeO0ZctSI1XrrNQ5rXHOPyIEy50lH1kPNZNUJjLJrjyEIMBN/Xo9KShmsZ2wkMtxsokUFfegupV2no7z8AI8xa7cRCScsYbD+HvT5tmy1FR97CxDSJzlCTCPTi6hd/nxPLEY1Vq7suLD83NXSXtJ6C8GaWzT8FjT2M8GkQ2cd8f8/IycuSPhstKRxB2Tf7+uE5gM4wXX3P374BVK7hjVLPV6c/LYAYZ/e3F33maZo+glyRP0DIWUtVQHhn7ZxlhatLYPrzwoM9MBFVjX4KHjRBWpZ/eSRKmDske0KnQ8nKPo2MsXxm6aKRNr4XJRC0FXqvz1CEa22ANOtCyfNmHHH6PC7R9rCCt8TZFAPDyyVq31KJe89cwdPngPBOIZdnW9U5pmG0aQrwU9ubafX5Yf+uDP0N9rEPEw5sU0QZHMai5751jQFIpej/6IA3mv0rscxP5Bjc8gGJynhj4BvpHWHZzdRQZquQG1CPKDm5yItuBAoHBANhF+ukAUkIvWMuD9WXSGcUuDsfPOSvx5Fx2riQYhbDFhb2W5zZ75Pi4E2OnQ8RDxCbUJ/iubLwm69LixW5e2+hvR8+AFV3bBgXQUl4uJHKCChz94JZHNgaUae1jjqWNxINLWNAIGUzR9ABslWRiE15InjOjMLf0is0IqryPUX1JtHLM9HnDtR5RbuZUhEUxp3a/msJJeM1sVbOMELolmC0O6ChhMM2mj7mSkexSE7XZwJLn+pIP5GZBhdi0Jz++wQKBwQDHxd55McC7gEIOPjy4SUHhE/JnJd0MRr6R2kHiZs7yMy8WBjDp+Ez2JiHHj00HY9hTRswO45ZtfaMFzfAjLJ7Dja7Pvbh48QkZJV+bul1pLdsLnzHtxqDaZSZluGBBUMoh+PE7WauGgflxtWrH0QX1kv+E0Z70F1fgsJkJ7L9j+M9TkKJxOtqS0BVo13Ko2LHN+6hFOeE5J7ItvdapWPXBGUySw9ELmLd38br0wTdzpWD6OupcM8M8Qb+vncEc5EMCgcEA1Vby07VFb5RU+y0IfZBra16rpd58fyT2J1/LGEA4YM/3xbV+DvjYPaEXP05YQtq2O7c8Vst454FdT4HzT5SzSO284KtwaE0N+94r4kuSGIK+hyrIyHUmjgcJFusGY7kdCIbi7ROQIX9aOrDiDUvR30ezBy0Leer4oJjUE30s3XI/Vp9m6lZr66RYyUzFzZvVngYUG2NujvU29Q5N0dIT8x6pVGvLQJH1ZRF4cK3mU5Shqki7nCmhHF22MrZDoVYBAoHBALCmIC5sty9Vn5N2pzyR0sZTXBKnoYo8eEECjSXEoRP7/JPuD4ykenFikJYk+gkh2eTxgnlb9+WDpgb47nI7/3uOKlkaOyf+g3wP1zYeGoFqAfqJ352Q+SWFMenamorHBKX7ulwv04OSJN/OesiL5UgcnwN0VKkkhxlxLzJefXLKTZJoH6weTa5qf7QAZyw0yS0KbeYg4y4mEuFtr4Z52n3QgCx7KLunY/yU7SuGOyFwyIscU6YKQ4Zh4T1KMrv4fwKBwExZH2XIvvu7setxg6IkNMnNJdeJ6mefk3kxdZX+ZprO3cyh60bv0lSjqrKADQuy2MknQKjx0NvI4vbzmhUUb18Koy66oh4r7M5iSKofWs3rybfeGjF4StETSW7fS1nLGlicYqIbX6TT4Hhg91RwT33vrEvvlBQFowV8cR5OmGq6aW6H6bh3UkzcxV2HI/QvwW2mvRvDQycnjfGjuYbVwi6tn2O2wet0Dka7y/AZfp9OBLJRBZJNoIViTn4Lx9FHlQ==" +// nolint:lll const testCACertificate = "MIIFmDCCBACgAwIBAgIUOlITUGXFrKZesL4LlawzVxLTXFYwDQYJKoZIhvcNAQELBQAwNzELMAkGA1UEBhMCQVUxFDASBgNVBAoMC0NBY2VydCBJbmMuMRIwEAYDVQQDDAlUZXN0IFJvb3QwHhcNMjEwMTA4MTMwNDM5WhcNMjYwMTA3MTMwNDM5WjA9MQswCQYDVQQGEwJBVTEUMBIGA1UECgwLQ0FjZXJ0IEluYy4xGDAWBgNVBAMMD0NsYXNzIDMgVGVzdCBDQTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKjFj9hHPw8SeZ+Wwr9HuqEwaBr7KZatah1bcDwxC/28JYVG04oBj0BqFCvmN3ktk+RWP6qXX6o1AYvxZz4ijcBJ5pGLkmpEkkuhsq4LD/O+s9cV7YedAiJFJwD0dWCdG2JoVKRIrUSYKFmuZst20vi7jbdYFRw/s8gjpW3zSWUyC50BxS7c4v8D4r5U1qVAycx1r76PiouHtvalkTGgGrYly1Kv8FtOn7EUmvaZZITb+45YPYavGNkhIntJkb7shYCCr5AZnE91+cj4QlmT6JrKkJxCVyp8Z6NEpKzPl6FlgXrrGD7GZH15JxsMv8L6DG1U+TH6sjGGNQk8PP82kg6Av7I/F5gC+OLfhLGfiRFGIElVumNy9ionrqgom4OAfhvKiOAEJtnI12p419ZFk1AgrRjuspcUSjatMwloX5RNotrP/uL/kbh0NZWmJ35d7yEGR89P4LUCJUdgKl3Njg0aEzvHfNHV2Y8CJp64x0P8h8eCxKxzKazY0G9Aaa7QgwIDAQABo4IBlDCCAZAwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBRI3ALoQ7dcRqu0gHtxaLVAv2DcOzAfBgNVHSMEGDAWgBTrwahlUMEoV1OavhMbkLcSar8PIzB3BggrBgEFBQcBAQRrMGkwNwYIKwYBBQUHMAKGK2h0dHA6Ly90ZXN0LmNhY2VydC5sb2NhbGhvc3QvY2Evcm9vdC9jYS5jcnQwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLnRlc3QuY2FjZXJ0LmxvY2FsaG9zdC8wPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC50ZXN0LmNhY2VydC5sb2NhbGhvc3QvY2xhc3MzLmNybDBUBgNVHSAETTBLMEkGCCsGAQUFBwIBMD0wOwYIKwYBBQUHAgEWL2h0dHA6Ly90ZXN0LmNhY2VydC5sb2NhbGhvc3QvY2EvY2xhc3MzL2Nwcy5odG1sMA0GCSqGSIb3DQEBCwUAA4IBgQAcqK68GOxTfM9zSRbHWHchsbiyKcbxPo42se9dm/nLHT/N2XEW9Ycj5dZD8+XgoW8dVPS3uVZGj57Pr8ix3OXhMKGqcdO2QRAQaoyjw7t9dCkaJ8b7h39sY/5pFSSIdYAyyb9uPgJ1FPLueOqm3bZHVFcbiiA8/miiwGWPVEfK7zdEmFKMAkY2wYtWBeovKNVnCbuQ1Pd8CxvkCs5R9KnMfbU7bgJK8zkhlHwdtalmg2IS4yMuvYeL9S3QwL7fYcCjjTLCKwkj3frsnkRC5pGPHQ6/iVVbdsqAI70A1Uqcl15Jcpzg0Nc2EABjhbWO7gLpHpzMI5Alt+Tr+oWhe2M7wnBhuojgwASA10CnXT27GYXziIzr8d3P+T0PVLD2WcvQeEUJoQySw6W8CIkaZEZG6YBWjrAkGcO6JB+YJ5UiJOCHA6W4pmwNkGR2oh6JMQCUikaFVywb1HMIGOINOBHymj4KkuywC2w6SXMD4OqJcsCmHSNcqjFvcT/22kYCtDE="