Adding a test for key wrapping:

This supports RFC 3394 and RFC 5649,
which is also described in NIST SP 800 38f.

I also have a mini analysis of the primitive:
If one cares about distinguishing attacks then
there is an attack with a somewhat smaller complexity
than what NIST claims in their standard:
https://docs.google.com/document/d/1oH36v6SNdff5tkydC75EnqRyqqgHbXR4hy_SMPzwqVU/edit?usp=sharing
But even if one cares about these attacks then the key
wrappings are nowhere comparable to the attacks possible against e.g.
AES-GCM.

PiperOrigin-RevId: 192747615
GitOrigin-RevId: 0bebc767022ba3053c1d0391378d5c0b32b7f5be
This commit is contained in:
Daniel Bleichenbacher
2018-04-13 03:51:15 -07:00
committed by Tink Team
parent 16b1d26f28
commit 933e2e10aa
10 changed files with 3791 additions and 0 deletions
@@ -35,6 +35,11 @@ import org.junit.runners.Suite.SuiteClasses;
EcdhTest.class,
EcdsaTest.class,
EciesTest.class,
JsonAeadTest.class,
JsonCipherTest.class,
JsonEcdhTest.class,
JsonKeyWrapTest.class,
JsonSignatureTest.class,
MessageDigestTest.class,
RsaEncryptionTest.class,
RsaKeyTest.class,
@@ -39,6 +39,7 @@ import org.junit.runners.Suite.SuiteClasses;
JsonAeadTest.class,
JsonCipherTest.class,
JsonEcdhTest.class,
JsonKeyWrapTest.class,
JsonSignatureTest.class,
MessageDigestTest.class,
RsaEncryptionTest.class,
@@ -32,6 +32,11 @@ import org.junit.runners.Suite.SuiteClasses;
EcKeyTest.class,
EcdhTest.class,
EcdsaTest.class,
JsonAeadTest.class,
JsonCipherTest.class,
JsonEcdhTest.class,
JsonKeyWrapTest.class,
JsonSignatureTest.class,
MessageDigestTest.class,
RsaEncryptionTest.class,
RsaKeyTest.class,
@@ -37,6 +37,7 @@ import org.junit.runners.Suite.SuiteClasses;
JsonAeadTest.class,
JsonCipherTest.class,
JsonEcdhTest.class,
JsonKeyWrapTest.class,
JsonSignatureTest.class,
MessageDigestTest.class,
RsaEncryptionTest.class,
@@ -36,6 +36,7 @@ import org.junit.runners.Suite.SuiteClasses;
JsonAeadTest.class,
JsonCipherTest.class,
JsonEcdhTest.class,
JsonKeyWrapTest.class,
JsonSignatureTest.class,
MessageDigestTest.class,
RsaEncryptionTest.class,
@@ -35,6 +35,11 @@ import org.spongycastle.jce.provider.BouncyCastleProvider;
EcdhTest.class,
EcdsaTest.class,
EciesTest.class,
JsonAeadTest.class,
JsonCipherTest.class,
JsonEcdhTest.class,
JsonKeyWrapTest.class,
JsonSignatureTest.class,
RsaEncryptionTest.class,
RsaKeyTest.class,
RsaSignatureTest.class,
@@ -42,6 +42,7 @@ import org.spongycastle.jce.provider.BouncyCastleProvider;
JsonAeadTest.class,
JsonCipherTest.class,
JsonEcdhTest.class,
JsonKeyWrapTest.class,
JsonSignatureTest.class,
MessageDigestTest.class,
RsaEncryptionTest.class,
@@ -0,0 +1,246 @@
/**
* 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.google.security.wycheproof;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.security.wycheproof.WycheproofRunner.NoPresubmitTest;
import com.google.security.wycheproof.WycheproofRunner.ProviderType;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* This test uses test vectors in JSON format to test key wrapping.
*
* <p>This test is mainly for key wrappings such as RFC 3349 and RFC 5469.
* I.e. these algorithms have the following properties:
* <ul>
* <li>The wrapping is deterministic. Hence wrapping can be compared against known
* results.
* <li>The wrapping has an integrity check. Modified wrapped keys can be detected
* with high probability.
* </ul>
* This test does not cover key wrapping with AEAD algorithms such as AES-GCM.
* Testing such algorithms would require an additional test vector type.
* I.e., the JCE interface requires that the caller himself handles the IV.
* I.e. each wrapping has to use a new unique IV. This IV has to be stored additionally
* with the wrapped key and has to be passed as parameter to unwrap.
*/
@RunWith(JUnit4.class)
public class JsonKeyWrapTest {
/** Convenience method to get a byte array from a JsonObject. */
protected static byte[] getBytes(JsonObject object, String name) throws Exception {
return JsonUtil.asByteArray(object.get(name));
}
/**
* Initialize a Cipher instance for key wrapping.
*
* @param cipher an instance of a symmetric cipher that will be initialized.
* @param algorithm the name of the algorithm used (e.g. 'KW')
* @param opmode either Cipher.WRAP_MODE or Cipher.UNWRAP_MODE
* @param key raw key bytes
*/
protected static void initCipher(Cipher cipher, String algorithm, int opmode, byte[] key)
throws Exception {
if (algorithm.toUpperCase().equals("AESWRAP")
|| algorithm.toUpperCase().equals("AESRFC5649WRAP")) {
cipher.init(opmode, new SecretKeySpec(key, "AES"));
} else {
fail("Unsupported algorithm:" + algorithm);
}
}
/**
* <p> The test also determines whether different paddings lead to different exceptions.
* Generally it is preferable when unwrapping keys with incorrect paddings does not leak
* information about invalid paddings through exceptions. Such information could be used
* for an attack. Ideally, providers should not include any distinguishing features in the
* exception.
*
* <p>However, such an observation does not necessarily imply a vulnerability.
* For example the algorithm KW (NIST SP 800 38f) is designed with the idea that the
* underlying algorithm W is a strong pseudorandom permuation. This would imply that the
* algorithm is resistant against attacks even if the attacker get access to all the
* byte of W^-1 in the case of a failure. For such primitives the exceptions thrown during
* the test are printed, but distinct execeptions do not fail the test.
*
* @param filename the local filename with the test vectors
* @param algorithm the algorithm name for the key wrapping (e.g. "AESWrap")
* @param wrappedAlgorithm the algorithm name for the secret key that is wrapped
* (e.g. "HMACSHA256"). The key wrap primitives wrap and unwrap byte arrays.
* However, the JCE interface requires an instance of java.security.Key with
* key material. The key wrap primitive does not depend on wrappedAlgorithm and hence
* neither does the test. "HMACSHA256" is used for this parameter in the tests below,
* simply because HMAC allows arbitrary key sizes.
* @param paddingAttacks determines whether the test fails if exceptions leak information
* about the padding.
*/
// This is a false positive, since errorprone cannot track values passed into a method.
@SuppressWarnings("InsecureCryptoUsage")
public void testKeywrap(String filename, String algorithm, String wrappedAlgorithm,
boolean paddingAttacks) throws Exception {
// Testing with old test vectors may a reason for a test failure.
// Version number have the format major.minor[status].
// Versions before 1.0 are experimental and use formats that are expected to change.
// Versions after 1.0 change the major number if the format changes and change
// the minor number if only the test vectors (but not the format) changes.
// Versions meant for distribution have no status.
final String expectedVersion = "0.4.1";
JsonObject test = JsonUtil.getTestVectors(filename);
Set<String> exceptions = new TreeSet<String>();
String generatorVersion = test.get("generatorVersion").getAsString();
if (!generatorVersion.equals(expectedVersion)) {
System.out.printf("%s expects test vectors with version %s found version %s.",
algorithm, expectedVersion, generatorVersion);
}
int errors = 0;
Cipher cipher;
try {
cipher = Cipher.getInstance(algorithm);
} catch (NoSuchAlgorithmException ex) {
System.out.println("Algorithm is not supported. Skipping test for " + algorithm);
return;
}
for (JsonElement g : test.getAsJsonArray("testGroups")) {
JsonObject group = g.getAsJsonObject();
for (JsonElement t : group.getAsJsonArray("tests")) {
JsonObject testcase = t.getAsJsonObject();
int tcid = testcase.get("tcId").getAsInt();
String tc = "tcId: " + tcid + " " + testcase.get("comment").getAsString();
byte[] key = getBytes(testcase, "key");
byte[] data = getBytes(testcase, "msg");
byte[] expected = getBytes(testcase, "ct");
// Result is one of "valid", "invalid", "acceptable".
// "valid" are test vectors with matching plaintext, ciphertext and tag.
// "invalid" are test vectors with invalid parameters or invalid ciphertext and tag.
// "acceptable" are test vectors with weak parameters or legacy formats.
String result = testcase.get("result").getAsString();
// Test wrapping
try {
initCipher(cipher, algorithm, Cipher.WRAP_MODE, key);
} catch (GeneralSecurityException ex) {
// Some libraries may restrict key size.
// Because of this the initialization of the cipher might fail.
System.out.println(ex.toString());
continue;
}
try {
SecretKeySpec keyspec = new SecretKeySpec(data, wrappedAlgorithm);
byte[] wrapped = cipher.wrap(keyspec);
boolean eq = Arrays.equals(expected, wrapped);
if (result.equals("invalid")) {
if (eq) {
// Some test vectors use invalid parameters that should be rejected.
System.out.printf("Wrapped test case:%s", tc);
errors++;
}
} else {
if (!eq) {
System.out.printf("Incorrect wrapping for test case:%s wrapped butes:%s",
tc, TestUtil.bytesToHex(wrapped));
errors++;
}
}
} catch (GeneralSecurityException | IllegalArgumentException ex) {
// IllegalArgumentException can be thrown by new SecretKeySpec
if (result.equals("valid")) {
System.out.printf("Failed to wrap test case:%s", tc);
errors++;
}
} catch (Exception ex) {
// Other exceptions are violating the interface.
System.out.printf("Test case %s throws %s.", tc, ex);
errors++;
}
// Test unwrapping
// The algorithms tested in this class are typically malleable. Hence, it is in possible
// that modifying ciphertext randomly results in some other valid ciphertext.
// However, all the test vectors in Wycheproof are constructed such that they have
// invalid padding. If this changes then the test below is too strict.
try {
initCipher(cipher, algorithm, Cipher.UNWRAP_MODE, key);
} catch (GeneralSecurityException ex) {
System.out.printf("Parameters accepted for wrapping but not unwrapping:%s", tc);
errors++;
continue;
}
try {
Key keyspec = cipher.unwrap(expected, wrappedAlgorithm, Cipher.SECRET_KEY);
byte[] unwrapped = keyspec.getEncoded();
boolean eq = Arrays.equals(data, unwrapped);
if (result.equals("invalid")) {
System.out.printf("Unwrapped invalid test case:%s unwrapped:%s", tc,
TestUtil.bytesToHex(unwrapped));
errors++;
} else {
if (!eq) {
System.out.printf("Incorrect unwrap. Excepted:%s actual:%s",
unwrapped, TestUtil.bytesToHex(unwrapped));
errors++;
}
}
} catch (GeneralSecurityException | IllegalArgumentException ex) {
// The JCE interface specifies that an incorrect wrapping should throw an
// InvalidKeyException. IllegalArgumentException is thrown by the SecretKeySpec
// constructor if the unwrapped key is empty. It is unclear whether this is a bug
// in the code or just should be documented.
exceptions.add(ex.toString());
if (result.equals("valid")) {
System.out.printf("Failed to unwrap:%s", tc);
errors++;
}
} catch (Exception ex) {
// Other exceptions may indicate a programming error.
System.out.printf("Test case:%s throws %s", tc, ex);
exceptions.add(ex.toString());
errors++;
}
}
}
System.out.printf("Number of distinct exceptions of %s:%d", algorithm, exceptions.size());
for (String ex : exceptions) {
System.out.println(ex);
}
assertEquals(0, errors);
if (paddingAttacks) {
assertEquals(1, exceptions.size());
}
}
@Test
public void testAesWrap() throws Exception {
testKeywrap("kw_test.json", "AESWRAP", "HMACSHA256", false);
}
@Test
public void testAesRFC5649Wrap() throws Exception {
testKeywrap("kwp_test.json", "AESRFC5649WRAP", "HMACSHA256", false);
}
}