Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/hyperboloide/lk

Simple licensing library for golang.
https://github.com/hyperboloide/lk

crypto golang sign

Last synced: about 2 months ago
JSON representation

Simple licensing library for golang.

Awesome Lists containing this project

README

        

# license-key

[![Tests](https://github.com/hyperboloide/lk/actions/workflows/tests.yml/badge.svg)](https://github.com/hyperboloide/lk/actions/workflows/tests.yml)
[![Go Reference](https://pkg.go.dev/badge/github.com/hyperboloide/lk.svg)](https://pkg.go.dev/github.com/hyperboloide/lk)
[![Go Report Card](https://goreportcard.com/badge/github.com/hyperboloide/lk)](https://goreportcard.com/report/github.com/hyperboloide/lk)

A simple licensing library in Golang, that generates license files containing
arbitrary data (ex: user email, end date...) that you can further validate if you want.

The license files are signed with the: Elliptic Curve Digital Signature Algorithm
(using [elliptic.P384 ](https://pkg.go.dev/crypto/elliptic#P384))

The license file can be marshalled in an easy to distribute format (ex: base32 encoded strings)

Note that this implementation is quite basic and that in no way it could
prevent someone to hack your software. The goal of this project is only
to provide a convenient way for software publishers to generate license keys
and distribute them without too much hassle for the user.

### How does it works?

1. Generate a private key (and keep it secure).
2. Transform the data you want to provide (end date, user email...) to a byte array (using json or gob for example).
3. The library takes the data and create a cryptographically signed hash that is appended to the data.
4. Convert the result to a Base64/Base32/Hex string and send it to the end user: this is the license.
5. when the user starts your program load the license and verify the signature using a public key.
6. validate the data in your license key (ex: the end date)

### lkgen

A command line helper [lkgen](lkgen) is also provided to generate private keys and create licenses.

This is also a good example of how to use the library.

### Examples

#### Generating a new license:

Below is an example of code that generates a license from a private key and a struct containing the end date and a user email that is marshalled to json.

```go
// first, you need a base32 encoded private key generated by `lkgen gen` note that you might
// prefer reading it from a file, and that it should stay secret (ie: dont distribute it with your app)!
const privateKeyBase32 = "FD7YCAYBAEFXA22DN5XHIYLJNZSXEAP7QIAACAQBANIHKYQBBIAACAKEAH7YIAAAAAFP7AYFAEBP7BQAAAAP7GP7QIAWCBCRKQVWKPT7UJDNP4LB5TXEQMO7EYEGDCE42KVBDNEGRIYIIJFBIWIVB6T6ZTKLSYSGK54DZ5VX6M5SJHBYZU2JXUFXJI25L2JJKJW4RL7UL2XBDT4GKYZ5IS6IWBCN7CWTMVBCBHJMH3RHZ5BVGVAY66MQAEYQEPSS2ANTYZIWXWSGIUJW3MDOO335JK3D4N3IV4L5UTAQMLS5YC7QASCAAUOHTZ5ZCCCYIBNCWBELBMAA===="

// Unmarshal the private key
privateKey, err := lk.PrivateKeyFromB32String(privateKeyBase32)
if err != nil {
log.Fatal(err)
}

// Define the data you need in your license,
// here we use a struct that is marshalled to json, but ultimately all you need is a []byte.
doc := struct {
Email string `json:"email"`
End time.Time `json:"end"`
}{
"[email protected]",
time.Now().Add(time.Hour * 24 * 365), // 1 year
}

// marshall the document to []bytes (this is the data that our license will contain).
docBytes, err := json.Marshal(doc)
if err != nil {
log.Fatal(err)
}

// generate your license with the private key and the document
license, err := lk.NewLicense(privateKey, docBytes)
if err != nil {
log.Fatal(err)

}
// the b32 representation of our license, this is what you give to your customer.
licenseB32, err := license.ToB32String()
if err != nil {
log.Fatal(err)
}
fmt.Println(licenseB32)
```

#### Validating a license:

Before your execute your program you want to check the user license, here is how with key generated from the previous example:

```go
// A previously generated license b32 encoded. In real life you should read it from a file...
const licenseB32 = "FT7YOAYBAEDUY2LDMVXHGZIB76EAAAIDAECEIYLUMEAQUAABAFJAD74EAAAQCUYB76CAAAAABL7YGBIBAL7YMAAAAD73H74IAFEHWITFNVQWS3BCHIRHIZLTORAGK6DBNVYGYZJOMNXW2IRMEJSW4ZBCHIRDEMBRHAWTCMBNGI3FIMJSHIYTSORTGMXDOMBZG43TIMJYHAVTAMR2GAYCE7IBGEBAPXB37ROJCUOYBVG4LAL3MSNKJKPGIKNT564PYK5X542NH62V7TAUEYHGLEOPZHRBAPH7M4SC55OHAEYQEXMKGG3JPO6BSHTDF3T5H6T42VUD7YAJ3TY5AP5MDE5QW4ZYWMSAPEK24HZOUXQ3LJ5YY34XYPVXBUAA===="

// the public key b32 encoded from the private key using: lkgen pub my_private_key_file`.
// It should be hardcoded somewhere in your app.
const publicKeyBase32 = "ARIVIK3FHZ72ERWX6FQ6Z3SIGHPSMCDBRCONFKQRWSDIUMEEESQULEKQ7J7MZVFZMJDFO6B46237GOZETQ4M2NE32C3UUNOV5EUVE3OIV72F5LQRZ6DFMM6UJPELARG7RLJWKQRATUWD5YT46Q2TKQMPPGIA===="

// Unmarshal the public key.
publicKey, err := lk.PublicKeyFromB32String(publicKeyBase32)
if err != nil {
log.Fatal(err)
}

// Unmarshal the customer license.
license, err := lk.LicenseFromB32String(licenseB32)
if err != nil {
log.Fatal(err)
}

// validate the license signature.
if ok, err := license.Verify(publicKey); err != nil {
log.Fatal(err)
} else if !ok {
log.Fatal("Invalid license signature")
}

result := struct {
Email string `json:"email"`
End time.Time `json:"end"`
}{}

// unmarshal the document.
if err := json.Unmarshal(license.Data, &result); err != nil {
log.Fatal(err)
}

// Now you just have to check that the end date is after time.Now() then you can continue!
if result.End.Before(time.Now()) {
log.Fatalf("License expired on: %s", result.End.Format("2006-01-02"))
} else {
fmt.Printf(`Licensed to %s until %s`, result.Email, result.End.Format("2006-01-02"))
}
```

#### A Complete example

Bellow is a sample function that generate a key pair, signs a license and verify it.

```go
// create a new Private key:
privateKey, err := lk.NewPrivateKey()
if err != nil {
log.Fatal(err)

}

// create a license document:
doc := MyLicence{
"[email protected]",
time.Now().Add(time.Hour * 24 * 365), // 1 year
}

// marshall the document to json bytes:
docBytes, err := json.Marshal(doc)
if err != nil {
log.Fatal(err)

}

// generate your license with the private key and the document:
license, err := lk.NewLicense(privateKey, docBytes)
if err != nil {
log.Fatal(err)

}

// encode the new license to b64, this is what you give to your customer.
str64, err := license.ToB64String()
if err != nil {
log.Fatal(err)

}
fmt.Println(str64)

// get the public key. The public key should be hardcoded in your app to check licences.
// Do not distribute the private key!
publicKey := privateKey.GetPublicKey()

// validate the license:
if ok, err := license.Verify(publicKey); err != nil {
log.Fatal(err)
} else if !ok {
log.Fatal("Invalid license signature")
}

// unmarshal the document and check the end date:
res := MyLicence{}
if err := json.Unmarshal(license.Data, &res); err != nil {
log.Fatal(err)
} else if res.End.Before(time.Now()) {
log.Fatalf("License expired on: %s", res.End.String())
} else {
fmt.Printf(`Licensed to %s until %s \n`, res.Email, res.End.Format("2006-01-02"))
}
```