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 support for NitroKey v3 #2842

Closed
wants to merge 14 commits into from

Conversation

sjlongland
Copy link
Contributor

@sjlongland sjlongland commented Jul 15, 2023

Description

This branch adds support for the NitroKey v3 family USB VID/PID to OpenKeychain.

Closes #2840

Motivation and Context

Plug in a NitroKey 3A Mini, at present it says it's not supported. The NFC version is not in stock, and it'll never be possible to do NFC with a NFC-free 3A mini.

#2840

How Has This Been Tested?

It has now, although I'd appreciate others giving this a shot.

Screenshots (if appropriate):

#2842 (comment)

Types of changes

  • ✅ New feature (non-breaking change which adds functionality)

@CLAassistant
Copy link

CLAassistant commented Jul 15, 2023

CLA assistant check
All committers have signed the CLA.

@vikanezrimaya
Copy link

I managed to compile and install it on my phone. The previous error message is gone, but a new one appears: "Error: failed to get pw status bytes". If I try again, it fails with an error to read USB-CCID header, and then just random-looking USB errors. So I guess the token is now recognized as supported, but the actual support might be missing or incomplete.

I suppose this is why it's a draft PR 😝

@sjlongland
Copy link
Contributor Author

I managed to compile and install it on my phone. The previous error message is gone, but a new one appears: "Error: failed to get pw status bytes". If I try again, it fails with an error to read USB-CCID header, and then just random-looking USB errors. So I guess the token is now recognized as supported, but the actual support might be missing or incomplete.

I suppose this is why it's a draft PR stuck_out_tongue_closed_eyes

Very much so, many thanks for giving it a shot. I'm still getting toolchains set up here.

@vikanezrimaya
Copy link

I'm subscribed to this PR and I'm very interested in trying it out. From what I remember, GitHub actually pings by email on new commits to PRs one is subscribed to, but do not hesitate to ping me whenever you specifically want me to test your changes on real hardware!

@sjlongland
Copy link
Contributor Author

Yep… well, I had tried downloading Android Studio 2023.1.1.10 (in Gentoo; emerge dev-util/android-studio, that's what it gives me), but its bundled version of gradle does not like this project.

Downloaded 2020.3.1.24, and things seem much happier after telling it to use the bundled JDK. So, I might be able to see that error for myself soon. :-)

The error message you mention seems to come from here: https://github.com/open-keychain/open-keychain/blob/master/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java#L441 -- now the question is, how do we wind up here?

@sjlongland
Copy link
Contributor Author

D/UsbConnectionDispatcher: Device: UsbDevice[mName=/dev/bus/usb/001/009,mVendorId=8352,mProductId=17074,mClass=239,mSubclass=2,mProtocol=1,mManufacturerName=Nitrokey,mProductName=Nitrokey 3,mVersion=1.05,mSerialNumberReader=android.hardware.usb.IUsbSerialReader$Stub$Proxy@9292414, mHasAudioPlayback=false, mHasAudioCapture=false, mHasMidi=false, mHasVideoCapture=false, mHasVideoPlayback=false, mConfigurations=[
    UsbConfiguration[mId=1,mName=null,mAttributes=128,mMaxPower=50,mInterfaces=[
    UsbInterface[mId=0,mAlternateSetting=0,mName=CCID/ICCD Interface,mClass=11,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=129,mAttributes=2,mMaxPacketSize=64,mInterval=0]
    UsbEndpoint[mAddress=1,mAttributes=2,mMaxPacketSize=64,mInterval=0]]
    UsbInterface[mId=1,mAlternateSetting=0,mName=null,mClass=3,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=2,mAttributes=3,mMaxPacketSize=64,mInterval=5]
    UsbEndpoint[mAddress=130,mAttributes=3,mMaxPacketSize=64,mInterval=5]]
    UsbInterface[mId=2,mAlternateSetting=0,mName=null,mClass=2,mSubclass=2,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=8,mInterval=255]]
    UsbInterface[mId=3,mAlternateSetting=0,mName=null,mClass=10,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=132,mAttributes=2,mMaxPacketSize=64,mInterval=0]
    UsbEndpoint[mAddress=3,mAttributes=2,mMaxPacketSize=64,mInterval=0]]]]
    Got permission!
D/UsbTransport: CCID Description: CcidDescription{maxSlotIndex=0, voltageSupport=1, protocols=2, features=264256}
D/ScrollView:  onsize change changed 
V/CcidTransceiver: CCID: attempting to power on with voltage _5V
D/UsbDeviceConnectionJNI: close
D/UsbTransport: Usb transport disconnected
D/BaseSecurityTokenActivity: Exception in handleSecurityTokenError
    org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException: USB-CCID error - failed to receive CCID header
        at org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver.receiveDataBlockImmediate(CcidTransceiver.java:211)
        at org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver.receiveDataBlock(CcidTransceiver.java:198)
        at org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver.iccPowerOnVoltage(CcidTransceiver.java:131)
        at org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver.iccPowerOn(CcidTransceiver.java:92)
        at org.sufficientlysecure.keychain.securitytoken.usb.tpdu.T1ShortApduProtocol.connect(T1ShortApduProtocol.java:33)
        at org.sufficientlysecure.keychain.securitytoken.usb.UsbTransport.connect(UsbTransport.java:170)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.connectToDevice(SecurityTokenConnection.java:115)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.connectIfNecessary(SecurityTokenConnection.java:105)
        at org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity$1.doInBackground(BaseSecurityTokenActivity.java:147)
        at org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity$1.doInBackground(BaseSecurityTokenActivity.java:137)
        at android.os.AsyncTask$3.call(AsyncTask.java:394)
        at java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:305)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
        at java.lang.Thread.run(Thread.java:1012)
I/.keychain.debug: Compiler allocated 6133KB to compile void android.view.ViewRootImpl.performTraversals()
W/.keychain.debug: Cleared Reference was only reachable from finalizer (only reported once)
I/.keychain.debug: Background concurrent copying GC freed 66470(4143KB) AllocSpace objects, 4(144KB) LOS objects, 53% free, 5423KB/11MB, paused 134us,34us total 112.604ms

Some sort of low-level USB glitch? It definitely found the correct device, but it seems it barfed when it attempted to communicate.

@vikanezrimaya
Copy link

Regarding toolchain issues: I use NixOS on my work laptop, so I just wrote the following Nix expression to pull in just enough Android SDK components to make the app build:

let
  pkgs = (builtins.getFlake "nixpkgs").legacyPackages.${builtins.currentSystem};
  sdkConfig = {
    cmdLineToolsVersion = "8.0";
    toolsVersion = "26.1.1";
    platformToolsVersion = "34.0.1";
    buildToolsVersions = [ "30.0.2" ];
    includeEmulator = false;
    emulatorVersion = "33.1.6";
    platformVersions = [ "28" "33" ];
    includeSources = false;
    includeSystemImages = false;
    systemImageTypes = [ "google_apis_playstore" ];
    abiVersions = [ "armeabi-v7a" "arm64-v8a" ];
    cmakeVersions = [ "3.10.2" ];
    includeNDK = true;
    ndkVersions = ["22.0.7026061"];
    useGoogleAPIs = false;
    useGoogleTVAddOns = false;
    includeExtras = [
      "extras;google;gcm"
    ];
  };
  androidComposition = pkgs.androidenv.composeAndroidPackages sdkConfig;
  sdk = androidComposition.androidsdk;
in pkgs.mkShell rec {
  buildInputs = [
    pkgs.openjdk11
  ];
  ANDROID_SDK_ROOT = "${sdk}/libexec/android-sdk";
  GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_SDK_ROOT}/build-tools/${builtins.elemAt sdkConfig.buildToolsVersions 0}/aapt2";

  shellHook = ''
    export PATH="$(echo "$ANDROID_SDK_ROOT/cmake/${builtins.elemAt sdkConfig.cmakeVersions 0}".*/bin):$PATH"
  '';
}

@vikanezrimaya
Copy link

Also, sadly, I am not proficient in low-level USB debugging, so I hope you'll be able to trace where the error comes from...

@sjlongland
Copy link
Contributor Author

Ahh yeah, I hear lots of things about NixOS. Interesting packaging management concept.

Anyway… part of my problem seems to be the USB-C dock I was using was confusing matters. If I use a plain USB-C→USB-A adaptor cable, we get a little closer:

2023-07-15 17:26:17.698 17530-17530/org.sufficientlysecure.keychain.debug D/UsbConnectionDispatcher: Device: UsbDevice[mName=/dev/bus/usb/001/002,mVendorId=8352,mProductId=17074,mClass=239,mSubclass=2,mProtocol=1,mManufacturerName=Nitrokey,mProductName=Nitrokey 3,mVersion=1.05,mSerialNumberReader=android.hardware.usb.IUsbSerialReader$Stub$Proxy@e25c0f7, mHasAudioPlayback=false, mHasAudioCapture=false, mHasMidi=false, mHasVideoCapture=false, mHasVideoPlayback=false, mConfigurations=[
    UsbConfiguration[mId=1,mName=null,mAttributes=128,mMaxPower=50,mInterfaces=[
    UsbInterface[mId=0,mAlternateSetting=0,mName=CCID/ICCD Interface,mClass=11,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=129,mAttributes=2,mMaxPacketSize=64,mInterval=0]
    UsbEndpoint[mAddress=1,mAttributes=2,mMaxPacketSize=64,mInterval=0]]
    UsbInterface[mId=1,mAlternateSetting=0,mName=null,mClass=3,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=2,mAttributes=3,mMaxPacketSize=64,mInterval=5]
    UsbEndpoint[mAddress=130,mAttributes=3,mMaxPacketSize=64,mInterval=5]]
    UsbInterface[mId=2,mAlternateSetting=0,mName=null,mClass=2,mSubclass=2,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=8,mInterval=255]]
    UsbInterface[mId=3,mAlternateSetting=0,mName=null,mClass=10,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=132,mAttributes=2,mMaxPacketSize=64,mInterval=0]
    UsbEndpoint[mAddress=3,mAttributes=2,mMaxPacketSize=64,mInterval=0]]]]
2023-07-15 17:26:17.699 17530-17530/org.sufficientlysecure.keychain.debug D/UsbConnectionDispatcher: Got permission!
2023-07-15 17:26:17.703 17530-17558/org.sufficientlysecure.keychain.debug D/UsbTransport: USB Bulk In max packet size: 64
2023-07-15 17:26:17.703 17530-17558/org.sufficientlysecure.keychain.debug D/UsbTransport: USB Bulk Out max packet size: 64
2023-07-15 17:26:17.708 17530-17558/org.sufficientlysecure.keychain.debug D/UsbTransport: CCID Description: CcidDescription{maxSlotIndex=0, voltageSupport=1, protocols=2, features=264256}
2023-07-15 17:26:17.724 17530-17530/org.sufficientlysecure.keychain.debug D/ScrollView:  onsize change changed 
2023-07-15 17:26:17.736 17530-17539/org.sufficientlysecure.keychain.debug I/.keychain.debug: Compiler allocated 4167KB to compile void android.widget.TextView.<init>(android.content.Context, android.util.AttributeSet, int, int)
2023-07-15 17:26:17.812 17530-17558/org.sufficientlysecure.keychain.debug V/CcidTransceiver: CCID: attempting to power on with voltage _5V
2023-07-15 17:26:17.814 17530-17558/org.sufficientlysecure.keychain.debug V/CcidTransceiver: CCID: powered on with voltage _5V
2023-07-15 17:26:17.815 17530-17558/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Usb transport connected, took 105ms, ATR=3b8f01805d4e6974726f6b657900000000006a
2023-07-15 17:26:17.816 17530-17558/org.sufficientlysecure.keychain.debug D/UsbTransport: USB >> 00a4040006d27600012401
2023-07-15 17:26:18.102 17530-17558/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 285ms
2023-07-15 17:26:18.103 17530-17558/org.sufficientlysecure.keychain.debug D/UsbTransport: USB << 9000
2023-07-15 17:26:18.104 17530-17558/org.sufficientlysecure.keychain.debug D/UsbTransport: USB >> 00ca006e00
2023-07-15 17:26:18.172 17530-17558/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 68ms
2023-07-15 17:26:18.172 17530-17558/org.sufficientlysecure.keychain.debug D/UsbTransport: USB << 4f10d276000124010304000f9b717b4800005f520a0031f573c001600590007f660802021db902021db97f74038101207381d8c00a3f
2023-07-15 17:26:18.173 17530-17558/org.sufficientlysecure.keychain.debug D/UsbDeviceConnectionJNI: close
2023-07-15 17:26:18.173 17530-17558/org.sufficientlysecure.keychain.debug D/UsbTransport: Usb transport disconnected
2023-07-15 17:26:18.199 17530-17530/org.sufficientlysecure.keychain.debug D/BaseSecurityTokenActivity: Exception in handleSecurityTokenError
    org.sufficientlysecure.keychain.securitytoken.CardException: Failed to get pw status bytes
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.readData(SecurityTokenConnection.java:441)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.refreshConnectionCapabilities(SecurityTokenConnection.java:168)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.connectToDevice(SecurityTokenConnection.java:129)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.connectIfNecessary(SecurityTokenConnection.java:105)
        at org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity$1.doInBackground(BaseSecurityTokenActivity.java:147)
        at org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity$1.doInBackground(BaseSecurityTokenActivity.java:137)
        at android.os.AsyncTask$3.call(AsyncTask.java:394)
        at java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:305)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
        at java.lang.Thread.run(Thread.java:1012)

So org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.refreshConnectionCapabilities(SecurityTokenConnection.java:168) is the smoking gun. The CCID request is to fetch the raw OpenPGP capabilities (0x006e), but that then trips up in the USB transfer.

@sjlongland
Copy link
Contributor Author

In the interests of science, this is what happens if I plug in a YubiKey 5 Nano into the same cable:

2023-07-15 17:50:14.344 19980-19980/org.sufficientlysecure.keychain.debug D/UsbConnectionDispatcher: Device: UsbDevice[mName=/dev/bus/usb/001/003,mVendorId=4176,mProductId=1030,mClass=0,mSubclass=0,mProtocol=0,mManufacturerName=Yubico,mProductName=YubiKey FIDO+CCID,mVersion=5.43,mSerialNumberReader=android.hardware.usb.IUsbSerialReader$Stub$Proxy@e25c0f7, mHasAudioPlayback=false, mHasAudioCapture=false, mHasMidi=false, mHasVideoCapture=false, mHasVideoPlayback=false, mConfigurations=[
    UsbConfiguration[mId=1,mName=null,mAttributes=128,mMaxPower=15,mInterfaces=[
    UsbInterface[mId=0,mAlternateSetting=0,mName=null,mClass=3,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=4,mAttributes=3,mMaxPacketSize=64,mInterval=2]
    UsbEndpoint[mAddress=132,mAttributes=3,mMaxPacketSize=64,mInterval=2]]
    UsbInterface[mId=1,mAlternateSetting=0,mName=null,mClass=11,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=2,mAttributes=2,mMaxPacketSize=64,mInterval=0]
    UsbEndpoint[mAddress=130,mAttributes=2,mMaxPacketSize=64,mInterval=0]
    UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=8,mInterval=32]]]]
2023-07-15 17:50:14.346 19980-19980/org.sufficientlysecure.keychain.debug D/UsbConnectionDispatcher: Got permission!
2023-07-15 17:50:14.350 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB Bulk In max packet size: 64
2023-07-15 17:50:14.351 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB Bulk Out max packet size: 64
2023-07-15 17:50:14.361 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: CCID Description: CcidDescription{maxSlotIndex=0, voltageSupport=7, protocols=2, features=262398}
2023-07-15 17:50:14.374 19980-19980/org.sufficientlysecure.keychain.debug D/ScrollView:  onsize change changed 
2023-07-15 17:50:14.386 19980-19989/org.sufficientlysecure.keychain.debug I/.keychain.debug: Compiler allocated 4167KB to compile void android.widget.TextView.<init>(android.content.Context, android.util.AttributeSet, int, int)
2023-07-15 17:50:14.472 19980-20006/org.sufficientlysecure.keychain.debug V/CcidTransceiver: CCID: attempting to power on with voltage AUTO
2023-07-15 17:50:14.474 19980-20006/org.sufficientlysecure.keychain.debug V/CcidTransceiver: CCID: powered on with voltage AUTO
2023-07-15 17:50:14.474 19980-20006/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Usb transport connected, took 105ms, ATR=3bfd1300008131fe158073c021c057597562694b657940
2023-07-15 17:50:14.476 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB >> 00a4040006d27600012401
2023-07-15 17:50:14.479 19980-20006/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 3ms
2023-07-15 17:50:14.479 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB << 9000
2023-07-15 17:50:14.480 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB >> 00ca006e00
2023-07-15 17:50:14.488 19980-20006/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 8ms
2023-07-15 17:50:14.488 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB << 6e8201444f10d27600012401030400062068095600005f520800730000e00590007f74038101207382011dc00a7d000bfe080000ff0000c10a162b06010401da470f01c20b122b060104019755010501c30a162b06010401da470f01da06010800001100c407017f7f7f030303c5500178788992382aa370ecc14811b3219d23fdefaeb01f3e903489f8eb5d3b81769c9cdda7f723d0d224dc0d2d1c006b279b80446719bdfea2b5f16cdbe9f5079a2003e46930c0d9c24a4e3318fdcbcf67c6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006148
2023-07-15 17:50:14.489 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB >> 00c0000048
2023-07-15 17:50:14.490 19980-20006/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 1ms
2023-07-15 17:50:14.490 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB << 0000000000000000000000000000000000cd1064914f6364914f0164914f87624d7083de0801010202030181027f660802020bfe02020bfed6020020d7020020d8020020d90200209000
2023-07-15 17:50:14.503 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB >> 00ca0065000000
2023-07-15 17:50:14.511 19980-20006/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 7ms
2023-07-15 17:50:14.511 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB << 65195b104c6f6e676c616e643c3c5374756172745f2d005f3501399000
2023-07-15 17:50:14.511 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB >> 00ca5f50000000
2023-07-15 17:50:14.512 19980-20006/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 1ms
2023-07-15 17:50:14.513 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB << 68747470733a2f2f6b65797365727665722e7562756e74752e636f6d2f706b732f6c6f6f6b75703f6f703d676574267365617263683d3078396332653364383661616535653337396538353234373564313039616130373062393062303433389000
2023-07-15 17:50:14.602 19980-19980/org.sufficientlysecure.keychain.debug D/ScrollView: initGoToTop
2023-07-15 17:50:14.704 19980-20006/org.sufficientlysecure.keychain.debug V/OperationResult$LogEntryParcel: log: LogEntryParcel{mLevel=START, mType=MSG_RET_LOCAL_START, mParameters=null, mIndent=0}
2023-07-15 17:50:14.708 19980-20006/org.sufficientlysecure.keychain.debug V/OperationResult$LogEntryParcel: log: LogEntryParcel{mLevel=DEBUG, mType=MSG_RET_LOCAL_SEARCH, mParameters=[0x11b3219d23fdefae], mIndent=1}
2023-07-15 17:50:14.715 19980-19980/org.sufficientlysecure.keychain.debug D/ScrollView:  onsize change changed 
2023-07-15 17:50:14.723 19980-20006/org.sufficientlysecure.keychain.debug W/KeychainDatabase: Creating database...

So something is definitely causing the USB transfer to drop early.

@sjlongland
Copy link
Contributor Author

Looking at the two tokens and their responses, my hunch is the bulk transfer for the OpenPGP capabilities is ending early in the NitroKey 3.

NitroKey 3A Nano (v1.5.0 firmware):

2023-07-15 17:26:18.104 17530-17558/org.sufficientlysecure.keychain.debug D/UsbTransport: USB >> 00ca006e00
2023-07-15 17:26:18.172 17530-17558/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 68ms
2023-07-15 17:26:18.172 17530-17558/org.sufficientlysecure.keychain.debug D/UsbTransport: USB << 4f10d276000124010304000f9b717b4800005f520a0031f573c001600590007f660802021db902021db97f74038101207381d8c00a3f

YubiKey 5 Nano:

2023-07-15 17:50:14.480 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB >> 00ca006e00
2023-07-15 17:50:14.488 19980-20006/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 8ms
2023-07-15 17:50:14.488 19980-20006/org.sufficientlysecure.keychain.debug D/UsbTransport: USB << 6e8201444f10d27600012401030400062068095600005f520800730000e00590007f74038101207382011dc00a7d000bfe080000ff0000c10a162b06010401da470f01c20b122b060104019755010501c30a162b06010401da470f01da06010800001100c407017f7f7f030303c5500178788992382aa370ecc14811b3219d23fdefaeb01f3e903489f8eb5d3b81769c9cdda7f723d0d224dc0d2d1c006b279b80446719bdfea2b5f16cdbe9f5079a2003e46930c0d9c24a4e3318fdcbcf67c6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006148

I don't know enough about the CCID protocol to fully comprehend what is being sent back, but the YubiKey response is far longer: 258 bytes vs 54. Maybe the NitroKey v3 has less capabilities to send? I don't know, but if the transfer stops short, that'd explain why things aren't working.

I tried upping DEVICE_COMMUNICATE_TIMEOUT_MILLIS to 20 seconds, didn't help.

@sjlongland
Copy link
Contributor Author

Both dongles use the same "protocol" (T1ShortApduProtocol).

@szszszsz
Copy link

Hi! Perhaps this one is connected: trussed-dev/apdu-dispatch#17

@sosthene-nitrokey
Copy link

sosthene-nitrokey commented Jul 17, 2023

Hi! Perhaps this one is connected: trussed-dev/apdu-dispatch#17

For OpenPGP, calls to `SELECT`` do not return any data.

There is one thing that seems suspicious to me:

258 bytes vs 54

We are sending 54 bytes. 54 bytes is exactly 64 - 10. 64 bytes is the length of the USB packets we are sending in usb-ccid, and 10 bytes is the length of a CCID header.
Maybe there is an issue with the CCID chaining mechanism.

@szszszsz
Copy link

szszszsz commented Jul 17, 2023

This is what I get over OpenSC:

~ $ opensc-tool -c default -s "00ca006e00"
Using reader with a card: Nitrokey Nitrokey 3 [CCID/ICCD Interface] 00 00
Sending: 00 CA 00 6E 00
Received (SW1=0x90, SW2=0x00):
4F 10 D2 76 00 01 24 01 03 04 00 0F 4B 1A BB 81 O..v..$.....K...
00 00 5F 52 0A 00 31 F5 73 C0 01 60 05 90 00 7F .._R..1.s..`....
66 08 02 02 1D B9 02 02 1D B9 7F 74 03 81 01 20 f..........t...
73 81 CB C0 0A 3F 00 10 00 10 00 10 00 00 01 C1 s....?..........
06 01 08 00 00 20 00 C2 06 01 08 00 00 20 00 C3 ..... ....... ..
06 01 08 00 00 20 00 C4 07 00 7F 7F 7F 03 00 03 ..... ..........
C5 3C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .<..............
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 C6 3C ...............<
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 CD 0C 00 00 ................
00 00 00 00 00 00 00 00 00 00 DE 06 01 00 02 00 ................
03 00 D6 02 00 20 D7 02 00 20 D8 02 00 20       ..... ... ...

Edit:
It works here, ergo its seems the problem is in the OK implementation.

For OpenPGP, calls to `SELECT`` do not return any data.

Yup. I meant here chaining as well, not the SELECT command per se.

@sosthene-nitrokey
Copy link

sosthene-nitrokey commented Jul 17, 2023

Looking at the data returned in the log, we have the following data objects:

4f 10 d276000124010304000f9b717b480000
5f52 0a 0031f573c00160059000
7f66 08 02021db902021db9
7f74 03 810120 
73 81 d8 
	c0 0a 3f

The first ones ( 4f10, 5f52, 7f66, 7f74) are as expected, but the last one (73) is truncated, is should contain 0xd8 bytes but only has 3.

Looking at OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/tpdu/T1ShortApduProtocol.java there is no CCID chaining being handled.

    @Override
    public byte[] transceive(@NonNull final byte[] apdu) throws UsbTransportException {
        CcidDataBlock response = ccidTransceiver.sendXfrBlock(apdu);
        return response.getData();
    }

I think it should instead check the chain byte and buffer input if it indicates that the block should be chained with the next, something like that: (not tested).

    @Override
    public byte[] transceive(@NonNull final byte[] apdu) throws UsbTransportException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        CcidDataBlock response = ccidTransceiver.sendXfrBlock(apdu);
        output.write(response.getData());
        while(output.getChainParameter() == 1 || output.getChainParameter() == 3) {
            response = ccidTransceiver.receiveDataBlock();
            output.write(response.getData());
        }
        return output.toByteArray();
    }

The relevant part of the CCID Specification is in Table 6.1-7, the bChainParameter:

00h: The response APDU begins and ends in this command
01h: The response APDU begins with this command and is to continue
02h: This abData field continues the response APDU and ends the response APDU
03h: This abData field continues the response APDU and another block is to follow

@sjlongland
Copy link
Contributor Author

@szszszsz and @sosthene-nitrokey -- Many thanks for the hint there. This is the first time I've done any sort of low-level USB code and also the first time I've tried tackling CCID. Nothing like throwing myself in at the deep end. :-)

A big problem being not knowing where to look -- the documentation you've just pointed me to and the example code is a great push in the right direction.

I'll certainly give that CCID chaining code a try and see how that goes.

The `PC_TO_RDR_XFR_BLOCK` structure has two fields which are used in
multi-block transfers:

- `blockWaitingTime` (that's what the comment called it, in the CCID
  specs this is "reserved")
- `levelParam`: Indicates if APDU begins or ends in this command

Some constants for `levelParam` are defined.
@sjlongland
Copy link
Contributor Author

I added some further debugging to show the raw frames being sent back and forth.
One whoopsie I made was sending the level parameter big-endian, since corrected. If I understand the CCID specs, I'm supposed to send an empty frame with level parameter 0x0010 in order to receive the follow-up frames.

So to start:

Host sends: 6f05000000000200000000ca006e00
Device responds: 803600000000020000014f10d276000124010304000f9b717b4800005f520a0031f573c001600590007f660802021db902021db97f74038101207381d8c00a3f

So far so good… I notice bChainParameter is 0x01, so I need to ask for the next block.

Host sends: 6f000000000003001000
Device responds: no data (I presume what I see as a hex dump below is what was previously in the buffer).

Was I too quick? Or was my "please continue" frame malformed in some way?

Full logs:

2023-07-17 22:55:16.546 5019-5019/org.sufficientlysecure.keychain.debug I/ViewRootImpl@e63234d[CreateKeyActivity]: ViewPostIme pointer 0
2023-07-17 22:55:16.606 5019-5019/org.sufficientlysecure.keychain.debug I/ViewRootImpl@e63234d[CreateKeyActivity]: ViewPostIme pointer 1
2023-07-17 22:55:16.614 5019-5019/org.sufficientlysecure.keychain.debug D/ScrollView: initGoToTop
2023-07-17 22:55:16.642 5019-5019/org.sufficientlysecure.keychain.debug D/UsbConnectionDispatcher: Device: UsbDevice[mName=/dev/bus/usb/001/002,mVendorId=8352,mProductId=17074,mClass=239,mSubclass=2,mProtocol=1,mManufacturerName=Nitrokey,mProductName=Nitrokey 3,mVersion=1.05,mSerialNumberReader=android.hardware.usb.IUsbSerialReader$Stub$Proxy@5f07f4f, mHasAudioPlayback=false, mHasAudioCapture=false, mHasMidi=false, mHasVideoCapture=false, mHasVideoPlayback=false, mConfigurations=[
    UsbConfiguration[mId=1,mName=null,mAttributes=128,mMaxPower=50,mInterfaces=[
    UsbInterface[mId=0,mAlternateSetting=0,mName=CCID/ICCD Interface,mClass=11,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=129,mAttributes=2,mMaxPacketSize=64,mInterval=0]
    UsbEndpoint[mAddress=1,mAttributes=2,mMaxPacketSize=64,mInterval=0]]
    UsbInterface[mId=1,mAlternateSetting=0,mName=null,mClass=3,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=2,mAttributes=3,mMaxPacketSize=64,mInterval=5]
    UsbEndpoint[mAddress=130,mAttributes=3,mMaxPacketSize=64,mInterval=5]]
    UsbInterface[mId=2,mAlternateSetting=0,mName=null,mClass=2,mSubclass=2,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=8,mInterval=255]]
    UsbInterface[mId=3,mAlternateSetting=0,mName=null,mClass=10,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=132,mAttributes=2,mMaxPacketSize=64,mInterval=0]
    UsbEndpoint[mAddress=3,mAttributes=2,mMaxPacketSize=64,mInterval=0]]]]
2023-07-17 22:55:16.643 5019-5019/org.sufficientlysecure.keychain.debug D/UsbConnectionDispatcher: Got permission!
2023-07-17 22:55:16.644 5019-5065/org.sufficientlysecure.keychain.debug D/UsbTransport: USB Bulk In max packet size: 64
2023-07-17 22:55:16.644 5019-5065/org.sufficientlysecure.keychain.debug D/UsbTransport: USB Bulk Out max packet size: 64
2023-07-17 22:55:16.647 5019-5065/org.sufficientlysecure.keychain.debug D/UsbTransport: CCID Description: CcidDescription{maxSlotIndex=0, voltageSupport=1, protocols=2, features=264256}
2023-07-17 22:55:16.647 5019-5065/org.sufficientlysecure.keychain.debug V/CcidDescription: Using T1ShortApduProtocol transport
2023-07-17 22:55:16.647 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Skipping available input
2023-07-17 22:55:16.663 5019-5019/org.sufficientlysecure.keychain.debug D/ScrollView:  onsize change changed 
2023-07-17 22:55:17.157 5019-5065/org.sufficientlysecure.keychain.debug V/CcidTransceiver: CCID: attempting to power on with voltage _5V
2023-07-17 22:55:17.159 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Receive data block immediate seq=0
2023-07-17 22:55:17.161 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received 29 bytes: 801300000000000000003b8f01805d4e6974726f6b657900000000006a0000000000000000000000000000000000000000000000000000000000000000000000
2023-07-17 22:55:17.164 5019-5065/org.sufficientlysecure.keychain.debug V/CcidTransceiver: CCID: powered on with voltage _5V
2023-07-17 22:55:17.164 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Usb transport connected, took 516ms, ATR=3b8f01805d4e6974726f6b657900000000006a
2023-07-17 22:55:17.170 5019-5065/org.sufficientlysecure.keychain.debug D/UsbTransport: USB >> 00a4040006d27600012401
2023-07-17 22:55:17.171 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sending: 6f0b000000000100000000a4040006d27600012401
2023-07-17 22:55:17.173 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sent, receiving response
2023-07-17 22:55:17.174 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Receive data block immediate seq=1
2023-07-17 22:55:17.462 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received 12 bytes: 80020000000001000000900001805d4e6974726f6b657900000000006a0000000000000000000000000000000000000000000000000000000000000000000000
2023-07-17 22:55:17.465 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 291ms
2023-07-17 22:55:17.469 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received: 9000
2023-07-17 22:55:17.472 5019-5065/org.sufficientlysecure.keychain.debug D/UsbTransport: USB << 9000
2023-07-17 22:55:17.478 5019-5065/org.sufficientlysecure.keychain.debug D/UsbTransport: USB >> 00ca006e00
2023-07-17 22:55:17.480 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sending: 6f05000000000200000000ca006e00
2023-07-17 22:55:17.484 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sent, receiving response
2023-07-17 22:55:17.486 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Receive data block immediate seq=2
2023-07-17 22:55:17.557 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received 64 bytes: 803600000000020000014f10d276000124010304000f9b717b4800005f520a0031f573c001600590007f660802021db902021db97f74038101207381d8c00a3f
2023-07-17 22:55:17.559 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 79ms
2023-07-17 22:55:17.561 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received: 4f10d276000124010304000f9b717b4800005f520a0031f573c001600590007f660802021db902021db97f74038101207381d8c00a3f
2023-07-17 22:55:17.564 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sending: 6f000000000003001000
2023-07-17 22:55:17.566 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sent, receiving response
2023-07-17 22:55:17.568 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Receive data block immediate seq=3
2023-07-17 22:55:17.570 5019-5065/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received 0 bytes: 803600000000020000014f10d276000124010304000f9b717b4800005f520a0031f573c001600590007f660802021db902021db97f74038101207381d8c00a3f
2023-07-17 22:55:17.572 5019-5065/org.sufficientlysecure.keychain.debug D/UsbDeviceConnectionJNI: close
2023-07-17 22:55:17.574 5019-5065/org.sufficientlysecure.keychain.debug D/UsbTransport: Usb transport disconnected
2023-07-17 22:55:17.580 5019-5019/org.sufficientlysecure.keychain.debug D/BaseSecurityTokenActivity: Exception in handleSecurityTokenError
    org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException: USB-CCID error - failed to receive CCID header
        at org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver.receiveDataBlockImmediate(CcidTransceiver.java:261)
        at org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver.receiveDataBlock(CcidTransceiver.java:246)
        at org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver.sendXfrBlock(CcidTransceiver.java:221)
        at org.sufficientlysecure.keychain.securitytoken.usb.tpdu.T1ShortApduProtocol.transceive(T1ShortApduProtocol.java:63)
        at org.sufficientlysecure.keychain.securitytoken.usb.UsbTransport.transceive(UsbTransport.java:189)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.transceiveWithChaining(SecurityTokenConnection.java:211)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.communicate(SecurityTokenConnection.java:197)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.readData(SecurityTokenConnection.java:439)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.refreshConnectionCapabilities(SecurityTokenConnection.java:168)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.connectToDevice(SecurityTokenConnection.java:129)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.connectIfNecessary(SecurityTokenConnection.java:105)
        at org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity$1.doInBackground(BaseSecurityTokenActivity.java:147)
        at org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity$1.doInBackground(BaseSecurityTokenActivity.java:137)
        at android.os.AsyncTask$3.call(AsyncTask.java:394)
        at java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:305)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
        at java.lang.Thread.run(Thread.java:1012)

@sosthene-nitrokey
Copy link

sosthene-nitrokey commented Jul 18, 2023

Looking at the same transaction on a laptop:

-> 6f0500000000b5000000 00ca006e00
<- 803600000000b50000014f10  d276000124010304000f566f86b000005f520a0031f573c001600590007f660802021db902021db97f74038101207381cbc00a3f
-> 6f0000000000b6001000
<- 803600000000b60000030010  00100010000001c106010800002000c206010800002000c306010800002000c407007f7f7f030003c53c00000000000000000000
-> 6f0000000000b7001000
<- 803600000000b70000030000  00000000000000000000000000000000000000000000000000000000d087f96c8c140ff2238a1adbe79fcdeaf85af318c63c0000
-> 6f0000000000b8001000
<- 803600000000b80000030000  00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-> 6f0000000000b9001000
<- 802800000000b90000020000  0000cd0c0000000000000000648ab35ede06010002000301d6020020d7020020d80200209000

What you're sending matches what PCSCD is sending (except for the sequence number but that's expected).

@sosthene-nitrokey
Copy link

The CCID is required to send a Zero Length Packet (ZLP) following any Bulk-In message that is a multiple of MaxPacketSize. This ZLP allows the CCID device driver to be more efficient, and is generally considered “good-practice” for USB bulk-in pipes.

I suppose you might need to do a bulkIn read with an empty buffer before sending the "please continue" XFR BLOCK. We do send those Zero Length Packets.

@sjlongland
Copy link
Contributor Author

Well… zero-sized packet certainly got it talking a little longer. The wheels fall off shortly after that.

2023-07-23 20:31:19.887 32174-32174/org.sufficientlysecure.keychain.debug I/ViewRootImpl@672ce96[CreateKeyActivity]: ViewPostIme pointer 0
2023-07-23 20:31:19.973 32174-32174/org.sufficientlysecure.keychain.debug I/ViewRootImpl@672ce96[CreateKeyActivity]: ViewPostIme pointer 1
2023-07-23 20:31:19.982 32174-32174/org.sufficientlysecure.keychain.debug D/ScrollView: initGoToTop
2023-07-23 20:31:20.010 32174-32174/org.sufficientlysecure.keychain.debug D/UsbConnectionDispatcher: Device: UsbDevice[mName=/dev/bus/usb/001/002,mVendorId=8352,mProductId=17074,mClass=239,mSubclass=2,mProtocol=1,mManufacturerName=Nitrokey,mProductName=Nitrokey 3,mVersion=1.05,mSerialNumberReader=android.hardware.usb.IUsbSerialReader$Stub$Proxy@7d04c56, mHasAudioPlayback=false, mHasAudioCapture=false, mHasMidi=false, mHasVideoCapture=false, mHasVideoPlayback=false, mConfigurations=[
    UsbConfiguration[mId=1,mName=null,mAttributes=128,mMaxPower=50,mInterfaces=[
    UsbInterface[mId=0,mAlternateSetting=0,mName=CCID/ICCD Interface,mClass=11,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=129,mAttributes=2,mMaxPacketSize=64,mInterval=0]
    UsbEndpoint[mAddress=1,mAttributes=2,mMaxPacketSize=64,mInterval=0]]
    UsbInterface[mId=1,mAlternateSetting=0,mName=null,mClass=3,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=2,mAttributes=3,mMaxPacketSize=64,mInterval=5]
    UsbEndpoint[mAddress=130,mAttributes=3,mMaxPacketSize=64,mInterval=5]]
    UsbInterface[mId=2,mAlternateSetting=0,mName=null,mClass=2,mSubclass=2,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=131,mAttributes=3,mMaxPacketSize=8,mInterval=255]]
    UsbInterface[mId=3,mAlternateSetting=0,mName=null,mClass=10,mSubclass=0,mProtocol=0,mEndpoints=[
    UsbEndpoint[mAddress=132,mAttributes=2,mMaxPacketSize=64,mInterval=0]
    UsbEndpoint[mAddress=3,mAttributes=2,mMaxPacketSize=64,mInterval=0]]]]
2023-07-23 20:31:20.011 32174-32174/org.sufficientlysecure.keychain.debug D/UsbConnectionDispatcher: Got permission!
2023-07-23 20:31:20.012 32174-32205/org.sufficientlysecure.keychain.debug D/UsbTransport: USB Bulk In max packet size: 64
2023-07-23 20:31:20.012 32174-32205/org.sufficientlysecure.keychain.debug D/UsbTransport: USB Bulk Out max packet size: 64
2023-07-23 20:31:20.015 32174-32205/org.sufficientlysecure.keychain.debug D/UsbTransport: CCID Description: CcidDescription{maxSlotIndex=0, voltageSupport=1, protocols=2, features=264256}
2023-07-23 20:31:20.015 32174-32205/org.sufficientlysecure.keychain.debug V/CcidDescription: Using T1ShortApduProtocol transport
2023-07-23 20:31:20.015 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Skipping available input
2023-07-23 20:31:20.031 32174-32174/org.sufficientlysecure.keychain.debug D/ScrollView:  onsize change changed 
2023-07-23 20:31:20.549 32174-32205/org.sufficientlysecure.keychain.debug V/CcidTransceiver: CCID: attempting to power on with voltage _5V
2023-07-23 20:31:20.551 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Receive data block immediate seq=0
2023-07-23 20:31:20.552 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received 29 bytes: 801300000000000000003b8f01805d4e6974726f6b657900000000006a0000000000000000000000000000000000000000000000000000000000000000000000
2023-07-23 20:31:20.553 32174-32205/org.sufficientlysecure.keychain.debug V/CcidTransceiver: CCID: powered on with voltage _5V
2023-07-23 20:31:20.553 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Usb transport connected, took 537ms, ATR=3b8f01805d4e6974726f6b657900000000006a
2023-07-23 20:31:20.554 32174-32205/org.sufficientlysecure.keychain.debug D/UsbTransport: USB >> 00a4040006d27600012401
2023-07-23 20:31:20.555 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sending: 6f0b000000000100000000a4040006d27600012401
2023-07-23 20:31:20.556 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sent, receiving response
2023-07-23 20:31:20.557 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Receive data block immediate seq=1
2023-07-23 20:31:20.851 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received 12 bytes: 80020000000001000000900001805d4e6974726f6b657900000000006a0000000000000000000000000000000000000000000000000000000000000000000000
2023-07-23 20:31:20.853 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 298ms
2023-07-23 20:31:20.854 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received: 9000
2023-07-23 20:31:20.855 32174-32205/org.sufficientlysecure.keychain.debug D/UsbTransport: USB << 9000
2023-07-23 20:31:20.856 32174-32205/org.sufficientlysecure.keychain.debug D/UsbTransport: USB >> 00ca006e00
2023-07-23 20:31:20.858 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sending: 6f05000000000200000000ca006e00
2023-07-23 20:31:20.862 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sent, receiving response
2023-07-23 20:31:20.866 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Receive data block immediate seq=2
2023-07-23 20:31:20.954 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received 64 bytes: 803600000000020000014f10d276000124010304000f9b717b4800005f520a0031f573c001600590007f660802021db902021db97f74038101207381d8c00a3f
2023-07-23 20:31:20.956 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 98ms
2023-07-23 20:31:20.956 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received: 4f10d276000124010304000f9b717b4800005f520a0031f573c001600590007f660802021db902021db97f74038101207381d8c00a3f
2023-07-23 20:31:20.957 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sending: 6f000000000003001000
2023-07-23 20:31:20.962 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sent, receiving response
2023-07-23 20:31:20.963 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Receive data block immediate seq=3
2023-07-23 20:31:20.966 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received 0 bytes: 803600000000020000014f10d276000124010304000f9b717b4800005f520a0031f573c001600590007f660802021db902021db97f74038101207381d8c00a3f
2023-07-23 20:31:20.967 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Poking device again due to zero-sized read
2023-07-23 20:31:20.970 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: (on retry) Received 64 bytes: 80360000000003000003001000100010000001c10a162b06010401da470f01c20b122b060104019755010501c30a162b06010401da470f01c407017f7f7f0300
2023-07-23 20:31:20.971 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: USB XferBlock call took 14ms
2023-07-23 20:31:20.972 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received: 001000100010000001c10a162b06010401da470f01c20b122b060104019755010501c30a162b06010401da470f01c407017f7f7f0300
2023-07-23 20:31:20.972 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sending: 6f000000000004001000
2023-07-23 20:31:20.974 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Sent, receiving response
2023-07-23 20:31:20.975 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Receive data block immediate seq=4
2023-07-23 20:31:42.599 32174-32205/org.sufficientlysecure.keychain.debug D/CcidTransceiver: Received -1 bytes: 80360000000003000003001000100010000001c10a162b06010401da470f01c20b122b060104019755010501c30a162b06010401da470f01c407017f7f7f0300
2023-07-23 20:31:42.600 32174-32205/org.sufficientlysecure.keychain.debug D/UsbDeviceConnectionJNI: close
2023-07-23 20:31:42.601 32174-32205/org.sufficientlysecure.keychain.debug D/UsbTransport: Usb transport disconnected
2023-07-23 20:31:42.604 32174-32174/org.sufficientlysecure.keychain.debug D/BaseSecurityTokenActivity: Exception in handleSecurityTokenError
    org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException: USB-CCID error - failed to receive CCID header
        at org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver.receiveDataBlockImmediate(CcidTransceiver.java:268)
        at org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver.receiveDataBlock(CcidTransceiver.java:246)
        at org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver.sendXfrBlock(CcidTransceiver.java:221)
        at org.sufficientlysecure.keychain.securitytoken.usb.tpdu.T1ShortApduProtocol.transceive(T1ShortApduProtocol.java:64)
        at org.sufficientlysecure.keychain.securitytoken.usb.UsbTransport.transceive(UsbTransport.java:189)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.transceiveWithChaining(SecurityTokenConnection.java:211)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.communicate(SecurityTokenConnection.java:197)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.readData(SecurityTokenConnection.java:439)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.refreshConnectionCapabilities(SecurityTokenConnection.java:168)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.connectToDevice(SecurityTokenConnection.java:129)
        at org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection.connectIfNecessary(SecurityTokenConnection.java:105)
        at org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity$1.doInBackground(BaseSecurityTokenActivity.java:147)
        at org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity$1.doInBackground(BaseSecurityTokenActivity.java:137)
        at android.os.AsyncTask$3.call(AsyncTask.java:394)
        at java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:305)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
        at java.lang.Thread.run(Thread.java:1012)

Received -1 bytes, and I'm guessing the rest of the data in that buffer is junk from the previous read. I note the code here is sending immediately after getting a response. Is there some sort of timing limitation I'm hitting here?

@sosthene-nitrokey
Copy link

Am I reading the timestamps correctly that the read failed with "-1" after 20 seconds?

@sjlongland
Copy link
Contributor Author

sjlongland commented Jul 24, 2023

Am I reading the timestamps correctly that the read failed with "-1" after 20 seconds?

That is correct, I extended the timeouts for debugging.

diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransceiver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransceiver.java
index 84c5e3c2c..7608cb37c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransceiver.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransceiver.java
@@ -78,8 +78,8 @@ public class CcidTransceiver {
 
     private static final int ICC_STATUS_SUCCESS = 0;
 
-    private static final int DEVICE_COMMUNICATE_TIMEOUT_MILLIS = 5000;
-    private static final int DEVICE_SKIP_TIMEOUT_MILLIS = 100;
+    private static final int DEVICE_COMMUNICATE_TIMEOUT_MILLIS = 20000;
+    private static final int DEVICE_SKIP_TIMEOUT_MILLIS = 500;

@sjlongland
Copy link
Contributor Author

Screenshot_20230729_142529_OpenKeychain (Debug)

Ohhh, worked that time!

@sjlongland
Copy link
Contributor Author

tempFileForShare_20230729-143515

Couldn't get Password Store to work with it (it's probably talking to the "stable release" of OpenKeychain) but the debug version seems to be able to digitally sign and decrypt using this security token.

@sjlongland sjlongland marked this pull request as ready for review July 29, 2023 04:38
@sjlongland
Copy link
Contributor Author

openkeychain-nitrokey3-v5.8.2-7-g3404cd2f6.zip

The .apk is inside that zip file, for brave souls that want to try it without building.

@vikanezrimaya
Copy link

I can verify that it indeed works with my NitroKey 3A mini 🎉

@szszszsz
Copy link

Ohhh, worked that time!

Cool! I had the same problem while I was connecting Nitrokey HOTP Verification tool to NK3's CCID, and just repeating the request has worked, regardless of the set timeout.

@Valodim
Copy link
Member

Valodim commented Jul 31, 2023

Hey folks, just quickly chiming in to let you know I appreciate the work here, and will try to review & merge this PR when it's ready.

From a quick glance: Please add some comments around introduced behavior that is otherwise inexplicable, e.g. the "retry three times" logic in that last commit. USB (and NFC) communication is a fickle beast and much of it is organically grown precisely from empirical "if I hold it like this it'll work" work like here, so it's important to retain at least some of that knowledge for future readers of this code.

Include references of where the magic numbers came from.
There is room in the 100-char width to fit this.  (Yeah, too used to
coding in 80 columns!)
100 characters with exceptions made for CLI commands, URIs and imports.
Some of this is existing code, but for the sake of consistency, we'll
wrap all at 100 characters.

Function arguments: when this happens, if we put the closing `)` on its
own line (like `}` in code blocks), it visually stands out better for
indicating where the function call ends.
@sjlongland
Copy link
Contributor Author

From a quick glance: Please add some comments around introduced behavior that is otherwise inexplicable, e.g. the "retry three times" logic in that last commit. USB (and NFC) communication is a fickle beast and much of it is organically grown precisely from empirical "if I hold it like this it'll work" work like here, so it's important to retain at least some of that knowledge for future readers of this code.

No problems… yes well… this also being my first time doing any kind of significant USB code (bar messing with some LUFA stack examples). I greatly appreciate the insights provided by @szszszsz and @sosthene-nitrokey, those were a big help to getting this across the line.

I've done a bit of tidy-up on the code submission this morning (spotted one tab that escaped my attention earlier -- I need to tell vi about that) and I've re-wrapped some of the lines so they meet the 100 character limit without looking untidy

Where I've introduced constants, unless its bleeding obvious (e.g. the USB product IDs, they're grouped together) I've added a reference to what document the constant was taken from. I hope that takes care of the coding style, but if there's anything more, let me know and I'll try to sort it out.

I've also tried this same code against a YubiKey 5C, and that too seems to work fine, so it appears no regressions that I can see. That said, users of non-NitroKey CCID devices, it'd be worth trying this branch out with your CCID device and make sure I didn't accidentally introduce regressions. :-)

@peterwilli
Copy link

peterwilli commented Aug 27, 2023

Ok I finally got the time to test it out! I used a 3A Mini, and it seems to work (took hours before I finally managed to make my own APK >~<). The only problem I have is that okc-agent when logging in to SSH it does recognize my key, but it doesn't ask to enter a pin number. If I encrypt a file directly in OpenKeyChain, it does ask for a pin number, so this might be a okc-agent issue which I will dive into further (see screenshot).

Another issue is that it didn't work to import my key if the pgp key was on a keyserver, I had to load the public key to my phone and then import the file in OpenKeyChain, then import the NitroKey. Not a big deal, but something you have to be aware of to avoid spending ages figuring out why it doesn't work.

Screenshot_20230827_130527_OpenKeychain

Edit: Idk why, but when I deleted my key (make sure to untick revoke!) on OpenKeyChain and re-loaded it, it was working!
Edit2: I found out that if I take the key out and then plug it back in, it won't do anything upon SSH login (it just freezes, lights dont blink up on the NitroKey). However, deleting and re-adding the key with the steps above makes it magically work again. I also find out that the issue narrows down to automatic pin entry. If I set OKC-Agent to forgetting the pin upon screen lock, the bug persists, but if I lock my screen and unlock, and then enter my pin, even after re-inserting the NitroKey, it works perfectly fine. So it must probably be something related to OKC's automated pin entry.

@sjlongland
Copy link
Contributor Author

Ok I finally got the time to test it out! I used a 3A Mini, and it seems to work (took hours before I finally managed to make my own APK >~<). The only problem I have is that okc-agent when logging in to SSH it does recognize my key, but it doesn't ask to enter a pin number. If I encrypt a file directly in OpenKeyChain, it does ask for a pin number, so this might be a okc-agent issue which I will dive into further (see screenshot).

I recall trying to get okc-agent working (with a YubiKey) but not having a lot of luck. I should give it another shot.

Another issue is that it didn't work to import my key if the pgp key was on a keyserver, I had to load the public key to my phone and then import the file in OpenKeyChain, then import the NitroKey. Not a big deal, but something you have to be aware of to avoid spending ages figuring out why it doesn't work.

This is odd… maybe there was an issue with communicating with the key server at that time? I have the URI set for the public key and that seems to work reliably -- provided the key server exports the UIDs (not all do).

@peterwilli
Copy link

Ok I finally got the time to test it out! I used a 3A Mini, and it seems to work (took hours before I finally managed to make my own APK >~<). The only problem I have is that okc-agent when logging in to SSH it does recognize my key, but it doesn't ask to enter a pin number. If I encrypt a file directly in OpenKeyChain, it does ask for a pin number, so this might be a okc-agent issue which I will dive into further (see screenshot).

I recall trying to get okc-agent working (with a YubiKey) but not having a lot of luck. I should give it another shot.

Another issue is that it didn't work to import my key if the pgp key was on a keyserver, I had to load the public key to my phone and then import the file in OpenKeyChain, then import the NitroKey. Not a big deal, but something you have to be aware of to avoid spending ages figuring out why it doesn't work.

This is odd… maybe there was an issue with communicating with the key server at that time? I have the URI set for the public key and that seems to work reliably -- provided the key server exports the UIDs (not all do).

There was a green checkmark when it found my public key from the keyserver. I have no idea if it had to do with the fact that my pgp key is imported and not generated on-device?

@sjlongland
Copy link
Contributor Author

There was a green checkmark when it found my public key from the keyserver. I have no idea if it had to do with the fact that my pgp key is imported and not generated on-device?

Doubtful… as I understand it, what's stored on the token is the private keys. Specifically the sub-keys for encryption, digital signatures, and authentication. To make those useful, you need the public key certificate which also includes UID information, and the public keys of the sub-keys (that match the private keys on the token).

How the private keys came to exist on the device is irrelevant: the one you're importing is the public "master key", which does not exist on the token (nor does its private counterpart -- if done properly, the master private key is stored offline somewhere safe).

There are two ways that I know of:

  • directly loading a public master key file from local storage on the device
  • downloading a public master key from a trusted key server: this requires the token to identify somehow which public key to use -- "URL of public key" according to gpg --card-status.

@danielkrajnik
Copy link

What's blocking to merge this NitroKey support to the master branch? It would be really useful for Nitrokey users.

@sjlongland
Copy link
Contributor Author

So, this PR has sat here for some time now. I understand that most development on OpenKeyChain has ceased with only major bug fixes and security issues being addressed now, which is a problem as I do not know of any alternatives.

As some may have gathered, I'm far from an "expert", and fumbled my way through this… so I'm likely not a good candidate for taking over maintainer-ship.

Technical debt: it relies on a quite old version of Android Studio to build, we'd have to spend a bit of time coaxing it to a newer release to keep dependencies current. I did not do this here as I wanted to keep the PR "pure". Modernisation of the code base to me should be a separate PR, and possibly is most practically done after merging NitroKey 3 support.

A possible option for the short-term, is we re-brand OKC (maybe we call it "NitroKeyChain"?) so that people can keep existing installations for other security tokens if needed, and we maintain a fork which adds NK3 support until such time as the final fate of this PR (and OKC in general) is known.

Personally though, I'd like to see OpenKeyChain continue as it is today, being a vendor-agnostic OpenPGP security token and keypair manager for Android. It already has support for other tools like PasswordStore (which I use), ConnectBot and K9Mail which would need to be duplicated if we forked it to a new program.

I guess the question is, who amongst us has prior experience with Android application development involving NFC and USB interfaces? I can pitch in and help where I can, but I think there are better qualified people than I to take the lead.

@Valodim
Copy link
Member

Valodim commented Jan 29, 2024

Took me a while to get everything running with all new dependencies and build tools, see #2876. We should have this done shortly and get all this work into a release. Sorry for the delay :)

@danielkrajnik
Copy link

@Valodim Great news! Thank you!

@Valodim
Copy link
Member

Valodim commented Feb 15, 2024

This PR is merged (with some changes) and part of the 6.0.0 release already, I had just forgotten to close it. Cheers, and thanks for your work again @sjlongland

@Valodim Valodim closed this Feb 15, 2024
@danielkrajnik
Copy link

danielkrajnik commented Feb 15, 2024

Has anyone got 3A NFC to work on GrapheneOS already by any chance? USE SECURITY TOKEN button returns "Key not found" when it's connected via Usb C and still doesn't seem to react to NFC.

@sjlongland sjlongland deleted the feature/nitrokey-v3 branch February 15, 2024 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for NitroKey 3A Mini
8 participants