convert from openSSH public key format to PEM
This commit is contained in:
parent
69c2226d8e
commit
aa0388f793
2 changed files with 166 additions and 0 deletions
63
convert.html
Normal file
63
convert.html
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>js-keygen: convert openssh to PEM</title>
|
||||||
|
<script>module = {};</script>
|
||||||
|
<script src="publicSshToPem.js"></script>
|
||||||
|
<link rel="stylesheet" href="js-keygen.css">
|
||||||
|
<link rel="icon" type="image/png" href="key.png">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
<h1>Convert openSSH public key format to PEM</h1>
|
||||||
|
<p>Form the command line you can run
|
||||||
|
<br>
|
||||||
|
<code>ssh-keygen -f ~/.ssh/id_rsa -m 'PEM' -e > public.pem</code>
|
||||||
|
<br> 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.</p>
|
||||||
|
<p>Again
|
||||||
|
<a href="http://blog.oddbit.com/2011/05/08/converting-openssh-public-keys/">Converting open ssh public key</a> proves to be invaluable to come up with the tight conversion</p>
|
||||||
|
<p>Result can be check with openssl asn.1 decoder
|
||||||
|
<br>
|
||||||
|
<code>openssl asn1parse -i -in public.pem</code>
|
||||||
|
</p>
|
||||||
|
<br> No data is being sent to the server, everything happens within the context of this web page.
|
||||||
|
<br>
|
||||||
|
<a href="https://twitter.com/share" class="twitter-share-button" data-via="pkr2">Tweet</a>
|
||||||
|
<script>!function (d, s, id) { var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https'; if (!d.getElementById(id)) { js = d.createElement(s); js.id = id; js.src = p + '://platform.twitter.com/widgets.js'; fjs.parentNode.insertBefore(js, fjs); } }(document, 'script', 'twitter-wjs');</script>
|
||||||
|
<hr>
|
||||||
|
<div>
|
||||||
|
<label for="key">OpenSSH public Key:</label>
|
||||||
|
<br>
|
||||||
|
<textarea id="key" style="height: 75px;" spellcheck="false">ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCD2bpwgflG43XNQ1pID6cjHfBLDeAptIiOpzD8ujYJXMNyFB5+omBsM3WZh9wN+qkFFCFx2PAxHlmlMJEgCQVye7rJw5SPjLcoZvMniyrYQTNk/+PkSKNs/hNCPTeuvhDWrC08U1dZwKNYR6ZcI+w4vZUNj26Ma6CqJZMxcrNuENWOPnaiuAayzBwC0x6TqwKwzK8FxPfG7VT3RADoU9CwKLjdz63NRXF7Kq2Q20dycPZk7lxmqt8bFt+WTkVtK8fPmAu+NjATdqhNmkw7FG7cfOYvf3n1kChLx923MWrKAaNoObx/b8otiz9wWSh8cxavDwzdsi1MJMXOvHSpwQT patrick@Patricks-MacBook-Pro.local
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
<button id="generate">Convert</button>
|
||||||
|
<div>
|
||||||
|
<label for="pem">PEM:</label>
|
||||||
|
<br>
|
||||||
|
<textarea id="pem" disabled style="height: 150px;" spellcheck="false"></textarea>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<hr> Made with
|
||||||
|
<span style="color:magenta;">♥</span> by
|
||||||
|
<a href="http://blog.roumanoff.com">Patrick Roumanoff</a>
|
||||||
|
<a href="https://github.com/PatrickRoumanoff/js-keygen">
|
||||||
|
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67"
|
||||||
|
alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
document.querySelector("#generate").addEventListener("click", () => {
|
||||||
|
document.querySelector("#pem").value = publicSshToPem(document.querySelector("#key").value)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
103
publicSshToPem.js
Normal file
103
publicSshToPem.js
Normal file
|
@ -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 };
|
Loading…
Reference in a new issue