Repository: openwsn-berkeley/lakers (language: python)

Issue:
Add authz device in Python wrapper
Currently only authenticator and server are exposed.


Discussion / hints:

Fix patch:
diff --git a/ead/lakers-ead-authz/src/device.rs b/ead/lakers-ead-authz/src/device.rs
index 0bd2ae32..0f7c1826 100644
--- a/ead/lakers-ead-authz/src/device.rs
+++ b/ead/lakers-ead-authz/src/device.rs
@@ -2,22 +2,22 @@ use super::shared::*;
 use crate::ZeroTouchError;
 use lakers_shared::{Crypto as CryptoTrait, *};
 
-#[derive(Debug)]
+#[derive(Default, Debug)]
 #[repr(C)]
 pub struct ZeroTouchDevice {
-    pub(crate) id_u: EdhocMessageBuffer, // identifier of the device (U), equivalent to ID_CRED_I in EDHOC
-    pub g_w: BytesP256ElemLen,           // public key of the enrollment server (W)
-    pub(crate) loc_w: EdhocMessageBuffer, // address of the enrollment server (W)
+    pub id_u: EdhocMessageBuffer, // identifier of the device (U), equivalent to ID_CRED_I in EDHOC
+    pub g_w: BytesP256ElemLen,    // public key of the enrollment server (W)
+    pub loc_w: EdhocMessageBuffer, // address of the enrollment server (W)
 }
 
-#[derive(Debug)]
+#[derive(Default, Debug)]
 #[repr(C)]
 pub struct ZeroTouchDeviceWaitEAD2 {
     prk: BytesHashLen,
     pub h_message_1: BytesHashLen,
 }
 
-#[derive(Debug)]
+#[derive(Default, Debug)]
 #[repr(C)]
 pub struct ZeroTouchDeviceDone {
     pub voucher: BytesMac,
diff --git a/lakers-python/Cargo.toml b/lakers-python/Cargo.toml
index 7a5c3d65..6a2e4793 100644
--- a/lakers-python/Cargo.toml
+++ b/lakers-python/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "lakers-python" # this will be the name of the package on pypi
 edition = "2021"
-version ="0.1.3"
+version ="0.2.0"
 repository.workspace = true
 license.workspace = true
 
diff --git a/lakers-python/src/ead_authz/authenticator.rs b/lakers-python/src/ead_authz/authenticator.rs
index 8c2230f0..b599b456 100644
--- a/lakers-python/src/ead_authz/authenticator.rs
+++ b/lakers-python/src/ead_authz/authenticator.rs
@@ -1,5 +1,8 @@
 use lakers::*;
-use pyo3::prelude::*;
+use pyo3::{
+    prelude::*,
+    types::{PyBytes, PyString},
+};
 
 #[pyclass(name = "AuthzAutenticator")]
 pub struct PyAuthzAutenticator {
@@ -17,18 +20,20 @@ impl PyAuthzAutenticator {
         }
     }
 
-    pub fn process_ead_1(
+    pub fn process_ead_1<'a>(
         &mut self,
+        py: Python<'a>,
         ead_1: EADItem,
         message_1: Vec<u8>,
-    ) -> PyResult<(Vec<u8>, Vec<u8>)> {
+    ) -> PyResult<(&'a PyString, &'a PyBytes)> {
         let message_1 = EdhocMessageBuffer::new_from_slice(message_1.as_slice())?;
         let (state, loc_w, voucher_request) =
             self.authenticator.process_ead_1(&ead_1, &message_1)?;
         self.authenticator_wait = state;
+        let loc_w = std::str::from_utf8(loc_w.as_slice()).unwrap();
         Ok((
-            Vec::from(loc_w.as_slice()),
-            Vec::from(voucher_request.as_slice()),
+            PyString::new(py, loc_w),
+            PyBytes::new(py, voucher_request.as_slice()),
         ))
     }
 
diff --git a/lakers-python/src/ead_authz/device.rs b/lakers-python/src/ead_authz/device.rs
new file mode 100644
index 00000000..9a18f2b8
--- /dev/null
+++ b/lakers-python/src/ead_authz/device.rs
@@ -0,0 +1,63 @@
+use lakers::*;
+use lakers_crypto::default_crypto;
+use pyo3::{exceptions::PyBaseException, prelude::*, types::PyBytes};
+
+#[pyclass(name = "AuthzDevice")]
+pub struct PyAuthzDevice {
+    device: ZeroTouchDevice,
+    device_wait: ZeroTouchDeviceWaitEAD2,
+    device_done: ZeroTouchDeviceDone,
+}
+
+#[pymethods]
+impl PyAuthzDevice {
+    #[new]
+    fn new(id_u: Vec<u8>, g_w: Vec<u8>, loc_w: &str) -> Self {
+        let id_u = EdhocMessageBuffer::new_from_slice(id_u.as_slice()).unwrap();
+        let loc_w = EdhocMessageBuffer::new_from_slice(loc_w.as_bytes()).unwrap();
+        let mut g_w_arr = BytesP256ElemLen::default();
+        g_w_arr.copy_from_slice(&g_w[..]);
+        Self {
+            device: ZeroTouchDevice {
+                id_u,
+                g_w: g_w_arr,
+                loc_w,
+            },
+            device_wait: ZeroTouchDeviceWaitEAD2::default(),
+            device_done: ZeroTouchDeviceDone::default(),
+        }
+    }
+
+    pub fn prepare_ead_1(&mut self, secret: Vec<u8>, ss: u8) -> PyResult<EADItem> {
+        let mut secret_arr = BytesP256ElemLen::default();
+        secret_arr.copy_from_slice(&secret[..]);
+        let (device_wait, ead_1) = self
+            .device
+            .prepare_ead_1(&mut default_crypto(), secret_arr, ss);
+        self.device_wait = device_wait;
+        Ok(ead_1)
+    }
+
+    pub fn process_ead_2(&mut self, ead_2: EADItem, cred_v: &[u8]) -> PyResult<bool> {
+        match self
+            .device_wait
+            .process_ead_2(&mut default_crypto(), ead_2, cred_v)
+        {
+            Ok(device_done) => {
+                self.device_done = device_done;
+                Ok(true)
+            }
+            Err(error) => Err(PyBaseException::new_err(error as i8)),
+        }
+    }
+
+    pub fn set_h_message_1(&mut self, h_message_1: Vec<u8>) {
+        let mut h_message_1_arr = BytesHashLen::default();
+        h_message_1_arr.copy_from_slice(&h_message_1[..]);
+        self.device_wait.set_h_message_1(h_message_1_arr);
+    }
+
+    pub fn get_g_w<'a>(&self, py: Python<'a>) -> PyResult<&'a PyBytes> {
+        Ok(PyBytes::new(py, &self.device.g_w[..]))
+    }
+}
diff --git a/lakers-python/src/ead_authz/mod.rs b/lakers-python/src/ead_authz/mod.rs
index 49c414e9..717dad15 100644
--- a/lakers-python/src/ead_authz/mod.rs
+++ b/lakers-python/src/ead_authz/mod.rs
@@ -1,4 +1,6 @@
 mod authenticator;
 pub use authenticator::*;
+mod device;
+pub use device::*;
 mod server;
 pub use server::*;
diff --git a/lakers-python/src/initiator.rs b/lakers-python/src/initiator.rs
index bcf81fb1..6de7f477 100644
--- a/lakers-python/src/initiator.rs
+++ b/lakers-python/src/initiator.rs
@@ -163,4 +163,23 @@ impl PyEdhocInitiator {
         );
         Ok(PyBytes::new(py, &res[..SHA256_DIGEST_LEN]))
     }
+
+    pub fn get_h_message_1<'a>(&self, py: Python<'a>) -> PyResult<&'a PyBytes> {
+        Ok(PyBytes::new(py, &self.wait_m2.h_message_1[..]))
+    }
+
+    pub fn compute_ephemeral_secret<'a>(
+        &self,
+        py: Python<'a>,
+        g_a: Vec<u8>,
+    ) -> PyResult<&'a PyBytes> {
+        let mut g_a_arr = BytesP256ElemLen::default();
+        g_a_arr.copy_from_slice(&g_a[..]);
+        let secret = default_crypto().p256_ecdh(&self.start.x, &g_a_arr);
+        Ok(PyBytes::new(py, &secret[..]))
+    }
+
+    pub fn selected_cipher_suite(&self) -> PyResult<u8> {
+        Ok(self.start.suites_i[self.start.suites_i_len - 1])
+    }
 }
diff --git a/lakers-python/src/lib.rs b/lakers-python/src/lib.rs
index b9943453..09946731 100644
--- a/lakers-python/src/lib.rs
+++ b/lakers-python/src/lib.rs
@@ -63,6 +63,7 @@ fn lakers_python(_py: Python, m: &PyModule) -> PyResult<()> {
     m.add_class::<lakers::CredentialTransfer>()?;
     m.add_class::<lakers::EADItem>()?;
     // ead-authz items
+    m.add_class::<ead_authz::PyAuthzDevice>()?;
     m.add_class::<ead_authz::PyAuthzAutenticator>()?;
     m.add_class::<ead_authz::PyAuthzEnrollmentServer>()?;
     Ok(())


Test patch:
diff --git a/lakers-python/test/test_ead_authz.py b/lakers-python/test/test_ead_authz.py
index 5ae585da..4a832b25 100644
--- a/lakers-python/test/test_ead_authz.py
+++ b/lakers-python/test/test_ead_authz.py
@@ -2,9 +2,15 @@
 import pytest
 
 # values from traces-zeroconf.ipynb
+ID_U = bytes.fromhex("a104412b")
+G_W = bytes.fromhex("FFA4F102134029B3B156890B88C9D9619501196574174DCB68A07DB0588E4D41")
+LOC_W = "coap://enrollment.server"
 W = bytes.fromhex("4E5E15AB35008C15B89E91F9F329164D4AACD53D9923672CE0019F9ACD98573F")
 KID_I = 0x2b
+CRED_I = bytes.fromhex("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8")
+I = bytes.fromhex("fb13adeb6518cee5f88417660841142e830a81fe334380a953406a1305e8706b")
 CRED_V = bytes.fromhex("a2026b6578616d706c652e65647508a101a501020241322001215820bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f02258204519e257236b2a0ce2023f0931f1f386ca7afda64fcde0108c224c51eabf6072")
+V = bytes.fromhex("72cc4761dbd4c78f758931aa589d348d1ef874a7e303ede2f140dcf3e6aa4aac")
 EAD_1_VALUE = bytes.fromhex("58287818636f61703a2f2f656e726f6c6c6d656e742e7365727665724dda9784962883c96ed01ff122c3")
 MESSAGE_1_WITH_EAD = bytes.fromhex("0382060258208af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b6370158287818636f61703a2f2f656e726f6c6c6d656e742e7365727665724dda9784962883c96ed01ff122c3")
 VOUCHER_RESPONSE = bytes.fromhex("8258520382060258208af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b6370158287818636f61703a2f2f656e726f6c6c6d656e742e7365727665724dda9784962883c96ed01ff122c34948c783671337f75bd5")
@@ -20,6 +26,7 @@ def test_authenticator_and_server():
 
     ead_1 = lakers.EADItem(1, True, EAD_1_VALUE)
     loc_w, voucher_request = authenticator.process_ead_1(ead_1, MESSAGE_1_WITH_EAD)
+    assert type(loc_w) == str
     voucher_response = enrollment_server.handle_voucher_request(voucher_request)
     assert type(voucher_response) == bytes
 
@@ -27,3 +34,51 @@ def test_authenticator_and_server():
     assert ead_2.label() == 1
     assert ead_2.is_critical() == True
     assert ead_2.value() == EAD_2_VALUE
+
+def test_handshake_with_authz():
+    initiator = lakers.EdhocInitiator()
+    responder = lakers.EdhocResponder(V, CRED_V)
+
+    device = lakers.AuthzDevice(
+        ID_U,
+        G_W,
+        LOC_W,
+    )
+    authenticator = lakers.AuthzAutenticator()
+    enrollment_server = lakers.AuthzEnrollmentServer(
+            W,
+            CRED_V,
+            [KID_I],
+    )
+
+    # initiator
+    ead_1 = device.prepare_ead_1(
+        initiator.compute_ephemeral_secret(device.get_g_w()),
+        initiator.selected_cipher_suite(),
+    )
+    message_1 = initiator.prepare_message_1(c_i=None, ead_1=ead_1)
+    device.set_h_message_1(initiator.get_h_message_1())
+
+    # responder
+    ead_1 = responder.process_message_1(message_1)
+    loc_w, voucher_request = authenticator.process_ead_1(ead_1, message_1)
+    voucher_response = enrollment_server.handle_voucher_request(voucher_request)
+    ead_2 = authenticator.prepare_ead_2(voucher_response)
+    message_2 = responder.prepare_message_2(lakers.CredentialTransfer.ByReference, None, ead_2)
+    assert type(message_2) == bytes
+
+    # initiator
+    c_r, id_cred_r, ead_2 = initiator.parse_message_2(message_2)
+    valid_cred_r = lakers.credential_check_or_fetch(id_cred_r, CRED_V)
+    assert device.process_ead_2(ead_2, CRED_V) # voucher is valid!
+    initiator.verify_message_2(I, CRED_I, valid_cred_r)
+    message_3, i_prk_out = initiator.prepare_message_3(lakers.CredentialTransfer.ByReference, None)
+    assert type(message_3) == bytes
+
+    # responder
+    id_cred_i, ead_3 = responder.parse_message_3(message_3)
+    assert ead_3 == None
+    valid_cred_i = lakers.credential_check_or_fetch(id_cred_i, CRED_I)
+    r_prk_out = responder.verify_message_3(valid_cred_i)
+
+    assert i_prk_out == r_prk_out