Files
egutierrez eff5771b03 feat: jwt_generate, jwt_validate, password_hash, password_verify
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
2026-04-18 17:39:00 +02:00

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
}