12. 用Rust手把手編寫一個wmproxy(代理,內網穿透等), TLS的雙向認證信息及token驗證

来源:https://www.cnblogs.com/luojiawaf/archive/2023/10/14/17763661.html
-Advertisement-
Play Games

TLS雙向認證的基本原理及示意流程圖,幫助更好的理解TLS的加密功能,及安全能力,此外還給出了部分源碼的實現及Token實現在的方案及能力 ...


12. 用Rust手把手編寫一個wmproxy(代理,內網穿透等), TLS的雙向認證信息及token驗證

項目 ++wmproxy++

gite: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

什麼是TLS雙向認證

TLS雙向認證是指客戶端和伺服器端都需要驗證對方的身份,也稱mTLS

在建立Https連接的過程中,握手的流程比單向認證多了幾步。

  • 單向認證的過程,客戶端從伺服器端下載伺服器端公鑰證書進行驗證,然後建立安全通信通道。
  • 雙向通信流程,客戶端除了需要從伺服器端下載伺服器的公鑰證書進行驗證外,還需要把客戶端的公鑰證書上傳到伺服器端給伺服器端進行驗證,等雙方都認證通過了,才開始建立安全通信通道進行數據傳輸。

TLS是安全套接層(SSL)的繼任者,叫傳輸層安全(transport layer security)。說直白點,就是在明文的上層和TCP層之間加上一層加密,這樣就保證上層信息傳輸的安全,然後解密完後又以原樣的數據回傳給應用層,做到與應用層無關,所以http加個s就成了https,ws加個s就成了wss,ftp加個s就成了ftps,都是從普通tcp傳輸轉換成tls傳輸實現安全加密,應用相當廣泛。

單向與雙向的差別

SSL單向驗證

單向通訊的示意圖如下

sequenceDiagram Client->>Server: Client Hello Client->>Server: 包含SSL/TLS版本,對稱加密演算法列表,隨機數A Server-->>Client: Server Hello,服務端先進行選擇 Server-->>Client: 雙方都支持的SSL/TLS協議版本,對稱加密演算法 Server-->>Client: 公鑰證書,服務端生成的隨機數B Server-->>Client: Change Cipher Spec,收到這消息後開始密文傳輸 Client-)Client: 驗證證書,是否過期,是否被吊銷,是否可信,功能變數名稱是否一致 Client->>Server: Change Cipher Spec Client->>Server: 應用數據(客戶端加密) Server-->>Client: 應用數據(服務端加密)

雙向通訊的示意圖如下,差別

sequenceDiagram Client->>Server: Client Hello Server-->>Client: Server Hello rect rgba(0, 0, 255, 0.5) Server-->>Client: 額外要求客戶端提供客戶端證書 end Client-)Client: 驗證證書 rect rgba(0, 0, 255, 0.5) Client-->>Server: 客戶端證書 Client-->>Server: 客戶端證書驗證信息(CertificateVerify message) Server->Server: 驗證客戶端證書是否有效 Server->Server: 驗證客戶端證書驗證消息的簽名是否有效 end Server-->>Client: 握手結束 Client->>Server: 握手結束

備註:客戶端將之前所有收到的和發送的消息組合起來,並用hash演算法得到一個hash值,然後用客戶端密鑰庫的私鑰對這個hash進行簽名,這個簽名就是CertificateVerify message;

代碼實現

將原來的rustls中的TlsAcceptor和TlsConnector進行相應的改造,變成可支持雙向認證的加密結構。

獲取TlsAcceptor的認證

/// 獲取服務端https的證書信息
pub async fn get_tls_accept(&mut self) -> ProxyResult<TlsAcceptor> {
    if !self.tc {
        return Err(ProxyError::ProtNoSupport);
    }
    let certs = Self::load_certs(&self.cert)?;
    let key = Self::load_keys(&self.key)?;

    let config = rustls::ServerConfig::builder().with_safe_defaults();

    // 開始雙向認證,需要客戶端提供證書信息
    let config = if self.two_way_tls {
        let mut client_auth_roots = rustls::RootCertStore::empty();
        for root in &certs {
            client_auth_roots.add(&root).unwrap();
        }
        let client_auth = rustls::server::AllowAnyAuthenticatedClient::new(client_auth_roots);

        config
            .with_client_cert_verifier(client_auth.boxed())
            .with_single_cert(certs, key)
            .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?
    } else {
        config
            .with_no_client_auth()
            .with_single_cert(certs, key)
            .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?
    };

    let acceptor = TlsAcceptor::from(Arc::new(config));
    Ok(acceptor)
}

獲取TlsAcceptor的認證

/// 獲取客戶端https的Config配置
pub async fn get_tls_request(&mut self) -> ProxyResult<Arc<rustls::ClientConfig>> {
    if !self.ts {
        return Err(ProxyError::ProtNoSupport);
    }
    let certs = Self::load_certs(&self.cert)?;
    let mut root_cert_store = rustls::RootCertStore::empty();
    // 信任通用的簽名商
    root_cert_store.add_trust_anchors(
        webpki_roots::TLS_SERVER_ROOTS
            .iter()
            .map(|ta| {
                rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
                    ta.subject,
                    ta.spki,
                    ta.name_constraints,
                )
            }),
    );
    for cert in &certs {
        let _ = root_cert_store.add(cert);
    }
    let config = rustls::ClientConfig::builder()
        .with_safe_defaults()
        .with_root_certificates(root_cert_store);

    if self.two_way_tls {
        let key = Self::load_keys(&self.key)?;
        Ok(Arc::new(config.with_client_auth_cert(certs, key).map_err(
            |err| io::Error::new(io::ErrorKind::InvalidInput, err),
        )?))
    } else {
        Ok(Arc::new(config.with_no_client_auth()))
    }
}

這裡預設信任的通用的CA簽發證書平臺,像系統證書,瀏覽器信任的證書,只有第一步把基礎的被信任才有資格做簽發證書平臺。

至此雙向TLS的能力已經達成,感謝前人的經典代碼才能如此輕鬆。

token驗證

首先先定義協議的Token結構,只有sock_map為0接收此消息

/// 進行身份的認證
#[derive(Debug)]
pub struct ProtToken {
    username: String,
    password: String,
}

下麵是編碼解碼,密碼要求不超過255個字元,即長度為1位元組編碼

pub fn parse<T: Buf>(_header: ProtFrameHeader, mut buf: T) -> ProxyResult<ProtToken> {
    let username = read_short_string(&mut buf)?;
    let password = read_short_string(&mut buf)?;
    Ok(Self { username, password })
}

pub fn encode<B: Buf + BufMut>(self, buf: &mut B) -> ProxyResult<usize> {
    let mut head = ProtFrameHeader::new(ProtKind::Token, ProtFlag::zero(), 0);
    head.length = self.username.as_bytes().len() as u32 + 1 + self.password.as_bytes().len() as u32 + 1;
    let mut size = 0;
    size += head.encode(buf)?;
    size += write_short_string(buf, &self.username)?;
    size += write_short_string(buf, &self.password)?;
    Ok(size)
}

服務端處理

如果服務端啟動的時候配置了usernamepassword則表示他需要密碼驗證,

let mut verify_succ = option.username.is_none() && option.password.is_none();

如果verify_succ不為true,那麼我們接下來的第一條消息必須為ProtToken,否則客戶端不合法,關閉
收到該消息則進行驗證

match &p {
    ProtFrame::Token(p) => {
        if !verify_succ
            && p.is_check_succ(&option.username, &option.password)
        {
            verify_succ = true;
            continue;
        }
    }
    _ => {}
}
if !verify_succ {
    ProtFrame::new_close_reason(0, "not verify so close".to_string())
        .encode(&mut write_buf)?;
    is_ready_shutdown = true;
    break;
}

認證通過後消息處理和之前的一樣,驗證流程完成


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文是Dart語言學習的第12天,和前面11天相比,本文可能相對比較簡單,因為本文要學習的是Dart語言的控制流,也就是迴圈和分支。且前面的11天學習中,多多少少都涉及到了控制流,同時對Java或者JavaScript比較熟悉的朋友,可能比較容易上手…… ...
  • 通過攔截器,我們可以針對特定 URI 做攔截,做相關業務處理,比如檢查用戶是否登錄,列印每個請求的處理耗時等。 一、新建一個攔截器 新建登錄驗證類 LoginValidationInterceptor.java: package site.exception.springbootintercepto ...
  • for遍歷 一:常規方式 1.遍曆數組 int arr[10] = {1,2,3,4,5,6,7,8,9,10}; for(int i = 0;i<10;i++) { cout<<arr[i]; } 3.遍歷容器類(迭代器) vector<int> s = {0,1,2,3,4,5,6,7,8,9} ...
  • 基本數據類型 標準數據類型 常見數據類型: Number(數字) String(字元串) bool(布爾類型) List(列表) Tuple(元組) Set(集合) Dictionary(字典) 六個標準數據類型中: 不可變數據(3 個):Number(數字)、String(字元串)、Tuple(元 ...
  • 本文介紹在Anaconda中,為Python的虛擬環境安裝第三方庫與Spyder等配套軟體的方法。 在文章創建Anaconda虛擬Python環境的方法中,我們介紹了在Anaconda環境下,創建、使用與刪除Python虛擬環境的方法;而創建虛擬環境後,就需要在對應的環境內配置各類庫與軟體,本文就對 ...
  • 代碼地址: https://gitee.com/Aes_yt/middleware-demo/tree/master/rabbitmq 安裝RabbitMq 1. docker拉取鏡像 docker pull rabbitmq:3.9.29-management 2. 創建rabbitmq容器 do ...
  • 自從開始搞YouTube中文配音以來,我們一直是7*24小時,夜以繼日的在批量處理一些優質的學習資源,一方面是翻譯,另一方面是配音。這樣用戶在打開的時候,就能獲得經過我們優化的翻譯和配音了。 這次我們剛剛處理完一個油管上非常火爆的​IT類學習頻道:Edureka。 該頻道內全是IT行業的免費學習視頻 ...
  • math庫常用函數+產生隨機數總結 1.對x開平方 double sqrt(x);//返回值為double類型,輸入的x類型隨意,只要是數的類型 2.求常數e的x次方 double exp(x);//返回值為double類型,輸入的x類型隨意,只要是數的類型 3.求x的y次方 double pow( ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...