diff --git a/xtypes/ecdsa_priv.go b/xtypes/ecdsa_priv.go index e04d8af..89132a6 100644 --- a/xtypes/ecdsa_priv.go +++ b/xtypes/ecdsa_priv.go @@ -29,7 +29,7 @@ var _ types.Redactor = &ECDSAPrivateKey{} // UnmarshalParam parses the input as a string. func (d *ECDSAPrivateKey) UnmarshalParam(in *string) error { var privK *ecdsa.PrivateKey - if in != nil { + if in != nil && *in != "" { var err error privK, err = parseECPrivKey(*in, d.Base64Encoder) if err != nil { @@ -65,6 +65,9 @@ func (d *ECDSAPrivateKey) Value() *ecdsa.PrivateKey { // ValueValid test if the provided parameter value is valid. Has no side // effects. func (d *ECDSAPrivateKey) ValueValid(s string) error { + if s == "" { + return types.ErrNoValue + } _, err := parseECPrivKey(s, d.Base64Encoder) return err } diff --git a/xtypes/ecdsa_priv_test.go b/xtypes/ecdsa_priv_test.go new file mode 100644 index 0000000..f5266ce --- /dev/null +++ b/xtypes/ecdsa_priv_test.go @@ -0,0 +1,142 @@ +package xtypes_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "reflect" + "testing" + + "github.com/simplesurance/proteus" + "github.com/simplesurance/proteus/internal/assert" + "github.com/simplesurance/proteus/sources/cfgtest" + "github.com/simplesurance/proteus/types" + "github.com/simplesurance/proteus/xtypes" +) + +func TestECDSAPrivateKey(t *testing.T) { + _, privateKeyStr := generateTestECKey(t) + defaultKey, _ := generateTestECKey(t) + + tests := []struct { + name string + params types.ParamValues + shouldErr bool + optionalIsNil bool + useDefault bool + }{ + { + name: "valid key for optional and required", + params: types.ParamValues{ + "": { + "optionalkey": privateKeyStr, + "requiredkey": privateKeyStr, + }, + }, + shouldErr: false, + optionalIsNil: false, + }, + { + name: "empty string for optional key", + params: types.ParamValues{ + "": { + "optionalkey": "", + "requiredkey": privateKeyStr, + }, + }, + shouldErr: false, + optionalIsNil: true, + }, + { + name: "empty string for optional key with default", + params: types.ParamValues{ + "": { + "optionalkey": "", + "requiredkey": privateKeyStr, + }, + }, + shouldErr: false, + optionalIsNil: false, + useDefault: true, + }, + { + name: "no value for optional key", + params: types.ParamValues{ + "": { + "requiredkey": privateKeyStr, + }, + }, + shouldErr: false, + optionalIsNil: true, + }, + { + name: "empty string for required key", + params: types.ParamValues{ + "": { + "requiredkey": "", + }, + }, + shouldErr: true, + }, + { + name: "no value for required key", + params: types.ParamValues{"": {}}, + shouldErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := struct { + OptionalKey *xtypes.ECDSAPrivateKey `param:",optional"` + RequiredKey *xtypes.ECDSAPrivateKey + }{} + + if tt.useDefault { + cfg.OptionalKey = &xtypes.ECDSAPrivateKey{DefaultValue: defaultKey} + } + + testProvider := cfgtest.New(tt.params) + defer testProvider.Stop() + + _, err := proteus.MustParse(&cfg, + proteus.WithProviders(testProvider)) + + if tt.shouldErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + if tt.useDefault { + assert.True(t, reflect.DeepEqual(defaultKey, cfg.OptionalKey.Value()), "default key should be used") + } else if tt.optionalIsNil { + assert.Equal(t, nil, cfg.OptionalKey.Value()) + } else { + assert.NotNil(t, cfg.OptionalKey.Value()) + } + + if _, ok := tt.params[""]["requiredkey"]; ok && tt.params[""]["requiredkey"] != "" { + assert.NotNil(t, cfg.RequiredKey.Value()) + } + } + }) + } +} + +func generateTestECKey(t *testing.T) (*ecdsa.PrivateKey, string) { + t.Helper() + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("failed to generate ECDSA private key: %v", err) + } + derBytes, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + t.Fatalf("failed to marshal ECDSA private key: %v", err) + } + pemBlock := &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: derBytes, + } + return privateKey, string(pem.EncodeToMemory(pemBlock)) +} diff --git a/xtypes/ecdsa_pub.go b/xtypes/ecdsa_pub.go index 93e34c6..fe8b294 100644 --- a/xtypes/ecdsa_pub.go +++ b/xtypes/ecdsa_pub.go @@ -27,7 +27,7 @@ var _ types.XType = &ECDSAPubKey{} // UnmarshalParam parses the input as a string. func (d *ECDSAPubKey) UnmarshalParam(in *string) error { var pubK *ecdsa.PublicKey - if in != nil { + if in != nil && *in != "" { var err error pubK, err = parseECPubKey(*in, d.Base64Encoder) if err != nil { @@ -63,6 +63,9 @@ func (d *ECDSAPubKey) Value() *ecdsa.PublicKey { // ValueValid test if the provided parameter value is valid. Has no side // effects. func (d *ECDSAPubKey) ValueValid(s string) error { + if s == "" { + return types.ErrNoValue + } _, err := parseECPubKey(s, d.Base64Encoder) return err } diff --git a/xtypes/ecdsa_pub_test.go b/xtypes/ecdsa_pub_test.go new file mode 100644 index 0000000..69402a4 --- /dev/null +++ b/xtypes/ecdsa_pub_test.go @@ -0,0 +1,142 @@ +package xtypes_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "reflect" + "testing" + + "github.com/simplesurance/proteus" + "github.com/simplesurance/proteus/internal/assert" + "github.com/simplesurance/proteus/sources/cfgtest" + "github.com/simplesurance/proteus/types" + "github.com/simplesurance/proteus/xtypes" +) + +func TestECDSAPublicKey(t *testing.T) { + _, publicKeyStr := generateTestECPubKey(t) + defaultKey, _ := generateTestECPubKey(t) + + tests := []struct { + name string + params types.ParamValues + shouldErr bool + optionalIsNil bool + useDefault bool + }{ + { + name: "valid key for optional and required", + params: types.ParamValues{ + "": { + "optionalkey": publicKeyStr, + "requiredkey": publicKeyStr, + }, + }, + shouldErr: false, + optionalIsNil: false, + }, + { + name: "empty string for optional key", + params: types.ParamValues{ + "": { + "optionalkey": "", + "requiredkey": publicKeyStr, + }, + }, + shouldErr: false, + optionalIsNil: true, + }, + { + name: "empty string for optional key with default", + params: types.ParamValues{ + "": { + "optionalkey": "", + "requiredkey": publicKeyStr, + }, + }, + shouldErr: false, + optionalIsNil: false, + useDefault: true, + }, + { + name: "no value for optional key", + params: types.ParamValues{ + "": { + "requiredkey": publicKeyStr, + }, + }, + shouldErr: false, + optionalIsNil: true, + }, + { + name: "empty string for required key", + params: types.ParamValues{ + "": { + "requiredkey": "", + }, + }, + shouldErr: true, + }, + { + name: "no value for required key", + params: types.ParamValues{"": {}}, + shouldErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := struct { + OptionalKey *xtypes.ECDSAPubKey `param:",optional"` + RequiredKey *xtypes.ECDSAPubKey + }{} + + if tt.useDefault { + cfg.OptionalKey = &xtypes.ECDSAPubKey{DefaultValue: defaultKey} + } + + testProvider := cfgtest.New(tt.params) + defer testProvider.Stop() + + _, err := proteus.MustParse(&cfg, + proteus.WithProviders(testProvider)) + + if tt.shouldErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + if tt.useDefault { + assert.True(t, reflect.DeepEqual(defaultKey, cfg.OptionalKey.Value()), "default key should be used") + } else if tt.optionalIsNil { + assert.Equal(t, nil, cfg.OptionalKey.Value()) + } else { + assert.NotNil(t, cfg.OptionalKey.Value()) + } + + if _, ok := tt.params[""]["requiredkey"]; ok && tt.params[""]["requiredkey"] != "" { + assert.NotNil(t, cfg.RequiredKey.Value()) + } + } + }) + } +} + +func generateTestECPubKey(t *testing.T) (*ecdsa.PublicKey, string) { + t.Helper() + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("failed to generate ECDSA private key: %v", err) + } + derBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) + if err != nil { + t.Fatalf("failed to marshal ECDSA public key: %v", err) + } + pemBlock := &pem.Block{ + Type: "PUBLIC KEY", + Bytes: derBytes, + } + return &privateKey.PublicKey, string(pem.EncodeToMemory(pemBlock)) +} diff --git a/xtypes/ed25519_priv.go b/xtypes/ed25519_priv.go index cfa68f0..41e6c19 100644 --- a/xtypes/ed25519_priv.go +++ b/xtypes/ed25519_priv.go @@ -29,7 +29,7 @@ var _ types.Redactor = &Ed25519PrivateKey{} // UnmarshalParam parses the input as a string. func (d *Ed25519PrivateKey) UnmarshalParam(in *string) error { var privK ed25519.PrivateKey - if in != nil { + if in != nil && *in != "" { var err error privK, err = parseEd25519PrivateKey(*in, d.Base64Encoder) if err != nil { @@ -65,6 +65,9 @@ func (d *Ed25519PrivateKey) Value() ed25519.PrivateKey { // ValueValid test if the provided parameter value is valid. Has no side // effects. func (d *Ed25519PrivateKey) ValueValid(s string) error { + if s == "" { + return types.ErrNoValue + } _, err := parseEd25519PrivateKey(s, d.Base64Encoder) return err } diff --git a/xtypes/ed25519_priv_test.go b/xtypes/ed25519_priv_test.go index c35b7ff..4cc572f 100644 --- a/xtypes/ed25519_priv_test.go +++ b/xtypes/ed25519_priv_test.go @@ -2,6 +2,10 @@ package xtypes_test import ( "bytes" + "crypto/ed25519" + "crypto/rand" + "crypto/x509" + "encoding/pem" "testing" "github.com/simplesurance/proteus" @@ -35,3 +39,130 @@ MC4CAQAwBQYDK2VwBCIEILeVMy9KxALhIuev5dTLmtb8u9weRofKqd+n7Vifb8G0 t.Logf("Priv: %v", params.Key.Value()) t.Logf("Public: %v", params.Key.Value().Public()) } + +func TestEd25519PrivateKeyEmpty(t *testing.T) { + _, privateKeyStr := generateTestEd25519Key(t) + defaultKey, _ := generateTestEd25519Key(t) + + tests := []struct { + name string + params types.ParamValues + shouldErr bool + optionalIsNil bool + useDefault bool + }{ + { + name: "valid key for optional and required", + params: types.ParamValues{ + "": { + "optionalkey": privateKeyStr, + "requiredkey": privateKeyStr, + }, + }, + shouldErr: false, + optionalIsNil: false, + }, + { + name: "empty string for optional key", + params: types.ParamValues{ + "": { + "optionalkey": "", + "requiredkey": privateKeyStr, + }, + }, + shouldErr: false, + optionalIsNil: true, + }, + { + name: "empty string for optional key with default", + params: types.ParamValues{ + "": { + "optionalkey": "", + "requiredkey": privateKeyStr, + }, + }, + shouldErr: false, + optionalIsNil: false, + useDefault: true, + }, + { + name: "no value for optional key", + params: types.ParamValues{ + "": { + "requiredkey": privateKeyStr, + }, + }, + shouldErr: false, + optionalIsNil: true, + }, + { + name: "empty string for required key", + params: types.ParamValues{ + "": { + "requiredkey": "", + }, + }, + shouldErr: true, + }, + { + name: "no value for required key", + params: types.ParamValues{"": {}}, + shouldErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := struct { + OptionalKey *xtypes.Ed25519PrivateKey `param:",optional"` + RequiredKey *xtypes.Ed25519PrivateKey + }{} + + if tt.useDefault { + cfg.OptionalKey = &xtypes.Ed25519PrivateKey{DefaultValue: defaultKey} + } + + testProvider := cfgtest.New(tt.params) + defer testProvider.Stop() + + _, err := proteus.MustParse(&cfg, + proteus.WithProviders(testProvider)) + + if tt.shouldErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + if tt.useDefault { + assert.True(t, bytes.Equal(defaultKey, cfg.OptionalKey.Value()), "default key should be used") + } else if tt.optionalIsNil { + if cfg.OptionalKey.Value() != nil { + t.Errorf("Expected nil, got %v", cfg.OptionalKey.Value()) + } + } else { + assert.NotNil(t, cfg.OptionalKey.Value()) + } + + if _, ok := tt.params[""]["requiredkey"]; ok && tt.params[""]["requiredkey"] != "" { + assert.NotNil(t, cfg.RequiredKey.Value()) + } + } + }) + } +} + +func generateTestEd25519Key(t *testing.T) (ed25519.PrivateKey, string) { + t.Helper() + _, privateKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("failed to generate Ed25519 private key: %v", err) + } + derBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + t.Fatalf("failed to marshal Ed25519 private key: %v", err) + } + pemBlock := &pem.Block{ + Type: "PRIVATE KEY", + Bytes: derBytes, + } + return privateKey, string(pem.EncodeToMemory(pemBlock)) +}