licensefile

package
v2.1.4 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2023 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.
  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")
)

Errors when validating a license key file.

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

ErrFieldDoesNotExist is returned when trying to retrieve a field from the Extra map of a File with a given key name using one of the ExtraAs... funcs but the named field does not exist in the 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.

This DOES NOT check if the license key file itself (the contents of the file and the signature) is valid nor does this check if the license is expired. You should call VerifySignature() and Expired() 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 only call this AFTER calling VerifySignature() otherwise the expiration date in the File is untrustworthy and could have been modified.

Signature verification and expiration date checking were kept separate on purpose so that each step can be handled more deliberately with specific handling of invalid states (i.e.: for more graceful handling).

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. The returned duration will be negative for an expired license.

You should only call this AFTER calling VerifySignature() otherwise the expiration date in the File is untrustworthy and could have been modified.

func (*File) ExpiresInDays added in v2.1.0

func (f *File) ExpiresInDays() (days int, err error)

ExpiresInDays is a wrapper around ExpiresIn that returns the number of days a license File expires in. The returned days will be negative for an expired license.

You should only call this AFTER calling VerifySignature() otherwise the expiration date in the File is untrustworthy and could have been modified.

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 deprecated

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

Verify calls VerifySignature().

Deprecated: This func is here just for legacy situations since the old Verify() func was renamed to VerifySignature() for better clarity. Use VerifySignature() instead.

func (*File) VerifyECDSA deprecated

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

VerifyECDSA calls VerifySignatureECDSA().

Deprecated: This func is here just for legacy situations since the old VerifyECDSA() func was renamed to VerifySignatureECDSA() for better clarity. Use VerifySignatureECDSA() instead.

func (*File) VerifyED25519 deprecated

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

VerifyED25519 calls VerifySignatureED25519().

Deprecated: This func is here just for legacy situations since the old VerifyED25519() func was renamed to VerifySignatureED25519() for better clarity. Use VerifySignatureED25519() instead.

func (*File) VerifyRSA deprecated

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

VerifyRSA calls VerifySignatureRSA().

Deprecated: This func is here just for legacy situations since the old VerifyRSA() func was renamed to VerifySignatureRSA() for better clarity. Use VerifySignatureRSA() instead.

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. You should call Expired() on the File after calling this func.

Signature verification and expiration date checking were kept separate on purpose so that each step can be handled more deliberately with specific handling of invalid states (i.e.: for more graceful handling).

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. You should call Expired() on the File after calling this func.

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. You should call Expired() on the File after calling this func.

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. You should call Expired() on the File after calling this func.

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