Code formatting and package re-structuring
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user