CL-DONGLE - A Lisp interface to SG-Lock dongles


 

Abstract

CL-DONGLE is a Common Lisp library which provides full access to all the functionality of SG-Lock's hardware-based copy protection system, i.e. you can use it to protect your Lisp applications natively using features like storing Lisp objects in the dongle or encrypting and signing arbitrary data.

The library also contains a fast Lisp implementation of the Tiny Encryption Algorithm which works independently of attached dongles or the SG-Lock DLL and might thus be useful on its own.

CL-DONGLE works only with the LispWorks Common Lisp implementation and, like the SG-Lock system itself, only on Microsoft Windows. It should be pretty easy to port the code to OS X, though, once SG-Lock dongles are available for Macs.

The code comes with a BSD-style license so you can basically do with it whatever you want.

Download shortcut: http://weitz.de/files/cl-dongle.tar.gz.


 

Contents

  1. Download and installation
  2. Basic functionality and administration
  3. Enryption
  4. Storing and retrieving data
  5. Signing data
  6. Conditions
  7. Symbol index
  8. Acknowledgements

 

Download and installation

CL-DONGLE together with this documentation can be downloaded from http://weitz.de/files/cl-dongle.tar.gz. The current version is 0.1.2.

Before you install CL-DONGLE you first need to install the LW-WIN library unless you already have it.

CL-DONGLE comes with a system definition for ASDF so you can install the library with

(asdf:oos 'asdf:load-op :cl-dongle)
if you've unpacked it in a place where ASDF can find it. Unless you only want to use the in-RAM encryption functions, you will also need to put the SG-Lock DLL in a place where your Lisp (or your delivered application) will find it.

Don't worry if you see warnings about HARP::NREG and HARP::GREG - this is a known compiler bug which is harmless.

You can run a test suite which tests all aspects of the library with

(asdf:oos 'asdf:test-op :cl-dongle)
if you're using a demo dongle. If you have a "real" dongle, load the CL-DONGLE-TEST system, invoke the function CL-DONGLE-TEST:RUN-TEST, but read its docstring first. Running all the tests might take a while...
 

Basic functionality and administration

Note that the function AUTHENTICATE must always be called first before any communication with a dongle can happen. Also note that the value of *PRODUCT-ID* is the default value for all optional and keyword parameters called product-id.


[Function]
authenticate code => |


Authenticates the Lisp application against the SG-Lock DLL and vice versa. code must be a sequence of twelve (UNSIGNED-BYTE 32) integers representing the authentication code you received from SG-Lock when you bought your dongles (or *DEMO-AUTHENTICATION-CODE* for demo dongles).

This function must be called before any communication with the corresponding dongle is attempted. It returns no values in the case of success and signals an AUTHENTICATION-FAILED error otherwise.

If you call any function which has to communicate with a dongle without successfully calling AUTHENTICATE first, you'll get an AUTHENTICATION-REQUIRED error. (Well, at least that's how it should be. In some cases you'll get a DONGLE-NOT-FOUND error instead which is slightly misleading.)


[Special variable]
*demo-authentication-code*


The authentication code for demo dongles distributed by SG-Lock.


[Special variable]
*product-id*


The default value for all functions which have an optional or keyword product-id parameter.


[Accessor]
product-id => product-id
(setf (product-id &optional product-id) new-product-id)


The reader returns the (first) dongle's product ID. The writer sets the product ID of the dongle with the product ID product-id to new-product-id. It does not change the value of a href="#*product-id*">*PRODUCT-ID*.


[Function]
dongle-present-p &optional product-id => boolean


Returns T if the dongle with the product ID product-id is present, NIL otherwise.

See also ASSERT-DONGLE.


[Function]
assert-dongle &optional product-id => |


Checks if the dongle with the product ID product-id is present. Signals a DONGLE-NOT-FOUND error if it is not, returns no value if successful.

See also DONGLE-PRESENT-P.


[Function]
dongle-type &optional product-id => integer


Returns the "type" of the dongle with the product ID product-id. The type is one of 2, 3, or 4 corresponding to the number in SG-Lock product names like "U2" or "L4".


[Function]
dongle-interface &optional product-id => keyword


Returns the "interface" of the dongle with the product ID product-id which is one of the keywords :USB or :LPT.


[Function]
dongle-memory-size &optional product-id => integer


Returns the memory size of the dongle with the product ID product-id. The size is measured in dwords, i.e. in 32-bit blocks. So, if the return value is, say, 256, the memory size is 1024 octets.


[Function]
dongle-number-of-counters &optional product-id => integer


Returns the number of available programmable counters of the dongle with the product ID product-id.


[Function]
dongle-number-of-keys &optional product-id => integer


Returns the number of available programmable keys of the dongle with the product ID product-id.


[Function]
dongle-hardware-version &optional product-id => list


Returns the hardware version of the dongle with the product ID product-id as a list of two integers - the major and minor version.


[Function]
dongle-software-version &optional product-id => list


Returns the software version of the dongle with the product ID product-id as a list of two integers - the major and minor version.


[Function]
dongle-serial-number &optional product-id => integer


Returns the serial number for the dongle with the product ID product-id.


[Function]
write-key key-number key &optional product-id => key


Overwrites the key numbered key-number in the dongle with product ID product-id and sets it to key. key must be a sequence of four (UNSIGNED-BYTE 32) integers. Returns key.

Note that for security reasons there's no inverse operation to read the keys of a dongle. See also RANDOM-KEY.


[Accessor]
counter-value counter-number &optional product-id => counter-value
(setf (counter-value counter-number &optional product-id) new-counter-value)


Returns or sets the value of the counter counter-number in the dongle with the product ID product-id.


[Generic function]
make-static-u32-array initial-elements &optional copyp => static-array


Utility function which returns an array of element type (UNSIGNED-BYTE 32) allocated in the static area and filled with the initial elements initial-elements (a list or an array). The length of the array is determined by initial-elements. If initial-elements is already a static array and copyp is NIL, then this array will simply be returned.

Static arrays are mainly used for performance reasons within CL-DONGLE. Most functions which accept sequence arguments can send (the addresses of) static arrays directly to the SG-Lock API while non-static arrays or lists have to be converted first.


[Function]
random-key => array


Utility function which returns a static array of 4 random (UNSIGNED-BYTE 32) integers, i.e. a value which can be used as a random key within CL-DONGLE.

 

Encryption

All functions in this section have two variants, one starting with "DONGLE-" which means that the operation is performed in the dongle and one starting with "TEA-" (for Tiny Encryption Algorithm) which means that the operation is implemented in pure Lisp and performed in RAM. (Specifically, this means that the "TEA-" functions can be used without a dongle and without the SG-Lock DLL.)

As long as they're using the same key, these functions should return the same values no matter whether they are performed in the dongle or not. So, for instance, you can encrypt something in RAM and then decrypt it using the dongle or vice versa. See the test code for examples.


[Function]
tea-encrypt key data &key length copyp => data'


Encrypts data in RAM (without using a dongle and without accessing the SG-Lock DLL) with the Tiny Encryption Algorithm using the key key. data must either be a sequence of (UNSIGNED-BYTE 32) integers or an FLI pointer. In the latter case, length is the number of dwords to be processed. In both cases, the number of dwords to be processed must be even. key must be a four-element sequence of (UNSIGNED-BYTE 32) integers representing the key to be used.

If data is a pointer, the encryption is performed in place and the pointer is returned, otherwise a static array containing the encrypted elements from data is the return value. If data is a static array of element type (UNSIGNED-BYTE 32) and copyp is NIL, the contents of data will also be modified in place and data will be returned.

This is the inverse of TEA-DECRYPT. See also DONGLE-ENCRYPT.


[Function]
tea-decrypt key data &key length copyp => data'


Decrypts data in RAM (without using a dongle and without accessing the SG-Lock DLL) with the Tiny Encryption Algorithm using the key key. data must either be a sequence of (unsigned-byte 32) integers or an FLI pointer. In the latter case, length is the number of dwords to be processed. In both cases, the number of dwords to be processed must be even. key must be a four-element sequence of (UNSIGNED-BYTE 32) integers representing the key to be used.

If data is a pointer, the encryption is performed in place and the pointer is returned, otherwise a static array containing the encrypted elements from DATA is the return value. If DATA is a static array of element type (UNSIGNED-BYTE 32) and copyp is NIL, the contents of data will also be modified in place and data will be returned.

This is the inverse of TEA-ENCRYPT. See also DONGLE-DECRYPT.


[Function]
dongle-encrypt key-number data &key product-id length copyp => data'


Encrypts data using the dongle with the product ID product-id and the key with the number key-number. data must either be a sequence of (UNSIGNED-BYTE 32) integers or an FLI pointer. In the latter case, length is the number of dwords to be processed. In both cases, the number of dwords to be processed must be even.

If data is a pointer, the encryption is performed in place and the pointer is returned, otherwise a static array containing the encrypted elements from DATA is the return value. If data is a static array of element type (UNSIGNED-BYTE 32) and copyp is NIL, the contents of data will also be modified in place and data will be returned.

This is the inverse of DONGLE-DECRYPT. See also TEA-ENCRYPT.


[Function]
dongle-decrypt key-number data &key product-id length copyp => data'


Decrypts data using the dongle with the product ID product-id and the key with the number key-number. data must either be a sequence of (UNSIGNED-BYTE 32) integers or an FLI pointer. In the latter case, length is the number of dwords to be processed. In both cases, the number of dwords to be processed must be even.

If data is a pointer, the decryption is performed in place and the pointer is returned, otherwise a static array containing the decrypted elements from data is the return value. If data is a static array of element type (UNSIGNED-BYTE 32) and copyp is NIL, the contents of data will also be modified in place and data will be returned.

This is the inverse of DONGLE-ENCRYPT. See also TEA-DECRYPT.


[Function]
tea-encrypt-string key string => array


Encrypts the Lisp string string in RAM (without using a dongle and without accessing the SG-Lock DLL) with the Tiny Encryption Algorithm using the key key. key must be a four-element sequence of (UNSIGNED-BYTE 32) integers representing the key to be used.

The return value is a (static) array of integers of type (UNSIGNED-BYTE 32) which can be (given the right key) decrypted back to a string using TEA-DECRYPT-TO-STRING or DONGLE-DECRYPT-TO-STRING.


[Function]
tea-decrypt-to-string key data => string


Decrypts data to a Lisp string in RAM (without using a dongle and without accessing the SG-Lock DLL) with the Tiny Encryption Algorithm using the key key. key must be a four-element sequence of (UNSIGNED-BYTE 32) integers representing the key to be used. data must be a sequence of integers of type (UNSIGNED-BYTE 32). This operation will obviously only succeed if data is (equivalent to) the result of TEA-ENCRYPT-STRING or DONGLE-ENCRYPT-STRING with the same key.

The returned string will be STRING= but not identical to the string that was originally encrypted.


[Function]
dongle-encrypt-string key-number string &key product-id => array


Encrypts the Lisp string string using the dongle with the product ID product-id and the key with the number key-number. The return value is a (static) array of integers of type (UNSIGNED-BYTE 32) which can be (given the right key) decrypted back to a string using DONGLE-DECRYPT-TO-STRING or TEA-DECRYPT-TO-STRING.


[Function]
dongle-decrypt-to-string key-number data &key product-id => string


Decrypts data to a Lisp string using the dongle with the product ID product-id and the key with the number key-number. data must be a sequence of integers of type (UNSIGNED-BYTE 32). This operation will obviously only succeed if data is (equivalent to) the result of DONGLE-ENCRYPT-STRING or TEA-ENCRYPT-STRING with the same key.

The returned string will be STRING= but not identical to the string that was originally encrypted.


[Function]
tea-encrypt-lisp-object key object => array


Encrypts the Lisp object object in RAM (without using a dongle and without accessing the SG-Lock DLL) with the Tiny Encryption Algorithm using the key key. key must be a four-element sequence of (UNSIGNED-BYTE 32) integers representing the key to be used.

The return value is a (static) array of integers of type (UNSIGNED-BYTE 32) which can be (given the right key) decrypted back to a Lisp object using TEA-DECRYPT-TO-LISP-OBJECT or DONGLE-DECRYPT-TO-LISP-OBJECT.

Objects are encrypted using a naïve serialization algorithm based on WRITE-TO-STRING which is neither fast nor space-efficient. Furthermore, only objects which can be printed readably can be encrypted this way.


[Function]
tea-decrypt-to-lisp-object key data => object


Decrypts data to a Lisp object in RAM (without using a dongle and without accessing the SG-Lock DLL) with the Tiny Encryption Algorithm using the key key. key must be a four-element sequence of (UNSIGNED-BYTE 32) integers representing the key to be used. This operation will obviously only succeed if data is (equivalent to) the result of TEA-ENCRYPT-LISP-OBJECT or DONGLE-ENCRYPT-LISP-OBJECT with the same key.

Decrypted numbers, symbols, and characters will be EQL to the objects originally encrypted. Other objects will only be EQUAL or EQUALP. See the docstring of STORE-LISP-OBJECT and the code in serialize.lisp.


[Function]
dongle-encrypt-lisp-object key-number object &key product-id => array


Encrypts the Lisp object object using the dongle with the product ID product-id and the key with the number key-number. The return value is a (static) array of integers of type (UNSIGNED-BYTE 32) which can be (given the right key) decrypted back to a Lisp object using DONGLE-DECRYPT-TO-LISP-OBJECT or TEA-DECRYPT-TO-LISP-OBJECT.

Objects are encrypted using a naïve serialization algorithm based on WRITE-TO-STRING which is neither fast nor space-efficient. Furthermore, only objects which can be printed readably can be encrypted this way.


[Function]
dongle-decrypt-to-lisp-object key-number data &key product-id => object


Decrypts data to a Lisp object using the dongle with the product ID product-id and the key with the number key-number. data must be a sequence of integers of type (UNSIGNED-BYTE 32). This operation will obviously only succeed if data is (equivalent to) the result of DONGLE-ENCRYPT-LISP-OBJECT or TEA-ENCRYPT-LISP-OBJECT with the same key.

Decrypted numbers, symbols, and characters will be EQL to the objects originally encrypted. Other objects will only be EQUAL or EQUALP. See the docstring of STORE-LISP-OBJECT and the code in serialize.lisp.


 

Storing and retrieving data

Note that what you can actually store into the dongle obviously depends on the amount of memory available.


[Function]
store-data address data &key product-id start end => address'


Stores the contents of the (UNSIGNED-BYTE 32) sequence data from start to end in the dongle with the product ID product-id starting at memory address address. address uses a 32-bit addressing scheme corresponding to the values returned by DONGLE-MEMORY-SIZE.

Returns the address following the stored data. (Note that depending on the memory size of the dongle this might be an illegal address.)


[Function]
store-string address string &key product-id start end => address'


Stores the Lisp string string from start to end in the dongle with the product ID product-id starting at memory address address. address uses a 32-bit addressing scheme corresponding to the values returned by DONGLE-MEMORY-SIZE. The string is stored in such a way that a string which is STRING= to it can be retrieved using RETRIEVE-STRING. A string of length N will use up
(ceiling (1+ n) 2)
dwords in the dongle.

Returns the address following the stored data. (Note that depending on the memory size of the dongle this might be an illegal address.)


[Function]
store-lisp-object address object &optional product-id => address'


Stores the Lisp object object in the dongle with the product ID product-id starting at memory address address. address uses a 32-bit addressing scheme corresponding to the values returned by DONGLE-MEMORY-SIZE. The object is stored in such a way that it can be retrieved using RETRIEVE-LISP-OBJECT.

Objects are stored using a naïve serialization algorithm based on WRITE-TO-STRING which is neither fast nor space-efficient. Furthermore, only objects which can be printed readably can be stored this way.

Returns the address following the stored data. (Note that depending on the memory size of the dongle this might be an illegal address.)


[Function]
retrieve-data address count &optional product-id => array


Returns count values stored at the memory area starting at address from the dongle with the product ID product-id as a static array of element-type (UNSIGNED-BYTE 32). address uses a 32-bit addressing scheme corresponding to the values returned by DONGLE-MEMORY-SIZE.


[Function]
retrieve-string address &optional product-id => string


Returns the Lisp string from the dongle with the product ID product-id which is stored at memory address address. Obviously, this will only work if the string was previously stored using STORE-STRING. address uses a 32-bit addressing scheme corresponding to the values returned by DONGLE-MEMORY-SIZE.

The returned string will be STRING= but not identical to the string that was originally stored.


[Function]
retrieve-lisp-object address &optional product-id => object


Returns the Lisp object from the dongle with the product ID product-id which is stored at memory address address. Obviously, this will only work if the object was previously stored using STORE-LISP-OBJECT. address uses a 32-bit addressing scheme corresponding to the values returned by DONGLE-MEMORY-SIZE.

Numbers, symbols, and characters will be EQL to the objects originally stored. Other objects will only be EQUAL or EQUALP. See the docstring of STORE-LISP-OBJECT and the code in serialize.lisp.


 

Signing data

One important factor to consider when signing large amounts of data (like files) is speed. On a laptop bought in 2007 I can process a 20MB file in not much more than a second if I sign everything in RAM (using pure Lisp code), i.e. using NIL as the value for interval below. If the whole file is signed in the dongle (interval being 0), I can have dinner in the meantime.

Unfortunately, the "all-in-RAM" method is orders of magnitude more insecure than using the dongle (which is the reason you're using this thingy in the first place), so the trick is to find the right compromise...


[Generic function]
sign data &key feedback-value interval key-number key product-id &allow-other-keys => integer


Signs data using the dongle with the product ID product-id (the default being *PRODUCT-ID* as usual) and/or the Tiny Encryption Algorithm in RAM. data can be a sequence (i.e. a list or an array) of (UNSIGNED-BYTE 32) integers or a pathname denoting a file or an FLI pointer pointing to an area of memory which is to be signed. In the last case, the keyword argument length specifies how many octets of data are to be signed.

If data is a pathname, the whole contents of the file denoted by DATA will be mapped into memory. If you don't want that, you will have to read smaller chunks of the file using READ-SEQUENCE and sign them using successive calls to SIGN. The test code contains an example for this technique - see the file sign.lisp.

If interval is NIL, neither a dongle nor the SG-Lock DLL is used and the signing takes place in RAM using the Tiny Encryption Algorithm. Otherwise, interval must be a non-negative integer N such that 2^(N+3) is still a fixnum. If M is 2^N, then each Mth 64-bit block (which always include the first one) will be signed using the dongle while all others will be signed in RAM. Specifically, if N is 0 (zero), all the data will be signed using the dongle. The default for interval is the greatest possible non-NIL value.

If interval is not NIL, key-number must be specified to denote which key of the dongle is to be used for signing. If interval is not 0 (zero), key must be specified to denote the key - a four-element sequence of (UNSIGNED-BYTE 32) integers - for the in-RAM signing.

The signing process operates on successive 64-bit blocks using the signature of the previous block as the initial "feedback" value. The signature of the last block is returned by SIGN as the signature of data. This, together with the fact that the first block is always signed using the dongle if interval is not NIL, ensures that the signature of data always depends on the dongle if interval is an integer.

If the block of data to be signed is a sequence of octets the length of which is not divisible by 8, then the function makes sure that the sequence is extended consistently. As a technical detail, this 64-bit "fill" block is only ever signed using the dongle if interval is not NIL and if data consists of less than eight octets.

The return value of SIGN is an integer of type (UNSIGNED-BYTE 64).

Signing always starts with the same "feedback" value unless you explicitly provide an (UNSIGNED-BYTE 64) integer feedback-value to start with. This can be utilized to sign larger amounts of data with several successive calls to SIGN usign the return value of one call as the "feedback value" for the next call. See also the remark about the test code above.


[Function]
sign-lisp-object object &key feedback-value interval key-number key product-id => integer


Signs the Lisp object object by first serializing it using a naïve algorithm based on WRITE-TO-STRING and then calling SIGN. All parameters except for object are fed to SIGN.

 

Conditions

All errors signalled by CL-DONGLE are of type DONGLE-ERROR. More details about the conditions you can encounter can be found in this section.


[Condition type]
dongle-error


The superclass for all errors signalled by the CL-DONGLE library.


[Condition type]
authentication-failed


This error is signalled if the mandatory initial authentication (function AUTHENTICATE) failed. This is equivalent to getting the return value SGL_AUTHENTICATION_FAILED from the SG-Lock API.


[Condition type]
authentication-required


This error is signalled if functions of the SG-Lock API are called prior to the mandatory initial authentication - see the function AUTHENTICATE. This is equivalent to getting the return value SGL_AUTHENTICATION_REQUIRED from the SG-Lock API.


[Condition type]
parameter-invalid


This error is signalled if an API function was called with parameters which are out of range. This is equivalent to getting the return value SGL_PARAMETER_INVALID from the SG-Lock API.


[Condition type]
function-not-supported


This error is signalled if an API function was called which is not supported by the current dongle. This is equivalent to getting the return value SGL_FUNCTION_NOT_SUPPORTED from the SG-Lock API.


[Condition type]
dongle-not-found


This error is signalled if no dongle corresponding to the product ID which was provided was found. This is equivalent to getting the return value SGL_DGL_NOT_FOUND from the SG-Lock API. But see also the description of the AUTHENTICATE function.


[Condition type]
usb-port-busy


This error is signalled if at least one USB port is busy and no matching dongles were found on the other ports, if there are any. This is equivalent to getting the return value SGL_USB_BUSY from the SG-Lock API.


[Condition type]
lpt-open-error


This error is signalled if the SG-Lock LPT driver wasn't found although LPT support was installed. This is equivalent to getting the return value SGL_LPT_OPEN_ERROR from the SG-Lock API.


[Condition type]
lpt-port-busy


This error is signalled if at least one LPT port is busy and no matching dongles were found on the other ports, if there are any. This is equivalent to getting the return value SGL_LPT_BUSY from the SG-Lock API.


[Condition type]
no-lpt-port-found


This error is signalled if no LPT port was found on the PC although LPT support was installed. This is equivalent to getting the return value SGL_NO_LPT_PORT_FOUND from the SG-Lock API.


[Condition type]
signature-invalid


This error is signalled if one of the API function was asked to verify a signature which turned out to be invalid. This is equivalent to getting the return value SGL_SIGNATURE_INVALID from the SG-Lock API.

You actually shouldn't see this error when using CL-DONGLE as you're supposed to check signatures yourself - by simply applying the function = to the values returned by SIGN or SIGN-LISP-OBJECT.


[Condition type]
unknown-return-value


This is a typical this-should-not-happen error. It will be signalled in the case that one of the C functions in the SG-Lock DLL returns an undocumented value.

 

Symbol index

Here are all exported symbols of CL-DONGLE in alphabetical order linked to their corresponding entries:
 

Acknowledgements

This documentation was prepared with DOCUMENTATION-TEMPLATE.

$Header: /usr/local/cvsrep/cl-dongle/doc/index.html,v 1.18 2008/05/01 14:47:43 edi Exp $

BACK TO MY HOMEPAGE