Merge pull request #294 from cconlon/kwDefaultIvJava17
JCE: match SunJCE AES Key Wrap default IV and update() behaviorpull/296/head
commit
64d4d29015
|
|
@ -559,8 +559,8 @@ the services below are not registered.
|
|||
Usage details:
|
||||
|
||||
- All four opmodes are supported: `WRAP_MODE`/`UNWRAP_MODE` for `wrap()`/
|
||||
`unwrap()`, and `ENCRYPT_MODE`/`DECRYPT_MODE` for `doFinal()` over raw
|
||||
data (`update()` buffers, all output comes from `doFinal()`).
|
||||
`unwrap()`, and `ENCRYPT_MODE`/`DECRYPT_MODE` for `doFinal()` over raw data
|
||||
(`update()` buffers and returns `null`, all output comes from `doFinal()`).
|
||||
- Input to wrap must be at least 16 bytes and a multiple of 8 bytes, and
|
||||
wrapped input to unwrap at least 24 bytes and a multiple of 8
|
||||
(`IllegalBlockSizeException` otherwise). Wrapped output is 8 bytes longer
|
||||
|
|
@ -569,10 +569,12 @@ Usage details:
|
|||
no parameters the default `A6A6A6A6A6A6A6A6` is used and wolfJCE never
|
||||
generates a random IV. An alternative value may be given as an 8-byte
|
||||
`IvParameterSpec` (or an `AlgorithmParameters("AES")` holding one) and
|
||||
must then be supplied to the unwrapping side as well. `getIV()` and
|
||||
`getParameters()` return `null` unless an IV was set explicitly, in which
|
||||
case `getParameters()` is an `AlgorithmParameters("AES")` holding the
|
||||
8-byte IV, as with SunJCE 17+. That object can be passed to another
|
||||
must then be supplied to the unwrapping side as well. Like SunJCE, on
|
||||
Java 17+ `getIV()` returns the default IV after `init()`, and
|
||||
`getParameters()` returns it even before `init()`. On older Java versions,
|
||||
where SunJCE accepts no AES Key Wrap parameters, both return `null` unless
|
||||
an IV was set. `getParameters()` is an `AlgorithmParameters("AES")`
|
||||
holding the 8-byte IV. That object can be passed to another
|
||||
provider's `Cipher.init()` (which uses `getParameterSpec()`). Its DER
|
||||
encoding, `04 08 <iv>`, is the same one SunJCE produces, but SunJCE's own
|
||||
DER decoder only accepts 16-byte AES IVs, where wolfJCE decodes both.
|
||||
|
|
@ -1124,15 +1126,6 @@ Castle. SunJCE (JDK 17+) throws `IllegalBlockSizeException` with the same
|
|||
message in this case. `UNWRAP_MODE` is unaffected: both providers throw
|
||||
`InvalidKeyException` for every `unwrap()` failure.
|
||||
|
||||
`getParameters()` on an AES Key Wrap Cipher returns `null` unless an IV was
|
||||
set explicitly (JDK 8 and Bouncy Castle behavior), whereas SunJCE 17+ returns
|
||||
`AlgorithmParameters` holding the default IV. This keeps
|
||||
`otherCipher.init(mode, key, wolfCipher.getParameters())` working against
|
||||
providers that accept no AES Key Wrap parameters at all. With an explicit IV
|
||||
both providers return `AlgorithmParameters("AES")` with the same `04 08 <iv>`
|
||||
DER encoding; `AlgorithmParameters.getInstance("AES", "SunJCE")` rejects that
|
||||
encoding (even its own), while wolfJCE accepts both 8 and 16 byte IVs.
|
||||
|
||||
#### Cipher `unwrap()` With a Null or Empty Algorithm Name
|
||||
|
||||
`Cipher.unwrap()` on any wolfJCE Cipher throws `NoSuchAlgorithmException`
|
||||
|
|
|
|||
|
|
@ -207,6 +207,17 @@ public class WolfCryptCipher extends CipherSpi {
|
|||
/* Size of generated AES-GCM IV, 96 bits */
|
||||
private static final int GCM_INTERNAL_IV_SIZE = 12;
|
||||
|
||||
/* RFC 3394 default AES Key Wrap IV */
|
||||
private static final byte[] KW_DEFAULT_IV = {
|
||||
(byte)0xA6, (byte)0xA6, (byte)0xA6, (byte)0xA6,
|
||||
(byte)0xA6, (byte)0xA6, (byte)0xA6, (byte)0xA6
|
||||
};
|
||||
|
||||
/* Report default AES Key Wrap IV on Java 17+, matching JDK behavior.
|
||||
* Older JDKs return null and reject all AES Key Wrap parameters. */
|
||||
private static final boolean KW_REPORT_DEFAULT_IV =
|
||||
WolfCryptUtil.getJavaVersion() >= 17;
|
||||
|
||||
/* Set when the AES-GCM IV is generated natively, cleared whenever the
|
||||
* native structs are recreated */
|
||||
private boolean gcmInternalIv = false;
|
||||
|
|
@ -1072,16 +1083,20 @@ public class WolfCryptCipher extends CipherSpi {
|
|||
break;
|
||||
|
||||
case WC_KW:
|
||||
/* Only return an explicitly-set IV. null is the RFC 3394
|
||||
* default. Always use wolfJCE AES parameters, JDK 8/11
|
||||
* SunJCE rejects 8-byte IVs. */
|
||||
if (this.iv != null) {
|
||||
/* Set IV, or the default before init() on Java 17+.
|
||||
* Always use wolfJCE AES parameters, JDK 8/11 AES
|
||||
* parameters reject 8-byte IVs. */
|
||||
paramIv = this.iv;
|
||||
if (paramIv == null && KW_REPORT_DEFAULT_IV) {
|
||||
paramIv = KW_DEFAULT_IV;
|
||||
}
|
||||
if (paramIv != null) {
|
||||
Provider wolf = Security.getProvider("wolfJCE");
|
||||
if (wolf == null) {
|
||||
wolf = FallbackProvider.INSTANCE;
|
||||
}
|
||||
params = AlgorithmParameters.getInstance("AES", wolf);
|
||||
params.init(new IvParameterSpec(this.iv));
|
||||
params.init(new IvParameterSpec(paramIv));
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -1192,7 +1207,7 @@ public class WolfCryptCipher extends CipherSpi {
|
|||
this.cipherMode == CipherMode.WC_KW) {
|
||||
|
||||
if (spec == null) {
|
||||
this.iv = null;
|
||||
this.iv = KW_REPORT_DEFAULT_IV ? KW_DEFAULT_IV.clone() : null;
|
||||
}
|
||||
else if (spec instanceof IvParameterSpec) {
|
||||
byte[] kwIv = ((IvParameterSpec)spec).getIV();
|
||||
|
|
@ -1875,9 +1890,13 @@ public class WolfCryptCipher extends CipherSpi {
|
|||
}
|
||||
|
||||
/* Some algos/modes keep data buffered until the doFinal() call, like
|
||||
* RSA or AES-GCM/CCM without stream mode compiled natively. Just
|
||||
* return an empty byte array in those cases here. */
|
||||
* RSA or AES-GCM/CCM without stream mode compiled natively. Return
|
||||
* an empty byte array, or null for AES Key Wrap matching JDK
|
||||
* behavior. */
|
||||
if (isNoOpUpdate(bufferedLen)) {
|
||||
if (this.cipherMode == CipherMode.WC_KW) {
|
||||
return null;
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1378,4 +1378,29 @@ public class WolfCryptUtil {
|
|||
|
||||
return (diff == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Java major version from java.specification.version - "1.8" on
|
||||
* Java 8, "17" on Java 17, "0.9" on Android.
|
||||
*
|
||||
* @return Java major version, or 0 on Android or if unknown
|
||||
*/
|
||||
public static int getJavaVersion() {
|
||||
try {
|
||||
String[] ver = System.getProperty(
|
||||
"java.specification.version", "0").split("\\.");
|
||||
|
||||
int major = Integer.parseInt(ver[0]);
|
||||
if (major == 1 && ver.length > 1) {
|
||||
major = Integer.parseInt(ver[1]);
|
||||
}
|
||||
|
||||
return major;
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
/* SecurityException or malformed version, never throw since
|
||||
* WolfCryptCipher calls this from its static initializer */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ import java.security.spec.AlgorithmParameterSpec;
|
|||
import java.security.spec.ECGenParameterSpec;
|
||||
|
||||
import com.wolfssl.provider.jce.WolfCryptProvider;
|
||||
import com.wolfssl.provider.jce.WolfCryptUtil;
|
||||
import com.wolfssl.wolfcrypt.AesKeyWrap;
|
||||
import com.wolfssl.wolfcrypt.FeatureDetect;
|
||||
import com.wolfssl.wolfcrypt.test.TimedTestWatcher;
|
||||
|
|
@ -144,6 +145,10 @@ public class WolfCryptCipherAesWrapTest {
|
|||
/* Alternative 8-byte IV */
|
||||
private static final byte[] ALT_IV = Util.h2b("0011223344556677");
|
||||
|
||||
/* IV reported when none set: the default on Java 17+, null on older Java */
|
||||
private static final byte[] REPORTED_DEFAULT_IV =
|
||||
(WolfCryptUtil.getJavaVersion() >= 17) ? DEFAULT_IV : null;
|
||||
|
||||
@Rule(order = Integer.MIN_VALUE)
|
||||
public TestRule testWatcher = TimedTestWatcher.create();
|
||||
|
||||
|
|
@ -237,6 +242,16 @@ public class WolfCryptCipherAesWrapTest {
|
|||
return b;
|
||||
}
|
||||
|
||||
/* IV held by AlgorithmParameters, or null */
|
||||
private static byte[] paramsIv(AlgorithmParameters params)
|
||||
throws Exception {
|
||||
|
||||
if (params == null) {
|
||||
return null;
|
||||
}
|
||||
return params.getParameterSpec(IvParameterSpec.class).getIV();
|
||||
}
|
||||
|
||||
/**
|
||||
* Names SunJCE (JDK 8 and 17+), Bouncy Castle, and NIST OID callers
|
||||
* expect to resolve, plus transformation forms that exercise
|
||||
|
|
@ -354,17 +369,14 @@ public class WolfCryptCipherAesWrapTest {
|
|||
assertArrayEquals("doFinal() decrypt does not match vector",
|
||||
data, dec.doFinal(expected));
|
||||
|
||||
/* ENCRYPT_MODE / DECRYPT_MODE, update() then doFinal() */
|
||||
byte[] part = enc.update(data, 0, 8);
|
||||
assertTrue("update() must buffer, not emit output",
|
||||
part == null || part.length == 0);
|
||||
part = enc.update(data, 8, data.length - 8);
|
||||
assertTrue(part == null || part.length == 0);
|
||||
/* ENCRYPT_MODE / DECRYPT_MODE, update() then doFinal(). update()
|
||||
* buffers and returns null. */
|
||||
assertNull(enc.update(data, 0, 8));
|
||||
assertNull(enc.update(data, 8, data.length - 8));
|
||||
assertArrayEquals("update()+doFinal() encrypt does not match",
|
||||
expected, enc.doFinal());
|
||||
|
||||
part = dec.update(expected, 0, 8);
|
||||
assertTrue(part == null || part.length == 0);
|
||||
assertNull(dec.update(expected, 0, 8));
|
||||
assertArrayEquals("update()+doFinal() decrypt does not match",
|
||||
data, dec.doFinal(expected, 8, expected.length - 8));
|
||||
}
|
||||
|
|
@ -777,8 +789,7 @@ public class WolfCryptCipherAesWrapTest {
|
|||
|
||||
/* chunked update() */
|
||||
int half = (sz / 2) & ~7;
|
||||
byte[] u = enc.update(data, 0, half);
|
||||
assertTrue(u == null || u.length == 0);
|
||||
assertNull(enc.update(data, 0, half));
|
||||
assertArrayEquals(ct, enc.doFinal(data, half, sz - half));
|
||||
}
|
||||
}
|
||||
|
|
@ -911,11 +922,12 @@ public class WolfCryptCipherAesWrapTest {
|
|||
assumeAes128();
|
||||
|
||||
Cipher c = Cipher.getInstance("AESWrap", jceProvider);
|
||||
assertNull("getIV() must be null before init()", c.getIV());
|
||||
assertArrayEquals(REPORTED_DEFAULT_IV, paramsIv(c.getParameters()));
|
||||
|
||||
c.init(Cipher.WRAP_MODE, aesKey(KEK_128));
|
||||
assertNull("getIV() must be null when no IV was set", c.getIV());
|
||||
assertNull("getParameters() must be null when no IV was set",
|
||||
c.getParameters());
|
||||
assertArrayEquals(REPORTED_DEFAULT_IV, c.getIV());
|
||||
assertArrayEquals(REPORTED_DEFAULT_IV, paramsIv(c.getParameters()));
|
||||
byte[] a = c.wrap(aesKey(DATA_128));
|
||||
|
||||
c.init(Cipher.WRAP_MODE, aesKey(KEK_128),
|
||||
|
|
@ -925,7 +937,7 @@ public class WolfCryptCipherAesWrapTest {
|
|||
|
||||
c.init(Cipher.WRAP_MODE, aesKey(KEK_128),
|
||||
(AlgorithmParameterSpec)null);
|
||||
assertNull(c.getIV());
|
||||
assertArrayEquals(REPORTED_DEFAULT_IV, c.getIV());
|
||||
byte[] d = c.wrap(aesKey(DATA_128));
|
||||
|
||||
assertArrayEquals(WRAP_128_KEK128, a);
|
||||
|
|
@ -934,11 +946,11 @@ public class WolfCryptCipherAesWrapTest {
|
|||
|
||||
/* A SecureRandom must never be used to invent an IV */
|
||||
c.init(Cipher.WRAP_MODE, aesKey(KEK_128), secureRandom);
|
||||
assertNull(c.getIV());
|
||||
assertArrayEquals(REPORTED_DEFAULT_IV, c.getIV());
|
||||
assertArrayEquals(WRAP_128_KEK128, c.wrap(aesKey(DATA_128)));
|
||||
|
||||
c.init(Cipher.ENCRYPT_MODE, aesKey(KEK_128), secureRandom);
|
||||
assertNull(c.getIV());
|
||||
assertArrayEquals(REPORTED_DEFAULT_IV, c.getIV());
|
||||
assertArrayEquals(WRAP_128_KEK128, c.doFinal(DATA_128));
|
||||
}
|
||||
|
||||
|
|
@ -1738,9 +1750,12 @@ public class WolfCryptCipherAesWrapTest {
|
|||
wolfWrap.init(Cipher.WRAP_MODE, kek);
|
||||
byte[] wrapped = wolfWrap.wrap(key);
|
||||
|
||||
/* Our getParameters() must init the interop provider on
|
||||
* any Java version */
|
||||
Cipher otherUnwrap = Cipher.getInstance("AESWrap",
|
||||
interopProvider);
|
||||
otherUnwrap.init(Cipher.UNWRAP_MODE, kek);
|
||||
otherUnwrap.init(Cipher.UNWRAP_MODE, kek,
|
||||
wolfWrap.getParameters());
|
||||
Key out = otherUnwrap.unwrap(wrapped, "AES",
|
||||
Cipher.SECRET_KEY);
|
||||
assertArrayEquals("wolfJCE wrap -> " + interopProvider +
|
||||
|
|
@ -1751,6 +1766,10 @@ public class WolfCryptCipherAesWrapTest {
|
|||
Cipher otherWrap = Cipher.getInstance("AESWrap",
|
||||
interopProvider);
|
||||
otherWrap.init(Cipher.WRAP_MODE, kek);
|
||||
if (interopProvider.equals("SunJCE")) {
|
||||
assertArrayEquals("getIV() must match SunJCE",
|
||||
otherWrap.getIV(), wolfWrap.getIV());
|
||||
}
|
||||
byte[] wrapped2 = otherWrap.wrap(key);
|
||||
assertArrayEquals("both providers must produce identical " +
|
||||
"wrapped output", wrapped, wrapped2);
|
||||
|
|
@ -2123,9 +2142,10 @@ public class WolfCryptCipherAesWrapTest {
|
|||
assertArrayEquals(DATA_128, u.unwrap(wrapped, "AES",
|
||||
Cipher.SECRET_KEY).getEncoded());
|
||||
|
||||
/* no IV set, no parameters regardless of provider lookup */
|
||||
/* no IV set, default IV reported per Java version */
|
||||
c.init(Cipher.WRAP_MODE, aesKey(KEK_128));
|
||||
assertNull(c.getParameters());
|
||||
assertArrayEquals(REPORTED_DEFAULT_IV,
|
||||
paramsIv(c.getParameters()));
|
||||
|
||||
} finally {
|
||||
Security.insertProviderAt(saved, savedPos);
|
||||
|
|
|
|||
|
|
@ -1660,4 +1660,37 @@ public class WolfCryptUtilTest {
|
|||
assertFalse(WolfCryptUtil.constantTimeEquals(a,
|
||||
"passwordpasswordX".toCharArray()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetJavaVersion() {
|
||||
final String prop = "java.specification.version";
|
||||
String[] specs = { "1.8", "11", "17", "0.9", "", "abc", "." };
|
||||
int[] expected = { 8, 11, 17, 0, 0, 0, 0 };
|
||||
|
||||
int ver = WolfCryptUtil.getJavaVersion();
|
||||
|
||||
/* 0 on Android, otherwise Java 8+ */
|
||||
assertTrue("unexpected Java version " + ver, ver == 0 || ver >= 8);
|
||||
|
||||
/* Mutates a global System property, restored in finally */
|
||||
String orig = System.getProperty(prop);
|
||||
try {
|
||||
for (int i = 0; i < specs.length; i++) {
|
||||
System.setProperty(prop, specs[i]);
|
||||
/* Android ignores changes to this property */
|
||||
Assume.assumeTrue(prop + " is read-only",
|
||||
specs[i].equals(System.getProperty(prop)));
|
||||
assertEquals("\"" + specs[i] + "\"", expected[i],
|
||||
WolfCryptUtil.getJavaVersion());
|
||||
}
|
||||
|
||||
System.clearProperty(prop);
|
||||
assertEquals(0, WolfCryptUtil.getJavaVersion());
|
||||
|
||||
} finally {
|
||||
if (orig != null) {
|
||||
System.setProperty(prop, orig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue