Skip to content

fix: Conditions for materials-management #185

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

Merged
merged 5 commits into from
Aug 6, 2019

Conversation

seebees
Copy link
Contributor

@seebees seebees commented Aug 4, 2019

Every condition needs a test.
This also uncovered some missing conditions and tests.

  • If a multi keyring has errors AND fails to decrypt the EDK it should throw and error.
  • Lower the test_conditions threshold

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? Moving files changes their URL, which breaks all hyperlinks to the files.

Every condition needs a test.
This also uncovered some missing conditions and tests.
* If a multi keyring has errors AND fails to decrypt the EDK it should throw and error.
* Lower the test_conditions threshold
@seebees seebees requested a review from a team August 4, 2019 23:39
@@ -58,7 +58,7 @@ function portableTimingSafeEqual (a: Uint8Array, b: Uint8Array) {
* Side channel attacks are pernicious and subtle.
*/
eval('') // eslint-disable-line no-eval
/* Check for early return (Postcondition): Size is well-know information
/* Check for early return (Postcondition) UNTESTED: Size is well-know information.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this untested? Is there a reason this is not testable, or is adding the test outside the scope of this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly because constant time function are not verifiable (as I understand it). So this function is not exported so it can not be used elsewhere.

The fact that I return in this place is something that needs to be explained, but I don't even know how I would verify this one way or another...

@@ -338,8 +338,9 @@ export function decorateCryptographicMaterial<T extends CryptographicMaterial<T>
udkForVerification.fill(0)
unencryptedDataKeyZeroed = true

/* Postcondition: Both unencryptedDataKey and udkForVerification must be either set or unset.
/* Postcondition UNTESTED: Both unencryptedDataKey and udkForVerification must be either set or unset.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this untested? Is there a reason this is not testable, or is adding the test outside the scope of this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes: It is not clear how this could ever happen, unless someone is manipulating the OS...

The values are private, I have no way to manipulate the values to verify that the function will indeed fail in this case.

@@ -50,7 +50,7 @@ export abstract class Keyring<S extends SupportedAlgorithmSuites> {
*/
needs(material === _material, 'New EncryptionMaterial instances can not be created.')

/* Postcondition: If this keyring generated data key, it must be the right length.
/* Postcondition UNTESTED: If this keyring generated data key, it must be the right length.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this untested? Is there a reason this is not testable, or is adding the test outside the scope of this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is covered by the materials themselves. The comment is there to make sure that people know it needs to be verified. But there is no code to be tested...

* However, if I was unable to decrypt a key AND I have errors,
* these errors should bubble up.
*/
needs(material.hasValidKey() || !keyringError, 'Keyring errors and no keyring was successfully able to decrypt the encrypted data key.')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naive JS question: How does JS handle the bubbling up of errors? If this needs condition is not met due to an error caught on L122, how does the error (and possibly others) propagate up, and how is that error handled?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The errors from L122 are lost and not wrapped. That is not a bad idea to add, but it will require some other re-work

@@ -28,10 +28,22 @@ describe('AlgorithmSuiteIdentifier', () => {
describe('AlgorithmSuite', () => {
it('should not allow an instance', () => {
// @ts-ignore Trying to test something that Typescript should deny...
expect(() => new AlgorithmSuite()).to.throw()
expect(() => new AlgorithmSuite()).to.throw('new AlgorithmSuite is not allowed')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another niave JS question, what is going on here: https://github.com/awslabs/aws-encryption-sdk-javascript/blob/master/modules/material-management/src/algorithm_suites.ts#L105

Is this preventing the AlgorithmSuite constructor from being called? Is this because you're trying to ensure that only extensions of AlgorithmSuite are actually constructed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this preventing the AlgorithmSuite constructor from being called?

yes

Is this because you're trying to ensure that only extensions of AlgorithmSuite are actually constructed?

yes

when you create an object with new there is a special property constructor that is just what it says. :)

const suite = new NodeAlgorithmSuite(AlgorithmSuiteIdentifier.ALG_AES128_GCM_IV12_TAG16)
const test = decorateCryptographicMaterial((<any>{ suite, keyringTrace: [] }), KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY)
test.zeroUnencryptedDataKey()
const dataKey = new Uint8Array(suite.keyLengthBytes).fill(1)
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY }
// It is very hard to test this perfectly. However, this tests the spirit.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the "spirit" that is actually being tested here, and what test is missing for the above preconditions?

This looks to me like were testing the postcondition at https://github.com/awslabs/aws-encryption-sdk-javascript/blob/master/modules/material-management/src/cryptographic_material.ts#L341

I'm not familiar with the precise definition of pre/post condition as used in this codebase, but if we aren't checking something and throwing an error on the precondition does it make sense to call it a precondition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the unencryptedDataKey has not been set, it should not be settable later.

i.e. if you create materials, then test.zeroUnencryptedDataKey(), you should not be able to set an unencrypted data key later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see the behavior now. This test depends on setUnencryptedDataKey maintaining a behavior where it checks whether the unencryptedDataKey/udkForVerification has been set. This makes the test more brittle than I'd like for the precondition as a whole, but at the very least we ensure with this test that the case really we care about ("you can't set after zeroing) is taken care of.

@@ -309,6 +352,27 @@ describe('decorateWebCryptoMaterial', () => {
expect(() => test.setCryptoKey(key2, trace)).to.throw()
})

it('Precondition: If the CryptoKey is the only version, the trace information must be set here.', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is unclear to me what "If the CryptoKey is the only version" means. Looking at the code, this should be a precondition about the keyring trace during setCryptoKey if the udk isn't already set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you only set a CryptoKey. And do not set an unencrypted data key. This happens in browsers when an EDK can be "unwrapped". The API lets you do this unwrapping and put the data key into an un exportable CryptoKey. This means that the unencrypted data key is never available to the javascript.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotcha 👍

package.json Outdated
@@ -26,7 +26,7 @@
"integration-node-encrypt": "npm run build; lerna run integration_node --stream --no-prefix -- -- encrypt -m $npm_package_config_encryptManifestList -k $npm_package_config_encryptKeyManifest -o $npm_package_config_decryptOracle",
"integration-node": "run-s integration-node-*",
"integration": "run-s integration-*",
"test_conditions": "./util/bootstrap_tsconfig 23"
"test_conditions": "./util/bootstrap_tsconfig 2"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this option do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to get rid of it. I just lets the test not fail if there are "exceptions" I want to remove it so that no one even sees it and thinks, "Hey, I can get aways with not writing a test by setting this..." But as I get to that point, I want to have it running...

@@ -27,7 +27,7 @@ chai.use(chaiAsPromised)
const { expect } = chai

describe('flattenMixedCryptoKey', () => {
it('Check for early return (Postcondition): empty inputs should return an empty array.', () => {
it('Check for early return (Postcondition): nothing is an empty array.', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this wording changed recently. Did you intend to change it back?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum, the test must match the condition. You may be right....

}
}

/* Postcondition: Need either a valid data key or no errors.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is in line with what is currently in the spec PR. However, I either want someone familiar with the C multikeyring implementation to either give this logic the OK, or give the current wording in the spec the OK, before I approve this. I will tag someone in the spec CR on this issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks :)

const suite = new NodeAlgorithmSuite(AlgorithmSuiteIdentifier.ALG_AES128_GCM_IV12_TAG16)
const test = decorateCryptographicMaterial((<any>{ suite, keyringTrace: [] }), KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY)
test.zeroUnencryptedDataKey()
const dataKey = new Uint8Array(suite.keyLengthBytes).fill(1)
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY }
// It is very hard to test this perfectly. However, this tests the spirit.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see the behavior now. This test depends on setUnencryptedDataKey maintaining a behavior where it checks whether the unencryptedDataKey/udkForVerification has been set. This makes the test more brittle than I'd like for the precondition as a whole, but at the very least we ensure with this test that the case really we care about ("you can't set after zeroing) is taken care of.

@seebees seebees merged commit 7f7228b into aws:master Aug 6, 2019
@seebees seebees deleted the conditions-material-management branch August 6, 2019 23:17
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.

2 participants