Skip to content

Commit

Permalink
Merge pull request #647 from appital/tls-bytes
Browse files Browse the repository at this point in the history
Support TLS configuration as raw bytes
  • Loading branch information
ackleymi authored Aug 9, 2024
2 parents 6e564c1 + d3b4f98 commit 9191a58
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 59 deletions.
41 changes: 41 additions & 0 deletions config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,47 @@ const (
// - A filepath to a file with read access.
SocketCAFile string = "SocketCAFile"

// SocketPrivateKeyBytes is an optional value containing raw bytes of a PEM
// encoded private key to use for secure TLS communications.
// Must be used with SocketCertificateBytes.
// Must contain PEM encoded data.
//
// Required: No
//
// Default: N/A
//
// Valid Values:
// - Raw bytes containing a valid PEM encoded private key.
SocketPrivateKeyBytes string = "SocketPrivateKeyBytes"

// SocketCertificateBytes is an optional value containing raw bytes of a PEM
// encoded certificate to use for secure TLS communications.
// Must be used with SocketPrivateKeyBytes.
// Must contain PEM encoded data.
//
// Required: No
//
// Default: N/A
//
// Valid Values:
// - Raw bytes containing a valid PEM encoded certificate.
SocketCertificateBytes string = "SocketCertificateBytes"

// SocketCABytes is an optional value containing raw bytes of a PEM encoded
// root CA to use for secure TLS communications. For acceptors, client
// certificates will be verified against this CA. For initiators, clients
// will use the CA to verify the server certificate. If not configured,
// initiators will verify the server certificates using the host's root CA
// set.
//
// Required: No
//
// Default: N/A
//
// Valid Values:
// - Raw bytes containing a valid PEM encoded CA.
SocketCABytes string = "SocketCABytes"

// SocketInsecureSkipVerify controls whether a client verifies the server's certificate chain and host name.
// If SocketInsecureSkipVerify is set to Y, crypto/tls accepts any certificate presented by the server and any host name in that certificate.
// In this mode, TLS is susceptible to machine-in-the-middle attacks unless custom verification is used.
Expand Down
6 changes: 3 additions & 3 deletions session_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ func (f sessionFactory) newSession(
for _, dayStr := range dayStrs {
day, ok := dayLookup[dayStr]
if !ok {
err = IncorrectFormatForSetting{Setting: config.Weekdays, Value: weekdaysStr}
err = IncorrectFormatForSetting{Setting: config.Weekdays, Value: []byte(weekdaysStr)}
return
}
weekdays = append(weekdays, day)
Expand Down Expand Up @@ -315,7 +315,7 @@ func (f sessionFactory) newSession(
parseDay := func(setting, dayStr string) (day time.Weekday, err error) {
day, ok := dayLookup[dayStr]
if !ok {
return day, IncorrectFormatForSetting{Setting: setting, Value: dayStr}
return day, IncorrectFormatForSetting{Setting: setting, Value: []byte(dayStr)}
}
return
}
Expand Down Expand Up @@ -355,7 +355,7 @@ func (f sessionFactory) newSession(
s.timestampPrecision = Nanos

default:
err = IncorrectFormatForSetting{Setting: config.TimeStampPrecision, Value: precisionStr}
err = IncorrectFormatForSetting{Setting: config.TimeStampPrecision, Value: []byte(precisionStr)}
return
}
}
Expand Down
73 changes: 46 additions & 27 deletions session_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (

// SessionSettings maps session settings to values with typed accessors.
type SessionSettings struct {
settings map[string]string
settings map[string][]byte
}

// ConditionallyRequiredSetting indicates a missing setting.
Expand All @@ -37,8 +37,9 @@ func (e ConditionallyRequiredSetting) Error() string {

// IncorrectFormatForSetting indicates a setting that is incorrectly formatted.
type IncorrectFormatForSetting struct {
Setting, Value string
Err error
Setting string
Value []byte
Err error
}

func (e IncorrectFormatForSetting) Error() string {
Expand All @@ -47,7 +48,7 @@ func (e IncorrectFormatForSetting) Error() string {

// Init initializes or resets SessionSettings.
func (s *SessionSettings) Init() {
s.settings = make(map[string]string)
s.settings = make(map[string][]byte)
}

// NewSessionSettings returns a newly initialized SessionSettings instance.
Expand All @@ -58,8 +59,8 @@ func NewSessionSettings() *SessionSettings {
return s
}

// Set assigns a value to a setting on SessionSettings.
func (s *SessionSettings) Set(setting string, val string) {
// SetRaw assigns a value to a setting on SessionSettings.
func (s *SessionSettings) SetRaw(setting string, val []byte) {
// Lazy init.
if s.settings == nil {
s.Init()
Expand All @@ -68,69 +69,87 @@ func (s *SessionSettings) Set(setting string, val string) {
s.settings[setting] = val
}

// Set assigns a string value to a setting on SessionSettings.
func (s *SessionSettings) Set(setting string, val string) {
// Lazy init
if s.settings == nil {
s.Init()
}

s.settings[setting] = []byte(val)
}

// HasSetting returns true if a setting is set, false if not.
func (s *SessionSettings) HasSetting(setting string) bool {
_, ok := s.settings[setting]
return ok
}

// Setting is a settings string accessor. Returns an error if the setting is missing.
func (s *SessionSettings) Setting(setting string) (string, error) {
// RawSetting is a settings accessor that returns the raw byte slice value of
// the setting. Returns an error if the setting is missing.
func (s *SessionSettings) RawSetting(setting string) ([]byte, error) {
val, ok := s.settings[setting]
if !ok {
return val, ConditionallyRequiredSetting{setting}
return nil, ConditionallyRequiredSetting{Setting: setting}
}

return val, nil
}

// IntSetting returns the requested setting parsed as an int. Returns an errror if the setting is not set or cannot be parsed as an int.
func (s *SessionSettings) IntSetting(setting string) (val int, err error) {
stringVal, err := s.Setting(setting)
// Setting is a settings string accessor. Returns an error if the setting is missing.
func (s *SessionSettings) Setting(setting string) (string, error) {
val, err := s.RawSetting(setting)
if err != nil {
return "", err
}

return string(val), nil
}

// IntSetting returns the requested setting parsed as an int. Returns an errror if the setting is not set or cannot be parsed as an int.
func (s *SessionSettings) IntSetting(setting string) (int, error) {
rawVal, err := s.RawSetting(setting)
if err != nil {
return
return 0, err
}

if val, err = strconv.Atoi(stringVal); err != nil {
return val, IncorrectFormatForSetting{Setting: setting, Value: stringVal, Err: err}
if val, err := strconv.Atoi(string(rawVal)); err == nil {
return val, nil
}

return
return 0, IncorrectFormatForSetting{Setting: setting, Value: rawVal, Err: err}
}

// DurationSetting returns the requested setting parsed as a time.Duration.
// Returns an error if the setting is not set or cannot be parsed as a time.Duration.
func (s *SessionSettings) DurationSetting(setting string) (val time.Duration, err error) {
stringVal, err := s.Setting(setting)

func (s *SessionSettings) DurationSetting(setting string) (time.Duration, error) {
rawVal, err := s.RawSetting(setting)
if err != nil {
return
return 0, err
}

if val, err = time.ParseDuration(stringVal); err != nil {
return val, IncorrectFormatForSetting{Setting: setting, Value: stringVal, Err: err}
if val, err := time.ParseDuration(string(rawVal)); err == nil {
return val, nil
}

return
return 0, IncorrectFormatForSetting{Setting: setting, Value: rawVal, Err: err}
}

// BoolSetting returns the requested setting parsed as a boolean. Returns an error if the setting is not set or cannot be parsed as a bool.
func (s SessionSettings) BoolSetting(setting string) (bool, error) {
stringVal, err := s.Setting(setting)

rawVal, err := s.RawSetting(setting)
if err != nil {
return false, err
}

switch stringVal {
switch string(rawVal) {
case "Y", "y":
return true, nil
case "N", "n":
return false, nil
}

return false, IncorrectFormatForSetting{Setting: setting, Value: stringVal}
return false, IncorrectFormatForSetting{Setting: setting, Value: rawVal}
}

func (s *SessionSettings) overlay(overlay *SessionSettings) {
Expand Down
65 changes: 63 additions & 2 deletions session_settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
package quickfix

import (
"bytes"
"testing"
"time"

"github.com/quickfixgo/quickfix/config"
)
Expand Down Expand Up @@ -55,10 +57,15 @@ func TestSessionSettings_IntSettings(t *testing.T) {
}

s.Set(config.SocketAcceptPort, "notanint")
if _, err := s.IntSetting(config.SocketAcceptPort); err == nil {
_, err := s.IntSetting(config.SocketAcceptPort)
if err == nil {
t.Error("Expected error for unparsable value")
}

if err.Error() != `"notanint" is invalid for SocketAcceptPort` {
t.Errorf("Expected %s, got %s", `"notanint" is invalid for SocketAcceptPort`, err)
}

s.Set(config.SocketAcceptPort, "1005")
val, err := s.IntSetting(config.SocketAcceptPort)
if err != nil {
Expand All @@ -77,10 +84,15 @@ func TestSessionSettings_BoolSettings(t *testing.T) {
}

s.Set(config.ResetOnLogon, "notabool")
if _, err := s.BoolSetting(config.ResetOnLogon); err == nil {
_, err := s.BoolSetting(config.ResetOnLogon)
if err == nil {
t.Error("Expected error for unparsable value")
}

if err.Error() != `"notabool" is invalid for ResetOnLogon` {
t.Errorf("Expected %s, got %s", `"notabool" is invalid for ResetOnLogon`, err)
}

var boolTests = []struct {
input string
expected bool
Expand All @@ -105,6 +117,55 @@ func TestSessionSettings_BoolSettings(t *testing.T) {
}
}

func TestSessionSettings_DurationSettings(t *testing.T) {
s := NewSessionSettings()
if _, err := s.BoolSetting(config.ReconnectInterval); err == nil {
t.Error("Expected error for unknown setting")
}

s.Set(config.ReconnectInterval, "not duration")

_, err := s.DurationSetting(config.ReconnectInterval)
if err == nil {
t.Error("Expected error for unparsable value")
}

if err.Error() != `"not duration" is invalid for ReconnectInterval` {
t.Errorf("Expected %s, got %s", `"not duration" is invalid for ReconnectInterval`, err)
}

s.Set(config.ReconnectInterval, "10s")

got, err := s.DurationSetting(config.ReconnectInterval)
if err != nil {
t.Error("Unexpected err", err)
}

expected, _ := time.ParseDuration("10s")

if got != expected {
t.Errorf("Expected %v, got %v", expected, got)
}
}

func TestSessionSettings_ByteSettings(t *testing.T) {
s := NewSessionSettings()
if _, err := s.RawSetting(config.SocketPrivateKeyBytes); err == nil {
t.Error("Expected error for unknown setting")
}

s.SetRaw(config.SocketPrivateKeyBytes, []byte("pembytes"))

got, err := s.RawSetting(config.SocketPrivateKeyBytes)
if err != nil {
t.Error("Unexpected err", err)
}

if !bytes.Equal([]byte("pembytes"), got) {
t.Errorf("Expected %v, got %v", []byte("pembytes"), got)
}
}

func TestSessionSettings_Clone(t *testing.T) {
s := NewSessionSettings()

Expand Down
Loading

0 comments on commit 9191a58

Please sign in to comment.