Fossil SCM

Merge protection against timing attacks into trunk.

dmitry 2011-10-04 15:15 trunk merge
Commit d4a341b49dd1b7017b8433224efdcb4ebe3a3369
3 files changed +26 +45 -11 +2 -2
+26
--- src/blob.c
+++ src/blob.c
@@ -311,10 +311,36 @@
311311
sz = szA<szB ? szA : szB;
312312
rc = memcmp(blob_buffer(pA), blob_buffer(pB), sz);
313313
if( rc==0 ){
314314
rc = szA - szB;
315315
}
316
+ return rc;
317
+}
318
+
319
+/*
320
+** Compare two blobs in constant time and return zero if they are equal.
321
+** Constant time comparison only applies for blobs of the same length.
322
+** If lengths are different, immediately returns 1.
323
+*/
324
+int blob_constant_time_cmp(Blob *pA, Blob *pB){
325
+ int szA, szB, i;
326
+ unsigned char *buf1, *buf2;
327
+ unsigned char rc = 0;
328
+
329
+ blob_is_init(pA);
330
+ blob_is_init(pB);
331
+ szA = blob_size(pA);
332
+ szB = blob_size(pB);
333
+ if( szA!=szB || szA==0 ) return 1;
334
+
335
+ buf1 = blob_buffer(pA);
336
+ buf2 = blob_buffer(pB);
337
+
338
+ for( i=0; i<szA; i++ ){
339
+ rc = rc | (buf1[i] ^ buf2[i]);
340
+ }
341
+
316342
return rc;
317343
}
318344
319345
/*
320346
** Compare a blob to a string. Return TRUE if they are equal.
321347
--- src/blob.c
+++ src/blob.c
@@ -311,10 +311,36 @@
311 sz = szA<szB ? szA : szB;
312 rc = memcmp(blob_buffer(pA), blob_buffer(pB), sz);
313 if( rc==0 ){
314 rc = szA - szB;
315 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316 return rc;
317 }
318
319 /*
320 ** Compare a blob to a string. Return TRUE if they are equal.
321
--- src/blob.c
+++ src/blob.c
@@ -311,10 +311,36 @@
311 sz = szA<szB ? szA : szB;
312 rc = memcmp(blob_buffer(pA), blob_buffer(pB), sz);
313 if( rc==0 ){
314 rc = szA - szB;
315 }
316 return rc;
317 }
318
319 /*
320 ** Compare two blobs in constant time and return zero if they are equal.
321 ** Constant time comparison only applies for blobs of the same length.
322 ** If lengths are different, immediately returns 1.
323 */
324 int blob_constant_time_cmp(Blob *pA, Blob *pB){
325 int szA, szB, i;
326 unsigned char *buf1, *buf2;
327 unsigned char rc = 0;
328
329 blob_is_init(pA);
330 blob_is_init(pB);
331 szA = blob_size(pA);
332 szB = blob_size(pB);
333 if( szA!=szB || szA==0 ) return 1;
334
335 buf1 = blob_buffer(pA);
336 buf2 = blob_buffer(pB);
337
338 for( i=0; i<szA; i++ ){
339 rc = rc | (buf1[i] ^ buf2[i]);
340 }
341
342 return rc;
343 }
344
345 /*
346 ** Compare a blob to a string. Return TRUE if they are equal.
347
+45 -11
--- src/login.c
+++ src/login.c
@@ -230,12 +230,14 @@
230230
}
231231
if( g.perm.Password && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){
232232
/* The user requests a password change */
233233
zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
234234
if( db_int(1, "SELECT 0 FROM user"
235
- " WHERE uid=%d AND (pw=%Q OR pw=%Q)",
236
- g.userUid, zPasswd, zSha1Pw) ){
235
+ " WHERE uid=%d"
236
+ " AND (constant_time_cmp(pw,%Q)=0"
237
+ " OR constant_time_cmp(pw,%Q)=0)",
238
+ g.userUid, zSha1Pw, zPasswd) ){
237239
sleep(1);
238240
zErrMsg =
239241
@ <p><span class="loginError">
240242
@ You entered an incorrect old password while attempting to change
241243
@ your password. Your password is unchanged.
@@ -308,12 +310,12 @@
308310
uid = db_int(0,
309311
"SELECT uid FROM user"
310312
" WHERE login=%Q"
311313
" AND length(cap)>0 AND length(pw)>0"
312314
" AND login NOT IN ('anonymous','nobody','developer','reader')"
313
- " AND (pw=%Q OR pw=%Q)",
314
- zUsername, zPasswd, zSha1Pw
315
+ " AND (constant_time_cmp(pw,%Q)=0 OR constant_time_cmp(pw,%Q)=0)",
316
+ zUsername, zSha1Pw, zPasswd
315317
);
316318
if( uid<=0 ){
317319
sleep(1);
318320
zErrMsg =
319321
@ <p><span class="loginError">
@@ -451,10 +453,37 @@
451453
@ </table>
452454
@ </form>
453455
}
454456
style_footer();
455457
}
458
+
459
+/*
460
+** SQL function for constant time comparison of two values.
461
+** Sets result to 0 if two values are equal.
462
+*/
463
+static void constant_time_cmp_function(
464
+ sqlite3_context *context,
465
+ int argc,
466
+ sqlite3_value **argv
467
+){
468
+ const unsigned char *buf1, *buf2;
469
+ int len, i;
470
+ unsigned char rc = 0;
471
+
472
+ assert( argc==2 );
473
+ len = sqlite3_value_bytes(argv[0]);
474
+ if( len==0 || len!=sqlite3_value_bytes(argv[1]) ){
475
+ rc = 1;
476
+ }else{
477
+ buf1 = sqlite3_value_text(argv[0]);
478
+ buf2 = sqlite3_value_text(argv[1]);
479
+ for( i=0; i<len; i++ ){
480
+ rc = rc | (buf1[i] ^ buf2[i]);
481
+ }
482
+ }
483
+ sqlite3_result_int(context, rc);
484
+}
456485
457486
/*
458487
** Attempt to find login credentials for user zLogin on a peer repository
459488
** with project code zCode. Transfer those credentials to the local
460489
** repository.
@@ -481,20 +510,22 @@
481510
if( zOtherRepo==0 ) return 0; /* No such peer repository */
482511
483512
rc = sqlite3_open(zOtherRepo, &pOther);
484513
if( rc==SQLITE_OK ){
485514
sqlite3_create_function(pOther,"now",0,SQLITE_ANY,0,db_now_function,0,0);
515
+ sqlite3_create_function(pOther, "constant_time_cmp", 2, SQLITE_UTF8, 0,
516
+ constant_time_cmp_function, 0, 0);
486517
sqlite3_busy_timeout(pOther, 5000);
487518
zSQL = mprintf(
488519
"SELECT cexpire FROM user"
489
- " WHERE cookie=%Q"
520
+ " WHERE login=%Q"
490521
" AND ipaddr=%Q"
491
- " AND login=%Q"
492522
" AND length(cap)>0"
493523
" AND length(pw)>0"
494
- " AND cexpire>julianday('now')",
495
- zHash, zRemoteAddr, zLogin
524
+ " AND cexpire>julianday('now')"
525
+ " AND constant_time_cmp(cookie,%Q)=0",
526
+ zLogin, zRemoteAddr, zHash
496527
);
497528
pStmt = 0;
498529
rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
499530
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
500531
db_multi_exec(
@@ -527,16 +558,16 @@
527558
if( fossil_strcmp(zLogin, "developer")==0 ) return 0;
528559
if( fossil_strcmp(zLogin, "reader")==0 ) return 0;
529560
uid = db_int(0,
530561
"SELECT uid FROM user"
531562
" WHERE login=%Q"
532
- " AND cookie=%Q"
533563
" AND ipaddr=%Q"
534564
" AND cexpire>julianday('now')"
535565
" AND length(cap)>0"
536
- " AND length(pw)>0",
537
- zLogin, zCookie, zRemoteAddr
566
+ " AND length(pw)>0"
567
+ " AND constant_time_cmp(cookie,%Q)=0",
568
+ zLogin, zRemoteAddr, zCookie
538569
);
539570
return uid;
540571
}
541572
542573
/*
@@ -554,10 +585,13 @@
554585
char *zRemoteAddr; /* Abbreviated IP address of the requestor */
555586
const char *zCap = 0; /* Capability string */
556587
557588
/* Only run this check once. */
558589
if( g.userUid!=0 ) return;
590
+
591
+ sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
592
+ constant_time_cmp_function, 0, 0);
559593
560594
/* If the HTTP connection is coming over 127.0.0.1 and if
561595
** local login is disabled and if we are using HTTP and not HTTPS,
562596
** then there is no need to check user credentials.
563597
**
564598
--- src/login.c
+++ src/login.c
@@ -230,12 +230,14 @@
230 }
231 if( g.perm.Password && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){
232 /* The user requests a password change */
233 zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
234 if( db_int(1, "SELECT 0 FROM user"
235 " WHERE uid=%d AND (pw=%Q OR pw=%Q)",
236 g.userUid, zPasswd, zSha1Pw) ){
 
 
237 sleep(1);
238 zErrMsg =
239 @ <p><span class="loginError">
240 @ You entered an incorrect old password while attempting to change
241 @ your password. Your password is unchanged.
@@ -308,12 +310,12 @@
308 uid = db_int(0,
309 "SELECT uid FROM user"
310 " WHERE login=%Q"
311 " AND length(cap)>0 AND length(pw)>0"
312 " AND login NOT IN ('anonymous','nobody','developer','reader')"
313 " AND (pw=%Q OR pw=%Q)",
314 zUsername, zPasswd, zSha1Pw
315 );
316 if( uid<=0 ){
317 sleep(1);
318 zErrMsg =
319 @ <p><span class="loginError">
@@ -451,10 +453,37 @@
451 @ </table>
452 @ </form>
453 }
454 style_footer();
455 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
457 /*
458 ** Attempt to find login credentials for user zLogin on a peer repository
459 ** with project code zCode. Transfer those credentials to the local
460 ** repository.
@@ -481,20 +510,22 @@
481 if( zOtherRepo==0 ) return 0; /* No such peer repository */
482
483 rc = sqlite3_open(zOtherRepo, &pOther);
484 if( rc==SQLITE_OK ){
485 sqlite3_create_function(pOther,"now",0,SQLITE_ANY,0,db_now_function,0,0);
 
 
486 sqlite3_busy_timeout(pOther, 5000);
487 zSQL = mprintf(
488 "SELECT cexpire FROM user"
489 " WHERE cookie=%Q"
490 " AND ipaddr=%Q"
491 " AND login=%Q"
492 " AND length(cap)>0"
493 " AND length(pw)>0"
494 " AND cexpire>julianday('now')",
495 zHash, zRemoteAddr, zLogin
 
496 );
497 pStmt = 0;
498 rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
499 if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
500 db_multi_exec(
@@ -527,16 +558,16 @@
527 if( fossil_strcmp(zLogin, "developer")==0 ) return 0;
528 if( fossil_strcmp(zLogin, "reader")==0 ) return 0;
529 uid = db_int(0,
530 "SELECT uid FROM user"
531 " WHERE login=%Q"
532 " AND cookie=%Q"
533 " AND ipaddr=%Q"
534 " AND cexpire>julianday('now')"
535 " AND length(cap)>0"
536 " AND length(pw)>0",
537 zLogin, zCookie, zRemoteAddr
 
538 );
539 return uid;
540 }
541
542 /*
@@ -554,10 +585,13 @@
554 char *zRemoteAddr; /* Abbreviated IP address of the requestor */
555 const char *zCap = 0; /* Capability string */
556
557 /* Only run this check once. */
558 if( g.userUid!=0 ) return;
 
 
 
559
560 /* If the HTTP connection is coming over 127.0.0.1 and if
561 ** local login is disabled and if we are using HTTP and not HTTPS,
562 ** then there is no need to check user credentials.
563 **
564
--- src/login.c
+++ src/login.c
@@ -230,12 +230,14 @@
230 }
231 if( g.perm.Password && zPasswd && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 ){
232 /* The user requests a password change */
233 zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
234 if( db_int(1, "SELECT 0 FROM user"
235 " WHERE uid=%d"
236 " AND (constant_time_cmp(pw,%Q)=0"
237 " OR constant_time_cmp(pw,%Q)=0)",
238 g.userUid, zSha1Pw, zPasswd) ){
239 sleep(1);
240 zErrMsg =
241 @ <p><span class="loginError">
242 @ You entered an incorrect old password while attempting to change
243 @ your password. Your password is unchanged.
@@ -308,12 +310,12 @@
310 uid = db_int(0,
311 "SELECT uid FROM user"
312 " WHERE login=%Q"
313 " AND length(cap)>0 AND length(pw)>0"
314 " AND login NOT IN ('anonymous','nobody','developer','reader')"
315 " AND (constant_time_cmp(pw,%Q)=0 OR constant_time_cmp(pw,%Q)=0)",
316 zUsername, zSha1Pw, zPasswd
317 );
318 if( uid<=0 ){
319 sleep(1);
320 zErrMsg =
321 @ <p><span class="loginError">
@@ -451,10 +453,37 @@
453 @ </table>
454 @ </form>
455 }
456 style_footer();
457 }
458
459 /*
460 ** SQL function for constant time comparison of two values.
461 ** Sets result to 0 if two values are equal.
462 */
463 static void constant_time_cmp_function(
464 sqlite3_context *context,
465 int argc,
466 sqlite3_value **argv
467 ){
468 const unsigned char *buf1, *buf2;
469 int len, i;
470 unsigned char rc = 0;
471
472 assert( argc==2 );
473 len = sqlite3_value_bytes(argv[0]);
474 if( len==0 || len!=sqlite3_value_bytes(argv[1]) ){
475 rc = 1;
476 }else{
477 buf1 = sqlite3_value_text(argv[0]);
478 buf2 = sqlite3_value_text(argv[1]);
479 for( i=0; i<len; i++ ){
480 rc = rc | (buf1[i] ^ buf2[i]);
481 }
482 }
483 sqlite3_result_int(context, rc);
484 }
485
486 /*
487 ** Attempt to find login credentials for user zLogin on a peer repository
488 ** with project code zCode. Transfer those credentials to the local
489 ** repository.
@@ -481,20 +510,22 @@
510 if( zOtherRepo==0 ) return 0; /* No such peer repository */
511
512 rc = sqlite3_open(zOtherRepo, &pOther);
513 if( rc==SQLITE_OK ){
514 sqlite3_create_function(pOther,"now",0,SQLITE_ANY,0,db_now_function,0,0);
515 sqlite3_create_function(pOther, "constant_time_cmp", 2, SQLITE_UTF8, 0,
516 constant_time_cmp_function, 0, 0);
517 sqlite3_busy_timeout(pOther, 5000);
518 zSQL = mprintf(
519 "SELECT cexpire FROM user"
520 " WHERE login=%Q"
521 " AND ipaddr=%Q"
 
522 " AND length(cap)>0"
523 " AND length(pw)>0"
524 " AND cexpire>julianday('now')"
525 " AND constant_time_cmp(cookie,%Q)=0",
526 zLogin, zRemoteAddr, zHash
527 );
528 pStmt = 0;
529 rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
530 if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
531 db_multi_exec(
@@ -527,16 +558,16 @@
558 if( fossil_strcmp(zLogin, "developer")==0 ) return 0;
559 if( fossil_strcmp(zLogin, "reader")==0 ) return 0;
560 uid = db_int(0,
561 "SELECT uid FROM user"
562 " WHERE login=%Q"
 
563 " AND ipaddr=%Q"
564 " AND cexpire>julianday('now')"
565 " AND length(cap)>0"
566 " AND length(pw)>0"
567 " AND constant_time_cmp(cookie,%Q)=0",
568 zLogin, zRemoteAddr, zCookie
569 );
570 return uid;
571 }
572
573 /*
@@ -554,10 +585,13 @@
585 char *zRemoteAddr; /* Abbreviated IP address of the requestor */
586 const char *zCap = 0; /* Capability string */
587
588 /* Only run this check once. */
589 if( g.userUid!=0 ) return;
590
591 sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
592 constant_time_cmp_function, 0, 0);
593
594 /* If the HTTP connection is coming over 127.0.0.1 and if
595 ** local login is disabled and if we are using HTTP and not HTTPS,
596 ** then there is no need to check user credentials.
597 **
598
+2 -2
--- src/xfer.c
+++ src/xfer.c
@@ -573,11 +573,11 @@
573573
blob_zero(&combined);
574574
blob_copy(&combined, pNonce);
575575
blob_append(&combined, blob_buffer(&pw), szPw);
576576
sha1sum_blob(&combined, &hash);
577577
assert( blob_size(&hash)==40 );
578
- rc = blob_compare(&hash, pSig);
578
+ rc = blob_constant_time_cmp(&hash, pSig);
579579
blob_reset(&hash);
580580
blob_reset(&combined);
581581
if( rc!=0 && szPw!=40 ){
582582
/* If this server stores cleartext passwords and the password did not
583583
** match, then perhaps the client is sending SHA1 passwords. Try
@@ -588,11 +588,11 @@
588588
blob_zero(&combined);
589589
blob_copy(&combined, pNonce);
590590
blob_append(&combined, zSecret, -1);
591591
free(zSecret);
592592
sha1sum_blob(&combined, &hash);
593
- rc = blob_compare(&hash, pSig);
593
+ rc = blob_constant_time_cmp(&hash, pSig);
594594
blob_reset(&hash);
595595
blob_reset(&combined);
596596
}
597597
if( rc==0 ){
598598
const char *zCap;
599599
--- src/xfer.c
+++ src/xfer.c
@@ -573,11 +573,11 @@
573 blob_zero(&combined);
574 blob_copy(&combined, pNonce);
575 blob_append(&combined, blob_buffer(&pw), szPw);
576 sha1sum_blob(&combined, &hash);
577 assert( blob_size(&hash)==40 );
578 rc = blob_compare(&hash, pSig);
579 blob_reset(&hash);
580 blob_reset(&combined);
581 if( rc!=0 && szPw!=40 ){
582 /* If this server stores cleartext passwords and the password did not
583 ** match, then perhaps the client is sending SHA1 passwords. Try
@@ -588,11 +588,11 @@
588 blob_zero(&combined);
589 blob_copy(&combined, pNonce);
590 blob_append(&combined, zSecret, -1);
591 free(zSecret);
592 sha1sum_blob(&combined, &hash);
593 rc = blob_compare(&hash, pSig);
594 blob_reset(&hash);
595 blob_reset(&combined);
596 }
597 if( rc==0 ){
598 const char *zCap;
599
--- src/xfer.c
+++ src/xfer.c
@@ -573,11 +573,11 @@
573 blob_zero(&combined);
574 blob_copy(&combined, pNonce);
575 blob_append(&combined, blob_buffer(&pw), szPw);
576 sha1sum_blob(&combined, &hash);
577 assert( blob_size(&hash)==40 );
578 rc = blob_constant_time_cmp(&hash, pSig);
579 blob_reset(&hash);
580 blob_reset(&combined);
581 if( rc!=0 && szPw!=40 ){
582 /* If this server stores cleartext passwords and the password did not
583 ** match, then perhaps the client is sending SHA1 passwords. Try
@@ -588,11 +588,11 @@
588 blob_zero(&combined);
589 blob_copy(&combined, pNonce);
590 blob_append(&combined, zSecret, -1);
591 free(zSecret);
592 sha1sum_blob(&combined, &hash);
593 rc = blob_constant_time_cmp(&hash, pSig);
594 blob_reset(&hash);
595 blob_reset(&combined);
596 }
597 if( rc==0 ){
598 const char *zCap;
599

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button