Peter Williams wrote:

Are you claiming the Applet code has a bug/misbehaviour in case (b) ?

Im trying to use the DES-based external authentication, for the first time - using it as programmed. Then, Ill change the security protocol to cooperate with the GP authentication process. But first, make it work correctly, as is!


Actual code Im using (originally from CVS) enclosed, per request. Compare the final few code lines of the CD_DECRYPT case, and the CD_VERIFY case. Only the signature verify case seems to handle the velocity counters, and the registration of the principal as authenticated - in the logged_id bitmask.

Nope. Code handles correctly such things, infact in both cases just the reslt boolean is set, then a break exits the switch statement, reaching the LoginStrongIdentity(key_nb) stuff.

I'm sorry this part of code gets so much confused due to #ifdefs,
and also the function has become too much long, quite unreadable and
maintainance gets to the impossible.

Yep. You discovered a misbehaviour on the DECRYPT case, due to the
SendData(..) statement that *is not needed*. Just delete that line
(a check on the plugin would be needed, too). That caused what you
told, return of data to application. Please, note that such data is
exactly the original random challenge, in case of successful auth,
instead it is the decrypted cryptogram provided by the application using
the on-card key in other cases, leading to known ciphertext attacks on
the key (possibly meaningful for symmetric keys, for asymmetric that
would be the public key anycase). Maybe I added that line at somepoint
during development for testing purposes, then I forgot it there.
AFAICR, I used to work with RSA authentication using the VERIFY mode, I
guess this other case was not tested.

I also verified the line is still there in the CVS on Alioth:

http://cvs.alioth.debian.org/cgi-bin/cvsweb.cgi/MCardApplet/?cvsroot=muscleplugins

at line 2517.

On a related note, I noticed how you switched to SHA-1 instead of MD5
in this version of the Applet you sent me, by using the FORCE_SHA1 flag.
This does not seem to me the right way. Just add a further constant to
the Applet and library:

    private final static byte CM_RSA_NOPAD =    (byte) 0x00;
    private final static byte CM_RSA_PAD_PKCS1 =(byte) 0x01;
    private final static byte CM_RSA_SHA1_PKCS1 =(byte) 0x02;   /* ? */

the use that other constant in your application in place of
RSA_PAD_[MD5_]PKCS1.

Hope this helped somehow. If you test the code with that line deleted,
please let me (and Ludovic) know so that code on CVS may be patched.

Bye,
        T.

#ifdef USE_AUTH
   private void GetChallenge(APDU apdu, byte[] buffer) {
if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00)
    ISOException.throwIt(SW_INCORRECT_P1);

short bytesLeft = Util.makeShort((byte) 0x00,
    buffer[ISO7816.OFFSET_LC]);
if (bytesLeft != apdu.setIncomingAndReceive())
    ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

if (bytesLeft < 4)
    ISOException.throwIt(SW_INVALID_PARAMETER);

short size = Util.getShort(buffer, ISO7816.OFFSET_CDATA);
short seed_size = Util.getShort(buffer, (short) (ISO7816.OFFSET_CDATA + 2));
if (bytesLeft != (short) (seed_size + 4))
ISOException.throwIt(SW_INVALID_PARAMETER);


byte data_loc = buffer[ISO7816.OFFSET_P2];

if ((data_loc != DL_APDU) && (data_loc != DL_OBJECT))
    ISOException.throwIt(SW_INVALID_PARAMETER);

if (randomData == null)
    randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);

if (seed_size != (short) 0x0000)
randomData.setSeed(buffer, (short) (ISO7816.OFFSET_CDATA + 4), seed_size);


// Allow size = 0 for only seeding purposes
if (size != (short) 0x0000) {
om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true);
// Automatically throws exception if no memory
#if 0
short base = om.createObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, (short) (size + 2), getRestrictedACL(), (short) 0);
#else
short base = om.createObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, (short) (size + 2), getPublicACL(), (short) 0);
#endif
mem.setShort(base, size);
randomData.generateData(mem.getBuffer(), (short) (base + 2), size);
/* Remember that out object contains getChallenge data (to avoid attacks
* pretending to write the out object before extAuth) */
getChallengeDone = true;
// Actually return data in APDU only if DL_APDU specified.
if (data_loc == DL_APDU) {
sendData(apdu, mem.getBuffer(), base, (short) (size + 2));
/* Don't destroy out object ! Generated data is needed in ExtAuth ! */
/* Not if running without external authentication */
#ifndef WITH_EXT_AUTH
om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true);
#endif
}
}
}


#ifdef WITH_EXT_AUTH
   private void ExternalAuthenticate(APDU apdu, byte[] buffer) {
if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00)
    ISOException.throwIt(SW_INCORRECT_P2);

short bytesLeft = Util.makeShort((byte) 0x00,
      buffer[ISO7816.OFFSET_LC]);
if (bytesLeft != apdu.setIncomingAndReceive())
    ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

byte key_nb = buffer[ISO7816.OFFSET_P1];

if ((key_nb < 0) || (key_nb >= MAX_NUM_AUTH_KEYS)
    || (keys[key_nb] == null))
    ISOException.throwIt(SW_INCORRECT_P1);

if (bytesLeft < 3)
    ISOException.throwIt(SW_INVALID_PARAMETER);

/* Verify that a GetChallenge has been issued */
if (! getChallengeDone)
    ISOException.throwIt(SW_OPERATION_NOT_ALLOWED);
/* Clear getChallengeDone flag */
getChallengeDone = false;
/* Retrieve getChallenge() data position and check it */
short chall_base = om.getBaseAddress(OUT_OBJECT_CLA, OUT_OBJECT_ID);
if (chall_base == MemoryManager.NULL_OFFSET)
    ISOException.throwIt(SW_OPERATION_NOT_ALLOWED);
short obj_size = om.getSizeFromAddress(chall_base);
if (obj_size < 3)
    ISOException.throwIt(SW_INVALID_PARAMETER);
short chall_size = mem.getShort(chall_base);
/* Actually GetChallenge() creates an object of exact size */
if (obj_size != (short) (2 + chall_size))
    ISOException.throwIt(SW_INVALID_PARAMETER);

byte ciph_mode = buffer[ISO7816.OFFSET_CDATA];
byte ciph_dir = buffer[(short) (ISO7816.OFFSET_CDATA + 1)];

byte[] src_buffer; /* The buffer of encrypted data */
short src_offset; /* The offset of encrypted data in src_buffer[] */
short src_avail; /* The available encrypted data (+ size) */
switch (buffer[(short) (ISO7816.OFFSET_CDATA + 2)]) {
case DL_APDU:
    src_buffer = buffer;
    src_offset = (short) (ISO7816.OFFSET_CDATA + 3);
    src_avail = (short) (bytesLeft - 3);
    break;
case DL_OBJECT:
    src_offset = om.getBaseAddress(IN_OBJECT_CLA, IN_OBJECT_ID);
    if (src_offset == mem.NULL_OFFSET)
 ISOException.throwIt(SW_OBJECT_NOT_FOUND);
    src_buffer = mem.getBuffer();
    src_avail = om.getSizeFromAddress(src_offset);
default:
    ISOException.throwIt(SW_INVALID_PARAMETER);
    return; // Suppress compiler warning
}
if (src_avail < 2)
    ISOException.throwIt(SW_INVALID_PARAMETER);
short size = Util.getShort(src_buffer, src_offset);
if (src_avail < (short) (size + 2))
    ISOException.throwIt(SW_INVALID_PARAMETER);

// Null key already checked above
Key key = keys[key_nb];

// Check if identity is actually blocked
if (keyTries[key_nb] == (byte) 0)
    ISOException.throwIt(SW_IDENTITY_BLOCKED);

byte key_type = key.getType();

boolean result = false;

switch (ciph_dir) {
case CD_DECRYPT:
#ifndef WITH_DECRYPT
 ISOException.throwIt(SW_UNSUPPORTED_FEATURE);
 return;
#else
    byte jc_ciph_alg;
    switch (ciph_mode) {
#ifdef WITH_RSA
    case CM_RSA_NOPAD:
 if (key_type != KeyBuilder.TYPE_RSA_PUBLIC)
     ISOException.throwIt(SW_INVALID_PARAMETER);
 jc_ciph_alg = Cipher.ALG_RSA_NOPAD;
 break;
    case CM_RSA_PAD_PKCS1:
 if (key_type != KeyBuilder.TYPE_RSA_PUBLIC)
     ISOException.throwIt(SW_INVALID_PARAMETER);
 jc_ciph_alg = Cipher.ALG_RSA_PKCS1;
 break;
#else
    case CM_RSA_NOPAD:
    case CM_RSA_PAD_PKCS1:
 ISOException.throwIt(SW_UNSUPPORTED_FEATURE);
#endif // ifdef WITH_RSA

#if defined(WITH_DES) || defined(WITH_3DES)
    case CM_DES_CBC_NOPAD:
 if (key_type != KeyBuilder.TYPE_DES)
     ISOException.throwIt(SW_INVALID_PARAMETER);
 jc_ciph_alg = Cipher.ALG_DES_CBC_NOPAD;
 break;
    case CM_DES_ECB_NOPAD:
 if (key_type != KeyBuilder.TYPE_DES)
     ISOException.throwIt(SW_INVALID_PARAMETER);
 jc_ciph_alg = Cipher.ALG_DES_ECB_NOPAD;
 break;
#else
    case CM_DES_CBC_NOPAD:
    case CM_DES_ECB_NOPAD:
 ISOException.throwIt(SW_UNSUPPORTED_FEATURE);
#endif // if defined(WITH_DES) || defined(WITH_3DES)
    default:
 ISOException.throwIt(SW_INVALID_PARAMETER);
 return;  // Suppress compiler warning
    }
    Cipher ciph = getCipher(key_nb, jc_ciph_alg);
    ciph.init(key, Cipher.MODE_DECRYPT);
    // Create temporary buffer
    short temp = mem.alloc(chall_size);
    if (temp == MemoryManager.NULL_OFFSET)
 ISOException.throwIt(SW_NO_MEMORY_LEFT);
    short written_bytes =
 ciph.doFinal(src_buffer, (short) (src_offset + 2), size,
       mem.getBuffer(), temp);

    /* JC specifies that, when decrypting, padding bytes are cut out *
     *   so after a decrypt we should get the same size as the challenge*
     *   and they should be less than provided encrypted data  */

#ifdef APPLET_DEBUG
    /* Just to be sure that I understood JC API in the right way...*/
    if (written_bytes > chall_size)
 ISOException.throwIt(SW_INTERNAL_ERROR);
#endif

    if ((written_bytes == chall_size)
 && (Util.arrayCompare(mem.getBuffer(), temp,
         mem.getBuffer(), (short) (chall_base + 2),
         chall_size) == (byte) 0)
 )
 result = true;

    sendData(apdu, mem.getBuffer(), temp, written_bytes);
    mem.free(temp);
    break;
#endif
case CD_VERIFY:
#ifndef WITH_SIGN
    ISOException.throwIt(SW_UNSUPPORTED_FEATURE);
    return;
#else
    byte jc_sign_alg;
    switch (ciph_mode) {
    case CM_DSA_SHA:
#ifdef WITH_DSA
 if (key_type != KeyBuilder.TYPE_DSA_PUBLIC)
     ISOException.throwIt(SW_INVALID_PARAMETER);
 jc_sign_alg = Signature.ALG_DSA_SHA;
 break;
#else
 ISOException.throwIt(SW_UNSUPPORTED_FEATURE);
 return;
#endif
    case CM_RSA_PAD_PKCS1:
#ifdef WITH_RSA
 if (key_type != KeyBuilder.TYPE_RSA_PUBLIC)
     ISOException.throwIt(SW_INVALID_PARAMETER);
#ifndef FORCE_SHA1
 jc_sign_alg = Signature.ALG_RSA_MD5_PKCS1;
#else
 jc_sign_alg = Signature.ALG_RSA_SHA_PKCS1;
#endif
 break;
#else
 ISOException.throwIt(SW_UNSUPPORTED_FEATURE);
 return;
#endif
    default:
 ISOException.throwIt(SW_INVALID_PARAMETER);
 return;  // Suppress compiler warning
    }
    Signature sign = getSignature(key_nb, jc_sign_alg);
    sign.init(key, Signature.MODE_VERIFY);
    if (sign.verify(mem.getBuffer(), (short) (chall_base + 2), chall_size,
      src_buffer, (short) (src_offset + 2), size))
 result = true;
    break;
#endif
default:
    ISOException.throwIt(SW_INVALID_PARAMETER);
}
if (result) {
    LoginStrongIdentity(key_nb);
    // Reset try counter
    keyTries[key_nb] = MAX_KEY_TRIES;
    om.destroyObject(IN_OBJECT_CLA, IN_OBJECT_ID, true);
    om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true);
} else {
    // Decrease try counter
    keyTries[key_nb]--;
    LogoutIdentity((byte) (key_nb + 8));
    om.destroyObject(IN_OBJECT_CLA, IN_OBJECT_ID, true);
    om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true);
    ISOException.throwIt(SW_AUTH_FAILED);
}
   }
#endif // ifdef WITH_EXT_AUTH
#endif

Please, send me the version you're referring to in your message, as it's
a while I just use to play with an internal version at our lab for
biometrics related stuff.

((maybe a key should be provided with a restriction on the allowed
ExtAuth modes of operation when it is created/injected for increasing
security .... anyway, in the first protocol formulation there were not
so many choices :-) ))

(a) when cipher-dir == CD_VERIFY, the applet will verify an MD5RSA signature (over the Value of challenge object), and perform strongLogon, if the verification suceeds.

(b) when cipher-dir == CD_DECRYPT, the applet will decrypt the incoming ciphertext using TDES_CBC_NOPAD, compare the plaintext for equality with the stored challenge value V, and reflect the plaintext value back to the consumer (over an insecure channel), if there is a match, without performing strongLogon.

What is the context for (b)? How is it to be used? Is there any host sample code? Are (a) and (b) supposed to be used in conjunction, perhaps?


Once upon a time, all of this was tested. If I manage to find the old
test programs, I'll be more than happy to send them to you. As an
alternative, maybe I have at reach some test code for the JNI bridge of
MuscleCard API -- have to check --... it might also be the case that
just writing a short program is actually faster than searching this old
stuff :-)


If the two cases are strict alternatives, we can make them equivalent in terms of the access control model.

Bye,
Tommaso.

--
,----------------------------------------------------.
|  Tommaso Cucinotta PhD <t.cucinotta *at* sssup.it> |
>----------------------------------------------------<
!       Scuola Superiore di Studi Universitari       !
!              e Perfezionamento S.Anna              !
!    Pisa                                   Italy    !
`----------------------------------------------------'
_______________________________________________
Muscle mailing list
[email protected]
http://lists.drizzle.com/mailman/listinfo/muscle

_______________________________________________
Muscle mailing list
[email protected]
http://lists.drizzle.com/mailman/listinfo/muscle

-- ,----------------------------------------------------. | Tommaso Cucinotta PhD <t.cucinotta *at* sssup.it> | >----------------------------------------------------< ! Scuola Superiore di Studi Universitari ! ! e Perfezionamento S.Anna ! ! Pisa Italy ! `----------------------------------------------------' _______________________________________________ Muscle mailing list [email protected] http://lists.drizzle.com/mailman/listinfo/muscle

Reply via email to