Designing SEE machines and SEE-ready HSMs
This manual addresses SEE for the Solo XC and Connect XC.
For Solo XC, see Writing SEE machines - Solo XC
Writing SEE machines - Solo XC
This chapter describes how to write a SEE machine for use on SEE-Ready HSMs.
An SEE machine is an executable binary file of a type appropriate for the HSM that communicates with the nShield core (which runs in kernel mode) using a defined set of software interrupts. These interrupts, and their wrapper functions, provide a run-time environment that includes memory and thread management as well as an interface for accepting and returning jobs and calling nCore API commands.
C source code is compiled using one of the GCC cross-compilers supplied with the CodeSafe Developer Kit. For details of required compiler options; see Example SEE machines and the makefiles supplied with the examples.
The compiled code can then be signed, packed, and encrypted by using the Trusted Code Tool (tct2
utility) to produce a secure archive; see Utilities.
In CodeSafe versions prior to 13.3, the Solo XC only supports SEE machines smaller than 70 MB. From 13.3 onwards, the Solo XC can support SEE machines up to 800 MB. |
Designing for the glibc architecture
The GNU C library glibc
is supplied together with libpthreads
, librt
and a system call underlay for use with CodeSafe SEE development.
A rich set of C function calls is available to use in SEE machine development. Native support for Unix-based system calls is provided, only restricted by an allowlist of the system calls (Allowlist for SEE machines) allowed in the SEE environment.
A subset of the Unix-based system calls, implemented in terms of the inter-process communication interface (IPC), allows access to the cryptographic HSM kernel. The provided system calls include a virtual file system and associated set of input and output devices with which you interact in the standard manner.
The virtual file system is supported as an extension to the file system.
Also provided are some link-time plug-ins that extend the virtual file system to provide additional capabilities:
-
hoststdioe.o: stdin
,stdout
, andstderr
facility hooks;seestream_stdio(7see)
-
hoststdoe.o: stdout
andstderr
facility hooks;seestream_stdio(7see)
-
hostinetsocks.o:
TCP socket facility hooks;seestream_inet(7see)
-
hoststdioeinetsocks.o:
TCP socket facility andstdin
,stdout
, andstderr
facility hooks;seestream_inet(7see)seestream_stdio(7see)
The link-time plug-in vulnerability.o is provided for the purposes of debugging (see Vulnerability test harness).
Entrust recommends that you do not link vulnerability.o into a production SEE machine.
|
Designing for the SEElib architecture
This section describes how to design SEE machines using the SEElib
architecture.
This kind of architecture requires host-side software to create the SEE World and communicate with the HSM.
To start the SEE machine running with a particular SEE userdata
, the host application calls the nCore API command CreateSEEWorld
.
This command creates a SEE World using data previously loaded into the HSM with the LoadBuffer
command from a buffer created with the CreateBuffer
command.
See the nCore API Documentation (supplied as HTML) for information about the nCore API commands.
You can also use or adapt the supplied example Java class SEEWorld
to initialize the SEE machine.
When the host application calls CreateSEEWorld
, the HSM allocates memory for the SEE World and sets up its input and output job queues.
It then runs the SEE machine’s main()
function.
The SEE machine’s main()
function must:
-
Call
SEElib_init()
before any other SEE library function to initialize the SEE library and to check that the HSM is running the expected version of the library -
If the machine accepts
userdata
:-
call
SEElib_GetUserDataLen
to determine the length of the byte block that was passed withCreateSEEWorld
-
call
SEElib_ReadUserData
to load the byte block -
determine whether the byte block is valid
-
initialize any required structures
-
-
Start at least one thread which receives and processes commands (this thread must call
SEElib_AwaitJob
)The SEElib_StartProcessorThreads
function can be used for this purpose. -
Call
SEElib_InitComplete
and return a status.
The status passed to SEElib_InitComplete
is returned to the calling application in the reply to CreateSEEWorld
The application can determine the status values, with one exception: if the machine fails before calling SEElib_InitComplete()
, the CreateSEEWorld
command returns a value of 1
(SEEInitStatus_MachineFailed
) in this field.
You should therefore avoid choosing the value 1
to indicate successful initialization.
When the application receives the reply to CreateSEEWorld
with Status_OK
and an acceptable initstatus
, it can start to submit jobs with the nCore API command SEEJob
.
You can also use or adapt the supplied example Java class SEEJob
to submit jobs to the SEE machine.
The SEEJob
command takes a byte block, which is passed to the SEElib_AwaitJob
function without being interpreted in any way.
It is up to the host application to assemble this byte block and the SEE machine to interpret it.
After the job has been processed, assemble the reply into a byte block and call SEElib_ReturnJob
to return it using the nShield core.
The nShield core assembles this byte block into a reply and returns it to the host application.
Provided that the job is returned before the command times out, the reply has the status OK
.
The SEE machine must include any necessary status information within the byte block it returns.
The calling application must remember to check this status as well as the status of the SEEJob
nCore API function and the transport call, for example NFastApp_Transact()
.
The SEE machine can call nCore API functions with SEElib_Transact
or SEElib_MarshalSendCommand
and SEElib_GetUnmarshalResponse
.
It may submit these as part of its initialization, before it calls SEElib_InitComplete()
.
However, if it does not call SEElib_InitComplete()
within 30 seconds of start-up, the CreateSEEWorld
command returns SEEInitStatus_MachineFailed
.
For this reason, you should not perform (for example) lengthy key generation operations during initialization.
SEElib_Transact
has syntax equivalent to the NFastApp_Transact
function in the C generic stub.
It takes a command structure and returns a reply structure.
SEElib_MarshalSendCommand
takes a command structure and submits it.
SEElib_GetUnmarshalResponse
reads a response from a buffer and returns a reply structure.
SEElib_StartTransactListener must be called successfully before you use SEElib_Transact to communicate with the nShield core.
|
SEE machines for new algorithms
In addition to being able to perform basic cryptographic operations, any SEE machine that implements an algorithm must also be able to:
-
Generate keys
-
Import keys
-
Store keys as key blobs.
The SEE machine can use the nCore API functions GenerateRandom
and GeneratePrime
to acquire random numbers and random prime numbers from the HSM’s hardware random number generator.
The SEE machine can perform its own multiprecision arithmetic.
Otherwise, it can use the nCore API BignumOp
command to perform multiprecision arithmetic and the ModExp
and ModExpCrt
commands to perform modular exponentiation.
If you are using keys as session keys, there is no requirement for them ever to be placed in the nShield core. The only time that you need to transfer a key to the core is if you need to create a key blob for long-term storage. However, if you need to keep track of several keys, you may want to make use of the nShield core’s object store rather than having to create a similar structure in your own code.
For an example of how See machines can implement a non-standard algorithm, see A3A8 example.
Key type
The SEE machine stores keys using the random
key type.
This is a plain byte block with no structure.
If the key contains several values, for example, exponent and modulus, the SEE machine must implement its own routines for marshalling and unmarshalling the byte block into the correct structure.
SEE machines using standard algorithms do not use the random key type.
Instead, they use standard nCore key types.
|
ACL
The ACL needs to be constructed so that the SEE machine and only the SEE machine can access the key.
To transfer a key from the nShield core to the SEE machine, the key must have the ExportAsPlain
flag set in its ACL.
The permission group with ExportAsPlain
must be protected by a certifier so that this operation can only be performed by the SEE machine.
Although one obvious solution is to use the key that was used to sign the SEE machine, K
Integ, as the certifier, using K
Integ in this way means that whoever signed the SEE machine could potentially access any key for this algorithm.
A better solution is to add an extra signature to the SEE machine by using a second key, K
Auth
The K
Integ signature proves that the code has not changed since it was signed.
The K
Auth signature is then used to control access to keys.
You can use the generatekey
command-line utility to generates keys for use as K
Auth and K
Integ by specifying the seeinteg
application as a key generation parameter.
The ACL must also have the correct MakeBlob
permissions.
If you want to use the standard Security World tools for key management and recovery, the host application can use these tools to create the ACL.
SEE machines using standard algorithms generally do not need to get the key as plain text in the SEE machine. |
Storage
For long-term storage, the key needs to be encapsulated in a key blob that is protected by the Security World or an OCS. To provide OCS replacement and recovery, you may also require additional key blobs protected by other card sets.
You could write a function where your SEE machine calls MakeBlob
and returns the blob to the host.
Alternatively, you could write a method that returns a key ticket and have the host application create the key blobs.
If you are using a Security World, the host application can use nfkm
library calls to create and store the key blobs.
Loading stored keys
In general, it is easier for the host application to manage tokens, because it has direct access to the user interface and can prompt the user to insert cards and enter passphrases.
When the token has been loaded, the host application can load the key and pass a key ticket to the SEE machine.
The SEE machine can then redeem the key ticket for a KeyID
and use this to access the key.
If you have several keys that are protected by a token, it usually makes sense to pass a ticket for the KeyID
of the logical token, rather than passing tickets for each key.
You should also pass in a ticket for the logical token if the host application that loads the token exits afterwards. When it exits, it destroys the logical token’s ID, which invalidates all loaded keys that were using it. Passing the logical token’s ID in to the SEE machine prevents its destruction when the application exits.
C run-time library
Entrust supplies a customized version of the GNU C (glibc) library for Solo XC SEE machines. Common features such as threading and mutexes are provided by glibc
.
See SEElib functions for reference information about glibc
functions.
Signing userdata for additional security
Signing userdata
files can help increase the security of CodeSafe SEE applications.
Both types of SEE machine architecture, using glibc and using SEElib, can take advantage of the security benefits offered by signing userdata
files.
For example, if your SEE machine is intended to perform some cryptography functions using a given key, it would be advantageous to prevent that key from being accessed by any unauthorized SEE machines.
This can be achieved by signing the userdata
file for your SEE machine.
The following figure provides an overview diagram of the process of signing a SEE machine’s userdata
file.
The following sequence, in which an original SEE machine is represented by machine.elf
and an original userdata
file is represented by userdata.bin
, demonstrates the process of signing a SEE machine’s userdata
file:
-
Create the key
K
seemach of typeseeinteg
to sign the SEE machine by running a command similar to:generatekey seeinteg plainname=seemach ...
-
Create the key
K
userdata of typeseeinteg
to sign theuserdata
by running a command similar to:generatekey seeinteg plainname=userdata ...
-
Run the
generatekey
command-line utility to create a keyK
crypto (the key with which your SEE machine is to perform its cryptography functions), specifyingK
userdata for itsseeintegname
:generatekey simple plainname=crypto --seeintegname=userdata ...
This example assumes K
crypto is being created as a Triple DES key. -
Run the
tct2
command-line utility to sign theuserdata
file for your SEE machine with the keyK
userdata, specifyingK
seemach as the SEE machine key:tct2 --sign --key=userdata --machine-key-ident=seemach --infile=userdata.bin --outfile=userdata.sar
For information about the tct2
command-line utility, see tct2. -
Run the
tct2
command-line utility to sign the SEE machine with the keyK
seemach:tct2 --sign --key=seemach -- machine-type=PowerPCELF --is-machine --infile=machine.elf --outfile=machine.sar
The result of the process demonstrated in this sequence of steps is that no SEE machine can use the key K
crypto unless at least one of the following conditions is met:
-
It has been signed by the correct
K
seemach and is used in conjunction with the correctuserdata
file -
You make use of the key recovery feature.
Building your SEE machine and host-side application
The following steps provide an overview of the process you follow to use your application with SEE:
-
If you want to sign or encrypt your application, generate code-signing and confidentiality keys as applicable.
-
Compile and link the host application’s source files using the native compiler on the host. See the diagram in the following step.
-
Compile and link the SEE machine source using the GCC cross compiler. See the following diagram.
-
If required, use the Trusted Code Tool (
tct2
) to sign the SEE machine with the code-signing keys. See Utilities for additional information. -
Use the Trusted Code Tool (
tct2
) to pack the HSM files and create a SAR file. You must pack the binary file even if signatures are not required. See Utilities for additional information. -
Use the Trusted Code Tool (
tct2
) to pack (and, if required, sign with the code-signing keys) theuserdata
file and create a SAR file. You must pack theuserdata
file even if signatures are not required (unless you use one of thesee-*-serv
host utilities with the--userdata-raw
option. See Utilities for additional information. -
If required, use the Trusted Code Tool (
tct2
) to encrypt theuserdata
file, using the confidentiality key. -
Place the
userdata
SAR file and the host application in an appropriate location to be used at runtime. -
For SEE machines using the
SEElib
architecture,userdata
file can either be either loaded automatically or can be loaded by running theloadmache
command-line utility.For SEE machines that require support from a host-side
see-*-serv
utility, the host utility loads theuserdata
file automatically.The following diagram shows these different methods for loading a SEE machine.
For more information, see Automatically loading a SEE machine.