From c2bab8807e8b17f73dffbd17d5662453de631471 Mon Sep 17 00:00:00 2001 From: Christian Hagau Date: Tue, 29 Sep 2020 00:00:00 +0000 Subject: [PATCH] Add support for KDF for security tokens --- .../SecurityTokenConnection.java | 76 ++++++++++++++++++- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java index 4215af30c..c56b8a8e8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.securitytoken; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.Arrays; import java.util.List; import android.content.Context; @@ -37,7 +38,12 @@ import timber.log.Timber; /** * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant * devices. - * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf + * For the full specs, see [0] + * + * References: + * [0] `Functional Specification of the OpenPGP application on ISO Smart Card Operating Systems` + * version 3.4.1 + * https://gnupg.org/ftp/specs/OpenPGP-smart-card-application-3.4.1.pdf */ public class SecurityTokenConnection { private static final int APDU_SW1_RESPONSE_AVAILABLE = 0x61; @@ -55,6 +61,7 @@ public class SecurityTokenConnection { private TokenType tokenType; private CardCapabilities cardCapabilities; private OpenPgpCapabilities openPgpCapabilities; + private KdfParameters kdfParameters; private SecureMessaging secureMessaging; @@ -304,6 +311,44 @@ public class SecurityTokenConnection { // region pin management + private byte[] calculateKdfIfNecessary(byte[] pin, KdfParameters.PasswordType type) throws IOException { + if (!this.openPgpCapabilities.isHasKdf()) { + // KDF-DO is not supported by token + return pin; + } + KdfParameters kdfParameters = retrieveKdfDo(); + if (kdfParameters == null || !kdfParameters.isHasUsesKdf()) { + return pin; + } else { + return KdfCalculator.calculateKdf(kdfParameters.forType(type), pin); + } + } + + private KdfParameters retrieveKdfDo() throws IOException { + if (this.kdfParameters != null) { + return this.kdfParameters; + } + + // query token for KDF-DO + // see page 18 of [0] + CommandApdu getKdfDoCommand = commandFactory.createGetDataCommand(0x00, 0xf9); + ResponseApdu kdfDoResponse = communicate(getKdfDoCommand); + if (!kdfDoResponse.isSuccess()) { + throw new CardException("Couldn't get KDF.DO!", kdfDoResponse.getSw()); + } + byte[] kdfDo = kdfDoResponse.getData(); + + // empty KDF-DO means plain UTF-8 password is being used + // see page 19 of [0] + if (kdfDo.length == 0) { + return null; + } + + this.kdfParameters = KdfParameters.fromKdfDo(kdfDo); + + return this.kdfParameters; + } + public void verifyPinForSignature() throws IOException { if (isPw1ValidatedForSignature) { return; @@ -314,7 +359,14 @@ public class SecurityTokenConnection { byte[] pin = cachedPin.toStringUnsafe().getBytes(); - CommandApdu verifyPw1ForSignatureCommand = commandFactory.createVerifyPw1ForSignatureCommand(pin); + byte[] transformedPin = this.calculateKdfIfNecessary(pin, KdfParameters.PasswordType.PW1); + + CommandApdu verifyPw1ForSignatureCommand = commandFactory.createVerifyPw1ForSignatureCommand(transformedPin); + + // delete secrets from memory + Arrays.fill(pin, (byte) 0); + Arrays.fill(transformedPin, (byte) 0); + ResponseApdu response = communicate(verifyPw1ForSignatureCommand); if (!response.isSuccess()) { throw new CardException("Bad PIN!", response.getSw()); @@ -333,7 +385,14 @@ public class SecurityTokenConnection { byte[] pin = cachedPin.toStringUnsafe().getBytes(); - CommandApdu verifyPw1ForOtherCommand = commandFactory.createVerifyPw1ForOtherCommand(pin); + byte[] transformedPin = this.calculateKdfIfNecessary(pin, KdfParameters.PasswordType.PW1); + + CommandApdu verifyPw1ForOtherCommand = commandFactory.createVerifyPw1ForOtherCommand(transformedPin); + + // delete secrets from memory + Arrays.fill(pin, (byte) 0); + Arrays.fill(transformedPin, (byte) 0); + ResponseApdu response = communicate(verifyPw1ForOtherCommand); if (!response.isSuccess()) { throw new CardException("Bad PIN!", response.getSw()); @@ -347,7 +406,16 @@ public class SecurityTokenConnection { return; } - CommandApdu verifyPw3Command = commandFactory.createVerifyPw3Command(adminPin.toStringUnsafe().getBytes()); + byte[] pin = adminPin.toStringUnsafe().getBytes(); + byte[] transformedPin = this.calculateKdfIfNecessary(pin, KdfParameters.PasswordType.PW3); + + CommandApdu verifyPw3Command = commandFactory.createVerifyPw3Command(transformedPin); + + // delete secrets from memory + Arrays.fill(pin, (byte) 0); + Arrays.fill(transformedPin, (byte) 0); + adminPin.removeFromMemory(); + ResponseApdu response = communicate(verifyPw3Command); if (!response.isSuccess()) { throw new CardException("Bad PIN!", response.getSw());