hashes.rs

  1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7use std::str::FromStr;
  8
  9use minidom::IntoAttributeValue;
 10
 11use error::Error;
 12
 13use helpers::Base64;
 14use base64;
 15
 16/// List of the algorithms we support, or Unknown.
 17#[allow(non_camel_case_types)]
 18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 19pub enum Algo {
 20    /// The Secure Hash Algorithm 1, with known vulnerabilities, do not use it.
 21    ///
 22    /// See https://tools.ietf.org/html/rfc3174
 23    Sha_1,
 24
 25    /// The Secure Hash Algorithm 2, in its 256-bit version.
 26    ///
 27    /// See https://tools.ietf.org/html/rfc6234
 28    Sha_256,
 29
 30    /// The Secure Hash Algorithm 2, in its 512-bit version.
 31    ///
 32    /// See https://tools.ietf.org/html/rfc6234
 33    Sha_512,
 34
 35    /// The Secure Hash Algorithm 3, based on Keccak, in its 256-bit version.
 36    ///
 37    /// See https://keccak.team/files/Keccak-submission-3.pdf
 38    Sha3_256,
 39
 40    /// The Secure Hash Algorithm 3, based on Keccak, in its 512-bit version.
 41    ///
 42    /// See https://keccak.team/files/Keccak-submission-3.pdf
 43    Sha3_512,
 44
 45    /// The BLAKE2 hash algorithm, for a 256-bit output.
 46    ///
 47    /// See https://tools.ietf.org/html/rfc7693
 48    Blake2b_256,
 49
 50    /// The BLAKE2 hash algorithm, for a 512-bit output.
 51    ///
 52    /// See https://tools.ietf.org/html/rfc7693
 53    Blake2b_512,
 54
 55    /// An unknown hash not in this list, you can probably reject it.
 56    Unknown(String),
 57}
 58
 59impl FromStr for Algo {
 60    type Err = Error;
 61
 62    fn from_str(s: &str) -> Result<Algo, Error> {
 63        Ok(match s {
 64            "" => return Err(Error::ParseError("'algo' argument can’t be empty.")),
 65
 66            "sha-1" => Algo::Sha_1,
 67            "sha-256" => Algo::Sha_256,
 68            "sha-512" => Algo::Sha_512,
 69            "sha3-256" => Algo::Sha3_256,
 70            "sha3-512" => Algo::Sha3_512,
 71            "blake2b-256" => Algo::Blake2b_256,
 72            "blake2b-512" => Algo::Blake2b_512,
 73            value => Algo::Unknown(value.to_owned()),
 74        })
 75    }
 76}
 77
 78impl From<Algo> for String {
 79    fn from(algo: Algo) -> String {
 80        String::from(match algo {
 81            Algo::Sha_1 => "sha-1",
 82            Algo::Sha_256 => "sha-256",
 83            Algo::Sha_512 => "sha-512",
 84            Algo::Sha3_256 => "sha3-256",
 85            Algo::Sha3_512 => "sha3-512",
 86            Algo::Blake2b_256 => "blake2b-256",
 87            Algo::Blake2b_512 => "blake2b-512",
 88            Algo::Unknown(text) => return text,
 89        })
 90    }
 91}
 92
 93impl IntoAttributeValue for Algo {
 94    fn into_attribute_value(self) -> Option<String> {
 95        Some(String::from(self))
 96    }
 97}
 98
 99generate_element!(
100    /// This element represents a hash of some data, defined by the hash
101    /// algorithm used and the computed value.
102    #[derive(PartialEq)]
103    Hash, "hash", HASHES,
104    attributes: [
105        /// The algorithm used to create this hash.
106        algo: Algo = "algo" => required
107    ],
108    text: (
109        /// The hash value, as a vector of bytes.
110        hash: Base64<Vec<u8>>
111    )
112);
113
114impl Hash {
115    /// Creates a [Hash] element with the given algo and data.
116    pub fn new(algo: Algo, hash: Vec<u8>) -> Hash {
117        Hash {
118            algo,
119            hash,
120        }
121    }
122
123    /// Like [new](#method.new) but takes base64-encoded data before decoding
124    /// it.
125    pub fn from_base64(algo: Algo, hash: &str) -> Result<Hash, Error> {
126        Ok(Hash::new(algo, base64::decode(hash)?))
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use try_from::TryFrom;
134    use minidom::Element;
135
136    #[test]
137    fn test_size() {
138        assert_size!(Algo, 32);
139        assert_size!(Hash, 56);
140    }
141
142    #[test]
143    fn test_simple() {
144        let elem: Element = "<hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=</hash>".parse().unwrap();
145        let hash = Hash::try_from(elem).unwrap();
146        assert_eq!(hash.algo, Algo::Sha_256);
147        assert_eq!(hash.hash, base64::decode("2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=").unwrap());
148    }
149
150    #[test]
151    fn test_unknown() {
152        let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0'/>".parse().unwrap();
153        let error = Hash::try_from(elem).unwrap_err();
154        let message = match error {
155            Error::ParseError(string) => string,
156            _ => panic!(),
157        };
158        assert_eq!(message, "This is not a hash element.");
159    }
160
161    #[test]
162    fn test_invalid_child() {
163        let elem: Element = "<hash xmlns='urn:xmpp:hashes:2'><coucou/></hash>".parse().unwrap();
164        let error = Hash::try_from(elem).unwrap_err();
165        let message = match error {
166            Error::ParseError(string) => string,
167            _ => panic!(),
168        };
169        assert_eq!(message, "Unknown child in hash element.");
170    }
171}