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:
eyedeekay
2025-10-19 16:37:03 -04:00
parent b6ead96fdc
commit 2130b0f435
4 changed files with 496 additions and 1 deletions

View File

@@ -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
}

View 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)
}

View 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
View 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")
})
}