Skip to content
This repository has been archived by the owner on Feb 14, 2024. It is now read-only.

Commit

Permalink
Merge branch 'master' into extended-apdu
Browse files Browse the repository at this point in the history
  • Loading branch information
darconeous authored Mar 17, 2020
2 parents 53f8290 + 384be71 commit bdcb1d1
Show file tree
Hide file tree
Showing 7 changed files with 456 additions and 362 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ Before using the applet, the attestation certificate shall be loaded using a pro

This implementation has been certified FIDO U2F compliant on December 17, 2015 (U2F100020151217001). See tag [u2f-certif-171215](https://github.com/LedgerHQ/ledger-u2f-javacard/tree/u2f-certif-171215)

# State model

![state model diagram](state-model.png)

# License

This application is licensed under [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0)
Expand Down
67 changes: 46 additions & 21 deletions src/main/java/com/ledger/u2f/FIDOAPI.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,55 @@
/*
*******************************************************************************
* 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.
*******************************************************************************
*/
*******************************************************************************
* 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 interface FIDOAPI {
/**
* Generate a new KeyPair over NIST P-256, for application of <code>applicationParameter</code>, export the
* public key into <code>publicKey</code> at <code>publicKeyOffset</code> and export the wrapped private key
* and application parameter into the <code>keyHandle</code> at <code>keyHandleOffset</code>.
*
* @param applicationParameter
* @param applicationParameterOffset
* @param generatedPrivateKey not used
* @param publicKey
* @param publicKeyOffset
* @param keyHandle output array
* @param keyHandleOffset offset into output array
* @return always 64
*/
short generateKeyAndWrap(byte[] applicationParameter, short applicationParameterOffset, ECPrivateKey generatedPrivateKey, byte[] publicKey, short publicKeyOffset, byte[] keyHandle, short keyHandleOffset);

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);

/**
* Unwrap a <code>keyHandle</code> at <code>keyHandleOffset</code> with <code>keyHandleLength</code> and set
* the unwrapped private key into <code>unwrappedPrivateKey</code> if the unwrapping was successful (if
* <code>applicationParameter</code> at <code>applicationParameterOffset</code> was the same as the unwrapped one).
*
* @param keyHandle
* @param keyHandleOffset
* @param keyHandleLength not used, assumed 64
* @param applicationParameter application to compare with
* @param applicationParameterOffset
* @param unwrappedPrivateKey output variable
* @return true if a valid key belonging to the indicated application is obtained
*/
boolean unwrap(byte[] keyHandle, short keyHandleOffset, short keyHandleLength, byte[] applicationParameter, short applicationParameterOffset, ECPrivateKey unwrappedPrivateKey);
}
155 changes: 94 additions & 61 deletions src/main/java/com/ledger/u2f/FIDOStandalone.java
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
/*
*******************************************************************************
* 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.
*******************************************************************************
*/
*******************************************************************************
* 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.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;
import javacard.security.RandomData;
import javacard.framework.Util;
import javacard.security.*;
import javacardx.crypto.Cipher;

public class FIDOStandalone implements FIDOAPI {

Expand All @@ -40,71 +34,110 @@ public class FIDOStandalone implements FIDOAPI {
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 };
private static final byte[] IV_ZERO_AES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

/**
* Init cipher engines and allocate memory.
*/
public FIDOStandalone() {
scratch = JCSystem.makeTransientByteArray((short)64, JCSystem.CLEAR_ON_DESELECT);
scratch = JCSystem.makeTransientByteArray((short) 64, 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);
(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_KEYGENERATION);
// Initialize the unique wrapping key
chipKey = (AESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false);
random.generateData(scratch, (short)0, (short)32);
chipKey.setKey(scratch, (short)0);
chipKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false);
random.nextBytes(scratch, (short) 0, (short) 32);
chipKey.setKey(scratch, (short) 0);
cipherEncrypt = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
cipherEncrypt.init(chipKey, Cipher.MODE_ENCRYPT, IV_ZERO_AES, (short)0, (short)IV_ZERO_AES.length);
cipherEncrypt.init(chipKey, Cipher.MODE_ENCRYPT, IV_ZERO_AES, (short) 0, (short) IV_ZERO_AES.length);
cipherDecrypt = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
cipherDecrypt.init(chipKey, Cipher.MODE_DECRYPT, IV_ZERO_AES, (short)0, (short)IV_ZERO_AES.length);
cipherDecrypt.init(chipKey, Cipher.MODE_DECRYPT, IV_ZERO_AES, (short) 0, (short) IV_ZERO_AES.length);
}

/**
* Interleave two byte arrays into the target one, nibble by nibble.
* Example:
* array1 = [0x12, 0x34]
* array2 = [0xab, 0xcd]
* -> [0x1a, 0x2b, 0x3c, 0x4d]
* <p>
* This is used to interleave the generated private key and the application parameter into two AES-CBC blocks,
* as not doing so would result in the application parameter being encrypted as a block with an all zero IV which
* would always result in the same first block for all generated private keys with the same application parameter
* wrapped under the same wrapping key, which would break privacy of U2F.
*
* @param array1
* @param array1Offset
* @param array2
* @param array2Offset
* @param target
* @param targetOffset
* @param length
*/
private static void interleave(byte[] array1, short array1Offset, byte[] array2, short array2Offset, byte[] target, short targetOffset, short length) {
for (short i=0; i<length; i++) {
short a = (short)(array1[(short)(array1Offset + i)] & 0xff);
short b = (short)(array2[(short)(array2Offset + i)] & 0xff);
target[(short)(targetOffset + 2 * i)] = (byte)((short)(a & 0xf0) | (short)(b >> 4));
target[(short)(targetOffset + 2 * i + 1)] = (byte)((short)((a & 0x0f) << 4) | (short)(b & 0x0f));
for (short i = 0; i < length; i++) {
short a = (short) (array1[(short) (array1Offset + i)] & 0xff);
short b = (short) (array2[(short) (array2Offset + i)] & 0xff);
target[(short) (targetOffset + 2 * i)] = (byte) ((short) (a & 0xf0) | (short) (b >> 4));
target[(short) (targetOffset + 2 * i + 1)] = (byte) ((short) ((a & 0x0f) << 4) | (short) (b & 0x0f));
}
}

/**
* Deinterleave a byte array back into two arrays of half size.
* Example:
* src = [0x1a, 0x2b, 0x3c, 0x4d]
* -> [0x12, 0x34] and [0xab, 0xcd]
*
* @param src
* @param srcOffset
* @param array1
* @param array1Offset
* @param array2
* @param array2Offset
* @param length
*/
private static void deinterleave(byte[] src, short srcOffset, byte[] array1, short array1Offset, byte[] array2, short array2Offset, short length) {
for (short i=0; i<length; i++) {
short a = (short)(src[(short)(srcOffset + 2 * i)] & 0xff);
short b = (short)(src[(short)(srcOffset + 2 * i + 1)] & 0xff);
array1[(short)(array1Offset + i)] = (byte)((short)(a & 0xf0) | (short)(b >> 4));
array2[(short)(array2Offset + i)] = (byte)(((short)(a & 0x0f) << 4) | (short)(b & 0x0f));
for (short i = 0; i < length; i++) {
short a = (short) (src[(short) (srcOffset + 2 * i)] & 0xff);
short b = (short) (src[(short) (srcOffset + 2 * i + 1)] & 0xff);
array1[(short) (array1Offset + i)] = (byte) ((short) (a & 0xf0) | (short) (b >> 4));
array2[(short) (array2Offset + i)] = (byte) (((short) (a & 0x0f) << 4) | (short) (b & 0x0f));
}
}

/* @override */
public short generateKeyAndWrap(byte[] applicationParameter, short applicationParameterOffset, ECPrivateKey generatedPrivateKey, byte[] publicKey, short publicKeyOffset, byte[] keyHandle, short keyHandleOffset) {
// Generate a new pair
keyPair.genKeyPair();
// Copy public key
((ECPublicKey)keyPair.getPublic()).getW(publicKey, publicKeyOffset);
((ECPublicKey) keyPair.getPublic()).getW(publicKey, publicKeyOffset);
// Wrap keypair and application parameters
((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;
((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;
}

/* @override */
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);
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;
}
Util.arrayFillNonAtomic(keyHandle, keyHandleOffset, (short)64, (byte)0x00);
Util.arrayFillNonAtomic(keyHandle, keyHandleOffset, (short) 64, (byte) 0x00);
if (unwrappedPrivateKey != null) {
unwrappedPrivateKey.setS(scratch, (short)32, (short)32);
unwrappedPrivateKey.setS(scratch, (short) 32, (short) 32);
}
Util.arrayFillNonAtomic(scratch, (short)32, (short)32, (byte)0x00);
Util.arrayFillNonAtomic(scratch, (short) 32, (short) 32, (byte) 0x00);
return true;
}

Expand Down
55 changes: 32 additions & 23 deletions src/main/java/com/ledger/u2f/FIDOUtils.java
Original file line number Diff line number Diff line change
@@ -1,39 +1,48 @@
/*
*******************************************************************************
* 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.
*******************************************************************************
*/
*******************************************************************************
* 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;

/**
* Utlity functions.
*/
public class FIDOUtils {

/**
* Comparison resistant to timing analysis.
* @param array1
* @param array1Offset
* @param array2
* @param array2Offset
* @param length
* @return true if the indicated number of bytes of the arrays starting at given offsets are equal
*/
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;
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)]));
status |= (byte) ((array1[(short) (array1Offset + length)]) ^ (array2[(short) (array2Offset + length)]));
counter++;
}
if (counter != givenLength) {
Expand Down
Loading

0 comments on commit bdcb1d1

Please sign in to comment.