From 56254aedb708476d9d770b3e5f203df8ec8dd7c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 16 Mar 2021 17:07:56 +0100 Subject: [PATCH] improve and simplify key formats --- .../keychain/Constants.java | 8 +- .../keychain/securitytoken/ECKeyFormat.java | 200 ------------------ .../keychain/securitytoken/EcKeyFormat.java | 131 ++++++++++++ .../securitytoken/EcObjectIdentifiers.java | 72 +++++++ .../securitytoken/EdDSAKeyFormat.java | 41 ---- .../keychain/securitytoken/KeyFormat.java | 40 +--- .../keychain/securitytoken/RSAKeyFormat.java | 103 --------- .../keychain/securitytoken/RsaKeyFormat.java | 120 +++++++++++ .../securitytoken/SCP11bSecureMessaging.java | 18 +- .../securitytoken/SecurityTokenUtils.java | 31 ++- .../operations/PsoDecryptTokenOp.java | 33 ++- .../SecurityTokenChangeKeyTokenOp.java | 53 +++-- .../SecurityTokenPsoSignTokenOp.java | 87 ++++---- .../securitytoken/SecurityTokenUtilsTest.java | 2 +- 14 files changed, 445 insertions(+), 494 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EcKeyFormat.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EcObjectIdentifiers.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EdDSAKeyFormat.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RsaKeyFormat.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 368e3fb06..f681769b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -26,7 +26,7 @@ import android.os.Environment; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.sufficientlysecure.keychain.securitytoken.KeyFormat; -import org.sufficientlysecure.keychain.securitytoken.RSAKeyFormat; +import org.sufficientlysecure.keychain.securitytoken.RsaKeyFormat; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; @@ -187,9 +187,9 @@ public final class Constants { * Default key format for OpenPGP smart cards v2: 2048 bit RSA (sign+certify, decrypt, auth) */ private static final int ELEN = 17; //65537 - public static final KeyFormat SECURITY_TOKEN_V2_SIGN = new RSAKeyFormat(2048, ELEN, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS); - public static final KeyFormat SECURITY_TOKEN_V2_DEC = new RSAKeyFormat(2048, ELEN, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS); - public static final KeyFormat SECURITY_TOKEN_V2_AUTH = new RSAKeyFormat(2048, ELEN, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS); + public static final KeyFormat SECURITY_TOKEN_V2_SIGN = RsaKeyFormat.getInstance(2048, ELEN, RsaKeyFormat.RsaImportFormat.CRT_WITH_MODULUS); + public static final KeyFormat SECURITY_TOKEN_V2_DEC = RsaKeyFormat.getInstance(2048, ELEN, RsaKeyFormat.RsaImportFormat.CRT_WITH_MODULUS); + public static final KeyFormat SECURITY_TOKEN_V2_AUTH = RsaKeyFormat.getInstance(2048, ELEN, RsaKeyFormat.RsaImportFormat.CRT_WITH_MODULUS); private static boolean isRunningUnitTest() { try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java deleted file mode 100644 index 11afd0e5b..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/ECKeyFormat.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * 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; - -import androidx.annotation.Nullable; - -import com.google.auto.value.AutoValue; - -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.nist.NISTNamedCurves; -import org.bouncycastle.asn1.x9.ECNamedCurveTable; -import org.bouncycastle.asn1.x9.X9ECParameters; -import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; -import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.math.ec.ECCurve; -import org.bouncycastle.util.Arrays; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; - -import java.io.IOException; - -import timber.log.Timber; - - -// 4.3.3.6 Algorithm Attributes -@AutoValue -public abstract class ECKeyFormat extends KeyFormat { - - public abstract byte[] oidField(); - - @Nullable // TODO - public abstract ECAlgorithmFormat ecAlgorithmFormat(); - - private static final byte ATTRS_IMPORT_FORMAT_WITH_PUBKEY = (byte) 0xff; - - ECKeyFormat() { - super(KeyFormatType.ECKeyFormatType); - } - - public static KeyFormat getInstance(byte[] oidField, ECAlgorithmFormat from) { - return new AutoValue_ECKeyFormat(oidField, from); - } - - public static ECKeyFormat getInstance(ASN1ObjectIdentifier oidAsn1, ECAlgorithmFormat from) { - byte[] oidField = asn1ToOidField(oidAsn1); - return new AutoValue_ECKeyFormat(oidField, from); - } - - public static KeyFormat getInstanceFromBytes(byte[] bytes) { - if (bytes.length < 2) { - throw new IllegalArgumentException("Bad length for EC attributes"); - } - - int len = bytes.length - 1; - if (bytes[bytes.length - 1] == ATTRS_IMPORT_FORMAT_WITH_PUBKEY) { - len -= 1; - } - - final byte[] oidField = new byte[len]; - System.arraycopy(bytes, 1, oidField, 0, len); - return getInstance(oidField, ECKeyFormat.ECAlgorithmFormat.from(bytes[0], bytes[bytes.length - 1])); - } - - public byte[] toBytes(KeyType slot) { - byte[] attrs = new byte[1 + oidField().length + 1]; - - attrs[0] = ecAlgorithmFormat().getAlgorithmId(); - System.arraycopy(oidField(), 0, attrs, 1, oidField().length); - attrs[attrs.length - 1] = ATTRS_IMPORT_FORMAT_WITH_PUBKEY; - - return attrs; - } - - public ASN1ObjectIdentifier asn1ParseOid() { - ASN1ObjectIdentifier asn1CurveOid = oidFieldToOidAsn1(oidField()); - String curveName = ECNamedCurveTable.getName(asn1CurveOid); - if (curveName == null) { - Timber.w("Unknown curve OID: %s. Could be YubiKey firmware bug < 5.2.8. Trying again with last byte removed.", asn1CurveOid.getId()); - - // https://bugs.chromium.org/p/chromium/issues/detail?id=1120933#c10 - // The OpenPGP applet of a Yubikey with firmware version below 5.2.8 appends - // a potentially arbitrary byte to the intended byte representation of an ECC - // curve OID. This case is handled by retrying the decoding with the last - // byte stripped if the resulting OID does not label a known curve. - byte[] oidRemoveLastByte = Arrays.copyOf(oidField(), oidField().length - 1); - ASN1ObjectIdentifier asn1CurveOidYubikey = oidFieldToOidAsn1(oidRemoveLastByte); - curveName = ECNamedCurveTable.getName(asn1CurveOidYubikey); - - if (curveName != null) { - Timber.w("Detected curve OID: %s", asn1CurveOidYubikey.getId()); - return asn1CurveOidYubikey; - } else { - Timber.e("Still Unknown curve OID: %s", asn1CurveOidYubikey.getId()); - return asn1CurveOid; - } - } - - return asn1CurveOid; - } - - private static byte[] asn1ToOidField(ASN1ObjectIdentifier oidAsn1) { - byte[] encodedAsn1Oid; - try { - encodedAsn1Oid = oidAsn1.getEncoded(); - } catch (IOException e) { - throw new IllegalStateException("Failed to encode curve OID!"); - } - byte[] oidField = new byte[encodedAsn1Oid.length - 2]; - System.arraycopy(encodedAsn1Oid, 2, oidField, 0, encodedAsn1Oid.length - 2); - - return oidField; - } - - private static ASN1ObjectIdentifier oidFieldToOidAsn1(byte[] oidField) { - final byte[] boid = new byte[2 + oidField.length]; - boid[0] = (byte) 0x06; - boid[1] = (byte) oidField.length; - System.arraycopy(oidField, 0, boid, 2, oidField.length); - return ASN1ObjectIdentifier.getInstance(boid); - } - - public enum ECAlgorithmFormat { - ECDH((byte) PublicKeyAlgorithmTags.ECDH, true, false), - ECDH_WITH_PUBKEY((byte) PublicKeyAlgorithmTags.ECDH, true, true), - ECDSA((byte) PublicKeyAlgorithmTags.ECDSA, false, false), - ECDSA_WITH_PUBKEY((byte) PublicKeyAlgorithmTags.ECDSA, false, true); - - private final byte mAlgorithmId; - private final boolean mIsECDH; - private final boolean mWithPubkey; - - ECAlgorithmFormat(final byte algorithmId, final boolean isECDH, final boolean withPubkey) { - mAlgorithmId = algorithmId; - mIsECDH = isECDH; - mWithPubkey = withPubkey; - } - - public static ECKeyFormat.ECAlgorithmFormat from(final byte bFirst, final byte bLast) { - for (ECKeyFormat.ECAlgorithmFormat format : values()) { - if (format.mAlgorithmId == bFirst && - ((bLast == ATTRS_IMPORT_FORMAT_WITH_PUBKEY) == format.isWithPubkey())) { - return format; - } - } - return null; - } - - public final byte getAlgorithmId() { - return mAlgorithmId; - } - - public final boolean isECDH() { - return mIsECDH; - } - - public final boolean isWithPubkey() { - return mWithPubkey; - } - } - - public void addToSaveKeyringParcel(SaveKeyringParcel.Builder builder, int keyFlags) { - ASN1ObjectIdentifier oidAsn1 = asn1ParseOid(); - final X9ECParameters params = NISTNamedCurves.getByOID(oidAsn1); - final ECCurve curve = params.getCurve(); - - SaveKeyringParcel.Algorithm algo = SaveKeyringParcel.Algorithm.ECDSA; - if (((keyFlags & KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS) - || ((keyFlags & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE)) { - algo = SaveKeyringParcel.Algorithm.ECDH; - } - - SaveKeyringParcel.Curve scurve; - if (oidAsn1.equals(NISTNamedCurves.getOID("P-256"))) { - scurve = SaveKeyringParcel.Curve.NIST_P256; - } else if (oidAsn1.equals(NISTNamedCurves.getOID("P-384"))) { - scurve = SaveKeyringParcel.Curve.NIST_P384; - } else if (oidAsn1.equals(NISTNamedCurves.getOID("P-521"))) { - scurve = SaveKeyringParcel.Curve.NIST_P521; - } else { - throw new IllegalArgumentException("Unsupported curve " + oidAsn1); - } - - builder.addSubkeyAdd(SubkeyAdd.createSubkeyAdd(algo, curve.getFieldSize(), scurve, keyFlags, 0L)); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EcKeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EcKeyFormat.java new file mode 100644 index 000000000..9dc7af9ff --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EcKeyFormat.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2017 Schürmann & Breitmoser GbR + * + * 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; + +import com.google.auto.value.AutoValue; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.nist.NISTNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.math.ec.ECCurve; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; + + +// OpenPGP Card Spec: Algorithm Attributes: ECC +@AutoValue +public abstract class EcKeyFormat extends KeyFormat { + + public abstract int algorithmId(); + + public abstract ASN1ObjectIdentifier curveOid(); + + public abstract boolean withPubkey(); + + private static final byte ATTRS_IMPORT_FORMAT_WITH_PUBKEY = (byte) 0xff; + + public static EcKeyFormat getInstance(int algorithmId, ASN1ObjectIdentifier oid, boolean withPubkey) { + return new AutoValue_EcKeyFormat(algorithmId, oid, withPubkey); + } + + public static EcKeyFormat getInstanceForKeyGeneration(KeyType keyType, ASN1ObjectIdentifier oidAsn1) { + if (keyType == KeyType.ENCRYPT) { + return getInstance(PublicKeyAlgorithmTags.ECDH, oidAsn1, true); + } else { // SIGN, AUTH + if (EcObjectIdentifiers.ED25519.equals(oidAsn1)) { + return getInstance(PublicKeyAlgorithmTags.EDDSA, oidAsn1, true); + } else { + return getInstance(PublicKeyAlgorithmTags.ECDSA, oidAsn1, true); + } + } + } + + public static EcKeyFormat getInstanceFromBytes(byte[] bytes) { + if (bytes.length < 2) { + throw new IllegalArgumentException("Bad length for EC attributes"); + } + + int algorithmId = bytes[0]; + int oidLen = bytes.length - 1; + + boolean withPubkey = false; + if (bytes[bytes.length - 1] == ATTRS_IMPORT_FORMAT_WITH_PUBKEY) { + withPubkey = true; + oidLen -= 1; + } + + final byte[] oidField = new byte[oidLen]; + System.arraycopy(bytes, 1, oidField, 0, oidLen); + ASN1ObjectIdentifier oid = EcObjectIdentifiers.parseOid(oidField); + + return getInstance(algorithmId, oid, withPubkey); + } + + public byte[] toBytes(KeyType slot) { + byte[] oidField = EcObjectIdentifiers.asn1ToOidField(curveOid()); + + int len = 1 + oidField.length; + if (withPubkey()) { + len += 1; + } + byte[] attrs = new byte[len]; + + attrs[0] = (byte) algorithmId(); + System.arraycopy(oidField, 0, attrs, 1, oidField.length); + if (withPubkey()) { + attrs[len - 1] = ATTRS_IMPORT_FORMAT_WITH_PUBKEY; + } + + return attrs; + } + + public boolean isX25519() { + return EcObjectIdentifiers.X25519.equals(curveOid()); + } + + public final boolean isEdDsa() { + return algorithmId() == PublicKeyAlgorithmTags.EDDSA; + } + + public void addToSaveKeyringParcel(SaveKeyringParcel.Builder builder, int keyFlags) { + final X9ECParameters params = NISTNamedCurves.getByOID(curveOid()); + final ECCurve curve = params.getCurve(); + + SaveKeyringParcel.Algorithm algo = SaveKeyringParcel.Algorithm.ECDSA; + if (((keyFlags & KeyFlags.ENCRYPT_COMMS) == KeyFlags.ENCRYPT_COMMS) + || ((keyFlags & KeyFlags.ENCRYPT_STORAGE) == KeyFlags.ENCRYPT_STORAGE)) { + algo = SaveKeyringParcel.Algorithm.ECDH; + } + + SaveKeyringParcel.Curve scurve; + if (EcObjectIdentifiers.NIST_P_256.equals(curveOid())) { + scurve = SaveKeyringParcel.Curve.NIST_P256; + } else if (EcObjectIdentifiers.NIST_P_384.equals(curveOid())) { + scurve = SaveKeyringParcel.Curve.NIST_P384; + } else if (EcObjectIdentifiers.NIST_P_521.equals(curveOid())) { + scurve = SaveKeyringParcel.Curve.NIST_P521; + } else { + throw new IllegalArgumentException("Unsupported curve " + curveOid()); + } + + builder.addSubkeyAdd(SubkeyAdd.createSubkeyAdd(algo, curve.getFieldSize(), scurve, keyFlags, 0L)); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EcObjectIdentifiers.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EcObjectIdentifiers.java new file mode 100644 index 000000000..a55f6d4c2 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EcObjectIdentifiers.java @@ -0,0 +1,72 @@ +package org.sufficientlysecure.keychain.securitytoken; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.cryptlib.CryptlibObjectIdentifiers; +import org.bouncycastle.asn1.gnu.GNUObjectIdentifiers; +import org.bouncycastle.asn1.sec.SECObjectIdentifiers; +import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; + +import timber.log.Timber; + +public class EcObjectIdentifiers { + + public static final ASN1ObjectIdentifier NIST_P_256 = SECObjectIdentifiers.secp256r1; + public static final ASN1ObjectIdentifier NIST_P_384 = SECObjectIdentifiers.secp384r1; + public static final ASN1ObjectIdentifier NIST_P_521 = SECObjectIdentifiers.secp521r1; + public static final ASN1ObjectIdentifier BRAINPOOL_P256_R1 = TeleTrusTObjectIdentifiers.brainpoolP256r1; + public static final ASN1ObjectIdentifier BRAINPOOL_P512_R1 = TeleTrusTObjectIdentifiers.brainpoolP512r1; + public static final ASN1ObjectIdentifier ED25519 = GNUObjectIdentifiers.Ed25519; // for use with EdDSA + public static final ASN1ObjectIdentifier X25519 = CryptlibObjectIdentifiers.curvey25519; // for use with ECDH + + public static HashSet sOids = new HashSet<>(Arrays.asList( + NIST_P_256, NIST_P_384, NIST_P_521, BRAINPOOL_P256_R1, BRAINPOOL_P512_R1, ED25519, X25519 + )); + + public static ASN1ObjectIdentifier parseOid(byte[] oidField) { + ASN1ObjectIdentifier asn1CurveOid = oidFieldToOidAsn1(oidField); + if (sOids.contains(asn1CurveOid)) { + return asn1CurveOid; + } + Timber.w("Unknown curve OID: %s. Could be YubiKey firmware bug < 5.2.8. Trying again with last byte removed.", asn1CurveOid.getId()); + + // https://bugs.chromium.org/p/chromium/issues/detail?id=1120933#c10 + // The OpenPGP applet of a Yubikey with firmware version below 5.2.8 appends + // a potentially arbitrary byte to the intended byte representation of an ECC + // curve OID. This case is handled by retrying the decoding with the last + // byte stripped if the resulting OID does not label a known curve. + byte[] oidRemoveLastByte = Arrays.copyOf(oidField, oidField.length - 1); + ASN1ObjectIdentifier asn1CurveOidYubikey = oidFieldToOidAsn1(oidRemoveLastByte); + if (sOids.contains(asn1CurveOidYubikey)) { + Timber.w("Detected curve OID: %s", asn1CurveOidYubikey.getId()); + } else { + Timber.e("Still Unknown curve OID: %s", asn1CurveOidYubikey.getId()); + } + return asn1CurveOidYubikey; + } + + public static byte[] asn1ToOidField(ASN1ObjectIdentifier oidAsn1) { + byte[] encodedAsn1Oid; + try { + encodedAsn1Oid = oidAsn1.getEncoded(); + } catch (IOException e) { + throw new IllegalStateException("Failed to encode curve OID!"); + } + byte[] oidField = new byte[encodedAsn1Oid.length - 2]; + System.arraycopy(encodedAsn1Oid, 2, oidField, 0, encodedAsn1Oid.length - 2); + + return oidField; + } + + public static ASN1ObjectIdentifier oidFieldToOidAsn1(byte[] oidField) { + final byte[] boid = new byte[2 + oidField.length]; + boid[0] = (byte) 0x06; + boid[1] = (byte) oidField.length; + System.arraycopy(oidField, 0, boid, 2, oidField.length); + return ASN1ObjectIdentifier.getInstance(boid); + } + +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EdDSAKeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EdDSAKeyFormat.java deleted file mode 100644 index 1ed413651..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EdDSAKeyFormat.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * 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; - -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.nist.NISTNamedCurves; -import org.bouncycastle.asn1.x9.X9ECParameters; -import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.math.ec.ECCurve; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; - - -// 4.3.3.6 Algorithm Attributes -public class EdDSAKeyFormat extends KeyFormat { - - public EdDSAKeyFormat() { - super(KeyFormatType.EdDSAKeyFormatType); - } - - @Override - public void addToSaveKeyringParcel(SaveKeyringParcel.Builder builder, int keyFlags) { - builder.addSubkeyAdd(SubkeyAdd.createSubkeyAdd(SaveKeyringParcel.Algorithm.EDDSA, - null, null, keyFlags, 0L)); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java index 1f56087ed..bcbb2e731 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java @@ -17,62 +17,44 @@ package org.sufficientlysecure.keychain.securitytoken; -import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.CreateSecurityTokenAlgorithmFragment; public abstract class KeyFormat { - public enum KeyFormatType { - RSAKeyFormatType, - ECKeyFormatType, - EdDSAKeyFormatType - } - - private final KeyFormatType mKeyFormatType; - - KeyFormat(final KeyFormatType keyFormatType) { - mKeyFormatType = keyFormatType; - } - - public final KeyFormatType keyFormatType() { - return mKeyFormatType; - } - public static KeyFormat fromBytes(byte[] bytes) { switch (bytes[0]) { case PublicKeyAlgorithmTags.RSA_GENERAL: - return RSAKeyFormat.fromBytes(bytes); + return RsaKeyFormat.getInstanceFromBytes(bytes); case PublicKeyAlgorithmTags.ECDH: case PublicKeyAlgorithmTags.ECDSA: - return ECKeyFormat.getInstanceFromBytes(bytes); case PublicKeyAlgorithmTags.EDDSA: - return new EdDSAKeyFormat(); - + return EcKeyFormat.getInstanceFromBytes(bytes); default: throw new IllegalArgumentException("Unsupported Algorithm id " + bytes[0]); } } + public abstract byte[] toBytes(KeyType slot); + public static KeyFormat fromCreationKeyType(CreateSecurityTokenAlgorithmFragment.SupportedKeyType t, boolean forEncryption) { final int elen = 17; //65537 - final ECKeyFormat.ECAlgorithmFormat kf = - forEncryption ? ECKeyFormat.ECAlgorithmFormat.ECDH_WITH_PUBKEY : ECKeyFormat.ECAlgorithmFormat.ECDSA_WITH_PUBKEY; + final int algorithmId = forEncryption ? PublicKeyAlgorithmTags.ECDH : PublicKeyAlgorithmTags.ECDSA; switch (t) { case RSA_2048: - return new RSAKeyFormat(2048, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS); + return RsaKeyFormat.getInstance(2048, elen, RsaKeyFormat.RsaImportFormat.CRT_WITH_MODULUS); case RSA_3072: - return new RSAKeyFormat(3072, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS); + return RsaKeyFormat.getInstance(3072, elen, RsaKeyFormat.RsaImportFormat.CRT_WITH_MODULUS); case RSA_4096: - return new RSAKeyFormat(4096, elen, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS); + return RsaKeyFormat.getInstance(4096, elen, RsaKeyFormat.RsaImportFormat.CRT_WITH_MODULUS); case ECC_P256: - return ECKeyFormat.getInstance(NISTNamedCurves.getOID("P-256"), kf); + return EcKeyFormat.getInstance(algorithmId, EcObjectIdentifiers.NIST_P_256, true); case ECC_P384: - return ECKeyFormat.getInstance(NISTNamedCurves.getOID("P-384"), kf); + return EcKeyFormat.getInstance(algorithmId, EcObjectIdentifiers.NIST_P_384, true); case ECC_P521: - return ECKeyFormat.getInstance(NISTNamedCurves.getOID("P-521"), kf); + return EcKeyFormat.getInstance(algorithmId, EcObjectIdentifiers.NIST_P_521, true); } throw new IllegalArgumentException("Unsupported Algorithm id " + t); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java deleted file mode 100644 index ce398cf5a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RSAKeyFormat.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * 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; - -import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; - - -// 4.3.3.6 Algorithm Attributes -public class RSAKeyFormat extends KeyFormat { - private int mModulusLength; - private int mExponentLength; - private RSAAlgorithmFormat mRSAAlgorithmFormat; - - public RSAKeyFormat(int modulusLength, - int exponentLength, - RSAAlgorithmFormat rsaAlgorithmFormat) { - super(KeyFormatType.RSAKeyFormatType); - mModulusLength = modulusLength; - mExponentLength = exponentLength; - mRSAAlgorithmFormat = rsaAlgorithmFormat; - } - - public int getModulusLength() { - return mModulusLength; - } - - public int getExponentLength() { - return mExponentLength; - } - - public static KeyFormat fromBytes(byte[] bytes) { - if (bytes.length < 6) { - throw new IllegalArgumentException("Bad length for RSA attributes"); - } - return new RSAKeyFormat(bytes[1] << 8 | bytes[2], - bytes[3] << 8 | bytes[4], - RSAKeyFormat.RSAAlgorithmFormat.from(bytes[5])); - } - - public RSAAlgorithmFormat getAlgorithmFormat() { - return mRSAAlgorithmFormat; - } - - public enum RSAAlgorithmFormat { - STANDARD((byte) 0x00, false, false), - STANDARD_WITH_MODULUS((byte) 0x01, false, true), - CRT((byte) 0x02, true, false), - CRT_WITH_MODULUS((byte) 0x03, true, true); - - private byte mImportFormat; - private boolean mIncludeModulus; - private boolean mIncludeCrt; - - RSAAlgorithmFormat(byte importFormat, boolean includeCrt, boolean includeModulus) { - mImportFormat = importFormat; - mIncludeModulus = includeModulus; - mIncludeCrt = includeCrt; - } - - public static RSAAlgorithmFormat from(byte importFormatByte) { - for (RSAAlgorithmFormat format : values()) { - if (format.mImportFormat == importFormatByte) { - return format; - } - } - return null; - } - - public byte getImportFormat() { - return mImportFormat; - } - - public boolean isIncludeModulus() { - return mIncludeModulus; - } - - public boolean isIncludeCrt() { - return mIncludeCrt; - } - } - - public void addToSaveKeyringParcel(SaveKeyringParcel.Builder builder, int keyFlags) { - builder.addSubkeyAdd(SubkeyAdd.createSubkeyAdd(SaveKeyringParcel.Algorithm.RSA, - mModulusLength, null, keyFlags, 0L)); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RsaKeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RsaKeyFormat.java new file mode 100644 index 000000000..ddaae6a0e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/RsaKeyFormat.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2017 Schürmann & Breitmoser GbR + * + * 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; + +import androidx.annotation.RestrictTo; + +import com.google.auto.value.AutoValue; + +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; + + +// OpenPGP Card Spec: Algorithm Attributes: RSA +@AutoValue +public abstract class RsaKeyFormat extends KeyFormat { + + public static final int ALGORITHM_ID = PublicKeyAlgorithmTags.RSA_GENERAL; + + public abstract int modulusLength(); + + public abstract int exponentLength(); + + public abstract RsaImportFormat rsaImportFormat(); + + public static RsaKeyFormat getInstance(int modulusLength, int exponentLength, RsaImportFormat from) { + return new AutoValue_RsaKeyFormat(modulusLength, exponentLength, from); + } + + public static RsaKeyFormat getInstanceDefault2048BitFormat() { + return getInstance(2048, 4, RsaImportFormat.CRT_WITH_MODULUS); + } + + public RsaKeyFormat withModulus(int modulus) { + return RsaKeyFormat.getInstance(modulus, exponentLength(), rsaImportFormat()); + } + + public static KeyFormat getInstanceFromBytes(byte[] bytes) { + if (bytes.length < 6) { + throw new IllegalArgumentException("Bad length for RSA attributes"); + } + int modulusLength = bytes[1] << 8 | bytes[2]; + int exponentLength = bytes[3] << 8 | bytes[4]; + RsaImportFormat importFormat = RsaImportFormat.from(bytes[5]); + + return getInstance(modulusLength, exponentLength, importFormat); + } + + @Override + public byte[] toBytes(KeyType slot) { + int i = 0; + byte[] attrs = new byte[6]; + attrs[i++] = (byte) ALGORITHM_ID; + attrs[i++] = (byte) ((modulusLength() >> 8) & 0xff); + attrs[i++] = (byte) (modulusLength() & 0xff); + attrs[i++] = (byte) ((exponentLength() >> 8) & 0xff); + attrs[i++] = (byte) (exponentLength() & 0xff); + attrs[i] = rsaImportFormat().getImportFormat(); + + return attrs; + } + + public enum RsaImportFormat { + STANDARD((byte) 0x00, false, false), + STANDARD_WITH_MODULUS((byte) 0x01, false, true), + CRT((byte) 0x02, true, false), + CRT_WITH_MODULUS((byte) 0x03, true, true); + + private byte importFormat; + private boolean includeModulus; + private boolean includeCrt; + + RsaImportFormat(byte importFormat, boolean includeCrt, boolean includeModulus) { + this.importFormat = importFormat; + this.includeModulus = includeModulus; + this.includeCrt = includeCrt; + } + + public static RsaImportFormat from(byte importFormatByte) { + for (RsaImportFormat format : values()) { + if (format.importFormat == importFormatByte) { + return format; + } + } + return null; + } + + public byte getImportFormat() { + return importFormat; + } + + public boolean isIncludeModulus() { + return includeModulus; + } + + public boolean isIncludeCrt() { + return includeCrt; + } + } + + public void addToSaveKeyringParcel(SaveKeyringParcel.Builder builder, int keyFlags) { + builder.addSubkeyAdd(SubkeyAdd.createSubkeyAdd(SaveKeyringParcel.Algorithm.RSA, + modulusLength(), null, keyFlags, 0L)); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SCP11bSecureMessaging.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SCP11bSecureMessaging.java index 3fab42670..3a140f61b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SCP11bSecureMessaging.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SCP11bSecureMessaging.java @@ -150,24 +150,24 @@ class SCP11bSecureMessaging implements SecureMessaging { && (mMacChaining != null); } - private static ECParameterSpec getAlgorithmParameterSpec(final ECKeyFormat kf) + private static ECParameterSpec getAlgorithmParameterSpec(final EcKeyFormat kf) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidParameterSpecException { final AlgorithmParameters algoParams = AlgorithmParameters.getInstance(SCP11B_KEY_AGREEMENT_KEY_ALGO, PROVIDER); - algoParams.init(new ECGenParameterSpec(ECNamedCurveTable.getName(kf.asn1ParseOid()))); + algoParams.init(new ECGenParameterSpec(ECNamedCurveTable.getName(kf.curveOid()))); return algoParams.getParameterSpec(ECParameterSpec.class); } - private static ECPublicKey newECDHPublicKey(final ECKeyFormat kf, byte[] data) + private static ECPublicKey newECDHPublicKey(final EcKeyFormat kf, byte[] data) throws InvalidKeySpecException, NoSuchAlgorithmException, InvalidParameterSpecException, NoSuchProviderException { if (ecdhFactory == null) { ecdhFactory = KeyFactory.getInstance(SCP11B_KEY_AGREEMENT_KEY_TYPE, PROVIDER); } - final X9ECParameters params = NISTNamedCurves.getByOID(kf.asn1ParseOid()); + final X9ECParameters params = NISTNamedCurves.getByOID(kf.curveOid()); if (params == null) { throw new InvalidParameterSpecException("unsupported curve"); } @@ -185,7 +185,7 @@ class SCP11bSecureMessaging implements SecureMessaging { return (ECPublicKey)(ecdhFactory.generatePublic(pk)); } - private static KeyPair generateECDHKeyPair(final ECKeyFormat kf) + private static KeyPair generateECDHKeyPair(final EcKeyFormat kf) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidParameterSpecException, InvalidAlgorithmParameterException { final KeyPairGenerator gen = KeyPairGenerator.getInstance(SCP11B_KEY_AGREEMENT_KEY_ALGO, PROVIDER); @@ -200,7 +200,7 @@ class SCP11bSecureMessaging implements SecureMessaging { } private static ECPublicKey verifyCertificate(final Context ctx, - final ECKeyFormat kf, + final EcKeyFormat kf, final byte[] data) throws IOException { try { @@ -299,13 +299,13 @@ class SCP11bSecureMessaging implements SecureMessaging { final KeyFormat kf = KeyFormat.fromBytes(tlvs[0].mV); - if (kf.keyFormatType() != KeyFormat.KeyFormatType.ECKeyFormatType) { + if (!(kf instanceof EcKeyFormat)) { throw new SecureMessagingException("invalid format of secure messaging key"); } - final ECKeyFormat eckf = (ECKeyFormat)kf; + final EcKeyFormat eckf = (EcKeyFormat)kf; - if (eckf.asn1ParseOid() == null) { + if (eckf.curveOid() == null) { throw new SecureMessagingException("unsupported curve"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtils.java index 2d249779b..55b1c7a17 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtils.java @@ -18,11 +18,10 @@ package org.sufficientlysecure.keychain.securitytoken; import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; 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.RSAKeyFormat.RSAAlgorithmFormat; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -37,15 +36,15 @@ public class SecurityTokenUtils { KeyFormat formatForKeyType) throws IOException { if (secretKey.isRSA()) { - return attributesForRsaKey(secretKey.getBitStrength(), (RSAKeyFormat) formatForKeyType); + return attributesForRsaKey(secretKey.getBitStrength(), (RsaKeyFormat) formatForKeyType); } else if (secretKey.isEC()) { byte[] oid = new ASN1ObjectIdentifier(secretKey.getCurveOid()).getEncoded(); byte[] attrs = new byte[1 + (oid.length - 2) + 1]; if (slot.equals(KeyType.ENCRYPT)) - attrs[0] = ECKeyFormat.ECAlgorithmFormat.ECDH_WITH_PUBKEY.getAlgorithmId(); + attrs[0] = PublicKeyAlgorithmTags.ECDH; else { // SIGN and AUTH is ECDSA - attrs[0] = ECKeyFormat.ECAlgorithmFormat.ECDSA_WITH_PUBKEY.getAlgorithmId(); + attrs[0] = PublicKeyAlgorithmTags.ECDSA; } System.arraycopy(oid, 2, attrs, 1, (oid.length - 2)); @@ -58,13 +57,13 @@ public class SecurityTokenUtils { } } - private static byte[] attributesForRsaKey(int modulusLength, RSAKeyFormat formatForKeyType) { - RSAAlgorithmFormat algorithmFormat = formatForKeyType.getAlgorithmFormat(); - int exponentLength = formatForKeyType.getExponentLength(); + private static byte[] attributesForRsaKey(int modulusLength, RsaKeyFormat formatForKeyType) { + RsaKeyFormat.RsaImportFormat algorithmFormat = formatForKeyType.rsaImportFormat(); + int exponentLength = formatForKeyType.exponentLength(); int i = 0; byte[] attrs = new byte[6]; - attrs[i++] = (byte) 0x01; + attrs[i++] = (byte) RsaKeyFormat.ALGORITHM_ID; attrs[i++] = (byte) ((modulusLength >> 8) & 0xff); attrs[i++] = (byte) (modulusLength & 0xff); attrs[i++] = (byte) ((exponentLength >> 8) & 0xff); @@ -87,18 +86,18 @@ public class SecurityTokenUtils { } public static byte[] createRSAPrivKeyTemplate(RSAPrivateCrtKey secretKey, KeyType slot, - RSAKeyFormat format) throws IOException { + RsaKeyFormat format) throws IOException { ByteArrayOutputStream stream = new ByteArrayOutputStream(), template = new ByteArrayOutputStream(), data = new ByteArrayOutputStream(), res = new ByteArrayOutputStream(); - int expLengthBytes = (format.getExponentLength() + 7) / 8; + int expLengthBytes = (format.exponentLength() + 7) / 8; // Public exponent template.write(new byte[]{(byte) 0x91, (byte) expLengthBytes}); writeBits(data, secretKey.getPublicExponent(), expLengthBytes); - final int modLengthBytes = format.getModulusLength() / 8; + final int modLengthBytes = format.modulusLength() / 8; final byte[] lengthByteArray = generateLengthByteArray(modLengthBytes / 2); // Prime P, length modLengthBytes / 2 @@ -112,7 +111,7 @@ public class SecurityTokenUtils { writeBits(data, secretKey.getPrimeQ(), modLengthBytes / 2); - if (format.getAlgorithmFormat().isIncludeCrt()) { + if (format.rsaImportFormat().isIncludeCrt()) { // Coefficient (1/q mod p), length modLengthBytes / 2 template.write(Hex.decode("94")); template.write(lengthByteArray); @@ -129,7 +128,7 @@ public class SecurityTokenUtils { writeBits(data, secretKey.getPrimeExponentQ(), modLengthBytes / 2); } - if (format.getAlgorithmFormat().isIncludeModulus()) { + if (format.rsaImportFormat().isIncludeModulus()) { // Modulus, length modLengthBytes, last item in private key template template.write(Hex.decode("97")); template.write(generateLengthByteArray(modLengthBytes)); @@ -162,7 +161,7 @@ public class SecurityTokenUtils { } public static byte[] createECPrivKeyTemplate(ECPrivateKey secretKey, ECPublicKey publicKey, KeyType slot, - ECKeyFormat format) throws IOException { + EcKeyFormat format) throws IOException { ByteArrayOutputStream stream = new ByteArrayOutputStream(), template = new ByteArrayOutputStream(), data = new ByteArrayOutputStream(), @@ -174,7 +173,7 @@ public class SecurityTokenUtils { template.write(Hex.decode("92")); template.write(encodeLength(data.size())); - if (format.ecAlgorithmFormat().isWithPubkey()) { + if (format.withPubkey()) { data.write(Hex.decode("04")); writeBits(data, publicKey.getW().getAffineX(), csize); writeBits(data, publicKey.getW().getAffineY(), csize); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/PsoDecryptTokenOp.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/PsoDecryptTokenOp.java index ea078e686..537c92b5a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/PsoDecryptTokenOp.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/PsoDecryptTokenOp.java @@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.securitytoken.operations; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import org.bouncycastle.asn1.cryptlib.CryptlibObjectIdentifiers; import org.bouncycastle.asn1.x9.ECNamedCurveTable; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.jcajce.util.MessageDigestUtils; @@ -34,9 +33,10 @@ import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.securitytoken.CardException; import org.sufficientlysecure.keychain.securitytoken.CommandApdu; -import org.sufficientlysecure.keychain.securitytoken.ECKeyFormat; +import org.sufficientlysecure.keychain.securitytoken.EcKeyFormat; import org.sufficientlysecure.keychain.securitytoken.KeyFormat; import org.sufficientlysecure.keychain.securitytoken.ResponseApdu; +import org.sufficientlysecure.keychain.securitytoken.RsaKeyFormat; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection; import java.io.IOException; @@ -73,15 +73,12 @@ public class PsoDecryptTokenOp { connection.verifyPinForOther(); KeyFormat kf = connection.getOpenPgpCapabilities().getEncryptKeyFormat(); - switch (kf.keyFormatType()) { - case RSAKeyFormatType: - return decryptSessionKeyRsa(encryptedSessionKeyMpi); - - case ECKeyFormatType: - return decryptSessionKeyEcdh(encryptedSessionKeyMpi, (ECKeyFormat) kf, publicKey); - - default: - throw new CardException("Unknown encryption key type!"); + if (kf instanceof RsaKeyFormat) { + return decryptSessionKeyRsa(encryptedSessionKeyMpi); + } else if (kf instanceof EcKeyFormat) { + return decryptSessionKeyEcdh(encryptedSessionKeyMpi, (EcKeyFormat) kf, publicKey); + } else { + throw new CardException("Unknown encryption key type!"); } } @@ -112,7 +109,7 @@ public class PsoDecryptTokenOp { return psoDecipherPayload; } - private byte[] decryptSessionKeyEcdh(byte[] encryptedSessionKeyMpi, ECKeyFormat eckf, CanonicalizedPublicKey publicKey) + private byte[] decryptSessionKeyEcdh(byte[] encryptedSessionKeyMpi, EcKeyFormat eckf, CanonicalizedPublicKey publicKey) throws IOException { int mpiLength = getMpiLength(encryptedSessionKeyMpi); byte[] encryptedPoint = Arrays.copyOfRange(encryptedSessionKeyMpi, 2, mpiLength + 2); @@ -165,14 +162,13 @@ public class PsoDecryptTokenOp { byte[] keyEncryptionKey = response.getData(); int xLen; - boolean isCurve25519 = CryptlibObjectIdentifiers.curvey25519.equals(eckf.asn1ParseOid()); - if (isCurve25519) { + if (eckf.isX25519()) { xLen = keyEncryptionKey.length; } else { xLen = (keyEncryptionKey.length - 1) / 2; } final byte[] kekX = new byte[xLen]; - System.arraycopy(keyEncryptionKey, isCurve25519 ? 0 : 1, kekX, 0, xLen); + System.arraycopy(keyEncryptionKey, eckf.isX25519() ? 0 : 1, kekX, 0, xLen); final byte[] keyEnc = new byte[encryptedSessionKeyMpi[mpiLength + 2]]; @@ -206,12 +202,11 @@ public class PsoDecryptTokenOp { } } - private byte[] getEcDecipherPayload(ECKeyFormat eckf, byte[] encryptedPoint) throws CardException { - // TODO is this the right curve? - if (CryptlibObjectIdentifiers.curvey25519.equals(eckf.asn1ParseOid())) { + private byte[] getEcDecipherPayload(EcKeyFormat eckf, byte[] encryptedPoint) throws CardException { + if (eckf.isX25519()) { return Arrays.copyOfRange(encryptedPoint, 1, 33); } else { - X9ECParameters x9Params = ECNamedCurveTable.getByOID(eckf.asn1ParseOid()); + X9ECParameters x9Params = ECNamedCurveTable.getByOID(eckf.curveOid()); ECPoint p = x9Params.getCurve().decodePoint(encryptedPoint); if (!p.isValid()) { throw new CardException("Invalid EC point!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/SecurityTokenChangeKeyTokenOp.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/SecurityTokenChangeKeyTokenOp.java index 6c784326d..ff5dcafb2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/SecurityTokenChangeKeyTokenOp.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/SecurityTokenChangeKeyTokenOp.java @@ -32,11 +32,11 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.securitytoken.CardException; import org.sufficientlysecure.keychain.securitytoken.CommandApdu; -import org.sufficientlysecure.keychain.securitytoken.ECKeyFormat; +import org.sufficientlysecure.keychain.securitytoken.EcKeyFormat; import org.sufficientlysecure.keychain.securitytoken.KeyFormat; import org.sufficientlysecure.keychain.securitytoken.KeyType; import org.sufficientlysecure.keychain.securitytoken.OpenPgpCapabilities; -import org.sufficientlysecure.keychain.securitytoken.RSAKeyFormat; +import org.sufficientlysecure.keychain.securitytoken.RsaKeyFormat; import org.sufficientlysecure.keychain.securitytoken.ResponseApdu; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenUtils; @@ -107,37 +107,32 @@ public class SecurityTokenChangeKeyTokenOp { OpenPgpCapabilities openPgpCapabilities = connection.getOpenPgpCapabilities(); KeyFormat formatForKeyType = openPgpCapabilities.getFormatForKeyType(slot); - switch (formatForKeyType.keyFormatType()) { - case RSAKeyFormatType: - if (!secretKey.isRSA()) { - throw new IOException("Security Token not configured for RSA key."); - } - crtSecretKey = secretKey.getSecurityTokenRSASecretKey(); + if (formatForKeyType instanceof RsaKeyFormat) { + if (!secretKey.isRSA()) { + throw new IOException("Security Token not configured for RSA key."); + } + crtSecretKey = secretKey.getSecurityTokenRSASecretKey(); - // Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537. - if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) { - throw new IOException("Invalid public exponent for smart Security Token."); - } + // Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537. + if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) { + throw new IOException("Invalid public exponent for smart Security Token."); + } - keyBytes = SecurityTokenUtils.createRSAPrivKeyTemplate(crtSecretKey, slot, - (RSAKeyFormat) formatForKeyType); - break; + keyBytes = SecurityTokenUtils.createRSAPrivKeyTemplate(crtSecretKey, slot, + (RsaKeyFormat) formatForKeyType); + } else if (formatForKeyType instanceof EcKeyFormat) { + if (!secretKey.isEC()) { + throw new IOException("Security Token not configured for EC key."); + } - case ECKeyFormatType: - if (!secretKey.isEC()) { - throw new IOException("Security Token not configured for EC key."); - } + secretKey.unlock(passphrase); + ecSecretKey = secretKey.getSecurityTokenECSecretKey(); + ecPublicKey = secretKey.getSecurityTokenECPublicKey(); - secretKey.unlock(passphrase); - ecSecretKey = secretKey.getSecurityTokenECSecretKey(); - ecPublicKey = secretKey.getSecurityTokenECPublicKey(); - - keyBytes = SecurityTokenUtils.createECPrivKeyTemplate(ecSecretKey, ecPublicKey, slot, - (ECKeyFormat) formatForKeyType); - break; - - default: - throw new IOException("Key type unsupported by security token."); + keyBytes = SecurityTokenUtils.createECPrivKeyTemplate(ecSecretKey, ecPublicKey, slot, + (EcKeyFormat) formatForKeyType); + } else { + throw new IOException("Key type unsupported by security token."); } } catch (PgpGeneralException e) { throw new IOException(e.getMessage()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/SecurityTokenPsoSignTokenOp.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/SecurityTokenPsoSignTokenOp.java index 1f0907970..71bee4638 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/SecurityTokenPsoSignTokenOp.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/SecurityTokenPsoSignTokenOp.java @@ -30,9 +30,10 @@ import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.securitytoken.CardException; import org.sufficientlysecure.keychain.securitytoken.CommandApdu; +import org.sufficientlysecure.keychain.securitytoken.EcKeyFormat; import org.sufficientlysecure.keychain.securitytoken.KeyFormat; import org.sufficientlysecure.keychain.securitytoken.OpenPgpCapabilities; -import org.sufficientlysecure.keychain.securitytoken.RSAKeyFormat; +import org.sufficientlysecure.keychain.securitytoken.RsaKeyFormat; import org.sufficientlysecure.keychain.securitytoken.ResponseApdu; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection; import timber.log.Timber; @@ -102,56 +103,56 @@ public class SecurityTokenPsoSignTokenOp { } private byte[] prepareData(byte[] hash, int hashAlgo, KeyFormat keyFormat) throws IOException { - byte[] data; - switch (keyFormat.keyFormatType()) { - case RSAKeyFormatType: - data = prepareDsi(hash, hashAlgo); - break; - case ECKeyFormatType: - case EdDSAKeyFormatType: - data = hash; - break; - default: - throw new IOException("Not supported key type!"); + if (keyFormat instanceof RsaKeyFormat) { + return prepareDsi(hash, hashAlgo); + } else if (keyFormat instanceof EcKeyFormat) { + return hash; + } else { + throw new IOException("Not supported key type!"); } - return data; } private byte[] encodeSignature(byte[] signature, KeyFormat keyFormat) throws IOException { // Make sure the signature we received is actually the expected number of bytes long! - switch (keyFormat.keyFormatType()) { - case RSAKeyFormatType: - // no encoding necessary - int modulusLength = ((RSAKeyFormat) keyFormat).getModulusLength(); - if (signature.length != (modulusLength / 8)) { - throw new IOException("Bad signature length! Expected " + (modulusLength / 8) + - " bytes, got " + signature.length); - } - break; - - case ECKeyFormatType: { - // "plain" encoding, see https://github.com/open-keychain/open-keychain/issues/2108 - if (signature.length % 2 != 0) { - throw new IOException("Bad signature length!"); - } - final byte[] br = new byte[signature.length / 2]; - final byte[] bs = new byte[signature.length / 2]; - 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.flush(); - signature = baos.toByteArray(); - break; + if (keyFormat instanceof RsaKeyFormat) { + // no encoding necessary + int modulusLength = ((RsaKeyFormat) keyFormat).modulusLength(); + if (signature.length != (modulusLength / 8)) { + throw new IOException("Bad signature length! Expected " + (modulusLength / 8) + + " bytes, got " + signature.length); } - case EdDSAKeyFormatType: - break; + return signature; + } else if (keyFormat instanceof EcKeyFormat) { + EcKeyFormat ecKeyFormat = (EcKeyFormat) keyFormat; + if (ecKeyFormat.isEdDsa()) { + return signature; + } + + // "plain" encoding, see https://github.com/open-keychain/open-keychain/issues/2108 + if (signature.length % 2 != 0) { + throw new IOException("Bad signature length!"); + } + byte[] br = new byte[signature.length / 2]; + byte[] bs = new byte[signature.length / 2]; + for (int i = 0; i < br.length; ++i) { + br[i] = signature[i]; + bs[i] = signature[br.length + i]; + } + if (br[0] == 0x00 && (br[1] & 0x80) == 0) { + br = Arrays.copyOfRange(br, 1, br.length); + } + if (bs[0] == 0x00 && (bs[1] & 0x80) == 0) { + bs = Arrays.copyOfRange(bs, 1, bs.length); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ASN1OutputStream out = ASN1OutputStream.create(baos); + out.writeObject(new DERSequence(new ASN1Encodable[]{new ASN1Integer(br), new ASN1Integer(bs)})); + out.flush(); + return baos.toByteArray(); + } else { + throw new IOException("Not supported key format!"); } - return signature; } /** diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java index f9ba6b136..f645e6dc1 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java @@ -157,7 +157,7 @@ public class SecurityTokenUtilsTest extends Mockito { "1212121212121212121212121212121212121212121212121212121212121212" + "1212121212121212121212121212121212121212121212121212121212121212" ), - SecurityTokenUtils.createRSAPrivKeyTemplate(key2048, KeyType.AUTH, new RSAKeyFormat(2048, exp.bitLength(), RSAKeyFormat.RSAAlgorithmFormat.STANDARD))); + SecurityTokenUtils.createRSAPrivKeyTemplate(key2048, KeyType.AUTH, new RsaKeyFormat(2048, exp.bitLength(), RsaKeyFormat.RSAAlgorithmFormat.STANDARD))); } @Test