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
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user