Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change how JWK validation works #20

Closed
MicahParks opened this issue Mar 4, 2024 · 10 comments · Fixed by #24
Closed

Change how JWK validation works #20

MicahParks opened this issue Mar 4, 2024 · 10 comments · Fixed by #24
Labels
enhancement New feature or request

Comments

@MicahParks
Copy link
Owner

Currently, validation in this project uses the result of marshaling the cryptographic key and reflect.DeepEqual() to ensure the jwkset.JWK is valid.

jwkset/jwk.go

Lines 311 to 314 in a0de971

ok := reflect.DeepEqual(j.marshal, marshalled)
if !ok {
return fmt.Errorf("%w: marshaled JWK does not match original JWK", ErrJWKValidation)
}

Rework validation to compare JWK parameters in a less strict way. This will allow the project to be compatible with other JWK projects that are not RFC compliant.

One case to note in particular is ECDSA key parameters with leading padding. The integers should be compared, not the base64 encoded strings. See #18 for additional context.

@chenzeshenga
Copy link

could we expose these options?

@kperreau
Copy link

Hi, not sure if it's related but i got the same error when i'm trying to fetch JWK from firebase app check.
URL: https://firebaseappcheck.googleapis.com/v1/jwks
Error:

ERROR Failed to refresh HTTP JWK Set from remote HTTP resource. error="failed to create JWK from JWK Marshal: failed to validate JSON Web Key: failed to validate JWK: marshaled JWK does not match original JWK" url=https://firebaseappcheck.googleapis.com/v1/jwks

@MicahParks
Copy link
Owner Author

MicahParks commented Mar 11, 2024

@chenzeshenga, I'm not sure exactly what the request is, but yes, new validation options will be exposed via jwkset.ValidateOptions when validation has been reworked to be less strict by default.

@kperreau thank you for posting the link to the JWK Set. It looks like this is a bug with this project.

RFC 7518 Section 6.3.1.1 notes that:

Note that implementers have found that some cryptographic libraries prefix an extra zero-valued octet to the modulus representations they return, for instance, returning 257 octets for a 2048-bit key, rather than 256. Implementations using such libraries will need to take care to omit the extra octet from the base64url-encoded representation.

When I implemented the "n" RSA JWK parameter, I treated it as a normal Base64urlUInt-encoded value, ignoring this note about needing to account for extra padding. This should be another issue marked as a bug and will be fixed in this validation update, which I plan on working on today. See #23.

Edit:
I think I read that wrong just now, seems like Firebase should be the one to respect the Base64urlUInt-encoded no padding format.

Small Go program showing one of the modulus, "n", from the Firebase JWK Set.

package main

import (
	"encoding/base64"
	"math/big"
)

func main() {
	noPad := "6kXl3CgKmZbZOHmQFooNmbKA6XJBImbcmrAa89NNWIcKzhmK2UsX3Nr34M_E_fOg1qvIRiUV8uFg4VVLuZSMwa0s1OQtp8FIgigXbyzL_PUau7EnZoVyFrMGKbEjUHQxdMp0jDhXbrySWs4QI7kUhwdqvsRX1bRepOgWifehW8dbDeqQ9Xij4DsZ2uHdiHu9FQT9PlcItgP2M7jWcCpiFZhEY34rG7nXHpUxrn3lNdOunOBgYNtrUwtDMH2pwdEi9GyS3saHwzTOBzKjpzqO-S1oTOHSPaR0vYkIAUeExdRl51FQ6l6qQR6GnEMNHQN6LPtQ-Ux7qXw62z3tabfntuBqMNzGepoYYQXkYiSvvEfDXkOtN85Y9jfl_SweM1M6f0BTZs9ZPLqgdk-GgWMd1I2ndQ4i6ed_V5dGVNrthnk8uZsUXKM01KdB1w4eArHWUkTEmpcIAnakGSnR1frm6KoIvPhZkFyYIcZOiLia9DnCkWqBQn_o3wdek_lktn6F"
	pad := "AOpF5dwoCpmW2Th5kBaKDZmygOlyQSJm3JqwGvPTTViHCs4ZitlLF9za9-DPxP3zoNaryEYlFfLhYOFVS7mUjMGtLNTkLafBSIIoF28sy_z1GruxJ2aFchazBimxI1B0MXTKdIw4V268klrOECO5FIcHar7EV9W0XqToFon3oVvHWw3qkPV4o-A7Gdrh3Yh7vRUE_T5XCLYD9jO41nAqYhWYRGN-Kxu51x6VMa595TXTrpzgYGDba1MLQzB9qcHRIvRskt7Gh8M0zgcyo6c6jvktaEzh0j2kdL2JCAFHhMXUZedRUOpeqkEehpxDDR0Deiz7UPlMe6l8Ots97Wm357bgajDcxnqaGGEF5GIkr7xHw15DrTfOWPY35f0sHjNTOn9AU2bPWTy6oHZPhoFjHdSNp3UOIunnf1eXRlTa7YZ5PLmbFFyjNNSnQdcOHgKx1lJExJqXCAJ2pBkp0dX65uiqCLz4WZBcmCHGToi4mvQ5wpFqgUJ_6N8HXpP5ZLZ-hQ"
	b, err := base64.RawURLEncoding.DecodeString(noPad)
	if err != nil {
		panic(err)
	}
	noPadI := new(big.Int).SetBytes(b)
	b, err = base64.RawURLEncoding.DecodeString(pad)
	if err != nil {
		panic(err)
	}
	padI := new(big.Int).SetBytes(b)
	if noPadI.Cmp(padI) != 0 {
		println("noPadI != padI")
	}
	println("noPadI == padI")
}

@MicahParks
Copy link
Owner Author

@kperreau, sorry for the back and forth, but I think I read that note wrong just now. I think that implementation note is saying that implementers need to respect the Base64urlUInt-encoded format. So not a bug in this project, but a bug I will report to Firebase. See this comment: #23 (comment)

Regardless, this project will be updated to account for any padding after this validation rework is implemented.

@kperreau
Copy link

All right, thank you for the explanation and your work! 🙏

@MicahParks
Copy link
Owner Author

@kperreau and @chenzeshenga I'm doing a release for the new validation soon. It will be less strict and handle invalid padding by default.

Any questions or comments are welcome. I think community feedback is important.

renovate bot referenced this issue in nobl9/nobl9-go Mar 12, 2024
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [github.com/MicahParks/jwkset](https://togithub.com/MicahParks/jwkset)
| `v0.5.14` -> `v0.5.15` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fMicahParks%2fjwkset/v0.5.15?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/github.com%2fMicahParks%2fjwkset/v0.5.15?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/github.com%2fMicahParks%2fjwkset/v0.5.14/v0.5.15?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fMicahParks%2fjwkset/v0.5.14/v0.5.15?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[github.com/MicahParks/keyfunc/v3](https://togithub.com/MicahParks/keyfunc)
| `v3.2.8` -> `v3.2.9` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fMicahParks%2fkeyfunc%2fv3/v3.2.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/github.com%2fMicahParks%2fkeyfunc%2fv3/v3.2.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/github.com%2fMicahParks%2fkeyfunc%2fv3/v3.2.8/v3.2.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fMicahParks%2fkeyfunc%2fv3/v3.2.8/v3.2.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>MicahParks/jwkset (github.com/MicahParks/jwkset)</summary>

###
[`v0.5.15`](https://togithub.com/MicahParks/jwkset/releases/tag/v0.5.15):
Less strict validation

[Compare
Source](https://togithub.com/MicahParks/jwkset/compare/v0.5.14...v0.5.15)

The purpose of this release is to use less strict validation for JWK.
This will allow users to work with non-RFC compliant JWK Sets for small
padding mistakes.

Two padding related reasons for this are:

1.  Mandatory leading padding for ECDSA JWK parameters.
2.  A common mistake adding leading padding to RSA JWK parameter "n".

For padding specifically, this project is only comparing integers after
they are parsed from Base64 raw URL encoding by default. To turn on
strict validation, there will be a new field on jwkset.ValidateOptions
named StrictPadding.

An example for `1` would be a bug in this project were mandatory leading
padding was absent:
[https://github.com/MicahParks/jwkset/issues/18](https://togithub.com/MicahParks/jwkset/issues/18)

An example for `2` would be a Firebase service that was reported to be
incompatible with this project:
[https://github.com/MicahParks/jwkset/issues/23](https://togithub.com/MicahParks/jwkset/issues/23)

Relevant issues:

-
[https://github.com/MicahParks/jwkset/issues/23](https://togithub.com/MicahParks/jwkset/issues/23)
-
[https://github.com/MicahParks/jwkset/issues/20](https://togithub.com/MicahParks/jwkset/issues/20)
-
[https://github.com/MicahParks/jwkset/issues/18](https://togithub.com/MicahParks/jwkset/issues/18)

Relevant pull requests:

-
[https://github.com/MicahParks/jwkset/pull/24](https://togithub.com/MicahParks/jwkset/pull/24)

</details>

<details>
<summary>MicahParks/keyfunc (github.com/MicahParks/keyfunc/v3)</summary>

###
[`v3.2.9`](https://togithub.com/MicahParks/keyfunc/compare/v3.2.8...v3.2.9)

[Compare
Source](https://togithub.com/MicahParks/keyfunc/compare/v3.2.8...v3.2.9)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 10pm every weekday,before 5am
every weekday,every weekend" (UTC), Automerge - At any time (no schedule
defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config help](https://togithub.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/nobl9/nobl9-go).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4yMzAuMCIsInVwZGF0ZWRJblZlciI6IjM3LjIzMC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
@chenzeshenga
Copy link

@MicahParks thanks for the quick update, and it would be really helpful.

For exposing the option, the original background is the jwks inside our company doesn't have the kid key, so I want to skip this validation, but not other members.

And I checked the document, the kid isn't a required member.

https://www.rfc-editor.org/rfc/rfc7517#section-4.5
The "kid" (key ID) parameter is used to match a specific key. This is used, for instance, to choose among a set of keys within a JWK Set during key rollover. The structure of the "kid" value is unspecified. When "kid" values are used within a JWK Set, different keys within the JWK Set SHOULD use distinct "kid" values. (One example in which different keys might use the same "kid" value is if they have different "kty" (key type) values but are considered to be equivalent alternatives by the application using them.) The "kid" value is a case-sensitive string. Use of this member is OPTIONAL. When used with JWS or JWE, the "kid" value is used to match a JWS or JWE "kid" Header Parameter value.

So if posible, could we expose some option like skipKid and recheck other members as well?

@MicahParks
Copy link
Owner Author

@chenzeshenga I don't fully understand why an option to skip validation of the JWK kid parameter is desired. Does the undesired behavior happen when creating a JWK Set or consuming one?

I would like to add an option to skip validating the kid if I can understand the use case. Right now, I believe the project should validate a JWK kid parameter if it's populated or empty. Perhaps another part of the project relies on the kid parameter being present?

An example would be best to convey your meaning. Would you please provide a runnable example of this jwkset project failing validation or other undesired behavior? You can use the below as a template:

package main

import (
	"context"
	"crypto/ed25519"
	"crypto/rand"
	"log/slog"

	"github.com/MicahParks/jwkset"
)

const (
	logErr = "error"
)

func main() {
	l := slog.Default()
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	pub, _, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		l.ErrorContext(ctx, "Failed to generate Ed25519 key.",
			logErr, err,
		)
		return
	}

	options := jwkset.JWKOptions{
		Metadata: jwkset.JWKMetadataOptions{
			KID: "",
		},
	}
	jwk, err := jwkset.NewJWKFromKey(pub, options)
	if err != nil {
		l.ErrorContext(ctx, "Failed to create JWK.",
			logErr, err,
		)
		return
	}

	_ = jwk
}

@kperreau
Copy link

Everything works fine now for me, thx!

@chenzeshenga
Copy link

@MicahParks never mind, I have found out the way to use jwkset to do the validation.
Thanks for ur kindly support and reply

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants