Skip to content

Commit 226359c

Browse files
authored
x.crypto.curve25519: fix possible double free, add consistencies check, cleanup dead test code (#24762)
1 parent 99be39c commit 226359c

File tree

2 files changed

+24
-55
lines changed

2 files changed

+24
-55
lines changed

vlib/x/crypto/curve25519/curve25519.v

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ pub fn (mut pv PrivateKey) x25519(point []u8) ![]u8 {
101101
if point.len != point_size {
102102
return error('bad point size, should be 32')
103103
}
104+
// We reject and disallow zero-bytes point to be passed
105+
// and check it here as a quick exit before heavy math
106+
// calculation on `x25519` call
107+
if is_zero(point) {
108+
return error('x25519: get zeros point')
109+
}
104110
// even technically its possible, but we limit to unallow it
105111
if subtle.constant_time_compare(pv.key, point) == 1 {
106112
return error('pv.key identical with point')
@@ -121,6 +127,12 @@ pub fn (pv PrivateKey) bytes() ![]u8 {
121127
// free releases underlying key. Dont use the key after calling .free
122128
@[unsafe]
123129
pub fn (mut pv PrivateKey) free() {
130+
// when private key has been marked as done (freed),
131+
// calling free on already freed key would lead to undefined behavior.
132+
// so, we check it
133+
if pv.done {
134+
return
135+
}
124136
unsafe { pv.key.free() }
125137
// sets flag to finish
126138
pv.done = true
@@ -143,7 +155,7 @@ pub fn PublicKey.new_from_bytes(bytes []u8) !&PublicKey {
143155
// in curve25519 is generally not needed for Diffie-Hellman key exchange.
144156
// See https://cr.yp.to/ecdh.html#validate
145157
// But there are availables suggestion to do validation on them spreads on the internet, likes
146-
// bytes return the clone of the bytes of the underlying PublicKey
158+
// - blacklisting the known bad public keys
147159
// - check the shared value and to raise exception if it is zero.
148160
// - You can also bind the exchanged public keys to the shared keys, i.e.,
149161
// instead of using H(abG) as the shared keys, you should use H(aG || bG || abG)
@@ -209,6 +221,7 @@ fn (rd RawDerivator) derive(sec []u8, opt DeriveOpts) ![]u8 {
209221
// 6. Diffie-Hellman with Curve25519
210222
// See https://datatracker.ietf.org/doc/html/rfc7748#section-6
211223
pub fn derive_shared_secret(mut local PrivateKey, remote PublicKey, opt SharedOpts) ![]u8 {
224+
// TODO: should this check be relaxed ?
212225
// check for safety
213226
local_pubkey := local.public_key()!
214227
if local_pubkey.equal(remote) {
@@ -220,6 +233,8 @@ pub fn derive_shared_secret(mut local PrivateKey, remote PublicKey, opt SharedOp
220233
// Both now share shared = X25519(local_privkey, remote_pubkey) = X25519(remote_privkey, local_pubkey)
221234
// as a shared secret, which is then used as a key or input to a key derivation function.
222235
sec := local.x25519(remote.key)!
236+
// Internally, x25519 has builtin check for zeros result
237+
// but only for non base point branch on x25519_generic routine
223238
if is_zero(sec) {
224239
return error('zeroes shared secret')
225240
}
@@ -247,6 +262,11 @@ pub fn derive_shared_secret(mut local PrivateKey, remote PublicKey, opt SharedOp
247262
// be either `base_point` or the output of another `x25519` call.
248263
@[direct_array_access]
249264
pub fn x25519(mut scalar []u8, point []u8) ![]u8 {
265+
// likes the previous comment, we add zeroes point check here
266+
// and reject if it happen.
267+
if is_zero(point) || is_zero(scalar) {
268+
return error('x25519: unallowed zeros/scalar point')
269+
}
250270
mut dst := []u8{len: 32}
251271
// we do bytes clamping here, to make sure scalar was ready to use
252272
clamp(mut scalar)!
@@ -264,8 +284,8 @@ fn x25519_generic(mut dst []u8, mut scalar []u8, point []u8) ![]u8 {
264284
return error('dst: get base_point')
265285
}
266286
} else {
267-
// base := point.clone()
268287
scalar_mult(mut dst, mut scalar, point)!
288+
// check for zeros point result
269289
if is_zero_point(dst) {
270290
return error('bad input point: low order point')
271291
}

vlib/x/crypto/curve25519/curve25519_test.v

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,10 @@ struct LowOrderPoint {
9999
}
100100

101101
const loworder_points = [
102+
// zeros point
102103
LowOrderPoint{[u8(0x00), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
103104
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
104-
0x00, 0x00, 0x00, 0x00, 0x00], error('bad input point: low order point')},
105+
0x00, 0x00, 0x00, 0x00, 0x00], error('x25519: unallowed zeros/scalar point')},
105106
LowOrderPoint{[u8(0x01), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
106107
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
107108
0x00, 0x00, 0x00, 0x00, 0x00], error('bad input point: low order point')},
@@ -354,55 +355,3 @@ const tests_vectors = [
354355
0x61, 0x96, 0x1e, 0xfc, 0xb]
355356
},
356357
]
357-
358-
// Please be aware, this is long running times test, its loop until 1.000.000 times.
359-
// so, please be patient. See the detail of the type 2 test from rfc
360-
// see at https://datatracker.ietf.org/doc/html/rfc7748#section-5.2
361-
//
362-
// Currently, this test was disabled due to its takes long time to complete,
363-
// please uncomment it if you need run the test.
364-
// On my test machine, its pass successfully in 2225590.282 ms
365-
// ```v
366-
// ... (previous line)
367-
// ...
368-
// start i: 999995
369-
// start i: 999996
370-
// start i: 999997
371-
// start i: 999998
372-
// start i: 999999
373-
//
374-
// OK 2225590.282 ms 3 asserts | curve25519.test_x25519_after_iteration_from_rfc_vector_type2()
375-
// Summary for running V tests in "curve25519_test.v": 3 passed, 3 total. Elapsed time: 2225590 ms.
376-
// ```
377-
/*
378-
fn test_x25519_after_iteration_from_rfc_vector_type2() ! {
379-
iteration1 := hex.decode('422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079')!
380-
iteration1000 := hex.decode('684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51') !
381-
iteration1000000 := hex.decode('7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424') !
382-
383-
// Initially, set k and u to be the following values
384-
key := '0900000000000000000000000000000000000000000000000000000000000000'
385-
mut k := hex.decode(key)!
386-
mut u := k.clone()
387-
mut r := []u8{len: 32}
388-
389-
// For each iteration, set k to be the result of calling the function and u
390-
// to be the old value of k. The final result is the value left in k.
391-
//
392-
for i in 0..1000000 {
393-
println("start i: $i")
394-
tmp_k := k.clone()
395-
r = x25519(mut k, u) !
396-
unsafe {u = tmp_k}
397-
unsafe {k = r}
398-
if i == 0 {
399-
assert k == iteration1
400-
} else if i == 999 {
401-
assert k == iteration1000
402-
} else if i == 999999 {
403-
assert k == iteration1000000
404-
}
405-
406-
}
407-
}
408-
*/

0 commit comments

Comments
 (0)