ITPub博客

首页 > 应用开发 > IT综合 > [转载]Internet安全编程----客户端

[转载]Internet安全编程----客户端

原创 IT综合 作者:ccainiao1 时间:2007-01-31 23:27:58 0 删除 编辑

客户端

  Java 安全套接扩展 (Java Secure Socket Extension, JSSE) 使 Internet 安全通信成为现实。

它是 SSL 3.0 (Secure Socket Layer) TLS 1.0 (Transport Layer Security,由 SSL 3.0 改善而来) 的框架和实现。这个包让 Java 开发人员能够开发安全的网络应用;为基于 TCP/IP 的何应用协议,如 HTTPFTPTelnet、或者 NTTP,在客户端和服务器端之间建立安全的数据通道。   

[@more@]

在这篇文章的第一部分 (服务器端),作者已经详细说明了 SSL JSSE,并且说明了如何开发服务器端支持 SSL 应用程序。那一部分中我们开发了一个 HTTPS 服务器,这是一个非常有用的应用程序,在这一部分中同样会用到它。  在这篇文章涉及到客户端的内容,它首先简述 JSSE,然后会做这样一些事情在客户端使用 JSSE API 一步步的开发一个支持 SSL 的客户端应用程序 开发简单的支持 SSL 的客户端应用程序 从服务器端导出证书并在客户端导入 开发一个支持 SSL 的网页浏览器
JSSE
  Java 安全套接扩展 (JSSE) 提供了 SSL TLS 协议的框架及实现。JSSE 将复杂的、根本的加密算法抽象化了,这样就降低了受到敏感或者危险的安全性攻击的风险。正如你在本文中看到的那样,由于它能将 SSL 无缝地结合在应用当然,使安全应用的开发变得非常简单。JSSE 框架可以支撑许多不同的安全通信协议,如 SSL 2.0 3.0 以及 TLS 1.0,但是 J2SE v1.4 只实现了 SSL 3.0 TLS 1.0 JSSE 编写客户端应用程序  JSSE API 提供了扩充的网络套接字类、信用和密匙管理,以及为简化套接字创建而设计的套接字工厂框架,以此扩充 java.security java.net 两个包。这些类都包含在 javax.net javax.net.ssl 包中。  javax.net.sll.SSLSocketFactory 类是一个创建安全套接字的对象工厂。可以通过下面两种方法获得 SSLSocketFactory 的实例:调用 SSLSocketFactory.getDefault 来获得默认的工厂。默认的工厂被配置为只允许服务器端验证 (不允许客户端验证)。注意许多电子商务网站不需要客户端验证。 使用指定的配置来构造一个新的工厂 (这不在本文讲述的范围内)   建立 SSLSocketFactory 实例之后,你就可以通过 SSLSocketFactory 实例的 createSocket 方法创建 SSLSocket 对象了。这里有一个例子,该例通过 SSL 端口 443 (这是 HTTPS 的默认端口) 创建套接字并连接到 Sun WWW 服务器。

// Get a Socket factory
SocketFactory factory = SSLSocketFactory.getDefault();

// Get Socket from factory
Socket socket = factory.createSocket(" 443);
使用低层的 SSL 套接字  现在,让我们看一个使用低层套接字在 HTTPS 服务器上打开一个 SSL 套接字连接的完整例子。在这个例子中,打开了一个到 HTTPS 服务器的 SSL 套接字连接,并且读入默认文档的内容。示例代码 1 展示了这个应用程序,其中用于打开 SSL 套接字的代码已经加黑显示了。你将会看到,应用程序中其余代码就是常规的输入/输出流代码。  代码示例 1ReadHttpsURL1

import java.net.*;
import javax.net.*;
import javax.net.ssl.*;

public class ReadHttpsURL1 {
static final int HTTPS_PORT = 443;

public static void main(String argv[]) throws Exception {
if (argv.length != 1) {
System.out.println("Usage: java ReadHttpsURL1 ");
System.exit(0);
}

// Get a Socket factory
SocketFactory factory = SSLSocketFactory.getDefault();

// Get Socket from factory
Socket socket = factory.createSocket(argv[0], HTTPS_PORT);

BufferedWriter out
= new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader in
= new BufferedReader(new InputStreamReader(socket.getInputStream()));
out.write("GET / HTTP/1.0nn");
out.flush();

String line;
StringBuffer sb = new StringBuffer();
while((line = in.readLine()) != null) {
sb.append(line);
}
out.close();
in.close();
System.out.println(sb.toString());
}
}
  用这个应用程序进行实验:拷贝 ReadHttpsURL1 类的代码并粘贴到一个新文件中,将该文件改名为 ReadHttpsURL1.java,并保存在一个你指定的目录下。 使用 javac 编译 ReadHttpsURL1.java 运行 ReadHttpsURL1 并提供一个域名作为参数,如:
Prompt> java ReadHttpsURL1
几秒种后,你会看到许多 HTML 代码显示在屏幕上。注意,即使我们提供的是域名 ,我们打开的连接也是 https://,这是因为我们使用的端口号 443 HTTPS 的默认端口号。   再试试另一个例子,如:

Prompt> java ReadHttpsURL1
  这次运行会抛出如下所示的异常,你能猜到是为什么吗?

Exception in thread "main" javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: Couldn't find trusted certificate at com.sun.net.ssl.internal.ssl.BaseSSLSocketImpl.a(DashoA6275)
  缘于一个很好的理由,它不能运行??因为远端的服务器发送了一个客户端不认识的证书。我在本文的第一部分提到过,当客户端连接服务器的时候,服务器发送它的证书到客户端请求验证。这样,第一个例子中,你进入了 ,服务器的确发送了证书,但 Java 检查了默认的证书库并认出了这个证书是由可信任的 CA 产生的,默认情况下,Java 信任这个 CA。第二个例子中,你进入的是 ,那个网端的证书不是它自己产生的,就是由一个 Java 不知道的 CA 产生的,因此不受信任。


--------------------------------------------------------------------------------
注意,如果系统时钟没有设置正确,那么它的时间就可能在证书的有效期之外,服务器会认为证书无效并抛出 CertificateException 异常。
--------------------------------------------------------------------------------
  为了让示例正确运行,你得从 导入证收到 Java 信任的证书库中。导出和导入证书  为了解释清楚如何输出和输入证书,我会使用我自己的 HTTPS 服务器。这个服务器在第一部分中讨论过。然后,跟着下面的内容开始:运行 HTTPS 服务器,像在第一部分中讨论的那样。 运行 ReadHttpsURL1java ReadHttpsURL1 localhost。你同样会得到上面所述的异常。 使用下面的 keytool 命令导出服务器证书: serverkeys 文件中导出别名为 qusay 的证书 将导出的证书保存在 server.cert 文件中,这个文件会由 keytool 创建如你看到的那样,我根据要求输入了密码。成功输入密码之后,服务器证书被成功的导出并保存在 server.cert 中。
Prompt> keytool -export -keystore serverkeys -alias qusay -file server.cert

Enter keystore password: hellothere
Certificate stored in file
将文件 server.cert 拷贝到 ReadHttpsURL1 所在的目录。使用 keytool 创建一个新的 keystore 并将服务器的 server.cert 证书导入其中。这里的命令示例:
Prompt> keytool -import -keystore trustedcerts -alias qusay -file server.cert
这个命令会产生下面那样的输出。它要求输入密码,这是一个新的密码,用于 trustedcerts 这个 keystore 的。这个 keystore keytool 创建。在输出信息的最后,它询问我是否愿意相信这个证书,我回答 yes
Enter keystore password: clientpass
Owner: CN=localhost, OU=Training and Consulting, O=javacourses.com, L=Toronto, ST=Ontario, C=CA
Issuer: CN=localhost, OU=Training and Consulting, O=javacourses.com, L=Toronto, ST=Ontario, C=CA
Serial number: 3dcf988a
Valid from: Mon Nov 11 06:46:18 EST 2002 until: Sun Feb 09 06:46:18 EST 2003
Certificate fingerprints:
MD5: 37:35:4D:3A:2B:7E:B5:09:A5:41:B3:FA:E4:3C:1D:C4
SHA1: CB:7C:77:36:79:A2:37:26:E2:98:61:C2:9D:10:50:69:
99:F9:B9:1B
Trust this certificate? [no]: yes
Certificate was added to keystore
现在运行 ReadHttpsURL1 并告诉它哪里能找到证书。使用下面的命令:
Prompt> java -Djavax.net.ssl.trustStore=trustedcerts ReadHttpsURL1 localhost
这将会与你的 HTTPS 服务器联接、校验证书,如果正确,它会下载默认页面 index.html

--------------------------------------------------------------------------------
注意:信任管理器负责决定远端的证书是否值得信任。它使用下面的规则: 如果 javax.net.sll.trustStore 系统属性指定了信任库,那么信任管理器会使用提供的文件来检查证书。如果那个系统属性存在但指定的文件不存在,那么就没有使用任何信任库,会抛出一个 CertificateException 异常。 如果 javax.net.sll.trustStore 系统属性没有定义,那么它会去寻找默认的信任库: 如果在你的 java.home 目录的 lib/security 子目录下存在名为 jssecacerts 的信任库,那么使用的就是它。 如果 jssecacerts 不存在,但是 cacerts 存在 (它随 J2SDK 一起发行,含有数量有限的可信任的基本证书),使用的就是 cacerts  在我的 Windows 2000 客户机中,java.home 目录是 c:Program Filejavajre1.4.1libsecurity,在上例中,如果你将 trustedcerts 更名为 jssecacerts 并将其移动到 lib/security 子目录中,那么你以后就不需要在命令行指定 javax.net.ssl.trustStore 属性了。  如果你不知道 java.home 在哪里,这里有一小段代码可以让你找到它:

public class FindJavaHome {
public static void main(String argv[]) {
System.out.println(System.getProperty("java.home"));
}
}

--------------------------------------------------------------------------------

URL
  示例代码 1 中的 ReadHttpsURL1 使用低层的套接字打开到 SSL 服务器的连接。这样做有一个缺点,如果不进行一番解析,我们就不能在命令行清楚的写出像 https:// 这样的 URL。这里有一个更简单的办法在客户端应用程序中使用 SSL JSSE  java.net.URL 类支持 HTTPS 地址。例如,下面的代码段创建一个 HTTPS 地址并建立一个输入流的读入器:

URL url = new URL("https://");
BufferedReader in
= new BufferedReader(new InputStreamReader(url.openStream()));
  是不是很简单?我希望当你学习 Java 的新东西时,你能欣赏到它的美好之处。  示例代码 1 中的 ReadHttpsURL1 可以由下面使用了 URL 类的示例代码 2 代替:  示例代码 2ReadHttpsURL2.java

import java.net.*;
import java.io.*;

public class ReadHttpsURL2 {
public static void main(String argv[]) throws Exception {
if(argv.length != 1) {
System.out.println("Usage: java ReadHttpsURL2 ");
System.exit(0);
}

URL url = new URL(argv[0]);
BufferedReader in
= new BufferedReader(new InputStreamReader(url.openStream()));

String line;
StringBuffer sb = new StringBuffer();
while ((line = in.readLine()) != null) {
sb.append(line);
}
in.close();
System.out.println(sb.toString());
}
}
  如果你想试试 ReadHttpsURL2,执行它的命令和上面讨论的类似。注意,无论如何,既然我们使用 URL 类,你就能在命令行指定 URL,包括协议的名称。这里是一个例子:

Prompt> java ReadHttpsURL2
开发一个支持 SSL 的网页浏览器  我们开发一个支持 SSL 的网页浏览器作为一个完整的例子。该浏览器要做下面的工作:用户输入 URL,浏览器能接收它。 浏览器能打开到 URL 指定主机的连接。 浏览器能发送 HTTP 命令。 浏览器会等待 HTTP/HTTPS 服务器的回应。 浏览器能接收 HTML 回应。 浏览器能解析 HTML 并显示出页面。   我们创建的浏览器要能处理任何 URL HTTPHTTPSFTP 等。注意我使用工具类 javax.swing.text.html.HTMLEditorKit 来解析 HTML,它提供了对 HTML 3.2 的支持。  示例代码 3 中展示了这个浏览器,QBrowser,的代码。注意 QBrowser 实现了 Runnable 接口。我这样做是因为这个浏览器没有提供停止按钮。  示例代码 3QBrowser.java

import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class QBrowser implements ActionListener, Runnable {
private JFrame frame;
private JButton go;
private JEditorPane content;
private JTextField url;
private JLabel statusLine;

// default constructor
public QBrowser () {
buildBrowserInterface();
}

private void buildBrowserInterface() {
frame = new JFrame("Q's Browser");
// on close, exit the application using System.exit(0);
frame.setDefaultCloseOperation (3);

url = new JTextField("", 25);
go = new JButton("Go Get It");
go.addActionListener(this);

JPanel controls = new JPanel(new FlowLayout ());
controls.add(new JLabel("URL:"));
controls.add(url);
controls.add(go);
content = new JEditorPane();
content.setEditable(false);
// HTML text. Use the kit in the class javax.swing.text.html.HTMLEditorKit, which
// provides support for HTML 3.2
content.setContentType("text/html");
content.setText("

Q's Browser

Copyright (c) 2002 Qusay H. Mahmoud

");
statusLine = new JLabel("Initialization Complete");

JPanel panel = new JPanel(new BorderLayout (0, 2));
frame.setContentPane(panel);

panel.add(controls, "North");
panel.add(new JScrollPane (content), "Center");
panel.add(statusLine, "South");
frame.pack();
frame.setVisible(true);
}

/**
* You cannot stop a download with QBrowser
* The thread allows multiple downloads to start
* concurrently in case a download freezes
*/
public void actionPerformed (ActionEvent event) {
Thread thread = new Thread(this);
thread.start();
}
// this is the Thread's run method
public void run () {
try {
String str = url.getText();
URL url = new URL(str);
readURL(url);
} catch (IOException ioe) {
statusLine.setText("Error: "+ioe.getMessage());
showException(ioe);
}
}

private void showException(Exception ex) {
StringWriter trace = new StringWriter ();
ex.printStackTrace (new PrintWriter (trace));
content.setContentType ("text/html");
content.setText ("

" + ex + "

" + trace + "
");
}

/**
* The URL class is capable of handling http:// and https:// URLs
*/
private void readURL(URL url) throws IOException {
statusLine.setText("Opening " + url.toExternalForm());
URLConnection connection = url.openConnection();
StringBuffer buffer = new StringBuffer();
BufferedReader in=null;
try {
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
buffer.append(line).append('n');
statusLine.setText("Read " + buffer.length () + " bytes...");
}
} finally {
if(in != null) in.close();
}
String type = connection.getContentType();
if(type == null) type = "text/plain";
statusLine.setText("Content type " + type);
content.setContentType(type);
content.setText(buffer.toString());
statusLine.setText("Done");
}

public static void main (String[] args) {
QBrowser browser = new QBrowser();
}
}  既然 QBrowser 使用 URL 类,它就可以处理 HTTP HTTPS 请求。你可以使用 HTTP HTTPS 地址测试 QBrowser。这里是一些测试:

1.
请求 ,你会看到如图 1 所示的内容。


2.
请求 https://,结果抛出了异常。因为这个网页服务器的证书不受信任并且不能在默认页中找到,所以它抛出如图 2 所示的异常。


3.
请求 ,这里运行着第一部分中写的 HttpServer。注意,如果你使用命令 java QBrowser 来运行 QBrowser,而服务器的证书导出后被导入默认文件 jssecacerts,那么应该将该文件拷贝到 java.home 目录的 lib/security 子目录中。如果证书被导入了其它文件,你可以使用 trustStore 选项,如:java -Djavax.net.ssl.trustStore=file QBrowser。使用其实任何一种方法,浏览器都会工作,并且你可以看到如图 3 所示的默认页面。


HttpsURLConnection
  这个类存在于 javax.net.ssl 包中,它扩展了 java.net.HttpURLConnection,以支持 HTTPS 描述的一些特性。它能够通过 SSL/TLS 套接字建立安全通道来请求/获取数据。示例代码 4 展示了一个小型客户端,它使用 HttpsURLConnection 类从 HTTPS 服务器下载文档。  示例代码 4ReadHttpsURL3.java

import java.io.*;
import java.net.*;
import javax.net.ssl.*;

public class ReadHttpsURL3 {
public static void main(String[] argv) throws Exception {
URL url = new URL(argv[0]);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setDoOutput(true);
BufferedReader in
= new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
in.close();
}
}
  现在试试 ReadHttpsURL3,完成上面讨论的内容。注意,无论如何,既然我们使用 URL 类,你就能在命令行指定 URL,包括协议的名称。这里是一个例子:

Prompt> java ReadHttpsURL3 https://
  HttpsURLConnection 有一个非常有趣的特点:一旦获得了连接,你就可以在网络连接之前使用一些有用的参数对其进行配置,如 HostnameVerifierHostnameVerifier 是一个接口,它申明了方法:public boolean verify (String hostname, SSLSession session)。而且,它像下面所述的那样工作:如果 SSL/TLS 标准主机名校验逻辑失败,执行过程中会调用回调类的 verify 方法。回调类是实现了 HostnameVerifier 接口的类。 如果回调类检查到主机名可以接受,则允许连接,否则,连接会被终止。   回调类遵循的规则即可以是基本的验证方法,也可以依赖其它验证方法。这里说明了如何实现:

public class MyVerified implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
// pop up a dialog box
// ...
// return either true or false
}
}
  现在,可以这样使用它:

HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setHostnameVerifier(new MyVerifier());
信任管理器  一个 SSL 客户端,如网页浏览器,连接到 SSL 服务器 (

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/8672542/viewspace-896351/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
  • 博文量
    38
  • 访问量
    5304677