Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial support for SCP #145

Merged
merged 52 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
49b96f5
Add initial support for SCP
dainnilsson Jul 2, 2024
31844e0
Ensure version is set prior to requirement check
dainnilsson Jul 2, 2024
3bedfa7
Return ApduResponse instead of byte[]
dainnilsson Jul 3, 2024
9ff4ed0
Implement SecurityDomain.reset()
dainnilsson Jul 5, 2024
aa06438
Refactor SCP
dainnilsson Jul 5, 2024
671420c
Implement Security Domain PUT KEY
dainnilsson Jul 5, 2024
5fa5447
initial integration tests of FIPS approved PIV
AdamVe Jul 5, 2024
234edbf
improve PIV device tests based on HW type
AdamVe Jul 8, 2024
53c1374
add FIPS PIV JCA provider tests
AdamVe Jul 8, 2024
40a2f5e
refactor FIPS PIV integration tests
AdamVe Jul 8, 2024
32b98a3
Merge branch 'adamve/test/fips_piv' into dain/scp
AdamVe Jul 8, 2024
e013959
Remove Destroyable interface
dainnilsson Jul 10, 2024
c7a782b
Don't include Le in MAC calculation
dainnilsson Jul 10, 2024
ac73b3d
add OpenPgp FIPS integration tests
AdamVe Jul 9, 2024
94af86c
Fix NFC timeout for tests, add test suites
AdamVe Jul 10, 2024
abaa35d
change state variable names
AdamVe Jul 10, 2024
9fb0522
Merge branch 'adamve/test/fips_openpgp' into dain/scp
AdamVe Jul 10, 2024
94fbcd6
add FIPS OATH device tests
AdamVe Jul 11, 2024
167e0b9
refactor test framework
AdamVe Jul 16, 2024
c6cbf16
refactor device test SCP parameters
AdamVe Jul 16, 2024
f92b730
refactor device tests
AdamVe Jul 22, 2024
11f7c59
add TestState to PIV, OATH, OpenPGP device tests
AdamVe Jul 23, 2024
1de4807
refactor MPE and PIN complexity tests
AdamVe Jul 23, 2024
5f9450b
TestState provides connection/session utils
AdamVe Jul 23, 2024
63e5e49
fix OpenPgp PIN complexity test
AdamVe Jul 23, 2024
9a7d87d
Merge PR #147
AdamVe Jul 23, 2024
ad5e169
refactor FIDO integration tests, support FIPS
AdamVe Jul 12, 2024
135ed4e
improve FIDO integration tests
AdamVe Jul 15, 2024
df7e8d6
batter category names
AdamVe Jul 22, 2024
935b84e
refactor FIDO device tests
AdamVe Jul 19, 2024
18856b2
inject state into tests
AdamVe Jul 22, 2024
69eaa07
self review
AdamVe Jul 22, 2024
3997845
rebase fixes
AdamVe Jul 23, 2024
85362e9
fix enterprise attestation tests
AdamVe Jul 23, 2024
5cae0f7
self review
AdamVe Jul 23, 2024
0a954a1
self review
AdamVe Jul 23, 2024
48fd760
self review
AdamVe Jul 23, 2024
274e2f6
Merge PR #146
AdamVe Jul 23, 2024
e9a6058
add first test for SCP03
AdamVe Jul 24, 2024
5bc0266
SCP03 tests
AdamVe Jul 31, 2024
07fa73e
add SCP11a import key tests
AdamVe Aug 6, 2024
117cc29
add additional tests
AdamVe Aug 6, 2024
e866021
refactor
AdamVe Aug 6, 2024
41efd9b
refactor and update style
AdamVe Aug 6, 2024
76d893d
make log message consistent
AdamVe Aug 6, 2024
c125463
Merge PR #148
AdamVe Aug 6, 2024
1fefaa2
remove unchecked calls in test code
AdamVe Aug 8, 2024
65ff8e7
self review
AdamVe Aug 8, 2024
dcb4f1e
Merge branch 'main' into dain/scp
AdamVe Aug 8, 2024
400cc85
remove 'default' from PIV test state var names
AdamVe Aug 9, 2024
fa702d5
remove unnecessary public methods, update tests
AdamVe Aug 9, 2024
f6e7c17
simplify storeAllowlist test
AdamVe Aug 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 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.core.smartcard;

import java.io.IOException;

abstract class ApduFormatProcessor implements ApduProcessor {
protected final SmartCardConnection connection;

ApduFormatProcessor(SmartCardConnection connection) {
this.connection = connection;
}

abstract byte[] formatApdu(byte cla, byte ins, byte p1, byte p2, byte[] data, int offset, int length, int le);

@Override
public ApduResponse sendApdu(Apdu apdu) throws IOException {
byte[] data = apdu.getData();
byte[] payload = formatApdu(apdu.getCla(), apdu.getIns(), apdu.getP1(), apdu.getP2(), data, 0, data.length, apdu.getLe());
return new ApduResponse(connection.sendAndReceive(payload));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022 Yubico.
* Copyright (C) 2024 Yubico.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,12 +14,13 @@
* limitations under the License.
*/

package com.yubico.yubikit.testing.piv;
package com.yubico.yubikit.core.smartcard;

import org.bouncycastle.util.encoders.Hex;
import com.yubico.yubikit.core.application.BadResponseException;

public class PivTestConstants {
static final byte[] DEFAULT_MANAGEMENT_KEY = Hex.decode("010203040506070801020304050607080102030405060708");
static final char[] DEFAULT_PIN = "123456".toCharArray();
static final char[] DEFAULT_PUK = "12345678".toCharArray();
import java.io.Closeable;
import java.io.IOException;

public interface ApduProcessor extends Closeable {
ApduResponse sendApdu(Apdu apdu) throws IOException, BadResponseException;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022 Yubico.
* Copyright (C) 2022,2024 Yubico.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,11 +17,13 @@
package com.yubico.yubikit.core.smartcard;

public final class AppId {
public static final byte[] MANAGEMENT = {(byte)0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17};
public static final byte[] MANAGEMENT = {(byte) 0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17};
public static final byte[] OTP = {(byte) 0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01};
public static final byte[] OATH = {(byte) 0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01, 0x01};
public static final byte[] PIV = {(byte) 0xa0, 0x00, 0x00, 0x03, 0x08};
public static final byte[] FIDO = {(byte) 0xa0, 0x00, 0x00, 0x06, 0x47, 0x2f, 0x00, 0x01};
public static final byte[] OPENPGP = {(byte) 0xd2, 0x76, 0x00, 0x01, 0x24, 0x01};
public static final byte[] HSMAUTH = {(byte) 0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x07, 0x01};
public static final byte[] SECURITYDOMAIN = {(byte) 0xa0, 0x00, 0x00, 0x01, 0x51, 0x00, 0x00, 0x00};
Dismissed Show dismissed Hide dismissed

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2024 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.core.smartcard;

import com.yubico.yubikit.core.application.BadResponseException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

class ChainedResponseProcessor implements ApduProcessor {
private static final byte SW1_HAS_MORE_DATA = 0x61;

private final SmartCardConnection connection;
protected final ApduFormatProcessor processor;
private final byte[] getData;

ChainedResponseProcessor(SmartCardConnection connection, boolean extendedApdus, int maxApduSize, byte insSendRemaining) {
this.connection = connection;
if (extendedApdus) {
processor = new ExtendedApduProcessor(connection, maxApduSize);
} else {
processor = new ShortApduProcessor(connection);
}
getData = processor.formatApdu((byte)0, insSendRemaining, (byte)0, (byte)0, new byte[0], 0, 0, 0);
}

@Override
public ApduResponse sendApdu(Apdu apdu) throws IOException, BadResponseException {
ApduResponse response = processor.sendApdu(apdu);
// Read full response
ByteArrayOutputStream readBuffer = new ByteArrayOutputStream();
while (response.getSw() >> 8 == SW1_HAS_MORE_DATA) {
readBuffer.write(response.getData());
response = new ApduResponse(connection.sendAndReceive(getData));
}
readBuffer.write(response.getData());
readBuffer.write(response.getSw() >> 8);
readBuffer.write(response.getSw() & 0xff);
return new ApduResponse(readBuffer.toByteArray());
}

@Override
public void close() throws IOException {
processor.close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2024 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.core.smartcard;

import java.io.IOException;
import java.nio.ByteBuffer;

class ExtendedApduProcessor extends ApduFormatProcessor {
private final int maxApduSize;

ExtendedApduProcessor(SmartCardConnection connection, int maxApduSize) {
super(connection);
this.maxApduSize = maxApduSize;
}

@Override
byte[] formatApdu(byte cla, byte ins, byte p1, byte p2, byte[] data, int offset, int length, int le) {
ByteBuffer buf = ByteBuffer.allocate(5 + (data.length > 0 ? 2 : 0) + data.length + (le > 0 ? 2 : 0))
.put(cla)
.put(ins)
.put(p1)
.put(p2)
.put((byte) 0x00);
if (data.length > 0) {
buf.putShort((short) data.length).put(data);
}
if (le > 0) {
buf.putShort((short) le);
}
if (buf.limit() > maxApduSize) {
throw new UnsupportedOperationException("APDU length exceeds YubiKey capability");
}
return buf.array();
}

@Override
public void close() throws IOException {
}
}
19 changes: 8 additions & 11 deletions ...ng/framework/DeviceInstrumentedTests.java → ...o/yubikit/core/smartcard/MaxApduSize.java
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,14 @@
* limitations under the License.
*/

package com.yubico.yubikit.testing.framework;
package com.yubico.yubikit.core.smartcard;

import com.yubico.yubikit.core.YubiKeyDevice;
final class MaxApduSize {
static final int NEO = 1390;
static final int YK4 = 2038;
static final int YK4_3 = 3062;

public class DeviceInstrumentedTests extends YKInstrumentedTests {

public interface Callback {
void invoke(YubiKeyDevice value) throws Throwable;
}

protected void withDevice(Callback callback) throws Throwable {
callback.invoke(device);
private MaxApduSize() {
throw new IllegalStateException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public final class SW {
public static final short REFERENCED_DATA_NOT_FOUND = 0x6A88;
public static final short WRONG_PARAMETERS_P1P2 = 0x6B00;
public static final short INVALID_INSTRUCTION = 0x6D00;
public static final short CLASS_NOT_SUPPORTED = 0x6E00;
public static final short COMMAND_ABORTED = 0x6F00;
public static final short OK = (short) 0x9000;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2024 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.core.smartcard;

import com.yubico.yubikit.core.application.BadResponseException;
import com.yubico.yubikit.core.smartcard.scp.ScpState;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;

public class ScpProcessor extends ChainedResponseProcessor {
private final ScpState state;

ScpProcessor(SmartCardConnection connection, ScpState state, int maxApduSize, byte insSendRemaining) {
super(connection, true, maxApduSize, insSendRemaining);
this.state = state;
}

@Override
public ApduResponse sendApdu(Apdu apdu) throws IOException, BadResponseException {
return sendApdu(apdu, true);
}

public ApduResponse sendApdu(Apdu apdu, boolean encrypt) throws IOException, BadResponseException {
byte[] data = apdu.getData();
if (encrypt) {
data = state.encrypt(data);
}
byte cla = (byte) (apdu.getCla() | 0x04);

// Calculate and add MAC to data
byte[] macedData = new byte[data.length + 8];
System.arraycopy(data, 0, macedData, 0, data.length);
byte[] apduData = processor.formatApdu(cla, apdu.getIns(), apdu.getP1(), apdu.getP2(), macedData, 0, macedData.length, 0);
byte[] mac = state.mac(Arrays.copyOf(apduData, apduData.length - 8));
System.arraycopy(mac, 0, macedData, macedData.length - 8, 8);

ApduResponse resp = super.sendApdu(new Apdu(cla, apdu.getIns(), apdu.getP1(), apdu.getP2(), macedData, apdu.getLe()));
byte[] respData = resp.getData();

// Un-MAC and decrypt, if needed
if (respData.length > 0) {
respData = state.unmac(respData, resp.getSw());
}
if (respData.length > 0) {
respData = state.decrypt(respData);
}

return new ApduResponse(ByteBuffer.allocate(respData.length + 2).put(respData).putShort(resp.getSw()).array());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2024 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.core.smartcard;

import java.io.IOException;
import java.nio.ByteBuffer;

class ShortApduProcessor extends ApduFormatProcessor {
private static final int SHORT_APDU_MAX_CHUNK = 0xff;

ShortApduProcessor(SmartCardConnection connection) {
super(connection);
}

@Override
byte[] formatApdu(byte cla, byte ins, byte p1, byte p2, byte[] data, int offset, int length, int le) {
if (length > SHORT_APDU_MAX_CHUNK) {
throw new IllegalArgumentException("Length must be no greater than " + SHORT_APDU_MAX_CHUNK);
}
if (le < 0 || le > SHORT_APDU_MAX_CHUNK) {
throw new IllegalArgumentException("Le must be between 0 and " + SHORT_APDU_MAX_CHUNK);
}

ByteBuffer buf = ByteBuffer.allocate(4 + (length > 0 ? 1 : 0) + length + (le > 0 ? 1 : 0))
.put(cla)
.put(ins)
.put(p1)
.put(p2);
if (length > 0) {
buf.put((byte) length).put(data, offset, length);
}
if (le > 0) {
buf.put((byte) le);
}
return buf.array();
}

@Override
public ApduResponse sendApdu(Apdu apdu) throws IOException {
byte[] data = apdu.getData();
int offset = 0;
while (data.length - offset > SHORT_APDU_MAX_CHUNK) {
ApduResponse response = new ApduResponse(connection.sendAndReceive(formatApdu((byte) (apdu.getCla() | 0x10), apdu.getIns(), apdu.getP1(), apdu.getP2(), data, offset, SHORT_APDU_MAX_CHUNK, apdu.getLe())));
if (response.getSw() != SW.OK) {
return response;
}
offset += SHORT_APDU_MAX_CHUNK;
}
return new ApduResponse(connection.sendAndReceive(formatApdu(apdu.getCla(), apdu.getIns(), apdu.getP1(), apdu.getP2(), data, offset, data.length - offset, apdu.getLe())));
}

@Override
public void close() throws IOException {
}
}
Loading