]> WPIA git - gigi.git/blob - src/org/cacert/gigi/ping/SSLPinger.java
6dab6b524ff51684e944db780b5f20b9becb69b9
[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.KeyManagementException;
12 import java.security.KeyStore;
13 import java.security.KeyStoreException;
14 import java.security.NoSuchAlgorithmException;
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.TrustManagerFactory;
27 import javax.security.cert.X509Certificate;
28
29 import org.cacert.gigi.dbObjects.Certificate;
30 import org.cacert.gigi.dbObjects.CertificateOwner;
31 import org.cacert.gigi.dbObjects.Domain;
32
33 public class SSLPinger extends DomainPinger {
34
35     public static final String[] TYPES = new String[] {
36             "xmpp", "server-xmpp", "smtp", "imap"
37     };
38
39     private KeyStore truststore;
40
41     public SSLPinger(KeyStore truststore) {
42         this.truststore = truststore;
43     }
44
45     @Override
46     public void ping(Domain domain, String configuration, CertificateOwner u, int confId) {
47         try (SocketChannel sch = SocketChannel.open()) {
48             sch.socket().setSoTimeout(5000);
49             String[] parts = configuration.split(":", 2);
50             sch.socket().connect(new InetSocketAddress(domain.getSuffix(), Integer.parseInt(parts[0])), 5000);
51             if (parts.length == 2) {
52                 switch (parts[1]) {
53                 case "xmpp":
54                     startXMPP(sch, false, domain.getSuffix());
55                     break;
56                 case "server-xmpp":
57                     startXMPP(sch, true, domain.getSuffix());
58                     break;
59                 case "smtp":
60                     startSMTP(sch);
61                     break;
62                 case "imap":
63                     startIMAP(sch);
64                     break;
65
66                 }
67             }
68             String res = test(sch, domain.getSuffix(), u);
69             enterPingResult(confId, res, res, null);
70             return;
71         } catch (IOException e) {
72             enterPingResult(confId, "error", "connection Failed", null);
73             return;
74         }
75
76     }
77
78     private void startIMAP(SocketChannel sch) throws IOException {
79         Socket s = sch.socket();
80         InputStream is = s.getInputStream();
81         OutputStream os = s.getOutputStream();
82         scanFor(is, "\n");
83         os.write("ENABLE STARTTLS\r\n".getBytes("UTF-8"));
84         os.flush();
85         scanFor(is, "\n");
86     }
87
88     private void startXMPP(SocketChannel sch, boolean server, String domain) throws IOException {
89         Socket s = sch.socket();
90         InputStream is = s.getInputStream();
91         OutputStream os = s.getOutputStream();
92         os.write(("<stream:stream to=\"" + domain + "\" xmlns=\"jabber:" + (server ? "server" : "client") + "\"" + " xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">").getBytes("UTF-8"));
93         os.flush();
94         os.write("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>".getBytes("UTF-8"));
95         os.flush();
96         scanFor(is, "<proceed");
97         scanFor(is, ">");
98
99     }
100
101     private void scanFor(InputStream is, String scanFor) throws IOException {
102         int pos = 0;
103         while (pos < scanFor.length()) {
104             if (is.read() == scanFor.charAt(pos)) {
105                 pos++;
106             } else {
107                 pos = 0;
108             }
109         }
110     }
111
112     private void startSMTP(SocketChannel sch) throws IOException {
113         Socket s = sch.socket();
114         InputStream is = s.getInputStream();
115         readSMTP(is);
116         s.getOutputStream().write("EHLO ssl.pinger\r\n".getBytes("UTF-8"));
117         s.getOutputStream().flush();
118         readSMTP(is);
119         s.getOutputStream().write("HELP\r\n".getBytes("UTF-8"));
120         s.getOutputStream().flush();
121         readSMTP(is);
122         s.getOutputStream().write("STARTTLS\r\n".getBytes("UTF-8"));
123         s.getOutputStream().flush();
124         readSMTP(is);
125     }
126
127     private void readSMTP(InputStream is) throws IOException {
128         int counter = 0;
129         boolean finish = true;
130         while (true) {
131             char c = (char) is.read();
132             if (counter == 3) {
133                 if (c == ' ') {
134                     finish = true;
135                 } else if (c == '-') {
136                     finish = false;
137                 } else {
138                     throw new Error("Invalid smtp: " + c);
139                 }
140             }
141             if (c == '\n') {
142                 if (finish) {
143                     return;
144                 }
145                 counter = 0;
146             } else {
147                 counter++;
148             }
149         }
150     }
151
152     private String test(SocketChannel sch, String domain, CertificateOwner subject) {
153         try {
154             sch.socket().setSoTimeout(5000);
155             SSLContext sc = SSLContext.getInstance("SSL");
156             try {
157                 TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
158                 tmf.init(truststore);
159                 sc.init(null, tmf.getTrustManagers(), new SecureRandom());
160             } catch (KeyManagementException e) {
161                 e.printStackTrace();
162             } catch (KeyStoreException e) {
163                 e.printStackTrace();
164             }
165             SSLEngine se = sc.createSSLEngine();
166             ByteBuffer enc_in = ByteBuffer.allocate(se.getSession().getPacketBufferSize());
167             ByteBuffer enc_out = ByteBuffer.allocate(se.getSession().getPacketBufferSize());
168             ByteBuffer dec_in = ByteBuffer.allocate(se.getSession().getApplicationBufferSize());
169             ByteBuffer dec_out = ByteBuffer.allocate(se.getSession().getApplicationBufferSize());
170             se.setUseClientMode(true);
171             SSLParameters sp = se.getSSLParameters();
172             sp.setServerNames(Arrays.<SNIServerName>asList(new SNIHostName(domain)));
173             se.setSSLParameters(sp);
174             se.beginHandshake();
175             enc_in.limit(0);
176             while (se.getHandshakeStatus() != HandshakeStatus.FINISHED && se.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING) {
177                 switch (se.getHandshakeStatus()) {
178                 case NEED_WRAP:
179                     dec_out.limit(0);
180                     se.wrap(dec_out, enc_out);
181                     enc_out.flip();
182                     while (enc_out.remaining() > 0) {
183                         sch.write(enc_out);
184                     }
185                     enc_out.clear();
186                     break;
187                 case NEED_UNWRAP:
188                     if (enc_in.remaining() == 0) {
189                         enc_in.clear();
190                         sch.read(enc_in);
191                         enc_in.flip();
192                     }
193                     while (se.unwrap(enc_in, dec_in).getStatus() == Status.BUFFER_UNDERFLOW) {
194                         enc_in.position(enc_in.limit());
195                         enc_in.limit(enc_in.capacity());
196                         sch.read(enc_in);
197                         enc_in.flip();
198                     }
199                     enc_in.compact();
200                     enc_in.flip();
201                     break;
202                 case NEED_TASK:
203                     se.getDelegatedTask().run();
204                     break;
205                 case NOT_HANDSHAKING:
206                 case FINISHED:
207
208                 }
209
210             }
211             X509Certificate[] peerCertificateChain = se.getSession().getPeerCertificateChain();
212             X509Certificate first = peerCertificateChain[0];
213
214             BigInteger serial = first.getSerialNumber();
215             Certificate c = Certificate.getBySerial(serial.toString(16));
216             if (c == null) {
217                 return "Certificate not found: Serial " + serial.toString(16) + " missing.";
218             }
219             if (c.getOwner().getId() != subject.getId()) {
220                 return "Owner mismatch";
221             }
222             return PING_SUCCEDED;
223         } catch (NoSuchAlgorithmException e) {
224             // e.printStackTrace(); TODO log for user debugging?
225             return "Security failed";
226         } catch (SSLException e) {
227             // e.printStackTrace(); TODO log for user debugging?
228             return "Security failed";
229         } catch (IOException e) {
230             // e.printStackTrace(); TODO log for user debugging?
231             return "Connection closed";
232         }
233     }
234 }