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