⬆️ ⬇️

We construct a local cryptographic TLS proxy with an electronic signature HTTP API





Electronic bidding, public services, corporate electronic document management systems, remote banking services and other similar systems are in the area of ​​interest of regulators and have to use Russian cryptographic tools to ensure confidentiality and legal significance.



The main means of user interaction and information system is slowly but surely becoming the browser.

If you carefully consider the integration of popular browsers and Russian cryptographic tools, the following problems appear:



')

In this situation, the most universal solution is to put the implementation of TLS-GOST and EDS functions into a separate network application that receives requests from the browser on localhost, allows you to “tunnel” connections between the browser and remote web servers (real proxy), and also provides the HTTP API for EDS functionality, work with certificates, tokens, etc.

Not to say that the idea is new, but let's try to make some constructor for its implementation.



Details of the constructor will be:





Fans of modular architectural experimental solutions are invited under cat.







The main idea of ​​the solution is that part of the HTTP requests to the information web-system is processed by the remote web server, and some part is local.

In order to make this possible, a web server should be started on localhost, which is configured so that it sends requests containing “local” in the URL to the local web application, and requests that contain “remote” in the URL reverses in accordance with its configuration file. The local web application “listens” to the addresses “local / login” and “local / api”.



Requests to the local / api address are a set of specified POST requests that provide HTTP APIs for:





The request for the local / login address is processed by a local web application that provides a web interface for authorization on the Rutoken EDS Flash, since a token is required to establish a TLS connection with a remote server. After authorization on the device, the local web application "lifts" the TLS tunnel to the remote server. From a remote server, the browser receives web pages that can use the described cryptographic HTTP API by sending special requests to the local / api address.



The schematic diagram of the solution is shown in the picture:





Let us consider in more detail the components of the solution.



NGINX web server


This web-server is successfully launched on localhost without installation, with FLASH-memory Rutoken EDS Flash.



The configuration file looks like this:

worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; client_max_body_size 0; access_log nul; error_log logs/error.log warn; log_not_found off; keepalive_timeout 5; affect the upstream waiting time. gzip on; include headers.conf; include firewall.conf; include upstream.conf; server { listen 8000; root www; index index.php; location / { expires -1; try_files $uri $uri/ /index.php; } location /local/login { root www; try_files $uri /login.php; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } location ~ \.php$ { root www; try_files $uri =404; include fastcgi_pass.conf; fastcgi_index index.php; include fastcgi.conf; #limit_req zone=req_limit_per_ip burst=10 nodelay; #A bug found on Windows 8. } location /remote/ { proxy_pass http://localhost:1443; } location ~* /rewrite+\. { deny all; } } include vhosts/*.conf; } 




Section

 location /remote/ { proxy_pass http://localhost:1443; } 




Enables forwarding requests to localhost: 1443.

And on localhost: 1443, in turn, they are accepted by the local TLS proxy, which, using the Rutoken EDS Flash controller, establishes the TLS-GOST connection in accordance with its configuration file, authenticates the user with a digital certificate within the TLS protocol and forwards requests to the remote server and back - through NGINX to the user's browser.



sTunnel




When entering the local / login URL, if the user successfully logs into the Rutoken EDS Flash, the local web application, using php-cgi, starts the sTunnel process, passing it the token PIN as one of the command line parameters.

In addition, the local WEB application can configure sTunnel to use a specific certificate for client authentication within TLS.



STunnel, listening on localhost, waits for a request from the browser reversed by NGINX. Upon receiving the first request, it establishes a TLS connection with the remote server in accordance with its configuration file and then sends requests and responses over the established connection.







Sample configuration file:



 ;     verify=1 ;  :  TLS- client=yes ;   CAFile=ca2001_A-root.crt ;   TLS sslVersion=TLSv1 ;      taskbar=no ;    DEBUG=7 output = log ;  engine PKCS11_GOST      engine=pkcs11_gost ;   PKCS#11     engineCtrl=MODULE_PATH:rtpkcs11ecp.dll ; -  engine engineDefault=ALL [remote system] ;     engine PKCS11_GOST engineNum = 1 ;      cert=client.crt ; ID      key = a0:59:d9:62:0d:09:69:2f:b6:ba:2d:9b:da:5b:2b:4d:fe:75:05:19 ;       accept = localhost:1443 ;   connect = xxxx:443 sni = localhost ;     TLS ciphers = GOST2001-GOST89-GOST89 ; for IE TIMEOUTclose = 0 




This configuration gives this:





HTTP API module




The HTTP API module is implemented by a local web application written in PHP using PHP-CGI. It should be noted that PHP also runs successfully on localhost from the FLASH-memory of Rutoken EDS Flash.



Briefly about the implementation. At / local / api, the web application listens to incoming POST requests in json format.

Each query must have the func and prm fields. Func is the name of the required method, prm is an array with parameters for the method. Something like {func: 'someFunc', prm: ['param1', 'param2', ..., 'paramN']}



In response, the web application returns a standard object containing the return / error code fields and, possibly, the result - {res: resultObject, retcode: int}



To implement the methods, we need to get access to the functions of Rutoken EDS from PHP. For this purpose, we will use the C ++ library , which contains both the functionality of working with tokens, and support for digital certificates, certificate requests and CMS messages.

With the help of the swig project from this library, it was possible to make a PHP module (“PHP-PKI-Core”), whose functions are invoked via the PHP-CGI mechanism.



Below is a script that implements the cryptographic HTTP API:



 <?php session_start(); if (isset($_SERVER['HTTP_ORIGIN'])) { header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}"); } header('Access-Control-Allow-Credentials: true'); header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS"); header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization"); if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { header("HTTP/1.1 200 OK"); exit(); } header('Content-type: application/json; charset=UTF-8'); $result = new stdClass(); $valid_chars = array('_'); //   json $request = json_decode(file_get_contents('php://input'), true); if (isset($request["func"]) && ctype_alnum(str_replace($valid_chars, '', $request["func"]))) { //   $result->prm = $request["prm"]; //   $result->func = $request["func"]; //    , automatically generated by SWIG (http://www.swig.org) include_once("pkiWrapper.php"); $pkicore = new _CryptoCore(); try { //   ,       $pkicore->enumerateDevices(); //    -  PIN-  ID    if ($result->func == 'login') { call_user_func_array(array($pkicore, $result->func), $result->prm); $_SESSION['pin'] = $result->prm[1]; $_SESSION['tokenid'] = $result->prm[0]; $result->retcode = 1; } else if (isset($_SESSION["pin"]) && isset($_SESSION['tokenid'])) { //    $pkicore->login($_SESSION['tokenid'], $_SESSION['pin']); //     $result->res = call_user_func_array(array($pkicore, $result->func), $result->prm); $result->retcode = 1; } else { //    ,  $result->retcode = 1000; $result->error = "  "; } } catch (Exception $ex) { $result->retcode = 500; $result->error = $ex->getMessage(); unset($_SESSION['pin']); unset($_SESSION['tokenid']); } } else { $result->retcode = 500; $result->error = 'No function / bad name'; $result->text = $request["func"]; } //  . echo json_encode($result); ?> 


The main nuance here is that when processing each HTTP request it is required to produce a new login on the token. Which, in turn, requires the storage of the token ID and its PIN-code in the session. In terms of security requirements, the session should be stored in RAM, and not in a temporary file.



This is what the request to the sign HTTP API function looks like:

 POST /local/api HTTP/1.1 Host: localhost:8080 Connection: keep-alive Content-Length: 884 Accept: application/json, text/plain, */* Origin: http://localhost:1443 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36 Content-Type: application/json;charset=UTF-8 Referer: http://localhost:1443/ Accept-Encoding: gzip, deflate Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4 Cookie: PHPSESSID=uq7jdu714nb9f4jec3plc6lpp2; .ASPXAUTH=F33760B6909CE6628C96863CF87B3B1477B161EAB8F603594B4FCB22F182AE083869BEFDD62EF5DB54FA9E3005D3BB2FB7F1400D8BE40C74B522ACD64C427A7D6976AA65E98EB413F2F706F619186D2CF8D6D976961EF09D5742DC6812F15AA5F5ABB68516B0214DED7774C1664FAB7C {"func":"sign","prm":{"0":0,"1":"dc:48:b8:f2:cf:a8:36:0d:bd:37:33:61:f7:32:ff:1b:a7:65:db:ce","2":"<!PINPADFILE UTF8><N> <V>2245<N><V>71269<N><V>11.12.2014<N><V> <N><V>7707083893<N><V> 775003035<N> <V>  <N> <V>><N><V>044525225<N>  <V>30101810400000000225<N><V> \"-\"<N> <V>  (  )<N><V>044525187<N>  <V>30101810700000000187","3":false,"4":{"detached":true,"addUserCertificate":true,"useHardwareHash":true}}} 




Answer:

 HTTP/1.1 200 OK Server: nginx/1.4.4 Date: Thu, 26 Feb 2015 10:19:53 GMT Content-Type: application/json; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Access-Control-Allow-Origin: http://localhost:1443 Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization {"prm":[0,"dc:48:b8:f2:cf:a8:36:0d:bd:37:33:61:f7:32:ff:1b:a7:65:db:ce","<!PINPADFILE UTF8><N>\u041f\u043b\u0430\u0442\u0435\u0436\u043d\u043e\u0435 \u043f\u043e\u0440\u0443\u0447\u0435\u043d\u0438\u0435<V>2245<N>\u0421\u0443\u043c\u043c\u0430<V>71269<N>\u0414\u0430\u0442\u0430<V>11.12.2014<N>\u041f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044c<V>\u0424\u0413\u0423\u041f \u0421\u0435\u0440\u0432\u0438\u0441\u041a\u043e\u043d\u0442\u0430\u043a\u0442<N>\u0418\u043d\u043d<V>7707083893<N>\u041a\u041f\u041f<V> 775003035<N>\u041d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043b\u0430\u0442\u0435\u0436\u0430<V>\u0417\u0430 \u0442\u0435\u043b\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0443\u0441\u043b\u0443\u0433\u0438<N>\u0411\u0430\u043d\u043a \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044f<V>\u0421\u0431\u0435\u0440\u0431\u0430\u043d\u043a<N>\u0411\u0418\u041a<V>044525225<N>\u041d\u043e\u043c\u0435\u0440 \u0441\u0447\u0435\u0442\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044f<V>30101810400000000225<N>\u041f\u043b\u0430\u0442\u0435\u043b\u044c\u0449\u0438\u043a<V>\u0417\u0410\u041e \"\u0410\u043a\u0442\u0438\u0432-\u0441\u043e\u0444\u0442\"<N>\u0411\u0430\u043d\u043a \u043f\u043b\u0430\u0442\u0435\u043b\u044c\u0449\u0438\u043a\u0430<V>\u0411\u0430\u043d\u043a \u0412\u0422\u0411 (\u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0435 \u0430\u043a\u0446\u0438\u043e\u043d\u0435\u0440\u043d\u043e\u0435 \u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e)<N>\u0411\u0418\u041a<V>044525187<N>\u041d\u043e\u043c\u0435\u0440 \u0441\u0447\u0435\u0442\u0430 \u043f\u043b\u0430\u0442\u0435\u043b\u044c\u0449\u0438\u043a\u0430<V>30101810700000000187",false,{"detached":true,"addUserCertificate":true,"useHardwareHash":true}],"func":"sign","res":"MIIDmQYJKoZIhvcNAQcCoIIDijCCA4YCAQExDDAKBgYqhQMCAgkFADALBgkqhkiG 9w0BBwGgggGnMIIBozCCAVCgAwIBAgIBATAKBgYqhQMCAgMFADBUMQswCQYDVQQG EwJSVTEPMA0GA1UEBxMGTW9zY293MSIwIAYDVQQKFBlPT08gIkdhcmFudC1QYXJr LVRlbGVjb20iMRAwDgYDVQQDEwdUZXN0IENBMB4XDTE1MDIyNjEzMzU1MloXDTE2 MDIyNjEzMzU1MlowGTEXMBUGA1UEAx4OBDIEQwRGBDIEQwRGBDIwYzAcBgYqhQMC AhMwEgYHKoUDAgIjAQYHKoUDAgIeAQNDAARAwXoeizbUWzwA1mkNfWpSQ8eslAIZ dpvMv7qM0n9KTehYyEj1+vkAmMZ9UOT3cE1C6E4fUjWJgXHJL6Ttu5rw86NEMEIw JQYDVR0lBB4wHAYIKwYBBQUHAwIGCCsGAQUFBwMEBgYpAQEBAQIwCwYDVR0PBAQD AgKkMAwGA1UdEwEB/wQCMAAwCgYGKoUDAgIDBQADQQBH8ihBKIWIILO3JJu4j4Bk NN5lQ4n5Y0HUozpHwMfCvR98rLHMmjGFwjdQHHXSrW6eCHryVD8oeV47+hO/U5HM MYIBuTCCAbUCAQEwWTBUMQswCQYDVQQGEwJSVTEPMA0GA1UEBxMGTW9zY293MSIw IAYDVQQKFBlPT08gIkdhcmFudC1QYXJrLVRlbGVjb20iMRAwDgYDVQQDEwdUZXN0 IENBAgEBMAoGBiqFAwICCQUAoIH6MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEw HAYJKoZIhvcNAQkFMQ8XDTE1MDIyNjEzMzYxNVowLwYJKoZIhvcNAQkEMSIEIIF3 veehPcgZGmj4rvJQtNwSYf7VzDZNraAiDR89eoysMIGOBgkqhkiG9w0BCQ8xgYAw fjALBglghkgBZQMEASowCAYGKoUDAgIJMAgGBiqFAwICFTALBglghkgBZQMEARYw CwYJYIZIAWUDBAECMAoGCCqGSIb3DQMHMA4GCCqGSIb3DQMCAgIAgDANBggqhkiG 9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG9w0DAgIBKDAKBgYqhQMCAhMFAARAZGY6 ZjA6MzU6ZDY6N2M6MjI6ZjA6MDc6ZDc6OGU6NDY6Nzk6YzU6YzU6NmU6MmQ6YzA6 ODg6ZWU6MDU6NjU6NA==y} 




In the res field there is a signature in the CMS format detached from the transmitted data.



It should be noted that using the PHP-PKI-Core module, the developer can make the cryptographic HTTP API as it is convenient for him.



Conclusion



The design, with all its apparent bulkiness, consists of 4 running applications - nginx, sTunnel, php, php-cgi.

These applications, as I have already noted, do not require installation, are stored on FLASH-memory and run from it.

It is not a big deal to write a control application that starts them neatly and “extinguishes” the processes at the end of work or in the case of token disconnection.

Source: https://habr.com/ru/post/247911/



All Articles