Coverage Report

Created: 2026-09-14 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibCrypto/Hash/HKDF.h
Line
Count
Source
1
/*
2
 * Copyright (c) 2023, stelar7 <dudedbz@gmail.com>
3
 * Copyright (c) 2024, Ben Wiederhake <BenWiederhake.GitHub@gmx.de>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#pragma once
9
10
#include <LibCrypto/Authentication/HMAC.h>
11
12
namespace Crypto::Hash {
13
14
// https://www.rfc-editor.org/rfc/rfc5869#section-2
15
template<typename HashT>
16
class HKDF {
17
public:
18
    using HashType = HashT;
19
    using DigestType = typename HashType::DigestType;
20
    using HMACType = typename Crypto::Authentication::HMAC<HashType>;
21
22
    // Note: The output is different for a salt of length zero and an absent salt,
23
    // so Optional<ReadonlyBytes> really is the correct type.
24
    static ErrorOr<ByteBuffer> derive_key(Optional<ReadonlyBytes> maybe_salt, ReadonlyBytes input_keying_material, ReadonlyBytes info, u32 output_key_length)
25
0
    {
26
0
        if (output_key_length > 255 * DigestType::Size) {
27
0
            return Error::from_string_view("requested output_key_length is too large"sv);
28
0
        }
29
        // Note that it feels like we should also refuse to run with output_key_length == 0,
30
        // but the spec allows this.
31
32
        // https://www.rfc-editor.org/rfc/rfc5869#section-2.1
33
        // Note that in the extract step, 'IKM' is used as the HMAC input, not as the HMAC key.
34
35
        // salt: optional salt value (a non-secret random value); if not provided, it is set to a string of HashLen zeros.
36
0
        ByteBuffer salt_buffer;
37
0
        auto salt = maybe_salt.value_or_lazy_evaluated([&] {
38
0
            salt_buffer.resize(DigestType::Size, ByteBuffer::ZeroFillNewElements::Yes);
39
0
            return salt_buffer.bytes();
40
0
        });
Unexecuted instantiation: Crypto::Hash::HKDF<Crypto::Hash::SHA1>::derive_key(AK::Optional<AK::Span<unsigned char const> >, AK::Span<unsigned char const>, AK::Span<unsigned char const>, unsigned int)::{lambda()#1}::operator()() const
Unexecuted instantiation: Crypto::Hash::HKDF<Crypto::Hash::SHA256>::derive_key(AK::Optional<AK::Span<unsigned char const> >, AK::Span<unsigned char const>, AK::Span<unsigned char const>, unsigned int)::{lambda()#1}::operator()() const
Unexecuted instantiation: Crypto::Hash::HKDF<Crypto::Hash::SHA384>::derive_key(AK::Optional<AK::Span<unsigned char const> >, AK::Span<unsigned char const>, AK::Span<unsigned char const>, unsigned int)::{lambda()#1}::operator()() const
Unexecuted instantiation: Crypto::Hash::HKDF<Crypto::Hash::SHA512>::derive_key(AK::Optional<AK::Span<unsigned char const> >, AK::Span<unsigned char const>, AK::Span<unsigned char const>, unsigned int)::{lambda()#1}::operator()() const
41
0
        HMACType hmac_salt(salt);
42
43
        // https://www.rfc-editor.org/rfc/rfc5869#section-2.2
44
        // PRK = HMAC-Hash(salt, IKM)
45
0
        auto prk_digest = hmac_salt.process(input_keying_material);
46
0
        auto prk = prk_digest.bytes();
47
0
        VERIFY(prk.size() == DigestType::Size);
48
49
        // https://www.rfc-editor.org/rfc/rfc5869#section-2.3
50
        // N = ceil(L/HashLen)
51
0
        auto num_iterations = ceil_div(static_cast<size_t>(output_key_length), DigestType::Size);
52
        // T = T(1) | T(2) | T(3) | ... | T(N)
53
0
        ByteBuffer output_buffer;
54
        // where:
55
        // T(0) = empty string (zero length)
56
        // T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
57
        // T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
58
        // T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
59
0
        HMACType hmac_prk(prk);
60
        // In iteration i we compute T(i), and deduce T(i - 1) from 'output_buffer'.
61
        // Hence, we do not need to run i == 0.
62
        // INVARIANT: At the beginning of each iteration, hmac_prk is freshly reset.
63
        // For the first iteration, this is given by the constructor of HMAC.
64
0
        for (size_t i = 1; i < 1 + num_iterations; ++i) {
65
0
            if (i > 1) {
66
0
                auto t_i_minus_one = output_buffer.bytes().slice_from_end(DigestType::Size);
67
0
                hmac_prk.update(t_i_minus_one);
68
0
            }
69
0
            hmac_prk.update(info);
70
0
            u8 const pad_byte = static_cast<u8>(i & 0xff);
71
0
            hmac_prk.update(ReadonlyBytes(&pad_byte, 1));
72
0
            auto t_i_digest = hmac_prk.digest();
73
0
            output_buffer.append(t_i_digest.bytes());
74
0
        }
75
76
        // OKM = first L octets of T
77
0
        VERIFY(output_buffer.size() >= output_key_length);
78
0
        output_buffer.trim(output_key_length, false);
79
80
        // 5. Output the derived key DK
81
0
        return { output_buffer };
82
0
    }
Unexecuted instantiation: Crypto::Hash::HKDF<Crypto::Hash::SHA1>::derive_key(AK::Optional<AK::Span<unsigned char const> >, AK::Span<unsigned char const>, AK::Span<unsigned char const>, unsigned int)
Unexecuted instantiation: Crypto::Hash::HKDF<Crypto::Hash::SHA256>::derive_key(AK::Optional<AK::Span<unsigned char const> >, AK::Span<unsigned char const>, AK::Span<unsigned char const>, unsigned int)
Unexecuted instantiation: Crypto::Hash::HKDF<Crypto::Hash::SHA384>::derive_key(AK::Optional<AK::Span<unsigned char const> >, AK::Span<unsigned char const>, AK::Span<unsigned char const>, unsigned int)
Unexecuted instantiation: Crypto::Hash::HKDF<Crypto::Hash::SHA512>::derive_key(AK::Optional<AK::Span<unsigned char const> >, AK::Span<unsigned char const>, AK::Span<unsigned char const>, unsigned int)
83
84
private:
85
    HKDF() = delete;
86
};
87
88
}