2009/12/31

WPF Frame.NavigationFailed 正確捕捉方式

我們的程式用到了 Frame 當作瀏覽器視窗,可以接受使用者輸入的 URL 顯示網頁。但如果使用者隨便輸入或鍵入一個不存在的網址要求 (使用 frame.Navigate(Uri)),Frame 會跳出 WebException。

因為這樣會強制結束程式,所以我加個 try ... catch 來攔截,這下總沒有問題了吧?

try{
    frame.Navigate(new Uri(url));
}catch{
    // show something ...
}

結果程式一跑,相同的 Exception 一樣憑空跳出,讓我丈二金剛摸不著頭腦,究竟是怎麼一回事呢?查了MSDN發現,原來 Frame 有專屬的 NavigationFailed 事件,要撰寫事件來抓這個錯誤才行,Navigate 方法本身不丟出錯誤的。

frmBrowser.NavigationFailed += new NavigationFailedEventHandler(this.NavigationFailedEventHandler);

private void NavigationFailedEventHandler(object sender, NavigationFailedEventArgs e) {
    // show something ...
}

好,我按照 MSDN 的說明,用了事件捕捉,這下總沒事了吧?事情通常沒我們想的簡單,Exception 還是偷偷的跑出來打斷了我們的程式,怎麼會這樣?

注意看 MSDN,尋找可能的問題,後來發現事件的其中一個參數 NavigationFailedEventArgs 非常可疑,一看才知道原來它隱藏著本篇的解藥!其實就是它的 Handled 屬性。說明是這樣寫的:
當處理 NavigationFailed 以處理因巡覽失敗所擲回的例外狀況,而您也不想 WPF 繼續處理例外狀況時,應該將 Handled 屬性設定為 true。
private void NavigationFailedEventHandler(object sender, NavigationFailedEventArgs e) {
    // show something ...
    e.Handled = true; // 已處理,不再擲出錯誤
}

結果加了一行,例外就乖乖的不出現了。如此重要的參數竟然在事件裡或範例中隻字未提,如果沒有發現 NavigationFailedEventArgs 參數的玄機,我想會卡非常久。特此將此經驗公諸於世,希望能救到後來人。

WPF ListView + GridView Items.Add

我們的程式用到了 ListView,而且又用了 GridView 分欄,讓清單看起來比較美觀,結果就像是Windows檔案總管的詳細資料那樣。


<ListView x:Name="listView1" Margin="178,29,35,51" SelectionMode="Single">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="FileName" Width="230" DisplayMemberBinding="{Binding Path=FileName}" />
            <GridViewColumn Header="Date" Width="140" DisplayMemberBinding="{Binding Path=Date}" />
        </GridView>
    </ListView.View>
</ListView>

但是加上了GridView的ListView,不再可以容易的以 物件名.Items.Add("String") 直接新增一條資料,這樣許多欄位的資料都會一樣。網路上的做法是使用資料繫結的方式,維護一個物件然後 Bind 上去即可,但是我還是習慣以程式動態新增,可不可以達成呢?當然可以。

因為是 WPF,所以沒有 Windows Form 的 ListViewItem 物件和物件名.SubItem 等屬性可以用。查了一下網路說明,需要建立一個物件而不是文字傳入 Items.Add()。

因為我是多欄,所以物件也要有多個屬性值,我建立一個結構來儲存:
private struct FileStructure{
    public string FileName { get; set; }
    public String Date { get; set; }
}
值得一提的是 { get; set; } 要加上,如果沒有加上這個繫結將會失敗。

然後在 WPF 的 GridView 的 DisplayMemberBinding 屬性設定 Binding,讓程式知道物件的哪個值對應到哪個欄位。請參考一開始的 WPF 原始碼。雖然我的方法還是有用到 Bind,但那是告訴 ListView 我的東西要怎樣呈現。

最後,以程式新增:

listView1.Items.Add(new FileStructure{ FileName = "test.zip", Date = "2009/12/21" });
listView1.Items.Add(new FileStructure{ FileName = "test.rar", Date = "2009/12/22" });
...

這樣就可以動態新增ListView的資料,而且每一欄的資料都正確的填進去了。
---
參考文獻:
http://stackoverflow.com/questions/1305406/wpf-listview-how-to-bind-single-items

2009/11/26

小論文格式

一.摘要
二.Abstract
三.前言
1.研究動機
四.使用者需求分析
五.研究與方法(文獻探討)
1.RFID技術
2.PKI技術
六.系統架構
七.系統評估
八.結論
九.參考文獻
-------------------------
大概這樣吧!要改再討論吧

2009/10/11

資通PKI應用競賽 時程表

活動網站:
http://csim.tca.org.tw/
http://www.ares.com.tw/pki_pk2009/a.php

1. 報名日期:98年10月1日(四)至11月19日(四) 下午六點截止
2. 初賽日期:98年11月23日(一)至11月27日(五)
3. 決賽日期:98年12月5日(六)
4. 競賽地點:台灣大學綜合體育館(台北市羅斯福路四段一號)

別忘了還有這個活動啊XD
---
報名還要錄製VCD,且邊操作邊說明,所以這系統一定要在期限內完成啊。

2009/10/04

One-Time Password

沒想像中的那麼容易,為了簡化文章,我直接將運作流程列出來:

  1. 先在「安全環境」(如你家電腦)設定產生OTP的各項參數: 密碼, Seed, 演算法, Count/Seq.,產生OTP密碼表格
  2. 到「非安全環境」(如網咖、學校)登入時,系統會要求你查找密碼表格的某一個密碼(例如第49個)
  3. 以查表法查出正確密碼,輸入後登入。如果不想查表,可以用 OTPGen/Mobile-OTP 等 Java程式,給予當初初始化的參數,算出表格再查表輸入

這是正統的 RFC2289 一次性密碼系統的流程,很明顯在我們的系統用這種東西根本不對。

因為教授說用預先定義好的內容作數位簽章,很容易被中間人攻擊,所以希望這個內容能夠隨機產生,最好是一次性的,這樣中間人攔截內容要重送時已經失效了。

不過正規的OTP系統,是寫死的流程,跟我們單純要求的隨機內容有點差距。所以又只能自己動手了。

因為我們要求的隨機密碼不會有讓使用者手動輸入的機會,得到後就立刻數位簽章送給伺服器認證,不必擔心太過複雜難以輸入的問題。我的想法是這樣的:

登入系統向伺服器要求(https)一次性隨機密碼

伺服器回傳(https),同時紀錄於 Session 內

登入系統收到密碼,簽章傳送給伺服器(https)

伺服器拿出使用者憑證跟 Session 紀錄的一次性隨機密碼,進行驗證。不論成功與否皆清除 Session 密碼

原先教授的疑慮是這樣的:當我用固定的內容(如: 「超爽的,撿到一百塊咧」或者是當天日期),系統送出給伺服器的過程中假如被攔截(假設 https 還會被攔截解密的話啦...),中間人可以進行重送攻擊 (因為簽章驗證會通過,驗證是驗證資料不是送的人)。如果是固定文字,那此攻擊可以不限時間成功;當天日期的話,當天重送攻擊都有效。所以教授才會說要將內容加到分鐘,甚至是秒,以避免這種攻擊。

改成隨機內容的話,由於使用者要登入都得先向伺服器要求產生一次性隨機密碼,這個密碼萬一真被中間人竊取成功,中間人並沒有私鑰可以進行簽章,所以攻擊失敗。要是在送出簽章的時候攔截實施重送攻擊,一來中間人並沒有要求伺服器發給一次性密碼,伺服器也就沒有用Session儲存,在進行檢查的時候就沒有東西可以用,而紀錄警告;二來這個一次性隨機密碼已經在剛才的正規使用者登入時,就已失效,所以還是沒有用。

如果中間人夠厲害,一次性隨機密碼、數位簽章都取得,再加上入侵伺服器將 Session 改成一次性隨機密碼,那就有機會。但是這需要攻擊 HTTPS, 伺服器,自有難度。Session 變造內容的攻擊法還沒有聽說過,因為 Session 存在伺服器除非入侵不然改不到。只有 Session ID 攻擊 (參見: Session Hijacking 攻擊與Cookie 欺騙(4/5) - 網路攻防戰):利用 Session ID 綁 Cookie 的設計,竊取正規使用者 Cookie,把自己的 Cookie 改的跟正規者一樣,Session ID自然一樣,對應的 Session 正是合法使用者的。

解決方式是每次要求就改變 Session ID,PHP 有函式 session_regenerate_id() 可以避免這種問題。

結論:根基於 HTTPS、一次性隨機密碼、數位簽章、Session 跟每次改變 Session ID的方式,讓中間人的破解難度增加,我想可以達到安全。

2009/09/29

09/29 開會結論

- C# <-> MySQL 加密連線傳輸使用法

pMA 開啟 MySQL SSL 連線
http://wiki.phpmyadmin.net/pma/Config/Servers#ssl

MySQL SSL 支援
http://www.option-c.com/xwiki/MySQL_Replication_with_SSL

好吧,C#部分不明,很多人在問但沒有一個好回答
http://dev.mysql.com/doc/refman/5.1/en/connector-net-programming-connection-options.html
官方是說連線字串加上 useSSL=true 就成,實際上沒那麼容易,一試就error。


- 數位簽章本文使用 OTP 方式建立,避免數位簽章被中間人攔截登入
- 闡明技術部份 (CA如何建立,如何管憑證、安全傳輸使用 ,etc)

還有,三本文件的催生。

最後右邊有個增加緊張感的自製 Widget,希望可以給大家一點推力XD

2009/09/12

設定 iptables - 防火牆規則設定

Ubuntu 預設的 iptables 設定是全部通行,防火牆如果全部通行那就等於沒設定,所以我們要對它設些規則。

目標:
  • 對內: 僅打開 SSH(22), HTTP(80), HTTPS(443), MYSQL(3306) PORT,其他全部封鎖
  • 對外: 不設限

這樣就可以防止有心人入侵其他 Port。

本來是要下一大堆指令的,不過 iptables 可以用 iptables-save 來輸出規則,所以我直接貼出規則檔:

# Generated by iptables-save v1.4.1.1 on Tue Aug 25 12:57:50 2009
*filter
:INPUT DROP [2858:374901]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [186:47949]
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 3306 -j ACCEPT
-A OUTPUT -o lo -j ACCEPT
COMMIT
# Completed on Tue Aug 25 12:57:50 2009


將此檔存到 /etc/iptables.up.rule。

因為每次開機後 iptables 的規則就會重置,要修改開機時能夠讀取剛才建立的規則檔,這樣我們建立的防火牆規則才有效。

用 vi 打開 /etc/network/interfaces,應該可以看到網路卡設定,加一行指令即可:


pre-up iptables-restore < /etc/iptables.up.rule


並存檔。這樣可以確保開機後自動回覆規則。

但是沒重新開機前,現在的 iptables 狀態還是空的,所以我們要手動載入規則檔,執行:

# iptables-restore < /etc/iptables.up.rule

就可以了,要查看是否成功的話可以下指令:

# iptables -L

應該會出現很多規則,這樣就成功了。

2009/08/31

resin-4.0 session的設定

我們
這在做管理介面時遇到鬼打牆
看session是啟用的
存活時間是30分鐘
但只要一換頁面session就會變空的
翻來翻去翻resin的說明

有這玩意兒可以來調session的設定
請參考這裡
前面講了一大推沒在用的我還去試他orz
最後試一試是只要設這個

<!-- enable persistent sessions -->

<session-config>
<save-mode>after-request</save-mode>
<use-persistent-store/>
</session-config>

<!--

增加save-mode這個child
並設定其質為after-request
一有request就存進去
解決這問題

(題外 管理介面有做一些小變更 包含登入驗證等頁面改變 可以去看看?)

2009/08/30

PKI Research #4.5 - Digital Signature 再臨

最近要實作檔案傳輸的部分,由於一開始作使用者登入時,驗證的部分是在本機端 (Client) 作的,伺服器端 (Server) 根本無法得知真實性。所以當有人聲稱某甲要求檔案或傳輸檔案過來時,我們也不能肯定他就是本人。因此,有必要在伺服器端再作一遍。

數位簽章的實作,其實在研究 #4 中已經有了,但是主要是驗證簽章的部分,要在伺服器中執行。搭配接收 POST 上傳檔案的設計,我採用 PHP 來實作。查了一下 PHP 也有 OpenSSL 套件支援 (API),所以我只要使用它提供的函式就可以驗證簽章,關鍵函式 openssl_verify。

openssl_verify 能接受一些參數 (原訊息、數位簽章、公鑰) 來驗證簽章正確性,其中簽章演算法只支援到 SHA1,沒有先前研究的 SHA256,只好把簽章的部分也改用 SHA1 以配合。公鑰的部分,得先從憑證裡抽出來,這有 openssl_get_publickey 可以使用。

大概像這樣:
<?php
$message = 'test'; // Original Message

try {
    $dbh = new PDO('mysql:dbname=FAST;host=127.0.0.1', 'user', 'pass');
} catch (PDOException $e) {
    echo 'Connection failed: ' . $e->getMessage();
}

$sth = $dbh->prepare('SELECT `Public_Key` FROM XXX WHERE ID = ?');
$sth->bindParam(1, $tagID, PDO::PARAM_STR, 8);
$sth->execute();

$row = $sth->fetch();
$pubkeyid = openssl_get_publickey($row['Public_Key']);

// state whether signature is okay or not
$ok = openssl_verify($message, base64_decode($_POST['sigData']), $pubkeyid);
if ($ok == 1) {
    echo "good";
} elseif ($ok == 0) {
    echo "bad";
} else {
    echo "ugly, error checking signature";
}

// free the key from memory
openssl_free_key($pubkeyid);
?>

sigData 是數位簽章內容,為了傳輸方便已先行以 BASE64 編碼,所以這邊再解碼還原。

實際測試時,發現結果一直回傳 bad 即簽章不符,因為 C# 實作驗證都一切正常,所以我懷疑是不是 PHP 的問題,改換使用 JSP 來實作簽章驗證的部分,結果一樣失敗,不同的是 Java 跳出簽章結構錯誤的訊息,我開始懷疑起實作簽章的部分了。在 PHP 上使用 key 私鑰檔實作簽名,跑出來的東西跟 C# 的完全不一樣。C# 簽章驗證的結果,兩種結果都能通過驗證,不過 C# 的結果丟給 PHP 是失敗的,百思不得其解。

回頭想想,會不是會當初實作簽章的時候,想法錯了呢?還記得 .NET Framework 不許我們使用公鑰解密,取而代之的提供 Verify 方法,找看看 Bouncy Castle 有沒有提供類似的 Sign 方法,結果真的有。
public static byte[] signature(byte[] data){
try{
AsymmetricCipherKeyPair keyPair;
using(var reader = File.OpenText(@"D:\Programs\OpenSSL\user1.key")) { // Direct read pem format RSA private key file
keyPair = (AsymmetricCipherKeyPair) new PemReader(reader).ReadObject();
}

RsaDigestSigner rds = new RsaDigestSigner(new Sha1Digest());
rds.Init(true, keyPair.Private);
rds.BlockUpdate(data, 0, data.Length);
return rds.GenerateSignature();
}catch(Exception e){
throw new IOException("problem creating private key: " + e.ToString());
}
}

public static bool verify(byte[] message, byte[] sig){
var crt = new X509Certificate2(@"D:\Programs\OpenSSL\user1.crt"); // Read X509 Certificate file
var rsa = (RSACryptoServiceProvider) crt.PublicKey.Key; // Get public key

return rsa.VerifyData(message, "SHA1", sig);
}

Bouncy Castle 提供了 Org.BouncyCastle.Crypto.Signer 類別來處理數位簽章,RsaDigestSigner 就是給 RSA 公開金鑰演算法使用的數位簽章處理類別,由於 PHP 方的 Hash 演算法只支援到 SHA1,所以我簽章的部分也僅使用 SHA1。使用這個 Signer 類別不必自己手動 Hash 訊息,交給類別去作。

驗證簽章的部分跟上次基本上沒很大的差別,只是我也捨棄 VerifyHash 方法改使用 VerifyData 方法,直接把訊息 Hash 的動作交給了方法去處理,我完全不必實作相關 Hash 方法。

這次修改後,產生簽章的結果跟 PHP 的一模一樣,這樣就可以由 C#產生簽章,由 PHP 接收來驗證,進而確認使用者身份,伺服器端也可以利用 SESSION 暫時紀錄結果,就不必每次都要驗證。預設 30 分鐘 SESSION 會過期,到時候再驗證一次即可。

2009/08/26

mod_jk (Apache + Tomcat) 在 Ubuntu 下的設定方式

因為組員只會寫JSP,所以原本的Apache以外勢必還要再架設一個跑JSP的服務,我就想到了 Tomcat。因為 Ubuntu 的套件庫很多已經編譯好的玩意,連相依性都幫我考慮好了,很方便。所以我就打開 Synaptic 套件管理工具來找尋 Tomcat 安裝。

安裝的是 Tomcat6,連帶安裝了 OpenJDK 等 Java 函式庫。我突然看到還有 mod_jk 可以安裝,說明是說可以把 Tomcat 跟 Apache 連接起來,透過 Apache 就可以執行 JSP (說穿了就是扮演代理角色)。因為 Apache 的效能比 Tomcat 還要好,所以透過 mod_jk 不但可以除去網址老是有 8080 Port 的不便,也可以獲得 Apache 的代理效能。

全部利用 Synaptic 套件管理工具安裝完後,mod_jk還需要手動設定,而且還滿多地方需要手動設定不然按照預設值是跑不起來的地方,不知道是不是版本的關係。我使用 Ubuntu 9.04 Server 版。

首先,要先去修改 /etc/libapache2-mod-jk/workers.properties 這個檔案。這個檔案是 mod_jk 使用,作為 Apache 跟 Tomcat 溝通的工人 (Worker)。使用 vi 打開後主要修改幾處地方:

workers.tomcat_home=/usr/share/tomcat6
這個值預設是安裝 Tomcat 5 的位置,因為我是安裝新版的 6 版,不修改一定不能用。改成 /usr/share/tomcat6 就可以順利指定 Tomcat6 的目錄。

workers.java_home=/usr/lib/jvm/default-java
這個值預設是 JavaVM 沒錯,但是現在已經有開放原始碼的 OpenJRE/OpenJDK,如果你也是安裝了 OpenJRE,那記得改一下位置: /usr/lib/jvm/default-java,這個路徑是一個 Symbolic Link,不知道其他版本是不是也有這個 link。


接下來請到 /etc/apache2/mods-available,打 ls 查看一下可以發現有 jk.load,這是載入 mod_jk 模組的設定檔,但卻沒看到對應的 jk.conf (照理說是 mod_jk 的設定),查了一下發現在 /usr/share/doc/libapache2-mod-jk 目錄下有 httpd_example_apache2.conf 範例設定檔,將這個檔案 cp 到 /etc/apache2/mods-available 並改名為 jk.conf 以方便作業。接著,打開檔案編輯。

這個檔案預設上是差不多可以用了,但是還是需要修改:

加上一行 JkMountCopy All
加這行的用意是因為預設 mod_jk 只會運作在 Apache 主網站,其他的虛擬網站 (VirtualHost) 設定不會套用。但是 Ubuntu 預設的結構是,主網站也是使用虛擬網站設定,並沒有主網站設定,所以按照預設值 mod_jk 會無法對應到正確的檔案 (這可以從 log 看的出來)。而這行就是將設定複製到全部的虛擬網站。

因為我的網站設定只有 80 port 跟 443 port,而且網站內容一模一樣,這樣的設定沒有什麼問題。但是如果你的虛擬網站很複雜,各自有不同內容,那就請只在想要連接到 Tomcat 的網站設定檔內修改加入一行 JkMountCopy On,讓你指定的虛擬網站能夠正常運作 mod_jk 設定。


結束編輯後,我們還要建立一個 Symbolic Link 讓 Apache 讀取。到 /etc/apache2/mods-enabled 執行以下指令:

ln -s ../mods-available/jk.conf jk.conf


最後重新啟動 Apache (執行 /etc/init.d/apache2 restart 指令),就大功告成了。

結果就是 http://127.0.0.1/*.jsp 會自動對應到 http://127.0.0.1:8080/*.jsp,如果你想要更特別的設定,打開 /etc/apache2/mods-available/jk.conf 就可以自由新增。

JkMount /*.jsp ajp13_worker
JkMount /*/servlet/ ajp13_worker

這是預設的設定,用意就是將符合的條件送給 Tomcat 處理。所以我打 /test.jsp 就會送到 :8080/test.jsp

2009/08/24

員工資料庫 介面 申請單 JSP上做MD5

員工資料庫完成
增(給員工填的申請表單)
刪(與列表、查合一)


介面皆讓大家看過
就不截圖了...
外觀上或功能上有意見請在此篇回應


再來說到身份ID做hash部份
使用到java.security內的方法MessagerDigest
我們得先import java.security.*後
執行如此下的方法(參考資料)
MessageDigest mdAlgorithm = MessageDigest.getInstance("MD5");
mdAlgorithm.update(User_ID.getBytes());
byte[] digest = mdAlgorithm.digest();
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < digest.length; i++) {
User_ID = Integer.toHexString(0xFF & digest[i]);
if (User_ID.length() < 2) {
User_ID = "0" + User_ID;
}
hexString.append(User_ID);
}
User_ID=hexString.toString();


就可以成功執行hash動作

2009/08/18

PKI Research #5 - 使用 OpenCA 產生的憑證跟加密過的私鑰

本篇是研究最卡的部分,足足卡了三天。欲知原因以下見曉。(關鍵字:PKCS#8, PKCS#5)

因為伊達說已經完成研究 OpenCA 發憑證的部分,因此我們使用 OpenCA 的最大目的「產生憑證、私鑰檔和 CRL 憑證廢止清冊」就已經達成,再來就是要研究如何拿來用。

OpenCA 主要使用 MySQL 來儲存相關資料,舉凡憑證申請、憑證存放、CA 憑證、CRL 憑證廢止清冊等全部都在資料庫內,而不是一般的檔案。以 phpMyAdmin 登入後可以看到。





最重要的就是 certificate 資料表了,data 欄位就是重點。裡面存放著憑證以及私鑰檔,我們只要取出來應該就可以利用了吧。我當初是這麼想,但是他的私鑰檔卻經過 DES 加密 (「私鑰經過 PKCS#5 密碼加密標準 "PBEwithMD5andDES-CBC" 加密過的」PKCS#8 私鑰格式標準),檔案格式看起來像這樣:

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIBgTAbBgkqhkiG9w0BBQMwDgQIjgXxhGkmgGcCAggABIIBYEXZfHjLUnXJ8h+F
...
mtuOc9U=
-----END ENCRYPTED PRIVATE KEY-----


看得出跟一般 OpenSSL 產生的未加密的私鑰檔不同吧,如下 (純粹私鑰,非 PKCS#8 格式):

-----BEGIN RSA PRIVATE KEY-----
MIIBOQIBAAJBAOWeK9TSYVnPEkmK45TeI7NrC8MzvwabSVg1aEuTmcNkLRg/Qibv
...
Ft4zOc8TnbXnGxXRfGVG0RZfXmxcQNv2yX2G4Vc=
-----END RSA PRIVATE KEY-----


如果以為 PKCS#8 私鑰 key 檔能跟 OpenSSL 的私鑰 key 檔一樣讀取操作 (PKI Research #3 - RSA 加解密),那你就錯了。你會發現 keyPair 永遠是 null,因為察看原始碼才發現,PemReader 沒有特別處理 BEGIN (ENCRYPTED) PRIVATE KEY 的加密私鑰檔 (90 行 switch 開始),更簡單的說法就是 PemReader 不支援 PKCS#8 格式私鑰檔,所以只好自己找方法了。

由於中文極度缺乏相關資源,英文的資源雖然有找到類似的討論串,但也是跟我有著一樣的疑問,而沒有人回答。例如:



其實還有很多,但是我一時忘了。嘗試用 PBEwithMD5andDES-CBC 當關鍵字知道 Bouncy Castle 的 Org.BouncyCastle.Security.PbeUtilities 有支援,但是它的說明手冊不夠完整 (或者該說,沒這種玩意,只有 API),到底是怎樣的使用法也沒人提到。於是我們就來看 PbeUtilities.cs 的原始碼吧:

如果你想知道為什麼是 PKCS#5 PBEwithMD5andDES-CBC 加密格式,用 OpenSSL asn1parse -in 檔案路徑 就可以查看 ASN.1 結構,就會寫到。


原始碼果然定義一堆加密的方法,這下子有希望了。看了一下大概是像這樣子使用:
using System;
using System.IO;
using System.Text;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;

const String pemp8header = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
const String pemp8footer = "-----END ENCRYPTED PRIVATE KEY-----";
char[] password = "xxxxxx".ToCharArray(); // 解密密碼

// 取出檔案中 BASE64 編碼的部分,還原
StringBuilder sb = new StringBuilder(File.ReadAllText(@"B:\tmp.key"));
sb.Replace(pemp8header, "");
sb.Replace(pemp8footer, "");


String pubstr = sb.ToString().Trim();
byte[] binkey = Convert.FromBase64String(pubstr);
var info = EncryptedPrivateKeyInfo.GetInstance(Asn1Object.FromByteArray(binkey));

// 開始建立解密引擎,準備進行 DES-CBC 解密
var algId = info.EncryptionAlgorithm;
var cipher = (BufferedBlockCipher) PbeUtilities.CreateEngine(algId.ObjectID);
var param = PbeUtilities.GenerateCipherParameters(algId.ObjectID, password, algId.Parameters);
cipher.Init(false, param);

// 取出加密資料,進行解密
byte[] data = info.GetEncryptedData();
byte[] outBytes = new byte[cipher.GetOutputSize(data.Length)];
int len = cipher.ProcessBytes(data, 0, data.Length, outBytes, 0);

try {
len += cipher.DoFinal(outBytes, len);
} catch(Exception e) {
throw new Exception("failed DoFinal - exception " + e.ToString());
}

// outBytes 為解密完的 PrivateKeyInfo (PKCS#8),接著取出 PrivateKey
var p = PrivateKeyInfo.GetInstance(Asn1Object.FromByteArray(outBytes));
var rsa = new RsaPrivateKeyStructure((Asn1Sequence) p.PrivateKey);
// privSpec 可用於解密
var privSpec = new RsaKeyParameters(true, rsa.Modulus, rsa.PrivateExponent);


手工實作法的步驟很繁雜,有沒有更好的方法呢?

想想可能還有其他已經包好的方法用了 PbeUtilities 來讀取加密私鑰了,就查查看。果不其然又讓我發現了 PrivateKeyInfoFactory.cs 原始碼,原來只要幾行:
using System;
using System.IO;
using System.Text;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;

const String pemp8header = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
const String pemp8footer = "-----END ENCRYPTED PRIVATE KEY-----";
char[] password = "xxxxxx".ToCharArray(); // 解密密碼

// 取出檔案中 BASE64 編碼的部分,還原
StringBuilder sb = new StringBuilder(File.ReadAllText(@"B:\tmp.key"));
sb.Replace(pemp8header, "");
sb.Replace(pemp8footer, "");

String pubstr = sb.ToString().Trim();
byte[] binkey = Convert.FromBase64String(pubstr);
var info = EncryptedPrivateKeyInfo.GetInstance(Asn1Object.FromByteArray(binkey));

// 重點方法: PrivateKeyInfoFactory.CreatePrivateKeyInfo() 一行抵十行
var p = PrivateKeyInfoFactory.CreatePrivateKeyInfo(password, info);
var rsa = new RsaPrivateKeyStructure((Asn1Sequence) p.PrivateKey);
// privSpec 可用於解密
var privSpec = new RsaKeyParameters(true, rsa.Modulus, rsa.PrivateExponent);


總結:開放原始碼是有必要的,畢竟最後還是得看原始碼找答案的嘛 (大誤)

2009/08/14

OpenCA 新增憑證 & 匯出


今天來說說那個OpenCA弄新憑證&匯出我們要的資料的方法



到PKI Init & Config的Initialization>CA administrator
到Pub頁面,下方就有Request a Certificate

去request一個新的憑證
需要填入的資料表單如下


基本上,只有名稱跟email是一定必填的
若要做給使用者填寫的表格,要再思考
其他欄位是否有必要性

精過一番精密...喔不 是簡單的過程後
這個"Request"就成功發出了 接下來就到CA operation去做 憑‧證 承~~認!


來這 選NEW(即能看到新進的憑證伸請要求)
然後承認後
可以到Information > CA Certificates > valid (合法的Certificates)

看到列表入下


那麼點選就能進入該觀看Certificate資料
並選擇下載(格式可選)


選我們要的格式就OK了w
Got it! Go to next.

2009/08/13

流程



可能還是要先多想想需要甚麼功能吧!

各部分研究目標

OpenCA
- 功能: 輸入使用者個人資訊以產生伺服器簽發憑證跟私鑰檔,並能產生廢止清冊 CRLs
- 目的: 產生憑證檔跟私鑰檔來驗證,還有 CRLs 讓程式判斷憑證是否可使用
- 研究: 如何申請使用者憑證,並能取得憑證檔

PKI
- 功能: 公開金鑰加解密、數位簽章及驗證
- 目的: 使用者身分鑑別
- 研究: 得出公鑰 crt 憑證、RSA 私鑰 key,讀取 CRL 檢查是否已撤銷

RFID
- 功能: 讀取/寫入私鑰檔跟個人設定之類的資訊
- 目的: 當作使用者身分鑑別的媒介
- 研究: RFID Tag Block 寫入資料區塊的利用分配

MySQL
- 功能: 儲存使用者相關資料
- 目的: 跟 RFID 連動取得進一步的使用者資料
- 研究: 資料表要放什麼

Apache
- 功能: 網頁伺服器
- 目的: 上傳/下載公文、個人檔案的傳輸媒介,利用 SSL 加密
- 研究: 如何讓使用者上下傳檔案

C#
- 功能: 撰寫程式
- 目的: 利用 RFID 讀取私鑰檔,連接 MySQL 取得資料進行驗證。前端 GUI 顯示公文、個人檔案等等
- 研究: 如何結合以上功能寫出程式

DB裡面

Database裡面應該會這樣放
覺得可以再更改的地方再提出吧!


ID_Transformation
Tag_IDDatabase_IDChanged
1212343456567878980000010
1212343456567899980000021
1212343456567800980000032
1212343456567812980000043
//Tag_ID 代表 Tag的ID
//Database_ID 代表Database上的ID
//changed 代表 Tag_ID更改次數,也可以代表Tag遺失次數!XD



Key(Key的儲存)
Database_ID Public_Key
98000001 qwerasdfzxcvtgbh
98000002 qasdfghjklmnbvcx
98000003 qasdfghjklmnbaaa
98000004 qasdfghjklmasdfg
// Public_Key 儲存 public key,認證用!



Paper(記錄公文的資訊)
Database_ID Paper_Read
98000001 y
98000002 n
98000003 n
98000004 y
// Paper_Read 代表 公文已讀與否!


Identity(user身分)
Database_ID NameLevelUser_ID
98000001 Jimmy Chairman A123456789
98000002 RobertCamel Manager B123456789
98000003 Scribe Manager C123456789
98000004 Soultaker Chairman D123456789
// Level 代表 職位
// User_ID 代表 身分證字號



ENV_Set(用來儲存user的設定值)
Database_ID Set_1Set_2Set_3
98000001 0 1 1
98000002 1 1 1
98000003 0 1 0
98000004 1 0 1
// set_1 代表 user環境的設定 0:off 1:on (例如button要不要透明化!?)

2009/08/12

架設 OpenCA 1.0.2 on Ubuntu 9.04 Server

終於完成啦!完全照著嚕嚕咪的教學(見此)照抄就完成了,沒啥好說的。

倒是順便安裝了 phpMyAdmin 套件 (apt-get phpmyadmin),不是直接下載原始碼安裝,最後會裝到 /usr/share/phpmyadmin,但是網頁預設目錄是 /var/www,所以要建立一個 Symbolic link,在 /var/www 目錄下 ln -s /usr/share/phpmyadmin phpmyadmin 就可以建立一個連結,不必複製整個目錄就可以使用。

既然架好了,請其他人盡速開始研究 MySQL 跟 OpenCA 囉。

2009/08/02

PKI Research #4 - Digital Signature

這篇預定要來研究數位簽章。

數位簽章的方式跟加解密剛好是反過來的,加解密使用公鑰加密,使用私鑰解密;數位簽章卻是使用私鑰簽章,使用公鑰驗證。

加解密的對象是明文檔案內容,而數位簽章的對象則是明文檔案內容經過雜湊後的值。常用的有 MD5 跟 SHA-1,聽說這兩個都有被碰撞破解的個案,所以使用 SHA256 (SHA-2 家族)。

.NET Fraamework 的 System.Security.Cryptography 命名空間實作不少相關雜湊,我直接寫成以下方法:
public static byte[] useSHA256(byte[] data){
SHA256 sha = new SHA256Managed();
return sha.ComputeHash(data);
}

流程圖一張:
http://en.wikipedia.org/wiki/File:Digital_Signature_diagram.svg

先從簽章實作。第一件事就是將文章進行 Hash,我使用上面寫好的 SHA256 方法來製造。然後要將此 Hash 以私鑰加密,因為 X509Certificate2 不能直接支援 RSA Private Key PEM 檔 (範例中使用 DER 格式方便 RFID Tag 儲存),所以我使用 Bouncy Castle C# 來進行私鑰的讀取,並用來加密 Hash 產生數位簽章 (稱為 Hash')。

另一邊進行驗證。驗證也需要將文章進行 Hash,以便跟數位簽章比對。一樣使用 SHA256 產生 Hash。然後將數位簽章使用 .crt 憑證內存的公鑰解密 (得到 Hash'),結果拿來跟 Hash 比對。如果完全正確 (Hash' = Hash) 表示文章沒有經過竄改,而且也能確定簽章者正是本人。但是 X509Certificate2 不能直接拿公鑰進行解密 (Decrypt),而是提供另一個驗證 (VerifyHash) 方法供呼叫,我也搞不懂為什麼不行。

public static byte[] signature(byte[] data){
byte[] hash = useSHA256(data);
try{
var seq = (Asn1Sequence) Asn1Object.FromStream(File.OpenRead(@"B:\OpenSSL\1-der.key"));
var rsa = new RsaPrivateKeyStructure(seq);
var privSpec = new RsaPrivateCrtKeyParameters(
rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent,
rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2,
rsa.Coefficient
);

var encryptEngine = new Pkcs1Encoding(new RsaEngine());
encryptEngine.Init(true, privSpec);
return encryptEngine.ProcessBlock(hash, 0, hash.Length); // Digital Signature
}catch(Exception e){
throw new IOException("problem creating private key: " + e.ToString());
}
}


public static bool verify(byte[] message, byte[] sig){
byte[] hash = useSHA256(message);

var crt = new X509Certificate2(@"B:\OpenSSL\1.crt"); // Read X509 Certificate file
var rsa = (RSACryptoServiceProvider) crt.PublicKey.Key; // Get public key

return rsa.VerifyHash(hash, "SHA256", sig);
}

PKI Research #3 - RSA 加解密

CA 部分還沒搞定,但生成憑證簽署方面都可以利用 OpenSSL 來完成。OpenSSL 是開放原始碼專案,在 Unix-like 界非常廣泛,Windows 有別人 Port 好的版本:OpenSSL Binary Distributions,可以下載來玩看看。下指令的方式上一篇已經有許多篇參考資料,不再說明。

複習一下 .key 檔跟 .crt 檔的樣子:

user.key: (PEM-encoded)
-----BEGIN RSA PRIVATE KEY-----
MIIBOQIBAAJBAOWeK9TSYVnPEkmK45TeI7NrC8MzvwabSVg1aEuTmcNkLRg/Qibv
B6ST5F056HCk0xvzm5rfO5A+q+4ncBMGMhkCAwEAAQJABGaLoICHrRjy2MX4ppm7
RWz/xLXxK0c+mJotbYVepQf1KYB6kPc4df1t4KrFFgxMyXzdJ/eiegf4j3HFT4Hs
gQIhAPiVVZNSMqp1uUx7OXB3GGW+3uslvA3+NvuAKqVguIo9AiEA7Hf6ykGeuRBN
k2EN+J+Vof4ryzFOtJGH+eyycmKPsQ0CIFJYteZ9nkcVhHKvh1GYQj7CQfpHn8pK
4k/iHz51kexJAiBwKs1kiVHv+QLDSQNmjtRcngNKBB6QWoQEkjlnNsdwNQIgbJYa
Ft4zOc8TnbXnGxXRfGVG0RZfXmxcQNv2yX2G4Vc=
-----END RSA PRIVATE KEY-----


user.crt: (PEM-encoded)
-----BEGIN CERTIFICATE-----
MIIBtDCCAV4CCQChpB3R8j5vwDANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQGEwJU
VzEPMA0GA1UECBMGVGFpd2FuMQ8wDQYDVQQHEwZUYWl3YW4xHDAaBgNVBAoTE05D
SFUgTUlTIERlcGFydG1lbnQxEjAQBgNVBAMTCXNvdWx0YWtlcjAeFw0wOTA3MzEw
NDE1MDJaFw0xMDA3MzEwNDE1MDJaMGExCzAJBgNVBAYTAlRXMQ8wDQYDVQQIEwZU
YWl3YW4xDzANBgNVBAcTBlRhaXdhbjEcMBoGA1UEChMTTkNIVSBNSVMgRGVwYXJ0
bWVudDESMBAGA1UEAxMJc291bHRha2VyMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
AOWeK9TSYVnPEkmK45TeI7NrC8MzvwabSVg1aEuTmcNkLRg/QibvB6ST5F056HCk
0xvzm5rfO5A+q+4ncBMGMhkCAwEAATANBgkqhkiG9w0BAQUFAANBAGQhB8VKtY2N
hecmukSUmJkTzFWwT15fPmTGzf0KdrccNCSK2bE/u6bTW9dkUF12MkTaHNVytmKd
u7MoPuNvCnA=
-----END CERTIFICATE-----


所以,當我有了 .key 檔 (RSA 私鑰) 跟 .crt 憑證 (含公鑰的憑證) 後,該怎麼使用?這是本篇要研究的目標。

C# 利用 X.509 憑證加密,網路上有很棒的教學:使用X.509数字证书加密解密实务(二)-- 使用RSA证书加密敏感数据,圖文並茂很容易了解。基本上 .NET Framework 的 X509Certificate2 類別非常好用,它屬於 System.Security.Cryptography.X509Certificates 命名空間。只要建構元放入 .crt 憑證檔路徑,就可以抽出公鑰,加密資料。

解密的話,X509Certificate2 支援公私鑰綁一起的 PKCS#12,但不支援 OpenSSL 直接匯出的私鑰格式 (稱為 PEM 格式),當你直接指定 .key 檔位置會丟出例外,所以解密要找其他方案。

剛好無意間找到利用 Bouncy Castle for C# API 直接讀取 PEM 格式 RSA 私鑰進行解密的教學:用openSSL生成了一对RSA密钥,在C#里面怎么使用它啊? ,回答的第二項就是我們要的。你可能覺得奇怪它說第三項也可以用啊,但實際上那是讀取 .pfx 或 .p12 的 PKCS#12 個人憑證,直接讀取 OpenSSL RSA 私鑰 PEM 格式檔是絕對不行的。

DER (Distinguished Encoding Rules):
二進位內容,是屬於 ASN.1 制定的編碼之一。

PEM (Privacy-enhanced Electronic Mail):
就是 DER 格式經過 BASE64 編碼後所能見到的內容,通常會有 ----- BEGIN XXX ----- / ----- END XXX ----- 之類的東西包夾起來。由於 PEM 格式採用了 BASE64 編碼,文字都被編成常用的英文數字符號,方便於網路上傳送及複製 (如即時通訊、電子郵件等)。OpenSSL 預設產生的檔案都是 PEM 格式。當然也可以透過 -inform / -outform 來指定 DER 等格式。

using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;


namespace PKI_RSAEnDecryptDemo {
class Program {
static void Main(string[] args) {
byte[] plainTextByte = Encoding.UTF8.GetBytes("超爽的,撿到100塊咧~咧?");

byte[] encTextType = encrypt(plainTextByte);
string base64 = Convert.ToBase64String(encTextType);
Console.WriteLine(base64);

byte[] oristr = decrypt_DER(encTextType);
Console.WriteLine(Encoding.UTF8.GetString(oristr));

Console.ReadKey();
}

public static byte[] encrypt(byte[] plainTextByte) {
var crt = new X509Certificate2(@"B:\OpenSSL\1.crt"); // Read X509 Certificate file
var rsa = (RSACryptoServiceProvider) crt.PublicKey.Key; // Get public key

byte[] Cryptograph = rsa.Encrypt(plainTextByte, false); // Encrypt
return Cryptograph;
}

public static byte[] decrypt(byte[] encTextType) {
AsymmetricCipherKeyPair keyPair;
using(var reader = File.OpenText(@"B:\OpenSSL\1.key")) { // Direct read pem format RSA private key file
keyPair = (AsymmetricCipherKeyPair) new PemReader(reader).ReadObject();
}
var decryptEngine = new Pkcs1Encoding(new RsaEngine());
decryptEngine.Init(false, keyPair.Private);

return decryptEngine.ProcessBlock(encTextType, 0, encTextType.Length); // Decrypt
}

public static byte[] decrypt_DER(byte[] encText) {
try{
var seq = (Asn1Sequence) Asn1Object.FromStream(File.OpenRead(@"B:\OpenSSL\1-der.key"));
var rsa = new RsaPrivateKeyStructure(seq);
var privSpec = new RsaPrivateCrtKeyParameters(
rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent,
rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2,
rsa.Coefficient
);

var decryptEngine = new Pkcs1Encoding(new RsaEngine());
decryptEngine.Init(false, privSpec);
return decryptEngine.ProcessBlock(encText, 0, encText.Length); // Decrypt
}catch(Exception e){
throw new IOException("problem creating private key: " + e.ToString());
}
}
}
}


---
參考資料:
OpenSSL 常用語法整理

2009/07/31

PKI Research #2 - 憑證製作

拿舊電腦安裝 OpenCA (on CentOS 5.3) 安裝失敗,無法啟動。看到 Fedora 有自己寫的 Dogtag 憑證系統,不知道有沒有用,這方面先交給別人實驗。

我們要 PKI 的目的是要使用產生的公私鑰對來驗證使用者身分,但是發憑證很簡單 (OpenSSL 就可以簽 X.509 了),但要集中管理使用者金鑰卻很困難,不得不架設 CA 一類的東西,尤其是憑證廢止清冊,這一定要中央集權式管理才可以。OpenSSL 簽憑證並沒有辦法考慮到廢止的問題吧 (頂多自動過期,離職等無法強制廢止) 所以似乎還是要 CA 中央管理憑證。

我提過我已經可以自行簽發憑證了 (見此 OpenSSL 教學),跟教授借來的那本也有提到 Bouncy Castle 這個 Java 函式庫可以簽發,但是重點的憑證管理卻隻字未提。還有簽完的 RSA Private Key 檔、憑證申請書和簽發憑證檔都是特定格式,要直接抽出公私鑰對存在 RFID Tag 太難了,我想許多程式設計都是直接將憑證檔案丟去處理的吧。

以下為絕對沒用過的 RSA Private Key 檔範例:
-----BEGIN RSA PRIVATE KEY-----
MIIBOQIBAAJBAOWeK9TSYVnPEkmK45TeI7NrC8MzvwabSVg1aEuTmcNkLRg/Qibv
B6ST5F056HCk0xvzm5rfO5A+q+4ncBMGMhkCAwEAAQJABGaLoICHrRjy2MX4ppm7
RWz/xLXxK0c+mJotbYVepQf1KYB6kPc4df1t4KrFFgxMyXzdJ/eiegf4j3HFT4Hs
gQIhAPiVVZNSMqp1uUx7OXB3GGW+3uslvA3+NvuAKqVguIo9AiEA7Hf6ykGeuRBN
k2EN+J+Vof4ryzFOtJGH+eyycmKPsQ0CIFJYteZ9nkcVhHKvh1GYQj7CQfpHn8pK
4k/iHz51kexJAiBwKs1kiVHv+QLDSQNmjtRcngNKBB6QWoQEkjlnNsdwNQIgbJYa
Ft4zOc8TnbXnGxXRfGVG0RZfXmxcQNv2yX2G4Vc=
-----END RSA PRIVATE KEY-----

你能看出這個檔案內的私鑰組成是什麼嗎?

openssl rsa -in 1.key -text
可看出私鑰的各組成資訊。

PEM 內文經過 BASE64 解碼後的 DER 編碼有 317 bytes,一個 Tag Block 有 16bytes,也要約 20 格。

-----BEGIN CERTIFICATE REQUEST-----
MIIBGzCBxgIBADBhMQswCQYDVQQGEwJUVzEPMA0GA1UECBMGVGFpd2FuMQ8wDQYD
VQQHEwZUYWl3YW4xHDAaBgNVBAoTE05DSFUgTUlTIERlcGFydG1lbnQxEjAQBgNV
BAMTCXNvdWx0YWtlcjBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDlnivU0mFZzxJJ
iuOU3iOzawvDM78Gm0lYNWhLk5nDZC0YP0Im7wekk+RdOehwpNMb85ua3zuQPqvu
J3ATBjIZAgMBAAGgADANBgkqhkiG9w0BAQUFAANBANOz3JapX/SSbdTCaQiriALm
ZlMeGW+L3XWnj4H7kXVljqsyYv6JJEAnQzdBcw1MiUZDYZqFOYJHxmx56eSIx8o=
-----END CERTIFICATE REQUEST-----

要求憑證簽發的申請檔,內含有國家、組織、姓名等個人資訊,你看得出來嗎?
-----BEGIN CERTIFICATE-----
MIIBtDCCAV4CCQChpB3R8j5vwDANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQGEwJU
VzEPMA0GA1UECBMGVGFpd2FuMQ8wDQYDVQQHEwZUYWl3YW4xHDAaBgNVBAoTE05D
SFUgTUlTIERlcGFydG1lbnQxEjAQBgNVBAMTCXNvdWx0YWtlcjAeFw0wOTA3MzEw
NDE1MDJaFw0xMDA3MzEwNDE1MDJaMGExCzAJBgNVBAYTAlRXMQ8wDQYDVQQIEwZU
YWl3YW4xDzANBgNVBAcTBlRhaXdhbjEcMBoGA1UEChMTTkNIVSBNSVMgRGVwYXJ0
bWVudDESMBAGA1UEAxMJc291bHRha2VyMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
AOWeK9TSYVnPEkmK45TeI7NrC8MzvwabSVg1aEuTmcNkLRg/QibvB6ST5F056HCk
0xvzm5rfO5A+q+4ncBMGMhkCAwEAATANBgkqhkiG9w0BAQUFAANBAGQhB8VKtY2N
hecmukSUmJkTzFWwT15fPmTGzf0KdrccNCSK2bE/u6bTW9dkUF12MkTaHNVytmKd
u7MoPuNvCnA=
-----END CERTIFICATE-----

crt 已簽署憑證內容。這張憑證是自我簽署的,沒什麼效用。

openssl x509 -in 1.crt -noout -modulus
輸出公鑰係數。

OpenSSL 參考資料:
http://www.ascc.sinica.edu.tw/nl/91/1819/02.txt
http://setnet.com.tw/ftp/%BA%FB%AD%D7%A7%DE%B3N%BB%A1%A9%FA/Linux/%C3%B1%B5o%BE%CC%B5%FD.txt
http://csc.ocean-pioneer.com/docum/ssl_basic.html