Repository: lestrrat-go/jwx
Pull request #671 (2022-03-29 22:29:18)

[v2] Allow RSA keys without p/q

codecov-commenter: # [Codecov](https://codecov.io/gh/lestrrat-go/jwx/pull/671?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) Report
> :exclamation: No coverage uploaded for pull request base (`develop/v2@31cb0ba`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#section-missing-base-commit).
> The diff coverage is `n/a`.

```diff
@@              Coverage Diff              @@
##             develop/v2     #671   +/-   ##
=============================================
  Coverage              ?   71.82%           
=============================================
  Files                 ?       90           
  Lines                 ?     9661           
  Branches              ?        0           
=============================================
  Hits                  ?     6939           
  Misses                ?     1926           
  Partials              ?      796           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/lestrrat-go/jwx/pull/671?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/lestrrat-go/jwx/pull/671?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go). Last update [31cb0ba...e2b494b](https://codecov.io/gh/lestrrat-go/jwx/pull/671?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go).


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 68131508..2984df76 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,13 +1,9 @@
 name: CI
 on:
-  push:
-    branches:
-      - main
-      - v2
   pull_request:
     branches:
-      - main
-      - v2
+      - v*
+      - develop/*
 
 jobs:
   build:
diff --git a/jwk/rsa.go b/jwk/rsa.go
index 46ef3896..191bdf40 100644
--- a/jwk/rsa.go
+++ b/jwk/rsa.go
@@ -22,21 +22,27 @@ func (k *rsaPrivateKey) FromRaw(rawKey *rsa.PrivateKey) error {
 	}
 	k.d = d
 
-	if len(rawKey.Primes) < 2 {
-		return fmt.Errorf(`invalid number of primes in rsa.PrivateKey: need 2, got %d`, len(rawKey.Primes))
+	l := len(rawKey.Primes)
+
+	if l < 0 /* I know, I'm being paranoid */ || l > 2 {
+		return fmt.Errorf(`invalid number of primes in rsa.PrivateKey: need 1 or 2, got %d`, len(rawKey.Primes))
 	}
 
-	p, err := bigIntToBytes(rawKey.Primes[0])
-	if err != nil {
-		return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err)
+	if l > 0 {
+		p, err := bigIntToBytes(rawKey.Primes[0])
+		if err != nil {
+			return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err)
+		}
+		k.p = p
 	}
-	k.p = p
 
-	q, err := bigIntToBytes(rawKey.Primes[1])
-	if err != nil {
-		return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err)
+	if l > 1 {
+		q, err := bigIntToBytes(rawKey.Primes[1])
+		if err != nil {
+			return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err)
+		}
+		k.q = q
 	}
-	k.q = q
 
 	// dp, dq, qi are optional values
 	if v, err := bigIntToBytes(rawKey.Precomputed.Dp); err == nil {
diff --git a/jwk/rsa_gen.go b/jwk/rsa_gen.go
index bbf93e44..60837290 100644
--- a/jwk/rsa_gen.go
+++ b/jwk/rsa_gen.go
@@ -1179,12 +1179,6 @@ LOOP:
 	if h.n == nil {
 		return fmt.Errorf(`required field n is missing`)
 	}
-	if h.p == nil {
-		return fmt.Errorf(`required field p is missing`)
-	}
-	if h.q == nil {
-		return fmt.Errorf(`required field q is missing`)
-	}
 	return nil
 }
 
diff --git a/tools/cmd/genjwk/objects.yml b/tools/cmd/genjwk/objects.yml
index 3bb0265f..872b46d1 100644
--- a/tools/cmd/genjwk/objects.yml
+++ b/tools/cmd/genjwk/objects.yml
@@ -66,10 +66,8 @@ key_types:
             required: true
           - name: p
             type: "[]byte"
-            required: true
           - name: q
             type: "[]byte"
-            required: true
           - name: dp
             getter: DP
             exported_name: DP


Test patch:
diff --git a/jwk/jwk_test.go b/jwk/jwk_test.go
index 1e1333d4..69414910 100644
--- a/jwk/jwk_test.go
+++ b/jwk/jwk_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/lestrrat-go/jwx/v2/internal/jose"
 	"github.com/lestrrat-go/jwx/v2/internal/json"
 	"github.com/lestrrat-go/jwx/v2/internal/jwxtest"
+	"github.com/lestrrat-go/jwx/v2/jws"
 
 	"github.com/lestrrat-go/jwx/v2/internal/base64"
 	"github.com/lestrrat-go/jwx/v2/jwa"
@@ -2006,3 +2007,57 @@ func TestCurveForAlgorithm(t *testing.T) {
 	// Not much to test here, but this silences the linters
 	_, _ = jwk.CurveForAlgorithm(jwa.P521)
 }
+
+func TestGH664(t *testing.T) {
+	privkey, err := jwxtest.GenerateRsaKey()
+	if !assert.NoError(t, err, `jwxtext.GenerateRsaKey() should succeed`) {
+		return
+	}
+
+	// first, test a stupid case where Primes > 2
+	privkey.Primes = append(privkey.Primes, &big.Int{})
+	_, err = jwk.FromRaw(privkey)
+	if !assert.Error(t, err, `jwk.FromRaw should fail`) {
+		return
+	}
+
+	privkey.Primes = privkey.Primes[:2]
+
+	// nuke p and q, dp, dq, qi
+	for i := 0; i < 3; i++ {
+		i := i
+		t.Run(fmt.Sprintf("Check what happens when primes are reduced to %d", i), func(t *testing.T) {
+			privkey.Primes = privkey.Primes[:i]
+			privkey.Precomputed.Dp = nil
+			privkey.Precomputed.Dq = nil
+			privkey.Precomputed.Qinv = nil
+			privkey.Precomputed.CRTValues = nil
+
+			jwkPrivkey, err := jwk.FromRaw(privkey)
+			if !assert.NoError(t, err, `jwk.FromRaw should succeed`) {
+				return
+			}
+
+			buf, _ := json.MarshalIndent(jwkPrivkey, "", "  ")
+			parsed, err := jwk.ParseKey(buf)
+			if !assert.NoError(t, err, `jwk.ParseKey should succeed`) {
+				return
+			}
+
+			payload := []byte(`hello , world!`)
+			signed, err := jws.Sign(payload, jws.WithKey(jwa.RS256, parsed))
+			if !assert.NoError(t, err, `jws.Sign should succeed`) {
+				return
+			}
+
+			verified, err := jws.Verify(signed, jws.WithKey(jwa.RS256, privkey.PublicKey))
+			if !assert.NoError(t, err, `jws.Verify should succeed`) {
+				return
+			}
+
+			if !assert.Equal(t, payload, verified, `verified content should match`) {
+				return
+			}
+		})
+	}
+}