feat: replace x509_certificate with SSL Certificate DocType

- Rename x509_certificate → SSL Certificate (DocType, table, files)
- Rename child tables to SSL Certificate Domain/Installation
- ZIP import: extracts .crt.pem, .key.pem, .ca.pem, .csr.pem
- Auto-parse: CN, issuer, SANs, validity, wildcard, cert type
- Private key stored encrypted (Password field)
- Download buttons: cert, key, fullchain, cert+key, CA, CSR
- Copy-to-clipboard with HTTP fallback
- Certificate details as HTML table
- Key verification (signature check) and CA chain validation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Malinowski
2026-04-12 21:05:58 +02:00
co-authored by Claude Opus 4.6
parent 7f5f7a5236
commit 549963affd
13 changed files with 766 additions and 263 deletions
@@ -0,0 +1,112 @@
function copy_to_clipboard(text) {
if (navigator.clipboard && window.isSecureContext) {
return navigator.clipboard.writeText(text);
}
// Fallback for non-HTTPS contexts
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
return Promise.resolve();
}
frappe.ui.form.on("SSL Certificate", {
refresh(frm) {
if (!frm.is_new() && frm.doc.certificate_data) {
frm.trigger("render_downloads");
}
},
render_downloads(frm) {
const cn = frm.doc.common_name || frm.doc.name;
const has_key = !!frm.doc.private_key;
const has_ca = !!frm.doc.ca_chain;
const has_csr = !!frm.doc.csr_data;
const btn = (label, icon, format_type, disabled) => {
const cls = disabled ? "btn-default disabled" : "btn-primary-light";
const title = disabled ? 'title="Nicht vorhanden"' : "";
return `<button class="btn btn-sm ${cls} mr-2 mb-2" ${title}
data-format="${format_type}" ${disabled ? "disabled" : ""}>
<i class="fa fa-${icon}"></i> ${label}
</button>`;
};
const copy_btn = (label, format_type, disabled) => {
const cls = disabled ? "btn-default disabled" : "btn-default";
const title = disabled ? 'title="Nicht vorhanden"' : "";
return `<button class="btn btn-sm ${cls} mr-2 mb-2 copy-btn" ${title}
data-format="${format_type}" ${disabled ? "disabled" : ""}>
<i class="fa fa-clipboard"></i> ${label}
</button>`;
};
const html = `
<div>
<div class="mb-3">
<strong class="text-muted d-block mb-2">Download</strong>
${btn("Certificate", "certificate", "cert", false)}
${btn("Private Key", "key", "key", !has_key)}
${btn("Fullchain", "link", "fullchain", false)}
${btn("Cert + Key", "files-o", "cert_key", !has_key)}
${btn("CA Chain", "sitemap", "ca", !has_ca)}
${btn("CSR", "file-text-o", "csr", !has_csr)}
</div>
<div>
<strong class="text-muted d-block mb-2">Copy to Clipboard</strong>
${copy_btn("Certificate", "cert", false)}
${copy_btn("Private Key", "key", !has_key)}
${copy_btn("Fullchain", "fullchain", false)}
${copy_btn("CA Chain", "ca", !has_ca)}
${copy_btn("CSR", "csr", !has_csr)}
</div>
</div>`;
frm.fields_dict.downloads_html.$wrapper.html(html);
// Download handlers
frm.fields_dict.downloads_html.$wrapper.find("button[data-format]:not(.copy-btn):not(:disabled)").on("click", function () {
const format_type = $(this).data("format");
frappe.call({
method: "msp.msp.doctype.ssl_certificate.ssl_certificate.get_certificate_pem",
args: { name: frm.doc.name, format_type },
callback(r) {
if (r.message) {
const blob = new Blob([r.message.content], { type: "application/x-pem-file" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = r.message.filename;
a.click();
URL.revokeObjectURL(url);
frappe.show_alert({ message: `${r.message.filename} heruntergeladen`, indicator: "green" });
}
},
});
});
// Copy handlers
frm.fields_dict.downloads_html.$wrapper.find(".copy-btn:not(:disabled)").on("click", function () {
const format_type = $(this).data("format");
const btn_el = $(this);
frappe.call({
method: "msp.msp.doctype.ssl_certificate.ssl_certificate.get_certificate_pem",
args: { name: frm.doc.name, format_type },
callback(r) {
if (r.message) {
copy_to_clipboard(r.message.content).then(() => {
const orig = btn_el.html();
btn_el.html('<i class="fa fa-check"></i> Copied!');
setTimeout(() => btn_el.html(orig), 2000);
frappe.show_alert({ message: "In Zwischenablage kopiert", indicator: "green" });
});
}
},
});
});
},
});
@@ -0,0 +1,330 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-06-04 14:19:04.168850",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"certificate_name",
"customer",
"it_landscape",
"column_break_meta",
"common_name",
"issuer",
"certificate_type",
"is_wildcard",
"status",
"validity_section",
"not_valid_before",
"column_break_validity",
"not_valid_after",
"renewal_section",
"auto_renew",
"renewal_method",
"import_section",
"attach_zip",
"import_html",
"certificate_pem_section",
"certificate_data",
"ca_chain",
"column_break_pem",
"private_key",
"csr_data",
"verification_section",
"private_key_verification",
"column_break_verify",
"ca_verification",
"certificate_info_section",
"certificate_information",
"column_break_info",
"ca_information",
"downloads_section",
"downloads_html",
"domains_section",
"domains",
"installations_section",
"installations"
],
"fields": [
{
"fieldname": "certificate_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Certificate Name",
"read_only": 1
},
{
"fieldname": "customer",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Customer",
"options": "Customer"
},
{
"fieldname": "it_landscape",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "IT Landscape",
"options": "IT Landscape"
},
{
"fieldname": "column_break_meta",
"fieldtype": "Column Break"
},
{
"fieldname": "common_name",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Common Name (CN)",
"read_only": 1
},
{
"fieldname": "issuer",
"fieldtype": "Data",
"label": "Issuer",
"read_only": 1
},
{
"fieldname": "certificate_type",
"fieldtype": "Select",
"in_standard_filter": 1,
"label": "Certificate Type",
"options": "\nDV\nOV\nEV\nSelf-Signed\nInternal CA"
},
{
"default": "0",
"fieldname": "is_wildcard",
"fieldtype": "Check",
"label": "Is Wildcard",
"read_only": 1
},
{
"default": "Active",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status",
"options": "Active\nExpired\nRevoked\nPending"
},
{
"fieldname": "validity_section",
"fieldtype": "Section Break",
"label": "Validity"
},
{
"fieldname": "not_valid_before",
"fieldtype": "Datetime",
"label": "Not Valid Before",
"read_only": 1
},
{
"fieldname": "column_break_validity",
"fieldtype": "Column Break"
},
{
"fieldname": "not_valid_after",
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "Not Valid After",
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "renewal_section",
"fieldtype": "Section Break",
"label": "Renewal"
},
{
"default": "0",
"fieldname": "auto_renew",
"fieldtype": "Check",
"label": "Auto Renew"
},
{
"fieldname": "renewal_method",
"fieldtype": "Select",
"label": "Renewal Method",
"options": "\nACME\nManual\nManaged"
},
{
"fieldname": "import_section",
"fieldtype": "Section Break",
"label": "Import"
},
{
"description": "ZIP-Datei mit .crt.pem, .key.pem, .ca.pem, .csr.pem, .fullchain.pem hochladen",
"fieldname": "attach_zip",
"fieldtype": "Attach",
"label": "Certificate ZIP"
},
{
"fieldname": "import_html",
"fieldtype": "HTML"
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:!doc.certificate_data",
"fieldname": "certificate_pem_section",
"fieldtype": "Section Break",
"label": "PEM Data"
},
{
"fieldname": "certificate_data",
"fieldtype": "Code",
"label": "Certificate (PEM)",
"options": "PEM"
},
{
"fieldname": "ca_chain",
"fieldtype": "Code",
"label": "CA Chain (PEM)",
"options": "PEM"
},
{
"fieldname": "column_break_pem",
"fieldtype": "Column Break"
},
{
"fieldname": "private_key",
"fieldtype": "Password",
"label": "Private Key (PEM)"
},
{
"fieldname": "csr_data",
"fieldtype": "Code",
"label": "CSR (PEM)",
"options": "PEM"
},
{
"collapsible": 1,
"depends_on": "eval:doc.certificate_data",
"fieldname": "verification_section",
"fieldtype": "Section Break",
"label": "Verification"
},
{
"fieldname": "private_key_verification",
"fieldtype": "Data",
"label": "Private Key Match",
"read_only": 1
},
{
"fieldname": "column_break_verify",
"fieldtype": "Column Break"
},
{
"fieldname": "ca_verification",
"fieldtype": "Data",
"label": "CA Chain Valid",
"read_only": 1
},
{
"collapsible": 1,
"depends_on": "eval:doc.certificate_data",
"fieldname": "certificate_info_section",
"fieldtype": "Section Break",
"label": "Certificate Details"
},
{
"fieldname": "certificate_information",
"fieldtype": "HTML",
"label": "Certificate Information",
"read_only": 1
},
{
"fieldname": "column_break_info",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.ca_chain",
"fieldname": "ca_information",
"fieldtype": "HTML",
"label": "CA Information",
"read_only": 1
},
{
"depends_on": "eval:doc.certificate_data",
"fieldname": "downloads_section",
"fieldtype": "Section Break",
"label": "Downloads"
},
{
"fieldname": "downloads_html",
"fieldtype": "HTML",
"label": "Downloads"
},
{
"fieldname": "domains_section",
"fieldtype": "Section Break",
"label": "SAN Domains"
},
{
"fieldname": "domains",
"fieldtype": "Table",
"label": "Domains",
"options": "SSL Certificate Domain"
},
{
"fieldname": "installations_section",
"fieldtype": "Section Break",
"label": "Installations"
},
{
"fieldname": "installations",
"fieldtype": "Table",
"label": "Installations",
"options": "SSL Certificate Installation"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-04-10 00:00:00.000000",
"modified_by": "Administrator",
"module": "MSP",
"name": "SSL Certificate",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "MSP User",
"share": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "MSP Admin",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "common_name",
"show_title_field_in_link": 1
}
@@ -0,0 +1,238 @@
import frappe
import zipfile
import os
import datetime
from frappe.model.document import Document
from frappe.utils import get_files_path
from cryptography import x509
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
class SSLCertificate(Document):
def before_save(self):
if self.attach_zip and self.has_value_changed("attach_zip"):
self._import_from_zip()
if self.certificate_data:
self._parse_certificate()
if self.ca_chain:
self._parse_ca()
if self.certificate_data and self.get_password("private_key"):
self._verify_private_key()
def after_insert(self):
if self.common_name and not self.certificate_name:
self._set_auto_name()
def _import_from_zip(self):
zip_path = self._resolve_file_path(self.attach_zip)
if not zip_path or not os.path.exists(zip_path):
frappe.throw("ZIP-Datei nicht gefunden")
with zipfile.ZipFile(zip_path, "r") as zf:
for info in zf.infolist():
name = info.filename
content = zf.read(name).decode("utf-8").strip()
if name.endswith(".crt.pem") and not name.endswith(".fullchain.pem"):
self.certificate_data = content
elif name.endswith(".key.pem"):
self.private_key = content
elif name.endswith(".ca.pem"):
self.ca_chain = content
elif name.endswith(".csr.pem"):
self.csr_data = content
def _parse_certificate(self):
try:
cert = x509.load_pem_x509_certificate(self.certificate_data.encode())
except Exception as e:
frappe.throw(f"Zertifikat konnte nicht gelesen werden: {e}")
self.common_name = self._get_attr(cert.subject, x509.NameOID.COMMON_NAME)
self.issuer = self._get_attr(cert.issuer, x509.NameOID.COMMON_NAME)
self.not_valid_before = cert.not_valid_before_utc.strftime("%Y-%m-%d %H:%M:%S")
self.not_valid_after = cert.not_valid_after_utc.strftime("%Y-%m-%d %H:%M:%S")
self.is_wildcard = 1 if self.common_name and self.common_name.startswith("*.") else 0
if not self.certificate_name:
exp = cert.not_valid_after_utc.strftime("%Y-%m")
self.certificate_name = f"{self.common_name} ({exp})"
# Auto-set status based on expiry
now_utc = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
if cert.not_valid_before_utc.replace(tzinfo=None) > now_utc:
self.status = "Pending"
elif cert.not_valid_after_utc.replace(tzinfo=None) < now_utc:
self.status = "Expired"
# Auto-detect certificate type from issuer
if not self.certificate_type:
issuer_org = self._get_attr(cert.issuer, x509.NameOID.ORGANIZATION_NAME) or ""
if self.common_name and self._get_attr(cert.issuer, x509.NameOID.COMMON_NAME) == self.common_name:
self.certificate_type = "Self-Signed"
elif "Let's Encrypt" in issuer_org or "ZeroSSL" in issuer_org or "Sectigo" in issuer_org:
self.certificate_type = "DV"
# Populate SAN domains
self._populate_san_domains(cert)
# Build certificate info HTML
self._build_cert_info_html(cert)
def _populate_san_domains(self, cert):
try:
ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
san_names = ext.value.get_values_for_type(x509.DNSName)
except x509.ExtensionNotFound:
san_names = []
if self.common_name and self.common_name not in san_names:
san_names.insert(0, self.common_name)
existing = {row.domain_name for row in self.domains}
for domain_name in san_names:
if domain_name not in existing:
# Try to find matching IT Domain
it_domain = frappe.db.exists("IT Domain", domain_name.lstrip("*."))
self.append("domains", {
"domain": it_domain or None,
"domain_name": domain_name,
})
def _build_cert_info_html(self, cert):
subject = cert.subject
cn = self._get_attr(subject, x509.NameOID.COMMON_NAME)
org = self._get_attr(subject, x509.NameOID.ORGANIZATION_NAME)
country = self._get_attr(subject, x509.NameOID.COUNTRY_NAME)
state = self._get_attr(subject, x509.NameOID.STATE_OR_PROVINCE_NAME)
try:
ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
sans = ext.value.get_values_for_type(x509.DNSName)
except x509.ExtensionNotFound:
sans = []
serial_hex = format(cert.serial_number, "X")
serial_formatted = ":".join(serial_hex[i:i+2] for i in range(0, len(serial_hex), 2))
self.certificate_information = f"""
<table class="table table-bordered table-sm" style="margin:0">
<tr><td style="width:180px"><strong>Common Name</strong></td><td>{cn or 'N/A'}</td></tr>
<tr><td><strong>Organization</strong></td><td>{org or 'N/A'}</td></tr>
<tr><td><strong>Country / State</strong></td><td>{country or 'N/A'} / {state or 'N/A'}</td></tr>
<tr><td><strong>Issuer</strong></td><td>{self._get_attr(cert.issuer, x509.NameOID.COMMON_NAME) or 'N/A'}</td></tr>
<tr><td><strong>SANs</strong></td><td>{', '.join(sans) if sans else 'None'}</td></tr>
<tr><td><strong>Serial</strong></td><td><code style="font-size:11px">{serial_formatted}</code></td></tr>
<tr><td><strong>Signature</strong></td><td>{cert.signature_hash_algorithm.name if cert.signature_hash_algorithm else 'N/A'}</td></tr>
</table>"""
def _parse_ca(self):
try:
ca_cert = x509.load_pem_x509_certificate(self.ca_chain.encode())
except Exception:
self.ca_verification = "CA Chain nicht lesbar"
return
ca_cn = self._get_attr(ca_cert.subject, x509.NameOID.COMMON_NAME)
ca_org = self._get_attr(ca_cert.issuer, x509.NameOID.ORGANIZATION_NAME)
# Verify CA signed the certificate
if self.certificate_data:
cert = x509.load_pem_x509_certificate(self.certificate_data.encode())
cert_issuer_cn = self._get_attr(cert.issuer, x509.NameOID.COMMON_NAME)
ca_subject_cn = self._get_attr(ca_cert.subject, x509.NameOID.COMMON_NAME)
if cert_issuer_cn == ca_subject_cn:
self.ca_verification = "CA Chain valid"
else:
self.ca_verification = f"Mismatch: Cert Issuer={cert_issuer_cn}, CA CN={ca_subject_cn}"
self.ca_information = f"""
<table class="table table-bordered table-sm" style="margin:0">
<tr><td style="width:180px"><strong>CA Common Name</strong></td><td>{ca_cn or 'N/A'}</td></tr>
<tr><td><strong>CA Issuer</strong></td><td>{ca_org or 'N/A'}</td></tr>
<tr><td><strong>Valid Until</strong></td><td>{ca_cert.not_valid_after_utc}</td></tr>
</table>"""
def _verify_private_key(self):
try:
cert = x509.load_pem_x509_certificate(self.certificate_data.encode())
key_pem = self.get_password("private_key")
private_key = serialization.load_pem_private_key(key_pem.encode(), password=None)
message = b"x509_verify"
signature = private_key.sign(message, PKCS1v15(), hashes.SHA256())
cert.public_key().verify(signature, message, PKCS1v15(), hashes.SHA256())
self.private_key_verification = "Key matches certificate"
except Exception as e:
self.private_key_verification = f"Key mismatch: {e}"
def _set_auto_name(self):
if self.not_valid_after:
dt = frappe.utils.get_datetime(self.not_valid_after)
new_name = f"{self.common_name}_{dt.strftime('%Y-%m-%d')}"
else:
new_name = self.common_name
if new_name != self.name:
frappe.rename_doc("SSL Certificate", self.name, new_name, force=True)
@staticmethod
def _get_attr(name_obj, oid):
try:
return name_obj.get_attributes_for_oid(oid)[0].value
except (IndexError, Exception):
return None
@staticmethod
def _resolve_file_path(file_url):
if not file_url:
return None
if file_url.startswith("/private/files/"):
return get_files_path(*file_url.split("/private/files/", 1)[1].split("/"), is_private=1)
elif file_url.startswith("/files/"):
return get_files_path(*file_url.split("/files/", 1)[1].split("/"))
return None
@frappe.whitelist()
def get_certificate_pem(name, format_type):
"""Return PEM data for download. format_type: cert, key, ca, csr, fullchain, cert_key"""
doc = frappe.get_doc("SSL Certificate", name)
doc.check_permission("read")
if format_type == "cert":
return {"content": doc.certificate_data, "filename": f"{doc.common_name or name}.crt.pem"}
elif format_type == "key":
key = doc.get_password("private_key")
if not key:
frappe.throw("Kein Private Key vorhanden")
return {"content": key, "filename": f"{doc.common_name or name}.key.pem"}
elif format_type == "ca":
if not doc.ca_chain:
frappe.throw("Keine CA Chain vorhanden")
return {"content": doc.ca_chain, "filename": f"{doc.common_name or name}.ca.pem"}
elif format_type == "csr":
if not doc.csr_data:
frappe.throw("Kein CSR vorhanden")
return {"content": doc.csr_data, "filename": f"{doc.common_name or name}.csr.pem"}
elif format_type == "fullchain":
parts = [doc.certificate_data]
if doc.ca_chain:
parts.append(doc.ca_chain)
return {"content": "\n".join(parts), "filename": f"{doc.common_name or name}.fullchain.pem"}
elif format_type == "cert_key":
key = doc.get_password("private_key")
if not key:
frappe.throw("Kein Private Key vorhanden")
return {"content": doc.certificate_data + "\n" + key, "filename": f"{doc.common_name or name}.combined.pem"}
frappe.throw(f"Unbekanntes Format: {format_type}")
@@ -0,0 +1,36 @@
{
"actions": [],
"creation": "2026-04-09 00:00:00.000000",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"domain",
"domain_name"
],
"fields": [
{
"fieldname": "domain",
"fieldtype": "Link",
"in_list_view": 1,
"label": "IT Domain",
"options": "IT Domain"
},
{
"description": "Fallback fuer externe Domains ohne IT Domain-Eintrag",
"fieldname": "domain_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Domain Name"
}
],
"istable": 1,
"links": [],
"modified": "2026-04-09 00:00:00.000000",
"modified_by": "Administrator",
"module": "MSP",
"name": "SSL Certificate Domain",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}
@@ -1,5 +1,6 @@
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
class x509_certificate(Document):
class SSLCertificateDomain(Document):
pass pass
@@ -0,0 +1,42 @@
{
"actions": [],
"creation": "2026-04-09 00:00:00.000000",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"it_object",
"service",
"path"
],
"fields": [
{
"fieldname": "it_object",
"fieldtype": "Link",
"in_list_view": 1,
"label": "IT Object",
"options": "IT Object"
},
{
"fieldname": "service",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Service"
},
{
"fieldname": "path",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Path"
}
],
"istable": 1,
"links": [],
"modified": "2026-04-09 00:00:00.000000",
"modified_by": "Administrator",
"module": "MSP",
"name": "SSL Certificate Installation",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}
@@ -0,0 +1,6 @@
import frappe
from frappe.model.document import Document
class SSLCertificateInstallation(Document):
pass
@@ -1,129 +0,0 @@
// Copyright (c) 2024, itsdave GmbH and Contributors
// For license information, please see license.txt
//-------------------------------------------------------------------------------------------------------------------------------
frappe.ui.form.on('x509_certificate', {
after_save: function(frm) {
//loadCertificateInformation(frm);
}
});
// Function to load certificate information
function loadCertificateInformation(frm) {
// Extract and attach ZIP files first
frappe.call({
method: 'msp.msp.doctype.x509_certificate.x509_certificate.extract_and_read_zip_file',
args: {
docname: frm.doc.name
},
callback: function(response) {
if (response.message && response.message.success) {
// Refresh the certificate_data and private_key fields
frm.set_value('certificate_data');
frm.set_value('private_key');
// Refresh the form to show updated values
frm.refresh_field('certificate_data');
frm.refresh_field('private_key');
}
// Proceed to load certificate information
frappe.call({
method: 'msp.msp.doctype.x509_certificate.x509_certificate.read_cert_data',
args: {
certificate_name: frm.doc.name,
doc: frm.doc
},
callback: function(response) {
if (response.message) {
if (response.message.error) {
if (response.message.error === 'Invalid certificate format') {
frappe.msgprint("Invalid Certificate Data");
} else {
frappe.msgprint(response.message.error);
}
return;
}
var parsedSubject = response.message.subject;
var parsedIssuer = parseIssuerInfo(response.message.issuer);
var certificateInfo = "<strong>Subject:</strong><br>" +
"- <strong>Common Name:</strong> " + (parsedSubject.common_name || 'N/A') + "<br>" +
"- <strong>Country:</strong> " + (parsedSubject.country || 'N/A') + "<br>" +
"- <strong>State/Province:</strong> " + (parsedSubject.state || 'N/A') + "<br>" +
"- <strong>City:</strong> " + (parsedSubject.city || 'N/A') + "<br>" +
"- <strong>Organization:</strong> " + (parsedSubject.organization || 'N/A') + "<br>" +
"- <strong>Organizational Unit:</strong> " + (parsedSubject.organization_unit || 'N/A') + "<br>" +
"<strong>Issuer:</strong> " + (parsedIssuer.commonName || 'N/A') + "<br>" +
"<strong>Subject Alternative Name:</strong> " + (response.message.subject_alt_names || 'N/A') + "<br>" +
"<strong>Validity Period:</strong><br>" +
"- <strong>Not Before:</strong> " + response.message.not_valid_before + "<br>" +
"- <strong>Not After:</strong> " + response.message.not_valid_after + "<br>" +
"<strong>Serial Number:</strong> " + response.message.serial_number;
frappe.msgprint("Setting information field with: " + frm.doc.name + "<br>" + certificateInfo);
frm.set_value('certificate_information', certificateInfo);
// Check if private key is empty
if (!frm.doc.private_key) {
frappe.msgprint("Private Key is empty");
} else {
// Check if private key is valid
frappe.msgprint(response.message.private_key_valid ? "Private Key is correct" : "Private Key is not valid");
}
// Set the create_date and expiry_date fields
frm.set_value('not_valid_before', response.message.not_valid_before);
frm.set_value('not_valid_after', response.message.not_valid_after);
// Refresh the form to show updated values
frm.refresh_field('not_valid_before');
frm.refresh_field('not_valid_after');
frm.refresh_field('certificate_information');
frm.refresh_field('certificate_data');
frm.refresh_field('private_key');
} else {
frappe.msgprint("Can't fetch certificate information.");
}
}
});
}
});
}
function parseSubjectInfo(subjectInfo) {
if (subjectInfo) {
// Regular expression to extract Common Name (CN) from subject
var parenthesesPattern = /CN=([^,]+)/;
var cnMatch = subjectInfo.match(parenthesesPattern);
var commonName = cnMatch ? cnMatch[1] : null;
return {
commonName: commonName
};
}
return null;
}
function parseIssuerInfo(issuerInfo) {
if (issuerInfo) {
var parenthesesPattern = /CN=([^,]+)/;
var cnMatch = issuerInfo.match(parenthesesPattern);
var commonName = cnMatch ? cnMatch[1] : null;
return {
commonName: commonName
};
}
return null;
}
// Function to format Subject Alternative Name
function formatSubjectAltName(subjectAltName) {
if (subjectAltName) {
return subjectAltName !== 'No more' ? subjectAltName : 'No hay nombres alternativos';
}
return 'No hay nombres alternativos';
}
@@ -1,133 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-06-04 14:19:04.168850",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"certificate_name",
"attach_zip",
"attach_certificate_copy",
"attach_key",
"certificate_data",
"private_key",
"private_key_verification",
"ca_label_content",
"ca_verification",
"not_valid_before",
"not_valid_after",
"certificate_information",
"ca_information"
],
"fields": [
{
"fieldname": "certificate_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Certificate Name"
},
{
"fieldname": "certificate_data",
"fieldtype": "Long Text",
"in_list_view": 1,
"label": "Certificate Data",
"read_only_depends_on": "eval:doc.attach_certificate_copy"
},
{
"fieldname": "private_key",
"fieldtype": "Long Text",
"in_list_view": 1,
"label": "Private Key",
"read_only_depends_on": "eval:doc.attach_key"
},
{
"fieldname": "not_valid_before",
"fieldtype": "Datetime",
"label": "Not Valid Before",
"read_only": 1
},
{
"fieldname": "not_valid_after",
"fieldtype": "Datetime",
"label": "Not Valid After",
"read_only": 1
},
{
"fieldname": "certificate_information",
"fieldtype": "Long Text",
"label": "Certificate Information",
"read_only": 1
},
{
"description": "Upload Key for PEM format Certificate",
"fieldname": "attach_key",
"fieldtype": "Attach",
"label": "Attach Key",
"read_only_depends_on": "eval:doc.attach_zip"
},
{
"description": "Upload Certificate in PEM or P12 Format",
"fieldname": "attach_certificate_copy",
"fieldtype": "Attach",
"label": "Attach Certificate Copy",
"read_only_depends_on": "eval:doc.attach_zip"
},
{
"fieldname": "attach_zip",
"fieldtype": "Attach",
"label": "zip"
},
{
"fieldname": "private_key_verification",
"fieldtype": "Data",
"label": "Private Key Verification",
"read_only": 1
},
{
"depends_on": "eval:doc.ca_label_content",
"fieldname": "ca_information",
"fieldtype": "Long Text",
"label": "CA Information",
"read_only": 1
},
{
"depends_on": "eval:doc.attach_zip",
"fieldname": "ca_label_content",
"fieldtype": "Long Text",
"label": "Certificate Authority",
"read_only_depends_on": "eval:doc.attach_zip"
},
{
"depends_on": "eval:doc.ca_label_content",
"fieldname": "ca_verification",
"fieldtype": "Data",
"label": "CA Verification",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-06-14 10:50:44.471107",
"modified_by": "Administrator",
"module": "MSP",
"name": "x509_certificate",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}