ChaCha20‑Poly1305 encryption

This commit is contained in:
Kirill 2026-07-24 20:57:32 +02:00
parent 3fa4d8a8a7
commit 45d2c1d8ec
32 changed files with 1949 additions and 126 deletions

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
project(ntptun LANGUAGES CXX)
project(ntptun LANGUAGES C CXX)
# ---- Standard / defaults ---------------------------------------------------
set(CMAKE_CXX_STANDARD 17)
@ -30,6 +30,17 @@ else()
-Wunused -Wnull-dereference -Wdouble-promotion -Wformat=2)
endif()
# ---- Vendored crypto -------------------------------------------------------
# RFC 8439 (ChaCha20-Poly1305) AEAD, public-domain (CC0) reference sources under
# third_party/portable8439. Built as a separate C library so the project's
# strict C++ warning set is not applied to third-party code.
add_library(ntptun_crypto STATIC
third_party/portable8439/src/portable8439.c
third_party/portable8439/src/chacha-portable/chacha-portable.c
third_party/portable8439/src/poly1305-donna/poly1305-donna.c)
target_include_directories(ntptun_crypto PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/third_party/portable8439/src)
# ---- Core library ----------------------------------------------------------
set(ntptun_core_sources
src/Ntp.cpp
@ -48,6 +59,7 @@ endif()
add_library(ntptun_core STATIC ${ntptun_core_sources})
target_include_directories(ntptun_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(ntptun_core PUBLIC ntptun_crypto)
target_link_libraries(ntptun_core PRIVATE ntptun_warnings)
if(WIN32)
target_link_libraries(ntptun_core PUBLIC ws2_32)

View file

@ -2,21 +2,16 @@
(and UDP too)
A small, dependency-free C++17 implementation of the IP-over-NTP tunnel and UDP-over-NTP: it carries one complete inner
IPv4/IPv6 or UDP datagram inside one NTPv4 packet, using an NTP extension field with a
lightweight SHA‑256‑based XOR obfuscation layer for traffic classification and
admission filtering.
> **Scope and honesty.** This is a *datagram* tunnel and an *obfuscation* layer,
> not a security layer. The XOR construction provides **no confidentiality and
> no authentication**. If you need those properties, run WireGuard or IPsec
> *inside* the tunnel.
IPv4/IPv6 or UDP datagram inside one NTPv4 packet, using an NTP extension field
sealed with ChaCha20‑Poly1305 (RFC 8439) authenticated encryption.
- **Targets:** Linux (full features) and Windows/MSVC (UDP transport only — see
below). The TUN transport uses `/dev/net/tun` and is Linux-only; sockets and
the event loop are portable over Winsock and BSD sockets.
- **Dependencies:** none. Only the C++17 standard library. SHA‑256 is a
self-contained header ([include/ntptun/Sha256.hpp](include/ntptun/Sha256.hpp),
FIPS 180‑4).
- **Dependencies:** none. Only the C++17 standard library. The carrier's
ChaCha20‑Poly1305 (RFC 8439) AEAD is a vendored, public‑domain (CC0) reference
implementation under `third_party/portable8439/`; no external crypto library
is required.
- **Interface model:** the app attaches to an **existing** TUN interface that
you create and configure out of band (see below), or — in UDP transport — it
needs no interface at all.
@ -51,10 +46,10 @@ by `transport =` in the config:
Every datagram the inner app sends must fit one NTP carrier: its UDP payload
must be ≤ `inner_mtu`, or ntptun drops it (with an error logged). The carrier's
own overhead is a fixed **64 bytes** (`48` NTP header + `4` ext type/len + `12`
carrier header), so the outer packet is `inner_mtu + 64`; keep
`ntp_payload_cap ≥ inner_mtu + 64`. Configure each protocol so its packets stay
under `inner_mtu`:
own overhead is a fixed **80 bytes** (`48` NTP header + `4` ext type/len + `2`
Client ID + `10` protected header + `16` Poly1305 tag), so the outer packet is
`inner_mtu + 80`; keep `ntp_payload_cap ≥ inner_mtu + 80`. Configure each
protocol so its packets stay under `inner_mtu`:
| Protocol | Hard min packet? | Path MTU discovery | What you configure |
|---|---|---|---|
@ -174,7 +169,7 @@ sudo ip addr add 10.9.0.2/24 dev tun0
Notes:
- **MTU.** With `ntp_payload_cap = 1200` the largest inner datagram is 1136
- **MTU.** With `ntp_payload_cap = 1216` the largest inner datagram is 1136
bytes. Set the TUN MTU to `inner_mtu` (1136) or lower.
- **Port 123.** Binding the standard NTP port needs root or
`CAP_NET_BIND_SERVICE`. For unprivileged testing use a high port (the example
@ -226,7 +221,8 @@ and `udp_base_port`.
| `tun` | TUN, both | — | existing TUN interface name (required when `transport = tun`) |
| `ext_type` | both | `0x0F4E` | NTP extension type used for the tunnel |
| `inner_mtu` | both | `1136` | max inner IP datagram (`tun`) or UDP payload (`udp`) size |
| `ntp_payload_cap` | both | `1200` | max NTP UDP payload; must accommodate `inner_mtu + 64` bytes of framing |
| `ntp_payload_cap` | both | `1216` | max NTP UDP payload; must accommodate `inner_mtu + 80` bytes of framing |
| `replay_window` | both | `1024` | anti‑replay window: recent carrier timestamps remembered per association to reject replays (`0` disables) |
| `log_level` | both | `info` | `error`/`warn`/`info`/`debug` |
| `default_key` | both | — | shared 32‑byte key (hex) |
| `client_key` | both | — | `id:hex` per‑client override (repeatable) |
@ -358,23 +354,31 @@ Takeaways:
## Design notes
- **Carrier & obfuscation** — [src/Carrier.cpp](src/Carrier.cpp): 12‑byte carrier
header (clear Client ID + obfuscated Magic/Version/Kind/Payload‑Length/Flags),
per‑offset SHA‑256 mask `M_j = SHA256("ntp-xor-v1" ‖ K_I ‖ I ‖ D ‖ T ‖ j)`,
4‑byte alignment padding, and all decode‑side validation rules.
- **Carrier & encryption** — [src/Carrier.cpp](src/Carrier.cpp): a clear 2‑byte
Client ID followed by a ChaCha20‑Poly1305 (RFC 8439) seal of the
Magic/Version/Kind/Payload‑Length/Flags header, the inner datagram and 4‑byte
alignment padding, plus the 16‑byte Poly1305 tag. The clear Client ID is the
AEAD associated data and the 96‑bit nonce is `direction ‖ Client ID ‖ Transmit
Timestamp`; decode authenticates before applying all validation rules.
- **NTP framing** — [src/Ntp.cpp](src/Ntp.cpp): 48‑byte header and 4‑byte‑aligned
extension‑field parsing/assembly.
- **Classification & relay** — [src/Server.cpp](src/Server.cpp): tunnel vs.
real‑NTP path, request/response correlation (`Origin == Transmit`),
non‑blocking relay with amplification and rate‑limit guards.
- **Transmit‑timestamp uniqueness** — each endpoint uses a monotonic generator so
a `T` (and therefore an XOR mask) is never reused in a direction;
a `T` (and therefore an AEAD nonce) is never reused in a direction;
responses keep `Receive ≤ Transmit`.
## Limitations
## Credits
The carrier's ChaCha20‑Poly1305 (RFC 8439) AEAD uses the vendored, public‑domain
(CC0) **portable8439** by Davy Landman —
<https://github.com/DavyLandman/portable8439> — which bundles the
`chacha-portable` (Davy Landman) and `poly1305-donna`
([Andrew Moon](https://github.com/floodyberry/poly1305-donna)) reference
implementations. The vendored sources live under
[third_party/portable8439/](third_party/portable8439/); their upstream license is
[third_party/portable8439/LICENSE](third_party/portable8439/LICENSE).
ntptun itself is licensed under the GNU GPL v2 (see [LICENSE](LICENSE)).
No cryptographic confidentiality or authentication, no integrity beyond UDP/IP
checksums, no replay protection, no retransmission, no ordering, no congestion
control, and no tunnel‑level fragmentation. An inner datagram larger than
`inner_mtu` is dropped, not fragmented. The private extension type, packet
sizes and traffic pattern remain visible to a passive observer.

View file

@ -10,7 +10,7 @@ server = 203.0.113.10:12300
ext_type = 0x0F4E
inner_mtu = 1136
ntp_payload_cap = 1200
ntp_payload_cap = 1216
# Downstream delivery mode (must match the server):
# poll (push=false, default): NTP-realistic. The client keeps `poll_window`

View file

@ -23,7 +23,7 @@ ext_type = 0x0F4E
# that is MTU = 1104 (put `MTU = 1104` under [Interface] in the WireGuard conf).
# The client also logs this recommended maximum at startup.
inner_mtu = 1136
ntp_payload_cap = 1200
ntp_payload_cap = 1216
# push = false
poll_interval_ms = 250

View file

@ -17,7 +17,7 @@ upstream = 192.168.1.1:123
ext_type = 0x0F4E
inner_mtu = 1136
ntp_payload_cap = 1200
ntp_payload_cap = 1216
relay_timeout_ms = 1000
relay_rate_per_sec = 10
log_level = info

View file

@ -24,7 +24,7 @@ ext_type = 0x0F4E
# Largest UDP payload (each direction) that fits one NTP carrier. Larger
# datagrams are discarded with an error in the log.
inner_mtu = 1136
ntp_payload_cap = 1200
ntp_payload_cap = 1216
relay_timeout_ms = 1000
relay_rate_per_sec = 10
log_level = info

View file

@ -1,5 +1,5 @@
// Tunnel carrier: the 12-byte carrier header, the SHA-256-based XOR obfuscation
// mask, and encode/decode of the NTP extension value.
// Tunnel carrier: the carrier header, ChaCha20-Poly1305 (RFC 8439) AEAD sealing
// of the protected region, and encode/decode of the NTP extension value.
#ifndef NTPTUN_CARRIER_HPP
#define NTPTUN_CARRIER_HPP
@ -12,7 +12,7 @@
namespace ntptun {
// Fixed deployment marker distinguishing tunnel carriers from unrelated NTP
// extensions ("NTX1"). Obfuscated on the wire.
// extensions ("NTX1"). Encrypted on the wire (inside the AEAD-sealed region).
constexpr std::uint32_t kCarrierMagic = 0x4E545831u;
constexpr std::uint8_t kCarrierVersion = 1;
@ -31,25 +31,19 @@ constexpr std::size_t kWireGuardOverhead = 32;
// Carrier layout sizes.
constexpr std::size_t kClientIdSize = 2; // clear
constexpr std::size_t kProtectedHeaderSize = 10; // obfuscated
constexpr std::size_t kProtectedHeaderSize = 10; // sealed (AEAD plaintext)
constexpr std::size_t kCarrierHeaderSize = kClientIdSize + kProtectedHeaderSize; // 12
// Obfuscation direction byte: 0 = client-to-server, 1 = server-to-client.
// Direction byte (also feeds the AEAD nonce): 0 = client-to-server,
// 1 = server-to-client.
enum class Direction : std::uint8_t {
ClientToServer = 0,
ServerToClient = 1,
};
// Apply (or, being XOR, remove) the keystream mask in place over `len` bytes of
// the protected region, using the key, clear Client ID, direction and the outer
// packet's own Transmit Timestamp.
void apply_xor_mask(Byte* data, std::size_t len, const Key& key,
std::uint16_t client_id, Direction dir,
std::uint64_t transmit_ts);
// Build a complete extension value (clear Client ID followed by the obfuscated
// protected header, inner datagram and alignment padding). `inner_ip` is empty
// for an empty poll.
// Build a complete extension value (clear Client ID followed by the AEAD-sealed
// protected header, inner datagram, alignment padding and the 16-byte Poly1305
// tag). `inner_ip` is empty for an empty poll.
ByteVector encode_carrier_value(std::uint16_t client_id, std::uint8_t kind,
ByteSpan inner_ip, const Key& key, Direction dir,
std::uint64_t transmit_ts);

View file

@ -0,0 +1,66 @@
// ChaCha20-Poly1305 (RFC 8439) authenticated encryption.
//
// A thin C++ facade over the vendored, public-domain reference implementation
// in third_party/portable8439 (portable8439 + chacha-portable + poly1305-donna,
// all CC0). This provides real AEAD confidentiality and integrity for the
// tunnel carrier; it is not a from-scratch reimplementation of the primitives.
#ifndef NTPTUN_CHACHA20POLY1305_HPP
#define NTPTUN_CHACHA20POLY1305_HPP
#include <array>
#include <cstddef>
#include <cstdint>
#include <optional>
#include "ntptun/Bytes.hpp"
extern "C" {
#include "portable8439.h"
}
// portable8439.h defines `restrict` as a macro under C++; do not leak it.
#ifdef restrict
#undef restrict
#endif
namespace ntptun {
constexpr std::size_t kAeadKeySize = RFC_8439_KEY_SIZE; // 32
constexpr std::size_t kAeadNonceSize = RFC_8439_NONCE_SIZE; // 12
constexpr std::size_t kAeadTagSize = RFC_8439_TAG_SIZE; // 16
using AeadNonce = std::array<Byte, kAeadNonceSize>;
// Seal `plaintext` under `key` (kAeadKeySize bytes) and `nonce`, authenticating
// `aad`. The (key, nonce) pair must never repeat. Returns the ciphertext
// followed by the 16-byte Poly1305 tag (plaintext.size() + kAeadTagSize bytes).
inline ByteVector aead_seal(const Byte* key, const AeadNonce& nonce,
ByteSpan aad, ByteSpan plaintext) {
ByteVector out(plaintext.size() + kAeadTagSize);
portable_chacha20_poly1305_encrypt(out.data(), key, nonce.data(), aad.data(),
aad.size(), plaintext.data(),
plaintext.size());
return out;
}
// Verify and decrypt `sealed` (ciphertext followed by its 16-byte tag) under
// `key`, `nonce` and `aad`. Returns the recovered plaintext, or nullopt if
// authentication fails (wrong key/nonce/aad, or the carrier was modified).
inline std::optional<ByteVector> aead_open(const Byte* key,
const AeadNonce& nonce, ByteSpan aad,
ByteSpan sealed) {
if (sealed.size() < kAeadTagSize) {
return std::nullopt;
}
ByteVector out(sealed.size() - kAeadTagSize);
const std::size_t n = portable_chacha20_poly1305_decrypt(
out.data(), key, nonce.data(), aad.data(), aad.size(), sealed.data(),
sealed.size());
if (n == static_cast<std::size_t>(-1)) {
return std::nullopt;
}
return out;
}
} // namespace ntptun
#endif // NTPTUN_CHACHA20POLY1305_HPP

View file

@ -16,6 +16,7 @@
#include "ntptun/Config.hpp"
#include "ntptun/Endpoint.hpp"
#include "ntptun/Platform.hpp"
#include "ntptun/ReplayWindow.hpp"
#include "ntptun/TunDevice.hpp"
#include "ntptun/UdpSocket.hpp"
@ -52,6 +53,7 @@ private:
std::uint64_t last_transmit_ts_ = 0;
std::deque<std::uint64_t> pending_; // outstanding request Transmit Timestamps
std::uint64_t last_send_ms_ = 0; // time of the last packet sent (keepalive)
ReplayWindow replay_; // anti-replay for server->client carriers
};
} // namespace ntptun

View file

@ -24,7 +24,10 @@ struct CommonConfig {
std::string tun;
std::uint16_t ext_type = 0x0F4E;
std::size_t inner_mtu = 1136;
std::size_t ntp_payload_cap = 1200;
std::size_t ntp_payload_cap = 1216;
// Anti-replay window: number of recently accepted carrier Transmit
// Timestamps remembered per association to reject replays. 0 disables it.
std::size_t replay_window = 1024;
KeyStore keys;
LogLevel log_level = LogLevel::Info;
// When true the server pushes downstream datagrams to the client's last

View file

@ -0,0 +1,50 @@
// Anti-replay window for the AEAD nonce (the outer NTP Transmit Timestamp).
//
// Each sender emits Transmit Timestamps strictly increasing per direction (see
// Client/Server next_transmit_ts). A receiver remembers the most recent accepted
// timestamps so a replayed or excessively delayed carrier is rejected, while
// still tolerating up to `capacity` packets of network reordering.
#ifndef NTPTUN_REPLAYWINDOW_HPP
#define NTPTUN_REPLAYWINDOW_HPP
#include <cstddef>
#include <cstdint>
#include <set>
namespace ntptun {
class ReplayWindow {
public:
// Test `ts` against the window and, if fresh, record it. Returns true to
// accept (fresh) or false to reject (a replay of an already-seen timestamp,
// or one older than the retained window). `capacity` is the number of recent
// timestamps to remember; 0 disables the check (always accept). The value is
// passed per call so the window needs no configuration at construction.
bool accept(std::uint64_t ts, std::size_t capacity) {
if (capacity == 0) {
return true; // disabled
}
// Below the window floor once the window is full: a replay of an evicted
// timestamp or one delayed beyond the reorder horizon.
if (seen_.size() >= capacity && ts < *seen_.begin()) {
return false;
}
if (!seen_.insert(ts).second) {
return false; // already accepted -> replay
}
// Keep only the `capacity` largest (most recent) timestamps.
while (seen_.size() > capacity) {
seen_.erase(seen_.begin());
}
return true;
}
void clear() noexcept { seen_.clear(); }
private:
std::set<std::uint64_t> seen_;
};
} // namespace ntptun
#endif // NTPTUN_REPLAYWINDOW_HPP

View file

@ -20,6 +20,7 @@
#include "ntptun/IpPacket.hpp"
#include "ntptun/Ntp.hpp"
#include "ntptun/Platform.hpp"
#include "ntptun/ReplayWindow.hpp"
#include "ntptun/TunDevice.hpp"
#include "ntptun/UdpSocket.hpp"
@ -37,6 +38,7 @@ private:
bool has_src = false; // whether last_src is known yet
std::uint64_t last_request_ts = 0; // echoed as Origin in pushed packets
std::deque<ByteVector> downstream; // queued datagrams awaiting a poll
ReplayWindow replay; // anti-replay for client->server carriers
};
struct PendingRelay {

View file

@ -1,9 +1,10 @@
// Self-contained SHA-256 (FIPS 180-4), header-only.
//
// This is used ONLY as a mask expander for the tunnel's XOR obfuscation layer.
// It is a straightforward, portable implementation and is NOT hardened against
// timing side-channels. The tunnel makes no cryptographic-confidentiality or
// authentication claims.
// A straightforward, portable implementation retained as a general-purpose
// utility (and exercised by the unit tests). The tunnel carrier is sealed with
// ChaCha20-Poly1305 (see ChaCha20Poly1305.hpp), so SHA-256 is no longer on the
// carrier's encode/decode path. This implementation is NOT hardened against
// timing side-channels.
#ifndef NTPTUN_SHA256_HPP
#define NTPTUN_SHA256_HPP

View file

@ -2,94 +2,74 @@
#include <cstring>
#include "ntptun/ChaCha20Poly1305.hpp"
#include "ntptun/IpPacket.hpp"
#include "ntptun/Sha256.hpp"
namespace ntptun {
namespace {
// Domain-separation label for the mask.
constexpr char kMaskLabel[] = "ntp-xor-v1";
constexpr std::size_t kMaskLabelLen = sizeof(kMaskLabel) - 1; // exclude NUL
// Padding needed so that (kClientIdSize + kProtectedHeaderSize + P) is a
// multiple of four. The 12-byte header is already aligned, so this aligns P.
// Padding needed so that (kClientIdSize + kProtectedHeaderSize + tag + P) is a
// multiple of four. The clear Client ID (2), protected header (10) and Poly1305
// tag (16) already sum to a multiple of four, so this only aligns the payload P.
std::size_t alignment_padding(std::size_t payload_len) {
return (4u - (payload_len % 4u)) % 4u;
}
} // namespace
void apply_xor_mask(Byte* data, std::size_t len, const Key& key,
std::uint16_t client_id, Direction dir,
std::uint64_t transmit_ts) {
Byte id_be[2];
store_be16(id_be, client_id);
Byte ts_be[8];
store_be64(ts_be, transmit_ts);
const Byte direction_byte = static_cast<Byte>(dir);
std::size_t offset = 0;
std::uint32_t block_index = 0;
while (offset < len) {
Sha256 hash;
hash.update(reinterpret_cast<const Byte*>(kMaskLabel), kMaskLabelLen);
hash.update(key.data(), key.size());
hash.update(id_be, sizeof(id_be));
hash.update(&direction_byte, 1);
hash.update(ts_be, sizeof(ts_be));
Byte index_be[4];
store_be32(index_be, block_index);
hash.update(index_be, sizeof(index_be));
const Sha256::Digest block = hash.finish();
const std::size_t n =
(len - offset < Sha256::kDigestSize) ? (len - offset)
: Sha256::kDigestSize;
for (std::size_t i = 0; i < n; ++i) {
data[offset + i] ^= block[i];
}
offset += n;
++block_index;
}
// Build the 96-bit AEAD nonce from the fields that make each carrier unique:
// direction, clear Client ID and the outer packet's Transmit Timestamp. A
// sender must never reuse a Transmit Timestamp in the same direction under the
// same key, which keeps every (key, nonce) pair unique.
AeadNonce make_nonce(std::uint16_t client_id, Direction dir,
std::uint64_t transmit_ts) {
AeadNonce nonce{}; // zero-filled; the final byte stays 0
nonce[0] = static_cast<Byte>(dir);
store_be16(nonce.data() + 1, client_id);
store_be64(nonce.data() + 3, transmit_ts);
return nonce;
}
} // namespace
ByteVector encode_carrier_value(std::uint16_t client_id, std::uint8_t kind,
ByteSpan inner_ip, const Key& key, Direction dir,
std::uint64_t transmit_ts) {
const std::size_t payload_len = inner_ip.size();
const std::size_t pad = alignment_padding(payload_len);
ByteVector value(kCarrierHeaderSize + payload_len + pad); // zero-initialized
// Clear Client ID.
store_be16(value.data(), client_id);
// Protected header (Magic || Version || Kind || Payload Length || Flags).
Byte* ph = value.data() + kClientIdSize;
store_be32(ph, kCarrierMagic);
ph[4] = kCarrierVersion;
ph[5] = kind;
store_be16(ph + 6, static_cast<std::uint16_t>(payload_len));
store_be16(ph + 8, 0); // Flags
// Protected plaintext (Magic || Version || Kind || Payload Length || Flags),
// then the inner datagram and zero alignment padding.
ByteVector plaintext(kProtectedHeaderSize + payload_len + pad); // zeroed
store_be32(plaintext.data(), kCarrierMagic);
plaintext[4] = kCarrierVersion;
plaintext[5] = kind;
store_be16(plaintext.data() + 6, static_cast<std::uint16_t>(payload_len));
store_be16(plaintext.data() + 8, 0); // Flags
if (payload_len > 0) {
std::memcpy(value.data() + kCarrierHeaderSize, inner_ip.data(),
std::memcpy(plaintext.data() + kProtectedHeaderSize, inner_ip.data(),
payload_len);
}
// Obfuscate everything after the clear Client ID.
apply_xor_mask(value.data() + kClientIdSize,
kProtectedHeaderSize + payload_len + pad, key, client_id, dir,
transmit_ts);
// The clear Client ID is authenticated as associated data so it cannot be
// swapped to redirect a carrier to a different key without detection.
Byte id_be[2];
store_be16(id_be, client_id);
const AeadNonce nonce = make_nonce(client_id, dir, transmit_ts);
const ByteVector sealed =
aead_seal(key.data(), nonce, ByteSpan(id_be, 2), ByteSpan(plaintext));
// Extension value: clear Client ID followed by ciphertext || tag.
ByteVector value(kClientIdSize + sealed.size());
store_be16(value.data(), client_id);
std::memcpy(value.data() + kClientIdSize, sealed.data(), sealed.size());
return value;
}
CarrierStatus decode_carrier_value(ByteSpan value, const KeyStore& keys,
Direction dir, std::uint64_t transmit_ts,
DecodedCarrier& out) {
// Structural gate: at least a full header, 4-byte aligned value.
if (value.size() < kCarrierHeaderSize || (value.size() % 4) != 0) {
// Structural gate: clear Client ID + protected header + tag, 4-byte aligned.
if (value.size() < kCarrierHeaderSize + kAeadTagSize ||
(value.size() % 4) != 0) {
return CarrierStatus::NotTunnel;
}
@ -102,16 +82,28 @@ CarrierStatus decode_carrier_value(ByteSpan value, const KeyStore& keys,
return CarrierStatus::NotTunnel; // no key configured -> relay path
}
// Deobfuscate the protected region into a scratch buffer.
const std::size_t protected_len = value.size() - kClientIdSize;
ByteVector buf(value.data() + kClientIdSize, value.data() + value.size());
apply_xor_mask(buf.data(), protected_len, *key, client_id, dir, transmit_ts);
// Authenticate and decrypt. A failure is cryptographically indistinguishable
// from unrelated traffic (wrong key, wrong nonce, or a modified carrier), so
// it falls through to the real-NTP relay path.
Byte id_be[2];
store_be16(id_be, client_id);
const AeadNonce nonce = make_nonce(client_id, dir, transmit_ts);
const ByteSpan sealed(value.data() + kClientIdSize,
value.size() - kClientIdSize);
const std::optional<ByteVector> opened =
aead_open(key->data(), nonce, ByteSpan(id_be, 2), sealed);
if (!opened) {
return CarrierStatus::NotTunnel;
}
const ByteVector& buf = *opened;
// From here the carrier is authenticated as ours; any inconsistency is a
// hard error (dropped), not a relay candidate.
if (load_be32(buf.data()) != kCarrierMagic) {
return CarrierStatus::NotTunnel; // wrong marker -> relay path
return CarrierStatus::Invalid; // wrong marker
}
if (buf[4] != kCarrierVersion) {
return CarrierStatus::NotTunnel; // unknown version -> relay path
return CarrierStatus::Invalid; // unknown version
}
const std::uint8_t kind = buf[5];
@ -126,9 +118,9 @@ CarrierStatus decode_carrier_value(ByteSpan value, const KeyStore& keys,
return CarrierStatus::Invalid;
}
// The obfuscated region must be exactly header + payload + alignment pad.
// The plaintext must be exactly header + payload + alignment pad.
const std::size_t pad = alignment_padding(payload_len);
if (kProtectedHeaderSize + payload_len + pad != protected_len) {
if (kProtectedHeaderSize + payload_len + pad != buf.size()) {
return CarrierStatus::Invalid;
}
for (std::size_t i = 0; i < pad; ++i) {

View file

@ -144,7 +144,7 @@ void Client::drain_inner() {
" bytes (max payload is ", cfg_.common.inner_mtu,
"); lower the inner app MTU, or for QUIC/Hysteria2 "
"raise inner_mtu >= 1200 and ntp_payload_cap >= "
"inner_mtu+64");
"inner_mtu+80");
continue;
}
send_carrier(kKindUdpDatagram,
@ -289,6 +289,10 @@ void Client::handle_response(ByteSpan packet) {
if (decoded.client_id != cfg_.client_id) {
return; // belongs to another logical association
}
if (!replay_.accept(header->transmit_ts, cfg_.common.replay_window)) {
log_debug("replay: dropping downstream carrier ts=", header->transmit_ts);
return;
}
if ((decoded.kind == kKindIpDatagram || decoded.kind == kKindUdpDatagram) &&
!decoded.inner_ip.empty()) {
deliver_inner(decoded.kind, decoded.inner_ip);

View file

@ -172,6 +172,9 @@ Config load_config(const std::string& path) {
if (const auto* v = get("ntp_payload_cap")) {
common.ntp_payload_cap = parse_uint("ntp_payload_cap", *v, 65535);
}
if (const auto* v = get("replay_window")) {
common.replay_window = parse_uint("replay_window", *v, 1048576);
}
if (const auto* v = get("log_level")) {
common.log_level = parse_log_level(*v);
}

View file

@ -183,6 +183,14 @@ void Server::process_request(ByteSpan packet, const Endpoint& from) {
tunnel_ext->value, cfg_.common.keys, Direction::ClientToServer,
header->transmit_ts, decoded);
if (status == CarrierStatus::Ok) {
// Reject replayed or too-old carriers before mutating any state.
ClientState& state = clients_[decoded.client_id];
if (!state.replay.accept(header->transmit_ts,
cfg_.common.replay_window)) {
log_debug("replay: dropping carrier for client ",
decoded.client_id, " ts=", header->transmit_ts);
return; // authenticated but replayed -> drop, do not relay
}
handle_tunnel(header->transmit_ts, decoded, from);
return;
}
@ -292,7 +300,7 @@ void Server::drain_relay(std::uint16_t client_id, UdpSocket& sock) {
if (static_cast<std::size_t>(n) > cfg_.common.inner_mtu) {
log_error("relay: dropping oversized reply of ", n, " bytes for client ",
client_id, " (max payload is ", cfg_.common.inner_mtu,
"); raise inner_mtu (and ntp_payload_cap >= inner_mtu+64), "
"); raise inner_mtu (and ntp_payload_cap >= inner_mtu+80), "
"e.g. inner_mtu >= 1200 for QUIC/Hysteria2");
continue;
}

View file

@ -2,6 +2,14 @@ add_executable(test_sha256 test_sha256.cpp)
target_link_libraries(test_sha256 PRIVATE ntptun_core)
add_test(NAME sha256 COMMAND test_sha256)
add_executable(test_aead test_aead.cpp)
target_link_libraries(test_aead PRIVATE ntptun_core)
add_test(NAME aead COMMAND test_aead)
add_executable(test_replay test_replay.cpp)
target_link_libraries(test_replay PRIVATE ntptun_core)
add_test(NAME replay COMMAND test_replay)
add_executable(test_carrier test_carrier.cpp)
target_link_libraries(test_carrier PRIVATE ntptun_core)
add_test(NAME carrier COMMAND test_carrier)

92
tests/test_aead.cpp Normal file
View file

@ -0,0 +1,92 @@
#include "ntptun/Bytes.hpp"
#include "ntptun/ChaCha20Poly1305.hpp"
#include "TestUtil.hpp"
#include <string>
using namespace ntptun;
namespace {
AeadNonce nonce_from_hex(const std::string& hex) {
const auto v = from_hex(hex);
AeadNonce n{};
if (v) {
for (std::size_t i = 0; i < n.size() && i < v->size(); ++i) {
n[i] = (*v)[i];
}
}
return n;
}
} // namespace
void run_tests() {
// RFC 8439 section 2.8.2 AEAD known-answer test. Validates the vendored
// ChaCha20-Poly1305 implementation against the specification test vector.
const auto key = from_hex(
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f");
const AeadNonce nonce = nonce_from_hex("070000004041424344454647");
const auto aad = from_hex("50515253c0c1c2c3c4c5c6c7");
const std::string pt_str =
"Ladies and Gentlemen of the class of '99: If I could offer you only "
"one tip for the future, sunscreen would be it.";
const ByteVector plaintext(pt_str.begin(), pt_str.end());
const auto expected = from_hex(
"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6"
"3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36"
"92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc"
"3ff4def08e4b7a9de576d26586cec64b6116"
"1ae10b594f09e26a7e902ecbd0600691");
CHECK(key.has_value());
CHECK(aad.has_value());
CHECK(expected.has_value());
const ByteVector sealed =
aead_seal(key->data(), nonce, ByteSpan(*aad), ByteSpan(plaintext));
CHECK_EQ(sealed.size(), expected->size());
CHECK(sealed == *expected);
// Round trip: open recovers the original plaintext.
const auto opened =
aead_open(key->data(), nonce, ByteSpan(*aad), ByteSpan(sealed));
CHECK(opened.has_value());
CHECK(opened && *opened == plaintext);
// A single flipped ciphertext bit fails authentication.
{
ByteVector bad = sealed;
bad[0] ^= 0x01;
CHECK(!aead_open(key->data(), nonce, ByteSpan(*aad), ByteSpan(bad)));
}
// A flipped tag bit fails authentication.
{
ByteVector bad = sealed;
bad.back() ^= 0x80;
CHECK(!aead_open(key->data(), nonce, ByteSpan(*aad), ByteSpan(bad)));
}
// Tampered associated data fails authentication.
{
ByteVector bad_aad = *aad;
bad_aad[0] ^= 0xFF;
CHECK(!aead_open(key->data(), nonce, ByteSpan(bad_aad), ByteSpan(sealed)));
}
// A different nonce fails authentication.
{
AeadNonce n2 = nonce;
n2[11] ^= 0x01;
CHECK(!aead_open(key->data(), n2, ByteSpan(*aad), ByteSpan(sealed)));
}
// Empty associated data still round-trips.
{
const ByteVector sealed2 =
aead_seal(key->data(), nonce, ByteSpan{}, ByteSpan(plaintext));
const auto opened2 =
aead_open(key->data(), nonce, ByteSpan{}, ByteSpan(sealed2));
CHECK(opened2 && *opened2 == plaintext);
}
}
TEST_MAIN()

View file

@ -68,10 +68,12 @@ void run_tests() {
CarrierStatus::Ok);
CHECK(out.inner_ip == inner);
// Tampering with a (obfuscated) padding byte must be rejected.
// Tampering with any sealed byte fails Poly1305 authentication; an
// unauthenticated carrier is indistinguishable from unrelated traffic
// and therefore takes the relay path.
value.back() ^= 0xFF;
CHECK_EQ(decode_carrier_value(value, keys, Direction::ServerToClient, ts, out),
CarrierStatus::Invalid);
CarrierStatus::NotTunnel);
}
// --- Empty poll ---------------------------------------------------------

69
tests/test_replay.cpp Normal file
View file

@ -0,0 +1,69 @@
#include "ntptun/ReplayWindow.hpp"
#include "TestUtil.hpp"
using namespace ntptun;
void run_tests() {
// Strictly increasing timestamps are all accepted.
{
ReplayWindow w;
for (std::uint64_t t = 1; t <= 100; ++t) {
CHECK(w.accept(t, 16));
}
}
// An exact replay of an in-window timestamp is rejected.
{
ReplayWindow w;
CHECK(w.accept(10, 16));
CHECK(w.accept(11, 16));
CHECK(!w.accept(10, 16)); // replay
CHECK(!w.accept(11, 16)); // replay
CHECK(w.accept(12, 16)); // fresh again
}
// Reordering within the window is tolerated (out-of-order but unseen).
{
ReplayWindow w;
CHECK(w.accept(100, 16));
CHECK(w.accept(105, 16));
CHECK(w.accept(102, 16)); // arrived late but never seen -> accept
CHECK(!w.accept(102, 16)); // now a replay
CHECK(!w.accept(105, 16)); // replay
}
// Timestamps older than the retained window are rejected once it is full.
{
ReplayWindow w;
const std::size_t cap = 8;
for (std::uint64_t t = 100; t < 100 + cap; ++t) {
CHECK(w.accept(t, cap)); // fills [100..107]
}
// Advancing evicts the smallest; 100 falls out of the window.
CHECK(w.accept(200, cap)); // window now drops its floor
CHECK(!w.accept(100, cap)); // below floor -> rejected (replay/too-old)
CHECK(!w.accept(50, cap)); // far too old -> rejected
}
// capacity 0 disables the check: everything, including duplicates, passes.
{
ReplayWindow w;
CHECK(w.accept(5, 0));
CHECK(w.accept(5, 0)); // duplicate still accepted when disabled
CHECK(w.accept(1, 0));
}
// A smaller capacity retains only the largest timestamps.
{
ReplayWindow w;
for (std::uint64_t t = 1; t <= 10; ++t) {
CHECK(w.accept(t, 10)); // set = {1..10}
}
CHECK(w.accept(11, 3)); // shrink to the 3 largest: {9, 10, 11}
CHECK(!w.accept(8, 3)); // below the floor (9) -> rejected
CHECK(!w.accept(9, 3)); // still retained -> replay rejected
CHECK(w.accept(12, 3)); // fresh -> accepted
}
}
TEST_MAIN()

121
third_party/portable8439/LICENSE vendored Normal file
View file

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View file

@ -0,0 +1,226 @@
#include "chacha-portable.h"
#include <string.h>
#include <assert.h>
// this is a fresh implementation of chacha20, based on the description in rfc8349
// it's such a nice compact algorithm that it is easy to do.
// In relationship to other c implementation this implementation:
// - pure c99
// - big & little endian support
// - safe for architectures that don't support unaligned reads
//
// Next to this, we try to be fast as possible without resorting inline assembly.
// based on https://sourceforge.net/p/predef/wiki/Endianness/
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \
__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define __HAVE_LITTLE_ENDIAN 1
#elif defined(__LITTLE_ENDIAN__) || \
defined(__ARMEL__) || \
defined(__THUMBEL__) || \
defined(__AARCH64EL__) || \
defined(_MIPSEL) || \
defined(__MIPSEL) || \
defined(__MIPSEL__) || \
defined(__XTENSA_EL__) || \
defined(__AVR__) || \
defined(LITTLE_ENDIAN)
# define __HAVE_LITTLE_ENDIAN 1
#endif
#ifndef TEST_SLOW_PATH
# if defined(__HAVE_LITTLE_ENDIAN)
# define FAST_PATH
# endif
#endif
#define CHACHA20_STATE_WORDS (16)
#define CHACHA20_BLOCK_SIZE (CHACHA20_STATE_WORDS * sizeof(uint32_t))
#ifdef FAST_PATH
#define store_32_le(target, source) \
memcpy(&(target), source, sizeof(uint32_t))
#else
#define store_32_le(target, source) \
target \
= (uint32_t)(source)[0] \
| ((uint32_t)(source)[1]) << 8 \
| ((uint32_t)(source)[2]) << 16 \
| ((uint32_t)(source)[3]) << 24
#endif
static void initialize_state(
uint32_t state[CHACHA20_STATE_WORDS],
const uint8_t key[CHACHA20_KEY_SIZE],
const uint8_t nonce[CHACHA20_NONCE_SIZE],
uint32_t counter
) {
#ifdef static_assert
static_assert(sizeof(uint32_t) == 4, "We don't support systems that do not conform to standard of uint32_t being exact 32bit wide");
#endif
state[0] = 0x61707865;
state[1] = 0x3320646e;
state[2] = 0x79622d32;
state[3] = 0x6b206574;
store_32_le(state[4], key);
store_32_le(state[5], key + 4);
store_32_le(state[6], key + 8);
store_32_le(state[7], key + 12);
store_32_le(state[8], key + 16);
store_32_le(state[9], key + 20);
store_32_le(state[10], key + 24);
store_32_le(state[11], key + 28);
state[12] = counter;
store_32_le(state[13], nonce);
store_32_le(state[14], nonce + 4);
store_32_le(state[15], nonce + 8);
}
#define increment_counter(state) (state)[12]++
// source: http://blog.regehr.org/archives/1063
#define rotl32a(x, n) ((x) << (n)) | ((x) >> (32 - (n)))
#define Qround(a,b,c,d) \
a += b; d ^= a; d = rotl32a(d, 16); \
c += d; b ^= c; b = rotl32a(b, 12); \
a += b; d ^= a; d = rotl32a(d, 8); \
c += d; b ^= c; b = rotl32a(b, 7);
#define TIMES16(x) \
x(0) x(1) x(2) x(3) x(4) x(5) x(6) x(7) \
x(8) x(9) x(10) x(11) x(12) x(13) x(14) x(15)
static void core_block(const uint32_t *restrict start, uint32_t *restrict output) {
// instead of working on the output array,
// we let the compiler allocate 16 local variables on the stack
#define __LV(i) uint32_t __s##i = start[i];
TIMES16(__LV)
#define __Q(a,b,c,d) Qround(__s##a, __s##b, __s##c, __s##d)
for (int i = 0; i < 10; i++) {
__Q(0, 4, 8, 12);
__Q(1, 5, 9, 13);
__Q(2, 6, 10, 14);
__Q(3, 7, 11, 15);
__Q(0, 5, 10, 15);
__Q(1, 6, 11, 12);
__Q(2, 7, 8, 13);
__Q(3, 4, 9, 14);
}
#define __FIN(i) output[i] = start[i] + __s##i;
TIMES16(__FIN)
}
#define U8(x) ((uint8_t)((x) & 0xFF))
#ifdef FAST_PATH
# define xor32_le(dst, src, pad) \
uint32_t __value; \
memcpy(&__value, src, sizeof(uint32_t)); \
__value ^= *(pad); \
memcpy(dst, &__value, sizeof(uint32_t));
#else
# define xor32_le(dst, src, pad) \
(dst)[0] = (src)[0] ^ U8(*(pad)); \
(dst)[1] = (src)[1] ^ U8(*(pad) >> 8); \
(dst)[2] = (src)[2] ^ U8(*(pad) >> 16); \
(dst)[3] = (src)[3] ^ U8(*(pad) >> 24);
#endif
#define index8_32(a, ix) ((a) + ((ix) * sizeof(uint32_t)))
#define xor32_blocks(dest, source, pad, words) \
for (unsigned int __i = 0; __i < words; __i++) { \
xor32_le(index8_32(dest, __i), index8_32(source, __i), (pad) + __i) \
}
static void xor_block(uint8_t *restrict dest, const uint8_t *restrict source, const uint32_t *restrict pad, unsigned int chunk_size) {
unsigned int full_blocks = chunk_size / sizeof(uint32_t);
// have to be carefull, we are going back from uint32 to uint8, so endianess matters again
xor32_blocks(dest, source, pad, full_blocks)
dest += full_blocks * sizeof(uint32_t);
source += full_blocks * sizeof(uint32_t);
pad += full_blocks;
switch(chunk_size % sizeof(uint32_t)) {
case 1:
dest[0] = source[0] ^ U8(*pad);
break;
case 2:
dest[0] = source[0] ^ U8(*pad);
dest[1] = source[1] ^ U8(*pad >> 8);
break;
case 3:
dest[0] = source[0] ^ U8(*pad);
dest[1] = source[1] ^ U8(*pad >> 8);
dest[2] = source[2] ^ U8(*pad >> 16);
break;
}
}
void chacha20_xor_stream(
uint8_t *restrict dest,
const uint8_t *restrict source,
size_t length,
const uint8_t key[CHACHA20_KEY_SIZE],
const uint8_t nonce[CHACHA20_NONCE_SIZE],
uint32_t counter
) {
uint32_t state[CHACHA20_STATE_WORDS];
initialize_state(state, key, nonce, counter);
uint32_t pad[CHACHA20_STATE_WORDS];
size_t full_blocks = length / CHACHA20_BLOCK_SIZE;
for (size_t b = 0; b < full_blocks; b++) {
core_block(state, pad);
increment_counter(state);
xor32_blocks(dest, source, pad, CHACHA20_STATE_WORDS)
dest += CHACHA20_BLOCK_SIZE;
source += CHACHA20_BLOCK_SIZE;
}
unsigned int last_block = (unsigned int)(length % CHACHA20_BLOCK_SIZE);
if (last_block > 0 ) {
core_block(state, pad);
xor_block(dest, source, pad, last_block);
}
}
#ifdef FAST_PATH
#define serialize(poly_key, result) memcpy(poly_key, result, 32)
#else
#define store32_le(target, source) \
(target)[0] = U8(*(source)); \
(target)[1] = U8(*(source) >> 8); \
(target)[2] = U8(*(source) >> 16); \
(target)[3] = U8(*(source) >> 24);
#define serialize(poly_key, result) \
for (unsigned int i = 0; i < 32 / sizeof(uint32_t); i++) { \
store32_le(index8_32(poly_key, i), result + i); \
}
#endif
void rfc8439_keygen(
uint8_t poly_key[32],
const uint8_t key[CHACHA20_KEY_SIZE],
const uint8_t nonce[CHACHA20_NONCE_SIZE]
) {
uint32_t state[CHACHA20_STATE_WORDS];
uint32_t result[CHACHA20_STATE_WORDS];
initialize_state(state, key, nonce, 0);
core_block(state, result);
serialize(poly_key, result);
}

View file

@ -0,0 +1,45 @@
#ifndef CHACHA_PORTABLE_H
#define CHACHA_PORTABLE_H
#if !defined(__cplusplus) && \
!defined(_MSC_VER) && \
(!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L)
# error "C99 or newer required"
#endif
#include <stddef.h>
#include <stdint.h>
#if CHAR_BIT > 8
# error "Systems without native octals not supported"
#endif
#define CHACHA20_KEY_SIZE (32)
#define CHACHA20_NONCE_SIZE (12)
#if defined(_MSC_VER) || defined(__cplusplus)
// add restrict support
# if (defined(_MSC_VER) && _MSC_VER >= 1900) || defined(__clang__) || defined(__GNUC__)
# define restrict __restrict
# else
# define restrict
# endif
#endif
// xor data with a ChaCha20 keystream as per RFC8439
void chacha20_xor_stream(
uint8_t *restrict dest,
const uint8_t *restrict source,
size_t length,
const uint8_t key[CHACHA20_KEY_SIZE],
const uint8_t nonce[CHACHA20_NONCE_SIZE],
uint32_t counter
);
void rfc8439_keygen(
uint8_t poly_key[32],
const uint8_t key[CHACHA20_KEY_SIZE],
const uint8_t nonce[CHACHA20_NONCE_SIZE]
);
#endif

View file

@ -0,0 +1,196 @@
/*
poly1305 implementation using 16 bit * 16 bit = 32 bit multiplication and 32 bit addition
*/
#if defined(_MSC_VER)
#define POLY1305_NOINLINE __declspec(noinline)
#elif defined(__GNUC__)
#define POLY1305_NOINLINE __attribute__((noinline))
#else
#define POLY1305_NOINLINE
#endif
#define poly1305_block_size 16
/* 17 + sizeof(size_t) + 18*sizeof(unsigned short) */
typedef struct poly1305_state_internal_t {
unsigned char buffer[poly1305_block_size];
size_t leftover;
unsigned short r[10];
unsigned short h[10];
unsigned short pad[8];
unsigned char final;
} poly1305_state_internal_t;
/* interpret two 8 bit unsigned integers as a 16 bit unsigned integer in little endian */
static unsigned short U8TO16(const unsigned char *p) {
return
(((unsigned short)(p[0] & 0xff) ) |
((unsigned short)(p[1] & 0xff) << 8));
}
/* store a 16 bit unsigned integer as two 8 bit unsigned integers in little endian */
static void U16TO8(unsigned char *p, unsigned short v) {
p[0] = (v ) & 0xff;
p[1] = (v >> 8) & 0xff;
}
void poly1305_init(poly1305_context *ctx, const unsigned char key[32]) {
poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx;
unsigned short t0,t1,t2,t3,t4,t5,t6,t7;
size_t i;
/* r &= 0xffffffc0ffffffc0ffffffc0fffffff */
t0 = U8TO16(&key[ 0]); st->r[0] = ( t0 ) & 0x1fff;
t1 = U8TO16(&key[ 2]); st->r[1] = ((t0 >> 13) | (t1 << 3)) & 0x1fff;
t2 = U8TO16(&key[ 4]); st->r[2] = ((t1 >> 10) | (t2 << 6)) & 0x1f03;
t3 = U8TO16(&key[ 6]); st->r[3] = ((t2 >> 7) | (t3 << 9)) & 0x1fff;
t4 = U8TO16(&key[ 8]); st->r[4] = ((t3 >> 4) | (t4 << 12)) & 0x00ff;
st->r[5] = ((t4 >> 1) ) & 0x1ffe;
t5 = U8TO16(&key[10]); st->r[6] = ((t4 >> 14) | (t5 << 2)) & 0x1fff;
t6 = U8TO16(&key[12]); st->r[7] = ((t5 >> 11) | (t6 << 5)) & 0x1f81;
t7 = U8TO16(&key[14]); st->r[8] = ((t6 >> 8) | (t7 << 8)) & 0x1fff;
st->r[9] = ((t7 >> 5) ) & 0x007f;
/* h = 0 */
for (i = 0; i < 10; i++)
st->h[i] = 0;
/* save pad for later */
for (i = 0; i < 8; i++)
st->pad[i] = U8TO16(&key[16 + (2 * i)]);
st->leftover = 0;
st->final = 0;
}
static void poly1305_blocks(poly1305_state_internal_t *st, const unsigned char *m, size_t bytes) {
const unsigned short hibit = (st->final) ? 0 : (1 << 11); /* 1 << 128 */
unsigned short t0,t1,t2,t3,t4,t5,t6,t7;
unsigned long d[10];
unsigned long c;
while (bytes >= poly1305_block_size) {
size_t i, j;
/* h += m[i] */
t0 = U8TO16(&m[ 0]); st->h[0] += ( t0 ) & 0x1fff;
t1 = U8TO16(&m[ 2]); st->h[1] += ((t0 >> 13) | (t1 << 3)) & 0x1fff;
t2 = U8TO16(&m[ 4]); st->h[2] += ((t1 >> 10) | (t2 << 6)) & 0x1fff;
t3 = U8TO16(&m[ 6]); st->h[3] += ((t2 >> 7) | (t3 << 9)) & 0x1fff;
t4 = U8TO16(&m[ 8]); st->h[4] += ((t3 >> 4) | (t4 << 12)) & 0x1fff;
st->h[5] += ((t4 >> 1) ) & 0x1fff;
t5 = U8TO16(&m[10]); st->h[6] += ((t4 >> 14) | (t5 << 2)) & 0x1fff;
t6 = U8TO16(&m[12]); st->h[7] += ((t5 >> 11) | (t6 << 5)) & 0x1fff;
t7 = U8TO16(&m[14]); st->h[8] += ((t6 >> 8) | (t7 << 8)) & 0x1fff;
st->h[9] += ((t7 >> 5) ) | hibit;
/* h *= r, (partial) h %= p */
for (i = 0, c = 0; i < 10; i++) {
d[i] = c;
for (j = 0; j < 10; j++) {
d[i] += (unsigned long)st->h[j] * ((j <= i) ? st->r[i - j] : (5 * st->r[i + 10 - j]));
/* Sum(h[i] * r[i] * 5) will overflow slightly above 6 products with an unclamped r, so carry at 5 */
if (j == 4) {
c = (d[i] >> 13);
d[i] &= 0x1fff;
}
}
c += (d[i] >> 13);
d[i] &= 0x1fff;
}
c = ((c << 2) + c); /* c *= 5 */
c += d[0];
d[0] = ((unsigned short)c & 0x1fff);
c = (c >> 13);
d[1] += c;
for (i = 0; i < 10; i++)
st->h[i] = (unsigned short)d[i];
m += poly1305_block_size;
bytes -= poly1305_block_size;
}
}
POLY1305_NOINLINE void poly1305_finish(poly1305_context *ctx, unsigned char mac[16]) {
poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx;
unsigned short c;
unsigned short g[10];
unsigned short mask;
unsigned long f;
size_t i;
/* process the remaining block */
if (st->leftover) {
size_t i = st->leftover;
st->buffer[i++] = 1;
for (; i < poly1305_block_size; i++)
st->buffer[i] = 0;
st->final = 1;
poly1305_blocks(st, st->buffer, poly1305_block_size);
}
/* fully carry h */
c = st->h[1] >> 13;
st->h[1] &= 0x1fff;
for (i = 2; i < 10; i++) {
st->h[i] += c;
c = st->h[i] >> 13;
st->h[i] &= 0x1fff;
}
st->h[0] += (c * 5);
c = st->h[0] >> 13;
st->h[0] &= 0x1fff;
st->h[1] += c;
c = st->h[1] >> 13;
st->h[1] &= 0x1fff;
st->h[2] += c;
/* compute h + -p */
g[0] = st->h[0] + 5;
c = g[0] >> 13;
g[0] &= 0x1fff;
for (i = 1; i < 10; i++) {
g[i] = st->h[i] + c;
c = g[i] >> 13;
g[i] &= 0x1fff;
}
/* select h if h < p, or h + -p if h >= p */
mask = (c ^ 1) - 1;
for (i = 0; i < 10; i++)
g[i] &= mask;
mask = ~mask;
for (i = 0; i < 10; i++)
st->h[i] = (st->h[i] & mask) | g[i];
/* h = h % (2^128) */
st->h[0] = ((st->h[0] ) | (st->h[1] << 13) ) & 0xffff;
st->h[1] = ((st->h[1] >> 3) | (st->h[2] << 10) ) & 0xffff;
st->h[2] = ((st->h[2] >> 6) | (st->h[3] << 7) ) & 0xffff;
st->h[3] = ((st->h[3] >> 9) | (st->h[4] << 4) ) & 0xffff;
st->h[4] = ((st->h[4] >> 12) | (st->h[5] << 1) | (st->h[6] << 14)) & 0xffff;
st->h[5] = ((st->h[6] >> 2) | (st->h[7] << 11) ) & 0xffff;
st->h[6] = ((st->h[7] >> 5) | (st->h[8] << 8) ) & 0xffff;
st->h[7] = ((st->h[8] >> 8) | (st->h[9] << 5) ) & 0xffff;
/* mac = (h + pad) % (2^128) */
f = (unsigned long)st->h[0] + st->pad[0];
st->h[0] = (unsigned short)f;
for (i = 1; i < 8; i++) {
f = (unsigned long)st->h[i] + st->pad[i] + (f >> 16);
st->h[i] = (unsigned short)f;
}
for (i = 0; i < 8; i++)
U16TO8(mac + (i * 2), st->h[i]);
/* zero out the state */
for (i = 0; i < 10; i++)
st->h[i] = 0;
for (i = 0; i < 10; i++)
st->r[i] = 0;
for (i = 0; i < 8; i++)
st->pad[i] = 0;
}

View file

@ -0,0 +1,214 @@
/*
poly1305 implementation using 32 bit * 32 bit = 64 bit multiplication and 64 bit addition
*/
#if defined(_MSC_VER)
#define POLY1305_NOINLINE __declspec(noinline)
#elif defined(__GNUC__)
#define POLY1305_NOINLINE __attribute__((noinline))
#else
#define POLY1305_NOINLINE
#endif
#define poly1305_block_size 16
/* 17 + sizeof(size_t) + 14*sizeof(unsigned long) */
typedef struct poly1305_state_internal_t {
unsigned long r[5];
unsigned long h[5];
unsigned long pad[4];
size_t leftover;
unsigned char buffer[poly1305_block_size];
unsigned char final;
} poly1305_state_internal_t;
/* interpret four 8 bit unsigned integers as a 32 bit unsigned integer in little endian */
static unsigned long U8TO32(const unsigned char *p) {
return
(((unsigned long)(p[0] & 0xff) ) |
((unsigned long)(p[1] & 0xff) << 8) |
((unsigned long)(p[2] & 0xff) << 16) |
((unsigned long)(p[3] & 0xff) << 24));
}
/* store a 32 bit unsigned integer as four 8 bit unsigned integers in little endian */
static void U32TO8(unsigned char *p, unsigned long v) {
p[0] = (v ) & 0xff;
p[1] = (v >> 8) & 0xff;
p[2] = (v >> 16) & 0xff;
p[3] = (v >> 24) & 0xff;
}
void poly1305_init(poly1305_context *ctx, const unsigned char key[32]) {
poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx;
/* r &= 0xffffffc0ffffffc0ffffffc0fffffff */
st->r[0] = (U8TO32(&key[ 0]) ) & 0x3ffffff;
st->r[1] = (U8TO32(&key[ 3]) >> 2) & 0x3ffff03;
st->r[2] = (U8TO32(&key[ 6]) >> 4) & 0x3ffc0ff;
st->r[3] = (U8TO32(&key[ 9]) >> 6) & 0x3f03fff;
st->r[4] = (U8TO32(&key[12]) >> 8) & 0x00fffff;
/* h = 0 */
st->h[0] = 0;
st->h[1] = 0;
st->h[2] = 0;
st->h[3] = 0;
st->h[4] = 0;
/* save pad for later */
st->pad[0] = U8TO32(&key[16]);
st->pad[1] = U8TO32(&key[20]);
st->pad[2] = U8TO32(&key[24]);
st->pad[3] = U8TO32(&key[28]);
st->leftover = 0;
st->final = 0;
}
static void poly1305_blocks(poly1305_state_internal_t *st, const unsigned char *m, size_t bytes) {
const unsigned long hibit = (st->final) ? 0 : (1UL << 24); /* 1 << 128 */
unsigned long r0,r1,r2,r3,r4;
unsigned long s1,s2,s3,s4;
unsigned long h0,h1,h2,h3,h4;
unsigned long long d0,d1,d2,d3,d4;
unsigned long c;
r0 = st->r[0];
r1 = st->r[1];
r2 = st->r[2];
r3 = st->r[3];
r4 = st->r[4];
s1 = r1 * 5;
s2 = r2 * 5;
s3 = r3 * 5;
s4 = r4 * 5;
h0 = st->h[0];
h1 = st->h[1];
h2 = st->h[2];
h3 = st->h[3];
h4 = st->h[4];
while (bytes >= poly1305_block_size) {
/* h += m[i] */
h0 += (U8TO32(m+ 0) ) & 0x3ffffff;
h1 += (U8TO32(m+ 3) >> 2) & 0x3ffffff;
h2 += (U8TO32(m+ 6) >> 4) & 0x3ffffff;
h3 += (U8TO32(m+ 9) >> 6) & 0x3ffffff;
h4 += (U8TO32(m+12) >> 8) | hibit;
/* h *= r */
d0 = ((unsigned long long)h0 * r0) + ((unsigned long long)h1 * s4) + ((unsigned long long)h2 * s3) + ((unsigned long long)h3 * s2) + ((unsigned long long)h4 * s1);
d1 = ((unsigned long long)h0 * r1) + ((unsigned long long)h1 * r0) + ((unsigned long long)h2 * s4) + ((unsigned long long)h3 * s3) + ((unsigned long long)h4 * s2);
d2 = ((unsigned long long)h0 * r2) + ((unsigned long long)h1 * r1) + ((unsigned long long)h2 * r0) + ((unsigned long long)h3 * s4) + ((unsigned long long)h4 * s3);
d3 = ((unsigned long long)h0 * r3) + ((unsigned long long)h1 * r2) + ((unsigned long long)h2 * r1) + ((unsigned long long)h3 * r0) + ((unsigned long long)h4 * s4);
d4 = ((unsigned long long)h0 * r4) + ((unsigned long long)h1 * r3) + ((unsigned long long)h2 * r2) + ((unsigned long long)h3 * r1) + ((unsigned long long)h4 * r0);
/* (partial) h %= p */
c = (unsigned long)(d0 >> 26); h0 = (unsigned long)d0 & 0x3ffffff;
d1 += c; c = (unsigned long)(d1 >> 26); h1 = (unsigned long)d1 & 0x3ffffff;
d2 += c; c = (unsigned long)(d2 >> 26); h2 = (unsigned long)d2 & 0x3ffffff;
d3 += c; c = (unsigned long)(d3 >> 26); h3 = (unsigned long)d3 & 0x3ffffff;
d4 += c; c = (unsigned long)(d4 >> 26); h4 = (unsigned long)d4 & 0x3ffffff;
h0 += c * 5; c = (h0 >> 26); h0 = h0 & 0x3ffffff;
h1 += c;
m += poly1305_block_size;
bytes -= poly1305_block_size;
}
st->h[0] = h0;
st->h[1] = h1;
st->h[2] = h2;
st->h[3] = h3;
st->h[4] = h4;
}
POLY1305_NOINLINE void poly1305_finish(poly1305_context *ctx, unsigned char mac[16]) {
poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx;
unsigned long h0,h1,h2,h3,h4,c;
unsigned long g0,g1,g2,g3,g4;
unsigned long long f;
unsigned long mask;
/* process the remaining block */
if (st->leftover) {
size_t i = st->leftover;
st->buffer[i++] = 1;
for (; i < poly1305_block_size; i++)
st->buffer[i] = 0;
st->final = 1;
poly1305_blocks(st, st->buffer, poly1305_block_size);
}
/* fully carry h */
h0 = st->h[0];
h1 = st->h[1];
h2 = st->h[2];
h3 = st->h[3];
h4 = st->h[4];
c = h1 >> 26; h1 = h1 & 0x3ffffff;
h2 += c; c = h2 >> 26; h2 = h2 & 0x3ffffff;
h3 += c; c = h3 >> 26; h3 = h3 & 0x3ffffff;
h4 += c; c = h4 >> 26; h4 = h4 & 0x3ffffff;
h0 += c * 5; c = h0 >> 26; h0 = h0 & 0x3ffffff;
h1 += c;
/* compute h + -p */
g0 = h0 + 5; c = g0 >> 26; g0 &= 0x3ffffff;
g1 = h1 + c; c = g1 >> 26; g1 &= 0x3ffffff;
g2 = h2 + c; c = g2 >> 26; g2 &= 0x3ffffff;
g3 = h3 + c; c = g3 >> 26; g3 &= 0x3ffffff;
g4 = h4 + c - (1UL << 26);
/* select h if h < p, or h + -p if h >= p */
mask = (g4 >> ((sizeof(unsigned long) * 8) - 1)) - 1;
g0 &= mask;
g1 &= mask;
g2 &= mask;
g3 &= mask;
g4 &= mask;
mask = ~mask;
h0 = (h0 & mask) | g0;
h1 = (h1 & mask) | g1;
h2 = (h2 & mask) | g2;
h3 = (h3 & mask) | g3;
h4 = (h4 & mask) | g4;
/* h = h % (2^128) */
h0 = ((h0 ) | (h1 << 26)) & 0xffffffff;
h1 = ((h1 >> 6) | (h2 << 20)) & 0xffffffff;
h2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff;
h3 = ((h3 >> 18) | (h4 << 8)) & 0xffffffff;
/* mac = (h + pad) % (2^128) */
f = (unsigned long long)h0 + st->pad[0] ; h0 = (unsigned long)f;
f = (unsigned long long)h1 + st->pad[1] + (f >> 32); h1 = (unsigned long)f;
f = (unsigned long long)h2 + st->pad[2] + (f >> 32); h2 = (unsigned long)f;
f = (unsigned long long)h3 + st->pad[3] + (f >> 32); h3 = (unsigned long)f;
U32TO8(mac + 0, h0);
U32TO8(mac + 4, h1);
U32TO8(mac + 8, h2);
U32TO8(mac + 12, h3);
/* zero out the state */
st->h[0] = 0;
st->h[1] = 0;
st->h[2] = 0;
st->h[3] = 0;
st->h[4] = 0;
st->r[0] = 0;
st->r[1] = 0;
st->r[2] = 0;
st->r[3] = 0;
st->r[4] = 0;
st->pad[0] = 0;
st->pad[1] = 0;
st->pad[2] = 0;
st->pad[3] = 0;
}

View file

@ -0,0 +1,219 @@
/*
poly1305 implementation using 64 bit * 64 bit = 128 bit multiplication and 128 bit addition
*/
#if defined(_MSC_VER)
#include <intrin.h>
typedef struct uint128_t {
unsigned long long lo;
unsigned long long hi;
} uint128_t;
#define MUL(out, x, y) out.lo = _umul128((x), (y), &out.hi)
#define ADD(out, in) { unsigned long long t = out.lo; out.lo += in.lo; out.hi += (out.lo < t) + in.hi; }
#define ADDLO(out, in) { unsigned long long t = out.lo; out.lo += in; out.hi += (out.lo < t); }
#define SHR(in, shift) (__shiftright128(in.lo, in.hi, (shift)))
#define LO(in) (in.lo)
#define POLY1305_NOINLINE __declspec(noinline)
#elif defined(__GNUC__)
#if defined(__SIZEOF_INT128__)
typedef unsigned __int128 uint128_t;
#else
typedef unsigned uint128_t __attribute__((mode(TI)));
#endif
#define MUL(out, x, y) out = ((uint128_t)x * y)
#define ADD(out, in) out += in
#define ADDLO(out, in) out += in
#define SHR(in, shift) (unsigned long long)(in >> (shift))
#define LO(in) (unsigned long long)(in)
#define POLY1305_NOINLINE __attribute__((noinline))
#endif
#define poly1305_block_size 16
/* 17 + sizeof(size_t) + 8*sizeof(unsigned long long) */
typedef struct poly1305_state_internal_t {
unsigned long long r[3];
unsigned long long h[3];
unsigned long long pad[2];
size_t leftover;
unsigned char buffer[poly1305_block_size];
unsigned char final;
} poly1305_state_internal_t;
/* interpret eight 8 bit unsigned integers as a 64 bit unsigned integer in little endian */
static unsigned long long U8TO64(const unsigned char *p) {
return
(((unsigned long long)(p[0] & 0xff) ) |
((unsigned long long)(p[1] & 0xff) << 8) |
((unsigned long long)(p[2] & 0xff) << 16) |
((unsigned long long)(p[3] & 0xff) << 24) |
((unsigned long long)(p[4] & 0xff) << 32) |
((unsigned long long)(p[5] & 0xff) << 40) |
((unsigned long long)(p[6] & 0xff) << 48) |
((unsigned long long)(p[7] & 0xff) << 56));
}
/* store a 64 bit unsigned integer as eight 8 bit unsigned integers in little endian */
static void U64TO8(unsigned char *p, unsigned long long v) {
p[0] = (v ) & 0xff;
p[1] = (v >> 8) & 0xff;
p[2] = (v >> 16) & 0xff;
p[3] = (v >> 24) & 0xff;
p[4] = (v >> 32) & 0xff;
p[5] = (v >> 40) & 0xff;
p[6] = (v >> 48) & 0xff;
p[7] = (v >> 56) & 0xff;
}
void poly1305_init(poly1305_context *ctx, const unsigned char key[32]) {
poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx;
unsigned long long t0,t1;
/* r &= 0xffffffc0ffffffc0ffffffc0fffffff */
t0 = U8TO64(&key[0]);
t1 = U8TO64(&key[8]);
st->r[0] = ( t0 ) & 0xffc0fffffff;
st->r[1] = ((t0 >> 44) | (t1 << 20)) & 0xfffffc0ffff;
st->r[2] = ((t1 >> 24) ) & 0x00ffffffc0f;
/* h = 0 */
st->h[0] = 0;
st->h[1] = 0;
st->h[2] = 0;
/* save pad for later */
st->pad[0] = U8TO64(&key[16]);
st->pad[1] = U8TO64(&key[24]);
st->leftover = 0;
st->final = 0;
}
static void poly1305_blocks(poly1305_state_internal_t *st, const unsigned char *m, size_t bytes) {
const unsigned long long hibit = (st->final) ? 0 : ((unsigned long long)1 << 40); /* 1 << 128 */
unsigned long long r0,r1,r2;
unsigned long long s1,s2;
unsigned long long h0,h1,h2;
unsigned long long c;
uint128_t d0,d1,d2,d;
r0 = st->r[0];
r1 = st->r[1];
r2 = st->r[2];
h0 = st->h[0];
h1 = st->h[1];
h2 = st->h[2];
s1 = r1 * (5 << 2);
s2 = r2 * (5 << 2);
while (bytes >= poly1305_block_size) {
unsigned long long t0,t1;
/* h += m[i] */
t0 = U8TO64(&m[0]);
t1 = U8TO64(&m[8]);
h0 += (( t0 ) & 0xfffffffffff);
h1 += (((t0 >> 44) | (t1 << 20)) & 0xfffffffffff);
h2 += (((t1 >> 24) ) & 0x3ffffffffff) | hibit;
/* h *= r */
MUL(d0, h0, r0); MUL(d, h1, s2); ADD(d0, d); MUL(d, h2, s1); ADD(d0, d);
MUL(d1, h0, r1); MUL(d, h1, r0); ADD(d1, d); MUL(d, h2, s2); ADD(d1, d);
MUL(d2, h0, r2); MUL(d, h1, r1); ADD(d2, d); MUL(d, h2, r0); ADD(d2, d);
/* (partial) h %= p */
c = SHR(d0, 44); h0 = LO(d0) & 0xfffffffffff;
ADDLO(d1, c); c = SHR(d1, 44); h1 = LO(d1) & 0xfffffffffff;
ADDLO(d2, c); c = SHR(d2, 42); h2 = LO(d2) & 0x3ffffffffff;
h0 += c * 5; c = (h0 >> 44); h0 = h0 & 0xfffffffffff;
h1 += c;
m += poly1305_block_size;
bytes -= poly1305_block_size;
}
st->h[0] = h0;
st->h[1] = h1;
st->h[2] = h2;
}
POLY1305_NOINLINE void poly1305_finish(poly1305_context *ctx, unsigned char mac[16]) {
poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx;
unsigned long long h0,h1,h2,c;
unsigned long long g0,g1,g2;
unsigned long long t0,t1;
/* process the remaining block */
if (st->leftover) {
size_t i = st->leftover;
st->buffer[i] = 1;
for (i = i + 1; i < poly1305_block_size; i++)
st->buffer[i] = 0;
st->final = 1;
poly1305_blocks(st, st->buffer, poly1305_block_size);
}
/* fully carry h */
h0 = st->h[0];
h1 = st->h[1];
h2 = st->h[2];
c = (h1 >> 44); h1 &= 0xfffffffffff;
h2 += c; c = (h2 >> 42); h2 &= 0x3ffffffffff;
h0 += c * 5; c = (h0 >> 44); h0 &= 0xfffffffffff;
h1 += c; c = (h1 >> 44); h1 &= 0xfffffffffff;
h2 += c; c = (h2 >> 42); h2 &= 0x3ffffffffff;
h0 += c * 5; c = (h0 >> 44); h0 &= 0xfffffffffff;
h1 += c;
/* compute h + -p */
g0 = h0 + 5; c = (g0 >> 44); g0 &= 0xfffffffffff;
g1 = h1 + c; c = (g1 >> 44); g1 &= 0xfffffffffff;
g2 = h2 + c - ((unsigned long long)1 << 42);
/* select h if h < p, or h + -p if h >= p */
c = (g2 >> ((sizeof(unsigned long long) * 8) - 1)) - 1;
g0 &= c;
g1 &= c;
g2 &= c;
c = ~c;
h0 = (h0 & c) | g0;
h1 = (h1 & c) | g1;
h2 = (h2 & c) | g2;
/* h = (h + pad) */
t0 = st->pad[0];
t1 = st->pad[1];
h0 += (( t0 ) & 0xfffffffffff) ; c = (h0 >> 44); h0 &= 0xfffffffffff;
h1 += (((t0 >> 44) | (t1 << 20)) & 0xfffffffffff) + c; c = (h1 >> 44); h1 &= 0xfffffffffff;
h2 += (((t1 >> 24) ) & 0x3ffffffffff) + c; h2 &= 0x3ffffffffff;
/* mac = h % (2^128) */
h0 = ((h0 ) | (h1 << 44));
h1 = ((h1 >> 20) | (h2 << 24));
U64TO8(&mac[0], h0);
U64TO8(&mac[8], h1);
/* zero out the state */
st->h[0] = 0;
st->h[1] = 0;
st->h[2] = 0;
st->r[0] = 0;
st->r[1] = 0;
st->r[2] = 0;
st->pad[0] = 0;
st->pad[1] = 0;
}

View file

@ -0,0 +1,180 @@
/*
poly1305 implementation using 8 bit * 8 bit = 16 bit multiplication and 32 bit addition
based on the public domain reference version in supercop by djb
*/
#if defined(_MSC_VER)
#define POLY1305_NOINLINE __declspec(noinline)
#elif defined(__GNUC__)
#define POLY1305_NOINLINE __attribute__((noinline))
#else
#define POLY1305_NOINLINE
#endif
#define poly1305_block_size 16
/* 17 + sizeof(size_t) + 51*sizeof(unsigned char) */
typedef struct poly1305_state_internal_t {
unsigned char buffer[poly1305_block_size];
size_t leftover;
unsigned char h[17];
unsigned char r[17];
unsigned char pad[17];
unsigned char final;
} poly1305_state_internal_t;
void poly1305_init(poly1305_context *ctx, const unsigned char key[32]) {
poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx;
size_t i;
st->leftover = 0;
/* h = 0 */
for (i = 0; i < 17; i++)
st->h[i] = 0;
/* r &= 0xffffffc0ffffffc0ffffffc0fffffff */
st->r[ 0] = key[ 0] & 0xff;
st->r[ 1] = key[ 1] & 0xff;
st->r[ 2] = key[ 2] & 0xff;
st->r[ 3] = key[ 3] & 0x0f;
st->r[ 4] = key[ 4] & 0xfc;
st->r[ 5] = key[ 5] & 0xff;
st->r[ 6] = key[ 6] & 0xff;
st->r[ 7] = key[ 7] & 0x0f;
st->r[ 8] = key[ 8] & 0xfc;
st->r[ 9] = key[ 9] & 0xff;
st->r[10] = key[10] & 0xff;
st->r[11] = key[11] & 0x0f;
st->r[12] = key[12] & 0xfc;
st->r[13] = key[13] & 0xff;
st->r[14] = key[14] & 0xff;
st->r[15] = key[15] & 0x0f;
st->r[16] = 0;
/* save pad for later */
for (i = 0; i < 16; i++)
st->pad[i] = key[i + 16];
st->pad[16] = 0;
st->final = 0;
}
static void poly1305_add(unsigned char h[17], const unsigned char c[17]) {
unsigned short u;
unsigned int i;
for (u = 0, i = 0; i < 17; i++) {
u += (unsigned short)h[i] + (unsigned short)c[i];
h[i] = (unsigned char)u & 0xff;
u >>= 8;
}
}
static void poly1305_squeeze(unsigned char h[17], unsigned long hr[17]) {
unsigned long u;
unsigned int i;
u = 0;
for (i = 0; i < 16; i++) {
u += hr[i];
h[i] = (unsigned char)u & 0xff;
u >>= 8;
}
u += hr[16];
h[16] = (unsigned char)u & 0x03;
u >>= 2;
u += (u << 2); /* u *= 5; */
for (i = 0; i < 16; i++) {
u += h[i];
h[i] = (unsigned char)u & 0xff;
u >>= 8;
}
h[16] += (unsigned char)u;
}
static void poly1305_freeze(unsigned char h[17]) {
static const unsigned char minusp[17] = {
0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xfc
};
unsigned char horig[17], negative;
unsigned int i;
/* compute h + -p */
for (i = 0; i < 17; i++)
horig[i] = h[i];
poly1305_add(h, minusp);
/* select h if h < p, or h + -p if h >= p */
negative = -(h[16] >> 7);
for (i = 0; i < 17; i++)
h[i] ^= negative & (horig[i] ^ h[i]);
}
static void poly1305_blocks(poly1305_state_internal_t *st, const unsigned char *m, size_t bytes) {
const unsigned char hibit = st->final ^ 1; /* 1 << 128 */
while (bytes >= poly1305_block_size) {
unsigned long hr[17], u;
unsigned char c[17];
unsigned int i, j;
/* h += m */
for (i = 0; i < 16; i++)
c[i] = m[i];
c[16] = hibit;
poly1305_add(st->h, c);
/* h *= r */
for (i = 0; i < 17; i++) {
u = 0;
for (j = 0; j <= i ; j++) {
u += (unsigned short)st->h[j] * st->r[i - j];
}
for (j = i + 1; j < 17; j++) {
unsigned long v = (unsigned short)st->h[j] * st->r[i + 17 - j];
v = ((v << 8) + (v << 6)); /* v *= (5 << 6); */
u += v;
}
hr[i] = u;
}
/* (partial) h %= p */
poly1305_squeeze(st->h, hr);
m += poly1305_block_size;
bytes -= poly1305_block_size;
}
}
POLY1305_NOINLINE void poly1305_finish(poly1305_context *ctx, unsigned char mac[16]) {
poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx;
size_t i;
/* process the remaining block */
if (st->leftover) {
size_t i = st->leftover;
st->buffer[i++] = 1;
for (; i < poly1305_block_size; i++)
st->buffer[i] = 0;
st->final = 1;
poly1305_blocks(st, st->buffer, poly1305_block_size);
}
/* fully reduce h */
poly1305_freeze(st->h);
/* h = (h + pad) % (1 << 128) */
poly1305_add(st->h, st->pad);
for (i = 0; i < 16; i++)
mac[i] = st->h[i];
/* zero out the state */
for (i = 0; i < 17; i++)
st->h[i] = 0;
for (i = 0; i < 17; i++)
st->r[i] = 0;
for (i = 0; i < 17; i++)
st->pad[i] = 0;
}

View file

@ -0,0 +1,69 @@
#include "poly1305-donna.h"
/* auto detect between 32bit / 64bit */
#if /* uint128 available on 64bit system*/ \
(defined(__SIZEOF_INT128__) && defined(__LP64__)) \
/* MSVC 64bit compiler */ \
|| (defined(_MSC_VER) && defined(_M_X64)) \
/* gcc >= 4.4 64bit */ \
|| (defined(__GNUC__) && defined(__LP64__) && \
((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4))))
# define __GUESS64
#else
# define __GUESS32
#endif
#if defined(POLY1305_8BIT)
# include "poly1305-donna-8.h"
#elif defined(POLY1305_16BIT)
# include "poly1305-donna-16.h"
#elif defined(POLY1305_32BIT) || (!defined(POLY1305_64BIT) && defined(__GUESS32))
# include "poly1305-donna-32.h"
#else
# include "poly1305-donna-64.h"
#endif
void poly1305_update(poly1305_context *ctx, const unsigned char *m, size_t bytes) {
poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx;
size_t i;
/* handle leftover */
if (st->leftover) {
size_t want = (poly1305_block_size - st->leftover);
if (want > bytes)
want = bytes;
for (i = 0; i < want; i++)
st->buffer[st->leftover + i] = m[i];
bytes -= want;
m += want;
st->leftover += want;
if (st->leftover < poly1305_block_size)
return;
poly1305_blocks(st, st->buffer, poly1305_block_size);
st->leftover = 0;
}
/* process full blocks */
if (bytes >= poly1305_block_size) {
size_t want = (bytes & ~(poly1305_block_size - 1));
poly1305_blocks(st, m, want);
m += want;
bytes -= want;
}
/* store leftover */
if (bytes) {
for (i = 0; i < bytes; i++)
st->buffer[st->leftover + i] = m[i];
st->leftover += bytes;
}
}
int poly1305_verify(const unsigned char mac1[16], const unsigned char mac2[16]) {
size_t i;
unsigned int dif = 0;
for (i = 0; i < 16; i++)
dif |= (mac1[i] ^ mac2[i]);
dif = (dif - 1) >> ((sizeof(unsigned int) * 8) - 1);
return (dif & 1);
}

View file

@ -0,0 +1,18 @@
#ifndef POLY1305_DONNA_H
#define POLY1305_DONNA_H
#include <stddef.h>
typedef struct poly1305_context {
size_t aligner;
unsigned char opaque[136];
} poly1305_context;
void poly1305_init(poly1305_context *ctx, const unsigned char key[32]);
void poly1305_update(poly1305_context *ctx, const unsigned char *m, size_t bytes);
void poly1305_finish(poly1305_context *ctx, unsigned char mac[16]);
int poly1305_verify(const unsigned char mac1[16], const unsigned char mac2[16]);
#endif /* POLY1305_DONNA_H */

View file

@ -0,0 +1,119 @@
#include "portable8439.h"
#include "chacha-portable/chacha-portable.h"
#include "poly1305-donna/poly1305-donna.h"
#define __CHACHA20_BLOCK_SIZE (64)
#define __POLY1305_KEY_SIZE (32)
static uint8_t __ZEROES[16] = { 0 };
static void pad_if_needed(poly1305_context *ctx, size_t size) {
size_t padding = size % 16;
if (padding != 0) {
poly1305_update(ctx, __ZEROES, 16 - padding);
}
}
#define __u8(v) ((uint8_t)((v) & 0xFF))
// TODO: make this depending on the unaligned/native read size possible
static void write_64bit_int(poly1305_context *ctx, uint64_t value) {
uint8_t result[8];
result[0] = __u8(value);
result[1] = __u8(value >> 8);
result[2] = __u8(value >> 16);
result[3] = __u8(value >> 24);
result[4] = __u8(value >> 32);
result[5] = __u8(value >> 40);
result[6] = __u8(value >> 48);
result[7] = __u8(value >> 56);
poly1305_update(ctx, result, 8);
}
static void poly1305_calculate_mac(
uint8_t *mac,
const uint8_t *cipher_text,
size_t cipher_text_size,
const uint8_t key[RFC_8439_KEY_SIZE],
const uint8_t nonce[RFC_8439_NONCE_SIZE],
const uint8_t *ad,
size_t ad_size
) {
// init poly key (section 2.6)
uint8_t poly_key[__POLY1305_KEY_SIZE] = {0};
rfc8439_keygen(poly_key, key, nonce);
// start poly1305 mac
poly1305_context poly_ctx;
poly1305_init(&poly_ctx, poly_key);
if (ad != NULL && ad_size > 0) {
// write AD if present
poly1305_update(&poly_ctx, ad, ad_size);
pad_if_needed(&poly_ctx, ad_size);
}
// now write the cipher text
poly1305_update(&poly_ctx, cipher_text, cipher_text_size);
pad_if_needed(&poly_ctx, cipher_text_size);
// write sizes
write_64bit_int(&poly_ctx, ad_size);
write_64bit_int(&poly_ctx, cipher_text_size);
// calculate MAC
poly1305_finish(&poly_ctx, mac);
}
#define PM(p) ((uintptr_t)(p))
// pointers overlap if the smaller either ahead of the end,
// or its end is before the start of the other
//
// s_size should be smaller or equal to b_size
#define OVERLAPPING(s, s_size, b, b_size) \
(PM(s) < PM((b) + (b_size))) \
&& (PM(b) < PM((s) + (s_size)))
size_t portable_chacha20_poly1305_encrypt(
uint8_t *restrict cipher_text,
const uint8_t key[RFC_8439_KEY_SIZE],
const uint8_t nonce[RFC_8439_NONCE_SIZE],
const uint8_t *restrict ad,
size_t ad_size,
const uint8_t *restrict plain_text,
size_t plain_text_size
) {
size_t new_size = plain_text_size + RFC_8439_TAG_SIZE;
if (OVERLAPPING(plain_text, plain_text_size, cipher_text, new_size)) {
return -1;
}
chacha20_xor_stream(cipher_text, plain_text, plain_text_size, key, nonce, 1);
poly1305_calculate_mac(cipher_text + plain_text_size, cipher_text, plain_text_size, key, nonce, ad, ad_size);
return new_size;
}
size_t portable_chacha20_poly1305_decrypt(
uint8_t *restrict plain_text,
const uint8_t key[RFC_8439_KEY_SIZE],
const uint8_t nonce[RFC_8439_NONCE_SIZE],
const uint8_t *restrict ad,
size_t ad_size,
const uint8_t *restrict cipher_text,
size_t cipher_text_size
) {
// first we calculate the mac and see if it lines up, only then do we decrypt
uint8_t actual_mac[RFC_8439_TAG_SIZE];
size_t actual_size = cipher_text_size - RFC_8439_TAG_SIZE;
if (OVERLAPPING(plain_text, actual_size, cipher_text, cipher_text_size)) {
return -1;
}
poly1305_calculate_mac(actual_mac, cipher_text, actual_size, key, nonce, ad, ad_size);
if (poly1305_verify(cipher_text + actual_size, actual_mac)) {
// valid mac, so decrypt cipher_text
chacha20_xor_stream(plain_text, cipher_text, actual_size, key, nonce, 1);
return actual_size;
}
return -1;
}

View file

@ -0,0 +1,104 @@
#ifndef COMPACT_8439_H
#define COMPACT_8439_H
/*
This library implements RFC 8439 a.k.a. ChaCha20-Poly1305 AEAD
You can use this library to avoid attackers mutating or reusing your
encrypted messages. This does assume you never reuse a nonce+key pair and,
if possible, carefully pick your associated data.
*/
// Make sure we are either nested in C++ or running in a C99+ compiler
#if !defined(__cplusplus) && \
!defined(_MSC_VER) && \
(!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L)
# error "C99 or newer required"
#endif
#include <stddef.h>
#include <stdint.h>
#if CHAR_BIT > 8
# error "Systems without native octals not suppoted"
#endif
#if defined(_MSC_VER) || defined(__cplusplus)
// add restrict support is possible
# if (defined(_MSC_VER) && _MSC_VER >= 1900) \
|| defined(__clang__) || defined(__GNUC__)
# define restrict __restrict
# else
# define restrict
# endif
#endif
#define RFC_8439_TAG_SIZE (16)
#define RFC_8439_KEY_SIZE (32)
#define RFC_8439_NONCE_SIZE (12)
/*
Encrypt/Seal plain text bytes into a cipher text that can only be
decrypted by knowing the key, nonce and associated data.
input:
- key: RFC_8439_KEY_SIZE bytes that all parties have agreed
upon beforehand
- nonce: RFC_8439_NONCE_SIZE bytes that should never be repeated
for the same key. A counter or a pseudo-random value are fine.
- ad: associated data to include with calculating the tag of the
cipher text. Can be null for empty.
- plain_text: data to be encrypted, pointer + size should not overlap
with cipher_text pointer
output:
- cipher_text: encrypted plain_text with a tag appended. Make sure to
allocate at least plain_text_size + RFC_8439_TAG_SIZE
returns:
- size of bytes written to cipher_text, can be -1 if overlapping
pointers are passed for plain_text and cipher_text
*/
size_t portable_chacha20_poly1305_encrypt(
uint8_t *restrict cipher_text,
const uint8_t key[RFC_8439_KEY_SIZE],
const uint8_t nonce[RFC_8439_NONCE_SIZE],
const uint8_t *restrict ad,
size_t ad_size,
const uint8_t *restrict plain_text,
size_t plain_text_size
);
/*
Decrypt/unseal cipher text given the right key, nonce, and additional data.
input:
- key: RFC_8439_KEY_SIZE bytes that all parties have agreed
upon beforehand
- nonce: RFC_8439_NONCE_SIZE bytes that should never be repeated for
the same key. A counter or a pseudo-random value are fine.
- ad: associated data to include with calculating the tag of the
cipher text. Can be null for empty.
- cipher_text: encrypted message.
output:
- plain_text: data to be encrypted, pointer + size should not overlap
with cipher_text pointer, leave at least enough room for
cipher_text_size - RFC_8439_TAG_SIZE
returns:
- size of bytes written to plain_text, -1 signals either:
- incorrect key/nonce/ad
- corrupted cipher_text
- overlapping pointers are passed for plain_text and cipher_text
*/
size_t portable_chacha20_poly1305_decrypt(
uint8_t *restrict plain_text,
const uint8_t key[RFC_8439_KEY_SIZE],
const uint8_t nonce[RFC_8439_NONCE_SIZE],
const uint8_t *restrict ad,
size_t ad_size,
const uint8_t *restrict cipher_text,
size_t cipher_text_size
);
#endif