1 /* Copyright (c) 2012-2013, The Tor Project, Inc. */
2 /* See LICENSE for licensing information */
7 #define ONION_NTOR_PRIVATE
8 #include "onion_ntor.h"
12 /** Free storage held in an ntor handshake state. */
14 ntor_handshake_state_free(ntor_handshake_state_t
*state
)
18 memwipe(state
, 0, sizeof(*state
));
22 /** Convenience function to represent HMAC_SHA256 as our instantiation of
23 * ntor's "tweaked hash'. Hash the <b>inp_len</b> bytes at <b>inp</b> into
24 * a DIGEST256_LEN-byte digest at <b>out</b>, with the hash changing
25 * depending on the value of <b>tweak</b>. */
28 const uint8_t *inp
, size_t inp_len
,
31 size_t tweak_len
= strlen(tweak
);
32 crypto_hmac_sha256((char*)out
, tweak
, tweak_len
, (const char*)inp
, inp_len
);
35 /** Wrapper around a set of tweak-values for use with the ntor handshake. */
36 typedef struct tweakset_t
{
43 /** The tweaks to be used with our handshake. */
44 const tweakset_t proto1_tweaks
= {
45 #define PROTOID "ntor-curve25519-sha256-1"
46 #define PROTOID_LEN 24
48 PROTOID
":key_extract",
53 /** Convenience macro: copy <b>len</b> bytes from <b>inp</b> to <b>ptr</b>,
54 * and advance <b>ptr</b> by the number of bytes copied. */
55 #define APPEND(ptr, inp, len) \
57 memcpy(ptr, (inp), (len)); \
62 * Compute the first client-side step of the ntor handshake for communicating
63 * with a server whose DIGEST_LEN-byte server identity is <b>router_id</b>,
64 * and whose onion key is <b>router_key</b>. Store the NTOR_ONIONSKIN_LEN-byte
65 * message in <b>onion_skin_out</b>, and store the handshake state in
66 * *<b>handshake_state_out</b>. Return 0 on success, -1 on failure.
69 onion_skin_ntor_create(const uint8_t *router_id
,
70 const curve25519_public_key_t
*router_key
,
71 ntor_handshake_state_t
**handshake_state_out
,
72 uint8_t *onion_skin_out
)
74 ntor_handshake_state_t
*state
;
77 state
= tor_malloc_zero(sizeof(ntor_handshake_state_t
));
79 memcpy(state
->router_id
, router_id
, DIGEST_LEN
);
80 memcpy(&state
->pubkey_B
, router_key
, sizeof(curve25519_public_key_t
));
81 if (curve25519_secret_key_generate(&state
->seckey_x
, 0) < 0) {
85 curve25519_public_key_generate(&state
->pubkey_X
, &state
->seckey_x
);
88 APPEND(op
, router_id
, DIGEST_LEN
);
89 APPEND(op
, router_key
->public_key
, CURVE25519_PUBKEY_LEN
);
90 APPEND(op
, state
->pubkey_X
.public_key
, CURVE25519_PUBKEY_LEN
);
91 tor_assert(op
== onion_skin_out
+ NTOR_ONIONSKIN_LEN
);
93 *handshake_state_out
= state
;
98 #define SERVER_STR "Server"
99 #define SERVER_STR_LEN 6
101 #define SECRET_INPUT_LEN (CURVE25519_PUBKEY_LEN * 3 + \
102 CURVE25519_OUTPUT_LEN * 2 + \
103 DIGEST_LEN + PROTOID_LEN)
104 #define AUTH_INPUT_LEN (DIGEST256_LEN + DIGEST_LEN + \
105 CURVE25519_PUBKEY_LEN*3 + \
106 PROTOID_LEN + SERVER_STR_LEN)
109 * Perform the server side of an ntor handshake. Given an
110 * NTOR_ONIONSKIN_LEN-byte message in <b>onion_skin</b>, our own identity
111 * fingerprint as <b>my_node_id</b>, and an associative array mapping public
112 * onion keys to curve25519_keypair_t in <b>private_keys</b>, attempt to
113 * perform the handshake. Use <b>junk_keys</b> if present if the handshake
114 * indicates an unrecognized public key. Write an NTOR_REPLY_LEN-byte
115 * message to send back to the client into <b>handshake_reply_out</b>, and
116 * generate <b>key_out_len</b> bytes of key material in <b>key_out</b>. Return
117 * 0 on success, -1 on failure.
120 onion_skin_ntor_server_handshake(const uint8_t *onion_skin
,
121 const di_digest256_map_t
*private_keys
,
122 const curve25519_keypair_t
*junk_keys
,
123 const uint8_t *my_node_id
,
124 uint8_t *handshake_reply_out
,
128 const tweakset_t
*T
= &proto1_tweaks
;
129 /* Sensitive stack-allocated material. Kept in an anonymous struct to make
130 * it easy to wipe. */
132 uint8_t secret_input
[SECRET_INPUT_LEN
];
133 uint8_t auth_input
[AUTH_INPUT_LEN
];
134 curve25519_public_key_t pubkey_X
;
135 curve25519_secret_key_t seckey_y
;
136 curve25519_public_key_t pubkey_Y
;
137 uint8_t verify
[DIGEST256_LEN
];
139 uint8_t *si
= s
.secret_input
, *ai
= s
.auth_input
;
140 const curve25519_keypair_t
*keypair_bB
;
143 /* Decode the onion skin */
144 /* XXXX Does this possible early-return business threaten our security? */
145 if (tor_memneq(onion_skin
, my_node_id
, DIGEST_LEN
))
147 /* Note that on key-not-found, we go through with this operation anyway,
148 * using "junk_keys". This will result in failed authentication, but won't
149 * leak whether we recognized the key. */
150 keypair_bB
= dimap_search(private_keys
, onion_skin
+ DIGEST_LEN
,
155 memcpy(s
.pubkey_X
.public_key
, onion_skin
+DIGEST_LEN
+DIGEST256_LEN
,
156 CURVE25519_PUBKEY_LEN
);
159 curve25519_secret_key_generate(&s
.seckey_y
, 0);
160 curve25519_public_key_generate(&s
.pubkey_Y
, &s
.seckey_y
);
162 /* NOTE: If we ever use a group other than curve25519, or a different
163 * representation for its points, we may need to perform different or
164 * additional checks on X here and on Y in the client handshake, or lose our
165 * security properties. What checks we need would depend on the properties
166 * of the group and its representation.
168 * In short: if you use anything other than curve25519, this aspect of the
169 * code will need to be reconsidered carefully. */
171 /* build secret_input */
172 curve25519_handshake(si
, &s
.seckey_y
, &s
.pubkey_X
);
173 bad
= safe_mem_is_zero(si
, CURVE25519_OUTPUT_LEN
);
174 si
+= CURVE25519_OUTPUT_LEN
;
175 curve25519_handshake(si
, &keypair_bB
->seckey
, &s
.pubkey_X
);
176 bad
|= safe_mem_is_zero(si
, CURVE25519_OUTPUT_LEN
);
177 si
+= CURVE25519_OUTPUT_LEN
;
179 APPEND(si
, my_node_id
, DIGEST_LEN
);
180 APPEND(si
, keypair_bB
->pubkey
.public_key
, CURVE25519_PUBKEY_LEN
);
181 APPEND(si
, s
.pubkey_X
.public_key
, CURVE25519_PUBKEY_LEN
);
182 APPEND(si
, s
.pubkey_Y
.public_key
, CURVE25519_PUBKEY_LEN
);
183 APPEND(si
, PROTOID
, PROTOID_LEN
);
184 tor_assert(si
== s
.secret_input
+ sizeof(s
.secret_input
));
186 /* Compute hashes of secret_input */
187 h_tweak(s
.verify
, s
.secret_input
, sizeof(s
.secret_input
), T
->t_verify
);
189 /* Compute auth_input */
190 APPEND(ai
, s
.verify
, DIGEST256_LEN
);
191 APPEND(ai
, my_node_id
, DIGEST_LEN
);
192 APPEND(ai
, keypair_bB
->pubkey
.public_key
, CURVE25519_PUBKEY_LEN
);
193 APPEND(ai
, s
.pubkey_Y
.public_key
, CURVE25519_PUBKEY_LEN
);
194 APPEND(ai
, s
.pubkey_X
.public_key
, CURVE25519_PUBKEY_LEN
);
195 APPEND(ai
, PROTOID
, PROTOID_LEN
);
196 APPEND(ai
, SERVER_STR
, SERVER_STR_LEN
);
197 tor_assert(ai
== s
.auth_input
+ sizeof(s
.auth_input
));
199 /* Build the reply */
200 memcpy(handshake_reply_out
, s
.pubkey_Y
.public_key
, CURVE25519_PUBKEY_LEN
);
201 h_tweak(handshake_reply_out
+CURVE25519_PUBKEY_LEN
,
202 s
.auth_input
, sizeof(s
.auth_input
),
205 /* Generate the key material */
206 crypto_expand_key_material_rfc5869_sha256(
207 s
.secret_input
, sizeof(s
.secret_input
),
208 (const uint8_t*)T
->t_key
, strlen(T
->t_key
),
209 (const uint8_t*)T
->m_expand
, strlen(T
->m_expand
),
210 key_out
, key_out_len
);
212 /* Wipe all of our local state */
213 memwipe(&s
, 0, sizeof(s
));
219 * Perform the final client side of the ntor handshake, using the state in
220 * <b>handshake_state</b> and the server's NTOR_REPLY_LEN-byte reply in
221 * <b>handshake_reply</b>. Generate <b>key_out_len</b> bytes of key material
222 * in <b>key_out</b>. Return 0 on success, -1 on failure.
225 onion_skin_ntor_client_handshake(
226 const ntor_handshake_state_t
*handshake_state
,
227 const uint8_t *handshake_reply
,
231 const tweakset_t
*T
= &proto1_tweaks
;
232 /* Sensitive stack-allocated material. Kept in an anonymous struct to make
233 * it easy to wipe. */
235 curve25519_public_key_t pubkey_Y
;
236 uint8_t secret_input
[SECRET_INPUT_LEN
];
237 uint8_t verify
[DIGEST256_LEN
];
238 uint8_t auth_input
[AUTH_INPUT_LEN
];
239 uint8_t auth
[DIGEST256_LEN
];
241 uint8_t *ai
= s
.auth_input
, *si
= s
.secret_input
;
242 const uint8_t *auth_candidate
;
246 memcpy(s
.pubkey_Y
.public_key
, handshake_reply
, CURVE25519_PUBKEY_LEN
);
247 auth_candidate
= handshake_reply
+ CURVE25519_PUBKEY_LEN
;
249 /* See note in server_handshake above about checking points. The
250 * circumstances under which we'd need to check Y for membership are
251 * different than those under which we'd be checking X. */
253 /* Compute secret_input */
254 curve25519_handshake(si
, &handshake_state
->seckey_x
, &s
.pubkey_Y
);
255 bad
= safe_mem_is_zero(si
, CURVE25519_OUTPUT_LEN
);
256 si
+= CURVE25519_OUTPUT_LEN
;
257 curve25519_handshake(si
, &handshake_state
->seckey_x
,
258 &handshake_state
->pubkey_B
);
259 bad
|= (safe_mem_is_zero(si
, CURVE25519_OUTPUT_LEN
) << 1);
260 si
+= CURVE25519_OUTPUT_LEN
;
261 APPEND(si
, handshake_state
->router_id
, DIGEST_LEN
);
262 APPEND(si
, handshake_state
->pubkey_B
.public_key
, CURVE25519_PUBKEY_LEN
);
263 APPEND(si
, handshake_state
->pubkey_X
.public_key
, CURVE25519_PUBKEY_LEN
);
264 APPEND(si
, s
.pubkey_Y
.public_key
, CURVE25519_PUBKEY_LEN
);
265 APPEND(si
, PROTOID
, PROTOID_LEN
);
266 tor_assert(si
== s
.secret_input
+ sizeof(s
.secret_input
));
268 /* Compute verify from secret_input */
269 h_tweak(s
.verify
, s
.secret_input
, sizeof(s
.secret_input
), T
->t_verify
);
271 /* Compute auth_input */
272 APPEND(ai
, s
.verify
, DIGEST256_LEN
);
273 APPEND(ai
, handshake_state
->router_id
, DIGEST_LEN
);
274 APPEND(ai
, handshake_state
->pubkey_B
.public_key
, CURVE25519_PUBKEY_LEN
);
275 APPEND(ai
, s
.pubkey_Y
.public_key
, CURVE25519_PUBKEY_LEN
);
276 APPEND(ai
, handshake_state
->pubkey_X
.public_key
, CURVE25519_PUBKEY_LEN
);
277 APPEND(ai
, PROTOID
, PROTOID_LEN
);
278 APPEND(ai
, SERVER_STR
, SERVER_STR_LEN
);
279 tor_assert(ai
== s
.auth_input
+ sizeof(s
.auth_input
));
282 h_tweak(s
.auth
, s
.auth_input
, sizeof(s
.auth_input
), T
->t_mac
);
284 bad
|= (tor_memneq(s
.auth
, auth_candidate
, DIGEST256_LEN
) << 2);
286 crypto_expand_key_material_rfc5869_sha256(
287 s
.secret_input
, sizeof(s
.secret_input
),
288 (const uint8_t*)T
->t_key
, strlen(T
->t_key
),
289 (const uint8_t*)T
->m_expand
, strlen(T
->m_expand
),
290 key_out
, key_out_len
);
292 memwipe(&s
, 0, sizeof(s
));
295 log_warn(LD_PROTOCOL
, "Invalid result from curve25519 handshake: %d", bad
);