crypto: Export cross signing related methods from the Rust side

This commit is contained in:
Damir Jelić 2021-08-10 16:37:46 +02:00
parent e2006f9dc6
commit b012a0ff75
7 changed files with 484 additions and 43 deletions

View file

@ -25,11 +25,11 @@ features = ["lax_deserialize"]
[dependencies.matrix-sdk-common]
git = "https://github.com/matrix-org/matrix-rust-sdk/"
rev = "3a8ff2f6b43f312b7582146ed712ff245ef9d5aa"
rev = "b2ff6cb6ae3d1983a510262021cba27a1bc70d77"
[dependencies.matrix-sdk-crypto]
git = "https://github.com/matrix-org/matrix-rust-sdk/"
rev = "3a8ff2f6b43f312b7582146ed712ff245ef9d5aa"
rev = "b2ff6cb6ae3d1983a510262021cba27a1bc70d77"
features = ["sled_cryptostore"]
[dependencies.tokio]
@ -38,8 +38,12 @@ default_features = false
features = ["rt-multi-thread"]
[dependencies.ruma]
version = "0.2.0"
version = "0.3.0"
features = ["client-api"]
[build-dependencies]
uniffi_build = "0.12.0"
[patch.crates-io]
ruma = { git = "https://github.com/matrix-org/ruma/", branch = "secrets" }
ruma-identifiers = { git = "https://github.com/matrix-org/ruma", branch = "secrets" }

View file

@ -2,17 +2,10 @@
use matrix_sdk_crypto::{
store::CryptoStoreError as InnerStoreError, KeyExportError, MegolmError, OlmError,
SignatureError as InnerSignatureError, SecretImportError as RustSecretImportError,
};
use ruma::identifiers::Error as RumaIdentifierError;
#[derive(Debug, thiserror::Error)]
pub enum MachineCreationError {
#[error(transparent)]
Identifier(#[from] RumaIdentifierError),
#[error(transparent)]
CryptoStore(#[from] InnerStoreError),
}
#[derive(Debug, thiserror::Error)]
pub enum KeyImportError {
#[error(transparent)]
@ -21,6 +14,28 @@ pub enum KeyImportError {
CryptoStore(#[from] InnerStoreError),
}
#[derive(Debug, thiserror::Error)]
pub enum SecretImportError {
#[error(transparent)]
CryptoStore(#[from] InnerStoreError),
#[error(transparent)]
Import(#[from] RustSecretImportError),
}
#[derive(Debug, thiserror::Error)]
pub enum SignatureError {
#[error(transparent)]
Signature(#[from] InnerSignatureError),
#[error(transparent)]
Identifier(#[from] RumaIdentifierError),
#[error(transparent)]
CryptoStore(#[from] InnerStoreError),
#[error("Unknown device {0} {1}")]
UnknownDevice(String, String),
#[error("Unknown user identity {0}")]
UnknownUserIdentity(String),
}
#[derive(Debug, thiserror::Error)]
pub enum CryptoStoreError {
#[error(transparent)]

View file

@ -15,15 +15,18 @@ mod error;
mod logger;
mod machine;
mod responses;
mod users;
mod verification;
pub use device::Device;
pub use error::{CryptoStoreError, DecryptionError, KeyImportError, MachineCreationError};
pub use error::{CryptoStoreError, DecryptionError, KeyImportError, SignatureError, SecretImportError};
pub use logger::{set_logger, Logger};
pub use machine::{KeyRequestPair, OlmMachine};
pub use responses::{
DeviceLists, KeysImportResult, OutgoingVerificationRequest, Request, RequestType,
DeviceLists, KeysImportResult, OutgoingVerificationRequest, Request, RequestType, SignatureUploadRequest,
BootstrapCrossSigningResult, UploadSigningKeysRequest,
};
pub use users::UserIdentity;
pub use verification::{
CancelInfo, QrCode, RequestVerificationResult, Sas, ScanResult, StartSasResult, Verification,
VerificationRequest,
@ -55,4 +58,59 @@ pub struct DecryptedEvent {
pub forwarding_curve25519_chain: Vec<String>,
}
/// Struct representing the state of our private cross signing keys, it shows
/// which private cross signing keys we have locally stored.
#[derive(Debug, Clone)]
pub struct CrossSigningStatus {
/// Do we have the master key.
pub has_master: bool,
/// Do we have the self signing key, this one is necessary to sign our own
/// devices.
pub has_self_signing: bool,
/// Do we have the user signing key, this one is necessary to sign other
/// users.
pub has_user_signing: bool,
}
/// A struct containing private cross signing keys that can be backed up or
/// uploaded to the secret store.
pub struct CrossSigningKeyExport {
/// The seed of the master key encoded as unpadded base64.
pub master_key: Option<String>,
/// The seed of the self signing key encoded as unpadded base64.
pub self_signing_key: Option<String>,
/// The seed of the user signing key encoded as unpadded base64.
pub user_signing_key: Option<String>,
}
impl From<matrix_sdk_crypto::CrossSigningKeyExport> for CrossSigningKeyExport {
fn from(e: matrix_sdk_crypto::CrossSigningKeyExport) -> Self {
Self {
master_key: e.master_key.clone(),
self_signing_key: e.self_signing_key.clone(),
user_signing_key: e.user_signing_key.clone(),
}
}
}
impl Into<matrix_sdk_crypto::CrossSigningKeyExport> for CrossSigningKeyExport {
fn into(self) -> matrix_sdk_crypto::CrossSigningKeyExport {
matrix_sdk_crypto::CrossSigningKeyExport {
master_key: self.master_key,
self_signing_key: self.self_signing_key,
user_signing_key: self.user_signing_key,
}
}
}
impl From<matrix_sdk_crypto::CrossSigningStatus> for CrossSigningStatus {
fn from(s: matrix_sdk_crypto::CrossSigningStatus) -> Self {
Self {
has_master: s.has_master,
has_self_signing: s.has_self_signing,
has_user_signing: s.has_user_signing,
}
}
}
include!(concat!(env!("OUT_DIR"), "/olm.uniffi.rs"));

View file

@ -12,6 +12,7 @@ use ruma::{
keys::{
claim_keys::Response as KeysClaimResponse, get_keys::Response as KeysQueryResponse,
upload_keys::Response as KeysUploadResponse,
upload_signatures::Response as SignatureUploadResponse,
},
sync::sync_events::{DeviceLists as RumaDeviceLists, ToDevice},
to_device::send_event_to_device::Response as ToDeviceResponse,
@ -31,14 +32,15 @@ use tokio::runtime::Runtime;
use matrix_sdk_common::{deserialized_responses::AlgorithmInfo, uuid::Uuid};
use matrix_sdk_crypto::{
decrypt_key_export, encrypt_key_export, matrix_qrcode::QrVerificationData, EncryptionSettings,
LocalTrust, OlmMachine as InnerMachine, Verification as RustVerification,
LocalTrust, OlmMachine as InnerMachine, UserIdentities, Verification as RustVerification,
};
use crate::{
error::{CryptoStoreError, DecryptionError, MachineCreationError},
error::{CryptoStoreError, DecryptionError, SecretImportError, SignatureError},
responses::{response_from_string, OutgoingVerificationRequest, OwnedResponse},
DecryptedEvent, Device, DeviceLists, KeyImportError, KeysImportResult, ProgressListener,
QrCode, Request, RequestType, RequestVerificationResult, ScanResult, StartSasResult,
BootstrapCrossSigningResult, CrossSigningKeyExport, CrossSigningStatus, DecryptedEvent, Device,
DeviceLists, KeyImportError, KeysImportResult, ProgressListener, QrCode, Request, RequestType,
RequestVerificationResult, ScanResult, SignatureUploadRequest, StartSasResult, UserIdentity,
Verification, VerificationRequest,
};
@ -68,7 +70,7 @@ impl OlmMachine {
/// * `device_id` - The unique ID of the device that owns this machine.
///
/// * `path` - The path where the state of the machine should be persisted.
pub fn new(user_id: &str, device_id: &str, path: &str) -> Result<Self, MachineCreationError> {
pub fn new(user_id: &str, device_id: &str, path: &str) -> Result<Self, CryptoStoreError> {
let user_id = UserId::try_from(user_id)?;
let device_id = device_id.into();
let runtime = Runtime::new().unwrap();
@ -91,6 +93,67 @@ impl OlmMachine {
self.inner.device_id().to_string()
}
/// Get the display name of our own device.
pub fn display_name(&self) -> Result<Option<String>, CryptoStoreError> {
Ok(self.runtime.block_on(self.inner.dislpay_name())?)
}
/// Get a cross signing user identity for the given user ID.
pub fn get_identity(&self, user_id: &str) -> Result<Option<UserIdentity>, CryptoStoreError> {
let user_id = UserId::try_from(user_id)?;
Ok(
if let Some(identity) = self.runtime.block_on(self.inner.get_identity(&user_id))? {
Some(self.runtime.block_on(UserIdentity::from_rust(identity))?)
} else {
None
},
)
}
/// Check if a user identity is considered to be verified by us.
pub fn is_identity_verified(&self, user_id: &str) -> Result<bool, CryptoStoreError> {
let user_id = UserId::try_from(user_id)?;
Ok(
if let Some(identity) = self.runtime.block_on(self.inner.get_identity(&user_id))? {
match identity {
UserIdentities::Own(i) => i.is_verified(),
UserIdentities::Other(i) => i.verified(),
}
} else {
false
},
)
}
/// Manually the user with the given user ID.
///
/// This method will attempt to sign the user identity using either our
/// private cross signing key, for other user identities, or our device keys
/// for our own user identity.
///
/// This methid can fail if we don't have the private part of our user-signing
/// key.
///
/// Returns a request that needs to be sent out for the user identity to be
/// marked as verified.
pub fn verify_identity(&self, user_id: &str) -> Result<SignatureUploadRequest, SignatureError> {
let user_id = UserId::try_from(user_id)?;
let user_identity = self.runtime.block_on(self.inner.get_identity(&user_id))?;
if let Some(user_identity) = user_identity {
Ok(match user_identity {
UserIdentities::Own(i) => self.runtime.block_on(i.verify())?,
UserIdentities::Other(i) => self.runtime.block_on(i.verify())?,
}
.into())
} else {
Err(SignatureError::UnknownUserIdentity(user_id.to_string()))
}
}
/// Get a `Device` from the store.
///
/// # Arguments
@ -111,6 +174,39 @@ impl OlmMachine {
.map(|d| d.into()))
}
/// Manually the device of the given user with the given device ID.
///
/// This method will attempt to sign the device using our private cross
/// signing key.
///
/// This method will always fail if the device belongs to someone else, we
/// can only sign our own devices.
///
/// It can also fail if we don't have the private part of our self-signing
/// key.
///
/// Returns a request that needs to be sent out for the device to be marked
/// as verified.
pub fn verify_device(
&self,
user_id: &str,
device_id: &str,
) -> Result<SignatureUploadRequest, SignatureError> {
let user_id = UserId::try_from(user_id)?;
let device = self
.runtime
.block_on(self.inner.get_device(&user_id, device_id.into()))?;
if let Some(device) = device {
Ok(self.runtime.block_on(device.verify())?.into())
} else {
Err(SignatureError::UnknownDevice(
user_id.to_string(),
device_id.to_string(),
))
}
}
/// Mark the device of the given user with the given device id as trusted.
pub fn mark_device_as_trusted(
&self,
@ -206,6 +302,9 @@ impl OlmMachine {
RequestType::KeysClaim => {
KeysClaimResponse::try_from_http_response(response).map(Into::into)
}
RequestType::SignatureUpload => {
SignatureUploadResponse::try_from_http_response(response).map(Into::into)
}
}
.expect("Can't convert json string to response");
@ -305,21 +404,7 @@ impl OlmMachine {
Ok(self
.runtime
.block_on(self.inner.get_missing_sessions(users.iter()))?
.map(|(request_id, request)| Request::KeysClaim {
request_id: request_id.to_string(),
one_time_keys: request
.one_time_keys
.into_iter()
.map(|(u, d)| {
(
u.to_string(),
d.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
)
})
.collect(),
}))
.map(|r| r.into()))
}
/// Share a room key with the given list of users for the given room.
@ -867,6 +952,8 @@ impl OlmMachine {
if let Some(verification) = self.inner.get_verification(&user_id, flow_id) {
match verification {
RustVerification::SasV1(v) => {
// TODO there's a signature upload request here, we'll
// want to return that one as well.
self.runtime.block_on(v.confirm())?.0.map(|r| r.into())
}
RustVerification::QrV1(v) => v.confirm_scanning().map(|r| r.into()),
@ -1114,4 +1201,49 @@ impl OlmMachine {
})
})
}
/// Create a new private cross signing identity and create a request to
/// upload the public part of it to the server.
pub fn bootstrap_cross_signing(&self) -> Result<BootstrapCrossSigningResult, CryptoStoreError> {
Ok(self
.runtime
.block_on(self.inner.bootstrap_cross_signing(true))?
.into())
}
/// Get the status of the private cross signing keys.
///
/// This can be used to check which private cross signing keys we have
/// stored locally.
pub fn cross_signing_status(&self) -> CrossSigningStatus {
self.runtime
.block_on(self.inner.cross_signing_status())
.into()
}
/// Export all our private cross signing keys.
///
/// The export will contain the seed for the ed25519 keys as a base64
/// encoded string.
///
/// This method returns `None` if we don't have any private cross signing keys.
pub fn export_cross_signing_keys(&self) -> Option<CrossSigningKeyExport> {
self.runtime
.block_on(self.inner.export_cross_signing_keys())
.map(|e| e.into())
}
/// Import our private cross signing keys.
///
/// The export needs to contain the seed for the ed25519 keys as a base64
/// encoded string.
pub fn import_cross_signing_keys(
&self,
export: CrossSigningKeyExport,
) -> Result<(), SecretImportError> {
self.runtime
.block_on(self.inner.import_cross_signing_keys(export.into()))?;
Ok(())
}
}

View file

@ -10,18 +10,28 @@ callback interface ProgressListener {
void on_progress(i32 progress, i32 total);
};
[Error]
enum MachineCreationError {
"Identifier",
"CryptoStore",
};
[Error]
enum KeyImportError {
"Export",
"CryptoStore",
};
[Error]
enum SignatureError {
"Signature",
"Identifier",
"CryptoStore",
"UnknownDevice",
"UnknownUserIdentity",
};
[Error]
enum SecretImportError {
"Import",
"CryptoStore",
};
[Error]
enum CryptoStoreError {
"CryptoStore",
@ -65,6 +75,45 @@ dictionary Device {
boolean cross_signing_trusted;
};
[Enum]
interface UserIdentity {
Own(
string user_id,
boolean trusts_our_own_device,
string master_key,
string self_signing_key,
string user_signing_key
);
Other(
string user_id,
string master_key,
string self_signing_key
);
};
dictionary CrossSigningStatus {
boolean has_master;
boolean has_self_signing;
boolean has_user_signing;
};
dictionary CrossSigningKeyExport {
string? master_key;
string? self_signing_key;
string? user_signing_key;
};
dictionary UploadSigningKeysRequest {
string master_key;
string self_signing_key;
string user_signing_key;
};
dictionary BootstrapCrossSigningResult {
UploadSigningKeysRequest upload_signing_keys_request;
SignatureUploadRequest signature_request;
};
dictionary CancelInfo {
string cancel_code;
string reason;
@ -155,6 +204,11 @@ interface Request {
KeysQuery(string request_id, sequence<string> users);
KeysClaim(string request_id, record<DOMString, record<DOMString, string>> one_time_keys);
RoomMessage(string request_id, string room_id, string event_type, string content);
SignatureUpload(string request_id, string body);
};
dictionary SignatureUploadRequest {
string body;
};
enum RequestType {
@ -162,10 +216,11 @@ enum RequestType {
"KeysClaim",
"KeysUpload",
"ToDevice",
"SignatureUpload",
};
interface OlmMachine {
[Throws=MachineCreationError]
[Throws=CryptoStoreError]
constructor([ByRef] string user_id, [ByRef] string device_id, [ByRef] string path);
record<DOMString, string> identity_keys();
@ -190,10 +245,16 @@ interface OlmMachine {
[Throws=CryptoStoreError]
string encrypt([ByRef] string room_id, [ByRef] string event_type, [ByRef] string content);
[Throws=CryptoStoreError]
UserIdentity? get_identity([ByRef] string user_id);
[Throws=SignatureError]
SignatureUploadRequest verify_identity([ByRef] string user_id);
[Throws=CryptoStoreError]
Device? get_device([ByRef] string user_id, [ByRef] string device_id);
[Throws=CryptoStoreError]
void mark_device_as_trusted([ByRef] string user_id, [ByRef] string device_id);
[Throws=SignatureError]
SignatureUploadRequest verify_device([ByRef] string user_id, [ByRef] string device_id);
[Throws=CryptoStoreError]
sequence<Device> get_user_devices([ByRef] string user_id);
@ -268,4 +329,13 @@ interface OlmMachine {
);
[Throws=CryptoStoreError]
void discard_room_key([ByRef] string room_id);
CrossSigningStatus cross_signing_status();
[Throws=CryptoStoreError]
BootstrapCrossSigningResult bootstrap_cross_signing();
CrossSigningKeyExport? export_cross_signing_keys();
[Throws=SecretImportError]
void import_cross_signing_keys(CrossSigningKeyExport export);
[Throws=CryptoStoreError]
boolean is_identity_verified([ByRef] string user_id);
};

View file

@ -3,13 +3,18 @@
use std::{collections::HashMap, convert::TryFrom};
use http::Response;
use matrix_sdk_common::uuid::Uuid;
use serde_json::json;
use ruma::{
api::client::r0::{
keys::{
claim_keys::Response as KeysClaimResponse, get_keys::Response as KeysQueryResponse,
claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse},
get_keys::Response as KeysQueryResponse,
upload_keys::Response as KeysUploadResponse,
upload_signatures::{
Request as RustSignatureUploadRequest, Response as SignatureUploadResponse,
},
},
sync::sync_events::DeviceLists as RumaDeviceLists,
to_device::send_event_to_device::Response as ToDeviceResponse,
@ -21,9 +26,65 @@ use ruma::{
use matrix_sdk_crypto::{
IncomingResponse, OutgoingRequest, OutgoingVerificationRequest as SdkVerificationRequest,
RoomMessageRequest, ToDeviceRequest,
RoomMessageRequest, ToDeviceRequest, UploadSigningKeysRequest as RustUploadSigningKeysRequest,
};
pub struct SignatureUploadRequest {
pub body: String,
}
impl From<RustSignatureUploadRequest> for SignatureUploadRequest {
fn from(r: RustSignatureUploadRequest) -> Self {
Self {
body: serde_json::to_string(&r.signed_keys)
.expect("Can't serialize signature upload request"),
}
}
}
pub struct UploadSigningKeysRequest {
pub master_key: String,
pub self_signing_key: String,
pub user_signing_key: String,
}
impl From<RustUploadSigningKeysRequest> for UploadSigningKeysRequest {
fn from(r: RustUploadSigningKeysRequest) -> Self {
Self {
master_key: serde_json::to_string(
&r.master_key.expect("Request didn't contain a master key"),
)
.expect("Can't serialize cross signing master key"),
self_signing_key: serde_json::to_string(
&r.self_signing_key
.expect("Request didn't contain a self-signing key"),
)
.expect("Can't serialize cross signing self-signing key"),
user_signing_key: serde_json::to_string(
&r.user_signing_key
.expect("Request didn't contain a user-signing key"),
)
.expect("Can't serialize cross signing user-signing key"),
}
}
}
pub struct BootstrapCrossSigningResult {
pub upload_signing_keys_request: UploadSigningKeysRequest,
pub signature_request: SignatureUploadRequest,
}
impl From<(RustUploadSigningKeysRequest, RustSignatureUploadRequest)>
for BootstrapCrossSigningResult
{
fn from(requests: (RustUploadSigningKeysRequest, RustSignatureUploadRequest)) -> Self {
Self {
upload_signing_keys_request: requests.0.into(),
signature_request: requests.1.into(),
}
}
}
pub enum OutgoingVerificationRequest {
ToDevice {
request_id: String,
@ -87,6 +148,10 @@ pub enum Request {
event_type: String,
content: String,
},
SignatureUpload {
request_id: String,
body: String,
},
}
impl From<OutgoingRequest> for Request {
@ -114,8 +179,13 @@ impl From<OutgoingRequest> for Request {
}
}
ToDeviceRequest(t) => Request::from(t),
SignatureUpload(_) => todo!("Uploading signatures isn't yet supported"),
SignatureUpload(t) => Request::SignatureUpload {
request_id: r.request_id().to_string(),
body: serde_json::to_string(&t.signed_keys)
.expect("Can't serialize signature upload request"),
},
RoomMessage(r) => Request::from(r),
KeysClaim(c) => (*r.request_id(), c.clone()).into(),
}
}
}
@ -130,6 +200,28 @@ impl From<ToDeviceRequest> for Request {
}
}
impl From<(Uuid, KeysClaimRequest)> for Request {
fn from(request_tuple: (Uuid, KeysClaimRequest)) -> Self {
let (request_id, request) = request_tuple;
Request::KeysClaim {
request_id: request_id.to_string(),
one_time_keys: request
.one_time_keys
.into_iter()
.map(|(u, d)| {
(
u.to_string(),
d.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
)
})
.collect(),
}
}
}
impl From<&ToDeviceRequest> for Request {
fn from(r: &ToDeviceRequest) -> Self {
Request::ToDevice {
@ -163,6 +255,7 @@ pub enum RequestType {
KeysClaim,
KeysUpload,
ToDevice,
SignatureUpload,
}
pub struct DeviceLists {
@ -197,6 +290,7 @@ pub(crate) enum OwnedResponse {
KeysUpload(KeysUploadResponse),
KeysQuery(KeysQueryResponse),
ToDevice(ToDeviceResponse),
SignatureUpload(SignatureUploadResponse),
}
impl From<KeysClaimResponse> for OwnedResponse {
@ -223,6 +317,12 @@ impl From<ToDeviceResponse> for OwnedResponse {
}
}
impl From<SignatureUploadResponse> for OwnedResponse {
fn from(response: SignatureUploadResponse) -> Self {
Self::SignatureUpload(response)
}
}
impl<'a> Into<IncomingResponse<'a>> for &'a OwnedResponse {
fn into(self) -> IncomingResponse<'a> {
match self {
@ -230,6 +330,7 @@ impl<'a> Into<IncomingResponse<'a>> for &'a OwnedResponse {
OwnedResponse::KeysQuery(r) => IncomingResponse::KeysQuery(r),
OwnedResponse::KeysUpload(r) => IncomingResponse::KeysUpload(r),
OwnedResponse::ToDevice(r) => IncomingResponse::ToDevice(r),
OwnedResponse::SignatureUpload(r) => IncomingResponse::SignatureUpload(r),
}
}
}

61
rust-sdk/src/users.rs Normal file
View file

@ -0,0 +1,61 @@
use matrix_sdk_crypto::UserIdentities;
use ruma::encryption::CrossSigningKey;
use crate::CryptoStoreError;
/// Enum representing cross signing identities of our own user or some other
/// user.
pub enum UserIdentity {
/// Our own user identity.
Own {
/// The unique id of our own user.
user_id: String,
/// Does our own user identity trust our own device.
trusts_our_own_device: bool,
/// The public master key of our identity.
master_key: String,
/// The public user-signing key of our identity.
user_signing_key: String,
/// The public self-signing key of our identity.
self_signing_key: String,
},
/// The user identity of other users.
Other {
/// The unique id of the user.
user_id: String,
/// The public master key of the identity.
master_key: String,
/// The public self-signing key of our identity.
self_signing_key: String,
},
}
impl UserIdentity {
pub(crate) async fn from_rust(i: UserIdentities) -> Result<Self, CryptoStoreError> {
Ok(match i {
UserIdentities::Own(i) => {
let master: CrossSigningKey = i.master_key().to_owned().into();
let user_signing: CrossSigningKey = i.user_signing_key().to_owned().into();
let self_signing: CrossSigningKey = i.self_signing_key().to_owned().into();
UserIdentity::Own {
user_id: i.user_id().to_string(),
trusts_our_own_device: i.trusts_our_own_device().await?,
master_key: serde_json::to_string(&master)?,
user_signing_key: serde_json::to_string(&user_signing)?,
self_signing_key: serde_json::to_string(&self_signing)?,
}
}
UserIdentities::Other(i) => {
let master: CrossSigningKey = i.master_key().to_owned().into();
let self_signing: CrossSigningKey = i.self_signing_key().to_owned().into();
UserIdentity::Other {
user_id: i.user_id().to_string(),
master_key: serde_json::to_string(&master)?,
self_signing_key: serde_json::to_string(&self_signing)?,
}
}
})
}
}