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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#![doc = include_str!("../Readme.md")]
#![allow(non_snake_case, non_camel_case_types)]

#[cfg(feature = "evercrypt")]
use evercrypt_cryptolib::*;
#[cfg(not(feature = "evercrypt"))]
use hacspec_cryptolib::*;
use hacspec_lib::*;

use hpke_errors::*;

type CryptoResult = Result<ByteSeq, CryptoError>;

/// ## Key Derivation Functions (KDFs)
///
/// | Value  | KDF         | Nh  | Reference |
/// | :----- | :---------- | --- | :-------- |
/// | 0x0000 | (reserved)  | N/A | N/A       |
/// | 0x0001 | HKDF-SHA256 | 32  | [RFC5869] |
/// | 0x0002 | HKDF-SHA384 | 48  | [RFC5869] |
/// | 0x0003 | HKDF-SHA512 | 64  | [RFC5869] |
///
/// ### KDF Identifiers
///
/// The "HPKE KDF Identifiers" registry lists identifiers for key derivation
/// functions defined for use with HPKE. These identifiers are two-byte values,
/// so the maximum possible value is 0xFFFF = 65535.
///
/// Template:
///
/// * Value: The two-byte identifier for the algorithm
/// * KDF: The name of the algorithm
/// * Nh: The output size of the Extract function in bytes
/// * Reference: Where this algorithm is defined
///
/// [RFC5869]: https://www.rfc-editor.org/info/rfc5869
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum KDF {
    /// 0x0001
    HKDF_SHA256,
    /// 0x0002
    HKDF_SHA384,
    /// 0x0003
    HKDF_SHA512,
}

// pub type Error = u8;
// pub const UNKNOWN_ALGORITHM: Error = 1u8;
// pub const HKDF_INVALID_OUTPUT_LENGTH: Error = 2u8;
// pub const CRYPTO_ERROR: Error = 3u8;

pub type InputKeyMaterial = ByteSeq;
pub type Info = ByteSeq;

/// Get the numeric value of the `kdf_id`.
///
/// See [`KDF`] for details.
pub fn kdf_value(kdf_id: KDF) -> U16 {
    match kdf_id {
        KDF::HKDF_SHA256 => U16(0x0001u16),
        KDF::HKDF_SHA384 => U16(0x0002u16),
        KDF::HKDF_SHA512 => U16(0x0003u16),
    }
}

/// The output size of the `Extract()` function in bytes.
///
/// See [`KDF`] for details.
pub fn Nh(kdf_id: KDF) -> usize {
    match kdf_id {
        KDF::HKDF_SHA256 => 32,
        KDF::HKDF_SHA384 => 48,
        KDF::HKDF_SHA512 => 64,
    }
}

/// The string literal "HPKE-v1" used in [`LabeledExtract()`] and [`LabeledExpand()`]
/// ensures that any secrets derived in HPKE are bound to the scheme's name
/// and version, even when possibly derived from the same Diffie-Hellman or
/// KEM shared secret as in another scheme or version.
fn hpke_version_label() -> ByteSeq {
    byte_seq!(0x48u8, 0x50u8, 0x4bu8, 0x45u8, 0x2du8, 0x76u8, 0x31u8)
}

fn hash_for_kdf(alg: KDF) -> HashAlgorithm {
    match alg {
        KDF::HKDF_SHA256 => HashAlgorithm::SHA256,
        KDF::HKDF_SHA384 => HashAlgorithm::SHA384,
        KDF::HKDF_SHA512 => HashAlgorithm::SHA512,
    }
}

/// LabeledExtract
///
/// ```text
/// def LabeledExtract(salt, label, ikm):
///   labeled_ikm = concat("HPKE-v1", suite_id, label, ikm)
///   return Extract(salt, labeled_ikm)
/// ```
pub fn LabeledExtract(
    alg: KDF,
    suite_id: &ByteSeq,
    salt: &ByteSeq,
    label: &ByteSeq,
    ikm: &InputKeyMaterial,
) -> HpkeByteSeqResult {
    match hkdf_extract(
        hash_for_kdf(alg),
        &hpke_version_label()
            .concat(suite_id)
            .concat(label)
            .concat(ikm),
        salt,
    ) {
        CryptoResult::Ok(prk) => HpkeByteSeqResult::Ok(prk),
        CryptoResult::Err(_) => HpkeByteSeqResult::Err(HpkeError::CryptoError),
    }
}

/// KDF: Labeled Expand
///
/// ```text
/// def LabeledExpand(prk, label, info, L):
///   labeled_info = concat(I2OSP(L, 2), "HPKE-v1", suite_id,
///                         label, info)
///   return Expand(prk, labeled_info, L)
/// ```
pub fn LabeledExpand(
    alg: KDF,
    suite_id: &ByteSeq,
    prk: &ByteSeq,
    label: &ByteSeq,
    info: &Info,
    L: usize,
) -> HpkeByteSeqResult {
    if L > (255 * Nh(alg)) {
        // This check is mentioned explicitly in the spec because because it
        // must be adhered to when exporting secrets.
        // The check comes from HKDF and will be performed there again.
        HpkeByteSeqResult::Err(HpkeError::InvalidParameters)
    } else {
        match hkdf_expand(
            hash_for_kdf(alg),
            prk,
            &U16_to_be_bytes(U16(L as u16))
                .concat(&hpke_version_label())
                .concat(suite_id)
                .concat(label)
                .concat(info),
            L,
        ) {
            CryptoResult::Ok(r) => HpkeByteSeqResult::Ok(r),
            CryptoResult::Err(_) => HpkeByteSeqResult::Err(HpkeError::CryptoError),
        }
    }
}