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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
keypairs.go
49
keypairs.go
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue