c0d6d4f74e4ee63a2c489d1ad76e812d029d6f98
[gigi.git] / tests / club / wpia / gigi / testUtils / ConfiguredTest.java
1 package club.wpia.gigi.testUtils;
2
3 import static org.junit.Assert.*;
4
5 import java.io.File;
6 import java.io.FileInputStream;
7 import java.io.FileOutputStream;
8 import java.io.IOException;
9 import java.io.ObjectInputStream;
10 import java.io.ObjectOutputStream;
11 import java.math.BigInteger;
12 import java.security.GeneralSecurityException;
13 import java.security.KeyFactory;
14 import java.security.KeyPair;
15 import java.security.KeyPairGenerator;
16 import java.security.PrivateKey;
17 import java.security.PublicKey;
18 import java.security.SecureRandom;
19 import java.security.Signature;
20 import java.security.spec.RSAPrivateKeySpec;
21 import java.security.spec.RSAPublicKeySpec;
22 import java.sql.SQLException;
23 import java.sql.Timestamp;
24 import java.text.SimpleDateFormat;
25 import java.util.Calendar;
26 import java.util.Date;
27 import java.util.Properties;
28 import java.util.Random;
29 import java.util.TimeZone;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32
33 import org.junit.AfterClass;
34 import org.junit.BeforeClass;
35
36 import club.wpia.gigi.Gigi;
37 import club.wpia.gigi.GigiApiException;
38 import club.wpia.gigi.database.DatabaseConnection;
39 import club.wpia.gigi.database.DatabaseConnection.Link;
40 import club.wpia.gigi.database.GigiPreparedStatement;
41 import club.wpia.gigi.database.SQLFileManager.ImportType;
42 import club.wpia.gigi.dbObjects.CATS;
43 import club.wpia.gigi.dbObjects.CATS.CATSType;
44 import club.wpia.gigi.dbObjects.CertificateProfile;
45 import club.wpia.gigi.dbObjects.Domain;
46 import club.wpia.gigi.dbObjects.DomainPingType;
47 import club.wpia.gigi.dbObjects.User;
48 import club.wpia.gigi.passwords.PasswordStrengthChecker;
49 import club.wpia.gigi.testUtils.TestEmailReceiver.TestMail;
50 import club.wpia.gigi.util.DatabaseManager;
51 import club.wpia.gigi.util.DomainAssessment;
52 import club.wpia.gigi.util.Notary;
53 import club.wpia.gigi.util.PEM;
54 import club.wpia.gigi.util.PasswordHash;
55 import club.wpia.gigi.util.ServerConstants;
56 import club.wpia.gigi.util.TimeConditions;
57 import sun.security.pkcs10.PKCS10;
58 import sun.security.pkcs10.PKCS10Attributes;
59 import sun.security.x509.X500Name;
60
61 /**
62  * Base class for a Testsuite that makes use of the config variables that define
63  * the environment.
64  */
65 public abstract class ConfiguredTest {
66
67     static Properties testProps = new Properties();
68
69     public static Properties getTestProps() {
70         return testProps;
71     }
72
73     private static boolean envInited = false;
74
75     /**
76      * Some password that fulfills the password criteria.
77      */
78     public static final String TEST_PASSWORD = "xvXV12°§";
79
80     public static final String DIFFICULT_CHARS = "ÜÖÄß𐀀";
81
82     @BeforeClass
83     public static void initEnvironmentHook() throws IOException {
84         initEnvironment();
85     }
86
87     private static Link l;
88
89     public static Properties initEnvironment() throws IOException {
90         TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
91         if (envInited) {
92             try {
93                 synchronized (ConfiguredTest.class) {
94                     if (l == null) {
95                         l = DatabaseConnection.newLink(false);
96                     }
97                 }
98             } catch (InterruptedException e) {
99                 throw new Error(e);
100             }
101             return generateProps();
102         }
103         envInited = true;
104         try (FileInputStream inStream = new FileInputStream("config/test.properties")) {
105             testProps.load(inStream);
106         }
107         Properties props = generateProps();
108         ServerConstants.init(props);
109         TimeConditions.init(props);
110         DomainAssessment.init(props);
111         PasswordHash.init(props);
112         Gigi.setPasswordChecker(new PasswordStrengthChecker());
113
114         if ( !DatabaseConnection.isInited()) {
115             DatabaseConnection.init(testProps);
116             try {
117                 synchronized (ConfiguredTest.class) {
118                     if (l == null) {
119                         l = DatabaseConnection.newLink(false);
120                     }
121                 }
122             } catch (InterruptedException e) {
123                 throw new Error(e);
124             }
125         }
126
127         return props;
128     }
129
130     @AfterClass
131     public static void closeDBLink() {
132         synchronized (ConfiguredTest.class) {
133             if (l != null) {
134                 l.close();
135                 l = null;
136             }
137         }
138     }
139
140     private static Properties generateProps() throws Error {
141         Properties mainProps = new Properties();
142         mainProps.setProperty("name.secure", testProps.getProperty("name.secure"));
143         mainProps.setProperty("name.www", testProps.getProperty("name.www"));
144         mainProps.setProperty("name.static", testProps.getProperty("name.static"));
145         mainProps.setProperty("name.api", testProps.getProperty("name.api"));
146         mainProps.setProperty("name.suffix", testProps.getProperty("name.suffix"));
147
148         mainProps.setProperty("appName", "SomeCA");
149         mainProps.setProperty("appIdentifier", "someca");
150
151         mainProps.setProperty("https.port", testProps.getProperty("serverPort.https"));
152         mainProps.setProperty("http.port", testProps.getProperty("serverPort.http"));
153
154         File out = new File("financial.dat");
155         if ( !out.exists()) {
156             try (FileOutputStream fos = new FileOutputStream(out)) {
157                 fos.write("google.com\ntwitter.com\n".getBytes("UTF-8"));
158             } catch (IOException e) {
159                 throw new Error(e);
160             }
161         }
162         mainProps.setProperty("highFinancialValue", out.getAbsolutePath());
163         mainProps.setProperty("scrypt.params", "1;1;1");
164         return mainProps;
165     }
166
167     public static KeyPair generateKeypair() throws GeneralSecurityException {
168         KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
169         kpg.initialize(4096);
170         KeyPair keyPair = null;
171         File f = new File("testKeypair");
172         if (f.exists()) {
173             try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
174                 keyPair = (KeyPair) ois.readObject();
175             } catch (ClassNotFoundException e) {
176                 e.printStackTrace();
177             } catch (IOException e) {
178                 e.printStackTrace();
179             }
180         } else {
181             keyPair = kpg.generateKeyPair();
182             try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f))) {
183                 oos.writeObject(keyPair);
184                 oos.close();
185             } catch (IOException e) {
186                 e.printStackTrace();
187             }
188         }
189         return keyPair;
190     }
191
192     public static KeyPair generateBrokenKeypair() throws GeneralSecurityException {
193         KeyPair keyPair = null;
194         File f = new File("testBrokenKeypair");
195         if (f.exists()) {
196             try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
197                 keyPair = (KeyPair) ois.readObject();
198             } catch (ClassNotFoundException e) {
199                 e.printStackTrace();
200             } catch (IOException e) {
201                 e.printStackTrace();
202             }
203         } else {
204             // -----BEGIN SHAMELESSLY ADAPTED BLOCK-----
205             /**
206              * Modified original RSA key generator to use three primes with one
207              * prime set to fixed value to allow simple checking for such faulty
208              * keys.
209              *
210              * @link sun.security.rsa.RSAKeyPairGenerator#generateKeyPair
211              */
212
213             KeyFactory factory = KeyFactory.getInstance("RSA");
214             Random random = new SecureRandom();
215             int keySize = 4096;
216             long r_lv = 7331;
217
218             // The generated numbers p q and r fall into the
219             // following ranges:
220             // - p: 2^(lp-1) < p < 2^lp
221             // - q: 2^(lq-1) < q < 2^lq
222             // - r: 2^12 < r < 2^13
223             // Thus the generated number has at least lp+lq+11 bit and
224             // can have at most lp+lq+13 bit.
225             // Thus for random selection of p and q the algorithm will
226             // at some point select a number of length n=n/2+lr+(n-n/2-lr)=>n
227             // bit.
228             int lp = (keySize + 1) >> 1;
229             int lr = BigInteger.valueOf(r_lv).bitLength();
230             int lq = keySize - lp - lr;
231
232             BigInteger e = BigInteger.valueOf(7331);
233
234             keyPair = null;
235             while (keyPair == null) {
236                 // generate two random primes of size lp/lq
237                 BigInteger p, q, r, n;
238
239                 r = BigInteger.valueOf(r_lv);
240                 do {
241                     p = BigInteger.probablePrime(lp, random);
242                     q = BigInteger.probablePrime(lq, random);
243
244                     // modulus n = p * q * r
245                     n = p.multiply(q).multiply(r);
246
247                     // even with correctly sized p, q and r, there is a chance
248                     // that n will be one bit short. re-generate the
249                     // primes if so.
250                 } while (n.bitLength() < keySize);
251
252                 // phi = (p - 1) * (q - 1) * (r - 1) must be relative prime to e
253                 // otherwise RSA just won't work ;-)
254                 BigInteger p1 = p.subtract(BigInteger.ONE);
255                 BigInteger q1 = q.subtract(BigInteger.ONE);
256                 BigInteger r1 = r.subtract(BigInteger.ONE);
257                 BigInteger phi = p1.multiply(q1).multiply(r1);
258
259                 // generate new p and q until they work. typically
260                 if (e.gcd(phi).equals(BigInteger.ONE) == false) {
261                     continue;
262                 }
263
264                 // private exponent d is the inverse of e mod phi
265                 BigInteger d = e.modInverse(phi);
266
267                 RSAPublicKeySpec publicSpec = new RSAPublicKeySpec(n, e);
268                 RSAPrivateKeySpec privateSpec = new RSAPrivateKeySpec(n, d);
269                 PublicKey publicKey = factory.generatePublic(publicSpec);
270                 PrivateKey privateKey = factory.generatePrivate(privateSpec);
271                 keyPair = new KeyPair(publicKey, privateKey);
272             }
273             // -----END SHAMELESSLY ADAPTED BLOCK-----
274
275             try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f))) {
276                 oos.writeObject(keyPair);
277                 oos.close();
278             } catch (IOException ioe) {
279                 ioe.printStackTrace();
280             }
281         }
282         return keyPair;
283     }
284
285     public static String generatePEMCSR(KeyPair kp, String dn) throws GeneralSecurityException, IOException {
286         return generatePEMCSR(kp, dn, new PKCS10Attributes());
287     }
288
289     public static String generatePEMCSR(KeyPair kp, String dn, PKCS10Attributes atts) throws GeneralSecurityException, IOException {
290         return generatePEMCSR(kp, dn, atts, "SHA512WithRSA");
291     }
292
293     public static String generatePEMCSR(KeyPair kp, String dn, PKCS10Attributes atts, String signature) throws GeneralSecurityException, IOException {
294         PKCS10 p10 = new PKCS10(kp.getPublic(), atts);
295         Signature s = Signature.getInstance(signature);
296         s.initSign(kp.getPrivate());
297         p10.encodeAndSign(new X500Name(dn), s);
298         return PEM.encode("CERTIFICATE REQUEST", p10.getEncoded());
299     }
300
301     static int count = 0;
302
303     public static String createRandomIDString() {
304         final char[] chars = "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
305         final int idStringLength = 16;
306
307         Random sr;
308         sr = new Random();
309
310         StringBuilder sb = new StringBuilder(idStringLength);
311         for (int i = 0; i < idStringLength; i++) {
312             sb.append(chars[sr.nextInt(chars.length)]);
313         }
314
315         return sb.toString();
316     }
317
318     public static synchronized String createUniqueName() {
319         return "test" + createRandomIDString() + "a" + (count++) + "u";
320     }
321
322     public static CertificateProfile getClientProfile() {
323         return CertificateProfile.getByName("client");
324     }
325
326     public static int countRegex(String text, String pattern) {
327         Pattern p = Pattern.compile(pattern);
328         Matcher m = p.matcher(text);
329         int i = 0;
330         while (m.find()) {
331             i++;
332         }
333         return i;
334     }
335
336     public static void makeAgent(int uid) {
337         addChallenge(uid, CATSType.AGENT_CHALLENGE);
338         add100Points(uid);
339     }
340
341     public static void addChallenge(int uid, CATSType ct) {
342         try (GigiPreparedStatement ps1 = new GigiPreparedStatement("INSERT INTO cats_passed SET user_id=?, variant_id=?, language='en_EN', version='1'")) {
343             ps1.setInt(1, uid);
344             ps1.setInt(2, ct.getId());
345             ps1.execute();
346         }
347     }
348
349     public static void addChallengeInPast(int uid, CATSType ct) {
350         try (GigiPreparedStatement ps1 = new GigiPreparedStatement("INSERT INTO cats_passed SET user_id=?, variant_id=?, pass_date=?, language='en_EN', version='1'")) {
351             ps1.setInt(1, uid);
352             ps1.setInt(2, ct.getId());
353             ps1.setTimestamp(3, new Timestamp(new Date(System.currentTimeMillis() - 24L * 60 * 60 * (CATS.TEST_MONTHS + 1) * 31 * 1000L).getTime()));
354             ps1.execute();
355         }
356     }
357
358     public static void add100Points(int uid) {
359         try (GigiPreparedStatement ps2 = new GigiPreparedStatement("INSERT INTO `notary` SET `from`=?, `to`=?, points='100'")) {
360             ps2.setInt(1, uid);
361             ps2.setInt(2, User.getById(uid).getPreferredName().getId());
362             ps2.execute();
363         }
364     }
365
366     public MailReceiver getMailReceiver() {
367         throw new Error("Feature requires Business or ManagedTest.");
368     }
369
370     public void verify(Domain d) {
371         try {
372             d.addPing(DomainPingType.EMAIL, "admin");
373             TestMail testMail = getMailReceiver().receive("admin@" + d.getSuffix());
374             testMail.verify();
375             // Enforce successful ping :-)
376             d.addPing(DomainPingType.HTTP, "a:b");
377             try (GigiPreparedStatement gps = new GigiPreparedStatement("INSERT INTO `domainPinglog` SET `configId`=(SELECT `id` FROM `pingconfig` WHERE `domainid`=? AND `type`='http'), state='success', needsAction=false")) {
378                 gps.setInt(1, d.getId());
379                 gps.execute();
380             }
381             assertTrue(d.isVerified());
382         } catch (GigiApiException e) {
383             throw new Error(e);
384         } catch (IOException e) {
385             throw new Error(e);
386         }
387     }
388
389     public static void purgeOnlyDB() throws SQLException, IOException {
390         System.out.println("... resetting Database");
391         long ms = System.currentTimeMillis();
392         try {
393             DatabaseManager.run(new String[] {
394                     testProps.getProperty("sql.driver"), testProps.getProperty("sql.url"), testProps.getProperty("sql.user"), testProps.getProperty("sql.password")
395             }, ImportType.TRUNCATE);
396         } catch (ClassNotFoundException e) {
397             e.printStackTrace();
398         }
399         System.out.println("Database reset complete in " + (System.currentTimeMillis() - ms) + " ms.");
400     }
401
402     public static String validVerificationDateString() {
403         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
404         Calendar c = Calendar.getInstance();
405         c.setTimeInMillis(System.currentTimeMillis());
406         c.add(Calendar.MONTH, -Notary.LIMIT_MAX_MONTHS_VERIFICATION + 1);
407         return sdf.format(new Date(c.getTimeInMillis()));
408     }
409 }