diff --git a/README.md b/README.md
index eaeefa3..b7556ee 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,41 @@
browser-csr
===========
+Create a CSR (Certificate Signing Request) in the browser that supports SAN altnames.
+
+Used for Let's Encrypt v2 with [greenlock-browser.js](https://git.coolaj86.com/coolaj86/greenlock-browser.js).
+
+Usage
+-----
+
+```html
+
+```
+
+```js
+CSR.generate({
+
+ /* A WebCrypto-generated keypair */
+ keypair: {}
+
+ /* Subject & SANS altnames */
+, domains: [ 'example.com', 'www.example.com' ]
+
+, format: 'rfc7515' // unpadded urlsafe base64
+
+}).then(function (csrweb64) {
+ console.log(csrweb64);
+});
+```
+
+Dependencies
+------------
+
+[`pkijs@v1.3.33`](https://github.com/PeculiarVentures/PKI.js/tree/41b63af760cacb565dd850fb3466ada4ca163eff)
+
+```
+
+
+
+
+```
diff --git a/csr.js b/csr.js
new file mode 100644
index 0000000..8fd57bf
--- /dev/null
+++ b/csr.js
@@ -0,0 +1,186 @@
+// Adapted from
+// https://github.com/PeculiarVentures/PKI.js/blob/1bc0ed3/examples/PKCS%2310%20complex%20example/PKCS10_complex_example.html
+// https://stackoverflow.com/questions/48419456/pki-js-v2-and-and-webcrypto-add-subject-alternative-name-with-csr-certificate/49896425#49896425
+// https://getwww.me/V1-csrhelp-master.zip
+// unibabel.js
+
+// Dependencies
+//
+//
+//
+//
+(function (exports) {
+'use strict';
+
+var CSR = exports.CSR = {};
+
+function formatPEM(pem_string) {
+ var string_length = pem_string.length;
+ var result_string = "";
+
+ for (var i = 0, count = 0; i < string_length; i++, count++) {
+ if (count > 63) {
+ result_string = result_string + "\r\n";
+ count = 0;
+ }
+
+ result_string = result_string + pem_string[i];
+ }
+
+ return result_string;
+}
+
+function arrayBufferToString(buffer) {
+ var result_string = "";
+ var view = new Uint8Array(buffer);
+
+ for (var i = 0; i < view.length; i++) {
+ result_string = result_string + String.fromCharCode(view[i]);
+ }
+
+ return result_string;
+}
+
+function createPkcs10Csr(domainKeypair, domains) {
+ var certf = {};
+ certf.algorithm = 'ECC';
+ certf.keysize = 'secp256r1';
+ certf.hostname = domains.shift();
+
+ var context = {};
+
+ // #region Get a "crypto" extension
+ var crypto = org.pkijs.getCrypto();
+ if (typeof crypto === "undefined") {
+ context.content = '';
+ return Promise.reject('No WebCrypto extension found');
+ }
+ // #endregion
+
+ // #region Prepare P10
+ context = context || {};
+ var sequence = Promise.resolve();
+ var pkcs10_simpl = new org.pkijs.simpl.PKCS10();
+ var publicKey = domainKeypair.publicKey;
+ var privateKey = domainKeypair.privateKey;
+ var hash_algorithm;
+ hash_algorithm = "sha-256";
+
+ var signature_algorithm_name, keylength;
+ switch (certf.algorithm) {
+ case "RSA":
+ signature_algorithm_name = "RSASSA-PKCS1-V1_5";
+ keylength = parseInt(certf.keysize);
+ break;
+ case "ECC":
+ signature_algorithm_name = "ECDSA";
+ switch (certf.keysize) {
+ case 'secp256r1':
+ keylength = "P-256";
+ break;
+ case 'secp384r1':
+ keylength = "P-384";
+ break;
+ case 'secp521r1':
+ keylength = "P-521";
+ break;
+ }
+ break;
+ default:
+ // do nothing
+ }
+ // #endregion
+
+ // #region Put a static values
+ pkcs10_simpl.version = 0;
+
+ pkcs10_simpl.subject.types_and_values.push(new org.pkijs.simpl.ATTR_TYPE_AND_VALUE({
+ type: "2.5.4.3",
+ value: new org.pkijs.asn1.UTF8STRING({
+ value: certf.hostname
+ })
+ }));
+
+ pkcs10_simpl.attributes = [];
+ // #endregion
+
+ // #region Exporting public key into "subjectPublicKeyInfo" value of PKCS#10
+ sequence = sequence.then(function() {
+ return pkcs10_simpl.subjectPublicKeyInfo.importKey(publicKey);
+ });
+ // #endregion
+
+ // #region SubjectKeyIdentifier
+ sequence = sequence.then(function() {
+ return crypto.digest({
+ name: "SHA-256"
+ }, pkcs10_simpl.subjectPublicKeyInfo.subjectPublicKey.value_block.value_hex);
+ }).then(function(result) {
+
+ var extensions = new org.pkijs.simpl.EXTENSIONS({
+ extensions_array: [
+ new org.pkijs.simpl.EXTENSION({
+ extnID: "2.5.29.14",
+ critical: false,
+ extnValue: (new org.pkijs.asn1.OCTETSTRING({ value_hex: result })).toBER(false)
+ })
+ ]
+ });
+
+ var altNames = new org.pkijs.simpl.GENERAL_NAMES({
+ names: [
+ new org.pkijs.simpl.GENERAL_NAME({
+ NameType: 2,
+ Name: domains.join(', DNS:') //"domain1.com, DNS:domain2.com, DNS:domain3.com"
+ })
+ ]
+ });
+
+ extensions.extensions_array.push(new org.pkijs.simpl.EXTENSION({
+ extnID: "2.5.29.17", // subjectAltName
+ critical: false,
+ extnValue: altNames.toSchema().toBER(false)
+ }));
+
+ var attribute = new org.pkijs.simpl.ATTRIBUTE({
+ type: "1.2.840.113549.1.9.14", // pkcs-9-at-extensionRequest
+ values: [extensions.toSchema()]
+ });
+
+ pkcs10_simpl.attributes.push(attribute);
+
+ });
+ // #endregion
+
+ // #region Signing final PKCS#10 request
+ sequence = sequence.then(function() {
+ context.privateKey = pkcs10_simpl.sign(privateKey, hash_algorithm);
+ return pkcs10_simpl.sign(privateKey, hash_algorithm);
+ });
+ // #endregion
+
+ sequence = sequence.then(function(/*result*/) {
+ var pkcs10_schema = pkcs10_simpl.toSchema();
+ var pkcs10_encoded = pkcs10_schema.toBER(false);
+
+ var pemder = window.btoa(arrayBufferToString(pkcs10_encoded));
+ var result_string = "-----BEGIN CERTIFICATE REQUEST-----\r\n";
+ result_string = result_string + formatPEM(pemder);
+ result_string = result_string + "\r\n-----END CERTIFICATE REQUEST-----\r\n";
+ context.content = result_string;
+
+ console.log('CSR as PEM:');
+ console.log(context.content);
+
+ return pemder.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
+ });
+
+ return sequence;
+}
+
+CSR.formatPem = formatPEM;
+CSR.generate = function (options) {
+ return createPkcs10Csr(options.keypair, options.domains);
+};
+
+}(window));
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..38ff19c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "browser-csr",
+ "version": "1.0.0-alpha",
+ "description": "Generate a CSR in your browser (i.e. for Let's Encrypt v2) with WebCrypto and PKIjs. Supports SAN altnames.",
+ "main": "csr.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "ssh://gitea@git.coolaj86.com:22042/coolaj86/browser-csr.js.git"
+ },
+ "keywords": [
+ "pki",
+ "pkijs",
+ "asn1",
+ "asn1js",
+ "Let's Encrypt",
+ "Let's Encrypt v2",
+ "letsencrypt",
+ "greenlock",
+ "browser",
+ "csr",
+ "certificate",
+ "signing",
+ "request",
+ "ACME"
+ ],
+ "author": "AJ ONeal (https://coolaj86.com/)",
+ "license": "(MIT OR Apache-2.0)"
+}