-
Notifications
You must be signed in to change notification settings - Fork 143
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
Symmetric keygen: JavaThemis #565
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* | ||
* Copyright (c) 2019 Cossack Labs Limited | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.cossacklabs.themis; | ||
|
||
/** | ||
* Symmetric encryption key. | ||
* | ||
* These keys are used with Secure Cell cryptosystem. | ||
*/ | ||
public class SymmetricKey extends KeyBytes { | ||
|
||
/** | ||
* Generates a new symmetric key. | ||
*/ | ||
public SymmetricKey() { | ||
super(newSymmetricKey()); | ||
} | ||
|
||
/** | ||
* Creates a symmetric key from byte array. | ||
* | ||
* @param key byte array | ||
* | ||
* @throws NullArgumentException if `key` is null. | ||
* @throws InvalidArgumentException if `key` is empty. | ||
*/ | ||
public SymmetricKey(byte[] key) { | ||
super(key); | ||
} | ||
|
||
private static native byte[] generateSymmetricKey(); | ||
|
||
private static byte[] newSymmetricKey() { | ||
byte[] key = generateSymmetricKey(); | ||
if (key == null || key.length == 0) { | ||
throw new KeyGenerationException("failed to generate symmetric key"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kinda want to zero key in case of error, but at the same time, if key is null or 0, i can't think of use case where zeroing would be necessary :D |
||
} | ||
return key; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* | ||
* Copyright (c) 2019 Cossack Labs Limited | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.cossacklabs.themis.test; | ||
|
||
import com.cossacklabs.themis.SymmetricKey; | ||
import com.cossacklabs.themis.InvalidArgumentException; | ||
import com.cossacklabs.themis.NullArgumentException; | ||
|
||
import static org.junit.Assert.*; | ||
import org.junit.Test; | ||
|
||
public class SymmetricKeyTest { | ||
|
||
private static final int defaultLength = 32; | ||
|
||
@Test | ||
public void generateNewKey() { | ||
SymmetricKey key = new SymmetricKey(); | ||
|
||
byte[] keyBytes = key.toByteArray(); | ||
assertNotNull(keyBytes); | ||
assertEquals(keyBytes.length, defaultLength); | ||
} | ||
|
||
@Test | ||
public void restoreKeyFromBytes() { | ||
byte[] buffer = { 3, 14, 15, 92, 6 }; | ||
SymmetricKey key = new SymmetricKey(buffer); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we really want to allow users to create their own 'insecure' keys? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the world where we provide strict type interfaces (accepting a concrete In the future we may impose additional restrictions to prohibit usage of weak keys. But right now we don’t have any other than keys being non-empty. Plus, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My point is that if we will have two interfaces (read this as illustration):
users might do following:
thus using low entropy string as input param for creating bad "symmetric key", avoiding KDF call. I'd suggest separate two interfaces:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They sure can do that, but there is no realistic way to prevent that situation with just API design. We cannot gauge 'randomness' of a key by a single sample. We could introduce a bottom limit on the key length, but that will not prevent the users from using You're right about the passphrase interface, but we cannot leave the users with only a constructor that returns new instances of |
||
assertArrayEquals(buffer, key.toByteArray()); | ||
} | ||
|
||
@Test(expected = NullArgumentException.class) | ||
public void restoreKeyFromNull() { | ||
SymmetricKey key = new SymmetricKey(null); | ||
} | ||
|
||
@Test(expected = InvalidArgumentException.class) | ||
public void restoreKeyFromEmpty() { | ||
SymmetricKey key = new SymmetricKey(new byte[0]); | ||
} | ||
} |
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.
shall we try to clean up / zero key data before exiting?
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.
Now that's a professional paranoid person at work!
My initial reaction to this was: "This BS, how is this exploitable? It's just a random buffer. It has not even been used as a key".
But then I switched on my paranoia gland... and realized that it could be exploitable. If the PRNG leaks some information on failure path, if that could be used to learn something abouth the seed, if that can be triggered by an attacker, then it could be possible, under certain circumstances, to predict PRNG output — that is, if you trick the user to register an account at the right moment then you could recover their data because you have predicted the random key generated for Secure Cell that keeps the user's master key. That's probably one of the gazillion reasons why BoringSSL aborts the process if its PRNG fails to fill the whole buffer.
I have also reflected on how the random data output is used throughout Themis, which also has some implications. For example, we could be leaking some partial computation results on failure paths (like the ephemeral key used in Secure Message).
So... how about the following mitigations?
Ensure that we never leak PRNG state.
Let's fix it for all wrappers at one fell swoop in soter_rand(). If the backend fails to generate random data then soter_wipe() the entire buffer to ensure that nothing gets leaked, in case the backend has partially filled the buffer.
Manually review soter_rand() call sites.
Make sure that we check the error code and if it's not successful then GTFO to the caller as fast as we can, never using the 'random' zeros we got.
Ensure that we never leak partial data.
At all Themis entry points, if we're not returning THEMIS_SUCCESS then soter_wipe() all output buffers, never leaking any partial information that may have been written there.
Ensure that we don't leave partial data in memory.
This is harder. We'd need to make sure that we wipe all sensitive data that we allocate on stack and heap. Just to be sure that we don't leave anything lying around.
Heap is easier since we can grep for it (34 malloc/calloc/realloc calls in Soter and Themis).
I'm sure that there are more creative exploits of the 'observe Themis while it tries to decrypt data with invalid keys and recover it' sort, but that's too paranoid for me to think about.
While we're here, I've remembered another thing about leaks. Remember the padding oracle attack? The one that lets the attacker recover the key if the implementation returns different error codes for "invalid input" and "corrupted data" conditions? Just because you leak one bit of information. It may be not applicable to encryption modes used by Themis, but Themis error codes are unnecessarily detailed.
How about leaving only the following error codes:
and deprecating everything else? Specifically, THEMIS_DATA_CORRUPT, THEMIS_INVALID_SIGNATURE, and SOTER_ENGINE_FAIL (may be returned by Themis, actually).
P.S. And here's me thinking: this was supposed to be a new, clean, simple, one-function API to return random bytes, how come it turned out to be this audit trip full of possible exploits that makes you doubt the security of the whole thing?
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.
Yes, please do
Would be good
Yes, but keep an eye that some output buffers are input pointers. I think we don't want to damage user vars.
This might be a hard one. We tried to audit
malloc/alloc
calls in Themis/Soter previously, but makes sense to re-read that code again.Agree with you. I'd deprecate
THEMIS_NO_MEMORY
in favor ofTHEMIS_FAIL
as well.We're getting older / smarter / wiser / more paranoid every time we're making large changes. Think about this as about evolution. But let's move code quality changes to the separate PR? :)
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.
Sure. All of this is definitely out of scope for this pull request.