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