diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EdDSAKeyFormat.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EdDSAKeyFormat.java new file mode 100644 index 000000000..1ed413651 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/EdDSAKeyFormat.java @@ -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 . + */ + +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 a1b00397a..fe043cd07 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyFormat.java @@ -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]); 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 4d227b023..814ca21fb 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 @@ -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; } 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 7a0332f41..1f0907970 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 @@ -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; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ArmoredInputStreamTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ArmoredInputStreamTest.java index 57452d64b..a4d95f02e 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ArmoredInputStreamTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ArmoredInputStreamTest.java @@ -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; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/EddsaTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/EddsaTest.java index 41ce42c9a..ddb0100a5 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/EddsaTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/EddsaTest.java @@ -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); diff --git a/OpenKeychain/src/test/resources/test-keys/eddsa-key.sec b/OpenKeychain/src/test/resources/test-keys/eddsa-key.sec new file mode 100644 index 000000000..fd2cd45ed Binary files /dev/null and b/OpenKeychain/src/test/resources/test-keys/eddsa-key.sec differ