Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update & refactor json-b64 crate #47

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions jose-b64/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u8>>,
/// JSON embedded as base64
inner: Json<Inner>,
/// Base64-encoded data, to be serialized/deserialized securely
secret: B64Secret<Vec<u8>>
}

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.
Expand Down
3 changes: 3 additions & 0 deletions jose-b64/src/zero.rs → jose-b64/src/fake_zeroize.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: 2022 Profian Inc. <opensource@profian.com>
// 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};
Expand Down
18 changes: 14 additions & 4 deletions jose-b64/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
18 changes: 0 additions & 18 deletions jose-b64/src/serde/mod.rs

This file was deleted.

51 changes: 27 additions & 24 deletions jose-b64/src/serde/bytes.rs → jose-b64/src/wrapper_bytes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: 2022 Profian Inc. <opensource@profian.com>
// SPDX-License-Identifier: Apache-2.0 OR MIT

#![cfg(feature = "serde")]

use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
Expand All @@ -15,52 +17,53 @@ 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<T = Box<[u8]>, E = Base64UrlUnpadded> {
pub struct B64Bytes<T = Box<[u8]>, E = Base64UrlUnpadded> {
buf: T,
cfg: PhantomData<E>,
}

impl<T: crate::Zeroize, E> crate::Zeroize for Bytes<T, E> {
impl<T: crate::Zeroize, E> crate::Zeroize for B64Bytes<T, E> {
fn zeroize(&mut self) {
self.buf.zeroize()
}
}

impl<T: Debug, E> Debug for Bytes<T, E> {
impl<T: Debug, E> Debug for B64Bytes<T, E> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("Bytes").field(&self.buf).finish()
}
}

impl<T, E> Deref for Bytes<T, E> {
impl<T, E> Deref for B64Bytes<T, E> {
type Target = T;

fn deref(&self) -> &Self::Target {
&self.buf
}
}

impl<T, E> DerefMut for Bytes<T, E> {
impl<T, E> DerefMut for B64Bytes<T, E> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buf
}
}

impl<T: AsRef<U>, U: ?Sized, E> AsRef<U> for Bytes<T, E> {
impl<T: AsRef<U>, U: ?Sized, E> AsRef<U> for B64Bytes<T, E> {
fn as_ref(&self) -> &U {
self.buf.as_ref()
}
}

impl<T: AsMut<U>, U: ?Sized, E> AsMut<U> for Bytes<T, E> {
impl<T: AsMut<U>, U: ?Sized, E> AsMut<U> for B64Bytes<T, E> {
fn as_mut(&mut self) -> &mut U {
self.buf.as_mut()
}
}

impl<T, E> From<T> for Bytes<T, E> {
impl<T, E> From<T> for B64Bytes<T, E> {
fn from(buf: T) -> Self {
Self {
buf,
Expand All @@ -69,31 +72,31 @@ impl<T, E> From<T> for Bytes<T, E> {
}
}

impl<E> From<Vec<u8>> for Bytes<Box<[u8]>, E> {
impl<E> From<Vec<u8>> for B64Bytes<Box<[u8]>, E> {
fn from(buf: Vec<u8>) -> Self {
Self::from(buf.into_boxed_slice())
}
}

impl<E> From<Bytes<Vec<u8>, E>> for Bytes<Box<[u8]>, E> {
fn from(bytes: Bytes<Vec<u8>, E>) -> Self {
impl<E> From<B64Bytes<Vec<u8>, E>> for B64Bytes<Box<[u8]>, E> {
fn from(bytes: B64Bytes<Vec<u8>, E>) -> Self {
Self::from(bytes.buf.into_boxed_slice())
}
}

impl<E> From<Box<[u8]>> for Bytes<Vec<u8>, E> {
impl<E> From<Box<[u8]>> for B64Bytes<Vec<u8>, E> {
fn from(buf: Box<[u8]>) -> Self {
Self::from(buf.into_vec())
}
}

impl<E> From<Bytes<Box<[u8]>, E>> for Bytes<Vec<u8>, E> {
fn from(bytes: Bytes<Box<[u8]>, E>) -> Self {
impl<E> From<B64Bytes<Box<[u8]>, E>> for B64Bytes<Vec<u8>, E> {
fn from(bytes: B64Bytes<Box<[u8]>, E>) -> Self {
Self::from(bytes.buf.into_vec())
}
}

impl<E: Encoding> FromStr for Bytes<Vec<u8>, E> {
impl<E: Encoding> FromStr for B64Bytes<Vec<u8>, E> {
type Err = Error<Infallible>;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Expand All @@ -104,22 +107,22 @@ impl<E: Encoding> FromStr for Bytes<Vec<u8>, E> {
}
}

impl<E: Encoding> FromStr for Bytes<Box<[u8]>, E> {
impl<E: Encoding> FromStr for B64Bytes<Box<[u8]>, E> {
type Err = Error<Infallible>;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Bytes::<Vec<u8>, E>::from_str(s).map(|x| x.buf.into_boxed_slice().into())
B64Bytes::<Vec<u8>, E>::from_str(s).map(|x| x.buf.into_boxed_slice().into())
}
}

impl<T: AsRef<[u8]>, E: Encoding> Serialize for Bytes<T, E> {
impl<T: AsRef<[u8]>, E: Encoding> Serialize for B64Bytes<T, E> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let b64 = crate::Zeroizing::from(E::encode_string(self.buf.as_ref()));
b64.serialize(serializer)
}
}

impl<'de, E: Encoding> Deserialize<'de> for Bytes<Vec<u8>, E> {
impl<'de, E: Encoding> Deserialize<'de> for B64Bytes<Vec<u8>, E> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let enc = crate::Zeroizing::from(String::deserialize(deserializer)?);
let dec = E::decode_vec(&enc).map_err(|_| D::Error::custom("invalid base64"))?;
Expand All @@ -131,15 +134,15 @@ impl<'de, E: Encoding> Deserialize<'de> for Bytes<Vec<u8>, E> {
}
}

impl<'de, E: Encoding> Deserialize<'de> for Bytes<Box<[u8]>, E> {
impl<'de, E: Encoding> Deserialize<'de> for B64Bytes<Box<[u8]>, E> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Bytes::<Vec<u8>, E>::deserialize(deserializer).map(|x| x.buf.into_boxed_slice().into())
B64Bytes::<Vec<u8>, 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<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let bytes = Bytes::<Vec<u8>, E>::deserialize(deserializer)?;
let bytes = B64Bytes::<Vec<u8>, E>::deserialize(deserializer)?;
let array = <[u8; N]>::try_from(bytes.buf);

Ok(array
Expand Down
37 changes: 19 additions & 18 deletions jose-b64/src/serde/json.rs → jose-b64/src/wrapper_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<B, E>: Serialize"))]
#[serde(bound(serialize = "B64Bytes<B, E>: Serialize"))]
#[serde(transparent)]
pub struct Json<T, B = Box<[u8]>, E = Base64UrlUnpadded> {
buf: Bytes<B, E>,
buf: B64Bytes<B, E>,

#[serde(skip_serializing)]
val: T,
Expand All @@ -55,14 +56,14 @@ impl<T, B: AsRef<[u8]>, E> AsRef<[u8]> for Json<T, B, E> {
}
}

impl<T, B, E> TryFrom<Bytes<B, E>> for Json<T, B, E>
impl<T, B, E> TryFrom<B64Bytes<B, E>> for Json<T, B, E>
where
Bytes<B, E>: AsRef<[u8]>,
B64Bytes<B, E>: AsRef<[u8]>,
T: DeserializeOwned,
{
type Error = serde_json::Error;

fn try_from(buf: Bytes<B, E>) -> Result<Self, Self::Error> {
fn try_from(buf: B64Bytes<B, E>) -> Result<Self, Self::Error> {
Ok(Self {
val: serde_json::from_slice(buf.as_ref())?,
buf,
Expand All @@ -72,7 +73,7 @@ where

impl<T, B, E> Json<T, B, E>
where
Bytes<B, E>: From<Vec<u8>>,
B64Bytes<B, E>: From<Vec<u8>>,
T: Serialize,
{
/// Creates a new instance by serializing the input to JSON.
Expand All @@ -89,27 +90,27 @@ where

impl<T, B, E: Encoding> FromStr for Json<T, B, E>
where
Bytes<B, E>: FromStr<Err = Error<Infallible>>,
Bytes<B, E>: AsRef<[u8]>,
B64Bytes<B, E>: FromStr<Err = Error<Infallible>>,
B64Bytes<B, E>: AsRef<[u8]>,
T: DeserializeOwned,
{
type Err = Error<serde_json::Error>;

fn from_str(s: &str) -> Result<Self, Self::Err> {
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<T, B, E>
where
Bytes<B, E>: Deserialize<'de>,
Bytes<B, E>: AsRef<[u8]>,
B64Bytes<B, E>: Deserialize<'de>,
B64Bytes<B, E>: AsRef<[u8]>,
T: DeserializeOwned,
E: Encoding,
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
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,
})
Expand Down
Loading