v0.2.0: add support for fetching /pem
This commit is contained in:
parent
90b05bac5f
commit
f2d341a3c0
|
@ -104,6 +104,28 @@ func JWK(kidOrThumb, iss string) (keypairs.PublicKey, error) {
|
||||||
return immediateOneOrFetch(kidOrThumb, iss, uncached.JWKs)
|
return immediateOneOrFetch(kidOrThumb, iss, uncached.JWKs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PEM tries to return a key from cache, falling back to the specified PEM url
|
||||||
|
func PEM(url string) (keypairs.PublicKey, error) {
|
||||||
|
// url is kid in this case
|
||||||
|
return immediateOneOrFetch(url, url, func(string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) {
|
||||||
|
m, key, err := uncached.PEM(url)
|
||||||
|
if nil != err {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// put in a map, just for caching
|
||||||
|
maps := map[string]map[string]string{}
|
||||||
|
maps[key.Thumbprint()] = m
|
||||||
|
maps[url] = m
|
||||||
|
|
||||||
|
keys := map[string]keypairs.PublicKey{}
|
||||||
|
keys[key.Thumbprint()] = key
|
||||||
|
keys[url] = key
|
||||||
|
|
||||||
|
return maps, keys, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch returns a key from cache, falling back to an exact url as the "issuer"
|
// Fetch returns a key from cache, falling back to an exact url as the "issuer"
|
||||||
func Fetch(url string) (keypairs.PublicKey, error) {
|
func Fetch(url string) (keypairs.PublicKey, error) {
|
||||||
// url is kid in this case
|
// url is kid in this case
|
||||||
|
|
|
@ -8,10 +8,20 @@ import (
|
||||||
"github.com/big-squid/go-keypairs/keyfetch/uncached"
|
"github.com/big-squid/go-keypairs/keyfetch/uncached"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var pubkey keypairs.PublicKey
|
||||||
|
|
||||||
func TestCachesKey(t *testing.T) {
|
func TestCachesKey(t *testing.T) {
|
||||||
testCachesKey(t, "https://bigsquid.auth0.com/")
|
testCachesKey(t, "https://bigsquid.auth0.com/")
|
||||||
clear()
|
clear()
|
||||||
testCachesKey(t, "https://bigsquid.auth0.com")
|
testCachesKey(t, "https://bigsquid.auth0.com")
|
||||||
|
// Get PEM
|
||||||
|
k3, err := PEM("https://bigsquid.auth0.com/pem")
|
||||||
|
if nil != err {
|
||||||
|
t.Fatal("Error fetching and caching key:", err)
|
||||||
|
}
|
||||||
|
if k3.Thumbprint() != pubkey.Thumbprint() {
|
||||||
|
t.Fatal("Error got different thumbprint for different versions of the same key:", err)
|
||||||
|
}
|
||||||
clear()
|
clear()
|
||||||
testCachesKey(t, "https://big-squid.github.io/")
|
testCachesKey(t, "https://big-squid.github.io/")
|
||||||
}
|
}
|
||||||
|
@ -39,7 +49,7 @@ func testCachesKey(t *testing.T, url string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get with caching
|
// Get with caching
|
||||||
k2, err := OIDCJWK(thumb, url)
|
pubkey, err = OIDCJWK(thumb, url)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
t.Fatal("Error fetching and caching key:", err)
|
t.Fatal("Error fetching and caching key:", err)
|
||||||
}
|
}
|
||||||
|
@ -48,9 +58,9 @@ func testCachesKey(t *testing.T, url string) {
|
||||||
if pub := Get(thumb, ""); nil == pub {
|
if pub := Get(thumb, ""); nil == pub {
|
||||||
t.Fatal("key was not properly cached by thumbprint", thumb)
|
t.Fatal("key was not properly cached by thumbprint", thumb)
|
||||||
}
|
}
|
||||||
if "" != k2.KeyID() {
|
if "" != pubkey.KeyID() {
|
||||||
if pub := Get(k2.KeyID(), url); nil == pub {
|
if pub := Get(pubkey.KeyID(), url); nil == pub {
|
||||||
t.Fatal("key was not properly cached by kid", k2.KeyID())
|
t.Fatal("key was not properly cached by kid", pubkey.KeyID())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.Log("Key did not have an explicit KeyID")
|
t.Log("Key did not have an explicit KeyID")
|
||||||
|
@ -67,7 +77,13 @@ func testCachesKey(t *testing.T, url string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check that the kid and thumb match
|
// Sanity check that the kid and thumb match
|
||||||
if key.KeyID() != k2.KeyID() || key.Thumbprint() != k2.Thumbprint() {
|
if key.KeyID() != pubkey.KeyID() || key.Thumbprint() != pubkey.Thumbprint() {
|
||||||
t.Fatal("SANITY: KeyIDs or Thumbprints do not match:", key.KeyID(), k2.KeyID(), key.Thumbprint(), k2.Thumbprint())
|
t.Fatal("SANITY: KeyIDs or Thumbprints do not match:", key.KeyID(), pubkey.KeyID(), key.Thumbprint(), pubkey.Thumbprint())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 404
|
||||||
|
_, err = PEM(url + "/will-not-be-found.xyz")
|
||||||
|
if nil == err {
|
||||||
|
t.Fatal("Should have an error when retrieving a 404 or index.html:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,11 @@
|
||||||
package uncached
|
package uncached
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -74,6 +77,41 @@ func JWKs(jwksurl string) (map[string]map[string]string, map[string]keypairs.Pub
|
||||||
return maps, keys, nil
|
return maps, keys, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PEM fetches and parses a PEM (assuming well-known format)
|
||||||
|
func PEM(pemurl string) (map[string]string, keypairs.PublicKey, error) {
|
||||||
|
var pub keypairs.PublicKey
|
||||||
|
if err := safeFetch(pemurl, func(body io.Reader) error {
|
||||||
|
pem, err := ioutil.ReadAll(body)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pub, err = keypairs.ParsePublicKey(pem)
|
||||||
|
return err
|
||||||
|
}); nil != err {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jwk := map[string]interface{}{}
|
||||||
|
body := bytes.NewBuffer(keypairs.MarshalJWKPublicKey(pub))
|
||||||
|
decoder := json.NewDecoder(body)
|
||||||
|
decoder.UseNumber()
|
||||||
|
_ = decoder.Decode(&jwk)
|
||||||
|
|
||||||
|
m := getStringMap(jwk)
|
||||||
|
m["kid"] = pemurl
|
||||||
|
|
||||||
|
switch p := pub.(type) {
|
||||||
|
case *keypairs.ECPublicKey:
|
||||||
|
p.KID = pemurl
|
||||||
|
case *keypairs.RSAPublicKey:
|
||||||
|
p.KID = pemurl
|
||||||
|
default:
|
||||||
|
return nil, nil, errors.New("impossible key type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, pub, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch retrieves a single JWK (plain, bare jwk) from a URL (off-spec)
|
// Fetch retrieves a single JWK (plain, bare jwk) from a URL (off-spec)
|
||||||
func Fetch(url string) (map[string]string, keypairs.PublicKey, error) {
|
func Fetch(url string) (map[string]string, keypairs.PublicKey, error) {
|
||||||
var m map[string]interface{}
|
var m map[string]interface{}
|
||||||
|
|
41
keypairs.go
41
keypairs.go
|
@ -314,7 +314,7 @@ func getPEMBytes(block []byte) ([][]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsePrivateKey will try to parse the bytes you give it
|
// ParsePublicKey will try to parse the bytes you give it
|
||||||
// in any of the supported formats: PEM, DER, PKIX/SPKI, PKCS1, x509 Certificate, and JWK
|
// in any of the supported formats: PEM, DER, PKIX/SPKI, PKCS1, x509 Certificate, and JWK
|
||||||
func ParsePublicKey(block []byte) (PublicKey, error) {
|
func ParsePublicKey(block []byte) (PublicKey, error) {
|
||||||
blocks, err := getPEMBytes(block)
|
blocks, err := getPEMBytes(block)
|
||||||
|
@ -324,7 +324,7 @@ func ParsePublicKey(block []byte) (PublicKey, error) {
|
||||||
|
|
||||||
// Parse PEM blocks (openssl generates junk metadata blocks for ECs)
|
// Parse PEM blocks (openssl generates junk metadata blocks for ECs)
|
||||||
// or the original DER, or the JWK
|
// or the original DER, or the JWK
|
||||||
for i, _ := range blocks {
|
for i := range blocks {
|
||||||
block = blocks[i]
|
block = blocks[i]
|
||||||
if key, err := parsePublicKey(block); nil == err {
|
if key, err := parsePublicKey(block); nil == err {
|
||||||
return key, nil
|
return key, nil
|
||||||
|
@ -341,8 +341,6 @@ func ParsePublicKeyString(block string) (PublicKey, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePublicKey(der []byte) (PublicKey, error) {
|
func parsePublicKey(der []byte) (PublicKey, error) {
|
||||||
var key PublicKey
|
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(der)
|
cert, err := x509.ParseCertificate(der)
|
||||||
if nil == err {
|
if nil == err {
|
||||||
switch k := cert.PublicKey.(type) {
|
switch k := cert.PublicKey.(type) {
|
||||||
|
@ -351,7 +349,7 @@ func parsePublicKey(der []byte) (PublicKey, error) {
|
||||||
case *ecdsa.PublicKey:
|
case *ecdsa.PublicKey:
|
||||||
return NewPublicKey(k), nil
|
return NewPublicKey(k), nil
|
||||||
default:
|
default:
|
||||||
err = errors.New("Only RSA and ECDSA (EC) Public Keys are supported")
|
return nil, errors.New("Only RSA and ECDSA (EC) Public Keys are supported")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,28 +362,27 @@ func parsePublicKey(der []byte) (PublicKey, error) {
|
||||||
case *ecdsa.PublicKey:
|
case *ecdsa.PublicKey:
|
||||||
return NewPublicKey(k), nil
|
return NewPublicKey(k), nil
|
||||||
default:
|
default:
|
||||||
err = errors.New("Only RSA and ECDSA (EC) Public Keys are supported")
|
return nil, errors.New("Only RSA and ECDSA (EC) Public Keys are supported")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if nil != err {
|
//fmt.Println("3. ParsePKCS1PrublicKey")
|
||||||
//fmt.Println("3. ParsePKCS1PrublicKey")
|
rkey, err := x509.ParsePKCS1PublicKey(der)
|
||||||
keyx, err := x509.ParsePKCS1PublicKey(der)
|
if nil == err {
|
||||||
key = NewPublicKey(keyx)
|
//fmt.Println("4. ParseJWKPublicKey")
|
||||||
|
return NewPublicKey(rkey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseJWKPublicKey(der)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// But did you know?
|
||||||
|
// You must return nil explicitly for interfaces
|
||||||
|
// https://golang.org/doc/faq#nil_error
|
||||||
if nil != err {
|
if nil != err {
|
||||||
//fmt.Println("4. ParseJWKPublicKey")
|
return nil, err
|
||||||
key, err = ParseJWKPublicKey(der)
|
|
||||||
}
|
}
|
||||||
}
|
*/
|
||||||
|
|
||||||
// But did you know?
|
|
||||||
// You must return nil explicitly for interfaces
|
|
||||||
// https://golang.org/doc/faq#nil_error
|
|
||||||
if nil != err {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewJWKPublicKey contstructs a PublicKey from the relevant pieces a map[string]string (generic JSON)
|
// NewJWKPublicKey contstructs a PublicKey from the relevant pieces a map[string]string (generic JSON)
|
||||||
|
|
Loading…
Reference in New Issue