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