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

View File

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

View File

@@ -108,6 +108,7 @@ public class SecurityTokenPsoSignTokenOp {
data = prepareDsi(hash, hashAlgo);
break;
case ECKeyFormatType:
case EdDSAKeyFormatType:
data = hash;
break;
default:
@@ -128,7 +129,7 @@ public class SecurityTokenPsoSignTokenOp {
}
break;
case ECKeyFormatType:
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!");
@@ -142,8 +143,12 @@ public class SecurityTokenPsoSignTokenOp {
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();
out.flush();
signature = baos.toByteArray();
break;
}
case EdDSAKeyFormatType:
break;
}
return signature;

View File

@@ -4,6 +4,7 @@ package org.sufficientlysecure.keychain;
import java.io.InputStream;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.util.encoders.Hex;
import org.junit.Test;
import org.junit.runner.RunWith;
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.OperationResult.OperationLog;
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.pgp.CanonicalizedKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
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.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
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.assertFalse;
@@ -56,6 +61,7 @@ import static junit.framework.Assert.assertTrue;
@SuppressWarnings("WeakerAccess")
@RunWith(KeychainTestRunner.class)
public class EddsaTest {
public static final byte[] SIGNED_BYTES = "hi".getBytes();
private KeyWritableRepository keyRepository;
private Application context;
@@ -75,7 +81,7 @@ public class EddsaTest {
@Test
public void testGpgSampleSignature() throws Exception {
// 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");
PgpDecryptVerifyInputParcel pgpDecryptVerifyInputParcel = PgpDecryptVerifyInputParcel.builder()
@@ -89,6 +95,32 @@ public class EddsaTest {
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
public void testCreateEddsa() throws Exception {
SaveKeyringParcel.Builder builder = SaveKeyringParcel.buildNewKeyringParcel();
@@ -106,7 +138,7 @@ public class EddsaTest {
assertNotNull(canonicalizedKeyRing);
}
private UncachedKeyRing loadKeyringFromResource(String name) throws Exception {
private UncachedKeyRing loadPubkeyFromResource(String name) throws Exception {
UncachedKeyRing ring = readRingFromResource(name);
SaveKeyringResult saveKeyringResult = keyRepository.savePublicKeyRing(ring);
assertTrue(saveKeyringResult.success());
@@ -114,6 +146,14 @@ public class EddsaTest {
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 {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream input = EddsaTest.class.getResourceAsStream(name);