iOS12 Network框架 自簽名證書認證


概述

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大師發表,拒絕轉載,轉載需要作者授權


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM