azure_core/
hmac.rs

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3
4//! HMAC encoding and decoding functions.
5
6use crate::credentials::Secret;
7#[cfg(any(feature = "hmac_rust", feature = "hmac_openssl"))]
8use crate::{
9    base64,
10    error::{ErrorKind, ResultExt},
11};
12
13/// Tries to create an HMAC SHA256 signature from the given `data` and `key`.
14///
15/// The `key` is expected to be a base64 encoded string and will be decoded
16/// before using it for signing. The returned signature is also base64 encoded.
17///
18/// If both `hmac_rust` and `hmac_openssl` are enabled, use `hmac_openssl`.
19///
20/// # Errors
21///
22/// - If the `key` is not a valid base64 encoded string.
23/// - If it fails to create the HMAC from the `key`.
24#[cfg(all(feature = "hmac_rust", not(feature = "hmac_openssl")))]
25pub fn hmac_sha256(data: &str, key: &Secret) -> crate::Result<String> {
26    use hmac::{Hmac, Mac};
27    use sha2::Sha256;
28    let key = base64::decode(key.secret())?;
29    let mut hmac = Hmac::<Sha256>::new_from_slice(&key)
30        .with_context_fn(ErrorKind::DataConversion, || {
31            "failed to create hmac from key"
32        })?;
33    hmac.update(data.as_bytes());
34    let signature = hmac.finalize().into_bytes();
35    Ok(base64::encode(signature))
36}
37
38/// Tries to create an HMAC SHA256 signature from the given `data` and `key`.
39///
40/// The `key` is expected to be a base64 encoded string and will be decoded
41/// before using it for signing. The returned signature is also base64 encoded.
42///
43/// If both `hmac_rust` and `hmac_openssl` are enabled, use `hmac_openssl`.
44///
45/// # Errors
46///
47/// - If the `key` is not a valid base64 encoded string.
48/// - If it fails to create the HMAC from the `key`.
49#[cfg(feature = "hmac_openssl")]
50pub fn hmac_sha256(data: &str, key: &Secret) -> crate::Result<String> {
51    // cspell:ignore pkey
52    use openssl::{error::ErrorStack, hash::MessageDigest, pkey::PKey, sign::Signer};
53
54    let decoded = base64::decode(key.secret())?;
55    let signature = || -> Result<Vec<u8>, ErrorStack> {
56        let pkey = PKey::hmac(&decoded)?;
57        let mut signer = Signer::new(MessageDigest::sha256(), &pkey)?;
58        signer.update(data.as_bytes())?;
59        signer.sign_to_vec()
60    }()
61    .with_context_fn(ErrorKind::DataConversion, || {
62        "failed to create hmac from key"
63    })?;
64    Ok(base64::encode(signature))
65}
66
67/// Tries to create an HMAC SHA256 signature from the given `data` and `key`.
68///
69/// # Errors
70///
71/// This implementation always returns an error. Enable `hmac_rust` and/or `hmac_openssl`.
72#[cfg(not(any(feature = "hmac_rust", feature = "hmac_openssl")))]
73pub fn hmac_sha256(_data: &str, _key: &Secret) -> crate::Result<String> {
74    unimplemented!("An HMAC signing request was called without an hmac implementation. Make sure to enable either the `hmac_rust` or `hmac_openssl` feature");
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[allow(dead_code)]
82    #[cfg_attr(any(feature = "hmac_rust", feature = "hmac_openssl"), test)]
83    fn test_hmac_sign() {
84        let data = "create hmac signature for data";
85        let key = Secret::new("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
86
87        let sig = hmac_sha256(data, &key).unwrap();
88
89        let expected_sig = "D/y9XyIEdUzEbdV570h8dou/mfkbMA1lKCOPqPDPAd0=";
90        assert_eq!(sig, expected_sig);
91    }
92}