License serialization is not platform independent
Creating a license and serializing it should be platform independent.
By using StringBuilder.AppendLine() in the serialization and Environment.NewLine in the deserialization part, an exception "License file has not a valid format." is thrown if serialization and deserialization happen on platforms with different NewLine characters (e.g. Linux and Windows).
The line break characters should be fixed or configurable to allow interoperability.
The usage of DateTime.MaxValue is not platform independent as well. https://github.com/JanDotNet/ThinkSharp.Licensing/blob/f68a6ee77bd50f2423464c2bd815f93035738ef7/ThinkSharp.Licensing/SignedLicense.cs#L79 A license generated by a Linux server without ExpirationDate will be incorrectly recognized as having an expiration date on a Windows client.
Creating a license and serializing it should be platform independent.
By using
StringBuilder.AppendLine()in the serialization andEnvironment.NewLinein the deserialization part, an exception "License file has not a valid format." is thrown if serialization and deserialization happen on platforms with different NewLine characters (e.g. Linux and Windows).The line break characters should be fixed or configurable to allow interoperability.
Same issue here with Linux signer and Windows verifier.
"License file has not a valid format." can be solved by manually replacing Environment.NewLine in deserialization part. Even then I got "Signature of license file is not valid.".
This is because the linebreaks are encoded twice in the final license base64 representation. I was able to get Linux signer and Windows verifier working by first decoding the license code manually and then providing a custom RSA signer that fixes verification. Overall a very ugly workaround for something that should be handled by the library.
// Since the licenseCode may be generated on a Linux platform, we have to manually replace the new line characters before verifying.
var licenseText = ThinkSharpCrossPlatformHelpers.ReplaceLinuxNewLines(licenseCode);
var license = Lic.Verifier
.WithCrossPlatformRsaSigner(PublicKey) // Same problem as described above -> we need a custom RSA signer that converts the linux linebreaks.
.WithApplicationCode(ApplicationCode)
.LoadAndVerify(licenseText);
internal static class ThinkSharpCrossPlatformHelpers
{
private class CrossPlatformRsaSigner : ISigner
{
private readonly RSACryptoServiceProvider _cryptoServiceProvider;
private readonly HashAlgorithm _hashAlgorithm;
private CrossPlatformRsaSigner()
{
_cryptoServiceProvider = new RSACryptoServiceProvider();
_hashAlgorithm = SHA512.Create();
}
public CrossPlatformRsaSigner(RSAParameters rsaParameters)
: this()
{
_cryptoServiceProvider.ImportParameters(rsaParameters);
}
public CrossPlatformRsaSigner(string base64EncodedCsbBlobKey)
: this(Convert.FromBase64String(base64EncodedCsbBlobKey))
{
}
public CrossPlatformRsaSigner(byte[] csbBlobKey)
: this()
{
_cryptoServiceProvider.ImportCspBlob(csbBlobKey);
}
public bool Verify(string content, string signature)
{
content = content.Replace("\r\n", "\n");
var bytesContent = Encoding.UTF8.GetBytes(content);
var bytesSignature = Convert.FromBase64String(signature);
UnConfuse(bytesSignature);
return _cryptoServiceProvider.VerifyData(bytesContent, _hashAlgorithm, bytesSignature);
}
public string Sign(string content)
{
var bytes = Encoding.UTF8.GetBytes(content);
var signature = _cryptoServiceProvider.SignData(bytes, _hashAlgorithm);
UnConfuse(signature);
var base64Signing = Convert.ToBase64String(signature);
return base64Signing;
}
private static void UnConfuse(byte[] bytes)
{
var confusingBytes = new byte[] { 2, 43, 2, 54, 199, 3, 43 };
for (int i = 0; i < bytes.Length; i++)
bytes[i] ^= confusingBytes[i % confusingBytes.Length];
}
}
public static string ReplaceLinuxNewLines(string licenseCode)
{
licenseCode = licenseCode.Unwrap();
licenseCode = Dencrypt(licenseCode);
var lines = licenseCode.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
return string.Join(Environment.NewLine, lines);
string Dencrypt(string input)
{
var confusingBytes = new byte[] { 32, 45, 12, 43, 33, 1 };
var bytes = Convert.FromBase64String(input);
for (int i = 0; i < bytes.Length; i++)
bytes[i] ^= confusingBytes[i % confusingBytes.Length];
return Encoding.UTF8.GetString(bytes);
}
}
public static IVerifier_ApplicationCode WithCrossPlatformRsaSigner(this IVerifier_Signer signer, string base64EncodedCsbBlobKey)
{
return signer.WithSigner(new CrossPlatformRsaSigner(base64EncodedCsbBlobKey));
}
}
Thanks for analyzing the issue and providing a workaround. I'll include it in the library.
Issue has been fixed: Now, the verifier tries both variants, one with \r\n and one with \r as line breaks. If one of the variants is valid, the licence will be accepted as valid. The fix will be part of Release 1.1.0.
@quadrat, @V0odo0 : Do you have a linux specific implementation of the IComputerCharacteristics interface you want to share?