Hello there!

>> 3. if we need to, transform this APDU into a sequence of chained
>> APDUs, or something else; for instance, if I understand correctly, the
>> Spanish DNIe doesn't support command chaining, but rather wants this a
>> sequence of ENVELOPE APDUs;
>
> Enveloped APDUs and Chained APDUs are not strictly the same: Enveloped
> APDUs can send effectively only one APDU (such as an APDU with a lot of
> data), where chained APDUs can perform a certain operation consisting of
> one or more APDUs (such as a get challenge chained with a PSO).

Sure. In the DNIe example that I brought, they share the same purpose
(breaking up long APDUs), but they are different beasts and can be
tamed for usage in different situations.

> So your transformation concept is not strictly bound to SM but rather to
> smart card operations in general. An other approach would be to hard
> code the single steps of transmission rather than to use a more flexible
> chain.

This is right.

> Note that hardcoding is already done in sc_transmit_apdu: An
> APDU, that is too long (e.g. extended length) is broken into multiple
> pieces and sent. The same is done for the response via get response
> commands. So what is the advantage of coding all this with your
> approach?

Code reuse and streamlining of the architecture. I've seen that
different cards do this differently. As en example, let me show how
OpenDNIe solved this problem: by

1. adding the "wrap_apdu" operation hook in the card driver
2. adding a global variable of type dnie_private_data_t to store all
information needed for SM processing
3. calling wrap_apdu() early in sc_transmit_apdu()
4. wrap_apdu() looks at the private data to check if SM is active; if
it is, it calls cw_encode_apdu() to process it
5. wrap_apdu() calls dnie_transmit_apdu(), the custom replacement for
sc_transmit_apdu(), which knows how to handle long APDUs for the DNIe
card
6. finally, if SM is active, wrap_apdu() calls cw_decode_apdu(), so
that it can return an authenticated and/or unencrypted response to the
upper layer.

This works (for one card), but hard-coding this approach means that
each driver has to reinvent the wheel: code duplication, tight
coupling, difficult maintenance. With a transformation chain, a driver
could pick the bits it needs and at least some of them could be "black
boxes" that carry out useful work without forcing the card driver to
know about their innards.

> Don't fix what is not broken. Your approach is useful and could be
> integrated, but if you don't have something in mind that can be done
> only or better by your approach, it's hard to justify the work it needs.

Yes, I am afraid that I'm over-engineering it. On the other hand, I
think that the code for this is rather short and that the other
approaches that come to my mind seem to lead to cut-and-paste drivers.

> The German identity card uses a very good replacement for basic access
> control. It is basically an anonymous key agreement consisting of
> something like 6 APDUs. If some other APDU slips into this sequence,
> then it breaks the command chaining (this is what I mean with
> interfering). The key agreement could be one single operation queued up
> with multiple APDUs in one chain. But it could also be superseded by
> sending a sequence of subsequent APDUs (one at a time). This would not
> require the overhead of a transmit chain. Take for example
> sc_set_security_env or sc_pin_cmd. They are interpreted by the card
> driver and the driver can also send multiple (hard coded) APDUs instead
> of just one (no matter if they are standardized or not).

I see what you mean. On one hand, I think that the usefulness of
transforming a whole APDU sequence (or an APDU into an APDU sequence)
only arises when you recycle the code you may need to break up long
APDUs for a given card. In the case that you show, there is certainly
no need for this; you can just lock the card and perform the key
agreement.

The ability to handle an APDU sequence, in my "concept" of a
transformation chain, is only useful in the last steps, not in the
early ones.

As for where this stuff belongs and for whether a transform chain is
needed, your example is a perfect fit. Let us imagine that other cards
use the same key agreement scheme or a similar one, but must perform
different checks to know what algorithms must be used for a given
objects (early steps before SM), and have a different way of dealing
with long APDUs (last steps after SM and before APDU transmission).

In the transform chain, a single function is responsible for *both*
ways of the transformation. If there is anything clever in the
approach (which I'm not sure of :) ), this is it.

A simpler approach would be to just provide a wrap_apdu() hook and
deal there with the whole sequence, just like in DNIe. This way we
would still achieve code reuse, by simply breaking up the steps in a
logical way to get the desired loose coupling, and provide them as
functions available to all drivers. So it looks simpler than a
transform chain, but I'm afraid it isn't, because then the call stack
does not match the transformation stack. This means that:

1. the wrapping function must invoke an encode() function for each
step, perform the card transaction, then invoke each decode() step in
reverse;
2. all encode() and decode() step must perform all their memory
management on the heap with malloc() when encoding and free() when
decoding, because they won't be able to leave anything on the stack.
This doesn't only apply to memory space for APDUs: state management
becomes somewhat trickier too;
3. when debugging, you aren't able to just look at the call stack and
see what was is going on.

Thanks,

-- 
Emanuele
_______________________________________________
opensc-devel mailing list
opensc-devel@lists.opensc-project.org
http://www.opensc-project.org/mailman/listinfo/opensc-devel

Reply via email to