licensefile

package
v2.1.0-beta1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2023 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package licensefile defines the format for a license key file and tooling for creating, signing, reading, verifying, and interacting with a license key file.

A license key file is a text file storing YAML or JSON encoded data. The data stored in a license key file has a standardized format that can include customized data per a third-party app's (app reading and verifying the license) needs. The Signature field contains a public-private keypair signature of the other publically marshalled fields in the license key file. The signature authenticates the data in the license key file; if any data in the license key file is changed, or the signature is changed, validation will fail.

Creating and Signing a License Key File

The process of creating a license key file and signing it is as follows:

  1. A File's fields are populated.
  2. The File is marshalled, using FileFormat, to a byte slice.
  3. The bytes are hashed.
  4. The hash is signed via a previously defined private key.
  5. The generated signature is encoded into a textual format.
  6. The human readable signature is set to the File's Signature field.
  7. The File is marshalled, again, but this time with the Signature field populated.
  8. The output from marshalling is saved to a text file or served to as a browser response.

Reading and Verifying a License Key File

The process of reading and verifying a license key file is as follows:

  1. A text file is read from the filesystem.
  2. The read bytes are unmarshalled to a File struct.
  3. The signature is removed from the File and decoded.
  4. The File is marshalled and the resulting bytes are hashed.
  5. The decoded signature is compared against the hash using a public key.
  6. If the signature is valid, the license key file's data can be used.
  7. Check that the license isn't expired.

Index

Constants

View Source
const (
	FileFormatYAML = FileFormat("yaml")
	FileFormatJSON = FileFormat("json")
)
View Source
const (
	KeyPairAlgoECDSAP256 = KeyPairAlgoType("ECDSA (P256)")
	KeyPairAlgoECDSAP384 = KeyPairAlgoType("ECDSA (P384)")
	KeyPairAlgoECDSAP521 = KeyPairAlgoType("ECDSA (P521)")
	KeyPairAlgoRSA2048   = KeyPairAlgoType("RSA (2048-bit)")
	KeyPairAlgoRSA4096   = KeyPairAlgoType("RSA (4096-bit)")
	KeyPairAlgoED25519   = KeyPairAlgoType("ED25519")
)

Variables

View Source
var (
	// ErrBadSignature is returned from Verify() or Verify...() when a File's Signature
	// cannot be verified with the given public key.
	ErrBadSignature = errors.New("signature invalid")

	// ErrMissingExpireDate is returned when trying to check if a license is expires
	// or in how long it expires via the Expired() or ExpiresIn() funcs. This error
	//should really never be returned since the only time these funcs are used are
	//with an existing license's data.
	ErrMissingExpireDate = errors.New("missing expire date")

	//ErrExpired is returned when checking if a license's expire date is in the past.
	//
	//This is ONLY used for the Verify() func that handles signature verification and
	//expiration checking since this func only returns an error, not multiple return
	//values. The Expired() and ExpiresIn() funcs both return a non-error value to
	//represent an expired license.
	ErrExpired = errors.New("license expired")
)

Errors.

View Source
var ErrFieldDoesNotExist = errors.New("extra field does not exist")

ErrFieldDoesNotExist is returned when trying to retrieve a field with a given name using one of the ExtraAs... type assertion retrieval functions but the field does not exist in the File's Extra map.

Functions

func GenerateKeyPair

func GenerateKeyPair(k KeyPairAlgoType) (private, public []byte, err error)

GenerateKeyPair creates and returns a new private and public key.

func GenerateKeyPairECDSA

func GenerateKeyPairECDSA(k KeyPairAlgoType) (private, public []byte, err error)

GenerateKeyPairECDSA creates and returns a new ECDSA private and public key. We don't just accept an elliptic.Curve as an input because this overall code base does not support every curve type.

func GenerateKeyPairED25519

func GenerateKeyPairED25519() (private, public []byte, err error)

GenerateKeyPairED25519 creates and returns a new ED25519 private and public key.

func GenerateKeyPairRSA

func GenerateKeyPairRSA(k KeyPairAlgoType) (private, public []byte, err error)

GenerateKeyPairRSA creates and returns a new RSA private and public key. We don't just accept a bitsize as an input because this overall code base does not support every bit size.

Types

type File

type File struct {
	//Optionally displayed fields per app. These are at the top of the struct
	//definition so that they will be displayed at the top of the marshalled data just
	//for ease of human reading of the license key file.
	LicenseID int64  `json:"LicenseID,omitempty" yaml:"LicenseID,omitempty"`
	AppName   string `json:"AppName,omitempty" yaml:"AppName,omitempty"`

	//This data copied from db-license.go and always included in each license key file.
	CompanyName    string `yaml:"CompanyName"`
	ContactName    string `yaml:"ContactName"`
	PhoneNumber    string `yaml:"PhoneNumber"`
	Email          string `yaml:"Email"`
	IssueDate      string `yaml:"IssueDate"`      //YYYY-MM-DD
	IssueTimestamp int64  `yaml:"IssueTimestamp"` //unix timestamp in seconds
	ExpireDate     string `yaml:"ExpireDate"`     //YYYY-MM-DD, in UTC timezone for easiest comparison in DaysUntilExpired()

	//The name and value for each custom field result. This is stored as a key
	//value pair and we use an interface since custom fields can have many types and
	//this is just easier.
	Extras map[string]interface{} `json:"Extras,omitempty" yaml:"Extras,omitempty"`

	//Signature is the result of signing the hash of File (all of the above fields)
	//using the private key. The result is stored here and File is output to a text
	//file known as the complete license key file. This file is distributed to and
	//imported into your app by the end-user to allow the app's use.
	Signature string `yaml:"Signature"`
	// contains filtered or unexported fields
}

File defines the format of data stored in a license key file. This is the body of the text file.

Struct tags are needed for YAML since otherwise when marshalling the field names will be converted to lowercase. We want to maintain camel case since that matches the format used when marshalling to JSON.

We use a struct with a map, instead of just map, so that we can more easily interact with common fields and store some non-marshalled license data. More simply, having a struct is just nicer for interacting with.

func Read

func Read(path string, format FileFormat) (f File, err error)

Read reads a license key file from the given path, unmarshals it, and returns it's data as a File. This checks if the file exists and the data is of the correct format, however, this DOES NOT check if the license key file itself (the contents of the file and the signature) is valid. You should call Verify() on the returned File immediately after calling this func.

func Unmarshal

func Unmarshal(in []byte, format FileFormat) (f File, err error)

Unmarshal takes data read from a file, or elsewhere, and deserializes it from the requested file format into a File. This is used when reading a license file for verifying it/the signature. If unmarshalling is successful, the format is saved to the File's FileFormat field. It is typically easier to call Read() instead since it handles reading a file from a path and deserializing it.

func (*File) Expired added in v2.1.0

func (f *File) Expired() (yes bool, err error)

Expired returns if a lincense File's expiration date is in the past.

You should call Verify() first!

func (*File) ExpiresIn added in v2.1.0

func (f *File) ExpiresIn() (d time.Duration, err error)

ExpiresIn calculates duration until a license File expires. If a license is expired, a negative duration is returned.

You should call Verify() first!

func (*File) ExtraAsBool

func (f *File) ExtraAsBool(name string) (b bool, err error)

ExtraAsBool returns the value of the Extra field with the given name as a bool. If the field cannot be found, an error is returned. If the field cannot be type asserted to an bool, an error is returned.

func (*File) ExtraAsFloat

func (f *File) ExtraAsFloat(name string) (x float64, err error)

ExtraAsFloat returns the value of the Extra field with the given name as a float64. If the field cannot be found, an error is returned. If the field cannot be type asserted to a float64, an error is returned.

func (*File) ExtraAsInt

func (f *File) ExtraAsInt(name string) (i int, err error)

ExtraAsInt returns the value of the Extra field with the given name as an int64. If the field cannot be found, an error is returned. If the field cannot be type asserted to an int, an error is returned.

func (*File) ExtraAsString

func (f *File) ExtraAsString(name string) (s string, err error)

ExtraAsString returns the value of the Extra field with the given name as a string. If the field cannot be found, an error is returned. If the field cannot be type asserted to a string, an error is returned.

func (*File) FileFormat

func (f *File) FileFormat() FileFormat

FileFormat returns a File's fileFormat field. This func is needed since the fileFormat field is not exported since it is not distributed/writted in a license file.

func (*File) Marshal

func (f *File) Marshal() (b []byte, err error)

Marshal serializes a File to the format specified in the File's FileFormat.

func (*File) SetFileFormat

func (f *File) SetFileFormat(format FileFormat)

SetFileFormat populates the fileFormat field. This func is needed since the fileFormat field is not exported since it is not distributed/written in a license file.

func (*File) Sign

func (f *File) Sign(privateKey []byte, keyPairAlgo KeyPairAlgoType) (err error)

Sign creates a signature for a license file. The signature is set in the provided File's Signature field. The private key must be decrypted, if needed, prior to being provided. The signature will be encoded per the File's EncodingType.

func (*File) SignECDSA

func (f *File) SignECDSA(privateKey []byte, keyPairAlgo KeyPairAlgoType) (err error)

SignECDSA signs File with the provided ECDSA private key. The generated signature will be set in the Signature field of File. You would need to call File.Marshal() after this func completes to return/serve the license key file. The private key must be decrypted, if needed, prior to being provided.

func (*File) SignED25519

func (f *File) SignED25519(privateKey []byte) (err error)

SignED25519 signs File with the provided ED25519 private key. The generated signature will be set in the Signature field of File. You would need to call File.Marshal() after this func completes to return/serve the license key file. The private key must be decrypted, if needed, prior to being provided.

A KeyPairAlgoType is not needed since there is only one version of ED25519 that can be used whereas with ECDSA or RSA there are multiple versions (curve, bitsize).

func (*File) SignRSA

func (f *File) SignRSA(privateKey []byte, keyPairAlgo KeyPairAlgoType) (err error)

SignRSA signs File with the provided RSA private key. The generated signature will be set in the Signature field of File. You would need to call File.Marshal() after this func completes to return/serve the license key file. The private key must be decrypted, if needed, prior to being provided.

func (*File) Verify

func (f *File) Verify(publicKey []byte, keyPairAlgo KeyPairAlgoType) (err error)

Verify checks if a File's signature is valid and if the license has expired. This calls VerifySignature() and Expired().

func (*File) VerifyECDSA

func (f *File) VerifyECDSA(publicKey []byte, keyPairAlgo KeyPairAlgoType) (err error)

VerifyECDSA checks if a File's signature is valid and if the license has expired. This calls VerifySignatureECDSA() and Expired().

func (File) VerifyED25519

func (f File) VerifyED25519(publicKey []byte) (err error)

VerifyED25519 checks if a File's signature is valid and if the license has expired. This calls VerifySignatureRSA() and Expired().

func (File) VerifyRSA

func (f File) VerifyRSA(publicKey []byte, keyPairAlgo KeyPairAlgoType) (err error)

VerifyRSA checks if a File's signature is valid and if the license has expired. This calls VerifySignatureRSA() and Expired().

func (*File) VerifySignature added in v2.1.0

func (f *File) VerifySignature(publicKey []byte, keyPairAlgo KeyPairAlgoType) (err error)

VerifySignature checks if a File's signature is valid by checking it against the publicKey. This DOES NOT check if a File is expired.

func (File) VerifySignatureECDSA added in v2.1.0

func (f File) VerifySignatureECDSA(publicKey []byte, keyPairAlgo KeyPairAlgoType) (err error)

VerifySignatureECDSA checks if a File's signature is valid by checking it against the ECDSA public key. This DOES NOT check if a File is expired.

This uses a copy of the File since need to remove the Signature field prior to hashing and verification but we don't want to modify the original File so it can be used as it was parsed/unmarshalled.

func (File) VerifySignatureED25519 added in v2.1.0

func (f File) VerifySignatureED25519(publicKey []byte) (err error)

VerifySignatureED25519 checks if a File's signature is valid by checking it against the ED25519 public key. This DOES NOT check if a File is expired.

This uses a copy of the File since need to remove the Signature field prior to hashing and verification but we don't want to modify the original File so it can be used as it was parsed/unmarshalled.

A KeyPairAlgoType is not needed since there is only one version of ED25519 that can be used whereas with ECDSA or RSA there are multiple versions (curve, bitsize).

func (File) VerifySignatureRSA added in v2.1.0

func (f File) VerifySignatureRSA(publicKey []byte, keyPairAlgo KeyPairAlgoType) (err error)

VerifySignatureRSA checks if the File's signature is valid by checking it against the RSA public key. This DOES NOT check if a File is expired.

This uses a copy of the File since need to remove the Signature field prior to hashing and verification but we don't want to modify the original File so it can be used as it was parsed/unmarshalled.

func (*File) Write

func (f *File) Write(out io.Writer) (err error)

Write writes a File to out. This is used to output the complete license key file. This can be used to write the File to a buffer, as is done when creating a license key file, write the File back to the browser as html, or write the File to an actual filesystem file.

For use with a buffer:

//b := bytes.Buffer{}
//err := f.Write(&b)

Writing to an http.ResponseWriter:

//func handler(w http.ResponseWriter, r *http.Request) {
//  //...
//  err := f.Write(w)
//}

type FileFormat

type FileFormat string

FileFormat is the format of the license key file's data.

func (FileFormat) Valid

func (f FileFormat) Valid() error

Valid checks if a provided file format is one of our supported file formats.

type KeyPairAlgoType

type KeyPairAlgoType string

KeyPairAlgoType is the key pair algorithm used to sign and verify a license key file.

func (KeyPairAlgoType) Valid

func (k KeyPairAlgoType) Valid() error

Valid checks if a provided algorithm is one of our supported key pair algorithms.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL