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.

drh 2022-12-30 20:54 trunk
Commit 41ba6ea7db6ce2ce159709ce9b95dee44f9f5f03a13f2f741f5a6ebc96c5f034
+6 -1
--- src/db.c
+++ src/db.c
@@ -1395,11 +1395,16 @@
13951395
if( 0==zIn ) return;
13961396
if( 0==(zOut = sqlite3_malloc64( nIn * 2 + 3 )) ){
13971397
sqlite3_result_error_nomem(context);
13981398
return;
13991399
}
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);
14011406
fossil_free(zTemp);
14021407
sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free);
14031408
}
14041409
14051410
/*
14061411
--- 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 @@
9090
/* Password failure while doing a sync from the command-line interface */
9191
url_prompt_for_password();
9292
zPw = g.url.passwd;
9393
}
9494
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.
97104
*/
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
+ }
100121
101122
blob_append(&pw, zPw, -1);
102123
sha1sum_blob(&pw, &sig);
103124
blob_appendf(pLogin, "login %F %b %b\n", zLogin, &nonce, &sig);
104125
blob_reset(&pw);
105126
--- 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
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -246,10 +246,12 @@
246246
if( local_bSqlCmdTest ){
247247
sqlite3_create_function(db, "db_protect", 1, SQLITE_UTF8, 0,
248248
sqlcmd_db_protect, 0, 0);
249249
sqlite3_create_function(db, "db_protect_pop", 0, SQLITE_UTF8, 0,
250250
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);
251253
}
252254
return SQLITE_OK;
253255
}
254256
255257
/*
256258
--- 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 @@
560560
** REF may be a URL or a NAME from a prior "add".
561561
*/
562562
void remote_url_cmd(void){
563563
char *zUrl, *zArg;
564564
int nArg;
565
+ int showPw;
565566
db_find_and_open_repository(0, 0);
567
+ showPw = find_option("show-passwords",0,0)!=0;
566568
567569
/* We should be done with options.. */
568570
verify_all_options();
569571
570572
/* 2021-10-25: A note about data structures.
@@ -760,32 +762,37 @@
760762
db_protect_pop();
761763
db_commit_transaction();
762764
return;
763765
}
764766
if( strncmp(zArg, "config-data", nArg)==0 ){
765
- /* Undocumented command: "fossil remote config-data"
767
+ /* Undocumented command: "fossil remote config-data [-show-passwords]"
766768
**
767769
** Show the CONFIG table entries that relate to remembering remote URLs
768770
*/
769771
Stmt q;
770772
int n;
773
+ sqlite3_create_function(g.db, "unobscure", 1, SQLITE_UTF8, &g.db,
774
+ db_obscure, 0, 0);
771775
n = db_int(13,
772776
"SELECT max(length(name))"
773777
" FROM config"
774778
" WHERE name GLOB 'sync-*:*'"
775779
" OR name GLOB 'last-sync-*'"
776780
" OR name GLOB 'parent-project-*'"
777781
);
778782
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
787794
);
788795
while( db_step(&q)==SQLITE_ROW ){
789796
fossil_print("%-*s %s\n",
790797
n, db_column_text(&q,0),
791798
db_column_text(&q,1)
792799
--- 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 @@
798798
**
799799
** login LOGIN NONCE SIGNATURE
800800
**
801801
** The NONCE is the SHA1 hash of the remainder of the input.
802802
** 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) );
804806
**
805807
** The parameters to this routine are ephemeral blobs holding the
806808
** LOGIN, NONCE and SIGNATURE.
807809
**
808810
** This routine attempts to locate the user and verify the signature.
809811
--- 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

Keyboard Shortcuts

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