Skip to content

Commit

Permalink
Changing password require oldHash
Browse files Browse the repository at this point in the history
  • Loading branch information
agix committed Dec 24, 2022
1 parent 920dad1 commit 6cd3136
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 39 deletions.
1 change: 0 additions & 1 deletion src/routes/ActivateTotp.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export default ({ couchdb, redis }) => {
};
doc.user[req.params.name].pass.totp = true;
doc.user[req.params.name].seed = jsonBody.seed;
doc.user[req.params.name].rescueCodes = Utils.generateRescueCodes();
return couchdb.update(couchdb.databaseName, doc);
})
.then(() => {
Expand Down
34 changes: 34 additions & 0 deletions src/routes/GetRescueCodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,39 @@ export default ({ couchdb, redis }) => {
});
});

route.put('/:name', (req, res) => {
Utils.checkSignature({
couchdb,
redis,
name: req.params.name,
sig: req.body.sig,
data: `${req.body.json}|${req.body.sigTime}`,
})
.then(rawUser => {
const jsonBody = JSON.parse(req.body.json);
const user = rawUser.data;

if (user.pass.totp) {
const doc = {
_id: rawUser.id,
_rev: rawUser.rev,
user: {
[req.params.name]: rawUser.data,
},
};

doc.user[req.params.name].rescueCodes = jsonBody.rescueCodes;
return couchdb.update(couchdb.databaseName, doc);
}
return Promise.resolve();
})
.then(() => {
Utils.reason(res, 200, 'RescueCodes saved');
})
.catch(error => {
Console.error(res, error);
});
});

return route;
};
50 changes: 30 additions & 20 deletions src/routes/GetUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,37 +55,47 @@ export default ({ redis, couchdb }) => {
totpValid = false;
const protectedSeed = Utils.hexStringToUint8Array(submitUser.seed);
const hash = Utils.hexStringToUint8Array(req.params.hash);
const seed = Utils.bytesToHexString(
Utils.xorSeed(hash, protectedSeed)
);
const seed = Utils.xorSeed(protectedSeed, hash)
totpValid = speakeasy.totp.verify({
secret: seed,
encoding: 'hex',
token: req.query.otp,
});
if (
!totpValid &&
typeof submitUser.rescueCodes !== 'undefined' &&
submitUser.rescueCodes.shift() === parseInt(req.query.otp, 10)
typeof submitUser.rescueCodes !== 'undefined'
) {
totpValid = true;
const doc = {
_id: rawUser.id,
_rev: rawUser.rev,
user: {
[req.params.name]: rawUser.data,
},
};
const protectedNextRescueCode = submitUser.rescueCodes.shift()
// New rescue code are more robust
if(protectedNextRescueCode.length === 8) {
const nextRescueCode = Utils.xorRescueCode(Utils.hexStringToUint8Array(protectedNextRescueCode), hash)
if(compare(nextRescueCode, req.query.otp)) {
totpValid = true
}
// Legacy check with weak rescue code
} else if (protectedNextRescueCode === parseInt(req.query.otp, 10)){
totpValid = true;
}

if(totpValid) {
const doc = {
_id: rawUser.id,
_rev: rawUser.rev,
user: {
[req.params.name]: rawUser.data,
},
};

doc.user[req.params.name].rescueCodes = submitUser.rescueCodes;
doc.user[req.params.name].rescueCodes = submitUser.rescueCodes;

if (submitUser.rescueCodes.length === 0) {
submitUser.pass.totp = false;
doc.user[req.params.name].pass.totp = false;
delete doc.user[req.params.name].seed;
delete doc.user[req.params.name].rescueCodes;
if (submitUser.rescueCodes.length === 0) {
submitUser.pass.totp = false;
doc.user[req.params.name].pass.totp = false;
delete doc.user[req.params.name].seed;
delete doc.user[req.params.name].rescueCodes;
}
return couchdb.update(couchdb.databaseName, doc);
}
return couchdb.update(couchdb.databaseName, doc);
}
}
return Promise.resolve();
Expand Down
64 changes: 49 additions & 15 deletions src/routes/UpdateUser.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import { Router } from 'express';
import {
Router
} from 'express';
import forge from 'node-forge';
import compare from 'secure-compare';

import Console from '../console';
import Utils from '../utils';

export default ({ couchdb, redis }) => {
export default ({
couchdb,
redis
}) => {
const route = Router();
route.put('/:name', (req, res) => {
let jsonBody;
Utils.checkSignature({
couchdb,
redis,
name: req.params.name,
sig: req.body.sig,
data: `${req.body.json}|${req.body.sigTime}`,
})
couchdb,
redis,
name: req.params.name,
sig: req.body.sig,
data: `${req.body.json}|${req.body.sigTime}`,
})
.then(rawUser => {
jsonBody = JSON.parse(req.body.json);
const doc = {
Expand All @@ -33,16 +39,44 @@ export default ({ couchdb, redis }) => {
} else {
doc.user[req.params.name].options = jsonBody;
}
} else {
const md = forge.md.sha256.create();
md.update(jsonBody.pass.hash);
jsonBody.pass.hash = md.digest().toHex();
return Promise.resolve(doc)
}

doc.user[req.params.name].privateKey = jsonBody.privateKey;
doc.user[req.params.name].pass = jsonBody.pass;
let ip;
if (
process.env.BEHIND_REVERSE_PROXY &&
process.env.BEHIND_REVERSE_PROXY === '1'
) {
ip = req.headers['x-forwarded-for'] || req.ip;
} else {
ip = req.ip;
}
return couchdb.update(couchdb.databaseName, doc);

return Utils.checkBruteforce({
redis,
ip
})
.then((isBruteforce) => {
if (!isBruteforce) {
// Changing password
const mdOldHash = forge.md.sha256.create();
mdOldHash.update(jsonBody.oldHash);
const validHash = compare(mdOldHash.digest().toHex(), doc.user[req.params.name].pass.hash);
if (!validHash) {
throw new Error('Invalid old password')
}

const md = forge.md.sha256.create();
md.update(jsonBody.pass.hash);
jsonBody.pass.hash = md.digest().toHex();

doc.user[req.params.name].privateKey = jsonBody.privateKey;
doc.user[req.params.name].pass = jsonBody.pass;
}
return doc
})
})
.then((doc) => couchdb.update(couchdb.databaseName, doc))
.then(() => {
Utils.reason(res, 200, 'User updated');
})
Expand Down
22 changes: 19 additions & 3 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,24 @@ function xorSeed(byteArray1, byteArray2) {
for (i = 0; i < 32; i += 1) {
buf[i] = byteArray1[i] ^ byteArray2[i];
}
return buf;
return bytesToHexString(buf);
}
throw 'xorSeed wait for 32 bytes arrays';
throw new Error('xorSeed wait for 32 bytes arrays');
}

function xorRescueCode(rescueCode, hash) {
if (
hash.length === 32 &&
rescueCode.length === 4
) {
const buf = new Uint8Array(rescueCode.length);
let i;
for (i = 0; i < rescueCode.length; i += 1) {
buf[i] = rescueCode[i] ^ hash[i];
}
return bytesToHexString(buf);
}
throw new Error('xorRescueCode wrong bytes arrays');
}

function checkSignature({ couchdb, redis, name, sig, data }) {
Expand Down Expand Up @@ -214,13 +229,14 @@ function generateRescueCodes() {
const Utils = {
userExists,
reason,
generateRescueCodes,
checkBruteforce,
hexStringToUint8Array,
bytesToHexString,
xorSeed,
checkSignature,
secretExists,
generateRescueCodes,
xorRescueCode,
};

export default Utils;

0 comments on commit 6cd3136

Please sign in to comment.