Fetch JWKs from OIDC URLs

This commit is contained in:
AJ ONeal 2019-02-08 01:26:45 +00:00
parent db7dcc62b4
commit 211016b05e
3 changed files with 175 additions and 9 deletions

101
fetch.go Normal file
View File

@ -0,0 +1,101 @@
package keypairs
import (
"crypto"
"encoding/json"
"errors"
"io"
"net"
"net/http"
"time"
)
var EInvalidJWKURL = errors.New("url does not lead to valid JWKs")
func FetchOIDCPublicKeys(host string) ([]crypto.PublicKey, error) {
oidcConf := struct {
JWKSURI string `json:"jwks_uri"`
}{}
// must come in as https://<domain>/
url := host + ".well-known/openid-configuration"
err := safeFetch(url, func(body io.Reader) error {
return json.NewDecoder(body).Decode(&oidcConf)
})
if nil != err {
return nil, err
}
return FetchPublicKeys(oidcConf.JWKSURI)
}
func FetchPublicKeys(jwksurl string) ([]crypto.PublicKey, error) {
var keys []crypto.PublicKey
resp := struct {
Keys []map[string]interface{} `json:"keys"`
}{
Keys: make([]map[string]interface{}, 0, 1),
}
if err := safeFetch(jwksurl, func(body io.Reader) error {
return json.NewDecoder(body).Decode(&resp)
}); nil != err {
return nil, err
}
for i := range resp.Keys {
n := map[string]string{}
k := resp.Keys[i]
// convert map[string]interface{} to map[string]string
for j := range k {
switch s := k[j].(type) {
case string:
n[j] = s
default:
// safely ignore
}
}
if key, err := NewJWKPublicKey(n); nil != err {
return nil, err
} else {
keys = append(keys, key)
}
}
return keys, nil
}
func FetchPublicKey(url string) (crypto.PublicKey, error) {
var m map[string]string
if err := safeFetch(url, func(body io.Reader) error {
return json.NewDecoder(body).Decode(&m)
}); nil != err {
return nil, err
}
return NewJWKPublicKey(m)
}
type decodeFunc func(io.Reader) error
func safeFetch(url string, decoder decodeFunc) error {
var netTransport = &http.Transport{
Dial: (&net.Dialer{
Timeout: 5 * time.Second,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
}
var netClient = &http.Client{
Timeout: time.Second * 10,
Transport: netTransport,
}
res, err := netClient.Get(url)
if nil != err {
return err
}
defer res.Body.Close()
return decoder(res.Body)
}

34
fetch_test.go Normal file
View File

@ -0,0 +1,34 @@
package keypairs
import (
"crypto/ecdsa"
"crypto/rsa"
"errors"
"testing"
)
func TestFetchOIDCPublicKeys(t *testing.T) {
urls := []string{
//"https://bigsquid.auth0.com/.well-known/jwks.json",
"https://bigsquid.auth0.com/",
"https://api-dev.bigsquid.com/",
}
for i := range urls {
url := urls[i]
keys, err := FetchOIDCPublicKeys(url)
if nil != err {
t.Fatal(url, err)
}
for i := range keys {
switch key := keys[i].(type) {
case *rsa.PublicKey:
_ = ThumbprintRSAPublicKey(key)
case *ecdsa.PublicKey:
_ = ThumbprintECPublicKey(key)
default:
t.Fatal(errors.New("unsupported interface type"))
}
}
}
}

View File

@ -13,6 +13,7 @@ import (
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
)
@ -166,23 +167,53 @@ func parsePrivateKey(der []byte) (PrivateKey, error) {
return key, nil
}
func ParseJWKPublicKey(b []byte) (crypto.PublicKey, error) {
m := make(map[string]string)
err := json.Unmarshal(b, &m)
if nil != err {
return nil, err
}
func NewJWKPublicKey(m map[string]string) (crypto.PublicKey, error) {
switch m["kty"] {
case "RSA":
return parseRSAPublicKey(m)
case "EC":
return parseECPublicKey(m)
default:
err = EInvalidKeyType
return nil, EInvalidKeyType
}
}
func ParseJWKPublicKey(b []byte) (crypto.PublicKey, error) {
return newJWKPublicKey(b)
}
func ParseJWKPublicKeyString(s string) (crypto.PublicKey, error) {
return newJWKPublicKey(s)
}
func DecodeJWKPublicKey(r io.Reader) (crypto.PublicKey, error) {
return newJWKPublicKey(r)
}
func newJWKPublicKey(data interface{}) (crypto.PublicKey, error) {
var m map[string]string
switch d := data.(type) {
case map[string]string:
m = d
case io.Reader:
m = make(map[string]string)
if err := json.NewDecoder(d).Decode(&m); nil != err {
return nil, err
}
case string:
if err := json.Unmarshal([]byte(d), &m); nil != err {
return nil, err
}
case []byte:
if err := json.Unmarshal(d, &m); nil != err {
return nil, err
}
default:
panic("Developer Error: unsupported interface type")
}
return nil, err
return NewJWKPublicKey(m)
}
func ParseJWKPrivateKey(b []byte) (PrivateKey, error) {