licensefile

package
v0.0.0-...-1518e3e Latest Latest
Warning

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

Go to latest
Published: Jul 8, 2022 License: MIT Imports: 22 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.

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 = errors.New("signature invalid")

ErrBadSignature is returned from Verify() or Verify...() when a File's Signature cannot be verified.

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.

View Source
var ErrMissingExpireDate = errors.New("missing expire date")

ErrMissingExpireDate is returned when trying to calculate the DaysUntilExpired but no expire date is set for the license file. This should never happen since the only time the DaysUntilExpired func is used is when using a license's data and the license file was already created and validated.

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) DaysUntilExpired

func (f *File) DaysUntilExpired() (diffDays int, err error)

DaysUntilExpired calculates the days from now until when a license will be expired.

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) Reverify

func (f *File) Reverify() (err error)

Reverify performs reverfication of a license. This can be used to make sure a license file hasn't been tampered with. This must be called after Verify() has been called because Verify() saves some information to the File to remove the need for arguments being passed to this func.

Typically you will want to run reverification every so often. You may want to wrap this func in a for/while loop that runs every "x" minutes/hours/days. Example:

//_ = f.Verify(...)
//
//go func() {
//  for {
//    time.Sleep(1*time.Hour)
//    err := f.Reverify()
//    if err != nil {
//      log.Println("License file invalid upon reverification.")
//      //do something about tampered with license,: os.Exit(1), log.Fatal(...), etc.
//    }
//  }
//}()

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, keyPairAlgo KeyPairAlgoType) (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.

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 is valid. This checks the signature against the File's contents using the provided public key.

func (File) VerifyECDSA

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

VerifyECDSA verifies the File's Signature with the provided ECDSA public key. You must populate the FileFormat field prior per to calling this func.

This uses a copy of the File since we are going 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) VerifyED25519

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

VerifyED25519 verifies the File's Signature with the provided ED25519 public key. You must populate the FileFormat field prior per to calling this func.

This uses a copy of the File since we are going 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) VerifyRSA

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

VerifyRSA verifies the File's Signature with the provided RSA public key. You must populate the FileFormat field prior per to calling this func.

This uses a copy of the File since we are going 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