Skip to content

Commit

Permalink
Better handling for NFC OTP issue on YK5.
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Sep 30, 2020
1 parent a28a1e5 commit 177562e
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 43 deletions.
Expand Up @@ -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);
}

/**
Expand Down
30 changes: 19 additions & 11 deletions 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;
Expand All @@ -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.",
Expand Down Expand Up @@ -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);
Expand Down
@@ -1,10 +1,27 @@
/*
* 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;

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
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down
Expand Up @@ -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}.
*
Expand Down Expand Up @@ -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<SmartCardProtocol>(protocol) {

backend = new Backend<SmartCardProtocol>(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
Expand All @@ -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<OtpProtocol>(protocol) {
Version version = Version.parse(statusBytes);
backend = new Backend<OtpProtocol>(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
Expand All @@ -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;
}

/**
Expand All @@ -186,7 +194,7 @@ public ConfigState getConfigState() {
* @return Yubikey firmware version
*/
public Version getVersion() {
return version;
return backend.version;
}

/**
Expand All @@ -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+");
}

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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+");
}

Expand All @@ -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) {
Expand All @@ -363,12 +371,16 @@ private static ConfigState parseConfigState(Version version, byte[] status) {

private static abstract class Backend<T extends Closeable> 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;

Expand Down

0 comments on commit 177562e

Please sign in to comment.