Add support for EC keys #3
@ -1,7 +1,7 @@
|
|||||||
FROM alpine
|
FROM alpine
|
||||||
MAINTAINER Pinecrypt Labs <info@pinecrypt.com>
|
MAINTAINER Pinecrypt Labs <info@pinecrypt.com>
|
||||||
RUN apk add --update npm nginx rsync bash
|
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
|
RUN test -e /usr/local/lib/node_modules/jquery/dist/jquery.min.js
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
EXPOSE 80 443 8443
|
EXPOSE 80 443 8443
|
||||||
@ -13,7 +13,7 @@ COPY templates templates
|
|||||||
COPY rollup.config.js .
|
COPY rollup.config.js .
|
||||||
RUN rollup -c
|
RUN rollup -c
|
||||||
RUN nunjucks-precompile --include snippets --include views templates >> js/bundle.js
|
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 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 mkdir /frontend-secrets
|
||||||
RUN ln -s ../server-secrets/self_cert.pem /frontend-secrets/fullchain.pem
|
RUN ln -s ../server-secrets/self_cert.pem /frontend-secrets/fullchain.pem
|
||||||
|
@ -4,27 +4,27 @@ import {
|
|||||||
arrayBufferToString,
|
arrayBufferToString,
|
||||||
stringToArrayBuffer,
|
stringToArrayBuffer,
|
||||||
toBase64,
|
toBase64,
|
||||||
fromBase64,
|
|
||||||
bufferToHexCodes
|
bufferToHexCodes
|
||||||
} from "pvutils";
|
} from "pvutils";
|
||||||
import {
|
import {
|
||||||
getCrypto,
|
getCrypto,
|
||||||
getAlgorithmParameters,
|
getAlgorithmParameters,
|
||||||
} from "../node_modules/pkijs/src/common.js";
|
} from "../node_modules/pkijs/src/common.js";
|
||||||
import { formatPEM } from "./formatPEM.js";
|
|
||||||
import CertificationRequest from "../node_modules/pkijs/src/CertificationRequest.js";
|
import CertificationRequest from "../node_modules/pkijs/src/CertificationRequest.js";
|
||||||
import AttributeTypeAndValue from "../node_modules/pkijs/src/AttributeTypeAndValue.js";
|
import AttributeTypeAndValue from "../node_modules/pkijs/src/AttributeTypeAndValue.js";
|
||||||
import Certificate from "../node_modules/pkijs/src/Certificate.js";
|
import { pkcs12chain } from "./pkcs12chain.js";
|
||||||
|
import {
|
||||||
|
pkijsToPem,
|
||||||
|
pkijsToBase64,
|
||||||
|
pemToBase64,
|
||||||
|
} 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"];
|
const DEVICE_KEYWORDS = ["Android", "iPhone", "iPad", "Windows", "Ubuntu", "Fedora", "Mac", "Linux"];
|
||||||
|
|
||||||
jQuery.timeago.settings.allowFuture = true;
|
jQuery.timeago.settings.allowFuture = true;
|
||||||
|
|
||||||
const crypto = getCrypto();
|
window.cryptoEngine = getCrypto();
|
||||||
if (typeof crypto === "undefined")
|
if (typeof window.cryptoEngine === "undefined")
|
||||||
console.error("No WebCrypto extension found");
|
console.error("No WebCrypto extension found");
|
||||||
|
|
||||||
function onLaunchShell(common_name) {
|
function onLaunchShell(common_name) {
|
||||||
@ -81,16 +81,14 @@ function onShowAll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onKeyGen() {
|
function onKeyGen() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
if (window.navigator.userAgent.indexOf(" Edge/") >= 0) {
|
if (window.navigator.userAgent.indexOf(" Edge/") >= 0) {
|
||||||
$("#enroll .loader-container").hide();
|
$("#enroll .loader-container").hide();
|
||||||
$("#enroll .edge-broken").show();
|
$("#enroll .edge-broken").show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sequence = Promise.resolve();
|
let pkcs10 = new CertificationRequest();
|
||||||
const pkcs10 = new CertificationRequest();
|
|
||||||
let publicKey, privateKey;
|
|
||||||
|
|
||||||
// Commonname
|
// Commonname
|
||||||
pkcs10.subject.typesAndValues.push(
|
pkcs10.subject.typesAndValues.push(
|
||||||
@ -102,45 +100,29 @@ function onKeyGen() {
|
|||||||
|
|
||||||
pkcs10.attributes = [];
|
pkcs10.attributes = [];
|
||||||
|
|
||||||
sequence = sequence.then(() => {
|
let algorithm;
|
||||||
const algorithm = getAlgorithmParameters(signAlg, "generatekey");
|
if (authority.certificate.algorithm == "rsa") {
|
||||||
|
algorithm = getAlgorithmParameters(
|
||||||
|
window.authority.certificate.key_type_specific, "generatekey");
|
||||||
|
}
|
||||||
|
if (authority.certificate.algorithm == "ec") {
|
||||||
|
algorithm = getAlgorithmParameters(
|
||||||
|
window.authority.certificate.curve, "generatekey");
|
||||||
|
}
|
||||||
if ("hash" in algorithm.algorithm)
|
if ("hash" in algorithm.algorithm)
|
||||||
algorithm.algorithm.hash.name = hashAlg;
|
algorithm.algorithm.hash.name = window.authority.certificate.hash_algorithm;
|
||||||
|
|
||||||
return crypto.generateKey(algorithm.algorithm, true, algorithm.usages);
|
const keyPair = await window.cryptoEngine.generateKey(
|
||||||
});
|
algorithm.algorithm, true, algorithm.usages);
|
||||||
|
|
||||||
sequence = sequence.then(
|
|
||||||
(keyPair) => {
|
|
||||||
window.keys = keyPair;
|
window.keys = keyPair;
|
||||||
publicKey = keyPair.publicKey;
|
const publicKey = keyPair.publicKey;
|
||||||
privateKey = keyPair.privateKey;
|
const privateKey = keyPair.privateKey;
|
||||||
},
|
|
||||||
(error) => Promise.reject(`Error during key generation: ${error}`)
|
|
||||||
);
|
|
||||||
|
|
||||||
sequence = sequence.then(() =>
|
await pkcs10.subjectPublicKeyInfo.importKey(publicKey);
|
||||||
pkcs10.subjectPublicKeyInfo.importKey(publicKey)
|
await pkcs10.sign(privateKey, window.authority.certificate.hash_algorithm);
|
||||||
);
|
|
||||||
|
|
||||||
sequence = sequence.then(
|
|
||||||
async () => {
|
|
||||||
pkcs10.sign(privateKey, hashAlg);
|
|
||||||
window.csr = pkcs10;
|
window.csr = pkcs10;
|
||||||
console.info("Certification request created");
|
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();
|
$("#enroll .loader-container").hide();
|
||||||
var prefix = null;
|
var prefix = null;
|
||||||
for (i in DEVICE_KEYWORDS) {
|
for (i in DEVICE_KEYWORDS) {
|
||||||
@ -167,13 +149,17 @@ function onKeyGen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$(".option.any").show();
|
$(".option.any").show();
|
||||||
});
|
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function blobToUuid(blob) {
|
function blobToUuid(blob) {
|
||||||
return new Promise((resolve, reject) => {
|
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 = bufferToHexCodes(res).toLowerCase();
|
||||||
res =
|
res =
|
||||||
res.substring(0, 8) +
|
res.substring(0, 8) +
|
||||||
@ -197,58 +183,38 @@ function onEnroll(encoding) {
|
|||||||
console.info("User agent:", window.navigator.userAgent);
|
console.info("User agent:", window.navigator.userAgent);
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', "/api/certificate/");
|
xhr.open('GET', "/api/certificate/");
|
||||||
xhr.onload = function() {
|
xhr.onload = async function() {
|
||||||
if (xhr.status === 200) {
|
if (xhr.status === 200) {
|
||||||
// const xhrPEM = xhr.responseText.replace(
|
const caBase64 = pemToBase64(xhr.responseText);
|
||||||
// /(-----(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:");
|
|
||||||
var xhr2 = new XMLHttpRequest();
|
var xhr2 = new XMLHttpRequest();
|
||||||
xhr2.open("PUT", "/api/token/?token=" + query.token );
|
xhr2.open("PUT", "/api/token/?token=" + query.token );
|
||||||
xhr2.onload = async function() {
|
xhr2.onload = async function() {
|
||||||
if (xhr2.status === 200) {
|
if (xhr2.status === 200) {
|
||||||
var a = document.createElement("a");
|
var a = document.createElement("a");
|
||||||
|
const certBase64 = pemToBase64(xhr.responseText);
|
||||||
|
|
||||||
// const xhr2PEM = xhr.responseText.replace(
|
// Private key to base64 (for pkcs12chain)
|
||||||
// /(-----(BEGIN|END) CERTIFICATE-----|\n)/g,
|
let privKeyBase64 = await pkijsToBase64(keys.privateKey);
|
||||||
// ""
|
|
||||||
// );
|
|
||||||
// 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();
|
|
||||||
|
|
||||||
switch(encoding) {
|
switch(encoding) {
|
||||||
case 'p12':
|
case 'p12':
|
||||||
var buf = forge.asn1.toDer(p12).getBytes();
|
var p12 = await pkcs12chain(privKeyBase64, [certBase64, caBase64], "", window.authority.certificate.hash_algorithm);
|
||||||
|
|
||||||
|
var buf = arrayBufferToString(p12.toSchema().toBER(false));
|
||||||
var mimetype = "application/x-pkcs12"
|
var mimetype = "application/x-pkcs12"
|
||||||
a.download = query.title + ".p12";
|
a.download = query.title + ".p12";
|
||||||
break
|
break
|
||||||
case 'sswan':
|
case 'sswan':
|
||||||
|
var p12 = arrayBufferToString(
|
||||||
|
(await pkcs12chain(privKeyBase64, [certBase64, caBase64], "", window.authority.certificate.hash_algorithm)).toSchema().toBER(false));
|
||||||
|
|
||||||
var buf = JSON.stringify({
|
var buf = JSON.stringify({
|
||||||
uuid: blobToUuid(authority.namespace),
|
uuid: await blobToUuid(authority.namespace),
|
||||||
name: authority.namespace,
|
name: authority.namespace,
|
||||||
type: "ikev2-cert",
|
type: "ikev2-cert",
|
||||||
'ike-proposal': 'aes256-sha384-prfsha384-modp2048',
|
'ike-proposal': window.authority.strongswan.ike,
|
||||||
'esp-proposal': 'aes128gcm16-modp2048',
|
'esp-proposal': window.authority.strongswan.esp,
|
||||||
remote: {
|
remote: {
|
||||||
addr: authority.namespace,
|
addr: authority.namespace,
|
||||||
revocation: {
|
revocation: {
|
||||||
@ -265,13 +231,7 @@ function onEnroll(encoding) {
|
|||||||
a.download = query.title + ".sswan";
|
a.download = query.title + ".sswan";
|
||||||
break
|
break
|
||||||
case 'ovpn':
|
case 'ovpn':
|
||||||
let privKey = await crypto.exportKey("pkcs8", keys.privateKey);
|
let privKeyPem = await pkijsToPem(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`;
|
|
||||||
|
|
||||||
var buf = nunjucks.render('snippets/openvpn-client.conf', {
|
var buf = nunjucks.render('snippets/openvpn-client.conf', {
|
||||||
authority: authority,
|
authority: authority,
|
||||||
@ -283,23 +243,23 @@ function onEnroll(encoding) {
|
|||||||
a.download = query.title + ".ovpn";
|
a.download = query.title + ".ovpn";
|
||||||
break
|
break
|
||||||
case 'mobileconfig':
|
case 'mobileconfig':
|
||||||
var p12 = forge.asn1.toDer(forge.pkcs12.toPkcs12Asn1(
|
var p12 = arrayBufferToString(
|
||||||
keys.privateKey, [cert, ca], "1234", {algorithm: '3des'})).getBytes();
|
(await pkcs12chain(
|
||||||
|
privKeyBase64, [certBase64, caBase64],
|
||||||
|
"1234", window.authority.certificate.hash_algorithm))
|
||||||
|
.toSchema().toBER(false));
|
||||||
|
|
||||||
var buf = nunjucks.render('snippets/ios.mobileconfig', {
|
var buf = nunjucks.render('snippets/ios.mobileconfig', {
|
||||||
authority: authority,
|
authority: authority,
|
||||||
service_uuid: blobToUuid(query.title),
|
service_uuid: await blobToUuid(query.title),
|
||||||
conf_uuid: blobToUuid(query.title + " conf1"),
|
conf_uuid: await blobToUuid(query.title + " conf1"),
|
||||||
title: query.title,
|
title: query.title,
|
||||||
common_name: common_name,
|
common_name: common_name,
|
||||||
gateway: authority.namespace,
|
gateway: authority.namespace,
|
||||||
p12_uuid: blobToUuid(p12),
|
p12_uuid: await blobToUuid(p12),
|
||||||
p12: toBase64(p12),
|
p12: toBase64(p12),
|
||||||
ca_uuid: blobToUuid(
|
ca_uuid: await blobToUuid(caBase64),
|
||||||
forge.asn1.toDer(forge.pki.certificateToAsn1(ca)).getBytes()
|
ca: caBase64,
|
||||||
),
|
|
||||||
ca: toBase64(
|
|
||||||
forge.asn1.toDer(forge.pki.certificateToAsn1(ca)).getBytes()
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
var mimetype = "application/x-apple-aspen-config";
|
var mimetype = "application/x-apple-aspen-config";
|
||||||
a.download = query.title + ".mobileconfig";
|
a.download = query.title + ".mobileconfig";
|
||||||
@ -321,19 +281,13 @@ function onEnroll(encoding) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let resultString = "-----BEGIN CERTIFICATE REQUEST-----\r\n";
|
xhr2.send(await pkijsToPem(window.csr));
|
||||||
resultString = `${resultString}${formatPEM(
|
|
||||||
toBase64(arrayBufferToString(csr.toSchema().toBER(false)))
|
|
||||||
)}`;
|
|
||||||
resultString = `${resultString}\r\n-----END CERTIFICATE REQUEST-----\r\n`;
|
|
||||||
|
|
||||||
xhr2.send(resultString);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
xhr.send();
|
xhr.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onHashChanged() {
|
async function onHashChanged() {
|
||||||
|
|
||||||
window.query = {};
|
window.query = {};
|
||||||
var a = location.hash.substring(1).split('&');
|
var a = location.hash.substring(1).split('&');
|
||||||
@ -358,6 +312,12 @@ function onHashChanged() {
|
|||||||
success: async function(authority) {
|
success: async function(authority) {
|
||||||
window.authority = authority
|
window.authority = authority
|
||||||
|
|
||||||
|
// 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";
|
var prefix = "unknown";
|
||||||
for (i in DEVICE_KEYWORDS) {
|
for (i in DEVICE_KEYWORDS) {
|
||||||
var keyword = DEVICE_KEYWORDS[i];
|
var keyword = DEVICE_KEYWORDS[i];
|
||||||
@ -385,8 +345,8 @@ function onHashChanged() {
|
|||||||
for (i = 0; i < options.length; i++) {
|
for (i = 0; i < options.length; i++) {
|
||||||
options[i].style.display = "none";
|
options[i].style.display = "none";
|
||||||
}
|
}
|
||||||
setTimeout(onKeyGen, 100);
|
|
||||||
console.info("Generating key pair...");
|
console.info("Generating key pair...");
|
||||||
|
await onKeyGen();
|
||||||
} else {
|
} else {
|
||||||
loadAuthority(query);
|
loadAuthority(query);
|
||||||
}
|
}
|
||||||
@ -810,12 +770,12 @@ function loadAuthority(query) {
|
|||||||
|
|
||||||
|
|
||||||
$("#enroll").click(async function() {
|
$("#enroll").click(async function() {
|
||||||
var keys = await crypto.generateKey(
|
var keys = await window.cryptoEngine.generateKey(
|
||||||
{
|
{
|
||||||
name: "RSASSA-PKCS1-v1_5",
|
name: window.authority.certificate.key_type_specific,
|
||||||
modulusLength: 1024,
|
modulusLength: window.authority.certificate.key_size,
|
||||||
publicExponent: new Uint8Array([1, 0, 1]),
|
publicExponent: new Uint8Array([1, 0, 1]),
|
||||||
hash: "SHA-256",
|
hash: window.authority.certificate.hash_algorithm,
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
["encrypt", "decrypt"]);
|
["encrypt", "decrypt"]);
|
||||||
@ -836,11 +796,7 @@ function loadAuthority(query) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
var pkcs8 = await crypto.exportKey("pkcs8", keys.privateKey);
|
var privateKeyBuffer = pkijsToPem(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`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
120
static/js/pkcs12chain.js
Normal file
120
static/js/pkcs12chain.js
Normal file
@ -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<numCerts;i++) {
|
||||||
|
const asn1 = asn1js.fromBER(stringToArrayBuffer(fromBase64(certs[i])));
|
||||||
|
const certSimpl = new Certificate({schema: asn1.result});
|
||||||
|
|
||||||
|
const certLocalIDBuffer = new ArrayBuffer(4);
|
||||||
|
const certLocalIDView = new Uint8Array(certLocalIDBuffer);
|
||||||
|
getRandomValues(certLocalIDView);
|
||||||
|
|
||||||
|
safeBags.push(
|
||||||
|
new SafeBag({
|
||||||
|
bagId: "1.2.840.113549.1.12.10.1.3",
|
||||||
|
bagValue: new CertBag({
|
||||||
|
parsedValue: certSimpl
|
||||||
|
}),
|
||||||
|
bagAttributes: [
|
||||||
|
new Attribute({
|
||||||
|
type: "1.2.840.113549.1.9.21", // localKeyID
|
||||||
|
values: [
|
||||||
|
new asn1js.OctetString({valueHex: certLocalIDBuffer})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let pkcs12 = new PFX({
|
||||||
|
parsedValue: {
|
||||||
|
integrityMode: 0, // Password-Based Integrity Mode
|
||||||
|
authenticatedSafe: new AuthenticatedSafe({
|
||||||
|
parsedValue: {
|
||||||
|
safeContents: [
|
||||||
|
{
|
||||||
|
privacyMode: 0, // "No-privacy" Protection Mode
|
||||||
|
value: new SafeContents({
|
||||||
|
safeBags: safeBags
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await pkcs12.parsedValue.authenticatedSafe.makeInternalValues({
|
||||||
|
safeContents: [{}]
|
||||||
|
});
|
||||||
|
|
||||||
|
await pkcs12.makeInternalValues({
|
||||||
|
password: stringToArrayBuffer(password),
|
||||||
|
iterations: 10000,
|
||||||
|
pbkdf2HashAlgorithm: hash_alg,
|
||||||
|
hmacHashAlgorithm: hash_alg
|
||||||
|
})
|
||||||
|
|
||||||
|
return pkcs12;
|
||||||
|
}
|
54
static/js/util.js
Normal file
54
static/js/util.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
arrayBufferToString,
|
||||||
|
toBase64,
|
||||||
|
} from "pvutils";
|
||||||
|
import { formatPEM } from "./formatPEM.js";
|
||||||
|
|
||||||
|
export function pkijsToBase64(pkijsObj) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
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 function pkijsToPem(pkijsObj) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
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 PRIVATE KEY-----\r\n${privKeyBody}\r\n-----END PRIVATE KEY-----\r\n`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pemToBase64(pem) {
|
||||||
|
return pem.replace(/(-----(BEGIN|END) CERTIFICATE-----|\n)/g, "");
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user