2

I wrote a small javascript to test digest authentication on monerod and monero-wallet-rpc.

When running on a http (no tls) website, authentication works, and I get my response.

Then trying the same javascript on a https website, I get the folowing log output from the wallet server.

D handle_accept
     D New server for RPC connections, SSL autodetection
     D set m_connection_type = RPC 
     D Spawned connection #4 to 0.0.0.0 currently we have sockets count:2
     D test, connection constructor set m_connection_type=1
     T [sock 0x7f9bdc044680] new connection from client.ip.address:62186 INC to server.ip.address:38083, total sockets objects 2
     T New connection from host client.ip.address: 0
     T Setting 00:00:10 expiry
     D  connection type RPC server.ip.address:38083 <--> client.ip.address:62186 (via client.ip.address:62186)
     T Setting 00:00:10.102921 expiry
     D SSL detection buffer, 517 bytes: 22 3 1 2 0 1 0 1 252
     D That looks like SSL
     D handle_accept
     D New server for RPC connections, SSL autodetection
     D set m_connection_type = RPC 
     D Spawned connection #5 to 0.0.0.0 currently we have sockets count:3
     D test, connection constructor set m_connection_type=1
     T [sock 0x7f9bdc02dce0] new connection from client.ip.address:61715 INC to server.ip.address:38083, total sockets objects 3
     T New connection from host client.ip.address: 1
     T Setting 00:00:10 expiry
     D  connection type RPC server.ip.address:38083 <--> client.ip.address:61715 (via client.ip.address:61715)
     T Setting 00:00:10.072703 expiry
     D SSL detection buffer, 517 bytes: 22 3 1 2 0 1 0 1 252
     D That looks like SSL
     E SSL handshake failed, connection dropped: short read
     E SSL handshake failed
     T Closed connection from host client.ip.address: 2
     D SSL handshake success
     T [sock 9] Socket destroyed
     D Destructing connection #4 to client.ip.address
     T [sock 8] Some not success at read: sslv3 alert bad certificate:336151570
     T [sock 8] Some problems at read: sslv3 alert bad certificate:336151570
     T Closed connection from host client.ip.address: 1
     T [sock 8] Socket destroyed
     D Destructing connection #3 to client.ip.address

And I get a generic error in Firefox.

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://server.ip.address:38083/json_rpc. (Reason: CORS request did not succeed).

What am I missing? Do I need to tell monero about my certificate?

EDIT: Added a successful session. No HTTPS and No certificates.

Website and the monero stuff is on the same host/ip-address.

 T Throttle <<< global-IN: packet of ~496b  (from 496 b) Speed AVG=   0[w=9.757]    0[w=9.757] /  Limit=16 KiB/sec  [496 0 0 0 0 0 0 0 0 0 ]
 T HTTP HEAD:
 T Host: host.example.com:38083
 T User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
 T Accept: */*
 T Accept-Language: en,da;q=0.5
 T Accept-Encoding: gzip, deflate
 T Access-Control-Request-Method: POST
 T Access-Control-Request-Headers: content-type
 T Referer: http://host.example.com/test-05/
 T Origin: http://host.example.com
 T DNT: 1
 T Connection: keep-alive
 T Pragma: no-cache
 T Cache-Control: no-cache
 T 
 T HTTP_RESPONSE_HEAD: << 
 T HTTP/1.1 200 OK
 T Server: Epee-based
 T Content-Length: 0
 T Last-Modified: Thu, 19 Mar 2020 14:55:13 GMT
 T Accept-Ranges: bytes
 T Access-Control-Allow-Origin: http://host.example.com
 T Access-Control-Expose-Headers: www-authenticate
 T Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
 T Access-Control-Allow-Methods: POST, PUT, GET, OPTIONS
 T 
 T Moving counter buffer by 1 second 0 < 300414 (last time 0)
 T Throttle throttle_speed_out: packet of ~373b  (from 373 b) Speed AVG=   0[w=1]    0[w=1] /  Limit=16 KiB/sec  [373 0 0 0 0 0 0 0 0 0 ]
 D do_send_chunk() NOW SENSD: packet=373 B
 T Setting 00:05:00 expiry
 T Setting 00:05:00 expiry
 T [client.ip.addr:60290 INC] [sock 8] Async send calledback 373
 D handle_accept
 D New server for RPC connections, SSL autodetection
 D set m_connection_type = RPC 
 D Spawned connection #8 to 0.0.0.0 currently we have sockets count:3
 D test, connection constructor set m_connection_type=1
 T [sock 0x7f147801ec40] new connection from client.ip.addr:34239 INC to server.ip.addr:38083, total sockets objects 3
 T New connection from host client.ip.addr: 1
 T Setting 00:00:10 expiry
 D  connection type RPC server.ip.addr:38083 <--> client.ip.addr:34239 (via client.ip.addr:34239)
 T Setting 00:00:09.738196 expiry
 D SSL detection buffer, 538 bytes: 80 79 83 84 32 47 106 115 111
 D That does not look like SSL
 T Moving counter buffer by 1 second 0 < 300415 (last time 0)
 T Throttle throttle_speed_in: packet of ~538b  (from 538 b) Speed AVG=   0[w=1]    0[w=1] /  Limit=16 KiB/sec  [538 0 0 0 0 0 0 0 0 0 ]
 T Moving counter buffer by 1 second 300413 < 300415 (last time 300414)
 T Moving counter buffer by 1 second 300414 < 300415 (last time 300415)
 T Throttle <<< global-IN: packet of ~538b  (from 538 b) Speed AVG=   0[w=9.159]    0[w=9.159] /  Limit=16 KiB/sec  [538 0 496 0 0 0 0 0 0 0 ]
 T HTTP HEAD:
 T Host: host.example.com:38083
 T User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
 T Accept: */*
 T Accept-Language: en,da;q=0.5
 T Accept-Encoding: gzip, deflate
 T Content-Type: application/json; charset=utf-8
 T Content-Length: 61
 T Origin: http://host.example.com
 T DNT: 1
 T Connection: keep-alive
 T Referer: http://host.example.com/test-05/
 T Pragma: no-cache
 T Cache-Control: no-cache
 T 
 T HTTP_RESPONSE_HEAD: << 
 T HTTP/1.1 401 Unauthorized
 T Server: Epee-based
 T Content-Length: 98
 T Content-Type: text/html
 T Last-Modified: Thu, 19 Mar 2020 14:55:14 GMT
 T Accept-Ranges: bytes
 T Access-Control-Allow-Origin: http://host.example.com
 T Access-Control-Expose-Headers: www-authenticate
 T Access-Control-Allow-Methods: POST, PUT, GET, OPTIONS
 T WWW-authenticate: Digest qop="auth",algorithm=MD5,realm="monero-rpc",nonce="TxiQqEIvHWWbjks8rvdMRA==",stale=false
 T WWW-authenticate: Digest qop="auth",algorithm=MD5-sess,realm="monero-rpc",nonce="TxiQqEIvHWWbjks8rvdMRA==",stale=false
 T 
 T Moving counter buffer by 1 second 0 < 300415 (last time 0)
 T Throttle throttle_speed_out: packet of ~665b  (from 665 b) Speed AVG=   0[w=1]    0[w=1] /  Limit=16 KiB/sec  [665 0 0 0 0 0 0 0 0 0 ]
 D do_send_chunk() NOW SENSD: packet=665 B
 T Setting 00:05:00 expiry
 T Setting 00:05:00 expiry
 T [client.ip.addr:34239 INC] [sock 9] Async send calledback 665
 T Throttle throttle_speed_in: packet of ~758b  (from 758 b) Speed AVG=   0[w=1]    0[w=1] /  Limit=16 KiB/sec  [1296 0 0 0 0 0 0 0 0 0 ]
 T Throttle <<< global-IN: packet of ~758b  (from 758 b) Speed AVG=   0[w=9.425]    0[w=9.425] /  Limit=16 KiB/sec  [1296 0 496 0 0 0 0 0 0 0 ]
 T HTTP HEAD:
 T Host: host.example.com:38083
 T User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
 T Accept: */*
 T Accept-Language: en,da;q=0.5
 T Accept-Encoding: gzip, deflate
 T Content-Type: application/json; charset=utf-8
 T Authorization: Digest username="rpc_user", realm="monero-rpc", nonce="TxiQqEIvHWWbjks8rvdMRA==", uri="/", algorithm="MD5", response="34638b90a7ae09644c8559fa7e1dc4a4", qop="auth", nc=00000001, cnonce="bd5fd9b093dccaa1"
 T Content-Length: 61
 T Origin: http://host.example.com
 T DNT: 1
 T Connection: keep-alive
 T Referer: http://host.example.com/test-05/
 T Pragma: no-cache
 T Cache-Control: no-cache
 T 
 I HTTP [client.ip.addr] POST /json_rpc
 I [client.ip.addr:34239 INC] Calling RPC method get_version
 D /json_rpc[get_version] processed with 0/0/0ms
 T HTTP_RESPONSE_HEAD: << 
 T HTTP/1.1 200 Ok
 T Server: Epee-based
 T Content-Length: 104
 T Content-Type: application/json
 T Last-Modified: Thu, 19 Mar 2020 14:55:14 GMT
 T Accept-Ranges: bytes
 T Access-Control-Allow-Origin: http://host.example.com
 T Access-Control-Expose-Headers: www-authenticate
 T Access-Control-Allow-Methods: POST, PUT, GET, OPTIONS
 T 
 T Throttle throttle_speed_out: packet of ~434b  (from 434 b) Speed AVG=   0[w=1]    0[w=1] /  Limit=16 KiB/sec  [1099 0 0 0 0 0 0 0 0 0 ]
 D do_send_chunk() NOW SENSD: packet=434 B
 T Setting 00:05:00 expiry
 T Setting 00:05:00 expiry
 T [client.ip.addr:34239 INC] [sock 9] Async send calledback 434
 T [sock 8] Some not success at read: End of file:2
 T [sock 8] peer closed connection
 T Closed connection from host client.ip.addr: 2
 T [sock 8] Socket destroyed
 D Destructing connection #6 to client.ip.addr
 T [sock 9] Some not success at read: End of file:2
 T [sock 9] peer closed connection
 T Closed connection from host client.ip.addr: 1
 T [sock 9] Socket destroyed
 D Destructing connection #7 to client.ip.addr

Certificate trusted by Firefox:

enter image description here

The code that does the talking:

function httpClient (config, callback) {
    let xhr = new XMLHttpRequest();

    xhr.onload = () => {
      //Step 4: Receive original request data
      if (xhr.status == 200) {
        callback({
          result: xhr.status,
          message: xhr.statusText,
          response: xhr.responseText
          })
      } else {
        callback({
          result: xhr.status,
          message: xhr.statusText,
          response: xhr.responseText
          })
      }
    };

    xhr.onerror = (e) => {
      console.log("Error:", e);
      console.log({
        result: xhr.status,
        message: xhr.statusText,
        response: xhr.responseText
      });
      callback({
          result: xhr.status,
          message: xhr.statusText,
          response: 'Some error ocurred during this request.'
        })
    };

    xhr.onreadystatechange = (e) => {
      if (xhr.readyState >= 2 && xhr.status == 401) {
        console.log("readyState:", xhr.readyState, xhr.status, xhr.statusText);

        let authHeader = xhr.getResponseHeader('www-authenticate');
        let authObject = parseResponseHeader(authHeader);
        let authResponse = buildAuthorizeHeader(config.auth, authObject);
        config.headers["Authorization"] = authResponse;
        xhr.abort();
        httpClient(config, callback);
      }
      else
        console.log("readyState:", xhr.readyState, xhr.status, xhr.statusText);
    };

    config.protocol = config.protocol.replace(/\:$/, '');

    let encodedURI = encodeURI(config.protocol + '://' + config.url);
    xhr.open(config.method, encodedURI, true);
    xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');

    for (let [key, value] of Object.entries(config.headers)) {
      xhr.setRequestHeader(key, value);
    }

    xhr.withCredentials = false;
    if (config.method.toUpperCase() == 'POST' && typeof config.data == "object" ) {
      xhr.send(JSON.stringify(config.data))
    } else {
      xhr.send()
    }

    const buildAuthorizeHeader = (config, tokensObj ) => {
      tokensObj.headers["cnonce"] = 'bd5fd9b093dccaa1';
      tokensObj.headers["nc"] = '00000001';
      tokensObj.headers["algorithm"] = 'MD5';
      tokensObj.headers["method"] = 'POST';
      tokensObj.headers["domain"] = '/';

      let HA1 = CryptoJS.MD5(config.user + ':' + tokensObj.headers["realm"] + ':' + config.pass).toString();
      let HA2 = CryptoJS.MD5(tokensObj.headers["method"] + ':' + tokensObj.headers["domain"]).toString();
      let authResponse = CryptoJS.MD5(HA1 + 
        ':' + tokensObj.headers["nonce"] + 
        ':' + tokensObj.headers["nc"] + 
        ':' + tokensObj.headers["cnonce"] + 
        ':' + tokensObj.headers["qop"] + 
        ':' + HA2).toString();

      let responseContentHeader = 'Digest username="' + config.user + '"' + 
        ', realm="' + tokensObj.headers["realm"] + '"' + 
        ', nonce="' + tokensObj.headers["nonce"] + '"' + 
        ', uri="' + tokensObj.headers["domain"] + '"' + 
        ', algorithm="' + tokensObj.headers["algorithm"] + '"' +
        ', response="' + authResponse + '"' + 
        ', qop="' + tokensObj.headers["qop"] + '"' + 
        ', nc=' + tokensObj.headers["nc"] + 
        ', cnonce="' + tokensObj.headers["cnonce"] + '"';

      return responseContentHeader;
    }

    const parseResponseHeader = (h) => {
      let auth = {
        headers : {}
      };
      var scre = /^\w+/;
      var scheme = scre.exec(h);
      auth.scheme = scheme[0];

      var nvre = /(\w+)=['"]([^'"]+)['"]/g;
      var pairs = h.match(nvre);

      var vre = /(\w+)=['"]([^'"]+)['"]/;
      var i = 0;
      for (; i < pairs.length; i++) {
        var v = vre.exec(pairs[i]);
        if (v) {
          auth.headers[v[1]] = v[2];
        }
      }
      return auth;
    }

}
Mogens TrasherDK
  • 484
  • 3
  • 11

1 Answers1

1

You are getting a CORS error:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://server.ip.address:38083/json_rpc. (Reason: CORS request did not succeed).

The wallet RPC has a parameter --rpc-access-control-origins which you will need to set to the domain hosting your javascript.

From the help:

  --rpc-access-control-origins arg      Specify a comma separated list of 
                                        origins to allow cross origin resource 
                                        sharing

Thus if the site hosting the javascript is https://www.example.com, run the monero-wallet-rpc with --rpc-access-control-origins https://www.example.com.

Aside from this, and dependent on your browser settings, you may need to provide your own SSL certificate at wallet startup (via the --rpc-ssl-... wallet RPC parameters), as the browser may not like the auto-generated certificates the wallet creates. For example, some browsers strictly prohibit self-signed certificates.

jtgrassie
  • 19,601
  • 4
  • 17
  • 54