對任何重要的 P2P 應用程序而言,對等機之間的安全通信都是一個(gè)核心要求。盡管安全的細節依賴(lài)于如何使用該應用程序和該應用程序將要保護什么,但通過(guò)使用現有技術(shù),例如 SSL 實(shí)現強壯的、一般用途的安全通常是可能的。本月,Todd Sundsted 演示如何在 P2P 安全中使用 SSL(通過(guò) JSSE)。
上月我們考察了 p2p 應用程序中的信任角色。信任的等級是衡量我們確信程度的尺度,即我們正與之通信的人是否是我們以為的那個(gè)人,以及我們正訪(fǎng)問(wèn)的資源是否是我們以為的那些。我們也研究了用于在所有分布式應用程序,包括 p2p 應用程序中建立信任的三個(gè)構件:認證、授權和加密。
現在我們將通過(guò)修改我們的簡(jiǎn)單 p2p 應用程序把上個(gè)月的課程應用到實(shí)踐中。特別地,我們將用 X.509 證書(shū)擴展該應用以支持 P2P 認證和加密。我們將在將來(lái)的文章中處理授權問(wèn)題。
安全認證
一個(gè)應用程序的安全需求在很大程度上依賴(lài)于將如何使用該應用程序和該應用程序將要保護什么。不過(guò),用現有技術(shù)實(shí)現強大的、一般用途的安全通常是可能的。認證就是一個(gè)很好的示例。
當顧客想從 Web 站點(diǎn)購買(mǎi)某個(gè)產(chǎn)品時(shí),顧客和 Web 站點(diǎn)都要進(jìn)行認證。顧客通常是以提供名字和密碼的方式來(lái)認證他自己。另一方面,Web 站點(diǎn)通過(guò)交換一塊簽名數據和一個(gè)有效的 X.509 證書(shū)(作為 SSL 握手的一部分)來(lái)認證它自己。顧客的瀏覽器驗證該證書(shū)并用所附的公用密鑰驗證簽名數據。一旦雙方都認證了,則交易就可以開(kāi)始了。
SSL 能用相同的機制處理服務(wù)器認證(就如在上面的示例中)和客戶(hù)機認證。Web 站點(diǎn)典型地對客戶(hù)機認證不依賴(lài) SSL ― 要求用戶(hù)提供密碼是較容易的。而 SSL 客戶(hù)機和服務(wù)器認證對于透明認證是完美的,對等機 ― 如 p2p 應用程序中的對等機之間一定會(huì )發(fā)生透明認證。
安全套接字層(Secure Sockets Layer(SSL)) SSL 是一種安全協(xié)議,它為網(wǎng)絡(luò )(例如因特網(wǎng))的通信提供私密性。SSL 使應用程序在通信時(shí)不用擔心被竊聽(tīng)和篡改。
SSL 實(shí)際上是共同工作的兩個(gè)協(xié)議:“SSL 記錄協(xié)議”(SSL Record Protocol)和“SSL 握手協(xié)議”(SSL Handshake Protocol)?!癝SL 記錄協(xié)議”是兩個(gè)協(xié)議中較低級別的協(xié)議,它為較高級別的協(xié)議,例如 SSL 握手協(xié)議對數據的變長(cháng)的記錄進(jìn)行加密和解密。SSL 握手協(xié)議處理應用程序憑證的交換和驗證。
當一個(gè)應用程序(客戶(hù)機)想和另一個(gè)應用程序(服務(wù)器)通信時(shí),客戶(hù)機打開(kāi)一個(gè)與服務(wù)器相連接的套接字連接。然后,客戶(hù)機和服務(wù)器對安全連接進(jìn)行協(xié)商。作為協(xié)商的一部分,服務(wù)器向客戶(hù)機作自我認證??蛻?hù)機可以選擇向服務(wù)器作或不作自我認證。一旦完成了認證并且建立了安全連接,則兩個(gè)應用程序就可以安全地進(jìn)行通信。請參閱 參考資料以獲得更多有關(guān) SSL 的信息。
按照慣例,我將把發(fā)起該通信的對等機看作客戶(hù)機,另一個(gè)對等機則看作服務(wù)器,不管連接之后它們充當什么角色。
在 Java 應用程序如何使用 SSL
用于 Java 應用程序的 SSL 由“Java 安全套接字擴展”(Java Secure Socket Extension(JSSE))提供。JSSE 是最近發(fā)布的 JDK 1.4 Beta 測試版的一個(gè)標準部件,但對早些版本的 Java 平臺它是作為一個(gè)擴展可用的。
JSSE 用 SSL 作它的安全套接字的底層機制。JSSE 安全套接字除了支持透明認證和加密之外,其工作方式與常規套接字相似。因為它們看起來(lái)也與普通套接字(它們是類(lèi) java.net.Socket 和類(lèi) java.net.ServerSocket 的子類(lèi))相似,所以使用 JSSE 的多數代碼不用修改。受到影響最多的代碼是那些處理安全套接字工廠(chǎng)(secure socket factory)的創(chuàng )建和初始化的代碼。
如果您想在早于版本 1.4 的 Java 平臺中使用 JSSE,那么您將不得不自己去下載并安裝 JSSE 擴展(請參閱 參考資料)。安裝說(shuō)明非常簡(jiǎn)單,所以我不想在這里重復。
模型
圖 1 說(shuō)明在對等機之間的通信中 SSL 將充當的角色。
圖 1. 工作中的 SSL
名為 A 和 B 的兩臺對等機想安全地進(jìn)行通信。 在我們簡(jiǎn)單的 p2p 應用程序的環(huán)境中,對等機 A 想查詢(xún)對等機 B 上的一個(gè)資源。
每個(gè)對等機都有包含其專(zhuān)用密鑰的一個(gè)數據庫(名為 keystore)和包含其公用密鑰的證書(shū)。密碼保護數據庫的內容。該數據庫還包含一個(gè)或多個(gè)來(lái)自被信任的對等機的自簽名證書(shū)。
對等機 A 發(fā)起這項事務(wù),每臺對等機相互認證,兩臺對等機協(xié)商采用的密碼及其長(cháng)度并建立一個(gè)安全通道。完成這些操作之后,每個(gè)對等機都知道它正在跟誰(shuí)交談并且知道通道是安全的。
初始化
因為 JSSE 和 SSL 的介紹對初始化代碼有很大影響,所以讓我們來(lái)考察對等機 A 中負責初始化的代碼。
清單 1. 安全初始化代碼
// Each peer has an identity that must be locally (but not globally)
// unique. This identity and its associated public and private keys
// are stored in a keystore and protected by a password. Each
// peer also has a name that must be globally unique.
String stringIdentity = null;
String stringPassword = null;
String stringName = null;
// The code that prompts the user for his/her identity
// and password goes here. the user‘s name is
// generated (if necessary) later.
// Create home directory. This is a very portable way
// to create a home directory, but it has its problems --
// the various flavors of Microsoft Windows put the directory
// in widely different locations in the directory hierarchy.
String stringHome = System.getProperty("user.home") + File.separator + "p2p";
File fileHome = new File(stringHome);
if (fileHome.exists() == false)
fileHome.mkdirs();
// Create keystore. We must run an external process to create the
// keystore, because the security APIs don‘t expose enough
// functionality to do this inline. I haven‘t tested this widely enough
// to know how portable this code is, but it works on everything I
// tried it on.
String stringKeyStore = stringHome + File.separator + "keystore";
File fileKeyStore = new File(stringKeyStore);
if (fileKeyStore.exists() == false)
{
System.out.println("Creating keystore...");
byte [] arb = new byte [16];
SecureRandom securerandom = SecureRandom.getInstance("SHA1PRNG");
securerandom.nextBytes(arb);
stringName = new String(Base64.encode(arb));
String [] arstringCommand = new String []
{
System.getProperty("java.home") + File.separator + "bin" + File.separator + "keytool",
"-genkey",
"-alias", stringIdentity,
"-keyalg", "RSA",
"-keysize", "1024",
"-dname", "CN=" + stringName,
"-keystore", stringHome + File.separator + "keystore",
"-keypass", stringPassword,
"-storetype", "JCEKS",
"-storepass", stringPassword
};
Process process = Runtime.getRuntime().exec(arstringCommand);
process.waitFor();
InputStream inputstream2 = process.getInputStream();
IOUtils.copy(inputstream2, System.out);
InputStream inputstream3 = process.getErrorStream();
IOUtils.copy(inputstream3, System.out);
if (process.exitValue() != 0)
System.exit(-1);
}
// Once the application has created/located the keystore, it
// opens it and creates a KeyStore instance from the data
// in it.
char [] archPassword = stringPassword.toCharArray();
FileInputStream fileinputstream = new FileInputStream(stringHome + File.separator +
"keystore");
KeyStore keystore = KeyStore.getInstance("JCEKS");
try
{
keystore.load(fileinputstream, archPassword);
}
catch (IOException ioexception)
{
System.out.println("Cannot load keystore. Password may be wrong.");
System.exit(-3);
}
if (keystore.containsAlias(stringIdentity) == false)
{
System.out.println("Cannot locate identity.");
System.exit(-2);
}
// Create key manager. The key manager holds this peer‘s
// private key.
KeyManagerFactory keymanagerfactory = KeyManagerFactory.getInstance("SunX509");
keymanagerfactory.init(keystore, archPassword);
KeyManager [] arkeymanager = keymanagerfactory.getKeyManagers();
// Create trust manager. The trust manager hold other peers‘
// certificates.
TrustManagerFactory trustmanagerfactory = TrustManagerFactory.getInstance("SunX509");
trustmanagerfactory.init(keystore);
TrustManager [] artrustmanager = trustmanagerfactory.getTrustManagers();
// Create SSL context.
SSLContext sslcontext = SSLContext.getInstance("SSL");
SecureRandom securerandom = SecureRandom.getInstance("SHA1PRNG");
sslcontext.init(arkeymanager, artrustmanager, securerandom);
// Create factories.
m_socketfactory = sslcontext.getSocketFactory();
m_serversocketfactory = sslcontext.getServerSocketFactory();
m_keystore = keystore;
|
當用戶(hù)第一次啟動(dòng)應用程序時(shí),應用程序提示輸入一個(gè)身份(別名)和一個(gè)密碼。身份只用于對對等機進(jìn)行本地識別 ― 它沒(méi)有全局意義。應用程序生成一個(gè)隨機的 128 位(16 字節)的字符串,應用程序用這個(gè)字符串對對等機進(jìn)行全局識別并將它轉換成字母數字字符串。應用程序用身份、密碼和名字創(chuàng )建 keystore 和公開(kāi)/專(zhuān)用密鑰對,密鑰對存儲在 keystore 中。密碼保護 keystore 中的信息。
我確信您已經(jīng)注意到我創(chuàng )建初始 keystore 時(shí)采取的辦法。應用程序把 keytool 啟動(dòng)為一個(gè)外部進(jìn)程,keytool 創(chuàng )建 keystore。我不得不這樣做,因為公共的 Java 安全 API 不提供創(chuàng )建證書(shū)的工具 ― 這些功能隱藏在 JSSE 中,它的 API 未發(fā)布。啟動(dòng) keytool 來(lái)創(chuàng )建 keystore 的最大缺點(diǎn)是要冒用戶(hù)提供的密碼被公開(kāi)的風(fēng)險,用戶(hù)提供的密碼作為參數列表的一部分被傳遞進(jìn)去。
應用程序創(chuàng )建了 keystore 之后,就打開(kāi) keystore 并將它裝入內存(如果我們已經(jīng)能夠直接創(chuàng )建 keystore,則我們就可以免去這一步驟)。應用程序從 keystore 創(chuàng )建一個(gè) 密鑰管理器(key manager)和一個(gè) 信任管理器(trust manager)。密鑰管理器管理密鑰,這些密鑰用于在安全套接字對面相對應用程序的對等機來(lái)認證它。信任管理器管理證書(shū),這些證書(shū)用于對位于安全套接字另一端的對等機進(jìn)行認證。
最后,應用程序創(chuàng )建一個(gè) SSLContext 實(shí)例,這個(gè)實(shí)例充當安全套接字工廠(chǎng)的工廠(chǎng)。應用程序創(chuàng )建 SocketFactory 和 ServerSocketFactory 類(lèi)的安全版本并將它們用于稍后的通信。
用戶(hù)界面
余下代碼的大部分跟我們的前發(fā)行版是相同的。(請參閱 “p2p 應用程序框架”獲取詳細信息。從 參考資料下載完整的源代碼。)這是 JSSE 帶來(lái)的好處之一 ― 用 JSSE 創(chuàng )建的安全套接字看起來(lái)和常規套接字相似,工作起來(lái)也和常規套接字相似。支持代碼也沒(méi)什么不同(除了創(chuàng )建和初始化代碼必須修改外)。然而,還是有和用戶(hù)界面有關(guān)的改動(dòng)。大部分這些改動(dòng)是受到了我想讓這個(gè)應用程序對那些只想快速地設置這個(gè)應用程序來(lái)試驗的愿望而激發(fā)的。
作為對讀者反饋的回應,我改善了命令行界面。對等機及其資源存在于一個(gè)層次結構中,用戶(hù)用命令 ls和 cd訪(fǎng)問(wèn)這個(gè)結構。 ls 命令列出層次結構中某個(gè)特定點(diǎn)的內容(與 UNIX 的 ls 命令或 DOS 的 dir命令所做的相似), cd 命令改變層次結構中的當前位置(再一次與 UNIX 和 DOS 中的同名命令相似)。 cd ..用于在層次結構中進(jìn)行回退。
為使應用程序更容易使用,我已經(jīng)創(chuàng )建了兩個(gè) zip 文件,每一個(gè)都包含該應用程序的一個(gè)已經(jīng)適當配置了的實(shí)例。一個(gè)實(shí)例在端口 7776 進(jìn)行通信,另一個(gè)在端口 7777。它們也都已被配置成彼此認證,為的是能夠建立安全通道。這些 zip 文件的名稱(chēng)為 peerA.zip 和 peerB.zip(請參閱 參考資料)?!白允鑫募保≧EADME)提供了更多詳細的使用說(shuō)明。
結束語(yǔ)
在 p2p 應用程序中加入 SSL 是提供簡(jiǎn)單卻強大的安全的極好辦法。下個(gè)月,我們將繼續我們的 P2P 之旅,考察更成熟的對等機發(fā)現方法。
參考資料
|