How to Self-Lock a Javacard Applet
It's interesting to see how this mechanism evolved from GlobalPlatform Card specification 2.1.1 to 2.2.1 (still the same in 2.3):
In GP 2.1.1, only the card issuer (off-card entity) or the OPEN (as a result of "exceptions") is allowed to initiate locking of an application:
The Card Issuer has a mechanism to disable the continued execution status of an on-card Application. This mechanism may be invoked from within the OPEN based on exceptions handled by the OPEN or from the use of externally invoked commands. The Card Issuer is the only entity that may initiate the locking of an Application.
The method
GPSystem.setCardContentState()
is clearly defined to allow only state changes to application specific life-cycle states (values between0x07
and0x7F
with the lowest 3 bits set). Since the constant forAPPLICATION_LOCKED
in later specifications is0x80
setting this state is not allowed. This is also made clear in the notes to this method:- The OPEN shall reject any transition request to the Life Cycle States INSTALLED or LOCKED.
Consequently, trying to set the application state to locked from within the application itself must fail on a card implementing GP 2.1.1.
UPDATE (2016-05-20): I tested this on a few NXP JCOP cards (that are claimed to comply to GP 2.1.1) and setting values that have the upper bit set or any of the lower 3 bits cleared indeed fails.
This changed in GP 2.2. Now, an application is allowed to lock itself:
The card has a mechanism to disable and subsequently re-enable the continued execution status of an on-card Application. This mechanism may be invoked from within the OPEN based on exceptions handled by the OPEN or from the use of externally invoked commands. An Application with Global Lock privilege, the Application itself or a directly or indirectly associated Security Domain are the only entities that may initiate the locking of an Application.
The GP Card specification does not require an application to hold any specific permission to lock itself.
Unfortunately, the API specification for the method
GPSystem.setCardContentState()
is still not quite clear. First, the description of the method still states that only values between0x07
and0x7F
with the lowest 3 bits set must be allowed:This method sets the Application specific Life Cycle State of the current applet context. Application specific Life Cycle States range from 0x07 to 0x7F as long as the 3 low order bits are set.
Second, there are deviating notes in the API documentation thats part of appendix A of the GP Card specification 2.2 document and the JavaDoc in the API export files. While the notes in the specification were changed to:
- The OPEN shall reject any transition request to the Life Cycle State INSTALLED;
- The OPEN shall reject any transition request from the Life Cycle State LOCKED;
The notes in the API export files (GPSystem.java and JavaDoc) remained the same as in GP 2.1.1.
Consequently, if this method was implemented according to the specification, it should still reject setting the application life-cycle state to
APPLICATION_LOCKED
.UPDATE (2016-05-20): I tested this on a NXP J3D081 (JCOP v2.4.2 R2) card (that is claimed to comply to GP 2.2). Setting values that have the upper bit set or any of the lower 3 bits cleared, unfortunately, fails.
However, there is also the method
GPRegistryEntry.setState()
. The documentation of this method states that:- A transition request to Life Cycle state other than APPLICATION_LOCKED and APPLICATION_UNLOCKED shall be accepted only if the invoking Application corresponds to this GPRegistryEntry;
- An Application shall be able to lock and shall not be able to unlock itself;
Thus, it would be interesting to see if the following worked on the same card where using
setCardContentState()
failed:GPSystem.getRegistryEntry(null).setState(GPSystem.APPLICATION_LOCKED);
UPDATE (2016-05-20): I tested this on a NXP J3D081 (JCOP v2.4.2 R2) card (that is claimed to comply to GP 2.2). Unfortunately, this fails as well. Btw. it does not seem to make a difference if
null
orJCSystem.getAID()
is used as parameter forgetRegistryEntry()
.UPDATE (2016-06-14): According to Paul Bastian, an NXP representative has confirmed that applications cannot set themselves to locked state on JCOP v2.4.x cards.
UPDATE (2016-06-06): I tested this on a Infineon SLE97CNFX card (that is claimed to comply to GP 2.2.1) and it worked. I could successfully set the state to locked by using
APPLICATION_LOCKED
(0x80). The state is then set toprevious_state | 0x80
. Trying to set other state values that have the upper bit set (e.g. 0x8F) does not work (just as I expected).In GP 2.2.1, the documentation of the method
GPSystem.setCardContentState()
was changed (again). The change note clearly indicates that the method was updated to now allow an application to lock itself (export file version 1.5. maps to GP 2.2.1):- export file version 1.5: this method now allows the application associated with the current applet context to lock itself.
The method definition was changed to:
This method allows the application associated with the current applet context to change its state to an application specific life cycle state or to lock itself. An application cannot unlock itself using this method.
The value range for the state parameter passed to the method now explicitly includes the value of
APPLICATION_LOCKED
:bState
- an application specific life cycle state (0x07 to 0x7F with 3 low order bits set), orAPPLICATION_LOCKED
(0x80).Consequently, cards implementing GP 2.2.1 or higher should eventually allow applications to change their own life-cycle state to locked using the method
GPSystem.setCardContentState()
.UPDATE (2016-06-06): I tested this on a Infineon SLE97CNFX card (that is claimed to comply to GP 2.2.1 (or is it 2.3?)) and it worked. I could successfully set the state to locked by using
APPLICATION_LOCKED
(0x80). The state is then set toprevious_state | 0x80
. Trying to set other state values that have the upper bit set (e.g. 0x8F) does not work (just as I expected).
An alternative solution
What you could do to overcome your problem without being able to set the application life-cycle to state APPLICATION_LOCKED
, is to use application-specific life-cycle states:
public class LockableApplet extends Applet {
[... applet installation / instantiation code ...]
private static final byte APPLICATION_STATE_UNLOCKED = (byte)0x07;
private static final byte APPLICATION_STATE_LOCKED = (byte)0x7F;
public boolean select() {
if (GPSystem.getCardContentState() == APPLICATION_STATE_LOCKED) {
return false;
}
return true;
}
public void process(APDU apdu) {
if (selectingApplet()) {
return;
}
if (GPSystem.getCardContentState() == APPLICATION_STATE_LOCKED) {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
[... applet logic code ...]
}
}
Upon detecting a problem that should cause your application to be locked, you could lock the applet with the following call:
GPSystem.setCardContentState(APPLICATION_STATE_LOCKED);
You could later unlock the application again using a SET STATUS command through the security domain.
(Buyer beware: It seems this way simply does not work -- see comments)
(In context of GlobalPlatform Card Specification 2.2.1)
You must obey the Application Life Cycle State rules depicted in figure 5-2 (the arrow marked '5' applies here).
The correct way should be:
GPSystem.setCardContentState((byte)(GPSystem.getCardContentState() | GPSystem.APPLICATION_LOCKED));
or
GPSystem.getRegistryEntry(JCSystem.getAID()).setState((byte)(GPSystem.getCardContentState() | GPSystem.APPLICATION_LOCKED))
The 0x80
life cycle state is invalid for an application. See table 11-4 ( at least the b1
and b2
bits must be set, the b3
bit probably as well).
EDIT>
(I confess to write this answer based solely on the remembrance of fact that OPEN keeps the original state from which the entity was locked)
I am quite curious about this so I did some tests using the following applet (excerpt):
public void process(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if(selectingApplet()) {
return;
}
short claIns = Util.getShort(buffer, ISO7816.OFFSET_CLA);
switch(claIns) {
case (short) 0x8007:
buffer[0]=GPSystem.getCardContentState();
if(buffer[0]==buffer[ISO7816.OFFSET_P1]) {
if(GPSystem.setCardContentState(buffer[ISO7816.OFFSET_P2])) {
buffer[1]=0x01;
} else {
buffer[1]=0x00;
}
} else {
buffer[1]=(byte)0xFF;
}
buffer[2]=GPSystem.getCardContentState();
apdu.setOutgoingAndSend((short)0, (short)3);
return;
default: {
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
return;
}
}
}
And the following APDUs:
8007070F03 // To test transition into Application Specific State
80070F8F03 // To test my theory
80070F8003 // To test the GPSystem.APPLICATION_LOCKED constant directly
The results for my set of cards (Gemalto, Morpho, JCOP -- unfortunately all of them are GP 2.1.1) are in line with Michael Roland's great answer and GP specs -- the application's attempt to block itself is refused.
Received response APDUs for all GP 2.1.1 cards:
8007070F03 -> 07010F9000 // Succeeded in transition from `07` to `0F`
80070F8F03 -> 0F000F9000 // Failed transition from `0F` to `8F`
80070F8003 -> 0F000F9000 // Failed transition from `0F` to `80`
Just a note: This tool is quite useful to determine the implemented GP version as it parses the Card Recognition Data.
Yes. It is common and intended operation of a GlobalPlatform application. You must install your application with the right privilege (gp -install -privs CardLock) and the code to do it is:
GPSystem.setCardContentState(GPSystem.APPLICATION_LOCKED);
You can later unlock the application with
gp -unlock-applet <aid>