一般的HTTPS是基于SSL(Secure Sockets Layer)协议。SSL是网景公司开发的位于TCP与HTTP之间的透明安全协议,通过SSL,可以把HTTP包数据以非对称加密的形式往返于浏览器和站点之间,从而避免被第三方非法获取。
1.加密通信流程
当用户在浏览器中输入一个以https开头的网址时,便开启了浏览器与被访问站点之间的加密通信。下面我们以一个用户访问https://123.sogou.com为例,给读者展现一下SSL/TLS的工作方式。
1).在浏览器中输入HTTPS协议的网址,如下图所示。
2)服务器向浏览器返回证书,浏览器检查该证书的合法性,如下图所示:
3).验证合法性,如下图所示:
4).浏览器使用证书中的公钥加密一个随机对称密钥,并将加密后的密钥和使用密钥加密后的请求URL一起发送到服务器。
5).服务器用私钥解密随机对称密钥,并用获取的密钥解密加密的请求URL。
6).服务器把用户请求的网页用密钥加密,并返回给用户。
7).用户浏览器用密钥解密服务器发来的网页数据,并将其显示出来。
SSL协议由两层组成,上层协议包括SSL握手协议、更改密码规格协议、警报协议、下层协议包括SSL记录协议。
SSL握手协议建立在SSL记录协议之上,在实际的数据传输开始前,用于客户与服务器之间进行“握手”。“握手”是一个协商过程。这个协议使得客户和服务器能够互相鉴别身份,协商加密算法。在任何数据传输之前,必须先进行“握手”。
在“握手”完成之后,才能进行SSL记录协议,它的主要功能是为高层协议提供数据封装、压缩、添加MAC、加密等支持。
2.支持HTTPS的Web服务器
package main
import (
"fmt"
"net/http"
)
const SERVER_PORT = 8080
const SERVER_DOMAIN = "localhost"
const RESPONSE_TEMPLATE = "hello"
func rootHandler(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Content-Length", fmt.Sprint(len(RESPONSE_TEMPLATE)))
w.Write([]byte(RESPONSE_TEMPLATE))
}
func main() {
http.HandleFunc(fmt.Sprintf("%s:%d/", SERVER_DOMAIN, SERVER_PORT), rootHandler)
http.ListenAndServeTLS(fmt.Sprintf(":%d", SERVER_PORT), "rui.crt", "rui.key", nil)
}
可以看到,我们使用了http.ListenAndServerTLS()这个方法,这表明它是执行在TLS层上
的HTTP协议。如果我们并不需要支持HTTPS,只需要把该方法替换为http.ListenAndServeTLS
(fmt.Sprintf(":%d", SERVER_PORT), nil)即可。
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"time"
)
const SERVER_PORT = 8080
const SERVER_DOMAIN = "localhost"
const RESPONSE_TEMPLATE = "hello"
func rootHandler(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Content-Length", fmt.Sprint(len(RESPONSE_TEMPLATE)))
w.Write([]byte(RESPONSE_TEMPLATE))
}
func YourListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error {
config := &tls.Config{
Rand: rand.Reader,
Time: time.Now,
NextProtos: []string{"http/1.1"},
}
var err error
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = YourLoadX509KeyPair(certFile, keyFile)
if err != nil {
return err
}
conn, err := net.Listen("tcp", addr)
if err != nil {
return errs
}
tlsListener := tls.NewListener(conn, config)
return http.Serve(tlsListener, handler)
}
func YourLoadX509KeyPair(certFile string, keyFile string) (cert tls.Certificate, err error) {
certPEMBlock, err := ioutil.ReadFile(certFile)
if err != nil {
return
}
certDERBlock, restPEMBlock := pem.Decode(certPEMBlock)
if certDERBlock == nil {
err = errors.New("crypto/tls: failed to parse certificate PEM data")
return
}
certDERBlockChain, _ := pem.Decode(restPEMBlock)
if certDERBlockChain == nil {
cert.Certificate = [][]byte{certDERBlock.Bytes}
} else {
cert.Certificate = [][]byte{certDERBlock.Bytes,
certDERBlockChain.Bytes}
}
keyPEMBlock, err := ioutil.ReadFile(keyFile)
if err != nil {
return
}
keyDERBlock, _ := pem.Decode(keyPEMBlock)
if keyDERBlock == nil {
err = errors.New("crypto/tls: failed to parse key PEM data")
return
}
key, err := x509.ParsePKCS1PrivateKey(keyDERBlock.Bytes)
if err != nil {
err = errors.New("crypto/tls: failed to parse key")
return
}
cert.PrivateKey = key
x509Cert, err := x509.ParseCertificate(certDERBlock.Bytes)
if err != nil {
return
}
if x509Cert.PublicKeyAlgorithm != x509.RSA ||
x509Cert.PublicKey.(*rsa.PublicKey).N.Cmp(key.PublicKey.N) != 0 {
err = errors.New("crypto/tls: private key does not match public key")
return
}
return
}
func main() {
http.HandleFunc(fmt.Sprintf("%s:%d/", SERVER_DOMAIN, SERVER_PORT), rootHandler)
YourListenAndServeTLS(fmt.Sprintf(":%d", SERVER_PORT), "rui.crt", "rui.key", nil)
}
rand,伪随机函数发生器,用于产生基于时间和CPU时钟的伪随机数; rsa,非对称加密算法, rsa是三个发明者名字的首字母拼接而成; tls,我们在上面已介绍过,它是传输层安全协议; x509,一种常用的数字证书格式;pem,在非对称加密体系下,一般用于存放公钥和私钥的文件。
3.支持HTTPS的文件服务器
package main
import (
"net/http"
)
func main() {
h := http.FileServer(http.Dir("."))
http.ListenAndServeTLS(":8001", "rui.crt", "rui.key", h)
}