From 0c6c6be4a09780c667bec349c1a27ca3b020b9a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Wed, 11 Jul 2018 13:34:03 +0200 Subject: [PATCH 01/40] JRC: Make joinedNodes a global var, add lookup helper --- .../openvisualizer/openvisualizer/JRC/JRC.py | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 8f4b9548e..df388f851 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -22,6 +22,22 @@ import binascii import os +# ======================== List of nodes that have joined ======================== +# format: +# { +# 'eui64' : node_address_hex_string, +# 'context' : oscoap.SecurityContext +# } +joinedNodes = [] + +# id as a string +def joinedNodesLookup(id): + for node in joinedNodes: + if node['eui64'] == id: + return node + + return None + # ======================== Top Level JRC Class ============================= class JRC(): def __init__(self): @@ -49,21 +65,22 @@ def securityContextLookup(self, kid): # if eui-64 is found in the list of joined nodes, return the appropriate context # this is important for replay protection - for dict in self.joinResource.joinedNodes: - if dict['eui64'] == u.buf2str(eui64): - log.info("Node {0} found in joinedNodes. Returning context {1}.".format(binascii.hexlify(dict['eui64']), - str(dict['context']))) - return dict['context'] - - # if eui-64 is not found, create a new tentative context but only add it to the list of joined nodes in the GET - # handler of the join resource - context = oscoap.SecurityContext(masterSecret=self.MASTERSECRET, - senderID=u.buf2str(senderID), - recipientID=u.buf2str(recipientID), - aeadAlgorithm=oscoap.AES_CCM_16_64_128()) - - log.info("Node {0} not found in joinedNodes. Instantiating new context based on the master secret.".format( - binascii.hexlify(u.buf2str(eui64)))) + node = joinedNodesLookup(u.buf2str(eui64)) + + if node is not None: + log.info("Node {0} found in joinedNodes. Returning context {1}.".format(binascii.hexlify(node['eui64']), + str(node['context']))) + context = node['context'] + else: + log.info("Node {0} not found in joinedNodes. Instantiating new context based on the master secret.".format( + binascii.hexlify(u.buf2str(eui64)))) + + # if eui-64 is not found, create a new tentative context but only add it to the list of joined nodes in the GET + # handler of the join resource + context = oscoap.SecurityContext(masterSecret=self.MASTERSECRET, + senderID=u.buf2str(senderID), + recipientID=u.buf2str(recipientID), + aeadAlgorithm=oscoap.AES_CCM_16_64_128()) return context @@ -248,7 +265,6 @@ def _receiveFromCoAP(self, timestamp, sender, data): # ==================== Implementation of CoAP join resource ===================== class joinResource(coapResource.coapResource): def __init__(self): - self.joinedNodes = [] #self.networkKey = u.str2buf(os.urandom(16)) # random key every time OpenVisualizer is initialized self.networkKey = u.str2buf(binascii.unhexlify('11111111111111111111111111111111')) # value of K1/K2 from 6TiSCH TD @@ -278,7 +294,7 @@ def POST(self,options=[], payload=[]): #objectSecurity = oscoap.objectSecurityOptionLookUp(options) #assert objectSecurity - #self.joinedNodes += [{'eui64' : u.buf2str(objectSecurity.kid[:8]), # remove last prepended byte + #joinedNodes += [{'eui64' : u.buf2str(objectSecurity.kid[:8]), # remove last prepended byte # 'context' : objectSecurity.context}] return (respCode,respOptions,respPayload) From 83c8806eb38de24b696a7f2e2d167e1a3611cccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Wed, 11 Jul 2018 13:32:27 +0200 Subject: [PATCH 02/40] ACE: Add constants from draft-ietf-ace-oauth-authz-13 --- .../openvisualizer/JRC/aceDefines.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 software/openvisualizer/openvisualizer/JRC/aceDefines.py diff --git a/software/openvisualizer/openvisualizer/JRC/aceDefines.py b/software/openvisualizer/openvisualizer/JRC/aceDefines.py new file mode 100644 index 000000000..dcfe3ffa4 --- /dev/null +++ b/software/openvisualizer/openvisualizer/JRC/aceDefines.py @@ -0,0 +1,31 @@ +# constants from draft-ietf-ace-oauth-authz-13 + +# Figure 12 draft-ietf-ace-oauth-authz-13: CBOR mappings used in token requests +ACE_PARAMETERS_LABELS_AUD = 3 # text string +ACE_PARAMETERS_LABELS_CLIENT_ID = 8 # text string +ACE_PARAMETERS_LABELS_CLIENT_SECRET = 9 # byte string +ACE_PARAMETERS_LABELS_RESPONSE_TYPE = 10 # text string +ACE_PARAMETERS_LABELS_REDIRECT_URI = 11 # text string +ACE_PARAMETERS_LABELS_SCOPE = 12 # text or byte string +ACE_PARAMETERS_LABELS_STATE = 13 # text string +ACE_PARAMETERS_LABELS_CODE = 14 # byte string +ACE_PARAMETERS_LABELS_ERROR = 15 # unsigned integer +ACE_PARAMETERS_LABELS_ERROR_DESCRIPTION = 16 # text string +ACE_PARAMETERS_LABELS_ERROR_URI = 17 # text string +ACE_PARAMETERS_LABELS_GRANT_TYPE = 18 # unsigned integer +ACE_PARAMETERS_LABELS_ACCESS_TOKEN = 19 # byte string +ACE_PARAMETERS_LABELS_TOKEN_TYPE = 20 # unsigned integer +ACE_PARAMETERS_LABELS_EXPIRES_IN = 21 # unsigned integer +ACE_PARAMETERS_LABELS_USERNAME = 22 # text string +ACE_PARAMETERS_LABELS_PASSWORD = 23 # text string +ACE_PARAMETERS_LABELS_REFRESH_TOKEN = 24 # byte string +ACE_PARAMETERS_LABELS_CNF = 25 # map +ACE_PARAMETERS_LABELS_PROFILE = 26 # unsigned integer +ACE_PARAMETERS_LABELS_RS_CNF = 31 # map + + +# Figure 11 from draft-ietf-ace-oauth-authz-13: CBOR abbreviations for common grant types +ACE_CBOR_ABBREVIATIONS_PASSWORD = 0 +ACE_CBOR_ABBREVIATIONS_AUTHORIZATION_CODE = 1 +ACE_CBOR_ABBREVIATIONS_CLIENT_CREDENTIALS = 2 +ACE_CBOR_ABBREVIATIONS_REFRESH_TOKEN = 3 From 9a60d8a8d7da1b065143827b54019bb62834fc42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Wed, 11 Jul 2018 13:34:19 +0200 Subject: [PATCH 03/40] ACE: Preliminary implementation of /token resource --- .../openvisualizer/openvisualizer/JRC/JRC.py | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index df388f851..a3580dec3 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -13,6 +13,7 @@ pass import cojpDefines +import aceDefines log = logging.getLogger('JRC') log.setLevel(logging.ERROR) @@ -38,6 +39,11 @@ def joinedNodesLookup(id): return None + +# ============ List of resources that can be accessed by any joined node ========= + +authorizedResources = ['resource1'] + # ======================== Top Level JRC Class ============================= class JRC(): def __init__(self): @@ -299,6 +305,94 @@ def POST(self,options=[], payload=[]): return (respCode,respOptions,respPayload) +# ==================== Implementation of /token resource for implementing ACE framework ===================== + +class tokenResource(coapResource.coapResource): + + def __init__(self): + + # initialize parent class + coapResource.coapResource.__init__( + self, + path = 'token', + ) + + self.addSecurityBinding((None, [d.METHOD_POST])) # security context should be returned by the callback + + def POST(self,options=[], payload=[]): + + respOptions = [] + respPayload = [] + + try: + objectSecurity = oscoap.objectSecurityOptionLookUp(options) + + # the request MUST come be received over a secure OSCORE channel + if objectSecurity is None: + raise AceUnauthorized + + clientId = u.buf2str(objectSecurity.kid[:8]) + + # if the client that is requesting an access token is not in the list of joined nodes, consider it unauthorized + if joinedNodesLookup(u.buf2str(clientId)) is None: + raise AceUnauthorized + # else: every joined node is considered authorized + + # proceed by checking the request format + contentFormat = self.lookupContentFormat(options) + if contentFormat is not None and contentFormat.format == d.FORMAT_CBOR: + raise TypeError + + request = cbor.loads(u.buf2str(payload)) + + if request[aceDefines.ACE_PARAMETERS_LABELS_GRANT_TYPE] != aceDefines.ACE_CBOR_ABBREVIATIONS_CLIENT_CREDENTIALS: + raise AceBadRequest + + if request[aceDefines.ACE_PARAMETERS_LABELS_SCOPE] not in authorizedResources: + raise AceUnauthorized + + configuration = {} + configuration_serialized = cbor.dumps(configuration) + + respCode = d.COAP_RC_2_04_CHANGED + respPayload = [ord(b) for b in configuration_serialized] + except (AceBadRequest, TypeError, NameError): + respCode = d.COAP_RC_4_00_BADREQUEST + except AceUnauthorized: + respCode = d.COAP_RC_4_01_UNAUTHORIZED + + return (respCode,respOptions,respPayload) + + # ======================== private ========================================= + + def lookupContentFormat(self, options=[]): + for option in options: + if isinstance(option, o.ContentFormat): + return option + return None + + +# ============================ custom exceptions ========================================= + +class AceException(Exception): + def __init__(self, reason=''): + assert type(reason) == str + + # store params + self.reason = reason + + def __str__(self): + return '{0}(reason={1})'.format(self.__class__.__name__, self.reason) + +class AceUnauthorized(AceException): + pass + +class AceBadRequest(AceException): + pass + + +# ============================ main ========================================= + if __name__ == "__main__": fileLogger = logging.handlers.RotatingFileHandler( From b51616c6e24b3a0dad1cc39100dad28d77927f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Wed, 11 Jul 2018 13:42:37 +0200 Subject: [PATCH 04/40] ACE: Add placeholder for constructing the access token --- software/openvisualizer/openvisualizer/JRC/JRC.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index a3580dec3..4e0a0d717 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -351,11 +351,12 @@ def POST(self,options=[], payload=[]): if request[aceDefines.ACE_PARAMETERS_LABELS_SCOPE] not in authorizedResources: raise AceUnauthorized - configuration = {} - configuration_serialized = cbor.dumps(configuration) + # TODO construct the access token + access_token = {} + access_token_serialized = cbor.dumps(access_token) respCode = d.COAP_RC_2_04_CHANGED - respPayload = [ord(b) for b in configuration_serialized] + respPayload = [ord(b) for b in access_token_serialized] except (AceBadRequest, TypeError, NameError): respCode = d.COAP_RC_4_00_BADREQUEST except AceUnauthorized: From b69b1756462e597be495d4ad6370a9d9ee2ef537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Wed, 11 Jul 2018 16:18:31 +0200 Subject: [PATCH 05/40] ACE: Reintroduce COSE defines --- .../openvisualizer/JRC/coseDefines.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 software/openvisualizer/openvisualizer/JRC/coseDefines.py diff --git a/software/openvisualizer/openvisualizer/JRC/coseDefines.py b/software/openvisualizer/openvisualizer/JRC/coseDefines.py new file mode 100644 index 000000000..dcd68a8e0 --- /dev/null +++ b/software/openvisualizer/openvisualizer/JRC/coseDefines.py @@ -0,0 +1,26 @@ +# COSE key labels +KEY_LABEL_KTY = 1 +KEY_LABEL_KID = 2 +KEY_LABEL_ALG = 3 +KEY_LABEL_KEYOPS = 4 +KEY_LABEL_BASEIV = 5 +KEY_LABEL_K = -1 +KEY_LABEL_ALL = [ + KEY_LABEL_KTY, + KEY_LABEL_KID, + KEY_LABEL_ALG, + KEY_LABEL_KEYOPS, + KEY_LABEL_BASEIV, + KEY_LABEL_K, +] + +# COSE key values +KEY_VALUE_OKP = 1 +KEY_VALUE_EC2 = 2 +KEY_VALUE_SYMMETRIC = 4 +KEY_VALUE_ALL = [ + KEY_VALUE_OKP, + KEY_VALUE_EC2, + KEY_VALUE_SYMMETRIC, +] + From af23217698d666f88b829d904506f6afce58cf1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Wed, 11 Jul 2018 17:53:05 +0200 Subject: [PATCH 06/40] ACE: Add additional COSE constants --- .../openvisualizer/JRC/coseDefines.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/software/openvisualizer/openvisualizer/JRC/coseDefines.py b/software/openvisualizer/openvisualizer/JRC/coseDefines.py index dcd68a8e0..18804f5d0 100644 --- a/software/openvisualizer/openvisualizer/JRC/coseDefines.py +++ b/software/openvisualizer/openvisualizer/JRC/coseDefines.py @@ -24,3 +24,40 @@ KEY_VALUE_SYMMETRIC, ] +ALG_AES_CCM_16_64_128 = 10 +ALG_AES_CCM_16_64_256 = 11 +ALG_AES_CCM_64_64_128 = 12 +ALG_AES_CCM_64_64_256 = 13 +ALG_AES_CCM_16_128_128 = 30 +ALG_AES_CCM_16_128_256 = 31 +ALG_AES_CCM_64_128_128 = 32 +ALG_AES_CCM_64_128_256 = 33 + +ALG_AES_CCM_ALL = [ + ALG_AES_CCM_16_64_128, + ALG_AES_CCM_16_64_256, + ALG_AES_CCM_64_64_128, + ALG_AES_CCM_64_64_256, + ALG_AES_CCM_16_128_128, + ALG_AES_CCM_16_128_256, + ALG_AES_CCM_64_128_128, + ALG_AES_CCM_64_128_256, +] + +COMMON_HEADER_PARAMETERS_ALG = 1 +COMMON_HEADER_PARAMETERS_CRIT = 2 +COMMON_HEADER_PARAMETERS_CONTENT_TYPE = 3 +COMMON_HEADER_PARAMETERS_KID = 4 +COMMON_HEADER_PARAMETERS_IV = 5 +COMMON_HEADER_PARAMETERS_PIV = 6 +COMMON_HEADER_PARAMETERS_COUNTER_SIGNATURE = 7 + +COMMON_HEADER_PARAMETERS_ALL = [ + COMMON_HEADER_PARAMETERS_ALG, + COMMON_HEADER_PARAMETERS_CRIT, + COMMON_HEADER_PARAMETERS_CONTENT_TYPE, + COMMON_HEADER_PARAMETERS_KID, + COMMON_HEADER_PARAMETERS_IV, + COMMON_HEADER_PARAMETERS_PIV, + COMMON_HEADER_PARAMETERS_COUNTER_SIGNATURE, +] From 063187feb5319c944c3f51e3d98f154acc74979c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Wed, 11 Jul 2018 17:53:23 +0200 Subject: [PATCH 07/40] ACE: Add COSE and ACE constants extended by different companion documents --- .../openvisualizer/openvisualizer/JRC/aceDefines.py | 10 ++++++++++ .../openvisualizer/openvisualizer/JRC/coseDefines.py | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/software/openvisualizer/openvisualizer/JRC/aceDefines.py b/software/openvisualizer/openvisualizer/JRC/aceDefines.py index dcfe3ffa4..94b440efc 100644 --- a/software/openvisualizer/openvisualizer/JRC/aceDefines.py +++ b/software/openvisualizer/openvisualizer/JRC/aceDefines.py @@ -29,3 +29,13 @@ ACE_CBOR_ABBREVIATIONS_AUTHORIZATION_CODE = 1 ACE_CBOR_ABBREVIATIONS_CLIENT_CREDENTIALS = 2 ACE_CBOR_ABBREVIATIONS_REFRESH_TOKEN = 3 + +ACE_OSCORE_PROFILE_ID = 23 + +ACE_ACCESS_TOKEN_TYPE_BEARER = 1 +ACE_ACCESS_TOKEN_TYPE_POP = 2 + +# from https://tools.ietf.org/html/draft-ietf-ace-cwt-proof-of-possession-03#section-3.1 +ACE_CWT_CNF_COSE_KEY = 1 +ACE_CWT_CNF_ENCRYPTED_COSE_KEY = 2 +ACE_CWT_CNF_KID = 3 \ No newline at end of file diff --git a/software/openvisualizer/openvisualizer/JRC/coseDefines.py b/software/openvisualizer/openvisualizer/JRC/coseDefines.py index 18804f5d0..3ba18bfe0 100644 --- a/software/openvisualizer/openvisualizer/JRC/coseDefines.py +++ b/software/openvisualizer/openvisualizer/JRC/coseDefines.py @@ -5,6 +5,10 @@ KEY_LABEL_KEYOPS = 4 KEY_LABEL_BASEIV = 5 KEY_LABEL_K = -1 +KEY_LABEL_CLIENT_ID = 6 # value TBD by IANA, registered in draft-ietf-ace-oscore-profile-02 +KEY_LABEL_SERVER_ID = 7 # value TBD by IANA, registered in draft-ietf-ace-oscore-profile-02 +KEY_LABEL_KDF = 8 # value TBD by IANA, registered in draft-ietf-ace-oscore-profile-02 +KEY_LABEL_SLT = 9 # value TBD by IANA, registered in draft-ietf-ace-oscore-profile-02 KEY_LABEL_ALL = [ KEY_LABEL_KTY, KEY_LABEL_KID, @@ -12,6 +16,10 @@ KEY_LABEL_KEYOPS, KEY_LABEL_BASEIV, KEY_LABEL_K, + KEY_LABEL_CLIENT_ID, + KEY_LABEL_SERVER_ID, + KEY_LABEL_KDF, + KEY_LABEL_SLT, ] # COSE key values From f8986d7c7eeaca5fdf77ea1e29ba2149357efdda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Wed, 11 Jul 2018 17:57:01 +0200 Subject: [PATCH 08/40] JRC: Add a function to randomly select a node from the list of joined nodes --- software/openvisualizer/openvisualizer/JRC/JRC.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 4e0a0d717..984dde932 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -22,6 +22,7 @@ import cbor import binascii import os +import random # ======================== List of nodes that have joined ======================== # format: @@ -39,6 +40,19 @@ def joinedNodesLookup(id): return None +# pick a node randomly from the list of all joined nodes, except the one passed as the parameter +def pickJoinedNodeRandomly(skip=None): + if (len(joinedNodes) == 1 and skip == joinedNodes[0]) or len(joinedNodes) == 0: + return None + + found = False + candidate = None + + while found is False: + candidate = random.choice(joinedNodes) + if candidate != skip: + found = True + return candidate # ============ List of resources that can be accessed by any joined node ========= From 25aabbd1e60c523983bc958e4ffdf3b7eaf5c0c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Wed, 11 Jul 2018 17:58:42 +0200 Subject: [PATCH 09/40] ACE: First implementation of AS generating the access token --- .../openvisualizer/openvisualizer/JRC/JRC.py | 77 ++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 984dde932..18fb4873c 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -13,6 +13,7 @@ pass import cojpDefines +import coseDefines import aceDefines log = logging.getLogger('JRC') @@ -348,10 +349,18 @@ def POST(self,options=[], payload=[]): clientId = u.buf2str(objectSecurity.kid[:8]) # if the client that is requesting an access token is not in the list of joined nodes, consider it unauthorized - if joinedNodesLookup(u.buf2str(clientId)) is None: + client = joinedNodesLookup(u.buf2str(clientId)) + if client is None: raise AceUnauthorized # else: every joined node is considered authorized + # we don't use aud parameter for the moment, RS is selected randomly by AS from the list of joined nodes + # this allows the JRC to act as a discovery server, allowing the client to specify the resource it is interested in + # and the JRC to communicate to the cliient which RS hosts such a resource + # client contacts the RS by constructing its IPv6 address, based on RS's identifier (EUI-64), assuming they are + # in the same 6TiSCH network + resourceServer = pickJoinedNodeRandomly(skip=client) + # proceed by checking the request format contentFormat = self.lookupContentFormat(options) if contentFormat is not None and contentFormat.format == d.FORMAT_CBOR: @@ -365,8 +374,72 @@ def POST(self,options=[], payload=[]): if request[aceDefines.ACE_PARAMETERS_LABELS_SCOPE] not in authorizedResources: raise AceUnauthorized - # TODO construct the access token + # construct the access token + + # Step 1. construct the CNF (confirmation) claim + cnf_value = { + aceDefines.ACE_CWT_CNF_COSE_KEY : { + coseDefines.KEY_LABEL_KTY : coseDefines.KEY_VALUE_SYMMETRIC, + coseDefines.KEY_LABEL_ALG : coseDefines.ALG_AES_CCM_16_64_128, + coseDefines.KEY_LABEL_CLIENT_ID : clientId, + coseDefines.KEY_LABEL_SERVER_ID : resourceServer['eui64'], + coseDefines.KEY_LABEL_K : os.urandom(16), # generate random 128-bit key + } + } + + # Step 2. Construct CWT claims set + cwt_claims_set = {} + cwt_claims_set[aceDefines.ACE_PARAMETERS_LABELS_SCOPE] = request[aceDefines.ACE_PARAMETERS_LABELS_SCOPE] + cwt_claims_set[aceDefines.ACE_PARAMETERS_LABELS_CNF] = cnf_value + + # Step 3. Construct CWT by encrypting in a COSE_Encrypt0 wrapper the CWT claims set + + # COSE_Encrypt0 protected bucket + cwt_protected = { + coseDefines.COMMON_HEADER_PARAMETERS_ALG : coseDefines.ALG_AES_CCM_16_64_128 + } + + # COSE_Encrypt0 unprotected bucket + # generate a random 13-byte nonce FIXME can we use AES-CCM with 7-byte nonces here? + nonce = os.urandom(13) + cwt_unprotected = { + coseDefines.COMMON_HEADER_PARAMETERS_IV : nonce + } + + # COSE Enc_structure from https://tools.ietf.org/html/draft-ietf-cose-msg-24#section-5.3 + encStructure = [ + unicode('Encrypt0'), + cbor.dumps(cwt_protected), + '', + ] + + # the key to encrypt the CWT is derived from the OSCORE master secret, with info set to 'ACE' + key = oscoap. _hkdfDeriveParameter(masterSecret=resourceServer['context'].masterSecret, + masterSalt=resourceServer['context'].masterSalt, + id=resourceServer['context'].senderId, + algorithm=coseDefines.AES_CCM_16_64_128, + type='ACE', + length=16) + + # generate the ciphertext by encrypting with the CCM algorithm + ciphertext = oscoap.AES_CCM_16_64_128().authenticateAndEncrypt(aad=cbor.dumps(encStructure), + plaintext=cbor.dumps(cwt_claims_set), + key=key, + nonce=nonce) + + # Step 4. Construct the CWT object + cwt = [ + cwt_protected, + cwt_unprotected, + ciphertext + ] + access_token = {} + access_token[aceDefines.ACE_PARAMETERS_LABELS_TOKEN_TYPE] = aceDefines.ACE_ACCESS_TOKEN_TYPE_POP # FIXME can be removed + access_token[aceDefines.ACE_PARAMETERS_LABELS_ACCESS_TOKEN] = cbor.dumps(cwt) + access_token[aceDefines.ACE_PARAMETERS_LABELS_PROFILE] = aceDefines.ACE_OSCORE_PROFILE_ID # FIXME can be removed + access_token[aceDefines.ACE_PARAMETERS_LABELS_CNF] = cnf_value + access_token_serialized = cbor.dumps(access_token) respCode = d.COAP_RC_2_04_CHANGED From 87fd2a09defc0871c83a24d25ab000ab1408cc30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 10:48:34 +0200 Subject: [PATCH 10/40] ACE: Remove token_type parameter since it's implied by the framework --- software/openvisualizer/openvisualizer/JRC/JRC.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 18fb4873c..c5622a934 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -380,7 +380,7 @@ def POST(self,options=[], payload=[]): cnf_value = { aceDefines.ACE_CWT_CNF_COSE_KEY : { coseDefines.KEY_LABEL_KTY : coseDefines.KEY_VALUE_SYMMETRIC, - coseDefines.KEY_LABEL_ALG : coseDefines.ALG_AES_CCM_16_64_128, + coseDefines.KEY_LABEL_ALG : coseDefines.ALG_AES_CCM_16_64_128, # FIXME can be removed? coseDefines.KEY_LABEL_CLIENT_ID : clientId, coseDefines.KEY_LABEL_SERVER_ID : resourceServer['eui64'], coseDefines.KEY_LABEL_K : os.urandom(16), # generate random 128-bit key @@ -435,7 +435,6 @@ def POST(self,options=[], payload=[]): ] access_token = {} - access_token[aceDefines.ACE_PARAMETERS_LABELS_TOKEN_TYPE] = aceDefines.ACE_ACCESS_TOKEN_TYPE_POP # FIXME can be removed access_token[aceDefines.ACE_PARAMETERS_LABELS_ACCESS_TOKEN] = cbor.dumps(cwt) access_token[aceDefines.ACE_PARAMETERS_LABELS_PROFILE] = aceDefines.ACE_OSCORE_PROFILE_ID # FIXME can be removed access_token[aceDefines.ACE_PARAMETERS_LABELS_CNF] = cnf_value From 88bffc7fa7515e2935a5ee13a721794350658fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 11:40:08 +0200 Subject: [PATCH 11/40] ACE: Use custom-defined exception to send 4.00 Bad Request --- software/openvisualizer/openvisualizer/JRC/JRC.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index c5622a934..948674bc4 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -364,7 +364,7 @@ def POST(self,options=[], payload=[]): # proceed by checking the request format contentFormat = self.lookupContentFormat(options) if contentFormat is not None and contentFormat.format == d.FORMAT_CBOR: - raise TypeError + raise AceBadRequest request = cbor.loads(u.buf2str(payload)) From d74dfd4794fe50f9e1a018f117b1b7d25b8caca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 11:39:35 +0200 Subject: [PATCH 12/40] ACE: Add logs to help debugging and improve exception handling --- .../openvisualizer/openvisualizer/JRC/JRC.py | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 948674bc4..3d30c5ed8 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -338,12 +338,14 @@ def POST(self,options=[], payload=[]): respOptions = [] respPayload = [] + clientId = [] try: objectSecurity = oscoap.objectSecurityOptionLookUp(options) # the request MUST come be received over a secure OSCORE channel if objectSecurity is None: + log.info("Client requesting access over unprotected transport.") raise AceUnauthorized clientId = u.buf2str(objectSecurity.kid[:8]) @@ -351,9 +353,13 @@ def POST(self,options=[], payload=[]): # if the client that is requesting an access token is not in the list of joined nodes, consider it unauthorized client = joinedNodesLookup(u.buf2str(clientId)) if client is None: + log.info( + "Client {0} not found in the list of authorized nodes.".format(binascii.hexlify(u.buf2str(clientId)))) raise AceUnauthorized # else: every joined node is considered authorized + log.info("Client {0}, deemed authorized, requests an access token.".format(binascii.hexlify(u.buf2str(clientId)))) + # we don't use aud parameter for the moment, RS is selected randomly by AS from the list of joined nodes # this allows the JRC to act as a discovery server, allowing the client to specify the resource it is interested in # and the JRC to communicate to the cliient which RS hosts such a resource @@ -364,14 +370,19 @@ def POST(self,options=[], payload=[]): # proceed by checking the request format contentFormat = self.lookupContentFormat(options) if contentFormat is not None and contentFormat.format == d.FORMAT_CBOR: + log.info("Request is malformed: Content-Format is not set to CBOR.") raise AceBadRequest request = cbor.loads(u.buf2str(payload)) + log.debug("Request decoded as: {0}".format(request)) if request[aceDefines.ACE_PARAMETERS_LABELS_GRANT_TYPE] != aceDefines.ACE_CBOR_ABBREVIATIONS_CLIENT_CREDENTIALS: + log.info("Request is malformed: grant_type is not set to \"client_credentials\".") raise AceBadRequest + # scope parameter is necessary for now if request[aceDefines.ACE_PARAMETERS_LABELS_SCOPE] not in authorizedResources: + log.info("Request scope {0}: deemed unauthorized.".format(request[aceDefines.ACE_PARAMETERS_LABELS_SCOPE])) raise AceUnauthorized # construct the access token @@ -414,7 +425,7 @@ def POST(self,options=[], payload=[]): ] # the key to encrypt the CWT is derived from the OSCORE master secret, with info set to 'ACE' - key = oscoap. _hkdfDeriveParameter(masterSecret=resourceServer['context'].masterSecret, + key = oscoap._hkdfDeriveParameter(masterSecret=resourceServer['context'].masterSecret, masterSalt=resourceServer['context'].masterSalt, id=resourceServer['context'].senderId, algorithm=coseDefines.AES_CCM_16_64_128, @@ -443,11 +454,18 @@ def POST(self,options=[], payload=[]): respCode = d.COAP_RC_2_04_CHANGED respPayload = [ord(b) for b in access_token_serialized] - except (AceBadRequest, TypeError, NameError): + except AceBadRequest: + respCode = d.COAP_RC_4_00_BADREQUEST + except (TypeError, NameError, ValueError, KeyError): + # in case of the built-in exceptions, the request is not properly formatted, send 4.00 + log.debug("Exception occured while processing the request:\n===============================\n{0}\n===============================".format(traceback.format_exc())) respCode = d.COAP_RC_4_00_BADREQUEST except AceUnauthorized: respCode = d.COAP_RC_4_01_UNAUTHORIZED - + except: + log.debug( + "Exception occured while processing the request:\n===============================\n{0}\n===============================".format(traceback.format_exc())) + raise return (respCode,respOptions,respPayload) # ======================== private ========================================= From bf8fa56c44d8e52ae766a07395138186e854afba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 11:57:14 +0200 Subject: [PATCH 13/40] ACE: Bug fix when checking for Content-Format --- software/openvisualizer/openvisualizer/JRC/JRC.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 3d30c5ed8..0c5d97424 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -369,7 +369,7 @@ def POST(self,options=[], payload=[]): # proceed by checking the request format contentFormat = self.lookupContentFormat(options) - if contentFormat is not None and contentFormat.format == d.FORMAT_CBOR: + if contentFormat is None or contentFormat.format != d.FORMAT_CBOR: log.info("Request is malformed: Content-Format is not set to CBOR.") raise AceBadRequest From ff51e9934d8e2188750693417b564f7684f5950b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 12:52:38 +0200 Subject: [PATCH 14/40] ACE: Bug fix on algorithm type when encrypting the access token --- software/openvisualizer/openvisualizer/JRC/JRC.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 0c5d97424..79ae617b4 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -428,7 +428,7 @@ def POST(self,options=[], payload=[]): key = oscoap._hkdfDeriveParameter(masterSecret=resourceServer['context'].masterSecret, masterSalt=resourceServer['context'].masterSalt, id=resourceServer['context'].senderId, - algorithm=coseDefines.AES_CCM_16_64_128, + algorithm=coseDefines.ALG_AES_CCM_16_64_128, type='ACE', length=16) From 0d614531b6a271e491fa2a422a1c053b00414980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 18:53:56 +0200 Subject: [PATCH 15/40] ACE: Remove OSCORE profile id value until it is assigned --- software/openvisualizer/openvisualizer/JRC/aceDefines.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/aceDefines.py b/software/openvisualizer/openvisualizer/JRC/aceDefines.py index 94b440efc..c94ccf7d5 100644 --- a/software/openvisualizer/openvisualizer/JRC/aceDefines.py +++ b/software/openvisualizer/openvisualizer/JRC/aceDefines.py @@ -30,8 +30,6 @@ ACE_CBOR_ABBREVIATIONS_CLIENT_CREDENTIALS = 2 ACE_CBOR_ABBREVIATIONS_REFRESH_TOKEN = 3 -ACE_OSCORE_PROFILE_ID = 23 - ACE_ACCESS_TOKEN_TYPE_BEARER = 1 ACE_ACCESS_TOKEN_TYPE_POP = 2 From e06212557993e5baaf2759772a989f04ad475441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 18:58:17 +0200 Subject: [PATCH 16/40] JRC: Add a generic Node class, representing a node that has been admitted into the network --- .../openvisualizer/openvisualizer/JRC/JRC.py | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 79ae617b4..fefdc0034 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -24,21 +24,15 @@ import binascii import os import random +# ======================== List of Node objects that have joined and helpers ======================== -# ======================== List of nodes that have joined ======================== -# format: -# { -# 'eui64' : node_address_hex_string, -# 'context' : oscoap.SecurityContext -# } joinedNodes = [] # id as a string def joinedNodesLookup(id): for node in joinedNodes: - if node['eui64'] == id: + if node.id == id: return node - return None # pick a node randomly from the list of all joined nodes, except the one passed as the parameter @@ -55,6 +49,8 @@ def pickJoinedNodeRandomly(skip=None): found = True return candidate +# ======================================================================================= + # ============ List of resources that can be accessed by any joined node ========= authorizedResources = ['resource1'] @@ -105,6 +101,16 @@ def securityContextLookup(self, kid): return context +# ======================== Generic Node ====================================== +class Node(): + + def __init__(self, id, context=None, appSessionKey='', appCounter=0): + + self.id = id # hex string + self.context = context # oscoap.securityContext + self.appSessionKey = appSessionKey # hex string + self.appCounter = appCounter # integer + # ======================== Interface with OpenVisualizer ====================================== class coapServer(eventBusClient.eventBusClient): # link-local prefix @@ -315,8 +321,18 @@ def POST(self,options=[], payload=[]): #objectSecurity = oscoap.objectSecurityOptionLookUp(options) #assert objectSecurity - #joinedNodes += [{'eui64' : u.buf2str(objectSecurity.kid[:8]), # remove last prepended byte - # 'context' : objectSecurity.context}] + # joinedNodes += [ Node( + # id=u.buf2str(objectSecurity.kid[:8]), + # context=objectSecurity.context, + # appSessionKey=oscoap.hkdfDeriveParameter( + # masterSecret=objectSecurity.context.masterSecret, + # masterSalt=objectSecurity.context.masterSalt, + # id=objectSecurity.context.recipientID, + # algorithm=coseDefines.ALG_AES_CCM_16_64_128, + # type='ACE', + # length=16), + # appCounter=1) + # ] return (respCode,respOptions,respPayload) From 35f51f04baeb08c13aae122a690845de32108c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 19:00:59 +0200 Subject: [PATCH 17/40] ACE: Extract client's ID from Object-Security --- software/openvisualizer/openvisualizer/JRC/JRC.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index fefdc0034..91afba6aa 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -364,17 +364,18 @@ def POST(self,options=[], payload=[]): log.info("Client requesting access over unprotected transport.") raise AceUnauthorized - clientId = u.buf2str(objectSecurity.kid[:8]) + clientId = binascii.hexlify(u.buf2str(objectSecurity.kid)) # if the client that is requesting an access token is not in the list of joined nodes, consider it unauthorized - client = joinedNodesLookup(u.buf2str(clientId)) + client = joinedNodesLookup(clientId) + if client is None: log.info( - "Client {0} not found in the list of authorized nodes.".format(binascii.hexlify(u.buf2str(clientId)))) + "Client {0} not found in the list of authorized nodes.".format(binascii.hexlify(clientId))) raise AceUnauthorized # else: every joined node is considered authorized - log.info("Client {0}, deemed authorized, requests an access token.".format(binascii.hexlify(u.buf2str(clientId)))) + log.info("Client {0}, deemed authorized, requests an access token.".format(binascii.hexlify(clientId))) # we don't use aud parameter for the moment, RS is selected randomly by AS from the list of joined nodes # this allows the JRC to act as a discovery server, allowing the client to specify the resource it is interested in From becf18dbfd05ce76e2603e413774aafd766b80e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 19:03:05 +0200 Subject: [PATCH 18/40] ACE: Remove implicitly known parameters to reduce overhead --- software/openvisualizer/openvisualizer/JRC/JRC.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 91afba6aa..f1d99a9ce 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -417,15 +417,13 @@ def POST(self,options=[], payload=[]): # Step 2. Construct CWT claims set cwt_claims_set = {} - cwt_claims_set[aceDefines.ACE_PARAMETERS_LABELS_SCOPE] = request[aceDefines.ACE_PARAMETERS_LABELS_SCOPE] + # claim to RS is implicitly known and corresponds to 'all-resources' cwt_claims_set[aceDefines.ACE_PARAMETERS_LABELS_CNF] = cnf_value # Step 3. Construct CWT by encrypting in a COSE_Encrypt0 wrapper the CWT claims set # COSE_Encrypt0 protected bucket - cwt_protected = { - coseDefines.COMMON_HEADER_PARAMETERS_ALG : coseDefines.ALG_AES_CCM_16_64_128 - } + cwt_protected = '' # COSE_Encrypt0 unprotected bucket # generate a random 13-byte nonce FIXME can we use AES-CCM with 7-byte nonces here? @@ -437,8 +435,8 @@ def POST(self,options=[], payload=[]): # COSE Enc_structure from https://tools.ietf.org/html/draft-ietf-cose-msg-24#section-5.3 encStructure = [ unicode('Encrypt0'), - cbor.dumps(cwt_protected), - '', + cwt_protected, # protected bucket + '', # additional data ] # the key to encrypt the CWT is derived from the OSCORE master secret, with info set to 'ACE' @@ -464,7 +462,6 @@ def POST(self,options=[], payload=[]): access_token = {} access_token[aceDefines.ACE_PARAMETERS_LABELS_ACCESS_TOKEN] = cbor.dumps(cwt) - access_token[aceDefines.ACE_PARAMETERS_LABELS_PROFILE] = aceDefines.ACE_OSCORE_PROFILE_ID # FIXME can be removed access_token[aceDefines.ACE_PARAMETERS_LABELS_CNF] = cnf_value access_token_serialized = cbor.dumps(access_token) From 8b4c096cb02060ff7469e2719af1f0b611d47470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 19:05:39 +0200 Subject: [PATCH 19/40] ACE: Pairwise IDs between C and RS can be short --- software/openvisualizer/openvisualizer/JRC/JRC.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index f1d99a9ce..eaa0c5385 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -408,9 +408,9 @@ def POST(self,options=[], payload=[]): cnf_value = { aceDefines.ACE_CWT_CNF_COSE_KEY : { coseDefines.KEY_LABEL_KTY : coseDefines.KEY_VALUE_SYMMETRIC, - coseDefines.KEY_LABEL_ALG : coseDefines.ALG_AES_CCM_16_64_128, # FIXME can be removed? - coseDefines.KEY_LABEL_CLIENT_ID : clientId, - coseDefines.KEY_LABEL_SERVER_ID : resourceServer['eui64'], + # TODO assign IDs so they are unique + coseDefines.KEY_LABEL_CLIENT_ID : binascii.unhexlify('00'), + coseDefines.KEY_LABEL_SERVER_ID : binascii.unhexlify('01'), coseDefines.KEY_LABEL_K : os.urandom(16), # generate random 128-bit key } } From a014f4fcf345319ef237b145d02fd146b922a5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 19:13:04 +0200 Subject: [PATCH 20/40] ACE: Optmize nonce generation and message overhead JRC keeps a counter called appCounter per node. appCounter is incremented each time an AEAD COSE object is generated (e.g. Encrypt0) towards a given node. The AEAD nonce used is appCounter in MSB format, 13 bytes long. The nonce construction mechanism is considered implicit knowledge between AS and RS. RS needs to implement the same mechanism in firmware. --- software/openvisualizer/openvisualizer/JRC/JRC.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index eaa0c5385..fd58a8783 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -426,12 +426,16 @@ def POST(self,options=[], payload=[]): cwt_protected = '' # COSE_Encrypt0 unprotected bucket - # generate a random 13-byte nonce FIXME can we use AES-CCM with 7-byte nonces here? - nonce = os.urandom(13) + + # nonce is appCounter prepended with 13 - len(appCounter) zeros + nonce = u.buf2str(u.int2buf(resourceServer.appCounter, 13)) cwt_unprotected = { - coseDefines.COMMON_HEADER_PARAMETERS_IV : nonce + coseDefines.COMMON_HEADER_PARAMETERS_PIV: resourceServer.appCounter } + # Don't forget to increment the appCounter + resourceServer.appCounter += 1 + # COSE Enc_structure from https://tools.ietf.org/html/draft-ietf-cose-msg-24#section-5.3 encStructure = [ unicode('Encrypt0'), From 50faa7553370bc83de4d1339256bbf8fb776b4ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 19:13:31 +0200 Subject: [PATCH 21/40] ACE: Missing import for exception handling --- software/openvisualizer/openvisualizer/JRC/JRC.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index fd58a8783..b9ea53015 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -24,6 +24,8 @@ import binascii import os import random +import traceback + # ======================== List of Node objects that have joined and helpers ======================== joinedNodes = [] From ba2e8483ad288c46a9e91db27e1234df3229a2ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 19:18:34 +0200 Subject: [PATCH 22/40] ACE: Derive key to encrypt Encrypt0 object on the fly --- software/openvisualizer/openvisualizer/JRC/JRC.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index b9ea53015..c4ed19489 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -445,10 +445,12 @@ def POST(self,options=[], payload=[]): '', # additional data ] + # FIXME code below is used for testing, appSessionKey derivation is done once the node joins the network # the key to encrypt the CWT is derived from the OSCORE master secret, with info set to 'ACE' - key = oscoap._hkdfDeriveParameter(masterSecret=resourceServer['context'].masterSecret, - masterSalt=resourceServer['context'].masterSalt, - id=resourceServer['context'].senderId, + key = oscoap.hkdfDeriveParameter( + #masterSecret=resourceServer['context'].masterSecret, + #masterSalt=resourceServer['context'].masterSalt, + #id=resourceServer['context'].senderId, algorithm=coseDefines.ALG_AES_CCM_16_64_128, type='ACE', length=16) From 641b9002143f42a629868db2dcd1b8c459ae7050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 12 Jul 2018 19:18:58 +0200 Subject: [PATCH 23/40] ACE: Add Content-Format to facilitate testing with Wireshark --- software/openvisualizer/openvisualizer/JRC/JRC.py | 1 + 1 file changed, 1 insertion(+) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index c4ed19489..c90b1011d 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -476,6 +476,7 @@ def POST(self,options=[], payload=[]): respCode = d.COAP_RC_2_04_CHANGED respPayload = [ord(b) for b in access_token_serialized] + respOptions += [o.ContentFormat(cformat=[d.FORMAT_CBOR])] except AceBadRequest: respCode = d.COAP_RC_4_00_BADREQUEST except (TypeError, NameError, ValueError, KeyError): From a142bf82759c4c241dab0433ccbdf030bd32f22c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 26 Jul 2018 17:21:16 +0200 Subject: [PATCH 24/40] ACE: Align the derivation of the application key with new API in oscoap --- software/openvisualizer/openvisualizer/JRC/JRC.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index c90b1011d..041447253 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -326,11 +326,8 @@ def POST(self,options=[], payload=[]): # joinedNodes += [ Node( # id=u.buf2str(objectSecurity.kid[:8]), # context=objectSecurity.context, - # appSessionKey=oscoap.hkdfDeriveParameter( - # masterSecret=objectSecurity.context.masterSecret, - # masterSalt=objectSecurity.context.masterSalt, + # appSessionKey=objectSecurity.context.hkdfDeriveParameter( # id=objectSecurity.context.recipientID, - # algorithm=coseDefines.ALG_AES_CCM_16_64_128, # type='ACE', # length=16), # appCounter=1) @@ -447,11 +444,8 @@ def POST(self,options=[], payload=[]): # FIXME code below is used for testing, appSessionKey derivation is done once the node joins the network # the key to encrypt the CWT is derived from the OSCORE master secret, with info set to 'ACE' - key = oscoap.hkdfDeriveParameter( - #masterSecret=resourceServer['context'].masterSecret, - #masterSalt=resourceServer['context'].masterSalt, - #id=resourceServer['context'].senderId, - algorithm=coseDefines.ALG_AES_CCM_16_64_128, + key = resourceServer.context.hkdfDeriveParameter( + id=resourceServer.context.senderID, type='ACE', length=16) From e0d4586a6e2aa4cf3f229a0d86b7f03e7aa76f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 26 Jul 2018 17:21:59 +0200 Subject: [PATCH 25/40] JRC: Add IPv6 address to the Node class --- software/openvisualizer/openvisualizer/JRC/JRC.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 041447253..94ce1e60f 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -106,9 +106,10 @@ def securityContextLookup(self, kid): # ======================== Generic Node ====================================== class Node(): - def __init__(self, id, context=None, appSessionKey='', appCounter=0): + def __init__(self, id, ipAddress='::1', context=None, appSessionKey='', appCounter=0): self.id = id # hex string + self.ipAddress = ipAddress # IPv6 as a string self.context = context # oscoap.securityContext self.appSessionKey = appSessionKey # hex string self.appCounter = appCounter # integer From cbc04e264c284d7a9a9612f98bf2310f29614e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 26 Jul 2018 17:22:40 +0200 Subject: [PATCH 26/40] ACE: Fix a bug when logging client ID --- software/openvisualizer/openvisualizer/JRC/JRC.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 94ce1e60f..956da7d87 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -371,11 +371,11 @@ def POST(self,options=[], payload=[]): if client is None: log.info( - "Client {0} not found in the list of authorized nodes.".format(binascii.hexlify(clientId))) + "Client {0} not found in the list of authorized nodes.".format(clientId)) raise AceUnauthorized # else: every joined node is considered authorized - log.info("Client {0}, deemed authorized, requests an access token.".format(binascii.hexlify(clientId))) + log.info("Client {0}, deemed authorized, requests an access token.".format(clientId)) # we don't use aud parameter for the moment, RS is selected randomly by AS from the list of joined nodes # this allows the JRC to act as a discovery server, allowing the client to specify the resource it is interested in From a60c40ff997b296b584145ec32a2a1b12213a8f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Thu, 26 Jul 2018 17:23:20 +0200 Subject: [PATCH 27/40] ACE: Return to the client audience where a given resource can be found --- software/openvisualizer/openvisualizer/JRC/JRC.py | 1 + 1 file changed, 1 insertion(+) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 956da7d87..6516f9f07 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -466,6 +466,7 @@ def POST(self,options=[], payload=[]): access_token = {} access_token[aceDefines.ACE_PARAMETERS_LABELS_ACCESS_TOKEN] = cbor.dumps(cwt) access_token[aceDefines.ACE_PARAMETERS_LABELS_CNF] = cnf_value + access_token[aceDefines.ACE_PARAMETERS_LABELS_AUD] = "coap://[{0}]".format(resourceServer.ipAddress) access_token_serialized = cbor.dumps(access_token) From b080a656c0e5bd141a135f4a42e180ee0a2fbd25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Fri, 27 Jul 2018 12:35:27 +0200 Subject: [PATCH 28/40] OpenLbr: Allow a packet from the Internet to be sent to the DAG root Only supports UDP packets for now. --- .../openvisualizer/openLbr/openLbr.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/software/openvisualizer/openvisualizer/openLbr/openLbr.py b/software/openvisualizer/openvisualizer/openLbr/openLbr.py index 73511c74d..6bc0267d1 100644 --- a/software/openvisualizer/openvisualizer/openLbr/openLbr.py +++ b/software/openvisualizer/openvisualizer/openLbr/openLbr.py @@ -217,6 +217,20 @@ def _v6ToMesh_notif(self,sender,signal,data): if log.isEnabledFor(logging.DEBUG): log.debug(self._format_IPv6(ipv6,ipv6_bytes)) + # before forwarding a packet into the mesh, check if it is destined for the DAG root + if self.networkPrefix and self.dagRootEui64 and ipv6['dst_addr'] == self.networkPrefix + self.dagRootEui64: + if ipv6['next_header'] == self.IANA_UDP: + udp_header = ipv6['payload'][:8] # UDP header is fixed length of 8 bytes + app_payload = ipv6['payload'][8:] # we don't care about length and checksum at this point + src_port = udp_header[0]<<8 | udp_header[1] # Network order + dst_port = udp_header[2]<<8 | udp_header[3] # Network order + dispatchSignal=(tuple(ipv6['dst_addr']), self.PROTO_UDP, dst_port) + self._dispatchProtocol(dispatchSignal,(ipv6['src_addr'], app_payload)) + return + else: + log.error('unsupported packet received for the DAG root, next header {0}'.format(ipv6['next_header'])) + return + # convert IPv6 dictionary into 6LoWPAN dictionary lowpan = self.ipv6_to_lowpan(ipv6) From 27777a3ab4a1bd65664a1eb7ee0be1956ee99fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Sat, 28 Jul 2018 00:23:52 +0200 Subject: [PATCH 29/40] JRC: Refactor code by introducing Node and Network classes --- .../openvisualizer/openvisualizer/JRC/JRC.py | 250 ++++++++++++++---- 1 file changed, 199 insertions(+), 51 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 6516f9f07..a3fa08f5b 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -26,55 +26,214 @@ import random import traceback -# ======================== List of Node objects that have joined and helpers ======================== +# ================ List of resources that can be accessed by any joined node ============ +authorizedResources = ['resource1'] +# ======================================================================================= + +# ==== App-layer ID of the JRC as defined by draft-ietf-6tisch-minimal-security-06 ====== +JRC_ID = '4a5243' +# ======================================================================================= + +# ======================== Top Level JRC Class ============================= +class JRC(): + + def __init__(self): + self.sixtischNetwork = SixtischNetwork( + networkKey=u.str2buf(binascii.unhexlify('11111111111111111111111111111111')), # value of K1/K2 from 6TiSCH TD) + networkKeyIndex=0x01, + groupPSK='DEADBEEFCAFEDEADBEEFCAFEDEADBEEF', + ) + + self.externalClients = Network( + joinedNodes= [ + Node( + ipAddress='::1', + id='636c69656e74', + peerID=JRC_ID, + pairwisePSK='000102030405060708090A0B0C0D0E0F', + aeadAlgorithm=oscoap.AES_CCM_16_64_128() + ), + ], + groupPSK='000102030405060708090A0B0C0D0E0F' + ) + + self.networks = (self.sixtischNetwork, self.externalClients) + + self.coapServer = CoapServer(networks=self.networks) + + def close(self): + self.coapServer.close() + +# ======================================================================================= + +# =========================== Generic Joined Node ======================================= +class Node(): + + def __init__(self, + ipAddress=None, + appSessionKey=None, + appCounter=1, + context=None, # oscoap.SecurityContext; used to bootstrap the identifier, if not passed security context is instantiated + id=None, # required when context is None + peerID=None, # required when context is None + pairwisePSK=None, # required when context is None + aeadAlgorithm=None, # required when context is None + ): + self.ipAddress = ipAddress # IPv6 as a string + + if context is not None: + assert (id is None and peerID is None and pairwisePSK is None and aeadAlgorithm is None) + self.id = context.recipientID + self.context = context + else: + assert (id is not None and peerID is not None and pairwisePSK is not None and aeadAlgorithm is not None) + self.id = id + + self.context = oscoap.SecurityContext( # deriving a new security context + masterSecret=binascii.unhexlify(pairwisePSK), + senderID=binascii.unhexlify(peerID), + recipientID=binascii.unhexlify(id), + aeadAlgorithm=aeadAlgorithm + ) + + # if appSessionKey is not explicitly passed, use OSCORE derivation with "ACE" label to HKDF + if appSessionKey is not None: + self.appSessionKey = self.context.hkdfDeriveParameter( # if no app session key is provided, derive a default + id=self.id, + type='ACE', + length=16 + ) + else: + self.appSessionKey = appSessionKey # hex string + + self.appCounter = appCounter # integer + + # ======================== public ========================================== + + def setIpAddress(self, ipAddress): + self.ipAddress = ipAddress + + def getIpAddress(self): + return self.ipAddress + +# ======================================================================================= + +# ======================= Abstraction of a Network ====================================== +class Network(): + + def __init__(self, networkPrefix=[], joinedNodes=[], groupPSK=''): + self.networkPrefix = networkPrefix + self.joinedNodes = joinedNodes + self.groupPSK = groupPSK -joinedNodes = [] + # ======================== public ========================================== + + def setNetworkPrefix(self, networkPrefix): + self.networkPrefix = networkPrefix + + def getNetworkPrefix(self): + return self.networkPrefix + + def joinedNodesUpdate(self, nodes): + assert len(self.getNetworkPrefix()) < 9 + + for node in nodes: + hostPart = u.str2buf(binascii.unhexlify(node.id)) + assert len(hostPart) < 9 -# id as a string -def joinedNodesLookup(id): - for node in joinedNodes: - if node.id == id: - return node - return None + ipAddress = [0]*16 + ipAddress[:len(self.getNetworkPrefix())] = self.getNetworkPrefix() + ipAddress[16-len(hostPart) : 16] = hostPart -# pick a node randomly from the list of all joined nodes, except the one passed as the parameter -def pickJoinedNodeRandomly(skip=None): - if (len(joinedNodes) == 1 and skip == joinedNodes[0]) or len(joinedNodes) == 0: + node.setIpAddress(openvisualizer.openvisualizer_utils.formatIPv6Addr(ipAddress)) + self.joinedNodes += [node] + + # id as a string + def joinedNodesLookup(self, id): + for node in self.joinedNodes: + if node.id == id: + return node return None - found = False - candidate = None + # pick a node randomly from the list of all joined nodes, except the one passed as the parameter + def pickJoinedNodeRandomly(self, skip=None): + if (len(self.joinedNodes) == 1 and skip == self.joinedNodes[0]) or len(self.joinedNodes) == 0: + return None + + found = False + candidate = None - while found is False: - candidate = random.choice(joinedNodes) - if candidate != skip: - found = True - return candidate + while found is False: + candidate = random.choice(self.joinedNodes) + if candidate != skip: + found = True + return candidate + + # ======================== Default context handler based on a group PSK ============================= + # ======================== needs to be registered with CoAP ============================= + def securityContextLookup(self, kid): + # if kid is found in the list of joined nodes, return the appropriate context + # this is crucial for replay protection + node = self.joinedNodesLookup(kid) + if node is not None: + log.info("Node {0} found in joinedNodes. Returning context {1}.".format(binascii.hexlify(node.id), str(node.context))) + context = node.context + else: + log.info("Node {0} not found in joinedNodes. Instantiating new context based on the master secret.".format(binascii.hexlify(kid))) + + # if recipientID is not found, create a new tentative context but only add it to the list of joined nodes in the GET + # handler of the join resource + context = oscoap.SecurityContext( # deriving a new security context + masterSecret=binascii.unhexlify(self.groupPSK), + senderID=binascii.unhexlify(JRC_ID), + recipientID=kid, + aeadAlgorithm=oscoap.AES_CCM_16_64_128() + ) + + return context # ======================================================================================= -# ============ List of resources that can be accessed by any joined node ========= +# ========================= a 6TiSCH Network Class ====================================== +class SixtischNetwork(Network): -authorizedResources = ['resource1'] + def __init__(self, dagRootEui64=None, panID=None, networkKey=None, networkKeyIndex=None, networkPrefix=None, joinedNodes=[], groupPSK=''): + Network.__init__(self, networkPrefix=networkPrefix, joinedNodes=joinedNodes, groupPSK=groupPSK) -# ======================== Top Level JRC Class ============================= -class JRC(): - def __init__(self): - coapResource = joinResource() - #self.coapServer = coapServer(coapResource, contextHandler(coapResource).securityContextLookup) - self.coapServer = coapServer(coapResource) + self.dagRootEui64 = dagRootEui64 + self.panID = panID + self.networkKey = networkKey + self.networkKeyIndex = networkKeyIndex - def close(self): - self.coapServer.close() + # ======================== public ========================================== + + def setDagRootEui64(self, dagRootEui64): + self.dagRootEui64 = dagRootEui64 + + def getDagRootEui64(self): + return self.dagRootEui64 + + def setPanID(self, panID): + self.panID = panID -# ======================== Security Context Handler ========================= -class contextHandler(): - MASTERSECRET = binascii.unhexlify('DEADBEEFCAFEDEADBEEFCAFEDEADBEEF') # value of the OSCORE Master Secret from 6TiSCH TD + def getPanID(self): + return self.panID - def __init__(self, joinResource): - self.joinResource = joinResource + def setNetworkKey(self, networkKey): + self.networkKey = networkKey + + def getNetworkKey(self): + return self.networkKey + + def setNetworkKeyIndex(self, networkKeyIndex): + self.networkKeyIndex = networkKeyIndex + + def getNetworkKeyIndex(self): + return self.networkKeyIndex + + def getNetworkKeyAndIndex(self): + return (self.networkKeyIndex, self.networkKey) - # ======================== Context Handler needs to be registered ============================= def securityContextLookup(self, kid): kidBuf = u.str2buf(kid) @@ -84,35 +243,24 @@ def securityContextLookup(self, kid): # if eui-64 is found in the list of joined nodes, return the appropriate context # this is important for replay protection - node = joinedNodesLookup(u.buf2str(eui64)) + node = self.joinedNodesLookup(u.buf2str(eui64)) if node is not None: - log.info("Node {0} found in joinedNodes. Returning context {1}.".format(binascii.hexlify(node['eui64']), - str(node['context']))) - context = node['context'] + log.info("Node {0} found in joinedNodes. Returning context {1}.".format(node.id, str(node.context))) + context = node.context else: - log.info("Node {0} not found in joinedNodes. Instantiating new context based on the master secret.".format( - binascii.hexlify(u.buf2str(eui64)))) + log.info("Node {0} not found in joinedNodes. Instantiating new context based on the master secret.".format(u.buf2str(eui64))) # if eui-64 is not found, create a new tentative context but only add it to the list of joined nodes in the GET # handler of the join resource - context = oscoap.SecurityContext(masterSecret=self.MASTERSECRET, + context = oscoap.SecurityContext(masterSecret=binascii.unhexlify(self.groupPSK), senderID=u.buf2str(senderID), recipientID=u.buf2str(recipientID), aeadAlgorithm=oscoap.AES_CCM_16_64_128()) return context -# ======================== Generic Node ====================================== -class Node(): - - def __init__(self, id, ipAddress='::1', context=None, appSessionKey='', appCounter=0): - - self.id = id # hex string - self.ipAddress = ipAddress # IPv6 as a string - self.context = context # oscoap.securityContext - self.appSessionKey = appSessionKey # hex string - self.appCounter = appCounter # integer +# ======================================================================================= # ======================== Interface with OpenVisualizer ====================================== class coapServer(eventBusClient.eventBusClient): From 5432a4ac02b7e218c3c20aab0a63c28d16d4720f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Sat, 28 Jul 2018 00:24:56 +0200 Subject: [PATCH 30/40] JRC: Bug fix when sending UDP datagrams and calculating checksum --- software/openvisualizer/openvisualizer/JRC/JRC.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index a3fa08f5b..a07de4d6a 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -399,8 +399,8 @@ def _receiveFromCoAP(self, timestamp, sender, data): # UDP udplen = len(data) + 8 - udp = u.int2buf(self.coapClient.udpPort,2) # src port - udp += u.int2buf(sender[1],2) # dest port + udp = u.int2buf(sender[1], 2) # src port + udp += u.int2buf(self.coapClient.udpPort,2) # dest port udp += [udplen >> 8, udplen & 0xff] # length udp += [0x00, 0x00] # checksum udp += data @@ -421,7 +421,7 @@ def _receiveFromCoAP(self, timestamp, sender, data): dst=dstIpv6Address, length=[0x00, 0x00] + udp[4:6], nh=[0x00, 0x00, 0x00, 17], # UDP as next header - payload=data, + payload=udp, ) # IPv6 From 6b2f8bbe7495cd557674ac6520c4f90ad95ca374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Sat, 28 Jul 2018 00:27:30 +0200 Subject: [PATCH 31/40] JRC: Add support for sending a packet from JRC to the Internet Refactor CoAP interface with the OpenVisualizer. --- .../openvisualizer/openvisualizer/JRC/JRC.py | 93 ++++++++++++------- 1 file changed, 58 insertions(+), 35 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index a07de4d6a..20bd6f327 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -262,29 +262,35 @@ def securityContextLookup(self, kid): # ======================================================================================= -# ======================== Interface with OpenVisualizer ====================================== -class coapServer(eventBusClient.eventBusClient): +# ====================== CoAP Interface with OpenVisualizer ============================= +class CoapServer(eventBusClient.eventBusClient): # link-local prefix LINK_LOCAL_PREFIX = [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] - def __init__(self, coapResource, contextHandler=None): + def __init__(self, networks=(None,None)): # log log.info("create instance") - self.coapResource = coapResource + (self.sixtischNetwork, self.externalNetwork) = networks + self.joinResource = JoinResource( + networkKeyLookupCb=self.sixtischNetwork.getNetworkKeyAndIndex, + updateJoinedNodesCb=self.sixtischNetwork.joinedNodesUpdate + ) + self.tokenResource = TokenResource( + clientAuthorizationCallback=self.externalNetwork.joinedNodesLookup, + resourceServerLookupCallback=self.sixtischNetwork.pickJoinedNodeRandomly) # run CoAP server in testing mode # this mode does not open a real socket, rather uses PyDispatcher for sending/receiving messages # We interface this mode with OpenVisualizer to run JRC co-located with the DAG root self.coapServer = coap.coap(udpPort=d.DEFAULT_UDP_PORT, testing=True) - self.coapServer.addResource(coapResource) - #self.coapServer.addSecurityContextHandler(contextHandler) + self.coapServer.addResource(self.joinResource) + self.coapServer.addResource(self.tokenResource) + self.coapServer.addSecurityContextHandler(self.externalNetwork.securityContextLookup) self.coapServer.maxRetransmit = 1 self.coapClient = None - self.dagRootEui64 = None - # store params # initialize parent class @@ -327,7 +333,7 @@ def _getL2SecurityKey_notif(self, sender, signal, data): ''' Return L2 security key for the network. ''' - return {'index' : [self.coapResource.networkKeyIndex], 'value' : self.coapResource.networkKey} + return {'index' : [self.sixtischNetwork.getNetworkKeyIndex()], 'value' : self.sixtischNetwork.getNetworkKey()} def _registerDagRoot_notif(self, sender, signal, data): # register for the global address of the DAG root @@ -338,7 +344,7 @@ def _registerDagRoot_notif(self, sender, signal, data): self.PROTO_UDP, d.DEFAULT_UDP_PORT ), - callback=self._receiveFromMesh, + callback=self._receiveFromMeshOrInternet, ) # register to receive at link-local DAG root's address @@ -349,10 +355,11 @@ def _registerDagRoot_notif(self, sender, signal, data): self.PROTO_UDP, d.DEFAULT_UDP_PORT ), - callback=self._receiveFromMesh, + callback=self._receiveFromMeshOrInternet, ) - self.dagRootEui64 = data['host'] + self.sixtischNetwork.setNetworkPrefix(data['prefix']) + self.sixtischNetwork.setDagRootEui64(data['host']) def _unregisterDagRoot_notif(self, sender, signal, data): # unregister global address @@ -363,7 +370,7 @@ def _unregisterDagRoot_notif(self, sender, signal, data): self.PROTO_UDP, d.DEFAULT_UDP_PORT ), - callback=self._receiveFromMesh, + callback=self._receiveFromMeshOrInternet, ) # unregister link-local address self.unregister( @@ -373,20 +380,27 @@ def _unregisterDagRoot_notif(self, sender, signal, data): self.PROTO_UDP, d.DEFAULT_UDP_PORT ), - callback=self._receiveFromMesh, + callback=self._receiveFromMeshOrInternet, ) - self.dagRootEui64 = None + self.sixtischNetwork.setDagRootEui64(None) + self.sixtischNetwork.setNetworkPrefix(None) - def _receiveFromMesh(self, sender, signal, data): + def _receiveFromMeshOrInternet(self, sender, signal, data): ''' Receive packet from the mesh destined for JRC's CoAP server. Forwards the packet to the virtual CoAP server running in test mode (PyDispatcher). ''' - sender = openvisualizer.openvisualizer_utils.formatIPv6Addr(data[0]) - # FIXME pass source port within the signal and open coap client at this port - self.coapClient = coap.coap(ipAddress=sender, udpPort=d.DEFAULT_UDP_PORT, testing=True, receiveCallback=self._receiveFromCoAP) - self.coapClient.socketUdp.sendUdp(destIp='', destPort=d.DEFAULT_UDP_PORT, msg=data[1]) # low level forward of the CoAP message + (srcIPv6, dataDict) = data + srcUdpPort = dataDict['udp_src_port'] + appData = dataDict['app_payload'] + + sender = openvisualizer.openvisualizer_utils.formatIPv6Addr(srcIPv6) + + # create an ephemeral coap client object to (re)transmit the message on the event bus of the coap server + self.coapClient = coap.coap(ipAddress=sender, udpPort=srcUdpPort , testing=True, receiveCallback=self._receiveFromCoAP) + self.coapClient.socketUdp.sendUdp(destIp='', destPort=d.DEFAULT_UDP_PORT, msg=appData) # low level forward of the CoAP message + self.coapClient.close() return True def _receiveFromCoAP(self, timestamp, sender, data): @@ -394,8 +408,6 @@ def _receiveFromCoAP(self, timestamp, sender, data): Receive CoAP response and forward it to the mesh network. Appends UDP and IPv6 headers to the CoAP message and forwards it on the Eventbus towards the mesh. ''' - self.coapClient.close() - # UDP udplen = len(data) + 8 @@ -411,7 +423,7 @@ def _receiveFromCoAP(self, timestamp, sender, data): # source address of the packet is DAG root's IPV6 address # use the same prefix (link-local or global) as in the destination address srcIpv6Address = dstIpv6Address[:8] - srcIpv6Address += self.dagRootEui64 + srcIpv6Address += self.sixtischNetwork.getDagRootEui64() assert len(srcIpv6Address)==16 # CRC See https://tools.ietf.org/html/rfc2460. @@ -434,20 +446,26 @@ def _receiveFromCoAP(self, timestamp, sender, data): ip += dstIpv6Address # destination ip += udp - # announce network prefix - self.dispatch( - signal = 'v6ToMesh', - data = ip - ) + # if destination IPv6 does not match the network prefix, and is not link-local, send to Internet + # special case is when sending from the TUN interface that has the same prefix as the mesh network but ends in 0x01 + if (dstIpv6Address[:8] != self.sixtischNetwork.getNetworkPrefix() and dstIpv6Address[:8] != self.LINK_LOCAL_PREFIX) \ + or (dstIpv6Address == self.sixtischNetwork.getNetworkPrefix() + [0, 0, 0, 0, 0, 0, 0, 1]): + self.dispatch( + signal='v6ToInternet', + data=ip + ) + else: + self.dispatch( + signal = 'v6ToMesh', + data = ip + ) -# ==================== Implementation of CoAP join resource ===================== -class joinResource(coapResource.coapResource): - def __init__(self): +# ======================================================================================= - #self.networkKey = u.str2buf(os.urandom(16)) # random key every time OpenVisualizer is initialized - self.networkKey = u.str2buf(binascii.unhexlify('11111111111111111111111111111111')) # value of K1/K2 from 6TiSCH TD - self.networkKeyIndex = 0x01 # L2 key index +# ======================== Implementation of CoAP Join Resource ========================= +class JoinResource(coapResource.coapResource): + def __init__(self, networkKeyLookupCb, updateJoinedNodesCb): # initialize parent class coapResource.coapResource.__init__( self, @@ -456,11 +474,16 @@ def __init__(self): self.addSecurityBinding((None, [d.METHOD_POST])) # security context should be returned by the callback + self.networkKeyLookupCb = networkKeyLookupCb + self.updateJoinedNodesCb = updateJoinedNodesCb + def POST(self,options=[], payload=[]): respCode = d.COAP_RC_2_04_CHANGED respOptions = [] - link_layer_keyset = [self.networkKeyIndex, u.buf2str(self.networkKey)] + assert self.networkKeyLookupCb + (id, key) = self.networkKeyLookupCb() + link_layer_keyset = [id, u.buf2str(key)] configuration = {} From 2790620788e4f4322a5613e0d035afb12a23f7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Sat, 28 Jul 2018 00:28:38 +0200 Subject: [PATCH 32/40] ACE: Set the IP address of the RS as audience parameter --- .../openvisualizer/openvisualizer/JRC/JRC.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 20bd6f327..204373039 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -507,12 +507,10 @@ def POST(self,options=[], payload=[]): return (respCode,respOptions,respPayload) -# ==================== Implementation of /token resource for implementing ACE framework ===================== - -class tokenResource(coapResource.coapResource): - - def __init__(self): +# ==================== Implementation of /token resource to hand out ACE authorization tokens ===================== +class TokenResource(coapResource.coapResource): + def __init__(self, clientAuthorizationCallback, resourceServerLookupCallback): # initialize parent class coapResource.coapResource.__init__( self, @@ -521,11 +519,12 @@ def __init__(self): self.addSecurityBinding((None, [d.METHOD_POST])) # security context should be returned by the callback - def POST(self,options=[], payload=[]): + self.clientAuthorizationCallback = clientAuthorizationCallback + self.resourceServerLookupCallback = resourceServerLookupCallback + def POST(self,options=[], payload=[]): respOptions = [] respPayload = [] - clientId = [] try: objectSecurity = oscoap.objectSecurityOptionLookUp(options) @@ -538,7 +537,8 @@ def POST(self,options=[], payload=[]): clientId = binascii.hexlify(u.buf2str(objectSecurity.kid)) # if the client that is requesting an access token is not in the list of joined nodes, consider it unauthorized - client = joinedNodesLookup(clientId) + assert self.clientAuthorizationCallback + client = self.clientAuthorizationCallback(clientId) if client is None: log.info( @@ -553,7 +553,8 @@ def POST(self,options=[], payload=[]): # and the JRC to communicate to the cliient which RS hosts such a resource # client contacts the RS by constructing its IPv6 address, based on RS's identifier (EUI-64), assuming they are # in the same 6TiSCH network - resourceServer = pickJoinedNodeRandomly(skip=client) + assert self.resourceServerLookupCallback + resourceServer = self.resourceServerLookupCallback() # proceed by checking the request format contentFormat = self.lookupContentFormat(options) @@ -637,7 +638,7 @@ def POST(self,options=[], payload=[]): access_token = {} access_token[aceDefines.ACE_PARAMETERS_LABELS_ACCESS_TOKEN] = cbor.dumps(cwt) access_token[aceDefines.ACE_PARAMETERS_LABELS_CNF] = cnf_value - access_token[aceDefines.ACE_PARAMETERS_LABELS_AUD] = "coap://[{0}]".format(resourceServer.ipAddress) + access_token[aceDefines.ACE_PARAMETERS_LABELS_AUD] = "coap://[{0}]".format(resourceServer.getIpAddress()) access_token_serialized = cbor.dumps(access_token) From 1784cf3edaa2b1a76a115675fa2aa31a2962cd85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Sat, 28 Jul 2018 00:30:06 +0200 Subject: [PATCH 33/40] JRC: Editorial --- software/openvisualizer/openvisualizer/JRC/JRC.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 204373039..e356f3f7e 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -667,9 +667,9 @@ def lookupContentFormat(self, options=[]): return option return None +# ======================================================================================= -# ============================ custom exceptions ========================================= - +# ============================ Custom Exceptions ======================================== class AceException(Exception): def __init__(self, reason=''): assert type(reason) == str @@ -686,6 +686,7 @@ class AceUnauthorized(AceException): class AceBadRequest(AceException): pass +# ======================================================================================= # ============================ main ========================================= From a729f035ccdc3aeb70864db2213d7188f10fcfed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Sat, 28 Jul 2018 00:41:17 +0200 Subject: [PATCH 34/40] JRC: Group PSK has lower priority than pairwise PSK If groupPSK is not set for a 'Network", None is returned as the security context. --- software/openvisualizer/openvisualizer/JRC/JRC.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index e356f3f7e..14474a3a1 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -121,7 +121,7 @@ def getIpAddress(self): # ======================= Abstraction of a Network ====================================== class Network(): - def __init__(self, networkPrefix=[], joinedNodes=[], groupPSK=''): + def __init__(self, networkPrefix=[], joinedNodes=[], groupPSK=None): self.networkPrefix = networkPrefix self.joinedNodes = joinedNodes self.groupPSK = groupPSK @@ -174,11 +174,12 @@ def pickJoinedNodeRandomly(self, skip=None): def securityContextLookup(self, kid): # if kid is found in the list of joined nodes, return the appropriate context # this is crucial for replay protection + context=None node = self.joinedNodesLookup(kid) if node is not None: log.info("Node {0} found in joinedNodes. Returning context {1}.".format(binascii.hexlify(node.id), str(node.context))) context = node.context - else: + elif self.groupPSK is not None: log.info("Node {0} not found in joinedNodes. Instantiating new context based on the master secret.".format(binascii.hexlify(kid))) # if recipientID is not found, create a new tentative context but only add it to the list of joined nodes in the GET @@ -235,6 +236,7 @@ def getNetworkKeyAndIndex(self): return (self.networkKeyIndex, self.networkKey) def securityContextLookup(self, kid): + context = None kidBuf = u.str2buf(kid) eui64 = kidBuf[:-1] @@ -248,7 +250,7 @@ def securityContextLookup(self, kid): if node is not None: log.info("Node {0} found in joinedNodes. Returning context {1}.".format(node.id, str(node.context))) context = node.context - else: + elif self.groupPSK is not None: log.info("Node {0} not found in joinedNodes. Instantiating new context based on the master secret.".format(u.buf2str(eui64))) # if eui-64 is not found, create a new tentative context but only add it to the list of joined nodes in the GET From 94c5c33af997f2074b315ce54cdf5430d1011a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Sat, 28 Jul 2018 00:42:11 +0200 Subject: [PATCH 35/40] ACE: Make a list of external clients authorized to join --- .../openvisualizer/openvisualizer/JRC/JRC.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 14474a3a1..3c6dc480c 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -30,6 +30,13 @@ authorizedResources = ['resource1'] # ======================================================================================= +# ============= List of external clients that are authorized to access /token =========== +# (OSCORE ID, OSCORE MasterSecret, IPv6 address) +authorizedClients = [ + ('636c69656e74', '000102030405060708090A0B0C0D0E0F', '::1'), +] +# ======================================================================================= + # ==== App-layer ID of the JRC as defined by draft-ietf-6tisch-minimal-security-06 ====== JRC_ID = '4a5243' # ======================================================================================= @@ -45,16 +52,15 @@ def __init__(self): ) self.externalClients = Network( - joinedNodes= [ + joinedNodes=[ Node( - ipAddress='::1', - id='636c69656e74', + ipAddress=ip, + id=id, peerID=JRC_ID, - pairwisePSK='000102030405060708090A0B0C0D0E0F', + pairwisePSK=psk, aeadAlgorithm=oscoap.AES_CCM_16_64_128() - ), + ) for (id, psk, ip) in authorizedClients ], - groupPSK='000102030405060708090A0B0C0D0E0F' ) self.networks = (self.sixtischNetwork, self.externalClients) From e4979a085cc08fcb3044499ef9722573f88da7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Sat, 28 Jul 2018 01:00:25 +0200 Subject: [PATCH 36/40] JRC: Bug fixes on string types in context lookup methods --- .../openvisualizer/openvisualizer/JRC/JRC.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 3c6dc480c..4b38f57e7 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -181,12 +181,13 @@ def securityContextLookup(self, kid): # if kid is found in the list of joined nodes, return the appropriate context # this is crucial for replay protection context=None - node = self.joinedNodesLookup(kid) + clientId=binascii.hexlify(kid) + node = self.joinedNodesLookup(clientId) if node is not None: - log.info("Node {0} found in joinedNodes. Returning context {1}.".format(binascii.hexlify(node.id), str(node.context))) + log.info("Node {0} found in joinedNodes. Returning context {1}.".format(node.id, str(node.context))) context = node.context elif self.groupPSK is not None: - log.info("Node {0} not found in joinedNodes. Instantiating new context based on the master secret.".format(binascii.hexlify(kid))) + log.info("Node {0} not found in joinedNodes. Instantiating new context based on a group PSK.".format(clientId)) # if recipientID is not found, create a new tentative context but only add it to the list of joined nodes in the GET # handler of the join resource @@ -196,6 +197,8 @@ def securityContextLookup(self, kid): recipientID=kid, aeadAlgorithm=oscoap.AES_CCM_16_64_128() ) + else: + log.info("Node {0} not found in joinedNodes. No group PSK set, returning None.".format(clientId)) return context @@ -251,13 +254,14 @@ def securityContextLookup(self, kid): # if eui-64 is found in the list of joined nodes, return the appropriate context # this is important for replay protection - node = self.joinedNodesLookup(u.buf2str(eui64)) + eui64HexStr = binascii.hexlify(u.buf2str(eui64)) + node = self.joinedNodesLookup(eui64HexStr) if node is not None: log.info("Node {0} found in joinedNodes. Returning context {1}.".format(node.id, str(node.context))) context = node.context elif self.groupPSK is not None: - log.info("Node {0} not found in joinedNodes. Instantiating new context based on the master secret.".format(u.buf2str(eui64))) + log.info("Node {0} not found in joinedNodes. Instantiating new context based on a group PSK.".format(eui64HexStr)) # if eui-64 is not found, create a new tentative context but only add it to the list of joined nodes in the GET # handler of the join resource @@ -265,6 +269,8 @@ def securityContextLookup(self, kid): senderID=u.buf2str(senderID), recipientID=u.buf2str(recipientID), aeadAlgorithm=oscoap.AES_CCM_16_64_128()) + else: + log.info("Node {0} not found in joinedNodes. No group PSK set, returning None.".format(eui64HexStr)) return context From 1b7f2141ec0c17b4ade36819be3bbb0e550c2f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Sat, 28 Jul 2018 01:05:39 +0200 Subject: [PATCH 37/40] openLbr: Enable JRC to communicate with the Internet --- .../openvisualizer/openvisualizer/openLbr/openLbr.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/software/openvisualizer/openvisualizer/openLbr/openLbr.py b/software/openvisualizer/openvisualizer/openLbr/openLbr.py index 6bc0267d1..174ffad36 100644 --- a/software/openvisualizer/openvisualizer/openLbr/openLbr.py +++ b/software/openvisualizer/openvisualizer/openLbr/openLbr.py @@ -218,14 +218,15 @@ def _v6ToMesh_notif(self,sender,signal,data): log.debug(self._format_IPv6(ipv6,ipv6_bytes)) # before forwarding a packet into the mesh, check if it is destined for the DAG root + # if it is, signal it on the event bus if self.networkPrefix and self.dagRootEui64 and ipv6['dst_addr'] == self.networkPrefix + self.dagRootEui64: if ipv6['next_header'] == self.IANA_UDP: udp_header = ipv6['payload'][:8] # UDP header is fixed length of 8 bytes - app_payload = ipv6['payload'][8:] # we don't care about length and checksum at this point src_port = udp_header[0]<<8 | udp_header[1] # Network order dst_port = udp_header[2]<<8 | udp_header[3] # Network order dispatchSignal=(tuple(ipv6['dst_addr']), self.PROTO_UDP, dst_port) - self._dispatchProtocol(dispatchSignal,(ipv6['src_addr'], app_payload)) + upperLayerData = {'udp_src_port' : src_port, 'app_payload' : ipv6['payload'][8:]} # omit UDP header + self._dispatchProtocol(dispatchSignal,(ipv6['src_addr'], upperLayerData)) return else: log.error('unsupported packet received for the DAG root, next header {0}'.format(ipv6['next_header'])) @@ -347,6 +348,7 @@ def _meshToV6_notif(self,sender,signal,data): ipv6dic['icmpv6_code']=ipv6dic['payload'][1] ipv6dic['icmpv6_checksum']=ipv6dic['payload'][2:4] ipv6dic['app_payload']=ipv6dic['payload'][4:] + upperLayerData = ipv6dic['app_payload'] #this function does the job dispatchSignal=(tuple(ipv6dic['dst_addr']),self.PROTO_ICMPv6,ipv6dic['icmpv6_type']) @@ -416,6 +418,7 @@ def _meshToV6_notif(self,sender,signal,data): #substitute udp header by the uncompressed header. ipv6dic['payload'] = newUdp ipv6dic['payload_length'] = len(newUdp) + upperLayerData = {'udp_src_port': ipv6dic['udp_src_port'], 'app_payload' : ipv6dic['app_payload']} else: #No UDP header compressed ipv6dic['udp_src_port']=u.buf2int(ipv6dic['payload'][:2]) @@ -423,6 +426,7 @@ def _meshToV6_notif(self,sender,signal,data): ipv6dic['udp_length']=ipv6dic['payload'][4:6] ipv6dic['udp_checksum']=ipv6dic['payload'][6:8] ipv6dic['app_payload']=ipv6dic['payload'][8:] + upperLayerData = {'udp_src_port': ipv6dic['udp_src_port'], 'app_payload' : ipv6dic['app_payload']} dispatchSignal=(tuple(ipv6dic['dst_addr']),self.PROTO_UDP,ipv6dic['udp_dest_port']) @@ -430,7 +434,7 @@ def _meshToV6_notif(self,sender,signal,data): #as source address is being retrieved from the IPHC header, the signal includes it in case #receiver such as RPL DAO processing needs to know the source. - success = self._dispatchProtocol(dispatchSignal,(ipv6dic['src_addr'],ipv6dic['app_payload'])) + success = self._dispatchProtocol(dispatchSignal,(ipv6dic['src_addr'], upperLayerData)) if success: return From 7eca051669216444a24f055eb102e3de28658886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Sat, 28 Jul 2018 01:13:12 +0200 Subject: [PATCH 38/40] JRC: Update list of joined nodes once join is successful --- .../openvisualizer/openvisualizer/JRC/JRC.py | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 4b38f57e7..8cb336dcd 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -506,18 +506,15 @@ def POST(self,options=[], payload=[]): respPayload = [ord(b) for b in configuration_serialized] - #objectSecurity = oscoap.objectSecurityOptionLookUp(options) - #assert objectSecurity - - # joinedNodes += [ Node( - # id=u.buf2str(objectSecurity.kid[:8]), - # context=objectSecurity.context, - # appSessionKey=objectSecurity.context.hkdfDeriveParameter( - # id=objectSecurity.context.recipientID, - # type='ACE', - # length=16), - # appCounter=1) - # ] + objectSecurity = oscoap.objectSecurityOptionLookUp(options) + + assert self.updateJoinedNodesCb + if objectSecurity is not None: + self.updateJoinedNodesCb( + [ + Node(context=objectSecurity.context), + ] + ) return (respCode,respOptions,respPayload) From f093ab05bd0b46a57a6bb2367995db260eddf647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Mon, 30 Jul 2018 03:20:06 +0200 Subject: [PATCH 39/40] ACE: Bug fixes in using the appKey with RS. --- software/openvisualizer/openvisualizer/JRC/JRC.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index 8cb336dcd..b48de88a9 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -103,9 +103,9 @@ def __init__(self, ) # if appSessionKey is not explicitly passed, use OSCORE derivation with "ACE" label to HKDF - if appSessionKey is not None: + if appSessionKey is None: self.appSessionKey = self.context.hkdfDeriveParameter( # if no app session key is provided, derive a default - id=self.id, + id=binascii.unhexlify(self.id), type='ACE', length=16 ) @@ -628,11 +628,7 @@ def POST(self,options=[], payload=[]): # FIXME code below is used for testing, appSessionKey derivation is done once the node joins the network # the key to encrypt the CWT is derived from the OSCORE master secret, with info set to 'ACE' - key = resourceServer.context.hkdfDeriveParameter( - id=resourceServer.context.senderID, - type='ACE', - length=16) - + key = resourceServer.appSessionKey # generate the ciphertext by encrypting with the CCM algorithm ciphertext = oscoap.AES_CCM_16_64_128().authenticateAndEncrypt(aad=cbor.dumps(encStructure), plaintext=cbor.dumps(cwt_claims_set), From e19f80fcad99e699a3b857fa65fe3d01efa3182d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mali=C5=A1a=20Vu=C4=8Dini=C4=87?= Date: Mon, 30 Jul 2018 15:55:55 +0200 Subject: [PATCH 40/40] ACE: Remove audience parameter as it is implicitly known --- software/openvisualizer/openvisualizer/JRC/JRC.py | 1 - 1 file changed, 1 deletion(-) diff --git a/software/openvisualizer/openvisualizer/JRC/JRC.py b/software/openvisualizer/openvisualizer/JRC/JRC.py index b48de88a9..b1dcb30b0 100644 --- a/software/openvisualizer/openvisualizer/JRC/JRC.py +++ b/software/openvisualizer/openvisualizer/JRC/JRC.py @@ -645,7 +645,6 @@ def POST(self,options=[], payload=[]): access_token = {} access_token[aceDefines.ACE_PARAMETERS_LABELS_ACCESS_TOKEN] = cbor.dumps(cwt) access_token[aceDefines.ACE_PARAMETERS_LABELS_CNF] = cnf_value - access_token[aceDefines.ACE_PARAMETERS_LABELS_AUD] = "coap://[{0}]".format(resourceServer.getIpAddress()) access_token_serialized = cbor.dumps(access_token)