Skip to content

Commit f0c9d1c

Browse files
author
Danny Diekroeger
committed
added support for key signatures: during import, during provisioning, added test coverage, added config option to include verification pub
1 parent b2d4528 commit f0c9d1c

File tree

5 files changed

+130
-5
lines changed

5 files changed

+130
-5
lines changed

app/admin.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const MasterKey = require('./models/masterkey.js');
1313
const signingTool = require('./sign.js');
1414
const WalletKey = require('./models/walletkey.js');
1515
const utils = require('./utils');
16+
const bitcoinMessage = require('bitcoinjs-message');
1617

1718
const parser = new ArgumentParser({
1819
version: pjson.version,
@@ -188,6 +189,28 @@ const validateKey = function(key, type) {
188189
return false;
189190
}
190191

192+
// if we have provided a verificationPub, then check the key signature
193+
if (process.config.verificationPub) {
194+
195+
if (!key.signature) {
196+
console.log('Key ${key.pub} requires a signature and does not have one.');
197+
return false;
198+
}
199+
200+
let validSig = false;
201+
try {
202+
validSig = bitcoinMessage.verify(key.pub, process.config.verificationPub, key.signature);
203+
} catch (err) {
204+
console.log('There was an error when verifying key ${key.pub}: ' + err.message);
205+
return false;
206+
}
207+
208+
if (!validSig) {
209+
console.log('Invalid signature detected on key ${key.pub}');
210+
return false;
211+
}
212+
}
213+
191214
return true;
192215
};
193216

@@ -208,7 +231,7 @@ const saveKeys = co(function *(keys, type) {
208231
type: type,
209232
pub: key.pub,
210233
path: key.path,
211-
keyid: key.keyid,
234+
signature: key.signature,
212235
keyCount: 0
213236
}));
214237

@@ -365,4 +388,4 @@ const run = co(function *(testArgs) {
365388
});
366389

367390
// For admin script and unit testing of functions
368-
module.exports = { run, validateKey, db };
391+
module.exports = { run, validateKey, saveKeys, db };

app/krs.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ exports.provisionKey = co(function *(req) {
157157

158158
yield key.save();
159159

160+
// If the master key has a signature, we include the signature in the response to the user
161+
if (masterKey.signature) {
162+
key.masterKeySig = masterKey.signature;
163+
}
164+
160165
yield masterKey.update({ $inc: { keyCount: 1 } });
161166

162167
if (!req.body.disableKRSEmail) {

app/models/masterkey.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ const masterKeySchema = new mongoose.Schema({
77
customerId: { type: String },
88
pub: { type: String },
99
path: { type: String },
10-
keyid: {type: String},
10+
signature: {type: String},
1111
keyCount: { type: Number }
1212
});
1313

1414
masterKeySchema.methods = {
1515
toJSON: function() {
16-
return _.pick(this, ['type', 'coin', 'customerId', 'pub', 'path', 'keyCount']);
16+
return _.pick(this, ['type', 'coin', 'customerId', 'pub', 'path', 'signature', 'keyCount']);
1717
}
1818
};
1919

config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ module.exports = {
4040
"bitgo": "changeThisSecret"
4141
}
4242
},
43+
"verificationPub": null,
4344
"neverReuseMasterKey": true,
4445
"lowKeyWarningLevels": [10000, 5000, 1000, 500, 100, 0]
4546
};

test/admin.js

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,18 @@ const should = require('should');
33
const testutils = require('./testutils');
44
const admin = require('../app/admin.js');
55
const WalletKey = require('../app/models/walletkey');
6+
const MasterKey = require('../app/models/masterkey');
67
const utils = require('../app/utils.js');
8+
const Promise = require('bluebird');
9+
const co = Promise.coroutine;
10+
let originalVerifcationPub = process.config.verificationPub;
11+
12+
const testVerificationPub = '1GS6JPCUpyFZLrE5bbJBcpeg1EdqW63nHd';
13+
const xpub = 'xpub661MyMwAqRbcGGPNi42htgwXoLuPjEtdRSRUm65GWRPAb31WPRRkxzL18TGrpU2sirNjXAHFjAEmiav9kmVfY83dTfxh3DVVwNcM9JNVebh';
14+
const xpubSig = 'IJB1Ubed4LNVvHfbH3s7i9duWmVi+98rCcGpB/t/V9xkY+VLl+YEv3w/g/79QgHYFv4D/nm2SGrT+FaOL1PomTU';
15+
const xlmPub = 'GCM67ICQVOYN7GYYHMMGVPQ7NDCLNVBM4R74IIQTG65H4DM3KGEDMW3S';
16+
const xlmSig = 'IBdvAO2063rsqo6zyhcoq4qGWMZFFQXCbcP4X7/vnbuyQO7VF2FHHr/M85CQb/yBfPgZRy5VsmDmrW3Qa6OXk6w';
17+
const badSig = 'IBadAO2063rsqo6zyhcoq4qGWMZFFQXCbcP4X7/vnbuyQO7VF2FHHr/M85CQb/yBfPgZRy5VsmDmrW3Qa6OXk6w';
718

819
describe('Offline Admin Tool', function() {
920
before(function() {
@@ -12,15 +23,18 @@ describe('Offline Admin Tool', function() {
1223

1324
after(function() {
1425
testutils.mongoose.connection.close();
26+
process.config.verificationPub = originalVerifcationPub;
1527
});
1628

1729
describe('Xpub validation', function() {
30+
1831
// Xpub importing was tested with a local file with contents: (w/o line breaks)
1932
// xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU,
2033
// xpub69pXXVvsBtZHWE1wgoix7xRAnbp1r6tR1kTK9cKvCd4QgYh4JBSLBmLA65Kg7rCMwGYrNHKFKxZDtjRkU4Ex2ozMYGfk14EyotJ5xjf2Goy,
2134
// xpub6AU2iYgKUY8FvUc2Nz2fcWpKU1HJTeYTbzv4MZLGPBw2YnhoZPkkUo54fvqZyVtxtszMdyksF8k3iqMcoegyvj72xKZCmuCjDWneXjjztLN
2235
// and successfully saved to local Mongo instance
2336
describe('failure', function() {
37+
process.config.verificationPub = null;
2438
it('should fail if length is not 111', function() {
2539
const SHORT_XPUB = { path: 'm/0\'', pub: 'xpub1234567890' };
2640

@@ -42,14 +56,15 @@ describe('Offline Admin Tool', function() {
4256

4357
describe('success', function() {
4458
it('should succeed with a valid key', function() {
59+
process.config.verificationPub = null;
4560
const GOOD_XPUB = { path: 'm/0\'', pub: 'xpub6B7XuUcPQ9MeszNzaTTGtni9W79MmFnHa7FUe7Hrbv3pefnaDFCHtJWaWdg1FVbocHhivnCRTCbHTjDrMBEyAGDJHGyqCnLhtEWP2rtb1sL' };
46-
4761
admin.validateKey(GOOD_XPUB, 'xpub').should.equal(true);
4862
});
4963
});
5064
});
5165

5266
describe('BIP32 child key derivation', function() {
67+
process.config.verificationPub = null;
5368
// from https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki test vector 2 chain m
5469
const MASTER_XPUB = 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB';
5570

@@ -80,6 +95,7 @@ describe('Offline Admin Tool', function() {
8095
});
8196

8297
describe('Stellar key derivation', function() {
98+
process.config.verificationPub = null;
8399
// from test 3 at https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md#test-cases
84100
const MASTER_SEED = '937ae91f6ab6f12461d9936dfc1375ea5312d097f3f1eb6fed6a82fbe38c85824da8704389831482db0433e5f6c6c9700ff1946aa75ad8cc2654d6e40f567866'
85101

@@ -120,6 +136,86 @@ describe('Offline Admin Tool', function() {
120136
})
121137
});
122138

139+
describe('Key signature verification', function() {
140+
141+
describe('failure', function() {
142+
it('should fail xpub validation with a bad signature', function() {
143+
process.config.verificationPub = testVerificationPub;
144+
const key = {
145+
pub: xpub,
146+
signature: badSig
147+
};
148+
const valid = admin.validateKey(key,'xpub');
149+
valid.should.equal(false);
150+
});
151+
152+
it('should fail xlm validation with a bad signature', function() {
153+
process.config.verificationPub = testVerificationPub;
154+
const key = {
155+
pub: xlmPub,
156+
signature: badSig
157+
};
158+
const valid = admin.validateKey(key,'xlm');
159+
valid.should.equal(false);
160+
});
161+
162+
it('should fail xpub validation with no signature', function() {
163+
process.config.verificationPub = testVerificationPub;
164+
const key = {
165+
pub: xpub
166+
};
167+
const valid = admin.validateKey(key,'xpub');
168+
valid.should.equal(false);
169+
});
170+
171+
it('should fail xlm validation with no signature', function() {
172+
process.config.verificationPub = testVerificationPub;
173+
const key = {
174+
pub: xlmPub
175+
};
176+
const valid = admin.validateKey(key,'xlm');
177+
valid.should.equal(false);
178+
});
179+
});
180+
181+
describe('success', function() {
182+
it('should validate xpub with a good signature', function() {
183+
process.config.verificationPub = testVerificationPub;
184+
const key = {
185+
pub: xpub,
186+
signature: xpubSig
187+
};
188+
const valid = admin.validateKey(key,'xpub');
189+
valid.should.equal(true);
190+
});
191+
192+
it('should validate xlm with a good signature', function() {
193+
process.config.verificationPub = testVerificationPub;
194+
const key = {
195+
pub: xlmPub,
196+
signature: xlmSig
197+
};
198+
const valid = admin.validateKey(key,'xlm');
199+
valid.should.equal(true);
200+
});
201+
});
202+
});
203+
204+
describe('Save a key with a signature', co(function *() {
205+
it('should successfully save a key with a signature to the database', co(function *() {
206+
process.config.verificationPub = testVerificationPub;
207+
const key = {
208+
pub: xpub,
209+
signature: xpubSig,
210+
path: '0'
211+
};
212+
const keyList = [key];
213+
yield admin.saveKeys(keyList, 'xpub');
214+
const foundKey = yield MasterKey.findOne({ pub: xpub });
215+
foundKey.should.have.property('signature');
216+
}));
217+
}));
218+
123219
describe('Verification', function() {
124220
before(function() {
125221
const key = new WalletKey({

0 commit comments

Comments
 (0)