AJ ONeal
5 years ago
commit
08e9349ee7
5 changed files with 331 additions and 0 deletions
@ -0,0 +1,81 @@ |
|||
# Bluecrypt SSH Fingerprint (for Browsers) |
|||
|
|||
A minimal library to read an SSH public key (`id_rsa.pub`) |
|||
and generate its fingerprint. |
|||
|
|||
Works for RSA and ECDSA public keys. |
|||
|
|||
# Features |
|||
|
|||
< 150 lines of code | 1.1kb gzipped | 2.4kb minified | 4.1kb with comments |
|||
|
|||
* [x] SSH Public Key SHA256 Fingerprints |
|||
* RSA (2048-bit, 3072-bit, 4096-bit) |
|||
* EC / ECDSA |
|||
* P-256 (prime256v1, secp256r1) |
|||
* P-384 (secp384r1) |
|||
* [x] Node.js version |
|||
* [Greenlock SSH Fingerprint](https://git.coolaj86.com/coolaj86/greenlock-ssh-fingerprint.js) |
|||
|
|||
### Need SSH Private Keys? |
|||
|
|||
SSH private keys (`id_rsa`) are just normal PEM files, |
|||
so you can use Eckles or Rasha, as mentioned above. |
|||
|
|||
# Web Demo |
|||
|
|||
<https://coolaj86.com/demos/ssh-fingerprint/> |
|||
|
|||
<img border="1" src="https://git.coolaj86.com/coolaj86/ssh-fingerprint.js/raw/branch/master/screenshot.png" /> |
|||
|
|||
```bash |
|||
git clone https://git.coolaj86.com/coolaj86/bluecrypt-ssh-fingerprint.js |
|||
pushd bluecrypt-ssh-fingerprint.js/ |
|||
``` |
|||
|
|||
```bash |
|||
open index.html |
|||
``` |
|||
|
|||
# Install |
|||
|
|||
You can use it as a plain-old javascript library: |
|||
|
|||
```html |
|||
<script src="https://git.coolaj86.com/coolaj86/bluecrypt-ssh-fingerprint.js/raw/branch/master/ssh-fingerprint.js"></script> |
|||
``` |
|||
|
|||
It's also on npm: |
|||
|
|||
```bash |
|||
npm install bluecrypt-ssh-fingerprint |
|||
``` |
|||
|
|||
# Usage |
|||
|
|||
Very simple: |
|||
|
|||
```js |
|||
var pub = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCba21UHE+VbDTpmYYFZUOV+OQ8AngOCdjROsPC0KiEfMvEaEM3NQl58u6QL7G7QsErKViiNPm9OTFo6HF5JijfWzK7haHFuRMEsgI4VwIYyhvqlJDfw/wt0AiVvSmoMfEQn1p1aiaO4V/RJSE3Vw/uz2bxiT22uSkSqOyShyfYE6dMHnuoBkzr4jvSifT+INmbv6Nyo4+AAMCZtYeHLrsFeSTjLL9jMPjI4ZkVdlw2n3Xn9NbltF3/8Ao8dQfElqw+LIQWqU0oFHYNIP4ttfl5ObMKHaKSvBMyNruZR0El/ZsrcHLkAHRCLj07KRQJ81l5CUTPtQ02P1Eamz/nT4I3 rsa@localhost'; |
|||
|
|||
SSH.fingerprint({ pub: pub }).then(function (fing) { |
|||
console.info('The key fingerprint is:'); |
|||
console.info(fing.fingerprint); |
|||
}); |
|||
``` |
|||
|
|||
# Other Tools in the Bluecrypt Suite |
|||
|
|||
* [Browser JWK to SSH](https://git.coolaj86.com/coolaj86/bluecrypt-jwk-to-ssh.js) |
|||
* [Browser SSH to JWK](https://git.coolaj86.com/coolaj86/bluecrypt-ssh-to-jwk.js) |
|||
* [Browser ASN.1 parser](https://git.coolaj86.com/coolaj86/asn1-parser.js) (for x509, etc) |
|||
* [Browser ASN.1 packer](https://git.coolaj86.com/coolaj86/asn1-packer.js) (for x509, etc) |
|||
|
|||
# Legal |
|||
|
|||
[ssh-fingerprint.js](https://git.coolaj86.com/coolaj86/bluecrypt-ssh-fingerprint.js) | |
|||
MPL-2.0 | |
|||
[Terms of Use](https://therootcompany.com/legal/#terms) | |
|||
[Privacy Policy](https://therootcompany.com/legal/#privacy) |
|||
|
|||
Bluecrypt™ is owned by AJ ONeal |
@ -0,0 +1,66 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<title>SSH Fingerprint Generator - Bluecrypt</title> |
|||
<style> |
|||
textarea { |
|||
width: 42em; |
|||
height: 10em; |
|||
} |
|||
pre { |
|||
white-space: pre-wrap; |
|||
} |
|||
.code { |
|||
width: 31em; |
|||
word-wrap: break-word; |
|||
} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<h1>Bluecrypt SSH Fingerprint Generator</h1> |
|||
|
|||
<textarea class="js-input" placeholder="Paste id_rsa.pub (or other SSH public key) here">ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCba21UHE+VbDTpmYYFZUOV+OQ8AngOCdjROsPC0KiEfMvEaEM3NQl58u6QL7G7QsErKViiNPm9OTFo6HF5JijfWzK7haHFuRMEsgI4VwIYyhvqlJDfw/wt0AiVvSmoMfEQn1p1aiaO4V/RJSE3Vw/uz2bxiT22uSkSqOyShyfYE6dMHnuoBkzr4jvSifT+INmbv6Nyo4+AAMCZtYeHLrsFeSTjLL9jMPjI4ZkVdlw2n3Xn9NbltF3/8Ao8dQfElqw+LIQWqU0oFHYNIP4ttfl5ObMKHaKSvBMyNruZR0El/ZsrcHLkAHRCLj07KRQJ81l5CUTPtQ02P1Eamz/nT4I3 root@localhost</textarea> |
|||
|
|||
<pre><code class="js-fingerprint"> </code></pre> |
|||
|
|||
<div class="code"> |
|||
<pre><code class="js-json"> </code></pre> |
|||
</div> |
|||
|
|||
<br> |
|||
<p>Made with <a href="https://git.coolaj86.com/coolaj86/bluecrypt-ssh-fingerprint.js/">ssh-fingerprint.js</a></p> |
|||
|
|||
<script src="./ssh-fingerprint.js"></script> |
|||
<script> |
|||
'use strict'; |
|||
|
|||
var $input = document.querySelector('.js-input'); |
|||
|
|||
function convert() { |
|||
console.log('keyup'); |
|||
|
|||
try { |
|||
var pub = document.querySelector('.js-input').value.trim(); |
|||
SSH.fingerprint({ pub: pub }).then(function (fing) { |
|||
var arr = []; |
|||
console.log(fing); |
|||
document.querySelector('.js-fingerprint').innerText = 'The key fingerprint is:\n' |
|||
+ fing.fingerprint + ' ' + fing.comment; |
|||
fing.digest.forEach(function (i) { arr.push(i); }); |
|||
fing.digest = 'Uint8Array <' + arr.join() + '>'; |
|||
document.querySelector('.js-json').innerText = JSON.stringify(fing, null, 1); |
|||
}).catch(function (e) { |
|||
var msg = { error: { message: e.message } }; |
|||
document.querySelector('.js-fingerprint').innerText = JSON.stringify(msg, null, 2); |
|||
}); |
|||
} catch(e) { |
|||
var msg = { error: { message: e.message } }; |
|||
document.querySelector('.js-fingerprint').innerText = JSON.stringify(msg, null, 2); |
|||
} |
|||
} |
|||
|
|||
$input.addEventListener('keyup', convert); |
|||
convert(); |
|||
</script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,27 @@ |
|||
{ |
|||
"name": "bluecrypt-ssh-fingerprint", |
|||
"version": "1.0.0", |
|||
"description": "SSH Fingerprint in < 150 lines of VanillaJS, part of the Bluecrypt suite for Browser Crypto.", |
|||
"homepage": "https://git.coolaj86.com/coolaj86/bluecrypt-ssh-fingerprint.js", |
|||
"main": "ssh-fingerprint.js", |
|||
"scripts": { |
|||
"prepare": "uglifyjs ssh-fingerprint.js > ssh-fingerprint.min.js" |
|||
}, |
|||
"directories": { |
|||
"lib": "lib" |
|||
}, |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "https://git.coolaj86.com/coolaj86/bluecrypt-ssh-fingerprint.js" |
|||
}, |
|||
"keywords": [ |
|||
"zero-dependency", |
|||
"SSH", |
|||
"Fingerprint", |
|||
"rsa", |
|||
"ec", |
|||
"ecdsa" |
|||
], |
|||
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", |
|||
"license": "MPL-2.0" |
|||
} |
After Width: | Height: | Size: 92 KiB |
@ -0,0 +1,157 @@ |
|||
/* This Source Code Form is subject to the terms of the Mozilla Public |
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this |
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|||
;(function (exports) { |
|||
'use strict'; |
|||
|
|||
if (!exports.Enc) { exports.Enc = {}; } |
|||
if (!exports.SSH) { exports.SSH = {}; } |
|||
|
|||
var Enc = exports.Enc; |
|||
var SSH = exports.SSH; |
|||
|
|||
SSH.fingerprint = function (opts) { |
|||
var ssh; |
|||
if (opts.bytes) { |
|||
ssh = opts; |
|||
} else { |
|||
ssh = SSH.parseBlock(opts.pub); |
|||
} |
|||
ssh = SSH.parseElements(ssh); |
|||
|
|||
// for browser compat
|
|||
return window.crypto.subtle.digest('SHA-256', ssh.bytes).then(function (digest) { |
|||
digest = new Uint8Array(digest); |
|||
// 2048 SHA256:yCB62vBVsOwqksgYwy/WDbaMF2PhPijAwcrlzmrxfko rsa@localhost (RSA)
|
|||
// 256 SHA256:wcH95nkL7ZkeRURHpLuoThIBEIIKkYgf9etD18PIx40 P-256@localhost (ECDSA)
|
|||
ssh = SSH.parseKeyType(ssh); |
|||
return { |
|||
type: 'sha256' |
|||
, digest: digest |
|||
, fingerprint: 'SHA256:' + Enc.bufToBase64(digest).replace(/=+$/g, '') |
|||
, size: ssh.size |
|||
, comment: ssh.comment |
|||
, kty: ssh.kty |
|||
}; |
|||
}); |
|||
}; |
|||
|
|||
SSH.parseBlock = function (ssh) { |
|||
ssh = ssh.split(/\s+/g); |
|||
|
|||
return { |
|||
type: ssh[0] |
|||
, bytes: Enc.base64ToBuf(ssh[1]) |
|||
, comment: ssh[2] |
|||
}; |
|||
}; |
|||
|
|||
SSH.parseElements = function (ssh) { |
|||
var buf = ssh.bytes; |
|||
var fulllen = buf.byteLength || buf.length; |
|||
var offset = (buf.byteOffset || 0); |
|||
var i = 0; |
|||
var index = 0; |
|||
// using dataview to be browser-compatible (I do want _some_ code reuse)
|
|||
var dv = new DataView(buf.buffer.slice(offset, offset + fulllen)); |
|||
var els = []; |
|||
var el; |
|||
var len; |
|||
|
|||
while (index < fulllen) { |
|||
i += 1; |
|||
if (i > 15) { throw new Error("15+ elements, probably not a public ssh key"); } |
|||
len = dv.getUint32(index, false); |
|||
index += 4; |
|||
el = buf.slice(index, index + len); |
|||
// remove BigUInt '00' prefix
|
|||
if (0x00 === el[0]) { |
|||
el = el.slice(1); |
|||
} |
|||
els.push(el); |
|||
index += len; |
|||
} |
|||
if (fulllen !== index) { |
|||
throw new Error("invalid ssh public key length \n" + els.map(function (b) { |
|||
return Enc.bufToHex(b); |
|||
}).join('\n')); |
|||
} |
|||
|
|||
ssh.elements = els; |
|||
return ssh; |
|||
}; |
|||
|
|||
SSH.parseKeyType = function (ssh) { |
|||
var els = ssh.elements; |
|||
var typ = Enc.bufToBin(els[0]); |
|||
|
|||
// RSA keys are all the same
|
|||
if (SSH.types.rsa === typ) { |
|||
ssh.kty = 'RSA'; |
|||
ssh.size = (ssh.elements[2].byteLength || ssh.lements[2].length); |
|||
return ssh; |
|||
} |
|||
|
|||
// EC keys are each different
|
|||
if (SSH.types.p256 === typ) { |
|||
ssh.kty = 'EC'; |
|||
ssh.size = 32; |
|||
} else if (SSH.types.p384 === typ) { |
|||
ssh.kty = 'EC'; |
|||
ssh.size = 48; |
|||
} else { |
|||
throw new Error("Unsupported ssh public key type: " |
|||
+ Enc.bufToBin(els[0])); |
|||
} |
|||
|
|||
return ssh; |
|||
}; |
|||
|
|||
SSH.types = { |
|||
// 19 '00000013'
|
|||
// e c d s a - s h a 2 - n i s t p 2 5 6
|
|||
// 65636473612d736861322d6e69737470323536
|
|||
// 6e69737470323536
|
|||
p256: 'ecdsa-sha2-nistp256' |
|||
|
|||
// 19 '00000013'
|
|||
// e c d s a - s h a 2 - n i s t p 3 8 4
|
|||
// 65636473612d736861322d6e69737470333834
|
|||
// 6e69737470323536
|
|||
, p384: 'ecdsa-sha2-nistp384' |
|||
|
|||
// 7 '00000007'
|
|||
// s s h - r s a
|
|||
// 7373682d727361
|
|||
, rsa: 'ssh-rsa' |
|||
}; |
|||
|
|||
Enc.base64ToBuf = function (b64) { |
|||
return Enc.binToBuf(atob(b64)); |
|||
}; |
|||
|
|||
Enc.binToBuf = function (bin) { |
|||
var arr = bin.split('').map(function (ch) { |
|||
return ch.charCodeAt(0); |
|||
}); |
|||
return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; |
|||
}; |
|||
|
|||
Enc.bufToBase64 = function (u8) { |
|||
var bin = ''; |
|||
u8.forEach(function (i) { |
|||
bin += String.fromCharCode(i); |
|||
}); |
|||
return btoa(bin); |
|||
}; |
|||
|
|||
Enc.bufToBin = function (buf) { |
|||
var bin = ''; |
|||
// cannot use .map() because Uint8Array would return only 0s
|
|||
buf.forEach(function (ch) { |
|||
bin += String.fromCharCode(ch); |
|||
}); |
|||
return bin; |
|||
}; |
|||
|
|||
}('undefined' !== typeof window ? window : module.exports)); |
Loading…
Reference in new issue