Merge "Suggestions to enhance the SQL call pattern."
[gigi.git] / tests / org / cacert / gigi / ping / TestSSL.java
1 package org.cacert.gigi.ping;
2
3 import static org.junit.Assert.*;
4 import static org.junit.Assume.*;
5
6 import java.io.ByteArrayInputStream;
7 import java.io.IOException;
8 import java.net.Socket;
9 import java.net.URLEncoder;
10 import java.security.GeneralSecurityException;
11 import java.security.KeyManagementException;
12 import java.security.KeyPair;
13 import java.security.NoSuchAlgorithmException;
14 import java.security.Principal;
15 import java.security.PrivateKey;
16 import java.security.SecureRandom;
17 import java.security.cert.CertificateException;
18 import java.security.cert.CertificateFactory;
19 import java.security.cert.X509Certificate;
20 import java.sql.SQLException;
21 import java.util.Arrays;
22 import java.util.Date;
23 import java.util.HashMap;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28
29 import javax.net.ssl.KeyManager;
30 import javax.net.ssl.SSLContext;
31 import javax.net.ssl.SSLServerSocket;
32 import javax.net.ssl.SSLServerSocketFactory;
33 import javax.net.ssl.TrustManager;
34 import javax.net.ssl.X509KeyManager;
35 import javax.net.ssl.X509TrustManager;
36 import javax.security.auth.x500.X500Principal;
37
38 import org.cacert.gigi.GigiApiException;
39 import org.cacert.gigi.database.DatabaseConnection;
40 import org.cacert.gigi.database.DatabaseConnection.Link;
41 import org.cacert.gigi.dbObjects.Certificate;
42 import org.cacert.gigi.dbObjects.Certificate.CSRType;
43 import org.cacert.gigi.dbObjects.CertificateProfile;
44 import org.cacert.gigi.dbObjects.Digest;
45 import org.cacert.gigi.dbObjects.User;
46 import org.cacert.gigi.testUtils.IOUtils;
47 import org.cacert.gigi.testUtils.PingTest;
48 import org.cacert.gigi.testUtils.TestEmailReceiver.TestMail;
49 import org.cacert.gigi.util.SimpleSigner;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.junit.runners.Parameterized;
53 import org.junit.runners.Parameterized.Parameter;
54 import org.junit.runners.Parameterized.Parameters;
55
56 @RunWith(Parameterized.class)
57 public class TestSSL extends PingTest {
58
59     @Parameters(name = "self-signed = {0}")
60     public static Iterable<Object[]> genParams() throws IOException {
61         return Arrays.asList(new Object[] {
62             true
63         }, new Object[] {
64             false
65         });
66
67     }
68
69     @Parameter
70     public Boolean self = false;
71
72     public abstract static class AsyncTask<T> {
73
74         T res;
75
76         Thread runner;
77
78         Exception ex;
79
80         public T join() throws InterruptedException {
81             runner.join();
82             if (ex != null) {
83                 throw new Error(ex);
84             }
85             return res;
86         }
87
88         public void start() {
89             runner = new Thread() {
90
91                 @Override
92                 public void run() {
93                     try {
94                         res = AsyncTask.this.run();
95                     } catch (Exception e) {
96                         ex = e;
97                     }
98                 }
99             };
100             runner.start();
101         }
102
103         public abstract T run() throws Exception;
104
105     }
106
107     private KeyPair kp;
108
109     private X509Certificate c;
110
111     @Test(timeout = 70000)
112     public void sslAndMailSuccess() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
113         testEmailAndSSL(0, 0, true);
114     }
115
116     @Test(timeout = 70000)
117     public void sslWongTypeAndMailSuccess() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
118         testEmailAndSSL(1, 0, true);
119     }
120
121     @Test(timeout = 70000)
122     public void sslOneMissingAndMailSuccess() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
123         testEmailAndSSL(2, 0, true);
124     }
125
126     @Test(timeout = 70000)
127     public void sslBothMissingAndMailSuccess() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
128         testEmailAndSSL(3, 0, true);
129     }
130
131     @Test(timeout = 70000)
132     public void sslWrongTypeAndMailFail() throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
133         testEmailAndSSL(1, 1, false);
134     }
135
136     private void testEmailAndSSL(int sslVariant, int emailVariant, boolean successMail) throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
137         try (Link link = DatabaseConnection.newLink(false)) {
138             testEmailAndSSLWithLink(sslVariant, emailVariant, successMail);
139         }
140     }
141
142     /**
143      * @param sslVariant
144      *            <ul>
145      *            <li>0= all valid</li>
146      *            <li>1= wrong type</li>
147      *            <li>2= one server missing</li>
148      *            <li>3= both servers missing</li>
149      *            </ul>
150      * @param emailVariant
151      * @param successSSL
152      * @param successMail
153      * @throws IOException
154      * @throws InterruptedException
155      * @throws SQLException
156      * @throws GeneralSecurityException
157      * @throws GigiApiException
158      */
159
160     private void testEmailAndSSLWithLink(int sslVariant, int emailVariant, boolean successMail) throws IOException, InterruptedException, SQLException, GeneralSecurityException, GigiApiException {
161         String test = getTestProps().getProperty("domain.local");
162         assumeNotNull(test);
163         Matcher m = initailizeDomainForm();
164         String value = m.group(2);
165
166         if (self) {
167             createCertificateSelf(test, sslVariant == 1 ? "clientAuth" : "serverAuth", value);
168         } else {
169             createCertificate(test, CertificateProfile.getByName(sslVariant == 1 ? "client" : "server"));
170         }
171
172         final SSLServerSocket sss = createSSLServer(kp.getPrivate(), c);
173         int port = sss.getLocalPort();
174         final SSLServerSocket sss2 = createSSLServer(kp.getPrivate(), c);
175         int port2 = sss2.getLocalPort();
176         if (sslVariant == 3 || sslVariant == 2) {
177             sss2.close();
178             if (sslVariant == 3) {
179                 sss.close();
180             }
181         }
182         String content = "adddomain&newdomain=" + URLEncoder.encode(test, "UTF-8") + //
183                 "&emailType=y&email=2&SSLType=y" + //
184                 "&ssl-type-0=direct&ssl-port-0=" + port + //
185                 "&ssl-type-1=direct&ssl-port-1=" + port2 + //
186                 "&ssl-type-2=direct&ssl-port-2=" + //
187                 "&ssl-type-3=direct&ssl-port-3=" + //
188                 "&adddomain&csrf=" + csrf;
189         String p2 = sendDomainForm(content);
190         boolean firstSucceeds = sslVariant != 0 && sslVariant != 2;
191         AsyncTask<Boolean> ass = new AsyncTask<Boolean>() {
192
193             @Override
194             public Boolean run() throws Exception {
195                 return acceptSSLServer(sss);
196             }
197         };
198         ass.start();
199         System.out.println(port + " and " + port2 + " ready");
200         System.err.println(port + " and " + port2 + " ready");
201         boolean accept2 = acceptSSLServer(sss2);
202         boolean accept1 = ass.join();
203         // assertTrue(firstSucceeds ^ accept1);
204         boolean secondsSucceeds = sslVariant != 0;
205         // assertTrue(secondsSucceeds ^ accept2);
206
207         TestMail mail = getMailReciever().receive();
208         if (emailVariant == 0) {
209             mail.verify();
210         }
211         waitForPings(3);
212
213         String newcontent = IOUtils.readURL(get(p2));
214         Pattern pat = Pattern.compile("<td>ssl</td>\\s*<td>success</td>");
215         Matcher matcher = pat.matcher(newcontent);
216         assertTrue(newcontent, firstSucceeds ^ matcher.find());
217         assertTrue(newcontent, secondsSucceeds ^ matcher.find());
218         assertFalse(newcontent, matcher.find());
219         pat = Pattern.compile("<td>email</td>\\s*<td>success</td>");
220         assertTrue(newcontent, !successMail ^ pat.matcher(newcontent).find());
221     }
222
223     private void createCertificate(String test, CertificateProfile profile) throws GeneralSecurityException, IOException, SQLException, InterruptedException, GigiApiException {
224         kp = generateKeypair();
225         String csr = generatePEMCSR(kp, "CN=" + test);
226         User u = User.getById(id);
227         Certificate c = new Certificate(u, u, Certificate.buildDN("CN", test), Digest.SHA256, csr, CSRType.CSR, profile);
228         c.issue(null, "2y", u).waitFor(60000);
229         this.c = c.cert();
230     }
231
232     private void createCertificateSelf(String test, String eku, String tok) throws GeneralSecurityException, IOException, SQLException, InterruptedException, GigiApiException {
233         kp = generateKeypair();
234         HashMap<String, String> name = new HashMap<>();
235         name.put("CN", "");
236         name.put("OU", tok);
237
238         Date from = new Date();
239         Date to = new Date(from.getTime() + 1000 * 60 * 60 * 2);
240         List<Certificate.SubjectAlternateName> l = new LinkedList<>();
241
242         byte[] cert = SimpleSigner.generateCert(kp.getPublic(), kp.getPrivate(), name, new X500Principal(SimpleSigner.genX500Name(name).getEncoded()), l, from, to, Digest.SHA256, eku);
243         c = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(new ByteArrayInputStream(cert));
244     }
245
246     private boolean acceptSSLServer(SSLServerSocket sss) throws IOException {
247         try (Socket s = sss.accept()) {
248             s.getOutputStream().write('b');
249             s.getOutputStream().close();
250             return true;
251         } catch (IOException e) {
252             return false;
253         }
254     }
255
256     private SSLServerSocket createSSLServer(final PrivateKey priv, final X509Certificate cert) throws Error, IOException {
257         SSLContext sc;
258         try {
259             sc = SSLContext.getInstance("SSL");
260             sc.init(new KeyManager[] {
261                 new X509KeyManager() {
262
263                     @Override
264                     public String[] getServerAliases(String keyType, Principal[] issuers) {
265                         return new String[] {
266                             "server"
267                         };
268                     }
269
270                     @Override
271                     public PrivateKey getPrivateKey(String alias) {
272                         return priv;
273                     }
274
275                     @Override
276                     public String[] getClientAliases(String keyType, Principal[] issuers) {
277                         throw new Error();
278                     }
279
280                     @Override
281                     public X509Certificate[] getCertificateChain(String alias) {
282                         return new X509Certificate[] {
283                             cert
284                         };
285                     }
286
287                     @Override
288                     public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
289                         throw new Error();
290                     }
291
292                     @Override
293                     public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
294                         return "server";
295                     }
296
297                 }
298             }, new TrustManager[] {
299                 new X509TrustManager() {
300
301                     @Override
302                     public X509Certificate[] getAcceptedIssuers() {
303                         return null;
304                     }
305
306                     @Override
307                     public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
308
309                     @Override
310                     public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
311                 }
312             }, new SecureRandom());
313         } catch (NoSuchAlgorithmException e) {
314             e.printStackTrace();
315             throw new Error(e);
316         } catch (KeyManagementException e) {
317             e.printStackTrace();
318             throw new Error(e);
319         }
320
321         SSLServerSocketFactory sssf = sc.getServerSocketFactory();
322         return (SSLServerSocket) sssf.createServerSocket(0);
323     }
324
325     public static void main(String[] args) throws Exception {
326         initEnvironment();
327         TestSSL t1 = new TestSSL();
328         t1.sslAndMailSuccess();
329         tearDownServer();
330     }
331
332 }