Skip to content

Commit

Permalink
Merge PR #164
Browse files Browse the repository at this point in the history
  • Loading branch information
AdamVe committed Jan 21, 2025
2 parents 401cadd + d5762ca commit a35cd4b
Show file tree
Hide file tree
Showing 69 changed files with 3,247 additions and 6 deletions.
1 change: 1 addition & 0 deletions DesktopDemo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
26 changes: 26 additions & 0 deletions DesktopDemo/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
= Desktop Demo Application

WARNING: This demo uses experimental APIs from the desktop module. The underlying APIs are not stable and may change in future versions without notice.

This is a demonstration application showing how to use the desktop module of our SDK.
It provides examples of basic usage and common implementation patterns.

== Overview

The DesktopDemo is a simple application that demonstrates:

* Basic setup of a desktop application
* Implementation of core functionality
* Common usage patterns

== Running the Demo

[source,bash]
----
./gradlew :DesktopDemo:run
----

== Note

This demo is built using the experimental desktop module. As the underlying APIs may change,
please ensure you're using compatible versions of all modules.
25 changes: 25 additions & 0 deletions DesktopDemo/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
id 'application'
id 'project-convention-spotless'
}

dependencies {
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'

implementation 'ch.qos.logback:logback-classic:1.5.16'

implementation project(':desktop')
implementation project(':oath')
implementation project(':fido')
implementation project(':yubiotp')
}

application {
mainClass = "com.yubico.yubikit.desktop.app.DesktopApp"
applicationName = 'DesktopApp'
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Copyright (C) 2024-2025 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.desktop.app;

import com.yubico.yubikit.core.YubiKeyDevice;
import com.yubico.yubikit.core.fido.FidoConnection;
import com.yubico.yubikit.core.otp.OtpConnection;
import com.yubico.yubikit.core.smartcard.SmartCardConnection;
import com.yubico.yubikit.desktop.CompositeDevice;
import com.yubico.yubikit.desktop.OperatingSystem;
import com.yubico.yubikit.desktop.YubiKitManager;
import com.yubico.yubikit.fido.ctap.Ctap2Session;
import com.yubico.yubikit.management.DeviceInfo;
import com.yubico.yubikit.oath.OathSession;
import com.yubico.yubikit.yubiotp.ConfigurationState;
import com.yubico.yubikit.yubiotp.Slot;
import com.yubico.yubikit.yubiotp.YubiOtpSession;
import java.util.List;
import java.util.Map;
import org.slf4j.LoggerFactory;

public class DesktopApp {

private static final org.slf4j.Logger logger = LoggerFactory.getLogger(DesktopApp.class);

public static void main(String[] argv) throws Exception {
if (OperatingSystem.isMac()) {

System.setProperty(
"sun.security.smartcardio.library",
"/System/Library/Frameworks/PCSC.framework/Versions/Current/PCSC");
}

YubiKitManager manager = new YubiKitManager();
Map<YubiKeyDevice, DeviceInfo> devices = manager.listAllDevices();
if (devices.isEmpty()) {
logger.info("No devices are connected.");
} else {
logger.info("Found {} devices", devices.size());
}

for (Map.Entry<YubiKeyDevice, DeviceInfo> entry : devices.entrySet()) {
YubiKeyDevice device = entry.getKey();
DeviceInfo info = entry.getValue();

String deviceType = device.getClass().getSimpleName();

if (device instanceof CompositeDevice) {
CompositeDevice compositeDevice = (CompositeDevice) device;
deviceType += " (" + compositeDevice.getPidGroup().getPid() + ")";
}

logger.info(
"- {}:{}/{}/{}",
deviceType,
info.getFormFactor(),
info.getVersion(),
info.getSerialNumber());

if (device.supportsConnection(SmartCardConnection.class)) {
device.requestConnection(
SmartCardConnection.class,
value -> {
try {
SmartCardConnection connection = value.getValue();
OathSession oath = new OathSession(connection);
logger.info(
" Device supports SmartCardConnection. OATH applet version is: {}",
oath.getVersion());
} catch (Exception e) {
logger.error(" SmartCard connection failed with error: {}", e.getMessage());
}
});

sleep();
} else {
logger.info(" Device does not support SmartCardConnection");
}

if (device.supportsConnection(FidoConnection.class)) {
device.requestConnection(
FidoConnection.class,
value -> {
try {
FidoConnection fidoConnection = value.getValue();
Ctap2Session ctap2Session = new Ctap2Session(fidoConnection);
final List<String> versions = ctap2Session.getCachedInfo().getVersions();
logger.info(
" Device supports FidoConnection. Supported versions: {}",
String.join(", ", versions));
} catch (Exception e) {
logger.error(" FIDO connection failed with error: {}", e.getMessage());
}
});
sleep();
} else {
logger.info(" Device does not support FidoConnection");
}

if (device.supportsConnection(OtpConnection.class)) {
device.requestConnection(
OtpConnection.class,
value -> {
try {
OtpConnection otpConnection = value.getValue();
YubiOtpSession yubiOtpSession = new YubiOtpSession(otpConnection);
ConfigurationState state = yubiOtpSession.getConfigurationState();
String configuredSlots = " ";
if (state.isConfigured(Slot.ONE)) {
configuredSlots += "SLOT1 ";
}
if (state.isConfigured(Slot.TWO)) {
configuredSlots += "SLOT2";
}
logger.info(
" Device supports OtpConnection. Configured slots:{}", configuredSlots);
} catch (Exception e) {
logger.error(" OTP connection failed with error: {}", e.getMessage());
}
});
sleep();
} else {
logger.info(" Device does not support OtpConnection");
}
}

for (YubiKeyDevice yubiKeyDevice : devices.keySet()) {
if (yubiKeyDevice instanceof CompositeDevice) {
CompositeDevice usbYubiKeyDevice = (CompositeDevice) yubiKeyDevice;
usbYubiKeyDevice.close();
}
}
logger.info("Application exited");
}

private static void sleep() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@PackageNonnullByDefault
package com.yubico.yubikit.desktop.app;

import com.yubico.yubikit.core.PackageNonnullByDefault;
11 changes: 11 additions & 0 deletions DesktopDemo/src/main/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%-20thread][%-5level] %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
11 changes: 11 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ If you run into any issues during the development process, please fill out a
developer https://support.yubico.com/support/tickets/new[support ticket] and
our team will be happy to assist you.

=== Support for desktop apps

WARNING: This is an experimental version and is not intended for production use.

Since version 2.8.0, the SDK contains the desktop module, which provides concrete implementations
of the core interfaces (USB and NFC connectivity) for building desktop Java applications.

There are two related modules:

* testing-desktop: SDK device test suite for desktop platforms
* DesktopDemo: A sample application demonstrating basic usage

=== FAQ

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,3 @@ java {
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_1_8
}

compileJava {
options.compilerArgs.addAll(['--release', '8'])
}
1 change: 1 addition & 0 deletions desktop/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
15 changes: 15 additions & 0 deletions desktop/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
= Desktop Module

WARNING: This module is experimental. The API is not stable and may change in future versions without notice.

This module provides concrete implementations of the core interfaces for desktop Java applications.
It enables building desktop applications using our SDK.

== Overview

The desktop module is part of our SDK's desktop support, introduced in version 2.8.0.
It implements the core interfaces necessary for desktop application development.

== Usage

See the DesktopDemo module for examples of how to use this implementation.
16 changes: 16 additions & 0 deletions desktop/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
id 'yubikit-java-library'
}

dependencies {
api project(':support')

implementation 'org.hid4java:hid4java:0.8.0'
}

ext.pomName = "Yubico YubiKit Desktop"
description = "This module is the core library desktop implementation and provides " +
"functionality to detect a YubiKey plugged in or tapped over NFC and to open " +
"an ISO/IEC 7816 connection, using the javax.smartcardio API."

apply from: rootProject.file('publish.gradle')
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2022-2025 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.desktop;

import com.yubico.yubikit.core.Transport;
import com.yubico.yubikit.core.YubiKeyConnection;
import com.yubico.yubikit.core.YubiKeyDevice;
import com.yubico.yubikit.core.util.Callback;
import com.yubico.yubikit.core.util.Result;
import java.io.Closeable;
import java.io.IOException;

public class CompositeDevice implements YubiKeyDevice, Closeable {
private final UsbPidGroup pidGroup;
private final String key;

CompositeDevice(UsbPidGroup pidGroup, String key) {
this.pidGroup = pidGroup;
this.key = key;
}

@Override
public Transport getTransport() {
return Transport.USB;
}

@Override
public boolean supportsConnection(Class<? extends YubiKeyConnection> connectionType) {
return pidGroup.supportsConnection(connectionType);
}

@Override
public <T extends YubiKeyConnection> void requestConnection(
Class<T> connectionType, Callback<Result<T, IOException>> callback) {
pidGroup.requestConnection(key, connectionType, callback);
}

@Override
public <T extends YubiKeyConnection> T openConnection(Class<T> connectionType)
throws IOException {
return pidGroup.openConnection(key, connectionType);
}

public UsbPidGroup getPidGroup() {
return pidGroup;
}

@Override
public void close() throws IOException {
pidGroup.close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2022-2025 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.desktop;

public class OperatingSystem {
public static final String Name = System.getProperty("os.name");

public static boolean isWindows() {
return Name.toLowerCase().contains("win");
}

public static boolean isMac() {
return Name.toLowerCase().contains("mac");
}

public static boolean isLinux() {
return Name.toLowerCase().contains("linux");
}
}
Loading

0 comments on commit a35cd4b

Please sign in to comment.