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