support SCRAM-SHA-256

lumi created

Change summary

src/sasl/mechanisms/mod.rs   |  2 
src/sasl/mechanisms/scram.rs | 42 +++++++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+), 1 deletion(-)

Detailed changes

src/sasl/mechanisms/mod.rs đź”—

@@ -6,4 +6,4 @@ mod scram;
 
 pub use self::anonymous::Anonymous;
 pub use self::plain::Plain;
-pub use self::scram::{Scram, Sha1, ScramProvider};
+pub use self::scram::{Scram, Sha1, Sha256, ScramProvider};

src/sasl/mechanisms/scram.rs đź”—

@@ -69,6 +69,29 @@ impl ScramProvider for Sha1 { // TODO: look at all these unwraps
     }
 }
 
+pub struct Sha256;
+
+impl ScramProvider for Sha256 { // TODO: look at all these unwraps
+    fn name() -> &'static str { "SCRAM-SHA-256" }
+
+    fn hash(data: &[u8]) -> Vec<u8> {
+        hash(MessageDigest::sha256(), data).unwrap()
+    }
+
+    fn hmac(data: &[u8], key: &[u8]) -> Vec<u8> {
+        let pkey = PKey::hmac(key).unwrap();
+        let mut signer = Signer::new(MessageDigest::sha256(), &pkey).unwrap();
+        signer.update(data).unwrap();
+        signer.finish().unwrap()
+    }
+
+    fn derive(data: &[u8], salt: &[u8], iterations: usize) -> Vec<u8> {
+        let mut result = vec![0; 32];
+        pbkdf2_hmac(data, salt, iterations, MessageDigest::sha256(), &mut result).unwrap();
+        result
+    }
+}
+
 enum ScramState {
     Init,
     SentInitialMessage { initial_message: Vec<u8> },
@@ -251,4 +274,23 @@ mod tests {
                   , String::from_utf8(client_final[..].to_owned()).unwrap() ); // again, depends on ordering…
         mechanism.success(&server_final[..]).unwrap();
     }
+
+    #[test]
+    fn scram_sha256_works() { // Source: RFC 7677
+        let username = "user";
+        let password = "pencil";
+        let client_nonce = "rOprNGfwEbeRWgbNEkqO";
+        let client_init = b"n,,n=user,r=rOprNGfwEbeRWgbNEkqO";
+        let server_init = b"r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096";
+        let client_final = b"c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=";
+        let server_final = b"v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=";
+        let mut mechanism = Scram::<Sha256>::new_with_nonce(username, password, client_nonce.to_owned());
+        let init = mechanism.initial().unwrap();
+        assert_eq!( String::from_utf8(init.clone()).unwrap()
+                  , String::from_utf8(client_init[..].to_owned()).unwrap() ); // depends on ordering…
+        let resp = mechanism.response(&server_init[..]).unwrap();
+        assert_eq!( String::from_utf8(resp.clone()).unwrap()
+                  , String::from_utf8(client_final[..].to_owned()).unwrap() ); // again, depends on ordering…
+        mechanism.success(&server_final[..]).unwrap();
+    }
 }