将 JWK json 转换为 public 密钥 golang (lestrrat-go)

Converting JWK json into a public key golang (lestrrat-go)

我正在使用 JWKS 格式从身份验证服务提供 public 密钥,该密钥可用于验证来自该身份验证服务的令牌。但是,要执行验证,我需要从 JWK 重建 public 密钥。如何转换?

type JWKeys struct {
    Keys []JWKey `json:"keys"`
}

type JWKey struct {
    Kty string `json:"kty"`
    Use string `json:"use,omitempty"`
    Kid string `json:"kid,omitempty"`
    Alg string `json:"alg,omitempty"`

    Crv string `json:"crv,omitempty"`
    X   string `json:"x,omitempty"`
    Y   string `json:"y,omitempty"`
    D   string `json:"d,omitempty"`
    N   string `json:"n,omitempty"`
    E   string `json:"e,omitempty"`
    K   string `json:"k,omitempty"`
}

var PublicKey *rsa.PublicKey

func SetUpExternalAuth() {
    res, err := http.Get("my_url")

    if err != nil {
        log.Fatal("Can't retrieve the key for authentication")
    }

    bodyBytes, err := ioutil.ReadAll(res.Body)
    if err != nil {
        log.Fatal(err)
    }

    var keys JWKeys

    json.Unmarshal(bodyBytes, &keys)

    //CONVERT JWK TO *rsa.PUBLICKEY???
}

更新

我尝试使用 github.com/lestrrat-go/jwx/jwk 库解析 JWK,但是我找不到如何继续:

set,err := jwk.Parse(bodyBytes)

key,err2 := set.Get(0)

//HOW TO CONVERT KEY INTO A *rsa.PublicKey?

最后我手动转换了它:

if singleJWK.Kty != "RSA" {
            log.Fatal("invalid key type:", singleJWK.Kty)
        }

        // decode the base64 bytes for n
        nb, err := base64.RawURLEncoding.DecodeString(singleJWK.N)
        if err != nil {
            log.Fatal(err)
        }

        e := 0
        // The default exponent is usually 65537, so just compare the
        // base64 for [1,0,1] or [0,1,0,1]
        if singleJWK.E == "AQAB" || singleJWK.E == "AAEAAQ" {
            e = 65537
        } else {
            // need to decode "e" as a big-endian int
            log.Fatal("need to deocde e:", singleJWK.E)
        }

        PublicKey = &rsa.PublicKey{
            N: new(big.Int).SetBytes(nb),
            E: e,
        }

我专门为此写了一个 Go 包:github.com/MicahParks/keyfunc

转换为 *rsa.PublicKey

在这个包中,一个 JSON Web Key (JWK) 看起来像这个 Go 结构。它同时支持 ECDSA 和 RSA JWK。

// JSONKey represents a raw key inside a JWKS.
type JSONKey struct {
    Curve       string `json:"crv"`
    Exponent    string `json:"e"`
    ID          string `json:"kid"`
    Modulus     string `json:"n"`
    X           string `json:"x"`
    Y           string `json:"y"`
    precomputed interface{}
}

原始 JSON 消息被解组为上述结构后,此方法将其转换为 *rsa.PublicKey.

package keyfunc

import (
    "crypto/rsa"
    "encoding/base64"
    "fmt"
    "math/big"
)

const (

    // rs256 represents a public cryptography key generated by a 256 bit RSA algorithm.
    rs256 = "RS256"

    // rs384 represents a public cryptography key generated by a 384 bit RSA algorithm.
    rs384 = "RS384"

    // rs512 represents a public cryptography key generated by a 512 bit RSA algorithm.
    rs512 = "RS512"

    // ps256 represents a public cryptography key generated by a 256 bit RSA algorithm.
    ps256 = "PS256"

    // ps384 represents a public cryptography key generated by a 384 bit RSA algorithm.
    ps384 = "PS384"

    // ps512 represents a public cryptography key generated by a 512 bit RSA algorithm.
    ps512 = "PS512"
)

// RSA parses a JSONKey and turns it into an RSA public key.
func (j *JSONKey) RSA() (publicKey *rsa.PublicKey, err error) {

    // Check if the key has already been computed.
    if j.precomputed != nil {
        return j.precomputed.(*rsa.PublicKey), nil
    }

    // Confirm everything needed is present.
    if j.Exponent == "" || j.Modulus == "" {
        return nil, fmt.Errorf("%w: rsa", ErrMissingAssets)
    }

    // Decode the exponent from Base64.
    //
    // According to RFC 7518, this is a Base64 URL unsigned integer.
    // https://tools.ietf.org/html/rfc7518#section-6.3
    var exponent []byte
    if exponent, err = base64.RawURLEncoding.DecodeString(j.Exponent); err != nil {
        return nil, err
    }

    // Decode the modulus from Base64.
    var modulus []byte
    if modulus, err = base64.RawURLEncoding.DecodeString(j.Modulus); err != nil {
        return nil, err
    }

    // Create the RSA public key.
    publicKey = &rsa.PublicKey{}

    // Turn the exponent into an integer.
    //
    // According to RFC 7517, these numbers are in big-endian format.
    // https://tools.ietf.org/html/rfc7517#appendix-A.1
    publicKey.E = int(big.NewInt(0).SetBytes(exponent).Uint64())

    // Turn the modulus into a *big.Int.
    publicKey.N = big.NewInt(0).SetBytes(modulus)

    // Keep the public key so it won't have to be computed every time.
    j.precomputed = publicKey

    return publicKey, nil
}

从 JSON Web 密钥集 (JWKS) 解析和验证 JWT。

我将此包与 github.com/dgrijalva/jwt-go 一起使用,以便更轻松地使用最受欢迎的包解析和验证 JWT。

使用您的示例 URL,my_url,这是一个如何解析和验证 JWT 的示例。

package main

import (
    "log"
    "time"

    "github.com/dgrijalva/jwt-go"

    "github.com/MicahParks/keyfunc"
)

func main() {

    // Get the JWKS URL.
    jwksURL := "my_url"

    // Create the keyfunc options. Refresh the JWKS every hour and log errors.
    refreshInterval := time.Hour
    options := keyfunc.Options{
        RefreshInterval: &refreshInterval,
        RefreshErrorHandler: func(err error) {
            log.Printf("There was an error with the jwt.KeyFunc\nError: %s", err.Error())
        },
    }

    // Create the JWKS from the resource at the given URL.
    jwks, err := keyfunc.Get(jwksURL, options)
    if err != nil {
        log.Fatalf("Failed to create JWKS from resource at the given URL.\nError: %s", err.Error())
    }

    // Get a JWT to parse.
    jwtB64 := "eyJhbGciOiJQUzM4NCIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMeDFGbWF5UDJZQnR4YXFTMVNLSlJKR2lYUktudzJvdjVXbVlJTUctQkxFIn0.eyJleHAiOjE2MTU0MDY5ODIsImlhdCI6MTYxNTQwNjkyMiwianRpIjoiMGY2NGJjYTktYjU4OC00MWFhLWFkNDEtMmFmZDM2OGRmNTFkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJhZDEyOGRmMS0xMTQwLTRlNGMtYjA5Ny1hY2RjZTcwNWJkOWIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0b2tlbmRlbG1lIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SG9zdCI6IjE3Mi4yMC4wLjEiLCJjbGllbnRJZCI6InRva2VuZGVsbWUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC10b2tlbmRlbG1lIiwiY2xpZW50QWRkcmVzcyI6IjE3Mi4yMC4wLjEifQ.Rxrq41AxbWKIQHWv-Tkb7rqwel3sKT_R_AGvn9mPIHqhw1m7nsQWcL9t2a_8MI2hCwgWtYdgTF1xxBNmb2IW3CZkML5nGfcRrFvNaBHd3UQEqbFKZgnIX29h5VoxekyiwFaGD-0RXL83jF7k39hytEzTatwoVjZ-frga0KFl-nLce3OwncRXVCGmxoFzUsyu9TQFS2Mm_p0AMX1y1MAX1JmLC3WFhH3BohhRqpzBtjSfs_f46nE1-HKjqZ1ERrAc2fmiVJjmG7sT702JRuuzrgUpHlMy2juBG4DkVcMlj4neJUmCD1vZyZBRggfaIxNkwUhHtmS2Cp9tOcwNu47tSg"

    // Parse the JWT.
    token, err := jwt.Parse(jwtB64, jwks.KeyFunc)
    if err != nil {
        log.Fatalf("Failed to parse the JWT.\nError: %s", err.Error())
    }

    // Check if the token is valid.
    if !token.Valid {
        log.Fatalf("The token is not valid.")
    }

    log.Println("The token is valid.")
}

我知道你有一个解决方案,但是当你尝试使用 github.com/lestrrat-go/jwx/jwk 时,这里有一个使用该包的方法(几乎是 example 中的内容):

package main

import (
    "context"
    "crypto/rsa"
    "fmt"
    "log"

    "github.com/lestrrat-go/jwx/jwk"
)

func main() {
    // Example jwk from https://www.googleapis.com/oauth2/v3/certs (but with only one cert for simplicity)
    jwkJSON := `{
  "keys": [ 
    {
      "kty": "RSA",
      "n": "o76AudS2rsCvlz_3D47sFkpuz3NJxgLbXr1cHdmbo9xOMttPMJI97f0rHiSl9stltMi87KIOEEVQWUgMLaWQNaIZThgI1seWDAGRw59AO5sctgM1wPVZYt40fj2Qw4KT7m4RLMsZV1M5NYyXSd1lAAywM4FT25N0RLhkm3u8Hehw2Szj_2lm-rmcbDXzvjeXkodOUszFiOqzqBIS0Bv3c2zj2sytnozaG7aXa14OiUMSwJb4gmBC7I0BjPv5T85CH88VOcFDV51sO9zPJaBQnNBRUWNLh1vQUbkmspIANTzj2sN62cTSoxRhSdnjZQ9E_jraKYEW5oizE9Dtow4EvQ",
      "use": "sig",
      "alg": "RS256",
      "e": "AQAB",
      "kid": "6a8ba5652a7044121d4fedac8f14d14c54e4895b"
    }
  ]
}
`

    set, err := jwk.Parse([]byte(jwkJSON))
    if err != nil {
        panic(err)
    }
    fmt.Println(set)
    for it := set.Iterate(context.Background()); it.Next(context.Background()); {
        pair := it.Pair()
        key := pair.Value.(jwk.Key)

        var rawkey interface{} // This is the raw key, like *rsa.PrivateKey or *ecdsa.PrivateKey
        if err := key.Raw(&rawkey); err != nil {
            log.Printf("failed to create public key: %s", err)
            return
        }

        // We know this is an RSA Key so...
        rsa, ok := rawkey.(*rsa.PublicKey)
        if !ok {
            panic(fmt.Sprintf("expected ras key, got %T", rawkey))
        }
        // As this is a demo just dump the key to the console
        fmt.Println(rsa)
    }
}