How to use Haxe SSL Socket?
Asked Answered
C

1

7

I'm trying to setup an https server but I can't find any example on how to use Haxe sys.ssl.Socket and the documentation doesn't quite speak to me.

So far I got the following code which throw an 'Access violation'.

var _aSocketDistant = new List<Socket>();

var _oSocketMaster =  new SocketSSL();
_oSocketMaster.setCA(Certificate.loadFile('ssl/homeplanet.pem'));
_oSocketMaster.setCertificate( 
    Certificate.loadFile('ssl/homeplanet.pem'), 
    Key.loadFile('ssl/homeplanet.key', false, 'homeplanet') 
);
_oSocketMaster.setHostname('localhost');

_oSocketMaster.bind( new Host( 'localhost' ), 8000);
_oSocketMaster.setBlocking( false );
_oSocketMaster.listen( 9999 );

while(true) {

    // Accepting socket
    var oSocketDistant = _oSocketMaster.accept();
    if ( oSocketDistant != null ) {
        trace( 'opening : ' + oSocketDistant.peer() );
        oSocketDistant.setBlocking( false );
        _aSocketDistant.add( oSocketDistant );
    }

    // Trying to read from each socket 
    for ( oSocketDistant in _aSocketDistant ) {
        try {
            Sys.print( oSocketDistant.input.readString(1) );
        } catch ( e :Dynamic ) {
            if ( e != Error.Blocked )
                throw e;
        }
    }
}

This give me the following result :

Uncaught exception: Access violation
Called from sys.ssl.Socket.accept(D:\HaxeToolkit4\haxe\std/hl/_std/sys/ssl/Socket.hx:203)
Called from $Main.main(Main.hx:39)
Called from fun$517(?:1)

The key/certificate files where generated using this tutorial.

Am I using the socket correctly ?

Crystallography answered 2/6, 2019 at 20:19 Comment(0)
O
10

Ok, I decided I wanted to test Haxe SSL sockets myself. Due to lots of caveats, this will probably not be an answer for your problem, but maybe some of the tidbits will help. And starting from "something that works" is better than nothing!

First, I'm on Linux. I've found that sockets (and threads) can exhibit different behaviors and problems on Windows than on Linux / OSX.

Second, I'm first trying this with a valid hostname. I've never used SSL on localhost, and wanted to remove all unknowns. So I happen to have a valid cert/key that I'm using. In the code below, it's referred to as foo.example.com. You can get a free cert for a domain you own at letsencrypt.org.

Third, I ran into an issue in the Haxe std library. To get around it, I simply changed line 17 in haxe/std/cpp/_std/sys/ssl/Key.hx:

var str = data.toString(); // cpp.Lib.stringReference(data);

Fourth, I have no idea about the "access violation" problem. That's gonna be Windows-specific, likely. I would have guessed perhaps a permissions or firewall problem, but googling "Windows socket access violation", I see lots of random discussion.

Finally, I'm not sure your non-blocking sockets with while-loops is a good idea. Maybe it could be done that way... but I've always had better luck with blocking sockets and threads (again, threads may behave better on 'nix than Windows.)

Note: If you do work with non-blocking sockets, sometimes you have to catch / ignore both haxe.io.Error.Blocked and haxe.io.Error.Custom(Blocked). An irritation, but meh. Use this:

  try {
    Sys.print( oSocketDistant.input.readString(1) );
  } catch ( e:haxe.io.Error ) {
    switch e {
      case haxe.io.Error.Blocked: // no problem
      case haxe.io.Error.Custom(c) if (c==haxe.io.Error.Blocked): // no problem
      default: throw e;
    }
  } catch ( e:haxe.io.Eof ) {
    trace('Got Eof');
  }

It's more efficient to use threads with blocking sockets. That way, the thread simply sleeps until the socket wakes it up. Which is what you want, so the CPU isn't spinning in a while loop, endlessly checking for unblocked sockets.

So I've modified your code a bit. My example uses the main thread to accept connections, and then passes the socket off to a reader thread. The reader thread prints anything received (like your example), and then exits on Eof.

import sys.net.Host;
import sys.net.Socket;
import sys.ssl.Socket as SocketSSL;
import sys.ssl.Certificate;
import sys.ssl.Key;

import cpp.vm.Mutex;
import cpp.vm.Thread;

class Main
{
  static var _mutex:Mutex = new Mutex();

  public static function main()
  {
    var _oSocketMaster =  new SocketSSL();
    var cert = Certificate.loadFile('my_chain.pem');
    _oSocketMaster.setCA(cert);
    _oSocketMaster.setCertificate(cert, 
                                  Key.loadFile('my_key.key'));
    _oSocketMaster.setHostname('foo.example.com');

    // e.g. for an application like an HTTPs server, the client
    // doesn't need to provide a certificate. Otherwise we get:
    // Error: SSL - No client certification received from the client, but required by the authentication mode
    _oSocketMaster.verifyCert = false;

    // Binding 0.0.0.0 means, listen on "any / all IP addresses on this host"
    _oSocketMaster.bind( new Host( '0.0.0.0' ), 8000);
    _oSocketMaster.listen( 9999 );

    while(true) {

      // Accepting socket
      trace('waiting to accept...');
      var oSocketDistant:SocketSSL = _oSocketMaster.accept();
      if ( oSocketDistant != null ) {
        trace( 'got connection from : ' + oSocketDistant.peer() );
        oSocketDistant.handshake(); // This may not be necessary, if !verifyCert

        // Spawn a reader thread for this connection:
        var thrd = Thread.create(reader);
        trace('sending socket...');
        thrd.sendMessage(oSocketDistant);
        trace('ok...');
      }

    }
  }

  static function reader()
  {
    var oSocketDistant:sys.net.Socket = cast Thread.readMessage(true);
    trace('new reader thread...');

    while(true) {

      try {
        Sys.print( oSocketDistant.input.readString(1) );
      } catch ( e:haxe.io.Eof ) {
        trace('Eof, reader thread exiting...');
        return;
      } catch ( e:Dynamic ) {
        trace('Uncaught: ${ e }'); // throw e;
      }
    }
  }
}

So, let's see it in action!

I compile and start the above server in one terminal:

> haxe -main Main -debug -cpp out && ./out/Main-debug
...compiling info removed...
Main.hx:37: waiting to accept...

And then I connect from another terminal with a client that's a command line utility for testing ssl connections:

> openssl s_client -connect foo.example.com:8000
...lots of info about the cert...
SSL handshake has read 3374 bytes and written 370 bytes
Verification: OK
---

And it hangs there, waiting for you to type input. On the server side we see:

Main.hx:38: got connection from : { host => Host, port => 57394 }
Main.hx:43: sending socket...
Main.hx:45: ok...
Main.hx:35: waiting to accept...
Main.hx:54: new reader thread...

We can open many clients in separate terminals, they each get their own reader thread. Typing messages into the client terminals show up in the server terminal, so the reader threads are working.

On the client, CTRL+C to exit, and on the server we see:

Main.hx:61: Eof, reader thread exiting...

Everything is working as expected!

Onomatology answered 20/6, 2019 at 19:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.