diff --git a/jose-b64/README.md b/jose-b64/README.md index ccf6d4b..13b2268 100644 --- a/jose-b64/README.md +++ b/jose-b64/README.md @@ -17,6 +17,57 @@ include: [Documentation][docs-link] +## Crate features + +- `secret`: This feature enables constant time operations (via `subtle`) and + memory zeroization (via `zeroize`) for secure use of Base64. This feature is + enabled by default. +- `serde`: Enable wrapper types usable with `serde` +- `json`: Enable a wrapper type for nested b64 serialization within JSON + +## Examples + +```rust +# #[cfg(all(feature = "json", feature = "secret"))] { +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; +use jose_b64::{B64Bytes, Json, B64Secret}; + +#[derive(Debug, PartialEq, Deserialize, Serialize)] +struct Inner { + name: String, + value: u64 +} + +#[derive(Debug, PartialEq, Deserialize, Serialize)] +struct Data { + /// Base64-encoded data + unsecure: B64Bytes>, + /// JSON embedded as base64 + inner: Json, + /// Base64-encoded data, to be serialized/deserialized securely + secret: B64Secret> +} + +let input = r#"{ + "unsecure": "SGVsbG8gd29ybGQh", + "inner": "eyJuYW1lIjoiYmFyIiwidmFsdWUiOjEyMzQ1Nn0", + "secret": "dG9wIHNlY3JldA" +}"#; + +let decoded: Data = serde_json::from_str(input).unwrap(); + +let expected = Data { + unsecure: Vec::from(b"Hello world!".as_slice()).into(), + inner: Json::new(Inner { name: String::from("bar"), value: 123456 }).unwrap(), + secret: Vec::from(b"top secret".as_slice()).into() +}; + +assert_eq!(expected, decoded); +# } +``` + ## Minimum Supported Rust Version This crate requires **Rust 1.65** at a minimum. diff --git a/jose-b64/src/zero.rs b/jose-b64/src/fake_zeroize.rs similarity index 86% rename from jose-b64/src/zero.rs rename to jose-b64/src/fake_zeroize.rs index 3ef1f26..c897845 100644 --- a/jose-b64/src/zero.rs +++ b/jose-b64/src/fake_zeroize.rs @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: 2022 Profian Inc. // SPDX-License-Identifier: Apache-2.0 OR MIT +//! When the zeroize crate is not used (`secret` feature is not enabled), this +//! implements the required zeroize trait in a non-secret way + #![cfg(not(feature = "secret"))] use core::ops::{Deref, DerefMut}; diff --git a/jose-b64/src/lib.rs b/jose-b64/src/lib.rs index a4abfc6..7b7ac76 100644 --- a/jose-b64/src/lib.rs +++ b/jose-b64/src/lib.rs @@ -21,15 +21,25 @@ extern crate alloc; -pub mod serde; +mod fake_zeroize; pub mod stream; - -mod zero; +mod wrapper_bytes; +mod wrapper_json; +mod wrapper_secret; pub use base64ct; +#[cfg(feature = "serde")] +pub use wrapper_bytes::B64Bytes; + +#[cfg(feature = "secret")] +pub use wrapper_secret::B64Secret; + +#[cfg(feature = "json")] +pub use wrapper_json::Json; + #[cfg(feature = "secret")] use zeroize::{Zeroize, Zeroizing}; #[cfg(not(feature = "secret"))] -use zero::{Zeroize, Zeroizing}; +use fake_zeroize::{Zeroize, Zeroizing}; diff --git a/jose-b64/src/serde/mod.rs b/jose-b64/src/serde/mod.rs deleted file mode 100644 index ee53b30..0000000 --- a/jose-b64/src/serde/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Profian Inc. -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! Utilities for encoding serde types - -#![cfg(feature = "serde")] - -mod bytes; -mod json; -mod secret; - -pub use bytes::Bytes; - -#[cfg(feature = "secret")] -pub use secret::Secret; - -#[cfg(feature = "json")] -pub use json::Json; diff --git a/jose-b64/src/serde/bytes.rs b/jose-b64/src/wrapper_bytes.rs similarity index 63% rename from jose-b64/src/serde/bytes.rs rename to jose-b64/src/wrapper_bytes.rs index c5bbcde..9e45a44 100644 --- a/jose-b64/src/serde/bytes.rs +++ b/jose-b64/src/wrapper_bytes.rs @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2022 Profian Inc. // SPDX-License-Identifier: Apache-2.0 OR MIT +#![cfg(feature = "serde")] + use alloc::boxed::Box; use alloc::string::String; use alloc::vec::Vec; @@ -15,26 +17,27 @@ use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use crate::stream::Error; -/// A serde wrapper for base64-encoded bytes. +/// A serde wrapper for non-secure base64-encoded bytes. Available with the +/// feature `serde`. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Bytes, E = Base64UrlUnpadded> { +pub struct B64Bytes, E = Base64UrlUnpadded> { buf: T, cfg: PhantomData, } -impl crate::Zeroize for Bytes { +impl crate::Zeroize for B64Bytes { fn zeroize(&mut self) { self.buf.zeroize() } } -impl Debug for Bytes { +impl Debug for B64Bytes { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_tuple("Bytes").field(&self.buf).finish() } } -impl Deref for Bytes { +impl Deref for B64Bytes { type Target = T; fn deref(&self) -> &Self::Target { @@ -42,25 +45,25 @@ impl Deref for Bytes { } } -impl DerefMut for Bytes { +impl DerefMut for B64Bytes { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.buf } } -impl, U: ?Sized, E> AsRef for Bytes { +impl, U: ?Sized, E> AsRef for B64Bytes { fn as_ref(&self) -> &U { self.buf.as_ref() } } -impl, U: ?Sized, E> AsMut for Bytes { +impl, U: ?Sized, E> AsMut for B64Bytes { fn as_mut(&mut self) -> &mut U { self.buf.as_mut() } } -impl From for Bytes { +impl From for B64Bytes { fn from(buf: T) -> Self { Self { buf, @@ -69,31 +72,31 @@ impl From for Bytes { } } -impl From> for Bytes, E> { +impl From> for B64Bytes, E> { fn from(buf: Vec) -> Self { Self::from(buf.into_boxed_slice()) } } -impl From, E>> for Bytes, E> { - fn from(bytes: Bytes, E>) -> Self { +impl From, E>> for B64Bytes, E> { + fn from(bytes: B64Bytes, E>) -> Self { Self::from(bytes.buf.into_boxed_slice()) } } -impl From> for Bytes, E> { +impl From> for B64Bytes, E> { fn from(buf: Box<[u8]>) -> Self { Self::from(buf.into_vec()) } } -impl From, E>> for Bytes, E> { - fn from(bytes: Bytes, E>) -> Self { +impl From, E>> for B64Bytes, E> { + fn from(bytes: B64Bytes, E>) -> Self { Self::from(bytes.buf.into_vec()) } } -impl FromStr for Bytes, E> { +impl FromStr for B64Bytes, E> { type Err = Error; fn from_str(s: &str) -> Result { @@ -104,22 +107,22 @@ impl FromStr for Bytes, E> { } } -impl FromStr for Bytes, E> { +impl FromStr for B64Bytes, E> { type Err = Error; fn from_str(s: &str) -> Result { - Bytes::, E>::from_str(s).map(|x| x.buf.into_boxed_slice().into()) + B64Bytes::, E>::from_str(s).map(|x| x.buf.into_boxed_slice().into()) } } -impl, E: Encoding> Serialize for Bytes { +impl, E: Encoding> Serialize for B64Bytes { fn serialize(&self, serializer: S) -> Result { let b64 = crate::Zeroizing::from(E::encode_string(self.buf.as_ref())); b64.serialize(serializer) } } -impl<'de, E: Encoding> Deserialize<'de> for Bytes, E> { +impl<'de, E: Encoding> Deserialize<'de> for B64Bytes, E> { fn deserialize>(deserializer: D) -> Result { let enc = crate::Zeroizing::from(String::deserialize(deserializer)?); let dec = E::decode_vec(&enc).map_err(|_| D::Error::custom("invalid base64"))?; @@ -131,15 +134,15 @@ impl<'de, E: Encoding> Deserialize<'de> for Bytes, E> { } } -impl<'de, E: Encoding> Deserialize<'de> for Bytes, E> { +impl<'de, E: Encoding> Deserialize<'de> for B64Bytes, E> { fn deserialize>(deserializer: D) -> Result { - Bytes::, E>::deserialize(deserializer).map(|x| x.buf.into_boxed_slice().into()) + B64Bytes::, E>::deserialize(deserializer).map(|x| x.buf.into_boxed_slice().into()) } } -impl<'de, E: Encoding, const N: usize> Deserialize<'de> for Bytes<[u8; N], E> { +impl<'de, E: Encoding, const N: usize> Deserialize<'de> for B64Bytes<[u8; N], E> { fn deserialize>(deserializer: D) -> Result { - let bytes = Bytes::, E>::deserialize(deserializer)?; + let bytes = B64Bytes::, E>::deserialize(deserializer)?; let array = <[u8; N]>::try_from(bytes.buf); Ok(array diff --git a/jose-b64/src/serde/json.rs b/jose-b64/src/wrapper_json.rs similarity index 76% rename from jose-b64/src/serde/json.rs rename to jose-b64/src/wrapper_json.rs index 70dcbd4..332a4cf 100644 --- a/jose-b64/src/serde/json.rs +++ b/jose-b64/src/wrapper_json.rs @@ -14,28 +14,29 @@ use base64ct::{Base64UrlUnpadded, Encoding}; use serde::de::{DeserializeOwned, Error as _}; use serde::{Deserialize, Deserializer, Serialize}; -use super::Bytes; +use super::B64Bytes; use crate::stream::Error; -/// A wrapper for nested, base64-encoded JSON +/// A wrapper for nested, base64-encoded JSON. Available with the feature +/// `json`. /// /// [`Json`] handles the case where a type (`T`) is serialized to JSON and then /// embedded into another JSON object as a base64-string. Note that [`Json`] /// internally stores both the originally decoded bytes **and** the -/// doubly-decoded value. While this uses additional memory, it ensures that -/// the original serialization is not lost. This is important in cryptogrpahic -/// contexts where the original serialization may be included in a -/// cryptogrpahic measurement. +/// doubly-decoded value. While this uses additional memory, it ensures that the +/// original serialization is not lost. This is important in cryptogrpahic +/// contexts where the original serialization may be included in a cryptogrpahic +/// measurement. /// /// During deserialization, a full double deserialization is performed. This /// ensures that an instantiated [`Json`] object is always fully parsed. During /// serialization, only the pre-serialized bytes are used; the type (`T`) is /// **not** reserialized. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[serde(bound(serialize = "Bytes: Serialize"))] +#[serde(bound(serialize = "B64Bytes: Serialize"))] #[serde(transparent)] pub struct Json, E = Base64UrlUnpadded> { - buf: Bytes, + buf: B64Bytes, #[serde(skip_serializing)] val: T, @@ -55,14 +56,14 @@ impl, E> AsRef<[u8]> for Json { } } -impl TryFrom> for Json +impl TryFrom> for Json where - Bytes: AsRef<[u8]>, + B64Bytes: AsRef<[u8]>, T: DeserializeOwned, { type Error = serde_json::Error; - fn try_from(buf: Bytes) -> Result { + fn try_from(buf: B64Bytes) -> Result { Ok(Self { val: serde_json::from_slice(buf.as_ref())?, buf, @@ -72,7 +73,7 @@ where impl Json where - Bytes: From>, + B64Bytes: From>, T: Serialize, { /// Creates a new instance by serializing the input to JSON. @@ -89,27 +90,27 @@ where impl FromStr for Json where - Bytes: FromStr>, - Bytes: AsRef<[u8]>, + B64Bytes: FromStr>, + B64Bytes: AsRef<[u8]>, T: DeserializeOwned, { type Err = Error; fn from_str(s: &str) -> Result { - let buf = Bytes::from_str(s).map_err(|e| e.cast())?; + let buf = B64Bytes::from_str(s).map_err(|e| e.cast())?; buf.try_into().map_err(Error::Inner) } } impl<'de, T, B, E> Deserialize<'de> for Json where - Bytes: Deserialize<'de>, - Bytes: AsRef<[u8]>, + B64Bytes: Deserialize<'de>, + B64Bytes: AsRef<[u8]>, T: DeserializeOwned, E: Encoding, { fn deserialize>(deserializer: D) -> Result { - Ok(match Self::try_from(Bytes::deserialize(deserializer)?) { + Ok(match Self::try_from(B64Bytes::deserialize(deserializer)?) { Err(e) => return Err(D::Error::custom(e)), Ok(x) => x, }) diff --git a/jose-b64/src/serde/secret.rs b/jose-b64/src/wrapper_secret.rs similarity index 54% rename from jose-b64/src/serde/secret.rs rename to jose-b64/src/wrapper_secret.rs index 16b0b31..3775c00 100644 --- a/jose-b64/src/serde/secret.rs +++ b/jose-b64/src/wrapper_secret.rs @@ -12,73 +12,74 @@ use serde::{Deserialize, Serialize}; use subtle::ConstantTimeEq; use zeroize::{Zeroize, Zeroizing}; -use super::Bytes; +use super::B64Bytes; -/// A serde wrapper for base64-encoded secrets. +/// A serde wrapper for base64-encoded secrets (secure version of [`B64Bytes`]). +/// Available with the feature `secret`. /// -/// A secret is like the [`Bytes`] type, with some additional protections: +/// A secret is like the [`B64Bytes`] type, with some additional protections: /// /// 1. It is zeroed on drop. /// 2. Its equality implementation is constant time. /// 2. Its contents are not printed in the debug formatter. #[derive(Clone, Serialize, Deserialize)] #[serde(transparent)] -#[serde(bound(serialize = "Bytes: Serialize"))] -#[serde(bound(deserialize = "Bytes: Deserialize<'de>"))] -pub struct Secret, E = Base64UrlUnpadded>(Zeroizing>); +#[serde(bound(serialize = "B64Bytes: Serialize"))] +#[serde(bound(deserialize = "B64Bytes: Deserialize<'de>"))] +pub struct B64Secret, E = Base64UrlUnpadded>(Zeroizing>); -impl Debug for Secret { +impl Debug for B64Secret { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "Secret(***)") } } -impl>, E> From for Secret { +impl>, E> From for B64Secret { fn from(value: U) -> Self { Self(Zeroizing::new(value.into())) } } -impl Deref for Secret { - type Target = Bytes; +impl Deref for B64Secret { + type Target = B64Bytes; fn deref(&self) -> &Self::Target { &self.0 } } -impl DerefMut for Secret { +impl DerefMut for B64Secret { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } -impl AsRef for Secret +impl AsRef for B64Secret where - Bytes: AsRef, + B64Bytes: AsRef, { fn as_ref(&self) -> &U { self.0.as_ref() } } -impl AsMut for Secret +impl AsMut for B64Secret where - Bytes: AsMut, + B64Bytes: AsMut, { fn as_mut(&mut self) -> &mut U { self.0.as_mut() } } -impl + Sized, E> ConstantTimeEq for Secret { +impl + Sized, E> ConstantTimeEq for B64Secret { fn ct_eq(&self, other: &Self) -> subtle::Choice { self.0.as_ref().ct_eq(other.0.as_ref()) } } -impl, E> Eq for Secret {} -impl, E> PartialEq for Secret { +impl, E> Eq for B64Secret {} +impl, E> PartialEq for B64Secret { fn eq(&self, other: &Self) -> bool { self.ct_eq(other).unwrap_u8() == 1 } diff --git a/jose-jwk/src/key/ec.rs b/jose-jwk/src/key/ec.rs index bfa8e4a..1024d91 100644 --- a/jose-jwk/src/key/ec.rs +++ b/jose-jwk/src/key/ec.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; -use jose_b64::serde::{Bytes, Secret}; +use jose_b64::{B64Bytes, B64Secret}; /// An elliptic-curve key. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -14,14 +14,14 @@ pub struct Ec { pub crv: EcCurves, /// The public x coordinate. - pub x: Bytes, + pub x: B64Bytes, /// The public y coordinate. - pub y: Bytes, + pub y: B64Bytes, /// The private key. #[serde(skip_serializing_if = "Option::is_none", default)] - pub d: Option, + pub d: Option, } /// The elliptic curve. diff --git a/jose-jwk/src/key/oct.rs b/jose-jwk/src/key/oct.rs index 2046803..4b71e8b 100644 --- a/jose-jwk/src/key/oct.rs +++ b/jose-jwk/src/key/oct.rs @@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize}; -use jose_b64::serde::Secret; +use jose_b64::B64Secret; /// A symmetric octet key. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Oct { /// The symmetric key. - pub k: Secret, + pub k: B64Secret, } diff --git a/jose-jwk/src/key/okp.rs b/jose-jwk/src/key/okp.rs index 00f7edd..d74e163 100644 --- a/jose-jwk/src/key/okp.rs +++ b/jose-jwk/src/key/okp.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; -use jose_b64::serde::{Bytes, Secret}; +use jose_b64::{B64Bytes, B64Secret}; /// A octet key pair CFRG-curve key, as defined in [RFC 8037] /// @@ -16,11 +16,11 @@ pub struct Okp { pub crv: OkpCurves, /// The public key. - pub x: Bytes, + pub x: B64Bytes, /// The private key. #[serde(skip_serializing_if = "Option::is_none", default)] - pub d: Option, + pub d: Option, } /// The CFRG Curve. diff --git a/jose-jwk/src/key/rsa.rs b/jose-jwk/src/key/rsa.rs index 27b762e..d35a808 100644 --- a/jose-jwk/src/key/rsa.rs +++ b/jose-jwk/src/key/rsa.rs @@ -7,16 +7,16 @@ use alloc::vec::Vec; use serde::{Deserialize, Serialize}; -use jose_b64::serde::{Bytes, Secret}; +use jose_b64::{B64Bytes, B64Secret}; /// An RSA key. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Rsa { /// The RSA modulus. - pub n: Bytes, + pub n: B64Bytes, /// The RSA public exponent. - pub e: Bytes, + pub e: B64Bytes, /// The RSA private key material. #[serde(skip_serializing_if = "Option::is_none", default, flatten)] @@ -27,16 +27,16 @@ pub struct Rsa { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct RsaPrivate { /// The RSA private key exponent. - pub d: Secret, + pub d: B64Secret, /// Optional RSA private key material. #[serde(skip_serializing_if = "Option::is_none", default, flatten)] pub opt: Option, } -impl From for RsaPrivate { +impl From for RsaPrivate { #[inline(always)] - fn from(bytes: Secret) -> Self { + fn from(bytes: B64Secret) -> Self { Self { d: bytes, opt: None, @@ -48,19 +48,19 @@ impl From for RsaPrivate { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct RsaOptional { /// The private first prime factor. - pub p: Secret, + pub p: B64Secret, /// The private second prime factor. - pub q: Secret, + pub q: B64Secret, /// The private first factor CRT exponent. - pub dp: Secret, + pub dp: B64Secret, /// The private second factor CRT exponent. - pub dq: Secret, + pub dq: B64Secret, /// The private first CRT coefficient. - pub qi: Secret, + pub qi: B64Secret, /// Additional RSA private primes. #[serde(skip_serializing_if = "Vec::is_empty", default)] @@ -71,11 +71,11 @@ pub struct RsaOptional { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct RsaOtherPrimes { /// The private prime factor. - pub r: Secret, + pub r: B64Secret, /// The private factor CRT exponent. - pub d: Secret, + pub d: B64Secret, /// The private factor CRT coefficient. - pub t: Secret, + pub t: B64Secret, } diff --git a/jose-jwk/src/prm.rs b/jose-jwk/src/prm.rs index f96b1c6..5a1ffe1 100644 --- a/jose-jwk/src/prm.rs +++ b/jose-jwk/src/prm.rs @@ -11,7 +11,7 @@ use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use jose_b64::base64ct::Base64; -use jose_b64::serde::Bytes; +use jose_b64::B64Bytes; use jose_jwa::Algorithm; /// JWK parameters unrelated to the key implementation @@ -40,7 +40,7 @@ pub struct Parameters { /// The X.509 certificate associated with this key. #[serde(skip_serializing_if = "Option::is_none", default)] - pub x5c: Option, Base64>>>, // base64, not base64url + pub x5c: Option, Base64>>>, // base64, not base64url /// The X.509 thumbprint associated with this key. #[serde(flatten)] @@ -98,9 +98,9 @@ pub enum Operations { pub struct Thumbprint { /// An X.509 thumbprint (SHA-1). #[serde(skip_serializing_if = "Option::is_none", rename = "x5t", default)] - pub s1: Option>, + pub s1: Option>, /// An X.509 thumbprint (SHA-2 256). #[serde(skip_serializing_if = "Option::is_none", rename = "x5t#S256", default)] - pub s256: Option>, + pub s256: Option>, } diff --git a/jose-jws/src/head.rs b/jose-jws/src/head.rs index c8c1137..37de9ae 100644 --- a/jose-jws/src/head.rs +++ b/jose-jws/src/head.rs @@ -5,7 +5,7 @@ use alloc::vec::Vec; use alloc::{boxed::Box, string::String}; use jose_b64::base64ct::Base64; -use jose_b64::serde::Bytes; +use jose_b64::B64Bytes; use jose_jwa::Signing; use jose_jwk::{Jwk, Thumbprint}; use serde::{Deserialize, Serialize}; @@ -34,7 +34,7 @@ pub struct Protected { /// RFC 8555 Section 6.5.2 #[serde(skip_serializing_if = "Option::is_none", default)] - pub nonce: Option, + pub nonce: Option, /// RFC 7797 Section 3 #[serde(skip_serializing_if = "b64_serialize", default = "b64_default")] @@ -86,7 +86,7 @@ pub struct Unprotected { /// RFC 7517 Section 4.1.6 #[serde(skip_serializing_if = "Option::is_none", default)] - pub x5c: Option, Base64>>>, // base64, not base64url + pub x5c: Option, Base64>>>, // base64, not base64url /// RFC 7517 Section 4.1.7-8 #[serde(flatten)] diff --git a/jose-jws/src/lib.rs b/jose-jws/src/lib.rs index c33c36d..6dbe1a5 100644 --- a/jose-jws/src/lib.rs +++ b/jose-jws/src/lib.rs @@ -30,7 +30,7 @@ pub use head::{Protected, Unprotected}; use alloc::{vec, vec::Vec}; -use jose_b64::serde::{Bytes, Json}; +use jose_b64::{B64Bytes, Json}; use serde::{Deserialize, Serialize}; /// A JSON Web Signature representation @@ -79,7 +79,7 @@ impl From for Jws { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct General { /// The payload of the signature. - pub payload: Option, + pub payload: Option, /// The signatures over the payload. pub signatures: Vec, @@ -110,7 +110,7 @@ impl From for General { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Flattened { /// The payload of the signature. - pub payload: Option, + pub payload: Option, /// The signature over the payload. #[serde(flatten)] @@ -127,5 +127,5 @@ pub struct Signature { pub protected: Option>, /// The Signature Bytes - pub signature: Bytes, + pub signature: B64Bytes, }