1 package club.wpia.gigi.testUtils;
3 import static org.junit.Assert.*;
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;
33 import org.junit.AfterClass;
34 import org.junit.BeforeClass;
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;
65 * Base class for a Testsuite that makes use of the config variables that define
68 public abstract class ConfiguredTest {
70 static Properties testProps = new Properties();
72 public static Properties getTestProps() {
76 private static boolean envInited = false;
79 * Some password that fulfills the password criteria.
81 public static final String TEST_PASSWORD = "xvXV12°§";
83 public static final String DIFFICULT_CHARS = "ÜÖÄß𐀀";
86 public static void initEnvironmentHook() throws IOException {
90 private static Link l;
92 public static Properties initEnvironment() throws IOException {
93 TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
96 synchronized (ConfiguredTest.class) {
98 l = DatabaseConnection.newLink(false);
101 } catch (InterruptedException e) {
104 return generateProps();
107 try (FileInputStream inStream = new FileInputStream("config/test.properties")) {
108 testProps.load(inStream);
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());
117 if ( !DatabaseConnection.isInited()) {
118 DatabaseConnection.init(testProps);
120 synchronized (ConfiguredTest.class) {
122 l = DatabaseConnection.newLink(false);
125 } catch (InterruptedException e) {
134 public static void closeDBLink() {
135 synchronized (ConfiguredTest.class) {
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"));
151 mainProps.setProperty("appName", "SomeCA");
152 mainProps.setProperty("appIdentifier", "someca");
154 mainProps.setProperty("https.port", testProps.getProperty("serverPort.https"));
155 mainProps.setProperty("http.port", testProps.getProperty("serverPort.http"));
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) {
165 mainProps.setProperty("highFinancialValue", out.getAbsolutePath());
166 mainProps.setProperty("scrypt.params", "1;1;1");
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");
176 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
177 keyPair = (KeyPair) ois.readObject();
178 } catch (ClassNotFoundException e) {
180 } catch (IOException e) {
184 keyPair = kpg.generateKeyPair();
185 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f))) {
186 oos.writeObject(keyPair);
188 } catch (IOException e) {
195 public static KeyPair generateBrokenKeypair() throws GeneralSecurityException {
196 KeyPair keyPair = null;
197 File f = new File("testBrokenKeypair");
199 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
200 keyPair = (KeyPair) ois.readObject();
201 } catch (ClassNotFoundException e) {
203 } catch (IOException e) {
207 // -----BEGIN SHAMELESSLY ADAPTED BLOCK-----
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
213 * @link sun.security.rsa.RSAKeyPairGenerator#generateKeyPair
216 KeyFactory factory = KeyFactory.getInstance("RSA");
217 Random random = new SecureRandom();
221 // The generated numbers p q and r fall into the
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
231 int lp = (keySize + 1) >> 1;
232 int lr = BigInteger.valueOf(r_lv).bitLength();
233 int lq = keySize - lp - lr;
235 BigInteger e = BigInteger.valueOf(7331);
238 while (keyPair == null) {
239 // generate two random primes of size lp/lq
240 BigInteger p, q, r, n;
242 r = BigInteger.valueOf(r_lv);
244 p = BigInteger.probablePrime(lp, random);
245 q = BigInteger.probablePrime(lq, random);
247 // modulus n = p * q * r
248 n = p.multiply(q).multiply(r);
250 // even with correctly sized p, q and r, there is a chance
251 // that n will be one bit short. re-generate the
253 } while (n.bitLength() < keySize);
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);
262 // generate new p and q until they work. typically
263 if (e.gcd(phi).equals(BigInteger.ONE) == false) {
267 // private exponent d is the inverse of e mod phi
268 BigInteger d = e.modInverse(phi);
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);
276 // -----END SHAMELESSLY ADAPTED BLOCK-----
278 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f))) {
279 oos.writeObject(keyPair);
281 } catch (IOException ioe) {
282 ioe.printStackTrace();
288 public static String generatePEMCSR(KeyPair kp, String dn) throws GeneralSecurityException, IOException {
289 return generatePEMCSR(kp, dn, new PKCS10Attributes());
292 public static String generatePEMCSR(KeyPair kp, String dn, PKCS10Attributes atts) throws GeneralSecurityException, IOException {
293 return generatePEMCSR(kp, dn, atts, "SHA512WithRSA");
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());
304 static int count = 0;
306 public static String createRandomIDString() {
307 final char[] chars = "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
308 final int idStringLength = 16;
313 StringBuilder sb = new StringBuilder(idStringLength);
314 for (int i = 0; i < idStringLength; i++) {
315 sb.append(chars[sr.nextInt(chars.length)]);
318 return sb.toString();
321 public static synchronized String createUniqueName() {
322 return "test" + createRandomIDString() + "a" + (count++) + "u";
325 public static CertificateProfile getClientProfile() {
326 return CertificateProfile.getByName("client");
329 public static int countRegex(String text, String pattern) {
330 Pattern p = Pattern.compile(pattern);
331 Matcher m = p.matcher(text);
339 public static void makeAgent(int uid) {
340 addChallenge(uid, CATSType.AGENT_CHALLENGE);
342 insertRAContract(uid);
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'")) {
348 ps1.setInt(2, ct.getId());
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'")) {
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()));
362 public static void add100Points(int uid) {
363 try (GigiPreparedStatement ps2 = new GigiPreparedStatement("INSERT INTO `notary` SET `from`=?, `to`=?, points='100'")) {
365 ps2.setInt(2, User.getById(uid).getPreferredName().getId());
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`=?")) {
374 ps.setString(2, RandomToken.generateToken(32));
375 ps.setEnum(3, ContractType.RA_AGENT_CONTRACT);
376 ps.setString(4, User.getById(uid).getPreferredName().toString());
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'")) {
385 ps2.setInt(2, User.getById(uid).getPreferredName().getId());
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'")) {
394 ps1.setInt(2, CATSType.AGENT_CHALLENGE.getId());
399 public MailReceiver getMailReceiver() {
400 throw new Error("Feature requires Business or ManagedTest.");
403 public void verify(Domain d) {
405 d.addPing(DomainPingType.EMAIL, "admin");
406 TestMail testMail = getMailReceiver().receive("admin@" + d.getSuffix());
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());
414 assertTrue(d.isVerified());
415 } catch (GigiApiException e) {
417 } catch (IOException e) {
422 public static void purgeOnlyDB() throws SQLException, IOException {
423 System.out.println("... resetting Database");
424 long ms = System.currentTimeMillis();
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) {
432 System.out.println("Database reset complete in " + (System.currentTimeMillis() - ms) + " ms.");
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()));
443 public void signRAContract(User u) throws GigiApiException {
444 new Contract(u, ContractType.RA_AGENT_CONTRACT);
445 getMailReceiver().receive(u.getEmail());