Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -191,20 +191,24 @@ const char *zName, /* Name of the cookie */ const char *zValue, /* Value of the cookie. Automatically escaped */ const char *zPath, /* Path cookie applies to. NULL means "/" */ int lifetime /* Expiration of the cookie in seconds from now */ ){ + char *zSecure = ""; if( zPath==0 ) zPath = g.zTop; + if( g.zBaseURL!=0 && strncmp(g.zBaseURL, "https:", 6)==0 ){ + zSecure = " secure;"; + } if( lifetime>0 ){ lifetime += (int)time(0); blob_appendf(&extraHeader, - "Set-Cookie: %s=%t; Path=%s; expires=%z; Version=1\r\n", - zName, zValue, zPath, cgi_rfc822_datestamp(lifetime)); + "Set-Cookie: %s=%t; Path=%s; expires=%z; HttpOnly;%s Version=1\r\n", + zName, zValue, zPath, cgi_rfc822_datestamp(lifetime), zSecure); }else{ blob_appendf(&extraHeader, - "Set-Cookie: %s=%t; Path=%s; Version=1\r\n", - zName, zValue, zPath); + "Set-Cookie: %s=%t; Path=%s; HttpOnly;%s Version=1\r\n", + zName, zValue, zPath, zSecure); } } #if 0 /* @@ -289,10 +293,28 @@ } if( blob_size(&extraHeader)>0 ){ fprintf(g.httpOut, "%s", blob_buffer(&extraHeader)); } + + /* Add headers to turn on useful security options in browsers. */ + fprintf(g.httpOut, "X-Frame-Options: DENY\r\n"); + /* This stops fossil pages appearing in frames or iframes, preventing + ** click-jacking attacks on supporting browsers. + ** + ** Other good headers would be + ** Strict-Transport-Security: max-age=62208000 + ** if we're using https. However, this would break sites which serve different + ** content on http and https protocols. Also, + ** X-Content-Security-Policy: allow 'self' + ** would help mitigate some XSS and data injection attacks, but will break + ** deliberate inclusion of external resources, such as JavaScript syntax + ** highlighter scripts. + ** + ** These headers are probably best added by the web server hosting fossil as + ** a CGI script. + */ if( g.isConst ){ /* constant means that the input URL will _never_ generate anything ** else. In the case of attachments, the contents won't change because ** an attempt to change them generates a new attachment number. In the Index: src/clone.c ================================================================== --- src/clone.c +++ src/clone.c @@ -37,10 +37,11 @@ ** ** Options: ** ** --admin-user|-A USERNAME Make USERNAME the administrator ** --private Also clone private branches +** --ssl-identity=filename Use the SSL identity if requested by the server ** */ void clone_cmd(void){ char *zPassword; const char *zDefaultUser; /* Optional name of the default user */ @@ -91,10 +92,18 @@ db_initial_setup(0, zDefaultUser, 0); user_select(); db_set("content-schema", CONTENT_SCHEMA, 0); db_set("aux-schema", AUX_SCHEMA, 0); db_set("last-sync-url", g.argv[2], 0); + if( g.zSSLIdentity!=0 ){ + /* If the --ssl-identity option was specified, store it as a setting */ + Blob fn; + blob_zero(&fn); + file_canonical_name(g.zSSLIdentity, &fn); + db_set("ssl-identity", blob_str(&fn), 0); + blob_reset(&fn); + } db_multi_exec( "REPLACE INTO config(name,value,mtime)" " VALUES('server-code', lower(hex(randomblob(20))), now());" ); url_enable_proxy(0); Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -1662,10 +1662,11 @@ { "mtime-changes", 0, 0, "on" }, { "pgp-command", 0, 32, "gpg --clearsign -o " }, { "proxy", 0, 32, "off" }, { "repo-cksum", 0, 0, "on" }, { "self-register", 0, 0, "off" }, + { "ssl-identity", 0, 40, "" }, { "ssh-command", 0, 32, "" }, { "web-browser", 0, 32, "" }, { 0,0,0,0 } }; @@ -1771,10 +1772,17 @@ ** ** self-register Allow users to register themselves through the HTTP UI. ** This is useful if you want to see other names than ** "Anonymous" in e.g. ticketing system. On the other hand ** users can not be deleted. Default: off. +** +** ssl-identity The full pathname to a file containing a certificate +** and private key in PEM format. Create by concatenating +** the certificate and private key files. +** This identity will be presented to SSL servers to +** authenticate this client, in addition to the normal +** password authentication. ** ** ssh-command Command used to talk to a remote machine with ** the "ssh://" protocol. ** ** web-browser A shell command used to launch your preferred Index: src/http_ssl.c ================================================================== --- src/http_ssl.c +++ src/http_ssl.c @@ -78,10 +78,19 @@ ** Return the current SSL error message */ const char *ssl_errmsg(void){ return sslErrMsg; } + +/* +** When a server requests a client certificate that hasn't been provided, +** display a warning message explaining what to do next. +*/ +static int ssl_client_cert_callback(SSL *ssl, X509 **x509, EVP_PKEY **pkey){ + fossil_warning("The remote server requested a client certificate for authentication. Specify the pathname to a file containing the PEM encoded certificate and private key with the --ssl-identity option or the ssl-identity setting."); + return 0; /* no cert available */ +} /* ** Call this routine once before any other use of the SSL interface. ** This routine does initial configuration of the SSL module. */ @@ -91,10 +100,22 @@ SSL_load_error_strings(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms(); sslCtx = SSL_CTX_new(SSLv23_client_method()); X509_STORE_set_default_paths(SSL_CTX_get_cert_store(sslCtx)); + + /* Load client SSL identity, preferring the filename specified on the command line */ + const char *identityFile = ( g.zSSLIdentity!= 0) ? g.zSSLIdentity : db_get("ssl-identity", 0); + if( identityFile!=0 && identityFile[0]!='\0' ){ + if( SSL_CTX_use_certificate_file(sslCtx, identityFile, SSL_FILETYPE_PEM)!= 1 + || SSL_CTX_use_PrivateKey_file(sslCtx, identityFile, SSL_FILETYPE_PEM)!=1 ){ + fossil_fatal("Could not load SSL identity from %s", identityFile); + } + } + /* Register a callback to tell the user what to do when the server asks for a cert */ + SSL_CTX_set_client_cert_cb(sslCtx, ssl_client_cert_callback); + sslIsInit = 1; } } /* @@ -182,15 +203,24 @@ if( SSL_get_verify_result(ssl) != X509_V_OK ){ char *desc, *prompt; char *warning = ""; Blob ans; BIO *mem; + unsigned char md[32]; + unsigned int mdLength = 31; mem = BIO_new(BIO_s_mem()); X509_NAME_print_ex(mem, X509_get_subject_name(cert), 2, XN_FLAG_MULTILINE); BIO_puts(mem, "\n\nIssued By:\n\n"); X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 2, XN_FLAG_MULTILINE); + BIO_puts(mem, "\n\nSHA1 Fingerprint:\n\n "); + if(X509_digest(cert, EVP_sha1(), md, &mdLength)){ + int j; + for( j = 0; j < mdLength; ++j ) { + BIO_printf(mem, " %02x", md[j]); + } + } BIO_write(mem, "", 1); // null-terminate mem buffer BIO_get_mem_data(mem, &desc); if( hasSavedCertificate ){ warning = "WARNING: Certificate doesn't match the " Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -104,10 +104,11 @@ char *urlProxyAuth; /* Proxy-Authorizer: string */ char *urlFossil; /* The path of the ?fossil=path suffix on ssh: */ int dontKeepUrl; /* Do not persist the URL */ const char *zLogin; /* Login name. "" if not logged in. */ + const char *zSSLIdentity; /* Value of --ssl-identity option, filename of SSL client identity */ int useLocalauth; /* No login required if from 127.0.0.1 */ int noPswd; /* Logged in without password (on 127.0.0.1) */ int userUid; /* Integer user id */ /* Information used to populate the RCVFROM table */ @@ -249,10 +250,11 @@ g.fSqlStats = find_option("sqlstats", 0, 0)!=0; if( g.fSqlTrace ) g.fSqlStats = 1; g.fSqlPrint = find_option("sqlprint", 0, 0)!=0; g.fHttpTrace = find_option("httptrace", 0, 0)!=0; g.zLogin = find_option("user", "U", 1); + g.zSSLIdentity = find_option("ssl-identity", 0, 1); if( find_option("help",0,0)!=0 ){ /* --help anywhere on the command line is translated into ** "fossil help argv[1] argv[2]..." */ int i; char **zNewArgv = fossil_malloc( sizeof(char*)*(g.argc+2) );