Fetch JWKs from OIDC URLs
This commit is contained in:
parent
db7dcc62b4
commit
211016b05e
|
@ -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)
|
||||||
|
}
|
|
@ -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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
keypairs.go
47
keypairs.go
|
@ -13,6 +13,7 @@ import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -166,24 +167,54 @@ func parsePrivateKey(der []byte) (PrivateKey, error) {
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseJWKPublicKey(b []byte) (crypto.PublicKey, error) {
|
func NewJWKPublicKey(m map[string]string) (crypto.PublicKey, error) {
|
||||||
m := make(map[string]string)
|
|
||||||
err := json.Unmarshal(b, &m)
|
|
||||||
if nil != err {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch m["kty"] {
|
switch m["kty"] {
|
||||||
case "RSA":
|
case "RSA":
|
||||||
return parseRSAPublicKey(m)
|
return parseRSAPublicKey(m)
|
||||||
case "EC":
|
case "EC":
|
||||||
return parseECPublicKey(m)
|
return parseECPublicKey(m)
|
||||||
default:
|
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
|
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 NewJWKPublicKey(m)
|
||||||
|
}
|
||||||
|
|
||||||
func ParseJWKPrivateKey(b []byte) (PrivateKey, error) {
|
func ParseJWKPrivateKey(b []byte) (PrivateKey, error) {
|
||||||
var m map[string]string
|
var m map[string]string
|
||||||
|
|
Loading…
Reference in New Issue