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