eff5771b03
Fase 2 del issue 0010 — auth core: - jwt_generate/validate: HS256 manual con crypto/hmac + crypto/sha256 - password_hash/verify: wrappers de golang.org/x/crypto/bcrypt (cost 12 default) - JWT rechaza alg != HS256 para mitigar ataque 'alg=none' - hmac.Equal para comparacion constant-time de firmas
73 lines
2.0 KiB
Go
73 lines
2.0 KiB
Go
package infra
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// JWTValidate verifica la firma HMAC-SHA256 de un JWT y decodifica sus claims.
|
|
// Rechaza tokens mal formados, con firma invalida o expirados (exp < time.Now()).
|
|
// Retorna las claims si todo es valido.
|
|
func JWTValidate(token string, secret string) (JWTClaims, error) {
|
|
var zero JWTClaims
|
|
if secret == "" {
|
|
return zero, errors.New("jwt_validate: secret vacio")
|
|
}
|
|
|
|
parts := strings.Split(token, ".")
|
|
if len(parts) != 3 {
|
|
return zero, errors.New("jwt_validate: token malformado (se esperaban 3 segmentos)")
|
|
}
|
|
|
|
enc := base64.RawURLEncoding
|
|
signingInput := parts[0] + "." + parts[1]
|
|
|
|
// Verificar firma
|
|
mac := hmac.New(sha256.New, []byte(secret))
|
|
mac.Write([]byte(signingInput))
|
|
expectedSig := mac.Sum(nil)
|
|
|
|
gotSig, err := enc.DecodeString(parts[2])
|
|
if err != nil {
|
|
return zero, errors.New("jwt_validate: firma mal codificada")
|
|
}
|
|
if !hmac.Equal(expectedSig, gotSig) {
|
|
return zero, errors.New("jwt_validate: firma invalida")
|
|
}
|
|
|
|
// Decodificar header y confirmar alg
|
|
headerBytes, err := enc.DecodeString(parts[0])
|
|
if err != nil {
|
|
return zero, errors.New("jwt_validate: header mal codificado")
|
|
}
|
|
var header map[string]string
|
|
if err := json.Unmarshal(headerBytes, &header); err != nil {
|
|
return zero, errors.New("jwt_validate: header no es JSON valido")
|
|
}
|
|
if alg, _ := header["alg"]; alg != "HS256" {
|
|
return zero, errors.New("jwt_validate: algoritmo no soportado (solo HS256)")
|
|
}
|
|
|
|
// Decodificar claims
|
|
payloadBytes, err := enc.DecodeString(parts[1])
|
|
if err != nil {
|
|
return zero, errors.New("jwt_validate: payload mal codificado")
|
|
}
|
|
var claims JWTClaims
|
|
if err := json.Unmarshal(payloadBytes, &claims); err != nil {
|
|
return zero, errors.New("jwt_validate: payload no es JSON valido")
|
|
}
|
|
|
|
// Validar expiracion si esta presente
|
|
if claims.ExpiresAt > 0 && time.Now().Unix() >= claims.ExpiresAt {
|
|
return zero, errors.New("jwt_validate: token expirado")
|
|
}
|
|
|
|
return claims, nil
|
|
}
|