Fossil SCM
Security enhancement: Do not store the passwords for remote URLs directly, but instead store the sha1_shared_secret() encoding of those passwords. It is the SHA1 encoding that gets transmitted to the server anyhow, so we might as well just store that. The SHA1 encoding cannot be used to log in. The password is still protected using obscure() even though it is now a SHA1 hash.
Commit
41ba6ea7db6ce2ce159709ce9b95dee44f9f5f03a13f2f741f5a6ebc96c5f034
Parent
6d0083adce5597e…
5 files changed
+6
-1
+25
-4
+2
+16
-9
+3
-1
M
src/db.c
+6
-1
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -1395,11 +1395,16 @@ | ||
| 1395 | 1395 | if( 0==zIn ) return; |
| 1396 | 1396 | if( 0==(zOut = sqlite3_malloc64( nIn * 2 + 3 )) ){ |
| 1397 | 1397 | sqlite3_result_error_nomem(context); |
| 1398 | 1398 | return; |
| 1399 | 1399 | } |
| 1400 | - strcpy(zOut, zTemp = obscure((char*)zIn)); | |
| 1400 | + if( sqlite3_user_data(context)==0 ){ | |
| 1401 | + zTemp = obscure((char*)zIn); | |
| 1402 | + }else{ | |
| 1403 | + zTemp = unobscure((char*)zIn); | |
| 1404 | + } | |
| 1405 | + strcpy(zOut, zTemp); | |
| 1401 | 1406 | fossil_free(zTemp); |
| 1402 | 1407 | sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free); |
| 1403 | 1408 | } |
| 1404 | 1409 | |
| 1405 | 1410 | /* |
| 1406 | 1411 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1395,11 +1395,16 @@ | |
| 1395 | if( 0==zIn ) return; |
| 1396 | if( 0==(zOut = sqlite3_malloc64( nIn * 2 + 3 )) ){ |
| 1397 | sqlite3_result_error_nomem(context); |
| 1398 | return; |
| 1399 | } |
| 1400 | strcpy(zOut, zTemp = obscure((char*)zIn)); |
| 1401 | fossil_free(zTemp); |
| 1402 | sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free); |
| 1403 | } |
| 1404 | |
| 1405 | /* |
| 1406 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1395,11 +1395,16 @@ | |
| 1395 | if( 0==zIn ) return; |
| 1396 | if( 0==(zOut = sqlite3_malloc64( nIn * 2 + 3 )) ){ |
| 1397 | sqlite3_result_error_nomem(context); |
| 1398 | return; |
| 1399 | } |
| 1400 | if( sqlite3_user_data(context)==0 ){ |
| 1401 | zTemp = obscure((char*)zIn); |
| 1402 | }else{ |
| 1403 | zTemp = unobscure((char*)zIn); |
| 1404 | } |
| 1405 | strcpy(zOut, zTemp); |
| 1406 | fossil_free(zTemp); |
| 1407 | sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free); |
| 1408 | } |
| 1409 | |
| 1410 | /* |
| 1411 |
+25
-4
| --- src/http.c | ||
| +++ src/http.c | ||
| @@ -90,15 +90,36 @@ | ||
| 90 | 90 | /* Password failure while doing a sync from the command-line interface */ |
| 91 | 91 | url_prompt_for_password(); |
| 92 | 92 | zPw = g.url.passwd; |
| 93 | 93 | } |
| 94 | 94 | |
| 95 | - /* The login card wants the SHA1 hash of the password, so convert the | |
| 96 | - ** password to its SHA1 hash if it isn't already a SHA1 hash. | |
| 95 | + /* The login card wants the SHA1 hash of the password (as computed by | |
| 96 | + ** sha1_shared_secret()), not the original password. So convert the | |
| 97 | + ** password to its SHA1 encoding if it isn't already a SHA1 hash. | |
| 98 | + ** | |
| 99 | + ** We assume that a hexadecimal string of exactly 40 characters is a | |
| 100 | + ** SHA1 hash, not an original password. If a user has a password which | |
| 101 | + ** just happens to be a 40-character hex string, then this routine won't | |
| 102 | + ** be able to distinguish it from a hash, the translation will not be | |
| 103 | + ** performed, and the sync won't work. | |
| 97 | 104 | */ |
| 98 | - /* fossil_print("\nzPw=[%s]\n", zPw); // TESTING ONLY */ | |
| 99 | - if( zPw && zPw[0] ) zPw = sha1_shared_secret(zPw, zLogin, 0); | |
| 105 | + if( zPw && zPw[0] && (strlen(zPw)!=40 || !validate16(zPw,40)) ){ | |
| 106 | + const char *zProjectCode = 0; | |
| 107 | + if( g.url.flags & URL_USE_PARENT ){ | |
| 108 | + zProjectCode = db_get("parent-project-code", 0); | |
| 109 | + }else{ | |
| 110 | + zProjectCode = db_get("project-code", 0); | |
| 111 | + } | |
| 112 | + zPw = sha1_shared_secret(zPw, zLogin, zProjectCode); | |
| 113 | + if( g.url.pwConfig!=0 ){ | |
| 114 | + char *x = obscure(zPw); | |
| 115 | + db_set(g.url.pwConfig/*works-like:"x"*/, x, 0); | |
| 116 | + fossil_free(x); | |
| 117 | + } | |
| 118 | + fossil_free(g.url.passwd); | |
| 119 | + g.url.passwd = fossil_strdup(zPw); | |
| 120 | + } | |
| 100 | 121 | |
| 101 | 122 | blob_append(&pw, zPw, -1); |
| 102 | 123 | sha1sum_blob(&pw, &sig); |
| 103 | 124 | blob_appendf(pLogin, "login %F %b %b\n", zLogin, &nonce, &sig); |
| 104 | 125 | blob_reset(&pw); |
| 105 | 126 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -90,15 +90,36 @@ | |
| 90 | /* Password failure while doing a sync from the command-line interface */ |
| 91 | url_prompt_for_password(); |
| 92 | zPw = g.url.passwd; |
| 93 | } |
| 94 | |
| 95 | /* The login card wants the SHA1 hash of the password, so convert the |
| 96 | ** password to its SHA1 hash if it isn't already a SHA1 hash. |
| 97 | */ |
| 98 | /* fossil_print("\nzPw=[%s]\n", zPw); // TESTING ONLY */ |
| 99 | if( zPw && zPw[0] ) zPw = sha1_shared_secret(zPw, zLogin, 0); |
| 100 | |
| 101 | blob_append(&pw, zPw, -1); |
| 102 | sha1sum_blob(&pw, &sig); |
| 103 | blob_appendf(pLogin, "login %F %b %b\n", zLogin, &nonce, &sig); |
| 104 | blob_reset(&pw); |
| 105 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -90,15 +90,36 @@ | |
| 90 | /* Password failure while doing a sync from the command-line interface */ |
| 91 | url_prompt_for_password(); |
| 92 | zPw = g.url.passwd; |
| 93 | } |
| 94 | |
| 95 | /* The login card wants the SHA1 hash of the password (as computed by |
| 96 | ** sha1_shared_secret()), not the original password. So convert the |
| 97 | ** password to its SHA1 encoding if it isn't already a SHA1 hash. |
| 98 | ** |
| 99 | ** We assume that a hexadecimal string of exactly 40 characters is a |
| 100 | ** SHA1 hash, not an original password. If a user has a password which |
| 101 | ** just happens to be a 40-character hex string, then this routine won't |
| 102 | ** be able to distinguish it from a hash, the translation will not be |
| 103 | ** performed, and the sync won't work. |
| 104 | */ |
| 105 | if( zPw && zPw[0] && (strlen(zPw)!=40 || !validate16(zPw,40)) ){ |
| 106 | const char *zProjectCode = 0; |
| 107 | if( g.url.flags & URL_USE_PARENT ){ |
| 108 | zProjectCode = db_get("parent-project-code", 0); |
| 109 | }else{ |
| 110 | zProjectCode = db_get("project-code", 0); |
| 111 | } |
| 112 | zPw = sha1_shared_secret(zPw, zLogin, zProjectCode); |
| 113 | if( g.url.pwConfig!=0 ){ |
| 114 | char *x = obscure(zPw); |
| 115 | db_set(g.url.pwConfig/*works-like:"x"*/, x, 0); |
| 116 | fossil_free(x); |
| 117 | } |
| 118 | fossil_free(g.url.passwd); |
| 119 | g.url.passwd = fossil_strdup(zPw); |
| 120 | } |
| 121 | |
| 122 | blob_append(&pw, zPw, -1); |
| 123 | sha1sum_blob(&pw, &sig); |
| 124 | blob_appendf(pLogin, "login %F %b %b\n", zLogin, &nonce, &sig); |
| 125 | blob_reset(&pw); |
| 126 |
+2
| --- src/sqlcmd.c | ||
| +++ src/sqlcmd.c | ||
| @@ -246,10 +246,12 @@ | ||
| 246 | 246 | if( local_bSqlCmdTest ){ |
| 247 | 247 | sqlite3_create_function(db, "db_protect", 1, SQLITE_UTF8, 0, |
| 248 | 248 | sqlcmd_db_protect, 0, 0); |
| 249 | 249 | sqlite3_create_function(db, "db_protect_pop", 0, SQLITE_UTF8, 0, |
| 250 | 250 | sqlcmd_db_protect_pop, 0, 0); |
| 251 | + sqlite3_create_function(db, "shared_secret", 2, SQLITE_UTF8, 0, | |
| 252 | + sha1_shared_secret_sql_function, 0, 0); | |
| 251 | 253 | } |
| 252 | 254 | return SQLITE_OK; |
| 253 | 255 | } |
| 254 | 256 | |
| 255 | 257 | /* |
| 256 | 258 |
| --- src/sqlcmd.c | |
| +++ src/sqlcmd.c | |
| @@ -246,10 +246,12 @@ | |
| 246 | if( local_bSqlCmdTest ){ |
| 247 | sqlite3_create_function(db, "db_protect", 1, SQLITE_UTF8, 0, |
| 248 | sqlcmd_db_protect, 0, 0); |
| 249 | sqlite3_create_function(db, "db_protect_pop", 0, SQLITE_UTF8, 0, |
| 250 | sqlcmd_db_protect_pop, 0, 0); |
| 251 | } |
| 252 | return SQLITE_OK; |
| 253 | } |
| 254 | |
| 255 | /* |
| 256 |
| --- src/sqlcmd.c | |
| +++ src/sqlcmd.c | |
| @@ -246,10 +246,12 @@ | |
| 246 | if( local_bSqlCmdTest ){ |
| 247 | sqlite3_create_function(db, "db_protect", 1, SQLITE_UTF8, 0, |
| 248 | sqlcmd_db_protect, 0, 0); |
| 249 | sqlite3_create_function(db, "db_protect_pop", 0, SQLITE_UTF8, 0, |
| 250 | sqlcmd_db_protect_pop, 0, 0); |
| 251 | sqlite3_create_function(db, "shared_secret", 2, SQLITE_UTF8, 0, |
| 252 | sha1_shared_secret_sql_function, 0, 0); |
| 253 | } |
| 254 | return SQLITE_OK; |
| 255 | } |
| 256 | |
| 257 | /* |
| 258 |
+16
-9
| --- src/sync.c | ||
| +++ src/sync.c | ||
| @@ -560,11 +560,13 @@ | ||
| 560 | 560 | ** REF may be a URL or a NAME from a prior "add". |
| 561 | 561 | */ |
| 562 | 562 | void remote_url_cmd(void){ |
| 563 | 563 | char *zUrl, *zArg; |
| 564 | 564 | int nArg; |
| 565 | + int showPw; | |
| 565 | 566 | db_find_and_open_repository(0, 0); |
| 567 | + showPw = find_option("show-passwords",0,0)!=0; | |
| 566 | 568 | |
| 567 | 569 | /* We should be done with options.. */ |
| 568 | 570 | verify_all_options(); |
| 569 | 571 | |
| 570 | 572 | /* 2021-10-25: A note about data structures. |
| @@ -760,32 +762,37 @@ | ||
| 760 | 762 | db_protect_pop(); |
| 761 | 763 | db_commit_transaction(); |
| 762 | 764 | return; |
| 763 | 765 | } |
| 764 | 766 | if( strncmp(zArg, "config-data", nArg)==0 ){ |
| 765 | - /* Undocumented command: "fossil remote config-data" | |
| 767 | + /* Undocumented command: "fossil remote config-data [-show-passwords]" | |
| 766 | 768 | ** |
| 767 | 769 | ** Show the CONFIG table entries that relate to remembering remote URLs |
| 768 | 770 | */ |
| 769 | 771 | Stmt q; |
| 770 | 772 | int n; |
| 773 | + sqlite3_create_function(g.db, "unobscure", 1, SQLITE_UTF8, &g.db, | |
| 774 | + db_obscure, 0, 0); | |
| 771 | 775 | n = db_int(13, |
| 772 | 776 | "SELECT max(length(name))" |
| 773 | 777 | " FROM config" |
| 774 | 778 | " WHERE name GLOB 'sync-*:*'" |
| 775 | 779 | " OR name GLOB 'last-sync-*'" |
| 776 | 780 | " OR name GLOB 'parent-project-*'" |
| 777 | 781 | ); |
| 778 | 782 | db_prepare(&q, |
| 779 | - "SELECT name," | |
| 780 | - " CASE WHEN name LIKE '%%sync-pw%%' OR name='parent-project-pw'" | |
| 781 | - " THEN printf('%%.*c',length(value),'*') ELSE value END" | |
| 782 | - " FROM config" | |
| 783 | - " WHERE name GLOB 'sync-*:*'" | |
| 784 | - " OR name GLOB 'last-sync-*'" | |
| 785 | - " OR name GLOB 'parent-project-*'" | |
| 786 | - " ORDER BY name LIKE '%%sync-pw%%' OR name='parent-project-pw', name" | |
| 783 | + "SELECT name," | |
| 784 | + " CASE WHEN name NOT LIKE '%%sync-pw%%' AND name<>'parent-project-pw'" | |
| 785 | + " THEN value" | |
| 786 | + " WHEN %d THEN unobscure(value)" | |
| 787 | + " ELSE printf('%%.*c',length(value)/2-1,'*') END" | |
| 788 | + " FROM config" | |
| 789 | + " WHERE name GLOB 'sync-*:*'" | |
| 790 | + " OR name GLOB 'last-sync-*'" | |
| 791 | + " OR name GLOB 'parent-project-*'" | |
| 792 | + " ORDER BY name LIKE '%%sync-pw%%' OR name='parent-project-pw', name", | |
| 793 | + showPw | |
| 787 | 794 | ); |
| 788 | 795 | while( db_step(&q)==SQLITE_ROW ){ |
| 789 | 796 | fossil_print("%-*s %s\n", |
| 790 | 797 | n, db_column_text(&q,0), |
| 791 | 798 | db_column_text(&q,1) |
| 792 | 799 |
| --- src/sync.c | |
| +++ src/sync.c | |
| @@ -560,11 +560,13 @@ | |
| 560 | ** REF may be a URL or a NAME from a prior "add". |
| 561 | */ |
| 562 | void remote_url_cmd(void){ |
| 563 | char *zUrl, *zArg; |
| 564 | int nArg; |
| 565 | db_find_and_open_repository(0, 0); |
| 566 | |
| 567 | /* We should be done with options.. */ |
| 568 | verify_all_options(); |
| 569 | |
| 570 | /* 2021-10-25: A note about data structures. |
| @@ -760,32 +762,37 @@ | |
| 760 | db_protect_pop(); |
| 761 | db_commit_transaction(); |
| 762 | return; |
| 763 | } |
| 764 | if( strncmp(zArg, "config-data", nArg)==0 ){ |
| 765 | /* Undocumented command: "fossil remote config-data" |
| 766 | ** |
| 767 | ** Show the CONFIG table entries that relate to remembering remote URLs |
| 768 | */ |
| 769 | Stmt q; |
| 770 | int n; |
| 771 | n = db_int(13, |
| 772 | "SELECT max(length(name))" |
| 773 | " FROM config" |
| 774 | " WHERE name GLOB 'sync-*:*'" |
| 775 | " OR name GLOB 'last-sync-*'" |
| 776 | " OR name GLOB 'parent-project-*'" |
| 777 | ); |
| 778 | db_prepare(&q, |
| 779 | "SELECT name," |
| 780 | " CASE WHEN name LIKE '%%sync-pw%%' OR name='parent-project-pw'" |
| 781 | " THEN printf('%%.*c',length(value),'*') ELSE value END" |
| 782 | " FROM config" |
| 783 | " WHERE name GLOB 'sync-*:*'" |
| 784 | " OR name GLOB 'last-sync-*'" |
| 785 | " OR name GLOB 'parent-project-*'" |
| 786 | " ORDER BY name LIKE '%%sync-pw%%' OR name='parent-project-pw', name" |
| 787 | ); |
| 788 | while( db_step(&q)==SQLITE_ROW ){ |
| 789 | fossil_print("%-*s %s\n", |
| 790 | n, db_column_text(&q,0), |
| 791 | db_column_text(&q,1) |
| 792 |
| --- src/sync.c | |
| +++ src/sync.c | |
| @@ -560,11 +560,13 @@ | |
| 560 | ** REF may be a URL or a NAME from a prior "add". |
| 561 | */ |
| 562 | void remote_url_cmd(void){ |
| 563 | char *zUrl, *zArg; |
| 564 | int nArg; |
| 565 | int showPw; |
| 566 | db_find_and_open_repository(0, 0); |
| 567 | showPw = find_option("show-passwords",0,0)!=0; |
| 568 | |
| 569 | /* We should be done with options.. */ |
| 570 | verify_all_options(); |
| 571 | |
| 572 | /* 2021-10-25: A note about data structures. |
| @@ -760,32 +762,37 @@ | |
| 762 | db_protect_pop(); |
| 763 | db_commit_transaction(); |
| 764 | return; |
| 765 | } |
| 766 | if( strncmp(zArg, "config-data", nArg)==0 ){ |
| 767 | /* Undocumented command: "fossil remote config-data [-show-passwords]" |
| 768 | ** |
| 769 | ** Show the CONFIG table entries that relate to remembering remote URLs |
| 770 | */ |
| 771 | Stmt q; |
| 772 | int n; |
| 773 | sqlite3_create_function(g.db, "unobscure", 1, SQLITE_UTF8, &g.db, |
| 774 | db_obscure, 0, 0); |
| 775 | n = db_int(13, |
| 776 | "SELECT max(length(name))" |
| 777 | " FROM config" |
| 778 | " WHERE name GLOB 'sync-*:*'" |
| 779 | " OR name GLOB 'last-sync-*'" |
| 780 | " OR name GLOB 'parent-project-*'" |
| 781 | ); |
| 782 | db_prepare(&q, |
| 783 | "SELECT name," |
| 784 | " CASE WHEN name NOT LIKE '%%sync-pw%%' AND name<>'parent-project-pw'" |
| 785 | " THEN value" |
| 786 | " WHEN %d THEN unobscure(value)" |
| 787 | " ELSE printf('%%.*c',length(value)/2-1,'*') END" |
| 788 | " FROM config" |
| 789 | " WHERE name GLOB 'sync-*:*'" |
| 790 | " OR name GLOB 'last-sync-*'" |
| 791 | " OR name GLOB 'parent-project-*'" |
| 792 | " ORDER BY name LIKE '%%sync-pw%%' OR name='parent-project-pw', name", |
| 793 | showPw |
| 794 | ); |
| 795 | while( db_step(&q)==SQLITE_ROW ){ |
| 796 | fossil_print("%-*s %s\n", |
| 797 | n, db_column_text(&q,0), |
| 798 | db_column_text(&q,1) |
| 799 |
+3
-1
| --- src/xfer.c | ||
| +++ src/xfer.c | ||
| @@ -798,11 +798,13 @@ | ||
| 798 | 798 | ** |
| 799 | 799 | ** login LOGIN NONCE SIGNATURE |
| 800 | 800 | ** |
| 801 | 801 | ** The NONCE is the SHA1 hash of the remainder of the input. |
| 802 | 802 | ** SIGNATURE is the SHA1 checksum of the NONCE concatenated |
| 803 | -** with the users password. | |
| 803 | +** with the sha1_shared_secret() encoding of the users password. | |
| 804 | +** | |
| 805 | +** SIGNATURE = sha1_sum( NONCE + sha1_shared_secret(PASSWORD) ); | |
| 804 | 806 | ** |
| 805 | 807 | ** The parameters to this routine are ephemeral blobs holding the |
| 806 | 808 | ** LOGIN, NONCE and SIGNATURE. |
| 807 | 809 | ** |
| 808 | 810 | ** This routine attempts to locate the user and verify the signature. |
| 809 | 811 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -798,11 +798,13 @@ | |
| 798 | ** |
| 799 | ** login LOGIN NONCE SIGNATURE |
| 800 | ** |
| 801 | ** The NONCE is the SHA1 hash of the remainder of the input. |
| 802 | ** SIGNATURE is the SHA1 checksum of the NONCE concatenated |
| 803 | ** with the users password. |
| 804 | ** |
| 805 | ** The parameters to this routine are ephemeral blobs holding the |
| 806 | ** LOGIN, NONCE and SIGNATURE. |
| 807 | ** |
| 808 | ** This routine attempts to locate the user and verify the signature. |
| 809 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -798,11 +798,13 @@ | |
| 798 | ** |
| 799 | ** login LOGIN NONCE SIGNATURE |
| 800 | ** |
| 801 | ** The NONCE is the SHA1 hash of the remainder of the input. |
| 802 | ** SIGNATURE is the SHA1 checksum of the NONCE concatenated |
| 803 | ** with the sha1_shared_secret() encoding of the users password. |
| 804 | ** |
| 805 | ** SIGNATURE = sha1_sum( NONCE + sha1_shared_secret(PASSWORD) ); |
| 806 | ** |
| 807 | ** The parameters to this routine are ephemeral blobs holding the |
| 808 | ** LOGIN, NONCE and SIGNATURE. |
| 809 | ** |
| 810 | ** This routine attempts to locate the user and verify the signature. |
| 811 |