關于https背景知識
密碼學的一些基本知識
大致上分為兩類,基于key的加密算法與不基于key的加密算法?,F(xiàn)在的算法基本都是基于key的,key就以一串隨機數(shù)數(shù),更換了key之后,算法還可以繼續(xù)使用。
基于key的 加密算法 又分為兩類,對稱加密和不對稱加密,比如DES,AES那種的,通信雙方一方用key加密之后,另一方用相同的key進行反向的運算就可以解密。
不對稱 加密 比較著名的就是RSA,加密的時候有一個公鑰和一個私鑰,公鑰是可以交給對方的,a給b發(fā)送信息,a用自己的私鑰加密,b用a的公鑰解密,反之,b給a發(fā)送信息,b用自己的私鑰加密。
在通信之前,需要經(jīng)過一些握手的過程,雙方交換公鑰,這個就是key exchange的過程,https最開始的階段就包含了這個key exchange的過程,大概原理是這樣,有些地方還要稍微復雜一些。
數(shù)字證書與CA
數(shù)字證書相當于是服務器的一個“身份證”,用于唯一標識一個服務器。一般而言,數(shù)字證書從受信的權威證書授權機構 (Certification Authority,證書授權機構)買來的(免費的很少),瀏覽器里面一般就內置好了一些權威的CA,在使用https的時候,只要是這些CA簽發(fā)的證書,瀏覽器都是可以 認證 的,要是在與服務器通信的時候,收到一個沒有權威CA認證的證書,就會報出提醒不受信任證書的錯誤,就像登錄12306一樣,但是也可以選擇接受。
在自己的一些項目中,通常是自己簽發(fā)一個ca根證書,之后這個根證書簽發(fā)一個server.crt,以及server.key給服務端, server.key是服務端的私鑰,server.crt包含了服務端的公鑰還有 服務端 的一些身份信息。在客戶端和服務端通信的時候(特別是使用代碼編寫的客戶端訪問的時候),要指定ca根證書,作用就相當于是瀏覽器中內置的那些權威證書一樣,用于進行服務端的身份檢測。
證書的格式:
ca證書在為server.crt證書簽名時候的大致流程參考這個( http://www.tuicool.com/articles/aymYbmM):
數(shù)字證書 由兩部分組成:
1、C:證書相關信息(對象名稱+過期時間+證書發(fā)布者+證書簽名算法….)
2、S:證書的數(shù)字簽名 (由CA證書通過 加密算法 生成的)
其中的數(shù)字簽名是通過公式S = F(Digest(C))得到的。
Digest為摘要 函數(shù) ,也就是 md5、sha-1或sha256等單向散列算法,用于將無限輸入值轉換為一個有限長度的“濃縮”輸出值。比如我們常用md5值來驗證下載的大文件是否完整。大文件的內容就是一個無限輸入。大文件被放在網(wǎng)站上用于下載時,網(wǎng)站會對大文件做一次md5計算,得出一個128bit的值作為大文件的摘要一同放在網(wǎng)站上。用戶在下載文件后,對下載后的文件再進行一次本地的md5計算,用得出的值與網(wǎng)站上的md5值進行比較,如果一致,則大 文件下載完好,否則下載過程大文件內容有損壞或源文件被篡改。這里還有一個小技巧 常常在機器之間copy或者下載壓縮文件的時候也可以用md5sum的命令來進行檢驗,看看文件是否完整。
F為簽名函數(shù)。CA自己的私鑰是唯一標識CA簽名的,因此CA用于生成數(shù)字證書的簽名函數(shù)一定要以自己的私鑰作為一個輸入?yún)?shù)。在RSA加密系統(tǒng)中,發(fā)送端的解密函數(shù)就是一個以私鑰作為參數(shù)的函數(shù),因此常常被用作簽名函數(shù)使用。因此CA用私鑰解密函數(shù)作為F,以CA證書中的私鑰進行 加密 ,生成最后的 數(shù)字簽名 ,正如最后一部分實踐時候給出的證書生成過程,生成server.crt的時候需要ca.crt(包含根證書的信息)和ca.key(根證書的私鑰)都加入進去。
接收端接收 服務端 數(shù)字證書后,如何驗證 數(shù)字證書 上攜帶的簽名是這個CA的簽名呢?當然接收端首先需要指定對應的CA,接收端會運用下面算法對數(shù)字證書的簽名進行 校驗 :
F'(S) ?= Digest(C)
接收端進行兩個計算,并將計算結果進行比對:
1、首先通過Digest(C),接收端計算出證書內容(除簽名之外)的摘要,C的內容都是明文可以看到到的。
2、數(shù)字證書攜帶的簽名是CA通過CA密鑰 加密 摘要后的結果,因此接收端通過一個解密 函數(shù) F’對S進行“解密”。就像最開始介紹的那樣,在RSA系統(tǒng)中,接收端使用CA公鑰(包含在ca.crt中)對S進行“解密”,這恰是CA用私鑰對S進行“加密”的逆過程。
將上述兩個運算的結果進行比較,如果一致,說明簽名的確屬于該CA,該證書有效,否則要么證書不是該CA的,要么就是中途被人篡改了。
對于self-signed(自簽發(fā))證書來說,接收端并沒有你這個self-CA的 數(shù)字證書 ,也就是沒有CA公鑰,也就沒有辦法對數(shù)字證書的簽名進行驗證。因此如果要編寫一個可以對self-signed證書進行 校驗 的接收端程序的話,首先我們要做的就是建立一個屬于自己的CA,用該CA簽發(fā)我們的server端證書,之后給客戶端發(fā)送信息的話,需要對這個根證書進行指定,之后按上面的方式進行驗證。
可以使用openssl x509 -text -in client.crt -noout 查看某個證書文件所包含的具體信息。
HTTPS基本過程概述
https協(xié)議是在http協(xié)議的基礎上組成的secure的協(xié)議。主要功能包含一下兩個方面:
1 通信雙方的身份認證
2 通信雙方的通信過程加密
下面通過詳細分析https的通信過程來解釋這兩個功能。
具體參考這兩個文章:
http://www.fenesky.com/blog/2014/07/19/how-https-works.html
http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html
1、client 發(fā)送 sayhello給server端,說明client所支持的加密套件,還有一個隨機數(shù)1。
2、server 發(fā)送 sayhello給client端,端把server.crt發(fā)送給客戶端,server.crt采用還有一個隨機數(shù)2。
3、client端生成preMaster key 這個是隨機數(shù)3,之后三個隨機數(shù)結合在一起生成MasterSecret,之后生成session secret,使用指定的ca進行 身份認證 ,就像之前介紹的那樣,都正常的話,就切換到 加密 模式。
4、client端使用server.crt中的公鑰對preMasterSecret進行加密,如果要進行雙向 認證 的話,client端會把client.crt一并發(fā)送過去,server端接受到數(shù)據(jù),解密之后,也有了三個隨機數(shù),采用同樣的方式,三個隨機數(shù)生成通信所使用的session secret。具體session secret的結構可以參考前面列出的兩個博客。server端完成相關工作之后,會發(fā)一個ChangeCipherSpec給client,通知client說明自己已經(jīng)切換到相關的加解密模式,之后發(fā)一段加密信息給client看是否正常。
5、client端解密正常,之后就可以按照之前的協(xié)議,使用session secret進行加密的通信了。
整體看下,開始的時候建立握手的過程就是 身份認證 的過程,之后認證完畢之后,就是 加密 通信的過程了,https的兩個主要做用就實現(xiàn)了。
相關實踐
比較典型的證書生成的過程:
openssl genrsa -out ca.key 2048#這里可以使用 -subj 不用進行交互 當然還可以添加更多的信息openssl req -x509 -new -nodes -key ca.key -subj "/CN=zju.com" -days 5000 -out ca.crtopenssl genrsa -out server.key 2048#這里的/cn可以是必須添加的 是服務端的域名 或者是etc/hosts中的ip別名openssl req -new -key server.key -subj "/CN=server" -out server.csropenssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 5000
注意生成client端證書的時候,注意要多添加一個字段,golang的server端認證程序會對這個字段進行認證:
openssl genrsa -out client.key 2048openssl req -new -key client.key -subj "/CN=client" -out client.csrecho extendedKeyUsage=clientAuth > extfile.cnfopenssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -extfile extfile.cnf -out client.crt -days 5000
https客戶端和服務端單向校驗
這部分參考了這個( http://www.tuicool.com/articles/aymYbmM
),里面代碼部分講得比較細致。
服務端 采用證書,客戶端采用普通方式訪問:
//server端代碼
package main
import (
"fmt"
"net/http"
"os"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi, This is an example of https service in golang!")
}
func main() {
http.HandleFunc("/", handler)
//http.ListenAndServe(":8080", nil)
_, err := os.Open("cert_server/server.crt")
if err != nil {
panic(err)
}
http.ListenAndServeTLS(":8081", "cert_server/server.crt", "cert_server/server.key", nil)}
使用瀏覽器直接訪問的話,之后點擊信賴證書,這個時候就可以正常get到消息
或者使用curl -k https://來經(jīng)行訪問,相當于忽略了第一步的身份驗證的工作。
要是不加-k的話 使用curl -v 參數(shù)打印出來詳細的信息,會看到如下的錯誤:
curl: (60) SSL certificate problem: Invalid certificate chain
說明是 認證 沒有通過,因為客戶端這面并沒有提供可以信賴的根證書來對服務端發(fā)過來的證書進行驗,/CN使用的直接是ip地址,就會報下面的錯誤:
Get https://10.183.47.206:8081: x509: cannot validate certificate for 10.183.47.206 because it doesn't contain any IP SANs
最好是生成證書的時候使用域名,或者是在/etc/hosts中加上對應的映射。
可以發(fā)送請求的客戶端的代碼如下,注意導入根證書的方式:
package main
import (
//"io"
//"log"
"crypto/tls"
"crypto/x509"
//"encoding/json"
"fmt" "io/ioutil"
"net/http"
//"strings"
)
func main() {
//x509.Certificate. pool := x509.NewCertPool()
//caCertPath := "etcdcerts/ca.crt"
caCertPath := "certs/cert_server/ca.crt"
caCrt, err := ioutil.ReadFile(caCertPath)
if err != nil {
fmt.Println("ReadFile err:", err)
return
}
pool.AppendCertsFromPEM(caCrt)
//pool.AddCert(caCrt)
tr := &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: pool},
DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://server:8081")
if err != nil {
panic(err)
}
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
fmt.Println(resp.Status)
}
使用curl命令的話,就加上–cacrt ca.crt證書,這樣就相當于添加了可信賴的證書, 身份認證 的操作就可以成功了。
比如生成 服務端 證書的時候/CN寫的是server 那client發(fā)送的時候也發(fā)送給https://server:8081就好,不過在本地的/etc/hosts中要加上對應的映射。
客戶端和服務端的雙向校驗:
按照之前的方式,客戶端生成證書,根證書就按之前的那個:
openssl genrsa -out client.key 2048openssl req -new -key client.key -subj "/CN=client" -out client.csropenssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 5000
server端代碼進行改進,添加受信任的根證書。
// gohttps/6-dual-verify-certs/server.go
package main
import (
"crypto/tls"
"crypto/x509"
"fmt" "io/ioutil"
"net/http"
)
type myhandler struct {}
func (h *myhandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, "Hi, This is an example of http service in golang!\n")
}
func main() {
pool := x509.NewCertPool()
caCertPath := "cert_server/ca.crt"
caCrt, err := ioutil.ReadFile(caCertPath)
if err != nil {
fmt.Println("ReadFile err:", err)
return
}
pool.AppendCertsFromPEM(caCrt)
s := &http.Server{
Addr: ":8081",
Handler: &myhandler{},
TLSConfig: &tls.Config{
ClientCAs: pool,
ClientAuth: tls.RequireAndVerifyClientCert,
},
}
err = s.ListenAndServeTLS("cert_server/server.crt", "cert_server/server.key") if err != nil { fmt.Println("ListenAndServeTLS err:", err) }
}
客戶端代碼改進,發(fā)送的時候把指定client端的client.crt以及client.key
// gohttps/6-dual-verify-certs/client.go
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
pool := x509.NewCertPool()
caCertPath := "certs/cert_<span id="0_nwp"><a id="0_nwl" target="_blank">server</a></span>/ca.crt"
caCrt, err := ioutil.ReadFile(caCertPath) if err != nil {
fmt.Println("ReadFile err:", err)
return
}
pool.AppendCertsFromPEM(caCrt)
cliCrt, err := tls.LoadX509KeyPair("certs/cert_server/client.crt", "certs/cert_server/client.key") if err != nil {
fmt.Println("Loadx509keypair err:", err) return }
tr := &http.Transport{
TLSClientConfig: &
tls.Config{
RootCAs: pool,
Certificates: []tls.Certificate{cliCrt},
},
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://<span id="1_nwp"><a id="1_nwl" target="_blank">server</a></span>:8081")
if err != nil {
fmt.Println("Get error:", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
但實際上,這樣是不行的, server 端會報這樣的錯誤:
client's certificate's extended key usage doesn't permit it to be used for client authentication
因為client的證書生成方式有一點不一樣,向開始介紹的那樣,goalng對于client端的認證要多一個參數(shù),生成證書的時候,要加上一個單獨的認證信息:
openssl genrsa -out client.key 2048openssl req -new -key client.key -subj "/CN=client" -out client.csrecho extendedKeyUsage=clientAuth > extfile.cnfopenssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -extfile extfile.cnf -out client.crt -days 5000
就是多添加一個 認證 文件的信息,之后使用新的證書就可以實現(xiàn)雙向認證了,這樣只有那些持有被認證過的證書的客戶端才能向服務端發(fā)送請求。
etcd的https的配置
docker 的https配置
k8的 apiserver的https的配置