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