Skip to content

Keyblob should parse field lengths as unsigned shorts #71

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 9 commits into from
Sep 5, 2018

Conversation

slyubomirsky
Copy link
Contributor

@slyubomirsky slyubomirsky commented Aug 28, 2018

Issue #, if available:
No number

Description of changes:
Done at the request of Rustan Leino: Currently, if the KeyBlob deserializer reads a negative length for the key provider ID, key provider info, or the key itself, this does not directly result in an exception. Instead, when the deserializer later tries to read the provider ID, provider info, or key, Arrays.copyOfRange() throws an IllegalArgumentException. From a client's perspective, this seems like a misleading exception, since the client passed all the correct arguments but the message's contents are what led to the exception. This change instead throws a ParseException if a negative length is read, corresponding to the line "the bytes does not properly represent the desired object" in the Javadoc for ParseException.

Unit tests are included for the situation described. The unit tests throw an IllegalArgumentException in the present version of the code; with the changes, they terminate normally but without deserializing the field that had a negative length. (This happens because deserialize() catches the ParseException. That may not be desired behavior in that case, since a client might retry deserialize(), not knowing that the message contents are what led to the failure to deserialize and thus never succeeding. Perhaps deserialize() should distinguish between bad message bodies versus not having enough bytes with different exceptions?)

As noted in the below conversation, according to https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/message-format.html#data-key-provider-id, the field lengths in KeyBlob should be unsigned shorts but were being parsed as signed shorts. This was leading to exceptions if a "negative" length was parsed, when the data format specification states that 2-byte unsigned values should be permitted.

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

@@ -98,7 +98,11 @@ public KeyBlob(final EncryptedDataKey edk) {
* length.
*/
private int parseKeyProviderIdLen(final byte[] b, final int off) throws ParseException {
keyProviderIdLen_ = PrimitivesParser.parseShort(b, off);
short len = PrimitivesParser.parseShort(b, off);
Copy link
Member

Choose a reason for hiding this comment

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

These methods are all doing the same thing, just with different error messages. This should be centralized into a single helper method that all three use.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do

@@ -170,4 +172,51 @@ public void checkKeyLen() {

assertEquals(mockDataKey_.getEncryptedDataKey().length, reconstructedKeyBlob.getEncryptedDataKeyLen());
}

Copy link
Member

Choose a reason for hiding this comment

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

In addition to the direct method calls, we should have a static test vector for each of these that contains bytes that will trigger the correct error when parsed.

Copy link
Contributor Author

@slyubomirsky slyubomirsky Aug 28, 2018

Choose a reason for hiding this comment

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

How should these vectors be provided? As a byte array literal? Are there similar tests that contain static test vectors, for comparison?

Copy link
Member

Choose a reason for hiding this comment

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

We're working on building out more standardized approaches for these, but for the time being, yes, just some byte array literals will do nicely.

Copy link
Contributor Author

@slyubomirsky slyubomirsky Aug 29, 2018

Choose a reason for hiding this comment

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

I added the vectors as functions returning byte array literals, but wasn't sure if a field would be preferable. I would be glad to present them in whatever manner would be most useful.


// a negative field length throws a parse exception, so deserialization is incomplete
assertIncomplete(keyBlobBytes);
assertIncomplete(negativeKeyProviderIdLenTestVector());
Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I should have clarified. What I'm looking for is a separate test that calls the full deserialize path on a byte array crafted to contain the bad values.

That being said, the vectors you provided triggered paths in my brain that I had skipped over, that change what this issue is.

Per the message format spec[1], every one of these values is an unsigned integer. What would be better to be testing is to test the methods in PrimitivesParser to verify that none of them can result in negative values.

[1] https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/message-format.html

Copy link
Contributor Author

@slyubomirsky slyubomirsky Aug 29, 2018

Choose a reason for hiding this comment

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

I see; in that case, there are methods for parsing unsigned values in PrimitivesParser that should be used here

@slyubomirsky slyubomirsky changed the title Keyblob parse exception Keyblob should parse field lengths as unsigned shorts Aug 29, 2018
@slyubomirsky
Copy link
Contributor Author

slyubomirsky commented Aug 29, 2018

Static test vectors pose more of an issue with the changes as they stand now, as they would have to be quite large to trigger the case here. Is there any particularly convenient way to provide one in this case? I would appreciate any recommendation

Copy link
Member

@mattsb42-aws mattsb42-aws left a comment

Choose a reason for hiding this comment

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

LGTM but I'd like to get one of our Java folks to look at it too.

@Test
public void checkKeyProviderIdLenUnsigned() {
// provider id length is too large for a signed short but fits in unsigned
final KeyBlob blob = generateRandomKeyBlob(Short.MAX_VALUE + 1, Short.MAX_VALUE, Short.MAX_VALUE);
Copy link
Contributor

@johnwalker johnwalker Aug 30, 2018

Choose a reason for hiding this comment

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

Why use Short.MAX_VALUE instead of the UNSIGNED_SHORT_MAX_VAL?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was an arbitrary choice (just needed it to be too large to be a positive signed short but a valid unsigned short), I can change it to make the intent of the test clearer


private KeyBlob generateRandomKeyBlob(int idLen, int infoLen, int keyLen) {
final byte[] idBytes = RandomBytesGenerator.generate(idLen);
// negative bytes translate into U+FFFD, so no thanks
Copy link
Contributor

Choose a reason for hiding this comment

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

If this array is intended to be parsed as UTF-8, then you shouldn't just generate random bytes (even with some amount of checks). Why not just fill it with 'A'?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point

}

@Test
public void checkKeyLenUnsigned() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a test for when it is too long to fit even in an unsigned short?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea; I will

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It occurs to me now that such tests already exist, namely overlyLargeKeyProviderIdLen(), overlyLargeKeyProviderInfoLen(), and overlyLargeKey(). They check that instantiating such a KeyBlob results in an exception. (I had changed them earlier to make sure they were treating Constants.UNSIGNED_SHORT_MAX_VAL as the max length.)

@slyubomirsky
Copy link
Contributor Author

Note: Technically a char could be used to store these unsigned 16-bit values, since the Java language standard guarantees that chars must be unsigned. This could be useful to have a compiler-provided guarantee that these lengths are non-negative, though admittedly a little strange.

@SalusaSecondus SalusaSecondus merged commit 7344d35 into aws:master Sep 5, 2018
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.

4 participants