|
23 | 23 |
|
24 | 24 | /*
|
25 | 25 | * @test
|
26 |
| - * @bug 8248268 8268621 |
| 26 | + * @bug 8248268 8268621 8271745 |
27 | 27 | * @summary Verify general properties of the AES/KW/NoPadding,
|
28 |
| - * AES/KW/PKCS5Padding, and AES/KWP/NoPadding. |
| 28 | + * AES/KW/PKCS5Padding, and AES/KWP/NoPadding impls of SunJCE provider. |
29 | 29 | * @run main TestGeneral
|
30 | 30 | */
|
| 31 | +import java.nio.ByteBuffer; |
31 | 32 | import java.util.Arrays;
|
32 | 33 | import java.security.Key;
|
| 34 | +import java.security.KeyPairGenerator; |
| 35 | +import java.security.PrivateKey; |
33 | 36 | import java.security.InvalidAlgorithmParameterException;
|
| 37 | +import java.security.AlgorithmParameters; |
34 | 38 | import javax.crypto.*;
|
35 | 39 | import javax.crypto.spec.*;
|
36 | 40 |
|
37 | 41 | public class TestGeneral {
|
38 | 42 |
|
39 |
| - private static final byte[] DATA_128 = |
40 |
| - Arrays.copyOf("1234567890123456789012345678901234".getBytes(), 128); |
| 43 | + private static final byte[] DATA_32 = |
| 44 | + Arrays.copyOf("1234567890123456789012345678901234".getBytes(), 32); |
41 | 45 | private static final SecretKey KEY =
|
42 |
| - new SecretKeySpec(DATA_128, 0, 16, "AES"); |
| 46 | + new SecretKeySpec(DATA_32, 0, 16, "AES"); |
43 | 47 | private static final int KW_IV_LEN = 8;
|
44 | 48 | private static final int KWP_IV_LEN = 4;
|
45 |
| - private static final int MAX_KW_PKCS5PAD_LEN = 16; // 1-16 |
46 |
| - private static final int MAX_KWP_PAD_LEN = 7; // 0...7 |
47 |
| - |
48 |
| - public static void testEnc(Cipher c, byte[] in, int inLen, int ivLen, |
49 |
| - int maxPadLen) throws Exception { |
50 |
| - |
51 |
| - System.out.println("input len: " + inLen); |
52 |
| - c.init(Cipher.ENCRYPT_MODE, KEY, new IvParameterSpec(in, 0, ivLen)); |
53 |
| - |
54 |
| - int estOutLen = c.getOutputSize(inLen); |
55 |
| - |
56 |
| - byte[] out = c.doFinal(in, 0, inLen); |
57 |
| - |
58 |
| - // for encryption output, the estimate should match the actual |
59 |
| - if (estOutLen != out.length) { |
60 |
| - System.out.println("=> estimated: " + estOutLen); |
61 |
| - System.out.println("=> actual enc out length: " + out.length); |
62 |
| - throw new RuntimeException("Failed enc output len check"); |
63 |
| - } |
64 |
| - |
65 |
| - // encryption outout should always be multiple of 8 and at least 8-byte |
66 |
| - // longer than input |
67 |
| - if ((out.length % 8 != 0) || (out.length - inLen < 8)) { |
68 |
| - throw new RuntimeException("Invalid length of encrypted data: " + |
69 |
| - out.length); |
70 |
| - } |
71 |
| - |
72 |
| - c.init(Cipher.DECRYPT_MODE, KEY, new IvParameterSpec(in, 0, ivLen)); |
73 |
| - estOutLen = c.getOutputSize(out.length); |
74 |
| - |
75 |
| - byte[] in2 = c.doFinal(out); |
76 |
| - |
77 |
| - // for decryption output, the estimate should match the actual for |
78 |
| - // AES/KW/NoPadding and slightly larger than the actual for the rest |
79 |
| - if (estOutLen < in2.length || (estOutLen - in2.length) > maxPadLen) { |
80 |
| - System.out.println("=> estimated: " + estOutLen); |
81 |
| - System.out.println("=> actual dec out length: " + in2.length); |
82 |
| - throw new RuntimeException("Failed dec output len check"); |
83 |
| - } |
84 |
| - |
85 |
| - if (!Arrays.equals(in, 0, inLen, in2, 0, inLen)) { |
86 |
| - throw new RuntimeException("Failed decrypted data check"); |
| 49 | + private static final int MAX_KW_PKCS5PAD_LEN = 8; // 1-8 |
| 50 | + private static final int MAX_KWP_PAD_LEN = 7; // 0-7 |
| 51 | + |
| 52 | + public static void testEnc(Cipher c, byte[] in, int startLen, int inc, |
| 53 | + IvParameterSpec[] ivs, int maxPadLen) throws Exception { |
| 54 | + |
| 55 | + System.out.println("testEnc, input len=" + startLen + " w/ inc=" + |
| 56 | + inc); |
| 57 | + |
| 58 | + for (IvParameterSpec iv : ivs) { |
| 59 | + System.out.print("\t=> w/ iv=" + iv); |
| 60 | + |
| 61 | + for (int inLen = startLen; inLen < in.length; inLen+=inc) { |
| 62 | + c.init(Cipher.ENCRYPT_MODE, KEY, iv); |
| 63 | + |
| 64 | + int estOutLen = c.getOutputSize(inLen); |
| 65 | + System.out.println(", inLen=" + inLen); |
| 66 | + byte[] out = c.doFinal(in, 0, inLen); |
| 67 | + |
| 68 | + // check the length of encryption output |
| 69 | + if (estOutLen != out.length || (out.length % 8 != 0) || |
| 70 | + (out.length - inLen < 8)) { |
| 71 | + System.out.println("=> estimated: " + estOutLen); |
| 72 | + System.out.println("=> actual: " + out.length); |
| 73 | + throw new RuntimeException("Failed enc output len check"); |
| 74 | + } |
| 75 | + |
| 76 | + c.init(Cipher.DECRYPT_MODE, KEY, iv); |
| 77 | + estOutLen = c.getOutputSize(out.length); |
| 78 | + byte[] recovered = new byte[estOutLen]; |
| 79 | + |
| 80 | + // do decryption using ByteBuffer and multi-part |
| 81 | + ByteBuffer outBB = ByteBuffer.wrap(out); |
| 82 | + ByteBuffer recoveredBB = ByteBuffer.wrap(recovered); |
| 83 | + int len = c.update(outBB, recoveredBB); |
| 84 | + len += c.doFinal(outBB, recoveredBB); |
| 85 | + |
| 86 | + // check the length of decryption output |
| 87 | + if (estOutLen < len || (estOutLen - len) > maxPadLen) { |
| 88 | + System.out.println("=> estimated: " + estOutLen); |
| 89 | + System.out.println("=> actual: " + len); |
| 90 | + throw new RuntimeException("Failed dec output len check"); |
| 91 | + } |
| 92 | + |
| 93 | + if (!Arrays.equals(in, 0, inLen, recovered, 0, len)) { |
| 94 | + throw new RuntimeException("Failed decrypted data check"); |
| 95 | + } |
| 96 | + } |
87 | 97 | }
|
88 | 98 | }
|
89 | 99 |
|
90 |
| - public static void testWrap(Cipher c, byte[] in, int inLen, int ivLen, |
| 100 | + public static void testWrap(Cipher c, Key[] inKeys, IvParameterSpec[] ivs, |
91 | 101 | int maxPadLen) throws Exception {
|
92 | 102 |
|
93 |
| - System.out.println("key len: " + inLen); |
94 |
| - c.init(Cipher.WRAP_MODE, KEY, new IvParameterSpec(in, 0, ivLen)); |
95 |
| - |
96 |
| - int estOutLen = c.getOutputSize(inLen); |
97 |
| - |
98 |
| - byte[] out = c.wrap(new SecretKeySpec(in, 0, inLen, "Any")); |
99 |
| - |
100 |
| - // for encryption output, the estimate should match the actual |
101 |
| - if (estOutLen != out.length) { |
102 |
| - System.out.println("=> estimated: " + estOutLen); |
103 |
| - System.out.println("=> actual wrap out length: " + out.length); |
104 |
| - throw new RuntimeException("Failed wrap output len check"); |
105 |
| - } |
106 |
| - |
107 |
| - // encryption outout should always be multiple of 8 and at least 8-byte |
108 |
| - // longer than input |
109 |
| - if ((out.length % 8 != 0) || (out.length - inLen < 8)) { |
110 |
| - throw new RuntimeException("Invalid length of encrypted data: " + |
111 |
| - out.length); |
112 |
| - } |
113 |
| - c.init(Cipher.UNWRAP_MODE, KEY, new IvParameterSpec(in, 0, ivLen)); |
114 |
| - estOutLen = c.getOutputSize(out.length); |
115 |
| - |
116 |
| - Key key2 = c.unwrap(out, "Any", Cipher.SECRET_KEY); |
117 |
| - |
118 |
| - if (!(key2 instanceof SecretKey)) { |
119 |
| - throw new RuntimeException("Failed unwrap output type check"); |
| 103 | + for (Key inKey : inKeys) { |
| 104 | + System.out.println("testWrap, key: " + inKey); |
| 105 | + for (IvParameterSpec iv : ivs) { |
| 106 | + System.out.println("\t=> w/ iv " + iv); |
| 107 | + |
| 108 | + c.init(Cipher.WRAP_MODE, KEY, iv); |
| 109 | + |
| 110 | + byte[] out = c.wrap(inKey); |
| 111 | + |
| 112 | + // output should always be multiple of cipher block size |
| 113 | + if (out.length % c.getBlockSize() != 0) { |
| 114 | + throw new RuntimeException("Invalid wrap len: " + |
| 115 | + out.length); |
| 116 | + } |
| 117 | + |
| 118 | + c.init(Cipher.UNWRAP_MODE, KEY, iv); |
| 119 | + |
| 120 | + // SecretKey or PrivateKey |
| 121 | + int keyType = (inKey instanceof SecretKey? Cipher.SECRET_KEY : |
| 122 | + Cipher.PRIVATE_KEY); |
| 123 | + |
| 124 | + int estOutLen = c.getOutputSize(out.length); |
| 125 | + Key key2 = c.unwrap(out, inKey.getAlgorithm(), keyType); |
| 126 | + |
| 127 | + if ((keyType == Cipher.SECRET_KEY && |
| 128 | + !(key2 instanceof SecretKey)) || |
| 129 | + (keyType == Cipher.PRIVATE_KEY && |
| 130 | + !(key2 instanceof PrivateKey))) { |
| 131 | + throw new RuntimeException("Failed unwrap type check"); |
| 132 | + } |
| 133 | + |
| 134 | + byte[] in2 = key2.getEncoded(); |
| 135 | + // check decryption output length |
| 136 | + if (estOutLen < in2.length || |
| 137 | + (estOutLen - in2.length) > maxPadLen) { |
| 138 | + System.out.println("=> estimated: " + estOutLen); |
| 139 | + System.out.println("=> actual: " + in2.length); |
| 140 | + throw new RuntimeException("Failed unwrap len check"); |
| 141 | + } |
| 142 | + |
| 143 | + if (!Arrays.equals(inKey.getEncoded(), in2) || |
| 144 | + !(inKey.getAlgorithm().equalsIgnoreCase |
| 145 | + (key2.getAlgorithm()))) { |
| 146 | + throw new RuntimeException("Failed unwrap key check"); |
| 147 | + } |
| 148 | + } |
120 | 149 | }
|
| 150 | + } |
121 | 151 |
|
122 |
| - byte[] in2 = key2.getEncoded(); |
123 |
| - // for decryption output, the estimate should match the actual for |
124 |
| - // AES/KW/NoPadding and slightly larger than the actual for the rest |
125 |
| - if (estOutLen < in2.length || (estOutLen - in2.length) > maxPadLen) { |
126 |
| - System.out.println("=> estimated: " + estOutLen); |
127 |
| - System.out.println("=> actual unwrap out length: " + in2.length); |
128 |
| - throw new RuntimeException("Failed unwrap output len check"); |
129 |
| - } |
| 152 | + public static void testIv(Cipher c, int defIvLen, boolean allowCustomIv) |
| 153 | + throws Exception { |
130 | 154 |
|
131 |
| - if (inLen != in2.length || |
132 |
| - !Arrays.equals(in, 0, inLen, in2, 0, inLen)) { |
133 |
| - throw new RuntimeException("Failed unwrap data check"); |
134 |
| - } |
135 |
| - } |
| 155 | + System.out.println("testIv: defIvLen = " + defIvLen + |
| 156 | + " allowCustomIv = " + allowCustomIv); |
136 | 157 |
|
137 |
| - public static void testIv(Cipher c) throws Exception { |
138 | 158 | // get a fresh Cipher instance so we can test iv with pre-init state
|
139 |
| - Cipher c2 = Cipher.getInstance(c.getAlgorithm(), c.getProvider()); |
140 |
| - if (c2.getIV() != null) { |
| 159 | + c = Cipher.getInstance(c.getAlgorithm(), c.getProvider()); |
| 160 | + if (c.getIV() != null) { |
141 | 161 | throw new RuntimeException("Expects null iv");
|
142 | 162 | }
|
143 |
| - if (c2.getParameters() == null) { |
| 163 | + |
| 164 | + AlgorithmParameters ivParams = c.getParameters(); |
| 165 | + if (ivParams == null) { |
144 | 166 | throw new RuntimeException("Expects non-null default parameters");
|
145 | 167 | }
|
146 |
| - |
147 |
| - c2.init(Cipher.ENCRYPT_MODE, KEY); |
148 |
| - byte[] defIv2 = c2.getIV(); |
| 168 | + IvParameterSpec ivSpec = |
| 169 | + ivParams.getParameterSpec(IvParameterSpec.class); |
| 170 | + byte[] iv = ivSpec.getIV(); |
| 171 | + // try through all opmodes |
149 | 172 | c.init(Cipher.ENCRYPT_MODE, KEY);
|
| 173 | + c.init(Cipher.DECRYPT_MODE, KEY); |
| 174 | + c.init(Cipher.WRAP_MODE, KEY); |
| 175 | + c.init(Cipher.UNWRAP_MODE, KEY); |
| 176 | + |
150 | 177 | byte[] defIv = c.getIV();
|
151 |
| - if (!Arrays.equals(defIv, defIv2)) { |
| 178 | + |
| 179 | + // try again through all opmodes |
| 180 | + c.init(Cipher.ENCRYPT_MODE, KEY); |
| 181 | + c.init(Cipher.DECRYPT_MODE, KEY); |
| 182 | + c.init(Cipher.WRAP_MODE, KEY); |
| 183 | + c.init(Cipher.UNWRAP_MODE, KEY); |
| 184 | + |
| 185 | + byte[] defIv2 = c.getIV(); |
| 186 | + if (iv.length != defIvLen || !Arrays.equals(iv, defIv) || |
| 187 | + !Arrays.equals(defIv, defIv2)) { |
152 | 188 | throw new RuntimeException("Failed default iv check");
|
153 | 189 | }
|
| 190 | + if (defIv == defIv2) { |
| 191 | + throw new RuntimeException("Failed getIV copy check"); |
| 192 | + } |
| 193 | + |
154 | 194 | // try init w/ an iv w/ invalid length
|
155 | 195 | try {
|
156 | 196 | c.init(Cipher.ENCRYPT_MODE, KEY, new IvParameterSpec(defIv, 0,
|
157 |
| - defIv.length/2)); |
| 197 | + defIv.length/2)); |
158 | 198 | throw new RuntimeException("Invalid iv accepted");
|
159 | 199 | } catch (InvalidAlgorithmParameterException iape) {
|
160 | 200 | System.out.println("Invalid IV rejected as expected");
|
161 | 201 | }
|
162 |
| - Arrays.fill(defIv, (byte) 0xFF); |
163 |
| - c.init(Cipher.ENCRYPT_MODE, KEY, new IvParameterSpec(defIv)); |
164 |
| - byte[] newIv = c.getIV(); |
165 |
| - if (!Arrays.equals(newIv, defIv)) { |
166 |
| - throw new RuntimeException("Failed set iv check"); |
167 |
| - } |
168 |
| - byte[] newIv2 = c.getIV(); |
169 |
| - if (newIv == newIv2) { |
170 |
| - throw new RuntimeException("Failed getIV copy check"); |
| 202 | + |
| 203 | + if (allowCustomIv) { |
| 204 | + Arrays.fill(defIv, (byte) 0xFF); |
| 205 | + // try through all opmodes |
| 206 | + c.init(Cipher.ENCRYPT_MODE, KEY, new IvParameterSpec(defIv)); |
| 207 | + c.init(Cipher.DECRYPT_MODE, KEY, new IvParameterSpec(defIv)); |
| 208 | + c.init(Cipher.WRAP_MODE, KEY, new IvParameterSpec(defIv)); |
| 209 | + c.init(Cipher.UNWRAP_MODE, KEY, new IvParameterSpec(defIv)); |
| 210 | + |
| 211 | + if (!Arrays.equals(defIv, c.getIV())) { |
| 212 | + throw new RuntimeException("Failed set iv check"); |
| 213 | + } |
171 | 214 | }
|
172 | 215 | }
|
173 | 216 |
|
174 | 217 | public static void main(String[] argv) throws Exception {
|
175 |
| - byte[] data = DATA_128; |
176 |
| - |
177 |
| - String ALGO = "AES/KW/PKCS5Padding"; |
178 |
| - System.out.println("Testing " + ALGO); |
179 |
| - Cipher c = Cipher.getInstance(ALGO, "SunJCE"); |
180 |
| - |
181 |
| - // test all possible pad lengths, i.e. 1 - 16 |
182 |
| - for (int i = 1; i <= MAX_KW_PKCS5PAD_LEN; i++) { |
183 |
| - testEnc(c, data, data.length - i, KW_IV_LEN, MAX_KW_PKCS5PAD_LEN); |
184 |
| - testWrap(c, data, data.length - i, KW_IV_LEN, MAX_KW_PKCS5PAD_LEN); |
| 218 | + byte[] data = DATA_32; |
| 219 | + |
| 220 | + SecretKey aes256 = new SecretKeySpec(DATA_32, "AES"); |
| 221 | + SecretKey any256 = new SecretKeySpec(DATA_32, "ANY"); |
| 222 | + PrivateKey priv = KeyPairGenerator.getInstance |
| 223 | + ("RSA", "SunRsaSign").generateKeyPair().getPrivate(); |
| 224 | + |
| 225 | + String[] algos = { |
| 226 | + "AES/KW/PKCS5Padding", "AES/KW/NoPadding", "AES/KWP/NoPadding" |
| 227 | + }; |
| 228 | + |
| 229 | + for (String a : algos) { |
| 230 | + System.out.println("Testing " + a); |
| 231 | + Cipher c = Cipher.getInstance(a, "SunJCE"); |
| 232 | + |
| 233 | + int blkSize = c.getBlockSize(); |
| 234 | + |
| 235 | + // set the default based on AES/KWP/NoPadding, the other two |
| 236 | + // override as needed |
| 237 | + int startLen = data.length - blkSize; |
| 238 | + int inc = 1; |
| 239 | + IvParameterSpec[] ivs = new IvParameterSpec[] { null }; |
| 240 | + int padLen = MAX_KWP_PAD_LEN; |
| 241 | + Key[] keys = new Key[] { aes256, any256, priv }; |
| 242 | + int ivLen = KWP_IV_LEN; |
| 243 | + boolean allowCustomIv = false; |
| 244 | + |
| 245 | + switch (a) { |
| 246 | + case "AES/KW/PKCS5Padding": |
| 247 | + ivs = new IvParameterSpec[] { |
| 248 | + null, new IvParameterSpec(DATA_32, 0, KW_IV_LEN) }; |
| 249 | + padLen = MAX_KW_PKCS5PAD_LEN; |
| 250 | + ivLen = KW_IV_LEN; |
| 251 | + allowCustomIv = true; |
| 252 | + break; |
| 253 | + case "AES/KW/NoPadding": |
| 254 | + startLen = data.length >> 1; |
| 255 | + inc = blkSize; |
| 256 | + ivs = new IvParameterSpec[] { |
| 257 | + null, new IvParameterSpec(DATA_32, 0, KW_IV_LEN) }; |
| 258 | + padLen = 0; |
| 259 | + keys = new Key[] { aes256, any256 }; |
| 260 | + ivLen = KW_IV_LEN; |
| 261 | + allowCustomIv = true; |
| 262 | + break; |
| 263 | + } |
| 264 | + |
| 265 | + // now test based on the configured arguments |
| 266 | + testEnc(c, data, startLen, inc, ivs, padLen); |
| 267 | + testWrap(c, keys, ivs, padLen); |
| 268 | + testIv(c, ivLen, allowCustomIv); |
185 | 269 | }
|
186 |
| - testIv(c); |
187 |
| - |
188 |
| - ALGO = "AES/KW/NoPadding"; |
189 |
| - System.out.println("Testing " + ALGO); |
190 |
| - c = Cipher.getInstance(ALGO, "SunJCE"); |
191 |
| - testEnc(c, data, data.length, KW_IV_LEN, 0); |
192 |
| - testEnc(c, data, data.length >> 1, KW_IV_LEN, 0); |
193 |
| - testWrap(c, data, data.length, KW_IV_LEN, 0); |
194 |
| - testWrap(c, data, data.length >> 1, KW_IV_LEN, 0); |
195 |
| - testIv(c); |
196 |
| - |
197 |
| - ALGO = "AES/KWP/NoPadding"; |
198 |
| - System.out.println("Testing " + ALGO); |
199 |
| - c = Cipher.getInstance(ALGO, "SunJCE"); |
200 |
| - |
201 |
| - // test all possible pad lengths, i.e. 0 - 7 |
202 |
| - for (int i = 0; i <= MAX_KWP_PAD_LEN; i++) { |
203 |
| - testEnc(c, data, data.length - i, KWP_IV_LEN, MAX_KWP_PAD_LEN); |
204 |
| - testWrap(c, data, data.length - i, KWP_IV_LEN, MAX_KWP_PAD_LEN); |
205 |
| - } |
206 |
| - testIv(c); |
207 |
| - |
208 | 270 | System.out.println("All Tests Passed");
|
209 | 271 | }
|
210 | 272 | }
|
0 commit comments