[an error occurred while processing this directive] IT • archiv :: Print

IT • archiv


[an error occurred while processing this directive]

[an error occurred while processing this directive]

Реализация тоннелирования HTTPS с помощью JSSE

[an error occurred while processing this directive](none) [an error occurred while processing this directive](none)[an error occurred while processing this directive] ::
[an error occurred while processing this directive](none)
[an error occurred while processing this directive]([an error occurred while processing this directive] Пу Йоу Чонг [an error occurred while processing this directive])

[an error occurred while processing this directive](none)

Создание собственного сокета для тоннелирования HTTPS под приложения Java Secure Socket Extension.

Tips 'N Tricks
PDF versionPDF версия
Обзор
Библиотека Java Secure Socket Extension (JSSE), созданная команией Sun, даёт вам доступ к защищённому Web-серверу из-за пределов firewall посредством proxy-тоннелирования. В этой статье вы узнаетет, как открыть SSLSocket, проходящий через proxy, и использовать его с URLConnection APIs для коммуникации с защищёнными Web-серверами, находясь за пределами firewall. (1100 слов)

Библиотека Java Secure Socket Extension (JSSE), созданная команией Sun, даёт вам доступ к защищённому Web-серверу из-за пределов firewall посредством proxy-тоннеллирования.При этом JSSE дожидается ответа от proxy на запрос о прохождении, чтобы начать с "HTTP 1.0"; в противном случае, она посылает IOException. Если ваш proxy не реагирует должным образом, вы должны реализовать свой собственный тоннельный протокол для proxy. В этой статье вы узнаетет, как открыть SSLSocket, проходящий через proxy, и использовать его с URLConnection APIs для коммуникации с защищёнными Web-серверами из-за пределов firewall. И так, Java Secure Socket Extension (JSSE) обеспечивает доступ к серверу, но чтобы это сделать, JSSE-приложение должно установить системные свойсва https.ProxyHost и https.ProxyPort. Тоннельный код в JSSE осуществляет поиск "HTTP 1.0" в полученном от proxy ответе. Если ваш proxy, как и многие другие, возвращает "HTTP 1.1", вы получите IOException. В этом случае, вам понадобиться реализовать свой собственный тоннельный HTTPS-протокол. Я покажу, как создать защищённый сокет, способный проходить через firewall, и передать его в обработчик HTTPS-потока, чтобы открыть HTTPS URL с помощью класса URLConnection.

Как открыть тоннельный сокет к proxy

Первым, что нужно сделать при создании своего безопасного сокета, будет открытие тоннельного сокета к порту proxy. Код для этой утомительной процедуры можно найти в примере SSLClientSocketWithTunneling.java , который вкладывается в пакет при получении JSSE. Сначала создаётся обычный сокет, соедининяющий вас с портом proxy на хосте этого proxy (строка 65). Затем создаётся сам сокет, которой передаётся методу doTunnelHandshake(), в котором вызывается тоннельный протокол нашего proxy:

54         SSLSocketFactory factory =
55                  (SSLSocketFactory)SSLSocketFactory.getDefault();
56
57         /*
58         * Set up a socket to do tunneling through the proxy.
59         * Start it off as a regular socket, then layer SSL
60         * over the top of it.
61         */
62         tunnelHost = System.getProperty("https.proxyHost");
63         tunnelPort = Integer.getInteger("https.proxyPort").intValue();
64
65         Socket tunnel = new Socket(tunnelHost, tunnelPort);
66         doTunnelHandshake(tunnel, host, port);

В doTunnelHandshake(), к proxy посылается http-команда "CONNECT", с именем защищённого сайта и номером порта в качестве параметров (строка 161). В исходном тоннельном коде в строке 206 в JSSE код затем проверяет наличие "HTTP/1.0 200" в полученном от proxy ответе. Если proxy вашей компании содержит в ответе "HTTP 1.1", вываливается IOException. Чтобы обойти это, в нашем случае код будет искать в ответе от proxy "200 Connection Established", что будет означать успешное построение тоннеля (строка 206). Вы можете модифицировать код, чтобы он проверял наличе нужного вам параметра в ответе от proxy.

139   private void doTunnelHandshake(Socket tunnel, String host, int port)
140                                 throws IOException
141   {
142      OutputStream out = tunnel.getOutputStream();
143      String msg = "CONNECT " + host + ":" + port + " HTTP/1.0\n"
144                  + "User-Agent: "
145                  + sun.net.www.protocol.http.HttpURLConnection.userAgent
146                  + "\r\n\r\n";
147      byte b[];
148      try {
149         /*
150         * We really do want ASCII7 -- the http protocol doesn't change
151         * with locale.
152         */
153         b = msg.getBytes("ASCII7");
154      } catch (UnsupportedEncodingException ignored) {
155         /*
156         * If ASCII7 isn't there, something serious is wrong, but
157         * Paranoia Is Good (tm)
158         */
159         b = msg.getBytes();
160      }
161      out.write(b);
162      out.flush();
163
164      /*
165      * We need to store the reply so we can create a detailed
166      * error message to the user.
167      */
168      byte           reply[] = new byte[200];
169      int            replyLen = 0;
170      int            newlinesSeen = 0;
171      boolean        headerDone = false;     /* Done on first newline */
172
173      InputStream    in = tunnel.getInputStream();
174      boolean        error = false;
175
176      while (newlinesSeen < 2) {
177         int i = in.read();
178         if (i < 0) {
179            throw new IOException("Unexpected EOF from proxy");
180         }
181         if (i == '\n') {
182            headerDone = true;
183            ++newlinesSeen;
184         } else if (i != '\r') {
185            newlinesSeen = 0;
186            if (!headerDone && replyLen < reply.length) {
187               reply[replyLen++] = (byte) i;
188            }
189         }
190      }
191
192      /*
193      * Converting the byte array to a string is slightly wasteful
194      * in the case where the connection was successful, but it's
195      * insignificant compared to the network overhead.
196      */
197      String replyStr;
198      try {
199         replyStr = new String(reply, 0, replyLen, "ASCII7");
200      } catch (UnsupportedEncodingException ignored) {
201         replyStr = new String(reply, 0, replyLen);
202      }
203
204      /* We check for Connection Established because our proxy returns
205       * HTTP/1.1 instead of 1.0 */
206      //if (!replyStr.startsWith("HTTP/1.0 200")) {
207      if(replyStr.toLowerCase().indexOf(
208                                    "200 connection established") == -1){
209         throw new IOException("Unable to tunnel through "
210                              + tunnelHost + ":" + tunnelPort
211                              + ".  Proxy returns \"" + replyStr + "\"");
212      }
213
214      /* tunneling Handshake was successful! */
215   }

Маскировка тоннельного сокета под SSL-сокет

После того как вы успешно создали тоннельный сокет, вы маскируетет его под SSL-сокет. Здесь тоже всё предельно просто:

54         SSLSocketFactory factory =
55                  (SSLSocketFactory)SSLSocketFactory.getDefault();
56
57         /*
58         * Set up a socket to do tunneling through the proxy.
59         * Start it off as a regular socket, then layer SSL
60         * over the top of it.
61         */
62         tunnelHost = System.getProperty("https.proxyHost");
63         tunnelPort = Integer.getInteger("https.proxyPort").intValue();
64
65         Socket tunnel = new Socket(tunnelHost, tunnelPort);
66         doTunnelHandshake(tunnel, host, port);
67
68         /*
69         * Ok, let's overlay the tunnel socket with SSL.
70         */
71         SSLSocket socket =
72               (SSLSocket)factory.createSocket(tunnel, host, port, true);
73
74         /*
75         * register a callback for handshaking completion event
76         */
77         socket.addHandshakeCompletedListener(
78            new HandshakeCompletedListener() {
79               public void handshakeCompleted(
80                  HandshakeCompletedEvent event) {
81                  System.out.println("Handshake finished!");
82                  System.out.println(
83                  "\t CipherSuite:" + event.getCipherSuite());
84                  System.out.println(
85                  "\t SessionId " + event.getSession());
86                  System.out.println(
87                  "\t PeerHost " + event.getSession().getPeerHost());
88               }
89            }
90         );

До этого код вызвал из SSLSocketFactory метод getDefault(), чтобы получить экземпляр SSLSocketFactory (строка 54). Затем он пересылает тоннельный сокет, созданный на предыдущем этапе, в метод createSocket() этого SSLSocketFactory. Метод createSocket() возвращает SSLSocket, подсоединённый к необходимому нам хосту и порту через тоннель proxy. По желанию можно добавить в сокет HandshakeCompletedListener, если вы хотите получать сигнал о завершении SSL-операции.

Созданный SSLSocket практически готов к работе для передачи защищённых данных. Метод startHandshake() вызывается для инициации процедуры подключения SSL (строка 98). После этого вы можете задать http-команду "GET", чтобы просмотреть полученные страницы (строка 105):

91
92         /*
93         * send http request
94         *
95         * See SSLSocketClient.java for more information about why
96         * there is a forced handshake here when using PrintWriters.
97         */
98         socket.startHandshake();
99
100         PrintWriter out = new PrintWriter(
101                              new BufferedWriter(
102                                 new OutputStreamWriter(
103                                    socket.getOutputStream())));
104
105         out.println("GET htt?://www.verisign.com/ HTTP/1.0");
106         out.println();
107         out.flush();

И всё же, использование http-команд для SSL-сокета при доступе к Web-страницам не является идеальным подходом, поскольку это может означать необходимость загрузки обработчика http-протокола. Вместо этого, стоит воспользоваться HTTPS URL APIs, включёнными в состав JSSE специально для этой цели. Для этого вам нужно передать тоннельный SSL-сокет в обработчик потока HTTPS URL.

Передача SSL-сокета в обработчик потока HTTPS URL

Библиотека JSSE содержит в пакете com.sun.net.ssl класс HttpsURLConnection, расширяющий класс java.net.URLConnection. Объект HttpsURLConnection возврящается методом openConnection() URL-объекта, в случае если в качестве протокола указан "HTTPS". Класс HttpsURLConnection содержит метод setSSLSocketFactory(), позволяющий устанавливать SSLSocketFactory по вашему выбору. Чтобы передать SSL-сокет в обработчик потока HTTPS URL, вам нужно будет установить параметры метода setSSLSocketFactory() с помощью конструктора сокета, возвращающего тоннельный SSL-сокет, созданный вами ранее. Для этого вам нужно вложить ранее рассмотренный код в класс SSLTunnelSocketFactory, расширяющий класс SSLSocketFactory. Класс SSLSocketFactory является абстрактным. Для его расширения вам нужно реализовать метод createSocket(), чтобы вернуть тоннельный SSL-сокет, созданный вами ранее:

12   public SSLTunnelSocketFactory(String proxyhost, String proxyport){
13      tunnelHost = proxyhost;
14      tunnelPort = Integer.parseInt(proxyport);
15      dfactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
16   }
.
.
.
44   public Socket createSocket(Socket s, String host, int port,
45                              boolean autoClose)
46                              throws IOException,UnknownHostException
47   {
48
49      Socket tunnel = new Socket(tunnelHost,tunnelPort);
50
51      doTunnelHandshake(tunnel,host,port);
52
53      SSLSocket result = (SSLSocket)dfactory.createSocket(
54                                       tunnel,host,port,autoClose);
55
56      result.addHandshakeCompletedListener(
57         new HandshakeCompletedListener() {
58         public void handshakeCompleted(HandshakeCompletedEvent event) {
59            System.out.println("Handshake finished!");
60            System.out.println(
61            "\t CipherSuite:" + event.getCipherSuite());
62            System.out.println(
63            "\t SessionId " + event.getSession());
64            System.out.println(
65            "\t PeerHost " + event.getSession().getPeerHost());
66         }
67         }
68      );
69
70      result.startHandshake();
71
72      return result;
73   }

Обратите внимание на то, что SSLTunnelSocketFactory по умолчанию содержит объект SSLSocketFactory. Этот объект SSLSocketFactory может устанавливаться вызовом статичного метода getDefault() (строка 15). Этот объект SSLSocketFactory необходим вам для наложения на тоннельный сокет SSL-сокета, как это говорилось ранее. Вы также вызываете методы getDefaultCipherSuites() и getSupportedCipherSuites() из вашего объекта по умолчанию в процессе реализации соответсвующих абстрактных методов супер-класса SSLSocketFactory. Детали этой реализации можно посмотреть в исходных кодах SSLTunnelSocketFactory в Дополнительных источниках.

Тоннель через proxy посредством URLConnection

Проход через proxy с помощью URLConnection в вашем JSSE-приложении будет выполнен после того, как вы вызовите метод openConnection() и проверите, является ли возвращённый объект объектом HttpsURLConnection. Если да, вы создаёте экземпляр своего объекта SSLTunnelSocketFactory и устанавливаете его в методе setSSLSocketFactory() (строки 22 - 25):

10 public class URLTunnelReader {
11   private final static String proxyHost = "proxy.sg.ibm.com";
12   private final static String proxyPort = "80";
13
14   public static void main(String[] args) throws Exception {
15      System.setProperty("java.protocol.handler.pkgs",
16                               "com.sun.net.ssl.internal.www.protocol");
17      //System.setProperty("https.proxyHost",proxyHost);
18      //System.setProperty("https.proxyPort",proxyPort);
19
20      URL verisign = new URL("htt?s://www.verisign.com");
21      URLConnection urlc = verisign.openConnection(); //from secure site
22      if(urlc instanceof com.sun.net.ssl.Htt?sURLConnection){
23             ((com.sun.net.ssl.HttsURLConnection)urlc).setSSLSocketFactory
24                      (new SSLTunnelSocketFactory(proxyHost,proxyPort));
25      }
26
27      BufferedReader in = new BufferedReader(
28                                    new InputStreamReader(
29                                              urlc.getInputStream()));
30
31      String inputLine;
32
33      while ((inputLine = in.readLine()) != null)
34         System.out.println(inputLine);
35
36      in.close();
37   }
38 }

Теперь вы можете получать доступ к HTTPS URL с помощью API, предоставляемыми классом URLConnection. Вам уже не нужно беспокоиться о формате http-команд GET и POST, как в случае если бы вы пользовались SSL Socket API. Полностью исходный код для SSLTunnelSocketFactory и код приложения для соединения с защищённым URL с помощью тоннеля proxy можно найти в Дополнительных источниках. Для компиляции и выполнения приложения вам придётся скачать и инсталлировать JSSE с сайта Sun, тоже указанного в Дополнительных источниках.

Заключение

Если ваше JSSE-приложение не может пробить себе тоннель через firewall вашей организации, вам нужно реализовать свой собственный тоннельный сокет. Образец кода включён пакет JSSE и показывает, как открывать тоннель SSL-сокета. В этой статье мы идём несколько дальше, чтобы показать, как передать тоннельный сокет обработчику потока HTTPS URL и помочь вам избежать необходимости переписывания http-обработчика.

Об авторе

Pua Yeow Cheong работает программым инженером в IBM Emerging Technology Center в Сингапуре, занимаясь созданием Java-решений для e-commerce и новых вычислительных технологий. В сферу его деятельности входит динамическое транскодирование защищённых Web-данных в WML для их отображения на беспроводных средствах связи, работающих через WAP. Суждения, высказанные в данной статье, не являются официальной позицией IBM или её подразделений, а принадлежат исключительно автору.

Ресурсы

Reprinted with permission from JavaWorld magazine. Copyright © ITworld.com, Inc., an IDG Communications company.
View the original article at: http://www.javaworld.com/javaworld/ javatips/jw-javatip111.html

Русский перевод опубликован с разрешения Java Журнал © IBA Java Team, 2001. Оригинал статьи: http://java.iba.by/javaweb/ibajavat.nsf/ lnarticlesview/79AD56F1218F5F9242256A560035D1DC

[an error occurred while processing this directive]
[an error occurred while processing this directive] Перевод на русский © Андрей Чернухо, 2001
< Вернуться на caйт :: Copyright © 1999 — 2010, IT • archiv.