1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// OPCUA for Rust
// SPDX-License-Identifier: MPL-2.0
// Copyright (C) 2017-2022 Adam Lock

//! Hashing functions used for producing and verifying digital signatures

use std::result::Result;

use openssl::{hash, pkey, sign};

use opcua_types::status_code::StatusCode;

use crate::{SHA1_SIZE, SHA256_SIZE};

/// Pseudo random `P_SHA` implementation for creating pseudo random range of bytes from an input
///
/// https://www.ietf.org/rfc/rfc4346.txt
/// https://tools.ietf.org/html/rfc5246
///
/// P_SHA1(secret, seed) = HMAC_SHA1(secret, A(1) + seed) +
///                        HMAC_SHA1(secret, A(2) + seed) +
///                        HMAC_SHA1(secret, A(3) + seed) + ...
///
/// Where A(n) is defined as:
///   A(0) = seed
///   A(n) = HMAC_SHA1(secret, A(n-1))
/// + indicates that the results are appended to previous results.
pub fn p_sha(
    message_digest: hash::MessageDigest,
    secret: &[u8],
    seed: &[u8],
    length: usize,
) -> Vec<u8> {
    let mut result = Vec::with_capacity(length);

    let mut hmac = Vec::with_capacity(seed.len() * 2);

    let mut a_last = Vec::with_capacity(seed.len());
    a_last.extend_from_slice(seed); // A(0) = seed

    while result.len() < length {
        // A(n) = HMAC_SHA1(secret, A(n-1))
        let a_next = hmac_vec(message_digest, secret, &a_last);

        // Append a slice of random data
        let bytes = {
            hmac.clear();
            hmac.extend(&a_next);
            hmac.extend_from_slice(seed);
            hmac_vec(message_digest, secret, &hmac)
        };
        result.extend(&bytes);

        a_last.clear();
        a_last.extend(&a_next);
    }

    result.truncate(length);
    result
}

fn hmac_vec(digest: hash::MessageDigest, key: &[u8], data: &[u8]) -> Vec<u8> {
    // Compute a signature
    let pkey = pkey::PKey::hmac(key).unwrap();
    let mut signer = sign::Signer::new(digest, &pkey).unwrap();
    signer.update(data).unwrap();
    signer.sign_to_vec().unwrap()
}

fn hmac(
    digest: hash::MessageDigest,
    key: &[u8],
    data: &[u8],
    signature: &mut [u8],
) -> Result<(), StatusCode> {
    let hmac = hmac_vec(digest, key, data);
    trace!("hmac length = {}", hmac.len());
    signature.copy_from_slice(&hmac);
    Ok(())
}

pub fn hmac_sha1(key: &[u8], data: &[u8], signature: &mut [u8]) -> Result<(), StatusCode> {
    if signature.len() == SHA1_SIZE {
        hmac(hash::MessageDigest::sha1(), key, data, signature)
    } else {
        error!(
            "Signature buffer length must be exactly {} bytes to receive hmac_sha1 signature",
            SHA1_SIZE
        );
        Err(StatusCode::BadInvalidArgument)
    }
}

/// Verify that the HMAC for the data block matches the supplied signature
pub fn verify_hmac_sha1(key: &[u8], data: &[u8], signature: &[u8]) -> bool {
    if signature.len() != SHA1_SIZE {
        false
    } else {
        let mut tmp_signature = [0u8; SHA1_SIZE];
        if hmac_sha1(key, data, &mut tmp_signature).is_err() {
            false
        } else {
            trace!("Original signature = {:?}", signature);
            trace!("Calculated signature = {:?}", tmp_signature);
            openssl::memcmp::eq(signature, &tmp_signature[..])
        }
    }
}

pub fn hmac_sha256(key: &[u8], data: &[u8], signature: &mut [u8]) -> Result<(), StatusCode> {
    if signature.len() == SHA256_SIZE {
        hmac(hash::MessageDigest::sha256(), key, data, signature)
    } else {
        error!(
            "Signature buffer length must be exactly {} bytes to receive hmac_sha256 signature",
            SHA256_SIZE
        );
        Err(StatusCode::BadInvalidArgument)
    }
}

/// Verify that the HMAC for the data block matches the supplied signature
pub fn verify_hmac_sha256(key: &[u8], data: &[u8], signature: &[u8]) -> bool {
    if signature.len() != SHA256_SIZE {
        false
    } else {
        let mut tmp_signature = [0u8; SHA256_SIZE];
        if hmac_sha256(key, data, &mut tmp_signature).is_err() {
            false
        } else {
            openssl::memcmp::eq(signature, &tmp_signature[..])
        }
    }
}