mirror of
https://github.com/sonr-io/crypto.git
synced 2026-08-02 23:41:37 +00:00
No commit suggestions generated
This commit is contained in:
+352
@@ -0,0 +1,352 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user