google example go oauth-2.0 rsa google-oauth jwt

go - example - oauth2 api



Error de validaciĆ³n de token de Golang (4)

Necesito validar un google id_token y un paso implica verificar la firma del token.

Primero obtengo el certificado de: https://www.googleapis.com/oauth2/v2/certs y extraigo la parte de módulo (n) y exponente (e) del certificado y genero una clave pública, luego desarmo el token (encabezado, carga y resumen), después de eso envío el header.payload decodificado.payload junto con el resumen de Google pKey + a la función rsa rsa.VerifyPKCS1v15 .

Estoy atascado con este error de verificación: crypto/rsa: verification error

Aquí está el código (he comentado parte del código que falla // validation here fails ):

func ValidateIDToken(auth_token string) (err error){ res, err := http.Get("https://www.googleapis.com/oauth2/v2/certs") if err != nil { log.Fatal(err) return err } certs, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { log.Fatal(err) return err } //get modulus and exponent from the cert var goCertificate interface{} err = json.Unmarshal(certs, &goCertificate) k := goCertificate.(map[string]interface{})["keys"] j := k.([]interface{}) x := j[1] h := x.(map[string]interface{})["n"] g := x.(map[string]interface{})["e"] e64 := base64.StdEncoding //build the google pub key nStr := h.(string) decN, err := base64.StdEncoding.DecodeString(nStr) if err != nil { log.Println(err) return } n := big.NewInt(0) n.SetBytes(decN) eStr := g.(string) decE, err := base64.StdEncoding.DecodeString(eStr) if err != nil { log.Println(err) return } var eBytes []byte if len(decE) < 8 { eBytes = make([]byte, 8-len(decE), 8) eBytes = append(eBytes, decE...) } else { eBytes = decE } eReader := bytes.NewReader(eBytes) var e uint64 err = binary.Read(eReader, binary.BigEndian, &e) if err != nil { log.Println(err) return } pKey := rsa.PublicKey{N: n, E: int(e)} w := strings.SplitAfter(auth_token, ".") for i, val := range w { w[i] = strings.Trim(val, ".") } y := w[0:2] //Join just the first two parts, the header and the payload without the signature o := strings.Join(y, ".") headerOauth := DecodeB64(nil,[]byte(w[0]),e64) inblockOauth := DecodeB64(nil,[]byte(w[1]),e64) toHash := string(headerOauth) + "}." + string(inblockOauth) digestOauth := DecodeB64(nil, []byte(w[2]),e64) hasherOauth := sha256.New() hasherOauth.Write([]byte(toHash)) // validation here fails err = rsa.VerifyPKCS1v15(&pKey,crypto.SHA256,hasherOauth.Sum(nil),digestOauth) if err != nil { log.Printf("Error verifying key %s",err.Error()) return err } return err }

ACTUALIZACIÓN 1: Aquí está toHash var que contiene encabezado y carga útil:

{"alg":"RS256","kid":"d91c503452d0f8849200a321ffbf7dea76f9371d"}.{"iss":"accounts.google.com","sub":"104869993929250743503","azp":"[email protected]","email":"[email protected]","at_hash":"KAm1M0g-ssMkdjds7jkbVQ","email_verified":true,"aud":[email protected]","hd":"test.hr","iat":1412246551,"exp":1412250451}

ACTUALIZACIÓN 2: Gracias por la respuesta @Florent Morselli, lo intenté de nuevo y falló, esta vez B64decodificó la tercera parte (firma) pero el error todavía estaba allí, podría alguien probarlo con su auth_token, solo poner el token de ID en la variable auth_token a continuación en el código, y avíseme si funcionó, gracias.

package main import( "strings" "encoding/binary" "errors" "fmt" "log" "encoding/base64" "io/ioutil" "crypto" "crypto/sha256" "crypto/rsa" "bytes" "encoding/json" "net/http" "math/big" ) func main() { auth_token := "" w := strings.SplitAfter(auth_token, ".") for i, val := range w { w[i] = strings.Trim(val, ".") } headerOauth, err := base64.URLEncoding.DecodeString(w[0]) res, err := http.Get("https://www.googleapis.com/oauth2/v2/certs") if err != nil { fmt.Println(err) } certs, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { fmt.Println(err) } //extract kid from token header var header interface{} err = json.Unmarshal([]byte(string(headerOauth)+"}"), &header) token_kid := header.(map[string]interface{})["kid"] fmt.Println("By 1") //get modulus and exponent from the cert var goCertificate interface{} err = json.Unmarshal(certs, &goCertificate) //k := goCertificate.(map[string]interface{})[token_kid.(string)] k := goCertificate.(map[string]interface{})["keys"] ///*mod & exp part j := k.([]interface{}) x := j[0] if j[0].(map[string]interface{})["kid"] == token_kid { x = j[0] }else{ if j[1].(map[string]interface{})["kid"] == token_kid { x = j[1] }else{ errors.New("Token is not valid, kid from token and certificate don''t match") } } h := x.(map[string]interface{})["n"] g := x.(map[string]interface{})["e"] //build the google pub key nStr := h.(string) decN, err := base64.URLEncoding.DecodeString(nStr) if err != nil { fmt.Println(err) return } n := big.NewInt(0) n.SetBytes(decN) eStr := g.(string) decE, err := base64.URLEncoding.DecodeString(eStr) if err != nil { fmt.Println(err) return } var eBytes []byte if len(decE) < 8 { eBytes = make([]byte, 8-len(decE), 8) eBytes = append(eBytes, decE...) } else { eBytes = decE } eReader := bytes.NewReader(eBytes) var e uint64 err = binary.Read(eReader, binary.BigEndian, &e) if err != nil { log.Println(err) return } pKey := rsa.PublicKey{N: n, E: int(e)} //inblockOauth := base64.URLEncoding.DecodeString(w[1]) toHash := w[0] + "." + w[1] digestOauth, err := base64.URLEncoding.DecodeString(w[2]) hasherOauth := sha256.New() hasherOauth.Write([]byte(toHash)) // verification here fails err = rsa.VerifyPKCS1v15(&pKey,crypto.SHA256,hasherOauth.Sum(nil),digestOauth) if err != nil { fmt.Printf("Error verifying key %s",err.Error()) } }


La entrada que debe verificarse debe ser $ base64_header. $ Base64_claim_set. De la documentación de Google :

JSON Web Signature (JWS) es la especificación que guía la mecánica de generar la firma para el JWT. La entrada para la firma es la matriz de bytes del siguiente contenido:
{Base64url encoded header}.{Base64url encoded claim set}

Creo que probablemente solo has codificado el índice de certificados para la demostración. En su código real, debe elegir el certificado correcto según el campo "secundario" en el encabezado.


No use StdEncoding, no es URL-Safe como lo requiere la especificación.

Use URLEncoding en su lugar. Consulte https://gobyexample.com/base64-encoding para obtener más información.

La Url Safe de Base64 es la misma que Base64 pero no contiene ''/'' y ''+'' (reemplazada por ''_'' y ''-'') y se eliminan ''=''.


Como se explicó en el chat, el problema es que el decodificador Base64 no puede decodificar el encabezado y la firma si faltan "=".

Solo tiene que agregarlos con el siguiente código:

if m := len(h_) % 4; m != 0 { h_ += strings.Repeat("=", 4-m) }

Aquí está el código completo:

package main import( "strings" "encoding/binary" "errors" "fmt" "log" "encoding/base64" "io/ioutil" "crypto" "crypto/sha256" "crypto/rsa" "bytes" "encoding/json" "net/http" "math/big" ) func main() { auth_token := "" w := strings.Split(auth_token, ".") h_, s_ := w[0], w[2] if m := len(h_) % 4; m != 0 { h_ += strings.Repeat("=", 4-m) } if m := len(s_) % 4; m != 0 { s_ += strings.Repeat("=", 4-m) } headerOauth, err := base64.URLEncoding.DecodeString(h_) res, err := http.Get("https://www.googleapis.com/oauth2/v2/certs") if err != nil { fmt.Println(err) } certs, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { fmt.Println(err) } //extract kid from token header var header interface{} err = json.Unmarshal([]byte(string(headerOauth)), &header) token_kid := header.(map[string]interface{})["kid"] fmt.Println("By 1") //get modulus and exponent from the cert var goCertificate interface{} err = json.Unmarshal(certs, &goCertificate) //k := goCertificate.(map[string]interface{})[token_kid.(string)] k := goCertificate.(map[string]interface{})["keys"] ///*mod & exp part j := k.([]interface{}) x := j[0] if j[0].(map[string]interface{})["kid"] == token_kid { x = j[0] }else{ if j[1].(map[string]interface{})["kid"] == token_kid { x = j[1] }else{ errors.New("Token is not valid, kid from token and certificate don''t match") } } h := x.(map[string]interface{})["n"] g := x.(map[string]interface{})["e"] //build the google pub key nStr := h.(string) decN, err := base64.URLEncoding.DecodeString(nStr) if err != nil { fmt.Println(err) return } n := big.NewInt(0) n.SetBytes(decN) eStr := g.(string) decE, err := base64.URLEncoding.DecodeString(eStr) if err != nil { fmt.Println(err) return } var eBytes []byte if len(decE) < 8 { eBytes = make([]byte, 8-len(decE), 8) eBytes = append(eBytes, decE...) } else { eBytes = decE } eReader := bytes.NewReader(eBytes) var e uint64 err = binary.Read(eReader, binary.BigEndian, &e) if err != nil { log.Println(err) return } pKey := rsa.PublicKey{N: n, E: int(e)} //inblockOauth := base64.URLEncoding.DecodeString(w[1]) toHash := w[0] + "." + w[1] digestOauth, err := base64.URLEncoding.DecodeString(s_) hasherOauth := sha256.New() hasherOauth.Write([]byte(toHash)) // verification of the signature err = rsa.VerifyPKCS1v15(&pKey,crypto.SHA256,hasherOauth.Sum(nil),digestOauth) if err != nil { fmt.Printf("Error verifying key %s",err.Error()) } fmt.Printf("OK!") }


I send the **decoded** header.payload together with the Google pKey + digest to the rsa function rsa.VerifyPKCS1v15.

Estás equivocado en esta parte. Debe enviar a la función RSA rsa.VerifyPKCS1v15 el encabezado codificado.payload

En otras palabras: marcó la firma de {"alg":"RS256","kid":"d91c503452d0f8849200a321ffbf7dea76f9371d"}.{"iss":"accounts.google.com","sub":"104869993929250743503","azp":"[email protected]","email":"[email protected]","at_hash":"KAm1M0g-ssMkdjds7jkbVQ","email_verified":true,"aud":[email protected]","hd":"test.hr","iat":1412246551,"exp":1412250451} que es incorrecto .

Debe comprobar la firma de eyJhbGciOiJSUzI1NiIsImtpZCI6ImQ5MWM1MDM0NTJkMGY4ODQ5MjAwYTMyMWZmYmY3ZGVhNzZmOTM3MWQifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTA0ODY5OTkzOTI5MjUwNzQzNTAzIiwiYXpwIjoiY2xpZW50X2VtYWlsX3RpbGxfQC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidGVzdEB0ZXN0LmhyIiwiYXRfaGFzaCI6IktBbTFNMGctc3NNa2RqZHM3amtiVlEiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXVkIjpjbGllbnRfZW1haWxfdGlsbF9ALmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaGQiOiJ0ZXN0LmhyIiwiaWF0IjoxNDEyMjQ2NTUxLCJleHAiOjE0MTIyNTA0NTF9 .