Added SSL support to butterfly.

Added SSL certificate capability to butterfly. Butterfly now
has the --secure option, which requires the following files
to be present in the local folder:
  - butterfly.crt
  - butterfly.key
  - butterflyca.crt
This option forces butterfly to use HTTPS and secure
WebSockets. The connection still requires a username
and password.

There is also the --reallysecure option, which forces
the user's browser to provide a client side certificate.
The certificate is validated against butterflyca.crt, before
allowing the connection. Afterward, the commonName in the
certificate is used as the username for the connection.
The connection still requires the user to provide a password.

Also forced a default user "daemon" to be returned by the
User class, as it prevents someone from finding valid users
on the remote host.
This commit is contained in:
OldGregg
2014-03-01 13:07:04 -05:00
parent 38cface138
commit 78cf01c1fd
5 changed files with 48 additions and 10 deletions

View File

@@ -20,16 +20,20 @@
import tornado.options
import tornado.ioloop
import tornado.httpserver
import ssl
tornado.options.define("secret", default='secret', help="Secret")
tornado.options.define("debug", default=False, help="Debug mode")
tornado.options.define("host", default='127.0.0.1', help="Server host")
tornado.options.define("port", default=57575, type=int, help="Server port")
tornado.options.define("shell", help="Shell to execute at login")
tornado.options.define("secure", default=False, \
help="Choose whether or not to use SSL")
tornado.options.define("reallysecure", default=False, \
help="Require certificate authentication.")
tornado.options.parse_command_line()
import logging
for logger in ('tornado.access', 'tornado.application',
'tornado.general', 'butterfly'):
@@ -42,11 +46,23 @@ ioloop = tornado.ioloop.IOLoop.instance()
from butterfly import application
http_server = tornado.httpserver.HTTPServer(application)
if tornado.options.options.reallysecure:
tornado.options.options.secure = True
reqs = ssl.CERT_REQUIRED
elif tornado.options.options.secure:
reqs = ssl.CERT_OPTIONAL
ssl_opts = None
if tornado.options.options.secure:
ssl_opts = dict(certfile="butterfly.crt", keyfile="butterfly.key", \
cert_reqs=reqs, ca_certs="butterflyca.crt")
http_server = tornado.httpserver.HTTPServer(application, ssl_options=ssl_opts)
http_server.listen(
tornado.options.options.port, address=tornado.options.options.host)
url = "http://%s:%d/*" % (
url = "http%s://%s:%d/*" % ( "s" if tornado.options.options.secure else "",
tornado.options.options.host, tornado.options.options.port)
# This is for debugging purpose

View File

@@ -123,8 +123,9 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
env["TERM"] = "xterm-256color"
env["COLORTERM"] = "butterfly"
env["HOME"] = self.callee.dir
env["LOCATION"] = "http://%s:%d/" % (
tornado.options.options.host, tornado.options.options.port)
env["LOCATION"] = "http%s://%s:%d/" % \
("s" if tornado.options.options.secure else "", \
tornado.options.options.host, tornado.options.options.port)
env["PATH"] = '%s:%s' % (os.path.abspath(os.path.join(
os.path.dirname(__file__), '..', 'bin')), env.get("PATH"))
args = [shell]
@@ -180,7 +181,8 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
self.fd, self.shell_handler, ioloop.READ | ioloop.ERROR)
def open(self, user, path):
if self.request.headers['Origin'] != 'http://%s' % (
if self.request.headers['Origin'] != 'http%s://%s' % \
("s" if tornado.options.options.secure else "",
self.request.headers['Host']):
self.log.warning(
'Unauthorized connection attempt: from : %s to: %s' % (
@@ -194,12 +196,19 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
self.path = path
self.user = user.decode('utf-8') if user else None
self.caller = self.callee = None
if tornado.options.options.secure:
cert = self.request.get_ssl_certificate()
if cert != None:
for field in cert['subject']:
if field[0][0] == 'commonName':
self.user = self.callee = field[0][1]
if self.socket.local:
self.caller = utils.User(uid=self.socket.uid)
else:
# We don't know uid is on the other machine
pass
if self.user:
try:
self.callee = utils.User(name=self.user)

View File

@@ -28,7 +28,11 @@ ctl = (type, args...) ->
if type == 'Resize'
ws.send 'R' + params
ws_url = 'ws://' + document.location.host + '/ws' + location.pathname
if localtion.protocol == 'https:'
ws_url = 'wss://'
else
ws_url = 'ws://'
ws_url += document.location.host + '/ws' + location.pathname
ws = new WebSocket ws_url
ws.addEventListener 'open', ->

View File

@@ -2792,7 +2792,13 @@ ctl = function() {
}
};
ws_url = 'ws://' + document.location.host + '/ws' + location.pathname;
if(location.protocol == 'https:'){
ws_url = 'wss://'
}
else{
ws_url = 'ws://'
}
ws_url += document.location.host + '/ws' + location.pathname;
ws = new WebSocket(ws_url);

View File

@@ -31,7 +31,10 @@ class User(object):
if uid is not None:
self.pw = pwd.getpwuid(uid)
else:
self.pw = pwd.getpwnam(name)
try:
self.pw = pwd.getpwnam(name)
except:
self.pw = pwd.getpwnam('daemon')
if self.pw is None:
raise LookupError('Unknown user')