SSL证书生成和使用

什么是SSL证书?

SSL是一种数字证书,用于加密和验证在互联网上进行的数据传输。它遵守SSL协议,由受信任的数字证书颁发机构(CA)在验证服务器身份后颁发。SSL证书具有服务器身份验证和数据传输加密功能,通过在客户端浏览器和Web服务器之间建立一条SSL安全通道,实现数据信息在客户端和服务器之间的加密传输。这种加密可以防止数据信息的泄露,保证了双方传递信息的安全性。 `通俗易懂的话就是这个证书可以保证我们能够使用https方式访问web页面,更加安全,有保障性`

TOC

生成SSL证书

我们需要使用到openssl命令工具来创建自签名证书。
openssl命令参数详解:
–days【时间/天】:指定证书的有效时间
-subj 【证书信息】:指定证书的一些信息

CA证书

用自己的私钥和证书申请文件生成自己签名的证书,俗称自签名证书,这里可以理解为根证书。

方法一(直接生成):

直接生成ca的私钥和公钥

openssl req -newkey rsa:2048 -x509 -nodes -keyout ca.key -out ca.crt -days 3650 -subj "/C=CN/O=People's Republic of China/CN=China CA"

方法二(分开生成):

分开生成ca的私钥和公钥

# 生成ca私钥
openssl genrsa -out ca.key 2048
# 生成ca公钥
openssl req -x509 -new -key ca.key -out ca.crt -days 3650 -subj "/C=CN/O=People's Republic of China/CN=China CA"

方法三(通过申请文件):

先生成ca私钥,通过私钥生成证书申请文件,再通过申请文件生成ca的公钥

# 生成ca私钥
openssl genrsa -out ca.key 2048
# 通过ca私钥生成证书申请文件
openssl req -new -out ca.csr -key ca.key -subj "/C=CN/O=People's Republic of China/CN=China CA"
# 生成ca公钥
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt -days 3650 -extfile /etc/ssl/openssl.cnf -extensions v3_ca

-extfile /etc/ssl/openssl.cnf-extensions v3_ca这两个参数很重要,生成x509 v3的根证书,推荐使用!!!

  • /C=CN:国家/地区代码为 “CN”,表示中国。
  • /O=People’s Republic of China:组织名称为"中华人民共和国"。
  • CN=China CA:通用名称(Common Name)为"China CA",表示这是一个中国认证机构(Certificate Authority)的根证书。

server端证书

方法一(无需CA签发):

# 生成server端私钥
openssl genrsa -out server.key 2048
# 生成server端证书申请文件
openssl req -new -key server.key -out server.csr -subj "/O=MyDomain/OU=MyDomain/CN=www.example.com"
# 有的浏览器证书比较严格,就需要添加额外配置
echo "subjectAltName=DNS:example.com,DNS:www.example.com" > cert_extensions
# 生成server端的自签名证书,无需CA签发
openssl x509 -req -in server.csr -signkey server.key -out server.crt -days 3650 -extfile cert_extension

方法二(使用CA签发):

# 生成server端私钥
openssl genrsa -out server.key 2048
# 生成server端证书申请文件
openssl req -new -key server.key -out server.csr -subj "/O=MyDomain/OU=MyDomain/CN=www.example.com"
# 有的浏览器证书比较严格,就需要添加额外配置
echo "subjectAltName=DNS:example.com,DNS:www.example.com" > cert_extensions
# 使用ca自签名生成server端公钥
openssl x509 -req -CA ca.crt -CAkey ca.key -in server.csr -out server.crt -CAcreateserial -days 3650 -extfile cert_extensions

补充:如果没有创建cert_extensions文件,直接在命令行指定subjectAltName

openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -in server.csr -out server.crt -extfile <(printf "subjectAltName=DNS:example.com,DNS:www.example.com")

client端证书

命令行所需格式

client端的证书需要用CA签发才能正常使用

# 生成client端私钥
openssl genrsa -out client.key 2048
# 生成client端证书申请文件
openssl req -new -key client.key -out client.csr -subj "/O=MyDomain/OU=MyDomain/CN=www.example.com"
# 有的浏览器证书比较严格,就需要添加额外配置
echo "subjectAltName=DNS:example.com,DNS:www.example.com" > cert_extensions
# 使用ca自签名生成client端公钥
openssl x509 -req -CA ca.crt -CAkey ca.key -in client.csr -out client.crt -CAcreateserial -days 3650 -extfile cert_extensions
  • 组织 (O) = “MyDomain”
  • 组织单位 (OU) = “MyDomain”
  • 通用名称 (CN) = “\www.example.com

pc浏览器所需格式

使用以下命令即可生成pc浏览器所需格式的文件:

openssl pkcs12 -export -clcerts -out client.p12 -in client.crt -inkey client.key

搭建nginx对证书的使用

这里我们使用docker进行nginx的部署验证证书。

1.整理需要的证书文件

创建一个目录用来放需要的证书:

mkdir certs
# 查看ssl证书
root@nginx:~# ll certs
-rw-r--r--  1 root  root   1.2K  6 20 11:44 ca.crt
-rw-r--r--  1 root  root   1.2K  6 20 11:55 client.crt
-rw-------  1 root  root   1.7K  6 20 11:53 client.key
-rw-r--r--  1 root  root   1.2K  6 20 11:52 server.crt
-rw-------  1 root  root   1.7K  6 20 11:47 server.key

2.编写nginx配置文件

nginx主配置文件:

# nginx.conf
user root;
worker_processes 1;
worker_rlimit_nofile 1024000;

error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

events {
    worker_connections 1024000;
    use epoll;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    server_tokens off;
    server_names_hash_bucket_size 128;
    server_names_hash_max_size 512;
    keepalive_timeout 300;

    large_client_header_buffers 4 32k;
    client_header_buffer_size 32k;
    client_header_timeout 15s;
    client_body_timeout 15s;
    send_timeout 180s;

    open_file_cache max=102400 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 1;

    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 32k;
    gzip_http_version 1.1;
    gzip_comp_level 9;
    gzip_types text/css text/xml application/javascript;
    gzip_vary on;

    include /etc/nginx/conf.d/*.conf;
}

nginx子配置文件:

# https.conf
server {
    listen 80;
    server_name www.example.com;
    return 301 https://$server_name$request_uri;
    root /usr/share/nginx/html;
    index index.html index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
}
server {
    listen 443 ssl;
    server_name www.example.com;
    ssl_certificate /certs/server.crt;
    ssl_certificate_key /certs/server.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    root /usr/share/nginx/html;
    index index.html index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
}

3.编写docker compose文件

# docker-compose.yaml
version: "3"

services:
  nginx:
    image: nginx:latest
    container_name: nginx
    restart: unless-stopped
    privileged: true
    expose:
      - 80
      - 443
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./https.conf:/etc/nginx/conf.d/https.conf
      - ./certs:/certs

启动服务并查看:

docker compose up -d
root@nginx:~# docker compose ps
NAME      IMAGE          COMMAND                   SERVICE   CREATED         STATUS         PORTS
nginx     nginx:latest   "/docker-entrypoint.…"   nginx     3 seconds ago   Up 2 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp

测试阶段

首先我们需要配置hosts文件,我使用的是mac电脑,直接修改/etc/hosts文件即可

vim /etc/hosts
--------------------------
127.0.0.1 www.example.com

浏览器使用访问https://www.example.com网页即可,如下图所示:
certs_ssl.001
我们可以点击左上角查看证书详情,如下图所示:
certs_ssl.002
我们可以看到证书的有效期是10年,公用名以及组织和组织单位是我们之前创建证书时设置的。
由于浏览器不信任我们的证书,我们没有在浏览器配置client的文件,所以我们用curl命令来验证可能比较直观一点,命令如下:

curl -k -vo /dev/null https://www.example.com/ --resolve www.example.com:443:127.0.0.1

测试结果:

root@nginx:~# curl -k -vo /dev/null https://www.example.com/ --resolve www.example.com:443:127.0.0.1
* Added www.example.com:443:127.0.0.1 to DNS cache
* Hostname www.example.com was found in DNS cache
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 127.0.0.1:443...
* Connected to www.example.com (127.0.0.1) port 443 (#0)
* ALPN: offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
} [320 bytes data]
* (304) (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* (304) (IN), TLS handshake, Unknown (8):
{ [25 bytes data]
* (304) (IN), TLS handshake, Certificate (11):
{ [911 bytes data]
* (304) (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* (304) (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* (304) (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
* ALPN: server accepted http/1.1
* Server certificate:
*  subject: O=MyDomain; OU=MyDomain; CN=www.example.com
*  start date: Jun 20 03:52:48 2024 GMT
*  expire date: Jun 18 03:52:48 2034 GMT
*  issuer: C=CN; O=People's Republic of China; CN=China CA
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* using HTTP/1.1
> GET / HTTP/1.1
> Host: www.example.com
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx
< Date: Thu, 20 Jun 2024 07:53:38 GMT
< Content-Type: text/html
< Content-Length: 615
< Last-Modified: Tue, 13 Jun 2023 15:08:10 GMT
< Connection: keep-alive
< ETag: "6488865a-267"
< Accept-Ranges: bytes
<
{ [615 bytes data]
100   615  100   615    0     0  15215      0 --:--:-- --:--:-- --:--:-- 15375
* Connection #0 to host www.example.com left intact

配置SSL双向认证

我们只需要在nginx的子配置文件的server配置段上加上一下配置文件即可:

vim https.conf
-------------------------
server{
    ......
    ssl_verify_client on;
    ssl_client_certificate /certs/ca.crt;
    ......
}

重启服务进行验证,先使用浏览器访问发现页面出现了400,如下图所示:
certs_ssl.003
我们再用curl命令进行测试:

root@nginx:~# curl -k -vo /dev/null https://www.example.com/ --resolve www.example.com:443:127.0.0.1
* Added www.example.com:443:127.0.0.1 to DNS cache
* Hostname www.example.com was found in DNS cache
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 127.0.0.1:443...
* Connected to www.example.com (127.0.0.1) port 443 (#0)
* ALPN: offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
} [320 bytes data]
* (304) (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* (304) (IN), TLS handshake, Unknown (8):
{ [25 bytes data]
* (304) (IN), TLS handshake, Request CERT (13):
{ [124 bytes data]
* (304) (IN), TLS handshake, Certificate (11):
{ [911 bytes data]
* (304) (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* (304) (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* (304) (OUT), TLS handshake, Certificate (11):
} [8 bytes data]
* (304) (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
* ALPN: server accepted http/1.1
* Server certificate:
*  subject: O=MyDomain; OU=MyDomain; CN=www.example.com
*  start date: Jun 20 03:52:48 2024 GMT
*  expire date: Jun 18 03:52:48 2034 GMT
*  issuer: C=CN; O=People's Republic of China; CN=China CA
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* using HTTP/1.1
> GET / HTTP/1.1
> Host: www.example.com
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 400 Bad Request
< Server: nginx
< Date: Thu, 20 Jun 2024 08:04:36 GMT
< Content-Type: text/html
< Content-Length: 230
< Connection: close
<
{ [230 bytes data]
100   230  100   230    0     0   7088      0 --:--:-- --:--:-- --:--:--  7187
* Closing connection 0

发现状态也是400,是因为我们开启了SSL双向认证,我们需要之前client端的私钥和公钥进行访问,如下所示:

root@nginx:~# curl -k -vo /dev/null https://www.example.com/ --resolve www.example.com:443:127.0.0.1 --cert ./certs/client.crt --key ./certs/client.key
* Added www.example.com:443:127.0.0.1 to DNS cache
* Hostname www.example.com was found in DNS cache
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 127.0.0.1:443...
* Connected to www.example.com (127.0.0.1) port 443 (#0)
* ALPN: offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
} [320 bytes data]
* (304) (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* (304) (IN), TLS handshake, Unknown (8):
{ [25 bytes data]
* (304) (IN), TLS handshake, Request CERT (13):
{ [124 bytes data]
* (304) (IN), TLS handshake, Certificate (11):
{ [911 bytes data]
* (304) (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* (304) (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* (304) (OUT), TLS handshake, Certificate (11):
} [911 bytes data]
* (304) (OUT), TLS handshake, CERT verify (15):
} [264 bytes data]
* (304) (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
* ALPN: server accepted http/1.1
* Server certificate:
*  subject: O=MyDomain; OU=MyDomain; CN=www.example.com
*  start date: Jun 20 03:52:48 2024 GMT
*  expire date: Jun 18 03:52:48 2034 GMT
*  issuer: C=CN; O=People's Republic of China; CN=China CA
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* using HTTP/1.1
> GET / HTTP/1.1
> Host: www.example.com
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx
< Date: Thu, 20 Jun 2024 08:06:30 GMT
< Content-Type: text/html
< Content-Length: 615
< Last-Modified: Tue, 13 Jun 2023 15:08:10 GMT
< Connection: keep-alive
< ETag: "6488865a-267"
< Accept-Ranges: bytes
<
{ [615 bytes data]
100   615  100   615    0     0  18559      0 --:--:-- --:--:-- --:--:-- 18636
* Connection #0 to host www.example.com left intact

之后发现访问返回200,说明配置双认证成功!!!