From 12785480ef02bfcec22d26e44b949ebb711405bd Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 22 May 2016 23:17:01 +0600 Subject: [PATCH] SecurityToken: fix put key, add key format handling --- .../keychain/securitytoken/KeyFormat.java | 87 ++++++++++ .../securitytoken/OpenPGPCapabilities.java | 54 +----- .../securitytoken/SecurityTokenHelper.java | 106 +----------- .../keychain/util/SecurityTokenUtils.java | 154 ++++++++++++++++++ 4 files changed, 253 insertions(+), 148 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SecurityTokenUtils.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java new file mode 100644 index 000000000..3b2e93f0a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken; + +// 4.3.3.6 Algorithm Attributes +public class KeyFormat { + private int mAlgorithmId; + private int mModulusLength; + private int mExponentLength; + private AlgorithmFormat mAlgorithmFormat; + + public KeyFormat(byte[] bytes) { + mAlgorithmId = bytes[0]; + mModulusLength = bytes[1] << 8 | bytes[2]; + mExponentLength = bytes[3] << 8 | bytes[4]; + mAlgorithmFormat = AlgorithmFormat.from(bytes[5]); + + if (mAlgorithmId != 1) { // RSA + throw new IllegalArgumentException("Unsupported Algorithm id " + mAlgorithmId); + } + } + + public int getAlgorithmId() { + return mAlgorithmId; + } + + public int getModulusLength() { + return mModulusLength; + } + + public int getExponentLength() { + return mExponentLength; + } + + public AlgorithmFormat getAlgorithmFormat() { + return mAlgorithmFormat; + } + + public enum AlgorithmFormat { + STANDARD(0, false, false), + STANDARD_WITH_MODULUS(1, false, true), + CRT(2, true, false), + CRT_WITH_MODULUS(3, true, true); + + private int mValue; + private boolean mIncludeModulus; + private boolean mIncludeCrt; + + AlgorithmFormat(int value, boolean includeCrt, boolean includeModulus) { + mValue = value; + mIncludeModulus = includeModulus; + mIncludeCrt = includeCrt; + } + + public static AlgorithmFormat from(byte b) { + for (AlgorithmFormat format : values()) { + if (format.mValue == b) { + return format; + } + } + return null; + } + + public boolean isIncludeModulus() { + return mIncludeModulus; + } + + public boolean isIncludeCrt() { + return mIncludeCrt; + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPGPCapabilities.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPGPCapabilities.java index 347386fd6..8aa86db02 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPGPCapabilities.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPGPCapabilities.java @@ -40,7 +40,7 @@ public class OpenPGPCapabilities { private int mMaxCmdLen; private int mMaxRspLen; - private Map mKeyFormats; + private Map mKeyFormats; public OpenPGPCapabilities(byte[] data) throws IOException { Iso7816TLV[] tlvs = Iso7816TLV.readList(data, true); @@ -64,13 +64,13 @@ public class OpenPGPCapabilities { parseExtendedCaps(tlv.mV); break; case 0xC1: - parseAlgoCaps(KeyType.SIGN, tlv.mV); + mKeyFormats.put(KeyType.SIGN, new KeyFormat(tlv.mV)); break; case 0xC2: - parseAlgoCaps(KeyType.ENCRYPT, tlv.mV); + mKeyFormats.put(KeyType.ENCRYPT, new KeyFormat(tlv.mV)); break; case 0xC3: - parseAlgoCaps(KeyType.AUTH, tlv.mV); + mKeyFormats.put(KeyType.AUTH, new KeyFormat(tlv.mV)); break; case 0xC4: mPw1ValidForMultipleSignatures = tlv.mV[0] == 1; @@ -86,13 +86,13 @@ public class OpenPGPCapabilities { parseExtendedCaps(tlv.mV); break; case 0xC1: - parseAlgoCaps(KeyType.SIGN, tlv.mV); + mKeyFormats.put(KeyType.SIGN, new KeyFormat(tlv.mV)); break; case 0xC2: - parseAlgoCaps(KeyType.ENCRYPT, tlv.mV); + mKeyFormats.put(KeyType.ENCRYPT, new KeyFormat(tlv.mV)); break; case 0xC3: - parseAlgoCaps(KeyType.AUTH, tlv.mV); + mKeyFormats.put(KeyType.AUTH, new KeyFormat(tlv.mV)); break; case 0xC4: mPw1ValidForMultipleSignatures = tlv.mV[0] == 1; @@ -101,10 +101,6 @@ public class OpenPGPCapabilities { } } - private void parseAlgoCaps(KeyType keyType, byte[] data) { - mKeyFormats.put(keyType, AlgorithmFormat.from(data[5])); - } - private void parseExtendedCaps(byte[] v) { mHasSM = (v[0] & MASK_SM) != 0; mHasKeyImport = (v[0] & MASK_KEY_IMPORT) != 0; @@ -152,41 +148,7 @@ public class OpenPGPCapabilities { return mMaxRspLen; } - public AlgorithmFormat getFormatForKeyType(KeyType keyType) { + public KeyFormat getFormatForKeyType(KeyType keyType) { return mKeyFormats.get(keyType); } - - public enum AlgorithmFormat { - STANDARD(0, false, false), - STANDARD_WITH_MODULUS(1, false, true), - CRT(2, true, false), - CRT_WITH_MODULUS(3, true, true); - - private int mValue; - private boolean mHasModulus; - private boolean mHasExtra; - - AlgorithmFormat(int value, boolean hasExtra, boolean hasModulus) { - mValue = value; - mHasModulus = hasModulus; - mHasExtra = hasExtra; - } - - public boolean isHasModulus() { - return mHasModulus; - } - - public boolean isHasExtra() { - return mHasExtra; - } - - public static AlgorithmFormat from(byte b) { - for (AlgorithmFormat format : values()) { - if (format.mValue == b) { - return format; - } - } - return null; - } - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java index fd9b95af6..71777143d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java @@ -19,7 +19,6 @@ * along with this program. If not, see . */ - package org.sufficientlysecure.keychain.securitytoken; import android.support.annotation.NonNull; @@ -36,6 +35,7 @@ import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; +import org.sufficientlysecure.keychain.util.SecurityTokenUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -331,69 +331,11 @@ public class SecurityTokenHelper { verifyPin(0x83); // (Verify PW3 with mode 83) } - ByteArrayOutputStream stream = new ByteArrayOutputStream(), - template = new ByteArrayOutputStream(), - data = new ByteArrayOutputStream(), - res = new ByteArrayOutputStream(); - // Public modulus, length 3 - template.write(Hex.decode("9104")); - writeBits(data, crtSecretKey.getPublicExponent(), 4); - - // Prime P, length 128 - template.write(Hex.decode("928180")); - writeBits(data, crtSecretKey.getPrimeP(), 128); - - // Prime Q, length 128 - template.write(Hex.decode("938180")); - writeBits(data, crtSecretKey.getPrimeQ(), 128); - - OpenPGPCapabilities.AlgorithmFormat format = mOpenPGPCapabilities.getFormatForKeyType(slot); - if (format.isHasExtra()) { - // Coefficient (1/q mod p), length 128 - template.write(Hex.decode("948180")); - writeBits(data, crtSecretKey.getCrtCoefficient(), 128); - - // Prime exponent P (d mod (p - 1)), length 128 - template.write(Hex.decode("958180")); - writeBits(data, crtSecretKey.getPrimeExponentP(), 128); - - // Prime exponent Q (d mod (1 - 1)), length 128 - template.write(Hex.decode("968180")); - writeBits(data, crtSecretKey.getPrimeExponentQ(), 128); - } - - if (format.isHasModulus()) { - // Modulus, length 256, last item in private key template - template.write(Hex.decode("97820100")); - writeBits(data, crtSecretKey.getModulus(), 256); - } - - // Bundle up - - // Ext header list data - // Control Reference Template to indicate the private key - stream.write(slot.getSlot()); - stream.write(0); - - // Cardholder private key template - stream.write(Hex.decode("7F48")); - stream.write(encodeLength(template.size())); - stream.write(template.toByteArray()); - - // Concatenation of key data as defined in DO 7F48 - stream.write(Hex.decode("5F48")); - stream.write(encodeLength(data.size())); - stream.write(data.toByteArray()); - - - // Result tlv - res.write(Hex.decode("4D")); - res.write(encodeLength(stream.size())); - res.write(stream.toByteArray()); - - byte[] bytes = res.toByteArray(); // Now we're ready to communicate with the token. + byte[] bytes = SecurityTokenUtils.createPrivKeyTemplate(crtSecretKey, slot, + mOpenPGPCapabilities.getFormatForKeyType(slot)); + CommandAPDU apdu = new CommandAPDU(0x00, 0xDB, 0x3F, 0xFF, bytes); ResponseAPDU response = communicate(apdu); @@ -402,46 +344,6 @@ public class SecurityTokenHelper { } } - private byte[] encodeLength(int len) { - byte[] res; - if (len < 128) { - res = new byte[1]; - res[0] = (byte) len; - } else if (len < 256) { - res = new byte[2]; - res[0] = -127; - res[1] = (byte) len; - } else if (len < 65536) { - res = new byte[3]; - res[0] = -126; - res[1] = (byte) (len / 256); - res[2] = (byte) (len % 256); - } else { - res = new byte[4]; - if (len >= 16777216) { - throw new IllegalStateException("length [" + len + "] out of range (0x1000000)"); - } - - res[0] = -125; - res[1] = (byte) (len / 65536); - res[2] = (byte) (len / 256); - res[3] = (byte) (len % 256); - } - return res; - } - - private void writeBits(ByteArrayOutputStream stream, BigInteger value, int bits) { - byte[] prime = value.toByteArray(); - byte[] res = new byte[bits]; - int empty = bits - prime.length + 1; - - System.arraycopy(prime, 1, res, Math.max(0, empty), Math.min(prime.length - 1, bits)); - - stream.write(res, 0, bits); - Arrays.fill(res, (byte) 0); - Arrays.fill(prime, (byte) 0); - } - /** * Return fingerprints of all keys from application specific data stored * on tag, or null if data not available. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SecurityTokenUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SecurityTokenUtils.java new file mode 100644 index 000000000..2f959f912 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/SecurityTokenUtils.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.util; + +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.securitytoken.KeyFormat; +import org.sufficientlysecure.keychain.securitytoken.KeyType; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.interfaces.RSAPrivateCrtKey; + +public class SecurityTokenUtils { + public static byte[] createPrivKeyTemplate(RSAPrivateCrtKey secretKey, KeyType slot, + KeyFormat format) throws IOException { + ByteArrayOutputStream stream = new ByteArrayOutputStream(), + template = new ByteArrayOutputStream(), + data = new ByteArrayOutputStream(), + res = new ByteArrayOutputStream(); + + int expLengthBytes = (format.getExponentLength() + 7) / 8; + // Public exponent + template.write(new byte[]{(byte) 0x91, (byte) expLengthBytes}); + writeBits(data, secretKey.getPublicExponent(), expLengthBytes); + + // Prime P, length 128 + template.write(Hex.decode("928180")); + writeBits(data, secretKey.getPrimeP(), 128); + + // Prime Q, length 128 + template.write(Hex.decode("938180")); + writeBits(data, secretKey.getPrimeQ(), 128); + + + if (format.getAlgorithmFormat().isIncludeCrt()) { + // Coefficient (1/q mod p), length 128 + template.write(Hex.decode("948180")); + writeBits(data, secretKey.getCrtCoefficient(), 128); + + // Prime exponent P (d mod (p - 1)), length 128 + template.write(Hex.decode("958180")); + writeBits(data, secretKey.getPrimeExponentP(), 128); + + // Prime exponent Q (d mod (1 - 1)), length 128 + template.write(Hex.decode("968180")); + writeBits(data, secretKey.getPrimeExponentQ(), 128); + } + + if (format.getAlgorithmFormat().isIncludeModulus()) { + // Modulus, length 256, last item in private key template + template.write(Hex.decode("97820100")); + writeBits(data, secretKey.getModulus(), 256); + } + + // Bundle up + + // Ext header list data + // Control Reference Template to indicate the private key + stream.write(slot.getSlot()); + stream.write(0); + + // Cardholder private key template + stream.write(Hex.decode("7F48")); + stream.write(encodeLength(template.size())); + stream.write(template.toByteArray()); + + // Concatenation of key data as defined in DO 7F48 + stream.write(Hex.decode("5F48")); + stream.write(encodeLength(data.size())); + stream.write(data.toByteArray()); + + + // Result tlv + res.write(Hex.decode("4D")); + res.write(encodeLength(stream.size())); + res.write(stream.toByteArray()); + + return res.toByteArray(); + } + + public static byte[] encodeLength(int len) { + if (len < 0) { + throw new IllegalArgumentException("length is negative"); + } else if (len >= 16777216) { + throw new IllegalArgumentException("length is too big: " + len); + } + byte[] res; + if (len < 128) { + res = new byte[1]; + res[0] = (byte) len; + } else if (len < 256) { + res = new byte[2]; + res[0] = -127; + res[1] = (byte) len; + } else if (len < 65536) { + res = new byte[3]; + res[0] = -126; + res[1] = (byte) (len / 256); + res[2] = (byte) (len % 256); + } else { + res = new byte[4]; + + res[0] = -125; + res[1] = (byte) (len / 65536); + res[2] = (byte) (len / 256); + res[3] = (byte) (len % 256); + } + return res; + } + + public static void writeBits(ByteArrayOutputStream stream, BigInteger value, int width) { + if (value.signum() == -1) { + throw new IllegalArgumentException("value is negative"); + } else if (width <= 0) { + throw new IllegalArgumentException("width <= 0"); + } + + byte[] prime = value.toByteArray(); + int stripIdx = 0; + while (prime[stripIdx] == 0 && stripIdx + 1 < prime.length) { + stripIdx++; + } + + if (prime.length - stripIdx > width) { + throw new IllegalArgumentException("not enough width to fit value: " + + prime.length + "/" + width); + } + byte[] res = new byte[width]; + int empty = width - (prime.length - stripIdx); + + System.arraycopy(prime, stripIdx, res, Math.max(0, empty), Math.min(prime.length, width)); + + stream.write(res, 0, width); + Arrays.fill(res, (byte) 0); + Arrays.fill(prime, (byte) 0); + } +}