Skip to content

Commit

Permalink
feat: nostd, core+alloc support
Browse files Browse the repository at this point in the history
* No-std support

* Fix tests

* Cleanly error out when building without the alloc feature

* Run no-std tests on arm-linux-gnu target

* Fix nostd tests

* Attempt 2 at fixing nostd tests

* Fix warnings when running tests in nostd mode

* fixup! No-std support
  • Loading branch information
roblabla committed Aug 7, 2020
1 parent 4274e83 commit e815294
Show file tree
Hide file tree
Showing 14 changed files with 171 additions and 70 deletions.
22 changes: 18 additions & 4 deletions .github/workflows/ci.yml
Expand Up @@ -11,7 +11,7 @@ env:

jobs:
build:
name: ${{ matrix.name }}
name: ${{ matrix.rust }} - ${{ matrix.target }}
runs-on: ubuntu-latest

# The build matrix does not yet support 'allow failures' at job level.
Expand All @@ -22,9 +22,19 @@ jobs:
- 1.36.0
- stable
- nightly
target:
- x86_64-unknown-linux-gnu
- thumbv7m-none-eabi
include:
- rust: nightly
target: x86_64-unknown-linux-gnu
args: --all-features
- target: thumbv7m-none-eabi
test-target: arm-unknown-linux-gnueabi
args: --no-default-features --features=alloc
exclude:
- rust: 1.36.0
target: thumbv7m-none-eabi

steps:
- name: Checkout
Expand All @@ -34,24 +44,28 @@ jobs:
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust || 'stable' }}
target: ${{ matrix.target }}
profile: minimal
override: true

- name: Build
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --verbose ${{ matrix.args }}
args: --target ${{ matrix.target }} --verbose ${{ matrix.args }}

- name: Test
uses: actions-rs/cargo@v1
with:
use-cross: true
command: test
args: --verbose ${{ matrix.args }}
args: --target ${{ matrix.test-target || matrix.target }} --verbose ${{ matrix.args }}

- name: Bench
uses: actions-rs/cargo@v1
with:
use-cross: true
command: bench
args: --verbose --no-run ${{ matrix.args }}
args: --target ${{ matrix.test-target || matrix.target }} --verbose --no-run ${{ matrix.args }}
if: matrix.rust == 'nightly'
33 changes: 17 additions & 16 deletions Cargo.toml
Expand Up @@ -12,19 +12,17 @@ categories = ["cryptography"]
readme = "README.md"

[dependencies]
num-bigint = { version = "0.6", features = ["rand", "i128", "u64_digit", "prime", "zeroize"], package = "num-bigint-dig" }
num-traits = "0.2.6"
num-integer = "0.1.39"
num-iter = "0.1.37"
lazy_static = "1.3.0"
rand = "0.7.0"
byteorder = "1.3.1"
thiserror = "1.0.11"
subtle = "2.0.0"
simple_asn1 = "0.4"
num-bigint = { version = "0.6", features = ["i128", "u64_digit", "prime", "zeroize"], default-features = false, package = "num-bigint-dig" }
num-traits = { version= "0.2.9", default-features = false, features = ["libm"] }
num-integer = { version = "0.1.39", default-features = false }
num-iter = { version = "0.1.37", default-features = false }
lazy_static = { version = "1.3.0", features = ["spin_no_std"] }
rand = { version = "0.7.0", default-features = false }
byteorder = { version = "1.3.1", default-features = false }
subtle = { version = "2.0.0", default-features = false }
simple_asn1 = { version = "0.4", optional = true }
pem = { version = "0.8", optional = true }
digest = { version = "0.9.0", features = ["std"] }
sha2 = "0.9.0"
digest = { version = "0.9.0", default-features = false }

[dependencies.zeroize]
version = "1.1.0"
Expand All @@ -35,16 +33,17 @@ package = "serde"
optional = true
version = "1.0.89"
default-features = false
features = ["std", "derive"]
features = ["derive"]

[dev-dependencies]
base64 = "0.12.0"
hex = "0.4.0"
serde_test = "1.0.89"
rand_xorshift = "0.2.0"
pem = "0.8"
sha-1 = "0.9.0"
sha3 = "0.9.0"
sha-1 = { default-features = false, version = "0.9.0" }
sha2 = { default-features = false, version = "0.9.0" }
sha3 = { default-features = false, version = "0.9.0" }

[[bench]]
name = "key"
Expand All @@ -56,8 +55,10 @@ name = "key"
# debug = true

[features]
default = ["pem"]
default = ["std", "pem"]
nightly = ["subtle/nightly", "num-bigint/nightly"]
serde = ["num-bigint/serde", "serde_crate"]
serde1 = ["serde"] # deprecated
expose-internals = []
std = ["alloc", "simple_asn1", "digest/std", "rand/std"]
alloc = ["digest/alloc"]
5 changes: 4 additions & 1 deletion src/algorithms.rs
Expand Up @@ -2,7 +2,10 @@ use digest::DynDigest;
use num_bigint::traits::ModInverse;
use num_bigint::{BigUint, RandPrime};
use num_traits::{FromPrimitive, One, Zero};
#[allow(unused_imports)]
use num_traits::Float;
use rand::Rng;
use alloc::vec;

use crate::errors::{Error, Result};
use crate::key::RSAPrivateKey;
Expand Down Expand Up @@ -143,7 +146,7 @@ pub fn mgf1_xor(out: &mut [u8], digest: &mut dyn DynDigest, seed: &[u8]) {
let mut counter = [0u8; 4];
let mut i = 0;

const MAX_LEN: u64 = std::u32::MAX as u64 + 1;
const MAX_LEN: u64 = core::u32::MAX as u64 + 1;
assert!(out.len() as u64 <= MAX_LEN);

while i < out.len() {
Expand Down
2 changes: 2 additions & 0 deletions src/encode.rs
Expand Up @@ -8,6 +8,8 @@ use num_bigint::{BigUint, ToBigInt};
use num_traits::Zero;
use pem::{EncodeConfig, LineEnding};
use simple_asn1::{to_der, ASN1Block};
use std::prelude::v1::*;
use std::{vec, format};

const DEFAULT_ENCODING_CONFIG: EncodeConfig = EncodeConfig {
line_ending: LineEnding::LF,
Expand Down
49 changes: 29 additions & 20 deletions src/errors.rs
@@ -1,42 +1,51 @@
use thiserror::Error;
use alloc::string::String;

pub type Result<T> = ::std::result::Result<T, Error>;
pub type Result<T> = core::result::Result<T, Error>;

/// Error types
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
#[error("invalid padding scheme")]
InvalidPaddingScheme,
#[error("decryption error")]
Decryption,
#[error("verification error")]
Verification,
#[error("message too long")]
MessageTooLong,
#[error("input must be hashed")]
InputNotHashed,
#[error("nprimes must be >= 2")]
NprimesTooSmall,
#[error("too few primes of given length to generate an RSA key")]
TooFewPrimes,
#[error("invalid prime value")]
InvalidPrime,
#[error("invalid modulus")]
InvalidModulus,
#[error("invalid exponent")]
InvalidExponent,
#[error("invalid coefficient")]
InvalidCoefficient,
#[error("public exponent too small")]
PublicExponentTooSmall,
#[error("public exponent too large")]
PublicExponentTooLarge,
#[error("parse error: {}", reason)]
ParseError { reason: String },
#[error("encoding error: {}", reason)]
EncodeError { reason: String },
#[error("internal error")]
Internal,
#[error("label too long")]
LabelTooLong,
}

#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
Error::InvalidPaddingScheme => write!(f, "invalid padding scheme"),
Error::Decryption => write!(f, "decryption error"),
Error::Verification => write!(f, "verification error"),
Error::MessageTooLong => write!(f, "message too long"),
Error::InputNotHashed => write!(f, "input must be hashed"),
Error::NprimesTooSmall => write!(f, "nprimes must be >= 2"),
Error::TooFewPrimes => write!(f, "too few primes of given length to generate an RSA key"),
Error::InvalidPrime => write!(f, "invalid prime value"),
Error::InvalidModulus => write!(f, "invalid modulus"),
Error::InvalidExponent => write!(f, "invalid exponent"),
Error::InvalidCoefficient => write!(f, "invalid coefficient"),
Error::PublicExponentTooSmall => write!(f, "public exponent too small"),
Error::PublicExponentTooLarge => write!(f, "public exponent too large"),
Error::ParseError { reason } => write!(f, "parse error: {}", reason),
Error::EncodeError { reason } => write!(f, "encoding error: {}", reason),
Error::Internal => write!(f, "internal error"),
Error::LabelTooLong => write!(f, "label too long"),
}
}
}
4 changes: 3 additions & 1 deletion src/internals.rs
@@ -1,8 +1,10 @@
use num_bigint::{BigInt, BigUint, IntoBigInt, IntoBigUint, ModInverse, RandBigInt, ToBigInt};
use num_traits::{One, Signed, Zero};
use rand::Rng;
use std::borrow::Cow;
use alloc::borrow::Cow;
use zeroize::Zeroize;
use alloc::vec::Vec;
use alloc::vec;

use crate::errors::{Error, Result};
use crate::key::{PublicKeyParts, RSAPrivateKey};
Expand Down
40 changes: 26 additions & 14 deletions src/key.rs
Expand Up @@ -2,11 +2,12 @@ use num_bigint::traits::ModInverse;
use num_bigint::Sign::Plus;
use num_bigint::{BigInt, BigUint};
use num_traits::{FromPrimitive, One};
use rand::{rngs::ThreadRng, Rng};
use rand::{rngs::StdRng, Rng};
#[cfg(feature = "serde")]
use serde_crate::{Deserialize, Serialize};
use std::ops::Deref;
use core::ops::Deref;
use zeroize::Zeroize;
use alloc::vec::Vec;

use crate::algorithms::{generate_multi_prime_key, generate_multi_prime_key_with_exp};
use crate::errors::{Error, Result};
Expand Down Expand Up @@ -247,6 +248,7 @@ impl RSAPublicKey {
/// let der_bytes = base64::decode(&der_encoded).expect("failed to decode base64 content");
/// let public_key = RSAPublicKey::from_pkcs1(&der_bytes).expect("failed to parse key");
/// ```
#[cfg(feature = "std")]
pub fn from_pkcs1(der: &[u8]) -> Result<RSAPublicKey> {
crate::parse::parse_public_key_pkcs1(der)
}
Expand Down Expand Up @@ -281,6 +283,7 @@ impl RSAPublicKey {
/// let der_bytes = base64::decode(&der_encoded).expect("failed to decode base64 content");
/// let public_key = RSAPublicKey::from_pkcs8(&der_bytes).expect("failed to parse key");
/// ```
#[cfg(feature = "std")]
pub fn from_pkcs8(der: &[u8]) -> Result<RSAPublicKey> {
crate::parse::parse_public_key_pkcs8(der)
}
Expand Down Expand Up @@ -405,6 +408,7 @@ impl RSAPrivateKey {
/// let der_bytes = base64::decode(&der_encoded).expect("failed to decode base64 content");
/// let private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key");
/// ```
#[cfg(feature = "std")]
pub fn from_pkcs1(der: &[u8]) -> Result<RSAPrivateKey> {
crate::parse::parse_private_key_pkcs1(der)
}
Expand Down Expand Up @@ -445,6 +449,7 @@ impl RSAPrivateKey {
/// let der_bytes = base64::decode(&der_encoded).expect("failed to decode base64 content");
/// let private_key = RSAPrivateKey::from_pkcs8(&der_bytes).expect("failed to parse key");
/// ```
#[cfg(feature = "std")]
pub fn from_pkcs8(der: &[u8]) -> Result<RSAPrivateKey> {
crate::parse::parse_private_key_pkcs8(der)
}
Expand Down Expand Up @@ -554,10 +559,10 @@ impl RSAPrivateKey {
match padding {
// need to pass any Rng as the type arg, so the type checker is happy, it is not actually used for anything
PaddingScheme::PKCS1v15Encrypt => {
pkcs1v15::decrypt::<ThreadRng, _>(None, self, ciphertext)
pkcs1v15::decrypt::<StdRng, _>(None, self, ciphertext)
}
PaddingScheme::OAEP { mut digest, label } => {
oaep::decrypt::<ThreadRng, _>(None, self, ciphertext, &mut *digest, label)
oaep::decrypt::<StdRng, _>(None, self, ciphertext, &mut *digest, label)
}
_ => Err(Error::InvalidPaddingScheme),
}
Expand All @@ -584,14 +589,15 @@ impl RSAPrivateKey {
/// Sign the given digest.
pub fn sign(&self, padding: PaddingScheme, digest_in: &[u8]) -> Result<Vec<u8>> {
match padding {
// need to pass any Rng as the type arg, so the type checker is happy, it is not actually used for anything
PaddingScheme::PKCS1v15Sign { ref hash } => {
pkcs1v15::sign::<ThreadRng, _>(None, self, hash.as_ref(), digest_in)
pkcs1v15::sign::<StdRng, _>(None, self, hash.as_ref(), digest_in)
}
PaddingScheme::PSS {
mut salt_rng,
mut digest,
salt_len,
} => pss::sign::<_, ThreadRng, _>(
} => pss::sign::<_, StdRng, _>(
&mut *salt_rng,
None,
self,
Expand Down Expand Up @@ -652,9 +658,10 @@ mod tests {
use super::*;
use crate::internals;

use std::time::SystemTime;
use digest::{Digest, DynDigest};
use num_traits::{FromPrimitive, ToPrimitive};
use rand::{distributions::Alphanumeric, rngs::ThreadRng, thread_rng};
use rand::{distributions::Alphanumeric, rngs::StdRng, SeedableRng};
use sha1::Sha1;
use sha2::{Sha224, Sha256, Sha384, Sha512};
use sha3::{Sha3_256, Sha3_384, Sha3_512};
Expand Down Expand Up @@ -687,10 +694,11 @@ mod tests {
let pub_key: RSAPublicKey = private_key.clone().into();
let m = BigUint::from_u64(42).expect("invalid 42");
let c = internals::encrypt(&pub_key, &m);
let m2 = internals::decrypt::<ThreadRng>(None, &private_key, &c)
let m2 = internals::decrypt::<StdRng>(None, &private_key, &c)
.expect("unable to decrypt without blinding");
assert_eq!(m, m2);
let mut rng = thread_rng();
let seed = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
let mut rng = StdRng::seed_from_u64(seed.as_secs());
let m3 = internals::decrypt(Some(&mut rng), &private_key, &c)
.expect("unable to decrypt with blinding");
assert_eq!(m, m3);
Expand All @@ -700,7 +708,8 @@ mod tests {
($name:ident, $multi:expr, $size:expr) => {
#[test]
fn $name() {
let mut rng = thread_rng();
let seed = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
let mut rng = StdRng::seed_from_u64(seed.as_secs());

for _ in 0..10 {
let private_key = if $multi == 2 {
Expand Down Expand Up @@ -730,7 +739,8 @@ mod tests {
#[test]
fn test_impossible_keys() {
// make sure not infinite loops are hit here.
let mut rng = thread_rng();
let seed = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
let mut rng = StdRng::seed_from_u64(seed.as_secs());
for i in 0..32 {
let _ = RSAPrivateKey::new(&mut rng, i).is_err();
let _ = generate_multi_prime_key(&mut rng, 3, i);
Expand Down Expand Up @@ -902,7 +912,8 @@ mod tests {
}

fn do_test_encrypt_decrypt_oaep<D: 'static + Digest + DynDigest>(prk: &RSAPrivateKey) {
let mut rng = thread_rng();
let seed = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
let mut rng = StdRng::seed_from_u64(seed.as_secs());

let k = prk.size();

Expand All @@ -913,7 +924,7 @@ mod tests {
}
let has_label: bool = rng.gen();
let label: Option<String> = if has_label {
Some(rng.sample_iter(&Alphanumeric).take(30).collect())
Some(rng.clone().sample_iter(&Alphanumeric).take(30).collect())
} else {
None
};
Expand Down Expand Up @@ -949,7 +960,8 @@ mod tests {

#[test]
fn test_decrypt_oaep_invalid_hash() {
let mut rng = thread_rng();
let seed = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
let mut rng = StdRng::seed_from_u64(seed.as_secs());
let priv_key = get_private_key();
let pub_key: RSAPublicKey = (&priv_key).into();
let ciphertext = pub_key
Expand Down

0 comments on commit e815294

Please sign in to comment.