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:
- A File's fields are populated.
- The File is marshalled, using FileFormat, to a byte slice.
- The bytes are hashed.
- The hash is signed via a previously defined private key.
- The generated signature is encoded into a textual format.
- The human readable signature is set to the File's Signature field.
- The File is marshalled, again, but this time with the Signature field populated.
- 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:
- A text file is read from the filesystem.
- The read bytes are unmarshalled to a File struct.
- The signature is removed from the File and decoded.
- The File is marshalled and the resulting bytes are hashed.
- The decoded signature is compared against the hash using a public key.
- If the signature is valid, the license key file's data can be used.
Index ¶
- Constants
- Variables
- func GenerateKeyPair(k KeyPairAlgoType) (private, public []byte, err error)
- func GenerateKeyPairECDSA(k KeyPairAlgoType) (private, public []byte, err error)
- func GenerateKeyPairED25519() (private, public []byte, err error)
- func GenerateKeyPairRSA(k KeyPairAlgoType) (private, public []byte, err error)
- type File
- func (f *File) DaysUntilExpired() (diffDays int, err error)
- func (f *File) ExtraAsBool(name string) (b bool, err error)
- func (f *File) ExtraAsFloat(name string) (x float64, err error)
- func (f *File) ExtraAsInt(name string) (i int, err error)
- func (f *File) ExtraAsString(name string) (s string, err error)
- func (f *File) FileFormat() FileFormat
- func (f *File) Marshal() (b []byte, err error)
- func (f *File) Reverify() (err error)
- func (f *File) SetFileFormat(format FileFormat)
- func (f *File) Sign(privateKey []byte, keyPairAlgo KeyPairAlgoType) (err error)
- func (f *File) SignECDSA(privateKey []byte, keyPairAlgo KeyPairAlgoType) (err error)
- func (f *File) SignED25519(privateKey []byte, keyPairAlgo KeyPairAlgoType) (err error)
- func (f *File) SignRSA(privateKey []byte, keyPairAlgo KeyPairAlgoType) (err error)
- func (f *File) Verify(publicKey []byte, keyPairAlgo KeyPairAlgoType) (err error)
- func (f File) VerifyECDSA(publicKey []byte, keyPairAlgo KeyPairAlgoType) (err error)
- func (f File) VerifyED25519(publicKey []byte, keyPairAlgo KeyPairAlgoType) (err error)
- func (f File) VerifyRSA(publicKey []byte, keyPairAlgo KeyPairAlgoType) (err error)
- func (f *File) Write(out io.Writer) (err error)
- type FileFormat
- type KeyPairAlgoType
Constants ¶
const ( FileFormatYAML = FileFormat("yaml") FileFormatJSON = FileFormat("json") )
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 ¶
var ErrBadSignature = errors.New("signature invalid")
ErrBadSignature is returned from Verify() or Verify...() when a File's Signature cannot be verified.
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.
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 ¶
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 ¶
DaysUntilExpired calculates the days from now until when a license will be expired.
func (*File) ExtraAsBool ¶
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 ¶
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 ¶
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 ¶
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) Reverify ¶
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 ¶
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.