use std::{borrow::Cow, str::FromStr};
use bitwarden_encoding::{B64, FromStrVisitor};
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;
use serde_repr::{Deserialize_repr, Serialize_repr};
use super::PublicKey;
use crate::{
CoseSign1Bytes, CryptoError, PublicKeyEncryptionAlgorithm, RawPublicKey, SignedObject,
SigningKey, SigningNamespace, SpkiPublicKeyBytes, VerifyingKey, cose::CoseSerializable,
error::EncodingError,
};
#[cfg(feature = "wasm")]
#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
const TS_CUSTOM_TYPES: &'static str = r#"
export type SignedPublicKey = Tagged<string, "SignedPublicKey">;
"#;
#[derive(Serialize_repr, Deserialize_repr)]
#[repr(u8)]
enum PublicKeyFormat {
Spki = 0,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SignedPublicKeyMessage {
algorithm: PublicKeyEncryptionAlgorithm,
content_format: PublicKeyFormat,
public_key: ByteBuf,
}
impl SignedPublicKeyMessage {
pub fn from_public_key(public_key: &PublicKey) -> Result<Self, CryptoError> {
match public_key.inner() {
RawPublicKey::RsaOaepSha1(_) => Ok(SignedPublicKeyMessage {
algorithm: PublicKeyEncryptionAlgorithm::RsaOaepSha1,
content_format: PublicKeyFormat::Spki,
public_key: ByteBuf::from(public_key.to_der()?.as_ref()),
}),
}
}
pub fn sign(&self, signing_key: &SigningKey) -> Result<SignedPublicKey, CryptoError> {
Ok(SignedPublicKey(
signing_key.sign(self, &SigningNamespace::SignedPublicKey)?,
))
}
}
#[derive(Clone, PartialEq)]
pub struct SignedPublicKey(pub(crate) SignedObject);
impl std::fmt::Debug for SignedPublicKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug_struct = f.debug_struct("SignedPublicKey");
if let Ok(key_id) = self.0.signed_by_id() {
debug_struct.field("signed_by", &key_id);
}
if let Some(msg) = self
.0
.dangerous_unverified_decode_do_not_use_except_for_debug_logs::<SignedPublicKeyMessage>(
)
{
debug_struct.field("algorithm", &msg.algorithm);
debug_struct.field("public_key", &hex::encode(&msg.public_key));
}
debug_struct.finish()
}
}
impl From<SignedPublicKey> for CoseSign1Bytes {
fn from(val: SignedPublicKey) -> Self {
val.0.to_cose()
}
}
impl TryFrom<CoseSign1Bytes> for SignedPublicKey {
type Error = EncodingError;
fn try_from(bytes: CoseSign1Bytes) -> Result<Self, EncodingError> {
Ok(SignedPublicKey(SignedObject::from_cose(&bytes)?))
}
}
impl From<SignedPublicKey> for String {
fn from(val: SignedPublicKey) -> Self {
let bytes: CoseSign1Bytes = val.into();
B64::from(bytes.as_ref()).to_string()
}
}
impl SignedPublicKey {
pub fn verify_and_unwrap(self, verifying_key: &VerifyingKey) -> Result<PublicKey, CryptoError> {
let public_key_message: SignedPublicKeyMessage = self
.0
.verify_and_unwrap(verifying_key, &SigningNamespace::SignedPublicKey)?;
match (
public_key_message.algorithm,
public_key_message.content_format,
) {
(PublicKeyEncryptionAlgorithm::RsaOaepSha1, PublicKeyFormat::Spki) => {
Ok(PublicKey::from_der(&SpkiPublicKeyBytes::from(
public_key_message.public_key.into_vec(),
))
.map_err(|_| EncodingError::InvalidValue("public key"))?)
}
}
}
}
impl FromStr for SignedPublicKey {
type Err = EncodingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = B64::try_from(s).map_err(|_| EncodingError::InvalidCborSerialization)?;
Self::try_from(CoseSign1Bytes::from(&bytes))
}
}
impl<'de> Deserialize<'de> for SignedPublicKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(FromStrVisitor::new())
}
}
impl serde::Serialize for SignedPublicKey {
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)
}
}
impl schemars::JsonSchema for SignedPublicKey {
fn schema_name() -> Cow<'static, str> {
"SignedPublicKey".into()
}
fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
generator.subschema_for::<String>()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{PrivateKey, PublicKeyEncryptionAlgorithm, SignatureAlgorithm};
#[test]
#[ignore = "Manual test to verify debug format"]
fn test_debug() {
let public_key =
PrivateKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1).to_public_key();
let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
let message = SignedPublicKeyMessage::from_public_key(&public_key).unwrap();
let signed_public_key = message.sign(&signing_key).unwrap();
println!("{:?}", signed_public_key);
}
#[test]
fn test_signed_asymmetric_public_key() {
let public_key =
PrivateKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1).to_public_key();
let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
let message = SignedPublicKeyMessage::from_public_key(&public_key).unwrap();
let signed_public_key = message.sign(&signing_key).unwrap();
let verifying_key = signing_key.to_verifying_key();
let verified_public_key = signed_public_key.verify_and_unwrap(&verifying_key).unwrap();
assert_eq!(
public_key.to_der().unwrap(),
verified_public_key.to_der().unwrap()
);
}
}