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

feat: Support sha256 and sha512 oaep algorithms #240

Merged
merged 7 commits into from
Jan 24, 2020

Conversation

richardTowers
Copy link
Contributor

Issue #, if available: #198

Description of changes:

AWS CloudFront Field Level Encryption uses the RSA_OAEP_SHA256_MGF1 suite of algorithms. Because this library's raw RSA code only supports OAEP_SHA1, it can't be used to decrypt CloudFront fields.

Support for oaepHash was added in node's crypto library in node 12.9 (nodejs/node#28335), so it's possible to expose this functionality through this library now.

I've tested this manually against AWS CloudFront with Field Level Encryption set, and it seems to work nicely.

I also spotted some async test warnings, so I've done my best to clean those up. See the individual commits for more detail.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Check any applicable:

  • Were any files moved? No.

We want to check that the async onDecrypt function returns a promise
that rejects.

We were trying to use chai-as-promised's rejectedWith matcher for this,
but within an `async` function.

It looks like mocha isn't clever enough to wait for the promise to
reject and run the assertion unless we return the promise from the
function. It prints a warning about the uncaught promise rejection
instead.

Once we fix the test to return the promise it fails, because the promise
is not actually being rejected, so I'm excluding it while I work on a
fix.
This test was excluded because the precondition wasn't hit when calling
`keyring.onDecrypt` (I think due to another precondition somewhere
else).

It might be a bit sneaky to call the "private" method (`_unwrapKey`) in
the tests, but it hits the precondition we're trying to test more
directly.
This is particularly useful because CloudFront's Field Level Encryption
uses RSA_OAEP_SHA256_MGF1, which this library doesn't support yet.

Support for oaepHash was added in node 12.9 (nodejs/node#28335), so this
won't work for older node versions. It's still a backwards compatible
change because by default `oaepHash` will be undefined, as before.

I've updated the tests to cover use of the new parameter, but they're
not very strict because they both encrypt and decrypt using the same
parameter.

This means if node silently ignores the oaepHash parameter (as it will
in versions < 12.9) the tests will still pass, which isn't great.

On the other hand, I think this project may still be being tested on an
older version of node, so perhaps the fact the tests won't break is an
unexpected blessing.

I've also tested this manually against AWS CloudFront's Field Level
Encryption and it seems to work.

Resolves aws#198
@richardTowers richardTowers changed the title OAEP SHA256 support feat: OAEP SHA256 support Jan 16, 2020
@richardTowers richardTowers changed the title feat: OAEP SHA256 support feat: Support sha256 and sha512 oaep algorithms Jan 16, 2020
@seebees
Copy link
Contributor

seebees commented Jan 16, 2020

This is most excellent. Thank you.
There are integration tests: https://github.com/aws/aws-encryption-sdk-javascript/blob/master/modules/integration-node/src/decrypt_materials_manager_node.ts#L86

This will also need to be updated.
I feel pretty confident that the OAEP hash is the same as the MGF1 hash,
but these integration tests will verify.
If you want to take a crack at that as well you are welcome,
but I can also push changes to this branch later this week.

Judging from the types in the test, this is supported by other
libraries, and it looks like it also works in node.
I haven't been able to run these tests myself to confirm they work
because there's a fair bit of setup I don't know how to do.

I'd expect the tests to fail on versions of node < 12.9, which may be a
problem for CI.
@richardTowers
Copy link
Contributor Author

👍 thanks!

I had a go at adding support in the integration tests. I haven't been able to run them myself because I don't fully understand the setup (they seem to want to connect to KMS, which I'm not going to let them do). Hopefully CI should run them for me, but if it's using a node version older than 12.9 they'll probably fail, at which point I might need some help - we'll see how we go :)

@richardTowers
Copy link
Contributor Author

I guess the failing build is due to < 12.9 node version in the integration tests, but I can't see the output. Over to you @seebees.

@seebees
Copy link
Contributor

seebees commented Jan 17, 2020

You are right. The issues is v10 vs v12. We are going to need 3 things.

  1. A precondition for: "The AWS ESDK only supports specific hash values for OAEP padding." for the RawRsaKeyringNode constructor.

OpenSSL will take, and Node.js will pass, any hash as a valid hash. But the ESDK only supports SHA-1 and SHA-2 :) . Also it will pass through the hash even if you select RSA_PKCS1_PADDING.

  1. A test to cover the precondition. Everything that has such a comment requires a test.

  2. The rsaPadding function in the integration module will need to sniff Node.js to see if it supports oaepHash or not.

I suppose the best way is just to try it in the initialization. Ugly but practical.
The integration tests should be able to run on different versions of Node.js and reasonably report what vectors are supported :)

To run the integration tests, all that is needed is AWS credentials.
The KMS keys we use for these tests are public and must never be use for anything other than our testing.

npm run node-integration will run them if you want to try it out locally.
Otherwise I'll button this up and push some commits late tomorrow.

Thanks again!

@richardTowers
Copy link
Contributor Author

Thanks - sounds good.

On the precondition, I can't think of a nice way of feature detecting support for the oaepHash parameter. I think the options are:

  • Decrypt a known payload with a known key in the constructor using sha256 OAEP - if it doesn't work then we only support sha1 - this feels like a lot of work to be doing in the constructor though
  • publicEncrypt.toString().includes('oaepHash') - obviously super gross, but should work
  • Don't do feature detection, and just do version detection like:
function supportsOaepHash() {
  const [major, minor] = process.version.replace('v', '').split('.').map(x => parseInt(x, 10))
  return major > 12 || major === 12 && minor >= 9
}

I guess that by "I suppose the best way is just to try it in the initialization" you mean you'd prefer the first option?

@seebees
Copy link
Contributor

seebees commented Jan 21, 2020

I did some work on this,
I'll push a commit today and you can take a look :)

I do think that actually testing it is, unfortunately, the way to go here.
Because if you accept the oaepHash parameter
on a version of Node.js
that does not support the option,
it will silently "work".
Now we have something encrypted
that does not match the security properties that were requested.
There is a part of me that enjoys stringfying the function,
but that is a little too clever.
And finally, parsing the version will not catch back ports.

It is important to be perscriptive in what options will work.
Node.js versions that do not support `oaepHash` will silently encrypt data.
This means that the encrypted data key would not have the security properties requested.
So, `oaep_hash_supported.ts` will attempt to encrypt
and report the success.
This will happen only once, on initializaion.

Both the tests, and the integration tests have been updated honor `oaepHashSupported` values
@seebees
Copy link
Contributor

seebees commented Jan 21, 2020

@richardTowers Turns out I don't have access to push to your fork :(
If you want to give me access, I can push these changes and we can talk about them.
You can take a look at there here

@richardTowers
Copy link
Contributor Author

Ah yes - I've just invited you to collaborate on the fork, so hopefully you should be able to push now.

Your code looks good to me - it's true that the feature detection is a bit confusingly backwards, but it seems robust. Shame there's no runtime types or reflection in JS, but you work with what you've got I suppose.

@seebees
Copy link
Contributor

seebees commented Jan 21, 2020

Pushed.
Since the older version of the function will ignore the parameter,
I really really don't want to be in a situation where a customer expects something to be true and it was not :)

@richardTowers
Copy link
Contributor Author

Agree completely, yeah. I'm happy with the commit you pushed, so as far as I'm concerned this PR is good to go.

That said, I haven't run the integration tests against node v12, and I'm guessing CI hasn't either. It looks like you've already got an issue to test against a matrix of node versions (#206), but perhaps a manual run would be enough for this PR.

@richardTowers
Copy link
Contributor Author

richardTowers commented Jan 22, 2020

Also note, if this lands, AWS should update this documentation to call out support in newer node versions:

https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/javascript.html#javascript-language-compatibility (edit: I had the wrong link)

@seebees
Copy link
Contributor

seebees commented Jan 22, 2020

Thanks. I have run this agains v12 locally.
I will again now that we are in agreement on the final code.
Thanks for the doc note as well.
Coordinating that should not slow down the merge,
but might slow down the release.

@seebees seebees merged commit 81b4562 into aws:master Jan 24, 2020
@seebees seebees deleted the oaep-hash-sha256-support branch January 24, 2020 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants