Mercurial > hg > rc2
diff program/js/publickey.js @ 0:4681f974d28b
vanilla 1.3.3 distro, I hope
author | Charlie Root |
---|---|
date | Thu, 04 Jan 2018 15:52:31 -0500 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/program/js/publickey.js Thu Jan 04 15:52:31 2018 -0500 @@ -0,0 +1,483 @@ +/** + * PublicKey.js - v0e011cb + * + * @source https://github.com/diafygi/publickeyjs/blob/master/publickey.js + * + * @licstart The following is the entire license notice for the + * JavaScript code in this file. + * + * Copyright (c) 2015 Daniel Roesler + * + * The JavaScript code in this page is free software: you can + * redistribute it and/or modify it under the terms of the GNU + * General Public License (GNU GPL) as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) + * any later version. The code is distributed WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. + * + * As additional permission under GNU GPL version 3 section 7, you + * may distribute non-source (e.g., minimized or compacted) forms of + * that code without the copy of the GNU GPL normally required by + * section 4, provided you include this license notice and a URL + * through which recipients can access the Corresponding Source. + * + * @licend The above is the entire license notice + * for the JavaScript code in this file. + */ +"use strict"; + +(function(context){ + /* + Default keyservers (HTTPS and CORS enabled) + */ + var DEFAULT_KEYSERVERS = [ + "https://keys.fedoraproject.org/", + "https://keybase.io/", + ]; + + /* + Initialization to create an PublicKey object. + + Arguments: + + * keyservers - Array of keyserver domains, default is: + ["https://keys.fedoraproject.org/", "https://keybase.io/"] + + Examples: + + //Initialize with the default keyservers + var hkp = new PublicKey(); + + //Initialize only with a specific keyserver + var hkp = new PublicKey(["https://key.ip6.li/"]); + */ + var PublicKey = function(keyservers){ + this.keyservers = keyservers || DEFAULT_KEYSERVERS; + }; + + /* + Get a public key from any keyserver based on keyId. + + Arguments: + + * keyId - String key id of the public key (this is usually a fingerprint) + + * callback - Function that is called when finished. Two arguments are + passed to the callback: publicKey and errorCode. publicKey is + an ASCII armored OpenPGP public key. errorCode is the error code + (either HTTP status code or keybase error code) returned by the + last keyserver that was tried. If a publicKey was found, + errorCode is null. If no publicKey was found, publicKey is null + and errorCode is not null. + + Examples: + + //Get a valid public key + var hkp = new PublicKey(); + hkp.get("F75BE4E6EF6E9DD203679E94E7F6FAD172EFEE3D", function(publicKey, errorCode){ + errorCode !== null ? console.log(errorCode) : console.log(publicKey); + }); + + //Try to get an invalid public key + var hkp = new PublicKey(); + hkp.get("bogus_id", function(publicKey, errorCode){ + errorCode !== null ? console.log(errorCode) : console.log(publicKey); + }); + */ + PublicKey.prototype.get = function(keyId, callback, keyserverIndex, err){ + //default starting point is at the first keyserver + if(keyserverIndex === undefined){ + keyserverIndex = 0; + } + + //no more keyservers to check, so no key found + if(keyserverIndex >= this.keyservers.length){ + return callback(null, err || 404); + } + + //set the keyserver to try next + var ks = this.keyservers[keyserverIndex]; + var _this = this; + + //special case for keybase + if(ks.indexOf("https://keybase.io/") === 0){ + + //don't need 0x prefix for keybase searches + if(keyId.indexOf("0x") === 0){ + keyId = keyId.substr(2); + } + + //request the public key from keybase + var xhr = new XMLHttpRequest(); + xhr.open("get", "https://keybase.io/_/api/1.0/user/lookup.json" + + "?fields=public_keys&key_fingerprint=" + keyId); + xhr.onload = function(){ + if(xhr.status === 200){ + var result = JSON.parse(xhr.responseText); + + //keybase error returns HTTP 200 status, which is silly + if(result['status']['code'] !== 0){ + return _this.get(keyId, callback, keyserverIndex + 1, result['status']['code']); + } + + //no public key found + if(result['them'].length === 0){ + return _this.get(keyId, callback, keyserverIndex + 1, 404); + } + + //found the public key + var publicKey = result['them'][0]['public_keys']['primary']['bundle']; + return callback(publicKey, null); + } + else{ + return _this.get(keyId, callback, keyserverIndex + 1, xhr.status); + } + }; + xhr.send(); + } + + //normal HKP keyserver + else{ + //add the 0x prefix if absent + if(keyId.indexOf("0x") !== 0){ + keyId = "0x" + keyId; + } + + //request the public key from the hkp server + var xhr = new XMLHttpRequest(); + xhr.open("get", ks + "pks/lookup?op=get&options=mr&search=" + keyId); + xhr.onload = function(){ + if(xhr.status === 200){ + return callback(xhr.responseText, null); + } + else{ + return _this.get(keyId, callback, keyserverIndex + 1, xhr.status); + } + }; + xhr.send(); + } + }; + + /* + Search for a public key in the keyservers. + + Arguments: + + * query - String to search for (usually an email, name, or username). + + * callback - Function that is called when finished. Two arguments are + passed to the callback: results and errorCode. results is an + Array of users that were returned by the search. errorCode is + the error code (either HTTP status code or keybase error code) + returned by the last keyserver that was tried. If any results + were found, errorCode is null. If no results are found, results + is null and errorCode is not null. + + Examples: + + //Search for diafygi's key id + var hkp = new PublicKey(); + hkp.search("diafygi", function(results, errorCode){ + errorCode !== null ? console.log(errorCode) : console.log(results); + }); + + //Search for a nonexistent key id + var hkp = new PublicKey(); + hkp.search("doesntexist123", function(results, errorCode){ + errorCode !== null ? console.log(errorCode) : console.log(results); + }); + */ + PublicKey.prototype.search = function(query, callback, keyserverIndex, results, err){ + //default starting point is at the first keyserver + if(keyserverIndex === undefined){ + keyserverIndex = 0; + } + + //initialize the results array + if(results === undefined){ + results = []; + } + + //no more keyservers to check + if(keyserverIndex >= this.keyservers.length){ + + //return error if no results + if(results.length === 0){ + return callback(null, err || 404); + } + + //return results + else{ + + //merge duplicates + var merged = {}; + for(var i = 0; i < results.length; i++){ + var k = results[i]; + + //see if there's duplicate key ids to merge + if(merged[k['keyid']] !== undefined){ + + for(var u = 0; u < k['uids'].length; u++){ + var has_this_uid = false; + + for(var m = 0; m < merged[k['keyid']]['uids'].length; m++){ + if(merged[k['keyid']]['uids'][m]['uid'] === k['uids'][u]){ + has_this_uid = true; + break; + } + } + + if(!has_this_uid){ + merged[k['keyid']]['uids'].push(k['uids'][u]) + } + } + } + + //no duplicate found, so add it to the dict + else{ + merged[k['keyid']] = k; + } + } + + //return a list of the merged results in the same order + var merged_list = []; + for(var i = 0; i < results.length; i++){ + var k = results[i]; + if(merged[k['keyid']] !== undefined){ + merged_list.push(merged[k['keyid']]); + delete(merged[k['keyid']]); + } + } + return callback(merged_list, null); + } + } + + //set the keyserver to try next + var ks = this.keyservers[keyserverIndex]; + var _this = this; + + //special case for keybase + if(ks.indexOf("https://keybase.io/") === 0){ + + //request a list of users from keybase + var xhr = new XMLHttpRequest(); + xhr.open("get", "https://keybase.io/_/api/1.0/user/autocomplete.json?q=" + encodeURIComponent(query)); + xhr.onload = function(){ + if(xhr.status === 200){ + var kb_json = JSON.parse(xhr.responseText); + + //keybase error returns HTTP 200 status, which is silly + if(kb_json['status']['code'] !== 0){ + return _this.search(query, callback, keyserverIndex + 1, results, kb_json['status']['code']); + } + + //no public key found + if(kb_json['completions'].length === 0){ + return _this.search(query, callback, keyserverIndex + 1, results, 404); + } + + //compose keybase user results + var kb_results = []; + for(var i = 0; i < kb_json['completions'].length; i++){ + var user = kb_json['completions'][i]['components']; + + //skip if no public key fingerprint + if(user['key_fingerprint'] === undefined){ + continue; + } + + //build keybase user result + var kb_result = { + "keyid": user['key_fingerprint']['val'].toUpperCase(), + "href": "https://keybase.io/" + user['username']['val'] + "/key.asc", + "info": "https://keybase.io/" + user['username']['val'], + "algo": user['key_fingerprint']['algo'], + "keylen": user['key_fingerprint']['nbits'], + "creationdate": null, + "expirationdate": null, + "revoked": false, + "disabled": false, + "expired": false, + "uids": [{ + "uid": user['username']['val'] + + " on Keybase <https://keybase.io/" + + user['username']['val'] + ">", + "creationdate": null, + "expirationdate": null, + "revoked": false, + "disabled": false, + "expired": false, + }] + }; + + //add full name + if(user['full_name'] !== undefined){ + kb_result['uids'].push({ + "uid": "Full Name: " + user['full_name']['val'], + "creationdate": null, + "expirationdate": null, + "revoked": false, + "disabled": false, + "expired": false, + }); + } + + //add twitter + if(user['twitter'] !== undefined){ + kb_result['uids'].push({ + "uid": user['twitter']['val'] + + " on Twitter <https://twitter.com/" + + user['twitter']['val'] + ">", + "creationdate": null, + "expirationdate": null, + "revoked": false, + "disabled": false, + "expired": false, + }); + } + + //add github + if(user['github'] !== undefined){ + kb_result['uids'].push({ + "uid": user['github']['val'] + + " on Github <https://github.com/" + + user['github']['val'] + ">", + "creationdate": null, + "expirationdate": null, + "revoked": false, + "disabled": false, + "expired": false, + }); + } + + //add reddit + if(user['reddit'] !== undefined){ + kb_result['uids'].push({ + "uid": user['reddit']['val'] + + " on Github <https://reddit.com/u/" + + user['reddit']['val'] + ">", + "creationdate": null, + "expirationdate": null, + "revoked": false, + "disabled": false, + "expired": false, + }); + } + + //add hackernews + if(user['hackernews'] !== undefined){ + kb_result['uids'].push({ + "uid": user['hackernews']['val'] + + " on Hacker News <https://news.ycombinator.com/user?id=" + + user['hackernews']['val'] + ">", + "creationdate": null, + "expirationdate": null, + "revoked": false, + "disabled": false, + "expired": false, + }); + } + + //add coinbase + if(user['coinbase'] !== undefined){ + kb_result['uids'].push({ + "uid": user['coinbase']['val'] + + " on Coinbase <https://www.coinbase.com/" + + user['coinbase']['val'] + ">", + "creationdate": null, + "expirationdate": null, + "revoked": false, + "disabled": false, + "expired": false, + }); + } + + //add websites + if(user['websites'] !== undefined){ + for(var w = 0; w < user['websites'].length; w++){ + kb_result['uids'].push({ + "uid": "Owns " + user['websites'][w]['val'], + "creationdate": null, + "expirationdate": null, + "revoked": false, + "disabled": false, + "expired": false, + }); + } + } + + kb_results.push(kb_result); + } + + results = results.concat(kb_results); + return _this.search(query, callback, keyserverIndex + 1, results, null); + } + else{ + return _this.search(query, callback, keyserverIndex + 1, results, xhr.status); + } + }; + xhr.send(); + } + + //normal HKP keyserver + else{ + var xhr = new XMLHttpRequest(); + xhr.open("get", ks + "pks/lookup?op=index&options=mr&fingerprint=on&search=" + encodeURIComponent(query)); + xhr.onload = function(){ + if(xhr.status === 200){ + var ks_results = []; + var raw = xhr.responseText.split("\n"); + var curKey = undefined; + for(var i = 0; i < raw.length; i++){ + var line = raw[i].trim(); + + //pub:<keyid>:<algo>:<keylen>:<creationdate>:<expirationdate>:<flags> + if(line.indexOf("pub:") == 0){ + if(curKey !== undefined){ + ks_results.push(curKey); + } + var vals = line.split(":"); + curKey = { + "keyid": vals[1], + "href": ks + "pks/lookup?op=get&options=mr&search=0x" + vals[1], + "info": ks + "pks/lookup?op=vindex&search=0x" + vals[1], + "algo": vals[2] === "" ? null : parseInt(vals[2]), + "keylen": vals[3] === "" ? null : parseInt(vals[3]), + "creationdate": vals[4] === "" ? null : parseInt(vals[4]), + "expirationdate": vals[5] === "" ? null : parseInt(vals[5]), + "revoked": vals[6].indexOf("r") !== -1, + "disabled": vals[6].indexOf("d") !== -1, + "expired": vals[6].indexOf("e") !== -1, + "uids": [], + } + } + + //uid:<escaped uid string>:<creationdate>:<expirationdate>:<flags> + if(line.indexOf("uid:") == 0){ + var vals = line.split(":"); + curKey['uids'].push({ + "uid": decodeURIComponent(vals[1]), + "creationdate": vals[2] === "" ? null : parseInt(vals[2]), + "expirationdate": vals[3] === "" ? null : parseInt(vals[3]), + "revoked": vals[4].indexOf("r") !== -1, + "disabled": vals[4].indexOf("d") !== -1, + "expired": vals[4].indexOf("e") !== -1, + }); + } + } + ks_results.push(curKey); + + results = results.concat(ks_results); + return _this.search(query, callback, keyserverIndex + 1, results, null); + } + else{ + return _this.search(query, callback, keyserverIndex + 1, results, xhr.status); + } + }; + xhr.send(); + } + }; + + context.PublicKey = PublicKey; +})(typeof exports === "undefined" ? this : exports);