Ensure generated RSA key primes satisfy P < Q constraint#305
Conversation
|
IMO since the established practice is to duplicate code between v1 and v2 it is reasonable to follow suit and duplicate the additional code, rather than attempting to deduplicate only these particular changes. It would be nice to have a test for p<q, however since this will only fail at random it may not be appropriate to include it in the unit tests here... |
👍
Yeah, that's why I didn't include them. That being said, https://github.com/jam-awake/gpg-verify-bug does have a few RSA keys exhibiting this very issue. Is it possible to force |
|
Thanks for the PR! If you pass a deterministic Also, could you please cite the new RFC 9580 rather than RFC 4880? 🙏 |
Done. I should note there are other references to RFC 4880 in the repo, but I don't plan on fixing those in this PR.
I misread this. I saw I also looked through the test file and saw Let me work on that, but I don't anticipate getting to this again until sometime next week. |
|
|
||
| for _, testCase := range tests { | ||
| t.Run(testCase.name, func(t *testing.T) { | ||
| for i := 0; i < 20; i++ { |
There was a problem hiding this comment.
Here's the context behind this loop.
generateRSAKey/generateRSAKeyWithPrimes calls rand.Prime to generate a prime number using Io.Reader. rand.Prime does so by running the following loop:
- Read data from the Reader. If there's not enough to fill the buffer, return
io.ErrUnexpectedEOF. - Generate a new number from the read data
- Call
ProbablyPrimeon that number. If it passes, return the number. Otherwise, jump to step 1.
Running this test without this loop will sometimes succeed and sometimes fail with Failed to generate RSA key: unexpected EOF. Presumably, a failing ProbablyPrime check will cause more data to be read from the reader than what it contains. And once we reach the end, the reader's EOF is converted to that error and rand.Prime returns.
At first, I thought one or both of p or q are not actually prime numbers. However, when I call ProbablyPrime(1_000) on p and q, neither number reports an issue. So, perhaps the issue is with the DeterministicReader implementation? I'm not sure.
Regardless, looping was my temporary solution to this problem. Per its docs, ProbablyPrime has a (1/4)^20 chance of returning a false positive (i.e. saying a number is a prime when it's non-prime). I thought attempting to generate a key 20 times might somehow cancel out that issue.
There was a problem hiding this comment.
ProbablyPrime is itself randomized, so this makes some sense to me.
|
@twiss I believe I've addressed all your feedback aside from my comment above. |
|
@twiss Any updates on this? |
1 similar comment
|
@twiss Any updates on this? |
| // RFC 9580 section 5.5.5.1 requires p < q for RSA keys. | ||
| // The Go standard library does not guarantee this, so we swap if needed | ||
| // and recompute the precomputed values. | ||
| if len(key.Primes) == 2 && key.Primes[0].Cmp(key.Primes[1]) > 0 { |
There was a problem hiding this comment.
Cmp is not constant time and exposes a timing side-channel on the secret primes. But should be fine for key generation in this case.
There was a problem hiding this comment.
I'm reading this comment as, "This would be a problem, but not in this particular usage." Is that correct?
542fba8 to
4b8e1ad
Compare
- Adds a wrapper around `rsa.GenerateKey` that swaps the P and Q primes if P > Q and recomputes the precomputed values - Applies this change to `generateRSAKeyWithPrimes` as well but only if the key has 2 prime numbers because I don't know what's safe to do in this context. - Adds tests verifying prime swap occurs using 2048-bit key from https://github.com/jam-awake/gpg-verify-bug
4b8e1ad to
bed1dc3
Compare
|
I apologize for force pushing, but since my previous commits were unsigned, this PR would not have been mergeable. In fixing that problem, I also collapsed the commits into a single one. The PR is unchanged, but now it's mergeable. |
|
@lubux Any updates on this PR? |
|
@lubux The PR builds and is approved, but I can't merge it. I'm assuming one of the maintainers here needs to do so? |
twiss
left a comment
There was a problem hiding this comment.
Apologies for the long silence on my part, I was off last month. One more nitpick below.
|
|
||
| for _, testCase := range tests { | ||
| t.Run(testCase.name, func(t *testing.T) { | ||
| for i := 0; i < 20; i++ { |
There was a problem hiding this comment.
ProbablyPrime is itself randomized, so this makes some sense to me.
|
@twiss I've addressed your feedback. Anything else? |
|
CI builds. I'm assuming one of the repo's maintainers here needs to merge the PR. Is that correct? |
rsa.GenerateKeythat swaps the P and Q primes if P > Q and recomputes the precomputed valuesgenerateRSAKeyWithPrimesas well but only if the key has 2 prime numbers because I don't know what's safe to do in this context.Questions I have:
Fixes #184
Also, per your contributing guidelines:
I asked an AI to investigate the problem and attempt to fix it. I then reviewed what it produced and edited it to something that I feel like I would have written. I'm not familiar with Go or cryptography, but this issue seems small enough that this method seemed appropriate.