diff --git a/middleware/admin/certificate.py b/middleware/admin/certificate.py index 64b3bb6b..1b86f791 100644 --- a/middleware/admin/certificate.py +++ b/middleware/admin/certificate.py @@ -21,4 +21,6 @@ # SOFTWARE. from .certificate_v1 import HSMCertificate, HSMCertificateElement -from .certificate_v2 import HSMCertificateV2, HSMCertificateV2Element +from .certificate_v2 import HSMCertificateV2, HSMCertificateV2ElementSGXQuote, \ + HSMCertificateV2ElementSGXAttestationKey, \ + HSMCertificateV2ElementX509 diff --git a/middleware/admin/certificate_v2.py b/middleware/admin/certificate_v2.py index 61e74ce5..962fa3a0 100644 --- a/middleware/admin/certificate_v2.py +++ b/middleware/admin/certificate_v2.py @@ -24,16 +24,62 @@ class HSMCertificateV2Element: - # TODO: actual logic and subclasses - def __init__(self, element_map): - self.element_map = element_map + pass - # Stub - def name(self): - return "attestation" + +class HSMCertificateV2ElementSGXQuote(HSMCertificateV2Element): + def __init__(self, name, message, custom_data, signature, signed_by): + self.name = name + self.message = message + self.custom_data = custom_data + self.signature = signature + self.signed_by = signed_by + + def to_dict(self): + return { + "name": self.name, + "type": "sgx_quote", + "message": self.message.hex(), + "custom_data": self.custom_data.hex(), + "signature": self.signature.hex(), + "signed_by": self.signed_by, + } + + +class HSMCertificateV2ElementSGXAttestationKey(HSMCertificateV2Element): + def __init__(self, name, message, key, auth_data, signature, signed_by): + self.name = name + self.message = message + self.key = key + self.auth_data = auth_data + self.signature = signature + self.signed_by = signed_by + + def to_dict(self): + return { + "name": self.name, + "type": "sgx_attestation_key", + "message": self.message.hex(), + "key": self.key.hex(), + "auth_data": self.auth_data.hex(), + "signature": self.signature.hex(), + "signed_by": self.signed_by, + } + + +class HSMCertificateV2ElementX509(HSMCertificateV2Element): + def __init__(self, name, message, signed_by): + self.name = name + self.message = message + self.signed_by = signed_by def to_dict(self): - return self.element_map + return { + "name": self.name, + "type": "x509_pem", + "message": self.message.decode('ASCII'), + "signed_by": self.signed_by, + } class HSMCertificateV2(HSMCertificate): diff --git a/middleware/admin/sgx_attestation.py b/middleware/admin/sgx_attestation.py index c261f0ba..364df5b2 100644 --- a/middleware/admin/sgx_attestation.py +++ b/middleware/admin/sgx_attestation.py @@ -20,9 +20,13 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import ecdsa from .misc import info, head, get_hsm, AdminError, get_ud_value_for_attestation from .unlock import do_unlock -from .certificate import HSMCertificateV2, HSMCertificateV2Element +from sgx.envelope import SgxEnvelope +from .certificate import HSMCertificateV2, HSMCertificateV2ElementSGXQuote, \ + HSMCertificateV2ElementSGXAttestationKey, \ + HSMCertificateV2ElementX509 def do_attestation(options): @@ -58,14 +62,72 @@ def do_attestation(options): hsm.disconnect() + # Parse envelope + info("Parsing the powHSM attestation envelope...") + try: + envelope = SgxEnvelope( + bytes.fromhex(powhsm_attestation["envelope"]), + bytes.fromhex(powhsm_attestation["message"])) + except Exception as e: + raise AdminError(f"SGX envelope parse error: {str(e)}") + + # Conversions + quote_signature = ecdsa.util.sigdecode_string( + envelope.quote_auth_data.signature.r + + envelope.quote_auth_data.signature.s, + ecdsa.NIST256p.order) + quote_signature = ecdsa.util.sigencode_der( + quote_signature[0], + quote_signature[1], + ecdsa.NIST256p.order) + att_key = ecdsa.VerifyingKey.from_string( + envelope.quote_auth_data.attestation_key.x + + envelope.quote_auth_data.attestation_key.y, + ecdsa.NIST256p) + qe_rb_signature = ecdsa.util.sigdecode_string( + envelope.quote_auth_data.qe_report_body_signature.r + + envelope.quote_auth_data.qe_report_body_signature.s, + ecdsa.NIST256p.order) + qe_rb_signature = ecdsa.util.sigencode_der( + qe_rb_signature[0], + qe_rb_signature[1], + ecdsa.NIST256p.order) + # Generate and save the attestation certificate info("Generating the attestation certificate... ", options.verbose) - att_cert = HSMCertificateV2() - # TODO: - # 1. Parse envelope - # 2. Add actual elements of the certificate - att_cert.add_element(HSMCertificateV2Element(powhsm_attestation)) + + att_cert.add_element( + HSMCertificateV2ElementSGXQuote( + name="quote", + message=envelope.quote.get_raw_data(), + custom_data=envelope.custom_message, + signature=quote_signature, + signed_by="attestation", + )) + att_cert.add_element( + HSMCertificateV2ElementSGXAttestationKey( + name="attestation", + message=envelope.quote_auth_data.qe_report_body.get_raw_data(), + key=att_key.to_string("uncompressed"), + auth_data=envelope.qe_auth_data.data, + signature=qe_rb_signature, + signed_by="quoting_enclave", + )) + att_cert.add_element( + HSMCertificateV2ElementX509( + name="quoting_enclave", + message=envelope.qe_cert_data.certs[0], + signed_by="platform_ca", + )) + att_cert.add_element( + HSMCertificateV2ElementX509( + name="platform_ca", + message=envelope.qe_cert_data.certs[1], + signed_by="sgx_root", + )) + + att_cert.add_target("quote") att_cert.save_to_jsonfile(options.output_file_path) info(f"Attestation certificate saved to {options.output_file_path}") diff --git a/middleware/comm/cstruct.py b/middleware/comm/cstruct.py new file mode 100644 index 00000000..77203988 --- /dev/null +++ b/middleware/comm/cstruct.py @@ -0,0 +1,163 @@ +# The MIT License (MIT) +# +# Copyright (c) 2021 RSK Labs Ltd +# +# 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. + + +import struct +import re + + +class CStruct: + MAP = { + "uint8_t": ["B", "s"], + "uint16_t": "H", + "uint32_t": "I", + "uint64_t": "Q", + } + SPEC = None + TYPENAME = None + + @classmethod + def _spec(cls, little=True): + if cls.SPEC is None or little not in cls.SPEC: + fmt = "<" if little else ">" + atrmap = {} + names = [] + types = [] + index = 0 + typename = None + for line in cls.__doc__.split("\n")[1:]: + line = re.sub(r"\s+", " ", line.strip()) + if line == "": + continue + if typename is None: + typename = line + continue + tspec = line.split(" ") + + length = "" if len(tspec) < 3 else str(int(tspec[2].strip(), 10)) + + typ = tspec[0].strip() + actual_type = None + derived_type = None + if typ not in cls.MAP.keys(): + for kls in cls.__base__.__subclasses__(): + if cls != kls: + if typ == kls._typename(): + actual_type = kls + derived_type = kls + if derived_type is None: + raise ValueError(f"Invalid type: {typ}") + else: + actual_type = cls.MAP[typ] + + if length != "" and not isinstance(actual_type, list): + raise ValueError(f"Invalid type spec: {line}") + + name = tspec[1].strip() + + if isinstance(actual_type, list): + actual_type = actual_type[0] if length == "" else actual_type[1] + elif not isinstance(actual_type, str) and \ + issubclass(actual_type, cls.__base__): + actual_type = str(actual_type.get_bytelength(little)) + "s" + + fmt += length + actual_type + names.append(name) + types.append(derived_type) + atrmap[name] = index + index += 1 + if cls.SPEC is None: + cls.SPEC = {} + cls.SPEC[little] = (struct.Struct(fmt), atrmap, names, types, typename) + + return cls.SPEC[little] + + @classmethod + def _struct(cls, little=True): + return cls._spec(little)[0] + + @classmethod + def _atrmap(cls, little=True): + return cls._spec(little)[1] + + @classmethod + def _names(cls, little=True): + return cls._spec(little)[2] + + @classmethod + def _types(cls, little=True): + return cls._spec(little)[3] + + @classmethod + def _typename(cls): + if cls.TYPENAME is None: + for line in cls.__doc__.split("\n"): + line = re.sub(r"\s+", " ", line.strip()) + if line == "": + continue + cls.TYPENAME = line + break + + return cls.TYPENAME + + @classmethod + def get_bytelength(cls, little=True): + return cls._struct(little).size + + def __init__(self, value, offset=0, little=True): + self._offset = offset + self._little = little + self._raw_value = value + + try: + self._parsed = list(self._struct(little).unpack_from(value, offset)) + except Exception as e: + raise ValueError(f"While parsing: {e}") + + for index, derived_type in enumerate(self._types(little)): + if derived_type is not None: + self._parsed[index] = derived_type(self._parsed[index], little=little) + + def _value(self, name): + amap = self._atrmap(self._little) + if name in amap: + return self._parsed[amap[name]] + raise NameError(f"Property {name} does not exist") + + def __getattr__(self, name): + return self._value(name) + + def get_raw_data(self): + return self._raw_value[ + self._offset:self._offset+self.get_bytelength(self._little)] + + def to_dict(self): + result = {} + for name in self._names(self._little): + value = self._value(name) + if isinstance(value, bytes): + value = value.hex() + result[name] = value.to_dict() if isinstance(value, CStruct) else value + return result + + def __repr__(self): + return f"<{self.__class__.__name__}: {self.to_dict()}>" diff --git a/middleware/sgx/envelope.py b/middleware/sgx/envelope.py new file mode 100644 index 00000000..39f86aab --- /dev/null +++ b/middleware/sgx/envelope.py @@ -0,0 +1,192 @@ +# The MIT License (MIT) +# +# Copyright (c) 2021 RSK Labs Ltd +# +# 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. + +from comm.cstruct import CStruct + + +class SgxEnvelope(CStruct): + """ + sgx_envelope_t + + sgx_quote_t quote + sgx_quote_tail_t quote_tail + sgx_quote_auth_data_t quote_auth_data + """ + + def __init__(self, envelope_bytes, custom_message_bytes, offset=0, little=True): + super().__init__(envelope_bytes, offset, little) + offset += self.get_bytelength() + + qead = SgxQeAuthData(envelope_bytes, offset, little) + offset += qead.get_total_bytelength() + self.qe_auth_data = qead + + qecd = SgxQeCertData(envelope_bytes, offset, little) + offset += qecd.get_total_bytelength() + self.qe_cert_data = qecd + + if envelope_bytes[offset:] != custom_message_bytes: + raise ValueError("Unexpected custom message in envelope tail") + self.custom_message = custom_message_bytes + +############################################################################## +# Types below taken from OpenEnclave's include/openenclave/bits/sgx/sgxtypes.h +############################################################################## + + +class SgxAttributes(CStruct): + """ + sgx_attributes_t + + uint64_t flags + uint64_t xfrm + """ + + +class SgxReportData(CStruct): + """ + sgx_report_data_t + + uint8_t field 64 + """ + + +class SgxReportBody(CStruct): + """ + sgx_report_body_t + + uint8_t cpusvn 16 + uint32_t miscselect + uint8_t reserved1 12 + uint8_t isvextprodid 16 + sgx_attributes_t attributes + uint8_t mrenclave 32 + uint8_t reserved2 32 + uint8_t mrsigner 32 + uint8_t reserved3 32 + uint8_t configid 64 + uint16_t isvprodid + uint16_t isvsvn + uint16_t configsvn + uint8_t reserved4 42 + uint8_t isvfamilyid 16 + sgx_report_data_t report_data + """ + + +class SgxEcdsa256Signature(CStruct): + """ + sgx_ecdsa256_signature_t + + uint8_t r 32 + uint8_t s 32 + """ + + +class SgxEcdsa256Key(CStruct): + """ + sgx_ecdsa256_key_t + + uint8_t x 32 + uint8_t y 32 + """ + + +class SgxQuote(CStruct): + """ + sgx_quote_t + + uint16_t version + uint16_t sign_type + uint32_t tee_type + uint16_t qe_svn + uint16_t pce_svn + uint8_t uuid 16 + uint8_t user_data 20 + sgx_report_body_t report_body + """ + + +# This is actually part of sgx_quote_t, separated +# for pratical reasons since the signature doesn't include +# this field +class SgxQuoteTail(CStruct): + """ + sgx_quote_tail_t + + uint32_t signature_len + """ + + +class SgxQuoteAuthData(CStruct): + """ + sgx_quote_auth_data_t + + sgx_ecdsa256_signature_t signature + sgx_ecdsa256_key_t attestation_key + sgx_report_body_t qe_report_body + sgx_ecdsa256_signature_t qe_report_body_signature + """ + +#################################################################### +# The following two structs are augmented with content parsing logic +#################################################################### + + +class SgxQeAuthData(CStruct): + """ + sgx_qe_auth_data_t + + uint16_t size + """ + + def __init__(self, value, offset=0, little=True): + super().__init__(value, offset, little) + os = offset + self.get_bytelength() + data = value[os:os+self.size] + if len(data) != self.size: + raise ValueError(f"Expected {self.size} data bytes but only got {len(data)}") + self.data = data + + def get_total_bytelength(self): + return self.get_bytelength() + len(self.data) + + +class SgxQeCertData(SgxQeAuthData): + """ + sgx_qe_cert_data_t + + uint16_t type + uint32_t size + """ + SPEC = None + TYPENAME = None + + X509_START_MARKER = b"-----BEGIN CERTIFICATE-----\n" + X509_END_MARKER = b"\n-----END CERTIFICATE-----\n" + + def __init__(self, value, offset=0, little=True): + super().__init__(value, offset, little) + self.certs = list(map(lambda c: c.replace(self.X509_START_MARKER, b""), + filter(lambda c: + c.strip().startswith(self.X509_START_MARKER), + self.data.split(self.X509_END_MARKER)))) diff --git a/middleware/tests/admin/test_certificate_v1.py b/middleware/tests/admin/test_certificate_v1.py index 591cfc91..f7503bba 100644 --- a/middleware/tests/admin/test_certificate_v1.py +++ b/middleware/tests/admin/test_certificate_v1.py @@ -26,7 +26,7 @@ from unittest import TestCase from unittest.mock import call, patch, mock_open -from admin.certificate import HSMCertificate, HSMCertificateElement +from admin.certificate_v1 import HSMCertificate, HSMCertificateElement class TestHSMCertificate(TestCase): diff --git a/middleware/tests/admin/test_certificate_v2.py b/middleware/tests/admin/test_certificate_v2.py new file mode 100644 index 00000000..f4a16da6 --- /dev/null +++ b/middleware/tests/admin/test_certificate_v2.py @@ -0,0 +1,91 @@ +# The MIT License (MIT) +# +# Copyright (c) 2021 RSK Labs Ltd +# +# 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. + +from unittest import TestCase +from admin.certificate_v1 import HSMCertificate +from admin.certificate_v2 import HSMCertificateV2, HSMCertificateV2ElementSGXQuote, \ + HSMCertificateV2ElementSGXAttestationKey, \ + HSMCertificateV2ElementX509 + + +class TestHSMCertificateV2(TestCase): + def test_behavior_inherited(self): + self.assertTrue(issubclass(HSMCertificateV2, HSMCertificate)) + + def test_create_empty_certificate_ok(self): + cert = HSMCertificateV2() + self.assertEqual({'version': 2, 'targets': [], 'elements': []}, cert.to_dict()) + + +class TestHSMCertificateV2ElementSGXQuote(TestCase): + def test_dict_ok(self): + elem = HSMCertificateV2ElementSGXQuote( + "thename", + bytes.fromhex("aabbcc"), + bytes.fromhex("ddeeff"), + bytes.fromhex("112233"), + "whosigned" + ) + self.assertEqual({ + "name": "thename", + "type": "sgx_quote", + "message": "aabbcc", + "custom_data": "ddeeff", + "signature": "112233", + "signed_by": "whosigned", + }, elem.to_dict()) + + +class TestHSMCertificateV2ElementSGXAttestationKey(TestCase): + def test_dict_ok(self): + elem = HSMCertificateV2ElementSGXAttestationKey( + "thename", + bytes.fromhex("aabbcc"), + bytes.fromhex("ddeeff"), + bytes.fromhex("112233"), + bytes.fromhex("44556677"), + "whosigned" + ) + self.assertEqual({ + "name": "thename", + "type": "sgx_attestation_key", + "message": "aabbcc", + "key": "ddeeff", + "auth_data": "112233", + "signature": "44556677", + "signed_by": "whosigned", + }, elem.to_dict()) + + +class TestHSMCertificateV2ElementX509(TestCase): + def test_dict_ok(self): + elem = HSMCertificateV2ElementX509( + "thename", + b"this is an ascii message", + "whosigned" + ) + self.assertEqual({ + "name": "thename", + "type": "x509_pem", + "message": "this is an ascii message", + "signed_by": "whosigned", + }, elem.to_dict()) diff --git a/middleware/tests/admin/test_sgx_attestation.py b/middleware/tests/admin/test_sgx_attestation.py index 2f05f2da..4b60fadb 100644 --- a/middleware/tests/admin/test_sgx_attestation.py +++ b/middleware/tests/admin/test_sgx_attestation.py @@ -20,17 +20,21 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import ecdsa from types import SimpleNamespace from unittest import TestCase -from unittest.mock import Mock, patch +from unittest.mock import Mock, patch, call from parameterized import parameterized from admin.sgx_attestation import do_attestation from admin.misc import AdminError @patch("sys.stdout") -@patch("admin.sgx_attestation.HSMCertificateV2Element") +@patch("admin.sgx_attestation.HSMCertificateV2ElementX509") +@patch("admin.sgx_attestation.HSMCertificateV2ElementSGXAttestationKey") +@patch("admin.sgx_attestation.HSMCertificateV2ElementSGXQuote") @patch("admin.sgx_attestation.HSMCertificateV2") +@patch("admin.sgx_attestation.SgxEnvelope") @patch("admin.sgx_attestation.do_unlock") @patch("admin.sgx_attestation.get_ud_value_for_attestation") @patch("admin.sgx_attestation.get_hsm") @@ -44,15 +48,56 @@ def setUp(self): self.options = options def setupMocks(self, get_hsm, get_ud_value_for_attestation, do_unlock, - HSMCertificateV2, HSMCertificateV2Element): + SgxEnvelope, HSMCertificateV2, HSMCertificateV2ElementSGXQuote, + HSMCertificateV2ElementSGXAttestationKey, HSMCertificateV2ElementX509): self.get_hsm = get_hsm self.get_ud_value_for_attestation = get_ud_value_for_attestation self.do_unlock = do_unlock + self.SgxEnvelope = SgxEnvelope self.HSMCertificateV2 = HSMCertificateV2 - self.HSMCertificateV2Element = HSMCertificateV2Element + self.HSMCertificateV2ElementSGXQuote = HSMCertificateV2ElementSGXQuote + self.HSMCertificateV2ElementSGXAttestationKey = \ + HSMCertificateV2ElementSGXAttestationKey + self.HSMCertificateV2ElementX509 = HSMCertificateV2ElementX509 self.hsm = Mock() - self.hsm.get_powhsm_attestation.return_value = "the-attestation" + self.hsm.get_powhsm_attestation.return_value = { + "envelope": "11"*32, + "message": "22"*32, + } + quote = SimpleNamespace(**{"get_raw_data": lambda: "quote-raw-data"}) + sig = SimpleNamespace(**{"r": b"a"*32, "s": b"a"*32}) + self.att_key = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p).\ + get_verifying_key() + att_key_str = self.att_key.to_string() + attkey = SimpleNamespace(**{"x": att_key_str[:32], "y": att_key_str[32:]}) + qesig = SimpleNamespace(**{"r": b"c"*32, "s": b"c"*32}) + qad = SimpleNamespace(**{ + "signature": sig, + "attestation_key": attkey, + "qe_report_body": SimpleNamespace(**{ + "get_raw_data": lambda: "qerb-raw-data"}), + "qe_report_body_signature": qesig, + }) + qead = SimpleNamespace(**{ + "data": "qead-data", + }) + qecd = SimpleNamespace(**{ + "certs": ["qecd-cert-0", "qecd-cert-1"], + }) + envelope = SimpleNamespace(**{ + "quote": quote, + "quote_auth_data": qad, + "qe_auth_data": qead, + "qe_cert_data": qecd, + "custom_message": "a-custom-message", + }) + self.SgxEnvelope.return_value = envelope + + self.HSMCertificateV2ElementSGXQuote.return_value = "quote_elem" + self.HSMCertificateV2ElementSGXAttestationKey.return_value = "attkey_elem" + self.HSMCertificateV2ElementX509.side_effect = ["cert0_elem", "cert1_elem"] + get_hsm.return_value = self.hsm get_ud_value_for_attestation.return_value = "some-random-value" @@ -74,10 +119,44 @@ def test_ok(self, *args): self.get_hsm.assert_called_with("is-verbose") self.hsm.get_powhsm_attestation.assert_called_with("some-random-value") self.hsm.disconnect.assert_called() - self.HSMCertificateV2Element.assert_called_with("the-attestation") - elem = self.HSMCertificateV2Element.return_value + self.SgxEnvelope.assert_called_with( + bytes.fromhex("11"*32), + bytes.fromhex("22"*32), + ) + self.HSMCertificateV2ElementSGXQuote.assert_called_with( + name="quote", + message="quote-raw-data", + custom_data="a-custom-message", + signature=bytes.fromhex("30440220"+"61"*32+"0220"+"61"*32), + signed_by="attestation", + ) + self.HSMCertificateV2ElementSGXAttestationKey.assert_called_with( + name="attestation", + message="qerb-raw-data", + key=self.att_key.to_string("uncompressed"), + auth_data="qead-data", + signature=bytes.fromhex("30440220"+"63"*32+"0220"+"63"*32), + signed_by="quoting_enclave", + ) + self.HSMCertificateV2ElementX509.assert_has_calls([ + call( + name="quoting_enclave", + message="qecd-cert-0", + signed_by="platform_ca", + ), + call( + name="platform_ca", + message="qecd-cert-1", + signed_by="sgx_root", + ) + ]) cert = self.HSMCertificateV2.return_value - cert.add_element.assert_called_with(elem) + cert.add_element.assert_has_calls([ + call("quote_elem"), + call("attkey_elem"), + call("cert0_elem"), + call("cert1_elem") + ]) cert.save_to_jsonfile.assert_called_with("an-output-file") def test_no_output_path(self, *args): @@ -92,10 +171,13 @@ def test_no_output_path(self, *args): self.get_hsm.assert_not_called() self.hsm.get_powhsm_attestation.assert_not_called() self.do_unlock.assert_not_called() + self.SgxEnvelope.assert_not_called() self.HSMCertificateV2.assert_not_called() - self.HSMCertificateV2Element.assert_not_called() + self.HSMCertificateV2ElementSGXQuote.assert_not_called() + self.HSMCertificateV2ElementSGXAttestationKey.assert_not_called() + self.HSMCertificateV2ElementX509.assert_not_called() - def test_adm_err(self, *args): + def test_adm_err_get_attestation(self, *args): self.setupMocks(*args[:-1]) self.hsm.get_powhsm_attestation.side_effect = RuntimeError("an error") @@ -109,5 +191,31 @@ def test_adm_err(self, *args): self.get_hsm.assert_called_with("is-verbose") self.hsm.get_powhsm_attestation.assert_called_with("some-random-value") self.hsm.disconnect.assert_not_called() + self.SgxEnvelope.assert_not_called() + self.HSMCertificateV2.assert_not_called() + self.HSMCertificateV2ElementSGXQuote.assert_not_called() + self.HSMCertificateV2ElementSGXAttestationKey.assert_not_called() + self.HSMCertificateV2ElementX509.assert_not_called() + + def test_adm_err_envelope_parsing(self, *args): + self.setupMocks(*args[:-1]) + + self.SgxEnvelope.side_effect = ValueError("an error") + + with self.assertRaises(AdminError) as e: + do_attestation(self.options) + self.assertIn("envelope parse error", str(e.exception)) + + self.get_ud_value_for_attestation.assert_called_with("an-ud-source") + self.do_unlock.assert_called_with(self.options, label=False) + self.get_hsm.assert_called_with("is-verbose") + self.hsm.get_powhsm_attestation.assert_called_with("some-random-value") + self.hsm.disconnect.assert_called() + self.SgxEnvelope.assert_called_with( + bytes.fromhex("11"*32), + bytes.fromhex("22"*32), + ) self.HSMCertificateV2.assert_not_called() - self.HSMCertificateV2Element.assert_not_called() + self.HSMCertificateV2ElementSGXQuote.assert_not_called() + self.HSMCertificateV2ElementSGXAttestationKey.assert_not_called() + self.HSMCertificateV2ElementX509.assert_not_called() diff --git a/middleware/tests/comm/test_cstruct.py b/middleware/tests/comm/test_cstruct.py new file mode 100644 index 00000000..8c04d47d --- /dev/null +++ b/middleware/tests/comm/test_cstruct.py @@ -0,0 +1,223 @@ +# The MIT License (MIT) +# +# Copyright (c) 2021 RSK Labs Ltd +# +# 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. + +from unittest import TestCase +from parameterized import parameterized +from comm.cstruct import CStruct + +import logging + +logging.disable(logging.CRITICAL) + + +class RandomBisStruct(CStruct): + """ + random_bis_t + + uint16_t another_double + uint8_t single_arr 10 + uint32_t another_quad + """ + + +class RandomTrisStruct(CStruct): + """ + random_tris_t + + uint8_t arr_one 2 + uint8_t arr_two 3 + """ + + +class RandomStruct(CStruct): + """ + random_t + + uint8_t single_val + uint16_t double_val + uint32_t quad_val + uint64_t oct_val + + random_bis_t other_random + random_tris_t yet_other_random + """ + + +class Invalid1(CStruct): + """ + invalid_1 + + nonexistent_type something + """ + + +class Invalid2(CStruct): + """ + invalid_2 + + uint32_t withlength 5 + """ + + +class ValidWithInvalid(CStruct): + """ + valid_with_invalid + + uint8_t a_number + uint16_t another_number + invalid_2 something_invalid + """ + + +class TestCStruct(TestCase): + def setUp(self): + self.packed = bytes.fromhex( + "99" # single_val + "0102" # double_val + "03040506" # quad_val + "0708090a0b0c0d0e" # oct_val + "8899" # another_double + "00112233445566778899" # single_arr + "d1d2d3d4" # another_quad + "aabb" # arr_one + "ccddee" # arr_two + ) + + def test_expected_sizes(self): + self.assertEqual(16, RandomBisStruct.get_bytelength()) + self.assertEqual(5, RandomTrisStruct.get_bytelength()) + self.assertEqual(15 + + RandomBisStruct.get_bytelength() + + RandomTrisStruct.get_bytelength(), + RandomStruct.get_bytelength()) + + def test_parsing_default(self): + parsed = RandomStruct(self.packed) + + self.assertEqual(0x99, parsed.single_val) + self.assertEqual(0x0201, parsed.double_val) + self.assertEqual(0x06050403, parsed.quad_val) + self.assertEqual(0x0e0d0c0b0a090807, parsed.oct_val) + + self.assertEqual(0x9988, parsed.other_random.another_double) + self.assertEqual(bytes.fromhex("00112233445566778899"), + parsed.other_random.single_arr) + self.assertEqual(0xd4d3d2d1, parsed.other_random.another_quad) + + self.assertEqual(bytes.fromhex("aabb"), parsed.yet_other_random.arr_one) + self.assertEqual(bytes.fromhex("ccddee"), parsed.yet_other_random.arr_two) + + self.assertEqual({ + "single_val": 0x99, + "double_val": 0x0201, + "quad_val": 0x06050403, + "oct_val": 0x0e0d0c0b0a090807, + "other_random": { + "another_double": 0x9988, + "single_arr": "00112233445566778899", + "another_quad": 0xd4d3d2d1, + }, + "yet_other_random": { + "arr_one": "aabb", + "arr_two": "ccddee", + } + }, parsed.to_dict()) + + def test_parsing_little_offset(self): + parsed = RandomStruct(b"thisisrandom" + self.packed, offset=12, little=True) + + self.assertEqual(0x99, parsed.single_val) + self.assertEqual(0x0201, parsed.double_val) + self.assertEqual(0x06050403, parsed.quad_val) + self.assertEqual(0x0e0d0c0b0a090807, parsed.oct_val) + + self.assertEqual(0x9988, parsed.other_random.another_double) + self.assertEqual(bytes.fromhex("00112233445566778899"), + parsed.other_random.single_arr) + self.assertEqual(0xd4d3d2d1, parsed.other_random.another_quad) + + self.assertEqual(bytes.fromhex("aabb"), parsed.yet_other_random.arr_one) + self.assertEqual(bytes.fromhex("ccddee"), parsed.yet_other_random.arr_two) + + self.assertEqual({ + "single_val": 0x99, + "double_val": 0x0201, + "quad_val": 0x06050403, + "oct_val": 0x0e0d0c0b0a090807, + "other_random": { + "another_double": 0x9988, + "single_arr": "00112233445566778899", + "another_quad": 0xd4d3d2d1, + }, + "yet_other_random": { + "arr_one": "aabb", + "arr_two": "ccddee", + } + }, parsed.to_dict()) + + def test_parsing_big(self): + parsed = RandomStruct(self.packed, little=False) + + self.assertEqual(0x99, parsed.single_val) + self.assertEqual(0x0102, parsed.double_val) + self.assertEqual(0x03040506, parsed.quad_val) + self.assertEqual(0x0708090a0b0c0d0e, parsed.oct_val) + + self.assertEqual(0x8899, parsed.other_random.another_double) + self.assertEqual(bytes.fromhex("00112233445566778899"), + parsed.other_random.single_arr) + self.assertEqual(0xd1d2d3d4, parsed.other_random.another_quad) + + self.assertEqual(bytes.fromhex("aabb"), parsed.yet_other_random.arr_one) + self.assertEqual(bytes.fromhex("ccddee"), parsed.yet_other_random.arr_two) + + self.assertEqual({ + "single_val": 0x99, + "double_val": 0x0102, + "quad_val": 0x03040506, + "oct_val": 0x0708090a0b0c0d0e, + "other_random": { + "another_double": 0x8899, + "single_arr": "00112233445566778899", + "another_quad": 0xd1d2d3d4, + }, + "yet_other_random": { + "arr_one": "aabb", + "arr_two": "ccddee", + } + }, parsed.to_dict()) + + def test_parsing_toosmall(self): + with self.assertRaises(ValueError): + RandomStruct(b"thisistoosmall") + + @parameterized.expand([ + ("invalid_one", Invalid1), + ("invalid_two", Invalid2), + ("valid_with_invalid", ValidWithInvalid) + ]) + def test_invalid_spec(self, _, kls): + with self.assertRaises(ValueError): + kls.get_bytelength() + + with self.assertRaises(ValueError): + kls(b'somethingtoparse') diff --git a/middleware/tests/sgx/test_envelope.py b/middleware/tests/sgx/test_envelope.py new file mode 100644 index 00000000..132a2861 --- /dev/null +++ b/middleware/tests/sgx/test_envelope.py @@ -0,0 +1,112 @@ +# The MIT License (MIT) +# +# Copyright (c) 2021 RSK Labs Ltd +# +# 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. + +from unittest import TestCase +from parameterized import parameterized +from sgx.envelope import SgxAttributes, \ + SgxReportData, \ + SgxReportBody, \ + SgxEcdsa256Signature, \ + SgxEcdsa256Key, \ + SgxQuote, \ + SgxQuoteTail, \ + SgxQuoteAuthData, \ + SgxQeCertData, \ + SgxQeAuthData, \ + SgxEnvelope + +import logging + +logging.disable(logging.CRITICAL) + +TEST_ENVELOPE = """ +03000200000000000a000f00939a7233f79c4ca9940a0db3957f0607ceae3549bc7273eb34d562f4564fc182000000000e0e100fffff01000000000000000000010000000000000000000000000000000000000000000000000000000000000005000000000000000700000000000000d32688d3c1f3dfcc8b0b36eac7c89d49af331800bd56248044166fa6699442c10000000000000000000000000000000000000000000000000000000000000000718c2f1a0efbd513e016fafd6cf62a624442f2d83708d4b33ab5a8d8c1cd4dd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000640001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009e95bb875c1a728071f70ad8c9d03f1744c19acb0580921e611ac9104f7701d00000000000000000000000000000000000000000000000000000000000000000ca100000e52b03a7bd6b5dd9feeeb375bd597730d2872643b47aff4dd641c5c3a2b8016ebbd227f67e7c23bbddeb4f8fddee031a2b961501d1c28dda082669d7ac861e6ca024cb34c90ea6a8f9f2181c9020cbcc7c073e69981733c8deed6f6c451822aa08376350ff7da01f842bb40c631cbb711f8b6f7a4fae398320a3884774d250ad0e0e100fffff0100000000000000000000000000000000000000000000000000000000000000000000000000000000001500000000000000e70000000000000096b347a64e5a045e27369c26e6dcda51fd7c850e9b3a3a79e718f43261dee1e400000000000000000000000000000000000000000000000000000000000000008c4f5775d796503e96137f77c68a829a0056ac8ded70140b081b094490c57bff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001fe721d0322954821589237fd27efb8fef1acb3ecd6b0352c31271550fc70f9400000000000000000000000000000000000000000000000000000000000000001f14d532274c4385fc0019ca2a21e53e17143cb62377ca4fcdd97fa9fef8fb2595d4ee272cf3c512e36779de67dc7814982f1160d981d138a32b265e928a05622000000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f0500620e00002d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494945387a4343424a69674177494241674955667232646c774e343244425541394358496b426c4750327656334177436759494b6f5a497a6a3045417749770a634445694d434147413155454177775a535735305a577767553064594946424453794251624746305a6d397962534244515445614d42674741315545436777520a535735305a577767513239796347397959585270623234784644415342674e564241634d43314e68626e526849454e7359584a684d51737743515944565151490a44414a445154454c4d416b474131554542684d4356564d774868634e4d6a51774d7a497a4d4451304e6a49785768634e4d7a45774d7a497a4d4451304e6a49780a576a42774d534977494159445651514444426c4a626e526c624342545231676755454e4c49454e6c636e52705a6d6c6a5958526c4d526f77474159445651514b0a4442464a626e526c6243424462334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e560a4241674d416b4e424d517377435159445651514745774a56557a425a4d424d4742797147534d34394167454743437147534d34394177454841304941424b6c370a52444e6c735a4b6b45744163573753664358314a656762764771344f3072525574307a2f4736665a4a734e6c706d527754423444596b72676b6d31742b3952700a4c77784658392f6b6768786944516d306a716d6a67674d4f4d494944436a416642674e5648534d4547444157674253566231334e765276683655424a796454300a4d383442567776655644427242674e56485238455a4442694d47436758714263686c706f64485277637a6f764c32467761533530636e567a6447566b633256790a646d6c6a5a584d75615735305a577775593239744c334e6e6543396a5a584a3061575a7059324630615739754c3359304c33426a61324e796244396a595431770a624746305a6d397962535a6c626d4e765a476c755a7a316b5a584977485159445652304f4242594546414c4b5635444631364b6e4562535735514d39656344710a425a61484d41344741315564447745422f775145417749477744414d42674e5648524d4241663845416a41414d4949434f77594a4b6f5a496876684e415130420a424949434c444343416967774867594b4b6f5a496876684e415130424151515174744a587569515677714d34733734672b4878664b54434341575547436971470a534962345451454e41514977676746564d42414743797147534962345451454e415149424167454f4d42414743797147534962345451454e415149434167454f0a4d42414743797147534962345451454e41514944416745444d42414743797147534962345451454e41514945416745444d42454743797147534962345451454e0a41514946416749412f7a415242677371686b69472b4530424451454342674943415038774541594c4b6f5a496876684e4151304241676343415145774541594c0a4b6f5a496876684e4151304241676743415141774541594c4b6f5a496876684e4151304241676b43415141774541594c4b6f5a496876684e4151304241676f430a415141774541594c4b6f5a496876684e4151304241677343415141774541594c4b6f5a496876684e4151304241677743415141774541594c4b6f5a496876684e0a4151304241673043415141774541594c4b6f5a496876684e4151304241673443415141774541594c4b6f5a496876684e4151304241673843415141774541594c0a4b6f5a496876684e4151304241684143415141774541594c4b6f5a496876684e4151304241684543415130774877594c4b6f5a496876684e41513042416849450a4541344f4177502f2f7745414141414141414141414141774541594b4b6f5a496876684e4151304241775143414141774641594b4b6f5a496876684e415130420a4241514741474271414141414d41384743697147534962345451454e4151554b415145774867594b4b6f5a496876684e41513042426751514456652f445855560a453467656d74674f357542707644424542676f71686b69472b453042445145484d4459774541594c4b6f5a496876684e4151304242774542416638774541594c0a4b6f5a496876684e4151304242774942415141774541594c4b6f5a496876684e4151304242774d4241514177436759494b6f5a497a6a304541774944535141770a52674968414a4667663738486767544274765150585a4a782f33466d373176434f6d74383270636539314d325a41493041694541695a4d5042625a5a6d7652320a762b316d727337364a65676c44512b704b2f534c4e39346c342b6a4d3544413d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436c6a4343416a32674177494241674956414a567658633239472b487051456e4a3150517a7a674658433935554d416f4743437147534d343942414d430a4d476778476a415942674e5642414d4d45556c756447567349464e48574342536232393049454e424d526f77474159445651514b4442464a626e526c624342440a62334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e564241674d416b4e424d5173770a435159445651514745774a56557a4165467730784f4441314d6a45784d4455774d5442614677307a4d7a41314d6a45784d4455774d5442614d484178496a41670a42674e5642414d4d47556c756447567349464e4857434251513073675547786864475a76636d306751304578476a415942674e5642416f4d45556c75644756730a49454e76636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b474131554543417743513045780a437a414a42674e5642415954416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741454e53422f377432316c58534f0a3243757a7078773734654a423732457944476757357258437478327456544c7136684b6b367a2b5569525a436e71523770734f766771466553786c6d546c4a6c0a65546d693257597a33714f42757a43427544416642674e5648534d4547444157674251695a517a575770303069664f44744a5653763141624f536347724442530a42674e5648523845537a424a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e567a6447566b633256790a646d6c6a5a584d75615735305a577775593239744c306c756447567355306459556d397664454e424c6d526c636a416442674e5648513445466751556c5739640a7a62306234656c4153636e553944504f4156634c336c517744675944565230504151482f42415144416745474d42494741315564457745422f7751494d4159420a4166384341514177436759494b6f5a497a6a30454177494452774177524149675873566b6930772b6936565947573355462f32327561586530594a446a3155650a6e412b546a44316169356343494359623153416d4435786b66545670766f34556f79695359787244574c6d5552344349394e4b7966504e2b0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436a7a4343416a53674177494241674955496d554d316c71644e496e7a6737535655723951477a6b6e42717777436759494b6f5a497a6a3045417749770a614445614d4267474131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e760a636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a0a42674e5642415954416c56544d423458445445344d4455794d5445774e4455784d466f58445451354d54497a4d54497a4e546b314f566f77614445614d4267470a4131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e76636e4276636d46300a615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a42674e56424159540a416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414543366e45774d4449595a4f6a2f69505773437a61454b69370a314f694f534c52466857476a626e42564a66566e6b59347533496a6b4459594c304d784f346d717379596a6c42616c54565978465032734a424b357a6c4b4f420a757a43427544416642674e5648534d4547444157674251695a517a575770303069664f44744a5653763141624f5363477244425342674e5648523845537a424a0a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e567a6447566b63325679646d6c6a5a584d75615735300a5a577775593239744c306c756447567355306459556d397664454e424c6d526c636a416442674e564851344546675155496d554d316c71644e496e7a673753560a55723951477a6b6e4271777744675944565230504151482f42415144416745474d42494741315564457745422f7751494d4159424166384341514577436759490a4b6f5a497a6a3045417749445351417752674968414f572f35516b522b533943695344634e6f6f774c7550524c735747662f59693747535839344267775477670a41694541344a306c72486f4d732b586f356f2f7358364f39515778485241765a55474f6452513763767152586171493d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a00746869732069732061206d657373616765 +""" + +TEST_MESSAGE = "746869732069732061206d657373616765" + + +class TestSgxQeAuthData(TestCase): + def test_parses_ok(self): + parsed = SgxQeAuthData(bytes.fromhex("0a00112233445566778899aa")) + self.assertEqual(10, parsed.size) + self.assertEqual(bytes.fromhex("112233445566778899aa"), parsed.data) + + def test_parses_error_tooshort(self): + with self.assertRaises(ValueError): + SgxQeAuthData(bytes.fromhex("0a0baabbcc")) + + +class TestSgxQeCertData(TestCase): + def test_parses_ok(self): + certs = \ +b""" +-----BEGIN CERTIFICATE----- +this is certificate one +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +this is certificate two +-----END CERTIFICATE----- +""" + parsed = SgxQeCertData( + bytes.fromhex("1122") + + len(certs).to_bytes(4, byteorder="little", signed=False) + + certs + ) + self.assertEqual(0x2211, parsed.type) + self.assertEqual(certs, parsed.data) + self.assertEqual(2, len(parsed.certs)) + + def test_parses_error_tooshort(self): + with self.assertRaises(ValueError): + SgxQeAuthData(bytes.fromhex("0a0baabbcc")) + + +class TestSgxEnvelope(TestCase): + def test_parses_ok(self): + envelope = SgxEnvelope( + bytes.fromhex(TEST_ENVELOPE), + bytes.fromhex(TEST_MESSAGE) + ) + + self.assertEqual(TEST_MESSAGE, envelope.custom_message.hex()) + + def test_parsing_fails_if_message_mismatch(self): + with self.assertRaises(ValueError): + SgxEnvelope(bytes.fromhex(TEST_ENVELOPE), b"some-other-message") + + +class TestSgxStructs(TestCase): + # Sizes taken from OpenEnclave's include/openenclave/bits/sgx/sgxtypes.h + # sgx_quote_t is smaller due to not including the last field (signature_len) + @parameterized.expand([ + ("sgx_attributes_t", SgxAttributes, 16), + ("sgx_report_data_t", SgxReportData, 64), + ("sgx_report_body_t", SgxReportBody, 384), + ("sgx_ecdsa256_signature_t", SgxEcdsa256Signature, 64), + ("sgx_ecdsa256_key_t", SgxEcdsa256Key, 64), + ("sgx_quote_t", SgxQuote, 432), + ("sgx_quote_tail_t", SgxQuoteTail, 4), + ("sgx_quote_auth_data_t", SgxQuoteAuthData, 576), + ]) + def test_sizes_ok(self, _, kls, exp_len): + self.assertEqual(exp_len, kls.get_bytelength()) diff --git a/setup.cfg b/setup.cfg index b11ff18e..da90c29b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,6 +25,7 @@ indent-size = 4 per-file-ignores = __init__.py:F401, middleware/admin/certificate.py:F401, + middleware/tests/sgx/test_envelope.py:E122, show-source = False statistics = True