diff --git a/app/lib/rawer.js b/app/lib/rawer.js index 8b96806b0e2fbba04523ba3de8c1546aeedf08bd..d197bfc3ddc3ad927b71aa98e504b80334cbe7e8 100644 --- a/app/lib/rawer.js +++ b/app/lib/rawer.js @@ -145,6 +145,9 @@ module.exports = new function() { return unix2dos(signed(that.getMembershipWithoutSignature(json), json)); }; + var KEYBLOCK_PUBK_PREFIX = "#####-----" + var KEYBLOCK_PUBK_SUFFIX = "-----#####" + this.getKeyblockWithoutSignature = function (json) { var raw = ""; raw += "Version: " + json.version + "\n"; @@ -165,8 +168,11 @@ module.exports = new function() { } raw += "PublicKeys:\n"; for(var i = 0; i < json.publicKeys.length; i++){ - raw += '#' + json.publicKeys[i].fingerprint + '\n'; - raw += json.publicKeys[i].packets; + var packets = json.publicKeys[i].packets; + raw += KEYBLOCK_PUBK_PREFIX + json.publicKeys[i].fingerprint + KEYBLOCK_PUBK_SUFFIX + '\n'; + raw += packets; + if (!packets.match(/\n$/)) + raw += '\n'; } raw += "Memberships:\n"; for(var i = 0; i < json.memberships.length; i++){ @@ -174,8 +180,11 @@ module.exports = new function() { } raw += "MembershipsSignatures:\n"; for(var i = 0; i < json.membershipsSigs.length; i++){ - raw += '#' + json.membershipsSigs[i].fingerprint + '\n'; - raw += json.membershipsSigs[i].packets; + var packets = json.membershipsSigs[i].packets; + raw += KEYBLOCK_PUBK_PREFIX + json.membershipsSigs[i].fingerprint + KEYBLOCK_PUBK_SUFFIX + '\n'; + raw += packets; + if (!packets.match(/\n$/)) + raw += '\n'; } return unix2dos(raw); }; diff --git a/app/lib/signature.js b/app/lib/signature.js new file mode 100644 index 0000000000000000000000000000000000000000..58198f38dd7874eef6b33c0f319a6a9a9c8ea025 --- /dev/null +++ b/app/lib/signature.js @@ -0,0 +1,42 @@ +var async = require('async'); +var openpgp = require('openpgp'); +var jpgp = require('./jpgp'); +var logger = require('./logger')('peerserver'); + +module.exports = function (armoredPrivateKey, password, withOpenPGPJS, done) { + var privateKey = openpgp.key.readArmored(armoredPrivateKey).keys[0]; + var fingerprint = privateKey.getKeyPacket().getFingerprint().toUpperCase(); + async.waterfall([ + function (next) { + if (withOpenPGPJS) { + var pgp = jpgp(); + privateKey.decrypt(password); + var signingFunc = async.apply(pgp.sign.bind(pgp.sign), privateKey); + next(null, function (message, done) { + jpgp().sign(message, privateKey, done); + }); + } else { + var asciiPrivateKey = armoredPrivateKey; + var keyring = '~/.gnupg/ucoin_' + fingerprint; + logger.debug("Keyring = %s", keyring); + var gnupg = new (require('./gnupg'))(asciiPrivateKey, password, fingerprint, keyring); + gnupg.init(function (err) { + next(err, function (message, done) { + gnupg.sign(message, done); + }); + }); + } + }, + function (signFunc, next){ + try{ + signFunc("some test\nwith line return", function (err) { + next(err, signFunc); + }); + } catch(ex){ + next("Wrong private key password."); + } + }, + ], function (err, signFunc) { + done(err, signFunc); + }); +}; diff --git a/app/lib/streams/multicaster.js b/app/lib/streams/multicaster.js index 37110b609c00c7d8b81d3c9b312ad475beeb77d9..fa757ee34819726c49f2404ab99a5dc1157176f5 100644 --- a/app/lib/streams/multicaster.js +++ b/app/lib/streams/multicaster.js @@ -130,6 +130,7 @@ function Multicaster () { }); this.sendPubkey = sendPubkey; + this.sendKeyblock = sendKeyblock; } util.inherits(Multicaster, stream.Transform); @@ -143,6 +144,15 @@ function sendPubkey(peer, pubkey, done) { }, done); } +function sendKeyblock(peer, keyblock, done) { + var keyID = peer.keyID(); + logger.info('POST keyblock to %s', keyID.match(/\?/) ? peer.getURL() : keyID); + post(peer, '/keychain/keyblock', { + "keyblock": keyblock.getRaw(), + "signature": keyblock.signature + }, done); +} + function sendTransaction(peer, transaction, done) { logger.info('POST transaction to %s', peer.keyID()); post(peer, '/hdc/transactions/process', { diff --git a/app/lib/streams/parsers/doc/keyblock.js b/app/lib/streams/parsers/doc/keyblock.js index 465c377a7edf83e1e5410c8b39b82fb01b04eaa8..f37d45857db1fd6c40a7fc70f68af5fa43846114 100644 --- a/app/lib/streams/parsers/doc/keyblock.js +++ b/app/lib/streams/parsers/doc/keyblock.js @@ -114,11 +114,11 @@ function extractFingerprintSeparatedPackets(raw) { var lines = raw.split(/\n/); var nbKeys = 0; lines.forEach(function(line){ - if (line.match(/^#[A-Z0-9]{40}$/)) { + if (line.match(/^#####-----[A-Z0-9]{40}-----#####$/)) { // New key block packetsByFPR.push({ number: nbKeys++, - fingerprint: line.substring(1), + fingerprint: line.substring(10, 50), packets: "" }); } else if (line.match(/^[A-Za-z0-9\/+=]{1,64}$/) && nbKeys > 0) { diff --git a/app/models/keyblock.js b/app/models/keyblock.js index f5874ab1796bad1598229433a3089827b1a3c177..bc438e5b8e5aac2ffe7a2f3a584740437f1cf7a7 100644 --- a/app/models/keyblock.js +++ b/app/models/keyblock.js @@ -258,6 +258,10 @@ KeyBlockSchema.methods = { }, getRaw: function() { + return require('../lib/rawer').getKeyblockWithoutSignature(this); + }, + + getRawSigned: function() { return require('../lib/rawer').getKeyblock(this); }, diff --git a/app/models/peer.js b/app/models/peer.js index 18d1d60ad13a30596e3fbcad17560b43e440bb31..56f8b59e5fb8d2580533a8fd1cfbac2cd2086792 100644 --- a/app/models/peer.js +++ b/app/models/peer.js @@ -15,7 +15,7 @@ var STATUS = { DOWN: "DOWN", NOTHING: "NOTHING" }; -var BMA_REGEXP = /^BASIC_MERKLED_API( ([a-z_][a-z0-9-_.]+))?( ([0-9.]+))?( ([0-9a-f:]+))?( ([0-9]+))$/; +var BMA_REGEXP = /^BASIC_MERKLED_API( ([a-z_][a-z0-9-_.]*))?( ([0-9.]+))?( ([0-9a-f:]+))?( ([0-9]+))$/; var PeerSchema = new Schema({ version: String, diff --git a/app/service/KeychainService.js b/app/service/KeychainService.js index 4d1437572251a27b9c5ab9109bbf0a41fccb5c9a..7fb2c3d6b140c88173a7e33ff9d41bec7b6b8d9d 100644 --- a/app/service/KeychainService.js +++ b/app/service/KeychainService.js @@ -2,10 +2,13 @@ var jpgp = require('../lib/jpgp'); var async = require('async'); var _ = require('underscore'); var openpgp = require('openpgp'); +var merkle = require('merkle'); var base64 = require('../lib/base64'); var unix2dos = require('../lib/unix2dos'); +var dos2unix = require('../lib/dos2unix'); var parsers = require('../lib/streams/parsers/doc'); -var mlogger = require('../lib/logger')('membership'); +var logger = require('../lib/logger')('membership'); +var moment = require('moment'); module.exports.get = function (conn, conf, PublicKeyService) { return new KeyService(conn, conf, PublicKeyService); @@ -34,7 +37,7 @@ function KeyService (conn, conf, PublicKeyService) { var entry = new Membership(ms); async.waterfall([ function (next){ - mlogger.debug('⬇ %s %s', entry.issuer, entry.membership); + logger.debug('⬇ %s %s', entry.issuer, entry.membership); // Get already existing Membership with same parameters Membership.getForHashAndIssuer(entry.hash, entry.issuer, next); }, @@ -51,7 +54,7 @@ function KeyService (conn, conf, PublicKeyService) { }); }, function (next){ - mlogger.debug('✔ %s %s', entry.issuer, entry.membership); + logger.debug('✔ %s %s', entry.issuer, entry.membership); next(null, entry); } ], done); @@ -581,6 +584,37 @@ function KeyService (conn, conf, PublicKeyService) { return certifs; }; + this.getPurePubkey = function () { + return new openpgp.key.Key(this.getPurePackets()); + }; + + // Get signatories' certification packet of the userid (not checked yet) + this.getPurePackets = function () { + var purePackets = []; + var packets = this.packets.filterByTag( + openpgp.enums.packet.publicKey, + openpgp.enums.packet.publicSubkey, + openpgp.enums.packet.userid, + openpgp.enums.packet.signature); + packets.forEach(function(packet){ + var signaturesToKeep = [ + openpgp.enums.signature.cert_generic, + openpgp.enums.signature.cert_persona, + openpgp.enums.signature.cert_casual, + openpgp.enums.signature.cert_positive, + openpgp.enums.signature.subkey_binding + ]; + if (~signaturesToKeep.indexOf(packet.signatureType)) { + var issuerKeyId = packet.issuerKeyId.toHex().toUpperCase(); + var isSelfSig = fingerprint.match(new RegExp(issuerKeyId + '$')); + if (isSelfSig) { + purePackets.push(packet); + } + } + }); + return purePackets; + }; + this.getPubKey = function () { return new openpgp.key.Key(this.packets); }; @@ -595,4 +629,127 @@ function KeyService (conn, conf, PublicKeyService) { return armor; } } + + this.current = function (done) { + KeyBlock.current(function (err, kb) { + done(err, kb || null); + }) + } + + this.generateRoot = function (uids, done) { + var joinData = {}; + var fingerprints = []; + async.forEach(uids, function(uid, callback){ + var join = { pubkey: null, ms: null }; + async.waterfall([ + function (next){ + Membership.find({ userid: uid }, next); + }, + function (mss, next){ + if (mss.length == 0) { + next('Membership not found?!') + return; + } + else if (mss.length > 1) { + next('Multiple membership found! Stopping.') + return; + } + else { + join.ms = mss[0]; + fingerprints.push(join.ms.issuer); + PublicKey.getTheOne(join.ms.issuer, next); + } + }, + function (pubk, next){ + join.pubkey = pubk; + joinData[join.pubkey.fingerprint] = join; + next(); + }, + ], callback); + }, function(err){ + var block = new KeyBlock(); + block.version = 1; + block.currency = joinData[fingerprints[0]].ms.currency; + block.number = 0; + // Members merkle + fingerprints.sort(); + var tree = merkle(fingerprints, 'sha1').process(); + block.membersCount = fingerprints.length; + block.membersRoot = tree.root(); + block.membersChanges = []; + fingerprints.forEach(function(fpr){ + block.membersChanges.push('+' + fpr); + }); + // Public keys + block.publicKeys = []; + _(joinData).values().forEach(function(join){ + var key = openpgp.key.readArmored(join.pubkey.raw).keys[0]; + var pkData = { + fingerprint: join.pubkey.fingerprint, + packets: base64.encode(key.toPacketlist().write()) + }; + block.publicKeys.push(pkData); + }); + // Memberships + block.memberships = []; + _(joinData).values().forEach(function(join){ + var ms = join.ms; + var shortMS = [1, join.pubkey.fingerprint, 'IN', ms.date.timestamp(), ms.userid].join(':'); + block.memberships.push(shortMS); + }); + // Memberships signatures + block.membershipsSigs = []; + _(joinData).values().forEach(function(join){ + var ms = join.ms; + var splits = dos2unix(ms.signature).split('\n'); + var signature = ""; + var keep = false; + splits.forEach(function(line){ + if (keep && !line.match('-----END PGP') && line != '') signature += line + '\n'; + if (line == "") keep = true; + }); + block.membershipsSigs.push({ + fingerprint: join.pubkey.fingerprint, + packets: signature + }); + }); + done(null, block); + }); + }; + + this.prove = function (block, sigFunc, nbZeros, done) { + var powRegexp = new RegExp('^0{' + nbZeros + '}'); + var pow = "", sig = "", raw = ""; + var start = new Date().timestamp(); + var testsCount = 0; + logger.debug('Generating proof-of-work...'); + async.whilst( + function(){ return !pow.match(powRegexp); }, + function (next) { + var newTS = new Date().timestamp(); + if (newTS == block.timestamp) { + block.nonce++; + } else { + block.nonce = 0; + block.timestamp = newTS; + } + raw = block.getRaw(); + sigFunc(raw, function (err, sigResult) { + sig = unix2dos(sigResult); + var full = raw + sig; + pow = full.hash(); + testsCount++; + if (testsCount % 100 == 0) process.stdout.write('.'); + next(); + }); + }, function (err) { + console.log(raw); + block.signature = sig; + var end = new Date().timestamp(); + var duration = moment.duration((end - start)) + 's'; + var testsPerSecond = (testsCount / (end - start)).toFixed(2); + logger.debug('Done: ' + pow + ' in ' + duration + ' (~' + testsPerSecond + ' tests/s)'); + done(err, block); + }); + }; } diff --git a/bin/ucoind b/bin/ucoind index 3d6727c7ebc6eb5a4bc4ce1a1cb48d2e5b50e9a2..b0bfd864b8ec9ebdf39ec0d138b1168f489c6fe8 100755 --- a/bin/ucoind +++ b/bin/ucoind @@ -13,6 +13,7 @@ var wizard = require('../app/lib/wizard'); var router = require('../app/lib/streams/router'); var multicaster = require('../app/lib/streams/multicaster'); var logger = require('../app/lib/logger')('ucoind'); +var signature = require('../app/lib/signature'); var ucoin = require('./..'); function keys (val) { @@ -220,6 +221,86 @@ function handleKey (server, key, isManaged, message) { }); } +program + .command('gen-root [host] [port] [difficulty]') + .description('Tries to generate the root keyblock of the keychain using already received keys & memberships') + .action(service(DO_NOT_LISTEN_HTTP, ucoin.createWOTServer, function (host, port, difficulty, server, conf) { + var Membership = server.conn.model('Membership'); + var KeychainService = server.KeychainService; + async.waterfall([ + function (next){ + if (!host || !port) { + next('usage: gen-root [host] [port]'); + return; + } + KeychainService.current(function (err, current) { + if (current) { + next('Local keychain is already started.'); + return; + } + else next(); + }) + }, + function (next){ + Membership.find({}, next); + }, + function (mss, next){ + var uids = []; + mss.forEach(function(ms){ + uids.push(ms.userid); + }); + inquirer.prompt([{ + type: "checkbox", + name: "uids", + message: "Initial members of the Web of Trust", + choices: uids, + default: uids[0] + }], function (answers) { + next(null, answers.uids); + }); + }, + function (uids, next){ + if (uids.length == 0) { + next('You must select at least 1 user'); + return; + } + KeychainService.generateRoot(uids, next); + }, + function (root, next){ + var wiz = wizard(server); + async.waterfall([ + function (next){ + wiz.configOpenpgp(conf, next); + }, + function (next){ + wiz.configKey(conf, next); + }, + function (next){ + signature(conf.pgpkey, conf.pgppasswd, conf.openpgpjs, next); + }, + function (sigFunc, next){ + KeychainService.prove(root, sigFunc, difficulty, next); + }, + function (block, next){ + var Peer = server.conn.model('Peer'); + var peer = new Peer({ + endpoints: [['BASIC_MERKLED_API', host, port].join(' ')] + }); + // console.log(block.getRaw()); + // console.log(block.signature); + multicaster().sendKeyblock(peer, block, next); + }, + ], next); + }, + ], function (err) { + if (err) { + logger.error(err); + } + server.disconnect(); + process.exit(); + }); + })); + program .command('check-config') .description('Checks the node\'s configuration') diff --git a/peerserver.js b/peerserver.js index 0b755b3cb56e9322bd5bc448cf6929c42531f7e5..9593a65b528a1c321cc528997876b5a78a666c04 100644 --- a/peerserver.js +++ b/peerserver.js @@ -8,7 +8,8 @@ var plogger = require('./app/lib/logger')('peer'); var flogger = require('./app/lib/logger')('forward'); var slogger = require('./app/lib/logger')('status'); var wlogger = require('./app/lib/logger')('wallet'); -var WOT = require('./wotserver'); +var WOT = require('./wotserver'); +var signature = require('./app/lib/signature'); var parsers = require('./app/lib/streams/parsers/doc'); function PeerServer (dbConf, overrideConf, interceptors, onInit) { @@ -178,37 +179,8 @@ function PeerServer (dbConf, overrideConf, interceptors, onInit) { }; this.createSignFunction = function (conf, done) { - async.waterfall([ - function (next) { - if (conf.openpgpjs) { - var pgp = jpgp(); - var privateKey = openpgp.key.readArmored(conf.pgpkey).keys[0]; - privateKey.decrypt(conf.pgppasswd); - var signingFunc = async.apply(pgp.sign.bind(pgp.sign), privateKey); - next(null, function (message, done) { - jpgp().sign(message, privateKey, done); - }); - } else { - var asciiPrivateKey = conf.pgpkey; - var keyring = '~/.gnupg/ucoin_' + that.PeeringService.cert.fingerprint; - logger.debug("Keyring = %s", keyring); - var gnupg = new (require('./app/lib/gnupg'))(asciiPrivateKey, conf.pgppasswd, that.PeeringService.cert.fingerprint, keyring); - gnupg.init(function (err) { - next(err, function (message, done) { - gnupg.sign(message, done); - }); - }); - } - }, - function (signFunc, next){ - that.sign = signFunc; - try{ - that.sign("some test\nwith line return", next); - } catch(ex){ - next("Wrong private key password."); - } - }, - ], function (err) { + signature(conf.pgpkey, conf.pgppasswd, conf.openpgpjs, function (err, sigFunc) { + that.sign = sigFunc; done(err); }); }