zoukankan      html  css  js  c++  java
  • iOS12 Network框架 自签名证书认证

    发布时间:2018-09-21
     
    技术:iOS12 xcode10 golang1.11
     

    概述

    iOS12 苹果发布了新的网络框架Network,可以更方便地操作底层网络通信了。使用TLS也很方便,但默认是使用系统安装的根证书验证网站证书的,如果使用自签名根证书来验证自架的网站证书,则麻烦一些,这里给大家演示一下。

    详细

    需求:

    并不是每个SSL/TLS站点都能得到一个全球公认的证书,很多时候需要自行生成自签名证书做为根证书。有了自签名根证书还需要手动地用它去验证服务端证书。

    概要:

    1.生成自签名概证书,服务端证书

    2.做一个使用服务端证书的SSL/TLS的服务

    3.做一个使用自签名证书访问服务的客户端

    结构:

    结构图.jpg

    效果:

    before.png after.png

    初始界面 发送接收后

    我们开始吧!

    • 证书生成:

    先构建目录:

    1.mkdir certs

    2.cd certs

    3.unzip store.zip

    操作之后,目录如下:

    image.png 

    生成自签名根证书,在centos上依次执行以下命令:

    1.私钥:openssl genrsa -out ca.key 1024

    2.公钥:openssl rsa -in ca.key -pubout -out ca.pem

    3.证书:openssl req -new -x509 -days 365 -key ca.key -out ca.crt

    执行完成在当前目录下产生以下文件:

    image.png

    其中ca.pem就是生成的自签名根证书。

    生成服务端证书,在centos上依次执行如下命令:

    1.私钥:openssl genrsa -out server.key 1024

    2.公钥:openssl rsa -in server.key -pubout -out server.pem

    3.请求:openssl req -new -nodes -key server.key -out server.csr

    4.签证:openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key -config store/openssl.cnf

    执行完成后当前目录如下所示:

    image.png

    其中server.pem,server.key分别是服务端的证书和私钥。因为iOS需der格式的证书,我们把根证书ca.pem转换一下。

    5.转换:openssl x509 -outform der -in ca.crt -out ca.der

    • 服务端程序:

    好了,所需证书都已生成。其中服务端需要server.crt, server.key,需客户端需要ca.der。下面我们先做一个非常简单的服务端,用来配合客户端的连接测试。在centos上创建文件server.go,内容如下:

    package main
    
    import (
    	"log"
    	"io"
    	"net"
    	"crypto/tls"
    )
    
    func main() {
    	crt, err := tls.LoadX509KeyPair("server.crt", "server.key")
    	if err != nil {
    		log.Fatal(err)
    	}
    	conf := &tls.Config{Certificates: []tls.Certificate{crt}}
    	listener, err := tls.Listen("tcp", ":8080", conf)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer listener.Close()
    	for {
    		conn, err := listener.Accept()
    		if err != nil {
    			log.Fatal(err)
    		}
    		process(conn)
    		conn.Close()
    	}
    }
    
    func process(conn net.Conn) {
    	rdata := make([]byte, 2048)
    	rlen, err := conn.Read(rdata)
    	if err != nil && err != io.EOF {
    		log.Println(err)
    		return
    	}
    	_, err = conn.Write(rdata[:rlen])
    	if err != nil {
    		log.Println(err)
    		return
    	}
    }

    程序很简单,创建支持tls的服务程序,接收到发送过来的内容,再原样返回出去。

    编译:go build server.go

    注意:server.crt, server.key与编译出来的server放在同一目录下。然后,执行程序,等待连接到来。

    执行:./server

    • 客户端程序:

    创建一个iOS工程, 然后把ca.der拖到工程下面:

    image.pngimage.png

    注意:添加ca.der时,一定要选上Add to targets选项。

    image.png

    Main.storyboard里添加一个Label和一个Button即可,我们毕竟只是演示tls如何工作,没必要搞那么花哨。

    image.png

    在ViewController里添加上如下代码:

    import UIKit
    import Network
    
    class ViewController: UIViewController {
        @IBOutlet weak var messageLabel: UILabel!
        
        let queue = DispatchQueue(label: "myqueue")
        var conn: NWConnection!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            messageLabel.layer.borderWidth = 1
        }
        
        @IBAction func start(_ sender: Any) {
            let host = NWEndpoint.Host("10.21.16.202")
            let port = NWEndpoint.Port(integerLiteral: 8080)
            
            let options = NWProtocolTLS.Options()
            sec_protocol_options_set_verify_block(options.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in
                
                // 为信任证书链设置自签名根证书
                let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue()
                if let url = Bundle.main.url(forResource: "ca", withExtension: "der"),
                    let data = try? Data(contentsOf: url),
                    let cert = SecCertificateCreateWithData(nil, data as CFData) {
                    if SecTrustSetAnchorCertificates(trust, [cert] as CFArray) != errSecSuccess {
                        sec_protocol_verify_complete(false)
                        return
                    }
                }
                
                // 设置验证策略
                let policy = SecPolicyCreateSSL(true, "myserver" as CFString)
                SecTrustSetPolicies(trust, policy)
                SecTrustSetAnchorCertificatesOnly(trust, true)
                
                // 验证证书链
                var error: CFError?
                if SecTrustEvaluateWithError(trust, &error) {
                    sec_protocol_verify_complete(true)
                    
                } else {
                    sec_protocol_verify_complete(false)
                    print(error!)
                }
            }, queue)
            
            conn = NWConnection(host: host, port: port, using: NWParameters(tls: options))
            conn.start(queue: queue)
            
            let messge = "hello"
            conn.send(content: messge.data(using: .utf8)!, completion: .contentProcessed({ (error) in
                if let error = error {
                    print(error)
                    self.conn.cancel()
                } else {
                    print("消息已发送:(messge)")
                }
            }))
            
            conn.receive(minimumIncompleteLength: 1, maximumLength: 1024) { (data, context, isComplete, error) in
                if let error = error {
                    print(error)
                    self.conn.cancel()
                    return
                }
                
                if let data = data {
                    DispatchQueue.main.async {
                        self.messageLabel.text = String(data: data, encoding: .utf8)!
                    }
                    
                    print("消息已收到:(String(data: data, encoding: .utf8)!)")
                }
                
                if isComplete {
                    self.conn.cancel()
                    self.conn = nil
                }
            }
        }
    }

    NWConnection需要一个NWParameters类型的选项,当NWConnection建立连接以及收发数据的时候会使用这些选项调整连接的行为。系统默认一个选项是NWParameters.tls,然后这个选项在tls连接建立时验证服务端证书的时候使用的是iOS系统里预置的要证书,这并不满足我们的需求。

    我们必须找到一个地方能定制化双方握手时的证书验证形为。我们可以通过配置NWProtocolTLS.Options.SecurityProtocolOptions添加一个验证回调块来达成这个需求。原型如下:

    typedef void (^sec_protocol_verify_t)(sec_protocol_metadata_t metadata, sec_trust_t trust_ref, sec_protocol_verify_complete_t complete);
    
    API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0))
    void
    sec_protocol_options_set_verify_block(sec_protocol_options_t options, sec_protocol_verify_t verify_block, dispatch_queue_t verify_block_queue);

    回调块的参数metadata,可以从中遍历出对端的证书列表。参数trust_ref,可以从中遍历出信任证书列表(对端的证书列表和对端证书链对应的根证书),当然我们自签名根证书不在iOS系统中,系统不会自动为我们添加上,需要我们手动添加。

    通过SecCertificateCreateWithData()我们从ca.der生成要证书对象,然后通过SecTrustSetAnchorCeritificates()把它添加信任证书链表中。接着我们通过SecPolicyCreateSSL()生成一个验证策略,其中"myserver"是服务端证书对应的名字,可以查看服务端证书得到,这里也即限制服务端证书的CN必须为myserver,否则验证失败。SecTrustSetPolicies()为信任证书链添加验证策略,SecTrunstSetAnchorCeritificatesOnly()只信任我们自已添加的根证书来验证服务端证书。

    SecTrustEvaluateWithError()来最终验证服务端证书,如若有错,通过打印error知道具体的错误原因。验证的成功否是失败都要通过参数complete回调来告知NWConnection以继续后续的握手操作。

    连接建立之后就可以自由的收发消息了。

    最后项目结构介绍:

    源码目录如下:

    image.png

    其中server.go是服务端的代码, learn.zip是客户端的代码 store.zip是生成证书的时一些配置文件。

    注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权

  • 相关阅读:
    团队项目冲刺第6天
    冲刺阶段第五天
    冲刺阶段前四天总结
    "博客园"用户体验分析
    测试计划
    scrum敏捷开发
    团队开发_软件项目风险管理
    sprint计划会议
    svn 之 svn的两种开发模式
    redis 之 搭建真实集群
  • 原文地址:https://www.cnblogs.com/demodashi/p/10486864.html
Copyright © 2011-2022 走看看