Merge branch 'gnuk-djb'

This commit is contained in:
Vincent Breitmoser
2018-02-20 01:06:35 +01:00
7 changed files with 113 additions and 14 deletions

View File

@@ -0,0 +1,41 @@
/*
* 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));
}
}

View File

@@ -27,7 +27,8 @@ public abstract class KeyFormat {
public enum KeyFormatType { public enum KeyFormatType {
RSAKeyFormatType, RSAKeyFormatType,
ECKeyFormatType ECKeyFormatType,
EdDSAKeyFormatType
} }
private final KeyFormatType mKeyFormatType; private final KeyFormatType mKeyFormatType;
@@ -53,7 +54,7 @@ public abstract class KeyFormat {
case PublicKeyAlgorithmTags.ECDH: case PublicKeyAlgorithmTags.ECDH:
case PublicKeyAlgorithmTags.ECDSA: case PublicKeyAlgorithmTags.ECDSA:
if (bytes.length < 2) { if (bytes.length < 2) {
throw new IllegalArgumentException("Bad length for RSA attributes"); throw new IllegalArgumentException("Bad length for EC attributes");
} }
int len = bytes.length - 1; int len = bytes.length - 1;
if (bytes[bytes.length - 1] == (byte)0xff) { if (bytes[bytes.length - 1] == (byte)0xff) {
@@ -65,6 +66,8 @@ public abstract class KeyFormat {
System.arraycopy(bytes, 1, boid, 2, len); System.arraycopy(bytes, 1, boid, 2, len);
final ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(boid); final ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(boid);
return new ECKeyFormat(oid, ECKeyFormat.ECAlgorithmFormat.from(bytes[0], bytes[bytes.length - 1])); return new ECKeyFormat(oid, ECKeyFormat.ECAlgorithmFormat.from(bytes[0], bytes[bytes.length - 1]));
case PublicKeyAlgorithmTags.EDDSA:
return new EdDSAKeyFormat();
default: default:
throw new IllegalArgumentException("Unsupported Algorithm id " + bytes[0]); throw new IllegalArgumentException("Unsupported Algorithm id " + bytes[0]);

View File

@@ -32,6 +32,7 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.jcajce.util.MessageDigestUtils; import org.bouncycastle.jcajce.util.MessageDigestUtils;
import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
@@ -114,13 +115,7 @@ public class PsoDecryptTokenOp {
int mpiLength = getMpiLength(encryptedSessionKeyMpi); int mpiLength = getMpiLength(encryptedSessionKeyMpi);
byte[] encryptedPoint = Arrays.copyOfRange(encryptedSessionKeyMpi, 2, mpiLength + 2); byte[] encryptedPoint = Arrays.copyOfRange(encryptedSessionKeyMpi, 2, mpiLength + 2);
X9ECParameters x9Params = NISTNamedCurves.getByOID(eckf.getCurveOID()); byte[] psoDecipherPayload = getEcDecipherPayload(eckf, encryptedPoint);
ECPoint p = x9Params.getCurve().decodePoint(encryptedPoint);
if (!p.isValid()) {
throw new CardException("Invalid EC point!");
}
byte[] psoDecipherPayload = p.getEncoded(false);
byte[] dataLen; byte[] dataLen;
if (psoDecipherPayload.length < 128) { if (psoDecipherPayload.length < 128) {
@@ -198,6 +193,20 @@ public class PsoDecryptTokenOp {
} }
} }
private byte[] getEcDecipherPayload(ECKeyFormat eckf, byte[] encryptedPoint) throws CardException {
if (CustomNamedCurves.CV25519.equals(eckf.getCurveOID())) {
return Arrays.copyOfRange(encryptedPoint, 1, 33);
} else {
X9ECParameters x9Params = NISTNamedCurves.getByOID(eckf.getCurveOID());
ECPoint p = x9Params.getCurve().decodePoint(encryptedPoint);
if (!p.isValid()) {
throw new CardException("Invalid EC point!");
}
return p.getEncoded(false);
}
}
private int getMpiLength(byte[] multiPrecisionInteger) { private int getMpiLength(byte[] multiPrecisionInteger) {
return ((((multiPrecisionInteger[0] & 0xff) << 8) + (multiPrecisionInteger[1] & 0xff)) + 7) / 8; return ((((multiPrecisionInteger[0] & 0xff) << 8) + (multiPrecisionInteger[1] & 0xff)) + 7) / 8;
} }

View File

@@ -108,6 +108,7 @@ public class SecurityTokenPsoSignTokenOp {
data = prepareDsi(hash, hashAlgo); data = prepareDsi(hash, hashAlgo);
break; break;
case ECKeyFormatType: case ECKeyFormatType:
case EdDSAKeyFormatType:
data = hash; data = hash;
break; break;
default: default:
@@ -128,7 +129,7 @@ public class SecurityTokenPsoSignTokenOp {
} }
break; break;
case ECKeyFormatType: case ECKeyFormatType: {
// "plain" encoding, see https://github.com/open-keychain/open-keychain/issues/2108 // "plain" encoding, see https://github.com/open-keychain/open-keychain/issues/2108
if (signature.length % 2 != 0) { if (signature.length % 2 != 0) {
throw new IOException("Bad signature length!"); throw new IOException("Bad signature length!");
@@ -146,6 +147,10 @@ public class SecurityTokenPsoSignTokenOp {
signature = baos.toByteArray(); signature = baos.toByteArray();
break; break;
} }
case EdDSAKeyFormatType:
break;
}
return signature; return signature;
} }

View File

@@ -4,6 +4,7 @@ package org.sufficientlysecure.keychain;
import java.io.InputStream; import java.io.InputStream;
import org.bouncycastle.bcpg.ArmoredInputStream; import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.util.encoders.Hex;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.sufficientlysecure.keychain.pgp.UncachedKeyringTest; import org.sufficientlysecure.keychain.pgp.UncachedKeyringTest;

View File

@@ -37,15 +37,20 @@ import org.sufficientlysecure.keychain.KeychainTestRunner;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertFalse;
@@ -56,6 +61,7 @@ import static junit.framework.Assert.assertTrue;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@RunWith(KeychainTestRunner.class) @RunWith(KeychainTestRunner.class)
public class EddsaTest { public class EddsaTest {
public static final byte[] SIGNED_BYTES = "hi".getBytes();
private KeyWritableRepository keyRepository; private KeyWritableRepository keyRepository;
private Application context; private Application context;
@@ -75,7 +81,7 @@ public class EddsaTest {
@Test @Test
public void testGpgSampleSignature() throws Exception { public void testGpgSampleSignature() throws Exception {
// key from GnuPG's test suite, sample msg generated using GnuPG v2.1.18 // key from GnuPG's test suite, sample msg generated using GnuPG v2.1.18
UncachedKeyRing ring = loadKeyringFromResource("/test-keys/eddsa-sample-1-pub.asc"); UncachedKeyRing ring = loadPubkeyFromResource("/test-keys/eddsa-sample-1-pub.asc");
byte[] signedText = readBytesFromResource("/test-keys/eddsa-sample-msg.asc"); byte[] signedText = readBytesFromResource("/test-keys/eddsa-sample-msg.asc");
PgpDecryptVerifyInputParcel pgpDecryptVerifyInputParcel = PgpDecryptVerifyInputParcel.builder() PgpDecryptVerifyInputParcel pgpDecryptVerifyInputParcel = PgpDecryptVerifyInputParcel.builder()
@@ -89,6 +95,32 @@ public class EddsaTest {
assertEquals(ring.getMasterKeyId(), result.getSignatureResult().getKeyId()); assertEquals(ring.getMasterKeyId(), result.getSignatureResult().getKeyId());
} }
@Test
public void testEddsaSign() throws Exception {
// key from GnuPG's test suite, sample msg generated using GnuPG v2.1.18
UncachedKeyRing ring = loadSeckeyFromResource("/test-keys/eddsa-key.sec");
PgpSignEncryptData data = PgpSignEncryptData.builder()
.setDetachedSignature(true)
.setSignatureMasterKeyId(ring.getMasterKeyId())
.build();
PgpSignEncryptInputParcel inputParcel = PgpSignEncryptInputParcel.createForBytes(
data, null, SIGNED_BYTES);
PgpSignEncryptOperation op = new PgpSignEncryptOperation(context, keyRepository, null);
PgpSignEncryptResult result = op.execute(inputParcel, CryptoInputParcel.createCryptoInputParcel());
assertTrue(result.success());
PgpDecryptVerifyInputParcel pgpDecryptVerifyInputParcel = PgpDecryptVerifyInputParcel.builder()
.setInputBytes(SIGNED_BYTES).setDetachedSignature(result.getDetachedSignature()).build();
PgpDecryptVerifyOperation decryptVerifyOperation = new PgpDecryptVerifyOperation(context, keyRepository, null);
DecryptVerifyResult result2 = decryptVerifyOperation.execute(pgpDecryptVerifyInputParcel, null);
assertTrue(result2.success());
}
@Test @Test
public void testCreateEddsa() throws Exception { public void testCreateEddsa() throws Exception {
SaveKeyringParcel.Builder builder = SaveKeyringParcel.buildNewKeyringParcel(); SaveKeyringParcel.Builder builder = SaveKeyringParcel.buildNewKeyringParcel();
@@ -106,7 +138,7 @@ public class EddsaTest {
assertNotNull(canonicalizedKeyRing); assertNotNull(canonicalizedKeyRing);
} }
private UncachedKeyRing loadKeyringFromResource(String name) throws Exception { private UncachedKeyRing loadPubkeyFromResource(String name) throws Exception {
UncachedKeyRing ring = readRingFromResource(name); UncachedKeyRing ring = readRingFromResource(name);
SaveKeyringResult saveKeyringResult = keyRepository.savePublicKeyRing(ring); SaveKeyringResult saveKeyringResult = keyRepository.savePublicKeyRing(ring);
assertTrue(saveKeyringResult.success()); assertTrue(saveKeyringResult.success());
@@ -114,6 +146,14 @@ public class EddsaTest {
return ring; return ring;
} }
private UncachedKeyRing loadSeckeyFromResource(String name) throws Exception {
UncachedKeyRing ring = readRingFromResource(name);
SaveKeyringResult saveKeyringResult = keyRepository.saveSecretKeyRing(ring);
assertTrue(saveKeyringResult.success());
assertFalse(saveKeyringResult.getLog().containsWarnings());
return ring;
}
private byte[] readBytesFromResource(String name) throws Exception { private byte[] readBytesFromResource(String name) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream input = EddsaTest.class.getResourceAsStream(name); InputStream input = EddsaTest.class.getResourceAsStream(name);