1# Copyright 2018 Donald Stufft and individual contributors
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14from __future__ import annotations
15
16from nacl import exceptions as exc
17from nacl._sodium import ffi, lib
18from nacl.exceptions import ensure
19
20__all__ = [
21 "crypto_kx_PUBLIC_KEY_BYTES",
22 "crypto_kx_SECRET_KEY_BYTES",
23 "crypto_kx_SEED_BYTES",
24 "crypto_kx_SESSION_KEY_BYTES",
25 "crypto_kx_client_session_keys",
26 "crypto_kx_keypair",
27 "crypto_kx_server_session_keys",
28]
29
30"""
31Implementations of client, server key exchange
32"""
33crypto_kx_PUBLIC_KEY_BYTES: int = lib.crypto_kx_publickeybytes()
34crypto_kx_SECRET_KEY_BYTES: int = lib.crypto_kx_secretkeybytes()
35crypto_kx_SEED_BYTES: int = lib.crypto_kx_seedbytes()
36crypto_kx_SESSION_KEY_BYTES: int = lib.crypto_kx_sessionkeybytes()
37
38
39def crypto_kx_keypair() -> tuple[bytes, bytes]:
40 """
41 Generate a key pair.
42 This is a duplicate crypto_box_keypair, but
43 is included for api consistency.
44 :return: (public_key, secret_key)
45 :rtype: (bytes, bytes)
46 """
47 public_key = ffi.new("unsigned char[]", crypto_kx_PUBLIC_KEY_BYTES)
48 secret_key = ffi.new("unsigned char[]", crypto_kx_SECRET_KEY_BYTES)
49 res = lib.crypto_kx_keypair(public_key, secret_key)
50 ensure(res == 0, "Key generation failed.", raising=exc.CryptoError)
51
52 return (
53 ffi.buffer(public_key, crypto_kx_PUBLIC_KEY_BYTES)[:],
54 ffi.buffer(secret_key, crypto_kx_SECRET_KEY_BYTES)[:],
55 )
56
57
58def crypto_kx_seed_keypair(seed: bytes) -> tuple[bytes, bytes]:
59 """
60 Generate a key pair with a given seed.
61 This is functionally the same as crypto_box_seed_keypair, however
62 it uses the blake2b hash primitive instead of sha512.
63 It is included mainly for api consistency when using crypto_kx.
64 :param seed: random seed
65 :type seed: bytes
66 :return: (public_key, secret_key)
67 :rtype: (bytes, bytes)
68 """
69 public_key = ffi.new("unsigned char[]", crypto_kx_PUBLIC_KEY_BYTES)
70 secret_key = ffi.new("unsigned char[]", crypto_kx_SECRET_KEY_BYTES)
71 ensure(
72 isinstance(seed, bytes) and len(seed) == crypto_kx_SEED_BYTES,
73 f"Seed must be a {crypto_kx_SEED_BYTES} byte long bytes sequence",
74 raising=exc.TypeError,
75 )
76 res = lib.crypto_kx_seed_keypair(public_key, secret_key, seed)
77 ensure(res == 0, "Key generation failed.", raising=exc.CryptoError)
78
79 return (
80 ffi.buffer(public_key, crypto_kx_PUBLIC_KEY_BYTES)[:],
81 ffi.buffer(secret_key, crypto_kx_SECRET_KEY_BYTES)[:],
82 )
83
84
85def crypto_kx_client_session_keys(
86 client_public_key: bytes,
87 client_secret_key: bytes,
88 server_public_key: bytes,
89) -> tuple[bytes, bytes]:
90 """
91 Generate session keys for the client.
92 :param client_public_key:
93 :type client_public_key: bytes
94 :param client_secret_key:
95 :type client_secret_key: bytes
96 :param server_public_key:
97 :type server_public_key: bytes
98 :return: (rx_key, tx_key)
99 :rtype: (bytes, bytes)
100 """
101 ensure(
102 isinstance(client_public_key, bytes)
103 and len(client_public_key) == crypto_kx_PUBLIC_KEY_BYTES,
104 f"Client public key must be a {crypto_kx_PUBLIC_KEY_BYTES} bytes long bytes sequence",
105 raising=exc.TypeError,
106 )
107 ensure(
108 isinstance(client_secret_key, bytes)
109 and len(client_secret_key) == crypto_kx_SECRET_KEY_BYTES,
110 f"Client secret key must be a {crypto_kx_PUBLIC_KEY_BYTES} bytes long bytes sequence",
111 raising=exc.TypeError,
112 )
113 ensure(
114 isinstance(server_public_key, bytes)
115 and len(server_public_key) == crypto_kx_PUBLIC_KEY_BYTES,
116 f"Server public key must be a {crypto_kx_PUBLIC_KEY_BYTES} bytes long bytes sequence",
117 raising=exc.TypeError,
118 )
119
120 rx_key = ffi.new("unsigned char[]", crypto_kx_SESSION_KEY_BYTES)
121 tx_key = ffi.new("unsigned char[]", crypto_kx_SESSION_KEY_BYTES)
122 res = lib.crypto_kx_client_session_keys(
123 rx_key, tx_key, client_public_key, client_secret_key, server_public_key
124 )
125 ensure(
126 res == 0,
127 "Client session key generation failed.",
128 raising=exc.CryptoError,
129 )
130
131 return (
132 ffi.buffer(rx_key, crypto_kx_SESSION_KEY_BYTES)[:],
133 ffi.buffer(tx_key, crypto_kx_SESSION_KEY_BYTES)[:],
134 )
135
136
137def crypto_kx_server_session_keys(
138 server_public_key: bytes,
139 server_secret_key: bytes,
140 client_public_key: bytes,
141) -> tuple[bytes, bytes]:
142 """
143 Generate session keys for the server.
144 :param server_public_key:
145 :type server_public_key: bytes
146 :param server_secret_key:
147 :type server_secret_key: bytes
148 :param client_public_key:
149 :type client_public_key: bytes
150 :return: (rx_key, tx_key)
151 :rtype: (bytes, bytes)
152 """
153 ensure(
154 isinstance(server_public_key, bytes)
155 and len(server_public_key) == crypto_kx_PUBLIC_KEY_BYTES,
156 f"Server public key must be a {crypto_kx_PUBLIC_KEY_BYTES} bytes long bytes sequence",
157 raising=exc.TypeError,
158 )
159 ensure(
160 isinstance(server_secret_key, bytes)
161 and len(server_secret_key) == crypto_kx_SECRET_KEY_BYTES,
162 f"Server secret key must be a {crypto_kx_PUBLIC_KEY_BYTES} bytes long bytes sequence",
163 raising=exc.TypeError,
164 )
165 ensure(
166 isinstance(client_public_key, bytes)
167 and len(client_public_key) == crypto_kx_PUBLIC_KEY_BYTES,
168 f"Client public key must be a {crypto_kx_PUBLIC_KEY_BYTES} bytes long bytes sequence",
169 raising=exc.TypeError,
170 )
171
172 rx_key = ffi.new("unsigned char[]", crypto_kx_SESSION_KEY_BYTES)
173 tx_key = ffi.new("unsigned char[]", crypto_kx_SESSION_KEY_BYTES)
174 res = lib.crypto_kx_server_session_keys(
175 rx_key, tx_key, server_public_key, server_secret_key, client_public_key
176 )
177 ensure(
178 res == 0,
179 "Server session key generation failed.",
180 raising=exc.CryptoError,
181 )
182
183 return (
184 ffi.buffer(rx_key, crypto_kx_SESSION_KEY_BYTES)[:],
185 ffi.buffer(tx_key, crypto_kx_SESSION_KEY_BYTES)[:],
186 )