Search code examples
gocryptographypublic-keypemjwk

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


I'm using JWKS format to provide from an authentication service the public key that can be used to validate tokens coming from that authentication service. However, to perform validation I need to rebuild the public key from the JWK. How can I convert it?

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???
}

UPDATE

I tried to parse the JWKs using github.com/lestrrat-go/jwx/jwk library, however I couldn't find how to continue:

set,err := jwk.Parse(bodyBytes)

key,err2 := set.Get(0)

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

At the end I've manually converted it:

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,
        }


Solution

  • Understand you have a solution but as you were making the attempt using github.com/lestrrat-go/jwx/jwk here is an approach with that package (pretty much what is in the 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)
        }
    }