diff --git a/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/ConfigState.java b/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/ConfigState.java index 604ccbba..f34be706 100755 --- a/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/ConfigState.java +++ b/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/ConfigState.java @@ -32,9 +32,9 @@ public class ConfigState { private final Version version; private final byte flags; - ConfigState(Version version, short touchlevel) { + ConfigState(Version version, short touchLevel) { this.version = version; - this.flags = (byte) (CONFIG_STATUS_MASK & touchlevel); + this.flags = (byte) (CONFIG_STATUS_MASK & touchLevel); } /** diff --git a/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/ConfigUtils.java b/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/ConfigUtils.java index 866bca12..863c6c4d 100755 --- a/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/ConfigUtils.java +++ b/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/ConfigUtils.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2020 Yubico. + * + * 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.yubico.yubikit.yubiotp; import com.yubico.yubikit.core.otp.ChecksumUtils; @@ -24,11 +40,6 @@ class ConfigUtils { // NDEF structure static final int NDEF_DATA_SIZE = 54; //Size of the NDEF payload data - // Flags valid for update - private static final byte TKTFLAG_UPDATE_MASK = SlotConfiguration.TKTFLAG_TAB_FIRST | SlotConfiguration.TKTFLAG_APPEND_TAB1 | SlotConfiguration.TKTFLAG_APPEND_TAB2 | SlotConfiguration.TKTFLAG_APPEND_DELAY1 | SlotConfiguration.TKTFLAG_APPEND_DELAY2 | SlotConfiguration.TKTFLAG_APPEND_CR; - private static final byte CFGFLAG_UPDATE_MASK = SlotConfiguration.CFGFLAG_PACING_10MS | SlotConfiguration.CFGFLAG_PACING_20MS; - private static final byte EXTFLAG_UPDATE_MASK = SlotConfiguration.EXTFLAG_SERIAL_BTN_VISIBLE | SlotConfiguration.EXTFLAG_SERIAL_USB_VISIBLE | SlotConfiguration.EXTFLAG_SERIAL_API_VISIBLE | SlotConfiguration.EXTFLAG_USE_NUMERIC_KEYPAD | SlotConfiguration.EXTFLAG_FAST_TRIG | SlotConfiguration.EXTFLAG_ALLOW_UPDATE | SlotConfiguration.EXTFLAG_DORMANT | SlotConfiguration.EXTFLAG_LED_INV; - // From nfcforum-ts-rtd-uri-1.0.pdf private static final String[] NDEF_URL_PREFIXES = { "http://www.", @@ -98,15 +109,12 @@ static byte[] buildConfig(byte[] fixed, byte[] uid, byte[] key, byte extFlags, b .array(); } - @SuppressFBWarnings(value = "BIT_AND_ZZ", justification = "Check EXTflag mask for completeness") static byte[] buildUpdateConfig(byte extFlags, byte tktFlags, byte cfgFlags, @Nullable byte[] accCode) { - if ((extFlags & ~EXTFLAG_UPDATE_MASK) != 0) { - throw new IllegalArgumentException("Unsupported EXT flags for update"); - } - if ((tktFlags & ~TKTFLAG_UPDATE_MASK) != 0) { + // NB: All EXT flags are valid for update. + if((tktFlags & ~(SlotConfiguration.TKTFLAG_TAB_FIRST | SlotConfiguration.TKTFLAG_APPEND_TAB1 | SlotConfiguration.TKTFLAG_APPEND_TAB2 | SlotConfiguration.TKTFLAG_APPEND_DELAY1 | SlotConfiguration.TKTFLAG_APPEND_DELAY2 | SlotConfiguration.TKTFLAG_APPEND_CR)) != 0) { throw new IllegalArgumentException("Unsupported TKT flags for update"); } - if ((cfgFlags & ~CFGFLAG_UPDATE_MASK) != 0) { + if ((cfgFlags & ~(SlotConfiguration.CFGFLAG_PACING_10MS | SlotConfiguration.CFGFLAG_PACING_20MS)) != 0) { throw new IllegalArgumentException("Unsupported CFG flags for update"); } return buildConfig(new byte[0], new byte[UID_SIZE], new byte[KEY_SIZE], extFlags, tktFlags, cfgFlags, accCode); diff --git a/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/SlotConfiguration.java b/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/SlotConfiguration.java index c3c51404..b34b55e2 100755 --- a/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/SlotConfiguration.java +++ b/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/SlotConfiguration.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2020 Yubico. + * + * 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.yubico.yubikit.yubiotp; import com.yubico.yubikit.core.Version; @@ -5,6 +20,8 @@ import javax.annotation.Nullable; public interface SlotConfiguration { + // Constants in this file come from https://github.com/Yubico/yubikey-personalization/blob/master/ykcore/ykdef.h + // Yubikey 1 and above byte TKTFLAG_TAB_FIRST = 0x01; // Send TAB before first part byte TKTFLAG_APPEND_TAB1 = 0x02; // Send TAB after first part @@ -42,7 +59,6 @@ public interface SlotConfiguration { byte CFGFLAG_OATH_FIXED_MODHEX = 0x50; // Fixed part sent as modhex // Yubikey 2.2 and above - byte TKTFLAG_CHAL_RESP = 0x40; // Challenge-response enabled (both must be set) byte CFGFLAG_CHAL_YUBICO = 0x20; // Challenge-response enabled - Yubico OTP mode byte CFGFLAG_CHAL_HMAC = 0x22; // Challenge-response enabled - HMAC-SHA1 @@ -54,21 +70,14 @@ public interface SlotConfiguration { byte EXTFLAG_SERIAL_API_VISIBLE = 0x04; // Serial number visible via API call // V2.3 flags only - byte EXTFLAG_USE_NUMERIC_KEYPAD = 0x08; // Use numeric keypad for digits byte EXTFLAG_FAST_TRIG = 0x10; // Use fast trig if only cfg1 set byte EXTFLAG_ALLOW_UPDATE = 0x20; // Allow update of existing configuration (selected flags + access code) byte EXTFLAG_DORMANT = 0x40; // Dormant configuration (can be woken up and flag removed = requires update flag) // V2.4/3.1 flags only - byte EXTFLAG_LED_INV = (byte) 0x80; // LED idle state is off rather than on - // Flags valid for update - //byte TKTFLAG_UPDATE_MASK = TKTFLAG_TAB_FIRST | TKTFLAG_APPEND_TAB1 | TKTFLAG_APPEND_TAB2 | TKTFLAG_APPEND_DELAY1 | TKTFLAG_APPEND_DELAY2 | TKTFLAG_APPEND_CR; - //byte CFGFLAG_UPDATE_MASK = CFGFLAG_PACING_10MS | CFGFLAG_PACING_20MS; - //byte EXTFLAG_UPDATE_MASK = EXTFLAG_SERIAL_BTN_VISIBLE | EXTFLAG_SERIAL_USB_VISIBLE | EXTFLAG_SERIAL_API_VISIBLE | EXTFLAG_USE_NUMERIC_KEYPAD | EXTFLAG_FAST_TRIG | EXTFLAG_ALLOW_UPDATE | EXTFLAG_DORMANT | EXTFLAG_LED_INV; - Version getMinimumVersion(); byte[] getConfig(@Nullable byte[] accCode); diff --git a/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/YubiOtpSession.java b/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/YubiOtpSession.java index 043b9d94..2e37bcf9 100755 --- a/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/YubiOtpSession.java +++ b/yubiotp/src/main/java/com/yubico/yubikit/yubiotp/YubiOtpSession.java @@ -68,11 +68,8 @@ public class YubiOtpSession implements Closeable { private static final byte CMD_CHALLENGE_HMAC_1 = 0x30; private static final byte CMD_CHALLENGE_HMAC_2 = 0x38; - private final Version version; private final Backend backend; - private ConfigState configState; - /** * Connect to a YubiKey session, and create a new instance of {@link YubiOtpSession}. * @@ -118,13 +115,25 @@ public YubiOtpSession(SmartCardConnection connection) throws IOException, Applic // We didn't get a version above, get it from the status struct. version = Version.parse(statusBytes); } - this.version = version; - configState = parseConfigState(version, statusBytes); + protocol.enableTouchWorkaround(version); - backend = new Backend(protocol) { + + backend = new Backend(protocol, version, parseConfigState(version, statusBytes)) { + // 5.0.0-5.2.5 have an issue with status over NFC + private final boolean dummyStatus = connection.getInterface() == Interface.NFC && version.isAtLeast(5, 0, 0) && version.isLessThan(5, 2, 5); + + { + if (dummyStatus) { // We can't read the status, so use a dummy with both slots marked as configured. + configState = new ConfigState(version, (short) 3); + } + } + @Override - byte[] writeConfig(byte slot, byte[] data) throws IOException, CommandException { - return delegate.sendAndReceive(new Apdu(0, INS_CONFIG, slot, 0, data)); + void writeConfig(byte slot, byte[] data) throws IOException, CommandException { + byte[] status = delegate.sendAndReceive(new Apdu(0, INS_CONFIG, slot, 0, data)); + if (!dummyStatus) { + configState = parseConfigState(this.version, status); + } } @Override @@ -147,12 +156,11 @@ byte[] sendAndReceive(byte slot, byte[] data, int expectedResponseLength, @Nulla public YubiOtpSession(OtpConnection connection) throws IOException { OtpProtocol protocol = new OtpProtocol(connection); byte[] statusBytes = protocol.readStatus(); - version = Version.parse(statusBytes); - configState = parseConfigState(version, statusBytes); - backend = new Backend(protocol) { + Version version = Version.parse(statusBytes); + backend = new Backend(protocol, version, parseConfigState(version, statusBytes)) { @Override - byte[] writeConfig(byte slot, byte[] data) throws IOException, CommandException { - return delegate.sendAndReceive(slot, data, null); + void writeConfig(byte slot, byte[] data) throws IOException, CommandException { + configState = parseConfigState(version, delegate.sendAndReceive(slot, data, null)); } @Override @@ -177,7 +185,7 @@ public void close() throws IOException { * @return the current configuration state of the two slots. */ public ConfigState getConfigState() { - return configState; + return backend.configState; } /** @@ -186,7 +194,7 @@ public ConfigState getConfigState() { * @return Yubikey firmware version */ public Version getVersion() { - return version; + return backend.version; } /** @@ -208,7 +216,7 @@ public int getSerialNumber() throws IOException, CommandException { * @throws CommandException in case of an error response from the YubiKey */ public void swapSlots() throws IOException, CommandException { - if (version.isLessThan(2, 3, 0)) { + if (backend.version.isLessThan(2, 3, 0)) { throw new NotSupportedOperation("This operation is supported for version 2.3+"); } @@ -266,7 +274,7 @@ public void putConfiguration(Slot slot, byte[] fixed, byte[] uid, byte[] key, by * @throws CommandException in case of an error response from the YubiKey */ public void putConfiguration(Slot slot, SlotConfiguration configuration, @Nullable byte[] accCode, @Nullable byte[] curAccCode) throws IOException, CommandException { - if (version.compareTo(configuration.getMinimumVersion()) < 0) { + if (backend.version.compareTo(configuration.getMinimumVersion()) < 0) { throw new NotSupportedOperation("This configuration type requires YubiKey " + configuration.getMinimumVersion() + "or later"); } writeConfig( @@ -329,7 +337,7 @@ public void setNdefConfiguration(Slot slot, @Nullable String uri, @Nullable byte */ public byte[] calculateHmacSha1(Slot slot, byte[] challenge, @Nullable CommandState state) throws IOException, CommandException { // works on version above 2.2 - if (version.isLessThan(2, 2, 0)) { + if (backend.version.isLessThan(2, 2, 0)) { throw new NotSupportedOperation("This operation is supported for version 2.2+"); } @@ -348,13 +356,13 @@ public byte[] calculateHmacSha1(Slot slot, byte[] challenge, @Nullable CommandSt } private void writeConfig(byte commandSlot, byte[] config, @Nullable byte[] curAccCode) throws IOException, CommandException { - configState = parseConfigState(version, backend.writeConfig( + backend.writeConfig( commandSlot, ByteBuffer.allocate(config.length + ConfigUtils.ACC_CODE_SIZE) .put(config) .put(curAccCode == null ? new byte[ConfigUtils.ACC_CODE_SIZE] : curAccCode) .array() - )); + ); } private static ConfigState parseConfigState(Version version, byte[] status) { @@ -363,12 +371,16 @@ private static ConfigState parseConfigState(Version version, byte[] status) { private static abstract class Backend implements Closeable { protected final T delegate; + protected final Version version; + protected ConfigState configState; - private Backend(T delegate) { + private Backend(T delegate, Version version, ConfigState configState) { + this.version = version; this.delegate = delegate; + this.configState = configState; } - abstract byte[] writeConfig(byte slot, byte[] data) throws IOException, CommandException; + abstract void writeConfig(byte slot, byte[] data) throws IOException, CommandException; abstract byte[] sendAndReceive(byte slot, byte[] data, int expectedResponseLength, @Nullable CommandState state) throws IOException, CommandException;