Code formatting and package re-structuring

This commit is contained in:
Dominik Schürmann
2017-01-05 13:56:09 +01:00
parent b89ba85313
commit 63244a113a
35 changed files with 131 additions and 142 deletions

View File

@@ -42,13 +42,15 @@ public class ECKeyFormat extends KeyFormat {
return mECAlgorithmFormat;
}
public ASN1ObjectIdentifier getCurveOID() { return mECCurveOID; }
public ASN1ObjectIdentifier getCurveOID() {
return mECCurveOID;
}
public enum ECAlgorithmFormat {
ECDH((byte)18, true, false),
ECDH_WITH_PUBKEY((byte)18, true, true),
ECDSA((byte)19, false, false),
ECDSA_WITH_PUBKEY((byte)19, false, true);
ECDH((byte) 18, true, false),
ECDH_WITH_PUBKEY((byte) 18, true, true),
ECDSA((byte) 19, false, false),
ECDSA_WITH_PUBKEY((byte) 19, false, true);
private final byte mValue;
private final boolean mIsECDH;
@@ -62,16 +64,24 @@ public class ECKeyFormat extends KeyFormat {
public static ECKeyFormat.ECAlgorithmFormat from(final byte bFirst, final byte bLast) {
for (ECKeyFormat.ECAlgorithmFormat format : values()) {
if (format.mValue == bFirst && ((bLast == (byte)0xff) == format.isWithPubkey())) {
if (format.mValue == bFirst && ((bLast == (byte) 0xff) == format.isWithPubkey())) {
return format;
}
}
return null;
}
public final byte getValue() { return mValue; }
public final boolean isECDH() { return mIsECDH; }
public final boolean isWithPubkey() { return mWithPubkey; }
public final byte getValue() {
return mValue;
}
public final boolean isECDH() {
return mIsECDH;
}
public final boolean isWithPubkey() {
return mWithPubkey;
}
}
public void addToSaveKeyringParcel(SaveKeyringParcel keyring, int keyFlags) {
@@ -80,7 +90,7 @@ public class ECKeyFormat extends KeyFormat {
SaveKeyringParcel.Algorithm algo = SaveKeyringParcel.Algorithm.ECDSA;
if (((keyFlags & KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS)
|| ((keyFlags & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE)) {
|| ((keyFlags & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE)) {
algo = SaveKeyringParcel.Algorithm.ECDH;
}

View File

@@ -0,0 +1,195 @@
/* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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 org.sufficientlysecure.keychain.securitytoken;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
/** TLV data structure and parser implementation.
*
* This class is used for parsing and working with TLV packets as specified in
* the Functional Specification of the OpenPGP application on ISO Smart Card
* Operating Systems, and ISO 7816-4.
*
* Objects of this class are immutable data structs parsed from BER-TLV data.
* They include at least the T, L and V fields they were originally parsed
* from, subclasses may also carry more specific information.
*
* @see { @linktourl http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-d.aspx}
*
*/
public class Iso7816TLV {
public final int mT;
public final int mL;
public final byte[] mV;
private Iso7816TLV(int T, int L, byte[] V) {
mT = T;
mL = L;
mV = V;
}
public String prettyPrint() {
return prettyPrint(0);
}
public String prettyPrint(int indent) {
// lol
String padding = " ".substring(0, indent*2);
return padding + String.format("tag T %4x L %04d", mT, mL);
}
/** Read a single Iso7816 TLV packet from a given ByteBuffer, either
* recursively or flat.
*
* This is a convenience wrapper for readSingle with ByteBuffer argument.
*/
public static Iso7816TLV readSingle(byte[] data, boolean recursive) throws IOException {
return readSingle(ByteBuffer.wrap(data), recursive);
}
/** Read a single Iso7816 TLV packet from a given ByteBuffer, either
* recursively or flat.
*
* If the recursive flag is true, a composite packet will be parsed and
* returned as an Iso7816CompositeTLV. Otherwise, a regular Iso7816TLV
* object will be returned regardless of actual packet type.
*
* This method is fail-fast, if any parsing error occurs it will throw an
* exception.
*
*/
public static Iso7816TLV readSingle(ByteBuffer data, boolean recursive) throws IOException {
int T = data.get() & 0xff;
boolean composite = (T & 0x20) == 0x20;
if ((T & 0x1f) == 0x1f) {
int T2 = data.get() & 0xff;
if ((T2 & 0x1f) == 0x1f) {
throw new IOException("Only tags up to two bytes are supported!");
}
T = (T << 8) | (T2 & 0x7f);
}
// Log.d(Constants.TAG, String.format("T %02x", T));
// parse length, according to ISO 7816-4 (openpgp card 2.0 specs, page 24)
int L = data.get() & 0xff;
if (L == 0x81) {
L = data.get() & 0xff;
} else if (L == 0x82) {
L = data.get() & 0xff;
L = (L << 8) | (data.get() & 0xff);
} else if (L >= 0x80) {
throw new IOException("Invalid length field!");
}
// Log.d(Constants.TAG, String.format("L %02x", L));
// read L bytes into new buffer
byte[] V = new byte[L];
data.get(V);
// if we are supposed to parse composites, do that
if (recursive && composite) {
// Log.d(Constants.TAG, "parsing composite TLV");
Iso7816TLV[] subs = readList(V, true);
return new Iso7816CompositeTLV(T, L, V, subs);
}
return new Iso7816TLV(T, L, V);
}
/** Parse a list of TLV packets from byte data, recursively or flat.
*
* This method is fail-fast, if any parsing error occurs it will throw an
* exception.
*
*/
public static Iso7816TLV[] readList(byte[] data, boolean recursive) throws IOException {
ByteBuffer buf = ByteBuffer.wrap(data);
ArrayList<Iso7816TLV> result = new ArrayList<>();
// read while data is available. this will fail if there is trailing data!
while (buf.hasRemaining()) {
// skip 0x00 and 0xFF filler bytes
buf.mark();
byte peek = buf.get();
if (peek == 0xff || peek == 0x00) {
continue;
}
buf.reset();
Iso7816TLV packet = readSingle(buf, recursive);
result.add(packet);
}
Iso7816TLV[] resultX = new Iso7816TLV[result.size()];
result.toArray(resultX);
return resultX;
}
/** This class represents a composite TLV packet.
*
* Note that only actual composite TLV packets are instances of this class.
* A regular non-composite Iso7816TLV object may however be a composite
* packet if it was parsed non-recursively.
*
*/
public static class Iso7816CompositeTLV extends Iso7816TLV {
public final Iso7816TLV[] mSubs;
public Iso7816CompositeTLV(int T, int L, byte[] V, Iso7816TLV[] subs) {
super(T, L, V);
mSubs = subs;
}
public String prettyPrint(int indent) {
StringBuilder result = new StringBuilder();
// lol
result.append(" ".substring(0, indent*2));
result.append(String.format("composite tag T %4x L %04d", mT, mL));
for (Iso7816TLV sub : mSubs) {
result.append('\n');
result.append(sub.prettyPrint(indent+1));
}
return result.toString();
}
}
/** Recursively searches for a specific tag in a composite TLV packet structure, depth first. */
public static Iso7816TLV findRecursive(Iso7816TLV in, int tag) {
if (in.mT == tag) {
return in;
} else if (in instanceof Iso7816CompositeTLV) {
for (Iso7816TLV sub : ((Iso7816CompositeTLV) in).mSubs) {
Iso7816TLV result = findRecursive(sub, tag);
if (result != null) {
return result;
}
}
}
return null;
}
}

View File

@@ -17,8 +17,6 @@
package org.sufficientlysecure.keychain.securitytoken;
import org.sufficientlysecure.keychain.util.Iso7816TLV;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@@ -108,7 +106,7 @@ public class OpenPgpCapabilities {
private void parseExtendedCaps(byte[] v) {
mHasSM = (v[0] & MASK_SM) != 0;
mHasKeyImport = (v[0] & MASK_KEY_IMPORT) != 0;
mAttriburesChangable =(v[0] & MASK_ATTRIBUTES_CHANGABLE) != 0;
mAttriburesChangable = (v[0] & MASK_ATTRIBUTES_CHANGABLE) != 0;
mSMAESKeySize = (v[1] == 1) ? 16 : 32;

View File

@@ -47,10 +47,10 @@ public class RSAKeyFormat extends KeyFormat {
}
public enum RSAAlgorithmFormat {
STANDARD((byte)0, false, false),
STANDARD_WITH_MODULUS((byte)1, false, true),
CRT((byte)2, true, false),
CRT_WITH_MODULUS((byte)3, true, true);
STANDARD((byte) 0, false, false),
STANDARD_WITH_MODULUS((byte) 1, false, true),
CRT((byte) 2, true, false),
CRT_WITH_MODULUS((byte) 3, true, true);
private byte mValue;
private boolean mIncludeModulus;
@@ -71,7 +71,9 @@ public class RSAKeyFormat extends KeyFormat {
return null;
}
public byte getValue() { return mValue; }
public byte getValue() {
return mValue;
}
public boolean isIncludeModulus() {
return mIncludeModulus;

View File

@@ -18,9 +18,7 @@
package org.sufficientlysecure.keychain.securitytoken;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.util.Log;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
@@ -28,19 +26,11 @@ import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Iterable;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.ui.SettingsSmartPGPAuthoritiesActivity;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Iso7816TLV;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.SecurityTokenUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
@@ -55,7 +45,6 @@ import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathBuilderException;
import java.security.cert.CertStore;
@@ -65,7 +54,6 @@ import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXCertPathBuilderResult;
import java.security.cert.TrustAnchor;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
@@ -75,10 +63,6 @@ import java.security.spec.ECParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@@ -89,7 +73,6 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.TrustManagerFactory;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;

View File

@@ -1,6 +1,6 @@
/*
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
* Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2013-2017 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
* Copyright (C) 2013-2014 Signe Rüsch
* Copyright (C) 2013-2014 Philipp Jakubeit
@@ -48,11 +48,10 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
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;
@@ -101,7 +100,7 @@ public class SecurityTokenHelper {
private boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming?
private boolean mPw3Validated;
protected SecurityTokenHelper() {
private SecurityTokenHelper() {
}
public static double parseOpenPgpVersion(final byte[] aid) {
@@ -169,12 +168,14 @@ public class SecurityTokenHelper {
private boolean isSlotEmpty(KeyType keyType) throws IOException {
// Note: special case: This should not happen, but happens with
// https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true
if (getKeyFingerprint(keyType) == null) return true;
if (getKeyFingerprint(keyType) == null) {
return true;
}
return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT);
}
public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException {
private boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException {
return java.util.Arrays.equals(getKeyFingerprint(keyType), fingerprint);
}
@@ -208,7 +209,7 @@ public class SecurityTokenHelper {
if (mOpenPgpCapabilities.isHasSM()) {
try {
SCP11bSecureMessaging.establish(this, ctx);
} catch(SecureMessagingException e) {
} catch (SecureMessagingException e) {
mSecureMessaging = null;
Log.e(Constants.TAG, "failed to establish secure messaging", e);
}
@@ -276,7 +277,7 @@ public class SecurityTokenHelper {
byte[] data;
int pLen = 0;
X9ECParameters x9Params = null;
X9ECParameters x9Params;
switch (kf.keyFormatType()) {
case RSAKeyFormatType:
@@ -292,7 +293,7 @@ public class SecurityTokenHelper {
System.arraycopy(encryptedSessionKey, 2, data, 0, pLen);
final ECKeyFormat eckf = (ECKeyFormat)kf;
final ECKeyFormat eckf = (ECKeyFormat) kf;
x9Params = NISTNamedCurves.getByOID(eckf.getCurveOID());
final ECPoint p = x9Params.getCurve().decodePoint(data);
@@ -303,15 +304,15 @@ public class SecurityTokenHelper {
data = p.getEncoded(false);
data = Arrays.concatenate(
Hex.decode("86"),
new byte[]{ (byte)data.length },
new byte[]{(byte) data.length},
data);
data = Arrays.concatenate(
Hex.decode("7F49"),
new byte[] { (byte)data.length },
new byte[]{(byte) data.length},
data);
data = Arrays.concatenate(
Hex.decode("A6"),
new byte[] { (byte)data.length },
new byte[]{(byte) data.length},
data);
break;
@@ -353,7 +354,7 @@ public class SecurityTokenHelper {
try {
final MessageDigest kdf = MessageDigest.getInstance(MessageDigestUtils.getDigestName(publicKey.getSecurityTokenHashAlgorithm()));
kdf.update(new byte[]{ (byte)0, (byte)0, (byte)0, (byte)1 });
kdf.update(new byte[]{(byte) 0, (byte) 0, (byte) 0, (byte) 1});
kdf.update(data);
kdf.update(publicKey.createUserKeyingMaterial(fingerprintCalculator));
@@ -364,7 +365,7 @@ public class SecurityTokenHelper {
final Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY);
Arrays.fill(kek, (byte)0);
Arrays.fill(kek, (byte) 0);
return PGPPad.unpadSessionData(paddedSessionKey.getEncoded());
} catch (NoSuchAlgorithmException e) {
@@ -443,7 +444,7 @@ public class SecurityTokenHelper {
private void setKeyAttributes(final KeyType slot, final CanonicalizedSecretKey secretKey)
throws IOException {
throws IOException {
if (mOpenPgpCapabilities.isAttributesChangable()) {
int tag;
@@ -619,10 +620,10 @@ public class SecurityTokenHelper {
}
dsi = Arrays.concatenate(Hex.decode(
"3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes
+ "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes
+ "0605" + "2B0E03021A" // OID of SHA1
+ "0500" // TLV coding of ZERO
+ "0414"), hash); // 0x14 are 20 hash bytes
+ "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes
+ "0605" + "2B0E03021A" // OID of SHA1
+ "0500" // TLV coding of ZERO
+ "0414"), hash); // 0x14 are 20 hash bytes
break;
case HashAlgorithmTags.RIPEMD160:
if (hash.length != 20) {
@@ -700,13 +701,13 @@ public class SecurityTokenHelper {
}
final byte[] br = new byte[signature.length / 2];
final byte[] bs = new byte[signature.length / 2];
for(int i = 0; i < br.length; ++i) {
for (int i = 0; i < br.length; ++i) {
br[i] = signature[i];
bs[i] = signature[br.length + i];
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
ASN1OutputStream out = new ASN1OutputStream(baos);
out.writeObject(new DERSequence(new ASN1Encodable[] { new ASN1Integer(br), new ASN1Integer(bs) }));
out.writeObject(new DERSequence(new ASN1Encodable[]{new ASN1Integer(br), new ASN1Integer(bs)}));
out.flush();
signature = baos.toByteArray();
break;
@@ -720,6 +721,7 @@ public class SecurityTokenHelper {
* Transceives APDU
* Splits extended APDU into short APDUs and chains them if necessary
* Performs GET RESPONSE command(ISO/IEC 7816-4 par.7.6.1) on retrieving if necessary
*
* @param apdu short or extended APDU to transceive
* @return response from the card
* @throws IOException
@@ -730,7 +732,7 @@ public class SecurityTokenHelper {
apdu = mSecureMessaging.encryptAndSign(apdu);
} catch (SecureMessagingException e) {
clearSecureMessaging();
throw new IOException("secure messaging encrypt/sign failure : " + e. getMessage());
throw new IOException("secure messaging encrypt/sign failure : " + e.getMessage());
}
}
@@ -787,7 +789,7 @@ public class SecurityTokenHelper {
lastResponse = mSecureMessaging.verifyAndDecrypt(lastResponse);
} catch (SecureMessagingException e) {
clearSecureMessaging();
throw new IOException("secure messaging verify/decrypt failure : " + e. getMessage());
throw new IOException("secure messaging verify/decrypt failure : " + e.getMessage());
}
}
@@ -923,7 +925,7 @@ public class SecurityTokenHelper {
return mTransport != null &&
mTransport.isPersistentConnectionAllowed() &&
(mSecureMessaging == null ||
!mSecureMessaging.isEstablished());
!mSecureMessaging.isEstablished());
}
public boolean isConnected() {
@@ -931,7 +933,7 @@ public class SecurityTokenHelper {
}
public void clearSecureMessaging() {
if(mSecureMessaging != null) {
if (mSecureMessaging != null) {
mSecureMessaging.clearSession();
}
mSecureMessaging = null;

View File

@@ -0,0 +1,242 @@
/*
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.securitytoken;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.securitytoken.ECKeyFormat;
import org.sufficientlysecure.keychain.securitytoken.RSAKeyFormat;
import org.sufficientlysecure.keychain.securitytoken.KeyType;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
public class SecurityTokenUtils {
public static byte[] attributesFromSecretKey(final KeyType slot, final CanonicalizedSecretKey secretKey) throws IOException, PgpGeneralException {
if (secretKey.isRSA()) {
final int mModulusLength = secretKey.getBitStrength();
final int mExponentLength = secretKey.getSecurityTokenRSASecretKey().getPublicExponent().bitLength();
final byte[] attrs = new byte[6];
int i = 0;
attrs[i++] = (byte) 0x01;
attrs[i++] = (byte) ((mModulusLength >> 8) & 0xff);
attrs[i++] = (byte) (mModulusLength & 0xff);
attrs[i++] = (byte) ((mExponentLength >> 8) & 0xff);
attrs[i++] = (byte) (mExponentLength & 0xff);
attrs[i++] = RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS.getValue();
return attrs;
} else if (secretKey.isEC()) {
final byte[] oid = new ASN1ObjectIdentifier(secretKey.getCurveOid()).getEncoded();
final byte[] attrs = new byte[1 + (oid.length - 2) + 1];
if (slot.equals(KeyType.SIGN))
attrs[0] = ECKeyFormat.ECAlgorithmFormat.ECDSA_WITH_PUBKEY.getValue();
else {
attrs[0] = ECKeyFormat.ECAlgorithmFormat.ECDH_WITH_PUBKEY.getValue();
}
System.arraycopy(oid, 2, attrs, 1, (oid.length - 2));
attrs[attrs.length - 1] = (byte) 0xff;
return attrs;
} else {
throw new IOException("Unsupported key type");
}
}
public static byte[] createRSAPrivKeyTemplate(RSAPrivateCrtKey secretKey, KeyType slot,
RSAKeyFormat 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[] createECPrivKeyTemplate(ECPrivateKey secretKey, ECPublicKey publicKey, KeyType slot,
ECKeyFormat format) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream(),
template = new ByteArrayOutputStream(),
data = new ByteArrayOutputStream(),
res = new ByteArrayOutputStream();
final int csize = (int) Math.ceil(publicKey.getParams().getCurve().getField().getFieldSize() / 8.0);
writeBits(data, secretKey.getS(), csize);
template.write(Hex.decode("92"));
template.write(encodeLength(data.size()));
if (format.getAlgorithmFormat().isWithPubkey()) {
data.write(Hex.decode("04"));
writeBits(data, publicKey.getW().getAffineX(), csize);
writeBits(data, publicKey.getW().getAffineY(), csize);
template.write(Hex.decode("99"));
template.write(encodeLength(1 + 2 * csize));
}
// 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");
}
final byte[] prime = value.toByteArray();
int skip = 0;
while ((skip < prime.length) && (prime[skip] == 0)) ++skip;
if ((prime.length - skip) > width) {
throw new IllegalArgumentException("not enough width to fit value: "
+ (prime.length - skip) + "/" + width);
}
byte[] res = new byte[width];
System.arraycopy(prime, skip,
res, width - (prime.length - skip),
prime.length - skip);
stream.write(res, 0, width);
Arrays.fill(res, (byte) 0);
Arrays.fill(prime, (byte) 0);
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.securitytoken;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity;
import org.sufficientlysecure.keychain.util.Log;
public class UsbConnectionDispatcher {
private Activity mActivity;
private OnDiscoveredUsbDeviceListener mListener;
private UsbManager mUsbManager;
/**
* Receives broadcast when a supported USB device get permission.
*/
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case UsbEventReceiverActivity.ACTION_USB_PERMISSION: {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,
false);
if (permission) {
Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName());
mListener.usbDeviceDiscovered(usbDevice);
}
break;
}
}
}
};
public UsbConnectionDispatcher(final Activity activity, final OnDiscoveredUsbDeviceListener listener) {
this.mActivity = activity;
this.mListener = listener;
this.mUsbManager = (UsbManager) activity.getSystemService(Context.USB_SERVICE);
}
public void onStart() {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION);
mActivity.registerReceiver(mUsbReceiver, intentFilter);
}
public void onStop() {
mActivity.unregisterReceiver(mUsbReceiver);
}
/**
* Rescans devices and triggers {@link OnDiscoveredUsbDeviceListener}
*/
public void rescanDevices() {
// Note: we don't check devices VID/PID because
// we check for permission instead.
// We should have permission only for matching devices
for (UsbDevice device : mUsbManager.getDeviceList().values()) {
if (mUsbManager.hasPermission(device)) {
if (mListener != null) {
mListener.usbDeviceDiscovered(device);
}
break;
}
}
}
public interface OnDiscoveredUsbDeviceListener {
void usbDeviceDiscovered(UsbDevice usbDevice);
}
}