Skip to content

Commit 902f246

Browse files
author
Jesper Glintborg
committed
Added handling of Z85 encoding.
1 parent 08f132e commit 902f246

File tree

2 files changed

+208
-2
lines changed

2 files changed

+208
-2
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Xunit;
2+
3+
namespace NetMQ.Tests
4+
{
5+
public class NetMQCertificateTest : IClassFixture<CleanupAfterFixture>
6+
{
7+
public NetMQCertificateTest() => NetMQConfig.Cleanup();
8+
9+
[Fact]
10+
public void X85()
11+
{
12+
for (int i = 0; i < 1000; i++)
13+
{
14+
var key = new NetMQCertificate();
15+
var copy = new NetMQCertificate(key.SecretKeyX85, key.PublicKeyX85);
16+
17+
Assert.Equal(key.SecretKeyX85, copy.SecretKeyX85);
18+
Assert.Equal(key.PublicKeyX85, copy.PublicKeyX85);
19+
20+
Assert.Equal(key.SecretKey, copy.SecretKey);
21+
Assert.Equal(key.PublicKey, copy.PublicKey);
22+
}
23+
}
24+
}
25+
}

src/NetMQ/NetMQCertificate.cs

Lines changed: 183 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
24
using NaCl;
35

46
namespace NetMQ
@@ -8,6 +10,115 @@ namespace NetMQ
810
/// </summary>
911
public class NetMQCertificate
1012
{
13+
// Z85 codec, taken from 0MQ RFC project, implements RFC32 Z85 encoding
14+
15+
// Maps base 256 to base 85
16+
private static string Encoder = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#";
17+
18+
// Maps base 85 to base 256
19+
// We chop off lower 32 and higher 128 ranges
20+
// 0xFF denotes invalid characters within this range
21+
private static byte[] Decoder = {
22+
0xFF, 0x44, 0xFF, 0x54, 0x53, 0x52, 0x48, 0xFF, 0x4B, 0x4C, 0x46, 0x41,
23+
0xFF, 0x3F, 0x3E, 0x45, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
24+
0x08, 0x09, 0x40, 0xFF, 0x49, 0x42, 0x4A, 0x47, 0x51, 0x24, 0x25, 0x26,
25+
0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
26+
0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x4D,
27+
0xFF, 0x4E, 0x43, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
28+
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C,
29+
0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x4F, 0xFF, 0x50, 0xFF, 0xFF};
30+
31+
// --------------------------------------------------------------------------
32+
// Encode a binary frame as a string; destination string MUST be at least
33+
// size * 5 / 4 bytes long plus 1 byte for the null terminator. Returns
34+
// dest. Size must be a multiple of 4.
35+
// Returns NULL for invalid input.
36+
private string Z85Encode(byte[] data)
37+
{
38+
if (data.Length % 4 != 0)
39+
{
40+
return null;
41+
}
42+
43+
byte byte_nbr = 0;
44+
UInt32 value = 0;
45+
string dest = null;
46+
while (byte_nbr<data.Length) {
47+
// Accumulate value in base 256 (binary)
48+
value = value* 256 + data[byte_nbr++];
49+
if (byte_nbr % 4 == 0)
50+
{
51+
// Output value in base 85
52+
UInt32 divisor = 85 * 85 * 85 * 85;
53+
while (divisor != 0)
54+
{
55+
dest += Encoder[(int)(value / divisor % 85)];
56+
divisor /= 85;
57+
}
58+
value = 0;
59+
}
60+
}
61+
62+
dest += char.MinValue;
63+
return dest;
64+
}
65+
66+
67+
// --------------------------------------------------------------------------
68+
// Decode an encoded string into a binary frame; dest must be at least
69+
// strlen (string) * 4 / 5 bytes long. Returns dest. strlen (string)
70+
// must be a multiple of 5.
71+
// Returns NULL for invalid input.
72+
byte[] Z85Decode(string key)
73+
{
74+
UInt32 byte_nbr = 0;
75+
UInt32 char_nbr = 0;
76+
UInt32 value = 0;
77+
var dest_ = new List<byte>();
78+
foreach (var cha in key.TakeWhile(c => c != char.MinValue))
79+
{
80+
81+
// Accumulate value in base 85
82+
if (UInt32.MaxValue / 85 < value)
83+
{
84+
// Invalid z85 encoding, represented value exceeds 0xffffffff
85+
return null;
86+
}
87+
value *= 85;
88+
char_nbr++;
89+
var index = cha - 32;
90+
if (index >= Decoder.Length)
91+
{
92+
// Invalid z85 encoding, character outside range
93+
return null;
94+
}
95+
UInt32 summand = Decoder[index];
96+
if (summand == 0xFF || summand > (UInt32.MaxValue - value))
97+
{
98+
// Invalid z85 encoding, invalid character or represented value exceeds 0xffffffff
99+
return null;
100+
}
101+
value += summand;
102+
if (char_nbr % 5 == 0)
103+
{
104+
// Output value in base 256
105+
UInt32 divisor = 256 * 256 * 256;
106+
while (divisor != 0)
107+
{
108+
dest_.Add((byte)(value / divisor % 256));
109+
divisor /= 256;
110+
}
111+
value = 0;
112+
}
113+
}
114+
if (char_nbr % 5 != 0)
115+
{
116+
return null;
117+
}
118+
return dest_.ToArray();
119+
120+
}
121+
11122
/// <summary>
12123
/// Create a Certificate with a random secret key and a derived public key for the curve encryption
13124
/// </summary>
@@ -36,6 +147,24 @@ public NetMQCertificate(byte[] secretKey, byte[] publicKey)
36147
PublicKey = publicKey;
37148
}
38149

150+
/// <summary>
151+
/// Create a certificate from secret key and public key
152+
/// </summary>
153+
/// <param name="secretKey">Secret key</param>
154+
/// <param name="publicKey">Public key</param>
155+
/// <exception cref="ArgumentException">If secretKey or publicKey are not 41-chars long</exception>
156+
public NetMQCertificate(string secretKey, string publicKey)
157+
{
158+
if (secretKey.Length != 41)
159+
throw new ArgumentException("secretKey must be 41 char long");
160+
161+
if (publicKey.Length != 41)
162+
throw new ArgumentException("publicKey must be 41 char long");
163+
164+
SecretKey = Z85Decode(secretKey);
165+
PublicKey = Z85Decode(publicKey);
166+
}
167+
39168
private NetMQCertificate(byte[] key, bool isSecret)
40169
{
41170
if (key.Length != 32)
@@ -49,7 +178,22 @@ private NetMQCertificate(byte[] key, bool isSecret)
49178
else
50179
PublicKey = key;
51180
}
52-
181+
182+
private NetMQCertificate(string keystr, bool isSecret)
183+
{
184+
if (keystr.Length != 41)
185+
throw new ArgumentException("key must be 41 bytes length");
186+
187+
var key = Z85Decode(keystr);
188+
if (isSecret)
189+
{
190+
SecretKey = key;
191+
PublicKey = Curve25519.ScalarMultiplicationBase(key);
192+
}
193+
else
194+
PublicKey = key;
195+
}
196+
53197
/// <summary>
54198
/// Create a certificate from secret key, public key is derived from the secret key
55199
/// </summary>
@@ -60,7 +204,18 @@ public NetMQCertificate FromSecretKey(byte[] secretKey)
60204
{
61205
return new NetMQCertificate(secretKey, true);
62206
}
63-
207+
208+
/// <summary>
209+
/// Create a certificate from secret key, public key is derived from the secret key
210+
/// </summary>
211+
/// <param name="secretKey">Secret Key</param>
212+
/// <exception cref="ArgumentException">If secret key is not 41-chars long</exception>
213+
/// <returns>The newly created certificate</returns>
214+
public NetMQCertificate FromSecretKey(string secretKey)
215+
{
216+
return new NetMQCertificate(secretKey, true);
217+
}
218+
64219
/// <summary>
65220
/// Create a public key only certificate.
66221
/// </summary>
@@ -72,11 +227,29 @@ public static NetMQCertificate FromPublicKey(byte[] publicKey)
72227
return new NetMQCertificate(publicKey, false);
73228
}
74229

230+
/// <summary>
231+
/// Create a public key only certificate.
232+
/// </summary>
233+
/// <param name="publicKey">Public key</param>
234+
/// <exception cref="ArgumentException">If public key is not 41-chars long</exception>
235+
/// <returns>The newly created certificate</returns>
236+
public static NetMQCertificate FromPublicKey(string publicKey)
237+
{
238+
return new NetMQCertificate(publicKey, false);
239+
}
240+
75241
/// <summary>
76242
/// Curve Secret key
77243
/// </summary>
78244
public byte[]? SecretKey { get; private set; }
79245

246+
247+
/// <summary>
248+
/// Curve Public key
249+
/// </summary>
250+
public string SecretKeyX85 => SecretKey != null ? Z85Encode(SecretKey) : null;
251+
252+
80253
/// <summary>
81254
/// Returns true if the certificate also includes a secret key
82255
/// </summary>
@@ -86,5 +259,13 @@ public static NetMQCertificate FromPublicKey(byte[] publicKey)
86259
/// Curve Public key
87260
/// </summary>
88261
public byte[] PublicKey { get; private set; }
262+
263+
/// <summary>
264+
/// Curve Public key
265+
/// </summary>
266+
public string PublicKeyX85
267+
{
268+
get => Z85Encode(PublicKey);
269+
}
89270
}
90271
}

0 commit comments

Comments
 (0)