Add support for A71CH
- Add a71ch.py and a71ch_pubkey.py. These provide the same functionality
as the ecc608 variants.
- Modify core to check both security chips. If both are present, ecc608
would be used.
- Adjust debian dependencies to require either python3-cryptoauthlib, or
a71ch-crypto-support.
- Downgrade messages about not finding a crypto chip to debug, since it
is unlikely that you have both chips.
Change-Id: I09e04eb7025b19a0c8b5b4e608f4e7d4605c9d19
diff --git a/python/coral-cloudiot/coral/cloudiot/a71ch.py b/python/coral-cloudiot/coral/cloudiot/a71ch.py
new file mode 100644
index 0000000..9c616b1
--- /dev/null
+++ b/python/coral-cloudiot/coral/cloudiot/a71ch.py
@@ -0,0 +1,141 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import base64
+import ctypes
+import jwt
+import logging
+import subprocess
+import tempfile
+from asn1crypto.core import Sequence
+from coral.cloudiot.utils import ascii_hex_string
+from cryptography.hazmat.primitives import hashes
+
+logger = logging.getLogger(__name__)
+library = None
+kA71ChOk = 0x9000
+
+
+def a71ch_serial():
+ uid_len = 18
+ ret_len = ctypes.c_uint16(uid_len)
+ uid = ctypes.create_string_buffer(uid_len)
+ get_unique_id = library.A71_GetUniqueID
+ get_unique_id.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint16)]
+ get_unique_id.restype = ctypes.c_uint16
+ assert get_unique_id(uid, ctypes.byref(ret_len)) == kA71ChOk
+ return ascii_hex_string(uid.raw, l=uid_len)
+
+
+def a71ch_public_key():
+ with tempfile.NamedTemporaryFile(mode='w+') as tempkey:
+ subprocess.check_call(['A71CHConfigTool', 'get', 'pub', '-c', '10', '-x',
+ '0', '-k', tempkey.name],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ public_key = '\n'.join([x.strip() for x in tempkey.readlines()])
+
+ return public_key
+
+
+def a71ch_hw_sign(msg, key_id=0):
+ get_sha256 = library.A71_GetSha256
+ get_sha256.argtypes = [ctypes.c_char_p, ctypes.c_uint16,
+ ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint16)]
+ get_sha256.restype = ctypes.c_uint16
+ hash = ctypes.create_string_buffer(32)
+ hash_len = ctypes.c_uint16(32)
+ assert get_sha256(msg, ctypes.c_uint16(len(msg)), hash,
+ ctypes.byref(hash_len)) == kA71ChOk
+
+ ecc_sign = library.A71_EccSign
+ ecc_sign.argtypes = [ctypes.c_uint8, ctypes.c_char_p,
+ ctypes.c_uint16, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint16)]
+ ecc_sign.restype = ctypes.c_uint16
+ sig = ctypes.create_string_buffer(256)
+ sig_len = ctypes.c_uint16(256)
+ assert ecc_sign(key_id, hash, hash_len, sig,
+ ctypes.byref(sig_len)) == kA71ChOk
+
+ asn1 = Sequence.load(sig.raw)
+ signature = asn1[0].native.to_bytes(
+ 32, 'big') + asn1[1].native.to_bytes(32, 'big')
+ return signature
+
+
+class HwEcAlgorithm(jwt.algorithms.Algorithm):
+ def __init__(self):
+ self.hash_alg = hashes.SHA256
+
+ def prepare_key(self, key):
+ return key
+
+ def sign(self, msg, key):
+ return a71ch_hw_sign(msg)
+
+ def verify(self, msg, key, sig):
+ try:
+ der_sig = jwt.utils.raw_to_der_signature(sig, key.curve)
+ except ValueError:
+ return False
+
+ try:
+ key.verify(der_sig, msg, ec.ECDSA(self.hash_alg()))
+ return True
+ except InvalidSignature:
+ return False
+
+
+class SmCommState_t(ctypes.Structure):
+ pass
+
+
+SmCommState_t.__slots__ = [
+ 'connType',
+ 'param1',
+ 'param2',
+ 'hostLibVersion',
+ 'appletVersion',
+ 'sbVersion',
+ 'skip_select_applet',
+]
+SmCommState_t.__fields__ = [
+ ('connType', ctypes.c_uint16),
+ ('param1', ctypes.c_uint16),
+ ('param2', ctypes.c_uint16),
+ ('hostLibVersion', ctypes.c_uint16),
+ ('appletVersion', ctypes.c_uint32),
+ ('sbVersion', ctypes.c_uint16),
+ ('skip_select_applet', ctypes.c_uint8),
+]
+
+try:
+ a71ch_jwt_with_hw_alg = None
+
+ library = ctypes.cdll.LoadLibrary('libsss_engine.so')
+ sm_connect = library.SM_Connect
+ sm_connect.argtypes = [ctypes.POINTER(None),
+ ctypes.POINTER(SmCommState_t), ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint16)]
+ sm_connect.restype = ctypes.c_uint16
+
+ comm_state = SmCommState_t()
+ atr_len = ctypes.c_uint16(64)
+ atr = ctypes.create_string_buffer(64)
+ assert sm_connect(None, ctypes.byref(comm_state), atr,
+ ctypes.byref(atr_len)) == kA71ChOk
+
+ a71ch_jwt_with_hw_alg = jwt.PyJWT(algorithms=[])
+ a71ch_jwt_with_hw_alg.register_algorithm('ES256', HwEcAlgorithm())
+
+except Exception as e:
+ logger.debug('Unable to load A71CH')
diff --git a/python/coral-cloudiot/coral/cloudiot/a71ch_pubkey.py b/python/coral-cloudiot/coral/cloudiot/a71ch_pubkey.py
new file mode 100755
index 0000000..b0b39f4
--- /dev/null
+++ b/python/coral-cloudiot/coral/cloudiot/a71ch_pubkey.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+
+from a71ch import a71ch_public_key, a71ch_serial
+
+
+def main():
+
+ print('Serial Number: %s\n\n' % a71ch_serial())
+
+ print(a71ch_public_key())
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/python/coral-cloudiot/coral/cloudiot/core.py b/python/coral-cloudiot/coral/cloudiot/core.py
index ef8f50c..e2e4bee 100644
--- a/python/coral-cloudiot/coral/cloudiot/core.py
+++ b/python/coral-cloudiot/coral/cloudiot/core.py
@@ -26,6 +26,7 @@
import threading
import time
+from coral.cloudiot.a71ch import a71ch_jwt_with_hw_alg
from coral.cloudiot.ecc608 import ecc608_jwt_with_hw_alg
logger = logging.getLogger(__name__)
@@ -50,13 +51,13 @@
"""
self._config = configparser.ConfigParser()
if not self._config.read(config_file):
- logger.warn('No valid config provided (reading %s).\nCloud IoT is disabled.' % config_file)
+ logger.warning('No valid config provided (reading %s).\nCloud IoT is disabled.' % config_file)
self._enabled = False
return
if not self._config.getboolean(config_section, 'Enabled'):
- logger.warn('Cloud IoT is disabled per configuration.')
+ logger.warning('Cloud IoT is disabled per configuration.')
self._enabled = False
return
@@ -77,8 +78,13 @@
self._algorithm = 'ES256'
self._private_key = None
self._jwt_inst = ecc608_jwt_with_hw_alg
+ elif a71ch_jwt_with_hw_alg:
+ self._algorithm = 'ES256'
+ self._private_key = None
+ self._jwt_inst = a71ch_jwt_with_hw_alg
else:
# For SW, use RS256 on a key file provided in the configuration.
+ logger.warning('Using SW crypto')
self._algorithm = 'RS256'
rsa_cert = config['RSACertFile']
with open(rsa_cert, 'r') as f:
diff --git a/python/coral-cloudiot/coral/cloudiot/ecc608.py b/python/coral-cloudiot/coral/cloudiot/ecc608.py
index e3c52e8..f4b89dc 100644
--- a/python/coral-cloudiot/coral/cloudiot/ecc608.py
+++ b/python/coral-cloudiot/coral/cloudiot/ecc608.py
@@ -19,25 +19,19 @@
import os
import sys
import jwt
+from coral.cloudiot.utils import ascii_hex_string
+from coral.cloudiot.utils import split_equal_parts
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
-from cryptoauthlib import *
+try:
+ from cryptoauthlib import *
+except:
+ pass
logger = logging.getLogger(__name__)
-def _split_equal_parts(line, n):
- return [line[i:i+n] for i in range(0, len(line), n)]
-
-
-def _ascii_hex_string(a, l=16):
- """
- Format a bytearray object into a formatted ascii hex string
- """
- return '\n'.join(x.hex().upper() for x in _split_equal_parts(a, l))
-
-
def _convert_ec_pub_to_pem(raw_pub_key):
"""
Convert to the key to PEM format. Expects bytes
@@ -46,7 +40,7 @@
'3059301306072A8648CE3D020106082A8648CE3D03010703420004') + raw_pub_key
public_key_b64 = base64.b64encode(public_key_der).decode('ascii')
public_key_pem = '-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----' % '\n'.join(
- _split_equal_parts(public_key_b64, 64))
+ split_equal_parts(public_key_b64, 64))
return public_key_pem
@@ -71,7 +65,7 @@
if _ecc608_check_address(bus, addr):
logger.info('Found crypto chip at 0x%x', addr)
return addr
- logger.warning('No crypto detected, using SW.')
+ logger.debug('ECC608 not detected')
return None
@@ -111,7 +105,7 @@
def ecc608_serial():
serial = bytearray(9)
assert atcab_read_serial_number(serial) == 0
- return _ascii_hex_string(serial)
+ return ascii_hex_string(serial)
def ecc608_public_key(key_id=0):
@@ -156,4 +150,4 @@
ecc608_jwt_with_hw_alg = jwt.PyJWT(algorithms=[])
ecc608_jwt_with_hw_alg.register_algorithm('ES256', HwEcAlgorithm())
except Exception:
- logger.warning('Unable to load HW crypto library, using SW.')
+ logger.debug('Unable to load ECC608')
diff --git a/python/coral-cloudiot/coral/cloudiot/utils.py b/python/coral-cloudiot/coral/cloudiot/utils.py
new file mode 100644
index 0000000..da302cb
--- /dev/null
+++ b/python/coral-cloudiot/coral/cloudiot/utils.py
@@ -0,0 +1,23 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+def split_equal_parts(line, n):
+ return [line[i:i+n] for i in range(0, len(line), n)]
+
+
+def ascii_hex_string(a, l=16):
+ """
+ Format a bytearray object into a formatted ascii hex string
+ """
+ return '\n'.join(x.hex().upper() for x in split_equal_parts(a, l))
diff --git a/python/coral-cloudiot/debian/changelog b/python/coral-cloudiot/debian/changelog
index da34dd8..a877311 100644
--- a/python/coral-cloudiot/debian/changelog
+++ b/python/coral-cloudiot/debian/changelog
@@ -1,3 +1,9 @@
+coral-cloudiot (1.3) stable; urgency=medium
+
+ * Add support for A71CH.
+
+ -- Coral <coral-support@google.com> Tue, 10 Nov 2020 12:04:06 -0800
+
coral-cloudiot (1.2) stable; urgency=low
* Bug fix for multiple i2c busses.
diff --git a/python/coral-cloudiot/debian/control b/python/coral-cloudiot/debian/control
index 3baa549..d159ef6 100644
--- a/python/coral-cloudiot/debian/control
+++ b/python/coral-cloudiot/debian/control
@@ -10,10 +10,10 @@
Architecture: all
Depends: ${misc:Depends},
${python3:Depends},
- python3-cryptoauthlib,
python3-jwt,
python3-paho-mqtt,
- python3-cryptography
+ python3-cryptography,
+ a71ch-crypto-support | python3-cryptoauthlib
Description: Coral Cloud IoT API
API for connected Coral devices to Google Cloud Platform.