Index: src/blob.c ================================================================== --- src/blob.c +++ src/blob.c @@ -311,10 +311,36 @@ sz = szA<szB ? szA : szB; rc = memcmp(blob_buffer(pA), blob_buffer(pB), sz); if( rc==0 ){ rc = szA - szB; } + return rc; +} + +/* +** Compare two blobs in constant time and return zero if they are equal. +** Constant time comparison only applies for blobs of the same length. +** If lengths are different, immediately returns 1. +*/ +int blob_constant_time_cmp(Blob *pA, Blob *pB){ + int szA, szB, i; + unsigned char *buf1, *buf2; + unsigned char rc = 0; + + blob_is_init(pA); + blob_is_init(pB); + szA = blob_size(pA); + szB = blob_size(pB); + if( szA!=szB || szA==0 ) return 1; + + buf1 = blob_buffer(pA); + buf2 = blob_buffer(pB); + + for( i=0; i<szA; i++ ){ + rc = rc | (buf1[i] ^ buf2[i]); + } + return rc; } /* ** Compare a blob to a string. Return TRUE if they are equal. Index: src/login.c ================================================================== --- src/login.c +++ src/login.c @@ -230,12 +230,14 @@ } if( g.perm.Password && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){ /* The user requests a password change */ zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0); if( db_int(1, "SELECT 0 FROM user" - " WHERE uid=%d AND (pw=%Q OR pw=%Q)", - g.userUid, zPasswd, zSha1Pw) ){ + " WHERE uid=%d" + " AND (constant_time_cmp(pw,%Q)=0" + " OR constant_time_cmp(pw,%Q)=0)", + g.userUid, zSha1Pw, zPasswd) ){ sleep(1); zErrMsg = @ <p><span class="loginError"> @ You entered an incorrect old password while attempting to change @ your password. Your password is unchanged. @@ -308,12 +310,12 @@ uid = db_int(0, "SELECT uid FROM user" " WHERE login=%Q" " AND length(cap)>0 AND length(pw)>0" " AND login NOT IN ('anonymous','nobody','developer','reader')" - " AND (pw=%Q OR pw=%Q)", - zUsername, zPasswd, zSha1Pw + " AND (constant_time_cmp(pw,%Q)=0 OR constant_time_cmp(pw,%Q)=0)", + zUsername, zSha1Pw, zPasswd ); if( uid<=0 ){ sleep(1); zErrMsg = @ <p><span class="loginError"> @@ -451,10 +453,37 @@ @ </table> @ </form> } style_footer(); } + +/* +** SQL function for constant time comparison of two values. +** Sets result to 0 if two values are equal. +*/ +static void constant_time_cmp_function( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *buf1, *buf2; + int len, i; + unsigned char rc = 0; + + assert( argc==2 ); + len = sqlite3_value_bytes(argv[0]); + if( len==0 || len!=sqlite3_value_bytes(argv[1]) ){ + rc = 1; + }else{ + buf1 = sqlite3_value_text(argv[0]); + buf2 = sqlite3_value_text(argv[1]); + for( i=0; i<len; i++ ){ + rc = rc | (buf1[i] ^ buf2[i]); + } + } + sqlite3_result_int(context, rc); +} /* ** Attempt to find login credentials for user zLogin on a peer repository ** with project code zCode. Transfer those credentials to the local ** repository. @@ -481,20 +510,22 @@ if( zOtherRepo==0 ) return 0; /* No such peer repository */ rc = sqlite3_open(zOtherRepo, &pOther); if( rc==SQLITE_OK ){ sqlite3_create_function(pOther,"now",0,SQLITE_ANY,0,db_now_function,0,0); + sqlite3_create_function(pOther, "constant_time_cmp", 2, SQLITE_UTF8, 0, + constant_time_cmp_function, 0, 0); sqlite3_busy_timeout(pOther, 5000); zSQL = mprintf( "SELECT cexpire FROM user" - " WHERE cookie=%Q" + " WHERE login=%Q" " AND ipaddr=%Q" - " AND login=%Q" " AND length(cap)>0" " AND length(pw)>0" - " AND cexpire>julianday('now')", - zHash, zRemoteAddr, zLogin + " AND cexpire>julianday('now')" + " AND constant_time_cmp(cookie,%Q)=0", + zLogin, zRemoteAddr, zHash ); pStmt = 0; rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0); if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ db_multi_exec( @@ -527,16 +558,16 @@ if( fossil_strcmp(zLogin, "developer")==0 ) return 0; if( fossil_strcmp(zLogin, "reader")==0 ) return 0; uid = db_int(0, "SELECT uid FROM user" " WHERE login=%Q" - " AND cookie=%Q" " AND ipaddr=%Q" " AND cexpire>julianday('now')" " AND length(cap)>0" - " AND length(pw)>0", - zLogin, zCookie, zRemoteAddr + " AND length(pw)>0" + " AND constant_time_cmp(cookie,%Q)=0", + zLogin, zRemoteAddr, zCookie ); return uid; } /* @@ -554,10 +585,13 @@ char *zRemoteAddr; /* Abbreviated IP address of the requestor */ const char *zCap = 0; /* Capability string */ /* Only run this check once. */ if( g.userUid!=0 ) return; + + sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0, + constant_time_cmp_function, 0, 0); /* If the HTTP connection is coming over 127.0.0.1 and if ** local login is disabled and if we are using HTTP and not HTTPS, ** then there is no need to check user credentials. ** Index: src/xfer.c ================================================================== --- src/xfer.c +++ src/xfer.c @@ -573,11 +573,11 @@ blob_zero(&combined); blob_copy(&combined, pNonce); blob_append(&combined, blob_buffer(&pw), szPw); sha1sum_blob(&combined, &hash); assert( blob_size(&hash)==40 ); - rc = blob_compare(&hash, pSig); + rc = blob_constant_time_cmp(&hash, pSig); blob_reset(&hash); blob_reset(&combined); if( rc!=0 && szPw!=40 ){ /* If this server stores cleartext passwords and the password did not ** match, then perhaps the client is sending SHA1 passwords. Try @@ -588,11 +588,11 @@ blob_zero(&combined); blob_copy(&combined, pNonce); blob_append(&combined, zSecret, -1); free(zSecret); sha1sum_blob(&combined, &hash); - rc = blob_compare(&hash, pSig); + rc = blob_constant_time_cmp(&hash, pSig); blob_reset(&hash); blob_reset(&combined); } if( rc==0 ){ const char *zCap;