Secure Messaging (SM) provides end-to-end security of the communication between ICC and off-card entity by adding confidentiality, integrity and authentication to the APDU transactions.
- Provide a possibility of different SM session initialization modes and SM session lifetimes
- initialized at card connection, valid until card is disconnected
- initialized when SM protected ACL is encountered, valid during the small number of transactions
- Provide a mechanism to encrypt/decrypt either all APDU-s or selectively a subset
- Provide a mechanism to specify the key material for the operations
- built-in (like in entersafe)
- provided in configuration
- provided by user input (like in nPA)
- encryption service provided by arbitrary loadable “crypto provider” module
- Provide a general framework to support different SM protocols
- 7816-4 (CWA-14890, IAS/ECC)
- GlobalPlatform SCP01
- OT-SCP03 (Oberthur’s proprietary SM protocol used in PIV cards)
- …
- ItaCNS http://itacns.corp.it/hg/itacns/
- OpenDNIe http://opendnie.cenatic.es/
- IAS/ECC http://www.opensc-project.org/opensc/wiki/IAS-ECC
- nPA http://vsmartcard.svn.sourceforge.net/viewvc/vsmartcard/npa
- Possibility to provide static objects with necessary keys or providers compiled in
- Common cards should work without any changes in the configuration file
- Possibility to re-open SM session with a different keyset during one card session
The proposal revolves around the idea of APDU transformations. An APDU transformation (xform for short) is a function that, very simply,
- takes a chain of APDUs as input
- processes their header and data field
- calls another transformation providing the processed APDU
- processes the response field
- returns.
The following new types are defined:
struct sc_xform_invocation;
typedef int (sc_apdu_xform_cb_t)(struct sc_card, sc_apdu_t *apdu,
struct sc_xform_invocation *next_xforms);
typedef struct sc_xform_invocation {
sc_apdu_xform_cb_t func;
void *arg;
} sc_xform_invocation_t;
The arg pointer can be used for whatever information should be provided to the specific xform function, just like callback function types in many APIs usually have void pointer arguments that can be freely used by the API user.
The card structure sc_card_t is then expanded with a new member:
typedef struct sc_xform_invocation_stack { sc_xform_invocation_t *inv; struct sc_xform_invocation_stack *prev; } sc_xform_invocation_stack_t;
typedef struct sc_card {
…
sc_xform_invocation_stack_t *xforms;
…
(We will see later where the stack comes into play.)
A xform chain is an array of sc_xform_invocation_stack_t structures, meaning that each “func” in a member should call the next func and pass the following arguments:
- a pointer to the current working sc_card_t structure
- a pointer to the processed APDU
- a pointer to the remainder of the xform chain.
Note that the next func is called in the middle of the procedure, not at the end. When the next func returns successfully, the xform can expect to find meaningful values in the processed APDU, process them, modify the original APDU – the one it was passed as an argument – accordingly, and return.
A func member set to NULL indicates the end of the chain.
Note that sending an APDU to a smart card can be seen as just another kind of transformation – one that isn’t carried out by OpenSC or by a remote module, but by the smart card itself.
At any time, the card drivers can set card→xforms to an empty chain (the default), or to whatever they see fit.
When sc_transmit_apdu() is invoked, after performing the consistency checks, it appends to the provided xform chain (or creates a new chain, if none is provided) two more xforms:
- the command chaining xform (a very simple refactoring of the current command chaining code snippet in sc_transmit_apdu())
- the APDU transmit xform, that performs do_single_transmit() on each APDU of the chain.
This approach makes it easy to deal with memory, as most of the times the transformations can use stack-allocated buffers, and each transformation may abort the chain by returning an error. The error will be cascaded back to the card driver.
Sometimes it will be necessary to “pause” the xform chain and carry out a whole card transaction independently of it. For example, think of a SM xform chain that must invoke the card’s GET CHALLENGE operation before performing an authentication or signature.
There are two ways to do this:
- the xform function can create a new temporary xform chain, making sure to invoke the APDU transmit xform (which is always final! regardless of whether it is followed by a NULL func in the chain array or not) at the end
- the xform function can invoke the card driver’s own functions to do this.
In the second case, we must remember that a second sc_transmit_apdu() invocation would result from this, and it would be using the same xform chain. If the xform functions are not intelligent enough to handle the command properly, we must remember to store the chain somewhere and reset the card structure’s current chain.
This is where the stack makes sense. sc_transmit_apdu() always uses the first xform chain on top of the stack. This means that at any time we may call two functions:
int sc_xform_push(sc_card_t *card, sc_xform_invocation_t *xform_chain);
int sc_xform_pop(sc_card_t *card)
that deal with this. Of course, they can also be used directly by the card driver. You push a new xform chain (or an empty xform chain) on top of the stack, start the procedure that will result in transmitting the desired APDU, then pop the chain. Preceding operations will resume without losing state.
We can implement functions for the small building blocks of SM (constructing and parsing the needed TLV tags), functions that perform relevant chunks of ISO 7816-4 and other specs, and finally functions that perform the whole SM operation by relying on those building blocks.
Individual card drivers might choose to implement new xforms by relying on the same building blocks, or work from scratch.
In italic there are new checks/calls/etc. introduced into the existing common procedure (in ‘normal’ text).
Initialize SM context:
- sc_connect_card() (libopensc/card.c)
- card specific ‘init()’
- ‘’ here card specific ’init()’ can decide to initialize the SM context with the handlers from the static SM module ’’
- …
- try to initialize SM context from the opensc configuration — load dynamic SM module and use its handlers to set the SM context
- ‘’ if valid SM context and SM mode is ’APDU’ ’’
- open SM session — initialize secure messaging
- return from sc_connect_card()
- sc_disconnect_card() (libopensc/card.c)
- ‘’ if valid SM context and SM mode is ’APDU ’’
- close SM session
- unload SM module
- return from sc_disconnect_card()
Encode and transmit APDU in ‘APDU’ mode:
- do_single_transmit() (libopensc/apdu.c)
- ‘’ if valid SM context and ’APDU’ mode ’’
- ‘’ card specific ’wrap_apdu’ ’’
- filter non-SM APDUs
- ‘’ call SM ’wrap_apdu’ handler ’’
- use wrapped APDU
- check APDU
- transmit APDU
- check errors
- ‘’ if valid SM context and ’APDU’ mode ’’
- check SM errors
- ‘’ card specific ’unwrap_apdu’ ’’
- ‘’ call SM ’unwrap_apdu’ handler ’’
- replace APDU→resp with the decoded response
- return
In ‘ACL’ mode:
initialize SM session, encode/transmit/decode APDU, implemented in the card driver:
- in card specific procedure (ex. ‘_write_binary’)
- check ACL (ex. ACLs of the ‘current_file’ from cache)
- if SM needed
- call SM variant of procedure (_sm_write_binary)
- initialize SM session
- ‘’ call SM ’wrap_apdu’ handler ’’
- sc_transmit_apdu()
- ‘’ call SM ’unwrap_apdu’ handler ’’
- build response from possibly multiple APDUs
- return
Proposal for the SM related data types can be found in sm.h. Here the ‘iasecc’ in names of the data types should be mentally replaced by ISO-7816-4 .
Spanish DNIe card uses CWA14890 standard for Secure Messaging. It’s a sort of “apdu mode” but with some minor differences:
- SM is not required for every operations, just for “security” ones
- Once SM is started, must be used for any further APDU sent
- Instead of APDU chaining, enveloped APDU is used for sending data longer than max_apdu_send_size
- On SM error, Secure channel is closed. Needs to implement a way to detect/recover SM failures
So my proposal is basically same than Victor’s with a minor differences:
At card init()
- Initialize and load (if required) specific SM module, but only start SM if card requires to do so
- Provide functions for start/stop/testAndSet SM
- do not wrap/unwrap at do_single_transmit(), but at sc_transmit_apdu() level, by providing an extra wrap/unwrap card operation. Cards that do not use SM, just leave this card_op as NULL
Also, it might be usefull to provide generic SM handlers for each supported SM standard. We can divide each SM handler into card
independent (common routines) and card dependent (provider for specific card data, certificates, keys and so )
You can see my OpenDNIe SM implementation at OpenDNie| pages