0%

使用Standard.Licensing来对软件系统进行授权管理

软件授权是为了限定软件部署后的部分或者全部功能在特定时间点之后不可用。Standard.Licensing 提供了基于非对称加密的许可生成、验证功能

本文生成部分都使用F#脚本(.fsx)方式实现,需要安装dotnet core sdk。

生成公私钥对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#r "nuget:Standard.Licensing"
open System.Text
open System.IO
open System

let baseDir = "./keys"
match Directory.Exists baseDir with
| false ->
Directory.CreateDirectory baseDir |>ignore
()
| true -> ()

let prefix = DateTime.Now.ToString("yyyyMMdd_hhmmss")

let initKeys (passPhrase:string) :(string * string) =
let generator = Standard.Licensing.Security.Cryptography.KeyGenerator.Create()
let keyPair = generator.GenerateKeyPair()
(keyPair.ToEncryptedPrivateKeyString(passPhrase),keyPair.ToPublicKeyString())


let privateKeyName = $"{prefix}_key.private"
let publicKeyName = $"{prefix}_key.public"


printf "please input your passPhrase:"
let passPhrase = Console.ReadLine()

let privateKey, publicKey = initKeys passPhrase

let privateKeyPath = Path.Combine(baseDir,privateKeyName)
let publicKeyPath = Path.Combine(baseDir,publicKeyName)
File.WriteAllText(privateKeyPath,privateKey,Encoding.UTF8);
File.WriteAllText(publicKeyPath,publicKey,Encoding.UTF8);

printfn "privateKey at: %s" privateKeyPath
printfn "publicKey at: %s" publicKeyPath


以上脚本需要自定义的passPhrase, 该passPhrase需要独立保存。执行后将会在./keys目录下生成公私密钥对, 其中公钥(xxx.public)需要随着应用程序分发,用作验证下一步生成的License是否合法。

使用密钥对生成License

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
open System
open System.IO
open System.Text

let expiryAt = DateTime.UtcNow.AddMonths(6) // CHANGE_ME
let licenseTo = "CHANGE_ME"
let licenseToMail = "CHANGE_ME@CHANGE_ME.com"


printf "please input your passPhrase:"
let passPhrase = Console.ReadLine()

printf "please input your licenseFileName:"
let licenseFileName = Console.ReadLine()

let privateKeyContent =
File.ReadAllText("./keys/key.private", Encoding.UTF8)

#r "nuget:Standard.Licensing"
open Standard.Licensing

let license =
License
.New()
.WithUniqueIdentifier(Guid.NewGuid())
.As(LicenseType.Standard)
.ExpiresAt(expiryAt)
.LicensedTo(licenseTo, licenseToMail)
.CreateAndSignWithPrivateKey(privateKeyContent, passPhrase)

printfn $"This license[{licenseFileName}] will be license to {licenseTo}({licenseToMail}) and expiryAt {expiryAt}"

printf "press Enter to continue!"

Console.ReadLine() |> ignore

let licContent =
license.ToString()
|> Encoding.UTF8.GetBytes
|> Convert.ToBase64String

File.WriteAllText(licenseFileName, licContent, Encoding.UTF8)

printfn "license %s generate success !" licenseFileName

该脚本需要之前步骤中的passPhrase来生成随应用程序分发的License文件。License内容经过base64后保存,内容举例:

1
2
3
4
5
6
7
8
9
10
<License>
<Id>3400bef5-bd01-4860-8bc1-cf8b7d281aaf</Id>
<Type>Standard</Type>
<Expiration>Wed, 19 Apr 2023 06:55:17 GMT</Expiration>
<Customer>
<Name>FILLME</Name>
<Email>[email protected]</Email>
</Customer>
<Signature>MEUCIQCcyH7Bp5Uvql9LfqJ9lXeutznGP5l9CdbkjctLe657PgIgbadrvrjZqHSV3hbyT04Cv/t/wilJya+IEyrdrtSnQFI=</Signature>
</License>

在程序中校验License

随程序分发的内容包括

  • 密钥对中的公钥
  • 后续步骤中生成的含有各项信息的License文件

验证举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static bool CheckLicense()
{
var licensePath = "Your License File Path";
var licContent = Encoding.UTF8.GetString(Convert.FromBase64String(File.ReadAllText(licensePath, Encoding.UTF8)));
var lic = License.Load(licContent);
var assembly = Assembly.GetEntryAssembly();
using var rStream = assembly.GetManifestResourceStream($"Some.Namespace.key.public"); //read public key as ManifestResource
using var sr = new StreamReader(rStream);
var publicKey = sr.ReadToEnd();
Log.Information($"This app is using init license from [{licensePath}]");

var failures =
lic.Validate()
.ExpirationDate()
.And()
.Signature(publicKey)
.AssertValidLicense().ToList();
if (failures.Any())
{
foreach (var failure in failures)
{
Log.Error(failure.GetType().Name + ": " + failure.Message + " - " + failure.HowToResolve);
}
return false;
}
return true;
}