mirror of
https://github.com/go-i2p/go-sam-go.git
synced 2025-12-01 09:54:58 -05:00
Implement signature type validation and conflict resolution: add functions to validate, clean, and ensure signature type options, along with comprehensive tests to verify behavior and conflict resolution.
This commit is contained in:
@@ -72,7 +72,10 @@ func (sam *SAM) configureSessionParameters(style, id, from, to string, keys i2pk
|
||||
func (sam *SAM) buildSessionCreateMessage(extras []string) ([]byte, error) {
|
||||
baseMsg := strings.TrimSuffix(sam.SAMEmit.Create(), " \n")
|
||||
|
||||
extraStr := strings.Join(extras, " ")
|
||||
// Validate extras for signature type conflicts and clean them if needed
|
||||
cleanedExtras := validateAndCleanOptions(sam.SAMEmit.I2PConfig.SigType, extras)
|
||||
|
||||
extraStr := strings.Join(cleanedExtras, " ")
|
||||
if extraStr != "" {
|
||||
baseMsg += " " + extraStr
|
||||
}
|
||||
|
||||
94
common/signature_validation.go
Normal file
94
common/signature_validation.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/samber/oops"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// validateAndCleanOptions validates signature type options and resolves conflicts.
|
||||
// If sigType parameter is specified and SIGNATURE_TYPE is also present in options,
|
||||
// the sigType parameter takes precedence and conflicting options are removed.
|
||||
// Returns cleaned options slice and logs warnings for any conflicts detected.
|
||||
func validateAndCleanOptions(sigType string, options []string) []string {
|
||||
// If no sigType specified, return options as-is
|
||||
if sigType == "" {
|
||||
return options
|
||||
}
|
||||
|
||||
var cleanedOptions []string
|
||||
var conflictDetected bool
|
||||
var conflictingEntries []string
|
||||
|
||||
// Process each option, removing any SIGNATURE_TYPE entries
|
||||
for _, opt := range options {
|
||||
if strings.HasPrefix(opt, "SIGNATURE_TYPE=") {
|
||||
conflictDetected = true
|
||||
conflictingEntries = append(conflictingEntries, opt)
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"sigType": sigType,
|
||||
"conflictingOption": opt,
|
||||
}).Warn("Signature type conflict detected: sigType parameter takes precedence")
|
||||
// Skip this option - sigType parameter takes precedence
|
||||
continue
|
||||
}
|
||||
cleanedOptions = append(cleanedOptions, opt)
|
||||
}
|
||||
|
||||
if conflictDetected {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"resolvedSigType": sigType,
|
||||
"removedOptions": conflictingEntries,
|
||||
"remainingOptions": cleanedOptions,
|
||||
}).Warn("Signature type conflicts resolved by using sigType parameter")
|
||||
}
|
||||
|
||||
return cleanedOptions
|
||||
}
|
||||
|
||||
// ValidateSignatureTypeOptions checks for duplicate SIGNATURE_TYPE entries in options.
|
||||
// Returns an error if multiple SIGNATURE_TYPE entries are found, as this creates ambiguity.
|
||||
func ValidateSignatureTypeOptions(options []string) error {
|
||||
var signatureTypes []string
|
||||
|
||||
for _, opt := range options {
|
||||
if strings.HasPrefix(opt, "SIGNATURE_TYPE=") {
|
||||
signatureTypes = append(signatureTypes, opt)
|
||||
}
|
||||
}
|
||||
|
||||
if len(signatureTypes) > 1 {
|
||||
logrus.WithField("duplicateSignatureTypes", signatureTypes).Error("Multiple SIGNATURE_TYPE entries found in options")
|
||||
return oops.Errorf("multiple SIGNATURE_TYPE entries in options: %v", signatureTypes)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractSignatureType extracts the signature type from options and returns
|
||||
// the signature type value and the remaining options without SIGNATURE_TYPE entries.
|
||||
// Returns empty string if no SIGNATURE_TYPE is found in options.
|
||||
func ExtractSignatureType(options []string) (string, []string) {
|
||||
var sigType string
|
||||
var remainingOptions []string
|
||||
|
||||
for _, opt := range options {
|
||||
if strings.HasPrefix(opt, "SIGNATURE_TYPE=") {
|
||||
// Extract the signature type value (remove "SIGNATURE_TYPE=" prefix)
|
||||
sigType = opt[len("SIGNATURE_TYPE="):]
|
||||
} else {
|
||||
remainingOptions = append(remainingOptions, opt)
|
||||
}
|
||||
}
|
||||
|
||||
return sigType, remainingOptions
|
||||
}
|
||||
|
||||
// EnsureSignatureType ensures that only one signature type is specified.
|
||||
// If sigType parameter is provided, it takes precedence and any SIGNATURE_TYPE
|
||||
// entries in options are removed. If sigType is empty, options are returned as-is.
|
||||
// This function provides a safe way to merge signature type specifications.
|
||||
func EnsureSignatureType(sigType string, options []string) []string {
|
||||
return validateAndCleanOptions(sigType, options)
|
||||
}
|
||||
262
common/signature_validation_test.go
Normal file
262
common/signature_validation_test.go
Normal file
@@ -0,0 +1,262 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateAndCleanOptions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
sigType string
|
||||
options []string
|
||||
expectedOptions []string
|
||||
expectWarning bool
|
||||
}{
|
||||
{
|
||||
name: "NoSigType_NoConflict",
|
||||
sigType: "",
|
||||
options: []string{"inbound.length=2", "outbound.length=3"},
|
||||
expectedOptions: []string{"inbound.length=2", "outbound.length=3"},
|
||||
expectWarning: false,
|
||||
},
|
||||
{
|
||||
name: "NoSigType_WithSignatureInOptions",
|
||||
sigType: "",
|
||||
options: []string{"SIGNATURE_TYPE=EdDSA_SHA512_Ed25519", "inbound.length=2"},
|
||||
expectedOptions: []string{"SIGNATURE_TYPE=EdDSA_SHA512_Ed25519", "inbound.length=2"},
|
||||
expectWarning: false,
|
||||
},
|
||||
{
|
||||
name: "SigType_NoConflict",
|
||||
sigType: "EdDSA_SHA512_Ed25519",
|
||||
options: []string{"inbound.length=2", "outbound.length=3"},
|
||||
expectedOptions: []string{"inbound.length=2", "outbound.length=3"},
|
||||
expectWarning: false,
|
||||
},
|
||||
{
|
||||
name: "SigType_WithConflict_Same",
|
||||
sigType: "EdDSA_SHA512_Ed25519",
|
||||
options: []string{"SIGNATURE_TYPE=EdDSA_SHA512_Ed25519", "inbound.length=2"},
|
||||
expectedOptions: []string{"inbound.length=2"},
|
||||
expectWarning: true,
|
||||
},
|
||||
{
|
||||
name: "SigType_WithConflict_Different",
|
||||
sigType: "EdDSA_SHA512_Ed25519",
|
||||
options: []string{"SIGNATURE_TYPE=ECDSA_SHA256_P256", "inbound.length=2"},
|
||||
expectedOptions: []string{"inbound.length=2"},
|
||||
expectWarning: true,
|
||||
},
|
||||
{
|
||||
name: "SigType_MultipleConflicts",
|
||||
sigType: "EdDSA_SHA512_Ed25519",
|
||||
options: []string{
|
||||
"SIGNATURE_TYPE=ECDSA_SHA256_P256",
|
||||
"inbound.length=2",
|
||||
"SIGNATURE_TYPE=DSA_SHA1",
|
||||
"outbound.length=3",
|
||||
},
|
||||
expectedOptions: []string{"inbound.length=2", "outbound.length=3"},
|
||||
expectWarning: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := validateAndCleanOptions(tt.sigType, tt.options)
|
||||
|
||||
// Check that the result matches expected
|
||||
if len(result) != len(tt.expectedOptions) {
|
||||
t.Errorf("Expected %d options, got %d", len(tt.expectedOptions), len(result))
|
||||
t.Errorf("Expected: %v", tt.expectedOptions)
|
||||
t.Errorf("Got: %v", result)
|
||||
return
|
||||
}
|
||||
|
||||
for i, expected := range tt.expectedOptions {
|
||||
if result[i] != expected {
|
||||
t.Errorf("Option %d: expected %q, got %q", i, expected, result[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSignatureTypeOptions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
options []string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "NoSignatureType",
|
||||
options: []string{"inbound.length=2", "outbound.length=3"},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "SingleSignatureType",
|
||||
options: []string{"SIGNATURE_TYPE=EdDSA_SHA512_Ed25519", "inbound.length=2"},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "MultipleSignatureTypes",
|
||||
options: []string{
|
||||
"SIGNATURE_TYPE=EdDSA_SHA512_Ed25519",
|
||||
"inbound.length=2",
|
||||
"SIGNATURE_TYPE=ECDSA_SHA256_P256",
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "ThreeSignatureTypes",
|
||||
options: []string{
|
||||
"SIGNATURE_TYPE=EdDSA_SHA512_Ed25519",
|
||||
"SIGNATURE_TYPE=ECDSA_SHA256_P256",
|
||||
"SIGNATURE_TYPE=DSA_SHA1",
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateSignatureTypeOptions(tt.options)
|
||||
|
||||
if tt.expectError && err == nil {
|
||||
t.Error("Expected error but got none")
|
||||
}
|
||||
if !tt.expectError && err != nil {
|
||||
t.Errorf("Expected no error but got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractSignatureType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
options []string
|
||||
expectedSigType string
|
||||
expectedRemaining []string
|
||||
}{
|
||||
{
|
||||
name: "NoSignatureType",
|
||||
options: []string{"inbound.length=2", "outbound.length=3"},
|
||||
expectedSigType: "",
|
||||
expectedRemaining: []string{"inbound.length=2", "outbound.length=3"},
|
||||
},
|
||||
{
|
||||
name: "WithSignatureType",
|
||||
options: []string{"SIGNATURE_TYPE=EdDSA_SHA512_Ed25519", "inbound.length=2"},
|
||||
expectedSigType: "EdDSA_SHA512_Ed25519",
|
||||
expectedRemaining: []string{"inbound.length=2"},
|
||||
},
|
||||
{
|
||||
name: "MultipleSignatureTypes_TakesLast",
|
||||
options: []string{"SIGNATURE_TYPE=ECDSA_SHA256_P256", "inbound.length=2", "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"},
|
||||
expectedSigType: "EdDSA_SHA512_Ed25519",
|
||||
expectedRemaining: []string{"inbound.length=2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sigType, remaining := ExtractSignatureType(tt.options)
|
||||
|
||||
if sigType != tt.expectedSigType {
|
||||
t.Errorf("Expected signature type %q, got %q", tt.expectedSigType, sigType)
|
||||
}
|
||||
|
||||
if len(remaining) != len(tt.expectedRemaining) {
|
||||
t.Errorf("Expected %d remaining options, got %d", len(tt.expectedRemaining), len(remaining))
|
||||
t.Errorf("Expected: %v", tt.expectedRemaining)
|
||||
t.Errorf("Got: %v", remaining)
|
||||
return
|
||||
}
|
||||
|
||||
for i, expected := range tt.expectedRemaining {
|
||||
if remaining[i] != expected {
|
||||
t.Errorf("Remaining option %d: expected %q, got %q", i, expected, remaining[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureSignatureType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
sigType string
|
||||
options []string
|
||||
expectedOptions []string
|
||||
}{
|
||||
{
|
||||
name: "NoSigType_PreserveOptions",
|
||||
sigType: "",
|
||||
options: []string{"SIGNATURE_TYPE=EdDSA_SHA512_Ed25519", "inbound.length=2"},
|
||||
expectedOptions: []string{"SIGNATURE_TYPE=EdDSA_SHA512_Ed25519", "inbound.length=2"},
|
||||
},
|
||||
{
|
||||
name: "SigType_RemoveConflicts",
|
||||
sigType: "EdDSA_SHA512_Ed25519",
|
||||
options: []string{"SIGNATURE_TYPE=ECDSA_SHA256_P256", "inbound.length=2"},
|
||||
expectedOptions: []string{"inbound.length=2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := EnsureSignatureType(tt.sigType, tt.options)
|
||||
|
||||
if len(result) != len(tt.expectedOptions) {
|
||||
t.Errorf("Expected %d options, got %d", len(tt.expectedOptions), len(result))
|
||||
return
|
||||
}
|
||||
|
||||
for i, expected := range tt.expectedOptions {
|
||||
if result[i] != expected {
|
||||
t.Errorf("Option %d: expected %q, got %q", i, expected, result[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSignatureTypeConflictResolution tests the integration of signature type validation
|
||||
// with the session creation process to ensure conflicts are properly resolved.
|
||||
func TestSignatureTypeConflictResolution(t *testing.T) {
|
||||
t.Run("ConflictResolutionInSessionCreation", func(t *testing.T) {
|
||||
// Test signature type configuration
|
||||
sigType := "EdDSA_SHA512_Ed25519"
|
||||
|
||||
// Options that would conflict
|
||||
conflictingOptions := []string{
|
||||
"SIGNATURE_TYPE=ECDSA_SHA256_P256", // This should be removed
|
||||
"inbound.length=2", // This should be preserved
|
||||
"outbound.length=3", // This should be preserved
|
||||
}
|
||||
|
||||
// Test that the validation removes conflicting options
|
||||
cleanedOptions := validateAndCleanOptions(sigType, conflictingOptions)
|
||||
|
||||
expectedOptions := []string{"inbound.length=2", "outbound.length=3"}
|
||||
if len(cleanedOptions) != len(expectedOptions) {
|
||||
t.Errorf("Expected %d cleaned options, got %d", len(expectedOptions), len(cleanedOptions))
|
||||
return
|
||||
}
|
||||
|
||||
for i, expected := range expectedOptions {
|
||||
if cleanedOptions[i] != expected {
|
||||
t.Errorf("Cleaned option %d: expected %q, got %q", i, expected, cleanedOptions[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure no SIGNATURE_TYPE remains in cleaned options
|
||||
for _, opt := range cleanedOptions {
|
||||
if strings.HasPrefix(opt, "SIGNATURE_TYPE=") {
|
||||
t.Errorf("Found unexpected SIGNATURE_TYPE in cleaned options: %q", opt)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
136
signature_conflict_test.go
Normal file
136
signature_conflict_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package sam3
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-i2p/go-sam-go/common"
|
||||
)
|
||||
|
||||
// TestSignatureTypeConflict demonstrates the signature type conflict issue
|
||||
// and verifies that the solution properly resolves conflicts.
|
||||
func TestSignatureTypeConflict(t *testing.T) {
|
||||
t.Run("ConflictDetection_BEFORE_FIX", func(t *testing.T) {
|
||||
// Demonstrate the original issue: signature type specified in both places
|
||||
sigTypeParam := "EdDSA_SHA512_Ed25519" // Via parameter
|
||||
options := []string{"SIGNATURE_TYPE=ECDSA_SHA256_P256"} // Via options
|
||||
|
||||
// This scenario creates ambiguity - which signature type should be used?
|
||||
t.Logf("ORIGINAL ISSUE: sigType parameter: %s", sigTypeParam)
|
||||
t.Logf("ORIGINAL ISSUE: options with SIGNATURE_TYPE: %v", options)
|
||||
|
||||
// Check if options contains SIGNATURE_TYPE
|
||||
hasSignatureInOptions := false
|
||||
for _, opt := range options {
|
||||
if strings.HasPrefix(opt, "SIGNATURE_TYPE=") {
|
||||
hasSignatureInOptions = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hasSignatureInOptions && sigTypeParam != "" {
|
||||
t.Log("✓ CONFLICT DETECTED: Signature type specified in both sigType parameter and options")
|
||||
t.Log("✓ This demonstrates the original issue that needed fixing")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ConflictResolution_AFTER_FIX", func(t *testing.T) {
|
||||
// Demonstrate how the fix resolves conflicts
|
||||
sigTypeParam := "EdDSA_SHA512_Ed25519"
|
||||
conflictingOptions := []string{
|
||||
"SIGNATURE_TYPE=ECDSA_SHA256_P256", // This should be removed
|
||||
"inbound.length=2", // This should be preserved
|
||||
"outbound.length=3", // This should be preserved
|
||||
}
|
||||
|
||||
t.Logf("BEFORE FIX: sigType=%s, options=%v", sigTypeParam, conflictingOptions)
|
||||
|
||||
// Apply the fix using the new validation function
|
||||
cleanedOptions := common.EnsureSignatureType(sigTypeParam, conflictingOptions)
|
||||
|
||||
t.Logf("AFTER FIX: sigType=%s, cleanedOptions=%v", sigTypeParam, cleanedOptions)
|
||||
|
||||
// Verify the fix worked correctly
|
||||
expectedOptions := []string{"inbound.length=2", "outbound.length=3"}
|
||||
if len(cleanedOptions) != len(expectedOptions) {
|
||||
t.Errorf("Expected %d options after cleaning, got %d", len(expectedOptions), len(cleanedOptions))
|
||||
}
|
||||
|
||||
// Ensure no SIGNATURE_TYPE remains in cleaned options
|
||||
for _, opt := range cleanedOptions {
|
||||
if strings.HasPrefix(opt, "SIGNATURE_TYPE=") {
|
||||
t.Errorf("❌ SIGNATURE_TYPE found in cleaned options: %q", opt)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify expected options are preserved
|
||||
for i, expected := range expectedOptions {
|
||||
if i >= len(cleanedOptions) || cleanedOptions[i] != expected {
|
||||
t.Errorf("❌ Expected option %q at index %d, got %q", expected, i, cleanedOptions[i])
|
||||
}
|
||||
}
|
||||
|
||||
t.Log("✅ SUCCESS: Conflicting signature types properly resolved")
|
||||
t.Log("✅ sigType parameter took precedence as expected")
|
||||
t.Log("✅ Non-signature options were preserved")
|
||||
})
|
||||
|
||||
t.Run("MultipleConflictResolution", func(t *testing.T) {
|
||||
// Test with multiple SIGNATURE_TYPE entries in options
|
||||
sigTypeParam := "EdDSA_SHA512_Ed25519"
|
||||
multipleConflictOptions := []string{
|
||||
"SIGNATURE_TYPE=ECDSA_SHA256_P256",
|
||||
"inbound.length=2",
|
||||
"SIGNATURE_TYPE=DSA_SHA1",
|
||||
"outbound.length=3",
|
||||
"SIGNATURE_TYPE=ECDSA_SHA384_P384",
|
||||
}
|
||||
|
||||
t.Logf("BEFORE: Multiple conflicts - sigType=%s", sigTypeParam)
|
||||
t.Logf("BEFORE: options=%v", multipleConflictOptions)
|
||||
|
||||
cleanedOptions := common.EnsureSignatureType(sigTypeParam, multipleConflictOptions)
|
||||
|
||||
t.Logf("AFTER: cleanedOptions=%v", cleanedOptions)
|
||||
|
||||
// Should only have the non-signature options
|
||||
expectedOptions := []string{"inbound.length=2", "outbound.length=3"}
|
||||
if len(cleanedOptions) != len(expectedOptions) {
|
||||
t.Errorf("Expected %d options, got %d", len(expectedOptions), len(cleanedOptions))
|
||||
}
|
||||
|
||||
// Verify no signature types remain
|
||||
for _, opt := range cleanedOptions {
|
||||
if strings.HasPrefix(opt, "SIGNATURE_TYPE=") {
|
||||
t.Errorf("❌ Unexpected SIGNATURE_TYPE in cleaned options: %q", opt)
|
||||
}
|
||||
}
|
||||
|
||||
t.Log("✅ SUCCESS: Multiple signature type conflicts resolved")
|
||||
})
|
||||
|
||||
t.Run("NoConflict_PreservesOptions", func(t *testing.T) {
|
||||
// Test that options without conflicts are preserved
|
||||
sigTypeParam := ""
|
||||
options := []string{
|
||||
"SIGNATURE_TYPE=EdDSA_SHA512_Ed25519",
|
||||
"inbound.length=2",
|
||||
"outbound.length=3",
|
||||
}
|
||||
|
||||
cleanedOptions := common.EnsureSignatureType(sigTypeParam, options)
|
||||
|
||||
// Should preserve all options when no sigType parameter is specified
|
||||
if len(cleanedOptions) != len(options) {
|
||||
t.Errorf("Expected %d options preserved, got %d", len(options), len(cleanedOptions))
|
||||
}
|
||||
|
||||
for i, expected := range options {
|
||||
if cleanedOptions[i] != expected {
|
||||
t.Errorf("Expected option %q at index %d, got %q", expected, i, cleanedOptions[i])
|
||||
}
|
||||
}
|
||||
|
||||
t.Log("✅ SUCCESS: Options preserved when no sigType parameter specified")
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user