use std::{fmt::Debug, str::FromStr};
use bitwarden_crypto::{
CoseSerializable, CoseSign1Bytes, CryptoError, EncodingError, KeySlotIds, KeyStoreContext,
SignedObject, SigningNamespace, VerifyingKey,
};
use bitwarden_encoding::{B64, FromStrVisitor};
use serde::{Deserialize, Serialize};
pub const MINIMUM_ENFORCE_ICON_URI_HASH_VERSION: u64 = 2;
pub const BLOB_SECURITY_VERSION: u64 = 2;
#[cfg(feature = "wasm")]
#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
const TS_CUSTOM_TYPES: &'static str = r#"
export type SignedSecurityState = string;
"#;
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SecurityState {
version: u64,
}
impl Default for SecurityState {
fn default() -> Self {
Self::new()
}
}
impl SecurityState {
pub fn new() -> Self {
SecurityState { version: 2 }
}
pub fn version(&self) -> u64 {
self.version
}
pub fn sign<Ids: KeySlotIds>(
&self,
signing_key_id: Ids::Signing,
ctx: &mut KeyStoreContext<Ids>,
) -> Result<SignedSecurityState, CryptoError> {
Ok(SignedSecurityState(ctx.sign(
signing_key_id,
&self,
&SigningNamespace::SecurityState,
)?))
}
}
#[derive(Clone, PartialEq)]
pub struct SignedSecurityState(pub(crate) SignedObject);
impl Debug for SignedSecurityState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug_struct = f.debug_struct("SignedSecurityState");
if let Ok(signed_by) = self.0.signed_by_id() {
debug_struct.field("signed_by", &signed_by);
}
if let Some(state) = self
.0
.dangerous_unverified_decode_do_not_use_except_for_debug_logs::<SecurityState>()
{
debug_struct.field("version", &state.version);
}
debug_struct.finish()
}
}
impl SignedSecurityState {
pub fn verify_and_unwrap(
self,
verifying_key: &VerifyingKey,
) -> Result<SecurityState, CryptoError> {
self.0
.verify_and_unwrap(verifying_key, &SigningNamespace::SecurityState)
}
}
impl From<SignedSecurityState> for CoseSign1Bytes {
fn from(val: SignedSecurityState) -> Self {
val.0.to_cose()
}
}
impl TryFrom<&CoseSign1Bytes> for SignedSecurityState {
type Error = EncodingError;
fn try_from(bytes: &CoseSign1Bytes) -> Result<Self, EncodingError> {
Ok(SignedSecurityState(SignedObject::from_cose(bytes)?))
}
}
impl From<&SignedSecurityState> for String {
fn from(val: &SignedSecurityState) -> Self {
val.to_owned().into()
}
}
impl From<SignedSecurityState> for String {
fn from(val: SignedSecurityState) -> Self {
let bytes: CoseSign1Bytes = val.into();
B64::from(bytes.as_ref()).to_string()
}
}
impl FromStr for SignedSecurityState {
type Err = EncodingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = B64::try_from(s).map_err(|_| EncodingError::InvalidBase64Encoding)?;
Self::try_from(&CoseSign1Bytes::from(&bytes))
}
}
impl<'de> Deserialize<'de> for SignedSecurityState {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(FromStrVisitor::new())
}
}
impl serde::Serialize for SignedSecurityState {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let b64_serialized_signed_public_key: String = self.clone().into();
serializer.serialize_str(&b64_serialized_signed_public_key)
}
}
#[cfg(test)]
mod tests {
use bitwarden_crypto::{CoseKeyBytes, KeyStore, SignatureAlgorithm, SigningKey};
use super::*;
use crate::key_management::KeySlotIds;
const TEST_SIGNED_SECURITY_STATE: &str = "hFgepAEnAxg8BFBHo5ojcqDqbynNymOZGgJzOgABOH8CoFgkomhlbnRpdHlJZFBHmj2OTpBFO7aDLgeNnbZPZ3ZlcnNpb24CWEA4mQbYRRoPpc77tVHH4LlwY52Vz6tutThv8b/BV3ntQmjuKUxbzIGRxSyOhzCn3ouFJGEVnfsl6SqSm6K9XcME";
const TEST_VERIFYING_KEY: &str =
"pgEBAlBHo5ojcqDqbynNymOZGgJzAycEgQIgBiFYIK9hIvbLIdnzKhykPt8jT/ktXAlzPUfx4Nyx4EYTpIp7";
#[test]
#[ignore = "Manual test for debug logs"]
fn test_security_state_debug_logs() {
let store: KeyStore<KeySlotIds> = KeyStore::default();
let mut ctx = store.context_mut();
let security_state = SecurityState::new();
let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
let key = ctx.add_local_signing_key(signing_key.clone());
let signed_security_state = security_state.sign(key, &mut ctx).unwrap();
println!("{:?}", signed_security_state);
}
#[test]
#[ignore = "Make test vectors"]
fn test_make_test_vector() {
let store: KeyStore<KeySlotIds> = KeyStore::default();
let mut ctx = store.context_mut();
let security_state = SecurityState::new();
let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
let key = ctx.add_local_signing_key(signing_key.clone());
let signed_security_state = security_state.sign(key, &mut ctx).unwrap();
let verifying_key = signing_key.to_verifying_key();
println!(
"const TEST_SIGNED_SECURITY_STATE: &str = \"{}\";",
String::from(&signed_security_state)
);
println!(
"const TEST_VERIFYING_KEY: &str = \"{}\";",
B64::from(verifying_key.to_cose())
);
}
#[test]
fn test_security_state_signing() {
let store: KeyStore<KeySlotIds> = KeyStore::default();
let mut ctx = store.context_mut();
let security_state = SecurityState::new();
let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
let key = ctx.add_local_signing_key(signing_key.clone());
let signed_security_state = security_state.sign(key, &mut ctx).unwrap();
let verifying_key = signing_key.to_verifying_key();
let verified_security_state = signed_security_state
.verify_and_unwrap(&verifying_key)
.unwrap();
assert_eq!(verified_security_state.version(), 2);
}
#[test]
fn test_stable_testvector() {
let b64 = B64::try_from(TEST_VERIFYING_KEY).unwrap();
let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(&b64)).unwrap();
let signed_security_state =
SignedSecurityState::from_str(TEST_SIGNED_SECURITY_STATE).unwrap();
let verified_security_state = signed_security_state
.verify_and_unwrap(&verifying_key)
.unwrap();
assert_eq!(verified_security_state.version(), 2);
}
}