Table of Contents
Validy technology is described more precisely in several documents and presentations available at Validy's web site. This chapter gives a short overview of the technology to understand the work performed by the translator.
The first characteristic of Validy technology is that the application is protected by subtraction. The coprocessor is not used to check whether the application is authorized to execute. Instead a subset of the application data is stored in the token memory and a subset of the instructions is executed by the token processor. Since the token is secure, the data it contains and the instructions it executes are not observable so they are virtually removed from the original application.
The protection of an application with Validy Technology begins
with the relocation of some of the instance and static fields of some
of its classes inside the memory of the secure coprocessor. When such
a transformed class is loaded or such a transformed object is created,
a chunk of memory is allocated in the heap of the coprocessor to hold
the values of the relocated fields. In the transformed classes, the
relocated instance and static fields are replaced by the address of
these chunks of memory. These addresses are stored in two fields named
k$this and
k$class for instance and static fields
respectively. Figure 3.1, “Relocation of fields to the coprocessor” shows an object of
class A and its fields before and after the
relocation of two fields to the coprocessor.
When the value of a relocated instance field must be read or
written by the application, the value of
k$this is first transmitted to the
coprocessor and then used as the address operand in a load or a store
instruction. If field3 in an object of
class A was originally initialized by the
following bytecode:
ALOAD0 LDC 125 STFLD A.field3 I
The transformed pseudo bytecode will look like:
ALOAD0 LDFLD A.k$this I OUT VM_LDI R1 125 VM_STORE R1 R0 1
where OUT is an instruction that transfers the top
of the Java stack to register R0 in the coprocessor and
VM_LDI and VM_STORE are coprocessor
instructions[1]. The 1 at the end of the store instruction is the offset
of field3 from the value of
k$this stored in R0.
While the Java virtual machine is stack based, the coprocessor uses registers. The conversion between the two models is performed by the translator.
Simply sending the value from the main processor to the coprocessor before each store and retrieving it after each load would not provide any protection to the application. Instead, the translator selects instructions around all the accesses to secured fields and transforms the bytecode so that these instructions are executed by the coprocessor. The virtual machine of the coprocessor is not limited to loading and storing field values to and from its registers. Its instruction set can be divided into the following categories:
Load, store, and move. The values can be immediate, stored in the heap, or on the stack .
Push to and pop from the coprocessor stack.
Binary operations such as arithmetic operations, bitwise logical operations, and comparisons.
Cryptographic operations.
Security support operations.
One important difference between this instruction set and the one of the Java VM is the lack of control flow instructions. The coprocessor does not maintain a program counter that could be changed to implement control flow[2]. Instead, the responsibility for control flow rests completely on the main processor. Each thread of the application executing in the Java VM produces a stream of coprocessor instructions. These streams are serialized, transmitted to the coprocessor and executed in the order they are received.
The instructions that are executed by the coprocessor instead of
the main processor are automatically selected by the translator. The
first step in the selection builds a data dependency graph of the
instructions of a method. In this graph, instructions that are
supported by the coprocessor instruction set and that are located less
than a given distance from instructions that load or store the value
of a secured field are selected. Straddling values, that is values
that are computed by an instruction of the main processor
(respectively the coprocessor) but used by an instruction of the
coprocessor (respectively the main processor) must be exchanged
between the two processors. The translator makes sure this is the case
by inserting out (respectively in)
instructions at the right locations. An out instruction
transfers the value on the top of the Java VM stack
to register R0 of the coprocessor. An in
instruction transfers the value in register R0 of the
coprocessor to the top of the Java VM stack.
After this step, the application is a mix of Java and
coprocessor VM instructions with out
and in instructions interspersed. When the translator
generates Java bytecode back from its intermediate representation,
coprocessor instruction words are ciphered using a secret key and
replaced by calls to a runtime method that transfer these enciphered
words to the coprocessor. This implies that the meaning of coprocessor
instructions cannot be found by simply looking at the bytecode.
[1] the way these instructions are represented in the final Java bytecode will be explained later on.
[2] the coprocessor does support a conditional move instruction
that can be used to implement some control flow by executing the
two branches of an if statement but keeping only the
result of one of the branches. However, the translator does not
use it to transform the original control flow yet.