improve and simplify key formats
This commit is contained in:
parent
39ef489a92
commit
56254aedb7
|
@ -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 {
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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<ASN1ObjectIdentifier> 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,14 +73,11 @@ public class PsoDecryptTokenOp {
|
|||
connection.verifyPinForOther();
|
||||
|
||||
KeyFormat kf = connection.getOpenPgpCapabilities().getEncryptKeyFormat();
|
||||
switch (kf.keyFormatType()) {
|
||||
case RSAKeyFormatType:
|
||||
if (kf instanceof RsaKeyFormat) {
|
||||
return decryptSessionKeyRsa(encryptedSessionKeyMpi);
|
||||
|
||||
case ECKeyFormatType:
|
||||
return decryptSessionKeyEcdh(encryptedSessionKeyMpi, (ECKeyFormat) kf, publicKey);
|
||||
|
||||
default:
|
||||
} 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!");
|
||||
|
|
|
@ -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,8 +107,7 @@ public class SecurityTokenChangeKeyTokenOp {
|
|||
|
||||
OpenPgpCapabilities openPgpCapabilities = connection.getOpenPgpCapabilities();
|
||||
KeyFormat formatForKeyType = openPgpCapabilities.getFormatForKeyType(slot);
|
||||
switch (formatForKeyType.keyFormatType()) {
|
||||
case RSAKeyFormatType:
|
||||
if (formatForKeyType instanceof RsaKeyFormat) {
|
||||
if (!secretKey.isRSA()) {
|
||||
throw new IOException("Security Token not configured for RSA key.");
|
||||
}
|
||||
|
@ -120,10 +119,8 @@ public class SecurityTokenChangeKeyTokenOp {
|
|||
}
|
||||
|
||||
keyBytes = SecurityTokenUtils.createRSAPrivKeyTemplate(crtSecretKey, slot,
|
||||
(RSAKeyFormat) formatForKeyType);
|
||||
break;
|
||||
|
||||
case ECKeyFormatType:
|
||||
(RsaKeyFormat) formatForKeyType);
|
||||
} else if (formatForKeyType instanceof EcKeyFormat) {
|
||||
if (!secretKey.isEC()) {
|
||||
throw new IOException("Security Token not configured for EC key.");
|
||||
}
|
||||
|
@ -133,10 +130,8 @@ public class SecurityTokenChangeKeyTokenOp {
|
|||
ecPublicKey = secretKey.getSecurityTokenECPublicKey();
|
||||
|
||||
keyBytes = SecurityTokenUtils.createECPrivKeyTemplate(ecSecretKey, ecPublicKey, slot,
|
||||
(ECKeyFormat) formatForKeyType);
|
||||
break;
|
||||
|
||||
default:
|
||||
(EcKeyFormat) formatForKeyType);
|
||||
} else {
|
||||
throw new IOException("Key type unsupported by security token.");
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
|
|
|
@ -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:
|
||||
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:
|
||||
if (keyFormat instanceof RsaKeyFormat) {
|
||||
// no encoding necessary
|
||||
int modulusLength = ((RSAKeyFormat) keyFormat).getModulusLength();
|
||||
int modulusLength = ((RsaKeyFormat) keyFormat).modulusLength();
|
||||
if (signature.length != (modulusLength / 8)) {
|
||||
throw new IOException("Bad signature length! Expected " + (modulusLength / 8) +
|
||||
" bytes, got " + signature.length);
|
||||
}
|
||||
break;
|
||||
|
||||
case ECKeyFormatType: {
|
||||
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!");
|
||||
}
|
||||
final byte[] br = new byte[signature.length / 2];
|
||||
final byte[] bs = new byte[signature.length / 2];
|
||||
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];
|
||||
}
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ASN1OutputStream out = new ASN1OutputStream(baos);
|
||||
out.writeObject(new DERSequence(new ASN1Encodable[] { new ASN1Integer(br), new ASN1Integer(bs) }));
|
||||
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();
|
||||
signature = baos.toByteArray();
|
||||
break;
|
||||
return baos.toByteArray();
|
||||
} else {
|
||||
throw new IOException("Not supported key format!");
|
||||
}
|
||||
|
||||
case EdDSAKeyFormatType:
|
||||
break;
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue