aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Austein <sra@hactrn.net>2017-01-05 23:43:22 -0500
committerRob Austein <sra@hactrn.net>2017-01-05 23:43:22 -0500
commit3c20fd189648b8182edbafed572898d1af744aa6 (patch)
treedb6d82e0472554e5d36546ae85fa35f469b7e17a
parent17366b5296920cd37f716426fd7c653063dd5d78 (diff)
Whack multiplexer to handle console too.
Renamed multiplexer to cryptech_muxd, since it now handles both RPC and CTY. Added new program cryptech_console to act as client for CTY multiplexer. Might want to add console logging capability eventually, not today. Probably want to incorporate UART probing (what cryptech_probe does now) eventually, also not today.
-rwxr-xr-xcryptech_console119
-rwxr-xr-xcryptech_muxd (renamed from cryptech_rpcmuxd)154
-rw-r--r--hal_internal.h2
-rw-r--r--libhal.py2
4 files changed, 244 insertions, 33 deletions
diff --git a/cryptech_console b/cryptech_console
new file mode 100755
index 0000000..80ec15d
--- /dev/null
+++ b/cryptech_console
@@ -0,0 +1,119 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2017, NORDUnet A/S All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+# - Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# - Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# - Neither the name of the NORDUnet nor the names of its contributors may
+# be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+Console client shim to work with Cryptech Python multiplexer.
+"""
+
+import os
+import sys
+import socket
+import atexit
+import termios
+import argparse
+
+import tornado.iostream
+import tornado.ioloop
+import tornado.gen
+
+
+class FemtoTerm(object):
+
+ def __init__(self, s):
+ self.termios_setup()
+ self.stdin_stream = tornado.iostream.PipeIOStream(sys.stdin.fileno())
+ self.stdout_stream = tornado.iostream.PipeIOStream(sys.stdout.fileno())
+ self.socket_stream = tornado.iostream.IOStream(s)
+ self.closed = False
+
+ def termios_setup(self):
+ self.fd = sys.stdin.fileno()
+ self.old_tcattr = termios.tcgetattr(self.fd)
+ self.new_tcattr = termios.tcgetattr(self.fd)
+ atexit.register(self.termios_teardown)
+ self.new_tcattr[3] &= ~(termios.ICANON | termios.ECHO) # | termios.ISIG
+ self.new_tcattr[6][termios.VMIN] = 1
+ self.new_tcattr[6][termios.VTIME] = 0
+ termios.tcsetattr(self.fd, termios.TCSANOW, self.new_tcattr)
+
+ def termios_teardown(self):
+ termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_tcattr)
+
+ @tornado.gen.coroutine
+ def stdin_loop(self):
+ try:
+ while not self.closed:
+ buffer = yield self.stdin_stream.read_bytes(1024, partial = True)
+ yield self.socket_stream.write(buffer.replace("\n", "\r"))
+ except tornado.iostream.StreamClosedError:
+ self.closed = True
+
+ @tornado.gen.coroutine
+ def stdout_loop(self):
+ try:
+ while not self.closed:
+ buffer = yield self.socket_stream.read_bytes(1024, partial = True)
+ yield self.stdout_stream.write(buffer.replace("\r\n", "\n"))
+ except tornado.iostream.StreamClosedError:
+ self.closed = True
+
+
+@tornado.gen.coroutine
+def main():
+ parser = argparse.ArgumentParser(formatter_class = argparse.ArgumentDefaultsHelpFormatter)
+ parser.add_argument("-v", "--verbose", action = "store_true", help = "produce human-readable output")
+ parser.add_argument("-d", "--debug", action = "store_true", help = "blather about what we're doing")
+
+ parser.add_argument("--cty-socket", help = "CTY PF_UNIX socket name",
+ default = os.getenv("CRYPTECH_CTY_CLIENT_SOCKET_NAME", "/tmp/.cryptech_muxd.cty"))
+
+ args = parser.parse_args()
+
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(args.cty_socket)
+
+ term = FemtoTerm(s)
+
+ if False:
+ yield [term.stdin_loop(), term.stdout_loop()]
+
+ else:
+ stdout_future = term.stdout_loop()
+ stdin_future = term.stdin_loop()
+ yield stdout_future
+ sys.stdin.close()
+ yield stdin_future
+
+
+if __name__ == "__main__":
+ try:
+ tornado.ioloop.IOLoop.current().run_sync(main)
+ except KeyboardInterrupt:
+ pass
diff --git a/cryptech_rpcmuxd b/cryptech_muxd
index d08d6df..80be443 100755
--- a/cryptech_rpcmuxd
+++ b/cryptech_muxd
@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
-# Copyright (c) 2016, NORDUnet A/S All rights reserved.
+# Copyright (c) 2016-2017, NORDUnet A/S All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
@@ -87,14 +87,9 @@ class SerialIOStream(tornado.iostream.BaseIOStream):
def __init__(self, device, baudrate = 921600, debug = False, *pargs, **kwargs):
self.serial = serial.Serial(device, baudrate, timeout = 0, write_timeout = 0)
- self.queues = weakref.WeakValueDictionary()
- self.debug = debug
- self.hsm_write_lock = tornado.locks.Lock()
+ self.debug = debug
super(SerialIOStream, self).__init__(*pargs, **kwargs)
- # The next four methods are required: BaseIOStream is an abstract
- # class, we provide a driver by overriding them.
-
def fileno(self):
return self.serial.fileno()
@@ -107,17 +102,46 @@ class SerialIOStream(tornado.iostream.BaseIOStream):
def read_from_fd(self):
return self.serial.read(self.read_chunk_size) or None
+
+class PFUnixServer(tornado.tcpserver.TCPServer):
+ """
+ Variant on tornado.tcpserver.TCPServer, listening on a PF_UNIX
+ (aka PF_LOCAL) socket instead of a TCP socket.
+ """
+
+ def listen(self, filename, mode = 0600):
+ self.socket_filename = filename
+ self.add_socket(tornado.netutil.bind_unix_socket(filename, mode))
+ atexit.register(self.atexit_unlink)
+
+ def atexit_unlink(self):
+ try:
+ os.unlink(self.socket_filename)
+ except:
+ pass
+
+
+class RPCIOStream(SerialIOStream):
+ """
+ Tornado IOStream for a serial RPC channel.
+ """
+
+ def __init__(self, *pargs, **kwargs):
+ super(RPCIOStream, self).__init__(*pargs, **kwargs)
+ self.queues = weakref.WeakValueDictionary()
+ self.rpc_input_lock = tornado.locks.Lock()
+
@tornado.gen.coroutine
- def hsm_write(self, query, handle, queue):
+ def rpc_input(self, query, handle, queue):
"Send a query to the HSM."
if self.debug:
sys.stdout.write("+send: {}\n".format(":".join("{:02x}".format(ord(c)) for c in query)))
self.queues[handle] = queue
- with (yield self.hsm_write_lock.acquire()):
+ with (yield self.rpc_input_lock.acquire()):
yield self.write(query)
@tornado.gen.coroutine
- def hsm_read_loop(self):
+ def rpc_output_loop(self):
"Handle reply stream HSM -> network."
while True:
reply = yield self.read_until(SLIP_END)
@@ -129,15 +153,11 @@ class SerialIOStream(tornado.iostream.BaseIOStream):
self.queues[handle].put_nowait(reply)
-class UnixServer(tornado.tcpserver.TCPServer):
+class RPCServer(PFUnixServer):
"""
- Variant on tornado.tcpserver.TCPServer, listening on a PF_UNIX
- (aka PF_LOCAL) socket instead of a TCP socket.
+ Serve multiplexed Cryptech RPC over a PF_UNIX socket.
"""
- def listen(self, filename, mode = 0600):
- self.add_socket(tornado.netutil.bind_unix_socket(filename, mode))
-
def set_serial(self, serial_stream):
self.serial = serial_stream
@@ -153,28 +173,100 @@ class UnixServer(tornado.tcpserver.TCPServer):
if len(query) < 9:
continue
query = slip_encode(client_handle_set(slip_decode(query), handle))
- yield self.serial.hsm_write(query, handle, queue)
+ yield self.serial.rpc_input(query, handle, queue)
reply = yield queue.get()
yield stream.write(SLIP_END + reply)
except tornado.iostream.StreamClosedError:
closed = True
+class CTYIOStream(SerialIOStream):
+ """
+ Tornado IOStream for a serial console channel.
+ """
-parser = argparse.ArgumentParser(formatter_class = argparse.ArgumentDefaultsHelpFormatter)
-parser.add_argument("-v", "--verbose", action = "store_true", help = "produce human-readable output")
-parser.add_argument("-d", "--debug", action = "store_true", help = "blather about what we're doing")
-parser.add_argument("device", nargs = "?", help = "serial device name",
- default = os.getenv("CRYPTECH_RPC_CLIENT_SERIAL_DEVICE", "/dev/ttyUSB0"))
-parser.add_argument("socket", nargs = "?", help = "PF_UNIX socket name",
- default = os.getenv("CRYPTECH_RPC_CLIENT_SOCKET_NAME", "/tmp/.cryptech_rpcmuxd"))
-args = parser.parse_args()
+ def __init__(self, *pargs, **kwargs):
+ super(CTYIOStream, self).__init__(*pargs, **kwargs)
+ self.attached_cty = None
-serial_stream = SerialIOStream(device = args.device, debug = args.debug)
+ @tornado.gen.coroutine
+ def cty_output_loop(self):
+ while True:
+ buffer = yield self.read_bytes(1024, partial = True)
+ try:
+ if self.attached_cty is not None:
+ yield self.attached_cty.write(buffer)
+ except tornado.iostream.StreamClosedError:
+ pass
-unix_server = UnixServer()
-unix_server.set_serial(serial_stream)
-unix_server.listen(args.socket)
-atexit.register(os.unlink, args.socket)
+class CTYServer(PFUnixServer):
+ """
+ Serve Cryptech console over a PF_UNIX socket.
+ """
+
+ def set_serial(self, serial_stream):
+ self.serial = serial_stream
+
+ @tornado.gen.coroutine
+ def handle_stream(self, stream, address):
+ "Handle one network connection."
-tornado.ioloop.IOLoop.current().run_sync(serial_stream.hsm_read_loop)
+ if self.serial.attached_cty is not None:
+ yield stream.write("[Console already in use, sorry]\n")
+ stream.close()
+ return
+
+ try:
+ self.serial.attached_cty = stream
+ while self.serial.attached_cty is stream:
+ yield self.serial.write((yield stream.read_bytes(1024, partial = True)))
+ except tornado.iostream.StreamClosedError:
+ pass
+ finally:
+ if self.serial.attached_cty is stream:
+ self.serial.attached_cty = None
+
+
+@tornado.gen.coroutine
+def main():
+ parser = argparse.ArgumentParser(formatter_class = argparse.ArgumentDefaultsHelpFormatter)
+ parser.add_argument("-v", "--verbose", action = "store_true", help = "produce human-readable output")
+ parser.add_argument("-d", "--debug", action = "store_true", help = "blather about what we're doing")
+
+ parser.add_argument("--rpc-device", help = "RPC serial device name",
+ default = os.getenv("CRYPTECH_RPC_CLIENT_SERIAL_DEVICE", "/dev/ttyUSB0"))
+ parser.add_argument("--rpc-socket", help = "RPC PF_UNIX socket name",
+ default = os.getenv("CRYPTECH_RPC_CLIENT_SOCKET_NAME", "/tmp/.cryptech_muxd.rpc"))
+
+ parser.add_argument("--cty-device", help = "CTY serial device name",
+ default = os.getenv("CRYPTECH_CTY_CLIENT_SERIAL_DEVICE", "/dev/ttyUSB0"))
+ parser.add_argument("--cty-socket", help = "CTY PF_UNIX socket name",
+ default = os.getenv("CRYPTECH_CTY_CLIENT_SOCKET_NAME", "/tmp/.cryptech_muxd.cty"))
+
+ args = parser.parse_args()
+
+ futures = []
+
+ rpc_stream = RPCIOStream(device = args.rpc_device, debug = args.debug)
+ rpc_server = RPCServer()
+ rpc_server.set_serial(rpc_stream)
+ rpc_server.listen(args.rpc_socket)
+ futures.append(rpc_stream.rpc_output_loop())
+
+ cty_stream = CTYIOStream(device = args.cty_device, debug = args.debug)
+ cty_server = CTYServer()
+ cty_server.set_serial(cty_stream)
+ cty_server.listen(args.cty_socket)
+ futures.append(cty_stream.cty_output_loop())
+
+ # Might want to use WaitIterator(dict(...)) here so we can
+ # diagnose and restart output loops if they fail. Worry about
+ # that when we get to automatic device probing.
+
+ yield futures
+
+if __name__ == "__main__":
+ try:
+ tornado.ioloop.IOLoop.current().run_sync(main)
+ except KeyboardInterrupt:
+ pass
diff --git a/hal_internal.h b/hal_internal.h
index ef3dd49..69d9e67 100644
--- a/hal_internal.h
+++ b/hal_internal.h
@@ -880,7 +880,7 @@ typedef enum {
*/
#ifndef HAL_CLIENT_DAEMON_DEFAULT_SOCKET_NAME
-#define HAL_CLIENT_DAEMON_DEFAULT_SOCKET_NAME "/tmp/.cryptech_rpcmuxd"
+#define HAL_CLIENT_DAEMON_DEFAULT_SOCKET_NAME "/tmp/.cryptech_muxd.rpc"
#endif
/*
diff --git a/libhal.py b/libhal.py
index 1e4ff02..e899d7b 100644
--- a/libhal.py
+++ b/libhal.py
@@ -407,7 +407,7 @@ class HSM(object):
if status != 0:
raise HALError.table[status]()
- def __init__(self, sockname = os.getenv("CRYPTECH_RPC_CLIENT_SOCKET_NAME", "/tmp/.cryptech_rpcmuxd")):
+ def __init__(self, sockname = os.getenv("CRYPTECH_RPC_CLIENT_SOCKET_NAME", "/tmp/.cryptech_muxd.rpc")):
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.socket.connect(sockname)
self.sockfile = self.socket.makefile("rb")