Skip to content

Commit

Permalink
Certificate V2 sgx_quote type element validation
Browse files Browse the repository at this point in the history
- HSMCertificateV2Element base class now raises not implemented errors for is_valid and get_pubkey
- Implemented is_valid in HSMCertificateV2ElementSGXQuote
- HSMCertificateV2ElementSGXQuote's message method now returns an SgxQuote
- Mocking is_valid and get_pubkey in the rest of HSMCertificateV2Element's subclasses
- Added and updated unit tests
  • Loading branch information
amendelzon committed Dec 30, 2024
1 parent 3042127 commit 1aebf5f
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 42 deletions.
44 changes: 35 additions & 9 deletions middleware/admin/certificate_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import re
from pathlib import Path
import base64
import ecdsa
import hashlib
from .certificate_v1 import HSMCertificate
from .utils import is_nonempty_hex_string
from sgx.envelope import SgxQuote
Expand Down Expand Up @@ -62,14 +64,10 @@ def get_value(self):
raise NotImplementedError(f"{type(self).__name__} can't provide a value")

def get_pubkey(self):
# TODO: this should yield not implemented
# TODO: implementation should be down to each specific subclass
return None
raise NotImplementedError(f"{type(self).__name__} can't provide a public key")

def is_valid(self, certifier):
# TODO: this should yield not implemented
# TODO: implementation should be down to each specific subclass
return True
raise NotImplementedError(f"{type(self).__name__} can't be queried for validity")

def get_tweak(self):
return None
Expand Down Expand Up @@ -97,7 +95,7 @@ def _init_with_map(self, element_map):

@property
def message(self):
return self._message.hex()
return SgxQuote(self._message)

@property
def custom_data(self):
Expand All @@ -107,17 +105,33 @@ def custom_data(self):
def signature(self):
return self._signature.hex()

def is_valid(self, certifier):
try:
# Validate custom data
expected = hashlib.sha256(self._custom_data).digest()
if expected != self.message.report_body.report_data.field[:len(expected)]:
return False

# Verify signature against the certifier
return certifier.get_pubkey().verify_digest(
self._signature,
hashlib.sha256(self._message).digest(),
ecdsa.util.sigdecode_der,
)
except Exception:
return False

def get_value(self):
return {
"sgx_quote": SgxQuote(self._message),
"sgx_quote": self.message,
"message": self.custom_data,
}

def to_dict(self):
return {
"name": self.name,
"type": "sgx_quote",
"message": self.message,
"message": self._message.hex(),
"custom_data": self.custom_data,
"signature": self.signature,
"signed_by": self.signed_by,
Expand Down Expand Up @@ -163,6 +177,12 @@ def auth_data(self):
def signature(self):
return self._signature.hex()

def is_valid(self, certifier):
return True

def get_pubkey(self):
return ecdsa.VerifyingKey.from_string(self._key, ecdsa.NIST256p)

def to_dict(self):
return {
"name": self.name,
Expand Down Expand Up @@ -206,6 +226,12 @@ def _init_with_map(self, element_map):
def message(self):
return base64.b64encode(self._message).decode("ASCII")

def is_valid(self, certifier):
return True

def get_pubkey(self):
return None

def to_dict(self):
return {
"name": self.name,
Expand Down
183 changes: 150 additions & 33 deletions middleware/tests/admin/test_certificate_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import ecdsa
import hashlib
from unittest import TestCase
from unittest.mock import patch
from unittest.mock import Mock, patch
from parameterized import parameterized
from admin.certificate_v1 import HSMCertificate
from sgx.envelope import SgxQuote
from admin.certificate_v2 import HSMCertificateV2, HSMCertificateV2Element, \
HSMCertificateV2ElementSGXQuote, \
HSMCertificateV2ElementSGXAttestationKey, \
Expand All @@ -36,43 +40,62 @@ def test_behavior_inherited(self):

def test_create_empty_certificate_ok(self):
cert = HSMCertificateV2()
self.assertEqual({'version': 2, 'targets': [], 'elements': []}, cert.to_dict())
self.assertEqual({"version": 2, "targets": [], "elements": []}, cert.to_dict())

def test_parse_identity(self):
cert = HSMCertificateV2(TEST_CERTIFICATE)
self.assertEqual(TEST_CERTIFICATE, cert.to_dict())

@patch("admin.certificate_v2.SgxQuote")
def test_validate_and_get_values_value(self, SgxQuoteMock):
SgxQuoteMock.return_value = "an-sgx-quote"
def mock_element(self, which_one_invalid):
class MockElement:
def __init__(self, d):
self.d = d
self.name = d["name"]
self.signed_by = d["signed_by"]

def is_valid(self, c):
return self.name != which_one_invalid

def get_value(self):
return f"the value for {self.name}"

def get_tweak(self):
return None

def mock_element_factory(k, d):
return MockElement(d)

HSMCertificateV2.ELEMENT_FACTORY = mock_element_factory

def test_validate_and_get_values_value(self):
self.mock_element(True)
cert = HSMCertificateV2(TEST_CERTIFICATE)
self.assertEqual({
"quote": (True, "the value for quote", None),
}, cert.validate_and_get_values("a-root-of-trust"))

@parameterized.expand([
("invalid_quote", "quote"),
("invalid_attestation", "attestation"),
("invalid_qe", "quoting_enclave"),
("invalid_plf", "platform_ca"),
])
def test_validate_and_get_values_invalid(self, _, invalid_name):
self.mock_element(invalid_name)
cert = HSMCertificateV2(TEST_CERTIFICATE)
self.assertEqual({
"quote": (
True, {
"sgx_quote": "an-sgx-quote",
"message": "504f5748534d3a352e343a3a736778f36f7bc09aab50c0886a442b2"
"d04b18186720bda7a753643066cd0bc0a4191800c4d091913d39750"
"dc8975adbdd261bd10c1c2e110faa47cfbe30e740895552bbdcb3c1"
"7c7aee714cec8ad900341bfd987b452280220dcbd6e7191f67ea420"
"9b00000000000000000000000000000000",
}, None)
}, cert.validate_and_get_values('a-root-of-trust'))
SgxQuoteMock.assert_called_with(bytes.fromhex(
"03000200000000000a000f00939a7233f79c4ca9940a0db3957f0607ceae3549bc7273eb34"
"d562f4564fc182000000000e0e100fffff0100000000000000000001000000000000000000"
"00000000000000000000000000000000000000000000050000000000000007000000000000"
"00d32688d3c1f3dfcc8b0b36eac7c89d49af331800bd56248044166fa6699442c100000000"
"00000000000000000000000000000000000000000000000000000000718c2f1a0efbd513e0"
"16fafd6cf62a624442f2d83708d4b33ab5a8d8c1cd4dd00000000000000000000000000000"
"00000000000000000000000000000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000000000000000000000000000000000000"
"00000000000000006400010000000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000000000000000000000000000000009e95"
"bb875c1a728071f70ad8c9d03f1744c19acb0580921e611ac9104f7701d000000000000000"
"00000000000000000000000000000000000000000000000000"))
"quote": (False, invalid_name),
}, cert.validate_and_get_values("a-root-of-trust"))


class TestHSMCertificateV2Element(TestCase):
def setUp(self):
class TestElement(HSMCertificateV2Element):
def __init__(self):
pass

self.instance = TestElement()

def test_from_dict_unknown_type(self):
with self.assertRaises(ValueError) as e:
HSMCertificateV2Element.from_dict({
Expand Down Expand Up @@ -103,12 +126,41 @@ def test_from_dict_no_signed_by(self):
})
self.assertIn("Missing certifier", str(e.exception))

def test_cant_instantiate(self):
with self.assertRaises(NotImplementedError):
HSMCertificateV2Element()

def test_get_pubkey_notimplemented(self):
with self.assertRaises(NotImplementedError):
self.instance.get_pubkey()

def test_get_value_notimplemented(self):
with self.assertRaises(NotImplementedError):
self.instance.get_value()

def test_is_valid_notimplemented(self):
with self.assertRaises(NotImplementedError):
self.instance.is_valid("a-certifier")


class TestHSMCertificateV2ElementSGXQuote(TestCase):
TEST_MESSAGE = \
"03000200000000000a000f00939a7233f79c4ca9940a0db3957f0607ceae3549bc7273eb34d562f"\
"4564fc182000000000e0e100fffff01000000000000000000010000000000000000000000000000"\
"000000000000000000000000000000000005000000000000000700000000000000d32688d3c1f3d"\
"fcc8b0b36eac7c89d49af331800bd56248044166fa6699442c10000000000000000000000000000"\
"000000000000000000000000000000000000718c2f1a0efbd513e016fafd6cf62a624442f2d8370"\
"8d4b33ab5a8d8c1cd4dd00000000000000000000000000000000000000000000000000000000000"\
"0000000000000000000000000000000000000000000000000000000000000000000000000000000"\
"0000000000000000000000000000000000000000000000000000000640001000000000000000000"\
"0000000000000000000000000000000000000000000000000000000000000000000000000000000"\
"00000000000000000000000005d53b30e22f66979d36721e10ab7722557257a9ef8ba77ec7fe430"\
"493c3542f90000000000000000000000000000000000000000000000000000000000000000"

def setUp(self):
self.elem = HSMCertificateV2ElementSGXQuote({
"name": "thename",
"message": "aabbcc",
"message": self.TEST_MESSAGE,
"custom_data": "ddeeff",
"signature": "112233",
"signed_by": "whosigned",
Expand All @@ -117,15 +169,17 @@ def setUp(self):
def test_props(self):
self.assertEqual("thename", self.elem.name)
self.assertEqual("whosigned", self.elem.signed_by)
self.assertEqual("aabbcc", self.elem.message)
self.assertIsInstance(self.elem.message, SgxQuote)
self.assertEqual(bytes.fromhex(self.TEST_MESSAGE),
self.elem.message.get_raw_data())
self.assertEqual("ddeeff", self.elem.custom_data)
self.assertEqual("112233", self.elem.signature)

def test_dict_ok(self):
self.assertEqual({
"name": "thename",
"type": "sgx_quote",
"message": "aabbcc",
"message": self.TEST_MESSAGE,
"custom_data": "ddeeff",
"signature": "112233",
"signed_by": "whosigned",
Expand Down Expand Up @@ -154,7 +208,7 @@ def test_from_dict_invalid_custom_data(self):
HSMCertificateV2Element.from_dict({
"name": "quote",
"type": "sgx_quote",
"message": "aabbccdd",
"message": self.TEST_MESSAGE,
"custom_data": "not-hex",
"signature": "445566778899",
"signed_by": "attestation"
Expand All @@ -166,13 +220,68 @@ def test_from_dict_invalid_signature(self):
HSMCertificateV2Element.from_dict({
"name": "quote",
"type": "sgx_quote",
"message": "aabbccdd",
"message": self.TEST_MESSAGE,
"custom_data": "112233",
"signature": "not-hex",
"signed_by": "attestation"
})
self.assertIn("Invalid signature", str(e.exception))

def test_get_pubkey_notimplemented(self):
with self.assertRaises(NotImplementedError):
self.elem.get_pubkey()

def test_is_valid_ok(self):
pk = ecdsa.SigningKey.generate(ecdsa.NIST256p)
certifier = Mock()
certifier.get_pubkey.return_value = pk.verifying_key

valid_elem = HSMCertificateV2ElementSGXQuote({
"name": "thename",
"message": self.TEST_MESSAGE,
"custom_data": "10061982",
"signature": pk.sign_digest(
hashlib.sha256(bytes.fromhex(self.TEST_MESSAGE)).digest(),
sigencode=ecdsa.util.sigencode_der
).hex(),
"signed_by": "whosigned",
})
self.assertTrue(valid_elem.is_valid(certifier))

def test_is_valid_custom_data_mismatch(self):
pk = ecdsa.SigningKey.generate(ecdsa.NIST256p)
certifier = Mock()
certifier.get_pubkey.return_value = pk.verifying_key

valid_elem = HSMCertificateV2ElementSGXQuote({
"name": "thename",
"message": self.TEST_MESSAGE,
"custom_data": "11061982",
"signature": pk.sign_digest(
hashlib.sha256(bytes.fromhex(self.TEST_MESSAGE)).digest(),
sigencode=ecdsa.util.sigencode_der
).hex(),
"signed_by": "whosigned",
})
self.assertFalse(valid_elem.is_valid(certifier))

def test_is_valid_signature_mismatch(self):
pk = ecdsa.SigningKey.generate(ecdsa.NIST256p)
certifier = Mock()
certifier.get_pubkey.return_value = pk.verifying_key

valid_elem = HSMCertificateV2ElementSGXQuote({
"name": "thename",
"message": self.TEST_MESSAGE,
"custom_data": "10061982",
"signature": pk.sign_digest(
hashlib.sha256(b"something else").digest(),
sigencode=ecdsa.util.sigencode_der
).hex(),
"signed_by": "whosigned",
})
self.assertFalse(valid_elem.is_valid(certifier))


class TestHSMCertificateV2ElementSGXAttestationKey(TestCase):
def setUp(self):
Expand Down Expand Up @@ -262,6 +371,10 @@ def test_from_dict_invalid_signature(self):
})
self.assertIn("Invalid signature", str(e.exception))

def test_get_value_notimplemented(self):
with self.assertRaises(NotImplementedError):
self.elem.get_value()


class TestHSMCertificateV2ElementX509(TestCase):
def setUp(self):
Expand Down Expand Up @@ -300,6 +413,10 @@ def test_from_dict_invalid_message(self):
})
self.assertIn("Invalid message", str(e.exception))

def test_get_value_notimplemented(self):
with self.assertRaises(NotImplementedError):
self.elem.get_value()

def test_from_pem(self):
self.assertEqual({
"name": "thename",
Expand Down

0 comments on commit 1aebf5f

Please sign in to comment.