Git Product home page Git Product logo

jwx's Introduction

github.com/lestrrat-go/jwx/v2 Go Reference codecov.io

Go module implementing various JWx (JWA/JWE/JWK/JWS/JWT, otherwise known as JOSE) technologies.

If you are using this module in your product or your company, please add your product and/or company name in the Wiki! It really helps keeping up our motivation.

Features

  • Complete coverage of JWA/JWE/JWK/JWS/JWT, not just JWT+minimum tool set.
    • Supports JWS messages with multiple signatures, both compact and JSON serialization
    • Supports JWS with detached payload
    • Supports JWS with unencoded payload (RFC7797)
    • Supports JWE messages with multiple recipients, both compact and JSON serialization
    • Most operations work with either JWK or raw keys e.g. *rsa.PrivateKey, *ecdsa.PrivateKey, etc).
  • Opinionated, but very uniform API. Everything is symmetric, and follows a standard convention
    • jws.Parse/Verify/Sign
    • jwe.Parse/Encrypt/Decrypt
    • Arguments are organized as explicit required paramters and optional WithXXXX() style options.
  • Extra utilities
    • jwk.Cache to always keep a JWKS up-to-date
    • bazel-ready

Some more in-depth discussion on why you might want to use this library over others can be found in the Description section

If you are using v0 or v1, you are strongly encouraged to migrate to using v2 (the version that comes with the README you are reading).

SYNOPSIS

package examples_test

import (
  "bytes"
  "fmt"
  "net/http"
  "time"

  "github.com/lestrrat-go/jwx/v2/jwa"
  "github.com/lestrrat-go/jwx/v2/jwe"
  "github.com/lestrrat-go/jwx/v2/jwk"
  "github.com/lestrrat-go/jwx/v2/jws"
  "github.com/lestrrat-go/jwx/v2/jwt"
)

func ExampleJWX() {
  // Parse, serialize, slice and dice JWKs!
  privkey, err := jwk.ParseKey(jsonRSAPrivateKey)
  if err != nil {
    fmt.Printf("failed to parse JWK: %s\n", err)
    return
  }

  pubkey, err := jwk.PublicKeyOf(privkey)
  if err != nil {
    fmt.Printf("failed to get public key: %s\n", err)
    return
  }

  // Work with JWTs!
  {
    // Build a JWT!
    tok, err := jwt.NewBuilder().
      Issuer(`github.com/lestrrat-go/jwx`).
      IssuedAt(time.Now()).
      Build()
    if err != nil {
      fmt.Printf("failed to build token: %s\n", err)
      return
    }

    // Sign a JWT!
    signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256, privkey))
    if err != nil {
      fmt.Printf("failed to sign token: %s\n", err)
      return
    }

    // Verify a JWT!
    {
      verifiedToken, err := jwt.Parse(signed, jwt.WithKey(jwa.RS256, pubkey))
      if err != nil {
        fmt.Printf("failed to verify JWS: %s\n", err)
        return
      }
      _ = verifiedToken
    }

    // Work with *http.Request!
    {
      req, err := http.NewRequest(http.MethodGet, `https://github.com/lestrrat-go/jwx`, nil)
      req.Header.Set(`Authorization`, fmt.Sprintf(`Bearer %s`, signed))

      verifiedToken, err := jwt.ParseRequest(req, jwt.WithKey(jwa.RS256, pubkey))
      if err != nil {
        fmt.Printf("failed to verify token from HTTP request: %s\n", err)
        return
      }
      _ = verifiedToken
    }
  }

  // Encrypt and Decrypt arbitrary payload with JWE!
  {
    encrypted, err := jwe.Encrypt(payloadLoremIpsum, jwe.WithKey(jwa.RSA_OAEP, jwkRSAPublicKey))
    if err != nil {
      fmt.Printf("failed to encrypt payload: %s\n", err)
      return
    }

    decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP, jwkRSAPrivateKey))
    if err != nil {
      fmt.Printf("failed to decrypt payload: %s\n", err)
      return
    }

    if !bytes.Equal(decrypted, payloadLoremIpsum) {
      fmt.Printf("verified payload did not match\n")
      return
    }
  }

  // Sign and Verify arbitrary payload with JWS!
  {
    signed, err := jws.Sign(payloadLoremIpsum, jws.WithKey(jwa.RS256, jwkRSAPrivateKey))
    if err != nil {
      fmt.Printf("failed to sign payload: %s\n", err)
      return
    }

    verified, err := jws.Verify(signed, jws.WithKey(jwa.RS256, jwkRSAPublicKey))
    if err != nil {
      fmt.Printf("failed to verify payload: %s\n", err)
      return
    }

    if !bytes.Equal(verified, payloadLoremIpsum) {
      fmt.Printf("verified payload did not match\n")
      return
    }
  }
  // OUTPUT:
}

source: examples/jwx_readme_example_test.go

How-to Documentation

Description

This Go module implements JWA, JWE, JWK, JWS, and JWT. Please see the following table for the list of available packages:

Package name Notes
jwt RFC 7519
jwk RFC 7517 + RFC 7638
jwa RFC 7518
jws RFC 7515 + RFC 7797
jwe RFC 7516

History

My goal was to write a server that heavily uses JWK and JWT. At first glance the libraries that already exist seemed sufficient, but soon I realized that

  1. To completely implement the protocols, I needed the entire JWT, JWK, JWS, JWE (and JWA, by necessity).
  2. Most of the libraries that existed only deal with a subset of the various JWx specifications that were necessary to implement their specific needs

For example, a certain library looks like it had most of JWS, JWE, JWK covered, but then it lacked the ability to include private claims in its JWT responses. Another library had support of all the private claims, but completely lacked in its flexibility to generate various different response formats.

Because I was writing the server side (and the client side for testing), I needed the entire JOSE toolset to properly implement my server, and they needed to be flexible enough to fulfill the entire spec that I was writing.

So here's github.com/lestrrat-go/jwx/v2. This library is extensible, customizable, and hopefully well organized to the point that it is easy for you to slice and dice it.

Why would I use this library?

There are several other major Go modules that handle JWT and related data formats, so why should you use this library?

From a purely functional perspective, the only major difference is this: Whereas most other projects only deal with what they seem necessary to handle JWTs, this module handles the entire spectrum of JWS, JWE, JWK, and JWT.

That is, if you need to not only parse JWTs, but also to control JWKs, or if you need to handle payloads that are NOT JWTs, you should probably consider using this module. You should also note that JWT is built on top of those other technologies. You simply cannot have a complete JWT package without implementing the entirety of JWS/JWE/JWK, which this library does.

Next, from an implementation perspective, this module differs significantly from others in that it tries very hard to expose only the APIs, and not the internal data. For example, individual JWT claims are not accessible through struct field lookups. You need to use one of the getter methods.

This is because this library takes the stance that the end user is fully capable and even willing to shoot themselves on the foot when presented with a lax API. By making sure that users do not have access to open structs, we can protect users from doing silly things like creating incomplete structs, or access the structs concurrently without any protection. This structure also allows us to put extra smarts in the structs, such as doing the right thing when you want to parse / write custom fields (this module does not require the user to specify alternate structs to parse objects with custom fields)

In the end I think it comes down to your usage pattern, and priorities. Some general guidelines that come to mind are:

  • If you want a single library to handle everything JWx, such as using JWE, JWK, JWS, handling auto-refreshing JWKs, use this module.
  • If you want to honor all possible custom fields transparently, use this module.
  • If you want a standardized clean API, use this module.

Otherwise, feel free to choose something else.

Contributions

Issues

For bug reports and feature requests, please try to follow the issue templates as much as possible. For either bug reports or feature requests, failing tests are even better.

Pull Requests

Please make sure to include tests that excercise the changes you made.

If you are editing auto-generated files (those files with the _gen.go suffix, please make sure that you do the following:

  1. Edit the generator, not the generated files (e.g. internal/cmd/genreadfile/main.go)
  2. Run make generate (or go generate) to generate the new code
  3. Commit both the generator and the generated files

Discussions / Usage

Please try discussions first.

Related Modules

Credits

  • Initial work on this library was generously sponsored by HDE Inc (https://www.hde.co.jp)
  • Lots of code, especially JWE was initially taken from go-jose library (https://github.com/square/go-jose)
  • Lots of individual contributors have helped this project over the years. Thank each and everyone of you very much.

jwx's People

Contributors

anatol avatar andorsk avatar benderscript avatar cloudkucooland avatar darnmason avatar dependabot[bot] avatar github-actions[bot] avatar imirkin avatar johejo avatar kohkimakimoto avatar lestrrat avatar leungyauming avatar loozhengyuan avatar mariosnikolaou avatar maxi-mega avatar nabeken avatar orisano avatar reinkrul avatar renannprado avatar rodrigc avatar satorunooshie avatar sding3 avatar segfault16 avatar sonatard avatar ste93cry avatar tchssk avatar thomas-fossati avatar warashi avatar wvh avatar yaosiang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jwx's Issues

Password protected pem && Headers?

I'm trying to duplicate functionality with PHP.

  $signer = new Sha256();

  // Set path of private_key.pem. Overwrite the default file as sample.
  $privateKeyString = new Key(
      "file:///box/" . $this->config['private_key_file'], $this->config['passphrase']
  );

  $assertion = (new Builder())
      ->setHeader('kid', $this->config['public_key_id'])
      ->setIssuer($this->config['au_client_id'])
      ->setSubject($id)
      ->set('box_sub_type', $type)
      ->setAudience($this->audience_url)
      ->setId(uniqid('ABC'))
      ->setIssuedAt(time())
      ->setExpiration(time() + $this->config['expiration'])
      ->sign($signer,  $privateKeyString)
      ->getToken();

Which outputs the following:

  ^ Lcobucci\JWT\Token^ {#985
    -headers: array:3 [
      "typ" => "JWT"
      "alg" => "RS256"
      "kid" => Lcobucci\JWT\Claim\Basic^ {#976
        -name: "kid"
        -value: "38u7xxxx"
      }
    ]
    -claims: array:7 [
      "iss" => Lcobucci\JWT\Claim\EqualsTo^ {#977
        -name: "iss"
        -value: "hd2meuymeq5erwineh2lt1vghxxx0000"
      }
      "sub" => Lcobucci\JWT\Claim\EqualsTo^ {#978
        -name: "sub"
        -value: "248410000"
      }
      "box_sub_type" => Lcobucci\JWT\Claim\Basic^ {#979
        -name: "box_sub_type"
        -value: "enterprise"
      }
      "aud" => Lcobucci\JWT\Claim\EqualsTo^ {#980
        -name: "aud"
        -value: "https://api.box.com/oauth2/token"
      }
      "jti" => Lcobucci\JWT\Claim\EqualsTo^ {#981
        -name: "jti"
        -value: "ABC5e0b3370a6458"
      }
      "iat" => Lcobucci\JWT\Claim\LesserOrEqualsTo^ {#982
        -name: "iat"
        -value: 1577792368
      }
      "exp" => Lcobucci\JWT\Claim\GreaterOrEqualsTo^ {#983
        -name: "exp"
        -value: 1577792428
      }
    ]
    -signature: Lcobucci\JWT\Signature^ {#984
    }
    -payload: array:3 [
      0 => "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjM4dTdwamxwIn0"
      1 => "eyJpc3MiOiJoZDJtZXV5bWVxNWVyd2luZWgybHQxdmdoY21rNDEwNCIsInN1YiI6IjI0ODQxOTQ2MCIsImJveF9zdWJfdHlwZSI6ImVudGVycHJpc2UiLCJhdWQiOiJodHRwczpcL1wvYXBpLmJveC5jb21cL29hdXRoMlwvdG9rZW4iLCJqdGkiOiJBQkM1ZTBiMzM3MGE2NDU4IiwiaWF0IjoxNTc3NzkyMzY4LCJleHAiOjE1Nzc3OTI0Mjh9"
      2 => "JUZqTydnL7UT4aksNAqXJpAYDqelvOXimc85p2qnrvCfr1ZV-DBQLt98GagwbHt2Wn0V5a6oYFgUwo5tfyHA5ElYBPYS7AeF96zTyDEJlCFe8IjzObRWP4tdTePsRiXlYGDt7CbWIQ_5mVLV5zrNF0o1ZBfwEenx0PBB1jbnGZ068rO7B5ejHIC7vy7sSmAb7Yn6xZFK5YLeZxBZUUmoch8bM59wFirAAyG8pkBZqUdHSExzt-Q7QisgeoqdLdc9QcklwROiBvDgOkWtCwgee77bsgc8V_m9BAltgu-zXnuIf2ztQRhlNFi-dp6GuNL15sxtz84uh13UroCM9A1hRw"
    ]
  }

With go, I have achieved the following result:

  {
    "aud": "https://api.box.com/oauth2/token",
    "exp": 1577792181,
    "iat": 1577792121,
    "iss": "hd2meuymeq5erwineh2lt1vghxxxxxxx",
    "jti": "ABC5e0b327973b533.11640101",
    "sub": "248000000",
    "alg": "RS256",
    "box_sub_type": "enterprise",
    "kid": "38u7xxxx",
    "typ": "JWT"
  }

The problem is that I'm getting from the service I wish to authenticate to:

{"error":"invalid_grant","error_description":"Algorithm not allowed"}

I'm not sure whether it's because I'm missing headers or that there's no password support for the pem?

Many thanks!

Couple of issues...

Hi,

Filing two issues at once - I was just checking out your library hoping to use it, but hit two blockers.

1

jwk.Fetch("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")

Returns err = "nil is invalid kty", but when looking at that page (document), all the key types there are specified as "rsa", so the error message (and the failure itself) don't make sense.

2

The readme at the repository level (main example) starts out with jwk.New() but New now takes a parameter. I figured it was the signing method but then hit another inconsistency and couldn't get past that.

Curve bits do not need to be a multiple of Keysize

In sign/ecdsa.go there is a the following check:

	if curveBits != keysiz*8 {
		return nil, errors.New("key size does not match curve bit size")
	}

But this check is not ok. See for example https://tools.ietf.org/html/rfc7515 ECDSA P-521 SHA-512. The README says this algorithm is supported but it returns an error.

The code needs to account for such curves. Example solution: https://github.com/kelseyhightower/app/blob/master/vendor/github.com/dgrijalva/jwt-go/ecdsa.go

gopkg.in/square/go-jose.v2/cryptosigner/cryptosigner.go

No method to verify essential claims

Many top level JWT libraries have checks that verify the following claims.

  • iss
  • sub
  • aud
  • exp
  • nbf
  • iat
  • jti

I was surprised when calling jws.Verify on an expired token did not return an error. The jwt package could probably benefit from a high level Verify function that verified the signature as well as the essential claims.

jws.Sign() removes whitespace and newlines from JSON

I would like to use jws.Sign() to parse any JSON payloads such as https://tools.ietf.org/html/rfc7515 Example A.1, A.2, etc

Unfortunately that is not possible because jws.Sign() strips whitespace, newlines and carriage return from the JWS Protected Header. Basically, jws.Sign() removes all whitespace from the JSON. So, instead of signing:

     {"typ":"JWT",
      "alg":"HS256"}

[123, 34, 116, 121, 112, 34, 58, 34, 74, 87, 84, 34, 44, 13, 10, 32,
   34, 97, 108, 103, 34, 58, 34, 72, 83, 50, 53, 54, 34, 125]

It is signing (no whitespace, no new lines):

{"typ":"JWT","alg":"HS256"}

jws.Sign() should be able to take the protected headers "as is", without trying to add to a structure and then serialize it. If it changes the JWS headers as presented, it will generate different signatures from other systems or that the original document holder expected.

Marshalling JWK with X.509 certs (x5c) to JSON is incorrect

When marshalling a JWK which contains an X.509 certificate chain (x5c) to JSON, the resulting JSON contains the actual JSON representation of the x509.Certificate struct, instead of the base64 encoded ASN.1 structure.

When you currently marshal a JWK with X.509 certificates, the result looks like this:

{
  "x5c": [
    {
      "Raw": "(clipped)",
      "RawTBSCertificate": "(clipped)",
      "RawSubjectPublicKeyInfo": "(clipped)",
      "RawSubject": "MBQxEjAQBgNVBAMTCVVuaXQgVGVzdA==",
      "RawIssuer": "MBQxEjAQBgNVBAMTCVVuaXQgVGVzdA==",
      "Signature": "(clipped)",
      "SignatureAlgorithm": 4,
      "PublicKeyAlgorithm": 1,
      "PublicKey": {
        "N": 24671087878926420765627594405100525472964852601392231600603270272161299869337708785559107093049690228584918572462867403668529786789555679458627290437270277064189717437664227011651887425808413331631123547651751178895658287613669051000122863770197975516965641305376431979431040052344634464408824817509747799621210526684428659090983239786693723934635095941965764398368338277672722674150808556704457344599250279636287247318930241951584729037497647296155529294298849498530189942558795798487951622261355315681698747217964838395441155647679174746086691141741172431393044470804431335408467980805992517414782405277307499944769,
        "E": 65537
      },

     ... etc

Low test coverage

I used the following to examine coverage.

go test -v ./... -coverprofile=coverage.out
go tool cover -html=coverage.out

  • Many components are below 70% and quite a few under 50%.
  • Some functions do not seem to be used throughout the code and have no coverage. In my copy of the repo I just removed most of these functions, rerun the tests and they all passed.

Fetch AWS Cognito JWK got the error message

Hello,
I try to get JWK from AWS Cognito, but I got the error message and I have no idea why return illegal base64 data.

Code

package main

import(
  "log"
  "github.com/lestrrat-go/jwx/jwk"
)

func main() {
  set, err := jwk.FetchHTTP("https://cognito-identity.amazonaws.com/.well-known/jwks_uri")
    if err != nil {
      log.Printf("failed to parse JWK: %s", err)
      return
    }

    log.Println(set)
}

Result:
2020/02/24 19:01:58 failed to parse JWK: failed to extract from map: failed to construct key from map: failed to extract key from map: failed to get required key n: failed to base64 decode key n: illegal base64 data at input byte 64

headers_gen doesn't properly handle key_ops

Change the test to the below and it will fail.

[
         {"kty":"EC",
		  "crv":"P-256",
		  "key_ops": [
			"verify"
		  ],
          "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
          "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
          "d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"
				 }
       ]
  }`
Error Trace:	ecdsa_test.go:30
    			Error:      	Received unexpected error:
    			            	invalid value for key_ops key: []interface {}

I changed headers_gen.go to the below to fix it.

case KeyOpsKey:
		if v, ok := value.([]interface{}); ok {
			for _, s := range v {
				h.keyops = append(h.keyops, KeyOperation(s.(string)))
			}
			return nil
		}

Cleanup Date Processing

In jwt_test.go we use NumericDate as string in all tests.

Source: {" + jwt.IssuedAtKey + ": + aLongLongTimeAgoString + },

There are no tests for NumericDate as a number, actually there is no coverage for:

func (n *NumericDate) UnmarshalJSON(b []byte) error {}

The RFC does not seem to allow NumbericDate as string, only JSON numeric value.

NumericDate
A JSON numeric value representing the number of seconds from
1970-01-01T00:00:00Z UTC until the specified UTC date/time,
ignoring leap seconds. This is equivalent to the IEEE Std 1003.1,
2013 Edition [POSIX.1] definition "Seconds Since the Epoch", in
which each day is accounted for by exactly 86400 seconds, other
than that non-integer values can be represented. See RFC 3339
[RFC3339] for details regarding date/times in general and UTC in
particular.

EssentialClaims Requires JSON array for aud claim

Due to the way that EssentialClaims are parsed from JSON, the aud claim must always be an array in the source data.

For example, this sample app will fail the audience check:

package main 

import "github.com/lestrrat/go-jwx/jwt"
import "fmt"
import "encoding/json"

func main() {
    var claimSet jwt.ClaimSet
    err := json.Unmarshal([]byte(`{
      "ver": 1,
      "jti": "AT.yhqxyRwZa5koTnwWQ8G1QV4s2cGYqApe6s22lLjzqJg",
      "iss": "https://my.issuer.domain",
      "aud": "http://my.audience",
      "iat": 1504131487,
      "exp": 1514678399,
      "cid": "1234",
      "uid": "5678",
      "scp": ["users"],
      "sub": "[email protected]"
    }`), &claimSet)

  if err != nil {
    panic(err)
  }
  // will print "aud not satisfied" assuming the `exp` time hasn't passed
  fmt.Println(claimSet.Verify(jwt.WithAudience("http://my.audience")))
}

On this the JWT RFC says, basically, aud can be an array or a string: https://tools.ietf.org/html/rfc7519#section-4.1.3

Planning to file a pull request for a fix later... submitting this partly as a reminder.

SemVer of the library?

Would be greatly appreciate if the library was released with a proper sematic release versioning.

Unable to unmarshal jwk.Set

Hi, I'm trying to cache my JWKS to avoid an external web request every time I need it, however I am running into this error:

json: cannot unmarshal object into Go struct field Set.keys of type jwk.Key

It seems to be marshaling fine because I see the object stored in my cache, so I'm not sure what the issue is unmarshaling.

Here are the lines attempting to unmarshal:

var keySet jwk.Set
err := json.Unmarshal(cacheValue, &keySet)

Thanks,
Ben

Intermittently generating an incorrect signature from jwt.SignedString

I wrote a test to reproduce:

func Test_Signature(t *testing.T) {
    for i := 0; i < 1000; i++ {
        tok := jwt.New()
        priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
        assert.FatalError(t, err)
        pub := &priv.PublicKey
        assert.FatalError(t, err)
        s, err := tok.Sign(jwa.ES256, priv)
        assert.FatalError(t, err)
        _, err = jws.Verify([]byte(s), jwa.ES256, pub)
        assert.FatalError(t, err)
    }
}

This breaks consistently within 1000 iterations whether I use the verify method from jws or one from another package.

Bug in rsautil.go and low coverage

Low coverage in rsautil.go hid a bug in (un)marshal and creation of keys. The field "Precomputed.CRTValues" was not populated or copied throughout the APIs. A roundtrip test detected the issue. I will submit a PR shortly.

VerifyWithJWK "use" and "kty" issues

Just wondering if this logic: https://github.com/lestrrat/go-jwx/blob/76ff5fd46c773bcf15f019da4b5247780efec796/jws/jws.go#L184 is also supposed to deal with a JWK that has the use "sig"?

Also, the specification states that the "alg" parameter of a JWK can be optional. Azure AD's OpenID implementation does not send this. Their own library seems to use RSA256 by default if the kty is RSA. Do you believe it is safe to always fallback to one of the "recommended" algorithms defined by the specification when the "alg" parameter is blank?

Improve ECDSA coverage

Some functions in ecdsa.go have no coverage such as (un)Marshal public and private keys.

Clean and harmonize jws/main.go/headers.go

Remove package name from const names
Use "name" instead of "method". We do not use "method" per se anymore after all changes, i.e., there is no direct call to any method.
Use GetAlgorithm() instead of Algorithm()
Added PrivateParams as a JSON tag. But if we want flattened we can change asap.

I am trying to get all generate code to look similar and follow a pattern.

jws.VerifyWithJKU Passes on invalid signature

I was using v0.9.0, jws.VerifyWithJKU() validation worked fine. But once I updated to the newer version v0.9.1, it no longer validates the signature, returns no error. This is a serious issue. Could I get any help regarding this?

Thank you.

Confused about API

Hello,

I'm trying to verify a HS256-signed jwt token.

In the jwt module, there's a jwt.Parse() function with the comment that it returns an error if the signature fails, but it doesn't take a key or algorithm as parameter when calling jws.Parse(); so it doesn't actually check the signature.

If I call jws.Verify(), I get back a []byte payload, but there is no clear way to feed this to anything in the jwt module. Feeding it to jwt.ParseBytes panics with an invalid address or nil pointer at github.com/lestrrat/go-jwx/jws/jws.go:422:

	plain.payload, err = base64.RawURLEncoding.DecodeString(wrapper.Payload)

... I assume wrapper.Payload is a nil pointer.

If I call jws.Verify(), throw away the []byte but keep the verification status and then call jwt.ParseString() on the original token to get a jwt struct, I think I have both a valid signature and token with claims, but I guess with double parsing.

I'm a bit confused about the way to go here and would appreciate some insight. Thanks!

Helper function to avoid casts to jws.Verify()

Instead of having to cast all method calls to jws.Verify(), it is better to have a helper function of that the entire call path is independent of key type.

OLD:

	ecdsaPrivateKey := key.(*ecdsa.PrivateKey)

	// Verify with API library
	verifiedPayload, err := jws.Verify(jwsCompact, alg, &ecdsaPrivateKey.PublicKey)		

NEW:

             publicKey, err := jwk.GetPublicKey(key)
	if err != nil {
		t.Fatal("Failed to get public from private key")
	}
	verifiedPayload, err := jws.Verify(jwsCompact, alg, publicKey)

Protected header

I need to communicate with a service, which requires the kid to be set to a specific value and can't figure out how to do it. The sample code supplied by the service suggests the following in Python:

 protected_header = {
        "alg": ""RSA-OAEP",
        "enc": "A256CBC-HS512",
        "typ": "JWE",
        "kid": "MY_KEY_ID",
    }
    jwetoken = jwe.JWE(payload_bytes, recipient=public_key, protected=protected_header)
    return jwetoken.serialize(compact=True)

Sorry about being new to jwx! I am grateful for any suggestions you could give.

Low coverage and unused functions in jws/headers.go

Function JWKSetURL() is not used or covered

func (h *StandardHeaders) JWKSetURL() string {

None of these functions are used or covered

jwx/jws/headers.go

Lines 103 to 122 in 9871a56

func (h *StandardHeaders) X509CertThumbprint() string {
if v := h.x509CertThumbprint; v != nil {
return *v
}
return ""
}
func (h *StandardHeaders) X509CertThumbprintS256() string {
if v := h.x509CertThumbprintS256; v != nil {
return *v
}
return ""
}
func (h *StandardHeaders) X509URL() string {
if v := h.x509URL; v != nil {
return *v
}
return ""
}

None of these switch cases are covered

jwx/jws/headers.go

Lines 174 to 196 in 9871a56

case X509CertThumbprintKey:
v := h.x509CertThumbprint
if v == nil {
return nil, false
}
return *v, true
case X509CertThumbprintS256Key:
v := h.x509CertThumbprintS256
if v == nil {
return nil, false
}
return *v, true
case X509URLKey:
v := h.x509URL
if v == nil {
return nil, false
}
return *v, true
default:
v, ok := h.privateParams[name]
return v, ok
}
}

None of these switch cases are covered

jwx/jws/headers.go

Lines 249 to 272 in 9871a56

case X509CertThumbprintKey:
if v, ok := value.(string); ok {
h.x509CertThumbprint = &v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value)
case X509CertThumbprintS256Key:
if v, ok := value.(string); ok {
h.x509CertThumbprintS256 = &v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value)
case X509URLKey:
if v, ok := value.(string); ok {
h.x509URL = &v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, X509URLKey, value)
default:
if h.privateParams == nil {
h.privateParams = map[string]interface{}{}
}
h.privateParams[name] = value
}

None of these lines are covered

jwx/jws/headers.go

Lines 379 to 395 in 9871a56

if v, ok := m[X509CertThumbprintKey]; ok {
if err := h.Set(X509CertThumbprintKey, v); err != nil {
return errors.Wrapf(err, `failed to set value for key %s`, X509CertThumbprintKey)
}
delete(m, X509CertThumbprintKey)
}
if v, ok := m[X509CertThumbprintS256Key]; ok {
if err := h.Set(X509CertThumbprintS256Key, v); err != nil {
return errors.Wrapf(err, `failed to set value for key %s`, X509CertThumbprintS256Key)
}
delete(m, X509CertThumbprintS256Key)
}
if v, ok := m[X509URLKey]; ok {
if err := h.Set(X509URLKey, v); err != nil {
return errors.Wrapf(err, `failed to set value for key %s`, X509URLKey)
}
delete(m, X509URLKey)

jwx/jws/headers.go

Lines 343 to 359 in 9871a56

if v, ok := m[CriticalKey]; ok {
if err := h.Set(CriticalKey, v); err != nil {
return errors.Wrapf(err, `failed to set value for key %s`, CriticalKey)
}
delete(m, CriticalKey)
}
if v, ok := m[JWKKey]; ok {
if err := h.Set(JWKKey, v); err != nil {
return errors.Wrapf(err, `failed to set value for key %s`, JWKKey)
}
delete(m, JWKKey)
}
if v, ok := m[JWKSetURLKey]; ok {
if err := h.Set(JWKSetURLKey, v); err != nil {
return errors.Wrapf(err, `failed to set value for key %s`, JWKSetURLKey)
}
delete(m, JWKSetURLKey)

Low coverage in jwk/jwk.go

This switch case is not exercised

jwx/jwk/jwk.go

Lines 78 to 89 in 9871a56

case "file":
f, err := os.Open(u.Path)
if err != nil {
return nil, errors.Wrap(err, `failed to open jwk file`)
}
defer f.Close()
buf, err := ioutil.ReadAll(f)
if err != nil {
return nil, errors.Wrap(err, `failed read content from jwk file`)
}
src = buf

Error paths are largely not covered, specially:

jwx/jwk/jwk.go

Lines 248 to 266 in 9871a56

func getKey(m map[string]interface{}, key string, required bool) ([]byte, error) {
v, ok := m[key]
if !ok {
if !required {
return nil, errors.Errorf(`missing parameter '%s'`, key)
}
return nil, errors.Errorf(`missing required parameter '%s'`, key)
}
vs, ok := v.(string)
if !ok {
return nil, errors.Errorf(`invalid type for parameter '%s': %T`, key, v)
}
buf, err := base64.DecodeString(vs)
if err != nil {
return nil, errors.Wrapf(err, `failed to base64 decode key %s`, key)
}
return buf, nil

Incorrect Parsing of Complete JWS JSON

Similar issues to #70

https://tools.ietf.org/html/rfc7515#appendix-A.6

I was trying to get coverage for protected header and key lookup in LookupSignature(kid string) and noticed a few things.

  • While running t.Run("CompleteJSON", func(t *testing.T)..) I noticed that after unmarshal:

  • Protected header structure was empty

  • Header structure contained both protected and unprotected headers.

  • Entries were not delete from map

  • Not checking length of map before assignment.

Optional HTTP client argument for jwk.Fetch and jwk.FetchHTTP

The HTTP client used for HTTP operations is fixed to the default HTTP client of Go.
This means we can't configure timeouts, additional CA certificates etc.
An additional method at least for jwk.Fetch and jwk.FetchHTTP with HTTP Client as argument would be helpful.

No method to return all claims.

For refreshing token, we use all the claims from previous tokens and modify the iat and exp claims. If we have several claims, it's painful to extract all claims from the token and sign a new one.

Modify JWK to use JSON Tag (Un)Marshal

Use JSON struct tags for most (un)marshal. Use custom (un)marshal only when strictly necessary, therefore removing most extract/populate map functions.

Low coverage and unused functions in jwk/headers_gen.go

These functions are not used or covered

jwx/jwk/headers_gen.go

Lines 98 to 117 in 9871a56

func (h *StandardHeaders) X509CertThumbprint() string {
if v := h.x509CertThumbprint; v != nil {
return *v
}
return ""
}
func (h *StandardHeaders) X509CertThumbprintS256() string {
if v := h.x509CertThumbprintS256; v != nil {
return *v
}
return ""
}
func (h *StandardHeaders) X509URL() string {
if v := h.x509URL; v != nil {
return *v
}
return ""
}

These functions are not used:

func (h StandardHeaders) MarshalJSON() ([]byte, error) {

func (h *StandardHeaders) UnmarshalJSON(buf []byte) error {

These lines are not covered

jwx/jwk/headers_gen.go

Lines 345 to 362 in 9871a56

if v, ok := m[X509CertThumbprintKey]; ok {
if err := h.Set(X509CertThumbprintKey, v); err != nil {
return errors.Wrapf(err, `failed to set value for key %s`, X509CertThumbprintKey)
}
delete(m, X509CertThumbprintKey)
}
if v, ok := m[X509CertThumbprintS256Key]; ok {
if err := h.Set(X509CertThumbprintS256Key, v); err != nil {
return errors.Wrapf(err, `failed to set value for key %s`, X509CertThumbprintS256Key)
}
delete(m, X509CertThumbprintS256Key)
}
if v, ok := m[X509URLKey]; ok {
if err := h.Set(X509URLKey, v); err != nil {
return errors.Wrapf(err, `failed to set value for key %s`, X509URLKey)
}
delete(m, X509URLKey)
}

Consider not rounding time or set `skew` to 1 in token validation

Hello,

When doing JWT claim validation, iat and Now() are rounded to the nearest second before the comparison with now.After() (see jwt/verify.go:174). This means that by default, tokens are always invalid in the same second they are created. It's probably very common for a server to respond with a token to a client which then immediately tries to validate it, which will very likely result in a validation error because of iat.

Setting skew works, but maybe the default skew could be 1 second or the comparison could be done without rounding the times, so by default, tokens are not invalid in the same second they are created.

Go has a time.After() and time.Equal, but no time.AfterOrEqual. Another option could also be to reverse the condition and use time.Before().

Issue with verifying JWT's with ES256

Verifying with RS256 succeeds while ES256 fails, returning an "empty buffer" error.

You can copy and paste the below into jwt_test.go. I haven't gotten to the bottom of the issue yet.

func TestVerifyWithRS(t *testing.T) {
	alg := jwa.RS256
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return
	}

	t1 := jwt.New()
	t1.Set("example", "RS256")
	signed, err := t1.Sign(alg, key)

	_, err = jwt.ParseVerify(bytes.NewReader(signed), alg, &key.PublicKey)
	if !assert.NoError(t, err, `jwt.ParseVerify should succeed`) {
		return
	}
}

func TestVerifyWithES(t *testing.T) {
	alg := jwa.ES256
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return
	}

	t1 := jwt.New()
	t1.Set("example", "ES256")
	signed, err := t1.Sign(alg, key)

	_, err = jwt.ParseVerify(bytes.NewReader(signed), alg, &key.PublicKey)
	if !assert.NoError(t, err, `jwt.ParseVerify should succeed`) {
		return
	}
}

Can we consider removing pdebug dependency?

I use an IDE and can step and debug the entire code without pdebug. I removed the entire pdebug dependency in a branch to experiment and coverage overall went up 12% and the number of lines of code went down considerably.

Loading JSON Web Keys from a String or []byte, can't figure it out.

This isn't a bug, but damn if I cannot figure out how to load keys from a Array or String, not a URL

set, err := jwk.Fetch(keyUrl) //This works
I'm trying to figure out how to load them directly, or create them from the keys I already have.

I kicked around doing something like this:

set,err:=jwk.New(array_like_json_string_below) //This is what I want to do
I'm trying to persist them, and not load them everytime.

The keys are the JSON format.

{"keys":
[{"alg":"RS256","e":"AQAB","n":"jqm5oX5Vth4JW1gZQHywIki2beYCgBSL-
EYlefDUlI6SShtEKfi-vWYbFh2pNNUAE4NHuYpYP-
FG1uRSKs6WK2k6KMB2Hyx3hBkWyu7Aqo_pb1WItkPSZS-AWOMp4N-

Thanks for help, great library

VerifyWithJWK requires optional alg attribute

The alg attribute is optional according to the RFC and some providers do not permit configuration of keys to include it. If the alg attribute is missing, there should probably be a default fallback depending on the type of key. Another option is to trust the header of the JWT, although because this is before verification that is a security concern.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.