diff --git a/.gitignore b/.gitignore
index 144585f..be8362d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+*.gz
+
# ---> Node
# Logs
logs
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..7e5d770
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "bracketSpacing": true,
+ "printWidth": 80,
+ "singleQuote": true,
+ "tabWidth": 4,
+ "trailingComma": "none",
+ "useTabs": true
+}
diff --git a/README.md b/README.md
index 772c13c..8240922 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,122 @@
-# pem.js
+# @root/pem
-Lightweight, Zero-Dependency, PEM (RFC 7468) encoder and decoder.
\ No newline at end of file
+Lightweight, Zero-Dependency PEM encoder and decoder.
+
+| ~300b gzipped
+| ~650b minified
+| ~1k full
+|
+
+- [x] VanillaJS
+- [x] Zero-Dependency
+- [x] Universal Support
+ - [x] Node.js
+ - [x] Browsers
+
+# Support
+
+This library supports PEM, which is pretty boring on its own.
+
+Most likely you are also interested in some of the following:
+
+- [keypairs.js](https://git.rootprojects.org/root/keypairs.js)
+ - RSA
+ - EC / ECDSA
+- [x509.js](https://git.rootprojects.org/root/x509.js)
+- [asn1.js](https://git.rootprojects.org/root/asn1.js)
+
+# Usage
+
+- PEM.parseBlock(str)
+- PEM.packBlock(options)
+
+Parsing
+
+```js
+var PEM = require('@root/pem/parser');
+
+var block = PEM.parseBlock(
+ '-----BEGIN Type-----\nSGVsbG8sIOS4lueVjCE=\n-----END Type-----\n'
+);
+```
+
+```js
+{
+ bytes: `<48 65 6c 6c 6f 2c 20 e4 b8 96 e7 95 8c 21>`;
+}
+```
+
+Packing
+
+```js
+var PEM = require('@root/pem/packer');
+
+var block = PEM.packBlock({
+ type: 'Type',
+ // Buffer or Uint8Array or Array
+ bytes: [0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c, 0x21]
+);
+```
+
+```txt
+-----BEGIN Type-----
+SGVsbG8sIOS4lueVjCE=
+-----END Type-----
+```
+
+# Install
+
+## Node / Webpack
+
+```js
+npm install -g @root/pem
+```
+
+## Browsers
+
+```html
+
+```
+
+```html
+
+```
+
+# A PEM Block
+
+A Block represents a PEM encoded structure.
+
+The encoded form is:
+
+```txt
+-----BEGIN Type-----
+Headers
+base64-encoded Bytes
+-----END Type-----
+```
+
+where Headers is a possibly empty sequence of Key: Value lines.
+
+(credit: https://golang.org/pkg/encoding/pem/)
+
+# PEM History
+
+PEM was introduced in 1993 via RFC 1421, but not formally
+standardized until RFC 7468 in April of 2015.
+
+It has served as the _de facto_ standard for a variety of
+DER-encoded X509 schemas of ASN.1 data for cryptographic
+keys and certificates such as:
+
+- [x] PKCS#10 (Certificate Signing Request [CSR])
+- [x] X509 Certificate (fullchain.pem, site.crt)
+- [x] PKIX (cert.pem, privkey.pem, priv.key)
+ - [x] PKCS#1 (RSA Public and Private Keys)
+ - [x] PKCS#8 (RSA and ECDSA Keypairs)
+- [x] SEC#1 (ECDSARSA Public and Private Keys)
+
+# Legal
+
+MPL-2.0 |
+[Terms of Use](https://therootcompany.com/legal/#terms) |
+[Privacy Policy](https://therootcompany.com/legal/#privacy)
diff --git a/browser/native.js b/browser/native.js
new file mode 100644
index 0000000..6d41385
--- /dev/null
+++ b/browser/native.js
@@ -0,0 +1,23 @@
+'use strict';
+
+// "A little copying is better than a little dependency" - Rob Pike
+var Enc = module.exports;
+
+Enc.bufToBase64 = function(u8) {
+ var bin = '';
+ // map is not part of u8
+ u8.forEach(function(i) {
+ bin += String.fromCharCode(i);
+ });
+ return btoa(bin);
+};
+
+Enc.base64ToBuf = function(b64) {
+ return Uint8Array.from(
+ atob(b64)
+ .split('')
+ .map(function(ch) {
+ return ch.charCodeAt(0);
+ })
+ );
+};
diff --git a/browser/parser.js b/browser/parser.js
new file mode 100644
index 0000000..e27248f
--- /dev/null
+++ b/browser/parser.js
@@ -0,0 +1,33 @@
+'use strict';
+
+var Enc = require('@root/encoding/base64');
+var PEM = module.exports;
+
+PEM.packBlock = function(opts) {
+ // TODO allow for headers?
+ return (
+ '-----BEGIN ' +
+ opts.type +
+ '-----\n' +
+ Enc.bufToBase64(opts.bytes)
+ .match(/.{1,64}/g)
+ .join('\n') +
+ '\n' +
+ '-----END ' +
+ opts.type +
+ '-----'
+ );
+};
+
+// don't replace the full parseBlock, if it exists
+PEM.parseBlock =
+ PEM.parseBlock ||
+ function(str) {
+ var der = str
+ .split(/\n/)
+ .filter(function(line) {
+ return !/-----/.test(line);
+ })
+ .join('');
+ return { bytes: Enc.base64ToBuf(der) };
+ };
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..2624b3c
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# TODO convert to JS
+cat browser/native.js parser.js packer.js > all.tmp.js
+sed -i '' '/use strict/d' all.tmp.js
+sed -i '' '/require/d' all.tmp.js
+sed -i '' '/exports/d' all.tmp.js
+echo ';(function () {' > all.js
+echo "'use strict';" >> all.js
+echo "var PEM = window.PEM = {};" >> all.js
+echo "var Enc = {};" >> all.js
+cat all.tmp.js >> all.js
+rm all.tmp.js
+echo '}());' >> all.js
+
+mv all.js dist/pem.all.js
+uglifyjs dist/pem.all.js > dist/pem.all.min.js
diff --git a/dist/pem.all.js b/dist/pem.all.js
new file mode 100644
index 0000000..4238650
--- /dev/null
+++ b/dist/pem.all.js
@@ -0,0 +1,56 @@
+;(function () {
+'use strict';
+var PEM = window.PEM = {};
+var Enc = {};
+
+// "A little copying is better than a little dependency" - Rob Pike
+
+Enc.bufToBase64 = function(u8) {
+ var bin = '';
+ // map is not part of u8
+ u8.forEach(function(i) {
+ bin += String.fromCharCode(i);
+ });
+ return btoa(bin);
+};
+
+Enc.base64ToBuf = function(b64) {
+ return Uint8Array.from(
+ atob(b64)
+ .split('')
+ .map(function(ch) {
+ return ch.charCodeAt(0);
+ })
+ );
+};
+
+
+
+PEM.parseBlock = function(str) {
+ var der = str
+ .split(/\n/)
+ .filter(function(line) {
+ return !/-----/.test(line);
+ })
+ .join('');
+ return { bytes: Enc.base64ToBuf(der) };
+};
+
+
+
+PEM.packBlock = function(opts) {
+ // TODO allow for headers?
+ return (
+ '-----BEGIN ' +
+ opts.type +
+ '-----\n' +
+ Enc.bufToBase64(opts.bytes)
+ .match(/.{1,64}/g)
+ .join('\n') +
+ '\n' +
+ '-----END ' +
+ opts.type +
+ '-----'
+ );
+};
+}());
diff --git a/dist/pem.all.min.js b/dist/pem.all.min.js
new file mode 100644
index 0000000..bb4d0bc
--- /dev/null
+++ b/dist/pem.all.min.js
@@ -0,0 +1 @@
+(function(){"use strict";var PEM=window.PEM={};var Enc={};Enc.bufToBase64=function(u8){var bin="";u8.forEach(function(i){bin+=String.fromCharCode(i)});return btoa(bin)};Enc.base64ToBuf=function(b64){return Uint8Array.from(atob(b64).split("").map(function(ch){return ch.charCodeAt(0)}))};PEM.parseBlock=function(str){var der=str.split(/\n/).filter(function(line){return!/-----/.test(line)}).join("");return{bytes:Enc.base64ToBuf(der)}};PEM.packBlock=function(opts){return"-----BEGIN "+opts.type+"-----\n"+Enc.bufToBase64(opts.bytes).match(/.{1,64}/g).join("\n")+"\n"+"-----END "+opts.type+"-----"}})();
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..46a3084
--- /dev/null
+++ b/index.js
@@ -0,0 +1,7 @@
+'use strict';
+
+var PEM = require('./parser.js');
+var PEMPacker = require('./packer.js');
+PEM.packBlock = PEMPacker.packBlock;
+
+module.exports = PEM;
diff --git a/node/native.js b/node/native.js
new file mode 100644
index 0000000..c2f7951
--- /dev/null
+++ b/node/native.js
@@ -0,0 +1,12 @@
+'use strict';
+
+// "A little copying is better than a little dependency" - Rob Pike
+var Enc = module.exports;
+
+Enc.bufToBase64 = function(buf) {
+ return Buffer.from(buf).toString('base64');
+};
+
+Enc.base64ToBuf = function(b64) {
+ return Buffer.from(b64, 'base64');
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..771ec6b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "@root/pem",
+ "version": "1.0.0",
+ "description": "VanillaJS, Lightweight, Zero-Dependency, PEM encoder and decoder.",
+ "main": "index.js",
+ "browser": {
+ "./node/native.js": "./browser/native.js"
+ },
+ "scripts": {
+ "test": "node tests"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://git.rootprojects.org/root/pem.js.git"
+ },
+ "keywords": [
+ "PEM",
+ "x509",
+ "ASN.1",
+ "asn1",
+ "RFC7468",
+ "RFC1421",
+ "RFC",
+ "7468",
+ "1421"
+ ],
+ "author": "AJ ONeal (https://coolaj86.com/)",
+ "license": "MPL-2.0"
+}
diff --git a/packer.js b/packer.js
new file mode 100644
index 0000000..f41937a
--- /dev/null
+++ b/packer.js
@@ -0,0 +1,22 @@
+'use strict';
+
+var PEM = module.exports;
+
+//var Enc = require('@root/encoding/base64');
+var Enc = require('./node/native.js');
+
+PEM.packBlock = function(opts) {
+ // TODO allow for headers?
+ return (
+ '-----BEGIN ' +
+ opts.type +
+ '-----\n' +
+ Enc.bufToBase64(opts.bytes)
+ .match(/.{1,64}/g)
+ .join('\n') +
+ '\n' +
+ '-----END ' +
+ opts.type +
+ '-----'
+ );
+};
diff --git a/parser.js b/parser.js
new file mode 100644
index 0000000..3064a5b
--- /dev/null
+++ b/parser.js
@@ -0,0 +1,15 @@
+'use strict';
+
+var PEM = module.exports;
+
+var Enc = require('./node/native.js');
+
+PEM.parseBlock = function(str) {
+ var der = str
+ .split(/\n/)
+ .filter(function(line) {
+ return !/-----/.test(line);
+ })
+ .join('');
+ return { bytes: Enc.base64ToBuf(der) };
+};
diff --git a/tests/index.js b/tests/index.js
new file mode 100644
index 0000000..354ae32
--- /dev/null
+++ b/tests/index.js
@@ -0,0 +1,36 @@
+'use strict';
+
+var PEM = require('../');
+//console.log(PEM);
+
+// "Hello, δΈη!";
+var contents = 'SGVs\nbG8sIOS4\nlueVjCE=';
+var pem = '-----BEGIN Type-----\n' + contents + '\n-----END Type-----\n';
+var block = PEM.parseBlock(pem);
+
+if (14 !== block.bytes.byteLength) {
+ throw new Error('should be 14 bytes');
+}
+
+if (0x48 !== block.bytes[0]) {
+ throw new Error('first byte should be 0x48 ("H")');
+}
+
+if (0x8c !== block.bytes[12]) {
+ throw new Error('13th byte should be 0x8c (3rd byte of "η")');
+}
+
+console.info("PASS: decodes 'bytes' field correctly");
+
+var pem2 =
+ '-----BEGIN Type-----\n' +
+ contents.replace(/\n/g, '') +
+ '\n-----END Type-----';
+
+block.type = 'Type';
+if (pem2 !== PEM.packBlock(block)) {
+ console.debug(PEM.packBlock(block));
+ throw new Error('should pack PEM correctly');
+}
+
+console.info("PASS: encodes 'bytes' and 'type' fields correctly");