refactor OpenPgpCapabilities to use AutoValue

This commit is contained in:
Vincent Breitmoser
2018-01-12 20:41:01 +01:00
parent 401b90a493
commit abf5e5d170
12 changed files with 215 additions and 189 deletions

View File

@@ -18,159 +18,223 @@
package org.sufficientlysecure.keychain.securitytoken;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.nio.ByteBuffer;
import android.support.annotation.NonNull;
import com.google.auto.value.AutoValue;
import org.jetbrains.annotations.Nullable;
@SuppressWarnings("unused") // just expose all included data
public class OpenPgpCapabilities {
@AutoValue
public abstract class OpenPgpCapabilities {
private final static int MASK_SM = 1 << 7;
private final static int MASK_KEY_IMPORT = 1 << 5;
private final static int MASK_ATTRIBUTES_CHANGABLE = 1 << 2;
private byte[] mAid;
private byte[] mHistoricalBytes;
private static final int MAX_PW1_LENGTH_INDEX = 1;
private static final int MAX_PW3_LENGTH_INDEX = 3;
private boolean mHasSM;
private boolean mAttriburesChangable;
private boolean mHasKeyImport;
public abstract byte[] getAid();
abstract byte[] getHistoricalBytes();
private int mSMType;
private int mMaxCmdLen;
private int mMaxRspLen;
@Nullable
@SuppressWarnings("mutable")
public abstract byte[] getFingerprintSign();
@Nullable
@SuppressWarnings("mutable")
public abstract byte[] getFingerprintEncrypt();
@Nullable
@SuppressWarnings("mutable")
public abstract byte[] getFingerprintAuth();
public abstract byte[] getPwStatusBytes();
private Map<KeyType, KeyFormat> mKeyFormats;
private byte[] mFingerprints;
private byte[] mPwStatusBytes;
public abstract KeyFormat getSignKeyFormat();
public abstract KeyFormat getEncryptKeyFormat();
public abstract KeyFormat getAuthKeyFormat();
public OpenPgpCapabilities(byte[] data) throws IOException {
mKeyFormats = new HashMap<>();
updateWithData(data);
abstract boolean isHasKeyImport();
public abstract boolean isAttributesChangable();
abstract boolean isHasSM();
abstract boolean isHasAesSm();
abstract boolean isHasScp11bSm();
@Nullable
abstract Integer getMaxCmdLen();
@Nullable
abstract Integer getMaxRspLen();
public static OpenPgpCapabilities fromBytes(byte[] rawOpenPgpCapabilities) throws IOException {
Iso7816TLV[] parsedTlvData = Iso7816TLV.readList(rawOpenPgpCapabilities, true);
return new AutoValue_OpenPgpCapabilities.Builder().updateWithTLV(parsedTlvData).build();
}
private void updateWithData(byte[] data) throws IOException {
Iso7816TLV[] tlvs = Iso7816TLV.readList(data, true);
if (tlvs.length == 1 && tlvs[0].mT == 0x6E) {
tlvs = ((Iso7816TLV.Iso7816CompositeTLV) tlvs[0]).mSubs;
public KeyFormat getFormatForKeyType(@NonNull KeyType keyType) {
switch (keyType) {
case SIGN: return getSignKeyFormat();
case ENCRYPT: return getEncryptKeyFormat();
case AUTH: return getAuthKeyFormat();
}
return null;
}
for (Iso7816TLV tlv : tlvs) {
switch (tlv.mT) {
case 0x4F:
mAid = tlv.mV;
break;
case 0x5F52:
mHistoricalBytes = tlv.mV;
break;
case 0x73:
parseDdo((Iso7816TLV.Iso7816CompositeTLV) tlv);
break;
case 0xC0:
parseExtendedCaps(tlv.mV);
break;
case 0xC1:
mKeyFormats.put(KeyType.SIGN, KeyFormat.fromBytes(tlv.mV));
break;
case 0xC2:
mKeyFormats.put(KeyType.ENCRYPT, KeyFormat.fromBytes(tlv.mV));
break;
case 0xC3:
mKeyFormats.put(KeyType.AUTH, KeyFormat.fromBytes(tlv.mV));
break;
case 0xC4:
mPwStatusBytes = tlv.mV;
break;
case 0xC5:
mFingerprints = tlv.mV;
break;
}
@Nullable
public byte[] getKeyFingerprint(@NonNull KeyType keyType) {
switch (keyType) {
case SIGN: return getFingerprintSign();
case ENCRYPT: return getFingerprintEncrypt();
case AUTH: return getFingerprintAuth();
}
}
private void parseDdo(Iso7816TLV.Iso7816CompositeTLV tlvs) {
for (Iso7816TLV tlv : tlvs.mSubs) {
switch (tlv.mT) {
case 0xC0:
parseExtendedCaps(tlv.mV);
break;
case 0xC1:
mKeyFormats.put(KeyType.SIGN, KeyFormat.fromBytes(tlv.mV));
break;
case 0xC2:
mKeyFormats.put(KeyType.ENCRYPT, KeyFormat.fromBytes(tlv.mV));
break;
case 0xC3:
mKeyFormats.put(KeyType.AUTH, KeyFormat.fromBytes(tlv.mV));
break;
case 0xC4:
mPwStatusBytes = tlv.mV;
break;
case 0xC5:
mFingerprints = tlv.mV;
break;
}
}
}
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;
mSMType = v[1];
mMaxCmdLen = (v[6] << 8) + v[7];
mMaxRspLen = (v[8] << 8) + v[9];
}
byte[] getAid() {
return mAid;
}
byte[] getPwStatusBytes() {
return mPwStatusBytes;
return null;
}
boolean isPw1ValidForMultipleSignatures() {
return mPwStatusBytes[0] == 1;
return getPwStatusBytes()[0] == 1;
}
byte[] getHistoricalBytes() {
return mHistoricalBytes;
public int getPw1MaxLength() {
return getPwStatusBytes()[MAX_PW1_LENGTH_INDEX];
}
boolean isHasSM() {
return mHasSM;
public int getPw3MaxLength() {
return getPwStatusBytes()[MAX_PW3_LENGTH_INDEX];
}
public boolean isAttributesChangable() {
return mAttriburesChangable;
}
@AutoValue.Builder
@SuppressWarnings("UnusedReturnValue")
abstract static class Builder {
abstract Builder aid(byte[] mV);
abstract Builder historicalBytes(byte[] historicalBytes);
boolean isHasKeyImport() {
return mHasKeyImport;
}
abstract Builder fingerprintSign(byte[] fingerprint);
abstract Builder fingerprintEncrypt(byte[] fingerprint);
abstract Builder fingerprintAuth(byte[] fingerprint);
boolean isHasAESSM() {
return isHasSM() && ((mSMType == 1) || (mSMType == 2));
}
abstract Builder pwStatusBytes(byte[] mV);
abstract Builder authKeyFormat(KeyFormat keyFormat);
abstract Builder encryptKeyFormat(KeyFormat keyFormat);
abstract Builder signKeyFormat(KeyFormat keyFormat);
boolean isHasSCP11bSM() {
return isHasSM() && (mSMType == 3);
}
int getMaxCmdLen() {
return mMaxCmdLen;
}
abstract Builder hasKeyImport(boolean hasKeyImport);
abstract Builder attributesChangable(boolean attributesChangable);
int getMaxRspLen() {
return mMaxRspLen;
}
abstract Builder hasSM(boolean hasSm);
abstract Builder hasAesSm(boolean hasAesSm);
abstract Builder hasScp11bSm(boolean hasScp11bSm);
public KeyFormat getFormatForKeyType(KeyType keyType) {
return mKeyFormats.get(keyType);
}
abstract Builder maxCmdLen(Integer maxCommandLen);
abstract Builder maxRspLen(Integer MaxResponseLen);
abstract OpenPgpCapabilities build();
public Builder() {
hasKeyImport(false);
attributesChangable(false);
hasSM(false);
hasAesSm(false);
hasScp11bSm(false);
}
Builder updateWithTLV(Iso7816TLV[] tlvs) {
if (tlvs.length == 1 && tlvs[0].mT == 0x6E) {
tlvs = ((Iso7816TLV.Iso7816CompositeTLV) tlvs[0]).mSubs;
}
for (Iso7816TLV tlv : tlvs) {
switch (tlv.mT) {
case 0x4F:
aid(tlv.mV);
break;
case 0x5F52:
historicalBytes(tlv.mV);
break;
case 0x73:
parseDdo((Iso7816TLV.Iso7816CompositeTLV) tlv);
break;
case 0xC0:
parseExtendedCaps(tlv.mV);
break;
case 0xC1:
signKeyFormat(KeyFormat.fromBytes(tlv.mV));
break;
case 0xC2:
encryptKeyFormat(KeyFormat.fromBytes(tlv.mV));
break;
case 0xC3:
authKeyFormat(KeyFormat.fromBytes(tlv.mV));
break;
case 0xC4:
pwStatusBytes(tlv.mV);
break;
case 0xC5:
parseFingerprints(tlv.mV);
break;
}
}
return this;
}
private void parseDdo(Iso7816TLV.Iso7816CompositeTLV tlvs) {
for (Iso7816TLV tlv : tlvs.mSubs) {
switch (tlv.mT) {
case 0xC0:
parseExtendedCaps(tlv.mV);
break;
case 0xC1:
signKeyFormat(KeyFormat.fromBytes(tlv.mV));
break;
case 0xC2:
encryptKeyFormat(KeyFormat.fromBytes(tlv.mV));
break;
case 0xC3:
authKeyFormat(KeyFormat.fromBytes(tlv.mV));
break;
case 0xC4:
pwStatusBytes(tlv.mV);
break;
case 0xC5:
parseFingerprints(tlv.mV);
break;
}
}
}
private void parseFingerprints(byte[] mV) {
ByteBuffer fpBuf = ByteBuffer.wrap(mV);
byte[] buf;
buf = new byte[20];
fpBuf.get(buf);
fingerprintSign(buf);
buf = new byte[20];
fpBuf.get(buf);
fingerprintEncrypt(buf);
buf = new byte[20];
fpBuf.get(buf);
fingerprintAuth(buf);
}
private void parseExtendedCaps(byte[] v) {
hasKeyImport((v[0] & MASK_KEY_IMPORT) != 0);
attributesChangable((v[0] & MASK_ATTRIBUTES_CHANGABLE) != 0);
if ((v[0] & MASK_SM) != 0) {
hasSM(true);
int smType = v[1];
hasAesSm(smType == 1 || smType == 2);
hasScp11bSm(smType == 3);
}
maxCmdLen((v[6] << 8) + v[7]);
maxRspLen((v[8] << 8) + v[9]);
}
public byte[] getFingerprints() {
return mFingerprints;
}
}

View File

@@ -155,7 +155,7 @@ public class SecurityTokenConnection {
public void refreshConnectionCapabilities() throws IOException {
byte[] rawOpenPgpCapabilities = getData(0x00, 0x6E);
OpenPgpCapabilities openPgpCapabilities = new OpenPgpCapabilities(rawOpenPgpCapabilities);
OpenPgpCapabilities openPgpCapabilities = OpenPgpCapabilities.fromBytes(rawOpenPgpCapabilities);
setConnectionCapabilities(openPgpCapabilities);
}
@@ -249,7 +249,7 @@ public class SecurityTokenConnection {
// region secure messaging
private void smEstablishIfAvailable(Context context) throws IOException {
if (!openPgpCapabilities.isHasSCP11bSM()) {
if (!openPgpCapabilities.isHasAesSm()) {
return;
}
@@ -379,19 +379,15 @@ public class SecurityTokenConnection {
}
public SecurityTokenInfo getTokenInfo() throws IOException {
byte[] rawFingerprints = openPgpCapabilities.getFingerprints();
byte[][] fingerprints = new byte[3][];
fingerprints[0] = openPgpCapabilities.getFingerprintSign();
fingerprints[1] = openPgpCapabilities.getFingerprintEncrypt();
fingerprints[2] = openPgpCapabilities.getFingerprintAuth();
byte[][] fingerprints = new byte[rawFingerprints.length / 20][];
ByteBuffer buf = ByteBuffer.wrap(rawFingerprints);
for (int i = 0; i < rawFingerprints.length / 20; i++) {
fingerprints[i] = new byte[20];
buf.get(fingerprints[i]);
}
byte[] aid = getAid();
byte[] aid = openPgpCapabilities.getAid();
String userId = parseHolderName(getUserId());
String url = getUrl();
byte[] pwInfo = getPwStatusBytes();
byte[] pwInfo = openPgpCapabilities.getPwStatusBytes();
boolean hasLifeCycleManagement = cardCapabilities.hasLifeCycleManagement();
TransportType transportType = transport.getTransportType();
@@ -423,29 +419,6 @@ public class SecurityTokenConnection {
return commandFactory;
}
public byte[] getPwStatusBytes() {
return openPgpCapabilities.getPwStatusBytes();
}
public byte[] getAid() {
return openPgpCapabilities.getAid();
}
public byte[] getKeyFingerprint(@NonNull KeyType keyType) {
byte[] data = openPgpCapabilities.getFingerprints();
if (data == null) {
return null;
}
// return the master key fingerprint
ByteBuffer fpbuf = ByteBuffer.wrap(data);
byte[] fp = new byte[20];
fpbuf.position(keyType.getIdx() * 20);
fpbuf.get(fp, 0, 20);
return fp;
}
private static String parseHolderName(byte[] name) {
try {

View File

@@ -11,7 +11,6 @@ import org.sufficientlysecure.keychain.util.Passphrase;
public class ModifyPinTokenOp {
private static final int MAX_PW3_LENGTH_INDEX = 3;
private static final int MIN_PW3_LENGTH = 8;
private final SecurityTokenConnection connection;
@@ -41,9 +40,8 @@ public class ModifyPinTokenOp {
private void modifyPw1PinWithEffectiveAdminPin(Passphrase effectiveAdminPin, byte[] newPin) throws IOException {
connection.verifyAdminPin(effectiveAdminPin);
final int MAX_PW1_LENGTH_INDEX = 1;
byte[] pwStatusBytes = connection.getPwStatusBytes();
if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) {
int maxPw1Length = connection.getOpenPgpCapabilities().getPw3MaxLength();
if (newPin.length < 6 || newPin.length > maxPw1Length) {
throw new IOException("Invalid PIN length");
}
@@ -60,9 +58,9 @@ public class ModifyPinTokenOp {
* conformance to the token's requirements for key length.
*/
private void modifyPw3Pin(byte[] newAdminPin) throws IOException {
byte[] pwStatusBytes = connection.getPwStatusBytes();
int maxPw3Length = connection.getOpenPgpCapabilities().getPw3MaxLength();
if (newAdminPin.length < MIN_PW3_LENGTH || newAdminPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) {
if (newAdminPin.length < MIN_PW3_LENGTH || newAdminPin.length > maxPw3Length) {
throw new IOException("Invalid PIN length");
}

View File

@@ -53,7 +53,7 @@ public class PsoDecryptTokenOp {
throws IOException {
connection.verifyPinForOther();
KeyFormat kf = connection.getOpenPgpCapabilities().getFormatForKeyType(KeyType.ENCRYPT);
KeyFormat kf = connection.getOpenPgpCapabilities().getEncryptKeyFormat();
switch (kf.keyFormatType()) {
case RSAKeyFormatType:
return decryptSessionKeyRsa(encryptedSessionKeyMpi);

View File

@@ -7,6 +7,7 @@ import java.nio.ByteBuffer;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.util.Arrays;
import android.support.annotation.VisibleForTesting;
@@ -174,14 +175,15 @@ public class SecurityTokenChangeKeyTokenOp {
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 (connection.getKeyFingerprint(keyType) == null) {
if (connection.getOpenPgpCapabilities().getKeyFingerprint(keyType) == null) {
return true;
}
return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT);
}
private boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException {
return java.util.Arrays.equals(connection.getKeyFingerprint(keyType), fingerprint);
private boolean keyMatchesFingerPrint(KeyType keyType, byte[] expectedFingerprint) throws IOException {
byte[] actualFp = connection.getOpenPgpCapabilities().getKeyFingerprint(keyType);
return Arrays.equals(actualFp, expectedFingerprint);
}
}

View File

@@ -144,7 +144,7 @@ public class SecurityTokenPsoSignTokenOp {
connection.verifyPinForSignature();
OpenPgpCapabilities openPgpCapabilities = connection.getOpenPgpCapabilities();
KeyFormat signKeyFormat = openPgpCapabilities.getFormatForKeyType(KeyType.SIGN);
KeyFormat signKeyFormat = openPgpCapabilities.getSignKeyFormat();
byte[] data = prepareData(hash, hashAlgo, signKeyFormat);
@@ -171,7 +171,7 @@ public class SecurityTokenPsoSignTokenOp {
connection.verifyPinForOther();
OpenPgpCapabilities openPgpCapabilities = connection.getOpenPgpCapabilities();
KeyFormat authKeyFormat = openPgpCapabilities.getFormatForKeyType(KeyType.AUTH);
KeyFormat authKeyFormat = openPgpCapabilities.getAuthKeyFormat();
byte[] data = prepareData(hash, hashAlgo, authKeyFormat);

View File

@@ -194,7 +194,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity {
switch (mRequiredInput.mType) {
case SECURITY_TOKEN_DECRYPT: {
long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(
stConnection.getKeyFingerprint(KeyType.ENCRYPT));
stConnection.getOpenPgpCapabilities().getFingerprintEncrypt());
if (tokenKeyId != mRequiredInput.getSubKeyId()) {
throw new IOException(getString(R.string.error_wrong_security_token));
@@ -221,7 +221,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity {
}
case SECURITY_TOKEN_SIGN: {
long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(
stConnection.getKeyFingerprint(KeyType.SIGN));
stConnection.getOpenPgpCapabilities().getFingerprintSign());
if (tokenKeyId != mRequiredInput.getSubKeyId()) {
throw new IOException(getString(R.string.error_wrong_security_token));
@@ -240,7 +240,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity {
}
case SECURITY_TOKEN_AUTH: {
long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(
stConnection.getKeyFingerprint(KeyType.AUTH));
stConnection.getOpenPgpCapabilities().getFingerprintAuth());
if (tokenKeyId != mRequiredInput.getSubKeyId()) {
throw new IOException(getString(R.string.error_wrong_security_token));
@@ -280,7 +280,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity {
long subkeyId = buf.getLong();
CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId);
byte[] tokenSerialNumber = Arrays.copyOf(stConnection.getAid(), 16);
byte[] tokenSerialNumber = Arrays.copyOf(stConnection.getOpenPgpCapabilities().getAid(), 16);
Passphrase passphrase;
try {

View File

@@ -20,20 +20,13 @@ package org.sufficientlysecure.keychain.ui.util;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.Locale;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.support.annotation.NonNull;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
@@ -42,7 +35,6 @@ import android.widget.ViewAnimator;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
@@ -52,13 +44,11 @@ import org.bouncycastle.util.encoders.Hex;
import org.openintents.openpgp.OpenPgpDecryptionResult;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpUtils;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.util.Log;
public class KeyFormattingUtils {

View File

@@ -41,7 +41,7 @@ public class PsoDecryptTokenOpTest {
@Test
public void testRsaDecrypt() throws Exception {
OpenPgpCapabilities openPgpCapabilities = new OpenPgpCapabilities(
OpenPgpCapabilities openPgpCapabilities = OpenPgpCapabilities.fromBytes(
Hex.decode("6e81de4f10d27600012401020000060364311500005f520f0073000080000000000000000000007381b7c00af" +
"00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f03" +
"0303c53c4ec5fee25c4e89654d58cad8492510a89d3c3d8468da7b24e15bfc624c6a792794f15b759" +

View File

@@ -90,7 +90,7 @@ public class SecurityTokenConnectionTest {
public void test_getTokenInfo() throws Exception {
SecurityTokenConnection securityTokenConnection =
new SecurityTokenConnection(transport, new Passphrase("123456"), new OpenPgpCommandApduFactory());
OpenPgpCapabilities openPgpCapabilities = new OpenPgpCapabilities(
OpenPgpCapabilities openPgpCapabilities = OpenPgpCapabilities.fromBytes(
Hex.decode(
"6e81de4f10d27600012401020000060364311500005f520f0073000080000000000000000000007381b7c00af" +
"00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f03" +
@@ -108,7 +108,6 @@ public class SecurityTokenConnectionTest {
securityTokenConnection.getTokenInfo();
verifyDialog();
}

View File

@@ -208,7 +208,7 @@ public class SecurityTokenUtilsTest extends Mockito {
"00000000000000000000000000000000000000cd0c5741e8695741e8695741e8" +
"69");
OpenPgpCapabilities caps = new OpenPgpCapabilities(data);
OpenPgpCapabilities caps = OpenPgpCapabilities.fromBytes(data);
Assert.assertEquals(caps.isHasSM(), true);
}

View File

@@ -49,7 +49,7 @@ public class SecurityTokenChangeKeyTokenOpTest {
@Test
public void testPutKey() throws Exception {
OpenPgpCapabilities openPgpCapabilities = new OpenPgpCapabilities(
OpenPgpCapabilities openPgpCapabilities = OpenPgpCapabilities.fromBytes(
Hex.decode("6e81de4f10d27600012401020000060364311500005f520f0073000080000000000000000000007381b7c00af" +
"00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f03" +
"0303c53c4ec5fee25c4e89654d58cad8492510a89d3c3d8468da7b24e15bfc624c6a792794f15b759" +