iOS12 Network框架 自签名证书认证
概述
详细
需求:
并不是每个SSL/TLS站点都能得到一个全球公认的证书,很多时候需要自行生成自签名证书做为根证书。有了自签名根证书还需要手动地用它去验证服务端证书。
概要:
1.生成自签名概证书,服务端证书
2.做一个使用服务端证书的SSL/TLS的服务
3.做一个使用自签名证书访问服务的客户端
结构:
效果:
初始界面 发送接收后
我们开始吧!
-
证书生成:
先构建目录:
1.mkdir certs
2.cd certs
3.unzip store.zip
操作之后,目录如下:
生成自签名根证书,在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
执行完成在当前目录下产生以下文件:
其中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
执行完成后当前目录如下所示:
其中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,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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拖到工程下面:
注意:添加ca.der时,一定要选上Add to targets选项。
Main.storyboard里添加一个Label和一个Button即可,我们毕竟只是演示tls如何工作,没必要搞那么花哨。
在ViewController里添加上如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 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添加一个验证回调块来达成这个需求。原型如下:
1 2 3 4 5 | 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以继续后续的握手操作。
连接建立之后就可以自由的收发消息了。
最后项目结构介绍:
源码目录如下:
其中server.go是服务端的代码, learn.zip是客户端的代码 store.zip是生成证书的时一些配置文件。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
2018-03-06 SearchView的全面解析
2018-03-06 网页聊天软件
2018-03-06 android实现免费短信验证
2018-03-06 使用 JavaScript开发的跨平台音乐、书籍播放器
2018-03-06 Bmob实现android云端存储
2018-03-06 基于JWT的Token开发案例
2018-03-06 Vue-cli + Express 构建的SPA Blog(前后分离)