2014-10-02 2 views
1

Мне нужно проверить идентификатор google id_token, и один шаг включает проверку сигнатуры токена.Ошибка проверки маркера Golang

Сначала я получаю сертификат от: https://www.googleapis.com/oauth2/v2/certs и извлекаю часть модуля (n) и экспоненты (e) из сертификата и генерирую открытый ключ, затем я разделяю токен (заголовок, полезную нагрузку и дайджест), после чего Я отправляю декодированный header.payload вместе с дайджестом pKey + Google в функцию rsa rsa.VerifyPKCS1v15.

Я застрял с этой ошибкой проверки: crypto/rsa: verification error

Вот код (я комментировал часть кода, который терпит неудачу с // 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: Вот toHash вар, который содержит заголовок и полезную нагрузку:

{"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} 

UPDATE 2: Спасибо за ответ @Florent Morselli, я попробовал еще раз, и он потерпел неудачу, я на этот раз B64decoded только третья часть (подпись), но ошибка все еще была там, может кто-то проверить ее с помощью своего auth_token, просто поместите идентификатор ID в переменную auth_token ниже в коде, и дайте мне знать, если это сработает, спасибо.

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

    } 

} 
+0

@RickyA - криптографический/RSA: ошибка верификации – CroiOS

+0

у вас есть тест auth_token проверить с? – RickyA

+0

@RickyA - У меня есть обновленный вопрос (я поставил содержимое переменной toHash в конце вопроса) – CroiOS

ответ

5

Как объясняется в чате, проблема в том, что декодер Base64 не может декодировать заголовок и подпись, если они отсутствуют «=».

Вы просто добавить их с помощью следующего кода:

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

Вот полный код:

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

Большое вам спасибо, это была действительно проблема. – CroiOS

1

Ввод, подлежащий проверке, должен быть $ base64_header. $ Base64_claim_set. От 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}

Я думаю, что вы, вероятно, просто жёстко индекс CERT для демонстрации. В вашем реальном коде вы должны выбрать правильный сертификат, основанный на поле «kid» в заголовке.

+0

Вы правы, я жестко закодировал индекс, и я заставлял его зависеть от «малыша» от токена. Я подтвердил свой токен на https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=, и все в порядке, я жестко закодировал header.payload (оба закодированы в формате b64) и подпись (декодировано b64) и отправили ее VerifyPKCS1v15, и он выдал ту же ошибку. Я подозреваю, что, возможно, я не получил общедоступный ключ google oauth. – CroiOS

+0

'Printf (" Pub key:% v ", & pKey) возвращает это: & {79385446464729122523482302355457174565335860319326282821160915491 65537}' Правильный формат? Спасибо – CroiOS

2

Не используйте StdEncoding, оно не является URL-безопасным, как того требует спецификация.

Использование URLEncoding вместо. См. https://gobyexample.com/base64-encoding для получения дополнительной информации.

Base64 Url Safe такой же, как Base64, но не содержит '/' и '+' (заменен на '_' и '-'), а trailing '=' удаляются.

+0

благодарю вас за ответ, это правильно, но это не решило мою проблему. – CroiOS

2

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

Вы не правы в этой части. Вы должны отправить функции RSA rsa.VerifyPKCS1v15 в кодируются header.payload

Другими словами: Вы проверили подпись {"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}что неправильно.

Вы должны проверить подпись eyJhbGciOiJSUzI1NiIsImtpZCI6ImQ5MWM1MDM0NTJkMGY4ODQ5MjAwYTMyMWZmYmY3ZGVhNzZmOTM3MWQifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTA0ODY5OTkzOTI5MjUwNzQzNTAzIiwiYXpwIjoiY2xpZW50X2VtYWlsX3RpbGxfQC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidGVzdEB0ZXN0LmhyIiwiYXRfaGFzaCI6IktBbTFNMGctc3NNa2RqZHM3amtiVlEiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXVkIjpjbGllbnRfZW1haWxfdGlsbF9ALmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaGQiOiJ0ZXN0LmhyIiwiaWF0IjoxNDEyMjQ2NTUxLCJleHAiOjE0MTIyNTA0NTF9.

+0

Я пробовал ваш метод, но безуспешно, я добавил обновление в свой вопрос с примером кода (ОБНОВЛЕНИЕ 2) – CroiOS

+0

Прошу вас отправить мне полный JWS, который вы хотите проверить? –

+0

вы хотите по электронной почте или? – CroiOS