From 8b9ca412ab52d76958f9a0df85f32431cd2fd344 Mon Sep 17 00:00:00 2001 From: J08nY Date: Thu, 5 Apr 2018 16:16:17 +0200 Subject: [PATCH 1/5] Reformat and code cleanup. --- src/main/java/com/ledger/u2f/FIDOAPI.java | 39 +- .../java/com/ledger/u2f/FIDOStandalone.java | 116 +++--- src/main/java/com/ledger/u2f/FIDOUtils.java | 43 +-- src/main/java/com/ledger/u2f/Secp256r1.java | 133 ++++--- src/main/java/com/ledger/u2f/U2FApplet.java | 357 +++++++++--------- 5 files changed, 330 insertions(+), 358 deletions(-) diff --git a/src/main/java/com/ledger/u2f/FIDOAPI.java b/src/main/java/com/ledger/u2f/FIDOAPI.java index 6df46c8..b75ab97 100644 --- a/src/main/java/com/ledger/u2f/FIDOAPI.java +++ b/src/main/java/com/ledger/u2f/FIDOAPI.java @@ -1,30 +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.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); + short generateKeyAndWrap(byte[] applicationParameter, short applicationParameterOffset, ECPrivateKey generatedPrivateKey, byte[] publicKey, short publicKeyOffset, byte[] keyHandle, short keyHandleOffset); + boolean 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 793f063..b4f001d 100644 --- a/src/main/java/com/ledger/u2f/FIDOStandalone.java +++ b/src/main/java/com/ledger/u2f/FIDOStandalone.java @@ -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 { @@ -40,41 +34,41 @@ public class FIDOStandalone implements FIDOAPI { private static RandomData random; private static 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}; 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); } private static void interleave(byte[] array1, short array1Offset, byte[] array2, short array2Offset, byte[] target, short targetOffset, short length) { - for (short i=0; i> 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)); } } private static void deinterleave(byte[] src, short srcOffset, byte[] array1, short array1Offset, byte[] array2, short array2Offset, short length) { - for (short i=0; i> 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)); } } @@ -82,29 +76,29 @@ public short generateKeyAndWrap(byte[] applicationParameter, short applicationPa // 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; } 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; } diff --git a/src/main/java/com/ledger/u2f/FIDOUtils.java b/src/main/java/com/ledger/u2f/FIDOUtils.java index 99e50e6..2687935 100644 --- a/src/main/java/com/ledger/u2f/FIDOUtils.java +++ b/src/main/java/com/ledger/u2f/FIDOUtils.java @@ -1,39 +1,36 @@ /* -******************************************************************************* -* 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 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; + 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) { diff --git a/src/main/java/com/ledger/u2f/Secp256r1.java b/src/main/java/com/ledger/u2f/Secp256r1.java index 0e64b71..857b34a 100644 --- a/src/main/java/com/ledger/u2f/Secp256r1.java +++ b/src/main/java/com/ledger/u2f/Secp256r1.java @@ -1,21 +1,21 @@ /* -******************************************************************************* -* 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; @@ -25,66 +25,65 @@ public class Secp256r1 { // Nice SECp256r1 constants, only available during NIST opening hours - protected static final byte SECP256R1_FP[] = { - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff + private static final byte SECP256R1_FP[] = { + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff }; - protected static final byte SECP256R1_A[] = { - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xfc + private static final byte SECP256R1_A[] = { + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xfc }; - protected static final byte SECP256R1_B[] = { - (byte) 0x5a, (byte) 0xc6, (byte) 0x35, (byte) 0xd8, (byte) 0xaa, (byte) 0x3a, - (byte) 0x93, (byte) 0xe7, (byte) 0xb3, (byte) 0xeb, (byte) 0xbd, (byte) 0x55, - (byte) 0x76, (byte) 0x98, (byte) 0x86, (byte) 0xbc, (byte) 0x65, (byte) 0x1d, - (byte) 0x06, (byte) 0xb0, (byte) 0xcc, (byte) 0x53, (byte) 0xb0, (byte) 0xf6, - (byte) 0x3b, (byte) 0xce, (byte) 0x3c, (byte) 0x3e, (byte) 0x27, (byte) 0xd2, - (byte) 0x60, (byte) 0x4b + private static final byte SECP256R1_B[] = { + (byte) 0x5a, (byte) 0xc6, (byte) 0x35, (byte) 0xd8, (byte) 0xaa, (byte) 0x3a, + (byte) 0x93, (byte) 0xe7, (byte) 0xb3, (byte) 0xeb, (byte) 0xbd, (byte) 0x55, + (byte) 0x76, (byte) 0x98, (byte) 0x86, (byte) 0xbc, (byte) 0x65, (byte) 0x1d, + (byte) 0x06, (byte) 0xb0, (byte) 0xcc, (byte) 0x53, (byte) 0xb0, (byte) 0xf6, + (byte) 0x3b, (byte) 0xce, (byte) 0x3c, (byte) 0x3e, (byte) 0x27, (byte) 0xd2, + (byte) 0x60, (byte) 0x4b }; - protected static final byte SECP256R1_G[] = { - (byte) 0x04, - (byte) 0x6b, (byte) 0x17, (byte) 0xd1, (byte) 0xf2, (byte) 0xe1, (byte) 0x2c, - (byte) 0x42, (byte) 0x47, (byte) 0xf8, (byte) 0xbc, (byte) 0xe6, (byte) 0xe5, - (byte) 0x63, (byte) 0xa4, (byte) 0x40, (byte) 0xf2, (byte) 0x77, (byte) 0x03, - (byte) 0x7d, (byte) 0x81, (byte) 0x2d, (byte) 0xeb, (byte) 0x33, (byte) 0xa0, - (byte) 0xf4, (byte) 0xa1, (byte) 0x39, (byte) 0x45, (byte) 0xd8, (byte) 0x98, - (byte) 0xc2, (byte) 0x96, - (byte) 0x4f, (byte) 0xe3, (byte) 0x42, (byte) 0xe2, (byte) 0xfe, (byte) 0x1a, - (byte) 0x7f, (byte) 0x9b, (byte) 0x8e, (byte) 0xe7, (byte) 0xeb, (byte) 0x4a, - (byte) 0x7c, (byte) 0x0f, (byte) 0x9e, (byte) 0x16, (byte) 0x2b, (byte) 0xce, - (byte) 0x33, (byte) 0x57, (byte) 0x6b, (byte) 0x31, (byte) 0x5e, (byte) 0xce, - (byte) 0xcb, (byte) 0xb6, (byte) 0x40, (byte) 0x68, (byte) 0x37, (byte) 0xbf, - (byte) 0x51, (byte) 0xf5 + private static final byte SECP256R1_G[] = { + (byte) 0x04, + (byte) 0x6b, (byte) 0x17, (byte) 0xd1, (byte) 0xf2, (byte) 0xe1, (byte) 0x2c, + (byte) 0x42, (byte) 0x47, (byte) 0xf8, (byte) 0xbc, (byte) 0xe6, (byte) 0xe5, + (byte) 0x63, (byte) 0xa4, (byte) 0x40, (byte) 0xf2, (byte) 0x77, (byte) 0x03, + (byte) 0x7d, (byte) 0x81, (byte) 0x2d, (byte) 0xeb, (byte) 0x33, (byte) 0xa0, + (byte) 0xf4, (byte) 0xa1, (byte) 0x39, (byte) 0x45, (byte) 0xd8, (byte) 0x98, + (byte) 0xc2, (byte) 0x96, + (byte) 0x4f, (byte) 0xe3, (byte) 0x42, (byte) 0xe2, (byte) 0xfe, (byte) 0x1a, + (byte) 0x7f, (byte) 0x9b, (byte) 0x8e, (byte) 0xe7, (byte) 0xeb, (byte) 0x4a, + (byte) 0x7c, (byte) 0x0f, (byte) 0x9e, (byte) 0x16, (byte) 0x2b, (byte) 0xce, + (byte) 0x33, (byte) 0x57, (byte) 0x6b, (byte) 0x31, (byte) 0x5e, (byte) 0xce, + (byte) 0xcb, (byte) 0xb6, (byte) 0x40, (byte) 0x68, (byte) 0x37, (byte) 0xbf, + (byte) 0x51, (byte) 0xf5 }; - protected static final byte SECP256R1_R[] = { - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xbc, (byte) 0xe6, - (byte) 0xfa, (byte) 0xad, (byte) 0xa7, (byte) 0x17, (byte) 0x9e, (byte) 0x84, - (byte) 0xf3, (byte) 0xb9, (byte) 0xca, (byte) 0xc2, (byte) 0xfc, (byte) 0x63, - (byte) 0x25, (byte) 0x51 + private static final byte SECP256R1_R[] = { + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xbc, (byte) 0xe6, + (byte) 0xfa, (byte) 0xad, (byte) 0xa7, (byte) 0x17, (byte) 0x9e, (byte) 0x84, + (byte) 0xf3, (byte) 0xb9, (byte) 0xca, (byte) 0xc2, (byte) 0xfc, (byte) 0x63, + (byte) 0x25, (byte) 0x51 }; - protected static final byte SECP256R1_K = (byte)0x01; + private static final byte SECP256R1_K = (byte) 0x01; protected static boolean setCommonCurveParameters(ECKey key) { try { - key.setA(SECP256R1_A, (short)0, (short)SECP256R1_A.length); - key.setB(SECP256R1_B, (short)0, (short)SECP256R1_B.length); - key.setFieldFP(SECP256R1_FP, (short)0, (short)SECP256R1_FP.length); - key.setG(SECP256R1_G, (short)0, (short)SECP256R1_G.length); - key.setR(SECP256R1_R, (short)0, (short)SECP256R1_R.length); + key.setA(SECP256R1_A, (short) 0, (short) SECP256R1_A.length); + key.setB(SECP256R1_B, (short) 0, (short) SECP256R1_B.length); + key.setFieldFP(SECP256R1_FP, (short) 0, (short) SECP256R1_FP.length); + key.setG(SECP256R1_G, (short) 0, (short) SECP256R1_G.length); + key.setR(SECP256R1_R, (short) 0, (short) SECP256R1_R.length); key.setK(SECP256R1_K); return true; - } - catch(Exception e) { + } catch (Exception e) { return false; } diff --git a/src/main/java/com/ledger/u2f/U2FApplet.java b/src/main/java/com/ledger/u2f/U2FApplet.java index 1d432fb..7eb129d 100644 --- a/src/main/java/com/ledger/u2f/U2FApplet.java +++ b/src/main/java/com/ledger/u2f/U2FApplet.java @@ -1,37 +1,30 @@ /* -******************************************************************************* -* 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.framework.APDU; -import javacard.framework.Applet; -import javacard.framework.ISO7816; -import javacard.framework.ISOException; -import javacard.framework.JCSystem; -import javacard.framework.Util; - -import javacard.security.KeyBuilder; -import javacardx.apdu.ExtendedLength; +import javacard.framework.*; +import javacard.security.CryptoException; import javacard.security.ECPrivateKey; -import javacard.security.ECPublicKey; +import javacard.security.KeyBuilder; import javacard.security.Signature; -import javacard.security.CryptoException; +import javacardx.apdu.ExtendedLength; public class U2FApplet extends Applet implements ExtendedLength { @@ -49,58 +42,58 @@ public class U2FApplet extends Applet implements ExtendedLength { private static Signature localSignature; private static FIDOAPI fidoImpl; - private static final byte VERSION[] = { 'U', '2', 'F', '_', 'V', '2' }; + 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 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 PROPRIETARY_CLA = (byte)0xF0; - private static final byte FIDO_ADM_SET_ATTESTATION_CERT = (byte)0x01; + 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; - private static final byte SCRATCH_CURRENT_OFFSET = (byte)1; - private static final byte SCRATCH_NONCERT_LENGTH = (byte)3; - private static final byte SCRATCH_INCLUDE_CERT = (byte)5; - private static final byte SCRATCH_SIGNATURE_LENGTH = (byte)6; - private static final byte SCRATCH_FULL_LENGTH = (byte)8; - private static final byte SCRATCH_PAD = (byte)10; + private static final byte SCRATCH_TRANSPORT_STATE = (byte) 0; + private static final byte SCRATCH_CURRENT_OFFSET = (byte) 1; + private static final byte SCRATCH_NONCERT_LENGTH = (byte) 3; + private static final byte SCRATCH_INCLUDE_CERT = (byte) 5; + private static final byte SCRATCH_SIGNATURE_LENGTH = (byte) 6; + private static final byte SCRATCH_FULL_LENGTH = (byte) 8; + private static final byte SCRATCH_PAD = (byte) 10; // Should hold 1 (version) + 65 (public key) + 1 (key handle length) + L (key handle) + largest signature - private static final short ENROLL_FIXED_RESPONSE_SIZE = (short)(1 + 65 + 1); - private static final short KEYHANDLE_MAX = (short)64; // Update if you change the KeyHandle encoding implementation - private static final short SIGNATURE_MAX = (short)72; // DER encoding with negative R and S - private static final short SCRATCH_PAD_SIZE = (short)(ENROLL_FIXED_RESPONSE_SIZE + KEYHANDLE_MAX + SIGNATURE_MAX); - private static final short SCRATCH_PUBLIC_KEY_OFFSET = (short)(SCRATCH_PAD + 1); - private static final short SCRATCH_KEY_HANDLE_LENGTH_OFFSET = (short)(SCRATCH_PAD + 66); - private static final short SCRATCH_KEY_HANDLE_OFFSET = (short)(SCRATCH_PAD + 67); - private static final short SCRATCH_SIGNATURE_OFFSET = (short)(SCRATCH_PAD + ENROLL_FIXED_RESPONSE_SIZE + KEYHANDLE_MAX); + private static final short ENROLL_FIXED_RESPONSE_SIZE = (short) (1 + 65 + 1); + private static final short KEYHANDLE_MAX = (short) 64; // Update if you change the KeyHandle encoding implementation + private static final short SIGNATURE_MAX = (short) 72; // DER encoding with negative R and S + private static final short SCRATCH_PAD_SIZE = (short) (ENROLL_FIXED_RESPONSE_SIZE + KEYHANDLE_MAX + SIGNATURE_MAX); + private static final short SCRATCH_PUBLIC_KEY_OFFSET = (short) (SCRATCH_PAD + 1); + private static final short SCRATCH_KEY_HANDLE_LENGTH_OFFSET = (short) (SCRATCH_PAD + 66); + private static final short SCRATCH_KEY_HANDLE_OFFSET = (short) (SCRATCH_PAD + 67); + private static final short SCRATCH_SIGNATURE_OFFSET = (short) (SCRATCH_PAD + ENROLL_FIXED_RESPONSE_SIZE + KEYHANDLE_MAX); - private static final byte TRANSPORT_NONE = (byte)0; - private static final byte TRANSPORT_EXTENDED = (byte)1; - private static final byte TRANSPORT_NOT_EXTENDED = (byte)2; - private static final byte TRANSPORT_NOT_EXTENDED_CERT = (byte)3; - private static final byte TRANSPORT_NOT_EXTENDED_SIGNATURE = (byte)4; + private static final byte TRANSPORT_NONE = (byte) 0; + private static final byte TRANSPORT_EXTENDED = (byte) 1; + private static final byte TRANSPORT_NOT_EXTENDED = (byte) 2; + private static final byte TRANSPORT_NOT_EXTENDED_CERT = (byte) 3; + private static final byte TRANSPORT_NOT_EXTENDED_SIGNATURE = (byte) 4; - private static final byte P1_SIGN_OPERATION = (byte)0x03; - private static final byte P1_SIGN_CHECK_ONLY = (byte)0x07; + private static final byte P1_SIGN_OPERATION = (byte) 0x03; + private static final byte P1_SIGN_CHECK_ONLY = (byte) 0x07; - private static final byte ENROLL_LEGACY_VERSION = (byte)0x05; - private static final byte RFU_ENROLL_SIGNED_VERSION[] = { (byte)0x00 }; + private static final byte ENROLL_LEGACY_VERSION = (byte) 0x05; + private static final byte RFU_ENROLL_SIGNED_VERSION[] = {(byte) 0x00}; - private static final short ENROLL_PUBLIC_KEY_OFFSET = (short)1; - private static final short ENROLL_KEY_HANDLE_LENGTH_OFFSET = (short)66; - private static final short ENROLL_KEY_HANDLE_OFFSET = (short)67; - private static final short APDU_CHALLENGE_OFFSET = (short)0; - private static final short APDU_APPLICATION_PARAMETER_OFFSET = (short)32; + private static final short ENROLL_PUBLIC_KEY_OFFSET = (short) 1; + private static final short ENROLL_KEY_HANDLE_LENGTH_OFFSET = (short) 66; + private static final short ENROLL_KEY_HANDLE_OFFSET = (short) 67; + private static final short APDU_CHALLENGE_OFFSET = (short) 0; + private static final short APDU_APPLICATION_PARAMETER_OFFSET = (short) 32; - private static final byte FLAG_USER_PRESENCE_VERIFIED = (byte)0x01; + private static final byte FLAG_USER_PRESENCE_VERIFIED = (byte) 0x01; private static final short FIDO_SW_TEST_OF_PRESENCE_REQUIRED = ISO7816.SW_CONDITIONS_NOT_SATISFIED; private static final short FIDO_SW_INVALID_KEY_HANDLE = ISO7816.SW_WRONG_DATA; - private static final byte INSTALL_FLAG_DISABLE_USER_PRESENCE = (byte)0x01; + private static final byte INSTALL_FLAG_DISABLE_USER_PRESENCE = (byte) 0x01; // Parameters // 1 byte : flags @@ -111,32 +104,30 @@ public U2FApplet(byte[] parameters, short parametersOffset, byte parametersLengt ISOException.throwIt(ISO7816.SW_WRONG_DATA); } counter = new byte[4]; - scratchPersistent = JCSystem.makeTransientByteArray((short)1, JCSystem.CLEAR_ON_RESET); - scratch = JCSystem.makeTransientByteArray((short)(SCRATCH_PAD + SCRATCH_PAD_SIZE), JCSystem.CLEAR_ON_DESELECT); + scratchPersistent = JCSystem.makeTransientByteArray((short) 1, JCSystem.CLEAR_ON_RESET); + 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); + localPrivateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE_TRANSIENT_DESELECT, KeyBuilder.LENGTH_EC_FP_256, false); localPrivateTransient = true; - } - catch(CryptoException e) { + } catch (CryptoException e) { try { // ok, let's save a bit less RAM - localPrivateKey = (ECPrivateKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE_TRANSIENT_RESET, KeyBuilder.LENGTH_EC_FP_256, false); + localPrivateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE_TRANSIENT_RESET, KeyBuilder.LENGTH_EC_FP_256, false); localPrivateTransient = true; - } - catch(CryptoException e1) { + } catch (CryptoException e1) { // ok, let's test the flash wear leveling \o/ - localPrivateKey = (ECPrivateKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false); + localPrivateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false); Secp256r1.setCommonCurveParameters(localPrivateKey); } } attestationSignature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); localSignature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); flags = parameters[parametersOffset]; - attestationCertificate = new byte[Util.getShort(parameters, (short)(parametersOffset + 1))]; - attestationPrivateKey = (ECPrivateKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false); + attestationCertificate = new byte[Util.getShort(parameters, (short) (parametersOffset + 1))]; + 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); + attestationPrivateKey.setS(parameters, (short) (parametersOffset + 3), (short) 32); attestationSignature.init(attestationPrivateKey, Signature.MODE_SIGN); fidoImpl = new FIDOStandalone(); } @@ -146,11 +137,11 @@ private void handleSetAttestationCert(APDU apdu) throws ISOException { short len = apdu.setIncomingAndReceive(); short dataOffset = apdu.getOffsetCdata(); short copyOffset = Util.makeShort(buffer[ISO7816.OFFSET_P1], buffer[ISO7816.OFFSET_P2]); - if ((short)(copyOffset + len) > (short)attestationCertificate.length) { + if ((short) (copyOffset + len) > (short) attestationCertificate.length) { ISOException.throwIt(ISO7816.SW_WRONG_DATA); } Util.arrayCopy(buffer, dataOffset, attestationCertificate, copyOffset, len); - if ((short)(copyOffset + len) == (short)attestationCertificate.length) { + if ((short) (copyOffset + len) == (short) attestationCertificate.length) { attestationCertificateSet = true; } } @@ -175,39 +166,38 @@ private void handleEnroll(APDU apdu) throws ISOException { ISOException.throwIt(ISO7816.SW_FILE_FULL); } // Set user presence - scratchPersistent[0] = (byte)1; + scratchPersistent[0] = (byte) 1; // 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), localPrivateKey, scratch, SCRATCH_PUBLIC_KEY_OFFSET, scratch, SCRATCH_KEY_HANDLE_OFFSET); scratch[SCRATCH_PAD] = ENROLL_LEGACY_VERSION; - scratch[SCRATCH_KEY_HANDLE_LENGTH_OFFSET] = (byte)keyHandleLength; + 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(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); + attestationSignature.update(scratch, SCRATCH_PUBLIC_KEY_OFFSET, (short) 65); + outOffset = (short) (ENROLL_PUBLIC_KEY_OFFSET + 65 + 1 + keyHandleLength); if (extendedLength) { // If using extended length, the message can be completed and sent immediately scratch[SCRATCH_TRANSPORT_STATE] = TRANSPORT_EXTENDED; - outOffset = Util.arrayCopyNonAtomic(scratch, SCRATCH_PAD, buffer, (short)0, outOffset); - outOffset = Util.arrayCopyNonAtomic(attestationCertificate, (short)0, buffer, outOffset, (short)attestationCertificate.length); - short signatureSize = attestationSignature.sign(buffer, (short)0, (short)0, buffer, outOffset); + outOffset = Util.arrayCopyNonAtomic(scratch, SCRATCH_PAD, buffer, (short) 0, outOffset); + outOffset = Util.arrayCopyNonAtomic(attestationCertificate, (short) 0, buffer, outOffset, (short) attestationCertificate.length); + short signatureSize = attestationSignature.sign(buffer, (short) 0, (short) 0, buffer, outOffset); outOffset += signatureSize; - apdu.setOutgoingAndSend((short)0, outOffset); - } - else { + apdu.setOutgoingAndSend((short) 0, outOffset); + } else { // Otherwise, keep the signature and proceed to send the first chunk - short signatureSize = attestationSignature.sign(buffer, (short)0, (short)0, scratch, SCRATCH_SIGNATURE_OFFSET); + short signatureSize = attestationSignature.sign(buffer, (short) 0, (short) 0, scratch, SCRATCH_SIGNATURE_OFFSET); scratch[SCRATCH_TRANSPORT_STATE] = TRANSPORT_NOT_EXTENDED; - Util.setShort(scratch, SCRATCH_CURRENT_OFFSET, (short)0); + Util.setShort(scratch, SCRATCH_CURRENT_OFFSET, (short) 0); Util.setShort(scratch, SCRATCH_SIGNATURE_LENGTH, signatureSize); Util.setShort(scratch, SCRATCH_NONCERT_LENGTH, outOffset); - Util.setShort(scratch, SCRATCH_FULL_LENGTH, (short)(outOffset + attestationCertificate.length + signatureSize)); - scratch[SCRATCH_INCLUDE_CERT] = (byte)1; + Util.setShort(scratch, SCRATCH_FULL_LENGTH, (short) (outOffset + attestationCertificate.length + signatureSize)); + scratch[SCRATCH_INCLUDE_CERT] = (byte) 1; handleGetData(apdu); } } @@ -225,14 +215,14 @@ private void handleSign(APDU apdu) throws ISOException { if (len < 65) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } - switch(p1) { - case P1_SIGN_OPERATION: - sign = true; - break; - case P1_SIGN_CHECK_ONLY: - break; - default: - ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); + switch (p1) { + case P1_SIGN_OPERATION: + sign = true; + break; + case P1_SIGN_CHECK_ONLY: + break; + default: + ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } // Check if the counter overflowed if (counterOverflowed) { @@ -242,8 +232,8 @@ private void handleSign(APDU apdu) throws ISOException { 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))) { + 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); } // If not signing, return with the "correct" exception @@ -256,18 +246,18 @@ private void handleSign(APDU apdu) throws ISOException { ISOException.throwIt(FIDO_SW_TEST_OF_PRESENCE_REQUIRED); } } - scratchPersistent[0] = (byte)1; + scratchPersistent[0] = (byte) 1; // Increase the counter boolean carry = false; JCSystem.beginTransaction(); - for (byte i=0; i<4; i++) { - short addValue = (i == 0 ? (short)1 : (short)0); - short val = (short)((short)(counter[(short)(4 - 1 - i)] & 0xff) + addValue); + for (byte i = 0; i < 4; i++) { + short addValue = (i == 0 ? (short) 1 : (short) 0); + short val = (short) ((short) (counter[(short) (4 - 1 - i)] & 0xff) + addValue); if (carry) { val++; } carry = (val > 255); - counter[(short)(4 - 1 - i)] = (byte)val; + counter[(short) (4 - 1 - i)] = (byte) val; } JCSystem.commitTransaction(); if (carry) { @@ -277,40 +267,39 @@ private void handleSign(APDU apdu) throws ISOException { } // Prepare reply scratch[outOffset++] = FLAG_USER_PRESENCE_VERIFIED; - outOffset = Util.arrayCopyNonAtomic(counter, (short)0, scratch, outOffset, (short)4); + outOffset = Util.arrayCopyNonAtomic(counter, (short) 0, scratch, outOffset, (short) 4); 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); + 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; - Util.arrayCopyNonAtomic(scratch, SCRATCH_PAD, buffer, (short)0, outOffset); - apdu.setOutgoingAndSend((short)0, (short)(outOffset - SCRATCH_PAD)); - } - else { + Util.arrayCopyNonAtomic(scratch, SCRATCH_PAD, buffer, (short) 0, outOffset); + apdu.setOutgoingAndSend((short) 0, (short) (outOffset - SCRATCH_PAD)); + } else { // Otherwise send the first chunk scratch[SCRATCH_TRANSPORT_STATE] = TRANSPORT_NOT_EXTENDED; - Util.setShort(scratch, SCRATCH_CURRENT_OFFSET, (short)0); - Util.setShort(scratch, SCRATCH_SIGNATURE_LENGTH, (short)0); - Util.setShort(scratch, SCRATCH_NONCERT_LENGTH, (short)(outOffset - SCRATCH_PAD)); - Util.setShort(scratch, SCRATCH_FULL_LENGTH, (short)(outOffset - SCRATCH_PAD)); - scratch[SCRATCH_INCLUDE_CERT] = (byte)0; + Util.setShort(scratch, SCRATCH_CURRENT_OFFSET, (short) 0); + Util.setShort(scratch, SCRATCH_SIGNATURE_LENGTH, (short) 0); + Util.setShort(scratch, SCRATCH_NONCERT_LENGTH, (short) (outOffset - SCRATCH_PAD)); + Util.setShort(scratch, SCRATCH_FULL_LENGTH, (short) (outOffset - SCRATCH_PAD)); + scratch[SCRATCH_INCLUDE_CERT] = (byte) 0; handleGetData(apdu); } } private void handleVersion(APDU apdu) throws ISOException { byte[] buffer = apdu.getBuffer(); - Util.arrayCopyNonAtomic(VERSION, (short)0, buffer, (short)0, (short)VERSION.length); - apdu.setOutgoingAndSend((short)0, (short)VERSION.length); + Util.arrayCopyNonAtomic(VERSION, (short) 0, buffer, (short) 0, (short) VERSION.length); + apdu.setOutgoingAndSend((short) 0, (short) VERSION.length); } private void handleGetData(APDU apdu) throws ISOException { byte[] buffer = apdu.getBuffer(); short currentOffset = Util.getShort(scratch, SCRATCH_CURRENT_OFFSET); short fullLength = Util.getShort(scratch, SCRATCH_FULL_LENGTH); - switch(scratch[SCRATCH_TRANSPORT_STATE]) { + switch (scratch[SCRATCH_TRANSPORT_STATE]) { case TRANSPORT_NOT_EXTENDED: case TRANSPORT_NOT_EXTENDED_CERT: case TRANSPORT_NOT_EXTENDED_SIGNATURE: @@ -319,60 +308,56 @@ private void handleGetData(APDU apdu) throws ISOException { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } short requestedSize = apdu.setOutgoing(); - short outOffset = (short)0; + short outOffset = (short) 0; if (scratch[SCRATCH_TRANSPORT_STATE] == TRANSPORT_NOT_EXTENDED) { short dataSize = Util.getShort(scratch, SCRATCH_NONCERT_LENGTH); - short blockSize = ((short)(dataSize - currentOffset) > requestedSize ? requestedSize : (short)(dataSize - currentOffset)); - Util.arrayCopyNonAtomic(scratch, (short)(SCRATCH_PAD + currentOffset), buffer, outOffset, blockSize); + short blockSize = ((short) (dataSize - currentOffset) > requestedSize ? requestedSize : (short) (dataSize - currentOffset)); + Util.arrayCopyNonAtomic(scratch, (short) (SCRATCH_PAD + currentOffset), buffer, outOffset, blockSize); outOffset += blockSize; currentOffset += blockSize; fullLength -= blockSize; if (currentOffset == dataSize) { - if (scratch[SCRATCH_INCLUDE_CERT] == (byte)1) { + if (scratch[SCRATCH_INCLUDE_CERT] == (byte) 1) { scratch[SCRATCH_TRANSPORT_STATE] = TRANSPORT_NOT_EXTENDED_CERT; - currentOffset = (short)0; - requestedSize -= blockSize; - } - else { + currentOffset = (short) 0; + requestedSize -= blockSize; + } else { scratch[SCRATCH_TRANSPORT_STATE] = TRANSPORT_NONE; } } } - if ((scratch[SCRATCH_TRANSPORT_STATE] == TRANSPORT_NOT_EXTENDED_CERT) && (requestedSize != (short)0)) { - short blockSize = ((short)(attestationCertificate.length - currentOffset) > requestedSize ? requestedSize : (short)(attestationCertificate.length - currentOffset)); + if ((scratch[SCRATCH_TRANSPORT_STATE] == TRANSPORT_NOT_EXTENDED_CERT) && (requestedSize != (short) 0)) { + short blockSize = ((short) (attestationCertificate.length - currentOffset) > requestedSize ? requestedSize : (short) (attestationCertificate.length - currentOffset)); Util.arrayCopyNonAtomic(attestationCertificate, currentOffset, buffer, outOffset, blockSize); outOffset += blockSize; currentOffset += blockSize; fullLength -= blockSize; - if (currentOffset == (short)attestationCertificate.length) { - if (Util.getShort(scratch, SCRATCH_SIGNATURE_LENGTH) != (short)0) { + if (currentOffset == (short) attestationCertificate.length) { + if (Util.getShort(scratch, SCRATCH_SIGNATURE_LENGTH) != (short) 0) { scratch[SCRATCH_TRANSPORT_STATE] = TRANSPORT_NOT_EXTENDED_SIGNATURE; - currentOffset = (short)0; - requestedSize -= blockSize; - } - else { - scratch[SCRATCH_TRANSPORT_STATE] = TRANSPORT_NONE; + currentOffset = (short) 0; + requestedSize -= blockSize; + } else { + scratch[SCRATCH_TRANSPORT_STATE] = TRANSPORT_NONE; } } } - if ((scratch[SCRATCH_TRANSPORT_STATE] == TRANSPORT_NOT_EXTENDED_SIGNATURE) && (requestedSize != (short)0)) { + if ((scratch[SCRATCH_TRANSPORT_STATE] == TRANSPORT_NOT_EXTENDED_SIGNATURE) && (requestedSize != (short) 0)) { short signatureSize = Util.getShort(scratch, SCRATCH_SIGNATURE_LENGTH); - short blockSize = ((short)(signatureSize - currentOffset) > requestedSize ? requestedSize : (short)(signatureSize - currentOffset)); - Util.arrayCopyNonAtomic(scratch, (short)(SCRATCH_SIGNATURE_OFFSET + currentOffset), buffer, outOffset, blockSize); + short blockSize = ((short) (signatureSize - currentOffset) > requestedSize ? requestedSize : (short) (signatureSize - currentOffset)); + Util.arrayCopyNonAtomic(scratch, (short) (SCRATCH_SIGNATURE_OFFSET + currentOffset), buffer, outOffset, blockSize); outOffset += blockSize; currentOffset += blockSize; fullLength -= blockSize; - } + } apdu.setOutgoingLength(outOffset); - apdu.sendBytes((short)0, outOffset); + apdu.sendBytes((short) 0, outOffset); Util.setShort(scratch, SCRATCH_CURRENT_OFFSET, currentOffset); Util.setShort(scratch, SCRATCH_FULL_LENGTH, fullLength); if (fullLength > 256) { ISOException.throwIt(ISO7816.SW_BYTES_REMAINING_00); - } - else - if (fullLength != 0) { - ISOException.throwIt((short)(ISO7816.SW_BYTES_REMAINING_00 + fullLength)); + } else if (fullLength != 0) { + ISOException.throwIt((short) (ISO7816.SW_BYTES_REMAINING_00 + fullLength)); } } @@ -380,8 +365,8 @@ public void process(APDU apdu) throws ISOException { byte[] buffer = apdu.getBuffer(); if (selectingApplet()) { if (attestationCertificateSet) { - Util.arrayCopyNonAtomic(VERSION, (short)0, buffer, (short)0, (short)VERSION.length); - apdu.setOutgoingAndSend((short)0, (short)VERSION.length); + Util.arrayCopyNonAtomic(VERSION, (short) 0, buffer, (short) 0, (short) VERSION.length); + apdu.setOutgoingAndSend((short) 0, (short) VERSION.length); } return; } @@ -389,45 +374,43 @@ public void process(APDU apdu) throws ISOException { if (attestationCertificateSet) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } - switch(buffer[ISO7816.OFFSET_INS]) { - case FIDO_ADM_SET_ATTESTATION_CERT: - handleSetAttestationCert(apdu); - break; - default: - ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + switch (buffer[ISO7816.OFFSET_INS]) { + case FIDO_ADM_SET_ATTESTATION_CERT: + handleSetAttestationCert(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); } - } - else if (buffer[ISO7816.OFFSET_CLA] == FIDO_CLA) { + } else if (buffer[ISO7816.OFFSET_CLA] == FIDO_CLA) { if (!attestationCertificateSet) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } - switch(buffer[ISO7816.OFFSET_INS]) { - case FIDO_INS_ENROLL: - handleEnroll(apdu); - break; - case FIDO_INS_SIGN: - handleSign(apdu); - break; - case FIDO_INS_VERSION: - handleVersion(apdu); - break; - case ISO_INS_GET_DATA: - handleGetData(apdu); - break; - default: - ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + switch (buffer[ISO7816.OFFSET_INS]) { + case FIDO_INS_ENROLL: + handleEnroll(apdu); + break; + case FIDO_INS_SIGN: + handleSign(apdu); + break; + case FIDO_INS_VERSION: + handleVersion(apdu); + break; + case ISO_INS_GET_DATA: + handleGetData(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); } - } - else { + } else { ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); } } - public static void install (byte bArray[], short bOffset, byte bLength) throws ISOException { + public static void install(byte bArray[], short bOffset, byte bLength) throws ISOException { short offset = bOffset; - offset += (short)(bArray[offset] + 1); // instance - offset += (short)(bArray[offset] + 1); // privileges - new U2FApplet(bArray, (short)(offset + 1), bArray[offset]).register(bArray, (short)(bOffset + 1), bArray[bOffset]); + offset += (short) (bArray[offset] + 1); // instance + offset += (short) (bArray[offset] + 1); // privileges + new U2FApplet(bArray, (short) (offset + 1), bArray[offset]).register(bArray, (short) (bOffset + 1), bArray[bOffset]); } } From 2a7eeba3515fb4795c769d3d1d5571b04657ae7a Mon Sep 17 00:00:00 2001 From: J08nY Date: Mon, 16 Apr 2018 11:39:59 +0200 Subject: [PATCH 2/5] Some docs in the applet. --- src/main/java/com/ledger/u2f/U2FApplet.java | 34 ++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ledger/u2f/U2FApplet.java b/src/main/java/com/ledger/u2f/U2FApplet.java index 7eb129d..394ae52 100644 --- a/src/main/java/com/ledger/u2f/U2FApplet.java +++ b/src/main/java/com/ledger/u2f/U2FApplet.java @@ -132,6 +132,13 @@ public U2FApplet(byte[] parameters, short parametersOffset, byte parametersLengt fidoImpl = new FIDOStandalone(); } + /** + * Handle the customs attestation cert command. + * After it is all set, switch the flag that it is. + * + * @param apdu + * @throws ISOException + */ private void handleSetAttestationCert(APDU apdu) throws ISOException { byte[] buffer = apdu.getBuffer(); short len = apdu.setIncomingAndReceive(); @@ -146,12 +153,19 @@ private void handleSetAttestationCert(APDU apdu) throws ISOException { } } + /** + * Handle U2F_REGISTER. + * + * @param apdu + * @throws ISOException + */ private void handleEnroll(APDU apdu) throws ISOException { byte[] buffer = apdu.getBuffer(); short len = apdu.setIncomingAndReceive(); short dataOffset = apdu.getOffsetCdata(); boolean extendedLength = (dataOffset != ISO7816.OFFSET_CDATA); short outOffset; + // Enroll should be exactly 64 bytes if (len != 64) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } @@ -202,13 +216,18 @@ private void handleEnroll(APDU apdu) throws ISOException { } } + /** + * Handle U2F_AUTHENTICATE. + * + * @param apdu + * @throws ISOException + */ private void handleSign(APDU apdu) throws ISOException { byte[] buffer = apdu.getBuffer(); short len = apdu.setIncomingAndReceive(); short dataOffset = apdu.getOffsetCdata(); byte p1 = buffer[ISO7816.OFFSET_P1]; boolean sign = false; - boolean counterOverflow = true; short keyHandleLength; boolean extendedLength = (dataOffset != ISO7816.OFFSET_CDATA); short outOffset = SCRATCH_PAD; @@ -289,12 +308,25 @@ private void handleSign(APDU apdu) throws ISOException { } } + /** + * Handle U2F_GET_VERSION. + * + * @param apdu + * @throws ISOException + */ private void handleVersion(APDU apdu) throws ISOException { byte[] buffer = apdu.getBuffer(); Util.arrayCopyNonAtomic(VERSION, (short) 0, buffer, (short) 0, (short) VERSION.length); apdu.setOutgoingAndSend((short) 0, (short) VERSION.length); } + /** + * Handle the ISO7816 GET_DATA command. + * Either send data from enrollment or authentication, what was last. + * + * @param apdu + * @throws ISOException + */ private void handleGetData(APDU apdu) throws ISOException { byte[] buffer = apdu.getBuffer(); short currentOffset = Util.getShort(scratch, SCRATCH_CURRENT_OFFSET); From 6a85cd526a63ca75da9ada47b519f09553775e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ou=C5=A1ek?= Date: Wed, 18 Apr 2018 14:06:42 +0200 Subject: [PATCH 3/5] state model --- README.md | 4 ++++ state-model.png | Bin 0 -> 98261 bytes 2 files changed, 4 insertions(+) create mode 100644 state-model.png diff --git a/README.md b/README.md index 5c2b7f3..6eb99dc 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,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) diff --git a/state-model.png b/state-model.png new file mode 100644 index 0000000000000000000000000000000000000000..6db591112cb6f70071a4598175861445f447ca02 GIT binary patch literal 98261 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 literal 0 HcmV?d00001 From d257eba193644c75b7e71e7562876e7a747374e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ou=C5=A1ek?= Date: Wed, 18 Apr 2018 17:32:08 +0200 Subject: [PATCH 4/5] more javadoc --- src/main/java/com/ledger/u2f/FIDOAPI.java | 22 +++++++++++++++- .../java/com/ledger/u2f/FIDOStandalone.java | 25 +++++++++++++++++++ src/main/java/com/ledger/u2f/FIDOUtils.java | 12 +++++++++ src/main/java/com/ledger/u2f/U2FApplet.java | 19 ++++++++++---- 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/ledger/u2f/FIDOAPI.java b/src/main/java/com/ledger/u2f/FIDOAPI.java index b75ab97..09f1851 100644 --- a/src/main/java/com/ledger/u2f/FIDOAPI.java +++ b/src/main/java/com/ledger/u2f/FIDOAPI.java @@ -22,8 +22,28 @@ import javacard.security.ECPrivateKey; public interface FIDOAPI { - + /** + * Generate a new key pair and wrap it. + * @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); + /** + * Unwrap a previously wrapped key. + * @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); } diff --git a/src/main/java/com/ledger/u2f/FIDOStandalone.java b/src/main/java/com/ledger/u2f/FIDOStandalone.java index b4f001d..c14a3bf 100644 --- a/src/main/java/com/ledger/u2f/FIDOStandalone.java +++ b/src/main/java/com/ledger/u2f/FIDOStandalone.java @@ -36,6 +36,9 @@ public class FIDOStandalone implements FIDOAPI { 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); keyPair = new KeyPair( @@ -54,6 +57,16 @@ public FIDOStandalone() { cipherDecrypt.init(chipKey, Cipher.MODE_DECRYPT, IV_ZERO_AES, (short) 0, (short) IV_ZERO_AES.length); } + /** + * Combine bytes of two arrays into the target array. + * @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); @@ -63,6 +76,16 @@ private static void interleave(byte[] array1, short array1Offset, byte[] array2, } } + /** + * Separate bytes of two interleaved arrays. + * @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); @@ -72,6 +95,7 @@ private static void deinterleave(byte[] src, short srcOffset, byte[] array1, sho } } + /* @override */ public short generateKeyAndWrap(byte[] applicationParameter, short applicationParameterOffset, ECPrivateKey generatedPrivateKey, byte[] publicKey, short publicKeyOffset, byte[] keyHandle, short keyHandleOffset) { // Generate a new pair keyPair.genKeyPair(); @@ -85,6 +109,7 @@ public short generateKeyAndWrap(byte[] applicationParameter, short applicationPa 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); diff --git a/src/main/java/com/ledger/u2f/FIDOUtils.java b/src/main/java/com/ledger/u2f/FIDOUtils.java index 2687935..7d58555 100644 --- a/src/main/java/com/ledger/u2f/FIDOUtils.java +++ b/src/main/java/com/ledger/u2f/FIDOUtils.java @@ -19,8 +19,20 @@ package com.ledger.u2f; +/** + * 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; diff --git a/src/main/java/com/ledger/u2f/U2FApplet.java b/src/main/java/com/ledger/u2f/U2FApplet.java index 394ae52..084307c 100644 --- a/src/main/java/com/ledger/u2f/U2FApplet.java +++ b/src/main/java/com/ledger/u2f/U2FApplet.java @@ -26,6 +26,9 @@ import javacard.security.Signature; import javacardx.apdu.ExtendedLength; +/** + * The FIDO U2F applet. + */ public class U2FApplet extends Applet implements ExtendedLength { private static byte flags; @@ -95,10 +98,14 @@ public class U2FApplet extends Applet implements ExtendedLength { private static final byte INSTALL_FLAG_DISABLE_USER_PRESENCE = (byte) 0x01; - // Parameters - // 1 byte : flags - // 2 bytes big endian short : length of attestation certificate - // 32 bytes : private attestation key + /** + * Applet setup which sets flags, attestation certificate length and private attestation key. + * Structure of the parameters array (starting at parametersOffset): + * flags (1 byte), length of attestation certificate (2 bytes big endian short), private attestation key (32 bytes). + * @param parameters + * @param parametersOffset + * @param parametersLength always 35 + */ public U2FApplet(byte[] parameters, short parametersOffset, byte parametersLength) { if (parametersLength != 35) { ISOException.throwIt(ISO7816.SW_WRONG_DATA); @@ -392,7 +399,8 @@ private void handleGetData(APDU apdu) throws ISOException { ISOException.throwIt((short) (ISO7816.SW_BYTES_REMAINING_00 + fullLength)); } } - + + /* @override */ public void process(APDU apdu) throws ISOException { byte[] buffer = apdu.getBuffer(); if (selectingApplet()) { @@ -438,6 +446,7 @@ public void process(APDU apdu) throws ISOException { } } + /* @override */ public static void install(byte bArray[], short bOffset, byte bLength) throws ISOException { short offset = bOffset; offset += (short) (bArray[offset] + 1); // instance From 2baa26766b45193a6f687be51523d46a4595854b Mon Sep 17 00:00:00 2001 From: J08nY Date: Wed, 18 Apr 2018 19:52:49 +0200 Subject: [PATCH 5/5] Clarify docs. --- src/main/java/com/ledger/u2f/FIDOAPI.java | 10 ++++++++-- .../java/com/ledger/u2f/FIDOStandalone.java | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/ledger/u2f/FIDOAPI.java b/src/main/java/com/ledger/u2f/FIDOAPI.java index 09f1851..0637d9f 100644 --- a/src/main/java/com/ledger/u2f/FIDOAPI.java +++ b/src/main/java/com/ledger/u2f/FIDOAPI.java @@ -23,7 +23,10 @@ public interface FIDOAPI { /** - * Generate a new key pair and wrap it. + * Generate a new KeyPair over NIST P-256, for application of applicationParameter, export the + * public key into publicKey at publicKeyOffset and export the wrapped private key + * and application parameter into the keyHandle at keyHandleOffset. + * * @param applicationParameter * @param applicationParameterOffset * @param generatedPrivateKey not used @@ -36,7 +39,10 @@ public interface FIDOAPI { short generateKeyAndWrap(byte[] applicationParameter, short applicationParameterOffset, ECPrivateKey generatedPrivateKey, byte[] publicKey, short publicKeyOffset, byte[] keyHandle, short keyHandleOffset); /** - * Unwrap a previously wrapped key. + * Unwrap a keyHandle at keyHandleOffset with keyHandleLength and set + * the unwrapped private key into unwrappedPrivateKey if the unwrapping was successful (if + * applicationParameter at applicationParameterOffset was the same as the unwrapped one). + * * @param keyHandle * @param keyHandleOffset * @param keyHandleLength not used, assumed 64 diff --git a/src/main/java/com/ledger/u2f/FIDOStandalone.java b/src/main/java/com/ledger/u2f/FIDOStandalone.java index c14a3bf..476dcfb 100644 --- a/src/main/java/com/ledger/u2f/FIDOStandalone.java +++ b/src/main/java/com/ledger/u2f/FIDOStandalone.java @@ -58,7 +58,17 @@ public FIDOStandalone() { } /** - * Combine bytes of two arrays into the target array. + * Interleave two byte arrays into the target one, nibble by nibble. + * Example: + * array1 = [0x12, 0x34] + * array2 = [0xab, 0xcd] + * -> [0x1a, 0x2b, 0x3c, 0x4d] + *

+ * 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 @@ -77,7 +87,11 @@ private static void interleave(byte[] array1, short array1Offset, byte[] array2, } /** - * Separate bytes of two interleaved arrays. + * 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