File: test_aead.py

package info (click to toggle)
python-nacl 1.5.0-7
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 14,776 kB
  • sloc: ansic: 45,889; python: 7,249; sh: 6,752; asm: 2,974; makefile: 1,011; cs: 35; xml: 30; pascal: 11
file content (159 lines) | stat: -rw-r--r-- 5,732 bytes parent folder | download | duplicates (2)
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
# Copyright 2016 Donald Stufft and individual contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import binascii
from typing import Callable, Dict, List, NamedTuple, Optional

from hypothesis import given, settings
from hypothesis.strategies import binary, sampled_from

import pytest

import nacl.bindings as b
import nacl.exceptions as exc

from .utils import read_kv_test_vectors


def chacha20poly1305_agl_vectors() -> List[Dict[str, bytes]]:
    # NIST vectors derived format
    DATA = "chacha20-poly1305-agl_ref.txt"
    return read_kv_test_vectors(DATA, delimiter=b":", newrecord=b"AEAD")


def chacha20poly1305_ietf_vectors() -> List[Dict[str, bytes]]:
    # NIST vectors derived format
    DATA = "chacha20-poly1305-ietf_ref.txt"
    return read_kv_test_vectors(DATA, delimiter=b":", newrecord=b"AEAD")


def xchacha20poly1305_ietf_vectors() -> List[Dict[str, bytes]]:
    # NIST vectors derived format
    DATA = "xchacha20-poly1305-ietf_ref.txt"
    return read_kv_test_vectors(DATA, delimiter=b":", newrecord=b"AEAD")


class Construction(NamedTuple):
    encrypt: Callable[[bytes, Optional[bytes], bytes, bytes], bytes]
    decrypt: Callable[[bytes, Optional[bytes], bytes, bytes], bytes]
    NPUB: int
    KEYBYTES: int


def _getconstruction(construction: bytes) -> Construction:
    if construction == b"chacha20-poly1305-old":
        encrypt = b.crypto_aead_chacha20poly1305_encrypt
        decrypt = b.crypto_aead_chacha20poly1305_decrypt
        NPUB = b.crypto_aead_chacha20poly1305_NPUBBYTES
        KEYBYTES = b.crypto_aead_chacha20poly1305_KEYBYTES
    elif construction == b"chacha20-poly1305":
        encrypt = b.crypto_aead_chacha20poly1305_ietf_encrypt
        decrypt = b.crypto_aead_chacha20poly1305_ietf_decrypt
        NPUB = b.crypto_aead_chacha20poly1305_ietf_NPUBBYTES
        KEYBYTES = b.crypto_aead_chacha20poly1305_ietf_KEYBYTES
    else:
        encrypt = b.crypto_aead_xchacha20poly1305_ietf_encrypt
        decrypt = b.crypto_aead_xchacha20poly1305_ietf_decrypt
        NPUB = b.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
        KEYBYTES = b.crypto_aead_xchacha20poly1305_ietf_KEYBYTES

    return Construction(encrypt, decrypt, NPUB, KEYBYTES)


@pytest.mark.parametrize(
    "kv",
    chacha20poly1305_agl_vectors()
    + chacha20poly1305_ietf_vectors()
    + xchacha20poly1305_ietf_vectors(),
)
def test_chacha20poly1305_variants_kat(kv: Dict[str, bytes]):
    msg = binascii.unhexlify(kv["IN"])
    ad = binascii.unhexlify(kv["AD"])
    nonce = binascii.unhexlify(kv["NONCE"])
    k = binascii.unhexlify(kv["KEY"])
    c = _getconstruction(kv["AEAD"])
    _tag = kv.get("TAG", b"")
    exp = binascii.unhexlify(kv["CT"]) + binascii.unhexlify(_tag)
    out = c.encrypt(msg, ad, nonce, k)
    assert out == exp


@given(
    sampled_from(
        (b"chacha20-poly1305-old", b"chacha20-poly1305", b"xchacha20-poly1305")
    ),
    binary(min_size=0, max_size=100),
    binary(min_size=0, max_size=50),
    binary(
        min_size=b.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
        max_size=b.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
    ),
    binary(
        min_size=b.crypto_aead_chacha20poly1305_KEYBYTES,
        max_size=b.crypto_aead_chacha20poly1305_KEYBYTES,
    ),
)
@settings(deadline=None, max_examples=20)
def test_chacha20poly1305_variants_roundtrip(
    construction: bytes, message: bytes, aad: bytes, nonce: bytes, key: bytes
):

    c = _getconstruction(construction)
    unonce = nonce[: c.NPUB]

    ct = c.encrypt(message, aad, unonce, key)
    pt = c.decrypt(ct, aad, unonce, key)

    assert pt == message
    with pytest.raises(exc.CryptoError):
        ct1 = bytearray(ct)
        ct1[0] = ct1[0] ^ 0xFF
        c.decrypt(ct1, aad, unonce, key)


@pytest.mark.parametrize(
    "construction",
    [b"chacha20-poly1305-old", b"chacha20-poly1305", b"xchacha20-poly1305"],
)
def test_chacha20poly1305_variants_wrong_params(construction: bytes):
    c = _getconstruction(construction)
    nonce = b"\x00" * c.NPUB
    key = b"\x00" * c.KEYBYTES
    aad = None
    c.encrypt(b"", aad, nonce, key)
    # The first two checks call encrypt with a nonce/key that's too short. Otherwise,
    # the types are fine. (TODO: Should this raise ValueError rather than TypeError?
    # Doing so would be a breaking change.)
    with pytest.raises(exc.TypeError):
        c.encrypt(b"", aad, nonce[:-1], key)
    with pytest.raises(exc.TypeError):
        c.encrypt(b"", aad, nonce, key[:-1])
    # Type safety: mypy spots these next two errors, but we want to check that they're
    # spotted at runtime too.
    with pytest.raises(exc.TypeError):
        c.encrypt(b"", aad, nonce.decode("utf-8"), key)  # type: ignore[arg-type]
    with pytest.raises(exc.TypeError):
        c.encrypt(b"", aad, nonce, key.decode("utf-8"))  # type: ignore[arg-type]


@pytest.mark.parametrize(
    "construction",
    [b"chacha20-poly1305-old", b"chacha20-poly1305", b"xchacha20-poly1305"],
)
def test_chacha20poly1305_variants_str_msg(construction: bytes):
    c = _getconstruction(construction)
    nonce = b"\x00" * c.NPUB
    key = b"\x00" * c.KEYBYTES
    aad = None
    with pytest.raises(exc.TypeError):
        c.encrypt("", aad, nonce, key)  # type: ignore[arg-type]