aboutsummaryrefslogtreecommitdiffstats
path: root/vespalib/src/vespa/vespalib/net/tls/crypto_codec.h
blob: 8b2f258199b480c11f295985140948dc418535f8 (plain) (blame)
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
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#pragma once

#include "capability_set.h"
#include <vespa/vespalib/net/socket_address.h>
#include <memory>

namespace vespalib { class SocketSpec; }

namespace vespalib::net::tls {

struct HandshakeResult {
    // Handshake bytes consumed from peer.
    size_t bytes_consumed = 0;
    // Handshake bytes produced that must be sent to the peer.
    size_t bytes_produced = 0;
    enum class State {
        Failed,
        Done,
        NeedsMorePeerData,
        NeedsWork
    };
    State state = State::Failed;

    bool failed() const noexcept { return (state == State::Failed); }
    bool done() const noexcept { return (state == State::Done); }
    bool needs_work() const noexcept { return (state == State::NeedsWork); }
};

struct EncodeResult {
    // Plaintext bytes consumed
    size_t bytes_consumed = 0;
    // Ciphertext bytes produced that must be sent to the peer
    size_t bytes_produced = 0;
    bool failed = true;
};

struct DecodeResult {
    // Ciphertext bytes consumed from peer
    size_t bytes_consumed = 0;
    // Plaintext bytes produced.
    size_t bytes_produced = 0;
    enum class State {
        Failed,
        OK,
        NeedsMorePeerData,
        Closed
    };
    State state = State::Failed;

    bool closed() const noexcept { return (state == State::Closed); }
    bool failed() const noexcept { return (state == State::Failed); }
    bool frame_decoded_ok() const noexcept { return (state == State::OK); }
};

struct TlsContext;
struct PeerCredentials;

// TODO move to different namespace, not dependent on TLS?

/*
 * A CryptoCodec provides a fully transport-independent way of negotiating
 * a secure, authenticated session towards another peer. The codec requires
 * the caller to handle any and all actual data transfer
 */
class CryptoCodec {
public:
    enum class Mode {
        Client, Server
    };

    virtual ~CryptoCodec() = default;

    /*
     * Minimum buffer size required to represent one wire format frame
     * of encrypted (ciphertext) data, including frame overhead.
     */
    virtual size_t min_encode_buffer_size() const noexcept = 0;
    /*
     * Minimum buffer size required to represent the decoded (plaintext)
     * output of a single frame of encrypted data.
     */
    virtual size_t min_decode_buffer_size() const noexcept = 0;

    /*
     * Initiates or progresses a handshake towards a peer. Guaranteed to be lightweight
     * in the sense that it will not perform any CPU-heavy operations by itself. When
     * handshaking requires more heavy lifting (such as cryptographic operations), handshake()
     * will return a result with needs_work() == true. When this is the case, the caller
     * must call do_handshake_work() before retrying handshake() again. At that point,
     * handshake() will return the result of the CPU-heavy work (which MAY itself be
     * needs_work() again).
     *
     * Basic call flow:
     *
     *     handshake() --> needs_work() ----.
     *        ^                             |
     *        |                             |
     *        `---- do_handshake_work() <---'  (possibly in different thread)
     *
     * Precondition:  to_peer_buf_size >= min_encode_buffer_size()
     *
     *                The handshake() <-> do_handshake_work() flow invariant must hold.
     *
     * Postcondition: If result.done(), the handshake process has completed
     *                and data may be passed through encode()/decode().
     *
     *                If result.needs_work(), do_handshake_work() MUST be called prior
     *                to calling handshake() again. The next time handshake() is called,
     *                it will return the result of the work performed as part of
     *                do_handshake_work(). The from/to buffers MUST remain valid and
     *                stable until do_handshake_work() is called.
     *
     *                If result.needs_work() it is guaranteed that zero bytes have been
     *                consumed from the from_peer buffer or produced to the to_peer buffer.
     */
    virtual HandshakeResult handshake(const char* from_peer, size_t from_peer_buf_size,
                                      char* to_peer, size_t to_peer_buf_size) noexcept = 0;

    /*
     * Perform any CPU-heavy handshake operations that have been initiated by handshake().
     *
     * MAY be called from a different thread than handshake() as long as caller guarantees
     * external synchronization between the threads. MUST NOT be called concurrently with
     * handshake() on the same instance.
     *
     * Precondition:  handshake() has been called immediately prior on this instance with
     *                a result of needs_work()
     *                do_handshake_work() has NOT been called immediately prior on this instance.
     * Postcondition: The next call to handshake() on this instance will return the result
     *                of the handshake work performed.
     */
    virtual void do_handshake_work() noexcept = 0;

    /*
     * Encodes a single ciphertext frame into `ciphertext`. If plaintext_size
     * is greater than can fit into a frame, the returned result's consumed_bytes
     * field will be < plaintext_size. The number of actual ciphertext bytes produced
     * is available in the returned result's produced_bytes field.
     *
     * Precondition:  handshake must be completed
     * Precondition:  ciphertext_size >= min_encode_buffer_size(), i.e. it must be
     *                possible to encode at least 1 frame.
     * Postcondition: if plaintext_size > 0 and result.failed == false, a single
     *                frame of ciphertext has been written into the to_peer buffer.
     *                Size of written frame is given by result.bytes_produced. This
     *                includes all protocol-specific frame overhead.
     */
    virtual EncodeResult encode(const char* plaintext, size_t plaintext_size,
                                char* ciphertext, size_t ciphertext_size) noexcept = 0;
    /*
     * Attempt to decode ciphertext sent by the peer into plaintext. Since
     * ciphertext is sent in frames, it's possible that invoking decode()
     * may produce a CodecResult with a state of `NeedsMorePeerData` if a
     * complete frame is not present in `ciphertext`. In this case, decode()
     * must be called again once more data is available.
     *
     * If result.closed() == true, the peer has half-closed their connection
     * and no more data may be decoded.
     *
     * Precondition:  handshake must be completed
     * Precondition:  plaintext_size >= min_decode_buffer_size()
     * Postcondition: if result.state == DecodeResult::State::OK, at least 1
     *                complete frame has been written to the `plaintext` buffer
     */
    virtual DecodeResult decode(const char* ciphertext, size_t ciphertext_size,
                                char* plaintext, size_t plaintext_size) noexcept = 0;

    /**
     * Encodes a frame into `ciphertext` which signals to the peer that all writes
     * are complete. The peer may still send data to be decoded.
     *
     * After calling this method, encode() must not be called on the same codec instance.
     *
     * Precondition: ciphertext_size >= min_encode_buffer_size(), i.e. it must be
     *               possible to encode at least 1 frame.
     */
    virtual EncodeResult half_close(char* ciphertext, size_t ciphertext_size) noexcept = 0;

    /**
     * Credentials of the _remote peer_ as observed during certificate exchange. E.g.
     * if this is a client codec, peer_credentials() returns the _server_ credentials
     * and vice versa.
     */
    [[nodiscard]] virtual const PeerCredentials& peer_credentials() const noexcept = 0;

    /**
     * Union set of all granted capabilities in the peer policy rules that fully matched the peer's credentials.
     */
    [[nodiscard]] virtual CapabilitySet granted_capabilities() const noexcept = 0;

    /*
     * Creates an implementation defined CryptoCodec that provides at least TLSv1.2
     * compliant handshaking and full duplex data transfer.
     *
     * Throws CryptoException if resources cannot be allocated for the codec.
     */
    static std::unique_ptr<CryptoCodec>
    create_default_client_codec(std::shared_ptr<TlsContext> ctx,
                                const SocketSpec& peer_spec,
                                const SocketAddress& peer_address);
    static std::unique_ptr<CryptoCodec>
    create_default_server_codec(std::shared_ptr<TlsContext> ctx,
                                const SocketAddress& peer_address);
};

}