From 743731c8044b18832944110885bab2096b3c65b8 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 21 Oct 2020 03:16:34 -0600 Subject: [PATCH] move away from PublicKeyDeprecated --- cmd/keypairs/keypairs.go | 20 +++++------ keyfetch/fetch.go | 71 +++++++++++++++++++++++--------------- keyfetch/fetch_test.go | 13 ++++--- keyfetch/uncached/fetch.go | 49 +++++++++++++++++--------- keypairs.go | 32 ++++++++--------- keyserve/keyserve.go | 6 ++-- keyserve/keyserve_test.go | 5 +-- sign.go | 8 ++--- verify.go | 22 ++++++------ 9 files changed, 132 insertions(+), 94 deletions(-) diff --git a/cmd/keypairs/keypairs.go b/cmd/keypairs/keypairs.go index c8587ac..92cb915 100644 --- a/cmd/keypairs/keypairs.go +++ b/cmd/keypairs/keypairs.go @@ -105,7 +105,7 @@ func gen(args []string) { key := keypairs.NewDefaultPrivateKey() marshalPriv(key, keyname) - pub := keypairs.NewPublicKey(key.Public()) + pub := key.Public().(keypairs.PublicKeyTransitional) marshalPub(pub, pubname) } @@ -255,33 +255,33 @@ func readKey(keyname string) (keypairs.PrivateKey, error) { return key, nil } -func readPub(pubname string) (keypairs.PublicKey, error) { - var pub keypairs.PublicKey = nil +func readPub(pubname string) (keypairs.PublicKeyTransitional, error) { + var pub keypairs.PublicKeyTransitional = nil // Read as file b, err := ioutil.ReadFile(pubname) if nil != err { // No file? Try as string! - var err2 error - pub, err2 = keypairs.ParsePublicKey([]byte(pubname)) + pub2, err2 := keypairs.ParsePublicKey([]byte(pubname)) if nil != err2 { return nil, fmt.Errorf( "could not read public key as file (or parse as string) %q:\n%w", pubname, err, ) } + pub = pub2.Key().(keypairs.PublicKeyTransitional) } // Oh, it was a file. if nil == pub { - var err3 error - pub, err3 = keypairs.ParsePublicKey(b) + pub3, err3 := keypairs.ParsePublicKey(b) if nil != err3 { return nil, fmt.Errorf( "could not parse public key from file %q:\n%w", pubname, err3, ) } + pub = pub3.Key().(keypairs.PublicKeyTransitional) } return pub, nil @@ -351,7 +351,7 @@ func marshalPriv(key keypairs.PrivateKey, keyname string) { ioutil.WriteFile(keyname, b, 0600) } -func marshalPub(pub keypairs.PublicKey, pubname string) { +func marshalPub(pub keypairs.PublicKeyTransitional, pubname string) { var b []byte if "" == pubname { b = indentJSON(keypairs.MarshalJWKPublicKey(pub)) @@ -363,9 +363,9 @@ func marshalPub(pub keypairs.PublicKey, pubname string) { if strings.HasSuffix(pubname, ".json") { b = indentJSON(keypairs.MarshalJWKPublicKey(pub)) } else if strings.HasSuffix(pubname, ".pem") { - b, _ = keypairs.MarshalPEMPublicKey(pub.Key().(keypairs.PublicKeyTransitional)) + b, _ = keypairs.MarshalPEMPublicKey(pub) } else if strings.HasSuffix(pubname, ".der") { - b, _ = keypairs.MarshalDERPublicKey(pub.Key().(keypairs.PublicKeyTransitional)) + b, _ = keypairs.MarshalDERPublicKey(pub) } ioutil.WriteFile(pubname, b, 0644) diff --git a/keyfetch/fetch.go b/keyfetch/fetch.go index 27d8f96..9f16df8 100644 --- a/keyfetch/fetch.go +++ b/keyfetch/fetch.go @@ -41,7 +41,7 @@ var ErrInsecureDomain = errors.New("Whitelists should only allow secure URLs (i. // CachableKey represents type CachableKey struct { - Key keypairs.PublicKey + Key keypairs.PublicKeyDeprecated Expiry time.Time } @@ -80,7 +80,7 @@ var MinimumKeyDuration = time.Hour var MaximumKeyDuration = 72 * time.Hour // PublicKeysMap is a newtype for a map of keypairs.PublicKey -type PublicKeysMap map[string]keypairs.PublicKey +type PublicKeysMap = map[string]keypairs.PublicKeyDeprecated // OIDCJWKs fetches baseURL + ".well-known/openid-configuration" and then fetches and returns the Public Keys. func OIDCJWKs(baseURL string) (PublicKeysMap, error) { @@ -135,20 +135,31 @@ func JWK(kidOrThumb, iss string) (keypairs.PublicKeyTransitional, error) { // PEM tries to return a key from cache, falling back to the specified PEM url func PEM(url string) (keypairs.PublicKeyTransitional, error) { // url is kid in this case - return immediateOneOrFetch(url, url, func(string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { + return immediateOneOrFetch(url, url, func(string) (map[string]map[string]string, map[string]keypairs.PublicKeyDeprecated, error) { m, key, err := uncached.PEM(url) if nil != err { return nil, nil, err } + pubd := keypairs.NewPublicKey(key) + // TODO bring this back + switch p := pubd.(type) { + case *keypairs.ECPublicKey: + p.KID = url + case *keypairs.RSAPublicKey: + p.KID = url + default: + return nil, nil, errors.New("impossible key type") + } + // put in a map, just for caching maps := map[string]map[string]string{} - maps[keypairs.Thumbprint(key.Key().(keypairs.PublicKeyTransitional))] = m + maps[keypairs.Thumbprint(key)] = m maps[url] = m - keys := map[string]keypairs.PublicKey{} - keys[keypairs.Thumbprint(key.Key().(keypairs.PublicKeyTransitional))] = key - keys[url] = key + keys := uncached.PublicKeysMap{} // map[string]keypairs.PublicKeyDeprecated{} + keys[keypairs.Thumbprint(key)] = pubd + keys[url] = pubd return maps, keys, nil }) @@ -157,28 +168,29 @@ func PEM(url string) (keypairs.PublicKeyTransitional, error) { // Fetch returns a key from cache, falling back to an exact url as the "issuer" func Fetch(url string) (keypairs.PublicKeyTransitional, 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.Fetch(url) - if nil != err { - return nil, nil, err - } + return immediateOneOrFetch(url, url, + func(string) (map[string]map[string]string, map[string]keypairs.PublicKeyDeprecated, error) { + m, key, err := uncached.Fetch(url) + if nil != err { + return nil, nil, err + } - // put in a map, just for caching - maps := map[string]map[string]string{} - maps[keypairs.Thumbprint(key.Key().(keypairs.PublicKeyTransitional))] = m + // put in a map, just for caching + maps := map[string]map[string]string{} + maps[keypairs.Thumbprint(key.Key().(keypairs.PublicKeyTransitional))] = m - keys := map[string]keypairs.PublicKey{} - keys[keypairs.Thumbprint(key.Key().(keypairs.PublicKeyTransitional))] = key + keys := map[string]keypairs.PublicKeyDeprecated{} + keys[keypairs.Thumbprint(key.Key().(keypairs.PublicKeyTransitional))] = key - return maps, keys, nil - }) + return maps, keys, nil + }) } // Get retrieves a key from cache, or returns an error. // The issuer string may be empty if using a thumbprint rather than a kid. -func Get(kidOrThumb, iss string) keypairs.PublicKey { +func Get(kidOrThumb, iss string) keypairs.PublicKeyTransitional { if pub := get(kidOrThumb, iss); nil != pub { - return pub.Key + return pub.Key.Key().(keypairs.PublicKeyTransitional) } return nil } @@ -212,21 +224,21 @@ func get(kidOrThumb, iss string) *CachableKey { func immediateOneOrFetch(kidOrThumb, iss string, fetcher myfetcher) (keypairs.PublicKeyTransitional, error) { now := time.Now() - key := get(kidOrThumb, iss) + hit := get(kidOrThumb, iss) - if nil == key { + if nil == hit { return fetchAndSelect(kidOrThumb, iss, fetcher) } // Fetch just a little before the key actually expires - if key.Expiry.Sub(now) <= StaleTime { + if hit.Expiry.Sub(now) <= StaleTime { go fetchAndSelect(kidOrThumb, iss, fetcher) } - return key.Key.Key().(keypairs.PublicKeyTransitional), nil + return hit.Key.Key().(keypairs.PublicKeyTransitional), nil } -type myfetcher func(string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) +type myfetcher func(string) (map[string]map[string]string, map[string]keypairs.PublicKeyDeprecated, error) func fetchAndSelect(id, baseURL string, fetcher myfetcher) (keypairs.PublicKeyTransitional, error) { maps, keys, err := fetcher(baseURL) @@ -250,7 +262,7 @@ func fetchAndSelect(id, baseURL string, fetcher myfetcher) (keypairs.PublicKeyTr return nil, fmt.Errorf("Key identified by '%s' was not found at %s", id, baseURL) } -func cacheKeys(maps map[string]map[string]string, keys map[string]keypairs.PublicKey, issuer string) { +func cacheKeys(maps map[string]map[string]string, keys PublicKeysMap, issuer string) { for i := range keys { key := keys[i] m := maps[i] @@ -260,10 +272,13 @@ func cacheKeys(maps map[string]map[string]string, keys map[string]keypairs.Publi } iss = normalizeIssuer(iss) cacheKey(m["kid"], iss, m["exp"], key) + if 0 == len(m[uncached.URLishKey]) { + cacheKey(m[uncached.URLishKey], iss, m["exp"], key) + } } } -func cacheKey(kid, iss, expstr string, pub keypairs.PublicKey) error { +func cacheKey(kid, iss, expstr string, pub keypairs.PublicKeyDeprecated) error { var expiry time.Time iss = normalizeIssuer(iss) diff --git a/keyfetch/fetch_test.go b/keyfetch/fetch_test.go index 2bb2df7..78df9fb 100644 --- a/keyfetch/fetch_test.go +++ b/keyfetch/fetch_test.go @@ -11,13 +11,14 @@ import ( var pubkey keypairs.PublicKeyTransitional func TestCachesKey(t *testing.T) { + // TODO set KeyID() in cache testCachesKey(t, "https://bigsquid.auth0.com/") clear() testCachesKey(t, "https://bigsquid.auth0.com") // Get PEM pubk3, err := PEM("https://bigsquid.auth0.com/pem") if nil != err { - t.Fatal("Error fetching and caching key:", err) + t.Fatal("[0] Error fetching and caching key:", err) } thumb3 := keypairs.Thumbprint(pubk3) thumb := keypairs.Thumbprint(pubkey) @@ -62,7 +63,7 @@ func testCachesKey(t *testing.T, url string) { // Get with caching pubkey, err = OIDCJWK(thumb, url) if nil != err { - t.Fatal("Error fetching and caching key:", err) + t.Fatal("[1] Error fetching and caching key:", err) } // Look in cache for each (and succeed) @@ -74,9 +75,13 @@ func testCachesKey(t *testing.T, url string) { thumb = keypairs.Thumbprint(pubkey) if pub := Get(thumb, url); nil == pub { t.Fatal("key was not properly cached by kid", pubkey) - } else { - t.Log("Key did not have an explicit KeyID") } + // TODO + /* + if 0 == len(keyfetch.GetID(thumb)) { + t.Log("Key did not have an explicit KeyID", thumb) + } + */ // Get again (should be sub-ms instant) now := time.Now() diff --git a/keyfetch/uncached/fetch.go b/keyfetch/uncached/fetch.go index b0a73bf..e37f9f9 100644 --- a/keyfetch/uncached/fetch.go +++ b/keyfetch/uncached/fetch.go @@ -4,6 +4,8 @@ package uncached import ( "bytes" "encoding/json" + "crypto/rsa" + "crypto/ecdsa" "errors" "io" "io/ioutil" @@ -15,8 +17,17 @@ import ( "git.rootprojects.org/root/keypairs" ) +// URLishKey is TODO +var URLishKey = "_kid_url" + +// JWKMapByID is TODO +type JWKMapByID = map[string]map[string]string + +// PublicKeysMap is TODO +type PublicKeysMap = map[string]keypairs.PublicKeyDeprecated + // OIDCJWKs gets the OpenID Connect configuration from the baseURL and then calls JWKs with the specified jwks_uri -func OIDCJWKs(baseURL string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { +func OIDCJWKs(baseURL string) (JWKMapByID, PublicKeysMap, error) { baseURL = normalizeBaseURL(baseURL) oidcConf := struct { JWKSURI string `json:"jwks_uri"` @@ -37,7 +48,7 @@ func OIDCJWKs(baseURL string) (map[string]map[string]string, map[string]keypairs } // WellKnownJWKs calls JWKs with baseURL + /.well-known/jwks.json as constructs the jwks_uri -func WellKnownJWKs(baseURL string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { +func WellKnownJWKs(baseURL string) (JWKMapByID, PublicKeysMap, error) { baseURL = normalizeBaseURL(baseURL) url := baseURL + ".well-known/jwks.json" @@ -45,9 +56,9 @@ func WellKnownJWKs(baseURL string) (map[string]map[string]string, map[string]key } // JWKs fetches and parses a jwks.json (assuming well-known format) -func JWKs(jwksurl string) (map[string]map[string]string, map[string]keypairs.PublicKey, error) { - keys := map[string]keypairs.PublicKey{} - maps := map[string]map[string]string{} +func JWKs(jwksurl string) (JWKMapByID, PublicKeysMap, error) { + keys := PublicKeysMap{} + maps := JWKMapByID{} resp := struct { Keys []map[string]interface{} `json:"keys"` }{ @@ -79,33 +90,39 @@ func JWKs(jwksurl string) (map[string]map[string]string, map[string]keypairs.Pub } // PEM fetches and parses a PEM (assuming well-known format) -func PEM(pemurl string) (map[string]string, keypairs.PublicKey, error) { - var pub keypairs.PublicKey +func PEM(pemurl string) (map[string]string, keypairs.PublicKeyTransitional, error) { + var pubd keypairs.PublicKeyDeprecated 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 + pubd, err = keypairs.ParsePublicKey(pem) + if nil != err { + return err + } + return nil }); nil != err { return nil, nil, err } jwk := map[string]interface{}{} + pub := pubd.Key().(keypairs.PublicKeyTransitional) body := bytes.NewBuffer(keypairs.MarshalJWKPublicKey(pub)) decoder := json.NewDecoder(body) decoder.UseNumber() _ = decoder.Decode(&jwk) m := getStringMap(jwk) - m["kid"] = pemurl + m["kid"] = keypairs.Thumbprint(pub) + // TODO is this just junk? + m[URLishKey] = pemurl - switch p := pub.(type) { - case *keypairs.ECPublicKey: - p.KID = pemurl - case *keypairs.RSAPublicKey: - p.KID = pemurl + switch pub.(type) { + case *ecdsa.PublicKey: + //p.KID = pemurl + case *rsa.PublicKey: + //p.KID = pemurl default: return nil, nil, errors.New("impossible key type") } @@ -114,7 +131,7 @@ func PEM(pemurl string) (map[string]string, keypairs.PublicKey, error) { } // 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.PublicKeyDeprecated, error) { var m map[string]interface{} if err := safeFetch(url, func(body io.Reader) error { decoder := json.NewDecoder(body) diff --git a/keypairs.go b/keypairs.go index 92ffce9..e8b8879 100644 --- a/keypairs.go +++ b/keypairs.go @@ -64,8 +64,8 @@ type PublicKeyTransitional interface { Equal(x crypto.PublicKey) bool } -// PublicKey thinly veils crypto.PublicKey for type safety -type PublicKey interface { +// PublicKeyDeprecated thinly veils crypto.PublicKey for type safety +type PublicKeyDeprecated interface { crypto.PublicKey //Equal(x crypto.PublicKey) bool //Thumbprint() string @@ -149,13 +149,13 @@ func (p *RSAPublicKey) ExpiresAt() time.Time { } // NewPublicKey wraps a crypto.PublicKey to make it typesafe. -func NewPublicKey(pub crypto.PublicKey, kid ...string) PublicKey { +func NewPublicKey(pub crypto.PublicKey, kid ...string) PublicKeyDeprecated { _, ok := pub.(PublicKeyTransitional) if !ok { panic("Developer Error: not a crypto.PublicKey") } - var k PublicKey + var k PublicKeyDeprecated switch p := pub.(type) { case *ecdsa.PublicKey: eckey := &ECPublicKey{ @@ -186,9 +186,9 @@ func NewPublicKey(pub crypto.PublicKey, kid ...string) PublicKey { // MarshalJWKPublicKey outputs a JWK with its key id (kid) and an optional expiration, // making it suitable for use as an OIDC public key. -func MarshalJWKPublicKey(key PublicKey, exp ...time.Time) []byte { +func MarshalJWKPublicKey(key PublicKeyTransitional, exp ...time.Time) []byte { // thumbprint keys are alphabetically sorted and only include the necessary public parts - switch k := key.Key().(type) { + switch k := key.(type) { case *rsa.PublicKey: return MarshalRSAPublicKey(k, exp...) case *ecdsa.PublicKey: @@ -206,7 +206,7 @@ func Thumbprint(pub PublicKeyTransitional) string { } // ThumbprintPublicKey returns the SHA256 RFC-spec JWK thumbprint -func ThumbprintPublicKey(pub PublicKey) string { +func ThumbprintPublicKey(pub PublicKeyDeprecated) string { return ThumbprintUntypedPublicKey(pub.Key().(PublicKeyTransitional)) } @@ -214,7 +214,7 @@ func ThumbprintPublicKey(pub PublicKey) string { // (but will still panic, to help you discover bugs in development rather than production). func ThumbprintUntypedPublicKey(pub crypto.PublicKey) string { switch p := pub.(type) { - case PublicKey: + case PublicKeyDeprecated: return ThumbprintUntypedPublicKey(p.Key().(PublicKeyTransitional)) case *ecdsa.PublicKey: return ThumbprintECPublicKey(p) @@ -379,7 +379,7 @@ func getPEMBytes(block []byte) ([][]byte, error) { // 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 -func ParsePublicKey(block []byte) (PublicKey, error) { +func ParsePublicKey(block []byte) (PublicKeyDeprecated, error) { blocks, err := getPEMBytes(block) if nil != err { return nil, ErrParsePublicKey @@ -406,11 +406,11 @@ func ParsePublicKey(block []byte) (PublicKey, error) { } // ParsePublicKeyString calls ParsePublicKey([]byte(key)) for all you lazy folk. -func ParsePublicKeyString(block string) (PublicKey, error) { +func ParsePublicKeyString(block string) (PublicKeyDeprecated, error) { return ParsePublicKey([]byte(block)) } -func parsePublicKey(der []byte) (PublicKey, error) { +func parsePublicKey(der []byte) (PublicKeyDeprecated, error) { cert, err := x509.ParseCertificate(der) if nil == err { switch k := cert.PublicKey.(type) { @@ -456,7 +456,7 @@ func parsePublicKey(der []byte) (PublicKey, error) { } // NewJWKPublicKey contstructs a PublicKey from the relevant pieces a map[string]string (generic JSON) -func NewJWKPublicKey(m map[string]string) (PublicKey, error) { +func NewJWKPublicKey(m map[string]string) (PublicKeyDeprecated, error) { switch m["kty"] { case "RSA": return parseRSAPublicKey(m) @@ -468,7 +468,7 @@ func NewJWKPublicKey(m map[string]string) (PublicKey, error) { } // ParseJWKPublicKey parses a JSON-encoded JWK and returns a PublicKey, or a (hopefully) helpful error message -func ParseJWKPublicKey(b []byte) (PublicKey, error) { +func ParseJWKPublicKey(b []byte) (PublicKeyDeprecated, error) { // RSA and EC have "d" as a private part if bytes.Contains(b, []byte(`"d"`)) { return nil, ErrUnexpectedPrivateKey @@ -477,7 +477,7 @@ func ParseJWKPublicKey(b []byte) (PublicKey, error) { } // ParseJWKPublicKeyString calls ParseJWKPublicKey([]byte(key)) for all you lazy folk. -func ParseJWKPublicKeyString(s string) (PublicKey, error) { +func ParseJWKPublicKeyString(s string) (PublicKeyDeprecated, error) { if strings.Contains(s, `"d"`) { return nil, ErrUnexpectedPrivateKey } @@ -485,7 +485,7 @@ func ParseJWKPublicKeyString(s string) (PublicKey, error) { } // DecodeJWKPublicKey stream-decodes a JSON-encoded JWK and returns a PublicKey, or a (hopefully) helpful error message -func DecodeJWKPublicKey(r io.Reader) (PublicKey, error) { +func DecodeJWKPublicKey(r io.Reader) (PublicKeyDeprecated, error) { m := make(map[string]string) if err := json.NewDecoder(r).Decode(&m); nil != err { return nil, err @@ -497,7 +497,7 @@ func DecodeJWKPublicKey(r io.Reader) (PublicKey, error) { } // the underpinnings of the parser as used by the typesafe wrappers -func newJWKPublicKey(data interface{}) (PublicKey, error) { +func newJWKPublicKey(data interface{}) (PublicKeyDeprecated, error) { var m map[string]string switch d := data.(type) { diff --git a/keyserve/keyserve.go b/keyserve/keyserve.go index 400d7d2..5059c7f 100644 --- a/keyserve/keyserve.go +++ b/keyserve/keyserve.go @@ -39,7 +39,7 @@ var never = time.Time{} // Middleware holds your public keys and has http handler methods for OIDC and Auth0 JWKs type Middleware struct { BaseURL *url.URL - Keys []keypairs.PublicKey + Keys []keypairs.PublicKeyDeprecated ExpiresIn time.Duration } @@ -148,7 +148,7 @@ func (m *Middleware) Auth0PEM(w http.ResponseWriter, r *http.Request) { } } -func marshalJWKs(keys []keypairs.PublicKey, exp2 time.Time) []string { +func marshalJWKs(keys []keypairs.PublicKeyDeprecated, exp2 time.Time) []string { jwks := make([]string, 0, 1) for i := range keys { @@ -163,7 +163,7 @@ func marshalJWKs(keys []keypairs.PublicKey, exp2 time.Time) []string { // Note that you don't have to embed `iss` in the JWK because the client // already has that info by virtue of getting to it in the first place. - jwk := string(keypairs.MarshalJWKPublicKey(key, exp)) + jwk := string(keypairs.MarshalJWKPublicKey(key.Key().(keypairs.PublicKeyTransitional), exp)) jwks = append(jwks, jwk) } diff --git a/keyserve/keyserve_test.go b/keyserve/keyserve_test.go index 5111258..a2a1046 100644 --- a/keyserve/keyserve_test.go +++ b/keyserve/keyserve_test.go @@ -18,7 +18,7 @@ import ( func TestServeKeys(t *testing.T) { eckey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - pubs := []keypairs.PublicKey{ + pubs := []keypairs.PublicKeyDeprecated{ keypairs.NewPublicKey(eckey.Public()), } @@ -42,8 +42,9 @@ func TestServeKeys(t *testing.T) { go func() { time.Sleep(15 * time.Second) - ctx, _ := context.WithTimeout(context.Background(), 15*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) h.Shutdown(ctx) + cancel() }() m := map[string]string{} diff --git a/sign.go b/sign.go index 59117ef..5758b2c 100644 --- a/sign.go +++ b/sign.go @@ -26,7 +26,7 @@ func SignClaims(privkey PrivateKey, header Object, claims Object) (*JWS, error) //delete(header, "_seed") } - protected, header, err := headerToProtected(NewPublicKey(privkey.Public()), header) + protected, header, err := headerToProtected(privkey.Public().(PublicKeyTransitional), header) if nil != err { return nil, err } @@ -56,7 +56,7 @@ func SignClaims(privkey PrivateKey, header Object, claims Object) (*JWS, error) }, nil } -func headerToProtected(pub PublicKey, header Object) ([]byte, Object, error) { +func headerToProtected(pub PublicKeyTransitional, header Object) ([]byte, Object, error) { if nil == header { header = Object{} } @@ -65,7 +65,7 @@ func headerToProtected(pub PublicKey, header Object) ([]byte, Object, error) { // because that's all that's practical and well-supported. // No security theatre here. alg := "ES256" - switch pub.Key().(type) { + switch pub.(type) { case *rsa.PublicKey: alg = "RS256" } @@ -80,7 +80,7 @@ func headerToProtected(pub PublicKey, header Object) ([]byte, Object, error) { // TODO what are the acceptable values? JWT. JWS? others? header["typ"] = "JWT" if _, ok := header["jwk"]; !ok { - thumbprint := ThumbprintPublicKey(pub) + thumbprint := ThumbprintPublicKey(NewPublicKey(pub)) kid, _ := header["kid"].(string) if "" != kid && thumbprint != kid { return nil, nil, errors.New("'kid' should be the key's thumbprint") diff --git a/verify.go b/verify.go index 80f3216..81c5ec6 100644 --- a/verify.go +++ b/verify.go @@ -15,7 +15,7 @@ import ( ) // VerifyClaims will check the signature of a parsed JWT -func VerifyClaims(pubkey PublicKey, jws *JWS) (errs []error) { +func VerifyClaims(pubkey PublicKeyTransitional, jws *JWS) (errs []error) { kid, _ := jws.Header["kid"].(string) jwkmap, hasJWK := jws.Header["jwk"].(Object) //var jwk JWK = nil @@ -27,7 +27,7 @@ func VerifyClaims(pubkey PublicKey, jws *JWS) (errs []error) { seed = int64(seedf64) } - var pub PublicKey = nil + var pub PublicKeyTransitional = nil if hasJWK { pub, errs = selfsignCheck(jwkmap, errs) } else { @@ -72,8 +72,8 @@ func VerifyClaims(pubkey PublicKey, jws *JWS) (errs []error) { return errs } -func selfsignCheck(jwkmap Object, errs []error) (PublicKey, []error) { - var pub PublicKey = nil +func selfsignCheck(jwkmap Object, errs []error) (PublicKeyTransitional, []error) { + var pub PublicKeyDeprecated = nil log.Println("Security TODO: did not check jws.Claims[\"sub\"] against 'jwk'") log.Println("Security TODO: did not check jws.Claims[\"iss\"]") kty := jwkmap["kty"] @@ -104,11 +104,11 @@ func selfsignCheck(jwkmap Object, errs []error) (PublicKey, []error) { } } - return pub, errs + return pub.Key().(PublicKeyTransitional), errs } -func pubkeyCheck(pubkey PublicKey, kid string, opts *keyOptions, errs []error) (PublicKey, []error) { - var pub PublicKey = nil +func pubkeyCheck(pubkey PublicKeyTransitional, kid string, opts *keyOptions, errs []error) (PublicKeyTransitional, []error) { + var pub PublicKeyTransitional = nil if "" == kid { err := errors.New("token should have 'kid' or 'jwk' in header to identify the public key") @@ -130,7 +130,7 @@ func pubkeyCheck(pubkey PublicKey, kid string, opts *keyOptions, errs []error) ( return nil, errs } privkey := newPrivateKey(opts) - pub = NewPublicKey(privkey.Public()) + pub = privkey.Public().(PublicKeyTransitional) return pub, errs } err := errors.New("no matching public key") @@ -140,7 +140,7 @@ func pubkeyCheck(pubkey PublicKey, kid string, opts *keyOptions, errs []error) ( } if nil != pub && "" != kid { - if 1 != subtle.ConstantTimeCompare([]byte(kid), []byte(Thumbprint(pub.Key().(PublicKeyTransitional)))) { + if 1 != subtle.ConstantTimeCompare([]byte(kid), []byte(Thumbprint(pub))) { err := errors.New("'kid' does not match the public key thumbprint") errs = append(errs, err) } @@ -149,9 +149,9 @@ func pubkeyCheck(pubkey PublicKey, kid string, opts *keyOptions, errs []error) ( } // Verify will check the signature of a hash -func Verify(pubkey PublicKey, hash []byte, sig []byte) bool { +func Verify(pubkey PublicKeyTransitional, hash []byte, sig []byte) bool { - switch pub := pubkey.Key().(type) { + switch pub := pubkey.(type) { case *rsa.PublicKey: //log.Printf("RSA VERIFY") // TODO Size(key) to detect key size ?