-
Notifications
You must be signed in to change notification settings - Fork 30
Home
The JCMathLib is focused on the provision of low-level operations with large numbers (Bignat, Integer) and ECPoints which executes reasonably fast and requires only a decent amount of resources (especially transient RAM which is a scarse resource). The implementation is fully based on the public API and requires no proprietary functions.
The JCMathLib contains the following principal components:
- Bignat.java - provides optimized implementation of big natural numbers (without sign). Multiple Bignats are usually allocated.
- Integer.java - provides an implementation of signed big integers. Use heavily underlying Bignat. Multiple Integer objects are usually allocated.
- ECPoint.java - provides low-level operations for a point on specified elliptic curve. Multiple ECPoints are usually allocated.
- ECCurve.java - holds the definition of used curve for particular ECPoint. A single ECCurve object is usually allocated and shared between multiple ECPoints unless multiple different curve specifications are used. ECCurve can be initialized from static arrays (for well-known curves) without any additional memory allocation or copy.
- ObjectAllocator.java - management class which controls the allocation of new arrays based on a specification of the array memory placement (transient RAM vs. persistent EEPROM) with an easy change.
- ObjectLocker.java - management class for logical locking and unlocking of arrays and other objects. Used to detect the conflicting use of shared resource (e.g., memory array) in nested methods and also to automatically clear the sensitive intermediate values left from previous computation before or after use (or both).
- Bignat_Helper.java - provides all helper pre-allocated arrays and objects necessary to implement the functionality of Bignat methods. As there is no concurrency in Java Card, all Bignat objects share single Bignat_Helper object.
- ECPoint_Helper.java - provides all helper pre-allocated arrays and objects necessary to implement the functionality of ECPoint methods. All ECPoint objects share single ECPoint_Helper object. ECPoint heavily utilizes Bignat objects.
- OCConfig.java - aggregation class for all helper objects listed above together with definitions of lengths as required for particular EC curve. The single OCConfig object is created.
- PM.java - Utility class for performance profiling. Contains a definition of performance trap constants and trap reaction for relevant of operation from Bignat and ECPoint.
- ReturnCodes.java - List of custom return codes returned by the library.
- SecP256r1.java, P512r1.java - constants defining well-known elliptic curves.
- ECExample.java - very simple example applet of allocation and use of ECPoint. No communication with a client-side application.
- OCUnitTests.java - applet used to execute unit tests for Bignat/Integer and ECPoint of JCMathLib. UnitTestsClient client-side application executes and processes relevant APDU commands and responses.
As already noted, memory (especially RAM) is a precious resource on the smart cards. But the implementation of more complex operations like square root (Bignat.sqrt_FP()) or addition of two points (ECPoint.add()) requires multiple arrays/objects to store intermediate results. Reuse of a shared array is common Java Card development practice but is also contributing to less readable code (tempRAMBuffer everywhere) and introduce the potential for corruption or information-leakage bugs. We, therefore, aim to provide a pool of shared arrays and objects (which is a must due to limited memory) yet achieve readable code and safe reuse.
More specifically:
- The variables to store intermediate results within a method have human-understandable names. E.g., if we need to store nominator computed during ECPoint.add() method, the variable name is fnc_add_nominator (attribute of ECPoint_Helper instead of method's local variable).
- As little memory arrays (or Bignat objects) as possible are allocated to a pool of shared objects. E.g., ECPoint implementation requires only six different Bignats which are stored in ECPoint_Helper as attributes named helperEC_BN_A to helperEC_BN_F.
- The attributes representing local variables (e.g., fnc_add_nominator) are assigned with reference to one of shared object (e.g., fnc_add_nominator = helperEC_BN_B) according to the carefully planned mapping. The mapping is done only once during an allocation of helper objects. As a result, many local variables with sensible names are used yet represented by an only small number of actually allocated objects.
- The mapping must be carefully done so that two local variables with the same underlying shared object are never used at the same time. As there is no concurrency in Java Card, we don't need to be worry of overlap by two executions of the same method. But the conflict within the same method can still arise and especially in the nested calls. To prevent corruption, every variable is logically locked before its use (fnc_add_nominator.lock()) and unlocked when not used anymore (fnc_add_nominator.unlock()). If the object is already locked, an exception is emitted. This way, it's very easy to detect the conflict. If detected, different mapping needs to be used or a number of underlying shared objects must be increased.
- The locking is implemented as dedicated method lock() and unlock() of Bignat objects and via ObjectLocker class for plain arrays and other objects.
- The locking and unlocking also provide easy way how to prevent unintended information leak. The memory of shared object can be automatically erased on a lock on unlock (depending on configuration flags) inside lock() and unlock() function.
// Example use of locking
fnc_add_x_p.lock(); // underlying array is cleared on lock() if required
... // some code
fnc_add_nominator.lock(); // Exception would be raised if fnc_add_x_p and fnc_add_nominator shares same underlaying object
ech.fnc_add_nominator.clone(fnc_add_x_p);
ech.fnc_add_nominator.mod_exp(TWO, this.theCurve.pBN);
ech.fnc_add_nominator.mod_mult(fnc_add_nominator, THREE, this.theCurve.pBN);
... // some code
ech.fnc_add_nominator.unlock(); // underlying array is cleared on unlock() if required
fnc_add_x_p.unlock();
Although placing all library's helper arrays into RAM memory will result in maximum performance, about 1KB of RAM is consumed (for 256bits EC). For same cards or applications with high RAM, demand makes sense to move some (in extreme all) helper arrays into EEPROM in exchange to slower write times. Generally, this is difficult task as one needs to change code in many places and exchange JCSystem.makeTransientByteArray() by operator new. Potentially many times as resulting setup needs to be tested on real cards.
We simplified this task by use of custom allocator ObjectAllocator with an easy specification of memory allocator for every single array. Helper objects like ECPoint_Helper and Bignat_Helper are delegating allocation to ObjectAllocator. By default, all helper arrays are allocated in RAM but can be all moved to EEPROM simply by calling setAllAllocatorsEEPROM(). Method setAllocatorsTradeoff() can be custom modified to place only arrays often used for write into RAM.
// Excerp from OCConfig constructor
...
memAlloc = new ObjectAllocator();
memAlloc.setAllAllocatorsRAM(); // default behavior, all helper arrays in RAM
//if required, memory for helper objects and arrays can be in persistent memory to save RAM (or some tradeoff)
//ObjectAllocator.setAllAllocatorsEEPROM();
//ObjectAllocator.setAllocatorsTradeoff();
// Now allocate helper objects for BN and EC
bnh = new Bignat_Helper(this);
ech = new ECPoint_Helper(this);
...
- : JavaCard API, EC / RSA / memory
- ant-javacard
- GlobalPlatformPro
- Example use for ECPoint operations
- Example use for Bignat and Integer operations
- Performance on real cards (table)
- Prerequisities and caveats
- Setup and execution
- How to intepret results
Note that Java Card programming is different from standard Java programming although basic Java Card applet can be compiled using javac tool. The list of notable differences can be found in Java Card documentation, but the most important ones are:
- Memory is very limited (especially fast transient RAM), an allocation is slow and garbage collection can be completely missing. As a result, all require arrays and objects are usually allocated in the applet constructor and reused during the whole lifetime of an applet on a card.
- Operator new will put an object into persistent memory which will persist loss removal of a card from a smart card reader but is also quite slow to write. If an array is to be allocated in fast RAM, a dedicated method like JCSystem.getTransientByteArray() needs to be is used.
- Object oriented hierarchies and features are used, but only in a limited fashion. Frequently accessed attributes might be public instead of modified via setters. A large number of arguments slow down method calls.
- For performance reasons, copy-free programming is preferred. Instead of creating new and new arrays with intermediate results, the in-place modification is performed with array passed together with start offset and data length. Dedicated methods from JCSystem class are used for fast memory operations like copy or fill or arrays.
- As a card environment defends itself against an adversary, it also makes difficult to debug and profile applet on a card. As a result, a code is usually first debugged in a simulator. But as a simulator itself is never a perfect representation of real card, execution on real hardware usually requires additional debugging.
So don't be surprised to see some deviations from good Java programming practices. But if you will spot something incorrect, let us know please - possibly by pull request? :)