Skip to content

Commit 04cf0b7

Browse files
v-rudkovskiyViacheslav Rudkovskyi
and
Viacheslav Rudkovskyi
authored
Add offline validation flag support (#38)
* add offline validation flag support * add offline validation support * improve paths * cleanup --------- Co-authored-by: Viacheslav Rudkovskyi <viachaslau.rudkovski@labs64.de>
1 parent 67479c1 commit 04cf0b7

File tree

9 files changed

+197
-80
lines changed

9 files changed

+197
-80
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><ns2:netlicensing xmlns="http://www.w3.org/2000/09/xmldsig#" xmlns:ns2="http://netlicensing.labs64.com/schema/context" ttl="2020-12-28T22:53:06.681Z"><Signature><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>ICC1F7ZKeQoayrzn1iGtw7Zt96s=</DigestValue></Reference></SignedInfo><SignatureValue>jYb0aGBPR9Rn4HVRJhBsIRafzW1k3D/GF7GZ62nT8dwvJszGB0kEXdFrXTj7FhUeiBcBlqxcsEUUKA5XbE7LGbNm+V+f5xZu/+n/d/miufKPAIk6CN5xoZQLDRGRSruW0LiKJsnkbzHvIF4u6G/HUyQpUfrxwiqXb+tYFpiaMgcSHL5xGzRq5Mqusw21Cdq81MhUb9oUoujgisIZWqNyebOqWuzOjNJOy0y2uml4I5U5tBAL1OlrQKjfPUiy369HwMl37A7dR9oVcahb6/YPI039xeLTn+WzyFPIV7f41o6Ytq4Iefl6swj/elNKTHi9bYzcnjmM7ebOd+hinm8feA==</SignatureValue></Signature><ns2:infos/><ns2:items><ns2:item type="ProductModuleValidation"><ns2:property name="productModuleNumber">Msb-DEMO</ns2:property><ns2:property name="valid">true</ns2:property><ns2:property name="expires">2021-04-21T20:15:45.694Z</ns2:property><ns2:property name="productModuleName">Module using "Subscription" licensing model</ns2:property><ns2:property name="licensingModel">Subscription</ns2:property></ns2:item></ns2:items></ns2:netlicensing>

NetLicensingClient-demo/NetLicensingClient-demo.cs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.IO;
34
using System.Linq;
45
using System.Runtime.InteropServices;
6+
using System.Security.Cryptography.X509Certificates;
7+
using System.Text;
8+
using System.Xml;
9+
using System.Xml.Linq;
10+
using System.Xml.Serialization;
511
using NetLicensingClient.Entities;
12+
using NetLicensingClient.Utils;
613

714
namespace NetLicensingClient
815
{
@@ -262,7 +269,7 @@ static int Main(string[] args)
262269
15OWo6dArRCyNUeROu+9Jp4tET+2RA/PuqQbWW6EbJWBhpkqNdzi9CYuhuUjvk8m
263270
dwIDAQAB
264271
-----END PUBLIC KEY-----";
265-
string publicKey_wrong = @"-----BEGIN PUBLIC KEY-----
272+
string publicKeyWrong = @"-----BEGIN PUBLIC KEY-----
266273
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtq96itv1m00/czFD7IzE
267274
mLiXPpvok1vjqB9VU6kTkq6QFGAfJF1G+m1fbK5NvpiDCsfuofdCFuhVnvLnzrpd
268275
xUlse8erWEr9p9RAyh25NMK9/v0MAEAYV7zRa+ZOh31G54DwR7zk0TxyVzxKpjPi
@@ -273,7 +280,7 @@ static int Main(string[] args)
273280
-----END PUBLIC KEY-----";
274281
Console.WriteLine("loaded privateKey: {0}", privateKey);
275282
Console.WriteLine("loaded publicKey: {0}", publicKey);
276-
Console.WriteLine("loaded publicKey_wrong: {0}", publicKey_wrong);
283+
Console.WriteLine("loaded publicKey_wrong: {0}", publicKeyWrong);
277284

278285
//NetLicensing supports API Key Identification to allow limited API access on vendor's behalf.
279286
//See: https://netlicensing.io/wiki/security for details.
@@ -337,7 +344,7 @@ static int Main(string[] args)
337344

338345
// Validate using APIKey wrongly signed
339346
context.securityMode = SecurityMode.APIKEY_IDENTIFICATION;
340-
context.publicKey = publicKey_wrong;
347+
context.publicKey = publicKeyWrong;
341348
try
342349
{
343350
validationResult = LicenseeService.validate(context, demoLicenseeNumber, validationParameters);
@@ -354,6 +361,30 @@ static int Main(string[] args)
354361

355362
#endregion
356363

364+
#region ****************** Offline validation
365+
Context offlineContext = new Context();
366+
offlineContext.publicKey = publicKey;
367+
368+
string validationFile = @"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?><ns2:netlicensing xmlns=""http://www.w3.org/2000/09/xmldsig#"" xmlns:ns2=""http://netlicensing.labs64.com/schema/context"" ttl=""2020-12-28T22:53:06.681Z""><Signature><SignedInfo><CanonicalizationMethod Algorithm=""http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments""/><SignatureMethod Algorithm=""http://www.w3.org/2000/09/xmldsig#rsa-sha1""/><Reference URI=""""><Transforms><Transform Algorithm=""http://www.w3.org/2000/09/xmldsig#enveloped-signature""/></Transforms><DigestMethod Algorithm=""http://www.w3.org/2000/09/xmldsig#sha1""/><DigestValue>ICC1F7ZKeQoayrzn1iGtw7Zt96s=</DigestValue></Reference></SignedInfo><SignatureValue>jYb0aGBPR9Rn4HVRJhBsIRafzW1k3D/GF7GZ62nT8dwvJszGB0kEXdFrXTj7FhUeiBcBlqxcsEUUKA5XbE7LGbNm+V+f5xZu/+n/d/miufKPAIk6CN5xoZQLDRGRSruW0LiKJsnkbzHvIF4u6G/HUyQpUfrxwiqXb+tYFpiaMgcSHL5xGzRq5Mqusw21Cdq81MhUb9oUoujgisIZWqNyebOqWuzOjNJOy0y2uml4I5U5tBAL1OlrQKjfPUiy369HwMl37A7dR9oVcahb6/YPI039xeLTn+WzyFPIV7f41o6Ytq4Iefl6swj/elNKTHi9bYzcnjmM7ebOd+hinm8feA==</SignatureValue></Signature><ns2:infos/><ns2:items><ns2:item type=""ProductModuleValidation""><ns2:property name=""productModuleNumber"">Msb-DEMO</ns2:property><ns2:property name=""valid"">true</ns2:property><ns2:property name=""expires"">2021-04-21T20:15:45.694Z</ns2:property><ns2:property name=""productModuleName"">Module using ""Subscription"" licensing model</ns2:property><ns2:property name=""licensingModel"">Subscription</ns2:property></ns2:item></ns2:items></ns2:netlicensing>";
369+
370+
Console.WriteLine("Offline validation file: {0}", validationFile);
371+
372+
ValidationResult validationOfflineResult = ValidationService.validateOffline(offlineContext, validationFile);
373+
ConsoleWriter.WriteEntity("Offline validation result (Basic Auth):", validationOfflineResult);
374+
375+
offlineContext.publicKey = publicKeyWrong;
376+
377+
try
378+
{
379+
validationOfflineResult = ValidationService.validateOffline(offlineContext, validationFile);
380+
}
381+
catch (NetLicensingException e)
382+
{
383+
Console.WriteLine("Offline validation result exception (APIKey / wrongly signed): {0}", e);
384+
}
385+
386+
#endregion
387+
357388
#region ****************** Transfer
358389

359390
Licensee transferLicensee = new Licensee();

NetLicensingClient-demo/NetLicensingClient-demo.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>

NetLicensingClient/Entities/Constants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ public class Shop
149149
public const String PROP_SHOPPING_CART = "shopping-cart";
150150
}
151151

152+
public class Validation
153+
{
154+
public const String FOR_OFFLINE_USE = "forOfflineUse";
155+
}
156+
152157
public class ValidationResult
153158
{
154159
internal const String VALIDATION_RESULT_TYPE = "ProductModuleValidation";

NetLicensingClient/Entities/ValidationParameters.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace NetLicensingClient.Entities
66
public class ValidationParameters : IEntity
77
{
88
private String productNumber;
9+
private Boolean forOfflineUse = false;
910
private Dictionary<String, String> licenseeProperties;
1011
private Dictionary<String, Dictionary<String, String>> parameters;
1112

@@ -19,6 +20,16 @@ public String getProductNumber ()
1920
return productNumber;
2021
}
2122

23+
public void setForOfflineUse(Boolean forOfflineUse)
24+
{
25+
this.forOfflineUse = forOfflineUse;
26+
}
27+
28+
public Boolean isForOfflineUse()
29+
{
30+
return forOfflineUse;
31+
}
32+
2233
public Dictionary<String, String> getLicenseeProperties() {
2334
return licenseeProperties;
2435
}

NetLicensingClient/LicenseeService.cs

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -100,32 +100,8 @@ public static ValidationResult validate(Context context, String number, String p
100100
/// See NetLicensingAPI for details: https://netlicensing.io/wiki/licensee-services#validate-licensee
101101
/// </summary>
102102
public static ValidationResult validate(Context context, String number, ValidationParameters validationParameters, int timeoutInMilliseconds = 100000)
103-
{
104-
Dictionary<String, String> parameters = new Dictionary<String, String> ();
105-
if (!String.IsNullOrEmpty(validationParameters.getProductNumber()))
106-
{
107-
parameters.Add(Constants.Product.PRODUCT_NUMBER, validationParameters.getProductNumber());
108-
}
109-
110-
foreach (KeyValuePair<String, String> property in validationParameters.getLicenseeProperties())
111-
{
112-
if (!String.IsNullOrEmpty(property.Key))
113-
{
114-
parameters.Add(property.Key, property.Value);
115-
}
116-
}
117-
118-
int pmIndex = 0;
119-
foreach (KeyValuePair<String, Dictionary<String, String>> productModuleValidationParams in validationParameters.getParameters ()) {
120-
parameters.Add (Constants.ProductModule.PRODUCT_MODULE_NUMBER + pmIndex, productModuleValidationParams.Key);
121-
foreach (KeyValuePair<String, String> param in productModuleValidationParams.Value) {
122-
parameters.Add (param.Key + pmIndex, param.Value);
123-
}
124-
pmIndex++;
125-
}
126-
127-
netlicensing output = NetLicensingAPI.request(context, NetLicensingAPI.Method.POST, Constants.Licensee.ENDPOINT_PATH + "/" + number + "/" + Constants.Licensee.ENDPOINT_PATH_VALIDATE, parameters, timeoutInMilliseconds);
128-
return new ValidationResult (output);
103+
{
104+
return ValidationService.validate(context, number, validationParameters, timeoutInMilliseconds);
129105
}
130106

131107
/// <summary>

NetLicensingClient/RestController/NetLicensingAPI.cs

Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,9 @@
55
using System.Xml.Serialization;
66
using System.Text;
77
using System.Text.RegularExpressions;
8-
using System.Web;
98
using NetLicensingClient.Entities;
109
using NetLicensingClient.Exceptions;
11-
using Org.BouncyCastle.Crypto.Parameters;
12-
using Org.BouncyCastle.OpenSsl;
13-
using System.Security.Cryptography;
14-
using System.Security.Cryptography.Xml;
15-
using System.Xml;
10+
using NetLicensingClient.Utils;
1611

1712
namespace NetLicensingClient.RestController
1813
{
@@ -143,7 +138,7 @@ public static netlicensing request(Context context, Method method, String path,
143138
using (StreamReader reader = new StreamReader(memoryStream))
144139
{
145140
var responseString = reader.ReadToEnd();
146-
if (!VerifyXmlSignature(responseString, context.publicKey))
141+
if (!SignatureUtils.check(responseString, context.publicKey))
147142
{
148143
throw new NetLicensingException("XML signature could not be verified");
149144
}
@@ -212,49 +207,6 @@ private static netlicensing deserialize(Stream responseStream)
212207
XmlSerializer NetLicensingSerializer = new XmlSerializer(typeof(netlicensing));
213208
return NetLicensingSerializer.Deserialize(responseStream) as netlicensing;
214209
}
215-
216-
private static bool VerifyXmlSignature(string xmlString, string publicKey)
217-
{
218-
using (var keyReader = new StringReader(publicKey))
219-
{
220-
var pemReader = new PemReader(keyReader);
221-
222-
RsaKeyParameters parameters = (RsaKeyParameters)pemReader.ReadObject();
223-
RSAParameters rParams = new RSAParameters();
224-
rParams.Modulus = parameters.Modulus.ToByteArray();
225-
rParams.Exponent = parameters.Exponent.ToByteArray();
226-
227-
RSA rsaKey = RSA.Create();
228-
rsaKey.ImportParameters(rParams);
229-
230-
XmlDocument xmlDoc = new XmlDocument();
231-
xmlDoc.PreserveWhitespace = true;
232-
xmlDoc.LoadXml(xmlString);
233-
234-
// Create a new SignedXml object and pass it the XML document class
235-
SignedXml signedXml = new SignedXml(xmlDoc);
236-
// Find the "Signature" node and create a new XmlNodeList object
237-
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
238-
239-
// Throw an exception if no signature was found
240-
if (nodeList.Count <= 0)
241-
{
242-
throw new CryptographicException("Verification failed: No Signature was found in the document.");
243-
}
244-
245-
// Throw an exception if more than one signature was found
246-
if (nodeList.Count >= 2)
247-
{
248-
throw new CryptographicException("Verification failed: More that one signature was found for the document.");
249-
}
250-
251-
// Load the first <signature> node
252-
signedXml.LoadXml((XmlElement)nodeList[0]);
253-
254-
// Check the signature and return the result
255-
return signedXml.CheckSignature(rsaKey);
256-
}
257-
}
258210
}
259211

260212
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System.Security.Cryptography.Xml;
2+
using System.Security.Cryptography;
3+
using System.Xml;
4+
using System.IO;
5+
using Org.BouncyCastle.Crypto.Parameters;
6+
using Org.BouncyCastle.OpenSsl;
7+
using System;
8+
using System.Xml.Serialization;
9+
10+
namespace NetLicensingClient.Utils
11+
{
12+
public class SignatureUtils
13+
{
14+
public static Boolean check(string xmlString, string publicKey)
15+
{
16+
using (var keyReader = new StringReader(publicKey))
17+
{
18+
var pemReader = new PemReader(keyReader);
19+
20+
RsaKeyParameters parameters = (RsaKeyParameters)pemReader.ReadObject();
21+
RSAParameters rParams = new RSAParameters();
22+
rParams.Modulus = parameters.Modulus.ToByteArray();
23+
rParams.Exponent = parameters.Exponent.ToByteArray();
24+
25+
RSA rsaKey = RSA.Create();
26+
rsaKey.ImportParameters(rParams);
27+
28+
XmlDocument xmlDoc = new XmlDocument();
29+
xmlDoc.PreserveWhitespace = true;
30+
xmlDoc.LoadXml(xmlString);
31+
32+
// Create a new SignedXml object and pass it the XML document class
33+
SignedXml signedXml = new SignedXml(xmlDoc);
34+
// Find the "Signature" node and create a new XmlNodeList object
35+
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
36+
37+
// Throw an exception if no signature was found
38+
if (nodeList.Count <= 0)
39+
{
40+
throw new CryptographicException("Verification failed: No Signature was found in the document.");
41+
}
42+
43+
// Throw an exception if more than one signature was found
44+
if (nodeList.Count >= 2)
45+
{
46+
throw new CryptographicException("Verification failed: More that one signature was found for the document.");
47+
}
48+
49+
// Load the first <signature> node
50+
signedXml.LoadXml((XmlElement)nodeList[0]);
51+
52+
// Check the signature and return the result
53+
return signedXml.CheckSignature(rsaKey);
54+
}
55+
}
56+
}
57+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using NetLicensingClient.Entities;
2+
using NetLicensingClient.RestController;
3+
using NetLicensingClient.Utils;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Text;
8+
using System.Xml.Linq;
9+
using System.Xml.Serialization;
10+
11+
namespace NetLicensingClient
12+
{
13+
public class ValidationService
14+
{
15+
public static ValidationResult validate(Context context, String number, ValidationParameters validationParameters, int timeoutInMilliseconds = 100000)
16+
{
17+
return convertValidationResult(retrieveValidationFile(context, number, validationParameters, timeoutInMilliseconds));
18+
}
19+
20+
public static ValidationResult validateOffline(Context context, string validationFile)
21+
{
22+
if (!SignatureUtils.check(validationFile, context.publicKey))
23+
{
24+
throw new NetLicensingException("XML signature could not be verified");
25+
}
26+
27+
byte[] byteArray = Encoding.UTF8.GetBytes(validationFile);
28+
MemoryStream stream = new MemoryStream(byteArray);
29+
30+
XmlSerializer serializer = new XmlSerializer(typeof(netlicensing));
31+
netlicensing response = (netlicensing)serializer.Deserialize(stream);
32+
33+
return convertValidationResult(response);
34+
}
35+
36+
internal static netlicensing retrieveValidationFile(Context context, String number, ValidationParameters validationParameters, int timeoutInMilliseconds = 100000)
37+
{
38+
Dictionary<String, String> parameters = convertValidationParameters(validationParameters);
39+
40+
return NetLicensingAPI.request(context, NetLicensingAPI.Method.POST, Constants.Licensee.ENDPOINT_PATH + "/" + number + "/" + Constants.Licensee.ENDPOINT_PATH_VALIDATE, parameters, timeoutInMilliseconds);
41+
}
42+
43+
internal static ValidationResult convertValidationResult(netlicensing validationFile)
44+
{
45+
return (validationFile != null) ? new ValidationResult(validationFile) : null;
46+
}
47+
48+
internal static Dictionary<String, String> convertValidationParameters(ValidationParameters validationParameters)
49+
{
50+
Dictionary<String, String> parameters = new Dictionary<String, String>();
51+
52+
if (!String.IsNullOrEmpty(validationParameters.getProductNumber()))
53+
{
54+
parameters.Add(Constants.Product.PRODUCT_NUMBER, validationParameters.getProductNumber());
55+
}
56+
57+
if (validationParameters.isForOfflineUse())
58+
{
59+
parameters.Add(Constants.Validation.FOR_OFFLINE_USE, "true");
60+
}
61+
62+
foreach (KeyValuePair<String, String> property in validationParameters.getLicenseeProperties())
63+
{
64+
if (!String.IsNullOrEmpty(property.Key))
65+
{
66+
parameters.Add(property.Key, property.Value);
67+
}
68+
}
69+
70+
int pmIndex = 0;
71+
foreach (KeyValuePair<String, Dictionary<String, String>> productModuleValidationParams in validationParameters.getParameters())
72+
{
73+
parameters.Add(Constants.ProductModule.PRODUCT_MODULE_NUMBER + pmIndex, productModuleValidationParams.Key);
74+
foreach (KeyValuePair<String, String> param in productModuleValidationParams.Value)
75+
{
76+
parameters.Add(param.Key + pmIndex, param.Value);
77+
}
78+
pmIndex++;
79+
}
80+
81+
return parameters;
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)