Skip to content

Commit 0192e4f

Browse files
committed
Merge branch 'fix/unauthorized-wallet-join' of https://github.com/MichaelAJay/bitcore
2 parents 2561b0f + e221d45 commit 0192e4f

4 files changed

Lines changed: 656 additions & 17 deletions

File tree

packages/bitcore-wallet-service/src/lib/model/copayer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export class Copayer {
5454
isMarketingStaff?: boolean;
5555

5656
static xPubToCopayerId(coin, xpub) {
57+
$.checkArgument(xpub, 'Missing xPubKey for copayer id derivation');
5758
const str = coin == Defaults.COIN ? xpub : coin + xpub;
5859
const hash = sjcl.hash.sha256.hash(str);
5960
return sjcl.codec.hex.fromBits(hash);

packages/bitcore-wallet-service/src/lib/server.ts

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,9 +1146,13 @@ export class WalletService implements IWalletService {
11461146
if (err) return cb(err);
11471147
if (!wallet) return cb(Errors.NOT_AUTHORIZED);
11481148

1149-
const xPubKey = wallet.copayers.find(c => c.id === opts.copayerId).xPubKey;
1149+
const target = wallet.copayers.find(c => c.id === opts.copayerId);
1150+
if (!target?.xPubKey) return cb(Errors.NOT_AUTHORIZED);
11501151

1151-
if (!this._verifyRequestPubKey(opts.requestPubKey, opts.signature, xPubKey)) {
1152+
try {
1153+
const isValid = this._verifyRequestPubKey(opts.requestPubKey, opts.signature, target.xPubKey);
1154+
if (!isValid) return cb(Errors.NOT_AUTHORIZED);
1155+
} catch (e) {
11521156
return cb(Errors.NOT_AUTHORIZED);
11531157
}
11541158

@@ -1209,7 +1213,17 @@ export class WalletService implements IWalletService {
12091213
* @param {boolean} [opts.dryRun] Simulate the action but do not change server state.
12101214
*/
12111215
joinWallet(opts, cb) {
1212-
if (!checkRequired(opts, ['walletId', 'name', 'requestPubKey', 'copayerSignature'], cb)) return;
1216+
// xPubKey is the canonical copayer identity in the current protocol. Alternate public-key
1217+
// fields are ancillary metadata; supporting an xPubKey-less copayer requires a versioned
1218+
// end-to-end identity protocol rather than a local identity fallback.
1219+
// SECURITY: xPubKey is required for every join, including hardwareSourcePublicKey/
1220+
// clientDerivedPublicKey ones. Copayer.xPubToCopayerId() (called below both for the TSS
1221+
// participant check and inside Copayer.create()) now throws on a missing xpub for every
1222+
// coin (see its own guard), so an xPubKey-less join can no longer derive a copayer id at
1223+
// all. bitcore-wallet-client's only join path (_doJoinWallet) always sends a real
1224+
// xPubKey regardless of these two fields, so this doesn't restrict any flow this repo's
1225+
// own client exercises - see the TSS-participant comment below for how that was confirmed.
1226+
if (!checkRequired(opts, ['walletId', 'name', 'requestPubKey', 'copayerSignature', 'xPubKey'], cb)) return;
12131227
if (!opts.name) return cb(new ClientError('Invalid copayer name'));
12141228

12151229
opts.coin = opts.coin || Defaults.COIN;
@@ -1218,17 +1232,17 @@ export class WalletService implements IWalletService {
12181232
}
12191233
if (!Utils.checkValueInCollection(opts.chain, Constants.CHAINS)) return cb(new ClientError('Invalid coin'));
12201234

1235+
// xPubKey is parsed and validated for every join; ancillary fields never suppress it.
1236+
// A future update will re-add conditional logic which allows opts.hardwareSourcePublicKey
1237+
// or opts.clientDerivedPublicKey to circumvent this requirement
12211238
let xPubKey;
1222-
if (!opts.hardwareSourcePublicKey && !opts.clientDerivedPublicKey) {
1223-
if (!checkRequired(opts, ['xPubKey'], cb)) return;
1224-
try {
1225-
xPubKey = Bitcore_[opts.chain].HDPublicKey(opts.xPubKey);
1226-
} catch {
1227-
return cb(new ClientError('Invalid extended public key'));
1228-
}
1229-
if (xPubKey.network == null) {
1230-
return cb(new ClientError('Invalid extended public key'));
1231-
}
1239+
try {
1240+
xPubKey = Bitcore_[opts.chain].HDPublicKey(opts.xPubKey);
1241+
} catch {
1242+
return cb(new ClientError('Invalid extended public key'));
1243+
}
1244+
if (xPubKey.network == null) {
1245+
return cb(new ClientError('Invalid extended public key'));
12321246
}
12331247

12341248
this.walletId = opts.walletId;
@@ -1237,10 +1251,9 @@ export class WalletService implements IWalletService {
12371251
if (err) return cb(err);
12381252
if (!wallet) return cb(Errors.WALLET_NOT_FOUND);
12391253

1240-
if ((opts.hardwareSourcePublicKey || opts.clientDerivedPublicKey) && !opts.tssKeyId) {
1241-
this._addCopayerToWallet(wallet, opts, cb);
1242-
return;
1243-
}
1254+
// SECURITY: opts.hardwareSourcePublicKey or opts.clientDerivedPublicKey may not
1255+
// circumvent the required checks below. A future update may enable either to replace xPubKey
1256+
// That is currently not supported
12441257

12451258
if (this._upgradeNeeded(UPGRADES.BCH_bwc_$lt_8_3_multisig, { chain: opts.chain, n: wallet.n })) {
12461259
return cb(Errors.UPGRADE_NEEDED.withMessage('BWC clients < 8.3 are no longer supported for multisig BCH wallets.'));

0 commit comments

Comments
 (0)