return createOCSPReqInternal().then(() => { const asn1 = asn1js.fromBER(ocspReqBuffer); // noinspection JSUnusedLocalSymbols const ocspRequest = new OCSPRequest({ schema: asn1.result }); });
//********************************************************************************* //endregion //********************************************************************************* //region Parse "CA Bundle" file //********************************************************************************* function parseCAbundle(buffer) { //region Initial variables const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; const startChars = "-----BEGIN CERTIFICATE-----"; const endChars = "-----END CERTIFICATE-----"; const endLineChars = "\r\n"; const view = new Uint8Array(buffer); let waitForStart = false; let middleStage = true; let waitForEnd = false; let waitForEndLine = false; let started = false; let certBodyEncoded = ""; //endregion for(let i = 0; i < view.length; i++) { if(started === true) { if(base64Chars.indexOf(String.fromCharCode(view[i])) !== (-1)) certBodyEncoded = certBodyEncoded + String.fromCharCode(view[i]); else { if(String.fromCharCode(view[i]) === "-") { //region Decoded trustedCertificates const asn1 = asn1js.fromBER(stringToArrayBuffer(window.atob(certBodyEncoded))); try { trustedCertificates.push(new Certificate({ schema: asn1.result })); } catch(ex) { alert("Wrong certificate format"); return; } //endregion //region Set all "flag variables" certBodyEncoded = ""; started = false; waitForEnd = true; //endregion } } } else { if(waitForEndLine === true) { if(endLineChars.indexOf(String.fromCharCode(view[i])) === (-1)) { waitForEndLine = false; if(waitForEnd === true) { waitForEnd = false; middleStage = true; } else { if(waitForStart === true) { waitForStart = false; started = true; certBodyEncoded = certBodyEncoded + String.fromCharCode(view[i]); } else middleStage = true; } } } else { if(middleStage === true) { if(String.fromCharCode(view[i]) === "-") { if((i === 0) || ((String.fromCharCode(view[i - 1]) === "\r") || (String.fromCharCode(view[i - 1]) === "\n"))) { middleStage = false; waitForStart = true; } } } else { if(waitForStart === true) { if(startChars.indexOf(String.fromCharCode(view[i])) === (-1)) waitForEndLine = true; } else { if(waitForEnd === true) { if(endChars.indexOf(String.fromCharCode(view[i])) === (-1)) waitForEndLine = true; } } } } } } }
//********************************************************************************* //endregion //********************************************************************************* //region Parse existing OCSP request //********************************************************************************* function parseOCSPReq() { //region Initial check if(ocspReqBuffer.byteLength === 0) { alert("Nothing to parse!"); return; } //endregion //region Initial activities document.getElementById("ocsp-req-extn-div").style.display = "none"; const requestsTable = document.getElementById("ocsp-req-requests"); while(requestsTable.rows.length > 1) requestsTable.deleteRow(requestsTable.rows.length - 1); const extensionTable = document.getElementById("ocsp-req-extn-table"); while(extensionTable.rows.length > 1) extensionTable.deleteRow(extensionTable.rows.length - 1); const requestorTable = document.getElementById("ocsp-req-name"); while(requestorTable.rows.length > 1) requestorTable.deleteRow(requestorTable.rows.length - 1); //endregion //region Decode existing OCSP request const asn1 = asn1js.fromBER(ocspReqBuffer); const ocspReqSimpl = new OCSPRequest({ schema: asn1.result }); //endregion //region Put information about OCSP request requestor if("requestorName" in ocspReqSimpl.tbsRequest) { switch(ocspReqSimpl.tbsRequest.requestorName.type) { case 1: // rfc822Name case 2: // dNSName case 6: // uniformResourceIdentifier // noinspection InnerHTMLJS document.getElementById("ocsp-req-name-simpl").innerHTML = ocspReqSimpl.tbsRequest.requestorName.value.valueBlock.value; document.getElementById("ocsp-req-nm-simpl").style.display = "block"; break; case 7: // iPAddress { const view = new Uint8Array(ocspReqSimpl.tbsRequest.requestorName.value.valueBlock.valueHex); // noinspection InnerHTMLJS document.getElementById("ocsp-req-name-simpl").innerHTML = `${view[0].toString()}.${view[1].toString()}.${view[2].toString()}.${view[3].toString()}`; document.getElementById("ocsp-req-nm-simpl").style.display = "block"; } break; case 3: // x400Address case 5: // ediPartyName // noinspection InnerHTMLJS document.getElementById("ocsp-req-name-simpl").innerHTML = (ocspReqSimpl.tbsRequest.requestorName.type === 3) ? "<type \"x400Address\">" : "<type \"ediPartyName\">"; document.getElementById("ocsp-req-nm-simpl").style.display = "block"; break; case 4: // directoryName { const rdnmap = { "2.5.4.6": "C", "2.5.4.10": "O", "2.5.4.11": "OU", "2.5.4.3": "CN", "2.5.4.7": "L", "2.5.4.8": "S", "2.5.4.12": "T", "2.5.4.42": "GN", "2.5.4.43": "I", "2.5.4.4": "SN", "1.2.840.113549.1.9.1": "E-mail" }; for(let i = 0; i < ocspReqSimpl.tbsRequest.requestorName.value.typesAndValues.length; i++) { let typeval = rdnmap[ocspReqSimpl.tbsRequest.requestorName.value.typesAndValues[i].type]; if(typeof typeval === "undefined") typeval = ocspReqSimpl.tbsRequest.requestorName.value.typesAndValues[i].type; const subjval = ocspReqSimpl.tbsRequest.requestorName.value.typesAndValues[i].value.valueBlock.value; const row = requestorTable.insertRow(requestorTable.rows.length); const cell0 = row.insertCell(0); // noinspection InnerHTMLJS cell0.innerHTML = typeval; const cell1 = row.insertCell(1); // noinspection InnerHTMLJS cell1.innerHTML = subjval; } document.getElementById("ocsp-req-name-div").style.display = "block"; } break; default: } } //endregion //region Put information about requests for(let i = 0; i < ocspReqSimpl.tbsRequest.requestList.length; i++) { const row = requestsTable.insertRow(requestsTable.rows.length); const cell0 = row.insertCell(0); // noinspection InnerHTMLJS cell0.innerHTML = bufferToHexCodes(ocspReqSimpl.tbsRequest.requestList[i].reqCert.serialNumber.valueBlock.valueHex); } //endregion //region Put information about request extensions if("requestExtensions" in ocspReqSimpl.tbsRequest) { for(let i = 0; i < ocspReqSimpl.tbsRequest.requestExtensions.length; i++) { const row = extensionTable.insertRow(extensionTable.rows.length); const cell0 = row.insertCell(0); // noinspection InnerHTMLJS cell0.innerHTML = ocspReqSimpl.tbsRequest.requestExtensions[i].extnID; } document.getElementById("ocsp-req-extn-div").style.display = "block"; } //endregion }
//********************************************************************************** parseInternalValues(parameters) { //region Check input data from "parameters" if((parameters instanceof Object) === false) return Promise.reject("The \"parameters\" must has \"Object\" type"); if(("safeContents" in parameters) === false) return Promise.reject("Absent mandatory parameter \"safeContents\""); if((parameters.safeContents instanceof Array) === false) return Promise.reject("The \"parameters.safeContents\" must has \"Array\" type"); if(parameters.safeContents.length !== this.safeContents.length) return Promise.reject("Length of \"parameters.safeContents\" must be equal to \"this.safeContents.length\""); //endregion //region Initial variables let sequence = Promise.resolve(); //endregion //region Create value for "this.parsedValue.authenticatedSafe" this.parsedValue = { safeContents: [] }; for(const [index, content] of this.safeContents.entries()) { switch(content.contentType) { //region data case "1.2.840.113549.1.7.1": { //region Check that we do have OCTETSTRING as "content" if((content.content instanceof asn1js.OctetString) === false) return Promise.reject("Wrong type of \"this.safeContents[j].content\""); //endregion //region Parse internal ASN.1 data const asn1 = asn1js.fromBER(content.content.valueBlock.valueHex); if(asn1.offset === (-1)) return Promise.reject("Error during parsing of ASN.1 data inside \"content.content\""); //endregion //region Finilly initialize initial values of "SafeContents" type this.parsedValue.safeContents.push({ privacyMode: 0, // No privacy, clear data value: new SafeContents({ schema: asn1.result }) }); //endregion } break; //endregion //region envelopedData case "1.2.840.113549.1.7.3": { //region Initial variables const cmsEnveloped = new EnvelopedData({ schema: content.content }); //endregion //region Check mandatory parameters if(("recipientCertificate" in parameters.safeContents[index]) === false) return Promise.reject("Absent mandatory parameter \"recipientCertificate\" in \"parameters.safeContents[j]\""); const recipientCertificate = parameters.safeContents[index].recipientCertificate; if(("recipientKey" in parameters.safeContents[index]) === false) return Promise.reject("Absent mandatory parameter \"recipientKey\" in \"parameters.safeContents[j]\""); const recipientKey = parameters.safeContents[index].recipientKey; //endregion //region Decrypt CMS EnvelopedData using first recipient information sequence = sequence.then( () => cmsEnveloped.decrypt(0, { recipientCertificate, recipientPrivateKey: recipientKey }) ); sequence = sequence.then( result => { const asn1 = asn1js.fromBER(result); if(asn1.offset === (-1)) return Promise.reject("Error during parsing of decrypted data"); this.parsedValue.safeContents.push({ privacyMode: 2, // Public-key privacy mode value: new SafeContents({ schema: asn1.result }) }); return Promise.resolve(); } ); //endregion } break; //endregion //region encryptedData case "1.2.840.113549.1.7.6": { //region Initial variables const cmsEncrypted = new EncryptedData({ schema: content.content }); //endregion //region Check mandatory parameters if(("password" in parameters.safeContents[index]) === false) return Promise.reject("Absent mandatory parameter \"password\" in \"parameters.safeContents[j]\""); const password = parameters.safeContents[index].password; //endregion //region Decrypt CMS EncryptedData using password sequence = sequence.then( () => cmsEncrypted.decrypt({ password }), error => Promise.reject(error) ); //endregion //region Initialize internal data sequence = sequence.then( result => { const asn1 = asn1js.fromBER(result); if(asn1.offset === (-1)) return Promise.reject("Error during parsing of decrypted data"); this.parsedValue.safeContents.push({ privacyMode: 1, // Password-based privacy mode value: new SafeContents({ schema: asn1.result }) }); return Promise.resolve(); }, error => Promise.reject(error) ); //endregion } break; //endregion //region default default: throw new Error(`Unknown \"contentType\" for AuthenticatedSafe: " ${content.contentType}`); //endregion } } //endregion return sequence; }
//********************************************************************************* //endregion //********************************************************************************* //region Parse existing TSP response //********************************************************************************* function parseTSPResp() { //region Initial activities document.getElementById("resp-accur").style.display = "none"; document.getElementById("resp-ord").style.display = "none"; document.getElementById("resp-non").style.display = "none"; document.getElementById("resp-ts-rdn").style.display = "none"; document.getElementById("resp-ts-simpl").style.display = "none"; document.getElementById("resp-ext").style.display = "none"; const imprTable = document.getElementById("resp-imprint"); while(imprTable.rows.length > 1) imprTable.deleteRow(imprTable.rows.length - 1); const accurTable = document.getElementById("resp-accuracy"); while(accurTable.rows.length > 1) accurTable.deleteRow(accurTable.rows.length - 1); const tsTable = document.getElementById("resp-tsa"); while(tsTable.rows.length > 1) tsTable.deleteRow(tsTable.rows.length - 1); const extTable = document.getElementById("resp-extensions"); while(extTable.rows.length > 1) extTable.deleteRow(extTable.rows.length - 1); //endregion //region Decode existing TSP response const asn1 = asn1js.fromBER(tspResponseBuffer); const tspRespSimpl = new TimeStampResp({ schema: asn1.result }); //endregion //region Put information about TSP response status let status = ""; switch(tspRespSimpl.status.status) { case 0: status = "granted"; break; case 1: status = "grantedWithMods"; break; case 2: status = "rejection"; break; case 3: status = "waiting"; break; case 4: status = "revocationWarning"; break; case 5: status = "revocationNotification"; break; default: } document.getElementById("resp-status").innerHTML = status; //endregion //region Parse internal CMS Signed Data if(("timeStampToken" in tspRespSimpl) === false) { alert("No additional info but PKIStatusInfo"); return; } const signedSimpl = new SignedData({ schema: tspRespSimpl.timeStampToken.content }); const asn1TST = asn1js.fromBER(signedSimpl.encapContentInfo.eContent.valueBlock.valueHex); const tstInfoSimpl = new TSTInfo({ schema: asn1TST.result }); //endregion //region Put information about policy document.getElementById("resp-policy").innerHTML = tstInfoSimpl.policy; //endregion //region Put information about TST info message imprint const dgstmap = { "1.3.14.3.2.26": "SHA-1", "2.16.840.1.101.3.4.2.1": "SHA-256", "2.16.840.1.101.3.4.2.2": "SHA-384", "2.16.840.1.101.3.4.2.3": "SHA-512" }; let hashAlgorithm = dgstmap[tstInfoSimpl.messageImprint.hashAlgorithm.algorithmId]; if(typeof hashAlgorithm === "undefined") hashAlgorithm = tstInfoSimpl.messageImprint.hashAlgorithm.algorithmId; const imprintTable = document.getElementById("resp-imprint"); const row = imprintTable.insertRow(imprintTable.rows.length); const cell0 = row.insertCell(0); cell0.innerHTML = hashAlgorithm; const cell1 = row.insertCell(1); cell1.innerHTML = bufferToHexCodes(tstInfoSimpl.messageImprint.hashedMessage.valueBlock.valueHex); //endregion //region Put information about TST info serial number document.getElementById("resp-serial").innerHTML = bufferToHexCodes(tstInfoSimpl.serialNumber.valueBlock.valueHex); //endregion //region Put information about the time when TST info was generated document.getElementById("resp-time").innerHTML = tstInfoSimpl.genTime.toString(); //endregion //region Put information about TST info accuracy if("accuracy" in tstInfoSimpl) { const accuracyTable = document.getElementById("resp-accuracy"); const rowInner = accuracyTable.insertRow(accuracyTable.rows.length); const cell0Inner = rowInner.insertCell(0); cell0Inner.innerHTML = ("seconds" in tstInfoSimpl.accuracy) ? tstInfoSimpl.accuracy.seconds : 0; const cell1Inner = rowInner.insertCell(1); cell1Inner.innerHTML = ("millis" in tstInfoSimpl.accuracy) ? tstInfoSimpl.accuracy.millis : 0; const cell2 = rowInner.insertCell(2); cell2.innerHTML = ("micros" in tstInfoSimpl.accuracy) ? tstInfoSimpl.accuracy.micros : 0; document.getElementById("resp-accur").style.display = "block"; } //endregion //region Put information about TST info ordering if("ordering" in tstInfoSimpl) { document.getElementById("resp-ordering").innerHTML = tstInfoSimpl.ordering.toString(); document.getElementById("resp-ord").style.display = "block"; } //endregion //region Put information about TST info nonce value if("nonce" in tstInfoSimpl) { document.getElementById("resp-nonce").innerHTML = bufferToHexCodes(tstInfoSimpl.nonce.valueBlock.valueHex); document.getElementById("resp-non").style.display = "block"; } //endregion //region Put information about TST info TSA if("tsa" in tstInfoSimpl) { switch(tstInfoSimpl.tsa.type) { case 1: // rfc822Name case 2: // dNSName case 6: // uniformResourceIdentifier document.getElementById("resp-tsa-simpl").innerHTML = tstInfoSimpl.tsa.value.valueBlock.value; document.getElementById("resp-ts-simpl").style.display = "block"; break; case 7: // iPAddress { const view = new Uint8Array(tstInfoSimpl.tsa.value.valueBlock.valueHex); document.getElementById("resp-tsa-simpl").innerHTML = `${view[0].toString()}.${view[1].toString()}.${view[2].toString()}.${view[3].toString()}`; document.getElementById("resp-ts-simpl").style.display = "block"; } break; case 3: // x400Address case 5: // ediPartyName document.getElementById("resp-tsa-simpl").innerHTML = (tstInfoSimpl.tsa.type === 3) ? "<type \"x400Address\">" : "<type \"ediPartyName\">"; document.getElementById("resp-ts-simpl").style.display = "block"; break; case 4: // directoryName { const rdnmap = { "2.5.4.6": "C", "2.5.4.10": "OU", "2.5.4.11": "O", "2.5.4.3": "CN", "2.5.4.7": "L", "2.5.4.8": "S", "2.5.4.12": "T", "2.5.4.42": "GN", "2.5.4.43": "I", "2.5.4.4": "SN", "1.2.840.113549.1.9.1": "E-mail" }; const rdnTable = document.getElementById("resp-tsa"); for(let i = 0; i < tstInfoSimpl.tsa.value.typesAndValues.length; i++) { let typeval = rdnmap[tstInfoSimpl.tsa.value.typesAndValues[i].type]; if(typeof typeval === "undefined") typeval = tstInfoSimpl.tsa.value.typesAndValues[i].type; const subjval = tstInfoSimpl.tsa.value.typesAndValues[i].value.valueBlock.value; const rowInner = rdnTable.insertRow(rdnTable.rows.length); const cell0Inner = rowInner.insertCell(0); cell0Inner.innerHTML = typeval; const cell1Inner = rowInner.insertCell(1); cell1Inner.innerHTML = subjval; } document.getElementById("resp-ts-rdn").style.display = "block"; } break; default: } } //endregion //region Put information about TST info extensions if("extensions" in tstInfoSimpl) { const extensionTable = document.getElementById("resp-extensions"); for(let i = 0; i < tstInfoSimpl.extensions.length; i++) { const rowInner = extensionTable.insertRow(extensionTable.rows.length); const cell0Inner = rowInner.insertCell(0); cell0Inner.innerHTML = tstInfoSimpl.extensions[i].extnID; } document.getElementById("resp-ext").style.display = "block"; } //endregion }
//********************************************************************************* async function parseOpenSSLPrivateKey() { let keyLength = 0; let base64 = ""; const headerExp = /([\x21-\x7e]+):\s*([\x21-\x7e\s^:]+)/; const stringPEM = document.getElementById("openssl_data").value.replace(/(-----(BEGIN|END) RSA PRIVATE KEY-----)/g, ""); const lines = stringPEM.split(/\r?\n/); let dekFound = false; let iv = new ArrayBuffer(0); for(let i = 0; i < lines.length; i++) { const lineMatch = lines[i].match(headerExp); if(lineMatch !== null) { if(lineMatch[1] === "DEK-Info") { dekFound = true; const values = lineMatch[2].split(","); for(let j = 0; j < values.length; j++) values[j] = values[j].trim(); switch(values[0].toLocaleUpperCase()) { case "AES-128-CBC": keyLength = 16; break; case "AES-192-CBC": keyLength = 24; break; case "AES-256-CBC": keyLength = 32; break; default: throw new Error(`Unsupported apgorithm ${values[0].toLocaleUpperCase()}`); } iv = hex2b(values[1]); } } else { if(dekFound) base64 += lines[i]; } } if(dekFound === false) throw new Error("Can not find DEK-Info section!"); const dataBuffer = await decryptOpenSSLPrivateKey(stringToArrayBuffer(fromBase64(base64.trim())), stringToArrayBuffer(document.getElementById("password").value), "AES-CBC", keyLength, iv); const asn1 = asn1js.fromBER(dataBuffer); if(asn1.offset === (-1)) throw new Error("Incorect encrypted key"); //const privateKeyInfo = new PrivateKeyInfo({ schema: asn1.result }); const rsaPrivateKey = new RSAPrivateKey({ schema: asn1.result }); let resultString = "-----BEGIN RSA PRIVATE KEY-----\r\n"; //resultString = `${resultString}${formatPEM(toBase64(arrayBufferToString(privateKeyInfo.toSchema().toBER(false))))}`; resultString = `${resultString}${formatPEM(toBase64(arrayBufferToString(rsaPrivateKey.toSchema().toBER(false))))}`; //resultString = `${resultString}${formatPEM(toBase64(arrayBufferToString(dataBuffer)))}`; resultString = `${resultString}\r\n-----END RSA PRIVATE KEY-----\r\n`; document.getElementById("pkijs_data").value = resultString; }
//********************************************************************************* function parseCertificate() { //region Initial check if(certificateBuffer.byteLength === 0) { alert("Nothing to parse!"); return; } //endregion //region Initial activities document.getElementById("cert-extn-div").style.display = "none"; const issuerTable = document.getElementById("cert-issuer-table"); while(issuerTable.rows.length > 1) issuerTable.deleteRow(issuerTable.rows.length - 1); const subjectTable = document.getElementById("cert-subject-table"); while(subjectTable.rows.length > 1) subjectTable.deleteRow(subjectTable.rows.length - 1); const extensionTable = document.getElementById("cert-extn-table"); while(extensionTable.rows.length > 1) extensionTable.deleteRow(extensionTable.rows.length - 1); //endregion //region Decode existing X.509 certificate const asn1 = asn1js.fromBER(certificateBuffer); const certificate = new Certificate({ schema: asn1.result }); //endregion //region Put information about X.509 certificate issuer const rdnmap = { "2.5.4.6": "C", "2.5.4.10": "O", "2.5.4.11": "OU", "2.5.4.3": "CN", "2.5.4.7": "L", "2.5.4.8": "S", "2.5.4.12": "T", "2.5.4.42": "GN", "2.5.4.43": "I", "2.5.4.4": "SN", "1.2.840.113549.1.9.1": "E-mail" }; for(const typeAndValue of certificate.issuer.typesAndValues) { let typeval = rdnmap[typeAndValue.type]; if(typeof typeval === "undefined") typeval = typeAndValue.type; const subjval = typeAndValue.value.valueBlock.value; const row = issuerTable.insertRow(issuerTable.rows.length); const cell0 = row.insertCell(0); // noinspection InnerHTMLJS cell0.innerHTML = typeval; const cell1 = row.insertCell(1); // noinspection InnerHTMLJS cell1.innerHTML = subjval; } //endregion //region Put information about X.509 certificate subject for(const typeAndValue of certificate.subject.typesAndValues) { let typeval = rdnmap[typeAndValue.type]; if(typeof typeval === "undefined") typeval = typeAndValue.type; const subjval = typeAndValue.value.valueBlock.value; const row = subjectTable.insertRow(subjectTable.rows.length); const cell0 = row.insertCell(0); // noinspection InnerHTMLJS cell0.innerHTML = typeval; const cell1 = row.insertCell(1); // noinspection InnerHTMLJS cell1.innerHTML = subjval; } //endregion //region Put information about X.509 certificate serial number // noinspection InnerHTMLJS document.getElementById("cert-serial-number").innerHTML = bufferToHexCodes(certificate.serialNumber.valueBlock.valueHex); //endregion //region Put information about issuance date // noinspection InnerHTMLJS document.getElementById("cert-not-before").innerHTML = certificate.notBefore.value.toString(); //endregion //region Put information about expiration date // noinspection InnerHTMLJS document.getElementById("cert-not-after").innerHTML = certificate.notAfter.value.toString(); //endregion //region Put information about subject public key size let publicKeySize = "< unknown >"; if(certificate.subjectPublicKeyInfo.algorithm.algorithmId.indexOf("1.2.840.113549") !== (-1)) { const asn1PublicKey = asn1js.fromBER(certificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex); const rsaPublicKey = new RSAPublicKey({ schema: asn1PublicKey.result }); const modulusView = new Uint8Array(rsaPublicKey.modulus.valueBlock.valueHex); let modulusBitLength = 0; if(modulusView[0] === 0x00) modulusBitLength = (rsaPublicKey.modulus.valueBlock.valueHex.byteLength - 1) * 8; else modulusBitLength = rsaPublicKey.modulus.valueBlock.valueHex.byteLength * 8; publicKeySize = modulusBitLength.toString(); } // noinspection InnerHTMLJS document.getElementById("cert-keysize").innerHTML = publicKeySize; //endregion //region Put information about signature algorithm const algomap = { "1.2.840.113549.1.1.2": "MD2 with RSA", "1.2.840.113549.1.1.4": "MD5 with RSA", "1.2.840.10040.4.3": "SHA1 with DSA", "1.2.840.10045.4.1": "SHA1 with ECDSA", "1.2.840.10045.4.3.2": "SHA256 with ECDSA", "1.2.840.10045.4.3.3": "SHA384 with ECDSA", "1.2.840.10045.4.3.4": "SHA512 with ECDSA", "1.2.840.113549.1.1.10": "RSA-PSS", "1.2.840.113549.1.1.5": "SHA1 with RSA", "1.2.840.113549.1.1.14": "SHA224 with RSA", "1.2.840.113549.1.1.11": "SHA256 with RSA", "1.2.840.113549.1.1.12": "SHA384 with RSA", "1.2.840.113549.1.1.13": "SHA512 with RSA" }; // array mapping of common algorithm OIDs and corresponding types let signatureAlgorithm = algomap[certificate.signatureAlgorithm.algorithmId]; if(typeof signatureAlgorithm === "undefined") signatureAlgorithm = certificate.signatureAlgorithm.algorithmId; else signatureAlgorithm = `${signatureAlgorithm} (${certificate.signatureAlgorithm.algorithmId})`; // noinspection InnerHTMLJS document.getElementById("cert-sign-algo").innerHTML = signatureAlgorithm; //endregion //region Put information about certificate extensions if("extensions" in certificate) { for(let i = 0; i < certificate.extensions.length; i++) { const row = extensionTable.insertRow(extensionTable.rows.length); const cell0 = row.insertCell(0); // noinspection InnerHTMLJS cell0.innerHTML = certificate.extensions[i].extnID; } document.getElementById("cert-extn-div").style.display = "block"; } //endregion }
//********************************************************************************* //endregion //********************************************************************************* //region Verify SMIME signature //********************************************************************************* function verifySMIME() { //region Parse MIME contents to find signature and detached data const parser = parse(document.getElementById("smime_message").value); //endregion // noinspection JSUnresolvedVariable if(("childNodes" in parser) || (parser.childNodes.length !== 2)) { // noinspection JSUnresolvedVariable const lastNode = parser.childNodes[1]; if((lastNode.contentType.value === "application/x-pkcs7-signature") || (lastNode.contentType.value === "application/pkcs7-signature")) { // Parse into pkijs types const asn1 = asn1js.fromBER(lastNode.content.buffer); if(asn1.offset === (-1)) { alert("Incorrect message format!"); return; } let cmsContentSimpl; let cmsSignedSimpl; try { cmsContentSimpl = new ContentInfo({ schema: asn1.result }); cmsSignedSimpl = new SignedData({ schema: cmsContentSimpl.content }); } catch(ex) { alert("Incorrect message format!"); return; } // Get signed data buffer // noinspection JSUnresolvedVariable const signedDataBuffer = stringToArrayBuffer(parser.childNodes[0].raw.replace(/\n/g, "\r\n")); // Verify the signed data let sequence = Promise.resolve(); sequence = sequence.then( () => cmsSignedSimpl.verify({ signer: 0, data: signedDataBuffer, trustedCerts: trustedCertificates }) ); sequence.then( result => { let failed = false; if(typeof result !== "undefined") { if(result === false) failed = true; } alert(`S/MIME message ${(failed) ? "verification failed" : "successfully verified"}!`); }, error => alert(`Error during verification: ${error}`) ); } } else alert("No child nodes!"); }
const trustedCertificates = []; // Array of Certificates //********************************************************************************* function verifyPDFSignature(buffer) { try { const view = new Uint8Array(buffer); const pdf = new window.PDFDocument(null, view, null); pdf.parseStartXRef(); pdf.parse(); const acroForm = pdf.xref.root.get("AcroForm"); if(typeof acroForm === "undefined") throw new Error("The PDF has no signature!"); const fields = acroForm.get("Fields"); if(window.isRef(fields[0]) === false) throw new Error("Wrong structure of PDF!"); const sigField = pdf.xref.fetch(fields[0]); const sigFieldType = sigField.get("FT"); if((typeof sigFieldType === "undefined") || (sigFieldType.name !== "Sig")) throw new Error("Wrong structure of PDF!"); const v = sigField.get("V"); const byteRange = v.get("ByteRange"); const contents = v.get("Contents"); const contentLength = contents.length; const contentBuffer = new ArrayBuffer(contentLength); const contentView = new Uint8Array(contentBuffer); for(let i = 0; i < contentLength; i++) contentView[i] = contents.charCodeAt(i); let sequence = Promise.resolve(); const asn1 = asn1js.fromBER(contentBuffer); const cmsContentSimp = new ContentInfo({ schema: asn1.result }); const cmsSignedSimp = new SignedData({ schema: cmsContentSimp.content }); const signedDataBuffer = new ArrayBuffer(byteRange[1] + byteRange[3]); const signedDataView = new Uint8Array(signedDataBuffer); let count = 0; for(let i = byteRange[0]; i < (byteRange[0] + byteRange[1]); i++, count++) signedDataView[count] = view[i]; for(let j = byteRange[2]; j < (byteRange[2] + byteRange[3]); j++, count++) signedDataView[count] = view[j]; sequence = sequence.then(() => cmsSignedSimp.verify({ signer: 0, data: signedDataBuffer, trustedCerts: trustedCertificates })); if("signedAttrs" in cmsSignedSimp.signerInfos[0]) { const crypto = getCrypto(); if(typeof crypto === "undefined") throw new Error("WebCrypto extension is not installed"); let shaAlgorithm = ""; switch(cmsSignedSimp.signerInfos[0].digestAlgorithm.algorithmId) { case "1.3.14.3.2.26": shaAlgorithm = "sha-1"; break; case "2.16.840.1.101.3.4.2.1": shaAlgorithm = "sha-256"; break; case "2.16.840.1.101.3.4.2.2": shaAlgorithm = "sha-384"; break; case "2.16.840.1.101.3.4.2.3": shaAlgorithm = "sha-512"; break; default: throw new Error("Unknown hashing algorithm"); } sequence = sequence.then((result) => { if(result === false) return Promise.reject(new Error("Signature verification failed")); return crypto.digest({ name: shaAlgorithm }, new Uint8Array(signedDataBuffer)); }); sequence = sequence.then((result) => { let messageDigest = new ArrayBuffer(0); for(let j = 0; j < cmsSignedSimp.signerInfos[0].signedAttrs.attributes.length; j++) { if(cmsSignedSimp.signerInfos[0].signedAttrs.attributes[j].type === "1.2.840.113549.1.9.4") { messageDigest = cmsSignedSimp.signerInfos[0].signedAttrs.attributes[j].values[0].valueBlock.valueHex; break; } } if(messageDigest.byteLength === 0) return Promise.reject(new Error("No signed attribute 'MessageDigest'")); const view1 = new Uint8Array(messageDigest); const view2 = new Uint8Array(result); if(view1.length !== view2.length) return Promise.reject(new Error("Hash is not correct")); for(let i = 0; i < view1.length; i++) { if(view1[i] !== view2[i]) return Promise.reject(new Error("Hash is not correct")); } }); } sequence = sequence.then((result) => { if(typeof result !== "undefined") { if(result === false) { alert("PDF verification failed!"); return; } } alert("PDF successfully verified!"); }); return sequence.catch((e) => { throw e; }); } catch(e) { console.error(e.stack); } }
//********************************************************************************** /** * Convert parsed asn1js object into current class * @param {!Object} schema */ fromSchema(schema) { //region Clear input data first clearProps(schema, [ "algorithm", "subjectPublicKey" ]); //endregion //region Check the schema is valid const asn1 = asn1js.compareSchema(schema, schema, PublicKeyInfo.schema({ names: { algorithm: { names: { blockName: "algorithm" } }, subjectPublicKey: "subjectPublicKey" } }) ); if(asn1.verified === false) throw new Error("Object's schema was not verified against input data for PublicKeyInfo"); //endregion //region Get internal properties from parsed schema this.algorithm = new AlgorithmIdentifier({ schema: asn1.result.algorithm }); this.subjectPublicKey = asn1.result.subjectPublicKey; switch(this.algorithm.algorithmId) { case "1.2.840.10045.2.1": // ECDSA if("algorithmParams" in this.algorithm) { if(this.algorithm.algorithmParams instanceof asn1js.ObjectIdentifier) { try { this.parsedKey = new ECPublicKey({ namedCurve: this.algorithm.algorithmParams.valueBlock.toString(), schema: this.subjectPublicKey.valueBlock.valueHex }); } catch(ex){} // Could be a problems during recognision of internal public key data here. Let's ignore them. } } break; case "1.2.840.113549.1.1.1": // RSA { const publicKeyASN1 = asn1js.fromBER(this.subjectPublicKey.valueBlock.valueHex); if(publicKeyASN1.offset !== (-1)) { try { this.parsedKey = new RSAPublicKey({ schema: publicKeyASN1.result }); } catch(ex){} // Could be a problems during recognision of internal public key data here. Let's ignore them. } } break; default: } //endregion }
//********************************************************************************** /** * Convert current object to asn1js object and set correct values * @param {boolean} encodeFlag If param equal to false then create TBS schema via decoding stored value. In othe case create TBS schema via assembling from TBS parts. * @returns {Object} asn1js object */ toSchema(encodeFlag = false) { //region Decode stored TBS value let tbsSchema; if(encodeFlag === false) { if(this.tbs.length === 0) // No stored certificate TBS part return ResponseData.schema(); tbsSchema = asn1js.fromBER(this.tbs).result; } //endregion //region Create TBS schema via assembling from TBS parts else { const outputArray = []; if("version" in this) { outputArray.push(new asn1js.Constructed({ idBlock: { tagClass: 3, // CONTEXT-SPECIFIC tagNumber: 0 // [0] }, value: [new asn1js.Integer({ value: this.version })] })); } if(this.responderID instanceof RelativeDistinguishedNames) { outputArray.push(new asn1js.Constructed({ idBlock: { tagClass: 3, // CONTEXT-SPECIFIC tagNumber: 1 // [1] }, value: [this.responderID.toSchema()] })); } else { outputArray.push(new asn1js.Constructed({ idBlock: { tagClass: 3, // CONTEXT-SPECIFIC tagNumber: 2 // [2] }, value: [this.responderID] })); } outputArray.push(new asn1js.GeneralizedTime({ valueDate: this.producedAt })); outputArray.push(new asn1js.Sequence({ value: Array.from(this.responses, element => element.toSchema()) })); if("responseExtensions" in this) { outputArray.push(new asn1js.Sequence({ value: Array.from(this.responseExtensions, element => element.toSchema()) })); } tbsSchema = new asn1js.Sequence({ value: outputArray }); } //endregion //region Construct and return new ASN.1 schema for this object return tbsSchema; //endregion }
//********************************************************************************** /** * Convert parsed asn1js object into current class * @param {!Object} schema */ fromSchema(schema) { //region Clear input data first clearProps(schema, [ "extnID", "critical", "extnValue" ]); //endregion //region Check the schema is valid let asn1 = asn1js.compareSchema(schema, schema, Extension.schema({ names: { extnID: "extnID", critical: "critical", extnValue: "extnValue" } }) ); if(asn1.verified === false) throw new Error("Object's schema was not verified against input data for Extension"); //endregion //region Get internal properties from parsed schema this.extnID = asn1.result.extnID.valueBlock.toString(); if("critical" in asn1.result) this.critical = asn1.result.critical.valueBlock.value; this.extnValue = asn1.result.extnValue; //region Get "parsedValue" for well-known extensions asn1 = asn1js.fromBER(this.extnValue.valueBlock.valueHex); if(asn1.offset === (-1)) return; switch(this.extnID) { case "2.5.29.9": // SubjectDirectoryAttributes try { this.parsedValue = new SubjectDirectoryAttributes({ schema: asn1.result }); } catch(ex) { this.parsedValue = new SubjectDirectoryAttributes(); this.parsedValue.parsingError = "Incorrectly formated SubjectDirectoryAttributes"; } break; case "2.5.29.14": // SubjectKeyIdentifier this.parsedValue = asn1.result; // Should be just a simple OCTETSTRING break; case "2.5.29.15": // KeyUsage this.parsedValue = asn1.result; // Should be just a simple BITSTRING break; case "2.5.29.16": // PrivateKeyUsagePeriod try { this.parsedValue = new PrivateKeyUsagePeriod({ schema: asn1.result }); } catch(ex) { this.parsedValue = new PrivateKeyUsagePeriod(); this.parsedValue.parsingError = "Incorrectly formated PrivateKeyUsagePeriod"; } break; case "2.5.29.17": // SubjectAltName case "2.5.29.18": // IssuerAltName try { this.parsedValue = new AltName({ schema: asn1.result }); } catch(ex) { this.parsedValue = new AltName(); this.parsedValue.parsingError = "Incorrectly formated AltName"; } break; case "2.5.29.19": // BasicConstraints try { this.parsedValue = new BasicConstraints({ schema: asn1.result }); } catch(ex) { this.parsedValue = new BasicConstraints(); this.parsedValue.parsingError = "Incorrectly formated BasicConstraints"; } break; case "2.5.29.20": // CRLNumber case "2.5.29.27": // BaseCRLNumber (delta CRL indicator) this.parsedValue = asn1.result; // Should be just a simple INTEGER break; case "2.5.29.21": // CRLReason this.parsedValue = asn1.result; // Should be just a simple ENUMERATED break; case "2.5.29.24": // InvalidityDate this.parsedValue = asn1.result; // Should be just a simple GeneralizedTime break; case "2.5.29.28": // IssuingDistributionPoint try { this.parsedValue = new IssuingDistributionPoint({ schema: asn1.result }); } catch(ex) { this.parsedValue = new IssuingDistributionPoint(); this.parsedValue.parsingError = "Incorrectly formated IssuingDistributionPoint"; } break; case "2.5.29.29": // CertificateIssuer try { this.parsedValue = new GeneralNames({ schema: asn1.result }); // Should be just a simple } catch(ex) { this.parsedValue = new GeneralNames(); this.parsedValue.parsingError = "Incorrectly formated GeneralNames"; } break; case "2.5.29.30": // NameConstraints try { this.parsedValue = new NameConstraints({ schema: asn1.result }); } catch(ex) { this.parsedValue = new NameConstraints(); this.parsedValue.parsingError = "Incorrectly formated NameConstraints"; } break; case "2.5.29.31": // CRLDistributionPoints case "2.5.29.46": // FreshestCRL try { this.parsedValue = new CRLDistributionPoints({ schema: asn1.result }); } catch(ex) { this.parsedValue = new CRLDistributionPoints(); this.parsedValue.parsingError = "Incorrectly formated CRLDistributionPoints"; } break; case "2.5.29.32": // CertificatePolicies case "1.3.6.1.4.1.311.21.10": // szOID_APPLICATION_CERT_POLICIES - Microsoft-specific OID try { this.parsedValue = new CertificatePolicies({ schema: asn1.result }); } catch(ex) { this.parsedValue = new CertificatePolicies(); this.parsedValue.parsingError = "Incorrectly formated CertificatePolicies"; } break; case "2.5.29.33": // PolicyMappings try { this.parsedValue = new PolicyMappings({ schema: asn1.result }); } catch(ex) { this.parsedValue = new PolicyMappings(); this.parsedValue.parsingError = "Incorrectly formated CertificatePolicies"; } break; case "2.5.29.35": // AuthorityKeyIdentifier try { this.parsedValue = new AuthorityKeyIdentifier({ schema: asn1.result }); } catch(ex) { this.parsedValue = new AuthorityKeyIdentifier(); this.parsedValue.parsingError = "Incorrectly formated AuthorityKeyIdentifier"; } break; case "2.5.29.36": // PolicyConstraints try { this.parsedValue = new PolicyConstraints({ schema: asn1.result }); } catch(ex) { this.parsedValue = new PolicyConstraints(); this.parsedValue.parsingError = "Incorrectly formated PolicyConstraints"; } break; case "2.5.29.37": // ExtKeyUsage try { this.parsedValue = new ExtKeyUsage({ schema: asn1.result }); } catch(ex) { this.parsedValue = new ExtKeyUsage(); this.parsedValue.parsingError = "Incorrectly formated ExtKeyUsage"; } break; case "2.5.29.54": // InhibitAnyPolicy this.parsedValue = asn1.result; // Should be just a simple INTEGER break; case "1.3.6.1.5.5.7.1.1": // AuthorityInfoAccess case "1.3.6.1.5.5.7.1.11": // SubjectInfoAccess try { this.parsedValue = new InfoAccess({ schema: asn1.result }); } catch(ex) { this.parsedValue = new InfoAccess(); this.parsedValue.parsingError = "Incorrectly formated InfoAccess"; } break; case "1.3.6.1.4.1.11129.2.4.2": // SignedCertificateTimestampList try { this.parsedValue = new SignedCertificateTimestampList({ schema: asn1.result }); } catch(ex) { this.parsedValue = new SignedCertificateTimestampList(); this.parsedValue.parsingError = "Incorrectly formated SignedCertificateTimestampList"; } break; case "1.3.6.1.4.1.311.20.2": // szOID_ENROLL_CERTTYPE_EXTENSION - Microsoft-specific extension this.parsedValue = asn1.result; // Used to be simple Unicode string break; case "1.3.6.1.4.1.311.21.2": // szOID_CERTSRV_PREVIOUS_CERT_HASH - Microsoft-specific extension this.parsedValue = asn1.result; // Used to be simple OctetString break; case "1.3.6.1.4.1.311.21.7": // szOID_CERTIFICATE_TEMPLATE - Microsoft-specific extension try { this.parsedValue = new CertificateTemplate({ schema: asn1.result }); } catch(ex) { this.parsedValue = new CertificateTemplate(); this.parsedValue.parsingError = "Incorrectly formated CertificateTemplate"; } break; case "1.3.6.1.4.1.311.21.1": // szOID_CERTSRV_CA_VERSION - Microsoft-specific extension try { this.parsedValue = new CAVersion({ schema: asn1.result }); } catch(ex) { this.parsedValue = new CAVersion(); this.parsedValue.parsingError = "Incorrectly formated CAVersion"; } break; default: } //endregion //endregion }