|
1
|
/* |
|
2
|
** Copyright (c) 2009 D. Richard Hipp |
|
3
|
** |
|
4
|
** This program is free software; you can redistribute it and/or |
|
5
|
** modify it under the terms of the Simplified BSD License (also |
|
6
|
** known as the "2-Clause License" or "FreeBSD License".) |
|
7
|
** |
|
8
|
** This program is distributed in the hope that it will be useful, |
|
9
|
** but without any warranty; without even the implied warranty of |
|
10
|
** merchantability or fitness for a particular purpose. |
|
11
|
** |
|
12
|
** Author contact information: |
|
13
|
** [email protected] |
|
14
|
** http://www.hwaci.com/drh/ |
|
15
|
** |
|
16
|
******************************************************************************* |
|
17
|
** |
|
18
|
** This file manages low-level SSL communications. |
|
19
|
** |
|
20
|
** This file implements a singleton. A single SSL connection may be active |
|
21
|
** at a time. State information is stored in static variables. |
|
22
|
** |
|
23
|
** The SSL connections can be either a client or a server. But all |
|
24
|
** connections for a single process must be of the same type, either client |
|
25
|
** or server. |
|
26
|
** |
|
27
|
** SSL support is abstracted out into this module because Fossil can |
|
28
|
** be compiled without SSL support (which requires OpenSSL library) |
|
29
|
*/ |
|
30
|
|
|
31
|
#include "config.h" |
|
32
|
#include "http_ssl.h" |
|
33
|
|
|
34
|
#ifdef FOSSIL_ENABLE_SSL |
|
35
|
|
|
36
|
#include <openssl/bio.h> |
|
37
|
#include <openssl/ssl.h> |
|
38
|
#include <openssl/err.h> |
|
39
|
#include <openssl/x509.h> |
|
40
|
|
|
41
|
#include <assert.h> |
|
42
|
#include <sys/types.h> |
|
43
|
|
|
44
|
/* |
|
45
|
** There can only be a single OpenSSL IO connection open at a time. |
|
46
|
** State information about that IO is stored in the following |
|
47
|
** local variables: |
|
48
|
*/ |
|
49
|
static int sslIsInit = 0; /* 0: uninit 1: init as client 2: init as server */ |
|
50
|
static BIO *iBio = 0; /* OpenSSL I/O abstraction */ |
|
51
|
static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */ |
|
52
|
static SSL_CTX *sslCtx; /* SSL context */ |
|
53
|
static SSL *ssl; |
|
54
|
static struct { /* Accept this SSL cert for this session only */ |
|
55
|
char *zHost; /* Subject or host name */ |
|
56
|
char *zHash; /* SHA2-256 hash of the cert */ |
|
57
|
} sException; |
|
58
|
static int sslNoCertVerify = 0; /* Do not verify SSL certs */ |
|
59
|
|
|
60
|
|
|
61
|
/* This is a self-signed cert in the PEM format that can be used when |
|
62
|
** no other certs are available. |
|
63
|
*/ |
|
64
|
static const char sslSelfCert[] = |
|
65
|
"-----BEGIN CERTIFICATE-----\n" |
|
66
|
"MIIDMTCCAhkCFGrDmuJkkzWERP/ITBvzwwI2lv0TMA0GCSqGSIb3DQEBCwUAMFQx\n" |
|
67
|
"CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOQzESMBAGA1UEBwwJQ2hhcmxvdHRlMRMw\n" |
|
68
|
"EQYDVQQKDApGb3NzaWwtU0NNMQ8wDQYDVQQDDAZGb3NzaWwwIBcNMjExMjI3MTEz\n" |
|
69
|
"MTU2WhgPMjEyMTEyMjcxMTMxNTZaMFQxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJO\n" |
|
70
|
"QzESMBAGA1UEBwwJQ2hhcmxvdHRlMRMwEQYDVQQKDApGb3NzaWwtU0NNMQ8wDQYD\n" |
|
71
|
"VQQDDAZGb3NzaWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCCbTU2\n" |
|
72
|
"6GRQHQqLq7vyZ0OxpAxmgfAKCxt6eIz+jBi2ZM/CB5vVXWVh2+SkSiWEA3UZiUqX\n" |
|
73
|
"xZlzmS/CglZdiwLLDJML8B4OiV72oivFH/vJ7+cbvh1dTxnYiHuww7GfQngPrLfe\n" |
|
74
|
"fiIYPDk1GTUJHBQ7Ue477F7F8vKuHdVgwktF/JDM6M60aSqlo2D/oysirrb+dlur\n" |
|
75
|
"Tlv0rjsYOfq6bLAajoL3qi/vek6DNssoywbge4PfbTgS9g7Gcgncbcet5pvaS12J\n" |
|
76
|
"avhFcd4JU4Ity49Hl9S/C2MfZ1tE53xVggRwKz4FPj65M5uymTdcxtjKXtCxIE1k\n" |
|
77
|
"KxJxXQh7rIYjm+RTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFkdtpqcybAzJN8G\n" |
|
78
|
"+ONuUm5sXNbWta7JGvm8l0BTSBcCUtJA3hn16iJqXA9KmLnaF2denC4EYk+KlVU1\n" |
|
79
|
"QXxskPJ4jB8A5B05jMijYv0nzCxKhviI8CR7GLEEGKzeg9pbW0+O3vaVehoZtdFX\n" |
|
80
|
"z3SsCssr9QjCLiApQxMzW1Iv3od2JXeHBwfVMFrWA1VCEUCRs8OSW/VOqDPJLVEi\n" |
|
81
|
"G6wxc4kN9dLK+5S29q3nzl24/qzXoF8P9Re5KBCbrwaHgy+OEEceq5jkmfGFxXjw\n" |
|
82
|
"pvVCNry5uAhH5NqbXZampUWqiWtM4eTaIPo7Y2mDA1uWhuWtO6F9PsnFJlQHCnwy\n" |
|
83
|
"s/TsrXk=\n" |
|
84
|
"-----END CERTIFICATE-----\n"; |
|
85
|
|
|
86
|
/* This is the private-key corresponding to the cert above |
|
87
|
*/ |
|
88
|
static const char sslSelfPKey[] = |
|
89
|
"-----BEGIN PRIVATE KEY-----\n" |
|
90
|
"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCCbTU26GRQHQqL\n" |
|
91
|
"q7vyZ0OxpAxmgfAKCxt6eIz+jBi2ZM/CB5vVXWVh2+SkSiWEA3UZiUqXxZlzmS/C\n" |
|
92
|
"glZdiwLLDJML8B4OiV72oivFH/vJ7+cbvh1dTxnYiHuww7GfQngPrLfefiIYPDk1\n" |
|
93
|
"GTUJHBQ7Ue477F7F8vKuHdVgwktF/JDM6M60aSqlo2D/oysirrb+dlurTlv0rjsY\n" |
|
94
|
"Ofq6bLAajoL3qi/vek6DNssoywbge4PfbTgS9g7Gcgncbcet5pvaS12JavhFcd4J\n" |
|
95
|
"U4Ity49Hl9S/C2MfZ1tE53xVggRwKz4FPj65M5uymTdcxtjKXtCxIE1kKxJxXQh7\n" |
|
96
|
"rIYjm+RTAgMBAAECggEANfTH1vc8yIe7HRzmm9lsf8jF+II4s2705y2H5qY+cvYx\n" |
|
97
|
"nKtZJGOG1X0KkYy7CGoFv5K0cSUl3lS5FVamM/yWIzoIex/Sz2C1EIL2aI5as6ez\n" |
|
98
|
"jB6SN0/J+XI8+Vt7186/rHxfdIPpxuzjHbxX3HTpScETNWcLrghbrPxakbTPPxwt\n" |
|
99
|
"+x7QlPmmkFNuMfvkzToFf9NdwL++44TeBPOpvD/Lrw+eyqdth9RJPq9cM96plh9V\n" |
|
100
|
"HuRqeD8+QNafaXBdSQs3FJK/cDK/vWGKZWIfFVSDbDhwYljkXGijreFjtXQfkkpF\n" |
|
101
|
"rl1J87/H9Ee7z8fTD2YXQHl+0/rghAVtac3u54dpQQKBgQC2XG3OEeMrOp9dNkUd\n" |
|
102
|
"F8VffUg0ecwG+9L3LCe7U71K0kPmXjV6xNnuYcNQu84kptc5vI8wD23p29LaxdNc\n" |
|
103
|
"9m0lcw06/YYBOPkNphcHkINYZTvVJF10mL3isymzMaTtwDkZUkOjL1B+MTiFT/qp\n" |
|
104
|
"ARKrTYGJ4HxY7+tUkI5pUmg4PQKBgQC3GA4d1Rz3Pb/RRpcsZgWknKsKhoN36mSn\n" |
|
105
|
"xFJ3wPBvVv2B1ltTMzh/+the0ty6clzMrvoLERzRcheDsNrc/j/TUVG8sVdBYJwX\n" |
|
106
|
"tMZyFW4NVMOErT/1ukh6jBqIMBo6NJL3EV/AKj0yniksgKOr0/AAduAccnGST8Jd\n" |
|
107
|
"SHOdjwvHzwKBgGZBq/zqgNTDuYseHGE07CMgcDWkumiMGv8ozlq3mSR0hUiPOTPP\n" |
|
108
|
"YFjQjyIdPXnF6FfiyPPtIvgIoNK2LVAqiod+XUPf152l4dnqcW13dn9BvOxGyPTR\n" |
|
109
|
"lWCikFaAFviOWjY9r9m4dU1dslDmySqthFd0TZgPvgps9ivkJ0cdw30NAoGAMC/E\n" |
|
110
|
"h1VvKiK2OP27C5ROJ+STn1GHiCfIFd81VQ8SODtMvL8NifgRBp2eFFaqgOdYRQZI\n" |
|
111
|
"CGGYlAbS6XXCJCdF5Peh62dA75PdgN+y2pOJQzjrvB9cle9Q4++7i9wdCvSLOTr5\n" |
|
112
|
"WDnFoWy+qVexu6crovOmR9ZWzYrwPFy1EOJ010ECgYBl7Q+jmjOSqsVwhFZ0U7LG\n" |
|
113
|
"diN+vXhWfn1wfOWd8u79oaqU/Oy7xyKW2p3H5z2KFrBM/vib53Lh4EwFZjcX+jVG\n" |
|
114
|
"krAmbL+M/hP7z3TD2UbESAzR/c6l7FU45xN84Lsz5npkR8H/uAHuqLgb9e430Mjx\n" |
|
115
|
"YNMwdb8rChHHChNZu6zuxw==\n" |
|
116
|
"-----END PRIVATE KEY-----\n"; |
|
117
|
|
|
118
|
/* |
|
119
|
** Read a PEM certificate from memory and push it into an SSL_CTX. |
|
120
|
** Return the number of errors. |
|
121
|
*/ |
|
122
|
static int sslctx_use_cert_from_mem( |
|
123
|
SSL_CTX *ctx, |
|
124
|
const char *pData, |
|
125
|
int nData |
|
126
|
){ |
|
127
|
BIO *in; |
|
128
|
int rc = 1; |
|
129
|
X509 *x = 0; |
|
130
|
X509 *cert = 0; |
|
131
|
|
|
132
|
in = BIO_new_mem_buf(pData, nData); |
|
133
|
if( in==0 ) goto end_of_ucfm; |
|
134
|
/* x = X509_new_ex(ctx->libctx, ctx->propq); */ |
|
135
|
x = X509_new(); |
|
136
|
if( x==0 ) goto end_of_ucfm; |
|
137
|
cert = PEM_read_bio_X509(in, &x, 0, 0); |
|
138
|
if( cert==0 ) goto end_of_ucfm; |
|
139
|
rc = SSL_CTX_use_certificate(ctx, x)<=0; |
|
140
|
end_of_ucfm: |
|
141
|
X509_free(x); |
|
142
|
BIO_free(in); |
|
143
|
return rc; |
|
144
|
} |
|
145
|
|
|
146
|
/* |
|
147
|
** Read a PEM private key from memory and add it to an SSL_CTX. |
|
148
|
** Return the number of errors. |
|
149
|
*/ |
|
150
|
static int sslctx_use_pkey_from_mem( |
|
151
|
SSL_CTX *ctx, |
|
152
|
const char *pData, |
|
153
|
int nData |
|
154
|
){ |
|
155
|
int rc = 1; |
|
156
|
BIO *in; |
|
157
|
EVP_PKEY *pkey = 0; |
|
158
|
|
|
159
|
in = BIO_new_mem_buf(pData, nData); |
|
160
|
if( in==0 ) goto end_of_upkfm; |
|
161
|
pkey = PEM_read_bio_PrivateKey(in, 0, 0, 0); |
|
162
|
if( pkey==0 ) goto end_of_upkfm; |
|
163
|
rc = SSL_CTX_use_PrivateKey(ctx, pkey)<=0; |
|
164
|
EVP_PKEY_free(pkey); |
|
165
|
end_of_upkfm: |
|
166
|
BIO_free(in); |
|
167
|
return rc; |
|
168
|
} |
|
169
|
|
|
170
|
/* |
|
171
|
** Clear the SSL error message |
|
172
|
*/ |
|
173
|
static void ssl_clear_errmsg(void){ |
|
174
|
free(sslErrMsg); |
|
175
|
sslErrMsg = 0; |
|
176
|
} |
|
177
|
|
|
178
|
/* |
|
179
|
** Set the SSL error message. |
|
180
|
*/ |
|
181
|
void ssl_set_errmsg(const char *zFormat, ...){ |
|
182
|
va_list ap; |
|
183
|
ssl_clear_errmsg(); |
|
184
|
va_start(ap, zFormat); |
|
185
|
sslErrMsg = vmprintf(zFormat, ap); |
|
186
|
va_end(ap); |
|
187
|
} |
|
188
|
|
|
189
|
/* |
|
190
|
** Return the current SSL error message |
|
191
|
*/ |
|
192
|
const char *ssl_errmsg(void){ |
|
193
|
return sslErrMsg; |
|
194
|
} |
|
195
|
|
|
196
|
/* |
|
197
|
** When a server requests a client certificate that hasn't been provided, |
|
198
|
** display a warning message explaining what to do next. |
|
199
|
*/ |
|
200
|
static int ssl_client_cert_callback(SSL *ssl, X509 **x509, EVP_PKEY **pkey){ |
|
201
|
fossil_warning("The remote server requested a client certificate for " |
|
202
|
"authentication. Specify the pathname to a file containing the PEM " |
|
203
|
"encoded certificate and private key with the --ssl-identity option " |
|
204
|
"or the ssl-identity setting."); |
|
205
|
return 0; /* no cert available */ |
|
206
|
} |
|
207
|
|
|
208
|
/* |
|
209
|
** Convert an OpenSSL ASN1_TIME to an ISO8601 timestamp. |
|
210
|
** |
|
211
|
** Per RFC 5280, ASN1 timestamps in X.509 certificates must |
|
212
|
** be in UTC (Zulu timezone) with no fractional seconds. |
|
213
|
** |
|
214
|
** If showUtc==1, add " UTC" at the end of the returned string. This is |
|
215
|
** not ISO8601-compliant, but makes the displayed value more user-friendly. |
|
216
|
*/ |
|
217
|
static const char *ssl_asn1time_to_iso8601(ASN1_TIME *asn1_time, |
|
218
|
int showUtc){ |
|
219
|
assert( showUtc==0 || showUtc==1 ); |
|
220
|
if( !ASN1_TIME_check(asn1_time) ){ |
|
221
|
return mprintf("Bad time value"); |
|
222
|
}else{ |
|
223
|
char res[20]; |
|
224
|
char *pr = res; |
|
225
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L |
|
226
|
#define ASN1_STRING_get0_data ASN1_STRING_data |
|
227
|
#endif |
|
228
|
const char *pt = (const char *)ASN1_STRING_get0_data(asn1_time); |
|
229
|
/* 0123456789 1234 |
|
230
|
** UTCTime: YYMMDDHHMMSSZ (YY >= 50 ? 19YY : 20YY) |
|
231
|
** GeneralizedTime: YYYYMMDDHHMMSSZ */ |
|
232
|
if( ASN1_STRING_length(asn1_time) < 15 ){ |
|
233
|
/* UTCTime, fill out century digits */ |
|
234
|
*pr++ = pt[0]>='5' ? '1' : '2'; |
|
235
|
*pr++ = pt[0]>='5' ? '9' : '0'; |
|
236
|
}else{ |
|
237
|
/* GeneralizedTime, copy century digits and advance source */ |
|
238
|
*pr++ = pt[0]; *pr++ = pt[1]; |
|
239
|
pt += 2; |
|
240
|
} |
|
241
|
*pr++ = pt[0]; *pr++ = pt[1]; *pr++ = '-'; |
|
242
|
*pr++ = pt[2]; *pr++ = pt[3]; *pr++ = '-'; |
|
243
|
*pr++ = pt[4]; *pr++ = pt[5]; *pr++ = ' '; |
|
244
|
*pr++ = pt[6]; *pr++ = pt[7]; *pr++ = ':'; |
|
245
|
*pr++ = pt[8]; *pr++ = pt[9]; *pr++ = ':'; |
|
246
|
*pr++ = pt[10]; *pr++ = pt[11]; *pr = '\0'; |
|
247
|
return mprintf("%s%s", res, (showUtc ? " UTC" : "")); |
|
248
|
} |
|
249
|
} |
|
250
|
|
|
251
|
/* |
|
252
|
** Call this routine once before any other use of the SSL interface. |
|
253
|
** This routine does initial configuration of the SSL module. |
|
254
|
*/ |
|
255
|
static void ssl_global_init_client(void){ |
|
256
|
const char *identityFile; |
|
257
|
|
|
258
|
if( sslIsInit==0 ){ |
|
259
|
const char *zFile; |
|
260
|
const char *zCaFile = 0; |
|
261
|
const char *zCaDirectory = 0; |
|
262
|
int i; |
|
263
|
|
|
264
|
SSL_library_init(); |
|
265
|
SSL_load_error_strings(); |
|
266
|
OpenSSL_add_all_algorithms(); |
|
267
|
sslCtx = SSL_CTX_new(SSLv23_client_method()); |
|
268
|
/* Disable SSLv2 and SSLv3 */ |
|
269
|
SSL_CTX_set_options(sslCtx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3); |
|
270
|
|
|
271
|
/* Find the trust store */ |
|
272
|
zFile = 0; |
|
273
|
for(i=0; zFile==0 && i<5; i++){ |
|
274
|
switch( i ){ |
|
275
|
case 0: /* First priority is environment variables */ |
|
276
|
zFile = fossil_getenv(X509_get_default_cert_file_env()); |
|
277
|
break; |
|
278
|
case 1: |
|
279
|
zFile = fossil_getenv(X509_get_default_cert_dir_env()); |
|
280
|
break; |
|
281
|
case 2: |
|
282
|
if( !g.repositoryOpen ) db_open_config(0,0); |
|
283
|
zFile = db_get("ssl-ca-location",0); |
|
284
|
break; |
|
285
|
case 3: |
|
286
|
zFile = X509_get_default_cert_file(); |
|
287
|
break; |
|
288
|
case 4: |
|
289
|
zFile = X509_get_default_cert_dir(); |
|
290
|
break; |
|
291
|
} |
|
292
|
if( zFile==0 ) continue; |
|
293
|
switch( file_isdir(zFile, ExtFILE) ){ |
|
294
|
case 0: { /* doesn't exist */ |
|
295
|
zFile = 0; |
|
296
|
break; |
|
297
|
} |
|
298
|
case 1: { /* directory */ |
|
299
|
zCaFile = 0; |
|
300
|
zCaDirectory = zFile; |
|
301
|
break; |
|
302
|
} |
|
303
|
case 2: { /* file */ |
|
304
|
zCaFile = zFile; |
|
305
|
zCaDirectory = 0; |
|
306
|
break; |
|
307
|
} |
|
308
|
} |
|
309
|
} |
|
310
|
if( zFile==0 ){ |
|
311
|
/* fossil_fatal("Cannot find a trust store"); */ |
|
312
|
}else if( SSL_CTX_load_verify_locations(sslCtx, zCaFile, zCaDirectory)==0 ){ |
|
313
|
fossil_fatal("Cannot load CA root certificates from %s", zFile); |
|
314
|
} |
|
315
|
|
|
316
|
/* Enable OpenSSL to use the Windows system ROOT certificate store to search for |
|
317
|
** certificates missing in the file and directory trust stores already loaded by |
|
318
|
** `SSL_CTX_load_verify_locations()'. |
|
319
|
** This feature was introduced with OpenSSL 3.2.0, and may be enabled by default |
|
320
|
** for future versions of OpenSSL, and explicit initialization may be redundant. |
|
321
|
** NOTE TO HACKERS TWEAKING THEIR OPENSSL CONFIGURATION: |
|
322
|
** The following OpenSSL configuration options must not be used for this feature |
|
323
|
** to be available: `no-autoalginit', `no-winstore'. The Fossil makefiles do not |
|
324
|
** currently set these options when building OpenSSL for Windows. */ |
|
325
|
#if defined(_WIN32) |
|
326
|
#if OPENSSL_VERSION_NUMBER >= 0x030200000 |
|
327
|
if( SSLeay()!=0x30500000 /* Don't use for 3.5.0 due to a bug */ |
|
328
|
&& SSL_CTX_load_verify_store(sslCtx, "org.openssl.winstore:")==0 |
|
329
|
){ |
|
330
|
fossil_print("NOTICE: Failed to load the Windows root certificates.\n"); |
|
331
|
} |
|
332
|
#endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */ |
|
333
|
#endif /* _WIN32 */ |
|
334
|
|
|
335
|
/* Load client SSL identity, preferring the filename specified on the |
|
336
|
** command line */ |
|
337
|
if( g.zSSLIdentity!=0 ){ |
|
338
|
identityFile = g.zSSLIdentity; |
|
339
|
}else{ |
|
340
|
identityFile = db_get("ssl-identity", 0); |
|
341
|
} |
|
342
|
if( identityFile!=0 && identityFile[0]!='\0' ){ |
|
343
|
if( SSL_CTX_use_certificate_chain_file(sslCtx,identityFile)!=1 |
|
344
|
|| SSL_CTX_use_PrivateKey_file(sslCtx,identityFile,SSL_FILETYPE_PEM)!=1 |
|
345
|
){ |
|
346
|
fossil_fatal("Could not load SSL identity from %s", identityFile); |
|
347
|
} |
|
348
|
} |
|
349
|
/* Register a callback to tell the user what to do when the server asks |
|
350
|
** for a cert */ |
|
351
|
SSL_CTX_set_client_cert_cb(sslCtx, ssl_client_cert_callback); |
|
352
|
|
|
353
|
sslIsInit = 1; |
|
354
|
}else{ |
|
355
|
assert( sslIsInit==1 ); |
|
356
|
} |
|
357
|
} |
|
358
|
|
|
359
|
/* |
|
360
|
** Call this routine to shutdown the SSL module prior to program exit. |
|
361
|
*/ |
|
362
|
void ssl_global_shutdown(void){ |
|
363
|
if( sslIsInit ){ |
|
364
|
SSL_CTX_free(sslCtx); |
|
365
|
ssl_clear_errmsg(); |
|
366
|
sslIsInit = 0; |
|
367
|
} |
|
368
|
socket_global_shutdown(); |
|
369
|
} |
|
370
|
|
|
371
|
/* |
|
372
|
** Close the currently open client SSL connection. If no connection is open, |
|
373
|
** this routine is a no-op. |
|
374
|
*/ |
|
375
|
void ssl_close_client(void){ |
|
376
|
if( iBio!=NULL ){ |
|
377
|
(void)BIO_reset(iBio); |
|
378
|
BIO_free_all(iBio); |
|
379
|
iBio = NULL; |
|
380
|
} |
|
381
|
socket_close(); |
|
382
|
} |
|
383
|
|
|
384
|
/* See RFC2817 for details */ |
|
385
|
static int establish_proxy_tunnel(UrlData *pUrlData, BIO *bio){ |
|
386
|
int rc, httpVerMin; |
|
387
|
char *bbuf; |
|
388
|
Blob snd, reply; |
|
389
|
int done=0,end=0; |
|
390
|
blob_zero(&snd); |
|
391
|
blob_appendf(&snd, "CONNECT %s:%d HTTP/1.1\r\n", pUrlData->hostname, |
|
392
|
pUrlData->proxyOrigPort); |
|
393
|
blob_appendf(&snd, "Host: %s:%d\r\n", |
|
394
|
pUrlData->hostname, pUrlData->proxyOrigPort); |
|
395
|
if( pUrlData->proxyAuth ){ |
|
396
|
blob_appendf(&snd, "Proxy-Authorization: %s\r\n", pUrlData->proxyAuth); |
|
397
|
} |
|
398
|
blob_append(&snd, "Proxy-Connection: keep-alive\r\n", -1); |
|
399
|
blob_appendf(&snd, "User-Agent: %s\r\n", get_user_agent()); |
|
400
|
blob_append(&snd, "\r\n", 2); |
|
401
|
BIO_write(bio, blob_buffer(&snd), blob_size(&snd)); |
|
402
|
blob_reset(&snd); |
|
403
|
|
|
404
|
/* Wait for end of reply */ |
|
405
|
blob_zero(&reply); |
|
406
|
do{ |
|
407
|
int len; |
|
408
|
char buf[256]; |
|
409
|
len = BIO_read(bio, buf, sizeof(buf)); |
|
410
|
blob_append(&reply, buf, len); |
|
411
|
|
|
412
|
bbuf = blob_buffer(&reply); |
|
413
|
len = blob_size(&reply); |
|
414
|
while(end < len) { |
|
415
|
if( bbuf[end]=='\n' ) { |
|
416
|
if( (end+1<len && bbuf[end+1]=='\n') |
|
417
|
|| (end+2<len && bbuf[end+1]=='\r' && bbuf[end+2]=='\n') |
|
418
|
){ |
|
419
|
done = 1; |
|
420
|
break; |
|
421
|
} |
|
422
|
} |
|
423
|
end++; |
|
424
|
} |
|
425
|
}while(!done); |
|
426
|
sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc); |
|
427
|
blob_reset(&reply); |
|
428
|
return rc; |
|
429
|
} |
|
430
|
|
|
431
|
/* |
|
432
|
** Invoke this routine to disable SSL cert verification. After |
|
433
|
** this call is made, any SSL cert that the server provides will |
|
434
|
** be accepted. Communication will still be encrypted, but the |
|
435
|
** client has no way of knowing whether it is talking to the |
|
436
|
** real server or a man-in-the-middle imposter. |
|
437
|
*/ |
|
438
|
void ssl_disable_cert_verification(void){ |
|
439
|
sslNoCertVerify = 1; |
|
440
|
} |
|
441
|
|
|
442
|
/* |
|
443
|
** Open an SSL connection as a client that is to connect to the server |
|
444
|
** identified by pUrlData. |
|
445
|
** |
|
446
|
* The identify of the server is determined as follows: |
|
447
|
** |
|
448
|
** pUrlData->name Name of the server. Ex: fossil-scm.org |
|
449
|
** g.url.name Name of the proxy server, if proxying. |
|
450
|
** pUrlData->port TCP/IP port to use. Ex: 80 |
|
451
|
** |
|
452
|
** Return the number of errors. |
|
453
|
*/ |
|
454
|
int ssl_open_client(UrlData *pUrlData){ |
|
455
|
X509 *cert; |
|
456
|
const char *zRemoteHost; |
|
457
|
BIO *sBio; |
|
458
|
|
|
459
|
ssl_global_init_client(); |
|
460
|
if( socket_open(pUrlData) ){ |
|
461
|
ssl_set_errmsg("SSL: cannot open socket (%s)", socket_errmsg()); |
|
462
|
return 1; |
|
463
|
} |
|
464
|
sBio = BIO_new_socket(socket_get_fd(), 0); |
|
465
|
if( pUrlData->useProxy ){ |
|
466
|
int rc = establish_proxy_tunnel(pUrlData, sBio); |
|
467
|
if( rc<200||rc>299 ){ |
|
468
|
ssl_set_errmsg("SSL: proxy connect failed with HTTP status code %d", rc); |
|
469
|
ssl_close_client(); |
|
470
|
return 1; |
|
471
|
} |
|
472
|
|
|
473
|
pUrlData->path = pUrlData->proxyUrlPath; |
|
474
|
} |
|
475
|
iBio = BIO_new_ssl(sslCtx, 1); |
|
476
|
BIO_push(iBio, sBio); |
|
477
|
BIO_set_ssl(sBio, ssl, BIO_NOCLOSE); |
|
478
|
BIO_set_ssl_mode(iBio, 1); |
|
479
|
BIO_get_ssl(iBio, &ssl); |
|
480
|
|
|
481
|
zRemoteHost = pUrlData->useProxy ? pUrlData->hostname : pUrlData->name; |
|
482
|
|
|
483
|
#if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT) |
|
484
|
if( !SSL_set_tlsext_host_name(ssl, zRemoteHost)){ |
|
485
|
fossil_warning("WARNING: failed to set server name indication (SNI), " |
|
486
|
"continuing without it.\n"); |
|
487
|
} |
|
488
|
#endif |
|
489
|
|
|
490
|
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); |
|
491
|
#if OPENSSL_VERSION_NUMBER >= 0x010002000 |
|
492
|
if( !sslNoCertVerify ){ |
|
493
|
X509_VERIFY_PARAM *param = 0; |
|
494
|
param = SSL_get0_param(ssl); |
|
495
|
if( !X509_VERIFY_PARAM_set1_host(param, zRemoteHost, strlen(zRemoteHost)) ){ |
|
496
|
fossil_fatal("failed to set hostname."); |
|
497
|
} |
|
498
|
/* SSL_set_verify(ssl, SSL_VERIFY_PEER, 0); */ |
|
499
|
} |
|
500
|
#endif |
|
501
|
|
|
502
|
if( BIO_do_handshake(iBio)<=0 ){ |
|
503
|
ssl_set_errmsg("Error establishing SSL connection %s:%d (%s)", |
|
504
|
zRemoteHost, |
|
505
|
pUrlData->useProxy ? pUrlData->proxyOrigPort : pUrlData->port, |
|
506
|
ERR_reason_error_string(ERR_get_error())); |
|
507
|
ssl_close_client(); |
|
508
|
return 1; |
|
509
|
} |
|
510
|
/* Check if certificate is valid */ |
|
511
|
cert = SSL_get_peer_certificate(ssl); |
|
512
|
|
|
513
|
if ( cert==NULL ){ |
|
514
|
ssl_set_errmsg("No SSL certificate was presented by the peer"); |
|
515
|
ssl_close_client(); |
|
516
|
return 1; |
|
517
|
} |
|
518
|
|
|
519
|
/* Debugging hint: On unix-like system, run something like: |
|
520
|
** |
|
521
|
** SSL_CERT_DIR=/tmp ./fossil sync |
|
522
|
** |
|
523
|
** to cause certificate validation to fail, and thus test the fallback |
|
524
|
** logic. |
|
525
|
*/ |
|
526
|
if( !sslNoCertVerify && SSL_get_verify_result(ssl)!=X509_V_OK ){ |
|
527
|
int x, desclen; |
|
528
|
char *desc, *prompt; |
|
529
|
Blob ans; |
|
530
|
char cReply; |
|
531
|
BIO *mem; |
|
532
|
unsigned char md[EVP_MAX_MD_SIZE]; |
|
533
|
char zHash[EVP_MAX_MD_SIZE*2+1]; |
|
534
|
unsigned int mdLength = (int)sizeof(md); |
|
535
|
|
|
536
|
memset(md, 0, sizeof(md)); |
|
537
|
zHash[0] = 0; |
|
538
|
/* MMNNFFPPS */ |
|
539
|
#if OPENSSL_VERSION_NUMBER >= 0x010000000 |
|
540
|
x = X509_digest(cert, EVP_sha256(), md, &mdLength); |
|
541
|
#else |
|
542
|
x = X509_digest(cert, EVP_sha1(), md, &mdLength); |
|
543
|
#endif |
|
544
|
if( x ){ |
|
545
|
unsigned j; |
|
546
|
for(j=0; j<mdLength && j*2+1<sizeof(zHash); ++j){ |
|
547
|
zHash[j*2] = "0123456789abcdef"[md[j]>>4]; |
|
548
|
zHash[j*2+1] = "0123456789abcdef"[md[j]&0xf]; |
|
549
|
} |
|
550
|
zHash[j*2] = 0; |
|
551
|
} |
|
552
|
|
|
553
|
if( ssl_certificate_exception_exists(pUrlData, zHash) ){ |
|
554
|
/* Ignore the failure because an exception exists */ |
|
555
|
ssl_one_time_exception(pUrlData, zHash); |
|
556
|
}else{ |
|
557
|
/* Tell the user about the failure and ask what to do */ |
|
558
|
mem = BIO_new(BIO_s_mem()); |
|
559
|
BIO_puts(mem, " subject: "); |
|
560
|
X509_NAME_print_ex(mem, X509_get_subject_name(cert), 0, XN_FLAG_ONELINE); |
|
561
|
BIO_puts(mem, "\n issuer: "); |
|
562
|
X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE); |
|
563
|
BIO_printf(mem, "\n notBefore: %s", |
|
564
|
ssl_asn1time_to_iso8601(X509_get_notBefore(cert), 1)); |
|
565
|
BIO_printf(mem, "\n notAfter: %s", |
|
566
|
ssl_asn1time_to_iso8601(X509_get_notAfter(cert), 1)); |
|
567
|
BIO_printf(mem, "\n sha256: %s", zHash); |
|
568
|
desclen = BIO_get_mem_data(mem, &desc); |
|
569
|
|
|
570
|
prompt = mprintf("Unable to verify SSL cert from %s\n%.*s\n" |
|
571
|
"accept this cert and continue (y/N/fingerprint)? ", |
|
572
|
pUrlData->name, desclen, desc); |
|
573
|
BIO_free(mem); |
|
574
|
|
|
575
|
prompt_user(prompt, &ans); |
|
576
|
free(prompt); |
|
577
|
cReply = blob_str(&ans)[0]; |
|
578
|
if( cReply!='y' && cReply!='Y' |
|
579
|
&& fossil_stricmp(blob_str(&ans),zHash)!=0 |
|
580
|
){ |
|
581
|
X509_free(cert); |
|
582
|
ssl_set_errmsg("SSL cert declined"); |
|
583
|
ssl_close_client(); |
|
584
|
blob_reset(&ans); |
|
585
|
return 1; |
|
586
|
} |
|
587
|
blob_reset(&ans); |
|
588
|
ssl_one_time_exception(pUrlData, zHash); |
|
589
|
prompt_user("remember this exception (y/N)? ", &ans); |
|
590
|
cReply = blob_str(&ans)[0]; |
|
591
|
if( cReply=='y' || cReply=='Y') { |
|
592
|
db_open_config(0,0); |
|
593
|
ssl_remember_certificate_exception(pUrlData, zHash); |
|
594
|
} |
|
595
|
blob_reset(&ans); |
|
596
|
} |
|
597
|
} |
|
598
|
|
|
599
|
X509_free(cert); |
|
600
|
return 0; |
|
601
|
} |
|
602
|
|
|
603
|
/* |
|
604
|
** Remember that the cert with the given hash is acceptable for |
|
605
|
** use with pUrlData->name. |
|
606
|
*/ |
|
607
|
LOCAL void ssl_remember_certificate_exception( |
|
608
|
UrlData *pUrlData, |
|
609
|
const char *zHash |
|
610
|
){ |
|
611
|
db_set_mprintf(zHash, 1, "cert:%s", pUrlData->name); |
|
612
|
} |
|
613
|
|
|
614
|
/* |
|
615
|
** Return true if the there exists a certificate exception for |
|
616
|
** pUrlData->name that matches the hash. |
|
617
|
*/ |
|
618
|
LOCAL int ssl_certificate_exception_exists( |
|
619
|
UrlData *pUrlData, |
|
620
|
const char *zHash |
|
621
|
){ |
|
622
|
char *zName, *zValue; |
|
623
|
if( fossil_strcmp(sException.zHost,pUrlData->name)==0 |
|
624
|
&& fossil_strcmp(sException.zHash,zHash)==0 |
|
625
|
){ |
|
626
|
return 1; |
|
627
|
} |
|
628
|
zName = mprintf("cert:%s", pUrlData->name); |
|
629
|
zValue = db_get(zName,0); |
|
630
|
fossil_free(zName); |
|
631
|
return zValue!=0 && strcmp(zHash,zValue)==0; |
|
632
|
} |
|
633
|
|
|
634
|
/* |
|
635
|
** Remember zHash as an acceptable certificate for this session only. |
|
636
|
*/ |
|
637
|
LOCAL void ssl_one_time_exception( |
|
638
|
UrlData *pUrlData, |
|
639
|
const char *zHash |
|
640
|
){ |
|
641
|
fossil_free(sException.zHost); |
|
642
|
sException.zHost = fossil_strdup(pUrlData->name); |
|
643
|
fossil_free(sException.zHash); |
|
644
|
sException.zHash = fossil_strdup(zHash); |
|
645
|
} |
|
646
|
|
|
647
|
/* |
|
648
|
** Send content out over the SSL connection from the client to |
|
649
|
** the server. |
|
650
|
*/ |
|
651
|
size_t ssl_send(void *NotUsed, void *pContent, size_t N){ |
|
652
|
size_t total = 0; |
|
653
|
while( N>0 ){ |
|
654
|
int sent = BIO_write(iBio, pContent, N); |
|
655
|
if( sent<=0 ){ |
|
656
|
if( BIO_should_retry(iBio) ){ |
|
657
|
continue; |
|
658
|
} |
|
659
|
break; |
|
660
|
} |
|
661
|
total += sent; |
|
662
|
N -= sent; |
|
663
|
pContent = (void*)&((char*)pContent)[sent]; |
|
664
|
} |
|
665
|
return total; |
|
666
|
} |
|
667
|
|
|
668
|
/* |
|
669
|
** Receive content back from the client SSL connection. In other |
|
670
|
** words read the reply back from the server. |
|
671
|
*/ |
|
672
|
size_t ssl_receive(void *NotUsed, void *pContent, size_t N){ |
|
673
|
size_t total = 0; |
|
674
|
while( N>0 ){ |
|
675
|
int got = BIO_read(iBio, pContent, N); |
|
676
|
if( got<=0 ){ |
|
677
|
if( BIO_should_retry(iBio) ){ |
|
678
|
continue; |
|
679
|
} |
|
680
|
break; |
|
681
|
} |
|
682
|
total += got; |
|
683
|
N -= got; |
|
684
|
pContent = (void*)&((char*)pContent)[got]; |
|
685
|
} |
|
686
|
return total; |
|
687
|
} |
|
688
|
|
|
689
|
/* |
|
690
|
** Initialize the SSL library so that it is able to handle |
|
691
|
** server-side connections. Invoke fossil_fatal() if there are |
|
692
|
** any problems. |
|
693
|
** |
|
694
|
** If zKeyFile and zCertFile are not NULL, then they are the names |
|
695
|
** of disk files that hold the certificate and private-key for the |
|
696
|
** server. If zCertFile is not NULL but zKeyFile is NULL, then |
|
697
|
** zCertFile is assumed to be a concatenation of the certificate and |
|
698
|
** the private-key in the PEM format. |
|
699
|
** |
|
700
|
** If zCertFile is "unsafe-builtin", then a built-in self-signed cert |
|
701
|
** is used. This built-in cert is insecure and should only be used for |
|
702
|
** testing and debugging. |
|
703
|
*/ |
|
704
|
void ssl_init_server(const char *zCertFile, const char *zKeyFile){ |
|
705
|
if( sslIsInit==0 && zCertFile ){ |
|
706
|
SSL_library_init(); |
|
707
|
SSL_load_error_strings(); |
|
708
|
OpenSSL_add_all_algorithms(); |
|
709
|
sslCtx = SSL_CTX_new(SSLv23_server_method()); |
|
710
|
if( sslCtx==0 ){ |
|
711
|
ERR_print_errors_fp(stderr); |
|
712
|
fossil_fatal("Error initializing the SSL server"); |
|
713
|
} |
|
714
|
if( fossil_strcmp(zCertFile,"unsafe-builtin")==0 ){ |
|
715
|
if( sslctx_use_cert_from_mem(sslCtx, sslSelfCert, -1) |
|
716
|
|| sslctx_use_pkey_from_mem(sslCtx, sslSelfPKey, -1) |
|
717
|
){ |
|
718
|
fossil_fatal("Error loading self-signed CERT and KEY"); |
|
719
|
} |
|
720
|
}else{ |
|
721
|
if( SSL_CTX_use_certificate_chain_file(sslCtx,zCertFile)!=1 ){ |
|
722
|
ERR_print_errors_fp(stderr); |
|
723
|
fossil_fatal("Error loading CERT file \"%s\"", zCertFile); |
|
724
|
} |
|
725
|
if( zKeyFile==0 ) zKeyFile = zCertFile; |
|
726
|
if( SSL_CTX_use_PrivateKey_file(sslCtx, zKeyFile, SSL_FILETYPE_PEM)<=0 ){ |
|
727
|
ERR_print_errors_fp(stderr); |
|
728
|
if( strcmp(zKeyFile,zCertFile)==0 ){ |
|
729
|
fossil_fatal("The private key is not found in \"%s\". " |
|
730
|
"Either append the private key to the certification in that " |
|
731
|
"file or use a separate --pkey option to specify the private key.", |
|
732
|
zKeyFile); |
|
733
|
}else{ |
|
734
|
fossil_fatal("Error loading the private key from file \"%s\"", |
|
735
|
zKeyFile); |
|
736
|
} |
|
737
|
} |
|
738
|
} |
|
739
|
if( !SSL_CTX_check_private_key(sslCtx) ){ |
|
740
|
fossil_fatal("PRIVATE KEY \"%s\" does not match CERT \"%s\"", |
|
741
|
zKeyFile, zCertFile); |
|
742
|
} |
|
743
|
SSL_CTX_set_mode(sslCtx, SSL_MODE_AUTO_RETRY); |
|
744
|
sslIsInit = 2; |
|
745
|
}else{ |
|
746
|
assert( sslIsInit==2 ); |
|
747
|
} |
|
748
|
} |
|
749
|
|
|
750
|
typedef struct SslServerConn { |
|
751
|
SSL *ssl; /* The SSL codec */ |
|
752
|
int iSocket; /* The socket */ |
|
753
|
BIO *bio; /* BIO object. Needed for EOF detection. */ |
|
754
|
} SslServerConn; |
|
755
|
|
|
756
|
/* |
|
757
|
** Create a new server-side codec. The argument is the socket's file |
|
758
|
** descriptor from which the codec reads and writes. The returned |
|
759
|
** memory must eventually be passed to ssl_close_server(). |
|
760
|
*/ |
|
761
|
void *ssl_new_server(int iSocket){ |
|
762
|
SslServerConn *pServer = fossil_malloc_zero(sizeof(*pServer)); |
|
763
|
BIO *b = BIO_new_socket(iSocket, 0); |
|
764
|
pServer->ssl = SSL_new(sslCtx); |
|
765
|
pServer->iSocket = iSocket; |
|
766
|
pServer->bio = b; |
|
767
|
SSL_set_bio(pServer->ssl, b, b); |
|
768
|
SSL_accept(pServer->ssl); |
|
769
|
return (void*)pServer; |
|
770
|
} |
|
771
|
|
|
772
|
/* |
|
773
|
** Close a server-side code previously returned from ssl_new_server(). |
|
774
|
*/ |
|
775
|
void ssl_close_server(void *pServerArg){ |
|
776
|
SslServerConn *pServer = (SslServerConn*)pServerArg; |
|
777
|
SSL_free(pServer->ssl); |
|
778
|
fossil_free(pServer); |
|
779
|
} |
|
780
|
|
|
781
|
/* |
|
782
|
** Return TRUE if there are no more bytes available to be read from |
|
783
|
** the client. |
|
784
|
*/ |
|
785
|
int ssl_eof(void *pServerArg){ |
|
786
|
SslServerConn *pServer = (SslServerConn*)pServerArg; |
|
787
|
return BIO_eof(pServer->bio); |
|
788
|
} |
|
789
|
|
|
790
|
/* |
|
791
|
** Read cleartext bytes that have been received from the client and |
|
792
|
** decrypted by the SSL server codec. |
|
793
|
** |
|
794
|
** If the expected payload size unknown, i.e. if the HTTP |
|
795
|
** Content-Length: header field has not been parsed, the doLoop |
|
796
|
** argument should be 0, or SSL_read() may block and wait for more |
|
797
|
** data than is eventually going to arrive (on Windows). On |
|
798
|
** non-Windows builds, it has been our experience that the final |
|
799
|
** argument must always be true, as discussed at length at: |
|
800
|
** |
|
801
|
** https://fossil-scm.org/forum/forumpost/2f818850abb72719 |
|
802
|
*/ |
|
803
|
size_t ssl_read_server(void *pServerArg, char *zBuf, size_t nBuf, int doLoop){ |
|
804
|
int n; |
|
805
|
size_t rc = 0; |
|
806
|
SslServerConn *pServer = (SslServerConn*)pServerArg; |
|
807
|
if( nBuf>0x7fffffff ){ fossil_fatal("SSL read too big"); } |
|
808
|
while( nBuf!=rc && BIO_eof(pServer->bio)==0 ){ |
|
809
|
n = SSL_read(pServer->ssl, zBuf + rc, (int)(nBuf - rc)); |
|
810
|
if( n>0 ){ |
|
811
|
rc += n; |
|
812
|
} |
|
813
|
if( doLoop==0 || n<=0 ){ |
|
814
|
break; |
|
815
|
} |
|
816
|
} |
|
817
|
return rc; |
|
818
|
} |
|
819
|
|
|
820
|
/* |
|
821
|
** Read a single line of text from the client, up to nBuf-1 bytes. On |
|
822
|
** success, writes nBuf-1 bytes to zBuf and NUL-terminates zBuf. |
|
823
|
** Returns NULL on an I/O error or at EOF. |
|
824
|
*/ |
|
825
|
char *ssl_gets(void *pServerArg, char *zBuf, int nBuf){ |
|
826
|
int n = 0; |
|
827
|
int i; |
|
828
|
SslServerConn *pServer = (SslServerConn*)pServerArg; |
|
829
|
|
|
830
|
if( BIO_eof(pServer->bio) ) return 0; |
|
831
|
for(i=0; i<nBuf-1; i++){ |
|
832
|
n = SSL_read(pServer->ssl, &zBuf[i], 1); |
|
833
|
if( n<=0 ){ |
|
834
|
return 0; |
|
835
|
} |
|
836
|
if( zBuf[i]=='\n' ) break; |
|
837
|
} |
|
838
|
zBuf[i+1] = 0; |
|
839
|
return zBuf; |
|
840
|
} |
|
841
|
|
|
842
|
|
|
843
|
/* |
|
844
|
** Write cleartext bytes into the SSL server codec so that they can |
|
845
|
** be encrypted and sent back to the client. |
|
846
|
*/ |
|
847
|
size_t ssl_write_server(void *pServerArg, char *zBuf, size_t nBuf){ |
|
848
|
int n; |
|
849
|
SslServerConn *pServer = (SslServerConn*)pServerArg; |
|
850
|
if( nBuf<=0 ) return 0; |
|
851
|
if( nBuf>0x7fffffff ){ fossil_fatal("SSL write too big"); } |
|
852
|
n = SSL_write(pServer->ssl, zBuf, (int)nBuf); |
|
853
|
if( n<=0 ){ |
|
854
|
return -SSL_get_error(pServer->ssl, n); |
|
855
|
}else{ |
|
856
|
return n; |
|
857
|
} |
|
858
|
} |
|
859
|
|
|
860
|
#endif /* FOSSIL_ENABLE_SSL */ |
|
861
|
|
|
862
|
#ifdef FOSSIL_ENABLE_SSL |
|
863
|
/* |
|
864
|
** zPath is a name that might be a file or directory containing a trust |
|
865
|
** store. *pzStore is the name of the trust store to actually use. |
|
866
|
** |
|
867
|
** If *pzStore is not NULL (meaning no trust store has been found yet) |
|
868
|
** and if zPath exists, then set *pzStore to point to zPath. |
|
869
|
*/ |
|
870
|
static void trust_location_usable(const char *zPath, const char **pzStore){ |
|
871
|
if( *pzStore!=0 ) return; |
|
872
|
if( file_isdir(zPath, ExtFILE)>0 ) *pzStore = zPath; |
|
873
|
} |
|
874
|
#endif /* FOSSIL_ENABLE_SSL */ |
|
875
|
|
|
876
|
/* |
|
877
|
** COMMAND: tls-config* abbrv-subcom |
|
878
|
** COMMAND: ssl-config abbrv-subcom |
|
879
|
** |
|
880
|
** Usage: %fossil ssl-config [SUBCOMMAND] [OPTIONS...] [ARGS...] |
|
881
|
** |
|
882
|
** This command is used to view or modify the TLS (Transport Layer |
|
883
|
** Security) configuration for Fossil. TLS (formerly SSL) is the |
|
884
|
** encryption technology used for secure HTTPS transport. |
|
885
|
** |
|
886
|
** Sub-commands: |
|
887
|
** |
|
888
|
** remove-exception DOMAINS Remove TLS cert exceptions for the domains |
|
889
|
** listed. Or remove them all if the --all |
|
890
|
** option is specified. |
|
891
|
** |
|
892
|
** scrub ?--force? Remove all SSL configuration data from the |
|
893
|
** repository. Use --force to omit the |
|
894
|
** confirmation. |
|
895
|
** |
|
896
|
** show ?-v? Show the TLS configuration. Add -v to see |
|
897
|
** additional explanation |
|
898
|
*/ |
|
899
|
void test_tlsconfig_info(void){ |
|
900
|
const char *zCmd; |
|
901
|
size_t nCmd; |
|
902
|
int nHit = 0; |
|
903
|
|
|
904
|
db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); |
|
905
|
db_open_config(1,0); |
|
906
|
if( g.argc==2 || (g.argc>=3 && g.argv[2][0]=='-') ){ |
|
907
|
zCmd = "show"; |
|
908
|
nCmd = 4; |
|
909
|
}else{ |
|
910
|
zCmd = g.argv[2]; |
|
911
|
nCmd = strlen(zCmd); |
|
912
|
} |
|
913
|
if( strncmp("scrub",zCmd,nCmd)==0 && nCmd>4 ){ |
|
914
|
int bForce = find_option("force","f",0)!=0; |
|
915
|
verify_all_options(); |
|
916
|
if( !bForce ){ |
|
917
|
Blob ans; |
|
918
|
char cReply; |
|
919
|
prompt_user( |
|
920
|
"Scrubbing the SSL configuration will permanently delete information.\n" |
|
921
|
"Changes cannot be undone. Continue (y/N)? ", &ans); |
|
922
|
cReply = blob_str(&ans)[0]; |
|
923
|
if( cReply!='y' && cReply!='Y' ){ |
|
924
|
fossil_exit(1); |
|
925
|
} |
|
926
|
} |
|
927
|
db_unprotect(PROTECT_ALL); |
|
928
|
db_multi_exec( |
|
929
|
"PRAGMA secure_delete=ON;" |
|
930
|
"DELETE FROM config WHERE name GLOB 'ssl-*';" |
|
931
|
); |
|
932
|
db_protect_pop(); |
|
933
|
}else |
|
934
|
if( strncmp("show",zCmd,nCmd)==0 ){ |
|
935
|
#if defined(FOSSIL_ENABLE_SSL) |
|
936
|
const char *zName, *zValue; |
|
937
|
const char *zUsed = 0; /* Trust store location actually used */ |
|
938
|
size_t nName; |
|
939
|
#endif |
|
940
|
Stmt q; |
|
941
|
int verbose = find_option("verbose","v",0)!=0; |
|
942
|
verify_all_options(); |
|
943
|
|
|
944
|
#if !defined(FOSSIL_ENABLE_SSL) |
|
945
|
fossil_print("OpenSSL-version: (none)\n"); |
|
946
|
if( verbose ){ |
|
947
|
fossil_print("\n" |
|
948
|
" The OpenSSL library is not used by this build of Fossil\n\n" |
|
949
|
); |
|
950
|
} |
|
951
|
#else |
|
952
|
fossil_print("OpenSSL-version: %s (0x%09llx)\n", |
|
953
|
SSLeay_version(SSLEAY_VERSION), (unsigned long long)SSLeay()); |
|
954
|
if( verbose ){ |
|
955
|
fossil_print("\n" |
|
956
|
" The version of the OpenSSL library being used\n" |
|
957
|
" by this instance of Fossil. Version 3.0.0 or\n" |
|
958
|
" later is recommended.\n\n" |
|
959
|
); |
|
960
|
} |
|
961
|
|
|
962
|
fossil_print("Trust store location\n"); |
|
963
|
zName = X509_get_default_cert_file_env(); |
|
964
|
zValue = fossil_getenv(zName); |
|
965
|
if( zValue==0 ) zValue = ""; |
|
966
|
trust_location_usable(zValue, &zUsed); |
|
967
|
nName = strlen(zName); |
|
968
|
fossil_print(" %s:%*s%s\n", zName, 19-nName, "", zValue); |
|
969
|
zName = X509_get_default_cert_dir_env(); |
|
970
|
zValue = fossil_getenv(zName); |
|
971
|
if( zValue==0 ) zValue = ""; |
|
972
|
trust_location_usable(zValue, &zUsed); |
|
973
|
nName = strlen(zName); |
|
974
|
fossil_print(" %s:%*s%s\n", zName, 19-nName, "", zValue); |
|
975
|
if( verbose ){ |
|
976
|
fossil_print("\n" |
|
977
|
" Environment variables that determine alternative locations for\n" |
|
978
|
" the root certificates used by Fossil when it is acting as a SSL\n" |
|
979
|
" client. If specified, these alternative locations take top\n" |
|
980
|
" priority.\n\n" |
|
981
|
); |
|
982
|
} |
|
983
|
|
|
984
|
zValue = db_get("ssl-ca-location",""); |
|
985
|
trust_location_usable(zValue, &zUsed); |
|
986
|
fossil_print(" ssl-ca-location: %s\n", zValue); |
|
987
|
if( verbose ){ |
|
988
|
fossil_print("\n" |
|
989
|
" This setting is the name of a file or directory that contains\n" |
|
990
|
" the complete set of root certificates used by Fossil when it\n" |
|
991
|
" is acting as a SSL client. If defined, this setting takes\n" |
|
992
|
" priority over built-in paths.\n\n" |
|
993
|
); |
|
994
|
} |
|
995
|
|
|
996
|
|
|
997
|
zValue = X509_get_default_cert_file(); |
|
998
|
trust_location_usable(zValue, &zUsed); |
|
999
|
fossil_print(" OpenSSL-cert-file: %s\n", zValue); |
|
1000
|
zValue = X509_get_default_cert_dir(); |
|
1001
|
trust_location_usable(zValue, &zUsed); |
|
1002
|
fossil_print(" OpenSSL-cert-dir: %s\n", X509_get_default_cert_dir()); |
|
1003
|
if( verbose ){ |
|
1004
|
fossil_print("\n" |
|
1005
|
" The default locations for the set of root certificates\n" |
|
1006
|
" used by the \"fossil sync\" and similar commands to verify\n" |
|
1007
|
" the identity of servers for \"https:\" URLs. These values\n" |
|
1008
|
" come into play when Fossil is used as a TLS client. These\n" |
|
1009
|
" values are built into your OpenSSL library.\n\n" |
|
1010
|
); |
|
1011
|
} |
|
1012
|
|
|
1013
|
#if defined(_WIN32) |
|
1014
|
fossil_print(" OpenSSL-winstore: %s\n", |
|
1015
|
(SSLeay()>=0x30200000 && SSLeay()!=0x30500000) ? "Yes" : "No"); |
|
1016
|
if( verbose ){ |
|
1017
|
fossil_print("\n" |
|
1018
|
" OpenSSL 3.2.0, or newer, but not version 3.5.0 due to a bug,\n" |
|
1019
|
" are able to use the root certificates managed by the Windows\n" |
|
1020
|
" operating system. The installed root certificates are listed\n" |
|
1021
|
" by the command:\n\n" |
|
1022
|
" certutil -store \"ROOT\"\n\n" |
|
1023
|
); |
|
1024
|
} |
|
1025
|
#endif /* _WIN32 */ |
|
1026
|
|
|
1027
|
if( zUsed==0 ) zUsed = ""; |
|
1028
|
fossil_print(" Trust store used: %s\n", zUsed); |
|
1029
|
if( verbose ){ |
|
1030
|
fossil_print("\n" |
|
1031
|
" The location that is actually used for the root certificates\n" |
|
1032
|
" used to verify the identity of servers for \"https:\" URLs.\n" |
|
1033
|
" This will be one of the first of the five locations listed\n" |
|
1034
|
" above that actually exists.\n\n" |
|
1035
|
); |
|
1036
|
} |
|
1037
|
|
|
1038
|
|
|
1039
|
#endif /* FOSSIL_ENABLE_SSL */ |
|
1040
|
|
|
1041
|
|
|
1042
|
fossil_print("ssl-identity: %s\n", db_get("ssl-identity","")); |
|
1043
|
if( verbose ){ |
|
1044
|
fossil_print("\n" |
|
1045
|
" This setting is the name of a file that contains the PEM-format\n" |
|
1046
|
" certificate and private-key used by Fossil clients to authenticate\n" |
|
1047
|
" with servers. Few servers actually require this, so this setting\n" |
|
1048
|
" is usually blank.\n\n" |
|
1049
|
); |
|
1050
|
} |
|
1051
|
|
|
1052
|
db_prepare(&q, |
|
1053
|
"SELECT name, '', value FROM global_config" |
|
1054
|
" WHERE name GLOB 'cert:*'" |
|
1055
|
"UNION ALL " |
|
1056
|
"SELECT name, date(mtime,'unixepoch'), value FROM config" |
|
1057
|
" WHERE name GLOB 'cert:*'" |
|
1058
|
" ORDER BY name" |
|
1059
|
); |
|
1060
|
nHit = 0; |
|
1061
|
while( db_step(&q)==SQLITE_ROW ){ |
|
1062
|
/* 123456789 123456789 123456789 */ |
|
1063
|
if( verbose ){ |
|
1064
|
fossil_print("exception: %-40s %s\n" |
|
1065
|
" hash: %.57s\n", |
|
1066
|
db_column_text(&q,0)+5, db_column_text(&q,1), |
|
1067
|
db_column_text(&q,2)); |
|
1068
|
}else{ |
|
1069
|
fossil_print("exception: %-40s %s\n", |
|
1070
|
db_column_text(&q,0)+5, db_column_text(&q,1)); |
|
1071
|
} |
|
1072
|
nHit++; |
|
1073
|
} |
|
1074
|
db_finalize(&q); |
|
1075
|
if( nHit && verbose ){ |
|
1076
|
fossil_print("\n" |
|
1077
|
" The exceptions are server certificates that the Fossil client\n" |
|
1078
|
" is unable to verify using root certificates, but which should be\n" |
|
1079
|
" accepted anyhow.\n\n" |
|
1080
|
); |
|
1081
|
} |
|
1082
|
|
|
1083
|
}else |
|
1084
|
if( strncmp("remove-exception",zCmd,nCmd)==0 ){ |
|
1085
|
int i; |
|
1086
|
Blob sql; |
|
1087
|
char *zSep = "("; |
|
1088
|
db_begin_transaction(); |
|
1089
|
blob_init(&sql, 0, 0); |
|
1090
|
if( g.argc==4 && find_option("all",0,0)!=0 ){ |
|
1091
|
blob_append_sql(&sql, |
|
1092
|
"DELETE FROM global_config WHERE name GLOB 'cert:*';\n" |
|
1093
|
"DELETE FROM global_config WHERE name GLOB 'trusted:*';\n" |
|
1094
|
"DELETE FROM config WHERE name GLOB 'cert:*';\n" |
|
1095
|
"DELETE FROM config WHERE name GLOB 'trusted:*';\n" |
|
1096
|
); |
|
1097
|
}else{ |
|
1098
|
if( g.argc<4 ){ |
|
1099
|
usage("remove-exception DOMAIN-NAME ..."); |
|
1100
|
} |
|
1101
|
blob_append_sql(&sql,"DELETE FROM global_config WHERE name IN "); |
|
1102
|
for(i=3; i<g.argc; i++){ |
|
1103
|
blob_append_sql(&sql,"%s'cert:%q','trust:%q'", |
|
1104
|
zSep/*safe-for-%s*/, g.argv[i], g.argv[i]); |
|
1105
|
zSep = ","; |
|
1106
|
} |
|
1107
|
blob_append_sql(&sql,");\n"); |
|
1108
|
zSep = "("; |
|
1109
|
blob_append_sql(&sql,"DELETE FROM config WHERE name IN "); |
|
1110
|
for(i=3; i<g.argc; i++){ |
|
1111
|
blob_append_sql(&sql,"%s'cert:%q','trusted:%q'", |
|
1112
|
zSep/*safe-for-%s*/, g.argv[i], g.argv[i]); |
|
1113
|
zSep = ","; |
|
1114
|
} |
|
1115
|
blob_append_sql(&sql,");"); |
|
1116
|
} |
|
1117
|
db_unprotect(PROTECT_CONFIG); |
|
1118
|
db_exec_sql(blob_str(&sql)); |
|
1119
|
db_protect_pop(); |
|
1120
|
db_commit_transaction(); |
|
1121
|
blob_reset(&sql); |
|
1122
|
}else |
|
1123
|
/*default*/{ |
|
1124
|
fossil_fatal("unknown sub-command \"%s\".\nshould be one of:" |
|
1125
|
" remove-exception scrub show", |
|
1126
|
zCmd); |
|
1127
|
} |
|
1128
|
} |
|
1129
|
|
|
1130
|
/* |
|
1131
|
** WEBPAGE: .well-known |
|
1132
|
** |
|
1133
|
** If the "--acme" option was supplied to "fossil server" or "fossil http" or |
|
1134
|
** similar, then this page returns the content of files found in the |
|
1135
|
** ".well-known" subdirectory of the same directory that contains the |
|
1136
|
** repository file. This facilitates Automated Certificate |
|
1137
|
** Management using tools like "certbot". |
|
1138
|
** |
|
1139
|
** The content is returned directly, without any interpretation, using |
|
1140
|
** a generic mimetype. |
|
1141
|
*/ |
|
1142
|
void wellknown_page(void){ |
|
1143
|
char *zPath = 0; |
|
1144
|
const char *zTail = P("name"); |
|
1145
|
Blob content; |
|
1146
|
int i; |
|
1147
|
char c; |
|
1148
|
if( !g.fAllowACME ) goto wellknown_notfound; |
|
1149
|
if( g.zRepositoryName==0 ) goto wellknown_notfound; |
|
1150
|
if( zTail==0 ) goto wellknown_notfound; |
|
1151
|
zPath = mprintf("%z/.well-known/%s", file_dirname(g.zRepositoryName), zTail); |
|
1152
|
for(i=0; (c = zTail[i])!=0; i++){ |
|
1153
|
if( fossil_isalnum(c) ) continue; |
|
1154
|
if( c=='.' ){ |
|
1155
|
if( i==0 || zTail[i-1]=='/' || zTail[i-1]=='.' ) goto wellknown_notfound; |
|
1156
|
continue; |
|
1157
|
} |
|
1158
|
if( c==',' || c!='-' || c=='/' || c==':' || c=='_' || c=='~' ) continue; |
|
1159
|
goto wellknown_notfound; |
|
1160
|
} |
|
1161
|
if( strstr("/..", zPath)!=0 ) goto wellknown_notfound; |
|
1162
|
if( !file_isfile(zPath, ExtFILE) ) goto wellknown_notfound; |
|
1163
|
blob_read_from_file(&content, zPath, ExtFILE); |
|
1164
|
cgi_set_content(&content); |
|
1165
|
cgi_set_content_type(mimetype_from_name(zPath)); |
|
1166
|
cgi_reply(); |
|
1167
|
return; |
|
1168
|
|
|
1169
|
wellknown_notfound: |
|
1170
|
fossil_free(zPath); |
|
1171
|
webpage_notfound_error(0 /*works-like:""*/); |
|
1172
|
return; |
|
1173
|
} |
|
1174
|
|
|
1175
|
/* |
|
1176
|
** Return the OpenSSL version number being used. Space to hold |
|
1177
|
** this name is obtained from fossil_malloc() and should be |
|
1178
|
** freed by the caller. |
|
1179
|
*/ |
|
1180
|
char *fossil_openssl_version(void){ |
|
1181
|
#if defined(FOSSIL_ENABLE_SSL) |
|
1182
|
return mprintf("%s (0x%09x)\n", |
|
1183
|
SSLeay_version(SSLEAY_VERSION), (sqlite3_uint64)SSLeay()); |
|
1184
|
#else |
|
1185
|
return mprintf("none"); |
|
1186
|
#endif |
|
1187
|
} |
|
1188
|
|