monorepo/cloud/maplefile-backend/pkg/security/jwt_utils/jwt.go

130 lines
4.3 KiB
Go

package jwt_utils
import (
"time"
"github.com/awnumar/memguard"
jwt "github.com/golang-jwt/jwt/v5"
)
// GenerateJWTToken Generate the `access token` for the secret key.
// SECURITY: HMAC secret is wiped from memory after signing to prevent memory dump attacks.
func GenerateJWTToken(hmacSecret []byte, uuid string, ad time.Duration) (string, time.Time, error) {
// SECURITY: Create a copy of the secret and wipe the copy after use
// Note: The original hmacSecret is owned by the caller
secretCopy := make([]byte, len(hmacSecret))
copy(secretCopy, hmacSecret)
defer memguard.WipeBytes(secretCopy) // SECURITY: Wipe secret copy after signing
token := jwt.New(jwt.SigningMethodHS256)
expiresIn := time.Now().Add(ad)
// CWE-391: Safe type assertion even though we just created the token
// Defensive programming to prevent future panics if jwt library changes
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return "", expiresIn, jwt.ErrTokenInvalidClaims
}
claims["session_uuid"] = uuid
claims["exp"] = expiresIn.Unix()
tokenString, err := token.SignedString(secretCopy)
if err != nil {
return "", expiresIn, err
}
return tokenString, expiresIn, nil
}
// GenerateJWTTokenPair Generate the `access token` and `refresh token` for the secret key.
// SECURITY: HMAC secret is wiped from memory after signing to prevent memory dump attacks.
func GenerateJWTTokenPair(hmacSecret []byte, uuid string, ad time.Duration, rd time.Duration) (string, time.Time, string, time.Time, error) {
// SECURITY: Create a copy of the secret and wipe the copy after use
secretCopy := make([]byte, len(hmacSecret))
copy(secretCopy, hmacSecret)
defer memguard.WipeBytes(secretCopy) // SECURITY: Wipe secret copy after signing
//
// Generate token.
//
token := jwt.New(jwt.SigningMethodHS256)
expiresIn := time.Now().Add(ad)
// CWE-391: Safe type assertion even though we just created the token
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return "", time.Now(), "", time.Now(), jwt.ErrTokenInvalidClaims
}
claims["session_uuid"] = uuid
claims["exp"] = expiresIn.Unix()
tokenString, err := token.SignedString(secretCopy)
if err != nil {
return "", time.Now(), "", time.Now(), err
}
//
// Generate refresh token.
//
refreshToken := jwt.New(jwt.SigningMethodHS256)
refreshExpiresIn := time.Now().Add(rd)
// CWE-391: Safe type assertion for refresh token
rtClaims, ok := refreshToken.Claims.(jwt.MapClaims)
if !ok {
return "", time.Now(), "", time.Now(), jwt.ErrTokenInvalidClaims
}
rtClaims["session_uuid"] = uuid
rtClaims["exp"] = refreshExpiresIn.Unix()
refreshTokenString, err := refreshToken.SignedString(secretCopy)
if err != nil {
return "", time.Now(), "", time.Now(), err
}
return tokenString, expiresIn, refreshTokenString, refreshExpiresIn, nil
}
// ProcessJWTToken validates either the `access token` or `refresh token` and returns either the `uuid` if success or error on failure.
// CWE-347: Implements proper algorithm validation to prevent JWT algorithm confusion attacks
// OWASP A02:2021: Cryptographic Failures - Prevents token forgery through algorithm switching
// SECURITY: HMAC secret copy is wiped from memory after validation.
func ProcessJWTToken(hmacSecret []byte, reqToken string) (string, error) {
// SECURITY: Create a copy of the secret and wipe the copy after use
secretCopy := make([]byte, len(hmacSecret))
copy(secretCopy, hmacSecret)
defer memguard.WipeBytes(secretCopy) // SECURITY: Wipe secret copy after validation
token, err := jwt.Parse(reqToken, func(t *jwt.Token) (any, error) {
// CRITICAL SECURITY FIX: Validate signing method to prevent algorithm confusion attacks
// Protects against:
// 1. "none" algorithm bypass (CVE-2015-9235)
// 2. HS256/RS256 algorithm confusion (CVE-2016-5431)
// 3. Token forgery through algorithm switching
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrTokenSignatureInvalid
}
// Additional check: Ensure it's specifically HS256
if t.Method.Alg() != "HS256" {
return nil, jwt.ErrTokenSignatureInvalid
}
return secretCopy, nil
})
if err == nil && token.Valid {
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
// Safe type assertion with validation
sessionUUID, ok := claims["session_uuid"].(string)
if !ok {
return "", jwt.ErrTokenInvalidClaims
}
return sessionUUID, nil
}
return "", err
}
return "", err
}