001/*
002 * $HeadURL: http://juliusdavies.ca/svn/not-yet-commons-ssl/tags/commons-ssl-0.3.9/src/java/org/apache/commons/ssl/Ping.java $
003 * $Revision: 129 $
004 * $Date: 2007-11-14 19:21:33 -0800 (Wed, 14 Nov 2007) $
005 *
006 * ====================================================================
007 * Licensed to the Apache Software Foundation (ASF) under one
008 * or more contributor license agreements.  See the NOTICE file
009 * distributed with this work for additional information
010 * regarding copyright ownership.  The ASF licenses this file
011 * to you under the Apache License, Version 2.0 (the
012 * "License"); you may not use this file except in compliance
013 * with the License.  You may obtain a copy of the License at
014 *
015 *   http://www.apache.org/licenses/LICENSE-2.0
016 *
017 * Unless required by applicable law or agreed to in writing,
018 * software distributed under the License is distributed on an
019 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
020 * KIND, either express or implied.  See the License for the
021 * specific language governing permissions and limitations
022 * under the License.
023 * ====================================================================
024 *
025 * This software consists of voluntary contributions made by many
026 * individuals on behalf of the Apache Software Foundation.  For more
027 * information on the Apache Software Foundation, please see
028 * <http://www.apache.org/>.
029 *
030 */
031
032package org.apache.commons.ssl;
033
034import javax.net.ssl.SSLSocket;
035import java.io.File;
036import java.io.InputStream;
037import java.io.OutputStream;
038import java.net.InetAddress;
039import java.net.Socket;
040import java.security.cert.X509Certificate;
041import java.util.Collections;
042import java.util.HashMap;
043import java.util.Iterator;
044import java.util.Map;
045import java.util.SortedSet;
046import java.util.TreeSet;
047
048/**
049 * @author Credit Union Central of British Columbia
050 * @author <a href="http://www.cucbc.com/">www.cucbc.com</a>
051 * @author <a href="mailto:juliusdavies@cucbc.com">juliusdavies@cucbc.com</a>
052 * @since 30-Mar-2006
053 */
054public class Ping {
055    protected static SortedSet ARGS = new TreeSet();
056    protected static Map ARGS_MATCH = new HashMap();
057    protected final static Arg ARG_TARGET = new Arg("-t", "--target", "[hostname[:port]]              default port=443", true);
058    protected final static Arg ARG_BIND = new Arg("-b", "--bind", "[hostname[:port]]              default port=0 \"ANY\"");
059    protected final static Arg ARG_PROXY = new Arg("-r", "--proxy", "[hostname[:port]]              default port=80");
060    protected final static Arg ARG_TRUST_CERT = new Arg("-tm", "--trust-cert", "[path to trust material]       {pem, der, crt, jks}");
061    protected final static Arg ARG_CLIENT_CERT = new Arg("-km", "--client-cert", "[path to client's private key] {jks, pkcs12, pkcs8}");
062    protected final static Arg ARG_CERT_CHAIN = new Arg("-cc", "--cert-chain", "[path to client's cert chain for pkcs8/OpenSSL key]");
063    protected final static Arg ARG_PASSWORD = new Arg("-p", "--password", "[client cert password]");
064    protected final static Arg ARG_HOST_HEADER = new Arg("-h", "--host-header", "[http-host-header]      in case -t is an IP address");
065    protected final static Arg ARG_PATH = new Arg("-u", "--path", "[path for GET/HEAD request]    default=/");
066    protected final static Arg ARG_METHOD = new Arg("-m", "--method", "[http method to use]           default=HEAD");
067
068    private static HostPort target;
069    private static HostPort local;
070    private static HostPort proxy;
071    private static String hostHeader;
072    private static String httpMethod = "HEAD";
073    private static String path = "/";
074    private static InetAddress targetAddress;
075    private static InetAddress localAddress;
076    private static int targetPort = 443;
077    private static int localPort = 0;
078    private static File clientCert;
079    private static File certChain;
080    private static char[] password;
081    private static TrustChain trustChain = new TrustChain();
082
083    static {
084        ARGS = Collections.unmodifiableSortedSet(ARGS);
085        ARGS_MATCH = Collections.unmodifiableMap(ARGS_MATCH);
086    }
087
088    public static void main(String[] args) throws Exception {
089        boolean showUsage = args.length == 0;
090        Exception parseException = null;
091        if (!showUsage) {
092            try {
093                parseArgs(args);
094            }
095            catch (Exception e) {
096                parseException = e;
097                showUsage = true;
098            }
099        }
100        if (showUsage) {
101            if (parseException != null) {
102                System.out.println();
103                System.out.println("* Error: " + parseException.getMessage() + ".");
104                parseException.printStackTrace(System.out);
105                System.out.println();
106            }
107            System.out.println("Usage:  java -jar not-yet-commons-ssl-0.3.9.jar [options]");
108            System.out.println(Version.versionString());
109            System.out.println("Options:   (*=required)");
110            Iterator it = ARGS.iterator();
111            while (it.hasNext()) {
112                Arg a = (Arg) it.next();
113                String s = Util.pad(a.shortArg, 3, false);
114                String l = Util.pad(a.longArg, 18, false);
115                String required = a.isRequired ? "*" : " ";
116                String d = a.description;
117                System.out.println(required + "  " + s + " " + l + " " + d);
118            }
119            System.out.println();
120            String example = "java -jar commons-ssl.jar -t host.com:443 -c ./client.pfx -p `cat ./pass.txt` ";
121            System.out.println("Example:");
122            System.out.println();
123            System.out.println(example);
124            System.out.println();
125            System.exit(1);
126            return;
127        }
128
129        SSLClient ssl = new SSLClient();
130        Socket s = null;
131        InputStream in = null;
132        OutputStream out = null;
133        Exception socketException = null;
134        Exception trustException = null;
135        Exception hostnameException = null;
136        Exception crlException = null;
137        Exception expiryException = null;
138        String sslCipher = null;
139        try {
140            try {
141                ssl.setCheckHostname(false);
142                ssl.setCheckExpiry(false);
143                ssl.setCheckCRL(false);
144                ssl.addTrustMaterial(TrustMaterial.TRUST_ALL);
145                if (clientCert != null) {
146
147                    KeyMaterial km;
148                    if (certChain != null) {
149                        km = new KeyMaterial(clientCert, certChain, password);
150                    } else {
151                        km = new KeyMaterial(clientCert, password);
152                    }
153                    if (password != null) {
154                        for (int i = 0; i < password.length; i++) {
155                            password[i] = 0;
156                        }
157                    }
158                    ssl.setKeyMaterial(km);
159                }
160
161                if (!trustChain.isEmpty()) {
162                    ssl.addTrustMaterial(trustChain);
163                }
164
165                ssl.setSoTimeout(10000);
166                ssl.setConnectTimeout(5000);
167
168                if (proxy != null) {
169                    s = new Socket(proxy.host, proxy.port,
170                        local.addr, local.port);
171                    s.setSoTimeout(10000);
172                    in = s.getInputStream();
173                    out = s.getOutputStream();
174                    String targetHost = target.host;
175                    String line1 = "CONNECT " + targetHost + ":" + targetPort + " HTTP/1.1\r\n";
176                    String line2 = "Proxy-Connection: keep-alive\r\n";
177                    String line3 = "Host: " + targetHost + "\r\n\r\n";
178                    out.write(line1.getBytes());
179                    out.write(line2.getBytes());
180                    out.write(line3.getBytes());
181                    out.flush();
182
183                    String read1 = Util.readLine(in);
184                    if (read1.startsWith("HTTP/1.1 200")) {
185                        int avail = in.available();
186                        in.skip(avail);
187                        Thread.yield();
188                        avail = in.available();
189                        while (avail != 0) {
190                            in.skip(avail);
191                            Thread.yield();
192                            avail = in.available();
193                        }
194                        s = ssl.createSocket(s, targetHost, targetPort, true);
195                    } else {
196                        System.out.print(line1);
197                        System.out.print(line2);
198                        System.out.print(line3);
199                        System.out.println("Server returned unexpected proxy response!");
200                        System.out.println("=============================================");
201                        System.out.println(read1);
202                        String line = Util.readLine(in);
203                        while (line != null) {
204                            System.out.println(line);
205                            line = Util.readLine(in);
206                        }
207                        System.exit(1);
208                    }
209                } else {
210                    s = ssl.createSocket(targetAddress, targetPort,
211                        localAddress, localPort);
212                }
213
214                sslCipher = ((SSLSocket) s).getSession().getCipherSuite();
215                System.out.println("Cipher: " + sslCipher);
216                System.out.println("================================================================================");
217
218                String line1 = httpMethod + " " + path + " HTTP/1.1";
219                if (hostHeader == null) {
220                    hostHeader = targetAddress.getHostName();
221                }
222                String line2 = "Host: " + hostHeader;
223                byte[] crlf = {'\r', '\n'};
224
225                System.out.println("Writing: ");
226                System.out.println("================================================================================");
227                System.out.println(line1);
228                System.out.println(line2);
229                System.out.println();
230
231                out = s.getOutputStream();
232                out.write(line1.getBytes());
233                out.write(crlf);
234                out.write(line2.getBytes());
235                out.write(crlf);
236                out.write(crlf);
237                out.flush();
238
239                in = s.getInputStream();
240
241                int c = in.read();
242                StringBuffer buf = new StringBuffer();
243                System.out.println("Reading: ");
244                System.out.println("================================================================================");
245                while (c >= 0) {
246                    byte b = (byte) c;
247                    buf.append((char) b);
248                    System.out.print((char) b);
249                    if (-1 == buf.toString().indexOf("\r\n\r\n")) {
250                        c = in.read();
251                    } else {
252                        break;
253                    }
254                }
255            }
256            catch (Exception e) {
257                socketException = e;
258            }
259            trustException = testTrust(ssl, sslCipher);
260            hostnameException = testHostname(ssl);
261            crlException = testCRL(ssl);
262            expiryException = testExpiry(ssl);
263        }
264        finally {
265            if (out != null) {
266                out.close();
267            }
268            if (in != null) {
269                in.close();
270            }
271            if (s != null) {
272                s.close();
273            }
274
275            X509Certificate[] peerChain = ssl.getCurrentServerChain();
276            if (peerChain != null) {
277                String title = "Server Certificate Chain for: ";
278                title = peerChain.length > 1 ? title : "Server Certificate for: ";
279                System.out.println(title + "[" + target + "]");
280                System.out.println("================================================================================");
281                for (int i = 0; i < peerChain.length; i++) {
282                    X509Certificate cert = peerChain[i];
283                    String certAsString = Certificates.toString(cert);
284                    String certAsPEM = Certificates.toPEMString(cert);
285                    if (i > 0) {
286                        System.out.println();
287                    }
288                    System.out.print(certAsString);
289                    System.out.print(certAsPEM);
290                }
291            }
292            if (hostnameException != null) {
293                hostnameException.printStackTrace();
294                System.out.println();
295            }
296            if (crlException != null) {
297                crlException.printStackTrace();
298                System.out.println();
299            }
300            if (expiryException != null) {
301                expiryException.printStackTrace();
302                System.out.println();
303            }
304            if (trustException != null) {
305                trustException.printStackTrace();
306                System.out.println();
307            }
308            if (socketException != null) {
309                socketException.printStackTrace();
310                System.out.println();
311            }
312        }
313    }
314
315    private static Exception testTrust(SSLClient ssl, String cipher) {
316        try {
317            X509Certificate[] chain = ssl.getCurrentServerChain();
318            String authType = Util.cipherToAuthType(cipher);
319            if (authType == null) {
320                // default of "RSA" just for Ping's purposes.
321                authType = "RSA";
322            }
323            if (chain != null) {
324                Object[] trustManagers = TrustMaterial.DEFAULT.getTrustManagers();
325                for (int i = 0; i < trustManagers.length; i++) {
326                    JavaImpl.testTrust(trustManagers[i], chain, authType);
327                }
328            }
329        }
330        catch (Exception e) {
331            return e;
332        }
333        return null;
334    }
335
336    private static Exception testHostname(SSLClient ssl) {
337        try {
338            X509Certificate[] chain = ssl.getCurrentServerChain();
339            if (chain != null) {
340                String hostName = target.host;
341                HostnameVerifier.DEFAULT.check(hostName, chain[0]);
342            }
343        }
344        catch (Exception e) {
345            return e;
346        }
347        return null;
348    }
349
350    private static Exception testCRL(SSLClient ssl) {
351        try {
352            X509Certificate[] chain = ssl.getCurrentServerChain();
353            if (chain != null) {
354                for (int i = 0; i < chain.length; i++) {
355                    Certificates.checkCRL(chain[i]);
356                }
357            }
358        }
359        catch (Exception e) {
360            return e;
361        }
362        return null;
363    }
364
365    private static Exception testExpiry(SSLClient ssl) {
366        try {
367            X509Certificate[] chain = ssl.getCurrentServerChain();
368            if (chain != null) {
369                for (int i = 0; i < chain.length; i++) {
370                    chain[i].checkValidity();
371                }
372            }
373        }
374        catch (Exception e) {
375            return e;
376        }
377        return null;
378    }
379
380
381    public static class Arg implements Comparable {
382        public final String shortArg;
383        public final String longArg;
384        public final String description;
385        public final boolean isRequired;
386        private final int id;
387
388        public Arg(String s, String l, String d) {
389            this(s, l, d, false);
390        }
391
392        public Arg(String s, String l, String d, boolean isRequired) {
393            this.isRequired = isRequired;
394            this.shortArg = s;
395            this.longArg = l;
396            this.description = d;
397            this.id = ARGS.size();
398            ARGS.add(this);
399            if (s != null && s.length() >= 2) {
400                ARGS_MATCH.put(s, this);
401            }
402            if (l != null && l.length() >= 3) {
403                ARGS_MATCH.put(l, this);
404            }
405        }
406
407        public int compareTo(Object o) {
408            return id - ((Arg) o).id;
409        }
410
411        public String toString() {
412            return shortArg + "/" + longArg;
413        }
414    }
415
416    private static void parseArgs(String[] cargs) throws Exception {
417        Map args = Util.parseArgs(cargs);
418        Iterator it = args.entrySet().iterator();
419        while (it.hasNext()) {
420            Map.Entry entry = (Map.Entry) it.next();
421            Arg arg = (Arg) entry.getKey();
422            String[] values = (String[]) entry.getValue();
423            if (arg == ARG_TARGET) {
424                target = Util.toAddress(values[0], 443);
425                targetAddress = target.addr;
426                targetPort = target.port;
427            } else if (arg == ARG_BIND) {
428                local = Util.toAddress(values[0], 443);
429                localAddress = local.addr;
430                localPort = local.port;
431            } else if (arg == ARG_PROXY) {
432                proxy = Util.toAddress(values[0], 80);
433            } else if (arg == ARG_CLIENT_CERT) {
434                clientCert = new File(values[0]);
435            } else if (arg == ARG_CERT_CHAIN) {
436                certChain = new File(values[0]);
437            } else if (arg == ARG_PASSWORD) {
438                password = values[0].toCharArray();
439            } else if (arg == ARG_METHOD) {
440                httpMethod = values[0].trim();
441            } else if (arg == ARG_PATH) {
442                path = values[0].trim();
443            } else if (arg == ARG_HOST_HEADER) {
444                hostHeader = values[0].trim();
445            } else if (arg == ARG_TRUST_CERT) {
446                for (int i = 0; i < values.length; i++) {
447                    File f = new File(values[i]);
448                    if (f.exists()) {
449                        TrustMaterial tm = new TrustMaterial(f);
450                        trustChain.addTrustMaterial(tm);
451                    }
452                }
453            }
454        }
455        args.clear();
456        for (int i = 0; i < cargs.length; i++) {
457            cargs[i] = null;
458        }
459
460        if (targetAddress == null) {
461            throw new IllegalArgumentException("\"" + ARG_TARGET + "\" is mandatory");
462        }
463    }
464}