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