From 554b0718cddf1eccc575bede16fb3f32cc44707e Mon Sep 17 00:00:00 2001 From: Robert Quattlebaum Date: Thu, 19 Mar 2020 23:21:09 -0700 Subject: [PATCH] Add support for multiple counters. This commit adds support for multiple (8) counters, which as assigned in a round-robin style to each registered credential. Each counter is independent and starts at zero. This makes it more difficult for colluding services to determine which authenticators might be shared between accounts. The impetus for this change is well articulated in the [Webauthn Specification][webauthn]: > [Authenticators] SHOULD implement per credential signature counters. > This prevents the signature counter value from being shared between > Relying Parties and being possibly employed as a correlation handle > for the user. [webauthn]: (https://www.w3.org/TR/webauthn/#sign-counter In order to do this, the `FIDOAPI` interface needed to be modified to allow the counter index to be stored at registration and recovered at authentication. Also, the implementation of `FIDOStandalone` was significantly modified to allow the counter index to be securely included in the key handle without increasing the key handle length. Previously, `FIDOStandalone` was storing the entire application parameter in the key handle. In this change only the CBC-MAC of the application parameter is stored in the key handle. Of the 16 bytes that were freed up by doing this, the first of them is used to store the counter index and the rest are set to zero. No other changes to the key handle format were made. While it would have been possible to reduce the key handle length to 49 bytes, this would have a negative impact on privacy since 49 bytes is an unusual key handle length. 64 byte key handles are very common, so this change maintains that length. --- src/main/java/com/ledger/u2f/FIDOAPI.java | 40 ++++- .../java/com/ledger/u2f/FIDOStandalone.java | 142 ++++++++++++++---- src/main/java/com/ledger/u2f/FIDOUtils.java | 45 ------ src/main/java/com/ledger/u2f/U2FApplet.java | 98 ++++++++---- 4 files changed, 223 insertions(+), 102 deletions(-) delete mode 100644 src/main/java/com/ledger/u2f/FIDOUtils.java diff --git a/src/main/java/com/ledger/u2f/FIDOAPI.java b/src/main/java/com/ledger/u2f/FIDOAPI.java index 6df46c8..d85514d 100644 --- a/src/main/java/com/ledger/u2f/FIDOAPI.java +++ b/src/main/java/com/ledger/u2f/FIDOAPI.java @@ -20,11 +20,45 @@ package com.ledger.u2f; import javacard.security.ECPrivateKey; -import javacard.security.ECPublicKey; public interface FIDOAPI { - public short generateKeyAndWrap(byte[] applicationParameter, short applicationParameterOffset, ECPrivateKey generatedPrivateKey, byte[] publicKey, short publicKeyOffset, byte[] keyHandle, short keyHandleOffset); - public boolean unwrap(byte[] keyHandle, short keyHandleOffset, short keyHandleLength, byte[] applicationParameter, short applicationParameterOffset, ECPrivateKey unwrappedPrivateKey); + /** + * Generates a credential tied to this authenticator. + * + * @param applicationParameter Input buffer containing the 32-byte applicaiton parameter. + * @param applicationParameterOffset The offset into applicationParameter at which + * the application parameter starts. + * @param publicKey Output buffer that will hold the generated 65 byte public key. + * @param publicKeyOffset Where in publicKey to start writing. + * @param keyHandle Output buffer that will hold the generated key handle. + * @param keyHandleOffset Where in keyHandle to start writing. + * @param info A byte of information that can be recovered when the key handle is + * unwrapped. This is typically used for authenticators with multiple + * counters. + * @return The length of the generated key handle. + */ + short generateKeyAndWrap(byte[] applicationParameter, short applicationParameterOffset, byte[] publicKey, short publicKeyOffset, byte[] keyHandle, short keyHandleOffset, byte info); + + /** + * Unwraps a previously generated key handle into a private key + * and info byte. + * + * @param keyHandle Input buffer containing the key handle. + * @param keyHandleOffset Offset into buffer where the key + * handle starts. + * @param keyHandleLength The length of the key handle. + * @param applicationParameter Input buffer containing the + * 32-byte applicaiton parameter. + * @param applicationParameterOffset Offset into buffer where the + * applicaiton parameter starts. + * @param unwrappedPrivateKey ECPrivateKey instance to insert unwrapped + * private key into. + * @return The value of the "info" byte from generateKeyAndWrap() + * @throws javacard.framework.ISOException ISO7816.SW_WRONG_DATA if the + * key handle doesn't match this applicaiton parameter or doesn't + * belong to this authenticator. + */ + byte unwrap(byte[] keyHandle, short keyHandleOffset, short keyHandleLength, byte[] applicationParameter, short applicationParameterOffset, ECPrivateKey unwrappedPrivateKey); } diff --git a/src/main/java/com/ledger/u2f/FIDOStandalone.java b/src/main/java/com/ledger/u2f/FIDOStandalone.java index 1579c97..fcace52 100644 --- a/src/main/java/com/ledger/u2f/FIDOStandalone.java +++ b/src/main/java/com/ledger/u2f/FIDOStandalone.java @@ -19,12 +19,13 @@ package com.ledger.u2f; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; import javacard.security.KeyBuilder; import javacard.security.KeyPair; import javacard.security.ECKey; import javacard.security.ECPrivateKey; import javacard.security.ECPublicKey; -import javacard.security.KeyPair; import javacard.security.AESKey; import javacardx.crypto.Cipher; import javacard.framework.JCSystem; @@ -32,26 +33,33 @@ import javacard.framework.Util; public class FIDOStandalone implements FIDOAPI { + // Most U2F authenticators use a key handle length of + // 64 bytes. While we could get away with using a 49 byte + // handle, this would be a tell about what kind of + // authenticator we were using. So to maintain privacy, + // we use a 64-byte key handle just like almost everyone + // else. + private static final short KEY_HANDLE_LENGTH = 64; + // Only used by generateKeyAndWrap(). private KeyPair keyPair; - private AESKey chipKey; + private Cipher cipherEncrypt; private Cipher cipherDecrypt; - private RandomData random; private byte[] scratch; private static final byte[] IV_ZERO_AES = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; public FIDOStandalone() { - scratch = JCSystem.makeTransientByteArray((short)64, JCSystem.CLEAR_ON_DESELECT); + scratch = JCSystem.makeTransientByteArray(KEY_HANDLE_LENGTH, JCSystem.CLEAR_ON_DESELECT); keyPair = new KeyPair( (ECPublicKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, KeyBuilder.LENGTH_EC_FP_256, false), (ECPrivateKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false)); Secp256r1.setCommonCurveParameters((ECKey)keyPair.getPrivate()); Secp256r1.setCommonCurveParameters((ECKey)keyPair.getPublic()); - random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); + RandomData random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); // Initialize the unique wrapping key - chipKey = (AESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false); + AESKey chipKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false); random.generateData(scratch, (short)0, (short)32); chipKey.setKey(scratch, (short)0); cipherEncrypt = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); @@ -78,34 +86,114 @@ private static void deinterleave(byte[] src, short srcOffset, byte[] array1, sho } } - public short generateKeyAndWrap(byte[] applicationParameter, short applicationParameterOffset, ECPrivateKey generatedPrivateKey, byte[] publicKey, short publicKeyOffset, byte[] keyHandle, short keyHandleOffset) { - // Generate a new pair + public short generateKeyAndWrap(byte[] applicationParameter, short applicationParameterOffset, byte[] publicKey, short publicKeyOffset, byte[] keyHandle, short keyHandleOffset, byte info) { + // Here we are using the cipherEncrypt object as + // a way to calculate a CBC-MAC. In this case we + // will be writing out the encrypted application + // parameter to bytes 16 thru 47 of `scratch`. + // However, we will only be using the last 16 + // bytes---the first 16 bytes will be overwritten + // by the private key a few steps down. + cipherEncrypt.doFinal(applicationParameter, applicationParameterOffset, (short)32, scratch, (short)16); + + // Put our "info" byte as the first byte after + // our CBC-MAC of the application parameter. + scratch[48] = info; + + // Fill bytes 49 through 63 with zeros. + // + // TODO: Would there be any advantage to + // doing a random fill here instead + // of zero fill? + Util.arrayFillNonAtomic(scratch, (short)49, (short)15, (byte)0x00); + + // Generate a new key pair. keyPair.genKeyPair(); - // Copy public key + + // Copy public key out. ((ECPublicKey)keyPair.getPublic()).getW(publicKey, publicKeyOffset); - // Wrap keypair and application parameters + + // Write the private key to bytes 0-31 of + // the scratch memory, overwriting the first + // block of the application parameter we + // encrypted above. This is OK because we + // only care about the later 16 bytes, which + // we will be using as a MAC. ((ECPrivateKey)keyPair.getPrivate()).getS(scratch, (short)0); - interleave(applicationParameter, applicationParameterOffset, scratch, (short)0, keyHandle, keyHandleOffset, (short)32); - cipherEncrypt.doFinal(keyHandle, keyHandleOffset, (short)64, keyHandle, keyHandleOffset); - Util.arrayFillNonAtomic(scratch, (short)0, (short)32, (byte)0x00); - return (short)64; + + // At this point the scratch looks like this: + // + // * bytes 0-31: Private key + // * bytes 32-47: CBC-MAC(chipKey, applicationParameter) + // * byte 48: "Info" byte + // * Bytes 49-63: Zero padding + + // Take the upper and lower parts of scratch + // memory and reversibly mix them together. + interleave(scratch, (short)32, scratch, (short)0, keyHandle, keyHandleOffset, (short)32); + + // Encrypt the mixed buffer using the chipKey. + cipherEncrypt.doFinal(keyHandle, keyHandleOffset, KEY_HANDLE_LENGTH, keyHandle, keyHandleOffset); + + // Zero out the bytes we used in scratch memory. + Util.arrayFillNonAtomic(scratch, (short)0, (short)49, (byte)0x00); + + return KEY_HANDLE_LENGTH; } - public boolean unwrap(byte[] keyHandle, short keyHandleOffset, short keyHandleLength, byte[] applicationParameter, short applicationParameterOffset, ECPrivateKey unwrappedPrivateKey) { - // Verify - cipherDecrypt.doFinal(keyHandle, keyHandleOffset, (short)64, keyHandle, keyHandleOffset); - deinterleave(keyHandle, keyHandleOffset, scratch, (short)0, scratch, (short)32, (short)32); - if (!FIDOUtils.compareConstantTime(applicationParameter, applicationParameterOffset, scratch, (short)0, (short)32)) { - Util.arrayFillNonAtomic(scratch, (short)32, (short)32, (byte)0x00); - Util.arrayFillNonAtomic(keyHandle, keyHandleOffset, (short)64, (byte)0x00); - return false; + public byte unwrap(byte[] keyHandle, short keyHandleOffset, short keyHandleLength, byte[] applicationParameter, short applicationParameterOffset, ECPrivateKey unwrappedPrivateKey) { + // Fail early if the key handle length is obviously wrong. + if (keyHandleLength != KEY_HANDLE_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_DATA); } - Util.arrayFillNonAtomic(keyHandle, keyHandleOffset, (short)64, (byte)0x00); + + // Decrypt the key handle in-place. + cipherDecrypt.doFinal(keyHandle, keyHandleOffset, KEY_HANDLE_LENGTH, keyHandle, keyHandleOffset); + + // Reverse the mixing step that we performed in + // generateKeyAndWrap. + deinterleave(keyHandle, keyHandleOffset, scratch, (short)32, scratch, (short)0, (short)32); + + // At this point the scratch *should* look like this: + // + // * bytes 0-31: Private key + // * bytes 32-47: CBC-MAC(chipKey, applicationParameter) + // * byte 48: "Info" byte + // * Bytes 49-63: Zero padding + + // Save our "info" byte for later. + byte info = scratch[48]; + + // In order to verify that this key handle is for this + // application parameter, we need to calculate the CBC-MAC + // of the application parameter so that we can compare it + // to the CBC-MAC in the decrypted and unmixed key handle. + // Here we encrypt the application parameter, but we will + // be using only the last 16-bytes. We encrypt it into the + // keyHandle buffer since we don't need it anymore. + cipherEncrypt.doFinal(applicationParameter, applicationParameterOffset, (short)32, keyHandle, keyHandleOffset); + + // This is where we actually verify if this key handle + // is for this application parameter on this device. + // We don't need to do a constant-time comparison here + // because we are comparing MAC values---so an attacker + // cannot glean any actionable information from a timing + // attack. + if (0 != Util.arrayCompare(keyHandle, (short)(keyHandleOffset+16), scratch, (short)32, (short)16)) { + // Clean up the buffers we used. + Util.arrayFillNonAtomic(scratch, (short)0, (short)64, (byte)0x00); + Util.arrayFillNonAtomic(keyHandle, keyHandleOffset, KEY_HANDLE_LENGTH, (byte)0x00); + + ISOException.throwIt(ISO7816.SW_WRONG_DATA); + } + if (unwrappedPrivateKey != null) { - unwrappedPrivateKey.setS(scratch, (short)32, (short)32); + unwrappedPrivateKey.setS(scratch, (short)0, (short)32); } - Util.arrayFillNonAtomic(scratch, (short)32, (short)32, (byte)0x00); - return true; - } + // Clean up the buffers we used. + Util.arrayFillNonAtomic(scratch, (short)0, (short)64, (byte)0x00); + Util.arrayFillNonAtomic(keyHandle, keyHandleOffset, KEY_HANDLE_LENGTH, (byte)0x00); + return info; + } } diff --git a/src/main/java/com/ledger/u2f/FIDOUtils.java b/src/main/java/com/ledger/u2f/FIDOUtils.java deleted file mode 100644 index 99e50e6..0000000 --- a/src/main/java/com/ledger/u2f/FIDOUtils.java +++ /dev/null @@ -1,45 +0,0 @@ -/* -******************************************************************************* -* FIDO U2F Authenticator -* (c) 2015 Ledger -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -******************************************************************************* -*/ - -package com.ledger.u2f; - -import javacard.security.ECPrivateKey; -import javacard.security.ECPublicKey; - -public class FIDOUtils { - - public static boolean compareConstantTime(byte[] array1, short array1Offset, byte[] array2, short array2Offset, short length) { - short givenLength = length; - byte status = (byte)0; - short counter = (short)0; - - if (length == 0) { - return false; - } - while ((length--) != 0) { - status |= (byte)((array1[(short)(array1Offset + length)]) ^ (array2[(short)(array2Offset + length)])); - counter++; - } - if (counter != givenLength) { - return false; - } - return (status == 0); - } - -} diff --git a/src/main/java/com/ledger/u2f/U2FApplet.java b/src/main/java/com/ledger/u2f/U2FApplet.java index 98e411a..cadc93c 100644 --- a/src/main/java/com/ledger/u2f/U2FApplet.java +++ b/src/main/java/com/ledger/u2f/U2FApplet.java @@ -24,8 +24,11 @@ import javacardx.apdu.ExtendedLength; public class U2FApplet extends Applet implements ExtendedLength { + private static final byte COUNTER_COUNT = 8; + private static final byte COUNTER_MASK = 7; - private Counter counter; + private Counter[] counters; + private byte next_counter; private Presence presence; private byte[] scratch; private byte[] attestationCertificate; @@ -38,14 +41,12 @@ public class U2FApplet extends Applet implements ExtendedLength { private static final byte VERSION[] = { 'U', '2', 'F', '_', 'V', '2' }; - private static final byte FIDO_CLA = (byte)0x00; private static final byte FIDO_INS_ENROLL = (byte)0x01; private static final byte FIDO_INS_SIGN = (byte)0x02; private static final byte FIDO_INS_VERSION = (byte)0x03; private static final byte ISO_INS_GET_DATA = (byte)0xC0; private static final byte FIDO2_INS_NFCCTAP_MSG = (byte)0x10; - private static final byte PROPRIETARY_CLA = (byte)0xF0; private static final byte FIDO_ADM_SET_ATTESTATION_CERT = (byte)0x01; private static final byte SCRATCH_TRANSPORT_STATE = (byte)0; @@ -99,8 +100,20 @@ public U2FApplet(byte[] parameters, short parametersOffset, byte parametersLengt if (parametersLength != 35) { ISOException.throwIt(ISO7816.SW_WRONG_DATA); } - counter = new Counter(); + + // Initialize with 8 counters. + counters = new Counter[] { + new Counter(), new Counter(), + new Counter(), new Counter(), + new Counter(), new Counter(), + new Counter(), new Counter() + }; + + // First counter is counter zero. + next_counter = 0; + scratch = JCSystem.makeTransientByteArray((short)(SCRATCH_PAD + SCRATCH_PAD_SIZE), JCSystem.CLEAR_ON_DESELECT); + try { // ok, let's save RAM localPrivateKey = (ECPrivateKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE_TRANSIENT_DESELECT, KeyBuilder.LENGTH_EC_FP_256, false); @@ -118,8 +131,10 @@ public U2FApplet(byte[] parameters, short parametersOffset, byte parametersLengt Secp256r1.setCommonCurveParameters(localPrivateKey); } } + attestationSignature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); localSignature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); + byte flags = parameters[parametersOffset]; if ((flags & INSTALL_FLAG_DISABLE_USER_PRESENCE) == 0) { @@ -129,10 +144,13 @@ public U2FApplet(byte[] parameters, short parametersOffset, byte parametersLengt } attestationCertificate = new byte[Util.getShort(parameters, (short)(parametersOffset + 1))]; + + // Set up our attestation signature object. ECPrivateKey attestationPrivateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false); Secp256r1.setCommonCurveParameters(attestationPrivateKey); attestationPrivateKey.setS(parameters, (short)(parametersOffset + 3), (short)32); attestationSignature.init(attestationPrivateKey, Signature.MODE_SIGN); + fidoImpl = new FIDOStandalone(); } @@ -155,8 +173,8 @@ private void handleEnroll(APDU apdu) throws ISOException { short len = apdu.setIncomingAndReceive(); short dataOffset = apdu.getOffsetCdata(); boolean extendedLength = (dataOffset != ISO7816.OFFSET_CDATA); - short outOffset; + // The length of this command must always be 64 bytes. if (len != 64) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } @@ -165,20 +183,28 @@ private void handleEnroll(APDU apdu) throws ISOException { presence.enforce_user_presence(); // Generate the key pair - if (localPrivateTransient) { - Secp256r1.setCommonCurveParameters(localPrivateKey); - } - short keyHandleLength = fidoImpl.generateKeyAndWrap(buffer, (short)(dataOffset + APDU_APPLICATION_PARAMETER_OFFSET), localPrivateKey, scratch, SCRATCH_PUBLIC_KEY_OFFSET, scratch, SCRATCH_KEY_HANDLE_OFFSET); + short keyHandleLength = fidoImpl.generateKeyAndWrap( + buffer, + (short)(dataOffset + APDU_APPLICATION_PARAMETER_OFFSET), + scratch, + SCRATCH_PUBLIC_KEY_OFFSET, + scratch, + SCRATCH_KEY_HANDLE_OFFSET, + (byte)(next_counter++ & COUNTER_MASK) + ); + scratch[SCRATCH_PAD] = ENROLL_LEGACY_VERSION; scratch[SCRATCH_KEY_HANDLE_LENGTH_OFFSET] = (byte)keyHandleLength; + // Prepare the attestation attestationSignature.update(RFU_ENROLL_SIGNED_VERSION, (short)0, (short)1); attestationSignature.update(buffer, (short)(dataOffset + APDU_APPLICATION_PARAMETER_OFFSET), (short)32); attestationSignature.update(buffer, (short)(dataOffset + APDU_CHALLENGE_OFFSET), (short)32); attestationSignature.update(scratch, SCRATCH_KEY_HANDLE_OFFSET, keyHandleLength); attestationSignature.update(scratch, SCRATCH_PUBLIC_KEY_OFFSET, (short)65); - outOffset = (short)(ENROLL_PUBLIC_KEY_OFFSET + 65 + 1 + keyHandleLength); + short signatureSize = attestationSignature.sign(buffer, (short)0, (short)0, scratch, SCRATCH_SIGNATURE_OFFSET); + short outOffset = (short)(ENROLL_PUBLIC_KEY_OFFSET + 65 + 1 + keyHandleLength); if (extendedLength) { // If using extended length, the message can be completed and sent immediately @@ -211,30 +237,40 @@ private void handleSign(APDU apdu) throws ISOException { short keyHandleLength; boolean extendedLength = (dataOffset != ISO7816.OFFSET_CDATA); short outOffset = SCRATCH_PAD; + if (len < 65) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } + switch(p1) { - case P1_ENFORCE_PRESENCE_AND_SIGN: - sign = true; - enforce_presence = true; - break; - case P1_IGNORE_PRESENCE_AND_SIGN: - sign = true; - break; - case P1_SIGN_CHECK_ONLY: - break; - default: - ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); + case P1_ENFORCE_PRESENCE_AND_SIGN: + sign = true; + enforce_presence = true; + break; + case P1_IGNORE_PRESENCE_AND_SIGN: + sign = true; + break; + case P1_SIGN_CHECK_ONLY: + break; + default: + ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } - // Verify key handle + if (localPrivateTransient) { Secp256r1.setCommonCurveParameters(localPrivateKey); } + keyHandleLength = (short)(buffer[(short)(dataOffset + 64)] & 0xff); - if (!fidoImpl.unwrap(buffer, (short)(dataOffset + 65), keyHandleLength, buffer, (short)(dataOffset + APDU_APPLICATION_PARAMETER_OFFSET), (sign ? localPrivateKey : null))) { - ISOException.throwIt(FIDO_SW_INVALID_KEY_HANDLE); - } + + // Verify key handle, unwrap our private + // key, and get our counter index. This + // will throw a ISO7816.SW_WRONG_DATA if + // the key handle is bad. + byte counter_index = fidoImpl.unwrap( + buffer, (short)(dataOffset + 65), keyHandleLength, + buffer, (short)(dataOffset + APDU_APPLICATION_PARAMETER_OFFSET), + (sign ? localPrivateKey : null) + ); // If not signing, return with the "correct" exception if (!sign) { @@ -242,20 +278,28 @@ private void handleSign(APDU apdu) throws ISOException { } if (enforce_presence) { + // This will either wait for user presence to be + // asserted or throw ISO7816.SW_CONDITIONS_NOT_SATISFIED. scratch[outOffset++] = presence.enforce_user_presence(); } else { + // This version returns immediately and never + // throws an exception. The returned value will + // indicate user presence if it makes sense for + // this authenticator. scratch[outOffset++] = presence.check_user_presence(); } - // Increase the signature counter + // Increase the signature counter and write it to scratch + Counter counter = counters[counter_index & COUNTER_MASK]; counter.inc(); - outOffset = counter.writeValue(scratch, outOffset); + // Create our signature. localSignature.init(localPrivateKey, Signature.MODE_SIGN); localSignature.update(buffer, (short)(dataOffset + APDU_APPLICATION_PARAMETER_OFFSET), (short)32); localSignature.update(scratch, SCRATCH_PAD, (short)5); outOffset += localSignature.sign(buffer, (short)(dataOffset + APDU_CHALLENGE_OFFSET), (short)32, scratch, outOffset); + if (extendedLength) { // If using extended length, the message can be completed and sent immediately scratch[SCRATCH_TRANSPORT_STATE] = TRANSPORT_EXTENDED;