前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >tokio_rustls 自签名证书

tokio_rustls 自签名证书

原创
作者头像
谛听
修改2022-01-27 23:35:09
2.3K0
修改2022-01-27 23:35:09
举报
文章被收录于专栏:随意记录随意记录

1 使用自签名证书的目的

本文使用自签名证书的目的:

  • 用于服务端校验客户端是否合法,避免任何一个客户端都可以连上服务端。
  • 基于 TLS,对服务端和客户端之间的传输数据进行加密。

2 自签名证书校验过程

How Client Certificate Authentication Works
How Client Certificate Authentication Works

3 原理

3.1 数字证书

服务端使用自己的域名向 CA(Certificate Authority,证书颁发机构)申请证书。

CA 颁发的证书中含有公钥、证书所有者、有效期、CA 利用自己的私钥生成的签名等信息。

如果客户端想要验证服务端的证书是否合法,可以看能否用 CA 的公钥解开证书上的签名,如果能解开,说明服务端的证书合法。但是怎么确认 CA 的公钥是合法的呢,万一有人冒充 CA 怎么办,这时候可以通过 CA 的 CA 来验证,一直向上追溯,直到追溯到著名的 root CA。

3.2 TLS

TLS(Transport Layer Security,安全传输层),TLS是 建立在传输层 TCP 协议之上的协议,服务于应用层,它的前身是 SSL(Secure Socket Layer,安全套接字层),它实现了将应用层的报文进行加密后再交由 TCP 进行传输的功能。

TLS 传输过程大致分为两阶段:

  • 第一阶段:客户端和服务端使用非对称加密交换信息,用于生成对称加密传输所需的 key。
    • 该过程中,使用私钥对数据加密,使用对方证书中的公钥对数据解密。
  • 第二阶段:使用生成的 key 对通信数据进行加密和解密。

4 自签名证书生成

参考 rustls 给出的示例进行了修改。

  • 自己作为 CA,生成 CA cert,后续该 CA cert 可被服务端和客户端所信任。
  • 利用 CA cert 颁发服务端和客户端的 SSL 证书。服务端和客户端的 SSL 证书生成步骤是一样的,具体如下:
    • 生成私钥。
    • 利用私钥生成证书请求。
    • 利用证书请求和 CA cert 生成证书,CA 会利用自己的私钥生成证书签名。

build-a-pki.sh:

代码语言:txt
复制
#!/bin/sh

set -xe

work_dir=$(cd $(dirname $0); pwd)
dir='dev'

if [ $# -eq 1 ]; then
  while getopts ":r" opt
  do
    case $opt in
        r)
        dir='release'
        ;;
        ?)
        echo "Unknow input"
        exit 1
        ;;
    esac
  done
fi

dir=$work_dir/$dir

rm -rf $dir
mkdir $dir

openssl req -nodes \
          -x509 \
          -days 3650 \
          -newkey rsa:4096 \
          -keyout $dir/ca.key \
          -out $dir/ca.cert \
          -sha256 \
          -batch \
          -subj "/CN=ponytown RSA CA"

openssl req -nodes \
          -newkey rsa:2048 \
          -keyout $dir/server.key \
          -out $dir/server.req \
          -sha256 \
          -batch \
          -subj "/CN=testserver.com"

openssl rsa \
          -in $dir/server.key \
          -out $dir/server.rsa

openssl req -nodes \
          -newkey rsa:2048 \
          -keyout $dir/client.key \
          -out $dir/client.req \
          -sha256 \
          -batch \
          -subj "/CN=ponytown client"

for kt in $dir ; do
  openssl x509 -req \
            -in $kt/server.req \
            -out $kt/server.cert \
            -CA $kt/ca.cert \
            -CAkey $kt/ca.key \
            -sha256 \
            -days 2000 \
            -set_serial 456 \
            -extensions v3_server -extfile openssl.cnf

  openssl x509 -req \
            -in $kt/client.req \
            -out $kt/client.cert \
            -CA $kt/ca.cert \
            -CAkey $kt/ca.key \
            -sha256 \
            -days 2000 \
            -set_serial 789 \
            -extensions v3_client -extfile openssl.cnf

  cat $kt/server.cert $kt/ca.cert > $kt/server.fullchain
done

rm $dir/*.req
rm $dir/ca.key
rm $dir/server.cert $dir/server.key

openssl.cnf:

代码语言:txt
复制
[ v3_server ]
basicConstraints = critical,CA:false
keyUsage = nonRepudiation, digitalSignature
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
subjectAltName = @alt_names

[ v3_client ]
basicConstraints = critical,CA:false
keyUsage = nonRepudiation, digitalSignature
extendedKeyUsage = critical, clientAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always

[ alt_names ]
DNS.1 = testserver.com
DNS.2 = second.testserver.com
DNS.3 = localhost

4 使用自签名证书示例

本文基于 rust 中 tokio_rustls 库实现 TLS。TLS是 建立在 TCP 之上的,服务于应用层,它将应用层的报文进行加密后再交由 TCP 进行传输。

客户端:

  • 加载证书,生成 rustls::ClientConfig
  • 利用 rustls::ClientConfig 生成 rustls::TlsConnector
  • 利用 rustls::TlsConnector 将 tokio::net::TcpStream 转为 tokio_rustls::client::TlsStream,后续可用 TlsStream 收发数据

服务端:

  • 加载证书,生成 rustls::ServerConfig
  • 利用 rustls::ServerConfig 生成 rustls::TlsAcceptor
  • 利用 rustls::TlsAcceptor 将 tokio::net::TcpStream 转为 tokio_rustls::server::TlsStream,后续可用 TlsStream 收发数据
代码语言:txt
复制
use std::convert::TryFrom;
use std::fs::File;
use std::io;
use std::io::BufReader;
use std::sync::Arc;
use std::net::{
    SocketAddr,
    ToSocketAddrs,
};
use rustls::{
    RootCertStore,
    server::{AllowAnyAuthenticatedClient},
};
use tokio::net::{TcpStream};
use tokio_rustls::{
    TlsAcceptor,
    TlsConnector,
    rustls::{self},
    client::TlsStream as ClientTlsStream,
};

pub fn load_certs(filename: &str) -> Vec<rustls::Certificate> {
    let certfile = File::open(filename).expect("cannot open certificate file");
    let mut reader = BufReader::new(certfile);
    rustls_pemfile::certs(&mut reader)
        .unwrap()
        .iter()
        .map(|v| rustls::Certificate(v.clone()))
        .collect()
}

pub fn load_private_key(filename: &str) -> rustls::PrivateKey {
    let keyfile = File::open(filename).expect("cannot open private key file");
    let mut reader = BufReader::new(keyfile);

    loop {
        match rustls_pemfile::read_one(&mut reader).expect("cannot parse private key .pem file") {
            Some(rustls_pemfile::Item::RSAKey(key)) => return rustls::PrivateKey(key),
            Some(rustls_pemfile::Item::PKCS8Key(key)) => return rustls::PrivateKey(key),
            None => break,
            _ => {}
        }
    }

    panic!(
        "no keys found in {:?} (encrypted keys not supported)",
        filename
    );
}

pub fn lookup_ipv4(host: &str, port: u16) -> SocketAddr {
    let addrs = (host, port).to_socket_addrs().unwrap();
    for addr in addrs {
        if let SocketAddr::V4(_) = addr {
            return addr;
        }
    }

    unreachable!("Cannot lookup address");
}

fn make_client_config(ca_file: &str, certs_file: &str, key_file: &str) -> Arc<rustls::ClientConfig> {
    let cert_file = File::open(&ca_file).expect("Cannot open CA file");
    let mut reader = BufReader::new(cert_file);

    let mut root_store = RootCertStore::empty();
    root_store.add_parsable_certificates(&rustls_pemfile::certs(&mut reader).unwrap());

    let suites = rustls::DEFAULT_CIPHER_SUITES.to_vec();
    let versions = rustls::DEFAULT_VERSIONS.to_vec();

    let certs = load_certs(certs_file);
    let key = load_private_key(key_file);

    let config = rustls::ClientConfig::builder()
        .with_cipher_suites(&suites)
        .with_safe_default_kx_groups()
        .with_protocol_versions(&versions)
        .expect("inconsistent cipher-suite/versions selected")
        .with_root_certificates(root_store)
        .with_single_cert(certs, key)
        .expect("invalid client auth certs/key");
    Arc::new(config)
}

fn make_server_config(certs: &str, key_file: &str) -> Arc<rustls::ServerConfig> {
    let roots = load_certs(certs);
    let certs = roots.clone();
    let mut client_auth_roots = RootCertStore::empty();
    for root in roots {
        client_auth_roots.add(&root).unwrap();
    }
    let client_auth = AllowAnyAuthenticatedClient::new(client_auth_roots);

    let privkey = load_private_key(key_file);
    let suites = rustls::ALL_CIPHER_SUITES.to_vec();
    let versions = rustls::ALL_VERSIONS.to_vec();

    let mut config = rustls::ServerConfig::builder()
        .with_cipher_suites(&suites)
        .with_safe_default_kx_groups()
        .with_protocol_versions(&versions)
        .expect("inconsistent cipher-suites/versions specified")
        .with_client_cert_verifier(client_auth)
        .with_single_cert_with_ocsp_and_sct(certs, privkey, vec![], vec![])
        .expect("bad certificates/private key");

    config.key_log = Arc::new(rustls::KeyLogFile::new());
    config.session_storage = rustls::server::ServerSessionMemoryCache::new(256);
    Arc::new(config)
}

pub async fn new_tls_stream(domain: &str, addr: std::net::SocketAddr, 
                            ca_file: &str, cert_file: &str, key_file: &str) -> ClientTlsStream<TcpStream> {
    let config = make_client_config(&ca_file, &cert_file, &key_file);

    let connector = TlsConnector::from(config);

    let stream = TcpStream::connect(&addr).await.unwrap();
    let domain = rustls::ServerName::try_from(domain)
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid dnsname")).unwrap();
    let stream = connector.connect(domain, stream).await.unwrap();
    stream
}

pub fn new_tls_acceptor(cert_file: &str, key_file: &str) -> TlsAcceptor {
    let config = make_server_config(&cert_file, &key_file);
    let acceptor = TlsAcceptor::from(config);
    acceptor
}

#[cfg(test)]
mod tests {
    use super::*;
    use tokio::net::TcpListener;
    use tokio::io::{AsyncWriteExt, AsyncReadExt};

    const CA_FILE: &str = "cert/dev/ca.cert";
    const CLIENT_CERT_FILE: &str = "cert/dev/client.cert";
    const CLIENT_KEY_FILE: &str = "cert/dev/client.key";
    const SERVER_CERT_FILE: &str = "cert/dev/server.fullchain";
    const SERVER_KEY_FILE: &str = "cert/dev/server.rsa";

    #[tokio::test]
    async fn tls() {
        let msg = b"Hello world\n";
        let mut buf = [0; 12];

        start_server().await;

        start_client(msg, &mut buf).await;
        assert_eq!(&buf, msg);
    }

    async fn start_server() {
        let tls_acceptor = new_tls_acceptor(SERVER_CERT_FILE, SERVER_KEY_FILE);
        let listener = TcpListener::bind("0.0.0.0:5002").await.unwrap();

        tokio::spawn(async move {
            let (stream, _peer_addr) = listener.accept().await.unwrap();
            let mut tls_stream = tls_acceptor.accept(stream).await.unwrap();
            println!("server: Accepted client conn with TLS");

            let mut buf = [0; 12];
            tls_stream.read(&mut buf).await.unwrap();
            println!("server: got data: {:?}", buf);
            tls_stream.write(&buf).await.unwrap();
            println!("server: flush the data out");
        });
    }

    async fn start_client(msg: &[u8], buf: &mut [u8]) {
        let addr = lookup_ipv4("127.0.0.1", 5002);
        let mut tls_stream =
            new_tls_stream("localhost", addr, CA_FILE, CLIENT_CERT_FILE, CLIENT_KEY_FILE).await;

        tls_stream.write(msg).await.unwrap();
        println!("client: send data");

        tls_stream.read(buf).await.unwrap();
        println!("client: read echoed data");
    }
}

参考

刘超. HTTPS协议:点外卖的过程原来这么复杂.

https://www.jscape.com/blog/client-certificate-authentication

https://www.makethenmakeinstall.com/2014/05/ssl-client-authentication-step-by-step/

https://www.jianshu.com/p/1fc7130eb2c2

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 使用自签名证书的目的
  • 2 自签名证书校验过程
  • 3 原理
    • 3.1 数字证书
      • 3.2 TLS
      • 4 自签名证书生成
      • 4 使用自签名证书示例
      • 参考
      相关产品与服务
      SSL 证书
      腾讯云 SSL 证书(SSL Certificates)为您提供 SSL 证书的申请、管理、部署等服务,为您提供一站式 HTTPS 解决方案。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
      http://www.vxiaotou.com