Golang令牌验证错误

I need to validate a google id_token and one step involves to check the token signature.

First I obtain the certificate from: https://www.googleapis.com/oauth2/v2/certs and extract the modulus (n) and exponent (e) part from the certificate and generate a public key, then I take apart the token (header, payload and digest), after then I send the decoded header.payload together with the Google pKey + digest to the rsa function rsa.VerifyPKCS1v15.

I am stuck with this verification error: crypto/rsa: verification error

Here's the code (I commented part of code which fails with // 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
}

UPDATE 1: Here is toHash var which contains header and payload:

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

UPDATE 2: Thanks for the reply @Florent Morselli, I tried it again and it failed, I B64decoded this time only the third part (signature) but the error was still there, could someone test it with their auth_token, just put the ID token in auth_token variable below in the code, and let me know if it worked, thank You.

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())

    }

}

As explained on the chat, the problem is that the Base64 decoder is unable to decode the header and the signature if they are missing "=".

You just have to add them with the following code:

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

Here is the complete code:

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!")
}

The input to be checked should be the $base64_header.$base64_claim_set. From Google's documentation:

JSON Web Signature (JWS) is the specification that guides the mechanics of generating the signature for the JWT. The input for the signature is the byte array of the following content:
{Base64url encoded header}.{Base64url encoded claim set}

I think you probably just hardcoded the cert index for demonstrating. In your real code, you should choose the correct cert based on the "kid" field in the header.

Do not use the StdEncoding, it is not URL-Safe as required by the specification.

Use URLEncoding instead. See https://gobyexample.com/base64-encoding for more informations.

The Base64 Url Safe is the same as Base64 but does not contain '/' and '+' (replaced by '_' and '-') and trailing '=' are removed.

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

You are wrong in this part. You must send to the RSA function rsa.VerifyPKCS1v15 the encoded header.payload

In another words: you checked the signature of {"alg":"RS256","kid":"d91c503452d0f8849200a321ffbf7dea76f9371d"}.{"iss":"accounts.google.com","sub":"104869993929250743503","azp":"client_email_till_@.apps.googleusercontent.com","email":"test@test.hr","at_hash":"KAm1M0g-ssMkdjds7jkbVQ","email_verified":true,"aud":client_email_till_@.apps.googleusercontent.com","hd":"test.hr","iat":1412246551,"exp":1412250451} which is wrong.

You must check the signature of eyJhbGciOiJSUzI1NiIsImtpZCI6ImQ5MWM1MDM0NTJkMGY4ODQ5MjAwYTMyMWZmYmY3ZGVhNzZmOTM3MWQifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTA0ODY5OTkzOTI5MjUwNzQzNTAzIiwiYXpwIjoiY2xpZW50X2VtYWlsX3RpbGxfQC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidGVzdEB0ZXN0LmhyIiwiYXRfaGFzaCI6IktBbTFNMGctc3NNa2RqZHM3amtiVlEiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXVkIjpjbGllbnRfZW1haWxfdGlsbF9ALmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaGQiOiJ0ZXN0LmhyIiwiaWF0IjoxNDEyMjQ2NTUxLCJleHAiOjE0MTIyNTA0NTF9.