Skip to content

Commit

Permalink
Added handling of Z85 encoding.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jesper Glintborg committed Jul 12, 2020
1 parent 08f132e commit 902f246
Show file tree
Hide file tree
Showing 2 changed files with 208 additions and 2 deletions.
25 changes: 25 additions & 0 deletions src/NetMQ.Tests/NetMQCertificateTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Xunit;

namespace NetMQ.Tests
{
public class NetMQCertificateTest : IClassFixture<CleanupAfterFixture>
{
public NetMQCertificateTest() => NetMQConfig.Cleanup();

[Fact]
public void X85()
{
for (int i = 0; i < 1000; i++)
{
var key = new NetMQCertificate();
var copy = new NetMQCertificate(key.SecretKeyX85, key.PublicKeyX85);

Assert.Equal(key.SecretKeyX85, copy.SecretKeyX85);
Assert.Equal(key.PublicKeyX85, copy.PublicKeyX85);

Assert.Equal(key.SecretKey, copy.SecretKey);
Assert.Equal(key.PublicKey, copy.PublicKey);
}
}
}
}
185 changes: 183 additions & 2 deletions src/NetMQ/NetMQCertificate.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NaCl;

namespace NetMQ
Expand All @@ -8,6 +10,115 @@ namespace NetMQ
/// </summary>
public class NetMQCertificate
{
// Z85 codec, taken from 0MQ RFC project, implements RFC32 Z85 encoding

// Maps base 256 to base 85
private static string Encoder = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#";

// Maps base 85 to base 256
// We chop off lower 32 and higher 128 ranges
// 0xFF denotes invalid characters within this range
private static byte[] Decoder = {
0xFF, 0x44, 0xFF, 0x54, 0x53, 0x52, 0x48, 0xFF, 0x4B, 0x4C, 0x46, 0x41,
0xFF, 0x3F, 0x3E, 0x45, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x40, 0xFF, 0x49, 0x42, 0x4A, 0x47, 0x51, 0x24, 0x25, 0x26,
0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x4D,
0xFF, 0x4E, 0x43, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C,
0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x4F, 0xFF, 0x50, 0xFF, 0xFF};

// --------------------------------------------------------------------------
// Encode a binary frame as a string; destination string MUST be at least
// size * 5 / 4 bytes long plus 1 byte for the null terminator. Returns
// dest. Size must be a multiple of 4.
// Returns NULL for invalid input.
private string Z85Encode(byte[] data)
{
if (data.Length % 4 != 0)
{
return null;
}

byte byte_nbr = 0;
UInt32 value = 0;
string dest = null;
while (byte_nbr<data.Length) {
// Accumulate value in base 256 (binary)
value = value* 256 + data[byte_nbr++];
if (byte_nbr % 4 == 0)
{
// Output value in base 85
UInt32 divisor = 85 * 85 * 85 * 85;
while (divisor != 0)
{
dest += Encoder[(int)(value / divisor % 85)];
divisor /= 85;
}
value = 0;
}
}

dest += char.MinValue;
return dest;
}


// --------------------------------------------------------------------------
// Decode an encoded string into a binary frame; dest must be at least
// strlen (string) * 4 / 5 bytes long. Returns dest. strlen (string)
// must be a multiple of 5.
// Returns NULL for invalid input.
byte[] Z85Decode(string key)
{
UInt32 byte_nbr = 0;
UInt32 char_nbr = 0;
UInt32 value = 0;
var dest_ = new List<byte>();
foreach (var cha in key.TakeWhile(c => c != char.MinValue))
{

// Accumulate value in base 85
if (UInt32.MaxValue / 85 < value)
{
// Invalid z85 encoding, represented value exceeds 0xffffffff
return null;
}
value *= 85;
char_nbr++;
var index = cha - 32;
if (index >= Decoder.Length)
{
// Invalid z85 encoding, character outside range
return null;
}
UInt32 summand = Decoder[index];
if (summand == 0xFF || summand > (UInt32.MaxValue - value))
{
// Invalid z85 encoding, invalid character or represented value exceeds 0xffffffff
return null;
}
value += summand;
if (char_nbr % 5 == 0)
{
// Output value in base 256
UInt32 divisor = 256 * 256 * 256;
while (divisor != 0)
{
dest_.Add((byte)(value / divisor % 256));
divisor /= 256;
}
value = 0;
}
}
if (char_nbr % 5 != 0)
{
return null;
}
return dest_.ToArray();

}

/// <summary>
/// Create a Certificate with a random secret key and a derived public key for the curve encryption
/// </summary>
Expand Down Expand Up @@ -36,6 +147,24 @@ public NetMQCertificate(byte[] secretKey, byte[] publicKey)
PublicKey = publicKey;
}

/// <summary>
/// Create a certificate from secret key and public key
/// </summary>
/// <param name="secretKey">Secret key</param>
/// <param name="publicKey">Public key</param>
/// <exception cref="ArgumentException">If secretKey or publicKey are not 41-chars long</exception>
public NetMQCertificate(string secretKey, string publicKey)
{
if (secretKey.Length != 41)
throw new ArgumentException("secretKey must be 41 char long");

if (publicKey.Length != 41)
throw new ArgumentException("publicKey must be 41 char long");

SecretKey = Z85Decode(secretKey);
PublicKey = Z85Decode(publicKey);
}

private NetMQCertificate(byte[] key, bool isSecret)
{
if (key.Length != 32)
Expand All @@ -49,7 +178,22 @@ private NetMQCertificate(byte[] key, bool isSecret)
else
PublicKey = key;
}


private NetMQCertificate(string keystr, bool isSecret)
{
if (keystr.Length != 41)
throw new ArgumentException("key must be 41 bytes length");

var key = Z85Decode(keystr);
if (isSecret)
{
SecretKey = key;
PublicKey = Curve25519.ScalarMultiplicationBase(key);
}
else
PublicKey = key;
}

/// <summary>
/// Create a certificate from secret key, public key is derived from the secret key
/// </summary>
Expand All @@ -60,7 +204,18 @@ public NetMQCertificate FromSecretKey(byte[] secretKey)
{
return new NetMQCertificate(secretKey, true);
}


/// <summary>
/// Create a certificate from secret key, public key is derived from the secret key
/// </summary>
/// <param name="secretKey">Secret Key</param>
/// <exception cref="ArgumentException">If secret key is not 41-chars long</exception>
/// <returns>The newly created certificate</returns>
public NetMQCertificate FromSecretKey(string secretKey)
{
return new NetMQCertificate(secretKey, true);
}

/// <summary>
/// Create a public key only certificate.
/// </summary>
Expand All @@ -72,11 +227,29 @@ public static NetMQCertificate FromPublicKey(byte[] publicKey)
return new NetMQCertificate(publicKey, false);
}

/// <summary>
/// Create a public key only certificate.
/// </summary>
/// <param name="publicKey">Public key</param>
/// <exception cref="ArgumentException">If public key is not 41-chars long</exception>
/// <returns>The newly created certificate</returns>
public static NetMQCertificate FromPublicKey(string publicKey)
{
return new NetMQCertificate(publicKey, false);
}

/// <summary>
/// Curve Secret key
/// </summary>
public byte[]? SecretKey { get; private set; }


/// <summary>
/// Curve Public key
/// </summary>
public string SecretKeyX85 => SecretKey != null ? Z85Encode(SecretKey) : null;


/// <summary>
/// Returns true if the certificate also includes a secret key
/// </summary>
Expand All @@ -86,5 +259,13 @@ public static NetMQCertificate FromPublicKey(byte[] publicKey)
/// Curve Public key
/// </summary>
public byte[] PublicKey { get; private set; }

/// <summary>
/// Curve Public key
/// </summary>
public string PublicKeyX85
{
get => Z85Encode(PublicKey);
}
}
}

0 comments on commit 902f246

Please sign in to comment.