设为首页 收藏本站
查看: 718|回复: 0

[经验分享] https原理以及golang基本实现

[复制链接]

尚未签到

发表于 2018-9-19 12:50:30 | 显示全部楼层 |阅读模式
关于https

背景知识

密码学的一些基本知识
  大致上分为两类,基于key的加密算法与不基于key的加密算法。现在的算法基本都是基于key的,key就以一串随机数数,更换了key之后,算法还可以继续使用。
  基于key的加密算法又分为两类,对称加密和不对称加密,比如DES,AES那种的,通信双方一方用key加密之后,另一方用相同的key进行反向的运算就可以解密。
  不对称加密比较著名的就是RSA,加密的时候有一个公钥和一个私钥,公钥是可以交给对方的,a给b发送信息,a用自己的私钥加密,b用a的公钥解密,反之,b给a发送信息,b用自己的私钥加密。
  在通信之前,需要经过一些握手的过程,双方交换公钥,这个就是key exchange的过程,https最开始的阶段就包含了这个key exchange的过程,大概原理是这样,有些地方还要稍微复杂一些。

数字证书与CA
  数字证书相当于是服务器的一个“身份证”,用于唯一标识一个服务器。一般而言,数字证书从受信的权威证书授权机构 (Certification Authority,证书授权机构)买来的(免费的很少),浏览器里面一般就内置好了一些权威的CA,在使用https的时候,只要是这些CA签发的证书,浏览器都是可以认证的,要是在与服务器通信的时候,收到一个没有权威CA认证的证书,就会报出提醒不受信任证书的错误,就像登录12306一样,但是也可以选择接受。
  在自己的一些项目中,通常是自己签发一个ca根证书,之后这个根证书签发一个server.crt,以及server.key给服务端,server.key是服务端的私钥,server.crt包含了服务端的公钥还有服务端的一些身份信息。在客户端和服务端通信的时候(特别是使用代码编写的客户端访问的时候),要指定ca根证书,作用就相当于是浏览器中内置的那些权威证书一样,用于进行服务端的身份检测。
  证书的格式:
  ca证书在为server.crt证书签名时候的大致流程参考这个(http://www.tuicool.com/articles/aymYbmM):
  数字证书由两部分组成:
  1、C:证书相关信息(对象名称+过期时间+证书发布者+证书签名算法….)
  2、S:证书的数字签名 (由CA证书通过加密算法生成的)
  其中的数字签名是通过公式S = F(Digest(C))得到的。
  Digest为摘要函数,也就是 md5、sha-1或sha256等单向散列算法,用于将无限输入值转换为一个有限长度的“浓缩”输出值。比如我们常用md5值来验证下载的大文件是否完整。大文件的内容就是一个无限输入。大文件被放在网站上用于下载时,网站会对大文件做一次md5计算,得出一个128bit的值作为大文件的摘要一同放在网站上。用户在下载文件后,对下载后的文件再进行一次本地的md5计算,用得出的值与网站上的md5值进行比较,如果一致,则大 文件下载完好,否则下载过程大文件内容有损坏或源文件被篡改。这里还有一个小技巧常常在机器之间copy或者下载压缩文件的时候也可以用md5sum的命令来进行检验,看看文件是否完整。
  F为签名函数。CA自己的私钥是唯一标识CA签名的,因此CA用于生成数字证书的签名函数一定要以自己的私钥作为一个输入参数。在RSA加密系统中,发送端的解密函数就是一个以私钥作为参数的函数,因此常常被用作签名函数使用。因此CA用私钥解密函数作为F,以CA证书中的私钥进行加密,生成最后的数字签名,正如最后一部分实践时候给出的证书生成过程,生成server.crt的时候需要ca.crt(包含根证书的信息)和ca.key(根证书的私钥)都加入进去。
  接收端接收服务端数字证书后,如何验证数字证书上携带的签名是这个CA的签名呢?当然接收端首先需要指定对应的CA,接收端会运用下面算法对数字证书的签名进行校验:
  
F'(S) ?= Digest(C)
  接收端进行两个计算,并将计算结果进行比对:
  1、首先通过Digest(C),接收端计算出证书内容(除签名之外)的摘要,C的内容都是明文可以看到到的。
  2、数字证书携带的签名是CA通过CA密钥加密摘要后的结果,因此接收端通过一个解密函数F'对S进行“解密”。就像最开始介绍的那样,在RSA系统中,接收端使用CA公钥(包含在ca.crt中)对S进行“解密”,这恰是CA用私钥对S进行“加密”的逆过程。
  将上述两个运算的结果进行比较,如果一致,说明签名的确属于该CA,该证书有效,否则要么证书不是该CA的,要么就是中途被人篡改了。
  对于self-signed(自签发)证书来说,接收端并没有你这个self-CA的数字证书,也就是没有CA公钥,也就没有办法对数字证书的签名进行验证。因此如果要编写一个可以对self-signed证书进行校验的接收端程序的话,首先我们要做的就是建立一个属于自己的CA,用该CA签发我们的server端证书,之后给客户端发送信息的话,需要对这个根证书进行指定,之后按上面的方式进行验证。
  可以使用openssl x509 -text -in client.crt -noout 查看某个证书文件所包含的具体信息。

HTTPS基本过程概述
  https协议是在http协议的基础上组成的secure的协议。主要功能包含一下两个方面:
  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 发送 sayhello给server端,说明client所支持的加密套件,还有一个随机数1。
  
2、server 发送 sayhello给client端,端把server.crt发送给客户端,server.crt采用还有一个随机数2。
  
3、client端生成preMaster key 这个是随机数3,之后三个随机数结合在一起生成MasterSecret,之后生成session secret,使用指定的ca进行身份认证,就像之前介绍的那样,都正常的话,就切换到加密模式。
  
4、client端使用server.crt中的公钥对preMasterSecret进行加密,如果要进行双向认证的话,client端会把client.crt一并发送过去,server端接受到数据,解密之后,也有了三个随机数,采用同样的方式,三个随机数生成通信所使用的session secret。具体session secret的结构可以参考前面列出的两个博客。server端完成相关工作之后,会发一个ChangeCipherSpec给client,通知client说明自己已经切换到相关的加解密模式,之后发一段加密信息给client看是否正常。
  
5、client端解密正常,之后就可以按照之前的协议,使用session secret进行加密的通信了。
  整体看下,开始的时候建立握手的过程就是身份认证的过程,之后认证完毕之后,就是加密通信的过程了,https的两个主要做用就实现了。

相关实践

比较典型的证书生成的过程:
  

openssl genrsa -out ca.key 2048  

  
#这里可以使用 -subj 不用进行交互 当然还可以添加更多的信息
  
openssl req -x509 -new -nodes -key ca.key -subj "/CN=zju.com" -days 5000 -out ca.crt
  

  
openssl genrsa -out server.key 2048
  

  
#这里的/cn可以是必须添加的 是服务端的域名 或者是etc/hosts中的ip别名
  
openssl req -new -key server.key -subj "/CN=server" -out server.csr
  

  
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 5000
  

  
#查询证书的情况
  
openssl x509 -in ./server.crt -noout -text
  

  

  注意生成client端证书的时候,注意要多添加一个字段,golang的server端认证程序会对这个字段进行认证:
  

openssl genrsa -out client.key 2048  

  
openssl req -new -key client.key -subj "/CN=client" -out client.csr
  

  
echo extendedKeyUsage=clientAuth > extfile.cnf
  

  
openssl 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)
  
}
  

  

  client端直接发请求,什么都不加,会报如下错误:
  

2015/07/11 18:13:50 http: TLS handshake error from 10.183.47.203:58042: remote error: bad certificate  

  使用浏览器直接访问的话,之后点击信赖证书,这个时候就可以正常get到消息
  或者使用curl -k https:// 来经行访问,相当于忽略了第一步的身份验证的工作。
  
要是不加-k的话 使用curl -v 参数打印出来详细的信息,会看到如下的错误:
  

curl: (60) SSL certificate problem: Invalid certificate chain  

  说明是认证没有通过,因为客户端这面并没有提供可以信赖的根证书来对服务端发过来的证书进行验,/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中加上对应的映射。
  可以发送请求的客户端的代码如下,注意导入根证书的方式:
  

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发送的时候也发送给https://server:8081就好,不过在本地的/etc/hosts中要加上对应的映射。
  补充:最近发现k8中的GCE证书生成的时候使用了esyrsa的一个包,貌似可以处理IPSAN的一些情况,可以在生成证书的时候把 ipsan 添加进去。
  貌似在ssl生成证书的时候也可以把ipsan添加进去,比如这几个文档(http://blog.csdn.net/linsanhua/article/details/16986701,http://apetec.com/support/GenerateSAN-CSR.htm),但是参数比较多,还没有实验成功。
  主要是设置一个subjectAltname 的字段,可以再参考这个:(http://wiki.cacert.org/FAQ/subjectAltName),资料中大多都是从一个配置文件中将信息读入,貌似可以在-subj的参数中制定?

客户端和服务端的双向校验:
  按照之前的方式,客户端生成证书,根证书就按之前的那个:
  

openssl genrsa -out client.key 2048  

  
openssl req -new -key client.key -subj "/CN=client" -out client.csr
  

  
openssl 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)
  }
  
}
  

  

  客户端代码改进,发送的时候把指定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_server/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://server: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端的认证要多一个参数,生成证书的时候,要加上一个单独的认证信息:
  

  
openssl genrsa -out client.key 2048
  

  
openssl req -new -key client.key -subj "/CN=client" -out client.csr
  

  
echo extendedKeyUsage=clientAuth > extfile.cnf
  

  
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -extfile extfile.cnf -out client.crt -days 5000
  

  

  就是多添加一个认证文件的信息,之后使用新的证书就可以实现双向认证了,这样只有那些持有被认证过的证书的客户端才能向服务端发送请求。

单向https加token的使用方式
  在实际操作的过程中,有的时候可能用到https的方式更多的是希望用到其安全传输的特性,身份验证的地方可能弱一点,比如在服务端放了server.crt以及server.key的证书,客户端单向使用https发请求的时候,必须还要指定自己的受信根证书,这时候还得把服务端的根证书提前分发给客户端,比较麻烦,可以在配置客户端的Transport的时候,把InsecureSkipVerify参数设置为true,这样就不会对服务端的证书进行身份验证了。服务端对客户端的验证可以通过在Header信息中添加token的参数来进行。但是这种只能用在测试的环境中,由于客户端没有对服务端传递过来的请求进行身份验证,很可能传递回来的请求被进行了篡改或者劫持,具体的细节不太清楚,总之还是有风险的,只适用于某些特殊的场合。
  etcd的https的配置
  docker 的https配置
  k8的 apiserver的https的配置

相关参考
  http://www.fenesky.com/blog/2014/07/19/how-https-works.html
  
http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html
  
http://www.tuicool.com/articles/aymYbmM
  
这个文章介绍了生成ca文件的一些具体参数:
  
http://blog.csdn.net/linsanhua/article/details/16878817



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-594278-1-1.html 上篇帖子: golang调试工具Delve 下篇帖子: golang string和[]byte的对比
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表