From 54033346f945ec196699a8ec123f5906d85d9ca4 Mon Sep 17 00:00:00 2001 From: pehmo1 Date: Thu, 12 Aug 2021 22:27:58 +0300 Subject: [PATCH 1/6] Add pkcs12chain.js and util.js pkcs12chain returns a PFX with private key and cert chain. Comes from: https://github.com/PeculiarVentures/PKI.js/issues/104 util.js contains various conversion functions, the asn1js examples all use inline conversions so no pre-made functions available. formatPEM.js came from pkijs examples (examples_common.js). --- static/js/pkcs12chain.js | 120 +++++++++++++++++++++++++++++++++++++++ static/js/util.js | 51 +++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 static/js/pkcs12chain.js create mode 100644 static/js/util.js diff --git a/static/js/pkcs12chain.js b/static/js/pkcs12chain.js new file mode 100644 index 0000000..65d0741 --- /dev/null +++ b/static/js/pkcs12chain.js @@ -0,0 +1,120 @@ +import * as asn1js from "asn1js"; +import { + stringToArrayBuffer, + fromBase64, +} from "pvutils"; +import { + getRandomValues +} from "../node_modules/pkijs/src/common.js"; +import Certificate from "../node_modules/pkijs/src/Certificate.js"; +import PrivateKeyInfo from "../node_modules/pkijs/src/PrivateKeyInfo"; +import Attribute from "../node_modules/pkijs/src/Attribute"; +import SafeBag from "../node_modules/pkijs/src/SafeBag"; +import PKCS8ShroudedKeyBag from "../node_modules/pkijs/src/PKCS8ShroudedKeyBag"; +import PFX from "../node_modules/pkijs/src/PFX"; +import AuthenticatedSafe from "../node_modules/pkijs/src/AuthenticatedSafe"; +import SafeContents from "../node_modules/pkijs/src/SafeContents"; +import CertBag from "../node_modules/pkijs/src/CertBag"; + +export async function pkcs12chain(priv, certs, password, hash_alg) { + const asn1 = asn1js.fromBER(stringToArrayBuffer(fromBase64(priv))); + const pkcs8Simpl = new PrivateKeyInfo({schema: asn1.result}); + + const keyLocalIDBuffer = new ArrayBuffer(4); + const keyLocalIDView = new Uint8Array(keyLocalIDBuffer); + getRandomValues(keyLocalIDView); + + const bitArray = new ArrayBuffer(1); + const bitView = new Uint8Array(bitArray); + + bitView[0] = bitView[0] | 0x80; + + const keyUsage = new asn1js.BitString({ + valueHex: bitArray, + unusedBits: 7 + }); + + pkcs8Simpl.attributes = [ + new Attribute({ + type: "2.5.29.15", + values: [ + keyUsage + ] + }) + ]; + + const safeBags = [ + new SafeBag({ + bagId: "1.2.840.113549.1.12.10.1.2", + bagValue: new PKCS8ShroudedKeyBag({ + parsedValue: pkcs8Simpl + }), + bagAttributes: [ + new Attribute({ + type: "1.2.840.113549.1.9.21", // localKeyID + values: [ + new asn1js.OctetString({valueHex: keyLocalIDBuffer}) + ] + }) + ] + }) + ]; + + const numCerts = certs.length; + for (let i=0;i { + let arrayBuf = new ArrayBuffer(0); + arrayBuf = await crypto.exportKey("pkcs8", privKey); + + resolve(formatPEM(toBase64(arrayBufferToString(arrayBuf)))); + }); +} + +export async function privKeyToPem(privKey, crypto) { + return new Promise(async (resolve, reject) => { + let privKeyExported = await crypto.exportKey("pkcs8", privKey); + let privKeyBody = formatPEM( + toBase64( + String.fromCharCode.apply(null, new Uint8Array(privKeyExported)) + ) + ); + let privKeyPem = `-----BEGIN RSA PRIVATE KEY-----\r\n${privKeyBody}\r\n-----END RSA PRIVATE KEY-----\r\n`; + + resolve(privKeyPem); + }); +} + +export async function certReqToPem(csr) { + return new Promise(async (resolve, reject) => { + let resPem = "-----BEGIN CERTIFICATE REQUEST-----\r\n"; + resPem = `${resPem}${formatPEM( + toBase64(arrayBufferToString(csr.toSchema().toBER(false))) + )}`; + resPem = `${resPem}\r\n-----END CERTIFICATE REQUEST-----\r\n`; + + resolve(resPem); + }); +} \ No newline at end of file From baa6acbf77bbbe0a0cb15842251c8777b24918da Mon Sep 17 00:00:00 2001 From: pehmo1 Date: Thu, 12 Aug 2021 23:01:23 +0300 Subject: [PATCH 2/6] Remove forge completely, clean up code, fix bugs --- static/js/certidude.js | 217 +++++++++++++++++------------------------ 1 file changed, 88 insertions(+), 129 deletions(-) diff --git a/static/js/certidude.js b/static/js/certidude.js index ab052ec..bbd224e 100644 --- a/static/js/certidude.js +++ b/static/js/certidude.js @@ -4,21 +4,22 @@ import { arrayBufferToString, stringToArrayBuffer, toBase64, - fromBase64, bufferToHexCodes } from "pvutils"; import { getCrypto, getAlgorithmParameters, } from "../node_modules/pkijs/src/common.js"; -import { formatPEM } from "./formatPEM.js"; import CertificationRequest from "../node_modules/pkijs/src/CertificationRequest.js"; import AttributeTypeAndValue from "../node_modules/pkijs/src/AttributeTypeAndValue.js"; -import Certificate from "../node_modules/pkijs/src/Certificate.js"; +import { formatPEM } from "./formatPEM.js"; +import { pkcs12chain } from "./pkcs12chain.js"; +import { + privKeyToBase64, + privKeyToPem, + certReqToPem +} from "./util.js" -let hashAlg = "SHA-384"; -let signAlg = "RSASSA-PKCS1-V1_5"; -const KEY_SIZE = 2048; const DEVICE_KEYWORDS = ["Android", "iPhone", "iPad", "Windows", "Ubuntu", "Fedora", "Mac", "Linux"]; jQuery.timeago.settings.allowFuture = true; @@ -81,16 +82,14 @@ function onShowAll() { } function onKeyGen() { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { if (window.navigator.userAgent.indexOf(" Edge/") >= 0) { $("#enroll .loader-container").hide(); $("#enroll .edge-broken").show(); return; } - let sequence = Promise.resolve(); - const pkcs10 = new CertificationRequest(); - let publicKey, privateKey; + let pkcs10 = new CertificationRequest(); // Commonname pkcs10.subject.typesAndValues.push( @@ -102,72 +101,57 @@ function onKeyGen() { pkcs10.attributes = []; - sequence = sequence.then(() => { - const algorithm = getAlgorithmParameters(signAlg, "generatekey"); - if ("hash" in algorithm.algorithm) - algorithm.algorithm.hash.name = hashAlg; + let algorithm; + if (authority.certificate.algorithm == "rsa") { + algorithm = getAlgorithmParameters( + window.authority.rsa_sign_alg, "generatekey"); + } + if (authority.certificate.algorithm == "ec") { + algorithm = getAlgorithmParameters( + window.authority.ec_sign_alg, "generatekey"); + } + if ("hash" in algorithm.algorithm) + algorithm.algorithm.hash.name = window.authority.hash_alg; - return crypto.generateKey(algorithm.algorithm, true, algorithm.usages); - }); + const keyPair = await crypto.generateKey( + algorithm.algorithm, true, algorithm.usages); + window.keys = keyPair; + const publicKey = keyPair.publicKey; + const privateKey = keyPair.privateKey; - sequence = sequence.then( - (keyPair) => { - window.keys = keyPair; - publicKey = keyPair.publicKey; - privateKey = keyPair.privateKey; - }, - (error) => Promise.reject(`Error during key generation: ${error}`) - ); + await pkcs10.subjectPublicKeyInfo.importKey(publicKey); + await pkcs10.sign(privateKey, window.authority.hash_alg); + window.csr = pkcs10; + console.info("Certification request created"); - sequence = sequence.then(() => - pkcs10.subjectPublicKeyInfo.importKey(publicKey) - ); - - sequence = sequence.then( - async () => { - pkcs10.sign(privateKey, hashAlg); - window.csr = pkcs10; - console.info("Certification request created"); - var pkcs8 = await crypto.exportKey("pkcs8", keys.privateKey); - var pem = formatPEM( - toBase64(String.fromCharCode.apply(null, new Uint8Array(pkcs8))) - ); - console.log( - `-----BEGIN RSA PRIVATE KEY-----\r\n${pem}\r\n-----END RSA PRIVATE KEY-----\r\n` - ); - resolve(); - }, - (error) => Promise.reject(`Error during exporting public key: ${error}`) - ); - - sequence = sequence.then(() => { - $("#enroll .loader-container").hide(); - var prefix = null; - for (i in DEVICE_KEYWORDS) { - var keyword = DEVICE_KEYWORDS[i]; - if (window.navigator.userAgent.indexOf(keyword) >= 0) { - prefix = keyword.toLowerCase(); - break; - } + $("#enroll .loader-container").hide(); + var prefix = null; + for (i in DEVICE_KEYWORDS) { + var keyword = DEVICE_KEYWORDS[i]; + if (window.navigator.userAgent.indexOf(keyword) >= 0) { + prefix = keyword.toLowerCase(); + break; } + } - if (prefix == null) { - $(".option").show(); - return; - } + if (prefix == null) { + $(".option").show(); + return; + } - var protocols = query.protocols.split(","); - console.info("Showing snippets for:", protocols); - for (var j = 0; j < protocols.length; j++) { - var options = document.querySelectorAll( - ".option." + protocols[j] + "." + prefix - ); - for (i = 0; i < options.length; i++) { - options[i].style.display = "block"; - } + var protocols = query.protocols.split(","); + console.info("Showing snippets for:", protocols); + for (var j = 0; j < protocols.length; j++) { + var options = document.querySelectorAll( + ".option." + protocols[j] + "." + prefix + ); + for (i = 0; i < options.length; i++) { + options[i].style.display = "block"; } - $(".option.any").show(); - }); + } + $(".option.any").show(); + + resolve(); }); } @@ -197,58 +181,40 @@ function onEnroll(encoding) { console.info("User agent:", window.navigator.userAgent); var xhr = new XMLHttpRequest(); xhr.open('GET', "/api/certificate/"); - xhr.onload = function() { + xhr.onload = async function() { if (xhr.status === 200) { - // const xhrPEM = xhr.responseText.replace( - // /(-----(BEGIN|END) CERTIFICATE-----|\n)/g, - // "" - // ); - // const xhrAsn1 = asn1js.fromBER(stringToArrayBuffer(fromBase64(xhrPEM))); - // var ca = new Certificate({ schema: xhrAsn1.result }); - var ca = forge.pki.certificateFromPem(xhr.responseText); - console.info("Got CA certificate:"); + const caBase64 = xhr.responseText.replace( + /(-----(BEGIN|END) CERTIFICATE-----|\n)/g, ""); + var xhr2 = new XMLHttpRequest(); xhr2.open("PUT", "/api/token/?token=" + query.token ); xhr2.onload = async function() { if (xhr2.status === 200) { var a = document.createElement("a"); + const certBase64 = xhr.responseText.replace( + /(-----(BEGIN|END) CERTIFICATE-----|\n)/g, ""); - // const xhr2PEM = xhr.responseText.replace( - // /(-----(BEGIN|END) CERTIFICATE-----|\n)/g, - // "" - // ); - // const xhr2asn1 = asn1js.fromBER( - // stringToArrayBuffer(fromBase64(xhr2PEM)) - // ); - // var cert = await new Certificate({ schema: xhr2asn1.result }); - - var cert = forge.pki.certificateFromPem(xhr2.responseText); - console.info("Got signed certificate:", xhr2.responseText); - - // Convert PKIJS key to forge key through PEM - let privateKeyArrayBuffer = new ArrayBuffer(0); - privateKeyArrayBuffer = await crypto.exportKey("pkcs8", keys.privateKey); - let tempPrivPem = `\r\n-----BEGIN PRIVATE KEY-----\r\n`; - tempPrivPem = `${tempPrivPem}${formatPEM(toBase64(arrayBufferToString(privateKeyArrayBuffer)))}`; - tempPrivPem = `${tempPrivPem}\r\n-----END PRIVATE KEY-----\r\n`; - let forgePrivKey = forge.pki.privateKeyFromPem(tempPrivPem); - - var p12 = forge.asn1.toDer(forge.pkcs12.toPkcs12Asn1( - forgePrivKey, [cert, ca], "", {algorithm: '3des'})).getBytes(); + // Private key to base64 (for pkcs12chain) + let privKeyBase64 = await privKeyToBase64(keys.privateKey, crypto); switch(encoding) { case 'p12': - var buf = forge.asn1.toDer(p12).getBytes(); + var p12 = await pkcs12chain(privKeyBase64, [certBase64, caBase64], "", window.authority.hash_alg); + + var buf = arrayBufferToString(p12.toSchema().toBER(false)); var mimetype = "application/x-pkcs12" a.download = query.title + ".p12"; break case 'sswan': + var p12 = arrayBufferToString( + (await pkcs12chain(privKeyBase64, [certBase64, caBase64], "", window.authority.hash_alg)).toSchema().toBER(false)); + var buf = JSON.stringify({ - uuid: blobToUuid(authority.namespace), + uuid: await blobToUuid(authority.namespace), name: authority.namespace, type: "ikev2-cert", - 'ike-proposal': 'aes256-sha384-prfsha384-modp2048', - 'esp-proposal': 'aes128gcm16-modp2048', + 'ike-proposal': window.authority.strongswan.ike, + 'esp-proposal': window.authority.strongswan.esp, remote: { addr: authority.namespace, revocation: { @@ -265,13 +231,7 @@ function onEnroll(encoding) { a.download = query.title + ".sswan"; break case 'ovpn': - let privKey = await crypto.exportKey("pkcs8", keys.privateKey); - let privKeyBody = formatPEM( - toBase64( - String.fromCharCode.apply(null, new Uint8Array(privKey)) - ) - ); - let privKeyPem = `-----BEGIN RSA PRIVATE KEY-----\r\n${privKeyBody}\r\n-----END RSA PRIVATE KEY-----\r\n`; + let privKeyPem = await privKeyToPem(keys.privateKey, crypto); var buf = nunjucks.render('snippets/openvpn-client.conf', { authority: authority, @@ -283,23 +243,23 @@ function onEnroll(encoding) { a.download = query.title + ".ovpn"; break case 'mobileconfig': - var p12 = forge.asn1.toDer(forge.pkcs12.toPkcs12Asn1( - keys.privateKey, [cert, ca], "1234", {algorithm: '3des'})).getBytes(); + var p12 = arrayBufferToString( + (await pkcs12chain( + privKeyBase64, [certBase64, caBase64], + "1234", window.authority.hash_alg)) + .toSchema().toBER(false)); + var buf = nunjucks.render('snippets/ios.mobileconfig', { authority: authority, - service_uuid: blobToUuid(query.title), - conf_uuid: blobToUuid(query.title + " conf1"), + service_uuid: await blobToUuid(query.title), + conf_uuid: await blobToUuid(query.title + " conf1"), title: query.title, common_name: common_name, gateway: authority.namespace, - p12_uuid: blobToUuid(p12), + p12_uuid: await blobToUuid(p12), p12: toBase64(p12), - ca_uuid: blobToUuid( - forge.asn1.toDer(forge.pki.certificateToAsn1(ca)).getBytes() - ), - ca: toBase64( - forge.asn1.toDer(forge.pki.certificateToAsn1(ca)).getBytes() - ), + ca_uuid: await blobToUuid(caBase64), + ca: caBase64, }); var mimetype = "application/x-apple-aspen-config"; a.download = query.title + ".mobileconfig"; @@ -321,11 +281,7 @@ function onEnroll(encoding) { } } }; - let resultString = "-----BEGIN CERTIFICATE REQUEST-----\r\n"; - resultString = `${resultString}${formatPEM( - toBase64(arrayBufferToString(csr.toSchema().toBER(false))) - )}`; - resultString = `${resultString}\r\n-----END CERTIFICATE REQUEST-----\r\n`; + let resultString = await certReqToPem(window.csr); xhr2.send(resultString); } @@ -333,7 +289,7 @@ function onEnroll(encoding) { xhr.send(); } -function onHashChanged() { +async function onHashChanged() { window.query = {}; var a = location.hash.substring(1).split('&'); @@ -357,6 +313,9 @@ function onHashChanged() { }, success: async function(authority) { window.authority = authority + window.authority.hash_alg = "SHA-384"; + window.authority.rsa_sign_alg = "RSASSA-PKCS1-v1_5"; + window.authority.ec_sign_alg = "ECDSA"; var prefix = "unknown"; for (i in DEVICE_KEYWORDS) { @@ -385,8 +344,8 @@ function onHashChanged() { for (i = 0; i < options.length; i++) { options[i].style.display = "none"; } - setTimeout(onKeyGen, 100); console.info("Generating key pair..."); + await onKeyGen(); } else { loadAuthority(query); } From 863db37ccbac0de9d6ae2fc105f6f19fa3047329 Mon Sep 17 00:00:00 2001 From: pehmo1 Date: Thu, 12 Aug 2021 23:22:52 +0300 Subject: [PATCH 3/6] Remove forge from Dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9850dff..8b80ebb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM alpine MAINTAINER Pinecrypt Labs RUN apk add --update npm nginx rsync bash -RUN npm install --prefix /usr/local --silent --no-optional -g nunjucks@2.5.2 nunjucks-date@1.2.0 node-forge bootstrap@4.0.0-alpha.6 jquery timeago tether font-awesome qrcode-svg xterm rollup +RUN npm install --prefix /usr/local --silent --no-optional -g nunjucks@2.5.2 nunjucks-date@1.2.0 bootstrap@4.0.0-alpha.6 jquery timeago tether font-awesome qrcode-svg xterm rollup RUN test -e /usr/local/lib/node_modules/jquery/dist/jquery.min.js COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 80 443 8443 @@ -13,7 +13,7 @@ COPY templates templates COPY rollup.config.js . RUN rollup -c RUN nunjucks-precompile --include snippets --include views templates >> js/bundle.js -RUN bash -c 'cat /usr/local/lib/node_modules/{jquery/dist/jquery.min.js,tether/dist/js/tether.min.js,bootstrap/dist/js/bootstrap.min.js,node-forge/dist/forge.all.min.js,qrcode-svg/dist/qrcode.min.js,timeago/jquery.timeago.js,nunjucks/browser/nunjucks-slim.min.js,xterm/lib/xterm.js} >> js/bundle.js' +RUN bash -c 'cat /usr/local/lib/node_modules/{jquery/dist/jquery.min.js,tether/dist/js/tether.min.js,bootstrap/dist/js/bootstrap.min.js,qrcode-svg/dist/qrcode.min.js,timeago/jquery.timeago.js,nunjucks/browser/nunjucks-slim.min.js,xterm/lib/xterm.js} >> js/bundle.js' RUN bash -c 'cat /usr/local/lib/node_modules/{tether/dist/css/tether.min.css,bootstrap/dist/css/bootstrap.min.css,font-awesome/css/font-awesome.min.css,xterm/css/xterm.css} >> css/bundle.css' RUN mkdir /frontend-secrets RUN ln -s ../server-secrets/self_cert.pem /frontend-secrets/fullchain.pem From e2077a89cf743416755ef970ccce73a7dc306b10 Mon Sep 17 00:00:00 2001 From: pehmo1 Date: Mon, 16 Aug 2021 19:20:29 +0300 Subject: [PATCH 4/6] Make util.js functions generic Also moved crypto to window.cryptoEngine for easy access in util.js --- static/js/certidude.js | 35 +++++++++----------- static/js/util.js | 73 +++++++++++++++++++++--------------------- 2 files changed, 51 insertions(+), 57 deletions(-) diff --git a/static/js/certidude.js b/static/js/certidude.js index bbd224e..5c76ba5 100644 --- a/static/js/certidude.js +++ b/static/js/certidude.js @@ -12,20 +12,18 @@ import { } from "../node_modules/pkijs/src/common.js"; import CertificationRequest from "../node_modules/pkijs/src/CertificationRequest.js"; import AttributeTypeAndValue from "../node_modules/pkijs/src/AttributeTypeAndValue.js"; -import { formatPEM } from "./formatPEM.js"; import { pkcs12chain } from "./pkcs12chain.js"; -import { - privKeyToBase64, - privKeyToPem, - certReqToPem +import { + pkijsToPem, + pkijsToBase64, } from "./util.js" const DEVICE_KEYWORDS = ["Android", "iPhone", "iPad", "Windows", "Ubuntu", "Fedora", "Mac", "Linux"]; jQuery.timeago.settings.allowFuture = true; -const crypto = getCrypto(); -if (typeof crypto === "undefined") +window.cryptoEngine = getCrypto(); +if (typeof window.cryptoEngine === "undefined") console.error("No WebCrypto extension found"); function onLaunchShell(common_name) { @@ -113,7 +111,7 @@ function onKeyGen() { if ("hash" in algorithm.algorithm) algorithm.algorithm.hash.name = window.authority.hash_alg; - const keyPair = await crypto.generateKey( + const keyPair = await window.cryptoEngine.generateKey( algorithm.algorithm, true, algorithm.usages); window.keys = keyPair; const publicKey = keyPair.publicKey; @@ -157,7 +155,10 @@ function onKeyGen() { function blobToUuid(blob) { return new Promise((resolve, reject) => { - crypto.digest({ name: "SHA-1" }, stringToArrayBuffer(blob)).then((res) => { + window.cryptoEngine.digest( + { name: "SHA-1" }, + stringToArrayBuffer(blob)) + .then((res) => { res = bufferToHexCodes(res).toLowerCase(); res = res.substring(0, 8) + @@ -195,7 +196,7 @@ function onEnroll(encoding) { /(-----(BEGIN|END) CERTIFICATE-----|\n)/g, ""); // Private key to base64 (for pkcs12chain) - let privKeyBase64 = await privKeyToBase64(keys.privateKey, crypto); + let privKeyBase64 = await pkijsToBase64(keys.privateKey); switch(encoding) { case 'p12': @@ -231,7 +232,7 @@ function onEnroll(encoding) { a.download = query.title + ".sswan"; break case 'ovpn': - let privKeyPem = await privKeyToPem(keys.privateKey, crypto); + let privKeyPem = await pkijsToPem(keys.privateKey); var buf = nunjucks.render('snippets/openvpn-client.conf', { authority: authority, @@ -281,9 +282,7 @@ function onEnroll(encoding) { } } }; - let resultString = await certReqToPem(window.csr); - - xhr2.send(resultString); + xhr2.send(await pkijsToPem(window.csr)); } } xhr.send(); @@ -769,7 +768,7 @@ function loadAuthority(query) { $("#enroll").click(async function() { - var keys = await crypto.generateKey( + var keys = await window.cryptoEngine.generateKey( { name: "RSASSA-PKCS1-v1_5", modulusLength: 1024, @@ -795,11 +794,7 @@ function loadAuthority(query) { - var pkcs8 = await crypto.exportKey("pkcs8", keys.privateKey); - var pem = formatPEM( - toBase64(String.fromCharCode.apply(null, new Uint8Array(pkcs8))) - ); - privateKeyBuffer = `-----BEGIN RSA PRIVATE KEY-----\r\n${pem}\r\n-----END RSA PRIVATE KEY-----\r\n`; + var privateKeyBuffer = pkijsToPem(keys.privateKey); }); /** diff --git a/static/js/util.js b/static/js/util.js index 87c0b9f..cf6afb8 100644 --- a/static/js/util.js +++ b/static/js/util.js @@ -1,51 +1,50 @@ -import * as asn1js from "asn1js"; import { arrayBufferToString, - stringToArrayBuffer, toBase64, - fromBase64, - bufferToHexCodes } from "pvutils"; -import { - getCrypto, - getAlgorithmParameters, -} from "../node_modules/pkijs/src/common.js"; -import CertificationRequest from "../node_modules/pkijs/src/CertificationRequest.js"; -import AttributeTypeAndValue from "../node_modules/pkijs/src/AttributeTypeAndValue.js"; -import Certificate from "../node_modules/pkijs/src/Certificate.js"; import { formatPEM } from "./formatPEM.js"; -export async function privKeyToBase64(privKey, crypto) { +export function pkijsToBase64(pkijsObj) { return new Promise(async (resolve, reject) => { - let arrayBuf = new ArrayBuffer(0); - arrayBuf = await crypto.exportKey("pkcs8", privKey); - - resolve(formatPEM(toBase64(arrayBufferToString(arrayBuf)))); + switch(pkijsObj.__proto__.constructor.name) { + case "CryptoKey": + let arrayBuf = new ArrayBuffer(0); + + if (pkijsObj.type == "private") + arrayBuf = await window.cryptoEngine.exportKey("pkcs8", pkijsObj); + else + arrayBuf = await window.cryptoEngine.exportKey("spki", pkijsObj); + + resolve(toBase64(arrayBufferToString(arrayBuf))); + break; + + case "CertificationRequest": + resolve(toBase64(arrayBufferToString(pkijsObj.toSchema().toBER(false)))); + break; + } }); } -export async function privKeyToPem(privKey, crypto) { +export function pkijsToPem(pkijsObj) { return new Promise(async (resolve, reject) => { - let privKeyExported = await crypto.exportKey("pkcs8", privKey); - let privKeyBody = formatPEM( - toBase64( - String.fromCharCode.apply(null, new Uint8Array(privKeyExported)) - ) - ); - let privKeyPem = `-----BEGIN RSA PRIVATE KEY-----\r\n${privKeyBody}\r\n-----END RSA PRIVATE KEY-----\r\n`; + switch(pkijsObj.__proto__.constructor.name) { + case "CryptoKey": + let privKeyExported = await window.cryptoEngine.exportKey("pkcs8", pkijsObj); + let privKeyBody = formatPEM( + toBase64( + String.fromCharCode.apply(null, new Uint8Array(privKeyExported)) + ) + ); + resolve(`-----BEGIN RSA PRIVATE KEY-----\r\n${privKeyBody}\r\n-----END RSA PRIVATE KEY-----\r\n`); + break; - resolve(privKeyPem); - }); -} - -export async function certReqToPem(csr) { - return new Promise(async (resolve, reject) => { - let resPem = "-----BEGIN CERTIFICATE REQUEST-----\r\n"; - resPem = `${resPem}${formatPEM( - toBase64(arrayBufferToString(csr.toSchema().toBER(false))) - )}`; - resPem = `${resPem}\r\n-----END CERTIFICATE REQUEST-----\r\n`; - - resolve(resPem); + case "CertificationRequest": + let resPem = "-----BEGIN CERTIFICATE REQUEST-----\r\n"; + resPem = `${resPem}${formatPEM( + toBase64(arrayBufferToString(pkijsObj.toSchema().toBER(false))) + )}`; + resolve(`${resPem}\r\n-----END CERTIFICATE REQUEST-----\r\n`); + break; + } }); } \ No newline at end of file From 4ee487073d443046bca1100819ad930200d2a592 Mon Sep 17 00:00:00 2001 From: pehmo1 Date: Tue, 17 Aug 2021 22:36:12 +0300 Subject: [PATCH 5/6] Make pkijsToPem return generic private key PEM --- static/js/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/util.js b/static/js/util.js index cf6afb8..ec1d4c2 100644 --- a/static/js/util.js +++ b/static/js/util.js @@ -35,7 +35,7 @@ export function pkijsToPem(pkijsObj) { String.fromCharCode.apply(null, new Uint8Array(privKeyExported)) ) ); - resolve(`-----BEGIN RSA PRIVATE KEY-----\r\n${privKeyBody}\r\n-----END RSA PRIVATE KEY-----\r\n`); + resolve(`-----BEGIN PRIVATE KEY-----\r\n${privKeyBody}\r\n-----END PRIVATE KEY-----\r\n`); break; case "CertificationRequest": From 613da4d0b5768876f379b9814e7aaff423576a15 Mon Sep 17 00:00:00 2001 From: pehmo1 Date: Thu, 19 Aug 2021 14:48:17 +0300 Subject: [PATCH 6/6] Get all crypto values from authority --- static/js/certidude.js | 36 +++++++++++++++++++----------------- static/js/util.js | 4 ++++ 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/static/js/certidude.js b/static/js/certidude.js index 5c76ba5..d2eef0e 100644 --- a/static/js/certidude.js +++ b/static/js/certidude.js @@ -16,6 +16,7 @@ import { pkcs12chain } from "./pkcs12chain.js"; import { pkijsToPem, pkijsToBase64, + pemToBase64, } from "./util.js" const DEVICE_KEYWORDS = ["Android", "iPhone", "iPad", "Windows", "Ubuntu", "Fedora", "Mac", "Linux"]; @@ -102,14 +103,14 @@ function onKeyGen() { let algorithm; if (authority.certificate.algorithm == "rsa") { algorithm = getAlgorithmParameters( - window.authority.rsa_sign_alg, "generatekey"); + window.authority.certificate.key_type_specific, "generatekey"); } if (authority.certificate.algorithm == "ec") { algorithm = getAlgorithmParameters( - window.authority.ec_sign_alg, "generatekey"); + window.authority.certificate.curve, "generatekey"); } if ("hash" in algorithm.algorithm) - algorithm.algorithm.hash.name = window.authority.hash_alg; + algorithm.algorithm.hash.name = window.authority.certificate.hash_algorithm; const keyPair = await window.cryptoEngine.generateKey( algorithm.algorithm, true, algorithm.usages); @@ -118,7 +119,7 @@ function onKeyGen() { const privateKey = keyPair.privateKey; await pkcs10.subjectPublicKeyInfo.importKey(publicKey); - await pkcs10.sign(privateKey, window.authority.hash_alg); + await pkcs10.sign(privateKey, window.authority.certificate.hash_algorithm); window.csr = pkcs10; console.info("Certification request created"); @@ -184,23 +185,21 @@ function onEnroll(encoding) { xhr.open('GET', "/api/certificate/"); xhr.onload = async function() { if (xhr.status === 200) { - const caBase64 = xhr.responseText.replace( - /(-----(BEGIN|END) CERTIFICATE-----|\n)/g, ""); + const caBase64 = pemToBase64(xhr.responseText); var xhr2 = new XMLHttpRequest(); xhr2.open("PUT", "/api/token/?token=" + query.token ); xhr2.onload = async function() { if (xhr2.status === 200) { var a = document.createElement("a"); - const certBase64 = xhr.responseText.replace( - /(-----(BEGIN|END) CERTIFICATE-----|\n)/g, ""); + const certBase64 = pemToBase64(xhr.responseText); // Private key to base64 (for pkcs12chain) let privKeyBase64 = await pkijsToBase64(keys.privateKey); switch(encoding) { case 'p12': - var p12 = await pkcs12chain(privKeyBase64, [certBase64, caBase64], "", window.authority.hash_alg); + var p12 = await pkcs12chain(privKeyBase64, [certBase64, caBase64], "", window.authority.certificate.hash_algorithm); var buf = arrayBufferToString(p12.toSchema().toBER(false)); var mimetype = "application/x-pkcs12" @@ -208,7 +207,7 @@ function onEnroll(encoding) { break case 'sswan': var p12 = arrayBufferToString( - (await pkcs12chain(privKeyBase64, [certBase64, caBase64], "", window.authority.hash_alg)).toSchema().toBER(false)); + (await pkcs12chain(privKeyBase64, [certBase64, caBase64], "", window.authority.certificate.hash_algorithm)).toSchema().toBER(false)); var buf = JSON.stringify({ uuid: await blobToUuid(authority.namespace), @@ -247,7 +246,7 @@ function onEnroll(encoding) { var p12 = arrayBufferToString( (await pkcs12chain( privKeyBase64, [certBase64, caBase64], - "1234", window.authority.hash_alg)) + "1234", window.authority.certificate.hash_algorithm)) .toSchema().toBER(false)); var buf = nunjucks.render('snippets/ios.mobileconfig', { @@ -312,9 +311,12 @@ async function onHashChanged() { }, success: async function(authority) { window.authority = authority - window.authority.hash_alg = "SHA-384"; - window.authority.rsa_sign_alg = "RSASSA-PKCS1-v1_5"; - window.authority.ec_sign_alg = "ECDSA"; + + // convert "sha512" to "SHA-512" + window.authority.certificate.hash_algorithm = + (window.authority.certificate.hash_algorithm.slice(0,3) + + "-" + window.authority.certificate.hash_algorithm.slice(3)) + .toUpperCase(); var prefix = "unknown"; for (i in DEVICE_KEYWORDS) { @@ -770,10 +772,10 @@ function loadAuthority(query) { $("#enroll").click(async function() { var keys = await window.cryptoEngine.generateKey( { - name: "RSASSA-PKCS1-v1_5", - modulusLength: 1024, + name: window.authority.certificate.key_type_specific, + modulusLength: window.authority.certificate.key_size, publicExponent: new Uint8Array([1, 0, 1]), - hash: "SHA-256", + hash: window.authority.certificate.hash_algorithm, }, true, ["encrypt", "decrypt"]); diff --git a/static/js/util.js b/static/js/util.js index ec1d4c2..e9a4c43 100644 --- a/static/js/util.js +++ b/static/js/util.js @@ -47,4 +47,8 @@ export function pkijsToPem(pkijsObj) { break; } }); +} + +export function pemToBase64(pem) { + return pem.replace(/(-----(BEGIN|END) CERTIFICATE-----|\n)/g, ""); } \ No newline at end of file