]> WPIA git - gigi.git/blob - src/org/cacert/gigi/ping/SSLPinger.java
fix: ResultSet.getDate is often wrong as it fetches day-precision times
[gigi.git] / src / org / cacert / gigi / ping / SSLPinger.java
1 package org.cacert.gigi.ping;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6 import java.math.BigInteger;
7 import java.net.InetSocketAddress;
8 import java.net.Socket;
9 import java.nio.ByteBuffer;
10 import java.nio.channels.SocketChannel;
11 import java.security.GeneralSecurityException;
12 import java.security.KeyManagementException;
13 import java.security.KeyStore;
14 import java.security.KeyStoreException;
15 import java.security.SecureRandom;
16 import java.util.Arrays;
17
18 import javax.net.ssl.SNIHostName;
19 import javax.net.ssl.SNIServerName;
20 import javax.net.ssl.SSLContext;
21 import javax.net.ssl.SSLEngine;
22 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
23 import javax.net.ssl.SSLEngineResult.Status;
24 import javax.net.ssl.SSLException;
25 import javax.net.ssl.SSLParameters;
26 import javax.net.ssl.TrustManager;
27 import javax.net.ssl.TrustManagerFactory;
28 import javax.net.ssl.X509TrustManager;
29 import javax.security.cert.CertificateException;
30 import javax.security.cert.X509Certificate;
31
32 import org.cacert.gigi.dbObjects.CACertificate;
33 import org.cacert.gigi.dbObjects.Certificate;
34 import org.cacert.gigi.dbObjects.CertificateOwner;
35 import org.cacert.gigi.dbObjects.Domain;
36
37 import sun.security.x509.AVA;
38 import sun.security.x509.X500Name;
39
40 public class SSLPinger extends DomainPinger {
41
42     private static final String OID_EKU_serverAuth = "1.3.6.1.5.5.7.3.1";
43
44     public static final String[] TYPES = new String[] {
45             "xmpp", "server-xmpp", "smtp", "imap"
46     };
47
48     private KeyStore truststore;
49
50     public SSLPinger(KeyStore truststore) {
51         this.truststore = truststore;
52     }
53
54     @Override
55     public void ping(Domain domain, String configuration, CertificateOwner u, int confId) {
56         try (SocketChannel sch = SocketChannel.open()) {
57             sch.socket().setSoTimeout(5000);
58             String[] parts = configuration.split(":", 4);
59             sch.socket().connect(new InetSocketAddress(domain.getSuffix(), Integer.parseInt(parts[2])), 5000);
60             if (parts.length == 4) {
61                 switch (parts[3]) {
62                 case "xmpp":
63                     startXMPP(sch, false, domain.getSuffix());
64                     break;
65                 case "server-xmpp":
66                     startXMPP(sch, true, domain.getSuffix());
67                     break;
68                 case "smtp":
69                     startSMTP(sch);
70                     break;
71                 case "imap":
72                     startIMAP(sch);
73                     break;
74
75                 }
76             }
77             String key = parts[0];
78             String value = parts[1];
79             String res = test(sch, domain.getSuffix(), u, value);
80             enterPingResult(confId, res, res, null);
81             return;
82         } catch (IOException e) {
83             enterPingResult(confId, "error", "connection Failed", null);
84             return;
85         }
86
87     }
88
89     private void startIMAP(SocketChannel sch) throws IOException {
90         Socket s = sch.socket();
91         InputStream is = s.getInputStream();
92         OutputStream os = s.getOutputStream();
93         scanFor(is, "\n");
94         os.write("ENABLE STARTTLS\r\n".getBytes("UTF-8"));
95         os.flush();
96         scanFor(is, "\n");
97     }
98
99     private void startXMPP(SocketChannel sch, boolean server, String domain) throws IOException {
100         Socket s = sch.socket();
101         InputStream is = s.getInputStream();
102         OutputStream os = s.getOutputStream();
103         os.write(("<stream:stream to=\"" + domain + "\" xmlns=\"jabber:" + (server ? "server" : "client") + "\"" + " xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">").getBytes("UTF-8"));
104         os.flush();
105         os.write("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>".getBytes("UTF-8"));
106         os.flush();
107         scanFor(is, "<proceed");
108         scanFor(is, ">");
109
110     }
111
112     private void scanFor(InputStream is, String scanFor) throws IOException {
113         int pos = 0;
114         while (pos < scanFor.length()) {
115             if (is.read() == scanFor.charAt(pos)) {
116                 pos++;
117             } else {
118                 pos = 0;
119             }
120         }
121     }
122
123     private void startSMTP(SocketChannel sch) throws IOException {
124         Socket s = sch.socket();
125         InputStream is = s.getInputStream();
126         readSMTP(is);
127         s.getOutputStream().write("EHLO ssl.pinger\r\n".getBytes("UTF-8"));
128         s.getOutputStream().flush();
129         readSMTP(is);
130         s.getOutputStream().write("HELP\r\n".getBytes("UTF-8"));
131         s.getOutputStream().flush();
132         readSMTP(is);
133         s.getOutputStream().write("STARTTLS\r\n".getBytes("UTF-8"));
134         s.getOutputStream().flush();
135         readSMTP(is);
136     }
137
138     private void readSMTP(InputStream is) throws IOException {
139         int counter = 0;
140         boolean finish = true;
141         while (true) {
142             char c = (char) is.read();
143             if (counter == 3) {
144                 if (c == ' ') {
145                     finish = true;
146                 } else if (c == '-') {
147                     finish = false;
148                 } else {
149                     throw new Error("Invalid smtp: " + c);
150                 }
151             }
152             if (c == '\n') {
153                 if (finish) {
154                     return;
155                 }
156                 counter = 0;
157             } else {
158                 counter++;
159             }
160         }
161     }
162
163     private String test(SocketChannel sch, String domain, CertificateOwner subject, String tok) {
164         System.out.println("SSL- connecting");
165
166         try {
167             sch.socket().setSoTimeout(5000);
168             SSLContext sc = SSLContext.getInstance("SSL");
169             try {
170                 TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
171                 tmf.init(truststore);
172                 sc.init(null, new TrustManager[] {
173                         new X509TrustManager() {
174
175                             @Override
176                             public java.security.cert.X509Certificate[] getAcceptedIssuers() {
177                                 return null;
178                             }
179
180                             @Override
181                             public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {
182                                 java.security.cert.X509Certificate c = chain[0];
183                                 if (c.getExtendedKeyUsage() == null || !c.getExtendedKeyUsage().contains(OID_EKU_serverAuth)) {
184                                     throw new java.security.cert.CertificateException("Extended Key Usage for SSL Server Authentication missing");
185                                 }
186                             }
187
188                             @Override
189                             public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {}
190                         }
191                 }, new SecureRandom());
192             } catch (KeyManagementException e) {
193                 e.printStackTrace();
194             } catch (KeyStoreException e) {
195                 e.printStackTrace();
196             }
197             SSLEngine se = sc.createSSLEngine();
198             ByteBuffer enc_in = ByteBuffer.allocate(se.getSession().getPacketBufferSize());
199             ByteBuffer enc_out = ByteBuffer.allocate(se.getSession().getPacketBufferSize());
200             ByteBuffer dec_in = ByteBuffer.allocate(se.getSession().getApplicationBufferSize());
201             ByteBuffer dec_out = ByteBuffer.allocate(se.getSession().getApplicationBufferSize());
202             se.setUseClientMode(true);
203             SSLParameters sp = se.getSSLParameters();
204             sp.setServerNames(Arrays.<SNIServerName>asList(new SNIHostName(domain)));
205             se.setSSLParameters(sp);
206             se.beginHandshake();
207             enc_in.limit(0);
208             while (se.getHandshakeStatus() != HandshakeStatus.FINISHED && se.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING) {
209                 switch (se.getHandshakeStatus()) {
210                 case NEED_WRAP:
211                     dec_out.limit(0);
212                     se.wrap(dec_out, enc_out);
213                     enc_out.flip();
214                     while (enc_out.remaining() > 0) {
215                         sch.write(enc_out);
216                     }
217                     enc_out.clear();
218                     break;
219                 case NEED_UNWRAP:
220                     if (enc_in.remaining() == 0) {
221                         enc_in.clear();
222                         sch.read(enc_in);
223                         enc_in.flip();
224                     }
225                     while (se.unwrap(enc_in, dec_in).getStatus() == Status.BUFFER_UNDERFLOW) {
226                         enc_in.position(enc_in.limit());
227                         enc_in.limit(enc_in.capacity());
228                         sch.read(enc_in);
229                         enc_in.flip();
230                     }
231                     enc_in.compact();
232                     enc_in.flip();
233                     break;
234                 case NEED_TASK:
235                     se.getDelegatedTask().run();
236                     break;
237                 case NOT_HANDSHAKING:
238                 case FINISHED:
239
240                 }
241
242             }
243             System.out.println("SSL- connected");
244             X509Certificate[] peerCertificateChain = se.getSession().getPeerCertificateChain();
245             X509Certificate first = peerCertificateChain[0];
246             if (first.getIssuerDN().equals(first.getSubjectDN())) {
247                 first.verify(first.getPublicKey());
248                 X500Name p = (X500Name) first.getSubjectDN();
249                 X500Name n = new X500Name(p.getEncoded());
250                 for (AVA i : n.allAvas()) {
251                     if (i.getObjectIdentifier().equals((Object) X500Name.orgUnitName_oid)) {
252                         String toke = i.getDerValue().getAsString();
253                         if (tok.equals(toke)) {
254                             return PING_SUCCEDED;
255                         } else {
256                             return "Self-signed certificate is wrong";
257                         }
258                     }
259                 }
260             }
261
262             BigInteger serial = first.getSerialNumber();
263             Certificate c = Certificate.getBySerial(serial.toString(16));
264             if (c == null) {
265                 return "Certificate not found: Serial " + serial.toString(16) + " missing.";
266             }
267             CACertificate p = c.getParent();
268             if ( !first.getIssuerDN().equals(p.getCertificate().getSubjectDN())) {
269                 return "Broken certificate supplied";
270             }
271             first.verify(p.getCertificate().getPublicKey());
272             if (c.getOwner().getId() != subject.getId()) {
273                 return "Owner mismatch";
274             }
275             return PING_SUCCEDED;
276         } catch (GeneralSecurityException e) {
277             // e.printStackTrace();
278             return "Security failed";
279         } catch (SSLException e) {
280             // e.printStackTrace(); TODO log for user debugging?
281             return "Security failed";
282         } catch (IOException e) {
283             // e.printStackTrace(); TODO log for user debugging?
284             return "Connection closed";
285         } catch (CertificateException e) {
286             // e.printStackTrace();
287             return "Security failed";
288         }
289     }
290 }