Skip to content

Commit

Permalink
Migrate to rust-argon2
Browse files Browse the repository at this point in the history
  • Loading branch information
thespooler committed Apr 13, 2020
1 parent d9b7dc3 commit c84ab4b
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 102 deletions.
2 changes: 2 additions & 0 deletions oxide-auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ autoexamples = false
[dependencies]
base64 = "0.11"
chrono = "0.4.2"
once_cell = "1.3.1"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
ring = ">=0.13,<0.15"
rust-argon2 = "0.8.2"
rmp-serde = "0.14"
url = "1.7"

Expand Down
2 changes: 2 additions & 0 deletions oxide-auth/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,11 @@
//! [`Scopes`]: endpoint/trait.Scopes.html
#![warn(missing_docs)]

extern crate argon2;
extern crate base64;
extern crate chrono;
extern crate url;
extern crate once_cell;
extern crate ring;
extern crate rmp_serde;
extern crate serde;
Expand Down
123 changes: 21 additions & 102 deletions oxide-auth/src/primitives/registrar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@ use std::cmp;
use std::collections::HashMap;
use std::fmt;
use std::iter::{Extend, FromIterator};
use std::num::NonZeroU32;
use std::sync::{Arc, MutexGuard, RwLockWriteGuard};
use std::rc::Rc;

use argon2::{self, Config};
use once_cell::sync::Lazy;
use url::Url;
use ring::{digest, pbkdf2};
use ring::error::Unspecified;
use ring::rand::{SystemRandom, SecureRandom};

/// Registrars provie a way to interact with clients.
///
Expand Down Expand Up @@ -188,12 +186,6 @@ impl fmt::Debug for ClientType {
}
}

impl RegistrarError {
fn from(err: Unspecified) -> Self {
match err { Unspecified => RegistrarError::Unspecified }
}
}

impl Client {
/// Create a public client.
pub fn public(client_id: &str, redirect_uri: Url, default_scope: Scope) -> Client {
Expand Down Expand Up @@ -281,8 +273,7 @@ impl cmp::PartialOrd<Self> for PreGrant {

/// Determines how passphrases are stored and checked.
///
/// The provided library implementation is based on `Pbkdf2`. Other users may prefer to write their
/// own adaption with `Argon2`. If you do so, you could send a pull request my way.
/// The provided library implementation is based on `Argon2`.
pub trait PasswordPolicy: Send + Sync {
/// Transform the passphrase so it can be stored in the confidential client.
fn store(&self, client_id: &str, passphrase: &[u8]) -> Vec<u8>;
Expand All @@ -291,107 +282,35 @@ pub trait PasswordPolicy: Send + Sync {
fn check(&self, client_id: &str, passphrase: &[u8], stored: &[u8]) -> Result<(), RegistrarError>;
}

/// Store passwords using `Pbkdf2` to derive the stored value.
///
/// Each instantiation generates a 16 byte random salt and prepends this additionally with the
/// username. This combined string is then used as the salt using the passphrase as the secret to
/// derive the output. The iteration count defaults to `65536` but can be customized.
pub struct Pbkdf2 {
/// A prebuilt random, or constructing one as needed.
random: Option<SystemRandom>,
iterations: NonZeroU32,
}
/// Store passwords using `Argon2` to derive the stored value.
#[derive(Clone, Debug, Default)]
pub struct Argon2 {}

impl Default for Pbkdf2 {
fn default() -> Self {
Pbkdf2 {
random: Some(SystemRandom::new()),
.. *PBKDF2_DEFAULTS
}
}
}

impl Clone for Pbkdf2 {
fn clone(&self) -> Self {
Pbkdf2 {
random: Some(SystemRandom::new()),
.. *self
}
}
}

impl fmt::Debug for Pbkdf2 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Pbkdf2")
.field("iterations", &self.iterations)
.field("random", &())
.finish()
}
}

impl Pbkdf2 {
/// Set the iteration count to `(1 << strength)`.
///
/// This function will panic when the `strength` is larger or equal to `32`.
pub fn set_relative_strength(&mut self, strength: u8) {
assert!(strength < 32, "Strength value out of range (0-31): {}", strength);
self.iterations = NonZeroU32::new(1u32 << strength).unwrap();
}

fn salt(&self, user_identifier: &[u8]) -> Vec<u8> {
let mut vec = Vec::with_capacity(user_identifier.len() + 64);
let mut rnd_salt = [0; 16];

match self.random.as_ref() {
Some(random) => random.fill(&mut rnd_salt),
None => SystemRandom::new().fill(&mut rnd_salt),
}.expect("Failed to property initialize password storage salt");

vec.extend_from_slice(user_identifier);
vec.extend_from_slice(&rnd_salt[..]);
vec
}
}

// A default instance for pbkdf2, randomness is sampled from the system each time.
//
// TODO: in the future there might be a way to get static memory initialized with an rng at load
// time by the loader. Then, a constant instance of the random generator may be available and we
// could get rid of the `Option`.
static PBKDF2_DEFAULTS: &Pbkdf2 = &Pbkdf2 {
random: None,
iterations: unsafe { NonZeroU32::new_unchecked(1 << 16) },
};

impl PasswordPolicy for Pbkdf2 {
impl PasswordPolicy for Argon2 {
fn store(&self, client_id: &str, passphrase: &[u8]) -> Vec<u8> {
let mut output = vec![0; 64];
output.append(&mut self.salt(client_id.as_bytes()));
{
let (output, salt) = output.split_at_mut(64);
pbkdf2::derive(&digest::SHA256, self.iterations.into(), salt, passphrase,
output);
}
output
let config = Config::default();
let encoded = argon2::hash_encoded(passphrase, client_id.as_bytes(), &config);
encoded.unwrap().as_bytes().to_vec()
}

fn check(&self, _client_id: &str /* Was interned */, passphrase: &[u8], stored: &[u8])
-> Result<(), RegistrarError>
{
if stored.len() < 64 {
return Err(RegistrarError::PrimitiveError)
}

let (verifier, salt) = stored.split_at(64);
pbkdf2::verify(&digest::SHA256, self.iterations.into(), salt, passphrase, verifier)
.map_err(RegistrarError::from)
String::from_utf8(stored.to_vec())
.map_err(|_| RegistrarError::PrimitiveError)
.and_then(|hash|
argon2::verify_encoded(&hash, passphrase)
.map_err(|_| RegistrarError::Unspecified ))
.and_then(|valid| if valid { Ok(()) } else { Err(RegistrarError::Unspecified) } )
}
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Standard Implementations of Registrars //
///////////////////////////////////////////////////////////////////////////////////////////////////

static DEFAULT_PASSWORD_POLICY: Lazy<Argon2> = Lazy::new(|| { Argon2::default() });

impl ClientMap {
/// Create an empty map without any clients in it.
pub fn new() -> ClientMap {
Expand All @@ -413,7 +332,7 @@ impl ClientMap {
fn current_policy<'a>(policy: &'a Option<Box<dyn PasswordPolicy>>) -> &'a dyn PasswordPolicy {
policy
.as_ref().map(|boxed| &**boxed)
.unwrap_or(PBKDF2_DEFAULTS)
.unwrap_or(&*DEFAULT_PASSWORD_POLICY)
}
}

Expand Down Expand Up @@ -616,7 +535,7 @@ mod tests {

#[test]
fn public_client() {
let policy = Pbkdf2::default();
let policy = Argon2::default();
let client = Client::public(
"ClientId",
"https://example.com".parse().unwrap(),
Expand All @@ -632,7 +551,7 @@ mod tests {

#[test]
fn confidential_client() {
let policy = Pbkdf2::default();
let policy = Argon2::default();
let pass = b"AB3fAj6GJpdxmEVeNCyPoA==";
let client = Client::confidential(
"ClientId",
Expand Down

0 comments on commit c84ab4b

Please sign in to comment.