Files
motr-enclave/internal/crypto/ucan/crypto.go

353 lines
9.5 KiB
Go

// Package ucan provides User-Controlled Authorization Networks (UCAN) implementation
// for decentralized authorization and capability delegation in the Sonr network.
// This package handles JWT-based tokens, cryptographic verification, and resource capabilities.
package ucan
import (
"crypto"
"crypto/ed25519"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"fmt"
"hash"
"strings"
"github.com/golang-jwt/jwt/v5"
)
// SupportedSigningMethods returns the list of supported JWT signing methods for UCAN
func SupportedSigningMethods() []jwt.SigningMethod {
return []jwt.SigningMethod{
jwt.SigningMethodRS256,
jwt.SigningMethodRS384,
jwt.SigningMethodRS512,
jwt.SigningMethodEdDSA,
}
}
// ValidateSignature validates the cryptographic signature of a UCAN token
func ValidateSignature(tokenString string, verifyKey any) error {
// Parse token without verification first to get signing method
token, err := jwt.ParseWithClaims(
tokenString,
jwt.MapClaims{},
func(token *jwt.Token) (any, error) {
return verifyKey, nil
},
)
if err != nil {
return fmt.Errorf("signature validation failed: %w", err)
}
if !token.Valid {
return fmt.Errorf("token signature is invalid")
}
return nil
}
// ExtractUnsignedToken extracts the unsigned portion of a JWT token (header + payload)
func ExtractUnsignedToken(tokenString string) (string, error) {
parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
return "", fmt.Errorf("invalid JWT format: expected 3 parts, got %d", len(parts))
}
return strings.Join(parts[:2], "."), nil
}
// ExtractSignature extracts the signature portion of a JWT token
func ExtractSignature(tokenString string) ([]byte, error) {
parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("invalid JWT format: expected 3 parts, got %d", len(parts))
}
signatureBytes, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil {
return nil, fmt.Errorf("failed to decode signature: %w", err)
}
return signatureBytes, nil
}
// VerifyRSASignature verifies an RSA signature using the specified hash algorithm
func VerifyRSASignature(
signingString string,
signature []byte,
publicKey *rsa.PublicKey,
hashAlg crypto.Hash,
) error {
// Create hash of signing string
hasher := hashAlg.New()
hasher.Write([]byte(signingString))
hashed := hasher.Sum(nil)
// Verify signature
err := rsa.VerifyPKCS1v15(publicKey, hashAlg, hashed, signature)
if err != nil {
return fmt.Errorf("RSA signature verification failed: %w", err)
}
return nil
}
// VerifyEd25519Signature verifies an Ed25519 signature
func VerifyEd25519Signature(
signingString string,
signature []byte,
publicKey ed25519.PublicKey,
) error {
valid := ed25519.Verify(publicKey, []byte(signingString), signature)
if !valid {
return fmt.Errorf("Ed25519 signature verification failed")
}
return nil
}
// GetHashAlgorithmForMethod returns the appropriate hash algorithm for a JWT signing method
func GetHashAlgorithmForMethod(method jwt.SigningMethod) (crypto.Hash, error) {
switch method {
case jwt.SigningMethodRS256:
return crypto.SHA256, nil
case jwt.SigningMethodRS384:
return crypto.SHA384, nil
case jwt.SigningMethodRS512:
return crypto.SHA512, nil
case jwt.SigningMethodEdDSA:
// Ed25519 doesn't use a separate hash algorithm
return crypto.Hash(0), nil
default:
return crypto.Hash(0), fmt.Errorf("unsupported signing method: %v", method)
}
}
// CreateHasher creates a hasher for the given crypto.Hash algorithm
func CreateHasher(hashAlg crypto.Hash) (hash.Hash, error) {
switch hashAlg {
case crypto.SHA256:
return sha256.New(), nil
case crypto.SHA384:
return sha512.New384(), nil
case crypto.SHA512:
return sha512.New(), nil
default:
return nil, fmt.Errorf("unsupported hash algorithm: %v", hashAlg)
}
}
// SigningValidator provides cryptographic validation for UCAN tokens
type SigningValidator struct {
allowedMethods map[string]jwt.SigningMethod
}
// NewSigningValidator creates a new signing validator with default allowed methods
func NewSigningValidator() *SigningValidator {
allowed := make(map[string]jwt.SigningMethod)
for _, method := range SupportedSigningMethods() {
allowed[method.Alg()] = method
}
return &SigningValidator{
allowedMethods: allowed,
}
}
// NewSigningValidatorWithMethods creates a validator with specific allowed methods
func NewSigningValidatorWithMethods(methods []jwt.SigningMethod) *SigningValidator {
allowed := make(map[string]jwt.SigningMethod)
for _, method := range methods {
allowed[method.Alg()] = method
}
return &SigningValidator{
allowedMethods: allowed,
}
}
// ValidateSigningMethod checks if a signing method is allowed
func (sv *SigningValidator) ValidateSigningMethod(method jwt.SigningMethod) error {
if _, ok := sv.allowedMethods[method.Alg()]; !ok {
return fmt.Errorf("signing method %s is not allowed", method.Alg())
}
return nil
}
// ValidateTokenSignature validates the cryptographic signature of a token
func (sv *SigningValidator) ValidateTokenSignature(
tokenString string,
keyFunc jwt.Keyfunc,
) (*jwt.Token, error) {
// Parse with validation
token, err := jwt.Parse(tokenString, keyFunc, jwt.WithValidMethods(sv.getAllowedMethodNames()))
if err != nil {
return nil, fmt.Errorf("token signature validation failed: %w", err)
}
// Additional signing method validation
if err := sv.ValidateSigningMethod(token.Method); err != nil {
return nil, err
}
return token, nil
}
// getAllowedMethodNames returns the names of allowed signing methods
func (sv *SigningValidator) getAllowedMethodNames() []string {
methods := make([]string, 0, len(sv.allowedMethods))
for name := range sv.allowedMethods {
methods = append(methods, name)
}
return methods
}
// KeyValidator provides validation for cryptographic keys
type KeyValidator struct{}
// NewKeyValidator creates a new key validator
func NewKeyValidator() *KeyValidator {
return &KeyValidator{}
}
// ValidateRSAPublicKey validates an RSA public key for UCAN usage
func (kv *KeyValidator) ValidateRSAPublicKey(key *rsa.PublicKey) error {
if key == nil {
return fmt.Errorf("RSA public key is nil")
}
// Check minimum key size (2048 bits recommended for security)
keySize := key.N.BitLen()
if keySize < 2048 {
return fmt.Errorf("RSA key size too small: %d bits (minimum 2048 bits required)", keySize)
}
// Check maximum reasonable key size to prevent DoS
if keySize > 8192 {
return fmt.Errorf("RSA key size too large: %d bits (maximum 8192 bits allowed)", keySize)
}
return nil
}
// ValidateEd25519PublicKey validates an Ed25519 public key for UCAN usage
func (kv *KeyValidator) ValidateEd25519PublicKey(key ed25519.PublicKey) error {
if key == nil {
return fmt.Errorf("Ed25519 public key is nil")
}
if len(key) != ed25519.PublicKeySize {
return fmt.Errorf(
"invalid Ed25519 public key size: %d bytes (expected %d)",
len(key),
ed25519.PublicKeySize,
)
}
return nil
}
// SignatureInfo contains information about a token's signature
type SignatureInfo struct {
Algorithm string
KeyType string
SigningString string
Signature []byte
Valid bool
}
// ExtractSignatureInfo extracts signature information from a JWT token
func ExtractSignatureInfo(tokenString string, verifyKey any) (*SignatureInfo, error) {
// Parse token to get method and claims
token, err := jwt.Parse(tokenString, func(t *jwt.Token) (any, error) {
return verifyKey, nil
})
var sigInfo SignatureInfo
sigInfo.Valid = (err == nil && token.Valid)
if token != nil {
sigInfo.Algorithm = token.Method.Alg()
// Get signing string
parts := strings.Split(tokenString, ".")
if len(parts) >= 2 {
sigInfo.SigningString = strings.Join(parts[:2], ".")
}
// Get signature
if len(parts) == 3 {
sig, decodeErr := base64.RawURLEncoding.DecodeString(parts[2])
if decodeErr == nil {
sigInfo.Signature = sig
}
}
// Determine key type
switch verifyKey.(type) {
case *rsa.PublicKey:
sigInfo.KeyType = "RSA"
case ed25519.PublicKey:
sigInfo.KeyType = "Ed25519"
default:
sigInfo.KeyType = "Unknown"
}
}
return &sigInfo, err
}
// SecurityConfig contains security configuration for UCAN validation
type SecurityConfig struct {
AllowedSigningMethods []jwt.SigningMethod
MinRSAKeySize int
MaxRSAKeySize int
RequireSecureAlgs bool
}
// DefaultSecurityConfig returns a secure default configuration
func DefaultSecurityConfig() *SecurityConfig {
return &SecurityConfig{
AllowedSigningMethods: SupportedSigningMethods(),
MinRSAKeySize: 2048,
MaxRSAKeySize: 8192,
RequireSecureAlgs: true,
}
}
// RestrictiveSecurityConfig returns a more restrictive configuration
func RestrictiveSecurityConfig() *SecurityConfig {
return &SecurityConfig{
AllowedSigningMethods: []jwt.SigningMethod{
jwt.SigningMethodRS256, // Only RS256 and EdDSA
jwt.SigningMethodEdDSA,
},
MinRSAKeySize: 3072, // Higher minimum
MaxRSAKeySize: 4096, // Lower maximum
RequireSecureAlgs: true,
}
}
// ValidateSecurityConfig validates that a security configuration is reasonable
func ValidateSecurityConfig(config *SecurityConfig) error {
if len(config.AllowedSigningMethods) == 0 {
return fmt.Errorf("no signing methods allowed")
}
if config.MinRSAKeySize < 1024 {
return fmt.Errorf("minimum RSA key size too small: %d", config.MinRSAKeySize)
}
if config.MaxRSAKeySize < config.MinRSAKeySize {
return fmt.Errorf("maximum RSA key size smaller than minimum")
}
if config.MaxRSAKeySize > 16384 {
return fmt.Errorf("maximum RSA key size too large: %d", config.MaxRSAKeySize)
}
return nil
}