1 /**
2  * @license
3  * Copyright 2016 Google Inc. All rights reserved.
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.security.wycheproof;
18 
19 import com.google.security.wycheproof.WycheproofRunner.ProviderType;
20 import com.google.security.wycheproof.WycheproofRunner.SlowTest;
21 import java.math.BigInteger;
22 import java.security.GeneralSecurityException;
23 import java.security.KeyPair;
24 import java.security.KeyPairGenerator;
25 import java.security.NoSuchAlgorithmException;
26 import java.security.PrivateKey;
27 import java.security.PublicKey;
28 import java.util.Arrays;
29 import javax.crypto.Cipher;
30 import javax.crypto.spec.DHParameterSpec;
31 import junit.framework.TestCase;
32 
33 /**
34  * Testing DHIES.
35  *
36  * @author bleichen@google.com (Daniel Bleichenbacher)
37  */
38 // TODO(bleichen):
39 // - maybe again CipherInputStream, CipherOutputStream,
40 // - byteBuffer.
41 // - Exception handling
42 // - Is DHIES using the key derivation function for the key stream?
43 // - BouncyCastle knows an algorithm IES. Is this the same as DHIES?
44 // - Bouncy fixed a padding oracle bug in version 1.56 (CVE-2016-1000345)
45 //   So far we have no test for this bug mainly because this cannot be tested
46 //   through the JCA interface. BC does not register and algorithm such as
47 //   Cipher.DHIESWITHAES-CBC.
48 // - So far only BouncyCastles is tesed because this is the only provider
49 //   we use that implements DHIES.
50 public class DhiesTest extends TestCase {
51 
52   // TODO(bleichen): This is the same as DhTest.java
53   //   We could move this into some TestUtil.
ike2048()54   public DHParameterSpec ike2048() {
55     final BigInteger p =
56         new BigInteger(
57             "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
58                 + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
59                 + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
60                 + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
61                 + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
62                 + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
63                 + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
64                 + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
65                 + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
66                 + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
67                 + "15728E5A8AACAA68FFFFFFFFFFFFFFFF",
68             16);
69     final BigInteger g = new BigInteger("2");
70     return new DHParameterSpec(p, g);
71   }
72 
73   /**
74    * WARNING: This test uses weak crypto (i.e. DHIESWithAES), if supported. Checks that key
75    * agreement using DHIES works in the sense that it can decrypt what it encrypts. Unfortunately it
76    * seems that there is no secure mode using AES.
77    */
78   @SuppressWarnings("InsecureCryptoUsage")
testDhiesBasic()79   public void testDhiesBasic() throws Exception {
80     DHParameterSpec params = ike2048();
81     KeyPairGenerator kf = KeyPairGenerator.getInstance("DH");
82     kf.initialize(params);
83     KeyPair keyPair = kf.generateKeyPair();
84     PrivateKey priv = keyPair.getPrivate();
85     PublicKey pub = keyPair.getPublic();
86     byte[] message = "Hello".getBytes("UTF-8");
87     Cipher dhies;
88     try {
89       dhies = Cipher.getInstance("DHIESwithAES");
90     } catch (NoSuchAlgorithmException ex) {
91       // The algorithm isn't supported - even better!
92       return;
93     }
94     dhies.init(Cipher.ENCRYPT_MODE, pub);
95     byte[] ciphertext = dhies.doFinal(message);
96     System.out.println("testDhiesBasic:" + TestUtil.bytesToHex(ciphertext));
97     dhies.init(Cipher.DECRYPT_MODE, priv);
98     byte[] decrypted = dhies.doFinal(ciphertext);
99     assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted));
100   }
101 
102   /**
103    * WARNING: This test uses weak crypto (i.e. DHIESWithAES). DHIES should be secure against chosen
104    * ciphertexts. Checks that a modification of the ciphertext is dectected.
105    */
106   @SlowTest(providers = {ProviderType.BOUNCY_CASTLE, ProviderType.SPONGY_CASTLE})
107   @SuppressWarnings("InsecureCryptoUsage")
testDhiesCorrupt()108   public void testDhiesCorrupt() throws Exception {
109     KeyPairGenerator kf = KeyPairGenerator.getInstance("DH");
110     kf.initialize(ike2048());
111     KeyPair keyPair = kf.generateKeyPair();
112     PrivateKey priv = keyPair.getPrivate();
113     PublicKey pub = keyPair.getPublic();
114     byte[] message = new byte[32];
115     Cipher dhies;
116     try {
117       dhies = Cipher.getInstance("DHIESwithAES");
118     } catch (NoSuchAlgorithmException ex) {
119       // The algorithm isn't supported - even better!
120       return;
121     }
122     dhies.init(Cipher.ENCRYPT_MODE, pub);
123     byte[] ciphertext = dhies.doFinal(message);
124     for (int i = 0; i < ciphertext.length; i++) {
125       byte[] corrupt = Arrays.copyOf(ciphertext, ciphertext.length);
126       corrupt[i] ^= (byte) 1;
127       try {
128         dhies.init(Cipher.DECRYPT_MODE, priv);
129         dhies.doFinal(corrupt);
130         fail("Corrupt ciphertext accepted:" + i);
131       } catch (GeneralSecurityException ex) {
132         // This is expected
133       }
134     }
135   }
136 
137   /**
138    * Tries to detect if an algorithm is using ECB. Unfortunately, many JCE algorithms use ECB if no
139    * encryption mode is specified.
140    */
141   @SuppressWarnings("InsecureCryptoUsage")
testNotEcb(String algorithm)142   public void testNotEcb(String algorithm) throws Exception {
143     Cipher dhies;
144     try {
145       dhies = Cipher.getInstance(algorithm);
146     } catch (NoSuchAlgorithmException ex) {
147       // This test is called with short algorithm names such as just "DHIES".
148       // Requiring full names is typically a good practice. Hence it is OK
149       // to not assigning default algorithms.
150       System.out.println("No implementation for:" + algorithm);
151       return;
152     }
153     KeyPairGenerator kf = KeyPairGenerator.getInstance("DH");
154     kf.initialize(ike2048());
155     KeyPair keyPair = kf.generateKeyPair();
156     PublicKey pub = keyPair.getPublic();
157     byte[] message = new byte[512];
158     dhies.init(Cipher.ENCRYPT_MODE, pub);
159     byte[] ciphertext = dhies.doFinal(message);
160     for (int i = 0; i + 32 <= ciphertext.length; i++) {
161       String block1 = TestUtil.bytesToHex(Arrays.copyOfRange(ciphertext, i, i + 16));
162       String block2 = TestUtil.bytesToHex(Arrays.copyOfRange(ciphertext, i + 16, i + 32));
163       assertTrue(
164           "Ciphertext repeats at " + i + ":" + TestUtil.bytesToHex(ciphertext),
165           !block1.equals(block2));
166     }
167   }
168 
testSemanticSecurityDhies()169   public void testSemanticSecurityDhies() throws Exception {
170     testNotEcb("DHIES");
171   }
172 }
173