diff --git a/convert.html b/convert.html new file mode 100644 index 0000000..6bf410a --- /dev/null +++ b/convert.html @@ -0,0 +1,63 @@ + + + + + js-keygen: convert openssh to PEM + + + + + + + +
+

Convert openSSH public key format to PEM

+

Form the command line you can run +
+ ssh-keygen -f ~/.ssh/id_rsa -m 'PEM' -e > public.pem +
in order to export your openssh public key to a PEM file, but doing in so in javascript is not too difficult and doesn't + require the webcrypto API, only a bit of ASN.1 knowledge.

+

Again + Converting open ssh public key proves to be invaluable to come up with the tight conversion

+

Result can be check with openssl asn.1 decoder +
+ openssl asn1parse -i -in public.pem +

+
No data is being sent to the server, everything happens within the context of this web page. +
+ Tweet + +
+
+ +
+ +
+ +
+ +
+ +
+
+ +
Made with + by + Patrick Roumanoff + + Fork me on GitHub + +
+ + + + + diff --git a/publicSshToPem.js b/publicSshToPem.js new file mode 100644 index 0000000..3c68e7d --- /dev/null +++ b/publicSshToPem.js @@ -0,0 +1,103 @@ +/* eslint no-bitwise: 0 */ + +function wrap(text, len) { + const length = len || 72; + let result = ""; + for (let i = 0; i < text.length; i += length) { + result += text.slice(i, i + length); + result += "\n"; + } + return result; +} + +function pemPublicKey(key) { + return `---- BEGIN RSA PUBLIC KEY ----\n${wrap(key, 65)}---- END RSA PUBLIC KEY ----`; +} + +function integerToOctet(n) { + const result = []; + for (let i = n; i > 0; i >>= 8) { + result.push(i & 0xff); + } + return result.reverse(); +} + +function asnEncodeLen(n) { + let result = []; + if (n >> 7) { + result = integerToOctet(n); + result.unshift(0x80 + result.length); + } else { + result.push(n); + } + return result; +} + +function checkHighestBit(v) { + if (v[0] >> 7 === 1) { + v.unshift(0); // add leading zero if first bit is set + } + return v; +} + +function asn1Int(int) { + const v = checkHighestBit(int); + const len = asnEncodeLen(v.length); + return [0x02].concat(len, v); // int tag is 0x02 +} + +function asn1Seq(seq) { + const len = asnEncodeLen(seq.length); + return [0x30].concat(len, seq); // seq tag is 0x30 +} + +function arrayToPem(a) { + return window.btoa(a.map(c => String.fromCharCode(c)).join("")); +} + +function arrayToString(a) { + return String.fromCharCode.apply(null, a); +} + +function stringToArray(s) { + return s.split("").map(c => c.charCodeAt()); +} + +function pemToArray(pem) { + return stringToArray(window.atob(pem)); +} + +function arrayToLen(a) { + let result = 0; + for (let i = 0; i < a.length; i += 1) { + result = result * 256 + a[i]; + } + return result; +} + +function decodePublicKey(s) { + const split = s.split(" "); + const prefix = split[0]; + if (prefix !== "ssh-rsa") { + throw new Error(`Unknown prefix: ${prefix}`); + } + const buffer = pemToArray(split[1]); + const nameLen = arrayToLen(buffer.splice(0, 4)); + const type = arrayToString(buffer.splice(0, nameLen)); + if (type !== "ssh-rsa") { + throw new Error(`Unknown key type: ${type}`); + } + const exponentLen = arrayToLen(buffer.splice(0, 4)); + const exponent = buffer.splice(0, exponentLen); + const keyLen = arrayToLen(buffer.splice(0, 4)); + const key = buffer.splice(0, keyLen); + return { type, exponent, key, name: split[2] }; +} + +function publicSshToPem(publicKey) { + const { key, exponent } = decodePublicKey(publicKey); + const seq = [key, exponent].map(asn1Int).reduce((acc, a) => acc.concat(a)); + return pemPublicKey(arrayToPem(asn1Seq(seq))); +} + +module.exports = { publicSshToPem };