支持删除CA和证书

This commit is contained in:
wzj 2025-06-14 11:22:40 +08:00
parent 56b4a91972
commit fd9f2d6e52
3 changed files with 145 additions and 22 deletions

147
app.py
View File

@ -130,6 +130,54 @@ def verify_captcha(user_input):
conn.close() conn.close()
return False return False
def validate_name(name, max_length=64):
"""
校验名称是否符合规范
规则
1. 长度1-64个字符
2. 只能包含字母数字中文下划线短横线
3. 不能以短横线开头或结尾
"""
if not name or len(name) > max_length:
return False
# 允许中文、字母、数字、下划线、短横线
pattern = r'^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$'
if not re.match(pattern, name):
return False
# 不能以短横线开头或结尾
if name.startswith('-') or name.endswith('-'):
return False
return True
def validate_common_name(cn):
"""
校验通用名(Common Name)是否符合规范
规则
1. 长度1-64个字符
2. 只能包含字母数字点号(.)和短横线(-)
3. 不能以点号或短横线开头或结尾
4. 不能连续两个点号或短横线
"""
if not cn or len(cn) > 64:
return False
# 只允许字母、数字、点号和短横线
if not re.match(r'^[a-zA-Z0-9.-]+$', cn):
return False
# 不能以点号或短横线开头或结尾
if cn.startswith('.') or cn.endswith('.') or cn.startswith('-') or cn.endswith('-'):
return False
# 不能连续两个点号或短横线
if '..' in cn or '--' in cn:
return False
return True
def create_ca(ca_name, common_name, organization, organizational_unit, country, state, locality, key_size, days_valid, def create_ca(ca_name, common_name, organization, organizational_unit, country, state, locality, key_size, days_valid,
created_by): created_by):
@ -625,16 +673,43 @@ def ca_list():
@login_required @login_required
def create_ca_view(): def create_ca_view():
if request.method == 'POST': if request.method == 'POST':
ca_name = request.form['ca_name'] ca_name = request.form['ca_name'].strip()
common_name = request.form['common_name'] common_name = request.form['common_name'].strip()
organization = request.form['organization'] organization = request.form['organization'].strip()
organizational_unit = request.form['organizational_unit'] organizational_unit = request.form['organizational_unit'].strip()
country = request.form['country'] country = request.form['country'].strip()
state = request.form['state'] state = request.form['state'].strip()
locality = request.form['locality'] locality = request.form['locality'].strip()
key_size = int(request.form['key_size']) key_size = int(request.form['key_size'])
days_valid = int(request.form['days_valid']) days_valid = int(request.form['days_valid'])
# 名称校验
if not validate_name(ca_name):
flash('CA名称无效只能包含中文、字母、数字、下划线和短横线且不能以短横线开头或结尾', 'danger')
return render_template('create_ca.html')
if not validate_common_name(common_name):
flash('通用名无效:只能包含字母、数字、点号和短横线,且不能以点号或短横线开头或结尾', 'danger')
return render_template('create_ca.html')
# 检查CA名称是否已存在
conn = get_db_connection()
if conn:
try:
cursor = conn.cursor(dictionary=True)
cursor.execute("SELECT id FROM certificate_authorities WHERE name = %s", (ca_name,))
if cursor.fetchone():
flash('CA名称已存在请使用其他名称', 'danger')
return render_template('create_ca.html')
except Error as e:
print(f"Database error: {e}")
flash('检查CA名称失败', 'danger')
return render_template('create_ca.html')
finally:
if conn.is_connected():
cursor.close()
conn.close()
ca_id = create_ca(ca_name, common_name, organization, organizational_unit, ca_id = create_ca(ca_name, common_name, organization, organizational_unit,
country, state, locality, key_size, days_valid, current_user.id) country, state, locality, key_size, days_valid, current_user.id)
@ -646,7 +721,6 @@ def create_ca_view():
return render_template('create_ca.html') return render_template('create_ca.html')
from datetime import timedelta # 确保顶部已导入 from datetime import timedelta # 确保顶部已导入
@app.route('/cas/<int:ca_id>') @app.route('/cas/<int:ca_id>')
@ -821,18 +895,58 @@ def certificate_list():
@login_required @login_required
def create_certificate_view(): def create_certificate_view():
if request.method == 'POST': if request.method == 'POST':
common_name = request.form['common_name'] common_name = request.form['common_name'].strip()
san_dns = request.form.get('san_dns', '') san_dns = request.form.get('san_dns', '').strip()
san_ip = request.form.get('san_ip', '') san_ip = request.form.get('san_ip', '').strip()
organization = request.form['organization'] organization = request.form['organization'].strip()
organizational_unit = request.form['organizational_unit'] organizational_unit = request.form['organizational_unit'].strip()
country = request.form['country'] country = request.form['country'].strip()
state = request.form['state'] state = request.form['state'].strip()
locality = request.form['locality'] locality = request.form['locality'].strip()
key_size = int(request.form['key_size']) key_size = int(request.form['key_size'])
days_valid = int(request.form['days_valid']) days_valid = int(request.form['days_valid'])
ca_id = int(request.form['ca_id']) ca_id = int(request.form['ca_id'])
# 通用名校验
if not validate_common_name(common_name):
flash('通用名无效:只能包含字母、数字、点号和短横线,且不能以点号或短横线开头或结尾', 'danger')
return redirect(url_for('create_certificate_view'))
# SAN DNS校验
if san_dns:
for dns in san_dns.split(','):
dns = dns.strip()
if not validate_common_name(dns):
flash(f'DNS SAN条目无效: {dns},只能包含字母、数字、点号和短横线', 'danger')
return redirect(url_for('create_certificate_view'))
# SAN IP校验
if san_ip:
for ip in san_ip.split(','):
ip = ip.strip()
if not re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', ip):
flash(f'IP SAN条目无效: {ip}请输入有效的IPv4地址', 'danger')
return redirect(url_for('create_certificate_view'))
# 检查证书是否已存在
conn = get_db_connection()
if conn:
try:
cursor = conn.cursor(dictionary=True)
cursor.execute("SELECT id FROM certificates WHERE common_name = %s AND ca_id = %s",
(common_name, ca_id))
if cursor.fetchone():
flash('该CA下已存在相同通用名的证书', 'danger')
return redirect(url_for('create_certificate_view'))
except Error as e:
print(f"Database error: {e}")
flash('检查证书名称失败', 'danger')
return redirect(url_for('create_certificate_view'))
finally:
if conn.is_connected():
cursor.close()
conn.close()
cert_id = create_certificate(ca_id, common_name, san_dns, san_ip, organization, cert_id = create_certificate(ca_id, common_name, san_dns, san_ip, organization,
organizational_unit, country, state, locality, organizational_unit, country, state, locality,
key_size, days_valid, current_user.id) key_size, days_valid, current_user.id)
@ -867,7 +981,6 @@ def create_certificate_view():
conn.close() conn.close()
return redirect(url_for('certificate_list')) return redirect(url_for('certificate_list'))
@app.route('/certificates/<int:cert_id>') @app.route('/certificates/<int:cert_id>')
@login_required @login_required
def certificate_detail(cert_id): def certificate_detail(cert_id):

View File

@ -12,12 +12,16 @@
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-6"> <div class="col-md-6">
<label for="ca_name" class="form-label">CA名称</label> <label for="ca_name" class="form-label">CA名称</label>
<input type="text" class="form-control" id="ca_name" name="ca_name" required> <input type="text" class="form-control" id="ca_name" name="ca_name"
required pattern="[a-zA-Z0-9_\-\u4e00-\u9fa5]+"
title="只能包含中文、字母、数字、下划线和短横线,且不能以短横线开头或结尾">
<div class="form-text">CA机构的显示名称</div> <div class="form-text">CA机构的显示名称</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label for="common_name" class="form-label">通用名(CN)</label> <label for="common_name" class="form-label">通用名(CN)</label>
<input type="text" class="form-control" id="common_name" name="common_name" required> <input type="text" class="form-control" id="common_name" name="common_name"
required pattern="[a-zA-Z0-9.-]+"
title="只能包含字母、数字、点号和短横线,且不能以点号或短横线开头或结尾">
<div class="form-text">证书的Common Name字段</div> <div class="form-text">证书的Common Name字段</div>
</div> </div>
</div> </div>

View File

@ -12,7 +12,9 @@
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-6"> <div class="col-md-6">
<label for="common_name" class="form-label">通用名(CN)</label> <label for="common_name" class="form-label">通用名(CN)</label>
<input type="text" class="form-control" id="common_name" name="common_name" required> <input type="text" class="form-control" id="common_name" name="common_name"
required pattern="[a-zA-Z0-9.-]+"
title="只能包含字母、数字、点号和短横线,且不能以点号或短横线开头或结尾">
<div class="form-text">证书的Common Name字段通常是域名</div> <div class="form-text">证书的Common Name字段通常是域名</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
@ -56,12 +58,16 @@
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-6"> <div class="col-md-6">
<label for="san_dns" class="form-label">SAN DNS (可选)</label> <label for="san_dns" class="form-label">SAN DNS (可选)</label>
<input type="text" class="form-control" id="san_dns" name="san_dns"> <input type="text" class="form-control" id="common_name" name="common_name"
required pattern="[a-zA-Z0-9.-]+"
title="只能包含字母、数字、点号和短横线,且不能以点号或短横线开头或结尾">
<div class="form-text">多个DNS用逗号分隔如: example.com,www.example.com</div> <div class="form-text">多个DNS用逗号分隔如: example.com,www.example.com</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label for="san_ip" class="form-label">SAN IP (可选)</label> <label for="san_ip" class="form-label">SAN IP (可选)</label>
<input type="text" class="form-control" id="san_ip" name="san_ip"> <input type="text" class="form-control" id="common_name" name="common_name"
required pattern="[a-zA-Z0-9.-]+"
title="只能包含字母、数字、点号和短横线,且不能以点号或短横线开头或结尾">
<div class="form-text">多个IP用逗号分隔如: 192.168.1.1,10.0.0.1</div> <div class="form-text">多个IP用逗号分隔如: 192.168.1.1,10.0.0.1</div>
</div> </div>
</div> </div>