프로젝트

일반

사용자정보

통계
| 브랜치(Branch): | 개정판:

markus / Rhino.Licensing / AbstractLicenseValidator.cs @ 42d49521

이력 | 보기 | 이력해설 | 다운로드 (21 KB)

1
using System;
2
using System.Collections.Generic;
3
using System.Globalization;
4
using System.Net.NetworkInformation;
5
using System.Security.Cryptography;
6
using System.Security.Cryptography.Xml;
7
using System.ServiceModel;
8
using System.Threading;
9
using System.Xml;
10
//using log4net;
11
using Rhino.Licensing.Discovery;
12

    
13
namespace Rhino.Licensing
14
{
15
    /// <summary>
16
    /// Base license validator.
17
    /// </summary>
18
    public abstract class AbstractLicenseValidator
19
    {
20
        /// <summary>
21
        /// License validator logger
22
        /// </summary>
23
        //protected static readonly ILog Log = LogManager.GetLogger(typeof(LicenseValidator));
24

    
25
        /// <summary>
26
        /// Standard Time servers
27
        /// </summary>
28
        protected static readonly string[] TimeServers =
29
        {
30
            "time.nist.gov",
31
            "time-nw.nist.gov",
32
            "time-a.nist.gov",
33
            "time-b.nist.gov",
34
            "time-a.timefreq.bldrdoc.gov",
35
            "time-b.timefreq.bldrdoc.gov",
36
            "time-c.timefreq.bldrdoc.gov",
37
            "utcnist.colorado.edu",
38
            "nist1.datum.com",
39
            "nist1.dc.certifiedtime.com",
40
            "nist1.nyc.certifiedtime.com",
41
            "nist1.sjc.certifiedtime.com"
42
        };
43

    
44
        private readonly string licenseServerUrl;
45
        private readonly Guid clientId;
46
        private readonly string publicKey;
47
        private readonly Timer nextLeaseTimer;
48
        private bool disableFutureChecks;
49
        private bool currentlyValidatingSubscriptionLicense;
50
        private readonly DiscoveryHost discoveryHost;
51
        private DiscoveryClient discoveryClient;
52
        private Guid senderId;
53

    
54
        /// <summary>
55
        /// Fired when license data is invalidated
56
        /// </summary>
57
        public event Action<InvalidationType> LicenseInvalidated;
58

    
59
        /// <summary>
60
        /// Fired when license is expired
61
        /// </summary>
62
        public event Action<DateTime> LicenseExpired;
63

    
64
        /// <summary>
65
        /// Event that's raised when duplicate licenses are found
66
        /// </summary>
67
        public event EventHandler<DiscoveryHost.ClientDiscoveredEventArgs> MultipleLicensesWereDiscovered;
68

    
69
        /// <summary>
70
        /// Disable the <see cref="ExpirationDate"/> validation with the time servers
71
        /// </summary>
72
        public bool DisableTimeServersCheck
73
        {
74
            get; set;
75
        }
76
        
77
        /// <summary>
78
        /// Gets the expiration date of the license
79
        /// </summary>
80
        public DateTime ExpirationDate
81
        {
82
            get; private set;
83
        }
84

    
85
        /// <summary>
86
        /// Lease timeout
87
        /// </summary>
88
        public TimeSpan LeaseTimeout { get; set; }
89

    
90
        /// <summary>
91
        /// How to behave when using the same license multiple times
92
        /// </summary>
93
        public MultipleLicenseUsage MultipleLicenseUsageBehavior { get; set; }
94

    
95
        /// <summary>
96
        /// Gets or Sets the endpoint address of the subscription service
97
        /// </summary>
98
        public string SubscriptionEndpoint
99
        {
100
            get; set;
101
        }
102

    
103
        /// <summary>
104
        /// Gets the Type of the license
105
        /// </summary>
106
        public LicenseType LicenseType
107
        {
108
            get; private set;
109
        }
110

    
111
        /// <summary>
112
        /// Gets the Id of the license holder
113
        /// </summary>
114
        public Guid UserId
115
        {
116
            get; private set;
117
        }
118

    
119
        /// <summary>
120
        /// Gets the name of the license holder
121
        /// </summary>
122
        public string Name
123
        {
124
            get; private set;
125
        }
126

    
127
        /// <summary>
128
        /// Gets or Sets Floating license support
129
        /// </summary>
130
        public bool DisableFloatingLicenses
131
        {
132
            get; set;
133
        }
134

    
135
        /// <summary>
136
        /// Whether the client discovery server is enabled. This detects duplicate licenses used on the same network.
137
        /// </summary>
138
        public bool DiscoveryEnabled { get; private set; }
139

    
140
        /// <summary>
141
        /// Gets extra license information
142
        /// </summary>
143
        public IDictionary<string, string> LicenseAttributes
144
        {
145
            get; private set;
146
        }
147

    
148
        /// <summary>
149
        /// Gets or Sets the license content
150
        /// </summary>
151
        protected abstract string License
152
        {
153
            get; set;
154
        }
155

    
156
        /// <summary>
157
        /// Creates a license validator with specfied public key.
158
        /// </summary>
159
        /// <param name="publicKey">public key</param>
160
        /// <param name="enableDiscovery">Whether to enable the client discovery server to detect duplicate licenses used on the same network.</param>
161
        protected AbstractLicenseValidator(string publicKey, bool enableDiscovery = false)
162
        {
163
            LeaseTimeout = TimeSpan.FromMinutes(5);
164
            LicenseAttributes = new Dictionary<string, string>();
165
            nextLeaseTimer = new Timer(LeaseLicenseAgain);
166
            this.publicKey = publicKey;
167

    
168
            DiscoveryEnabled = enableDiscovery;
169

    
170
            if (DiscoveryEnabled)
171
            {
172
                senderId = Guid.NewGuid();
173
                discoveryHost = new DiscoveryHost();
174
                discoveryHost.ClientDiscovered += DiscoveryHostOnClientDiscovered;
175
                discoveryHost.Start();
176
            }
177
        }
178

    
179
        /// <summary>
180
        /// Creates a license validator using the client information
181
        /// and a service endpoint address to validate the license.
182
        /// </summary>
183
        protected AbstractLicenseValidator(string publicKey, string licenseServerUrl, Guid clientId)
184
            : this(publicKey)
185
        {
186
            this.licenseServerUrl = licenseServerUrl;
187
            this.clientId = clientId;
188
        }
189

    
190
        private void LeaseLicenseAgain(object state)
191
        {
192
            var client = discoveryClient;
193
            if (client != null)
194
                client.PublishMyPresence();
195

    
196
            if (HasExistingLicense())
197
                return;
198

    
199
            RaiseLicenseInvalidated();
200
        }
201

    
202
        private void RaiseLicenseInvalidated()
203
        {
204
            var licenseInvalidated = LicenseInvalidated;
205
            if (licenseInvalidated == null)
206
                throw new InvalidOperationException("License was invalidated, but there is no one subscribe to the LicenseInvalidated event");
207

    
208
            licenseInvalidated(LicenseType == LicenseType.Floating ? InvalidationType.CannotGetNewLicense :
209
                                                                     InvalidationType.TimeExpired);
210
        }
211

    
212
        private void RaiseMultipleLicenseDiscovered(DiscoveryHost.ClientDiscoveredEventArgs args)
213
        {
214
            var onMultipleLicensesWereDiscovered = MultipleLicensesWereDiscovered;
215
            if (onMultipleLicensesWereDiscovered != null)
216
            {
217
                onMultipleLicensesWereDiscovered(this, args);
218
            }
219
        }
220

    
221
        private void DiscoveryHostOnClientDiscovered(object sender, DiscoveryHost.ClientDiscoveredEventArgs clientDiscoveredEventArgs)
222
        {
223
            if (senderId == clientDiscoveredEventArgs.SenderId) // we got our own notification, ignore it
224
                return;
225

    
226
            if (UserId != clientDiscoveredEventArgs.UserId) // another license, we don't care
227
                return;
228

    
229
            // same user id, different senders
230
            switch (MultipleLicenseUsageBehavior)
231
            {
232
                case MultipleLicenseUsage.AllowForSameUser:
233
                    if (Environment.UserName == clientDiscoveredEventArgs.UserName)
234
                        return;
235
                    break;
236
            }
237

    
238
            RaiseLicenseInvalidated();
239
            RaiseMultipleLicenseDiscovered(clientDiscoveredEventArgs);
240
        }
241

    
242
        /// <summary>
243
        /// Validates loaded license
244
        /// </summary>
245
        public virtual void AssertValidLicense()
246
        {
247
            LicenseAttributes.Clear();
248
            if (HasExistingLicense())
249
            {
250
                if (DiscoveryEnabled)
251
                {
252
                    discoveryClient = new DiscoveryClient(senderId, UserId, Environment.MachineName, Environment.UserName);
253
                    discoveryClient.PublishMyPresence();
254
                }
255
                return;
256
            }
257

    
258
            //Log.WarnFormat("Could not validate existing license\r\n{0}", License);
259
            throw new LicenseNotFoundException();
260
        }
261

    
262
        private bool HasExistingLicense()
263
        {
264
            try
265
            {
266
                if (TryLoadingLicenseValuesFromValidatedXml() == false)
267
                {
268
                    //Log.WarnFormat("Failed validating license:\r\n{0}", License);
269
                    return false;
270
                }
271
                //Log.InfoFormat("License expiration date is {0}", ExpirationDate);
272
                
273
                bool result;
274
                if (LicenseType == LicenseType.Subscription)
275
                {
276
                    result = ValidateSubscription();
277
                }
278
                else
279
                {
280
                    result = DateTime.UtcNow < ExpirationDate;
281
                }
282

    
283
                if (result &&
284
                    !DisableTimeServersCheck)
285
                {
286
                    ValidateUsingNetworkTime();
287
                }
288

    
289
                if (!result)
290
                {
291
                    if (LicenseExpired == null)
292
                        throw new LicenseExpiredException("Expiration Date : " + ExpirationDate);
293

    
294
                    DisableFutureChecks();
295
                    LicenseExpired(ExpirationDate);
296
                }
297

    
298
                return true;
299
            }
300
            catch (RhinoLicensingException)
301
            {
302
                throw;
303
            }
304
            catch (Exception)
305
            {
306
                return false;
307
            }
308
        }
309

    
310
        private bool ValidateSubscription()
311
        {
312
            if ((ExpirationDate - DateTime.UtcNow).TotalDays > 4)
313
                return true;
314

    
315
            if (currentlyValidatingSubscriptionLicense)
316
                return DateTime.UtcNow < ExpirationDate;
317

    
318
            if (SubscriptionEndpoint == null)
319
                throw new InvalidOperationException("Subscription endpoints are not supported for this license validator");
320

    
321
            try
322
            {
323
                TryGettingNewLeaseSubscription();
324
            }
325
            catch (Exception e)
326
            {
327
                //Log.Error("Could not re-lease subscription license", e);
328
                throw new Exception("Could not re-lease subscription license", e);
329
            }
330

    
331
            return ValidateWithoutUsingSubscriptionLeasing();
332
        }
333

    
334
        private bool ValidateWithoutUsingSubscriptionLeasing()
335
        {
336
            currentlyValidatingSubscriptionLicense = true;
337
            try
338
            {
339
                return HasExistingLicense();
340
            }
341
            finally
342
            {
343
                currentlyValidatingSubscriptionLicense = false;
344
            }
345
        }
346

    
347
        private void TryGettingNewLeaseSubscription()
348
        {
349
            var service = ChannelFactory<ISubscriptionLicensingService>.CreateChannel(new BasicHttpBinding(), new EndpointAddress(SubscriptionEndpoint));
350
            try
351
            {
352
                var newLicense = service.LeaseLicense(License);
353
                TryOverwritingWithNewLicense(newLicense);
354
            }
355
            finally
356
            {
357
                var communicationObject = service as ICommunicationObject;
358
                if (communicationObject != null)
359
                {
360
                    try
361
                    {
362
                        communicationObject.Close(TimeSpan.FromMilliseconds(200));
363
                    }
364
                    catch
365
                    {
366
                        communicationObject.Abort();
367
                    }
368
                }
369
            }
370
        }
371

    
372
        /// <summary>
373
        /// Loads the license file.
374
        /// </summary>
375
        /// <param name="newLicense"></param>
376
        /// <returns></returns>
377
        protected bool TryOverwritingWithNewLicense(string newLicense)
378
        {
379
            if (string.IsNullOrEmpty(newLicense))
380
                return false;
381
            try
382
            {
383
                var xmlDocument = new XmlDocument();
384
                xmlDocument.LoadXml(newLicense);
385
            }
386
            catch (Exception e)
387
            {
388
                // Log.Error("New license is not valid XML\r\n" + newLicense, e);
389
                System.Diagnostics.Debug.WriteLine("New license is not valid XML\r\n" + newLicense, e);
390
                //throw new Exception("New license is not valid XML\r\n" + newLicense, e);
391
                return false;
392
            }
393
            License = newLicense;
394
            return true;
395
        }
396

    
397
        private void ValidateUsingNetworkTime()
398
        {
399
            if (!NetworkInterface.GetIsNetworkAvailable())
400
                return;
401

    
402
            var sntp = new SntpClient(GetTimeServers());
403
            sntp.BeginGetDate(time =>
404
            {
405
                if (time > ExpirationDate)
406
                    RaiseLicenseInvalidated();
407
            }
408
            , () =>
409
            {
410
                /* ignored */
411
            });
412
        }
413

    
414
        /// <summary>
415
        /// Extension point to return different time servers
416
        /// </summary>
417
        /// <returns></returns>
418
        protected virtual string[] GetTimeServers()
419
        {
420
            return TimeServers;
421
        }
422

    
423
        /// <summary>
424
        /// Removes existing license from the machine.
425
        /// </summary>
426
        public virtual void RemoveExistingLicense()
427
        {
428
        }
429

    
430
        /// <summary>
431
        /// Loads license data from validated license file.
432
        /// </summary>
433
        /// <returns></returns>
434
        public bool TryLoadingLicenseValuesFromValidatedXml()
435
        {
436
            try
437
            {
438
                var doc = new XmlDocument();
439
                doc.LoadXml(License);
440

    
441
                if (TryGetValidDocument(publicKey, doc) == false)
442
                {
443
                    //Log.WarnFormat("Could not validate xml signature of:\r\n{0}", License);
444
                    return false;
445
                }
446

    
447
                if (doc.FirstChild == null)
448
                {
449
                    //Log.WarnFormat("Could not find first child of:\r\n{0}", License);
450
                    return false;
451
                }
452

    
453
                if (doc.SelectSingleNode("/floating-license") != null)
454
                {
455
                    var node = doc.SelectSingleNode("/floating-license/license-server-public-key/text()");
456
                    if (node == null)
457
                    {
458
                        //Log.WarnFormat("Invalid license, floating license without license server public key:\r\n{0}", License);
459
                        throw new InvalidOperationException(
460
                            "Invalid license file format, floating license without license server public key");
461
                    }
462
                    return ValidateFloatingLicense(node.InnerText);
463
                }
464

    
465
                var result = ValidateXmlDocumentLicense(doc);
466
                if (result && disableFutureChecks == false)
467
                {
468
                    nextLeaseTimer.Change(LeaseTimeout, LeaseTimeout);
469
                }
470
                return result;
471
            }
472
            catch (RhinoLicensingException)
473
            {
474
                throw;
475
            }
476
            catch (Exception e)
477
            {
478
                //Log.Error("Could not validate license", e);
479
                System.Diagnostics.Debug.WriteLine("Could not validate license", e);
480
                return false;
481
            }
482
        }
483

    
484
        private bool ValidateFloatingLicense(string publicKeyOfFloatingLicense)
485
        {
486
            if (DisableFloatingLicenses)
487
            {
488
               // Log.Warn("Floating licenses have been disabled");
489
                System.Diagnostics.Debug.WriteLine("Floating licenses have been disabled");
490

    
491
                return false;
492
            }
493
            if (licenseServerUrl == null)
494
            {
495
                //Log.Warn("Could not find license server url");
496
                throw new InvalidOperationException("Floating license encountered, but licenseServerUrl was not set");
497
            }
498
            
499
            var success = false;
500
            var licensingService = ChannelFactory<ILicensingService>.CreateChannel(new WSHttpBinding(), new EndpointAddress(licenseServerUrl));
501
            try
502
            {
503
                   var leasedLicense = licensingService.LeaseLicense(
504
                    Environment.MachineName,
505
                    Environment.UserName,
506
                    clientId);
507
                ((ICommunicationObject)licensingService).Close();
508
                success = true;
509
                if (leasedLicense == null)
510
                {
511
                    //Log.WarnFormat("Null response from license server: {0}", licenseServerUrl);
512
                    throw new FloatingLicenseNotAvailableException();
513
                }
514

    
515
                var doc = new XmlDocument();
516
                doc.LoadXml(leasedLicense);
517

    
518
                if (TryGetValidDocument(publicKeyOfFloatingLicense, doc) == false)
519
                {
520
                    //Log.WarnFormat("Could not get valid license from floating license server {0}", licenseServerUrl);
521
                    throw new FloatingLicenseNotAvailableException();
522
                }
523

    
524
                var validLicense = ValidateXmlDocumentLicense(doc);
525
                if (validLicense)
526
                {
527
                    //setup next lease
528
                    var time = (ExpirationDate.AddMinutes(-5) - DateTime.UtcNow);
529
                    System.Diagnostics.Debug.WriteLine("Will lease license again at {0}", time);
530
                    //Log.DebugFormat("Will lease license again at {0}", time);
531
                    if (disableFutureChecks == false)
532
                        nextLeaseTimer.Change(time, time);
533
                }
534
                return validLicense;
535
            }
536
            finally
537
            {
538
                if (success == false)
539
                    ((ICommunicationObject)licensingService).Abort();
540
            }
541
        }
542

    
543
        internal bool ValidateXmlDocumentLicense(XmlDocument doc)
544
        {
545
            var id = doc.SelectSingleNode("/license/@id");
546
            if (id == null)
547
            {
548
                //Log.WarnFormat("Could not find id attribute in license:\r\n{0}", License);
549
                return false;
550
            }
551

    
552
            UserId = new Guid(id.Value);
553

    
554
            var date = doc.SelectSingleNode("/license/@expiration");
555
            if (date == null)
556
            {
557
                //Log.WarnFormat("Could not find expiration in license:\r\n{0}", License);
558
                return false;
559
            }
560

    
561
            ExpirationDate = DateTime.ParseExact(date.Value, "yyyy-MM-ddTHH:mm:ss.fffffff", CultureInfo.InvariantCulture);
562

    
563
            var licenseType = doc.SelectSingleNode("/license/@type");
564
            if (licenseType == null)
565
            {
566
                //Log.WarnFormat("Could not find license type in {0}", licenseType);
567
                return false;
568
            }
569

    
570
            LicenseType = (LicenseType)Enum.Parse(typeof(LicenseType), licenseType.Value);
571

    
572
            var name = doc.SelectSingleNode("/license/name/text()");
573
            if (name == null)
574
            {
575
                //Log.WarnFormat("Could not find licensee's name in license:\r\n{0}", License);
576
                return false;
577
            }
578

    
579
            Name = name.Value;
580

    
581
            var license = doc.SelectSingleNode("/license");
582
            foreach (XmlAttribute attrib in license.Attributes)
583
            {
584
                if (attrib.Name == "type" || attrib.Name == "expiration" || attrib.Name == "id")
585
                    continue;
586

    
587
                LicenseAttributes[attrib.Name] = attrib.Value;
588
            }
589

    
590
            return true;
591
        }
592

    
593
        private bool TryGetValidDocument(string licensePublicKey, XmlDocument doc)
594
        {
595
            var rsa = new RSACryptoServiceProvider();
596
            rsa.FromXmlString(licensePublicKey);
597

    
598
            var nsMgr = new XmlNamespaceManager(doc.NameTable);
599
            nsMgr.AddNamespace("sig", "http://www.w3.org/2000/09/xmldsig#");
600

    
601
            var signedXml = new SignedXml(doc);
602
            var sig = (XmlElement)doc.SelectSingleNode("//sig:Signature", nsMgr);
603
            if (sig == null)
604
            {
605
                //Log.WarnFormat("Could not find this signature node on license:\r\n{0}", License);
606
                return false;
607
            }
608
            signedXml.LoadXml(sig);
609

    
610
            return signedXml.CheckSignature(rsa);
611
        }
612

    
613
        /// <summary>
614
        /// Disables further license checks for the session.
615
        /// </summary>
616
        public void DisableFutureChecks()
617
        {
618
            disableFutureChecks = true;
619
            nextLeaseTimer.Dispose();
620
        }
621

    
622
        /// <summary>
623
        /// Options for detecting multiple licenses
624
        /// </summary>
625
        public enum MultipleLicenseUsage
626
        {
627
            /// <summary>
628
            /// Deny if multiple licenses are used
629
            /// </summary>
630
            Deny,
631
            /// <summary>
632
            /// Only allow if it is running for the same user
633
            /// </summary>
634
            AllowForSameUser
635
        }
636
    }
637
}
클립보드 이미지 추가 (최대 크기: 500 MB)