修复bug
This commit is contained in:
parent
649823180d
commit
240409ccf9
131
app.py
131
app.py
@ -10,7 +10,20 @@ import random
|
||||
import string
|
||||
from io import BytesIO
|
||||
import zipfile
|
||||
from pypinyin import pinyin, Style
|
||||
import uuid
|
||||
def to_pinyin(text):
|
||||
"""将中文转换为拼音"""
|
||||
if not text:
|
||||
return ""
|
||||
# 获取拼音列表,不带声调
|
||||
pinyin_list = pinyin(text, style=Style.NORMAL)
|
||||
# 拼接成字符串
|
||||
return "_".join([item[0] for item in pinyin_list])
|
||||
|
||||
@app.template_filter('to_pinyin')
|
||||
def jinja2_to_pinyin(text):
|
||||
return to_pinyin(text)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = 'your-secret-key-here'
|
||||
@ -116,10 +129,12 @@ def verify_captcha(user_input):
|
||||
|
||||
def create_ca(ca_name, common_name, organization, organizational_unit, country, state, locality, key_size, days_valid,
|
||||
created_by):
|
||||
# 创建CA目录
|
||||
ca_dir = os.path.join(CERT_STORE, f"ca_{ca_name}")
|
||||
# 创建拼音格式的目录名
|
||||
pinyin_name = to_pinyin(ca_name)
|
||||
ca_dir = os.path.join(CERT_STORE, f"ca_{pinyin_name}")
|
||||
os.makedirs(ca_dir, exist_ok=True)
|
||||
|
||||
# 使用原始common_name作为文件名
|
||||
key_path = os.path.join(ca_dir, f"{common_name}.key")
|
||||
cert_path = os.path.join(ca_dir, f"{common_name}.crt")
|
||||
|
||||
@ -357,69 +372,77 @@ def generate_crl(ca_id):
|
||||
if not ca:
|
||||
return False
|
||||
|
||||
crl_path = os.path.join(os.path.dirname(ca['cert_path']), f"crl_{ca['name']}.pem")
|
||||
# 将中文路径转换为拼音
|
||||
ca_dir = os.path.dirname(ca['cert_path'])
|
||||
pinyin_name = to_pinyin(ca['name'])
|
||||
crl_path = os.path.join(ca_dir, f"crl_{pinyin_name}.pem")
|
||||
|
||||
# 获取所有被吊销的证书
|
||||
revoked_certs = []
|
||||
conn = get_db_connection()
|
||||
if conn:
|
||||
try:
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
cursor.execute("""
|
||||
SELECT cert_path FROM certificates
|
||||
WHERE ca_id = %s AND status = 'revoked'
|
||||
""", (ca_id,))
|
||||
revoked_certs = [row['cert_path'] for row in cursor.fetchall()]
|
||||
except Error as e:
|
||||
print(f"Database error: {e}")
|
||||
return False
|
||||
finally:
|
||||
if conn.is_connected():
|
||||
cursor.close()
|
||||
conn.close()
|
||||
# 创建必要的配置文件
|
||||
openssl_cnf = f"""
|
||||
[ ca ]
|
||||
default_ca = CA_default
|
||||
|
||||
# 创建索引文件
|
||||
index_file = os.path.join(os.path.dirname(ca['cert_path']), 'index.txt')
|
||||
[ CA_default ]
|
||||
database = {os.path.join(ca_dir, 'index.txt')}
|
||||
certificate = {ca['cert_path']}
|
||||
private_key = {ca['key_path']}
|
||||
crl = {crl_path}
|
||||
RANDFILE = {os.path.join(ca_dir, '.rand')}
|
||||
"""
|
||||
|
||||
# 确保目录存在
|
||||
os.makedirs(ca_dir, exist_ok=True)
|
||||
|
||||
cnf_path = os.path.join(ca_dir, 'openssl.cnf')
|
||||
with open(cnf_path, 'w', encoding='utf-8') as f:
|
||||
f.write(openssl_cnf)
|
||||
|
||||
# 确保index.txt存在
|
||||
index_file = os.path.join(ca_dir, 'index.txt')
|
||||
if not os.path.exists(index_file):
|
||||
open(index_file, 'w').close()
|
||||
open(index_file, 'a').close()
|
||||
|
||||
# 生成CRL
|
||||
subprocess.run([
|
||||
'openssl', 'ca', '-gencrl', '-out', crl_path,
|
||||
'-keyfile', ca['key_path'], '-cert', ca['cert_path'],
|
||||
'-crldays', '30'
|
||||
], check=True)
|
||||
try:
|
||||
subprocess.run([
|
||||
'openssl', 'ca', '-gencrl',
|
||||
'-config', cnf_path,
|
||||
'-out', crl_path,
|
||||
'-crldays', '30'
|
||||
], check=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf-8')
|
||||
|
||||
# 更新数据库
|
||||
next_update = datetime.now() + timedelta(days=30)
|
||||
|
||||
conn = get_db_connection()
|
||||
if conn:
|
||||
try:
|
||||
cursor = conn.cursor()
|
||||
# 检查是否已有CRL记录
|
||||
cursor.execute("SELECT id FROM certificate_revocation_list WHERE ca_id = %s", (ca_id,))
|
||||
if cursor.fetchone():
|
||||
cursor.execute("""
|
||||
UPDATE certificate_revocation_list
|
||||
SET crl_path = %s, last_updated = NOW(), next_update = %s
|
||||
WHERE ca_id = %s
|
||||
""", (crl_path, next_update, ca_id))
|
||||
else:
|
||||
# 更新数据库
|
||||
next_update = datetime.now() + timedelta(days=30)
|
||||
conn = get_db_connection()
|
||||
if conn:
|
||||
try:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
INSERT INTO certificate_revocation_list
|
||||
(ca_id, crl_path, next_update)
|
||||
VALUES (%s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
crl_path = VALUES(crl_path),
|
||||
last_updated = NOW(),
|
||||
next_update = VALUES(next_update)
|
||||
""", (ca_id, crl_path, next_update))
|
||||
conn.commit()
|
||||
return True
|
||||
except Error as e:
|
||||
print(f"Database error: {e}")
|
||||
return False
|
||||
finally:
|
||||
if conn.is_connected():
|
||||
cursor.close()
|
||||
conn.close()
|
||||
conn.commit()
|
||||
return True
|
||||
except Error as e:
|
||||
print(f"Database error: {e}")
|
||||
return False
|
||||
finally:
|
||||
if conn.is_connected():
|
||||
cursor.close()
|
||||
conn.close()
|
||||
except subprocess.CalledProcessError as e:
|
||||
error_msg = f"OpenSSL错误: {e.stderr}" if e.stderr else "未知OpenSSL错误"
|
||||
print(error_msg)
|
||||
flash(f'CRL生成失败: {error_msg}', 'danger')
|
||||
except Exception as e:
|
||||
print(f"CRL生成异常: {str(e)}")
|
||||
flash(f'CRL生成异常: {str(e)}', 'danger')
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@ -4,12 +4,29 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2>CA机构详情: {{ ca.name }}</h2>
|
||||
<h2>CA机构详情: {{ ca.name }}
|
||||
<small class="text-muted fs-6">(路径: {{ ca.name|to_pinyin }})</small>
|
||||
</h2>
|
||||
<div>
|
||||
<a href="{{ url_for('export_ca_view', ca_id=ca.id) }}" class="btn btn-primary me-2">导出CA</a>
|
||||
<a href="{{ url_for('generate_crl_view', ca_id=ca.id) }}" class="btn btn-warning me-2">生成CRL</a>
|
||||
<a href="{{ url_for('export_ca_view', ca_id=ca.id) }}"
|
||||
class="btn btn-primary me-2"
|
||||
data-bs-toggle="tooltip"
|
||||
title="导出CA证书(管理员可导出私钥)">
|
||||
<i class="fas fa-file-export"></i> 导出CA
|
||||
</a>
|
||||
<a href="{{ url_for('generate_crl_view', ca_id=ca.id) }}"
|
||||
class="btn btn-warning me-2"
|
||||
data-bs-toggle="tooltip"
|
||||
title="重新生成证书吊销列表">
|
||||
<i class="fas fa-sync-alt"></i> 生成CRL
|
||||
</a>
|
||||
{% if crl %}
|
||||
<a href="{{ url_for('download_crl', ca_id=ca.id) }}" class="btn btn-success">下载CRL</a>
|
||||
<a href="{{ url_for('download_crl', ca_id=ca.id) }}"
|
||||
class="btn btn-success"
|
||||
data-bs-toggle="tooltip"
|
||||
title="下载当前CRL文件(有效期至 {{ crl.next_update.strftime('%Y-%m-%d') }})">
|
||||
<i class="fas fa-download"></i> 下载CRL
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@ -18,26 +35,28 @@
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">基本信息</h5>
|
||||
<h5 class="card-title">
|
||||
<i class="fas fa-info-circle"></i> 基本信息
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4">通用名</dt>
|
||||
<dt class="col-sm-4"><i class="fas fa-id-card"></i> 通用名</dt>
|
||||
<dd class="col-sm-8">{{ ca.common_name }}</dd>
|
||||
|
||||
<dt class="col-sm-4">组织</dt>
|
||||
<dt class="col-sm-4"><i class="fas fa-building"></i> 组织</dt>
|
||||
<dd class="col-sm-8">{{ ca.organization }}</dd>
|
||||
|
||||
<dt class="col-sm-4">组织单位</dt>
|
||||
<dt class="col-sm-4"><i class="fas fa-users"></i> 组织单位</dt>
|
||||
<dd class="col-sm-8">{{ ca.organizational_unit or 'N/A' }}</dd>
|
||||
|
||||
<dt class="col-sm-4">国家</dt>
|
||||
<dt class="col-sm-4"><i class="fas fa-globe"></i> 国家</dt>
|
||||
<dd class="col-sm-8">{{ ca.country }}</dd>
|
||||
|
||||
<dt class="col-sm-4">州/省</dt>
|
||||
<dt class="col-sm-4"><i class="fas fa-map-marked"></i> 州/省</dt>
|
||||
<dd class="col-sm-8">{{ ca.state or 'N/A' }}</dd>
|
||||
|
||||
<dt class="col-sm-4">城市</dt>
|
||||
<dt class="col-sm-4"><i class="fas fa-city"></i> 城市</dt>
|
||||
<dd class="col-sm-8">{{ ca.locality or 'N/A' }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
@ -47,27 +66,35 @@
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">技术信息</h5>
|
||||
<h5 class="card-title">
|
||||
<i class="fas fa-cogs"></i> 技术信息
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4">密钥长度</dt>
|
||||
<dt class="col-sm-4"><i class="fas fa-key"></i> 密钥长度</dt>
|
||||
<dd class="col-sm-8">{{ ca.key_size }}位</dd>
|
||||
|
||||
<dt class="col-sm-4">有效期</dt>
|
||||
<dd class="col-sm-8">{{ ca.days_valid }}天</dd>
|
||||
<dt class="col-sm-4"><i class="fas fa-calendar-check"></i> 有效期</dt>
|
||||
<dd class="col-sm-8">{{ ca.days_valid }}天 (至 {{ (ca.created_at + timedelta(days=ca.days_valid)).strftime('%Y-%m-%d') }})</dd>
|
||||
|
||||
<dt class="col-sm-4">创建者</dt>
|
||||
<dd class="col-sm-8">{{ ca.created_by }}</dd>
|
||||
<dt class="col-sm-4"><i class="fas fa-user"></i> 创建者</dt>
|
||||
<dd class="col-sm-8">{{ get_username(ca.created_by) }}</dd>
|
||||
|
||||
<dt class="col-sm-4">创建时间</dt>
|
||||
<dt class="col-sm-4"><i class="fas fa-clock"></i> 创建时间</dt>
|
||||
<dd class="col-sm-8">{{ ca.created_at.strftime('%Y-%m-%d %H:%M') }}</dd>
|
||||
|
||||
<dt class="col-sm-4">证书路径</dt>
|
||||
<dd class="col-sm-8"><code>{{ ca.cert_path }}</code></dd>
|
||||
<dt class="col-sm-4"><i class="fas fa-file-certificate"></i> 证书路径</dt>
|
||||
<dd class="col-sm-8">
|
||||
<code class="text-truncate d-block" style="max-width: 250px;">{{ ca.cert_path }}</code>
|
||||
<small class="text-muted">拼音路径: {{ ca.cert_path|to_pinyin }}</small>
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-4">私钥路径</dt>
|
||||
<dd class="col-sm-8"><code>{{ ca.key_path }}</code></dd>
|
||||
<dt class="col-sm-4"><i class="fas fa-lock"></i> 私钥路径</dt>
|
||||
<dd class="col-sm-8">
|
||||
<code class="text-truncate d-block" style="max-width: 250px;">{{ ca.key_path }}</code>
|
||||
<small class="text-muted">拼音路径: {{ ca.key_path|to_pinyin }}</small>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@ -76,13 +103,24 @@
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">颁发的证书</h5>
|
||||
<a href="{{ url_for('create_certificate_view') }}?ca_id={{ ca.id }}" class="btn btn-sm btn-primary">创建证书</a>
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-certificate"></i> 颁发的证书
|
||||
<span class="badge bg-primary rounded-pill">{{ certificates|length }}</span>
|
||||
</h5>
|
||||
<div>
|
||||
<a href="{{ url_for('create_certificate_view') }}?ca_id={{ ca.id }}"
|
||||
class="btn btn-sm btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
title="使用此CA颁发新证书">
|
||||
<i class="fas fa-plus"></i> 创建证书
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if certificates %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead>
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>通用名</th>
|
||||
@ -99,27 +137,58 @@
|
||||
<td>{{ cert.common_name }}</td>
|
||||
<td>
|
||||
{% if cert.status == 'active' %}
|
||||
<span class="badge bg-success">有效</span>
|
||||
<span class="badge bg-success"><i class="fas fa-check-circle"></i> 有效</span>
|
||||
{% elif cert.status == 'revoked' %}
|
||||
<span class="badge bg-danger">已吊销</span>
|
||||
<span class="badge bg-danger"><i class="fas fa-ban"></i> 已吊销</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">已过期</span>
|
||||
<span class="badge bg-secondary"><i class="fas fa-clock"></i> 已过期</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ cert.expires_at.strftime('%Y-%m-%d') }}</td>
|
||||
<td>{{ cert.created_at.strftime('%Y-%m-%d') }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('certificate_detail', cert_id=cert.id) }}" class="btn btn-sm btn-info">详情</a>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{{ url_for('certificate_detail', cert_id=cert.id) }}"
|
||||
class="btn btn-info"
|
||||
data-bs-toggle="tooltip"
|
||||
title="查看证书详情">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<a href="{{ url_for('export_certificate_view', cert_id=cert.id) }}"
|
||||
class="btn btn-success"
|
||||
data-bs-toggle="tooltip"
|
||||
title="导出证书">
|
||||
<i class="fas fa-download"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">该CA尚未颁发任何证书</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-certificate fa-3x text-muted mb-3"></i>
|
||||
<h5 class="text-muted">该CA尚未颁发任何证书</h5>
|
||||
<a href="{{ url_for('create_certificate_view') }}?ca_id={{ ca.id }}" class="btn btn-primary mt-2">
|
||||
<i class="fas fa-plus"></i> 立即创建证书
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
// 启用工具提示
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user