Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions node/db/Pad.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,9 +489,13 @@ Class('Pad', {
this.passwordHash = password == null ? null : hash(password, generateSalt());
db.setSub("pad:"+this.id, ["passwordHash"], this.passwordHash);
},
getPasswordSalt: function()
{
return this.passwordHash.split("$")[1];
},
isCorrectPassword: function(password)
{
return compare(this.passwordHash, password)
return timeSensitiveCompare(this.passwordHash, password)
},
isPasswordProtected: function()
{
Expand All @@ -512,17 +516,21 @@ function hash(password, salt)
function generateSalt()
{
var len = 86;
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./";
var charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./";
var randomstring = '';
for (var i = 0; i < len; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
var rnum = Math.floor(Math.random() * charset.length);
randomstring += charset[rnum];
}
return randomstring;
}

function compare(hashStr, password)
/* Compare the timed password hash with the saved value.
* If the hash was generated too far in the past, it is rejected. */
function timeSensitiveCompare(hashStr, password)
{
return hash(password, hashStr.split("$")[1]) === hashStr;
var timestamp = password.split("$")[1];
return password === hash(hashStr, timestamp)
&& timestamp > new Date().getTime();
}
9 changes: 7 additions & 2 deletions node/db/SecurityManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
var groupID = padID.split("$")[0];
var padExists = false;
var validSession = false;
var pwsalt;
var sessionAuthor;
var tokenAuthor;
var isPublic;
Expand Down Expand Up @@ -131,6 +132,9 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)

//is it password protected?
isPasswordProtected = pad.isPasswordProtected();

//get the password salt used by the hash function
pwsalt = pad.getPasswordSalt();

//is password correct?
if(isPasswordProtected && password && pad.isCorrectPassword(password))
Expand Down Expand Up @@ -162,13 +166,14 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
else if(isPasswordProtected && passwordStatus == "wrong")
{
//--> deny access, ask for new password and tell them that the password is wrong
statusObject = {accessStatus: "wrongPassword"};
//The salt can be safely shared since it is not secret. It does its job (improving resistence against rainbow table attacks) even when public.
statusObject = {accessStatus: "wrongPassword", passwordSalt: pwsalt};
}
//- the pad is password protected but no password given
else if(isPasswordProtected && passwordStatus == "notGiven")
{
//--> ask for password
statusObject = {accessStatus: "needPassword"};
statusObject = {accessStatus: "needPassword", passwordSalt: pwsalt};
}
else
{
Expand Down
24 changes: 18 additions & 6 deletions static/js/pad2.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function randomString()
for (var i = 0; i < string_length; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
randomstring += chars[rnum];
}
return "t." + randomstring;
}
Expand Down Expand Up @@ -147,10 +147,22 @@ function getUrlVars()
return vars;
}

function savePassword()
function hash(password, salt)
{
return sha512(password + salt) + "$" + salt;
}

/* Generate the "timed hash" used to get access.
* The password is hashed with the database's salt, afterwards it is hashed again with a timestamp a few days in the future as "salt".
* The server checks the two hashe's equality as usual, but also checks whether this timestamp is still in the future (grant access)
* or if it has passed (deny access). This provides an saved-password expiry mechanism which is a) independent of the browser's cookie
* retention and b) provides some level of security against "cookie stealing" (be it by xss or otherwise): If Eve steals a cookie, she
* does "only" get a timed-hash lifetime access to the pad, but *not* the actual password.
*/
function savePassword(pwsalt)
{
//set the password cookie
createCookie("password",$("#passwordinput").val(),null,document.location.pathname);
createCookie("password",hash(hash($("#passwordinput").val(), pwsalt), new Date().getTime() + 14 * 24 * 3600 * 1000),null,document.location.pathname); //FIXME some means of configuring this threshold would be really great
//reload
document.location=document.location;
}
Expand Down Expand Up @@ -214,13 +226,13 @@ function handshake()
{
$("#editorloadingbox").html("<b>You need a password to access this pad</b><br>" +
"<input id='passwordinput' type='password' name='password'>"+
"<button type='button' onclick='savePassword()'>ok</button>");
"<button type='button' onclick='savePassword("+obj.passwordSalt+")'>ok</button>");
}
else if(obj.accessStatus == "wrongPassword")
{
$("#editorloadingbox").html("<b>You're password was wrong</b><br>" +
$("#editorloadingbox").html("<b>Your password was wrong</b><br>" +
"<input id='passwordinput' type='password' name='password'>"+
"<button type='button' onclick='savePassword()'>ok</button>");
"<button type='button' onclick='savePassword("+obj.passwordSalt+")'>ok</button>");
}
}

Expand Down