-
Notifications
You must be signed in to change notification settings - Fork 122
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
Keyblob should parse field lengths as unsigned shorts #71
Conversation
@@ -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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()); | |||
} | |||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
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 |
There was a problem hiding this 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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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'
?
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea; I will
There was a problem hiding this comment.
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.)
…h max unsigned short value
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. |
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 anIllegalArgumentException
. 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 aParseException
if a negative length is read, corresponding to the line "the bytes does not properly represent the desired object" in the Javadoc forParseException.
Unit tests are included for the situation described. The unit tests throw anIllegalArgumentException
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 becausedeserialize()
catches theParseException
. That may not be desired behavior in that case, since a client might retrydeserialize()
, not knowing that the message contents are what led to the failure to deserialize and thus never succeeding. Perhapsdeserialize()
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.