Skip to content

Instantly share code, notes, and snippets.

@ayende
Created February 7, 2016 23:25
Show Gist options
  • Save ayende/6ecb9e2f4efb95dd98a0 to your computer and use it in GitHub Desktop.
Save ayende/6ecb9e2f4efb95dd98a0 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using Newtonsoft.Json;
namespace ConsoleApp6
{
public class Program
{
const string license = @"{
'expiration': '2017-01-17T00:00:00.0000000',
'type': 1,
'version': 3,
'maxRamUtilization': 12,
'maxParallelism': 6,
'allowWindowsClustering': false,
'OEM': false,
'numberOfDatabases': 0,
'fips': false,
'periodicBackup': true,
'quotas': false,
'authorization': true,
'documentExpiration': true,
'replication': true,
'versioning': true,
'maxSizeInGb': 0,
'ravenfs': true,
'encryption': true,
'compression': false,
'updatesExpiration': '2017-01-17T00:00:00.0000000',
}";
public static void Main(string[] args)
{
using (var dsa = new DSACryptoServiceProvider())
{
var exportCspBlob = dsa.ExportCspBlob(true);
var licenseGeneratorAndValidator = new LicenseGeneratorAndValidator(exportCspBlob, exportCspBlob);
var licenseAttributes = JsonConvert.DeserializeObject<Dictionary<string, object>>(license);
foreach (var key in licenseAttributes.Keys.ToArray())
{
var val = licenseAttributes[key] as string;
if (val == null)
continue;
Guid guid;
if (Guid.TryParse(val, out guid))
{
licenseAttributes[key] = guid;
continue;
}
DateTime time;
if (DateTime.TryParse(val, out time))
{
licenseAttributes[key] = time;
}
}
var name = "Hibernating Rhinos";
var result = licenseGeneratorAndValidator.Generate(name, licenseAttributes);
var dictionary = licenseGeneratorAndValidator.Validate(name, result);
Console.WriteLine(JsonConvert.SerializeObject(dictionary, Formatting.Indented));
}
}
}
public class LicenseGeneratorAndValidator
{
private readonly byte[] _publicKey;
private readonly byte[] _privateKey;
public static string[] Terms =
{
"id","expiration","type","version","maxRamUtilization","maxParallelism",
"allowWindowsClustering","OEM","numberOfDatabases","fips","periodicBackup",
"quotas","authorization","documentExpiration","replication","versioning",
"maxSizeInGb","ravenfs","encryption","compression","updatesExpiration",
};
public enum ValueType : byte
{
False = 0,
True = 1,
Int = 2,
Date = 3,
}
public LicenseGeneratorAndValidator(byte[] publicKey, byte[] privateKey)
{
if (Terms.Length > 32)
throw new InvalidOperationException("Too many terms");
_publicKey = publicKey;
_privateKey = privateKey;
}
public static DateTime FromDosDate(ushort number)
{
var year = (number >> 9) + 1980;
var month = (number & 0x01e0) >> 5;
var day = number & 0x1F;
return new DateTime(year, month, day);
}
public static ushort ToDosDateTime(DateTime dateTime)
{
uint day = (uint)dateTime.Day; // Between 1 and 31
uint month = (uint)dateTime.Month; // Between 1 and 12
uint years = (uint)(dateTime.Year - 1980); // From 1980
if (years > 127)
throw new ArgumentOutOfRangeException(nameof(dateTime), "Cannot represent this year.");
uint dosDateTime = 0;
dosDateTime |= day << (16 - 16);
dosDateTime |= month << (21 - 16);
dosDateTime |= years << (25 - 16);
return unchecked((ushort)dosDateTime);
}
const int TypeBitsToShift = 5;
public byte[] Generate(string name, Dictionary<string, object> attributes)
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);
foreach (var attribute in attributes)
{
if (attribute.Value == null)
throw new InvalidOperationException("Cannot write a null value");
var index = Array.IndexOf(Terms, attribute.Key);
if (index == -1)
throw new InvalidOperationException("Unknown term " + attribute.Key);
if (attribute.Value is bool)
{
var type = (byte)((bool)attribute.Value ? ValueType.True : ValueType.False) << TypeBitsToShift;
bw.Write((byte)((byte)index | type));
continue;
}
if (attribute.Value is DateTime)
{
bw.Write((byte)((byte)index | ((byte)ValueType.Date << TypeBitsToShift)));
var dt = (DateTime)(attribute.Value);
bw.Write(ToDosDateTime(dt));
continue;
}
if (attribute.Value is int || attribute.Value is long)
{
var val = Convert.ToByte(attribute.Value);
bw.Write((byte)((byte)index | ((byte)ValueType.Int << TypeBitsToShift)));
bw.Write((byte)val);
continue;
}
throw new InvalidOperationException("Cannot understand type of " + attribute.Key + " because it is " + attribute.Value.GetType().FullName);
}
var attributesLen = ms.Position;
bw.Write(name);
using (var sha1 = SHA1.Create())
{
ms.Position = 0;
var hash = sha1.ComputeHash(ms);
using (var dsa = new DSACryptoServiceProvider())
{
dsa.ImportCspBlob(_privateKey);
ms.SetLength(attributesLen);
var signature = dsa.CreateSignature(hash);
bw.Write(signature);
return ms.ToArray();
}
}
}
public Dictionary<string, object> Validate(string name, byte[] license)
{
var result = new Dictionary<string, object>();
var ms = new MemoryStream(license);
var br = new BinaryReader(ms);
const int sizeOfDsaSignature = 40;
while (ms.Position < ms.Length - sizeOfDsaSignature)
{
var token = ms.ReadByte();
var index = token & 0x1F;
object val;
var curr = (ValueType)(token>> TypeBitsToShift);
switch (curr)
{
case ValueType.False:
val = false;
break;
case ValueType.True:
val = true;
break;
case ValueType.Int:
val = (int) br.ReadByte();
break;
case ValueType.Date:
val = FromDosDate(br.ReadUInt16());
break;
default:
throw new ArgumentOutOfRangeException();
}
if (index >= Terms.Length)
continue; // new field, just skip
result[Terms[index]] = val;
}
var attributesLen = ms.Position;
var signature = br.ReadBytes(40);
ms.SetLength(attributesLen);
new BinaryWriter(ms).Write(name);
using (var dsa = new DSACryptoServiceProvider())
{
dsa.ImportCspBlob(_publicKey);
using (var sha1 = SHA1.Create())
{
ms.Position = 0;
var hash = sha1.ComputeHash(ms);
if (dsa.VerifySignature(hash, signature) == false)
throw new InvalidDataException("Could not validate signature on license");
}
}
return result;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment