|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="utf-8" /> |
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1"> |
|
|
|
|
|
<link |
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" |
|
rel="stylesheet" |
|
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" |
|
crossorigin="anonymous" |
|
/> |
|
|
|
<script |
|
src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" |
|
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" |
|
crossorigin="anonymous" |
|
></script> |
|
|
|
<link rel="stylesheet" href="style.css" type="text/css" /> |
|
|
|
<script |
|
type="module" |
|
src="https://display.truepic.com/truepic_display.es.js" |
|
></script> |
|
|
|
<link rel="stylesheet" href="json_viewer.css" /> |
|
<script src="https://unpkg.com/@peculiar/x509"></script> |
|
<script type="text/javascript" src="json_viewer.js"></script> |
|
</head> |
|
<body> |
|
<div class="container-fluid mt-2" id="head"> |
|
<div class="container-xl"> |
|
<div class="row"> |
|
<div class="col position-relative"> |
|
<div class="row"> |
|
<div class="col"> |
|
<h1> |
|
Watermarked Content Credentials |
|
<div class="badge bg-secondary">experimental</div> |
|
</h1> |
|
<p> |
|
Generate images with C2PA content credentials and Steg.ai |
|
digital watermarking for enhanced AI label robustness |
|
</p> |
|
|
|
<div class="alert alert-primary d-md-none" role="alert"> |
|
<h4>Support our work!</h4> |
|
<p> |
|
<img src="images/heart_icon.svg" /> Like this Space in the nav |
|
above |
|
</p> |
|
<p> |
|
<img src="images/wave_icon.svg" /> Joining the conversation in |
|
the Community tab |
|
</p> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="d-flex flex-row position-absolute bottom-0 tabs"> |
|
<div id="generateTab" class="active"> |
|
<img src="images/generate_icon.svg" /> Generate |
|
</div> |
|
<div id="verifyTab"> |
|
<img src="images/verify_icon.svg" /> Verify |
|
</div> |
|
</div> |
|
</div> |
|
<div class="col right-column d-none d-md-block"> |
|
<div class="alert alert-primary" role="alert"> |
|
<h4>Support our work!</h4> |
|
<p> |
|
<img src="images/heart_icon.svg" /> Like this Space in the nav |
|
above |
|
</p> |
|
<p> |
|
<img src="images/wave_icon.svg" /> Joining the conversation in |
|
the Community tab |
|
</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="container-fluid" id="body"> |
|
<div class="container-xl"> |
|
<div class="row"> |
|
<div class="col" style="min-width: 0; padding-right: 0"> |
|
<div class="display-generate"> |
|
<div class="image-container"> |
|
<img src="images/placeholder.png" class="placeholder" /> |
|
<div class="spinner" style="display: none"> |
|
<img src="images/spinner.svg" /> |
|
<h3 class="mt-3">Generating image...</h3> |
|
<p class="small">In some cases, this may take 1-2 minutes.</p> |
|
</div> |
|
</div> |
|
|
|
<div class="alert alert-secondary action-menu" role="alert"> |
|
<div class="row"> |
|
<div class="col"> |
|
<p class="mb-0"> |
|
<strong |
|
>Download the signed, watermarked image to share |
|
it.</strong |
|
> |
|
</p> |
|
<p class="mb-0"> |
|
Use the verify tab two view the details for an image you |
|
have or that's been shared with you. |
|
</p> |
|
</div> |
|
<div class="col flex-grow-0"> |
|
<a id="download-button" class="btn btn-outline-primary" |
|
>Download image <img src="images/download_icon.svg" |
|
/></a> |
|
</div> |
|
<div class="col flex-grow-0"> |
|
<button |
|
type="button" |
|
id="goto-verify-button" |
|
class="btn btn-outline-primary" |
|
> |
|
Go to verify tab <img src="images/link_icon.svg" /> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="display-verify"> |
|
<div id="original-image"> |
|
<div class="row mt-5"> |
|
<div class="col mb-4"> |
|
<h2>Uploaded image</h2> |
|
</div> |
|
</div> |
|
<div class="row pb-4"> |
|
<div class="col flex-grow-0"> |
|
<img id="uploaded-image" class="thumbnail" /> |
|
</div> |
|
<div class="col"> |
|
<div id="content-crendentials-icon"></div> |
|
Content Credentials<br /> |
|
<div id="digital-watermark-icon"></div> |
|
Digital watermark |
|
</div> |
|
</div> |
|
</div> |
|
<div class="mt-3" id="resultLabel"> |
|
<h2 class="mb-4">Result</h2> |
|
<div |
|
class="alert alert-secondary" |
|
role="alert" |
|
id="verifyResultDescription" |
|
></div> |
|
</div> |
|
<div class="image-container"> |
|
<img src="images/placeholder.png" class="placeholder" /> |
|
<div class="spinner" style="display: none"> |
|
<img src="images/spinner.svg" /> |
|
<h3 class="mt-3">Verifying image...</h3> |
|
<p class="small">In some cases, this may take 1-2 minutes.</p> |
|
</div> |
|
</div> |
|
<div class="output-container mt-5" style="display: none"> |
|
<h3 class="mt-4 mb-4">Verification output</h3> |
|
<div id="verification-output"></div> |
|
|
|
<h3 class="mt-4 pt-4 mb-4" id="certificates-h3"> |
|
Certificates |
|
</h3> |
|
<div class="certificate" id="certificate-output"> |
|
<h4>Certificate chain</h4> |
|
<ul id="certificate-list"></ul> |
|
<h4>Details</h4> |
|
<div class="details"> |
|
<strong>Basic info</strong> |
|
<div> |
|
<label>Type</label> |
|
X.509 Certificate |
|
</div> |
|
<div> |
|
<label>Serial Number</label> |
|
<div class="serialNumber"></div> |
|
</div> |
|
<div> |
|
<label>Issued</label> |
|
<div class="issued"></div> |
|
</div> |
|
<div> |
|
<label>Expired</label> |
|
<div class="expired"></div> |
|
</div> |
|
<strong>Subject</strong> |
|
<div> |
|
<label>Common Name</label> |
|
<div class="subjectCommonName"></div> |
|
</div> |
|
<div> |
|
<label>Organization</label> |
|
<div class="subjectOrganization"></div> |
|
</div> |
|
<div> |
|
<label>Organization Unit</label> |
|
<div class="subjectOrganizationUnit"></div> |
|
</div> |
|
<div> |
|
<label>Country</label> |
|
<div class="subjectCountry"></div> |
|
</div> |
|
|
|
<strong>Issuer</strong> |
|
<div> |
|
<label>Common Name</label> |
|
<div class="issuerCommonName"></div> |
|
</div> |
|
<div> |
|
<label>Organization</label> |
|
<div class="issuerOrganization"></div> |
|
</div> |
|
<div> |
|
<label>Organization Unit</label> |
|
<div class="issuerOrganizationUnit"></div> |
|
</div> |
|
<div> |
|
<label>Country</label> |
|
<div class="issuerCountry"></div> |
|
</div> |
|
<strong>Public Key Info</strong> |
|
|
|
<div> |
|
<label>Algorithm</label> |
|
<div class="algorithm"></div> |
|
</div> |
|
<div id="modulusContainer"> |
|
<label>Modulus</label> |
|
<div class="modulus"></div> |
|
</div> |
|
<div id="curveContainer"> |
|
<label>Curve</label> |
|
<div class="namedCurve"></div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="col mt-4 pt-4" id="logo"> |
|
<img src="images/logo.png" /> |
|
<p class="small mt-3 mb-5"> |
|
This is a conceptual application meant for demonstration |
|
purposes only. |
|
</p> |
|
</div> |
|
</div> |
|
<div class="col right-column"> |
|
<div class="display-generate"> |
|
<form class="image-gen-form"> |
|
<div class="form-group mb-3"> |
|
<label for="prompt">Image prompt</label> |
|
<textarea id="prompt" class="form-control"></textarea> |
|
</div> |
|
|
|
<div class="form-group mb-3"> |
|
<label>Model</label> |
|
<div class="custom-select"> |
|
<select id="model" class="form-control"> |
|
<option disabled selected value>Select</option> |
|
<option value="runwayml/stable-diffusion-v1-5,1.5"> |
|
runwayml/stable-diffusion-v1-5 |
|
</option> |
|
<option value="CompVis/stable-diffusion-v1-4,1.4"> |
|
CompVis/stable-diffusion-v1-4 |
|
</option> |
|
<option value="stabilityai/stable-diffusion-2-1,2.1"> |
|
stabilityai/stable-diffusion-2-1 |
|
</option> |
|
</select> |
|
</div> |
|
</div> |
|
|
|
<div class="form-check mb-3"> |
|
<input |
|
class="form-check-input" |
|
type="checkbox" |
|
value="" |
|
id="generate-terms" |
|
/> |
|
<label class="form-check-label" for="defaultCheck1"> |
|
By using this demo you agree to the terms and conditions of |
|
<a |
|
href="https://truepic.com/terms-and-conditions/" |
|
target="_blank" |
|
>Truepic</a |
|
> |
|
and |
|
<a href="https://steg.ai/tos.html" target="_blank">Steg.ai</a> |
|
</label> |
|
</div> |
|
|
|
<button |
|
type="submit" |
|
class="btn btn-primary" |
|
id="generate-button" |
|
> |
|
Submit |
|
</button> |
|
|
|
<div class="error mt-3"> |
|
Something went wrong. Please try again. |
|
</div> |
|
</form> |
|
|
|
<div class="how-it-works"> |
|
<p><strong>How it works</strong></p> |
|
|
|
<p> |
|
When an image is generated and signed with C2PA Content |
|
Credentials, an imperceptible digital watermark, powered by |
|
Steg.ai, is also added to the image pixels. The watermark |
|
serves as a backup in case the Content Credentials are lost, |
|
such as when sharing the image between currently incompatible |
|
services like text messaging. By using the watermark, it is |
|
possible to retrieve a restored, signed version of the image |
|
from before the data was decoupled, which you can try in the |
|
verify tab. |
|
</p> |
|
</div> |
|
</div> |
|
<div class="display-verify"> |
|
<form class="verify-upload-form" enctype="multipart/form-data"> |
|
<div class="form-group mb-3"> |
|
<label>Upload image</label> |
|
|
|
<input |
|
type="file" |
|
class="form-control" |
|
name="fileUpload" |
|
id="fileUpload" |
|
/> |
|
</div> |
|
|
|
<div class="form-check mb-3"> |
|
<input |
|
class="form-check-input" |
|
type="checkbox" |
|
value="" |
|
id="verify-terms" |
|
/> |
|
<label class="form-check-label" for="defaultCheck1"> |
|
By using this demo you agree to the terms and conditions of |
|
<a |
|
href="https://truepic.com/terms-and-conditions/" |
|
target="_blank" |
|
>Truepic</a |
|
> |
|
and |
|
<a href="https://steg.ai/tos.html" target="_blank">Steg.ai</a> |
|
</label> |
|
</div> |
|
|
|
<button |
|
type="submit" |
|
class="btn btn-primary" |
|
id="verify-button" |
|
> |
|
Submit |
|
</button> |
|
|
|
<div class="error mt-3"> |
|
Something went wrong. Please try again. |
|
</div> |
|
</form> |
|
<div class="how-it-works"> |
|
<p><strong>How it works</strong></p> |
|
|
|
<p> |
|
When an image is generated and signed with C2PA Content |
|
Credentials, an imperceptible digital watermark, powered by |
|
Steg.ai, is also added to the image pixels. The watermark |
|
serves as a backup in case the Content Credentials are lost, |
|
such as when sharing the image between currently incompatible |
|
services like text messaging. By using the watermark, it is |
|
possible to retrieve a restored, signed version of the image |
|
from before the data was decoupled, which you can try in the |
|
verify tab. |
|
</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</body> |
|
|
|
<script> |
|
const generateTab = document.querySelector("#generateTab"); |
|
const verifyTab = document.querySelector("#verifyTab"); |
|
const displayVerify = document.querySelectorAll(".display-verify"); |
|
const displayGenerate = document.querySelectorAll(".display-generate"); |
|
const uploadForm = document.querySelector(".verify-upload-form"); |
|
const imageGenForm = document.querySelector(".image-gen-form"); |
|
const imagePrompt = document.getElementById("prompt"); |
|
const model = document.getElementById("model"); |
|
const generateTerms = document.getElementById("generate-terms"); |
|
const generateButton = document.getElementById("generate-button"); |
|
|
|
const fileUpload = document.getElementById("fileUpload"); |
|
const verifyButton = document.getElementById("verify-button"); |
|
const verifyTerms = document.getElementById("verify-terms"); |
|
|
|
const generateImageContainer = document.querySelector( |
|
".display-generate .image-container" |
|
); |
|
const generateActionMenu = document.querySelector(".action-menu"); |
|
const verifyImageContainer = document.querySelector( |
|
".display-verify .image-container" |
|
); |
|
const uploadedImageContainer = document.querySelector("#original-image"); |
|
const downloadButton = document.getElementById("download-button"); |
|
const verifyResultDescription = document.getElementById( |
|
"verifyResultDescription" |
|
); |
|
const verificationOutput = document.getElementById("verification-output"); |
|
const certificateList = document.getElementById("certificate-list"); |
|
const outputContainer = document.querySelector(".output-container"); |
|
const contentCredentialsIcon = document.getElementById( |
|
"content-crendentials-icon" |
|
); |
|
const digitalWatermarkIcon = document.getElementById( |
|
"digital-watermark-icon" |
|
); |
|
|
|
var certificates = []; |
|
|
|
[imagePrompt, model, generateTerms].forEach((item) => { |
|
item.addEventListener("change", async (event) => { |
|
setGenerateButtonStatus(); |
|
}); |
|
}); |
|
|
|
const setGenerateButtonStatus = () => { |
|
if (imagePrompt.value && model.value && generateTerms.checked) |
|
generateButton.classList.add("active"); |
|
else generateButton.classList.remove("active"); |
|
}; |
|
|
|
[fileUpload, verifyTerms].forEach((item) => { |
|
item.addEventListener("change", async (event) => { |
|
setVerifyButtonStatus(); |
|
}); |
|
}); |
|
|
|
const setVerifyButtonStatus = () => { |
|
if (fileUpload.value && verifyTerms.checked) |
|
verifyButton.classList.add("active"); |
|
else verifyButton.classList.remove("active"); |
|
}; |
|
|
|
generateTab.addEventListener("click", (event) => { |
|
event.target.classList.add("active"); |
|
verifyTab.classList.remove("active"); |
|
|
|
setGenerateElementsDisplay("block"); |
|
setVerifyElementsDisplay("none"); |
|
}); |
|
|
|
verifyTab.addEventListener("click", (event) => { |
|
event.target.classList.add("active"); |
|
generateTab.classList.remove("active"); |
|
|
|
setGenerateElementsDisplay("none"); |
|
setVerifyElementsDisplay("block"); |
|
}); |
|
|
|
document |
|
.getElementById("goto-verify-button") |
|
.addEventListener("click", (event) => { |
|
verifyTab.classList.add("active"); |
|
generateTab.classList.remove("active"); |
|
|
|
setVerifyElementsDisplay("block"); |
|
setGenerateElementsDisplay("none"); |
|
}); |
|
|
|
function setGenerateElementsDisplay(displayStatus) { |
|
displayGenerate.forEach((item) => { |
|
item.style.display = displayStatus; |
|
}); |
|
} |
|
|
|
function setVerifyElementsDisplay(displayStatus) { |
|
displayVerify.forEach((item) => { |
|
item.style.display = displayStatus; |
|
}); |
|
} |
|
|
|
uploadForm.addEventListener("submit", (e) => { |
|
e.preventDefault(); |
|
submitForm(); |
|
}); |
|
|
|
function submitForm() { |
|
if (!fileUpload.value || !verifyTerms.checked) return; |
|
|
|
const file = document.getElementById("fileUpload").files[0]; |
|
|
|
let fileReader = new FileReader(); |
|
fileReader.readAsDataURL(file); |
|
fileReader.onload = function () { |
|
document |
|
.getElementById("uploaded-image") |
|
.setAttribute("src", fileReader.result); |
|
}; |
|
|
|
document.querySelector(".display-verify .error").style.display = "none"; |
|
placeholder = document.querySelector(".display-verify .placeholder"); |
|
spinner = document.querySelector(".display-verify .spinner"); |
|
placeholder.style.display = "none"; |
|
|
|
if (document.getElementById("verifyresult")) |
|
document.getElementById("verifyresult").remove(); |
|
spinner.style.display = "block"; |
|
|
|
outputContainer.style.display = "none"; |
|
document.getElementById("resultLabel").style.display = "none"; |
|
uploadedImageContainer.style.display = "none"; |
|
|
|
const formData = new FormData(uploadForm); |
|
|
|
|
|
|
|
|
|
|
|
submitFormData(formData); |
|
} |
|
|
|
function submitFormData(formData) { |
|
fetch("verify", { |
|
method: "POST", |
|
body: formData, |
|
}) |
|
.then((response) => response.json()) |
|
.then((data) => { |
|
console.log(data); |
|
|
|
fileUpload.value = ""; |
|
setVerifyButtonStatus(); |
|
|
|
if (data.contains_c2pa == "true") { |
|
contentCredentialsIcon.setAttribute("class", "check-icon"); |
|
} else { |
|
contentCredentialsIcon.setAttribute("class", "x-icon"); |
|
} |
|
|
|
if (data.contains_watermark == "true") { |
|
digitalWatermarkIcon.setAttribute("class", "check-icon"); |
|
} else { |
|
digitalWatermarkIcon.setAttribute("class", "x-icon"); |
|
} |
|
|
|
if ( |
|
data.contains_c2pa == "true" && |
|
data.contains_watermark == "true" |
|
) { |
|
verifyResultDescription.innerHTML = |
|
"Your image contains content credentials, which are displayed below."; |
|
} else if ( |
|
data.contains_c2pa == "false" && |
|
data.contains_watermark == "true" |
|
) { |
|
verifyResultDescription.innerHTML = |
|
"The watermark was found, but image modifications were also detected. The last untampered, signed version on file is displayed."; |
|
} else if ( |
|
data.contains_c2pa == "true" && |
|
data.contains_watermark == "false" |
|
) { |
|
verifyResultDescription.innerHTML = |
|
"Your image contains content credentials, which are displayed below. A watermark was not detected."; |
|
} else if ( |
|
data.contains_c2pa == "false" && |
|
data.contains_watermark == "false" |
|
) { |
|
verifyResultDescription.innerHTML = |
|
"Nothing to show: this image contains neither content credentials, nor a watermark."; |
|
} |
|
|
|
uploadedImageContainer.style.display = "flex"; |
|
document.getElementById("resultLabel").style.display = "block"; |
|
|
|
document.querySelector(".display-verify .spinner").style.display = |
|
"none"; |
|
|
|
if (data.result_media != "n/a") { |
|
const path = "/" + data.result_media; |
|
|
|
var truepicDisplay = document.createElement("truepic-display"); |
|
|
|
truepicDisplay.addEventListener( |
|
"validate", |
|
setVerificationOutputFromValidation |
|
); |
|
truepicDisplay.addEventListener( |
|
"validate", |
|
setCertificateOutputFromValidation |
|
); |
|
|
|
truepicDisplay.setAttribute("id", "verifyresult"); |
|
truepicDisplay.setAttribute("active", ""); |
|
var truepic = document.createElement("img"); |
|
truepic.src = path; |
|
|
|
truepicDisplay.appendChild(truepic); |
|
|
|
verifyImageContainer.appendChild(truepicDisplay); |
|
|
|
outputContainer.style.display = "block"; |
|
} |
|
|
|
}) |
|
.catch((error) => { |
|
console.log(error); |
|
document.querySelector(".display-verify .error").style.display = |
|
"block"; |
|
|
|
fileUpload.value = ""; |
|
setVerifyButtonStatus(); |
|
|
|
document.querySelector(".display-verify .spinner").style.display = |
|
"none"; |
|
document.querySelector(".display-verify .placeholder").style.display = |
|
"block"; |
|
|
|
}); |
|
} |
|
|
|
const generateImage = async (text, model) => { |
|
const inferResponse = await fetch( |
|
`generate?prompt=${text}&model=${model}` |
|
); |
|
const inferJson = await inferResponse.json(); |
|
|
|
return inferJson.response; |
|
}; |
|
|
|
imageGenForm.addEventListener("submit", async (event) => { |
|
event.preventDefault(); |
|
|
|
if (!imagePrompt.value || !model.value || !generateTerms.checked) return; |
|
|
|
placeholder = document.querySelector(".display-generate .placeholder"); |
|
spinner = document.querySelector(".display-generate .spinner"); |
|
generateActionMenu.style.display = "none"; |
|
document.querySelector(".display-generate .error").style.display = "none"; |
|
|
|
|
|
|
|
try { |
|
placeholder.style.display = "none"; |
|
if (document.getElementById("genresult")) |
|
document.getElementById("genresult").remove(); |
|
|
|
spinner.style.display = "block"; |
|
|
|
const resp = await generateImage(imagePrompt.value, model.value); |
|
const path = "/" + resp; |
|
|
|
var truepicDisplay = document.createElement("truepic-display"); |
|
|
|
truepicDisplay.setAttribute("id", "genresult"); |
|
truepicDisplay.setAttribute("active", ""); |
|
var truepic = document.createElement("img"); |
|
truepic.src = path; |
|
|
|
truepicDisplay.appendChild(truepic); |
|
|
|
spinner.style.display = "none"; |
|
generateImageContainer.appendChild(truepicDisplay); |
|
|
|
downloadButton.href = path; |
|
downloadButton.download = resp; |
|
|
|
generateActionMenu.style.display = "block"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (err) { |
|
console.error(err); |
|
document.querySelector(".display-generate .error").style.display = |
|
"block"; |
|
spinner.style.display = "none"; |
|
placeholder.style.display = "block"; |
|
} |
|
}); |
|
|
|
function setVerificationOutputFromValidation(event) { |
|
|
|
return setVerificationOutput(event.detail.manifestStore.toJSON()); |
|
} |
|
|
|
function setCertificateOutputFromValidation(event) { |
|
return setCertificateOutput(event.detail.manifestStore); |
|
} |
|
|
|
function setVerificationOutput(output = null) { |
|
verificationOutput.innerHTML = ""; |
|
|
|
if (!output) { |
|
return; |
|
} |
|
|
|
const viewer = new JSONViewer(); |
|
|
|
verificationOutput.appendChild(viewer.getContainer()); |
|
|
|
viewer.showJSON(output); |
|
} |
|
|
|
function setCertificateOutput(manifestStore = null) { |
|
const certificate = manifestStore?.activeManifest?.certificate; |
|
|
|
if (!certificate) { |
|
return; |
|
} |
|
|
|
certificates = [ |
|
{ |
|
der: certificate.der, |
|
name: certificate.subjectName, |
|
decoded: new x509.X509Certificate(certificate.der), |
|
}, |
|
...certificate.chain.map((certificate) => ({ |
|
der: certificate.der, |
|
decoded: new x509.X509Certificate(certificate.der), |
|
})), |
|
]; |
|
|
|
certificates.forEach((certificate) => { |
|
certificate.transformed = transformCert(certificate.decoded); |
|
}); |
|
|
|
console.log("certificates", certificates); |
|
|
|
certificateList.innerHTML = ""; |
|
|
|
certificates.forEach((certificate, index) => { |
|
var li = document.createElement("li"); |
|
if (index == 0) li.classList.add("active"); |
|
li.appendChild( |
|
document.createTextNode(certificate.transformed.subjectCommonName) |
|
); |
|
li.addEventListener("click", function (e) { |
|
setCertificate(index); |
|
const lis = document.querySelectorAll("#certificate-list li"); |
|
|
|
lis.forEach((element) => { |
|
element.classList.remove("active"); |
|
}); |
|
|
|
this.classList.add("active"); |
|
}); |
|
|
|
certificateList.appendChild(li); |
|
}); |
|
|
|
setCertificate(0); |
|
} |
|
|
|
function transformCert(certificate) { |
|
const { |
|
issuer, |
|
subject, |
|
notAfter: expired, |
|
notBefore: issued, |
|
serialNumber, |
|
publicKey: { |
|
algorithm: { |
|
name: algorithm, |
|
modulusLength: modulus, |
|
namedCurve: namedCurve, |
|
}, |
|
}, |
|
} = certificate; |
|
|
|
const parsedSubject = parseCertificateValues(subject); |
|
const parsedIssuer = parseCertificateValues(issuer); |
|
|
|
return { |
|
issuerCommonName: parsedIssuer["CN"], |
|
issuerOrganizationUnit: parsedIssuer["OU"], |
|
issuerOrganization: parsedIssuer["O"], |
|
issuerCountry: parsedIssuer["C"], |
|
subjectCommonName: parsedSubject["CN"], |
|
subjectOrganizationUnit: parsedSubject["OU"], |
|
subjectOrganization: parsedSubject["O"], |
|
subjectCountry: parsedSubject["C"], |
|
issued, |
|
expired, |
|
serialNumber, |
|
algorithm, |
|
modulus, |
|
namedCurve, |
|
}; |
|
} |
|
|
|
function parseCertificateValues(input) { |
|
const params = new URLSearchParams(input.replaceAll(",", "&")); |
|
const responses = {}; |
|
|
|
for (const entry of params.entries()) { |
|
responses[entry[0].trim()] = entry[1]; |
|
} |
|
|
|
return responses; |
|
} |
|
|
|
function setCertificate(ind) { |
|
const certificate = certificates[ind].transformed; |
|
|
|
document.querySelector(".details .issuerCommonName").innerHTML = |
|
certificate.issuerCommonName; |
|
document.querySelector(".details .issuerOrganizationUnit").innerHTML = |
|
certificate.issuerOrganizationUnit; |
|
document.querySelector(".details .issuerOrganization").innerHTML = |
|
certificate.issuerOrganization; |
|
document.querySelector(".details .issuerCountry").innerHTML = |
|
certificate.issuerCountry; |
|
document.querySelector(".details .subjectCommonName").innerHTML = |
|
certificate.subjectCommonName; |
|
document.querySelector(".details .subjectOrganizationUnit").innerHTML = |
|
certificate.subjectOrganizationUnit; |
|
document.querySelector(".details .subjectOrganization").innerHTML = |
|
certificate.subjectOrganization; |
|
document.querySelector(".details .subjectCountry").innerHTML = |
|
certificate.subjectCountry; |
|
document.querySelector(".details .issued").innerHTML = certificate.issued; |
|
document.querySelector(".details .expired").innerHTML = |
|
certificate.expired; |
|
document.querySelector(".details .serialNumber").innerHTML = |
|
certificate.serialNumber; |
|
document.querySelector(".details .algorithm").innerHTML = |
|
certificate.algorithm; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (certificate.namedCurve !== undefined) { |
|
document.querySelector(".details .namedCurve").innerHTML = |
|
certificate.namedCurve; |
|
document.querySelector("#curveContainer").style.display = "block"; |
|
} else { |
|
document.querySelector("#curveContainer").style.display = "none"; |
|
} |
|
} |
|
</script> |
|
</html> |
|
|