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:

ndt 1 11

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:

ndt 1 12

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 passphrase. Type a passphrase and press Enter.

ppmk prompts you to confirm the passphrase you have entered. Type the passphrase 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 and EasyConnection. 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

kid

M_KeyID

Public key ID

c

EasyConnection

Connection to the hardserver

wcb

WorldCallbacks

Callback object which defines how user interaction is handled

world

SecurityWorld

Security World object

appname

String

Application name

ident

String

Key identity

type

String

Key type

size

int

Key size in bytes

chanmech

int

Cryptographic mechanism used by the secure channel

chanop

int

Secure channel ID

iv

M_IV

Initialization vector

ch

Channel

Secure channel object

softcard

SoftCard

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 passphrase. 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 passphrase: ");
    } 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:

ndt 1 13
This tutorial does not cover details of ACL generation.

The parameters of the generate_key() function are:

Parameter name Parameter Type Parameter description

wcb

WorldCallbacks

Callback class that defines user interaction behavior.

world

SecurityWorld

Contains information about the Security World you are using.

type

String

The type of key, for example, AES, RSA, DSA.

len

int

The length of the key you want to generate, in bits.

protection

int

The type of key protection to be used. This can be any of the flags defined in NFKM_Key_flags

prot_name

String

The name of the softcard / module / card that is used to protect the key you want to generate.

appname

String

The name of the application that is requesting that a key is generated. The key name is formed by a combination of the appname and the ident.

ident

String

An arbitrary string that becomes part of the key name. The key name is formed by a combination of the appname and the ident.

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();

Cleaning up resources

Finally, unload the keys in the module memory.

if(kid != null) c.destroy(kid);
if(pubkid != null) c.destroy(pubkid);