Pages

Tuesday, 31 July 2018

政治局提出六稳

习近平访非归来下车伊始中共便召开政治局会议,这次会议有个奇特的安排,把“分析当前经济形势,部署下半年经济工作”与“中国共产党纪律处分条例”绑在一起。会议精神在于突出“六稳”,却绑上纪律处分条例,给人一种中美商贸战正酣,北戴河休闲会也来淌浑水的诡异气氛。当局求稳,稳定压倒一切,十九大结束后到两会修宪完毕那段时间,习近平稳如泰山的那种气势似已成为过去。
这次会议的一个重点,下半年,要做好六个稳。“稳就业、稳金融、稳外贸、稳外资、稳投资、稳预期”。这六个稳指向经济民生,但矛头却是对外。为什么强调六个稳?新华社的会议报道称,因为当前经济运行,稳中有变,面临一些新问题、新挑战,外部环境发生明显变化。这里面当然有指中国社会不稳定的因素,但恐怕更重要的是指中美贸易战以来引发的世界情势的新变化,而这一新情势又反过来作用于中国社会,引发社会预期不明,动荡因素增大。有人分析指出,因为不稳,所以要稳。同时提六稳,好像是多年来首次,似乎形势真的严重

如果没有特朗普亮出底牌,不惜把这场贸易战打到底,甚至一直下好了准备对五千亿美元中国出口产品征税的决心,中共政治局会议无须如此强调六稳,北京预感到某种不确定因素,某种潜在的危险,某种难以预知的后果
观察人士注意到,中美贸易战进行到目前这一阶段,如果不说完败的话,至少可以说中方严重受挫,这一挫败是在北京当局毫无先见之明的背景下造成的,比如对特朗普打商贸战的决心估计不足,比如过分重视人事外交的作用,比如以为特朗普是孤家寡人等等。当美国敲响前奏,禁止对中国高科技企业中兴出口其必须的零配件,中国方面始知特朗普的厉害,中兴最后以缴了巨额罚款还要接受美国派遣人员监督10年作为代价达成了复工的协议。但是中兴事件并未引发北京当局做出更深的思考,更全面地战略性筹划,而是对特朗普要打商贸战总以为说说而已,就像特朗普口头上已经念叨了快两年一样,就像他的所有前任那样,最后以中方做出巨额采购等方式不了了之。然而特朗普推出了500亿美元中国产品清单,加征25%关税,中方在习近平以牙还牙精神鼓励下,随即以等额反击,结果特朗普推出第二轮2000亿美元关税清单,中方难以为继,特朗普再接再厉,表示如果中方反击,他会对全数5000亿中国出口美国产品课征关税,中方除了要求对方理性或指其搞贸易霸权主义,已经失去了起初的气势。
更严重的问题还在后面,北京本指望拉拢同样遭美国加征钢铝税的欧盟一道反击美国,或者至少得到欧盟方面的理解,李克强访问欧盟,北京召开中欧峰会,表面看有声有色,然而,欧盟委员会主席容克与美国总统特朗普一夜之间就达成双方停止贸易纠纷的和解声明,并同意进一步商谈降低关税直至零关税,有着同样价值观的欧美消解了贸易冲突,重新变成了合作伙伴。其实,中国政府如果深思,在对中国不尊重知识产权,不公平竞争等一些重大问题的认知上,欧美向来几乎是一致的。不过表述的方式不一样而已。欧盟没有任何理由站在中国一边对付美国。加上之前日本与欧盟轻易达成的自贸协定,如果美欧有一日实现零关税,中国面临的情形将十分严重。
中共政治局会议强调六稳,在外交、经贸连连遭受挫折的情形下,先设法稳住自家阵脚,免得引发社会动荡。尤其在自身经济发展本已放缓,在遭商贸战围攻的情形下,社会问题的爆发可能性极大,稳定又成为北京最迫切的任务。
但是,形势至此,中共党内一部分人认为习近平本人应负有重大责任,首先,如果不是习近平改变邓小平的韬光养晦战略,中国不至于如此腹背受敌,内外交困,而习近平放弃韬光养晦,与他错误判断形势,在一片近臣营造的超级个人崇拜气氛的熏陶下陶醉不无重大关系。中共党内的、或者说体制内的不满最近通过多种方式体现出来,传言元老要求召开中共政治局扩大会议讨论习近平的个人崇拜危害;学者许章润要求废除国家主席终身制,还有网络四传的学者高善文本人不认领的‘报告’,说的也是体制内人士普遍担心的问题。要点是谴责习近平极为短视地将邓小平苦心经营得来的中美关系搞坏了。
都不是无的放矢。加之北戴河休闲在即,压力倍增。中共高官或退休高官北戴河休闲不可小瞧,胡平有精辟的分析,“名义上,党国大佬们去北戴河是去避暑休闲的,有的还会带上家眷,这就为他们提供了大好的机会,可以串门聊天,互通声气,交换政见,这就使得他们有可能连起手来,采取某种共同行动,平时他们根本没有这样交流的机会。打电话上网都被监控,副部级三人以上聚会就要报告。”这可以说就是北戴河休闲的潜在威力。习近平不得不考虑
这可能一定程度上解释了这次如此严峻的对应“外敌”稳住内政的政治局会议,硬是同一个共产党纪律处分条例绑在一起。当然,习近平仍然大权在握,他不需要任何处分条例就可以处置敌手,如同他与王岐山联手反腐一样。但现在的情形不同,嗡嗡嗡的声音太多太杂,党内不显身不露面的敌人太多,他们的不满某种程度上同小圈子的社会舆论呼应,对习的威信的杀伤力自然不小,因此,制定这样一个“名正言顺、合乎规则”的处分条例,等于把不许妄议中央更加具体化到方方面面。习近平在非洲访问的时候,有一个红二代出身的退休副部级就以妄议的名义遭到处置,似乎为中共纪律处分条例应声而出做了铺垫.

虎嗅网:该以穷人之名原谅拼多多吗

拼多多上市了,但这家年轻公司所引发的争议却越来越大。
就在7月28日,知名电视品牌创维发表声明称,该公司正与‌‌“拼多多‌‌”严正交涉,要求即日停止所有假冒创维电视产品的展示及销售活动,并保留追究‌‌“拼多多‌‌”及相关侵权方的全部法律责任。创维愤怒的原因在于,拼多多平台上顶着创维之名售卖的产品竟然不止一款,他们罗列的‌‌“李鬼‌‌”产品至少包括:创维先锋、创维云视听TV、创维嘉、创维美、创维酷酷、创维云视听、创维e家……
大概不止创维一家对拼多多平台上的假冒产品感到愤怒——从目前社交网络上流传那些令人哭笑不得的图片来看,至少还有‌‌“超熊‌‌”洗衣液、‌‌“立日‌‌”洗衣液、‌‌“云南中药‌‌”牙膏、‌‌“红午‌‌”功能饮料、‌‌“TGL‌‌”电视、‌‌“小米新品‌‌”液晶电视……除了日用品和家用电器这些重灾区外,连知名童话作家郑渊洁也在微博吐槽,拼多多竟然还在卖‌‌“皮皮鲁系列‌‌”盗版书。
过去十几年,中国艰难地甩掉了‌‌“山寨大国‌‌”的帽子,万万没想到,有些电商公司却通过‌‌“模式创新‌‌”,又帮我们捡回来戴上了。微信公众号‌‌“起朱楼宴宾客‌‌”讽刺道:
‌‌“拼夕夕的上市让历史倒退了二十年。‌‌”
要什么体面,拿下市场就是胜利
几个月前,拼多多的年度活跃用户就将近3亿,而GMV已仅次于淘宝和京东。
拼多多的成功路径类似今日头条——它们发现了曾经被边缘化但如今正在走向互联网舞台中央的消费者们,它们都抓住了三四线以下最广大城镇互联网用户的信息与产品消费痛点:廉价、易得、对产品质量没有识别力或完全不在意。
一言以蔽之:这些‌‌“农村包围城市‌‌”的公司,抓住了温饱(不是小康)人群的消费需求,倘说‌‌“仓禀实而知荣辱‌‌”,那他们的字典里压根儿就不存在‌‌“体面‌‌”一词。
拼多多CEO黄峥对此辩称:‌‌“淘宝吃过的苦,(拼多多)都躲不过。‌‌”言外之意,你淘宝当年不也是靠各种山寨假冒产品获取用户野蛮生长起来的吗?淘宝做得,拼多多就做不得?拿三岁的淘宝与三岁拼多多的GMV做对比的话,拼多多这‌‌“苦头‌‌”怕是要多得多。
目前,目前还没有友商愿意对这个逻辑陷阱发表看法。
因为没钱,所以正义?
黄峥曾说,(北京)五环内的人不懂拼多多——好的,感谢诡辩大师黄峥,北京的媒体人还是看得懂拼多多的,毕竟他们绝大部分在五环内上班,却在五环外睡觉和消费。
拼多多并非没有同情者,而他们本着‌‌“存在即合理‌‌”的精神,也道出了一个事实:你在三四线以下的城镇和村头小卖部购物,货架上的东西和拼多多没有任何区别。让他们买正品,首先这些消费者得能买得起啊。
这里蕴含两个争议:
第一,因为这些消费者生活的环境本就充斥着廉价山寨假冒产品,所以他们使用拼多多,不过是把消费场景从线下转移到线上,为什么只怪拼多多,不怪小卖部?
第二,因为没钱,所以买不起正品,穷人之前不敢想象的产品,如今竟然可以出低价唾手可得。这不算为消费者造福?
首先,因为赚钱且暂时没有受到制裁,就丢掉正义观念,这是近些年来商业发展的惯例,即令某些跨国公司亦如此。现在你需要树立起是非观念了:小城镇消费者面对的恶劣的线下零售环境,是亟需革除的商业毒瘤,而不是新商业模式赖以成长和催熟的温床。不能因为小卖部卖假货就认为‌‌“存在即合理‌‌”,从而推定拼多多卖山寨假货就合理。事实上,凡是卖假冒山寨货的,无论线上线下,都应该被抵制而非被鼓励。
其次,拼多多的同情者们大概认为,拼多多之所以善莫大焉,是因为其‌‌“品牌降级‌‌”换来了穷人的‌‌“消费升级‌‌”。拨丝抽茧后的逻辑是:既然山寨假货便宜而且还‌‌“能凑合着用‌‌”,那么穷人就应该感到高兴,也应该感谢拼多多的商业模式。
换你是穷人,你愿意接受这种犬儒逻辑吗?
诛心地说,‌‌“正当的目的使手段正当,至于不正当的目的,就不会使手段正当‌‌”,这样的公司股票,不买也罢;这样的商业模式,不支持也罢。
2004年,黄峥加入Google,两年后,他和李开复一起被委派到中国拓展业务。12年后,他的身价已经超越了那个白手起家的刘强东。但他好像忘了老东家Google赖以起家、也最受人尊敬的的口号:Don’t be evil.

不要作恶。

让mac信任自签的证书

Here is a quick trick you can use to make sure your browsers accept self-generated SSL certificates on OS X.

This might be common knowledge in the web development community but today I am going to show you a quick trick to get your Mac to accept staging or development SSL certificates as if they were production certs.
Often we will have to work around the fact that SSL certificates in development don’t pass muster. We can get our local environment to load in an SSL certificate, but it won’t be valid.
Recently one of the team working on the football tipster site I run was explaining how they were having issues getting some service workers working properly because in development we use the domain name https://thefootytipster.dev and the SSL certificate is a self-signed thing that I made on my machine. Service workers rely on either being on localhost or on a trusted SSL connection.
I’ve been bitten before by updates to browsers suddenly deciding that the SSL certificate I was using in staging or development now isn’t good enough to allow me in.
Well, no more!

Getting OS X to trust self-signed SSL Certificates

Here is the guide for getting your browsers to accept self-generated SSL certificates on OS X. I am sure it is just as easy on other operating systems and hopefully this guide will give you a head start on what to search for.
  1. Locate where your certificate file is. It is likely to be somewhere near your web server configurations.
  2. Open up Keychain Access. You can get to it from Application/Utilities/Keychain Access.app.
  3. Drag your certificate into Keychain Access.
  4. Go into the Certificates section and locate the certificate you just added
  5. Double click on it, enter the trust section and under “When using this certificate” select “Always Trust”
Et viola, now when viewing your website locally your certificate will be trusted.

Video Guide to getting OS X to trust self-signed SSL Certificates

If you would prefer to follow along with a video, you can!

from https://tosbourn.com/getting-os-x-to-trust-self-signed-ssl-certificates/

利用一个HTTPS Proxy in Golang翻墙

The goal is to implement a proxy server for HTTP and HTTPS. Handling of HTTP is a matter of parsing request, passing such request to destination server, reading response and passing it back to the client. All we need for that is built-in HTTP server and client (net/http). HTTPS is different as it’ll use technique called HTTP CONNECT tunneling. First client sends request using HTTP CONNECT method to set up the tunnel between the client and destination server. When such tunnel consisting of two TCP connections is ready, client starts regular TLS handshake with destination server to establish secure connection and later send requests and receive responses.

Certificates

Our proxy will be an HTTPS server (when —-proto https will be used) so we need certificate and private key. For the purpose of this post let’s use self-signed certificate. To generate one use such script:
#!/usr/bin/env bash
case `uname -s` in
    Linux*)     sslConfig=/etc/ssl/openssl.cnf;;
    Darwin*)    sslConfig=/System/Library/OpenSSL/openssl.cnf;;
esac
openssl req \
    -newkey rsa:2048 \
    -x509 \
    -nodes \
    -keyout server.key \
    -new \
    -out server.pem \
    -subj /CN=localhost \
    -reqexts SAN \
    -extensions SAN \
    -config <(cat $sslConfig \
        <(printf '[SAN]\nsubjectAltName=DNS:localhost')) \
    -sha256 \
    -days 3650
It’s required to convince your OS to trust such certificate. In OS X it can be done with Keychain Access — https://tosbourn.com/getting-os-x-to-trust-self-signed-ssl-certificates/.

HTTP

To support HTTP we’ll use built-in HTTP server and client. The role of proxy is to handle HTTP request, pass such request to destination server and send response back to the client.






HTTP CONNECT tunneling

Suppose client wants to use either HTTPS or WebSockets in order to talk to server. Client is aware of using proxy. Simple HTTP request / response flow cannot be used since client needs to e.g. establish secure connection with server (HTTPS) or wants to use other protocol over TCP connection (WebSockets). Technique which works is to use HTTP CONNECT method. It tells the proxy server to establish TCP connection with destination server and when done to proxy the TCP stream to and from the client. This way proxy server won’t terminate SSL but will simply pass data between client and destination server so these two parties can establish secure connection.






Implementation:

package main
import (
    "crypto/tls"
    "flag"
    "io"
    "log"
    "net"
    "net/http"
    "time"
)
func handleTunneling(w http.ResponseWriter, r *http.Request) {
    dest_conn, err := net.DialTimeout("tcp", r.Host, 10*time.Second)
    if err != nil {
        http.Error(w, err.Error(), http.StatusServiceUnavailable)
        return
    }
    w.WriteHeader(http.StatusOK)
    hijacker, ok := w.(http.Hijacker)
    if !ok {
        http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
        return
    }
    client_conn, _, err := hijacker.Hijack()
    if err != nil {
        http.Error(w, err.Error(), http.StatusServiceUnavailable)
    }
    go transfer(dest_conn, client_conn)
    go transfer(client_conn, dest_conn)
}
func transfer(destination io.WriteCloser, source io.ReadCloser) {
    defer destination.Close()
    defer source.Close()
    io.Copy(destination, source)
}
func handleHTTP(w http.ResponseWriter, req *http.Request) {
    resp, err := http.DefaultTransport.RoundTrip(req)
    if err != nil {
        http.Error(w, err.Error(), http.StatusServiceUnavailable)
        return
    }
    defer resp.Body.Close()
    copyHeader(w.Header(), resp.Header)
    w.WriteHeader(resp.StatusCode)
    io.Copy(w, resp.Body)
}
func copyHeader(dst, src http.Header) {
    for k, vv := range src {
        for _, v := range vv {
            dst.Add(k, v)
        }
    }
}
func main() {
    var pemPath string
    flag.StringVar(&pemPath, "pem", "server.pem", "path to pem file")
    var keyPath string
    flag.StringVar(&keyPath, "key", "server.key", "path to key file")
    var proto string
    flag.StringVar(&proto, "proto", "https", "Proxy protocol (http or https)")
    flag.Parse()
    if proto != "http" && proto != "https" {
        log.Fatal("Protocol must be either http or https")
    }
    server := &http.Server{
        Addr: ":2288",
        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if r.Method == http.MethodConnect {
                handleTunneling(w, r)
            } else {
                handleHTTP(w, r)
            }
        }),
        // Disable HTTP/2.
        TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
    }
    if proto == "http" {
        log.Fatal(server.ListenAndServe())
    } else {
        log.Fatal(server.ListenAndServeTLS(pemPath, keyPath))
    }
}
The above Presented code is not a production-grade solution. It lacks e.g. handling hop-by-hop headers, setting up timeouts while copying data between two connections or the ones exposed by net/http — more on this in “The complete guide to Go net/http timeouts”.
Our server while getting request will take one of two paths: handling HTTP or handling HTTP CONNECT tunneling. This is done with:
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if r.Method == http.MethodConnect {
        handleTunneling(w, r)
    } else {
        handleHTTP(w, r)
    }
})
Function to handle HTTP — handleHTTP is self-explanatory so let’s focus on handling tunneling. The first part of handleTunneling is about setting connection to destination server:
dest_conn, err := net.DialTimeout("tcp", r.Host, 10*time.Second)
if err != nil {
    http.Error(w, err.Error(), http.StatusServiceUnavailable)
    return
 }
 w.WriteHeader(http.StatusOK)
Next we’ve a part to hijack connection maintained by HTTP server:
hijacker, ok := w.(http.Hijacker)
    if !ok {
        http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
        return
    }
    client_conn, _, err := hijacker.Hijack()
    if err != nil {
        http.Error(w, err.Error(), http.StatusServiceUnavailable)
    }
Hijacker interface allows to take over the connection. After that the caller is responsible to manage such connection (HTTP library won’t do it anymore).
Once we’ve two TCP connections (client→proxy, proxy→destination server) we need to set tunnel up:
go transfer(dest_conn, client_conn)
go transfer(client_conn, dest_conn)
In two goroutines data is copied in two directions: from the client to the destination server and backward.

Testing

To test our proxy you can use e.g. Chrome in windows:
chrome.exe --proxy-server=https://vps-ip:2288
 
from  https://medium.com/@mlowicki/http-s-proxy-in-golang-in-less-than-100-lines-of-code-6a51c2f2c38c
-----------


我的补充说明


测试可行。登陆linux vps.先安装go环境。然后,
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout server.key -out server.pem
会在当前目录/root下面生成server.key和server.pem文件。
然后新建文件https-proxy.go ,把上面的橘红色代码复制粘贴到https-proxy.go文件中,里面我已把端口改为了2288,
把"path to pem file"和 "path to key file"分别改为 "/root/server.pem"和 "/root/server.key"
然后运行:go build https-proxy.go
此命令会在当前目录下生成可执行文件https-proxy,
./https-proxy &
不过命令:./https-proxy &容易退出运行,我们可利用systemd把该命令运行为service:
nano /etc/systemd/system/https-proxy-in-go.service
内容为:
[Unit]
After=network.target
[Service]
WorkingDirectory=/root/
ExecStart=/root/https-proxy
Restart=always
[Install]
WantedBy=multi-user.target
然后运行:
systemctl start https-proxy-in-go
systemctl enable https-proxy-in-go
服务器端搭建完成。
在本地机器mac上。
运行命令:
open "/applications/vivaldi.app/" --args --proxy-server=https://vps-ip:2288 --ignore-certificate-errors
就会启动基于chrome内核的浏览器vivaldi,然后在浏览器vivaldi里面,即可翻墙。
你可把命令:open "/applications/vivaldi.app/" --args --proxy-server=https://vps-ip:2288 --ignore-certificate-errors
保存为start.sh ,然后chmod 755 start.sh ,以后运行./start.sh ,即可启动浏览器vivaldi,进行翻墙。
更新:
现在由于letsencrypt.org提供了免费证书,所以可以把“绑定的域名:2288"作为代理服务器的地址。具体操作如下:
在linux vps上。
按此文https://briteming.blogspot.com/2018/10/acmeshletsencryptssl.html ,生成你所绑定的域名
的证书文件和私匙文件,然后复制该证书文件和私匙文件到/root/目录,并分别重命名为server.pem和server.key,
(当然要先删除以前用openssl命令生成的server.key和server.pem文件)
在本地机器mac上。安装chrome的switchyomega插件。在switchyomega里面,新建情景模式,代理协议选择https,
'代理服务器'地址栏填写你的域名,代理端口填写2288,点击“应用选项”。选择该情景模式,即可在chrome里面翻墙。
---------------

Go和HTTPS


对HTTPS、数字证书等的基本原理并未求甚解。于是想趁这次的机会,对HTTPS做一些深度挖掘。主要途 径:翻阅网上资料、书籍,并利用golang编写一些实验examples。
一、HTTPS简介
日常生活中,我们上网用的最多的应用层协议就是HTTP协议了,直至目前全世界的网站中大多数依然只支持HTTP访问。
使用Go创建一个HTTP Server十分Easy,十几行代码就能搞定:
//gohttps/1-http/server.go package main
import (     "fmt"     "net/http" )
func handler(w http.ResponseWriter, r *http.Request) {     fmt.Fprintf(w,      "Hi, This is an example of http service in golang!") }
func main() {     http.HandleFunc("/", handler)     http.ListenAndServe(":8080", nil) }
执行这段代码: $ go run server.go
打开浏览器,在地址栏输入"http://localhost:8080", 你会看到“ Hi, This is an example of http service in golang!"输出到浏览器窗口。
不过HTTP毕竟是明文的,在这样一个不安全的世界里,随时存在着窃听(sniffer工具可以简单办到)、篡改甚至是冒充等风险,因此对于一些 对安全比较care的站点或服务,它们需要一种安全的HTTP协议,于是就有了HTTPS。
HTTPS只是我们在浏览器地址栏中看到协议标识,实际上它可以被理解为运行在SSL(Secure Sockets Layer)或TLS(Transport Layer Security)协议所构建的安全层之上的HTTP协议,协议的传输安全性以及内容完整性实际上是由SSL或TLS保证的。
关于HTTPS协议原理的详细说明,没有个百八十页是搞不定的,后续我会在各个实验之前将相关的原理先作一些说明,整体原理这里就不赘述了。有兴 趣的朋友可以参考以下资料: 1、《HTTP权威指南》第十四章 2、《图解HTTP》第七章 3、阮一峰老师的两篇博文“SSL/TLS协议运行机制的概述"和"图解SSL/TLS协议"。
二、实现一个最简单的HTTPS Web Server
Golang的标准库net/http提供了https server的基本实现,我们修改两行代码就能将上面的HTTP Server改为一个HTTPS Web Server:
// gohttps/2-https/server.go package main
import (     "fmt"     "net/http" )
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.ListenAndServeTLS(":8081", "server.crt",                            "server.key", nil) }
我们用http.ListenAndServeTLS替换掉了http.ListenAndServe,就将一个HTTP Server转换为HTTPS Web Server了。不过ListenAndServeTLS 新增了两个参数certFile和keyFile,需要我们传入两个文件路径。到这里,我们不得不再学习一点HTTPS协议的原理了。不过为 了让这个例子能先Run起来,我们先执行下面命令,利用openssl生成server.crt和server.key文件,供程序使用,原 理后续详述:
$openssl genrsa -out server.key 2048
Generating RSA private key, 2048 bit long modulus …………….+++ ……………+++ e is 65537 (0×10001)
$openssl req -new -x509 -key server.key -out server.crt -days 365
You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. —– Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:localhost Email Address []:
执行程序:go run server.go 通过浏览器访问:https://localhost:8081,chrome浏览器会显示如下画面:
忽略继续后,才能看到"Hi, This is an example of https service in golang!"这个结果输出在窗口上。
也可以使用curl工具验证这个HTTPS server:
curl -k https://localhost:8081 Hi, This is an example of http service in golang!
注意如果不加-k,curl会报如下错误:
$curl https://localhost:8081 curl: (60) SSL certificate problem: Invalid certificate chain More details here: http://curl.haxx.se/docs/sslcerts.html
curl performs SSL certificate verification by default, using a "bundle"  of Certificate Authority (CA) public keys (CA certs). If the default  bundle file isn't adequate, you can specify an alternate file  using the –cacert option. If this HTTPS server uses a certificate signed by a CA represented in  the bundle, the certificate verification probably failed due to a  problem with the certificate (it might be expired, or the name might  not match the domain name in the URL). If you'd like to turn off curl's verification of the certificate, use  the -k (or –insecure) option.
三、非对称加密和数字证书
前面说过,HTTPS的数据传输是加密的。实际使用中,HTTPS利用的是对称与非对称加密算法结合的方式。
对称加密,就是通信双方使用一个密钥,该密钥既用于数据加密(发送方),也用于数据解密(接收方)。 非对称加密,使用两个密钥。发送方使用公钥(公开密钥)对数据进行加密,数据接收方使用私钥对数据进行解密。
实际操作中,单纯使用对称加密或单纯使用非对称加密都会存在一些问题,比如对称加密的密钥管理复杂;非对称加密的处理性能低、资源占用高等,因 此HTTPS结合了这两种方式。
HTTPS服务端在连接建立过程(ssl shaking握手协议)中,会将自身的公钥发送给客户端。客户端拿到公钥后,与服务端协商数据传输通道的对称加密密钥-对话密钥,随后的这个协商过程则 是基于非对称加密的(因为这时客户端已经拿到了公钥,而服务端有私钥)。一旦双方协商出对话密钥,则后续的数据通讯就会一直使用基于该对话密 钥的对称加密算法了。
上述过程有一个问题,那就是双方握手过程中,如何保障HTTPS服务端发送给客户端的公钥信息没有被篡改呢?实际应用中,HTTPS并非直接 传输公钥信息,而是使用携带公钥信息的数字证书来保证公钥的安全性和完整性。
数字证书,又称互联网上的"身份证",用于唯一标识一个组织或一个服务器的,这就好比我们日常生活中使用的"居民身份证",用于唯一标识一个 人。服务端将数字证书传输给客户端,客户端如何校验这个证书的真伪呢?我们知道居民身份证是由国家统一制作和颁发的,个人向户 口所在地公安机关申请,国家颁发的身份证才具有法律 效力,任何地方这个身份证都是有效和可被接纳的。大悦城的会员卡也是一种身份标识,但你若用大悦城的会员卡去买机票,对不起, 不卖。航空公司可不认大悦城的会员卡,只认居民身份证。网站的证书也是同样的道理。一般来说数字证书从受信的权威证书授权机构 (Certification Authority,证书授权机构)买来的(免费的很少)。一般浏览器在出厂时就内置了诸多知名CA(如Verisign、GoDaddy、美国国防部、 CNNIC等)的数字证书校验方法,只要是这些CA机构颁发的证书,浏览器都能校验。对于CA未知的证书,浏览器则会报错(就像上面那个截图一 样)。主流浏览器都有证书管理功能,但鉴于这些功能比较高级,一般用户是不用去关心的。
初步原理先讲到这,我们再回到上面的例子。
四、服务端私钥与证书
接上面的例子,我们来说说服务端私钥与证书的生成。
go的http.ListenAndServeTLS需要两个特别参数,一个是服务端的私钥 文件路径,另外一个是服务端的数字证书文件路径。在测试环境,我们没有必要花钱去购买什么证书,利用openssl工具,我们可以自己生成相 关私钥和自签发的数字证书。
openssl genrsa -out server.key 2048 用于生成服务端私钥文件server.key,后面的参数2048单位是bit,是私钥的长度。 openssl生成的私钥中包含了公钥的信息,我们可以根据私钥生成公钥:
$openssl rsa -in server.key -out server.key.public
我们也可以根据私钥直接生成自签发的数字证书:
$openssl req -new -x509 -key server.key -out server.crt -days 365
server.key和server.crt将作为ListenAndServeTLS的两个输入参数。
我们编写一个Go程序来尝试与这个HTTPS server建立连接并通信。
//gohttps/4-https/client1.go package main
import (     "fmt"     "io/ioutil"     "net/http" )
func main() {     resp, err := http.Get("https://localhost:8081")     if err != nil {         fmt.Println("error:", err)         return     }     defer resp.Body.Close()     body, err := ioutil.ReadAll(resp.Body)     fmt.Println(string(body)) }
运行这个client,我们得到如下错误:
$go run client1.go error: Get https://localhost:8081: x509: certificate signed by unknown authority
此时服务端也给出了错误日志提示: 2015/04/30 16:03:31 http: TLS handshake error from 127.0.0.1:62004: remote error: bad certificate
显然从客户端日志来看,go实现的Client端默认也是要对服务端传过来的数字证书进行校验的,但客户端提示:这个证书是由不知名CA签发 的!
我们可以修改一下client1.go的代码,让client端略过对证书的校验:
//gohttps/4-https/client2.go package main
import (     "crypto/tls"     "fmt"     "io/ioutil"     "net/http" )
func main() {     tr := &http.Transport{         TLSClientConfig:    &tls.Config{InsecureSkipVerify: true},     }     client := &http.Client{Transport: tr}     resp, err := client.Get("https://localhost:8081")
    if err != nil {         fmt.Println("error:", err)         return     }     defer resp.Body.Close()     body, err := ioutil.ReadAll(resp.Body)     fmt.Println(string(body)) }
通过设置tls.Config的InsecureSkipVerify为true,client将不再对服务端的证书进行校验。执行后的结果 也证实了这一点: $go run client2.go Hi, This is an example of http service in golang!
五、对服务端的证书进行校验
多数时候,我们需要对服务端的证书进行校验,而不是像上面client2.go那样忽略这个校验。我大脑中的这个产品需要服务端和客户端双向 校验,我们先来看看如何能让client端实现对Server端证书的校验呢?
client端校验证书的原理是什么呢?回想前面我们提到的浏览器内置了知名CA的相关信息,用来校验服务端发送过来的数字证书。那么浏览器 存储的到底是CA的什么信息呢?其实是CA自身的数字证书(包含CA自己的公钥)。而且为了保证CA证书的真实性,浏览器是在出厂时就内置了 这些CA证书的,而不是后期通过通信的方式获取的。CA证书就是用来校验由该CA颁发的数字证书的。
那么如何使用CA证书校验Server证书的呢?这就涉及到数字证书到底是什么了!
我们可以通过浏览器中的"https/ssl证书管理"来查看证书的内容,一般服务器证书都会包含诸如站点的名称和主机名、公钥、签发机构 (CA)名称和来自签发机构的签名等。我们重点关注这个来自签发机构的签名,因为对于证书的校验,就是使用客户端CA证书来验证服务端证书的签名是否这 个CA签的。
通过签名验证我们可以来确认两件事: 1、服务端传来的数字证书是由某个特定CA签发的(如果是self-signed,也无妨),数字证书中的签名类似于日常生活中的签名,首先 验证这个签名签的是Tony Bai,而不是Tom Bai, Tony Blair等。 2、服务端传来的数字证书没有被中途篡改过。这类似于"Tony Bai"有无数种写法,这里验证必须是我自己的那种写法,而不是张三、李四写的"Tony Bai"。
一旦签名验证通过,我们因为信任这个CA,从而信任这个服务端证书。由此也可以看出,CA机构的最大资本就是其信用度。
CA在为客户签发数字证书时是这样在证书上签名的:
数字证书由两部分组成: 1、C:证书相关信息(对象名称+过期时间+证书发布者+证书签名算法….) 2、S:证书的数字签名
其中的数字签名是通过公式S = F(Digest(C))得到的。
Digest为摘要函数,也就是 md5、sha-1或sha256等单向散列算法,用于将无限输入值转换为一个有限长度的“浓缩”输出值。比如我们常用md5值来验证下载的大文件是否完 整。大文件的内容就是一个无限输入。大文件被放在网站上用于下载时,网站会对大文件做一次md5计算,得出一个128bit的值作为大文件的 摘要一同放在网站上。用户在下载文件后,对下载后的文件再进行一次本地的md5计算,用得出的值与网站上的md5值进行比较,如果一致,则大 文件下载完好,否则下载过程大文件内容有损坏或源文件被篡改。
F为签名函数。CA自己的私钥是唯一标识CA签名的,因此CA用于生成数字证书的签名函数一定要以自己的私钥作为一个输入参数。在RSA加密 系统中,发送端的解密函数就是一个以私钥作 为参数的函数,因此常常被用作签名函数使用。签名算法是与证书一并发送给接收 端的,比如apple的一个服务的证书中关于签名算法的描述是“带 RSA 加密的 SHA-256 ( 1.2.840.113549.1.1.11 )”。因此CA用私钥解密函数作为F,对C的摘要进行运算得到了客户数字证书的签名,好比大学毕业证上的校长签名,所有毕业证都是校长签发的。
接收端接收服务端数字证书后,如何验证数字证书上携带的签名是这个CA的签名呢?接收端会运用下面算法对数字证书的签名进行校验: F'(S) ?= Digest(C)
接收端进行两个计算,并将计算结果进行比对: 1、首先通过Digest(C),接收端计算出证书内容(除签名之外)的摘要。 2、数字证书携带的签名是CA通过CA密钥加密摘要后的结果,因此接收端通过一个解密函数F'对S进行“解密”。RSA系统中,接收端使用 CA公钥对S进行“解密”,这恰是CA用私钥对S进行“加密”的逆过程。
将上述两个运算的结果进行比较,如果一致,说明签名的确属于该CA,该证书有效,否则要么证书不是该CA的,要么就是中途被人篡改了。
但对于self-signed(自签发)证书来说,接收端并没有你这个self-CA的数字证书,也就是没有CA公钥,也就没有办法对数字证 书的签名进行验证。因此如果要编写一个可以对self-signed证书进行校验的接收端程序的话,首先我们要做的就是建立一个属于自己的 CA,用该CA签发我们的server端证书,并将该CA自身的数字证书随客户端一并发布。
这让我想起了在《搭建自己的ngrok服务》一文中为ngrok服务端、客户端生成证书的那几个步骤,我们来重温并分析一下每一步都在做什么。
(1)openssl genrsa -out rootCA.key 2048 (2)openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=*.tunnel.tonybai.com" -days 5000 -out rootCA.pem
(3)openssl genrsa -out device.key 2048 (4)openssl req -new -key device.key -subj "/CN=*.tunnel.tonybai.com" -out device.csr (5)openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000
(6)cp rootCA.pem assets/client/tls/ngrokroot.crt (7)cp device.crt assets/server/tls/snakeoil.crt (8)cp device.key assets/server/tls/snakeoil.key
自己搭建ngrok服务,客户端要验证服务端证书,我们需要自己做CA,因此步骤(1)和步骤(2)就是生成CA自己的相关信息。 步骤(1) ,生成CA自己的私钥 rootCA.key 步骤(2),根据CA自己的私钥生成自签发的数字证书,该证书里包含CA自己的公钥。
步骤(3)~(5)是用来生成ngrok服务端的私钥和数字证书(由自CA签发)。 步骤(3),生成ngrok服务端私钥。 步骤(4),生成Certificate Sign Request,CSR,证书签名请求。 步骤(5),自CA用自己的CA私钥对服务端提交的csr进行签名处理,得到服务端的数字证书device.crt。
步骤(6),将自CA的数字证书同客户端一并发布,用于客户端对服务端的数字证书进行校验。 步骤(7)和步骤(8),将服务端的数字证书和私钥同服务端一并发布。
接下来我们来验证一下客户端对服务端数字证书进行验证(gohttps/5-verify-server-cert)!
首先我们来建立我们自己的CA,需要生成一个CA私钥和一个CA的数字证书:
$openssl genrsa -out ca.key 2048 Generating RSA private key, 2048 bit long modulus ……….+++ ………………………….+++ e is 65537 (0×10001)
$openssl req -x509 -new -nodes -key ca.key -subj "/CN=tonybai.com" -days 5000 -out ca.crt
接下来,生成server端的私钥,生成数字证书请求,并用我们的ca私钥签发server的数字证书:
openssl genrsa -out server.key 2048 Generating RSA private key, 2048 bit long modulus ….+++ …………………….+++ e is 65537 (0×10001)
$openssl req -new -key server.key -subj "/CN=localhost" -out server.csr
$openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 5000 Signature ok subject=/CN=localhost Getting CA Private Key
现在我们的工作目录下有如下一些私钥和证书文件: CA:     私钥文件 ca.key     数字证书 ca.crt
Server:     私钥文件 server.key     数字证书 server.crt
接下来,我们就来完成我们的程序。
Server端的程序几乎没有变化:
// gohttps/5-verify-server-cert/server.go package main
import (     "fmt"     "net/http" )
func handler(w http.ResponseWriter, r *http.Request) {     fmt.Fprintf(w,         "Hi, This is an example of http service in golang!") }
func main() {     http.HandleFunc("/", handler)     http.ListenAndServeTLS(":8081",         "server.crt", "server.key", nil) }
client端程序变化较大,由于client端需要验证server端的数字证书,因此client端需要预先加载ca.crt,以用于服务端数字证书的校验:
// gohttps/5-verify-server-cert/client.go package main
import (     "crypto/tls"     "crypto/x509"     "fmt"     "io/ioutil"     "net/http" )
func main() {     pool := x509.NewCertPool()     caCertPath := "ca.crt"
    caCrt, err := ioutil.ReadFile(caCertPath)     if err != nil {         fmt.Println("ReadFile err:", err)         return     }     pool.AppendCertsFromPEM(caCrt)
    tr := &http.Transport{         TLSClientConfig: &tls.Config{RootCAs: pool},     }     client := &http.Client{Transport: tr}     resp, err := client.Get("https://localhost: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:
$go run server.go
go run client.go Hi, This is an example of http service in golang!
六、对客户端的证书进行校验(双向证书校验)
服务端可以要求对客户端的证书进行校验,以更严格识别客户端的身份,限制客户端的访问。
要对客户端数字证书进行校验,首先客户端需要先有自己的证书。我们以上面的例子为基础,生成客户端的私钥与证书。
$openssl genrsa -out client.key 2048 Generating RSA private key, 2048 bit long modulus ………………..+++ ………………..+++ e is 65537 (0×10001) $openssl req -new -key client.key -subj "/CN=tonybai_cn" -out client.csr $openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 5000 Signature ok subject=/CN=tonybai_cn Getting CA Private Key
接下来我们来改造我们的程序,首先是server端。
首先server端需要要求校验client端的数字证书,并且加载用于校验数字证书的ca.crt,因此我们需要对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 := "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("server.crt", "server.key")     if err != nil {         fmt.Println("ListenAndServeTLS err:", err)     } }
可以看出代码通过将tls.Config.ClientAuth赋值为tls.RequireAndVerifyClientCert来实现Server强制校验client端证书。ClientCAs是用来校验客户端证书的ca certificate。
Client端变化也很大,需要加载client.key和client.crt用于server端连接时的证书校验:
// 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 := "ca.crt"
    caCrt, err := ioutil.ReadFile(caCertPath)     if err != nil {         fmt.Println("ReadFile err:", err)         return     }     pool.AppendCertsFromPEM(caCrt)
    cliCrt, err := tls.LoadX509KeyPair("client.crt", "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://localhost:8081")     if err != nil {         fmt.Println("Get error:", err)         return     }     defer resp.Body.Close()     body, err := ioutil.ReadAll(resp.Body)     fmt.Println(string(body)) }
好了,让我们来试着运行一下这两个程序,结果如下:
$go run server.go 2015/04/30 22:13:33 http: TLS handshake error from 127.0.0.1:53542: tls: client's certificate's extended key usage doesn't permit it to be used for client authentication
$go run client.go Get error: Get https://localhost:8081: remote error: handshake failure
失败了!从server端的错误日志来看,似乎是client端的client.crt文件不满足某些条件。
根据server端的错误日志,搜索了Golang的源码,发现错误出自crypto/tls/handshake_server.go。
k := false for _, ku := range certs[0].ExtKeyUsage {     if ku == x509.ExtKeyUsageClientAuth {         ok = true         break     } } if !ok {     c.sendAlert(alertHandshakeFailure)     return nil, errors.New("tls: client's certificate's extended key usage doesn't permit it to be used for client authentication") }
大致判断是证书中的ExtKeyUsage信息应该包含clientAuth。翻看openssl的相关资料,了解到自CA签名的数字证书中包含的都是一些basic的信息,根本没有ExtKeyUsage的信息。我们可以用命令来查看一下当前client.crt的内容:
$ openssl x509 -text -in client.crt -noout Certificate:     Data:         Version: 1 (0×0)         Serial Number:             d6:e3:f6:fa:ae:65:ed:df         Signature Algorithm: sha1WithRSAEncryption         Issuer: CN=tonybai.com         Validity             Not Before: Apr 30 14:11:34 2015 GMT             Not After : Jan  6 14:11:34 2029 GMT         Subject: CN=tonybai_cn         Subject Public Key Info:             Public Key Algorithm: rsaEncryption             RSA Public Key: (2048 bit)                 Modulus (2048 bit):                     00:e4:12:22:50:75:ae:b2:8a:9e:56:d5:f3:7d:31:                     7b:aa:75:5d:3f:90:05:4e:ff:ed:9a:0a:2a:75:15:                     … …                 Exponent: 65537 (0×10001)     Signature Algorithm: sha1WithRSAEncryption         76:3b:31:3e:9d:b0:66:ad:c0:03:d4:19:c6:f2:1a:52:91:d6:         13:31:3a:c5:d5:58:ea:42:1d:b7:33:b8:43:a8:a8:28:91:ac:          … …
而偏偏golang的tls又要校验ExtKeyUsage,如此我们需要重新生成client.crt,并在生成时指定extKeyUsage。经过摸索,可以用如下方法重新生成client.crt:
1、创建文件client.ext 内容: extendedKeyUsage=clientAuth
2、重建client.crt
$openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -extfile client.ext -out client.crt -days 5000 Signature ok subject=/CN=tonybai_cn Getting CA Private Key
再通过命令查看一下新client.crt:
看到输出的文本中多了这么几行:         X509v3 extensions:             X509v3 Extended Key Usage:                 TLS Web Client Authentication
这说明client.crt的extended key usage已经添加成功了。我们再来执行一下server和client:
$ go run client.go Hi, This is an example of http service in golang!
client端证书验证成功,也就是说双向证书验证均ok了。
七、小结
通过上面的例子可以看出,使用golang开发https相关程序十分便利,Golang标准库已经实现了TLS 1.2版本协议。上述所有example代码均放在我的github上的experiments/gohttps中。
------------------------------