Created
February 7, 2016 23:25
-
-
Save ayende/6ecb9e2f4efb95dd98a0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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