File: data_epoch.hpp

package info (click to toggle)
openvpn3-client 25%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 19,276 kB
  • sloc: cpp: 190,085; python: 7,218; ansic: 1,866; sh: 1,361; java: 402; lisp: 81; makefile: 17
file content (231 lines) | stat: -rw-r--r-- 7,165 bytes parent folder | download
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012- OpenVPN Inc.
//
//    SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
//


#ifndef CRYPTO_DATA_EPOCH_H
#define CRYPTO_DATA_EPOCH_H

#include <cstdint>
#include <cstdio>
#include <array>

#include "openvpn/crypto/static_key.hpp"
#include "openvpn/crypto/cryptoalgs.hpp"
#include "openvpn/crypto/cryptochoose.hpp"
#include "openvpn/crypto/packet_id_data.hpp"
#include "openvpn/crypto/aead_usage_limit.hpp"


namespace openvpn {
/**
 * Implementation of the RFC5869 HKDF-Expand function with the following
 * restrictions
 *  - salt is always assumed to be zero length (ie not supported)
 *  - IKM (secret) is assumed to be always 32 bytes
 *  - HASH is always SHA256
 *
 *  @param secret   the input keying material (HMAC key)
 *  @param info     context and application specific information
 *  @param info_len length of the application specific information
 *  @param out      output keying material
 *  @param out_len  length of output keying material
 */
void ovpn_hkdf_expand(const uint8_t *secret,
                      const uint8_t *info,
                      int info_len,
                      uint8_t *out,
                      int out_len);

/**
 * Variant of the RFC 8446 TLS 1.3  HKDF-Expand-Label function with the
 * following differences/restrictions:
 *  - secret must 32 bytes in length
 *  - label prefix is "ovpn " instead of "tls13 "
 *  - HASH is always SHA256
 *
 * @param secret        Input secret
 * @param secret_len    length of the input secret
 * @param label         Label for the exported key material
 * @param label_len     length of the label
 * @param context       optional context
 * @param context_len   length of the context
 * @param out      output keying material
 * @param out_len  length of output keying material
 *
 * Note, this function accepts size_t parameter only to make using this function easier. All values must be
 * uin16_t or smaller.
 */
void ovpn_expand_label(const uint8_t *secret,
                       size_t secret_len,
                       const uint8_t *label,
                       size_t label_len,
                       const uint8_t *context,
                       size_t context_len,
                       uint8_t *out,
                       size_t out_len);

OPENVPN_EXCEPTION(epoch_key_exception);

struct EpochDataChannelCryptoContext
{
    /** The IV size in bytes. Since all currently supported AEAD ciphers uses 96 bits, we hardcode it for now */
    constexpr static int IV_SIZE = 12;

    std::uint16_t epoch = 0;
    openvpn::SSLLib::CryptoAPI::CipherContextAEAD cipher;
    std::array<uint8_t, IV_SIZE> implicit_iv{};

    /* will calculate the ID from the packet id and the implicit IV and store the result in
     * the iv_dest parameter */
    void calculate_iv(uint8_t *packet_id, std::array<uint8_t, IV_SIZE> &iv_dest);
};


struct EpochDataChannelEncryptContext : public EpochDataChannelCryptoContext
{
    openvpn::PacketIDDataSend pid{true, 0};
};

struct EpochDataChannelDecryptContext : public EpochDataChannelCryptoContext
{
    openvpn::PacketIDDataReceive pid;
};

class EpochKey
{
  public:
    /* SHA256 digest size */
    constexpr static int SECRET_SIZE = 32;

    std::array<uint8_t, SECRET_SIZE> keydata{};
    std::uint16_t epoch = 0;


    /* Constructs a default epoch that is not initialised. Epoch 0 doubles as
     * marker of an uninitialised key */
    EpochKey() = default;

    /** Constructs an epoch key with the given key material and epoch */
    EpochKey(decltype(keydata) keydata, uint16_t epoch)
        : keydata(keydata), epoch(epoch)
    {
    }

    /** Constructs an epoch key with the given OpenVPNStaticKey as epoch 1 key. \param key is assumed to be already
     * prepared as the correct slice of the Data channel key using key.slice */
    EpochKey(StaticKey key);

    /**
     * Iterates the epoch key to make it E_n+1, ie increase the epoch by one
     * and derive the new key material accordingly
     */
    void iterate();

    /** Derives the data channel keys that are tied to the current epoch.
     * @return Key material for the encryption/decryption key and the implicit IV material
     * */
    std::pair<StaticKey, StaticKey> data_key(openvpn::CryptoAlgs::Type cipher);

    /** Generate a context that can be used to encrypt or decrypt using this epoch */
    EpochDataChannelCryptoContext key_context(openvpn::SSLLib::Ctx libctx, openvpn::CryptoAlgs::Type cipher, int mode);
};

class DataChannelEpoch
{
  protected:
    /** Cipher to use to generate the keys */
    openvpn::CryptoAlgs::Type cipher;

    /** TLS library context to initialise the ciphers */
    SSLLib::Ctx libctx;

    /** Usage limit (q+s) for plaintext blocks + number of invocations */

    /** the number of future receive keys that we calculate in advance */
    uint16_t future_keys_count;

    EpochDataChannelEncryptContext encrypt_ctx{};

    EpochDataChannelDecryptContext decrypt_ctx{};

    EpochDataChannelDecryptContext retiring_decrypt_ctx{};


    std::vector<EpochDataChannelDecryptContext> future_keys;


    /** The key used to generate the last  send data channel keys */
    EpochKey send{};

    /** The key used to generate the last receive data channel keys */
    EpochKey receive{};

    void generate_future_receive_keys();

    void generate_encrypt_ctx();

  public:
    /**
     * Forces the use of a new epoch key for sending
     */
    void iterate_send_key();


    /**
     * Returns the number of future receive keys that this will consider as validate candidates for decryption
     */
    uint16_t get_future_keys_count()
    {
        return future_keys_count;
    }


    /**
     * Check if the VPN session should be renegotiated to generate new epoch send/receive keys
     */
    bool should_renegotiate()
    {
        return send.epoch > 0xFF00;
    }

    DataChannelEpoch() = default;

    DataChannelEpoch(decltype(cipher) cipher, openvpn::StaticKey e1send, openvpn::StaticKey e1recv, SSLLib::Ctx libctx = nullptr, uint16_t future_key_count = 16);

    void replace_update_recv_key(std::uint16_t new_epoch, const SessionStats::Ptr &stats_arg);

    /**
     * Checks if the send epoch needs to be iterated and update the encryption context if needed
     */
    void check_send_iterate();

    /**
     * Using an epoch, this function will try to retrieve a decryption
     * key context that matches that epoch from the \c opt argument
     * @param epoch     epoch of the key to lookup
     * @return          the key context with
     */
    EpochDataChannelDecryptContext *lookup_decrypt_key(uint16_t epoch);

    /**
     * Return the context that should be used to encrypt packets
     */
    EpochDataChannelEncryptContext &encrypt()
    {
        return encrypt_ctx;
    }
};



}; // namespace openvpn

#endif // CRYPTO_DATA_EPOCH_H