Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #5 - Implement BIND-level authentication #6

Merged
merged 5 commits into from
Apr 30, 2018
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions bliss/sle/bin/bliss_sle_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,21 @@
from bliss.core import log

import bliss.sle
import bliss.sle.frames
from bliss.sle.pdu.raf import *

def process_pdu():
def process_pdu(raf_mngr):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
gevent.sleep(0)
if bliss.sle.DATA_QUEUE.empty():
if raf_mngr._data_queue.empty():
continue

hdr, body = bliss.sle.DATA_QUEUE.get()
log.info('Empty {}'.format(raf_mngr._data_queue.empty()))
pdu = raf_mngr._data_queue.get()

try:
decoded_pdu, remainder = bliss.sle.RAF.decode(body)
decoded_pdu, remainder = raf_mngr.decode(pdu)
except pyasn1.error.PyAsn1Error as e:
log.error('Unable to decode PDU. Skipping ...')
continue
Expand All @@ -57,35 +59,39 @@ def process_pdu():
# Object does not contain data or data is not initalized. Skipping ...
continue

tmf = bliss.sle.TMTransFrame(trans_data)
tmf = bliss.sle.frames.TMTransFrame(trans_data)
log.info('Emitting {} bytes of telemetry to GUI'.format(len(tmf._data[0])))
sock.sendto(tmf._data[0], ('localhost', 3076))


if __name__ == '__main__':
raf_mngr = bliss.sle.RAF(hostname='atb-ocio-sspsim.jpl.nasa.gov', port=5100)
raf_mngr = bliss.sle.RAF(hostname='atb-ocio-sspsim.jpl.nasa.gov', port=5100,
auth_level="bind",
peer_auth_level="bind",
inst_id="sagr=LSE-SSC.spack=Test.rsl-fg=1.raf=onlc1")
raf_mngr.connect()
time.sleep(1)

raf_mngr.bind()
time.sleep(1)

raf_mngr.send_start_invocation(datetime.datetime(2017, 1, 1), datetime.datetime(2018, 1, 1))
raf_mngr.start(datetime.datetime(2017, 1, 1), datetime.datetime(2018, 1, 1))

tlm_monitor = gevent.spawn(process_pdu)
tlm_monitor = gevent.spawn(process_pdu, raf_mngr)
gevent.sleep(0)
# log.info('Processing telemetry. Press <Ctrl-c> to terminate connection ...')
log.info('Processing telemetry. Press <Ctrl-c> to terminate connection ...')
try:
while True:
gevent.sleep(0)
except:
pass
finally:

tlm_monitor.kill()
tlm_monitor.kill()

raf_mngr.stop()
time.sleep(1)
raf_mngr.stop()
time.sleep(1)

raf_mngr.unbind()
time.sleep(1)
raf_mngr.unbind()
time.sleep(1)

4 changes: 3 additions & 1 deletion bliss/sle/bin/examples/cltu_api_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@
cltu_mngr = bliss.sle.CLTU(
hostname='atb-ocio-sspsim.jpl.nasa.gov',
port=5100,
inst_id='sagr=LSE-SSC.spack=Test.fsl-fg=1.cltu=cltu1'
inst_id='sagr=LSE-SSC.spack=Test.fsl-fg=1.cltu=cltu1',
auth_level="bind",
peer_auth_level="bind"
)

cltu_mngr.connect()
Expand Down
26 changes: 18 additions & 8 deletions bliss/sle/bin/examples/rcf_api_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@
rcf_mngr = bliss.sle.RCF(
hostname='atb-ocio-sspsim.jpl.nasa.gov',
port=5100,
inst_id='sagr=LSE-SSC.spack=Test.rsl-fg=1.rcf=onlc2'
inst_id='sagr=LSE-SSC.spack=Test.rsl-fg=1.rcf=onlc2',
spacecraft_id=250,
trans_frame_ver_num=1,
auth_level="bind",
peer_auth_level="bind"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need both of these. The auth level should cover outgoing/inbound auth in all 3 cases. I.e., 'Bind' level auth means the outgoing bind request is auth'ed and the receipt has auth info from the harness that we should decode

)

rcf_mngr.connect()
Expand All @@ -65,13 +69,19 @@
end = dt.datetime(2018, 01, 01)
# rcf_mngr.start(start, end, 250, 0, virtual_channel=6)
rcf_mngr.start(start, end, 250, 0, master_channel=True)
time.sleep(20)

rcf_mngr.stop()
time.sleep(2)
try:
while True:
time.sleep(0)
except:
pass
finally:

rcf_mngr.unbind()
time.sleep(2)
rcf_mngr.stop()
time.sleep(2)

rcf_mngr.disconnect()
time.sleep(2)
rcf_mngr.unbind()
time.sleep(2)

rcf_mngr.disconnect()
time.sleep(2)
38 changes: 30 additions & 8 deletions bliss/sle/cltu.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ def start(self):
'''
start_invoc = CltuUserToProviderPdu()

if self._credentials:
pass
if self._auth_level in ['all']:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we set this to be _auth_level == 'all' instead of doing membership checks?

start_invoc['cltuStartInvocation']['invokerCredentials']['used'] = self.make_credentials()
else:
start_invoc['cltuStartInvocation']['invokerCredentials']['unused'] = None

Expand Down Expand Up @@ -189,8 +189,8 @@ def upload_cltu(self, tc_data, earliest_time=None, latest_time=None, delay=0, no
'''
pdu = CltuUserToProviderPdu()

if self._credentials:
pass
if self._auth_level in ['all']:
pdu['cltuTransferDataInvocation']['invokerCredentials']['used'] = self.make_credentials()
else:
pdu['cltuTransferDataInvocation']['invokerCredentials']['unused'] = None

Expand Down Expand Up @@ -237,8 +237,8 @@ def schedule_status_report(self, report_type='immediately', cycle=None):
'''
pdu = CltuUserToProviderPdu()

if self._credentials:
pass
if self._auth_level in ['all']:
pdu['cltuScheduleStatusReportInvocation']['invokerCredentials']['used'] = self.make_credentials()
else:
pdu['cltuScheduleStatusReportInvocation']['invokerCredentials']['unused'] = None

Expand Down Expand Up @@ -282,8 +282,8 @@ def throw_event(self, event_id, event_qualifier):
'''
pdu = CltuUserToProviderPdu()

if self._credentials:
pass
if self._auth_level in ['all']:
pdu['cltuThrowEventInvocation']['invokerCredentials']['used'] = self.make_credentials()
else:
pdu['cltuThrowEventInvocation']['invokerCredentials']['unused'] = None

Expand Down Expand Up @@ -327,7 +327,29 @@ def decode(self, message):
def _bind_return_handler(self, pdu):
''''''
result = pdu['cltuBindReturn']['result']
responder_identifier = pdu['cltuBindReturn']['responderIdentifier']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the same across the 3 interfaces let's move the bulk of the logic to the common class between the 3. I realize there was probably a lot of duplication before as well though =)


# Check that responder_id in the response matches what we know
if responder_identifier != self._responder_id:
# Invoke PEER-ABORT with unexpected responder id
self.peer_abort(1)
self._state = 'unbound'
return

if 'positive' in result:
if self._peer_auth_level in ['bind', 'all']:
responder_performer_credentials = pdu['cltuBindReturn']['performerCredentials']['used']
if not self._check_return_credentials(responder_performer_credentials, self._responder_id,
self._peer_password):
# Authentication failed. Ignore processing the return
bliss.core.log.info('Bind unsuccessful. Authentication failed.')
return

if self._state == 'ready' or self._state == 'active':
# Peer abort with protocol error (3)
bliss.core.log.info('Bind unsuccessful. State already in READY or ACTIVE.')
self.peer_abort(3)

bliss.core.log.info('Bind successful')
self._state = 'ready'
else:
Expand Down
108 changes: 93 additions & 15 deletions bliss/sle/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
import datetime as dt
import errno
import fcntl
import hashlib
import random
import socket
import struct
import time
Expand All @@ -59,14 +61,15 @@

import pyasn1.error
from pyasn1.codec.ber.encoder import encode
from pyasn1.codec.der.encoder import encode as derencode
from pyasn1.codec.der.encoder import encode as der_encode
from pyasn1.codec.der.decoder import decode

import bliss.core
import bliss.core.log

from bliss.sle.pdu import service_instance
from bliss.sle.pdu.service_instance import *
from bliss.sle.pdu.common import HashInput, ISP1Credentials
import util

TML_SLE_FORMAT = '!ii'
Expand Down Expand Up @@ -94,30 +97,48 @@ class SLE(object):

def __init__(self, *args, **kwargs):
''''''
self._hostname = bliss.config.get('sle.hostname',
self._hostname = bliss.config.get('dsn.sle.hostname',
kwargs.get('hostname', None))
self._port = bliss.config.get('sle.port',
self._port = bliss.config.get('dsn.sle.port',
kwargs.get('port', None))
self._heartbeat = bliss.config.get('sle.heartbeat',
self._heartbeat = bliss.config.get('dsn.sle.heartbeat',
kwargs.get('heartbeat', 25))
self._deadfactor = bliss.config.get('sle.deadfactor',
self._deadfactor = bliss.config.get('dsn.sle.deadfactor',
kwargs.get('deadfactor', 5))
self._buffer_size = bliss.config.get('sle.buffer_size',
self._buffer_size = bliss.config.get('dsn.sle.buffer_size',
kwargs.get('buffer_size', 256000))
self._credentials = bliss.config.get('sle.credentials', None)
self._initiator_id = bliss.config.get('sle.initiator_id',
self._initiator_id = bliss.config.get('dsn.sle.initiator_id',
kwargs.get('initiator_id', 'LSE'))
self._responder_port= bliss.config.get('sle.responder_port',
self._responder_id = bliss.config.get('dsn.sle.responder_id',
kwargs.get('responder_id', 'SSE'))
self._password = bliss.config.get('dsn.sle.password', None)
self._peer_password = bliss.config.get('dsn.sle.peer_password',
kwargs.get('peer_password', None))
self._responder_port = bliss.config.get('dsn.sle.responder_port',
kwargs.get('responder_port', 'default'))
self._telem_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self._inst_id = kwargs.get('inst_id', None)
self._auth_level = kwargs.get('auth_level', 'none')
self._peer_auth_level = kwargs.get('peer_auth_level', 'none')

if not self._hostname or not self._port:
msg = 'Connection configuration missing hostname ({}) or port ({})'
msg = msg.format(self._hostname, self._port)
bliss.core.log.error(msg)
raise ValueError(msg)

if self._auth_level not in ['none', 'bind', 'all']:
raise ValueError('Authentication level must be one of: "none", "bind", "all"')
if self._peer_auth_level not in ['none', 'bind', 'all']:
raise ValueError('Peer Authentication level must be one of: "none", "bind", "all"')

self._local_entity_auth = {
'local_entity_id': self._initiator_id,
'auth_level': self._auth_level,
'time': None,
'random_number': None
}

self._conn_monitor = gevent.spawn(conn_handler, self)
self._data_processor = gevent.spawn(data_processor, self)

Expand Down Expand Up @@ -201,8 +222,8 @@ def bind(self, pdu, **kwargs):
The PyASN1 class instance that should be configured with
generic SLE attributes, encoded, and sent to SLE.
'''
if self._credentials:
pass
if self._auth_level in ['bind', 'all']:
pdu['invokerCredentials']['used'] = self.make_credentials()
else:
pdu['invokerCredentials']['unused'] = None

Expand Down Expand Up @@ -244,8 +265,8 @@ def unbind(self, pdu, reason=0):
reason:
The reason code for why the unbind is happening.
'''
if self._credentials:
pass
if self._auth_level in ['all']:
pdu['invokerCredentials']['used'] = self.make_credentials()
else:
pdu['invokerCredentials']['unused'] = None

Expand Down Expand Up @@ -307,8 +328,8 @@ def stop(self, pdu):
The PyASN1 class instance that should be configured with
generic SLE attributes, encoded, and sent to SLE.
'''
if self._credentials:
pass
if self._auth_level in ['all']:
pdu['invokerCredentials']['used'] = self.make_credentials()
else:
pdu['invokerCredentials']['unused'] = None

Expand Down Expand Up @@ -346,6 +367,63 @@ def _handle_pdu(self, pdu):
)
bliss.core.log.error(err.format(pdu_key))

def make_credentials(self):
'''Makes credentials for the initiator'''
now = dt.datetime.utcnow()
## This random number for DSN spec
# random_number = random.randint(0, 42949667295)

# This random number for generic
# Taken from https://public.ccsds.org/Pubs/913x1b2.pdf 3.2.3
random_number = random.randint(0, 2147483647)
self._local_entity_auth['time'] = now
self._local_entity_auth['random_number'] = random_number
return self._generate_encoded_credentials(now, random_number, self._initiator_id, self._password)

def _check_return_credentials(self, responder_performer_credentials, username, password):
decoded_credentials = decode(responder_performer_credentials.asOctets(), ISP1Credentials())[0]
days, ms, us = struct.unpack('!HIH', str(bytearray(decoded_credentials['time'].asNumbers())))
time_delta = dt.timedelta(days=days, milliseconds=ms, microseconds=us)
cred_time = time_delta + dt.datetime(1958, 1, 1)
random_number = decoded_credentials['randomNumber']
performer_credentials = self._generate_encoded_credentials(cred_time,
random_number,
self._responder_id,
self._peer_password)
return performer_credentials == responder_performer_credentials

def _generate_encoded_credentials(self, current_time, random_number, username, password):
'''Generates encoded ISP1 credentials

Arguments:
current_time:
The datetime object representing the time to use to create the credentials.
random_number:
The random number to use to create the credentials.
username:
The username to use to create the credentials.
password:
The password to use to create the credentials.
'''
hash_input = HashInput()
days = (current_time - dt.datetime(1958, 1, 1)).days
millisecs = (current_time - current_time.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds() * 1000
credential_time = struct.pack('!HIH', days, millisecs, 0)

hash_input['time'] = credential_time
hash_input['randomNumber'] = random_number
hash_input['username'] = username
hash_input['password'] = password
der_encoded_hash_input = der_encode(hash_input)
the_protected = bytearray.fromhex(hashlib.sha1(der_encoded_hash_input).hexdigest())

isp1_creds = ISP1Credentials()
isp1_creds['time'] = credential_time
isp1_creds['randomNumber'] = random_number
isp1_creds['theProtected'] = the_protected

return encode(isp1_creds)


def conn_handler(handler):
''' Handler for processing data received from the DSN into PDUs'''
Expand Down
Loading