Skip to content

Commit 037e0a4

Browse files
committed
First draft of parsec plugin.
1 parent 1d8f306 commit 037e0a4

32 files changed

+2702
-68
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace Chaos.NaCl
5+
{
6+
internal static class CryptoBytes
7+
{
8+
public static bool ConstantTimeEquals(byte[] x, byte[] y)
9+
{
10+
if (x == null)
11+
throw new ArgumentNullException("x");
12+
if (y == null)
13+
throw new ArgumentNullException("y");
14+
if (x.Length != y.Length)
15+
throw new ArgumentException("x.Length must equal y.Length");
16+
return InternalConstantTimeEquals(x, 0, y, 0, x.Length) != 0;
17+
}
18+
19+
public static bool ConstantTimeEquals(ArraySegment<byte> x, ArraySegment<byte> y)
20+
{
21+
if (x.Array == null)
22+
throw new ArgumentNullException("x.Array");
23+
if (y.Array == null)
24+
throw new ArgumentNullException("y.Array");
25+
if (x.Count != y.Count)
26+
throw new ArgumentException("x.Count must equal y.Count");
27+
28+
return InternalConstantTimeEquals(x.Array, x.Offset, y.Array, y.Offset, x.Count) != 0;
29+
}
30+
31+
public static bool ConstantTimeEquals(byte[] x, int xOffset, byte[] y, int yOffset, int length)
32+
{
33+
if (x == null)
34+
throw new ArgumentNullException("x");
35+
if (xOffset < 0)
36+
throw new ArgumentOutOfRangeException("xOffset", "xOffset < 0");
37+
if (y == null)
38+
throw new ArgumentNullException("y");
39+
if (yOffset < 0)
40+
throw new ArgumentOutOfRangeException("yOffset", "yOffset < 0");
41+
if (length < 0)
42+
throw new ArgumentOutOfRangeException("length", "length < 0");
43+
if (x.Length - xOffset < length)
44+
throw new ArgumentException("xOffset + length > x.Length");
45+
if (y.Length - yOffset < length)
46+
throw new ArgumentException("yOffset + length > y.Length");
47+
48+
return InternalConstantTimeEquals(x, xOffset, y, yOffset, length) != 0;
49+
}
50+
51+
private static uint InternalConstantTimeEquals(byte[] x, int xOffset, byte[] y, int yOffset, int length)
52+
{
53+
int differentbits = 0;
54+
for (int i = 0; i < length; i++)
55+
differentbits |= x[xOffset + i] ^ y[yOffset + i];
56+
return (1 & (unchecked((uint) differentbits - 1) >> 8));
57+
}
58+
59+
public static void Wipe(byte[] data)
60+
{
61+
if (data == null)
62+
throw new ArgumentNullException("data");
63+
InternalWipe(data, 0, data.Length);
64+
}
65+
66+
public static void Wipe(byte[] data, int offset, int count)
67+
{
68+
if (data == null)
69+
throw new ArgumentNullException("data");
70+
if (offset < 0)
71+
throw new ArgumentOutOfRangeException("offset");
72+
if (count < 0)
73+
throw new ArgumentOutOfRangeException("count", "Requires count >= 0");
74+
if ((uint) offset + (uint) count > (uint) data.Length)
75+
throw new ArgumentException("Requires offset + count <= data.Length");
76+
InternalWipe(data, offset, count);
77+
}
78+
79+
public static void Wipe(ArraySegment<byte> data)
80+
{
81+
if (data.Array == null)
82+
throw new ArgumentNullException("data.Array");
83+
InternalWipe(data.Array, data.Offset, data.Count);
84+
}
85+
86+
// Secure wiping is hard
87+
// * the GC can move around and copy memory
88+
// Perhaps this can be avoided by using unmanaged memory or by fixing the position of the array in memory
89+
// * Swap files and error dumps can contain secret information
90+
// It seems possible to lock memory in RAM, no idea about error dumps
91+
// * Compiler could optimize out the wiping if it knows that data won't be read back
92+
// I hope this is enough, suppressing inlining
93+
// but perhaps `RtlSecureZeroMemory` is needed
94+
[MethodImpl(MethodImplOptions.NoInlining)]
95+
internal static void InternalWipe(byte[] data, int offset, int count)
96+
{
97+
Array.Clear(data, offset, count);
98+
}
99+
100+
// shallow wipe of structs
101+
[MethodImpl(MethodImplOptions.NoInlining)]
102+
internal static void InternalWipe<T>(ref T data)
103+
where T : struct
104+
{
105+
data = default(T);
106+
}
107+
108+
// constant time hex conversion
109+
// see http://stackoverflow.com/a/14333437/445517
110+
//
111+
// An explanation of the weird bit fiddling:
112+
//
113+
// 1. `bytes[i] >> 4` extracts the high nibble of a byte
114+
// `bytes[i] & 0xF` extracts the low nibble of a byte
115+
// 2. `b - 10`
116+
// is `< 0` for values `b < 10`, which will become a decimal digit
117+
// is `>= 0` for values `b > 10`, which will become a letter from `A` to `F`.
118+
// 3. Using `i >> 31` on a signed 32 bit integer extracts the sign, thanks to sign extension.
119+
// It will be `-1` for `i < 0` and `0` for `i >= 0`.
120+
// 4. Combining 2) and 3), shows that `(b-10)>>31` will be `0` for letters and `-1` for digits.
121+
// 5. Looking at the case for letters, the last summand becomes `0`, and `b` is in the range 10 to 15. We want to map it to `A`(65) to `F`(70), which implies adding 55 (`'A'-10`).
122+
// 6. Looking at the case for digits, we want to adapt the last summand so it maps `b` from the range 0 to 9 to the range `0`(48) to `9`(57). This means it needs to become -7 (`'0' - 55`).
123+
// Now we could just multiply with 7. But since -1 is represented by all bits being 1, we can instead use `& -7` since `(0 & -7) == 0` and `(-1 & -7) == -7`.
124+
//
125+
// Some further considerations:
126+
//
127+
// * I didn't use a second loop variable to index into `c`, since measurement shows that calculating it from `i` is cheaper.
128+
// * Using exactly `i < bytes.Length` as upper bound of the loop allows the JITter to eliminate bounds checks on `bytes[i]`, so I chose that variant.
129+
// * Making `b` an int avoids unnecessary conversions from and to byte.
130+
public static string ToHexStringUpper(byte[] data)
131+
{
132+
if (data == null)
133+
return null;
134+
char[] c = new char[data.Length * 2];
135+
int b;
136+
for (int i = 0; i < data.Length; i++)
137+
{
138+
b = data[i] >> 4;
139+
c[i * 2] = (char) (55 + b + (((b - 10) >> 31) & -7));
140+
b = data[i] & 0xF;
141+
c[i * 2 + 1] = (char) (55 + b + (((b - 10) >> 31) & -7));
142+
}
143+
return new string(c);
144+
}
145+
146+
// Explanation is similar to ToHexStringUpper
147+
// constant 55 -> 87 and -7 -> -39 to compensate for the offset 32 between lowercase and uppercase letters
148+
public static string ToHexStringLower(byte[] data)
149+
{
150+
if (data == null)
151+
return null;
152+
char[] c = new char[data.Length * 2];
153+
int b;
154+
for (int i = 0; i < data.Length; i++)
155+
{
156+
b = data[i] >> 4;
157+
c[i * 2] = (char) (87 + b + (((b - 10) >> 31) & -39));
158+
b = data[i] & 0xF;
159+
c[i * 2 + 1] = (char) (87 + b + (((b - 10) >> 31) & -39));
160+
}
161+
return new string(c);
162+
}
163+
164+
public static byte[] FromHexString(string hexString)
165+
{
166+
if (hexString == null)
167+
return null;
168+
if (hexString.Length % 2 != 0)
169+
throw new FormatException("The hex string is invalid because it has an odd length");
170+
var result = new byte[hexString.Length / 2];
171+
for (int i = 0; i < result.Length; i++)
172+
result[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
173+
return result;
174+
}
175+
176+
public static string ToBase64String(byte[] data)
177+
{
178+
if (data == null)
179+
return null;
180+
return Convert.ToBase64String(data);
181+
}
182+
183+
public static byte[] FromBase64String(string s)
184+
{
185+
if (s == null)
186+
return null;
187+
return Convert.FromBase64String(s);
188+
}
189+
}
190+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using System;
2+
using System.Security.Cryptography;
3+
using Chaos.NaCl.Internal.Ed25519Ref10;
4+
5+
namespace Chaos.NaCl
6+
{
7+
internal static class Ed25519
8+
{
9+
public static readonly int PublicKeySizeInBytes = 32;
10+
public static readonly int SignatureSizeInBytes = 64;
11+
public static readonly int ExpandedPrivateKeySizeInBytes = 32 * 2;
12+
public static readonly int PrivateKeySeedSizeInBytes = 32;
13+
public static readonly int SharedKeySizeInBytes = 32;
14+
15+
public static bool Verify(ArraySegment<byte> signature, ArraySegment<byte> message, ArraySegment<byte> publicKey)
16+
{
17+
if (signature.Count != SignatureSizeInBytes)
18+
throw new ArgumentException(string.Format("Signature size must be {0}", SignatureSizeInBytes), "signature.Count");
19+
if (publicKey.Count != PublicKeySizeInBytes)
20+
throw new ArgumentException(string.Format("Public key size must be {0}", PublicKeySizeInBytes), "publicKey.Count");
21+
return Ed25519Operations.crypto_sign_verify(signature.Array, signature.Offset, message.Array, message.Offset, message.Count, publicKey.Array, publicKey.Offset);
22+
}
23+
24+
public static bool Verify(byte[] signature, byte[] message, byte[] publicKey)
25+
{
26+
if (signature == null)
27+
throw new ArgumentNullException("signature");
28+
if (message == null)
29+
throw new ArgumentNullException("message");
30+
if (publicKey == null)
31+
throw new ArgumentNullException("publicKey");
32+
if (signature.Length != SignatureSizeInBytes)
33+
throw new ArgumentException(string.Format("Signature size must be {0}", SignatureSizeInBytes), "signature.Length");
34+
if (publicKey.Length != PublicKeySizeInBytes)
35+
throw new ArgumentException(string.Format("Public key size must be {0}", PublicKeySizeInBytes), "publicKey.Length");
36+
return Ed25519Operations.crypto_sign_verify(signature, 0, message, 0, message.Length, publicKey, 0);
37+
}
38+
39+
public static void Sign(ArraySegment<byte> signature, ArraySegment<byte> message, ArraySegment<byte> expandedPrivateKey)
40+
{
41+
if (signature.Array == null)
42+
throw new ArgumentNullException("signature.Array");
43+
if (signature.Count != SignatureSizeInBytes)
44+
throw new ArgumentException("signature.Count");
45+
if (expandedPrivateKey.Array == null)
46+
throw new ArgumentNullException("expandedPrivateKey.Array");
47+
if (expandedPrivateKey.Count != ExpandedPrivateKeySizeInBytes)
48+
throw new ArgumentException("expandedPrivateKey.Count");
49+
if (message.Array == null)
50+
throw new ArgumentNullException("message.Array");
51+
Ed25519Operations.crypto_sign2(signature.Array, signature.Offset, message.Array, message.Offset, message.Count, expandedPrivateKey.Array, expandedPrivateKey.Offset);
52+
}
53+
54+
public static byte[] Sign(byte[] message, byte[] expandedPrivateKey)
55+
{
56+
var signature = new byte[SignatureSizeInBytes];
57+
Sign(new ArraySegment<byte>(signature), new ArraySegment<byte>(message), new ArraySegment<byte>(expandedPrivateKey));
58+
return signature;
59+
}
60+
61+
public static byte[] PublicKeyFromSeed(byte[] privateKeySeed)
62+
{
63+
byte[] privateKey;
64+
byte[] publicKey;
65+
KeyPairFromSeed(out publicKey, out privateKey, privateKeySeed);
66+
CryptoBytes.Wipe(privateKey);
67+
return publicKey;
68+
}
69+
70+
public static byte[] ExpandedPrivateKeyFromSeed(byte[] privateKeySeed)
71+
{
72+
byte[] privateKey;
73+
byte[] publicKey;
74+
KeyPairFromSeed(out publicKey, out privateKey, privateKeySeed);
75+
CryptoBytes.Wipe(publicKey);
76+
return privateKey;
77+
}
78+
79+
public static void KeyPairFromSeed(out byte[] publicKey, out byte[] expandedPrivateKey, byte[] privateKeySeed)
80+
{
81+
if (privateKeySeed == null)
82+
throw new ArgumentNullException("privateKeySeed");
83+
if (privateKeySeed.Length != PrivateKeySeedSizeInBytes)
84+
throw new ArgumentException("privateKeySeed");
85+
var pk = new byte[PublicKeySizeInBytes];
86+
var sk = new byte[ExpandedPrivateKeySizeInBytes];
87+
Ed25519Operations.crypto_sign_keypair(pk, 0, sk, 0, privateKeySeed, 0);
88+
publicKey = pk;
89+
expandedPrivateKey = sk;
90+
}
91+
92+
public static void KeyPairFromSeed(ArraySegment<byte> publicKey, ArraySegment<byte> expandedPrivateKey, ArraySegment<byte> privateKeySeed)
93+
{
94+
if (publicKey.Array == null)
95+
throw new ArgumentNullException("publicKey.Array");
96+
if (expandedPrivateKey.Array == null)
97+
throw new ArgumentNullException("expandedPrivateKey.Array");
98+
if (privateKeySeed.Array == null)
99+
throw new ArgumentNullException("privateKeySeed.Array");
100+
if (publicKey.Count != PublicKeySizeInBytes)
101+
throw new ArgumentException("publicKey.Count");
102+
if (expandedPrivateKey.Count != ExpandedPrivateKeySizeInBytes)
103+
throw new ArgumentException("expandedPrivateKey.Count");
104+
if (privateKeySeed.Count != PrivateKeySeedSizeInBytes)
105+
throw new ArgumentException("privateKeySeed.Count");
106+
Ed25519Operations.crypto_sign_keypair(
107+
publicKey.Array, publicKey.Offset,
108+
expandedPrivateKey.Array, expandedPrivateKey.Offset,
109+
privateKeySeed.Array, privateKeySeed.Offset);
110+
}
111+
}
112+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace Chaos.NaCl.Internal
5+
{
6+
// Array16<UInt32> Salsa20 state
7+
// Array16<UInt64> SHA-512 block
8+
internal struct Array16<T>
9+
{
10+
public T x0;
11+
public T x1;
12+
public T x2;
13+
public T x3;
14+
public T x4;
15+
public T x5;
16+
public T x6;
17+
public T x7;
18+
public T x8;
19+
public T x9;
20+
public T x10;
21+
public T x11;
22+
public T x12;
23+
public T x13;
24+
public T x14;
25+
public T x15;
26+
}
27+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
3+
namespace Chaos.NaCl.Internal
4+
{
5+
// Array8<UInt32> Poly1305 key
6+
// Array8<UInt64> SHA-512 state/output
7+
internal struct Array8<T>
8+
{
9+
public T x0;
10+
public T x1;
11+
public T x2;
12+
public T x3;
13+
public T x4;
14+
public T x5;
15+
public T x6;
16+
public T x7;
17+
}
18+
}

0 commit comments

Comments
 (0)