From d5340f927b7a3e20794facc87fbd186397e062e1 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 11 Nov 2024 17:23:24 +0000 Subject: [PATCH 1/4] Add tappd python client --- python/tappd_client/.gitignore | 1 + python/tappd_client/README.md | 33 +++++++++ python/tappd_client/pyproject.toml | 28 ++++++++ python/tappd_client/tappd_client.py | 105 ++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 python/tappd_client/.gitignore create mode 100644 python/tappd_client/README.md create mode 100644 python/tappd_client/pyproject.toml create mode 100644 python/tappd_client/tappd_client.py diff --git a/python/tappd_client/.gitignore b/python/tappd_client/.gitignore new file mode 100644 index 000000000..178135c2b --- /dev/null +++ b/python/tappd_client/.gitignore @@ -0,0 +1 @@ +/dist/ diff --git a/python/tappd_client/README.md b/python/tappd_client/README.md new file mode 100644 index 000000000..f4410c0b3 --- /dev/null +++ b/python/tappd_client/README.md @@ -0,0 +1,33 @@ +# Tappd Client + +A Python client for interacting with the Tappd daemon via Unix socket communication. + +## Installation + +```bash +pip install tappd-client +``` + +## Usage + +```python +from tappd_client import TappdClient + +# Initialize the client +client = TappdClient() # Default socket path: /var/run/tappd.sock +# Or specify custom socket path +client = TappdClient(socket_path='/custom/path/tappd.sock') + +# Get TDX quote +# Using binary data +binary_data = b'Hello World' +response = client.tdx_quote(binary_data) + +# Using string data +string_data = "Hello World" +response = client.tdx_quote(string_data) +``` + +## License + +This project is licensed under the MIT License. See the LICENSE file for more details. diff --git a/python/tappd_client/pyproject.toml b/python/tappd_client/pyproject.toml new file mode 100644 index 000000000..649f9a739 --- /dev/null +++ b/python/tappd_client/pyproject.toml @@ -0,0 +1,28 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "tappd-client" +version = "0.1.1" +description = "Python client for Tappd in DStack CVM" +requires-python = ">=3.7" +license = "MIT" +authors = [ + {name = "Kevin Wang", email = "wy721@qq.com"} +] +dependencies = [ + "requests-unixsocket2>=0.4.2", +] +readme = "README.md" +keywords = ["dstack", "tappd"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", +] + +[tool.hatch.build] +include = ["tappd_client.py"] diff --git a/python/tappd_client/tappd_client.py b/python/tappd_client/tappd_client.py new file mode 100644 index 000000000..75f204f91 --- /dev/null +++ b/python/tappd_client/tappd_client.py @@ -0,0 +1,105 @@ +import requests_unixsocket +import json +import binascii +import hashlib +from urllib.parse import quote + + +def replay_rtmr(history: list[str]): + """ + Replay the RTMR history to calculate the final RTMR value. + """ + INIT_MR= "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + if len(history) == 0: + return INIT_MR + mr = bytes.fromhex(INIT_MR) + for content in history: + # mr = sha384(concat(mr, content)) + # if content is shorter than 48 bytes, pad it with zeros + content = bytes.fromhex(content) + if len(content) < 48: + content = content.ljust(48, b'\0') + mr = hashlib.sha384(mr + content).digest() + return mr.hex() + + +class QuoteResponse: + def __init__(self, quote, event_log): + """Initialize a QuoteResponse object with quote data and event log. + + Args: + quote: The quote response from the TDX service + event_log: List of events containing RTMR measurements + """ + self.quote = quote + self.event_log = json.loads(event_log) + + def replay_rtmrs(self) -> dict: + """Replay the RTMR history from the event log to calculate final RTMR values. + + Returns: + dict: Dictionary mapping RTMR indices to their calculated values + """ + rtmrs = {} + for idx in range(4): + history = [] + for event in self.event_log: + if event.get('imr') == idx: + history.append(event['digest']) + rtmrs[idx] = replay_rtmr(history) + return rtmrs + + +class TappdClient: + def __init__(self, socket_path='/var/run/tappd.sock'): + self.socket_path = socket_path + + def _unix_socket_post(self, endpoint, data): + """Make a POST request to a Unix socket with the given path and data.""" + session = requests_unixsocket.Session() + try: + encoded_path = quote(self.socket_path, safe='') + socket_url = f"http+unix://{encoded_path}/{endpoint}?json" + + response = session.post(socket_url, json=data) + response.raise_for_status() + return response.json() + finally: + session.close() + + def _rpc_call(self, data, method): + """Make an RPC call to the tappd service.""" + endpoint = 'prpc/' + method + try: + return self._unix_socket_post(endpoint, data) + except Exception as e: + print(f"Error: {e}") + return None + + def tdx_quote(self, report_data) -> QuoteResponse: + """ + Get TDX quote for the given report data. + + Args: + report_data: Can be either: + - bytes object + - regular string + + Returns: + QuoteResponse: An object containing the quote and event log + """ + if report_data is None: + raise ValueError("report_data cannot be None") + if not report_data: # Check for empty string/bytes + raise ValueError("report_data cannot be empty") + + if isinstance(report_data, bytes): + hex_data = binascii.hexlify(report_data).decode('ascii') + elif isinstance(report_data, str): + hex_data = report_data.encode('utf-8').hex() + else: + raise TypeError("report_data must be either bytes or str") + + data = {"report_data": hex_data} + response = self._rpc_call(data, 'Tappd.TdxQuote') + return QuoteResponse(response['quote'], response['event_log']) From c9b742f91bf3f1ec790a83e12b5182fc5a4d0c2e Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 12 Nov 2024 02:10:00 +0000 Subject: [PATCH 2/4] tappd_client: Update README --- python/tappd_client/README.md | 13 +++++++++++++ python/tappd_client/pyproject.toml | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/python/tappd_client/README.md b/python/tappd_client/README.md index f4410c0b3..e6775f915 100644 --- a/python/tappd_client/README.md +++ b/python/tappd_client/README.md @@ -26,6 +26,19 @@ response = client.tdx_quote(binary_data) # Using string data string_data = "Hello World" response = client.tdx_quote(string_data) + +# Get the quote +quote = response.quote + +# Get event log +event_log = response.event_log + +# Replay RTMRS from the event log +rtmrs = response.replay_rtmrs() +RTMR0 = rtmrs[0] +RTMR1 = rtmrs[1] +RTMR2 = rtmrs[2] +RTMR3 = rtmrs[3] ``` ## License diff --git a/python/tappd_client/pyproject.toml b/python/tappd_client/pyproject.toml index 649f9a739..f534a4ee0 100644 --- a/python/tappd_client/pyproject.toml +++ b/python/tappd_client/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "tappd-client" -version = "0.1.1" +version = "0.1.2" description = "Python client for Tappd in DStack CVM" requires-python = ">=3.7" license = "MIT" From 84abd28f9d2460662723c5ee313fe014814c156c Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 12 Nov 2024 02:21:56 +0000 Subject: [PATCH 3/4] tappd_client: Add fn derive_key --- python/tappd_client/tappd_client.py | 30 ++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/python/tappd_client/tappd_client.py b/python/tappd_client/tappd_client.py index 75f204f91..4678c4fa9 100644 --- a/python/tappd_client/tappd_client.py +++ b/python/tappd_client/tappd_client.py @@ -50,6 +50,23 @@ def replay_rtmrs(self) -> dict: return rtmrs +class DerivedKey: + """ + A class to hold the derived key and certificate. + """ + key: str + """ + The derived key in pem format. + """ + certs: list[str] + """ + The derived certificate chain in pem format. + """ + def __init__(self, key, certs): + self.key = key + self.certs = certs + + class TappdClient: def __init__(self, socket_path='/var/run/tappd.sock'): self.socket_path = socket_path @@ -76,7 +93,7 @@ def _rpc_call(self, data, method): print(f"Error: {e}") return None - def tdx_quote(self, report_data) -> QuoteResponse: + def tdx_quote(self, report_data: bytes | str) -> QuoteResponse: """ Get TDX quote for the given report data. @@ -103,3 +120,14 @@ def tdx_quote(self, report_data) -> QuoteResponse: data = {"report_data": hex_data} response = self._rpc_call(data, 'Tappd.TdxQuote') return QuoteResponse(response['quote'], response['event_log']) + + def derive_key(self, path: str, subject: str | None = None, alt_names: list[str] | None = None) -> DerivedKey: + """ + Derive the key and certificate from the given path. + """ + data = {"path": path} + data["subject"] = subject or path + if alt_names: + data["alt_names"] = alt_names + response = self._rpc_call(data, 'Tappd.DeriveKey') + return DerivedKey(response['key'], response['certificate_chain']) From 05d3269f56247881c5b23ea213041f74d82a043d Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 12 Nov 2024 02:26:38 +0000 Subject: [PATCH 4/4] tappd: Support for setting alt names in derive_key --- ra-tls/src/cert.rs | 12 +++++++----- tappd/rpc/proto/tappd_rpc.proto | 2 ++ tappd/src/rpc_service.rs | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ra-tls/src/cert.rs b/ra-tls/src/cert.rs index 1596dc582..fc7bf4bc2 100644 --- a/ra-tls/src/cert.rs +++ b/ra-tls/src/cert.rs @@ -83,7 +83,7 @@ pub struct CertRequest<'a> { key: &'a KeyPair, org_name: Option<&'a str>, subject: &'a str, - alt_subject: Option<&'a str>, + alt_names: Option<&'a [String]>, ca_level: Option, quote: Option<&'a [u8]>, event_log: Option<&'a [u8]>, @@ -98,10 +98,12 @@ impl<'a> CertRequest<'a> { } dn.push(DnType::CommonName, self.subject); params.distinguished_name = dn; - if let Some(alt_subject) = self.alt_subject { - params - .subject_alt_names - .push(SanType::DnsName(alt_subject.try_into()?)); + if let Some(alt_names) = self.alt_names { + for alt_name in alt_names { + params + .subject_alt_names + .push(SanType::DnsName(alt_name.clone().try_into()?)); + } } if let Some(quote) = self.quote { let content = yasna::construct_der(|writer| { diff --git a/tappd/rpc/proto/tappd_rpc.proto b/tappd/rpc/proto/tappd_rpc.proto index 23cf448ff..159a6b155 100644 --- a/tappd/rpc/proto/tappd_rpc.proto +++ b/tappd/rpc/proto/tappd_rpc.proto @@ -19,6 +19,8 @@ message DeriveKeyArgs { string path = 1; // Subject of the certificate to request string subject = 2; + // DNS alternative names for the certificate + repeated string alt_names = 3; } // The response to a DeriveKey request diff --git a/tappd/src/rpc_service.rs b/tappd/src/rpc_service.rs index 49c1154c1..495e91d37 100644 --- a/tappd/src/rpc_service.rs +++ b/tappd/src/rpc_service.rs @@ -50,6 +50,7 @@ impl TappdRpc for InternalRpcHandler { .context("Failed to derive key")?; let req = CertRequest::builder() .subject(&request.subject) + .alt_names(&request.alt_names) .key(&derived_key) .build(); let cert = self