Files
toolbox/plugin/dev/csr/index.html
T
2023-11-24 22:45:30 +08:00

382 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{extend name="common/plugin_layout" /}
{block name="title"}{$plugin.title} - {:config_get('title')}{/block}
{block name="main"}
<style>
.query-title{text-align: right; }
@media (min-width: 576px) { .query-title{width: 150px;} }
</style>
<div class="container-xl" id="app">
<div class="col-sm-12 col-md-10 col-xl-8 center-block">
<div class="card card-preview">
<div class="card-inner mt-3">
<div class="nya-title nk-ibx-action-item progress-rating">
<span class="nk-menu-text font-weight-bold">CSR生成与查看</span>
</div>
<ul class="nav nav-tabs">
<li class="nav-item"><a class="nav-link" @click="show=0" :class="{'active':show===0}" href="javascript:">CSR查看</a></li>
<li class="nav-item"><a class="nav-link" @click="show=1" :class="{'active':show===1}" href="javascript:">CSR生成</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane" id="tabItem1" :class="{'active':show===0}">
<div class="form-group mt-3">
<div class="form-control-wrap">
<div class="custom-control custom-radio mr-3">
<input type="radio" class="custom-control-input" v-model="inputtype" id="customRadio1" :value="0">
<label class="custom-control-label" for="customRadio1">粘贴文件内容</label>
</div>
<div class="custom-control custom-radio mr-3">
<input type="radio" class="custom-control-input" v-model="inputtype" id="customRadio2" :value="1">
<label class="custom-control-label" for="customRadio2">上传文件</label>
</div>
</div>
</div>
<div class="form-group" v-show="inputtype==0">
<label class="form-label">CSR请求文件:</label>
<div class="form-control-wrap">
<textarea class="form-control" rows="6" v-model="csr" placeholder="-----BEGIN CERTIFICATE REQUEST-----"></textarea>
</div>
</div>
<div class="form-group" v-show="inputtype==1">
<label class="form-label">CSR请求文件:</label>
<div class="form-control-wrap">
<div class="custom-file">
<input type="file" class="custom-file-input" id="customFile1" accept=".csr" required @change="handleFile">
<label class="custom-file-label" for="customFile1">点击选择文件</label>
</div>
</div>
</div>
<button class="btn btn-dim btn-outline-primary btn-block card-link mb-3" @click="query">
查看CSR信息
</button>
</div>
<div class="tab-pane" id="tabItem2" :class="{'active':show===1}">
<h6 class="text-info">主题信息(以下均为选填)</h6>
<div class="row">
<div class="form-group col-md-4 col-6">
<label class="form-label">通用名(CN)</label>
<div class="form-control-wrap">
<input type="text" v-model="input.commonName" placeholder="站点的域名" class="form-control">
</div>
</div>
<div class="form-group col-md-4 col-6">
<label class="form-label">组织(O)</label>
<div class="form-control-wrap">
<input type="text" v-model="input.organizationName" placeholder="机构名称,例如:Tencent" class="form-control">
</div>
</div>
<div class="form-group col-md-4 col-6">
<label class="form-label">部门(OU)</label>
<div class="form-control-wrap">
<input type="text" v-model="input.organizationalUnitName" placeholder="部门名称" class="form-control">
</div>
</div>
<div class="form-group col-md-4 col-6">
<label class="form-label">国家(C)</label>
<div class="form-control-wrap">
<input type="text" v-model="input.countryName" placeholder="国家简称,例如:CN" class="form-control">
</div>
</div>
<div class="form-group col-md-4 col-6">
<label class="form-label">省份(S)</label>
<div class="form-control-wrap">
<input type="text" v-model="input.stateOrProvinceName" placeholder="省级地区,例如:Guangzhou" class="form-control">
</div>
</div>
<div class="form-group col-md-4 col-6">
<label class="form-label">城市(L)</label>
<div class="form-control-wrap">
<input type="text" v-model="input.localityName" placeholder="市级地区,例如:Shenzhen" class="form-control">
</div>
</div>
<div class="form-group col-md-8 col-6">
<label class="form-label">备用名</label>
<div class="form-control-wrap">
<input type="text" v-model="input.subjectAltName" placeholder="备用域名,多个用,分隔" class="form-control">
</div>
</div>
<div class="form-group col-md-4 col-6">
<label class="form-label">邮箱</label>
<div class="form-control-wrap">
<input type="text" v-model="input.emailAddress" placeholder="邮箱" class="form-control">
</div>
</div>
</div>
<h6 class="text-info">密钥信息</h6>
<div class="row">
<div class="form-group col-6">
<label class="form-label">密钥算法</label>
<select class="form-control" v-model="input.private_key_type">
<option v-for="v,index in private_key_type" :value="index">{{v}}</option>
</select>
</div>
<div class="form-group col-6" v-if="input.private_key_type!=3">
<label class="form-label">密钥长度</label>
<select class="form-control" v-model="input.private_key_bits">
<option v-for="v in private_key_bits" :value="v">{{v}} bit</option>
</select>
</div>
<div class="form-group col-6" v-if="input.private_key_type==3">
<label class="form-label">曲线名称</label>
<select class="form-control" v-model="input.curve_name">
<option v-for="v in curve_name" :value="v">{{v}}</option>
</select>
</div>
<div class="form-group col-6">
<label class="form-label">签名算法</label>
<select class="form-control" v-model="input.digest_alg">
<option v-for="v in digest_alg" :value="v">{{v}}</option>
</select>
</div>
<div class="form-group col-6">
<label class="form-label">私钥密码</label>
<div class="form-control-wrap">
<input type="text" v-model="input.pass_phrase" placeholder="可不设置" class="form-control">
</div>
</div>
</div>
<button class="btn btn-dim btn-outline-primary btn-block card-link mb-3" @click="generate">
生成CSR文件
</button>
</div>
</div>
</div>
</div>
<div class="card card-preview" v-show="show==0&&showresult" style="display:none">
<div class="card-inner mt-3">
<div class="nya-title nk-ibx-action-item progress-rating">
<span class="nk-menu-text font-weight-bold">查询结果</span>
</div>
<div class="table-responsive mb-2">
<table class="table table-hover table-bordered">
<tbody>
<tr v-for="(item,index) in info"><td class="query-title">{{index}}</td><td>{{item}}</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card card-preview" v-show="show==1&&showresult2" style="display:none">
<div class="card-inner mt-3">
<div class="nya-title nk-ibx-action-item progress-rating">
<span class="nk-menu-text font-weight-bold">生成结果</span>
</div>
<div class="table-responsive mb-2">
<div class="form-group">
<label class="form-label">CSRPEM格式) <em class="icon ni ni-download" title="下载保存" v-show="result.csr" @click="download('csr')"></em></label>
<div class="form-control-wrap">
<textarea class="form-control" rows="6" v-model="result.csr"></textarea>
</div>
</div>
<div class="d-flex flex-wrap">
<div class="form-group flex-fill mr-2">
<label class="form-label">公钥 <em class="icon ni ni-download" title="下载保存" v-show="result.public_key" @click="download('public_key')"></em></label>
<div class="form-control-wrap">
<textarea class="form-control" rows="8" v-model="result.public_key"></textarea>
</div>
</div>
<div class="form-group flex-fill">
<label class="form-label">私钥 <em class="icon ni ni-download" title="下载保存" v-show="result.private_key" @click="download('private_key')"></em></label>
<div class="form-control-wrap">
<textarea class="form-control" rows="8" v-model="result.private_key"></textarea>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">公私钥格式</label>
<select class="form-control" v-model="result.key_type">
<option value="pem">PEM格式</option>
<option value="base64">Base64无换行</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
{/block}
{block name="script"}
<script src="{$cdn_cdnjs}vue/2.6.14/vue.min.js"></script>
<script src="{$cdn_cdnjs}FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script>
function pemToBase64(pem) {
var lines = pem.split('\n');
var encoded = '';
for (var i = 0; i < lines.length; i++) {
if (lines[i].trim().length > 0 && lines[i].indexOf('-----BEGIN') === -1 && lines[i].indexOf('-----END') === -1) {
encoded += lines[i].trim();
}
}
return encoded;
}
function base64ToPem(base64Key, type) {
var pemHeader = '-----BEGIN ' + type + '-----\n';
var pemFooter = '\n-----END ' + type + '-----\n';
var chunkSize = 64;
var base64KeyWithBreaks = base64Key.match(new RegExp('.{1,' + chunkSize + '}', 'g')).join('\n');
var pemKey = pemHeader + base64KeyWithBreaks + pemFooter;
return pemKey;
}
new Vue({
el: '#app',
data: {
show: 0,
inputtype: 0,
csr: '',
showresult: false,
showresult2: false,
info: [],
private_key_type: [
'RSA',
'DSA',
'DH',
'ECC'
],
private_key_bits: [512,1024,2048,4096],
curve_name: [
'prime256v1',
'secp256k1',
'secp384r1',
'secp521r1',
],
digest_alg: [
'md5',
'sha1',
'sha224',
'sha256',
'sha384',
'sha512',
],
input: {
commonName: '',
countryName: '',
stateOrProvinceName: '',
localityName: '',
organizationName: '',
organizationalUnitName: '',
subjectAltName: '',
emailAddress: '',
private_key_type: 0,
private_key_bits: 2048,
curve_name: 'prime256v1',
digest_alg: 'sha256',
pass_phrase: '',
},
result:{
csr: '',
key_type: 'pem',
public_key: '',
private_key: '',
}
},
watch: {
"input.private_key_type"(newVal) {
if(newVal == 2){
this.private_key_bits = [512,1024,2048]
}else{
this.private_key_bits = [512,1024,2048,4096]
}
},
"result.key_type"(newVal) {
if(this.result.public_key != ''){
if(newVal == 'pem'){
this.result.public_key = base64ToPem(this.result.public_key, 'PUBLIC KEY')
}else{
this.result.public_key = pemToBase64(this.result.public_key)
}
}
if(this.result.private_key != ''){
if(newVal == 'pem'){
this.result.private_key = base64ToPem(this.result.private_key, this.input.pass_phrase ? 'ENCRYPTED PRIVATE KEY' : 'PRIVATE KEY')
}else{
this.result.private_key = pemToBase64(this.result.private_key)
}
}
},
},
methods: {
handleFile(event) {
var file = event.target.files[0];
layer.load(0, {shade:0.1});
var that = this;
that.csr = '';
var reader = new FileReader();
reader.onload = () => {
layer.closeAll();
var csr = reader.result;
if(csr.indexOf('BEGIN CERTIFICATE REQUEST') == -1){
layer.alert('证书文件错误', {icon: 5});return;
}
that.csr = csr;
};
reader.onerror = () => {
layer.closeAll();
layer.alert('证书文件读取失败', {icon: 5});return;
};
reader.readAsText(file);
},
query() {
if(this.csr == ''){
layer.alert('证书内容不能为空');return;
}
layer.load(0, {shade:0.1});
var that=this;
$.ajax({
url: '/api/{$plugin.alias}/query',
type: 'post',
dataType: 'json',
data: {csr: that.csr},
cache: false,
success: function (data) {
layer.closeAll();
if(data.status=='ok'){
that.info = data.data.info;
that.showresult = true;
}else{
layer.alert(data.message, {icon: 5});
}
},
error: function () {
layer.closeAll();
layer.msg('服务器错误', {icon: 5});
}
});
},
generate() {
layer.load(0, {shade:0.1});
var that=this;
$.ajax({
url: '/api/{$plugin.alias}/generate',
type: 'post',
dataType: 'json',
data: {...that.input},
cache: false,
success: function (data) {
layer.closeAll();
if(data.status=='ok'){
that.result.csr = data.data.csr;
that.result.public_key = data.data.public_key;
that.result.private_key = data.data.private_key;
that.showresult2 = true;
}else{
layer.alert(data.message, {icon: 5});
}
},
error: function () {
layer.closeAll();
layer.msg('服务器错误', {icon: 5});
}
});
},
download(name) {
var content = this.result[name]
if(!content) return;
var ext = this.result.key_type == 'base64' ? '.txt' : '.pem';
if(name == 'csr') ext = '.csr';
var filename = name + ext
var blob = new Blob([content], {type: "application/force-download"});
saveAs(blob, filename);
}
},
})
</script>
{/block}