Java tutorial
Overview
This overview section provides a description of how to achieve two fundamental nCore API programming tasks: connecting to the hardserver and transacting a command. These two tasks are common to almost all cryptographic applications. The rest of this chapter works through a simple example of a basic cryptographic application.
All applications that require nCore functionality will first need to create a connection to a hardserver running on a nShield module. The following diagram illustrates the steps required to create a connection to a hardserver running on Entrust hardware:
Once connected to the hardserver, an application can send an M_Command
to a module.
The module processes the command and then returns the results along with any relevant error and status codes.
The following diagram illustrates the process of transacting a cryptographic operation with a module:
The M_Reply
structure contains the results of the operation and an M_Status
message that indicates the outcome of the operation.
If a problem is encountered, the M_Status
value gives an indication of what went wrong.
The M_Reply
contains the results of the command, for example, a key handle or the bytes of an encrypted file.
Creating a softcard
This tutorial demonstrates how to protect a key using a softcard.
Use the command line utility ppmk
to create a softcard in a manner similar to the following:
In a terminal window, type:
ppmk --new --non-recoverable WorkedExampleSoftcard
ppmk
prompts you to provide a pass phrase.
Type a pass phrase and press Enter.
ppmk
prompts you to confirm the pass phrase you have entered.
Type the pass phrase again to confirm it, and press Enter.
nCore classes used in this tutorial
This tutorial describes some of the functionality in the following nCore classes.
You may find it useful to familiarize yourself with these classes by reading the API documentation, which can be found at <nfast_dir>/java/docs/index.html
.
-
com.ncipher.km.nfkm.*
Security World classes.
-
com.ncipher.km.marshall.*
Marshals Security World objects.
-
com.ncipher.jutils.*
Various utility classes provided by Entrust.
-
com.ncipher.nfast.*
More utility classes.
-
com.ncipher.nfast.marshall.*
Classes which represent nCore commands and related data structures, and which can be used to marshal and unmarshal them from the nShield byte stream format for transmission.
-
com.ncipher.nfast.connect.utils.*
Connection and Channel utility classes. The code in this chapter also uses two connection utility classes,
Channel
andEasyConnection
. The source code for these examples can be found at<nfast_dir>/java/examples/connutils
.
Variables used in this tutorial
The following table lists and describes the variables used in this tutorial. You may also find it useful to view the API documentation of these classes.
Variable name | Variable type | Variable description |
---|---|---|
|
|
Public key ID |
|
|
Connection to the hardserver |
|
|
Callback object which defines how user interaction is handled |
|
|
Security World object |
|
|
Application name |
|
|
Key identity |
|
|
Key type |
|
|
Key size in bytes |
|
|
Cryptographic mechanism used by the secure channel |
|
|
Secure channel ID |
|
|
Initialization vector |
|
|
Secure channel object |
|
|
Softcard object |
Before connecting to the hardserver
The WorldCallbacks
class defines how the hardserver interacts with the user when obtaining authorization to create or use a key.
The WorldCallbacks
class extends the DefaultCallBack
class to customize how the user will be prompted to enter a softcard pass phrase.
An instance of this class is used as a parameter when instantiating a SecurityWorld
object.
If you do not pass an instance of a similar class the behavior defined in the DefaultCallBack
class is used.
class WorldCallbacks extends DefaultCallBack {
public SoftCard configured_softcard = null;
public String reqPPCallBack(String ReqPPAction) throws NFException {
try {
return Passphrase.readPassphrase("Enter softcard pass phrase: ");
} catch(IOException e) {
throw new NFException(e.toString());
}
}
// Callback to choose a softcard
public SoftCard getSoftCardCallback() throws NFException {
return configured_softcard;
};
};
Before connecting to the hardserver, instantiate a WorldCallBacks
object and a SecurityWorld
object as follows:
WorldCallbacks wcb = new WorldCallbacks();
SecurityWorld world = new SecurityWorld(null, wcb,
null,
true);
Connecting to the hardserver
The following code creates the connection to the hardserver using the EasyConnection
utility class constructor to wrap an NFConnection
object:
c = new EasyConnection(world.getConnection());
Generating a key
The first step is to specify the parameters of a key that can be used to sign a file. In this case we choose to generate a DSA key. We specify the key-generation parameters as follows:
appname = "simple";
ident = "worked-example-sign";
type = "DSA";
size = 1024;
chanmech = M_Mech.SHA1Hash;
sigmech = M_Mech.DSA;
iv = new M_IV();
chanop = M_ChannelMode.Sign;
Before attempting to generate a key, use the getKey()
method of the SecurityWorld
class to check if a key with the given appname
and ident
already exists.
The getKey()
method returns null
if it cannot find the specified key.
Key k = world.getKey(appname, ident);
If getKey()
returns null
this example attempts to generate a key.
If no softcard has been named to protect this key, the key is protected using module protection.
if(k == null) {
if(softcard_name != "") {
k = generate_key(wcb, world, type, size,
NFKM_Key_flags.f_ProtectionPassPhrase,
softcard_name,
appname, ident);
} else {
k = generate_key(wcb, world, type, size,
NFKM_Key_flags.f_ProtectionModule,
null,
appname, ident);
}
}
generate_key()
is a utility function written specifically for this example. generate_key()
uses an AppKeyGenerator
object which is obtained by calling the getAppKeyGenerator()
method of the SecurityWorld
object.
The AppKeyGenerator
class requires a AppKeyGenProperty[]
array which contains the parameters that specify the key you want to generate.
If a key cannot be generated using the specified parameters, AppKeyGenerator
throws an nfkmInvalidPropValuesException
. You can call the check()
method to test whether the AppKeyGenProperty[]
contains valid values.
The properties themselves differ according to your Security World configuration.
The generate_key
method uses two utility functions written specifically for this tutorial, setStringProperty()
and setMenuProperty()
, which are used to set the AppKeyGenProperty[]
array.
The following diagram illustrates the process of generating a key:
This tutorial does not cover details of ACL generation. |
The parameters of the generate_key()
function are:
Parameter name | Parameter Type | Parameter description |
---|---|---|
|
|
Callback class that defines user interaction behavior. |
|
|
Contains information about the Security World you are using. |
|
|
The type of key, for example, AES, RSA, DSA. |
|
|
The length of the key you want to generate, in bits. |
|
|
The type of key protection to be used. This can be any of the flags defined in NFKM_Key_flags |
|
|
The name of the softcard / module / card that is used to protect the key you want to generate. |
|
|
The name of the application that is requesting that a key is generated.
The key name is formed by a combination of the |
|
|
An arbitrary string that becomes part of the key name.
The key name is formed by a combination of the |
The first step is to obtain an AppKeyGenerator
object from the SecurityWorld
object:
AppKeyGenerator akg = world.getAppKeyGenerator(appname);
Next, as a safety measure we check that all the required key properties are supported by this AppKeyGenerator
object.
In this example, the most likely reason that required key properties are not supported is that no softcard which can be used to protect the key to be generated exists in the Security World:
String[] properties = new String[] {
"ident",
"type",
"size",
"protect"
};
for (int i = 0; i < properties.length; i++) {
if (akg.getProperty(properties[i]) == null) {
System.out.println("Property " + properties[i] + " does not exist." +
"Does your security world contain a usable softcard?");
System.exit(0);
}
}
If all properties exist, populate the AppKeyGenProperty[]
using the setStringProperty()
and setMenuProperty()
functions.
The protect
property is set, dependent on how the key is to be protected.
This example expects the key to be softcard protected.
Failing that the example defaults to module protection.
Card set protection is not supported in this example.
setStringProperty(akg, "ident", ident);
setMenuProperty(akg, "type", type);
setStringProperty(akg, "size", Integer.toString(len));
switch(protection) {
case NFKM_Key_flags.f_ProtectionModule:
setMenuProperty(akg, "protect", "module");
break;
case NFKM_Key_flags.f_ProtectionPassPhrase:
setMenuProperty(akg, "protect", "softcard");
SoftCard cards[] = world.getSoftCards();
wcb.configured_softcard = null;
for(int n = 0; n < cards.length; ++n) {
if(cards[n].getName().equals(prot_name)) {
wcb.configured_softcard = cards[n];
}
if(wcb.configured_softcard == null) {
throw new NoSuchSoftCard(prot_name);
break;
}
}
}
Before calling the generate()
function of the AppKeyGenerator
class to generate the key, it is good practice to check that the values assigned to the properties are valid.
If the properties are valid, call the generate()
function, which returns a reference to the newly created key:
InvalidPropValue badprops[] = akg.check();
if(badprops.length > 0) {
throw new BadKeyGenProperties(badprops);
}
return akg.generate(getUsableModule(world), null);
Finally, call the cancel()
method to destroy key information that is resident in memory.
akg.cancel();
Methods used in generate_key()
The getUsableModule()
method was written for the purposes of this example and simply cycles through all the modules in the Security World until it finds one that is suitable:
public static Module getUsableModule(SecurityWorld world)
throws NFException {
Module modules[] = world.getModules();
for(int m = 0; m < modules.length; ++m)
if(modules[m].isUsable())
return modules[m];
throw new NoUsableModules();
}
To select a specific module, use the getModule()
function of the SecurityWorld
class.
The getModule()
function is overloaded to accept either a module number or a module Electronic Serial Number (ESN) as a parameter.
The setStringProperty()
method was written for the purposes of this example and sets a string property.
public static void setStringProperty(AppKeyGenerator akg,
String propname,
String propvalue)
throws NFException {
PropValueString pvs = (PropValueString)akg.getProperty(propname).getValue();
pvs.value = propvalue;
}
The setMenuProperty()
method was written for the purposes of this example and sets a menu property.
public static void setMenuProperty(AppKeyGenerator akg,
String propname,
String propvalue)
throws NFException {
PropValueMenu pvm = (PropValueMenu)akg.getProperty(propname).getValue();
MenuOption options[] = pvm.getOptions();
for(int i = 0; i < options.length; ++i)
if(options[i].getName().equals(propvalue)) {
pvm.value = i;
return;
}
}
throw new InvalidMenuItem(propvalue);
}
Using a key
Before using a key the key must be loaded onto a module. In this example we expect the key being loaded to be softcard protected, or failing that, module protected.
Module module = getUsableModule(world);
SoftCard softcard = k.getSoftCard();
if(softcard != null) {
softcard.load(module, wcb);
kid = k.load(softcard, module);
} else {
kid = k.load(module);
}
Signing a file
Now that the key is loaded onto the module, open a secure channel to use to sign a text file.
Channel ch = c.openChannel(chanop, kid, chanmech, iv, true, true);
The openChannel()
method of the EasyConnection
class returns a subclassed Channel
object.
For this example, the openChannel()
function transacts an M_Cmd.ChannelOpen
command and uses the M_Cmd_Reply_ChannelOpen
object returned in the reply to instantiate and then return a Channel.Sign
object.
M_Cmd_Args_ChannelOpen args = new M_Cmd_Args_ChannelOpen(
new M_ModuleID(0), M_ChannelType.Simple, 0, how, mech);
if (!keyless) {
args.set_key(key);
}
if (!generateIV) {
args.set_given_iv(given_iv);
}
M_Reply rep = transactChecked(new M_Command(M_Cmd.ChannelOpen, 0,args));
M_Cmd_Reply_ChannelOpen corep = (M_Cmd_Reply_ChannelOpen) rep.reply;
if ( 0 != (corep.flags & corep.flags_new_iv) ) {
given_iv.mech = corep.new_iv.mech;
given_iv.iv = corep.new_iv.iv;
}
return new Channel.Sign(mech, key, corep.new_iv, corep.idch, this);
Channel.Sign
extends the abstract Channel
class.
The update()
function reads the specified byte[]
into the channel.
The updateFinal()
method reads the specified byte array into the channel, but should only be called when reading the final byte[]
array that you want to process through the channel.
public static class Sign extends Channel {
public Sign(long mech, M_KeyID keyID, M_IV iv, M_KeyID channelID, EasyConnection parent) {
super(M_ChannelMode.Sign, mech, keyID, iv,channelID, parent);
}
public void update(byte[] input) throws MarshallTypeError,
CommandTooBig,
ClientException,
ConnectionClosed,
StatusNotOK {
super.update(input, false, false);
}
public byte[] updateFinal(byte[] input) throws MarshallTypeError,
CommandTooBig,
ClientException,
ConnectionClosed,
StatusNotOK {
return super.update(input, true, false);
}
}
Now that the signing channel is open, open the input file to be signed, and a FileOutputStream
for the signature.
FileInputStream input = null;
FileOutputStream output = null;
input = new FileInputStream(plaintext_path);
Finally, use the channel to read in the input file bytes:
byte inputbytes[] = new byte[4096];
int len = input.read(inputbytes);
while(len != -1) {
byte outputbytes[] = ch.update(arrayTruncate(inputbytes, len),
false,
false);
if(output != null)
output.write(outputbytes);
len = input.read(inputbytes);
}
}
byte outputbytes[] = ch.update(new byte[0],
true,
false);
The arrayTruncate()
function was written specifically for this example, and ensures that the byte[]
used to update the channel is consistently chunked.
static byte[] arrayTruncate(byte[] in, int len) {
byte out[] = new byte[len];
for(int i = 0; i < len; ++i)
out[i] = in[i];
return out;
}
Next, create the hash
and plaintext
objects.
hash = new M_Hash(outputbytes);
plaintext = new M_PlainText(M_PlainTextType.Hash,
new M_PlainTextType_Data_Hash(hash));
Transact an M_Cmd.Sign
operation to sign the hashed plaintext:
cmd = new M_Command(M_Cmd.Sign,
0,
new M_Cmd_Args_Sign(0,
kid,
sigmech,
plaintext));
try {
reply = c.transactChecked(cmd);
} catch (StatusNotOK sno) {
System.exit(0);
}
If the M_Cmd.Sign
operation succeeded, marshal the signature
to a stream of bytes, and saves the bytes as a signature file:
signature = ((M_Cmd_Reply_Sign)reply.reply).sig;
MarshallContext mc = new MarshallContext();
signature.marshall(mc);
output = new FileOutputStream(signature_path);
output.write(mc.getBytes());
if(output != null) output.close();