From 799dc5d5a57da1276687a6e8c308959be9ab583e Mon Sep 17 00:00:00 2001 From: Jim Bennett Date: Fri, 8 May 2020 18:57:41 -0700 Subject: [PATCH 1/7] Adding HMAC in code instead of a library Doing this removes a dependency on Adafruit_hashlib saving 18K in memory usage --- README.rst | 1 - adafruit_azureiot/device_registration.py | 17 +- adafruit_azureiot/hmac.py | 548 +++++++++++++++++++++++ adafruit_azureiot/iot_mqtt.py | 4 +- adafruit_azureiot/keys.py | 19 + setup.py | 1 - 6 files changed, 571 insertions(+), 19 deletions(-) create mode 100644 adafruit_azureiot/hmac.py create mode 100644 adafruit_azureiot/keys.py diff --git a/README.rst b/README.rst index 1c7bab7..8c8d287 100644 --- a/README.rst +++ b/README.rst @@ -47,7 +47,6 @@ This driver depends on: * `Adafruit CircuitPython MiniMQTT `_ * `CircuitPython Base64 `_ -* `CircuitPython HMAC `_ * `CircuitPython Parse `_ Please ensure all dependencies are available on the CircuitPython filesystem. diff --git a/adafruit_azureiot/device_registration.py b/adafruit_azureiot/device_registration.py index dc83ab3..1fe1713 100644 --- a/adafruit_azureiot/device_registration.py +++ b/adafruit_azureiot/device_registration.py @@ -32,14 +32,12 @@ import gc import json import time -import circuitpython_base64 as base64 -import circuitpython_hmac as hmac import circuitpython_parse as parse import adafruit_requests as requests import adafruit_logging as logging from adafruit_logging import Logger -import adafruit_hashlib as hashlib from . import constants +from .keys import compute_derived_symmetric_key # Azure HTTP error status codes AZURE_HTTP_ERROR_CODES = [400, 401, 404, 403, 412, 429, 500] @@ -89,17 +87,6 @@ def __init__(self, socket, id_scope: str, device_id: str, key: str, logger: Logg requests.set_socket(socket) - @staticmethod - def compute_derived_symmetric_key(secret: str, msg: str) -> bytes: - """Computes a derived symmetric key from a secret and a message - :param str secret: The secret to use for the key - :param str msg: The message to use for the key - :returns: The derived symmetric key - :rtype: bytes - """ - secret = base64.b64decode(secret) - return base64.b64encode(hmac.new(secret, msg=msg.encode("utf8"), digestmod=hashlib.sha256).digest()) - def _loop_assign(self, operation_id, headers) -> str: uri = "https://%s/%s/registrations/%s/operations/%s?api-version=%s" % ( constants.DPS_END_POINT, @@ -205,7 +192,7 @@ def register_device(self, expiry: int) -> str: """ # pylint: disable=C0103 sr = self._id_scope + "%2Fregistrations%2F" + self._device_id - sig_no_encode = DeviceRegistration.compute_derived_symmetric_key(self._key, sr + "\n" + str(expiry)) + sig_no_encode = compute_derived_symmetric_key(self._key, sr + "\n" + str(expiry)) sig_encoded = parse.quote(sig_no_encode, "~()*!.'") auth_string = "SharedAccessSignature sr=" + sr + "&sig=" + sig_encoded + "&se=" + str(expiry) + "&skn=registration" diff --git a/adafruit_azureiot/hmac.py b/adafruit_azureiot/hmac.py new file mode 100644 index 0000000..13431d4 --- /dev/null +++ b/adafruit_azureiot/hmac.py @@ -0,0 +1,548 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 Jim Bennett +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +`circuitpython_hmac` +================================================================================ + +HMAC (Keyed-Hashing for Message Authentication) Python module. +Implements the HMAC algorithm as described by RFC 2104. + +This is here as code instead of using https://github.com/jimbobbennett/CircuitPython_HMAC.git +as we only need sha256, so just having the code we need saves 19k of RAM + +""" + +# pylint: disable=C0103, W0108, R0915, C0116, C0115 + +def __translate(key, translation): + return bytes(translation[x] for x in key) + +TRANS_5C = bytes((x ^ 0x5C) for x in range(256)) +TRANS_36 = bytes((x ^ 0x36) for x in range(256)) + +SHA_BLOCKSIZE = 64 +SHA_DIGESTSIZE = 32 + +def new_shaobject(): + """Struct. for storing SHA information.""" + return { + "digest": [0] * 8, + "count_lo": 0, + "count_hi": 0, + "data": [0] * SHA_BLOCKSIZE, + "local": 0, + "digestsize": 0, + } + +def sha_init(): + """Initialize the SHA digest.""" + sha_info = new_shaobject() + sha_info["digest"] = [ + 0x6A09E667, + 0xBB67AE85, + 0x3C6EF372, + 0xA54FF53A, + 0x510E527F, + 0x9B05688C, + 0x1F83D9AB, + 0x5BE0CD19, + ] + sha_info["count_lo"] = 0 + sha_info["count_hi"] = 0 + sha_info["local"] = 0 + sha_info["digestsize"] = 32 + return sha_info + +ROR = ( + lambda x, y: (((x & 0xFFFFFFFF) >> (y & 31)) | (x << (32 - (y & 31)))) & 0xFFFFFFFF +) +Ch = lambda x, y, z: (z ^ (x & (y ^ z))) +Maj = lambda x, y, z: (((x | y) & z) | (x & y)) +S = lambda x, n: ROR(x, n) +R = lambda x, n: (x & 0xFFFFFFFF) >> n +Sigma0 = lambda x: (S(x, 2) ^ S(x, 13) ^ S(x, 22)) +Sigma1 = lambda x: (S(x, 6) ^ S(x, 11) ^ S(x, 25)) +Gamma0 = lambda x: (S(x, 7) ^ S(x, 18) ^ R(x, 3)) +Gamma1 = lambda x: (S(x, 17) ^ S(x, 19) ^ R(x, 10)) + +def sha_transform(sha_info): + W = [] + + d = sha_info["data"] + for i in range(0, 16): + W.append( + (d[4 * i] << 24) + (d[4 * i + 1] << 16) + (d[4 * i + 2] << 8) + d[4 * i + 3] + ) + + for i in range(16, 64): + W.append( + (Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]) & 0xFFFFFFFF + ) + + ss = sha_info["digest"][:] + + # pylint: disable=too-many-arguments, line-too-long + def RND(a, b, c, d, e, f, g, h, i, ki): + """Compress""" + t0 = h + Sigma1(e) + Ch(e, f, g) + ki + W[i] + t1 = Sigma0(a) + Maj(a, b, c) + d += t0 + h = t0 + t1 + return d & 0xFFFFFFFF, h & 0xFFFFFFFF + + ss[3], ss[7] = RND( + ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 0, 0x428A2F98 + ) + ss[2], ss[6] = RND( + ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 1, 0x71374491 + ) + ss[1], ss[5] = RND( + ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 2, 0xB5C0FBCF + ) + ss[0], ss[4] = RND( + ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 3, 0xE9B5DBA5 + ) + ss[7], ss[3] = RND( + ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 4, 0x3956C25B + ) + ss[6], ss[2] = RND( + ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 5, 0x59F111F1 + ) + ss[5], ss[1] = RND( + ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 6, 0x923F82A4 + ) + ss[4], ss[0] = RND( + ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 7, 0xAB1C5ED5 + ) + ss[3], ss[7] = RND( + ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 8, 0xD807AA98 + ) + ss[2], ss[6] = RND( + ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 9, 0x12835B01 + ) + ss[1], ss[5] = RND( + ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 10, 0x243185BE + ) + ss[0], ss[4] = RND( + ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 11, 0x550C7DC3 + ) + ss[7], ss[3] = RND( + ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 12, 0x72BE5D74 + ) + ss[6], ss[2] = RND( + ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 13, 0x80DEB1FE + ) + ss[5], ss[1] = RND( + ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 14, 0x9BDC06A7 + ) + ss[4], ss[0] = RND( + ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 15, 0xC19BF174 + ) + ss[3], ss[7] = RND( + ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 16, 0xE49B69C1 + ) + ss[2], ss[6] = RND( + ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 17, 0xEFBE4786 + ) + ss[1], ss[5] = RND( + ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 18, 0x0FC19DC6 + ) + ss[0], ss[4] = RND( + ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 19, 0x240CA1CC + ) + ss[7], ss[3] = RND( + ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 20, 0x2DE92C6F + ) + ss[6], ss[2] = RND( + ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 21, 0x4A7484AA + ) + ss[5], ss[1] = RND( + ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 22, 0x5CB0A9DC + ) + ss[4], ss[0] = RND( + ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 23, 0x76F988DA + ) + ss[3], ss[7] = RND( + ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 24, 0x983E5152 + ) + ss[2], ss[6] = RND( + ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 25, 0xA831C66D + ) + ss[1], ss[5] = RND( + ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 26, 0xB00327C8 + ) + ss[0], ss[4] = RND( + ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 27, 0xBF597FC7 + ) + ss[7], ss[3] = RND( + ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 28, 0xC6E00BF3 + ) + ss[6], ss[2] = RND( + ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 29, 0xD5A79147 + ) + ss[5], ss[1] = RND( + ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 30, 0x06CA6351 + ) + ss[4], ss[0] = RND( + ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 31, 0x14292967 + ) + ss[3], ss[7] = RND( + ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 32, 0x27B70A85 + ) + ss[2], ss[6] = RND( + ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 33, 0x2E1B2138 + ) + ss[1], ss[5] = RND( + ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 34, 0x4D2C6DFC + ) + ss[0], ss[4] = RND( + ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 35, 0x53380D13 + ) + ss[7], ss[3] = RND( + ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 36, 0x650A7354 + ) + ss[6], ss[2] = RND( + ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 37, 0x766A0ABB + ) + ss[5], ss[1] = RND( + ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 38, 0x81C2C92E + ) + ss[4], ss[0] = RND( + ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 39, 0x92722C85 + ) + ss[3], ss[7] = RND( + ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 40, 0xA2BFE8A1 + ) + ss[2], ss[6] = RND( + ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 41, 0xA81A664B + ) + ss[1], ss[5] = RND( + ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 42, 0xC24B8B70 + ) + ss[0], ss[4] = RND( + ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 43, 0xC76C51A3 + ) + ss[7], ss[3] = RND( + ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 44, 0xD192E819 + ) + ss[6], ss[2] = RND( + ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 45, 0xD6990624 + ) + ss[5], ss[1] = RND( + ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 46, 0xF40E3585 + ) + ss[4], ss[0] = RND( + ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 47, 0x106AA070 + ) + ss[3], ss[7] = RND( + ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 48, 0x19A4C116 + ) + ss[2], ss[6] = RND( + ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 49, 0x1E376C08 + ) + ss[1], ss[5] = RND( + ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 50, 0x2748774C + ) + ss[0], ss[4] = RND( + ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 51, 0x34B0BCB5 + ) + ss[7], ss[3] = RND( + ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 52, 0x391C0CB3 + ) + ss[6], ss[2] = RND( + ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 53, 0x4ED8AA4A + ) + ss[5], ss[1] = RND( + ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 54, 0x5B9CCA4F + ) + ss[4], ss[0] = RND( + ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 55, 0x682E6FF3 + ) + ss[3], ss[7] = RND( + ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 56, 0x748F82EE + ) + ss[2], ss[6] = RND( + ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 57, 0x78A5636F + ) + ss[1], ss[5] = RND( + ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 58, 0x84C87814 + ) + ss[0], ss[4] = RND( + ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 59, 0x8CC70208 + ) + ss[7], ss[3] = RND( + ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 60, 0x90BEFFFA + ) + ss[6], ss[2] = RND( + ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 61, 0xA4506CEB + ) + ss[5], ss[1] = RND( + ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 62, 0xBEF9A3F7 + ) + ss[4], ss[0] = RND( + ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 63, 0xC67178F2 + ) + + # Feedback + dig = [] + for i, x in enumerate(sha_info["digest"]): + dig.append((x + ss[i]) & 0xFFFFFFFF) + sha_info["digest"] = dig + +def sha_update(sha_info, buffer): + """Update the SHA digest. + :param dict sha_info: SHA Digest. + :param str buffer: SHA buffer size. + """ + if isinstance(buffer, str): + raise TypeError("Unicode strings must be encoded before hashing") + count = len(buffer) + buffer_idx = 0 + clo = (sha_info["count_lo"] + (count << 3)) & 0xFFFFFFFF + if clo < sha_info["count_lo"]: + sha_info["count_hi"] += 1 + sha_info["count_lo"] = clo + + sha_info["count_hi"] += count >> 29 + + if sha_info["local"]: + i = SHA_BLOCKSIZE - sha_info["local"] + if i > count: + i = count + + # copy buffer + for x in enumerate(buffer[buffer_idx : buffer_idx + i]): + sha_info["data"][sha_info["local"] + x[0]] = x[1] + + count -= i + buffer_idx += i + + sha_info["local"] += i + if sha_info["local"] == SHA_BLOCKSIZE: + sha_transform(sha_info) + sha_info["local"] = 0 + else: + return + + while count >= SHA_BLOCKSIZE: + # copy buffer + sha_info["data"] = list(buffer[buffer_idx : buffer_idx + SHA_BLOCKSIZE]) + count -= SHA_BLOCKSIZE + buffer_idx += SHA_BLOCKSIZE + sha_transform(sha_info) + + # copy buffer + pos = sha_info["local"] + sha_info["data"][pos : pos + count] = list(buffer[buffer_idx : buffer_idx + count]) + sha_info["local"] = count + +def getbuf(s): + if isinstance(s, str): + return s.encode("ascii") + return bytes(s) + +def sha_final(sha_info): + """Finish computing the SHA Digest.""" + lo_bit_count = sha_info["count_lo"] + hi_bit_count = sha_info["count_hi"] + count = (lo_bit_count >> 3) & 0x3F + sha_info["data"][count] = 0x80 + count += 1 + if count > SHA_BLOCKSIZE - 8: + # zero the bytes in data after the count + sha_info["data"] = sha_info["data"][:count] + ([0] * (SHA_BLOCKSIZE - count)) + sha_transform(sha_info) + # zero bytes in data + sha_info["data"] = [0] * SHA_BLOCKSIZE + else: + sha_info["data"] = sha_info["data"][:count] + ([0] * (SHA_BLOCKSIZE - count)) + + sha_info["data"][56] = (hi_bit_count >> 24) & 0xFF + sha_info["data"][57] = (hi_bit_count >> 16) & 0xFF + sha_info["data"][58] = (hi_bit_count >> 8) & 0xFF + sha_info["data"][59] = (hi_bit_count >> 0) & 0xFF + sha_info["data"][60] = (lo_bit_count >> 24) & 0xFF + sha_info["data"][61] = (lo_bit_count >> 16) & 0xFF + sha_info["data"][62] = (lo_bit_count >> 8) & 0xFF + sha_info["data"][63] = (lo_bit_count >> 0) & 0xFF + + sha_transform(sha_info) + + dig = [] + for i in sha_info["digest"]: + dig.extend( + [((i >> 24) & 0xFF), ((i >> 16) & 0xFF), ((i >> 8) & 0xFF), (i & 0xFF)] + ) + return bytes(dig) + +# pylint: disable=protected-access +class sha256: + digest_size = digestsize = SHA_DIGESTSIZE + block_size = SHA_BLOCKSIZE + name = "sha256" + + def __init__(self, s=None): + """Constructs a SHA256 hash object. + """ + self._sha = sha_init() + if s: + sha_update(self._sha, getbuf(s)) + + def update(self, s): + """Updates the hash object with a bytes-like object, s.""" + sha_update(self._sha, getbuf(s)) + + def digest(self): + """Returns the digest of the data passed to the update() + method so far.""" + return sha_final(self._sha.copy())[: self._sha["digestsize"]] + + def hexdigest(self): + """Like digest() except the digest is returned as a string object of + double length, containing only hexadecimal digits. + """ + return "".join(["%.2x" % i for i in self.digest()]) + + def copy(self): + """Return a copy (“clone”) of the hash object. + """ + new = sha256() + new._sha = self._sha.copy() + return new + +class HMAC: + """RFC 2104 HMAC class. Also complies with RFC 4231. + + This supports the API for Cryptographic Hash Functions (PEP 247). + """ + + blocksize = 64 # 512-bit HMAC; can be changed in subclasses. + + def __init__(self, key, msg=None): + """Create a new HMAC object. + + key: key for the keyed hash object. + msg: Initial input for the hash, if provided. + digestmod: A module supporting PEP 247. *OR* + A hashlib constructor returning a new hash object. *OR* + A hash name suitable for hashlib.new(). + Defaults to hashlib.md5. + Implicit default to hashlib.md5 is deprecated and will be + removed in Python 3.6. + + Note: key and msg must be a bytes or bytearray objects. + """ + + if not isinstance(key, (bytes, bytearray)): + raise TypeError( + "key: expected bytes or bytearray, but got %r" % type(key).__name__ + ) + + digestmod = sha256 + + self.digest_cons = digestmod + + self.outer = self.digest_cons() + self.inner = self.digest_cons() + self.digest_size = self.inner.digest_size + + if hasattr(self.inner, "block_size"): + blocksize = self.inner.block_size + if blocksize < 16: + blocksize = self.blocksize + else: + blocksize = self.blocksize + + # self.blocksize is the default blocksize. self.block_size is + # effective block size as well as the public API attribute. + self.block_size = blocksize + + if len(key) > blocksize: + key = self.digest_cons(key).digest() + + key = key + bytes(blocksize - len(key)) + self.outer.update(__translate(key, TRANS_5C)) + self.inner.update(__translate(key, TRANS_36)) + if msg is not None: + self.update(msg) + + @property + def name(self): + """Return the name of this object + """ + return "hmac-" + self.inner.name + + def update(self, msg): + """Update this hashing object with the string msg. + """ + self.inner.update(msg) + + def copy(self): + """Return a separate copy of this hashing object. + + An update to this copy won't affect the original object. + """ + # Call __new__ directly to avoid the expensive __init__. + other = self.__class__.__new__(self.__class__) + other.digest_cons = self.digest_cons + other.digest_size = self.digest_size + other.inner = self.inner.copy() + other.outer = self.outer.copy() + return other + + def _current(self): + """Return a hash object for the current state. + + To be used only internally with digest() and hexdigest(). + """ + hmac = self.outer.copy() + hmac.update(self.inner.digest()) + return hmac + + def digest(self): + """Return the hash value of this hashing object. + + This returns a string containing 8-bit data. The object is + not altered in any way by this function; you can continue + updating the object after calling this function. + """ + hmac = self._current() + return hmac.digest() + + def hexdigest(self): + """Like digest(), but returns a string of hexadecimal digits instead. + """ + hmac = self._current() + return hmac.hexdigest() + +def new_hmac(key, msg=None): + """Create a new hashing object and return it. + + key: The starting key for the hash. + msg: if available, will immediately be hashed into the object's starting + state. + + You can now feed arbitrary strings into the object using its update() + method, and can ask for the hash value at any time by calling its digest() + method. + """ + return HMAC(key, msg) diff --git a/adafruit_azureiot/iot_mqtt.py b/adafruit_azureiot/iot_mqtt.py index 62537cb..3394b43 100644 --- a/adafruit_azureiot/iot_mqtt.py +++ b/adafruit_azureiot/iot_mqtt.py @@ -35,8 +35,8 @@ from adafruit_minimqtt import MQTT import circuitpython_parse as parse import adafruit_logging as logging -from .device_registration import DeviceRegistration from .iot_error import IoTError +from .keys import compute_derived_symmetric_key from . import constants # pylint: disable=R0903 @@ -107,7 +107,7 @@ class IoTMQTT: def _gen_sas_token(self) -> str: token_expiry = int(time.time() + self._token_expires) uri = self._hostname + "%2Fdevices%2F" + self._device_id - signed_hmac_sha256 = DeviceRegistration.compute_derived_symmetric_key(self._key, uri + "\n" + str(token_expiry)) + signed_hmac_sha256 = compute_derived_symmetric_key(self._key, uri + "\n" + str(token_expiry)) signature = parse.quote(signed_hmac_sha256, "~()*!.'") if signature.endswith("\n"): # somewhere along the crypto chain a newline is inserted signature = signature[:-1] diff --git a/adafruit_azureiot/keys.py b/adafruit_azureiot/keys.py new file mode 100644 index 0000000..3bf87c7 --- /dev/null +++ b/adafruit_azureiot/keys.py @@ -0,0 +1,19 @@ +"""Computes a derived symmetric key from a secret and a message +:param str secret: The secret to use for the key +:param str msg: The message to use for the key +:returns: The derived symmetric key +:rtype: bytes +""" + +import circuitpython_base64 as base64 +from .hmac import new_hmac + +def compute_derived_symmetric_key(secret: str, msg: str) -> bytes: + """Computes a derived symmetric key from a secret and a message + :param str secret: The secret to use for the key + :param str msg: The message to use for the key + :returns: The derived symmetric key + :rtype: bytes + """ + secret = base64.b64decode(secret) + return base64.b64encode(new_hmac(secret, msg=msg.encode("utf8")).digest()) diff --git a/setup.py b/setup.py index 8cc5055..a0cbef2 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,6 @@ "Adafruit-Blinka", "Adafruit_CircuitPython_ESP32SPI", "Adafruit-CircuitPython-miniMQTT", - "CircuitPython-HMAC", "CircuitPython-Base64", "CircuitPython-Parse", ], From 40ab461098e8b3950768ee4bdd03c95f18bdf0fd Mon Sep 17 00:00:00 2001 From: Jim Bennett Date: Fri, 8 May 2020 19:00:29 -0700 Subject: [PATCH 2/7] Black formatting --- adafruit_azureiot/hmac.py | 288 +++++++++++--------------------------- adafruit_azureiot/keys.py | 1 + 2 files changed, 82 insertions(+), 207 deletions(-) diff --git a/adafruit_azureiot/hmac.py b/adafruit_azureiot/hmac.py index 13431d4..424069b 100644 --- a/adafruit_azureiot/hmac.py +++ b/adafruit_azureiot/hmac.py @@ -33,15 +33,18 @@ # pylint: disable=C0103, W0108, R0915, C0116, C0115 + def __translate(key, translation): return bytes(translation[x] for x in key) + TRANS_5C = bytes((x ^ 0x5C) for x in range(256)) TRANS_36 = bytes((x ^ 0x36) for x in range(256)) SHA_BLOCKSIZE = 64 SHA_DIGESTSIZE = 32 + def new_shaobject(): """Struct. for storing SHA information.""" return { @@ -53,6 +56,7 @@ def new_shaobject(): "digestsize": 0, } + def sha_init(): """Initialize the SHA digest.""" sha_info = new_shaobject() @@ -72,9 +76,8 @@ def sha_init(): sha_info["digestsize"] = 32 return sha_info -ROR = ( - lambda x, y: (((x & 0xFFFFFFFF) >> (y & 31)) | (x << (32 - (y & 31)))) & 0xFFFFFFFF -) + +ROR = lambda x, y: (((x & 0xFFFFFFFF) >> (y & 31)) | (x << (32 - (y & 31)))) & 0xFFFFFFFF Ch = lambda x, y, z: (z ^ (x & (y ^ z))) Maj = lambda x, y, z: (((x | y) & z) | (x & y)) S = lambda x, n: ROR(x, n) @@ -84,19 +87,16 @@ def sha_init(): Gamma0 = lambda x: (S(x, 7) ^ S(x, 18) ^ R(x, 3)) Gamma1 = lambda x: (S(x, 17) ^ S(x, 19) ^ R(x, 10)) + def sha_transform(sha_info): W = [] d = sha_info["data"] for i in range(0, 16): - W.append( - (d[4 * i] << 24) + (d[4 * i + 1] << 16) + (d[4 * i + 2] << 8) + d[4 * i + 3] - ) + W.append((d[4 * i] << 24) + (d[4 * i + 1] << 16) + (d[4 * i + 2] << 8) + d[4 * i + 3]) for i in range(16, 64): - W.append( - (Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]) & 0xFFFFFFFF - ) + W.append((Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]) & 0xFFFFFFFF) ss = sha_info["digest"][:] @@ -109,198 +109,70 @@ def RND(a, b, c, d, e, f, g, h, i, ki): h = t0 + t1 return d & 0xFFFFFFFF, h & 0xFFFFFFFF - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 0, 0x428A2F98 - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 1, 0x71374491 - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 2, 0xB5C0FBCF - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 3, 0xE9B5DBA5 - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 4, 0x3956C25B - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 5, 0x59F111F1 - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 6, 0x923F82A4 - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 7, 0xAB1C5ED5 - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 8, 0xD807AA98 - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 9, 0x12835B01 - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 10, 0x243185BE - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 11, 0x550C7DC3 - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 12, 0x72BE5D74 - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 13, 0x80DEB1FE - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 14, 0x9BDC06A7 - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 15, 0xC19BF174 - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 16, 0xE49B69C1 - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 17, 0xEFBE4786 - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 18, 0x0FC19DC6 - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 19, 0x240CA1CC - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 20, 0x2DE92C6F - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 21, 0x4A7484AA - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 22, 0x5CB0A9DC - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 23, 0x76F988DA - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 24, 0x983E5152 - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 25, 0xA831C66D - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 26, 0xB00327C8 - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 27, 0xBF597FC7 - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 28, 0xC6E00BF3 - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 29, 0xD5A79147 - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 30, 0x06CA6351 - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 31, 0x14292967 - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 32, 0x27B70A85 - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 33, 0x2E1B2138 - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 34, 0x4D2C6DFC - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 35, 0x53380D13 - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 36, 0x650A7354 - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 37, 0x766A0ABB - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 38, 0x81C2C92E - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 39, 0x92722C85 - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 40, 0xA2BFE8A1 - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 41, 0xA81A664B - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 42, 0xC24B8B70 - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 43, 0xC76C51A3 - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 44, 0xD192E819 - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 45, 0xD6990624 - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 46, 0xF40E3585 - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 47, 0x106AA070 - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 48, 0x19A4C116 - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 49, 0x1E376C08 - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 50, 0x2748774C - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 51, 0x34B0BCB5 - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 52, 0x391C0CB3 - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 53, 0x4ED8AA4A - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 54, 0x5B9CCA4F - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 55, 0x682E6FF3 - ) - ss[3], ss[7] = RND( - ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 56, 0x748F82EE - ) - ss[2], ss[6] = RND( - ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 57, 0x78A5636F - ) - ss[1], ss[5] = RND( - ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 58, 0x84C87814 - ) - ss[0], ss[4] = RND( - ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 59, 0x8CC70208 - ) - ss[7], ss[3] = RND( - ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 60, 0x90BEFFFA - ) - ss[6], ss[2] = RND( - ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 61, 0xA4506CEB - ) - ss[5], ss[1] = RND( - ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 62, 0xBEF9A3F7 - ) - ss[4], ss[0] = RND( - ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 63, 0xC67178F2 - ) + ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 0, 0x428A2F98) + ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 1, 0x71374491) + ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 2, 0xB5C0FBCF) + ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 3, 0xE9B5DBA5) + ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 4, 0x3956C25B) + ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 5, 0x59F111F1) + ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 6, 0x923F82A4) + ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 7, 0xAB1C5ED5) + ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 8, 0xD807AA98) + ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 9, 0x12835B01) + ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 10, 0x243185BE) + ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 11, 0x550C7DC3) + ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 12, 0x72BE5D74) + ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 13, 0x80DEB1FE) + ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 14, 0x9BDC06A7) + ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 15, 0xC19BF174) + ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 16, 0xE49B69C1) + ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 17, 0xEFBE4786) + ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 18, 0x0FC19DC6) + ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 19, 0x240CA1CC) + ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 20, 0x2DE92C6F) + ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 21, 0x4A7484AA) + ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 22, 0x5CB0A9DC) + ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 23, 0x76F988DA) + ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 24, 0x983E5152) + ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 25, 0xA831C66D) + ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 26, 0xB00327C8) + ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 27, 0xBF597FC7) + ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 28, 0xC6E00BF3) + ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 29, 0xD5A79147) + ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 30, 0x06CA6351) + ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 31, 0x14292967) + ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 32, 0x27B70A85) + ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 33, 0x2E1B2138) + ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 34, 0x4D2C6DFC) + ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 35, 0x53380D13) + ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 36, 0x650A7354) + ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 37, 0x766A0ABB) + ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 38, 0x81C2C92E) + ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 39, 0x92722C85) + ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 40, 0xA2BFE8A1) + ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 41, 0xA81A664B) + ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 42, 0xC24B8B70) + ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 43, 0xC76C51A3) + ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 44, 0xD192E819) + ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 45, 0xD6990624) + ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 46, 0xF40E3585) + ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 47, 0x106AA070) + ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 48, 0x19A4C116) + ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 49, 0x1E376C08) + ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 50, 0x2748774C) + ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 51, 0x34B0BCB5) + ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 52, 0x391C0CB3) + ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 53, 0x4ED8AA4A) + ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 54, 0x5B9CCA4F) + ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 55, 0x682E6FF3) + ss[3], ss[7] = RND(ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], 56, 0x748F82EE) + ss[2], ss[6] = RND(ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], 57, 0x78A5636F) + ss[1], ss[5] = RND(ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], ss[5], 58, 0x84C87814) + ss[0], ss[4] = RND(ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], ss[4], 59, 0x8CC70208) + ss[7], ss[3] = RND(ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], ss[3], 60, 0x90BEFFFA) + ss[6], ss[2] = RND(ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], ss[2], 61, 0xA4506CEB) + ss[5], ss[1] = RND(ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], ss[1], 62, 0xBEF9A3F7) + ss[4], ss[0] = RND(ss[1], ss[2], ss[3], ss[4], ss[5], ss[6], ss[7], ss[0], 63, 0xC67178F2) # Feedback dig = [] @@ -308,6 +180,7 @@ def RND(a, b, c, d, e, f, g, h, i, ki): dig.append((x + ss[i]) & 0xFFFFFFFF) sha_info["digest"] = dig + def sha_update(sha_info, buffer): """Update the SHA digest. :param dict sha_info: SHA Digest. @@ -355,11 +228,13 @@ def sha_update(sha_info, buffer): sha_info["data"][pos : pos + count] = list(buffer[buffer_idx : buffer_idx + count]) sha_info["local"] = count + def getbuf(s): if isinstance(s, str): return s.encode("ascii") return bytes(s) + def sha_final(sha_info): """Finish computing the SHA Digest.""" lo_bit_count = sha_info["count_lo"] @@ -389,11 +264,10 @@ def sha_final(sha_info): dig = [] for i in sha_info["digest"]: - dig.extend( - [((i >> 24) & 0xFF), ((i >> 16) & 0xFF), ((i >> 8) & 0xFF), (i & 0xFF)] - ) + dig.extend([((i >> 24) & 0xFF), ((i >> 16) & 0xFF), ((i >> 8) & 0xFF), (i & 0xFF)]) return bytes(dig) + # pylint: disable=protected-access class sha256: digest_size = digestsize = SHA_DIGESTSIZE @@ -429,6 +303,7 @@ def copy(self): new._sha = self._sha.copy() return new + class HMAC: """RFC 2104 HMAC class. Also complies with RFC 4231. @@ -453,9 +328,7 @@ def __init__(self, key, msg=None): """ if not isinstance(key, (bytes, bytearray)): - raise TypeError( - "key: expected bytes or bytearray, but got %r" % type(key).__name__ - ) + raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__) digestmod = sha256 @@ -534,6 +407,7 @@ def hexdigest(self): hmac = self._current() return hmac.hexdigest() + def new_hmac(key, msg=None): """Create a new hashing object and return it. diff --git a/adafruit_azureiot/keys.py b/adafruit_azureiot/keys.py index 3bf87c7..ff81c55 100644 --- a/adafruit_azureiot/keys.py +++ b/adafruit_azureiot/keys.py @@ -8,6 +8,7 @@ import circuitpython_base64 as base64 from .hmac import new_hmac + def compute_derived_symmetric_key(secret: str, msg: str) -> bytes: """Computes a derived symmetric key from a secret and a message :param str secret: The secret to use for the key From e8a903122bd9e2ef8fc16c017bf677f668277da9 Mon Sep 17 00:00:00 2001 From: Jim Bennett Date: Sat, 9 May 2020 11:25:40 -0700 Subject: [PATCH 3/7] Removing other dependencies and moving to minimal code --- README.rst | 8 +- adafruit_azureiot/__init__.py | 3 - adafruit_azureiot/base64.py | 81 +++++++++++++ adafruit_azureiot/device_registration.py | 12 +- adafruit_azureiot/hmac.py | 2 +- adafruit_azureiot/iot_mqtt.py | 5 +- adafruit_azureiot/keys.py | 26 ++++- adafruit_azureiot/mpy.sh | 11 ++ adafruit_azureiot/quote.py | 138 +++++++++++++++++++++++ examples/iotcentral_commands.py | 6 +- examples/iotcentral_notconnected.py | 6 +- examples/iotcentral_properties.py | 6 +- examples/iotcentral_simpletest.py | 6 +- examples/iothub_directmethods.py | 6 +- examples/iothub_messages.py | 6 +- examples/iothub_simpletest.py | 6 +- examples/iothub_twin_operations.py | 6 +- requirements.txt | 4 +- setup.py | 4 +- 19 files changed, 274 insertions(+), 68 deletions(-) create mode 100644 adafruit_azureiot/base64.py create mode 100755 adafruit_azureiot/mpy.sh create mode 100644 adafruit_azureiot/quote.py diff --git a/README.rst b/README.rst index 8c8d287..6ca6b08 100644 --- a/README.rst +++ b/README.rst @@ -45,15 +45,11 @@ This driver depends on: * `Adafruit CircuitPython `_ * `Adafruit CircuitPython MiniMQTT `_ - -* `CircuitPython Base64 `_ -* `CircuitPython Parse `_ +* `Adafruit CircuitPython Requests `_ Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading -`the Adafruit library and driver bundle `_ -and -`the CircuitPython community library and driver bundle `_ +`the Adafruit library and driver bundle `_. Usage Example ============= diff --git a/adafruit_azureiot/__init__.py b/adafruit_azureiot/__init__.py index 5f7f04a..7ef6597 100644 --- a/adafruit_azureiot/__init__.py +++ b/adafruit_azureiot/__init__.py @@ -37,9 +37,6 @@ * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice * Adafruit's ESP32SPI library: https://github.com/adafruit/Adafruit_CircuitPython_ESP32SPI -* Community HMAC library: https://github.com/jimbobbennett/CircuitPython_HMAC -* Community base64 library: https://github.com/jimbobbennett/CircuitPython_Base64 -* Community Parse library: https://github.com/jimbobbennett/CircuitPython_Parse """ from .iot_error import IoTError diff --git a/adafruit_azureiot/base64.py b/adafruit_azureiot/base64.py new file mode 100644 index 0000000..4cce82d --- /dev/null +++ b/adafruit_azureiot/base64.py @@ -0,0 +1,81 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 Jim Bennett +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +`base64` +================================================================================ + +RFC 3548: Base64 Data Encodings + + +* Author(s): Jim Bennett + +Implementation Notes +-------------------- + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +import adafruit_binascii as binascii + +__all__ = ["b64encode", "b64decode"] + + +def _bytes_from_decode_data(data: str): + try: + return data.encode("ascii") + except: + raise ValueError("string argument should contain only ASCII characters") + + +def b64encode(toencode: bytes) -> bytes: + """Encode a byte string using Base64. + + toencode is the byte string to encode. Optional altchars must be a byte + string of length 2 which specifies an alternative alphabet for the + '+' and '/' characters. This allows an application to + e.g. generate url or filesystem safe Base64 strings. + + The encoded byte string is returned. + """ + # Strip off the trailing newline + return binascii.b2a_base64(toencode)[:-1] + + +def b64decode(todecode: str) -> bytes: + """Decode a Base64 encoded byte string. + + todecode is the byte string to decode. Optional altchars must be a + string of length 2 which specifies the alternative alphabet used + instead of the '+' and '/' characters. + + The decoded string is returned. A binascii.Error is raised if todecode is + incorrectly padded. + + If validate is False (the default), non-base64-alphabet characters are + discarded prior to the padding check. If validate is True, + non-base64-alphabet characters in the input result in a binascii.Error. + """ + return binascii.a2b_base64(_bytes_from_decode_data(todecode)) diff --git a/adafruit_azureiot/device_registration.py b/adafruit_azureiot/device_registration.py index 1fe1713..6b1f8ef 100644 --- a/adafruit_azureiot/device_registration.py +++ b/adafruit_azureiot/device_registration.py @@ -32,11 +32,11 @@ import gc import json import time -import circuitpython_parse as parse import adafruit_requests as requests import adafruit_logging as logging from adafruit_logging import Logger from . import constants +from .quote import quote from .keys import compute_derived_symmetric_key # Azure HTTP error status codes @@ -96,9 +96,8 @@ def _loop_assign(self, operation_id, headers) -> str: constants.DPS_API_VERSION, ) self._logger.info("- iotc :: _loop_assign :: " + uri) - target = parse.urlparse(uri) - response = self._run_get_request_with_retry(target.geturl(), headers) + response = self._run_get_request_with_retry(uri, headers) try: data = response.json() @@ -193,7 +192,7 @@ def register_device(self, expiry: int) -> str: # pylint: disable=C0103 sr = self._id_scope + "%2Fregistrations%2F" + self._device_id sig_no_encode = compute_derived_symmetric_key(self._key, sr + "\n" + str(expiry)) - sig_encoded = parse.quote(sig_no_encode, "~()*!.'") + sig_encoded = quote(sig_no_encode, "~()*!.'") auth_string = "SharedAccessSignature sr=" + sr + "&sig=" + sig_encoded + "&se=" + str(expiry) + "&skn=registration" headers = { @@ -213,13 +212,12 @@ def register_device(self, expiry: int) -> str: self._device_id, constants.DPS_API_VERSION, ) - target = parse.urlparse(uri) self._logger.info("Connecting...") - self._logger.info("URL: " + target.geturl()) + self._logger.info("URL: " + uri) self._logger.info("body: " + json.dumps(body)) - response = self._run_put_request_with_retry(target.geturl(), body, headers) + response = self._run_put_request_with_retry(uri, body, headers) data = None try: diff --git a/adafruit_azureiot/hmac.py b/adafruit_azureiot/hmac.py index 424069b..ec9737f 100644 --- a/adafruit_azureiot/hmac.py +++ b/adafruit_azureiot/hmac.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """ -`circuitpython_hmac` +`HMAC` ================================================================================ HMAC (Keyed-Hashing for Message Authentication) Python module. diff --git a/adafruit_azureiot/iot_mqtt.py b/adafruit_azureiot/iot_mqtt.py index 3394b43..e4e485d 100644 --- a/adafruit_azureiot/iot_mqtt.py +++ b/adafruit_azureiot/iot_mqtt.py @@ -33,10 +33,10 @@ import time import adafruit_minimqtt as minimqtt from adafruit_minimqtt import MQTT -import circuitpython_parse as parse import adafruit_logging as logging from .iot_error import IoTError from .keys import compute_derived_symmetric_key +from .quote import quote from . import constants # pylint: disable=R0903 @@ -108,7 +108,7 @@ def _gen_sas_token(self) -> str: token_expiry = int(time.time() + self._token_expires) uri = self._hostname + "%2Fdevices%2F" + self._device_id signed_hmac_sha256 = compute_derived_symmetric_key(self._key, uri + "\n" + str(token_expiry)) - signature = parse.quote(signed_hmac_sha256, "~()*!.'") + signature = quote(signed_hmac_sha256, "~()*!.'") if signature.endswith("\n"): # somewhere along the crypto chain a newline is inserted signature = signature[:-1] token = "SharedAccessSignature sr={}&sig={}&se={}".format(uri, signature, token_expiry) @@ -447,6 +447,7 @@ def loop(self) -> None: return self._mqtts.loop() + gc.collect() def send_device_to_cloud_message(self, message, system_properties: dict = None) -> None: """Send a device to cloud message from this device to Azure IoT Hub diff --git a/adafruit_azureiot/keys.py b/adafruit_azureiot/keys.py index ff81c55..08e1b7e 100644 --- a/adafruit_azureiot/keys.py +++ b/adafruit_azureiot/keys.py @@ -1,3 +1,24 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 Jim Bennett +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. """Computes a derived symmetric key from a secret and a message :param str secret: The secret to use for the key :param str msg: The message to use for the key @@ -5,7 +26,7 @@ :rtype: bytes """ -import circuitpython_base64 as base64 +from .base64 import b64decode, b64encode from .hmac import new_hmac @@ -16,5 +37,4 @@ def compute_derived_symmetric_key(secret: str, msg: str) -> bytes: :returns: The derived symmetric key :rtype: bytes """ - secret = base64.b64decode(secret) - return base64.b64encode(new_hmac(secret, msg=msg.encode("utf8")).digest()) + return b64encode(new_hmac(b64decode(secret), msg=msg.encode("utf8")).digest()) diff --git a/adafruit_azureiot/mpy.sh b/adafruit_azureiot/mpy.sh new file mode 100755 index 0000000..80f362f --- /dev/null +++ b/adafruit_azureiot/mpy.sh @@ -0,0 +1,11 @@ +~/Downloads/mpy-cross/mpy-cross __init__.py +~/Downloads/mpy-cross/mpy-cross constants.py +~/Downloads/mpy-cross/mpy-cross device_registration.py +~/Downloads/mpy-cross/mpy-cross hmac.py +~/Downloads/mpy-cross/mpy-cross iot_error.py +~/Downloads/mpy-cross/mpy-cross iot_mqtt.py +~/Downloads/mpy-cross/mpy-cross iotcentral_device.py +~/Downloads/mpy-cross/mpy-cross iothub_device.py +~/Downloads/mpy-cross/mpy-cross keys.py +~/Downloads/mpy-cross/mpy-cross quote.py +~/Downloads/mpy-cross/mpy-cross base64.py \ No newline at end of file diff --git a/adafruit_azureiot/quote.py b/adafruit_azureiot/quote.py new file mode 100644 index 0000000..5c8dd7a --- /dev/null +++ b/adafruit_azureiot/quote.py @@ -0,0 +1,138 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 Jim Bennett +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +`quote` +================================================================================ + +The quote function %-escapes all characters that are neither in the +unreserved chars ("always safe") nor the additional chars set via the +safe arg. + +""" +_ALWAYS_SAFE = frozenset(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ" b"abcdefghijklmnopqrstuvwxyz" b"0123456789" b"_.-~") +_ALWAYS_SAFE_BYTES = bytes(_ALWAYS_SAFE) +SAFE_QUOTERS = {} + + +def quote(bytes_val: bytes, safe="/"): + """The quote function %-escapes all characters that are neither in the + unreserved chars ("always safe") nor the additional chars set via the + safe arg. + """ + if not isinstance(bytes_val, (bytes, bytearray)): + raise TypeError("quote_from_bytes() expected bytes") + if not bytes_val: + return "" + if isinstance(safe, str): + # Normalize 'safe' by converting to bytes and removing non-ASCII chars + safe = safe.encode("ascii", "ignore") + else: + safe = bytes([char for char in safe if char < 128]) + if not bytes_val.rstrip(_ALWAYS_SAFE_BYTES + safe): + return bytes_val.decode() + try: + quoter = SAFE_QUOTERS[safe] + except KeyError: + SAFE_QUOTERS[safe] = quoter = Quoter(safe).__getitem__ + return "".join([quoter(char) for char in bytes_val]) + + +# pylint: disable=C0103 +class defaultdict: + """ + Default Dict Implementation. + + Defaultdcit that returns the key if the key is not found in dictionnary (see + unswap in karma-lib): + >>> d = defaultdict(default=lambda key: key) + >>> d['foo'] = 'bar' + >>> d['foo'] + 'bar' + >>> d['baz'] + 'baz' + DefaultDict that returns an empty string if the key is not found (see + prefix in karma-lib for typical usage): + >>> d = defaultdict(default=lambda key: '') + >>> d['foo'] = 'bar' + >>> d['foo'] + 'bar' + >>> d['baz'] + '' + Representation of a default dict: + >>> defaultdict([('foo', 'bar')]) + defaultdict(None, {'foo': 'bar'}) + """ + + @staticmethod + # pylint: disable=W0613 + def __new__(cls, default_factory=None, **kwargs): + self = super(defaultdict, cls).__new__(cls) + # pylint: disable=C0103 + self.d = {} + return self + + def __init__(self, default_factory=None, **kwargs): + self.d = kwargs + self.default_factory = default_factory + + def __getitem__(self, key): + try: + return self.d[key] + except KeyError: + val = self.__missing__(key) + self.d[key] = val + return val + + def __setitem__(self, key, val): + self.d[key] = val + + def __delitem__(self, key): + del self.d[key] + + def __contains__(self, key): + return key in self.d + + def __missing__(self, key): + if self.default_factory is None: + raise KeyError(key) + return self.default_factory() + + +class Quoter(defaultdict): + """A mapping from bytes (in range(0,256)) to strings. + + String values are percent-encoded byte values, unless the key < 128, and + in the "safe" set (either the specified safe set, or default set). + """ + + # Keeps a cache internally, using defaultdict, for efficiency (lookups + # of cached keys don't call Python code at all). + def __init__(self, safe): + """safe: bytes object.""" + super(Quoter, self).__init__() + self.safe = _ALWAYS_SAFE.union(safe) + + def __missing__(self, b): + # Handle a cache miss. Store quoted string in cache and return. + res = chr(b) if b in self.safe else "%{:02X}".format(b) + self[b] = res + return res diff --git a/examples/iotcentral_commands.py b/examples/iotcentral_commands.py index e8c3a56..a12a801 100644 --- a/examples/iotcentral_commands.py +++ b/examples/iotcentral_commands.py @@ -85,11 +85,7 @@ # # From the Adafruit CircuitPython Bundle (https://github.com/adafruit/Adafruit_CircuitPython_Bundle): # * adafruit-circuitpython-minimqtt -# -# From the CircuitPython Community LIbrary and Driver Bundle (https://github.com/adafruit/CircuitPython_Community_Bundle): -# * circuitpython-hmac -# * circuitpython-base64 -# * circuitpython-parse +# * adafruit-circuitpython-requests from adafruit_azureiot import IoTCentralDevice from adafruit_azureiot.iot_mqtt import IoTResponse diff --git a/examples/iotcentral_notconnected.py b/examples/iotcentral_notconnected.py index 4a9f4d6..d81107a 100644 --- a/examples/iotcentral_notconnected.py +++ b/examples/iotcentral_notconnected.py @@ -87,11 +87,7 @@ # # From the Adafruit CircuitPython Bundle (https://github.com/adafruit/Adafruit_CircuitPython_Bundle): # * adafruit-circuitpython-minimqtt -# -# From the CircuitPython Community LIbrary and Driver Bundle (https://github.com/adafruit/CircuitPython_Community_Bundle): -# * circuitpython-hmac -# * circuitpython-base64 -# * circuitpython-parse +# * adafruit-circuitpython-requests from adafruit_azureiot import IoTCentralDevice, IoTError # Create an IoT Hub device client and connect diff --git a/examples/iotcentral_properties.py b/examples/iotcentral_properties.py index 15dde99..71caf6b 100644 --- a/examples/iotcentral_properties.py +++ b/examples/iotcentral_properties.py @@ -86,11 +86,7 @@ # # From the Adafruit CircuitPython Bundle (https://github.com/adafruit/Adafruit_CircuitPython_Bundle): # * adafruit-circuitpython-minimqtt -# -# From the CircuitPython Community LIbrary and Driver Bundle (https://github.com/adafruit/CircuitPython_Community_Bundle): -# * circuitpython-hmac -# * circuitpython-base64 -# * circuitpython-parse +# * adafruit-circuitpython-requests from adafruit_azureiot import IoTCentralDevice # Create an IoT Hub device client and connect diff --git a/examples/iotcentral_simpletest.py b/examples/iotcentral_simpletest.py index 571ee38..e0cbf96 100644 --- a/examples/iotcentral_simpletest.py +++ b/examples/iotcentral_simpletest.py @@ -87,11 +87,7 @@ # # From the Adafruit CircuitPython Bundle (https://github.com/adafruit/Adafruit_CircuitPython_Bundle): # * adafruit-circuitpython-minimqtt -# -# From the CircuitPython Community LIbrary and Driver Bundle (https://github.com/adafruit/CircuitPython_Community_Bundle): -# * circuitpython-hmac -# * circuitpython-base64 -# * circuitpython-parse +# * adafruit-circuitpython-requests from adafruit_azureiot import IoTCentralDevice # Create an IoT Hub device client and connect diff --git a/examples/iothub_directmethods.py b/examples/iothub_directmethods.py index 341a51f..2a00462 100644 --- a/examples/iothub_directmethods.py +++ b/examples/iothub_directmethods.py @@ -80,11 +80,7 @@ # # From the Adafruit CircuitPython Bundle (https://github.com/adafruit/Adafruit_CircuitPython_Bundle): # * adafruit-circuitpython-minimqtt -# -# From the CircuitPython Community LIbrary and Driver Bundle (https://github.com/adafruit/CircuitPython_Community_Bundle): -# * circuitpython-hmac -# * circuitpython-base64 -# * circuitpython-parse +# * adafruit-circuitpython-requests from adafruit_azureiot import IoTHubDevice from adafruit_azureiot.iot_mqtt import IoTResponse diff --git a/examples/iothub_messages.py b/examples/iothub_messages.py index 52524b1..decd7bc 100644 --- a/examples/iothub_messages.py +++ b/examples/iothub_messages.py @@ -82,11 +82,7 @@ # # From the Adafruit CircuitPython Bundle (https://github.com/adafruit/Adafruit_CircuitPython_Bundle): # * adafruit-circuitpython-minimqtt -# -# From the CircuitPython Community LIbrary and Driver Bundle (https://github.com/adafruit/CircuitPython_Community_Bundle): -# * circuitpython-hmac -# * circuitpython-base64 -# * circuitpython-parse +# * adafruit-circuitpython-requests from adafruit_azureiot import IoTHubDevice # Create an IoT Hub device client and connect diff --git a/examples/iothub_simpletest.py b/examples/iothub_simpletest.py index 703190c..5ae4f0f 100644 --- a/examples/iothub_simpletest.py +++ b/examples/iothub_simpletest.py @@ -82,11 +82,7 @@ # # From the Adafruit CircuitPython Bundle (https://github.com/adafruit/Adafruit_CircuitPython_Bundle): # * adafruit-circuitpython-minimqtt -# -# From the CircuitPython Community LIbrary and Driver Bundle (https://github.com/adafruit/CircuitPython_Community_Bundle): -# * circuitpython-hmac -# * circuitpython-base64 -# * circuitpython-parse +# * adafruit-circuitpython-requests from adafruit_azureiot import IoTHubDevice # Create an IoT Hub device client and connect diff --git a/examples/iothub_twin_operations.py b/examples/iothub_twin_operations.py index 83ef88b..12a78f8 100644 --- a/examples/iothub_twin_operations.py +++ b/examples/iothub_twin_operations.py @@ -84,11 +84,7 @@ # # From the Adafruit CircuitPython Bundle (https://github.com/adafruit/Adafruit_CircuitPython_Bundle): # * adafruit-circuitpython-minimqtt -# -# From the CircuitPython Community LIbrary and Driver Bundle (https://github.com/adafruit/CircuitPython_Community_Bundle): -# * circuitpython-hmac -# * circuitpython-base64 -# * circuitpython-parse +# * adafruit-circuitpython-requests from adafruit_azureiot import IoTHubDevice # Create an IoT Hub device client and connect diff --git a/requirements.txt b/requirements.txt index 711349f..a851f35 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,3 @@ Adafruit-Blinka Adafruit-CircuitPython-miniMQTT -CircuitPython-HMAC -CircuitPython-Base64 -CircuitPython-Parse \ No newline at end of file +Adafruit-CircuitPython-Requests \ No newline at end of file diff --git a/setup.py b/setup.py index a0cbef2..8550902 100644 --- a/setup.py +++ b/setup.py @@ -31,10 +31,8 @@ author_email="circuitpython@adafruit.com", install_requires=[ "Adafruit-Blinka", - "Adafruit_CircuitPython_ESP32SPI", "Adafruit-CircuitPython-miniMQTT", - "CircuitPython-Base64", - "CircuitPython-Parse", + "Adafruit-CircuitPython-Requests" ], # Choose your license license="MIT", From f241826da8ba2f3b480e1ae9143ddef82e0f5a81 Mon Sep 17 00:00:00 2001 From: Jim Bennett Date: Sat, 9 May 2020 11:32:25 -0700 Subject: [PATCH 4/7] Deleting rogue testing file --- adafruit_azureiot/mpy.sh | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100755 adafruit_azureiot/mpy.sh diff --git a/adafruit_azureiot/mpy.sh b/adafruit_azureiot/mpy.sh deleted file mode 100755 index 80f362f..0000000 --- a/adafruit_azureiot/mpy.sh +++ /dev/null @@ -1,11 +0,0 @@ -~/Downloads/mpy-cross/mpy-cross __init__.py -~/Downloads/mpy-cross/mpy-cross constants.py -~/Downloads/mpy-cross/mpy-cross device_registration.py -~/Downloads/mpy-cross/mpy-cross hmac.py -~/Downloads/mpy-cross/mpy-cross iot_error.py -~/Downloads/mpy-cross/mpy-cross iot_mqtt.py -~/Downloads/mpy-cross/mpy-cross iotcentral_device.py -~/Downloads/mpy-cross/mpy-cross iothub_device.py -~/Downloads/mpy-cross/mpy-cross keys.py -~/Downloads/mpy-cross/mpy-cross quote.py -~/Downloads/mpy-cross/mpy-cross base64.py \ No newline at end of file From 3fc34530648dc8c3ec6cf6a89368742124380910 Mon Sep 17 00:00:00 2001 From: Jim Bennett Date: Sat, 9 May 2020 11:34:10 -0700 Subject: [PATCH 5/7] formatting --- setup.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 8550902..141d791 100644 --- a/setup.py +++ b/setup.py @@ -29,11 +29,7 @@ # Author details author="Adafruit Industries", author_email="circuitpython@adafruit.com", - install_requires=[ - "Adafruit-Blinka", - "Adafruit-CircuitPython-miniMQTT", - "Adafruit-CircuitPython-Requests" - ], + install_requires=["Adafruit-Blinka", "Adafruit-CircuitPython-miniMQTT", "Adafruit-CircuitPython-Requests"], # Choose your license license="MIT", # See https://pypi.python.org/pypi?%3Aaction=list_classifiers From 8c0b6a539906e4a38cbf93784b7c47280d78915a Mon Sep 17 00:00:00 2001 From: Jim Bennett Date: Sat, 9 May 2020 11:44:03 -0700 Subject: [PATCH 6/7] Fixing build --- README.rst | 1 + docs/conf.py | 2 +- requirements.txt | 3 ++- setup.py | 7 ++++++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 6ca6b08..ba80538 100644 --- a/README.rst +++ b/README.rst @@ -46,6 +46,7 @@ This driver depends on: * `Adafruit CircuitPython `_ * `Adafruit CircuitPython MiniMQTT `_ * `Adafruit CircuitPython Requests `_ +* `Adafruit CircuitPython BinASCII `_ Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading diff --git a/docs/conf.py b/docs/conf.py index 41677b7..e468391 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,7 +21,7 @@ # Uncomment the below if you use native CircuitPython modules such as # digitalio, micropython and busio. List the modules you use. Without it, the # autodoc module docs will fail to generate with a warning. -autodoc_mock_imports = ["adafruit_logging", "adafruit_requests", "adafruit_hashlib", "adafruit_ntp"] +autodoc_mock_imports = ["adafruit_binascii", "adafruit_logging", "adafruit_requests", "adafruit_hashlib", "adafruit_ntp"] intersphinx_mapping = { diff --git a/requirements.txt b/requirements.txt index a851f35..f488495 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ Adafruit-Blinka Adafruit-CircuitPython-miniMQTT -Adafruit-CircuitPython-Requests \ No newline at end of file +Adafruit-CircuitPython-Requests +Adafruit-CircuitPython-Binascii \ No newline at end of file diff --git a/setup.py b/setup.py index 141d791..0af8ac5 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,12 @@ # Author details author="Adafruit Industries", author_email="circuitpython@adafruit.com", - install_requires=["Adafruit-Blinka", "Adafruit-CircuitPython-miniMQTT", "Adafruit-CircuitPython-Requests"], + install_requires=[ + "Adafruit-Blinka", + "Adafruit-CircuitPython-miniMQTT", + "Adafruit-CircuitPython-Requests", + "Adafruit-CircuitPython-Binascii", + ], # Choose your license license="MIT", # See https://pypi.python.org/pypi?%3Aaction=list_classifiers From 3c3e4fc0ac863de6629e2ea278a29a3a48013577 Mon Sep 17 00:00:00 2001 From: Jim Bennett Date: Sat, 9 May 2020 13:44:14 -0700 Subject: [PATCH 7/7] Improving runtime memory usage with large data blocks --- adafruit_azureiot/iot_mqtt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_azureiot/iot_mqtt.py b/adafruit_azureiot/iot_mqtt.py index e4e485d..f819557 100644 --- a/adafruit_azureiot/iot_mqtt.py +++ b/adafruit_azureiot/iot_mqtt.py @@ -235,6 +235,7 @@ def _handle_direct_method(self, msg: str, topic: str) -> None: method_name = topic[len_temp : topic.find("/", len_temp + 1)] ret = self._callback.direct_method_invoked(method_name, msg) + gc.collect() ret_code = 200 ret_message = "{}" @@ -261,13 +262,14 @@ def _handle_cloud_to_device_message(self, msg: str, topic: str) -> None: properties[key_value[0]] = key_value[1] self._callback.cloud_to_device_message_received(msg, properties) + gc.collect() # pylint: disable=W0702, R0912 def _on_message(self, client, msg_topic, payload) -> None: topic = "" msg = None - self._logger.info("- iot_mqtt :: _on_message :: payload(" + str(payload) + ")") + self._logger.info("- iot_mqtt :: _on_message") if payload is not None: try: