通过自签证书进行C/S信任访问

通过自签证书进行C/S 信任访问

auther: 3inter.net

2025/12/19 11:42

CA管理器

#!/bin/bash

# ============================================
# CA 管理器脚本
# 版本: 4.2
# 功能:创建根CA、签发客户端证书
# 加密标准:RSA 3072, SHA-512
# ============================================

set -e

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# 目录结构
BASE_DIR="${HOME}/my-ca"
ROOT_CA_DIR="${BASE_DIR}/root-ca"
USERS_DIR="${BASE_DIR}/users"
CERTS_DIR="${BASE_DIR}/certs"
PRIVATE_DIR="${BASE_DIR}/private"
CONFIG_DIR="${BASE_DIR}/config"
TEMPLATES_DIR="${BASE_DIR}/templates"
PASSWORD_FILE="${BASE_DIR}/.ca-passphrase"
DB_FILE="${BASE_DIR}/cert-database.csv"

# 初始化目录结构
init_directories() {
    echo -e "${GREEN}初始化目录结构...${NC}"
    mkdir -p ${ROOT_CA_DIR}/{certs,private,newcerts,crl}
    mkdir -p ${USERS_DIR}
    mkdir -p ${CERTS_DIR}
    mkdir -p ${PRIVATE_DIR}
    mkdir -p ${CONFIG_DIR}
    mkdir -p ${TEMPLATES_DIR}
    
    # 创建必要的文件
    touch ${ROOT_CA_DIR}/index.txt
    echo 1000 > ${ROOT_CA_DIR}/serial
    echo 1000 > ${ROOT_CA_DIR}/crlnumber
    
    # 初始化证书数据库
    echo "username,email,department,organization,country,state,city,created_date,expiry_date,serial_number,status" > ${DB_FILE}
    
    echo -e "${GREEN}目录结构创建完成!${NC}"
    echo -e "${YELLOW}用户证书将存放在: ${USERS_DIR}/${NC}"
}

# 密码强度检查函数
check_password_strength() {
    local password="$1"
    
    if [ ${#password} -lt 8 ]; then
        echo "密码长度至少8位"
        return 1
    fi
    
    if ! [[ "$password" =~ [A-Z] ]]; then
        echo "密码必须包含至少一个大写字母"
        return 1
    fi
    
    if ! [[ "$password" =~ [a-z] ]]; then
        echo "密码必须包含至少一个小写字母"
        return 1
    fi
    
    if ! [[ "$password" =~ [0-9] ]]; then
        echo "密码必须包含至少一个数字"
        return 1
    fi
    
    if ! [[ "$password" =~ [[:punct:]] ]]; then
        echo "密码必须包含至少一个特殊字符"
        return 1
    fi
    
    echo "密码强度: 强"
    return 0
}

# 设置CA密码
set_ca_password() {
    echo -e "${YELLOW}设置CA私钥密码${NC}"
    echo -e "${CYAN}密码要求:${NC}"
    echo "  - 长度至少8位"
    echo "  - 包含大写字母"
    echo "  - 包含小写字母"
    echo "  - 包含数字"
    echo "  - 包含特殊字符"
    echo ""
    
    while true; do
        read -sp "输入密码: " PASSWORD1
        echo
        
        if check_password_strength "$PASSWORD1"; then
            read -sp "确认密码: " PASSWORD2
            echo
            
            if [ "$PASSWORD1" == "$PASSWORD2" ]; then
                echo "$PASSWORD1" > ${PASSWORD_FILE}
                chmod 600 ${PASSWORD_FILE}
                echo -e "${GREEN}密码设置成功!${NC}"
                return 0
            else
                echo -e "${RED}两次输入的密码不一致!${NC}"
            fi
        fi
    done
}

# 获取CA密码
get_ca_password() {
    if [ -f "${PASSWORD_FILE}" ]; then
        cat ${PASSWORD_FILE}
    else
        echo ""
    fi
}

# 生成配置文件
generate_config_files() {
    echo -e "${GREEN}生成配置文件...${NC}"
    
    # 主配置文件
    cat > ${CONFIG_DIR}/ca-config.cnf << 'EOF'
# OpenSSL CA配置文件

[ ca ]
default_ca = CA_default

[ CA_default ]
# 目录和文件设置
dir               = $ENV::CA_DIR
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

# CA证书和私钥
certificate       = $dir/certs/ca.crt
private_key       = $dir/private/ca.key

# 默认设置
default_days      = 365
default_crl_days  = 30
default_md        = sha512
preserve          = no
policy            = policy_anything

# 证书策略
[ policy_anything ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

# 证书请求配置
[ req ]
default_bits        = 3072
default_md          = sha512
distinguished_name  = req_distinguished_name
x509_extensions     = v3_ca

[ req_distinguished_name ]
countryName                     = Country Name (2 letter code)
countryName_default             = CN
countryName_min                 = 2
countryName_max                 = 2
stateOrProvinceName             = State or Province Name (full name)
stateOrProvinceName_default     = Beijing
localityName                    = Locality Name (eg, city)
localityName_default            = Beijing
0.organizationName              = Organization Name (eg, company)
0.organizationName_default      = My Company
organizationalUnitName          = Organizational Unit Name (eg, section)
organizationalUnitName_default  = IT Department
commonName                      = Common Name (e.g. server FQDN or YOUR name)
commonName_max                  = 64
emailAddress                    = Email Address
emailAddress_max                = 64

# 扩展配置
[ v3_ca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints       = CA:true
keyUsage               = digitalSignature, cRLSign, keyCertSign

[ v3_client_cert ]
basicConstraints       = CA:FALSE
keyUsage               = digitalSignature, keyEncipherment
extendedKeyUsage       = clientAuth
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
subjectAltName         = email:copy

[ v3_req ]
basicConstraints       = CA:FALSE
keyUsage               = digitalSignature, keyEncipherment
EOF

    echo -e "${GREEN}配置文件生成完成!${NC}"
}

# 创建根CA
create_root_ca() {
    echo -e "${GREEN}创建根CA...${NC}"
    
    if [ -f "${ROOT_CA_DIR}/private/ca.key" ]; then
        echo -e "${YELLOW}根CA已经存在,是否重新创建? (y/N)${NC}"
        read -r response
        if [[ ! "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
            return
        fi
    fi
    
    echo -e "${CYAN}请输入根CA的详细信息:${NC}"
    
    read -p "国家代码 (2字母,如CN) [CN]: " COUNTRY
    read -p "省/州 [Beijing]: " STATE
    read -p "城市 [Beijing]: " LOCALITY
    read -p "组织名称 [My Company]: " ORGANIZATION
    read -p "组织单元 [Certificate Authority]: " ORGANIZATIONAL_UNIT
    read -p "通用名称 (CA名称) [My Root CA]: " COMMON_NAME
    read -p "邮箱地址 [ca-admin@example.com]: " EMAIL
    
    COUNTRY=${COUNTRY:-CN}
    STATE=${STATE:-Beijing}
    LOCALITY=${LOCALITY:-Beijing}
    ORGANIZATION=${ORGANIZATION:-"My Company"}
    ORGANIZATIONAL_UNIT=${ORGANIZATIONAL_UNIT:-"Certificate Authority"}
    COMMON_NAME=${COMMON_NAME:-"My Root CA"}
    EMAIL=${EMAIL:-"ca-admin@example.com"}
    
    # 询问是否设置密码
    echo -e "${YELLOW}为CA私钥设置密码? (Y/n)${NC}"
    read -r USE_PASSWORD
    USE_PASSWORD=${USE_PASSWORD:-Y}
    
    if [[ "$USE_PASSWORD" =~ ^([yY][eE][sS]|[yY])$ ]]; then
        set_ca_password
        CA_PASSWORD=$(get_ca_password)
        
        echo -e "${BLUE}生成带密码的RSA 3072私钥...${NC}"
        openssl genrsa -aes256 -passout pass:"${CA_PASSWORD}" \
            -out ${ROOT_CA_DIR}/private/ca.key 3072
        
        echo -e "${BLUE}生成自签名根证书...${NC}"
        
        # 创建临时配置文件
        cat > /tmp/ca-req.cnf << EOF
[ req ]
default_bits        = 3072
default_md          = sha512
distinguished_name  = dn
prompt              = no
x509_extensions     = v3_ca

[ dn ]
C = ${COUNTRY}
ST = ${STATE}
L = ${LOCALITY}
O = ${ORGANIZATION}
OU = ${ORGANIZATIONAL_UNIT}
CN = ${COMMON_NAME}
emailAddress = ${EMAIL}

[ v3_ca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints       = CA:true
keyUsage               = digitalSignature, cRLSign, keyCertSign
EOF
        
        openssl req -new -x509 -sha512 -days 7300 \
            -config /tmp/ca-req.cnf \
            -key ${ROOT_CA_DIR}/private/ca.key \
            -passin pass:"${CA_PASSWORD}" \
            -out ${ROOT_CA_DIR}/certs/ca.crt
        
        rm -f /tmp/ca-req.cnf
    else
        echo -e "${YELLOW}生成无密码私钥...${NC}"
        openssl genrsa -out ${ROOT_CA_DIR}/private/ca.key 3072
        
        # 创建临时配置文件
        cat > /tmp/ca-req.cnf << EOF
[ req ]
default_bits        = 3072
default_md          = sha512
distinguished_name  = dn
prompt              = no
x509_extensions     = v3_ca

[ dn ]
C = ${COUNTRY}
ST = ${STATE}
L = ${LOCALITY}
O = ${ORGANIZATION}
OU = ${ORGANIZATIONAL_UNIT}
CN = ${COMMON_NAME}
emailAddress = ${EMAIL}

[ v3_ca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints       = CA:true
keyUsage               = digitalSignature, cRLSign, keyCertSign
EOF
        
        openssl req -new -x509 -sha512 -days 7300 \
            -config /tmp/ca-req.cnf \
            -key ${ROOT_CA_DIR}/private/ca.key \
            -out ${ROOT_CA_DIR}/certs/ca.crt
        
        rm -f /tmp/ca-req.cnf
    fi
    
    chmod 400 ${ROOT_CA_DIR}/private/ca.key
    chmod 444 ${ROOT_CA_DIR}/certs/ca.crt
    
    # 显示证书信息
    echo -e "${GREEN}根CA创建完成!${NC}"
    echo -e "${CYAN}证书信息:${NC}"
    openssl x509 -in ${ROOT_CA_DIR}/certs/ca.crt -noout -subject -issuer -dates
}

# 创建用户证书(每个用户独立文件夹)
create_user_cert() {
    echo -e "${GREEN}创建用户证书...${NC}"
    
    if [ ! -f "${ROOT_CA_DIR}/private/ca.key" ]; then
        echo -e "${RED}错误:根CA不存在,请先创建根CA!${NC}"
        return 1
    fi
    
    # 获取用户信息
    echo -e "${CYAN}请输入用户信息:${NC}"
    
    while true; do
        read -p "用户名 (英文,无空格): " USERNAME
        if [[ "$USERNAME" =~ ^[a-zA-Z0-9_\-]+$ ]]; then
            break
        else
            echo -e "${RED}用户名只能包含字母、数字、下划线和连字符!${NC}"
        fi
    done
    
    read -p "用户全名 (显示名称): " FULL_NAME
    read -p "邮箱地址: " EMAIL
    read -p "部门: " DEPARTMENT
    read -p "组织名称: " ORGANIZATION
    read -p "国家代码 [CN]: " COUNTRY
    read -p "省/州 [Beijing]: " STATE
    read -p "城市 [Beijing]: " LOCALITY
    
    # 设置默认值
    FULL_NAME=${FULL_NAME:-$USERNAME}
    EMAIL=${EMAIL:-"${USERNAME}@example.com"}
    COUNTRY=${COUNTRY:-CN}
    ORGANIZATION=${ORGANIZATION:-"My Company"}
    DEPARTMENT=${DEPARTMENT:-"IT Department"}
    STATE=${STATE:-"Beijing"}
    LOCALITY=${LOCALITY:-"Beijing"}
    
    # 检查用户是否已存在
    USER_DIR="${USERS_DIR}/${USERNAME}"
    if [ -d "${USER_DIR}" ]; then
        echo -e "${YELLOW}用户 ${USERNAME} 已存在,是否重新创建? (y/N)${NC}"
        read -r response
        if [[ ! "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
            return
        fi
        rm -rf "${USER_DIR}"
    fi
    
    # 创建用户目录
    mkdir -p "${USER_DIR}"
    mkdir -p "${USER_DIR}/private"
    mkdir -p "${USER_DIR}/certs"
    mkdir -p "${USER_DIR}/backup"
    
    # 生成用户私钥
    USER_KEY="${USER_DIR}/private/${USERNAME}.key"
    echo -e "${BLUE}生成用户私钥...${NC}"
    openssl genrsa -out "${USER_KEY}" 3072
    chmod 400 "${USER_KEY}"
    
    # 生成证书签名请求
    CSR="${USER_DIR}/certs/${USERNAME}.csr"
    echo -e "${BLUE}生成证书签名请求...${NC}"
    
    # 创建CSR配置文件
    cat > /tmp/user-csr.cnf << EOF
[ req ]
default_bits        = 3072
default_md          = sha512
distinguished_name  = dn
req_extensions      = v3_req
prompt              = no

[ dn ]
C = ${COUNTRY}
ST = ${STATE}
L = ${LOCALITY}
O = ${ORGANIZATION}
OU = ${DEPARTMENT}
CN = ${FULL_NAME}
emailAddress = ${EMAIL}

[ v3_req ]
basicConstraints       = CA:FALSE
keyUsage               = digitalSignature, keyEncipherment
extendedKeyUsage       = clientAuth
subjectAltName         = email:${EMAIL}
EOF
    
    openssl req -new -sha512 \
        -key "${USER_KEY}" \
        -out "${CSR}" \
        -config /tmp/user-csr.cnf
    
    if [ ! -f "${CSR}" ]; then
        echo -e "${RED}生成CSR失败!${NC}"
        rm -f /tmp/user-csr.cnf
        return 1
    fi
    
    echo -e "${GREEN}CSR生成成功${NC}"
    
    # 签署证书
    USER_CERT="${USER_DIR}/certs/${USERNAME}.crt"
    echo -e "${BLUE}签署用户证书...${NC}"
    
    CA_PASSWORD=$(get_ca_password)
    
    # 创建签名配置文件(包含完整扩展)
    cat > /tmp/signing.cnf << EOF
[ ca ]
default_ca = CA_default

[ CA_default ]
dir               = ${ROOT_CA_DIR}
certs             = \$dir/certs
crl_dir           = \$dir/crl
new_certs_dir     = \$dir/newcerts
database          = \$dir/index.txt
serial            = \$dir/serial
RANDFILE          = \$dir/private/.rand
certificate       = \$dir/certs/ca.crt
private_key       = \$dir/private/ca.key
default_days      = 365
default_crl_days  = 30
default_md        = sha512
preserve          = no
policy            = policy_anything

[ policy_anything ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ v3_client_cert ]
basicConstraints       = CA:FALSE
keyUsage               = digitalSignature, keyEncipherment
extendedKeyUsage       = clientAuth
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
subjectAltName         = email:copy
EOF
    
    # 使用x509命令直接签署证书(更简单可靠)
    if [ -z "$CA_PASSWORD" ]; then
        # 无密码情况
        openssl x509 -req -sha512 -days 365 \
            -in "${CSR}" \
            -CA ${ROOT_CA_DIR}/certs/ca.crt \
            -CAkey ${ROOT_CA_DIR}/private/ca.key \
            -CAcreateserial \
            -out "${USER_CERT}" \
            -extfile /tmp/user-csr.cnf \
            -extensions v3_req
    else
        # 有密码情况
        openssl x509 -req -sha512 -days 365 \
            -in "${CSR}" \
            -CA ${ROOT_CA_DIR}/certs/ca.crt \
            -CAkey ${ROOT_CA_DIR}/private/ca.key \
            -passin pass:"${CA_PASSWORD}" \
            -CAcreateserial \
            -out "${USER_CERT}" \
            -extfile /tmp/user-csr.cnf \
            -extensions v3_req
    fi
    
    if [ ! -f "${USER_CERT}" ]; then
        echo -e "${RED}签署证书失败!${NC}"
        rm -f /tmp/user-csr.cnf /tmp/signing.cnf
        return 1
    fi
    
    echo -e "${GREEN}证书签署成功${NC}"
    
    # 创建证书链
    CHAIN="${USER_DIR}/certs/${USERNAME}-chain.crt"
    cat "${USER_CERT}" > "${CHAIN}"
    cat ${ROOT_CA_DIR}/certs/ca.crt >> "${CHAIN}"
    
    # 设置PKCS#12密码
    echo -e "${YELLOW}为PKCS#12文件设置密码:${NC}"
    while true; do
        read -sp "输入PKCS#12密码 (至少8位): " P12_PASSWORD1
        echo
        if [ ${#P12_PASSWORD1} -lt 8 ]; then
            echo -e "${RED}密码至少需要8位!${NC}"
            continue
        fi
        read -sp "确认密码: " P12_PASSWORD2
        echo
        if [ "$P12_PASSWORD1" == "$P12_PASSWORD2" ]; then
            P12_PASSWORD="$P12_PASSWORD1"
            break
        else
            echo -e "${RED}两次输入的密码不一致!${NC}"
        fi
    done
    
    # 创建PKCS#12文件
    P12_FILE="${USER_DIR}/certs/${USERNAME}.p12"
    echo -e "${BLUE}创建PKCS#12文件...${NC}"
    openssl pkcs12 -export \
        -in "${USER_CERT}" \
        -inkey "${USER_KEY}" \
        -certfile ${ROOT_CA_DIR}/certs/ca.crt \
        -out "${P12_FILE}" \
        -passout pass:"${P12_PASSWORD}" \
        -name "${FULL_NAME}"
    
    if [ ! -f "${P12_FILE}" ]; then
        echo -e "${RED}创建PKCS#12文件失败!${NC}"
    else
        chmod 400 "${P12_FILE}"
        echo -e "${GREEN}PKCS#12文件创建成功${NC}"
    fi
    
    # 创建信息文件
    INFO_FILE="${USER_DIR}/user-info.txt"
    cat > "${INFO_FILE}" << EOF
用户证书信息
============

基本信息:
----------
用户名: ${USERNAME}
全名: ${FULL_NAME}
邮箱: ${EMAIL}
部门: ${DEPARTMENT}
组织: ${ORGANIZATION}
位置: ${LOCALITY}, ${STATE}, ${COUNTRY}

证书信息:
----------
创建日期: $(date)
有效期: 365天
PKCS#12密码: ${P12_PASSWORD}

文件清单:
----------
1. 私钥: private/${USERNAME}.key
2. 证书: certs/${USERNAME}.crt
3. 证书链: certs/${USERNAME}-chain.crt
4. CSR: certs/${USERNAME}.csr
5. PKCS#12: certs/${USERNAME}.p12
6. CA证书: certs/ca.crt (拷贝)

重要提醒:
----------
1. 私钥文件必须严格保密!
2. PKCS#12文件包含私钥,需安全传输
3. 证书过期前请及时续期
EOF
    
    # 创建README文件
    README_FILE="${USER_DIR}/README.txt"
    cat > "${README_FILE}" << 'EOF'
使用说明
========

1. 导入证书到浏览器:
   - 双击 .p12 文件
   - 输入密码导入
   - 可用于HTTPS客户端认证

2. 验证证书:
   openssl verify -CAfile certs/ca.crt certs/用户名.crt

3. 查看证书信息:
   openssl x509 -in certs/用户名.crt -text -noout

4. 证书续期:
   联系管理员重新签发证书

安全注意事项:
1. 私钥文件 (.key) 必须严格保密
2. 不要共享PKCS#12密码
3. 证书丢失立即报告
EOF
    
    # 复制CA证书到用户目录
    cp ${ROOT_CA_DIR}/certs/ca.crt "${USER_DIR}/certs/ca.crt"
    
    # 记录到数据库
    EXPIRY_DATE=$(date -d "+365 days" "+%Y-%m-%d")
    SERIAL=$(openssl x509 -in "${USER_CERT}" -noout -serial | cut -d= -f2)
    echo "\"${USERNAME}\",\"${EMAIL}\",\"${DEPARTMENT}\",\"${ORGANIZATION}\",\"${COUNTRY}\",\"${STATE}\",\"${LOCALITY}\",\"$(date '+%Y-%m-%d')\",\"${EXPIRY_DATE}\",\"${SERIAL}\",\"ACTIVE\"" >> ${DB_FILE}
    
    # 打包用户证书
    ZIP_FILE="${USERS_DIR}/${USERNAME}-certificates.zip"
    echo -e "${BLUE}打包用户证书...${NC}"
    cd "${USER_DIR}"
    zip -q -r "${ZIP_FILE}" .
    cd - > /dev/null
    
    chmod 400 "${ZIP_FILE}"
    
    # 验证证书
    echo -e "${BLUE}验证证书...${NC}"
    if openssl verify -CAfile "${USER_DIR}/certs/ca.crt" "${USER_CERT}" > /dev/null 2>&1; then
        echo -e "${GREEN}证书验证成功!${NC}"
    else
        echo -e "${YELLOW}证书验证失败,但文件已生成${NC}"
    fi
    
    # 显示证书信息
    echo -e "${CYAN}证书信息摘要:${NC}"
    openssl x509 -in "${USER_CERT}" -noout -subject -issuer -dates
    
    echo -e "${GREEN}用户证书创建完成!${NC}"
    echo -e "${YELLOW}用户目录: ${USER_DIR}${NC}"
    echo -e "${YELLOW}打包文件: ${ZIP_FILE}${NC}"
    echo -e "${YELLOW}PKCS#12密码: ${P12_PASSWORD}${NC}"
    
    # 显示文件列表
    echo -e "\n${CYAN}生成的文件:${NC}"
    echo "私钥目录:"
    ls -la "${USER_DIR}/private/"
    echo -e "\n证书目录:"
    ls -la "${USER_DIR}/certs/"
    
    # 清理临时文件
    rm -f /tmp/user-csr.cnf /tmp/signing.cnf
}

# 列出所有用户
list_users() {
    echo -e "${GREEN}用户列表:${NC}"
    
    if [ ! -d "${USERS_DIR}" ] || [ -z "$(ls -A ${USERS_DIR} 2>/dev/null)" ]; then
        echo -e "${YELLOW}暂无用户${NC}"
        return
    fi
    
    echo "用户名 | 邮箱 | 部门 | 创建时间 | 状态"
    echo "----------------------------------------"
    
    # 从数据库读取
    if [ -f "${DB_FILE}" ]; then
        tail -n +2 "${DB_FILE}" | while IFS=, read -r username email department organization country state city created expiry serial status
        do
            # 移除引号
            username=$(echo "$username" | tr -d '"')
            email=$(echo "$email" | tr -d '"')
            department=$(echo "$department" | tr -d '"')
            created=$(echo "$created" | tr -d '"')
            status=$(echo "$status" | tr -d '"')
            
            echo "$username | $email | $department | $created | $status"
        done
    else
        # 从目录结构读取
        for user_dir in ${USERS_DIR}/*/; do
            if [ -d "$user_dir" ]; then
                username=$(basename "$user_dir")
                if [ -f "${user_dir}/user-info.txt" ]; then
                    email=$(grep "邮箱:" "${user_dir}/user-info.txt" | cut -d: -f2 | xargs)
                    department=$(grep "部门:" "${user_dir}/user-info.txt" | cut -d: -f2 | xargs)
                    echo "$username | $email | $department | $(stat -c %y "$user_dir" | cut -d' ' -f1) | ACTIVE"
                else
                    echo "$username | N/A | N/A | $(stat -c %y "$user_dir" | cut -d' ' -f1) | UNKNOWN"
                fi
            fi
        done
    fi
}

# 查看用户详情
view_user() {
    echo -e "${GREEN}查看用户详情${NC}"
    
    read -p "输入用户名: " USERNAME
    
    USER_DIR="${USERS_DIR}/${USERNAME}"
    if [ ! -d "${USER_DIR}" ]; then
        echo -e "${RED}用户不存在!${NC}"
        return 1
    fi
    
    echo -e "${CYAN}用户信息:${NC}"
    if [ -f "${USER_DIR}/user-info.txt" ]; then
        cat "${USER_DIR}/user-info.txt"
    else
        echo "基本信息文件不存在"
    fi
    
    echo -e "\n${CYAN}证书状态:${NC}"
    if [ -f "${USER_DIR}/certs/${USERNAME}.crt" ]; then
        openssl x509 -in "${USER_DIR}/certs/${USERNAME}.crt" -noout -subject -issuer -dates
    fi
    
    echo -e "\n${CYAN}文件列表:${NC}"
    ls -la "${USER_DIR}/"
    ls -la "${USER_DIR}/private/" 2>/dev/null || echo "私钥目录不存在"
    ls -la "${USER_DIR}/certs/" 2>/dev/null || echo "证书目录不存在"
}

# 吊销用户证书
revoke_user_cert() {
    echo -e "${GREEN}吊销用户证书${NC}"
    
    read -p "输入用户名: " USERNAME
    
    USER_DIR="${USERS_DIR}/${USERNAME}"
    if [ ! -d "${USER_DIR}" ]; then
        echo -e "${RED}用户不存在!${NC}"
        return 1
    fi
    
    USER_CERT="${USER_DIR}/certs/${USERNAME}.crt"
    if [ ! -f "${USER_CERT}" ]; then
        echo -e "${RED}证书文件不存在!${NC}"
        return 1
    fi
    
    echo -e "${YELLOW}确认吊销用户 ${USERNAME} 的证书? (y/N)${NC}"
    read -r response
    if [[ ! "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
        return
    fi
    
    CA_PASSWORD=$(get_ca_password)
    
    # 创建吊销配置文件
    cat > /tmp/revoke.cnf << EOF
[ ca ]
default_ca = CA_default

[ CA_default ]
dir               = ${ROOT_CA_DIR}
certs             = \$dir/certs
crl_dir           = \$dir/crl
new_certs_dir     = \$dir/newcerts
database          = \$dir/index.txt
serial            = \$dir/serial
RANDFILE          = \$dir/private/.rand
certificate       = \$dir/certs/ca.crt
private_key       = \$dir/private/ca.key
EOF
    
    if [ -z "$CA_PASSWORD" ]; then
        openssl ca -config /tmp/revoke.cnf -revoke "${USER_CERT}"
    else
        openssl ca -config /tmp/revoke.cnf -revoke "${USER_CERT}" -passin pass:"${CA_PASSWORD}"
    fi
    
    # 更新数据库状态
    if [ -f "${DB_FILE}" ]; then
        sed -i "/\"${USERNAME}\",/s/\"ACTIVE\"/\"REVOKED\"/" "${DB_FILE}"
    fi
    
    # 移动文件到备份目录
    BACKUP_DIR="${USER_DIR}/backup/revoked-$(date +%Y%m%d-%H%M%S)"
    mkdir -p "${BACKUP_DIR}"
    mv "${USER_DIR}/certs/"*.crt "${BACKUP_DIR}/" 2>/dev/null || true
    mv "${USER_DIR}/private/"*.key "${BACKUP_DIR}/" 2>/dev/null || true
    
    echo -e "${GREEN}证书已吊销!${NC}"
    
    # 生成CRL
    echo -e "${BLUE}生成新的CRL...${NC}"
    if [ -z "$CA_PASSWORD" ]; then
        openssl ca -gencrl -out ${ROOT_CA_DIR}/crl/ca.crl -config /tmp/revoke.cnf
    else
        openssl ca -gencrl -out ${ROOT_CA_DIR}/crl/ca.crl -config /tmp/revoke.cnf -passin pass:"${CA_PASSWORD}"
    fi
    
    rm -f /tmp/revoke.cnf
}

# 验证证书
verify_cert() {
    echo -e "${GREEN}验证证书${NC}"
    
    read -p "输入用户名: " USERNAME
    
    USER_DIR="${USERS_DIR}/${USERNAME}"
    if [ ! -d "${USER_DIR}" ]; then
        echo -e "${RED}用户不存在!${NC}"
        return 1
    fi
    
    USER_CERT="${USER_DIR}/certs/${USERNAME}.crt"
    CA_CERT="${USER_DIR}/certs/ca.crt"
    
    if [ ! -f "${USER_CERT}" ]; then
        echo -e "${RED}证书文件不存在!${NC}"
        return 1
    fi
    
    if [ ! -f "${CA_CERT}" ]; then
        echo -e "${RED}CA证书文件不存在!${NC}"
        return 1
    fi
    
    echo -e "${CYAN}验证结果:${NC}"
    if openssl verify -CAfile "${CA_CERT}" "${USER_CERT}"; then
        echo -e "${GREEN}证书有效!${NC}"
    else
        echo -e "${RED}证书无效!${NC}"
    fi
}

# 显示帮助信息
show_help() {
    echo -e "${GREEN}CA 管理器 v4.2${NC}"
    echo "==============="
    echo "功能:"
    echo "  - 每个用户独立文件夹管理"
    echo "  - RSA 3072 + SHA-512加密"
    echo "  - 完整的证书生命周期管理"
    echo "  - 支持密码保护的CA私钥"
    echo ""
    echo "目录结构:"
    echo "  ~/my-ca/"
    echo "  ├── root-ca/          # CA根证书"
    echo "  ├── users/            # 用户证书目录"
    echo "  │   └── username/     # 每个用户独立目录"
    echo "  │       ├── private/  # 私钥"
    echo "  │       ├── certs/    # 证书文件"
    echo "  │       ├── backup/   # 备份"
    echo "  │       ├── user-info.txt"
    echo "  │       └── README.txt"
    echo "  └── cert-database.csv # 证书数据库"
}

# 主菜单
main_menu() {
    clear
    echo -e "${PURPLE}=================================${NC}"
    echo -e "${PURPLE}     CA 管理器 v4.2            ${NC}"
    echo -e "${PURPLE}  RSA 3072 | SHA-512           ${NC}"
    echo -e "${PURPLE}=================================${NC}"
    echo ""
    
    while true; do
        echo -e "${CYAN}请选择操作:${NC}"
        echo "1) 初始化CA环境"
        echo "2) 创建根CA"
        echo "3) 创建用户证书"
        echo "4) 列出所有用户"
        echo "5) 查看用户详情"
        echo "6) 吊销用户证书"
        echo "7) 验证证书"
        echo "8) 显示帮助"
        echo "9) 退出"
        echo ""
        
        read -p "请输入选择 (1-9): " choice
        
        case $choice in
            1)
                init_directories
                generate_config_files
                ;;
            2)
                create_root_ca
                ;;
            3)
                create_user_cert
                ;;
            4)
                list_users
                ;;
            5)
                view_user
                ;;
            6)
                revoke_user_cert
                ;;
            7)
                verify_cert
                ;;
            8)
                show_help
                ;;
            9)
                echo -e "${GREEN}再见!${NC}"
                exit 0
                ;;
            *)
                echo -e "${RED}无效的选择!${NC}"
                ;;
        esac
        
        echo ""
        read -p "按回车键继续..."
        clear
    done
}

# 启动脚本
if [ "$1" = "--init" ]; then
    init_directories
    generate_config_files
    echo -e "${GREEN}初始化完成!${NC}"
    echo -e "${YELLOW}用户证书将存放在: ${USERS_DIR}/${NC}"
elif [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
    show_help
else
    # 检查是否已初始化
    if [ ! -d "${CONFIG_DIR}" ]; then
        echo -e "${YELLOW}CA环境未初始化!${NC}"
        echo -e "请先运行: $0 --init"
        exit 1
    fi
    
    main_menu
fi

配置Nginx服务

  • 生成服务端证书

    openssl req -newkey rsa:3072 -nodes -keyout srv.key \
      -subj "/C=CN/ST=Beijing/L=Beijing/O=Winter/CN=YourIP/Domain" \
      -addext "subjectAltName = DNS:127.0.0.1" \
      -addext "keyUsage = digitalSignature, keyEncipherment" \
      -addext "extendedKeyUsage = serverAuth, clientAuth" \
      -sha512 | \
    openssl x509 -req -days 365 -sha512 \
      -CA root-ca/certs/ca.crt \
      -CAkey root-ca/private/ca.key \
      -CAcreateserial \
      -out srv.crt
  • Nginx 配置信息

    [root@localhost cert]# cat /etc/nginx/nginx.conf
    # For more information on configuration, see:
    #   * Official English Documentation: http://nginx.org/en/docs/
    #   * Official Russian Documentation: http://nginx.org/ru/docs/
    
    user nginx;
    worker_processes auto;
    error_log /var/log/nginx/error.log;
    pid /run/nginx.pid;
    
    # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
    include /usr/share/nginx/modules/*.conf;
    
    events {
        worker_connections 1024;
    }
    
    http {
        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;
        keepalive_timeout   65;
        types_hash_max_size 4096;
    
        include             /etc/nginx/mime.types;
        default_type        application/octet-stream;
    
        # Load modular configuration files from the /etc/nginx/conf.d directory.
        # See http://nginx.org/en/docs/ngx_core_module.html#include
        # for more information.
        include /etc/nginx/conf.d/*.conf;
    
        server {
            listen       443 ssl;
            listen       [::]:443 ssl;
    
            server_name  _;
            root         /usr/share/nginx/html;
    
            # Load configuration files for the default server block.
            include /etc/nginx/default.d/*.conf;
    
            ssl_certificate /etc/nginx/cert/3interserver.crt;
            ssl_certificate_key /etc/nginx/cert/3interserver.key;
    
            # CA证书用于验证客户端
            ssl_client_certificate /etc/nginx/cert/ca.crt;
            ssl_verify_client on;                           # 关键:开启客户端验证
            ssl_verify_depth 2;
    
            error_page 404 /404.html;
            location = /404.html {
            }
    
            error_page 500 502 503 504 /50x.html;
            location = /50x.html {
            }
        }

效果

  • 不带客户端证书访问

    image

  • 命令行带客户端证书访问

    curl -v \                                                                                                                                                                                                                                     ─╯
    --cert winter.crt \
    --key ../private/winter.key \
    --cacert ca.crt \
    https://10.211.55.8

    image