130 lines
4.3 KiB
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
|
|
}
|