Use new officially supported way for local unit tests, many dependencies upgraded for this, temporary disabled separate debug builds

This commit is contained in:
Dominik Schürmann
2015-06-11 00:05:13 +02:00
parent 05fcbcae7b
commit d16b09b2a6
131 changed files with 151 additions and 234 deletions

View File

@@ -0,0 +1,251 @@
/*
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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.operations;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import org.sufficientlysecure.keychain.util.TestingUtils;
import java.io.PrintStream;
import java.security.Security;
import java.util.ArrayList;
import java.util.Random;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
public class CertifyOperationTest {
static UncachedKeyRing mStaticRing1, mStaticRing2;
static Passphrase mKeyPhrase1 = TestingUtils.genPassphrase(true);
static Passphrase mKeyPhrase2 = TestingUtils.genPassphrase(true);
static PrintStream oldShadowStream;
@BeforeClass
public static void setUpOnce() throws Exception {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
oldShadowStream = ShadowLog.stream;
// ShadowLog.stream = System.out;
Random random = new Random();
PgpKeyOperation op = new PgpKeyOperation(null);
{
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
parcel.mAddUserIds.add("derp");
parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase1);
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
Assert.assertTrue("initial test key creation must succeed", result.success());
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
mStaticRing1 = result.getRing();
}
{
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
parcel.mAddUserIds.add("ditz");
byte[] uatdata = new byte[random.nextInt(150)+10];
random.nextBytes(uatdata);
parcel.mAddUserAttribute.add(
WrappedUserAttribute.fromSubpacket(random.nextInt(100)+1, uatdata));
parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase2);
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
Assert.assertTrue("initial test key creation must succeed", result.success());
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
mStaticRing2 = result.getRing();
}
}
@Before
public void setUp() throws Exception {
ProviderHelper providerHelper = new ProviderHelper(RuntimeEnvironment.application);
// don't log verbosely here, we're not here to test imports
ShadowLog.stream = oldShadowStream;
providerHelper.saveSecretKeyRing(mStaticRing1, new ProgressScaler());
providerHelper.savePublicKeyRing(mStaticRing2.extractPublicKeyRing(), new ProgressScaler());
// ok NOW log verbosely!
ShadowLog.stream = System.out;
}
@Test
public void testSelfCertifyFlag() throws Exception {
CanonicalizedPublicKeyRing ring = new ProviderHelper(RuntimeEnvironment.application)
.getCanonicalizedPublicKeyRing(mStaticRing1.getMasterKeyId());
Assert.assertEquals("secret key must be marked self-certified in database",
// TODO this should be more correctly be VERIFIED_SELF at some point!
Certs.VERIFIED_SECRET, ring.getVerified());
}
@Test
public void testCertifyId() throws Exception {
CertifyOperation op = new CertifyOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null, null);
{
CanonicalizedPublicKeyRing ring = new ProviderHelper(RuntimeEnvironment.application)
.getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId());
Assert.assertEquals("public key must not be marked verified prior to certification",
Certs.UNVERIFIED, ring.getVerified());
}
CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId());
actions.add(new CertifyAction(mStaticRing2.getMasterKeyId(),
mStaticRing2.getPublicKey().getUnorderedUserIds()));
CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null);
Assert.assertTrue("certification must succeed", result.success());
{
CanonicalizedPublicKeyRing ring = new ProviderHelper(RuntimeEnvironment.application)
.getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId());
Assert.assertEquals("new key must be verified now",
Certs.VERIFIED_SECRET, ring.getVerified());
}
}
@Test
public void testCertifyAttribute() throws Exception {
CertifyOperation op = new CertifyOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null, null);
{
CanonicalizedPublicKeyRing ring = new ProviderHelper(RuntimeEnvironment.application)
.getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId());
Assert.assertEquals("public key must not be marked verified prior to certification",
Certs.UNVERIFIED, ring.getVerified());
}
CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId());
actions.add(new CertifyAction(mStaticRing2.getMasterKeyId(), null,
mStaticRing2.getPublicKey().getUnorderedUserAttributes()));
CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null);
Assert.assertTrue("certification must succeed", result.success());
{
CanonicalizedPublicKeyRing ring = new ProviderHelper(RuntimeEnvironment.application)
.getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId());
Assert.assertEquals("new key must be verified now",
Certs.VERIFIED_SECRET, ring.getVerified());
}
}
@Test
public void testCertifySelf() throws Exception {
CertifyOperation op = new CertifyOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null, null);
CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId());
actions.add(new CertifyAction(mStaticRing1.getMasterKeyId(),
mStaticRing2.getPublicKey().getUnorderedUserIds()));
CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null);
Assert.assertFalse("certification with itself must fail!", result.success());
Assert.assertTrue("error msg must be about self certification",
result.getLog().containsType(LogType.MSG_CRT_ERROR_SELF));
}
@Test
public void testCertifyNonexistent() throws Exception {
CertifyOperation op = new CertifyOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null, null);
{
CertifyActionsParcel actions = new CertifyActionsParcel(mStaticRing1.getMasterKeyId());
ArrayList<String> uids = new ArrayList<String>();
uids.add("nonexistent");
actions.add(new CertifyAction(1234L, uids));
CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null);
Assert.assertFalse("certification of nonexistent key must fail", result.success());
Assert.assertTrue("must contain error msg about not found",
result.getLog().containsType(LogType.MSG_CRT_WARN_NOT_FOUND));
}
{
CertifyActionsParcel actions = new CertifyActionsParcel(1234L);
actions.add(new CertifyAction(mStaticRing1.getMasterKeyId(),
mStaticRing2.getPublicKey().getUnorderedUserIds()));
CertifyResult result = op.certify(actions, new CryptoInputParcel(mKeyPhrase1), null);
Assert.assertFalse("certification of nonexistent key must fail", result.success());
Assert.assertTrue("must contain error msg about not found",
result.getLog().containsType(LogType.MSG_CRT_ERROR_MASTER_NOT_FOUND));
}
}
}

View File

@@ -0,0 +1,228 @@
/*
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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.operations;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow;
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import org.sufficientlysecure.keychain.util.TestingUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.security.Security;
import java.util.Iterator;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
public class ExportTest {
static Passphrase mPassphrase = TestingUtils.genPassphrase(true);
static UncachedKeyRing mStaticRing1, mStaticRing2;
static Passphrase mKeyPhrase1 = TestingUtils.genPassphrase(true);
static Passphrase mKeyPhrase2 = TestingUtils.genPassphrase(true);
static PrintStream oldShadowStream;
@BeforeClass
public static void setUpOnce() throws Exception {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
oldShadowStream = ShadowLog.stream;
// ShadowLog.stream = System.out;
PgpKeyOperation op = new PgpKeyOperation(null);
{
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
parcel.mAddUserIds.add("snips");
parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase1);
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
Assert.assertTrue("initial test key creation must succeed", result.success());
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
mStaticRing1 = result.getRing();
}
{
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
parcel.mAddUserIds.add("snails");
parcel.mNewUnlock = new ChangeUnlockParcel(null, new Passphrase("1234"));
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
Assert.assertTrue("initial test key creation must succeed", result.success());
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
mStaticRing2 = result.getRing();
}
}
@Before
public void setUp() {
ProviderHelper providerHelper = new ProviderHelper(RuntimeEnvironment.application);
// don't log verbosely here, we're not here to test imports
ShadowLog.stream = oldShadowStream;
providerHelper.saveSecretKeyRing(mStaticRing1, new ProgressScaler());
providerHelper.saveSecretKeyRing(mStaticRing2, new ProgressScaler());
// ok NOW log verbosely!
ShadowLog.stream = System.out;
}
@Test
public void testExportAll() throws Exception {
ImportExportOperation op = new ImportExportOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null);
// make sure there is a local cert (so the later checks that there are none are meaningful)
Assert.assertTrue("second keyring has local certification", checkForLocal(mStaticRing2));
ByteArrayOutputStream out = new ByteArrayOutputStream();
ExportResult result = op.exportKeyRings(new OperationLog(), null, false, out);
Assert.assertTrue("export must be a success", result.success());
long masterKeyId1, masterKeyId2;
if (mStaticRing1.getMasterKeyId() < mStaticRing2.getMasterKeyId()) {
masterKeyId1 = mStaticRing1.getMasterKeyId();
masterKeyId2 = mStaticRing2.getMasterKeyId();
} else {
masterKeyId2 = mStaticRing1.getMasterKeyId();
masterKeyId1 = mStaticRing2.getMasterKeyId();
}
IteratorWithIOThrow<UncachedKeyRing> unc =
UncachedKeyRing.fromStream(new ByteArrayInputStream(out.toByteArray()));
{
Assert.assertTrue("export must have two keys (1/2)", unc.hasNext());
UncachedKeyRing ring = unc.next();
Assert.assertEquals("first exported key has correct masterkeyid",
masterKeyId1, ring.getMasterKeyId());
Assert.assertFalse("first exported key must not be secret", ring.isSecret());
Assert.assertFalse("there must be no local signatures in an exported keyring",
checkForLocal(ring));
}
{
Assert.assertTrue("export must have two keys (2/2)", unc.hasNext());
UncachedKeyRing ring = unc.next();
Assert.assertEquals("second exported key has correct masterkeyid",
masterKeyId2, ring.getMasterKeyId());
Assert.assertFalse("second exported key must not be secret", ring.isSecret());
Assert.assertFalse("there must be no local signatures in an exported keyring",
checkForLocal(ring));
}
out = new ByteArrayOutputStream();
result = op.exportKeyRings(new OperationLog(), null, true, out);
Assert.assertTrue("export must be a success", result.success());
unc = UncachedKeyRing.fromStream(new ByteArrayInputStream(out.toByteArray()));
{
Assert.assertTrue("export must have four keys (1/4)", unc.hasNext());
UncachedKeyRing ring = unc.next();
Assert.assertEquals("1/4 exported key has correct masterkeyid",
masterKeyId1, ring.getMasterKeyId());
Assert.assertFalse("1/4 exported key must not be public", ring.isSecret());
Assert.assertFalse("there must be no local signatures in an exported keyring",
checkForLocal(ring));
Assert.assertTrue("export must have four keys (2/4)", unc.hasNext());
ring = unc.next();
Assert.assertEquals("2/4 exported key has correct masterkeyid",
masterKeyId1, ring.getMasterKeyId());
Assert.assertTrue("2/4 exported key must be public", ring.isSecret());
Assert.assertFalse("there must be no local signatures in an exported keyring",
checkForLocal(ring));
}
{
Assert.assertTrue("export must have four keys (3/4)", unc.hasNext());
UncachedKeyRing ring = unc.next();
Assert.assertEquals("3/4 exported key has correct masterkeyid",
masterKeyId2, ring.getMasterKeyId());
Assert.assertFalse("3/4 exported key must not be public", ring.isSecret());
Assert.assertFalse("there must be no local signatures in an exported keyring",
checkForLocal(ring));
Assert.assertTrue("export must have four keys (4/4)", unc.hasNext());
ring = unc.next();
Assert.assertEquals("4/4 exported key has correct masterkeyid",
masterKeyId2, ring.getMasterKeyId());
Assert.assertTrue("4/4 exported key must be public", ring.isSecret());
Assert.assertFalse("there must be no local signatures in an exported keyring",
checkForLocal(ring));
}
}
/** This function checks whether or not there are any local signatures in a keyring. */
private boolean checkForLocal(UncachedKeyRing ring) {
Iterator<WrappedSignature> sigs = ring.getPublicKey().getSignatures();
while (sigs.hasNext()) {
if (sigs.next().isLocal()) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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.operations;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import org.sufficientlysecure.keychain.util.TestingUtils;
import java.io.PrintStream;
import java.security.Security;
import java.util.Iterator;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
public class PromoteKeyOperationTest {
static UncachedKeyRing mStaticRing;
static Passphrase mKeyPhrase1 = TestingUtils.genPassphrase(true);
static PrintStream oldShadowStream;
@BeforeClass
public static void setUpOnce() throws Exception {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
oldShadowStream = ShadowLog.stream;
// ShadowLog.stream = System.out;
PgpKeyOperation op = new PgpKeyOperation(null);
{
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
parcel.mAddUserIds.add("derp");
parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase1);
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
Assert.assertTrue("initial test key creation must succeed", result.success());
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
mStaticRing = result.getRing();
}
}
@Before
public void setUp() throws Exception {
ProviderHelper providerHelper = new ProviderHelper(RuntimeEnvironment.application);
// don't log verbosely here, we're not here to test imports
ShadowLog.stream = oldShadowStream;
providerHelper.savePublicKeyRing(mStaticRing.extractPublicKeyRing(), new ProgressScaler());
// ok NOW log verbosely!
ShadowLog.stream = System.out;
}
@Test
public void testPromote() throws Exception {
PromoteKeyOperation op = new PromoteKeyOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null, null);
PromoteKeyResult result = op.execute(mStaticRing.getMasterKeyId(), null, null);
Assert.assertTrue("promotion must succeed", result.success());
{
CachedPublicKeyRing ring = new ProviderHelper(RuntimeEnvironment.application)
.getCachedPublicKeyRing(mStaticRing.getMasterKeyId());
Assert.assertTrue("key must have a secret now", ring.hasAnySecret());
Iterator<UncachedPublicKey> it = mStaticRing.getPublicKeys();
while (it.hasNext()) {
long keyId = it.next().getKeyId();
Assert.assertEquals("all subkeys must be gnu dummy",
SecretKeyType.GNU_DUMMY, ring.getSecretKeyType(keyId));
}
}
}
@Test
public void testPromoteDivert() throws Exception {
PromoteKeyOperation op = new PromoteKeyOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null, null);
byte[] aid = Hex.decode("D2760001240102000000012345670000");
PromoteKeyResult result = op.execute(mStaticRing.getMasterKeyId(), aid, null);
Assert.assertTrue("promotion must succeed", result.success());
{
CanonicalizedSecretKeyRing ring = new ProviderHelper(RuntimeEnvironment.application)
.getCanonicalizedSecretKeyRing(mStaticRing.getMasterKeyId());
for (CanonicalizedSecretKey key : ring.secretKeyIterator()) {
Assert.assertEquals("all subkeys must be divert-to-card",
SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType());
Assert.assertArrayEquals("all subkeys must have correct iv",
aid, key.getIv());
}
}
}
@Test
public void testPromoteDivertSpecific() throws Exception {
PromoteKeyOperation op = new PromoteKeyOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null, null);
byte[] aid = Hex.decode("D2760001240102000000012345670000");
// only promote the first, rest stays dummy
long keyId = KeyringTestingHelper.getSubkeyId(mStaticRing, 1);
PromoteKeyResult result = op.execute(mStaticRing.getMasterKeyId(), aid, new long[] {
keyId
});
Assert.assertTrue("promotion must succeed", result.success());
{
CanonicalizedSecretKeyRing ring = new ProviderHelper(RuntimeEnvironment.application)
.getCanonicalizedSecretKeyRing(mStaticRing.getMasterKeyId());
for (CanonicalizedSecretKey key : ring.secretKeyIterator()) {
if (key.getKeyId() == keyId) {
Assert.assertEquals("subkey must be divert-to-card",
SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType());
Assert.assertArrayEquals("subkey must have correct iv",
aid, key.getIv());
} else {
Assert.assertEquals("some subkeys must be gnu dummy",
SecretKeyType.GNU_DUMMY, key.getSecretKeyType());
}
}
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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.pgp;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.sufficientlysecure.keychain.BuildConfig;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
public class KeyRingTest {
@Test
public void splitCompleteUserIdShouldReturnAll3Components() throws Exception {
KeyRing.UserId info = KeyRing.splitUserId("Max Mustermann (this is a comment) <max@example.com>");
Assert.assertEquals("Max Mustermann", info.name);
Assert.assertEquals("this is a comment", info.comment);
Assert.assertEquals("max@example.com", info.email);
}
@Test
public void splitUserIdWithAllButCommentShouldReturnNameAndEmail() throws Exception {
KeyRing.UserId info = KeyRing.splitUserId("Max Mustermann <max@example.com>");
Assert.assertEquals("Max Mustermann", info.name);
Assert.assertNull(info.comment);
Assert.assertEquals("max@example.com", info.email);
}
@Test
public void splitUserIdWithAllButEmailShouldReturnNameAndComment() throws Exception {
KeyRing.UserId info = KeyRing.splitUserId("Max Mustermann (this is a comment)");
Assert.assertEquals(info.name, "Max Mustermann");
Assert.assertEquals(info.comment, "this is a comment");
Assert.assertNull(info.email);
}
}

View File

@@ -0,0 +1,587 @@
/*
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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.pgp;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.robolectric.*;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import org.sufficientlysecure.keychain.util.TestingUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.security.Security;
import java.util.HashSet;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
public class PgpEncryptDecryptTest {
static Passphrase mPassphrase = TestingUtils.genPassphrase(true);
static UncachedKeyRing mStaticRing1, mStaticRing2;
static Passphrase mKeyPhrase1 = TestingUtils.genPassphrase(true);
static Passphrase mKeyPhrase2 = TestingUtils.genPassphrase(true);
static PrintStream oldShadowStream;
@BeforeClass
public static void setUpOnce() throws Exception {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
oldShadowStream = ShadowLog.stream;
// ShadowLog.stream = System.out;
PgpKeyOperation op = new PgpKeyOperation(null);
{
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
parcel.mAddUserIds.add("bloom");
parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase1);
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
Assert.assertTrue("initial test key creation must succeed", result.success());
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
mStaticRing1 = result.getRing();
}
{
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.DSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.ELGAMAL, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
parcel.mAddUserIds.add("belle");
parcel.mNewUnlock = new ChangeUnlockParcel(mKeyPhrase2);
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
Assert.assertTrue("initial test key creation must succeed", result.success());
Assert.assertNotNull("initial test key creation must succeed", result.getRing());
mStaticRing2 = result.getRing();
}
}
@Before
public void setUp() {
ProviderHelper providerHelper = new ProviderHelper(RuntimeEnvironment.application);
// don't log verbosely here, we're not here to test imports
ShadowLog.stream = oldShadowStream;
providerHelper.saveSecretKeyRing(mStaticRing1, new ProgressScaler());
providerHelper.saveSecretKeyRing(mStaticRing2, new ProgressScaler());
// ok NOW log verbosely!
ShadowLog.stream = System.out;
}
@Test
public void testSymmetricEncryptDecrypt() {
String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true);
byte[] ciphertext;
{ // encrypt data with a given passphrase
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(plaintext.getBytes());
PgpSignEncryptOperation op = new PgpSignEncryptOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null);
InputData data = new InputData(in, in.available());
PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
b.setSymmetricPassphrase(mPassphrase);
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
PgpSignEncryptResult result = op.execute(b, new CryptoInputParcel(), data, out);
Assert.assertTrue("encryption must succeed", result.success());
ciphertext = out.toByteArray();
}
{ // decryption with same passphrase should yield the same result
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify op = new PgpDecryptVerify(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
input.setAllowSymmetricDecryption(true);
DecryptVerifyResult result = op.execute(
input, new CryptoInputParcel(mPassphrase), data, out);
Assert.assertTrue("decryption must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext should equal plaintext",
out.toByteArray(), plaintext.getBytes());
Assert.assertNull("signature should be an error", result.getSignatureResult());
OpenPgpMetadata metadata = result.getDecryptMetadata();
Assert.assertEquals("filesize must be correct",
out.toByteArray().length, metadata.getOriginalSize());
}
{ // decryption with a bad passphrase should fail
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify op = new PgpDecryptVerify(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
input.setAllowSymmetricDecryption(true);
DecryptVerifyResult result = op.execute(input,
new CryptoInputParcel(new Passphrase(new String(mPassphrase.getCharArray()) + "x")),
data, out);
Assert.assertFalse("decryption must fail", result.success());
Assert.assertEquals("decrypted plaintext should be empty", 0, out.size());
Assert.assertNull("signature should be an error", result.getSignatureResult());
}
{ // decryption with an unset passphrase should fail
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify op = new PgpDecryptVerify(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
input.setAllowSymmetricDecryption(true);
DecryptVerifyResult result = op.execute(input,
new CryptoInputParcel(), data, out);
Assert.assertFalse("decryption must fail", result.success());
Assert.assertEquals("decrypted plaintext should be empty", 0, out.size());
Assert.assertNull("signature should be an error", result.getSignatureResult());
}
{ // decryption if symmetric decryption isn't allowed should fail
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify op = new PgpDecryptVerify(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
input.setAllowSymmetricDecryption(false);
DecryptVerifyResult result = op.execute(input,
new CryptoInputParcel(), data, out);
Assert.assertFalse("decryption must fail", result.success());
Assert.assertEquals("decrypted plaintext should be empty", 0, out.size());
Assert.assertNull("signature should be an error", result.getSignatureResult());
}
}
@Test
public void testAsymmetricEncryptDecrypt() {
String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true);
byte[] ciphertext;
{ // encrypt data with key
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(plaintext.getBytes());
PgpSignEncryptOperation op = new PgpSignEncryptOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null);
InputData data = new InputData(in, in.available());
PgpSignEncryptInputParcel input = new PgpSignEncryptInputParcel();
input.setEncryptionMasterKeyIds(new long[] { mStaticRing1.getMasterKeyId() });
input.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
PgpSignEncryptResult result = op.execute(input, new CryptoInputParcel(), data, out);
Assert.assertTrue("encryption must succeed", result.success());
ciphertext = out.toByteArray();
}
{ // decryption with provided passphrase should yield the same result
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify op = operationWithFakePassphraseCache(null, null, null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(mKeyPhrase1), data, out);
Assert.assertTrue("decryption with provided passphrase must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext with provided passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes());
Assert.assertNull("signature be empty", result.getSignatureResult());
OpenPgpMetadata metadata = result.getDecryptMetadata();
Assert.assertEquals("filesize must be correct",
out.toByteArray().length, metadata.getOriginalSize());
}
{ // decryption with passphrase cached should succeed
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify op = operationWithFakePassphraseCache(
mKeyPhrase1, mStaticRing1.getMasterKeyId(), null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(), data, out);
Assert.assertTrue("decryption with cached passphrase must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes());
Assert.assertNull("signature should be empty", result.getSignatureResult());
}
{ // decryption with no passphrase provided should return status pending
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify op = operationWithFakePassphraseCache(
null, mStaticRing1.getMasterKeyId(), null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(), data, out);
Assert.assertFalse("decryption with no passphrase must return pending", result.success());
Assert.assertTrue("decryption with no passphrase should return pending", result.isPending());
Assert.assertEquals("decryption with no passphrase should return pending passphrase",
RequiredInputType.PASSPHRASE, result.getRequiredInputParcel().mType);
}
}
@Test
public void testMultiAsymmetricEncryptDecrypt() {
String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true);
byte[] ciphertext;
{ // encrypt data with a given passphrase
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(plaintext.getBytes());
PgpSignEncryptOperation op = new PgpSignEncryptOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null);
InputData data = new InputData(in, in.available());
PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
b.setEncryptionMasterKeyIds(new long[] {
mStaticRing1.getMasterKeyId(),
mStaticRing2.getMasterKeyId()
});
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
PgpSignEncryptResult result = op.execute(b, new CryptoInputParcel(), data, out);
Assert.assertTrue("encryption must succeed", result.success());
ciphertext = out.toByteArray();
}
{ // decryption with passphrase cached should succeed for the first key
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify op = operationWithFakePassphraseCache(
mKeyPhrase1, mStaticRing1.getMasterKeyId(), null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(), data, out);
Assert.assertTrue("decryption with cached passphrase must succeed for the first key", result.success());
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes());
Assert.assertNull("signature should be empty", result.getSignatureResult());
OpenPgpMetadata metadata = result.getDecryptMetadata();
Assert.assertEquals("filesize must be correct",
out.toByteArray().length, metadata.getOriginalSize());
}
{ // decryption should succeed if key is allowed
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
// allow only the second to decrypt
HashSet<Long> allowed = new HashSet<>();
allowed.add(mStaticRing2.getMasterKeyId());
// provide passphrase for the second, and check that the first is never asked for!
PgpDecryptVerify op = operationWithFakePassphraseCache(
mKeyPhrase2, mStaticRing2.getMasterKeyId(), null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
input.setAllowedKeyIds(allowed);
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(), data, out);
Assert.assertTrue("decryption with cached passphrase must succeed for allowed key", result.success());
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes());
Assert.assertTrue("other key was skipped", result.getLog().containsType(LogType.MSG_DC_ASKIP_NOT_ALLOWED));
Assert.assertNull("signature should be empty", result.getSignatureResult());
}
{ // decryption should fail if no key is allowed
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
// provide passphrase for the second, and check that the first is never asked for!
PgpDecryptVerify op = operationWithFakePassphraseCache(
mKeyPhrase2, mStaticRing2.getMasterKeyId(), null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
input.setAllowedKeyIds(new HashSet<Long>());
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(), data, out);
Assert.assertFalse("decryption must fail if no key allowed", result.success());
Assert.assertEquals("decryption must fail with key disllowed status",
DecryptVerifyResult.RESULT_KEY_DISALLOWED, result.getResult());
}
{ // decryption with passphrase cached should succeed for the other key if first is gone
// delete first key from database
new ProviderHelper(RuntimeEnvironment.application).getContentResolver().delete(
KeyRingData.buildPublicKeyRingUri(mStaticRing1.getMasterKeyId()), null, null
);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify op = operationWithFakePassphraseCache(
mKeyPhrase2, mStaticRing2.getMasterKeyId(), null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(), data, out);
Assert.assertTrue("decryption with cached passphrase must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes());
Assert.assertNull("signature should be empty", result.getSignatureResult());
}
}
@Test
public void testMultiAsymmetricSignEncryptDecryptVerify() {
String plaintext = "dies ist ein plaintext ☭" + TestingUtils.genPassphrase(true);
byte[] ciphertext;
{ // encrypt data with a given passphrase
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(plaintext.getBytes());
PgpSignEncryptOperation op = new PgpSignEncryptOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null);
InputData data = new InputData(in, in.available());
PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
b.setEncryptionMasterKeyIds(new long[] {
mStaticRing1.getMasterKeyId(),
mStaticRing2.getMasterKeyId()
});
b.setSignatureMasterKeyId(mStaticRing1.getMasterKeyId());
b.setSignatureSubKeyId(KeyringTestingHelper.getSubkeyId(mStaticRing1, 1));
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
PgpSignEncryptResult result = op.execute(b, new CryptoInputParcel(mKeyPhrase1), data, out);
Assert.assertTrue("encryption must succeed", result.success());
ciphertext = out.toByteArray();
}
{ // decryption with passphrase cached should succeed for the first key
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify op = operationWithFakePassphraseCache(
mKeyPhrase1, mStaticRing1.getMasterKeyId(), null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(), data, out);
Assert.assertTrue("decryption with cached passphrase must succeed for the first key", result.success());
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes());
Assert.assertEquals("signature should be verified and certified",
OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED, result.getSignatureResult().getStatus());
OpenPgpMetadata metadata = result.getDecryptMetadata();
Assert.assertEquals("filesize must be correct",
out.toByteArray().length, metadata.getOriginalSize());
}
{ // decryption with passphrase cached should succeed for the other key if first is gone
// delete first key from database
new ProviderHelper(RuntimeEnvironment.application).getContentResolver().delete(
KeyRingData.buildPublicKeyRingUri(mStaticRing1.getMasterKeyId()), null, null
);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify op = operationWithFakePassphraseCache(
mKeyPhrase2, mStaticRing2.getMasterKeyId(), null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(), data, out);
Assert.assertTrue("decryption with cached passphrase must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext with cached passphrase should equal plaintext",
out.toByteArray(), plaintext.getBytes());
Assert.assertEquals("signature key should be missing",
OpenPgpSignatureResult.SIGNATURE_KEY_MISSING,
result.getSignatureResult().getStatus());
}
}
@Test
public void testForeignEncoding() throws Exception {
String plaintext = "ウィキペディア";
byte[] plaindata = plaintext.getBytes("iso-2022-jp");
{ // some quick sanity checks
Assert.assertEquals(plaintext, new String(plaindata, "iso-2022-jp"));
Assert.assertNotEquals(plaintext, new String(plaindata, "utf-8"));
}
byte[] ciphertext;
{ // encrypt data with a given passphrase
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(plaindata);
PgpSignEncryptOperation op = new PgpSignEncryptOperation(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null);
InputData data = new InputData(in, in.available());
PgpSignEncryptInputParcel b = new PgpSignEncryptInputParcel();
b.setEncryptionMasterKeyIds(new long[] { mStaticRing1.getMasterKeyId() });
b.setSymmetricEncryptionAlgorithm(PGPEncryptedData.AES_128);
// this only works with ascii armored output!
b.setEnableAsciiArmorOutput(true);
b.setCharset("iso-2022-jp");
PgpSignEncryptResult result = op.execute(b, new CryptoInputParcel(), data, out);
Assert.assertTrue("encryption must succeed", result.success());
ciphertext = out.toByteArray();
}
{ // decryption with provided passphrase should yield the same result
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(ciphertext);
InputData data = new InputData(in, in.available());
PgpDecryptVerify op = operationWithFakePassphraseCache(null, null, null);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel();
DecryptVerifyResult result = op.execute(input, new CryptoInputParcel(mKeyPhrase1), data, out);
Assert.assertTrue("decryption with provided passphrase must succeed", result.success());
Assert.assertArrayEquals("decrypted ciphertext should equal plaintext bytes",
out.toByteArray(), plaindata);
Assert.assertEquals("charset should be read correctly",
"iso-2022-jp", result.getCharset());
Assert.assertEquals("decrypted ciphertext should equal plaintext",
new String(out.toByteArray(), result.getCharset()), plaintext);
Assert.assertNull("signature be empty", result.getSignatureResult());
}
}
private PgpDecryptVerify operationWithFakePassphraseCache(
final Passphrase passphrase, final Long checkMasterKeyId, final Long checkSubKeyId) {
return new PgpDecryptVerify(RuntimeEnvironment.application,
new ProviderHelper(RuntimeEnvironment.application), null) {
@Override
public Passphrase getCachedPassphrase(long masterKeyId, long subKeyId)
throws NoSecretKeyException {
if (checkMasterKeyId != null) {
Assert.assertEquals("requested passphrase should be for expected master key id",
(long) checkMasterKeyId, masterKeyId);
}
if (checkSubKeyId != null) {
Assert.assertEquals("requested passphrase should be for expected sub key id",
(long) checkSubKeyId, subKeyId);
}
if (passphrase == null) {
return null;
}
return passphrase;
}
};
}
}

View File

@@ -0,0 +1,767 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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.pgp;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.Assert;
import org.junit.Test;
import org.junit.Before;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.spongycastle.bcpg.BCPGInputStream;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.bcpg.Packet;
import org.spongycastle.bcpg.PacketTags;
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.spongycastle.bcpg.UserIDPacket;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.PGPDigestCalculator;
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.spongycastle.util.Strings;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.io.ByteArrayInputStream;
import java.security.Security;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
/** Tests for the UncachedKeyring.canonicalize method.
*
* This is a complex and crypto-relevant method, which takes care of sanitizing keyrings.
* Test cases are made for all its assertions.
*/
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
public class UncachedKeyringCanonicalizeTest {
static UncachedKeyRing staticRing;
static int totalPackets;
UncachedKeyRing ring;
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>();
OperationResult.OperationLog log = new OperationResult.OperationLog();
PGPSignatureSubpacketGenerator subHashedPacketsGen;
PGPSecretKey secretKey;
@BeforeClass
public static void setUpOnce() throws Exception {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
ShadowLog.stream = System.out;
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
parcel.mAddUserIds.add("twi");
parcel.mAddUserIds.add("pink");
{
WrappedUserAttribute uat = WrappedUserAttribute.fromSubpacket(100,
"sunshine, sunshine, ladybugs awake~".getBytes());
parcel.mAddUserAttribute.add(uat);
}
// passphrase is tested in PgpKeyOperationTest, just use empty here
parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase());
PgpKeyOperation op = new PgpKeyOperation(null);
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
Assert.assertTrue("initial test key creation must succeed", result.success());
staticRing = result.getRing();
Assert.assertNotNull("initial test key creation must succeed", staticRing);
staticRing = staticRing.canonicalize(new OperationLog(), 0).getUncachedKeyRing();
// just for later reference
totalPackets = 11;
// we sleep here for a second, to make sure all new certificates have different timestamps
Thread.sleep(1000);
}
@Before public void setUp() throws Exception {
// show Log.x messages in system.out
ShadowLog.stream = System.out;
ring = staticRing;
subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
secretKey = new PGPSecretKeyRing(ring.getEncoded(), new JcaKeyFingerprintCalculator())
.getSecretKey();
}
/** Make sure the assumptions made about the generated ring packet structure are valid. */
@Test public void testGeneratedRingStructure() throws Exception {
Iterator<RawPacket> it = KeyringTestingHelper.parseKeyring(ring.getEncoded());
Assert.assertEquals("packet #0 should be secret key",
PacketTags.SECRET_KEY, it.next().tag);
Assert.assertEquals("packet #1 should be user id",
PacketTags.USER_ID, it.next().tag);
Assert.assertEquals("packet #2 should be signature",
PacketTags.SIGNATURE, it.next().tag);
Assert.assertEquals("packet #3 should be user id",
PacketTags.USER_ID, it.next().tag);
Assert.assertEquals("packet #4 should be signature",
PacketTags.SIGNATURE, it.next().tag);
Assert.assertEquals("packet #5 should be user id",
PacketTags.USER_ATTRIBUTE, it.next().tag);
Assert.assertEquals("packet #6 should be signature",
PacketTags.SIGNATURE, it.next().tag);
Assert.assertEquals("packet #7 should be secret subkey",
PacketTags.SECRET_SUBKEY, it.next().tag);
Assert.assertEquals("packet #8 should be signature",
PacketTags.SIGNATURE, it.next().tag);
Assert.assertEquals("packet #9 should be secret subkey",
PacketTags.SECRET_SUBKEY, it.next().tag);
Assert.assertEquals("packet #10 should be signature",
PacketTags.SIGNATURE, it.next().tag);
Assert.assertFalse("exactly 11 packets total", it.hasNext());
Assert.assertArrayEquals("created keyring should be constant through canonicalization",
ring.getEncoded(), ring.canonicalize(log, 0).getEncoded());
}
@Test public void testUidSignature() throws Exception {
UncachedPublicKey masterKey = ring.getPublicKey();
final WrappedSignature sig = masterKey.getSignaturesForRawId(Strings.toUTF8ByteArray("twi")).next();
byte[] raw = sig.getEncoded();
// destroy the signature
raw[raw.length - 5] += 1;
final WrappedSignature brokenSig = WrappedSignature.fromBytes(raw);
{ // bad certificates get stripped
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, brokenSig.getEncoded(), 3);
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
Assert.assertTrue("canonicalized keyring with invalid extra sig must be same as original one",
!KeyringTestingHelper.diffKeyrings(
ring.getEncoded(), canonicalized.getEncoded(), onlyA, onlyB));
}
// remove user id certificate for one user
final UncachedKeyRing base = KeyringTestingHelper.removePacket(ring, 2);
{ // user id without certificate should be removed
CanonicalizedKeyRing modified = base.canonicalize(log, 0);
Assert.assertTrue("canonicalized keyring must differ", KeyringTestingHelper.diffKeyrings(
ring.getEncoded(), modified.getEncoded(), onlyA, onlyB));
Assert.assertEquals("two packets should be stripped after canonicalization", 2, onlyA.size());
Assert.assertEquals("no new packets after canonicalization", 0, onlyB.size());
Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(0).buf)).readPacket();
Assert.assertTrue("first stripped packet must be user id", p instanceof UserIDPacket);
Assert.assertEquals("missing user id must be the expected one",
"twi", ((UserIDPacket) p).getID());
Assert.assertArrayEquals("second stripped packet must be signature we removed",
sig.getEncoded(), onlyA.get(1).buf);
}
{ // add error to signature
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(base, brokenSig.getEncoded(), 3);
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
Assert.assertTrue("canonicalized keyring must differ", KeyringTestingHelper.diffKeyrings(
ring.getEncoded(), canonicalized.getEncoded(), onlyA, onlyB));
Assert.assertEquals("two packets should be missing after canonicalization", 2, onlyA.size());
Assert.assertEquals("no new packets after canonicalization", 0, onlyB.size());
Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(0).buf)).readPacket();
Assert.assertTrue("first stripped packet must be user id", p instanceof UserIDPacket);
Assert.assertEquals("missing user id must be the expected one",
"twi", ((UserIDPacket) p).getID());
Assert.assertArrayEquals("second stripped packet must be signature we removed",
sig.getEncoded(), onlyA.get(1).buf);
}
}
@Test public void testUidDestroy() throws Exception {
// signature for "twi"
ring = KeyringTestingHelper.removePacket(ring, 2);
// signature for "pink"
ring = KeyringTestingHelper.removePacket(ring, 3);
// canonicalization should fail, because there are no valid uids left
CanonicalizedKeyRing canonicalized = ring.canonicalize(log, 0);
Assert.assertNull("canonicalization of keyring with no valid uids should fail", canonicalized);
}
@Test public void testRevocationRedundant() throws Exception {
PGPSignature revocation = forgeSignature(
secretKey, PGPSignature.KEY_REVOCATION, subHashedPacketsGen, secretKey.getPublicKey());
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, revocation.getEncoded(), 1);
// try to add the same packet again, it should be rejected in all positions
injectEverywhere(modified, revocation.getEncoded());
// an older (but different!) revocation should be rejected as well
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
revocation = forgeSignature(
secretKey, PGPSignature.KEY_REVOCATION, subHashedPacketsGen, secretKey.getPublicKey());
injectEverywhere(modified, revocation.getEncoded());
}
@Test public void testUidRedundant() throws Exception {
// an older uid certificate should be rejected
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
PGPSignature revocation = forgeSignature(
secretKey, PGPSignature.DEFAULT_CERTIFICATION, subHashedPacketsGen, "twi", secretKey.getPublicKey());
injectEverywhere(ring, revocation.getEncoded());
}
@Test public void testUidRevocationOutdated() throws Exception {
// an older uid revocation cert should be rejected
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
PGPSignature revocation = forgeSignature(
secretKey, PGPSignature.CERTIFICATION_REVOCATION, subHashedPacketsGen, "twi", secretKey.getPublicKey());
injectEverywhere(ring, revocation.getEncoded());
}
@Test public void testUidRevocationRedundant() throws Exception {
PGPSignature revocation = forgeSignature(
secretKey, PGPSignature.CERTIFICATION_REVOCATION, subHashedPacketsGen, "twi", secretKey.getPublicKey());
// add that revocation to the base, and check if the redundant one will be rejected as well
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, revocation.getEncoded(), 2);
injectEverywhere(modified, revocation.getEncoded());
// an older (but different!) uid revocation should be rejected as well
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
revocation = forgeSignature(
secretKey, PGPSignature.CERTIFICATION_REVOCATION, subHashedPacketsGen, "twi", secretKey.getPublicKey());
injectEverywhere(modified, revocation.getEncoded());
}
@Test public void testDuplicateUid() throws Exception {
// get subkey packets
Iterator<RawPacket> it = KeyringTestingHelper.parseKeyring(ring.getEncoded());
RawPacket uidPacket = KeyringTestingHelper.getNth(it, 3);
RawPacket uidSig = it.next();
// inject at a second position
UncachedKeyRing modified = ring;
modified = KeyringTestingHelper.injectPacket(modified, uidPacket.buf, 5);
modified = KeyringTestingHelper.injectPacket(modified, uidSig.buf, 6);
// canonicalize, and check if we lose the bad signature
OperationLog log = new OperationLog();
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
Assert.assertNotNull("canonicalization with duplicate user id should succeed", canonicalized);
Assert.assertTrue("log should contain uid_dup event", log.containsType(LogType.MSG_KC_UID_DUP));
/* TODO actually test ths, and fix behavior
Assert.assertTrue("duplicate user id packets should be gone after canonicalization",
KeyringTestingHelper.diffKeyrings(modified.getEncoded(), canonicalized.getEncoded(),
onlyA, onlyB)
);
Assert.assertEquals("canonicalized keyring should have lost the two duplicate packets",
2, onlyA.size());
Assert.assertTrue("canonicalized keyring should still contain the user id",
canonicalized.getUnorderedUserIds().contains(new UserIDPacket(uidPacket.buf).getID()));
*/
}
@Test public void testSignatureBroken() throws Exception {
injectEverytype(secretKey, ring, subHashedPacketsGen, true);
}
@Test public void testForeignSignature() throws Exception {
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddUserIds.add("trix");
PgpKeyOperation op = new PgpKeyOperation(null);
OperationResult.OperationLog log = new OperationResult.OperationLog();
UncachedKeyRing foreign = op.createSecretKeyRing(parcel).getRing();
Assert.assertNotNull("initial test key creation must succeed", foreign);
PGPSecretKey foreignSecretKey =
new PGPSecretKeyRing(foreign.getEncoded(), new JcaKeyFingerprintCalculator())
.getSecretKey();
injectEverytype(foreignSecretKey, ring, subHashedPacketsGen);
}
@Test public void testSignatureFuture() throws Exception {
// generate future timestamp (we allow up to one day future timestamps)
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_YEAR, 2);
subHashedPacketsGen.setSignatureCreationTime(false, cal.getTime());
injectEverytype(secretKey, ring, subHashedPacketsGen);
}
@Test public void testSignatureLocal() throws Exception {
// make key local only
subHashedPacketsGen.setExportable(false, false);
injectEverytype(secretKey, ring, subHashedPacketsGen);
}
@Test public void testSubkeyDestroy() throws Exception {
// signature for second key (first subkey)
UncachedKeyRing modified = KeyringTestingHelper.removePacket(ring, 8);
// canonicalization should fail, because there are no valid uids left
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
Assert.assertTrue("keyring with missing subkey binding sig should differ from intact one after canonicalization",
KeyringTestingHelper.diffKeyrings(ring.getEncoded(), canonicalized.getEncoded(),
onlyA, onlyB)
);
Assert.assertEquals("canonicalized keyring should have two extra packets", 2, onlyA.size());
Assert.assertEquals("canonicalized keyring should have no extra packets", 0, onlyB.size());
Assert.assertEquals("first missing packet should be the subkey",
PacketTags.SECRET_SUBKEY, onlyA.get(0).tag);
Assert.assertEquals("second missing packet should be subkey's signature",
PacketTags.SIGNATURE, onlyA.get(1).tag);
Assert.assertEquals("second missing packet should be next to subkey",
onlyA.get(0).position + 1, onlyA.get(1).position);
}
@Test public void testSubkeyBindingNoPKB() throws Exception {
UncachedPublicKey pKey = KeyringTestingHelper.getNth(ring.getPublicKeys(), 1);
PGPSignature sig;
subHashedPacketsGen.setKeyFlags(false, KeyFlags.SIGN_DATA);
{
// forge a (newer) signature, which has the sign flag but no primary key binding sig
PGPSignatureSubpacketGenerator unhashedSubs = new PGPSignatureSubpacketGenerator();
// just add any random signature, because why not
unhashedSubs.setEmbeddedSignature(false, forgeSignature(
secretKey, PGPSignature.POSITIVE_CERTIFICATION, subHashedPacketsGen,
secretKey.getPublicKey()
)
);
sig = forgeSignature(
secretKey, PGPSignature.SUBKEY_BINDING, subHashedPacketsGen, unhashedSubs,
secretKey.getPublicKey(), pKey.getPublicKey());
// inject in the right position
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig.getEncoded(), 8);
// canonicalize, and check if we lose the bad signature
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
Assert.assertFalse("subkey binding signature should be gone after canonicalization",
KeyringTestingHelper.diffKeyrings(ring.getEncoded(), canonicalized.getEncoded(),
onlyA, onlyB)
);
}
{ // now try one with a /bad/ primary key binding signature
PGPSignatureSubpacketGenerator unhashedSubs = new PGPSignatureSubpacketGenerator();
// this one is signed by the primary key itself, not the subkey - but it IS primary binding
unhashedSubs.setEmbeddedSignature(false, forgeSignature(
secretKey, PGPSignature.PRIMARYKEY_BINDING, subHashedPacketsGen,
secretKey.getPublicKey(), pKey.getPublicKey()
)
);
sig = forgeSignature(
secretKey, PGPSignature.SUBKEY_BINDING, subHashedPacketsGen, unhashedSubs,
secretKey.getPublicKey(), pKey.getPublicKey());
// inject in the right position
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig.getEncoded(), 8);
// canonicalize, and check if we lose the bad signature
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
Assert.assertFalse("subkey binding signature should be gone after canonicalization",
KeyringTestingHelper.diffKeyrings(ring.getEncoded(), canonicalized.getEncoded(),
onlyA, onlyB)
);
}
}
@Test public void testSubkeyBindingRedundant() throws Exception {
UncachedPublicKey pKey = KeyringTestingHelper.getNth(ring.getPublicKeys(), 2);
subHashedPacketsGen.setKeyFlags(false, KeyFlags.ENCRYPT_COMMS);
PGPSignature sig2 = forgeSignature(
secretKey, PGPSignature.SUBKEY_BINDING, subHashedPacketsGen,
secretKey.getPublicKey(), pKey.getPublicKey());
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
PGPSignature sig1 = forgeSignature(
secretKey, PGPSignature.SUBKEY_REVOCATION, subHashedPacketsGen,
secretKey.getPublicKey(), pKey.getPublicKey());
subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -100*1000));
PGPSignature sig3 = forgeSignature(
secretKey, PGPSignature.SUBKEY_BINDING, subHashedPacketsGen,
secretKey.getPublicKey(), pKey.getPublicKey());
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig1.getEncoded(), 10);
modified = KeyringTestingHelper.injectPacket(modified, sig2.getEncoded(), 11);
modified = KeyringTestingHelper.injectPacket(modified, sig1.getEncoded(), 12);
modified = KeyringTestingHelper.injectPacket(modified, sig3.getEncoded(), 13);
// canonicalize, and check if we lose the bad signature
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
Assert.assertTrue("subkey binding signature should be gone after canonicalization",
KeyringTestingHelper.diffKeyrings(modified.getEncoded(), canonicalized.getEncoded(),
onlyA, onlyB)
);
Assert.assertEquals("canonicalized keyring should have lost two packets", 3, onlyA.size());
Assert.assertEquals("canonicalized keyring should have no extra packets", 0, onlyB.size());
Assert.assertEquals("first missing packet should be the subkey",
PacketTags.SIGNATURE, onlyA.get(0).tag);
Assert.assertEquals("second missing packet should be a signature",
PacketTags.SIGNATURE, onlyA.get(1).tag);
Assert.assertEquals("second missing packet should be a signature",
PacketTags.SIGNATURE, onlyA.get(2).tag);
}
@Test
public void testDuplicateSubkey() throws Exception {
{ // duplicate subkey
// get subkey packets
Iterator<RawPacket> it = KeyringTestingHelper.parseKeyring(ring.getEncoded());
RawPacket subKey = KeyringTestingHelper.getNth(it, 7);
RawPacket subSig = it.next();
// inject at a second position
UncachedKeyRing modified = ring;
modified = KeyringTestingHelper.injectPacket(modified, subKey.buf, 9);
modified = KeyringTestingHelper.injectPacket(modified, subSig.buf, 10);
// canonicalize, and check if we lose the bad signature
OperationLog log = new OperationLog();
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
Assert.assertNull("canonicalization with duplicate subkey should fail", canonicalized);
Assert.assertTrue("log should contain dup_key event", log.containsType(LogType.MSG_KC_ERROR_DUP_KEY));
}
{ // duplicate subkey, which is the same as the master key
// We actually encountered one of these in the wild:
// https://www.sparkasse-holstein.de/firmenkunden/electronic_banking/secure-e-mail/pdf/Spk_Holstein_PGP_Domain-Zertifikat.asc
CanonicalizedSecretKeyRing canonicalized = (CanonicalizedSecretKeyRing) ring.canonicalize(log, 0);
CanonicalizedSecretKey masterSecretKey = canonicalized.getSecretKey();
masterSecretKey.unlock(new Passphrase());
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
CryptoInputParcel cryptoInput = new CryptoInputParcel();
PGPSignature cert = PgpKeyOperation.generateSubkeyBindingSignature(
PgpKeyOperation.getSignatureGenerator(masterSecretKey.getSecretKey(), cryptoInput),
cryptoInput.getSignatureTime(),
masterPublicKey, masterSecretKey.getPrivateKey(), masterSecretKey.getPrivateKey(),
masterPublicKey, masterSecretKey.getKeyUsage(), 0);
PGPPublicKey subPubKey = PGPPublicKey.addSubkeyBindingCertification(masterPublicKey, cert);
PGPSecretKey sKey;
{
// Build key encrypter and decrypter based on passphrase
PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder()
.build().get(HashAlgorithmTags.SHA256);
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
SymmetricKeyAlgorithmTags.AES_256, encryptorHashCalc, 10)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
// NOTE: only SHA1 is supported for key checksum calculations.
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
.build().get(HashAlgorithmTags.SHA1);
sKey = new PGPSecretKey(masterSecretKey.getPrivateKey(), subPubKey, sha1Calc, false, keyEncryptor);
}
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sKey.getEncoded(), 7);
// canonicalize, and check if we lose the bad signature
OperationLog log = new OperationLog();
CanonicalizedKeyRing result = modified.canonicalize(log, 0);
Assert.assertNull("canonicalization with duplicate subkey (from master) should fail", result);
Assert.assertTrue("log should contain dup_key event", log.containsType(LogType.MSG_KC_ERROR_DUP_KEY));
}
}
private static final int[] sigtypes_direct = new int[] {
PGPSignature.KEY_REVOCATION,
PGPSignature.DIRECT_KEY,
};
private static final int[] sigtypes_uid = new int[] {
PGPSignature.DEFAULT_CERTIFICATION,
PGPSignature.NO_CERTIFICATION,
PGPSignature.CASUAL_CERTIFICATION,
PGPSignature.POSITIVE_CERTIFICATION,
PGPSignature.CERTIFICATION_REVOCATION,
};
private static final int[] sigtypes_subkey = new int[] {
PGPSignature.SUBKEY_BINDING,
PGPSignature.PRIMARYKEY_BINDING,
PGPSignature.SUBKEY_REVOCATION,
};
private static void injectEverytype(PGPSecretKey secretKey,
UncachedKeyRing ring,
PGPSignatureSubpacketGenerator subHashedPacketsGen)
throws Exception {
injectEverytype(secretKey, ring, subHashedPacketsGen, false);
}
private static void injectEverytype(PGPSecretKey secretKey,
UncachedKeyRing ring,
PGPSignatureSubpacketGenerator subHashedPacketsGen,
boolean breakSig)
throws Exception {
for (int sigtype : sigtypes_direct) {
PGPSignature sig = forgeSignature(
secretKey, sigtype, subHashedPacketsGen, secretKey.getPublicKey());
byte[] encoded = sig.getEncoded();
if (breakSig) {
encoded[encoded.length-10] += 1;
}
injectEverywhere(ring, encoded);
}
for (int sigtype : sigtypes_uid) {
PGPSignature sig = forgeSignature(
secretKey, sigtype, subHashedPacketsGen, "twi", secretKey.getPublicKey());
byte[] encoded = sig.getEncoded();
if (breakSig) {
encoded[encoded.length-10] += 1;
}
injectEverywhere(ring, encoded);
}
for (int sigtype : sigtypes_subkey) {
PGPSignature sig = forgeSignature(
secretKey, sigtype, subHashedPacketsGen,
secretKey.getPublicKey(), secretKey.getPublicKey());
byte[] encoded = sig.getEncoded();
if (breakSig) {
encoded[encoded.length-10] += 1;
}
injectEverywhere(ring, encoded);
}
}
private static void injectEverywhere(UncachedKeyRing ring, byte[] packet) throws Exception {
OperationResult.OperationLog log = new OperationResult.OperationLog();
byte[] encodedRing = ring.getEncoded();
for(int i = 0; i < totalPackets; i++) {
byte[] brokenEncoded = KeyringTestingHelper.injectPacket(encodedRing, packet, i);
try {
UncachedKeyRing brokenRing = UncachedKeyRing.decodeFromData(brokenEncoded);
CanonicalizedKeyRing canonicalized = brokenRing.canonicalize(log, 0);
if (canonicalized == null) {
System.out.println("ok, canonicalization failed.");
continue;
}
Assert.assertArrayEquals("injected bad signature must be gone after canonicalization",
ring.getEncoded(), canonicalized.getEncoded());
} catch (Exception e) {
System.out.println("ok, rejected with: " + e.getMessage());
}
}
}
private static PGPSignature forgeSignature(PGPSecretKey key, int type,
PGPSignatureSubpacketGenerator subpackets,
PGPPublicKey publicKey)
throws Exception {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
PGPPrivateKey privateKey = key.extractPrivateKey(keyDecryptor);
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
publicKey.getAlgorithm(), PGPUtil.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.setHashedSubpackets(subpackets.generate());
sGen.init(type, privateKey);
return sGen.generateCertification(publicKey);
}
private static PGPSignature forgeSignature(PGPSecretKey key, int type,
PGPSignatureSubpacketGenerator subpackets,
String userId, PGPPublicKey publicKey)
throws Exception {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
PGPPrivateKey privateKey = key.extractPrivateKey(keyDecryptor);
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
publicKey.getAlgorithm(), PGPUtil.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.setHashedSubpackets(subpackets.generate());
sGen.init(type, privateKey);
return sGen.generateCertification(userId, publicKey);
}
private static PGPSignature forgeSignature(PGPSecretKey key, int type,
PGPSignatureSubpacketGenerator subpackets,
PGPPublicKey publicKey, PGPPublicKey signedKey)
throws Exception {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
PGPPrivateKey privateKey = key.extractPrivateKey(keyDecryptor);
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
publicKey.getAlgorithm(), PGPUtil.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.setHashedSubpackets(subpackets.generate());
sGen.init(type, privateKey);
return sGen.generateCertification(publicKey, signedKey);
}
private static PGPSignature forgeSignature(PGPSecretKey key, int type,
PGPSignatureSubpacketGenerator hashedSubs,
PGPSignatureSubpacketGenerator unhashedSubs,
PGPPublicKey publicKey, PGPPublicKey signedKey)
throws Exception {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
PGPPrivateKey privateKey = key.extractPrivateKey(keyDecryptor);
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
publicKey.getAlgorithm(), PGPUtil.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.setHashedSubpackets(hashedSubs.generate());
sGen.setUnhashedSubpackets(unhashedSubs.generate());
sGen.init(type, privateKey);
return sGen.generateCertification(publicKey, signedKey);
}
}

View File

@@ -0,0 +1,496 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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.pgp;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.spongycastle.bcpg.BCPGInputStream;
import org.spongycastle.bcpg.PacketTags;
import org.spongycastle.bcpg.S2K;
import org.spongycastle.bcpg.SecretKeyPacket;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.util.Strings;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.ByteArrayInputStream;
import java.security.Security;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.Random;
/** Tests for the UncachedKeyring.merge method.
*
* This is another complex, crypto-related method. It merges information from one keyring into
* another, keeping information from the base (ie, called object) keyring in case of conflicts.
* The types of keys may be Public or Secret and can be mixed, For mixed types the result type
* will be the same as the base keyring.
*
* Test cases:
* - Merging keyrings with different masterKeyIds should fail
* - Merging a key with itself should be a no-operation
* - Merging a key with an extra revocation certificate, it should have that certificate
* - Merging a key with an extra user id, it should have that extra user id and its certificates
* - Merging a key with an extra user id certificate, it should have that certificate
* - Merging a key with an extra subkey, it should have that subkey
* - Merging a key with an extra subkey certificate, it should have that certificate
* - All of the above operations should work regardless of the key types. This means in particular
* that for new subkeys, an equivalent subkey of the proper type must be generated.
* - In case of two secret keys with the same id but different S2K, the key of the base keyring
* should be preferred (TODO or should it?)
*
* Note that the merge operation does not care about certificate validity, a bad certificate or
* packet will be copied regardless. Filtering out bad packets is done with canonicalization.
*
*/
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
public class UncachedKeyringMergeTest {
static UncachedKeyRing staticRingA, staticRingB;
UncachedKeyRing ringA, ringB;
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>();
OperationResult.OperationLog log = new OperationResult.OperationLog();
PgpKeyOperation op;
SaveKeyringParcel parcel;
@BeforeClass
public static void setUpOnce() throws Exception {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
ShadowLog.stream = System.out;
{
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
parcel.mAddUserIds.add("twi");
parcel.mAddUserIds.add("pink");
{
WrappedUserAttribute uat = WrappedUserAttribute.fromSubpacket(100,
"sunshine, sunshine, ladybugs awake~".getBytes());
parcel.mAddUserAttribute.add(uat);
}
// passphrase is tested in PgpKeyOperationTest, just use empty here
parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase());
PgpKeyOperation op = new PgpKeyOperation(null);
OperationResult.OperationLog log = new OperationResult.OperationLog();
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
staticRingA = result.getRing();
staticRingA = staticRingA.canonicalize(new OperationLog(), 0).getUncachedKeyRing();
}
{
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddUserIds.add("shy");
// passphrase is tested in PgpKeyOperationTest, just use empty here
parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase());
PgpKeyOperation op = new PgpKeyOperation(null);
OperationResult.OperationLog log = new OperationResult.OperationLog();
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
staticRingB = result.getRing();
staticRingB = staticRingB.canonicalize(new OperationLog(), 0).getUncachedKeyRing();
}
Assert.assertNotNull("initial test key creation must succeed", staticRingA);
Assert.assertNotNull("initial test key creation must succeed", staticRingB);
// we sleep here for a second, to make sure all new certificates have different timestamps
Thread.sleep(1000);
}
@Before
public void setUp() throws Exception {
// show Log.x messages in system.out
ShadowLog.stream = System.out;
ringA = staticRingA;
ringB = staticRingB;
// setting up some parameters just to reduce code duplication
op = new PgpKeyOperation(new ProgressScaler(null, 0, 100, 100));
// set this up, gonna need it more than once
parcel = new SaveKeyringParcel();
parcel.mMasterKeyId = ringA.getMasterKeyId();
parcel.mFingerprint = ringA.getFingerprint();
}
public void testSelfNoOp() throws Exception {
UncachedKeyRing merged = mergeWithChecks(ringA, ringA, null);
Assert.assertArrayEquals("keyring merged with itself must be identical",
ringA.getEncoded(), merged.getEncoded()
);
}
@Test
public void testDifferentMasterKeyIds() throws Exception {
Assert.assertNotEquals("generated key ids must be different",
ringA.getMasterKeyId(), ringB.getMasterKeyId());
Assert.assertNull("merging keys with differing key ids must fail",
ringA.merge(ringB, log, 0));
Assert.assertNull("merging keys with differing key ids must fail",
ringB.merge(ringA, log, 0));
}
@Test
public void testAddedUserId() throws Exception {
UncachedKeyRing modifiedA, modifiedB; {
CanonicalizedSecretKeyRing secretRing =
new CanonicalizedSecretKeyRing(ringA.getEncoded(), false, 0);
parcel.reset();
parcel.mAddUserIds.add("flim");
modifiedA = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
parcel.reset();
parcel.mAddUserIds.add("flam");
modifiedB = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
}
{ // merge A into base
UncachedKeyRing merged = mergeWithChecks(ringA, modifiedA);
Assert.assertEquals("merged keyring must have lost no packets", 0, onlyA.size());
Assert.assertEquals("merged keyring must have gained two packets", 2, onlyB.size());
Assert.assertTrue("merged keyring must contain new user id",
merged.getPublicKey().getUnorderedUserIds().contains("flim"));
}
{ // merge A into B
UncachedKeyRing merged = mergeWithChecks(modifiedA, modifiedB, ringA);
Assert.assertEquals("merged keyring must have lost no packets", 0, onlyA.size());
Assert.assertEquals("merged keyring must have gained four packets", 4, onlyB.size());
Assert.assertTrue("merged keyring must contain first new user id",
merged.getPublicKey().getUnorderedUserIds().contains("flim"));
Assert.assertTrue("merged keyring must contain second new user id",
merged.getPublicKey().getUnorderedUserIds().contains("flam"));
}
}
@Test
public void testAddedSubkeyId() throws Exception {
UncachedKeyRing modifiedA, modifiedB;
long subKeyIdA, subKeyIdB;
{
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ringA.getEncoded(), false, 0);
parcel.reset();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
modifiedA = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
modifiedB = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
subKeyIdA = KeyringTestingHelper.getSubkeyId(modifiedA, 2);
subKeyIdB = KeyringTestingHelper.getSubkeyId(modifiedB, 2);
}
{
UncachedKeyRing merged = mergeWithChecks(ringA, modifiedA);
Assert.assertEquals("merged keyring must have lost no packets", 0, onlyA.size());
Assert.assertEquals("merged keyring must have gained two packets", 2, onlyB.size());
long mergedKeyId = KeyringTestingHelper.getSubkeyId(merged, 2);
Assert.assertEquals("merged keyring must contain the new subkey", subKeyIdA, mergedKeyId);
}
{
UncachedKeyRing merged = mergeWithChecks(modifiedA, modifiedB, ringA);
Assert.assertEquals("merged keyring must have lost no packets", 0, onlyA.size());
Assert.assertEquals("merged keyring must have gained four packets", 4, onlyB.size());
Iterator<UncachedPublicKey> it = merged.getPublicKeys();
it.next(); it.next();
Assert.assertEquals("merged keyring must contain the new subkey",
subKeyIdA, it.next().getKeyId());
Assert.assertEquals("merged keyring must contain both new subkeys",
subKeyIdB, it.next().getKeyId());
}
}
@Test
public void testAddedKeySignature() throws Exception {
final UncachedKeyRing modified; {
parcel.reset();
parcel.mRevokeSubKeys.add(KeyringTestingHelper.getSubkeyId(ringA, 1));
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(
ringA.getEncoded(), false, 0);
modified = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
}
{
UncachedKeyRing merged = ringA.merge(modified, log, 0);
Assert.assertNotNull("merge must succeed", merged);
Assert.assertFalse(
"merging keyring with extra signatures into its base should yield that same keyring",
KeyringTestingHelper.diffKeyrings(merged.getEncoded(), modified.getEncoded(), onlyA, onlyB)
);
}
}
@Test
public void testAddedUserIdSignature() throws Exception {
final UncachedKeyRing pubRing = ringA.extractPublicKeyRing();
final UncachedKeyRing modified; {
CanonicalizedPublicKeyRing publicRing = new CanonicalizedPublicKeyRing(
pubRing.getEncoded(), 0);
CanonicalizedSecretKey secretKey = new CanonicalizedSecretKeyRing(
ringB.getEncoded(), false, 0).getSecretKey();
secretKey.unlock(new Passphrase());
PgpCertifyOperation op = new PgpCertifyOperation();
CertifyAction action = new CertifyAction(pubRing.getMasterKeyId(), publicRing.getPublicKey().getUnorderedUserIds());
// sign all user ids
PgpCertifyResult result = op.certify(secretKey, publicRing, new OperationLog(), 0, action, null, new Date());
Assert.assertTrue("certification must succeed", result.success());
Assert.assertNotNull("certification must yield result", result.getCertifiedRing());
modified = result.getCertifiedRing();
}
{
UncachedKeyRing merged = ringA.merge(modified, log, 0);
Assert.assertNotNull("merge must succeed", merged);
Assert.assertArrayEquals("foreign signatures should not be merged into secret key",
ringA.getEncoded(), merged.getEncoded()
);
}
{
byte[] sig = KeyringTestingHelper.getNth(
modified.getPublicKey().getSignaturesForRawId(Strings.toUTF8ByteArray("twi")), 1).getEncoded();
// inject the (foreign!) signature into subkey signature position
UncachedKeyRing moreModified = KeyringTestingHelper.injectPacket(modified, sig, 1);
UncachedKeyRing merged = ringA.merge(moreModified, log, 0);
Assert.assertNotNull("merge must succeed", merged);
Assert.assertArrayEquals("foreign signatures should not be merged into secret key",
ringA.getEncoded(), merged.getEncoded()
);
merged = pubRing.merge(moreModified, log, 0);
Assert.assertNotNull("merge must succeed", merged);
Assert.assertTrue(
"merged keyring should contain new signature",
KeyringTestingHelper.diffKeyrings(pubRing.getEncoded(), merged.getEncoded(), onlyA, onlyB)
);
Assert.assertEquals("merged keyring should be missing no packets", 0, onlyA.size());
Assert.assertEquals("merged keyring should contain exactly two more packets", 2, onlyB.size());
Assert.assertEquals("first added packet should be a signature",
PacketTags.SIGNATURE, onlyB.get(0).tag);
Assert.assertEquals("first added packet should be in the position we injected it at",
1, onlyB.get(0).position);
Assert.assertEquals("second added packet should be a signature",
PacketTags.SIGNATURE, onlyB.get(1).tag);
}
{
UncachedKeyRing merged = pubRing.merge(modified, log, 0);
Assert.assertNotNull("merge must succeed", merged);
Assert.assertFalse(
"merging keyring with extra signatures into its base should yield that same keyring",
KeyringTestingHelper.diffKeyrings(merged.getEncoded(), modified.getEncoded(), onlyA, onlyB)
);
}
}
@Test
public void testAddedUserAttributeSignature() throws Exception {
final UncachedKeyRing modified; {
parcel.reset();
Random r = new Random();
int type = r.nextInt(110)+1;
byte[] data = new byte[r.nextInt(2000)];
new Random().nextBytes(data);
WrappedUserAttribute uat = WrappedUserAttribute.fromSubpacket(type, data);
parcel.mAddUserAttribute.add(uat);
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(
ringA.getEncoded(), false, 0);
modified = op.modifySecretKeyRing(secretRing, new CryptoInputParcel(new Passphrase()), parcel).getRing();
}
{
UncachedKeyRing merged = ringA.merge(modified, log, 0);
Assert.assertNotNull("merge must succeed", merged);
Assert.assertFalse(
"merging keyring with extra user attribute into its base should yield that same keyring",
KeyringTestingHelper.diffKeyrings(merged.getEncoded(), modified.getEncoded(), onlyA, onlyB)
);
}
}
private UncachedKeyRing mergeWithChecks(UncachedKeyRing a, UncachedKeyRing b)
throws Exception {
return mergeWithChecks(a, b, a);
}
private UncachedKeyRing mergeWithChecks(UncachedKeyRing a, UncachedKeyRing b,
UncachedKeyRing base)
throws Exception {
Assert.assertTrue("merging keyring must be secret type", a.isSecret());
Assert.assertTrue("merged keyring must be secret type", b.isSecret());
final UncachedKeyRing resultA;
UncachedKeyRing resultB;
{ // sec + sec
resultA = a.merge(b, log, 0);
Assert.assertNotNull("merge must succeed as sec(a)+sec(b)", resultA);
resultB = b.merge(a, log, 0);
Assert.assertNotNull("merge must succeed as sec(b)+sec(a)", resultB);
// check commutativity, if requested
Assert.assertFalse("result of merge must be commutative",
KeyringTestingHelper.diffKeyrings(
resultA.getEncoded(), resultB.getEncoded(), onlyA, onlyB)
);
}
final UncachedKeyRing pubA = a.extractPublicKeyRing();
final UncachedKeyRing pubB = b.extractPublicKeyRing();
{ // sec + pub
// this one is special, because GNU_DUMMY keys might be generated!
resultB = a.merge(pubB, log, 0);
Assert.assertNotNull("merge must succeed as sec(a)+pub(b)", resultA);
// these MAY diff
KeyringTestingHelper.diffKeyrings(resultA.getEncoded(), resultB.getEncoded(),
onlyA, onlyB);
Assert.assertEquals("sec(a)+pub(b): results must have equal number of packets",
onlyA.size(), onlyB.size());
for (int i = 0; i < onlyA.size(); i++) {
Assert.assertEquals("sec(a)+pub(c): old packet must be secret subkey",
PacketTags.SECRET_SUBKEY, onlyA.get(i).tag);
Assert.assertEquals("sec(a)+pub(c): new packet must be dummy secret subkey",
PacketTags.SECRET_SUBKEY, onlyB.get(i).tag);
SecretKeyPacket pA = (SecretKeyPacket) new BCPGInputStream(new ByteArrayInputStream(onlyA.get(i).buf)).readPacket();
SecretKeyPacket pB = (SecretKeyPacket) new BCPGInputStream(new ByteArrayInputStream(onlyB.get(i).buf)).readPacket();
Assert.assertArrayEquals("sec(a)+pub(c): both packets must have equal pubkey parts",
pA.getPublicKeyPacket().getEncoded(), pB.getPublicKeyPacket().getEncoded()
);
Assert.assertEquals("sec(a)+pub(c): new packet should have GNU_DUMMY S2K type",
S2K.GNU_DUMMY_S2K, pB.getS2K().getType());
Assert.assertEquals("sec(a)+pub(c): new packet should have GNU_DUMMY protection mode 0x1",
0x1, pB.getS2K().getProtectionMode());
Assert.assertEquals("sec(a)+pub(c): new packet secret key data should have length zero",
0, pB.getSecretKeyData().length);
Assert.assertNull("sec(a)+pub(c): new packet should have no iv data", pB.getIV());
}
}
{ // pub + sec, and pub + pub
final UncachedKeyRing pubResult = resultA.extractPublicKeyRing();
resultB = pubA.merge(b, log, 0);
Assert.assertNotNull("merge must succeed as pub(a)+sec(b)", resultA);
Assert.assertFalse("result of pub(a)+sec(b) must be same as pub(sec(a)+sec(b))",
KeyringTestingHelper.diffKeyrings(
pubResult.getEncoded(), resultB.getEncoded(), onlyA, onlyB)
);
resultB = pubA.merge(pubB, log, 0);
Assert.assertNotNull("merge must succeed as pub(a)+pub(b)", resultA);
Assert.assertFalse("result of pub(a)+pub(b) must be same as pub(sec(a)+sec(b))",
KeyringTestingHelper.diffKeyrings(
pubResult.getEncoded(), resultB.getEncoded(), onlyA, onlyB)
);
}
if (base != null) {
// set up onlyA and onlyB to be a diff to the base
Assert.assertTrue("merged keyring must differ from base",
KeyringTestingHelper.diffKeyrings(
base.getEncoded(), resultA.getEncoded(), onlyA, onlyB)
);
}
return resultA;
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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.pgp;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Random;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
public class UncachedKeyringTest {
static UncachedKeyRing staticRing, staticPubRing;
UncachedKeyRing ring, pubRing;
@BeforeClass
public static void setUpOnce() throws Exception {
ShadowLog.stream = System.out;
SaveKeyringParcel parcel = new SaveKeyringParcel();
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.CERTIFY_OTHER, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.SIGN_DATA, 0L));
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 1024, null, KeyFlags.ENCRYPT_COMMS, 0L));
parcel.mAddUserIds.add("twi");
parcel.mAddUserIds.add("pink");
{
Random r = new Random();
int type = r.nextInt(110)+1;
byte[] data = new byte[r.nextInt(2000)];
new Random().nextBytes(data);
WrappedUserAttribute uat = WrappedUserAttribute.fromSubpacket(type, data);
parcel.mAddUserAttribute.add(uat);
}
// passphrase is tested in PgpKeyOperationTest, just use empty here
parcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase());
PgpKeyOperation op = new PgpKeyOperation(null);
PgpEditKeyResult result = op.createSecretKeyRing(parcel);
staticRing = result.getRing();
staticPubRing = staticRing.extractPublicKeyRing();
Assert.assertNotNull("initial test key creation must succeed", staticRing);
// we sleep here for a second, to make sure all new certificates have different timestamps
Thread.sleep(1000);
}
@Before
public void setUp() throws Exception {
// show Log.x messages in system.out
ShadowLog.stream = System.out;
ring = staticRing;
pubRing = staticPubRing;
}
@Test(expected = UnsupportedOperationException.class)
public void testPublicKeyItRemove() throws Exception {
Iterator<UncachedPublicKey> it = ring.getPublicKeys();
it.remove();
}
@Test(expected = PgpGeneralException.class)
public void testDecodeFromEmpty() throws Exception {
UncachedKeyRing.decodeFromData(new byte[0]);
}
@Test
public void testArmorIdentity() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ring.encodeArmored(out, "OpenKeychain");
Assert.assertArrayEquals("armor encoded and decoded ring should be identical to original",
ring.getEncoded(),
UncachedKeyRing.decodeFromData(out.toByteArray()).getEncoded());
}
@Test(expected = PgpGeneralException.class)
public void testDecodeEncodeMulti() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// encode secret and public ring in here
ring.encodeArmored(out, "OpenKeychain");
pubRing.encodeArmored(out, "OpenKeychain");
IteratorWithIOThrow<UncachedKeyRing> it =
UncachedKeyRing.fromStream(new ByteArrayInputStream(out.toByteArray()));
Assert.assertTrue("there should be two rings in the stream", it.hasNext());
Assert.assertArrayEquals("first ring should be the first we put in",
ring.getEncoded(), it.next().getEncoded());
Assert.assertTrue("there should be two rings in the stream", it.hasNext());
Assert.assertArrayEquals("second ring should be the second we put in",
pubRing.getEncoded(), it.next().getEncoded());
Assert.assertFalse("there should be two rings in the stream", it.hasNext());
// this should fail with PgpGeneralException, since it expects exactly one ring
UncachedKeyRing.decodeFromData(out.toByteArray());
}
@Test(expected = RuntimeException.class)
public void testPublicExtractPublic() throws Exception {
// can't do this, either!
pubRing.extractPublicKeyRing();
}
@Test(expected = IOException.class)
public void testBrokenVersionCert() throws Throwable {
// this is a test for one of the patches we use on top of stock bouncycastle, which
// returns an IOException rather than a RuntimeException in case of a bad certificate
// version byte
readRingFromResource("/test-keys/broken_cert_version.asc");
}
UncachedKeyRing readRingFromResource(String name) throws Throwable {
return UncachedKeyRing.fromStream(UncachedKeyringTest.class.getResourceAsStream(name)).next();
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) Art O Cathain
*
* 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.provider;
import java.util.Collections;
import java.util.Arrays;
import java.util.Collection;
import java.util.ArrayList;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.*;
import org.robolectric.annotation.Config;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
public class ProviderHelperKeyringTest {
@Test
public void testSavePublicKeyring() throws Exception {
Assert.assertTrue(new KeyringTestingHelper(RuntimeEnvironment.application).addKeyring(Collections.singleton(
"/public-key-for-sample.blob"
)));
}
// @Test
public void testSavePublicKeyringRsa() throws Exception {
Assert.assertTrue(new KeyringTestingHelper(RuntimeEnvironment.application).addKeyring(prependResourcePath(Arrays.asList(
"000001-006.public_key",
"000002-013.user_id",
"000003-002.sig",
"000004-012.ring_trust",
"000005-002.sig",
"000006-012.ring_trust",
"000007-002.sig",
"000008-012.ring_trust",
"000009-002.sig",
"000010-012.ring_trust",
"000011-002.sig",
"000012-012.ring_trust",
"000013-014.public_subkey",
"000014-002.sig",
"000015-012.ring_trust"
))));
}
// @Test
public void testSavePublicKeyringDsa() throws Exception {
Assert.assertTrue(new KeyringTestingHelper(RuntimeEnvironment.application).addKeyring(prependResourcePath(Arrays.asList(
"000016-006.public_key",
"000017-002.sig",
"000018-012.ring_trust",
"000019-013.user_id",
"000020-002.sig",
"000021-012.ring_trust",
"000022-002.sig",
"000023-012.ring_trust",
"000024-014.public_subkey",
"000025-002.sig",
"000026-012.ring_trust"
))));
}
// @Test
public void testSavePublicKeyringDsa2() throws Exception {
Assert.assertTrue(new KeyringTestingHelper(RuntimeEnvironment.application).addKeyring(prependResourcePath(Arrays.asList(
"000027-006.public_key",
"000028-002.sig",
"000029-012.ring_trust",
"000030-013.user_id",
"000031-002.sig",
"000032-012.ring_trust",
"000033-002.sig",
"000034-012.ring_trust"
))));
}
private static Collection<String> prependResourcePath(Collection<String> files) {
Collection<String> prependedFiles = new ArrayList<String>();
for (String file: files) {
prependedFiles.add("/OpenPGP-Haskell/tests/data/" + file);
}
return prependedFiles;
}
}

View File

@@ -0,0 +1,255 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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.provider;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.util.Arrays;
import java.util.Iterator;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
public class ProviderHelperSaveTest {
ProviderHelper mProviderHelper = new ProviderHelper(RuntimeEnvironment.application);
@BeforeClass
public static void setUpOnce() throws Exception {
ShadowLog.stream = System.out;
}
@Test public void testImportCooperPair() throws Exception {
// insert two keys with same long key id, make sure the second one gets rejected either way!
UncachedKeyRing first =
readRingFromResource("/test-keys/cooperpair/9E669861368BCA0BE42DAF7DDDA252EBB8EBE1AF.asc");
UncachedKeyRing second =
readRingFromResource("/test-keys/cooperpair/A55120427374F3F7AA5F1166DDA252EBB8EBE1AF.asc");
SaveKeyringResult result;
// insert both keys, second should fail
result = new ProviderHelper(RuntimeEnvironment.application).savePublicKeyRing(first);
Assert.assertTrue("first keyring import should succeed", result.success());
result = new ProviderHelper(RuntimeEnvironment.application).savePublicKeyRing(second);
Assert.assertFalse("second keyring import should fail", result.success());
new KeychainDatabase(RuntimeEnvironment.application).clearDatabase();
// and the other way around
result = new ProviderHelper(RuntimeEnvironment.application).savePublicKeyRing(second);
Assert.assertTrue("first keyring import should succeed", result.success());
result = new ProviderHelper(RuntimeEnvironment.application).savePublicKeyRing(first);
Assert.assertFalse("second keyring import should fail", result.success());
}
@Test public void testImportSymantec() throws Exception {
// symantec pgp desktop exports secret keys without self certificates. we don't support
// those on their own, but if they are imported together with their public key (or if
// the public key is already known), the self certs info will be merged in as a special
// case.
UncachedKeyRing seckey =
readRingFromResource("/test-keys/symantec_secret.asc");
UncachedKeyRing pubkey =
readRingFromResource("/test-keys/symantec_public.asc");
SaveKeyringResult result;
// insert secret, this should fail because of missing self-cert
result = new ProviderHelper(RuntimeEnvironment.application).saveSecretKeyRing(seckey, new ProgressScaler());
Assert.assertFalse("secret keyring import before pubring import should fail", result.success());
// insert pubkey, then seckey - both should succeed
result = new ProviderHelper(RuntimeEnvironment.application).savePublicKeyRing(pubkey);
Assert.assertTrue("public keyring import should succeed", result.success());
result = new ProviderHelper(RuntimeEnvironment.application).saveSecretKeyRing(seckey, new ProgressScaler());
Assert.assertTrue("secret keyring import after pubring import should succeed", result.success());
}
@Test public void testImportNoFlagKey() throws Exception {
UncachedKeyRing pub = readRingFromResource("/test-keys/mailvelope_07_no_key_flags.asc");
long keyId = pub.getMasterKeyId();
Assert.assertEquals("key flags should be zero",
0, (long) pub.canonicalize(new OperationLog(), 0).getPublicKey().getKeyUsage());
mProviderHelper.savePublicKeyRing(pub);
CachedPublicKeyRing cachedRing = mProviderHelper.getCachedPublicKeyRing(keyId);
CanonicalizedPublicKeyRing pubRing = mProviderHelper.getCanonicalizedPublicKeyRing(keyId);
Assert.assertEquals("master key should be encryption key", keyId, pubRing.getEncryptId());
Assert.assertEquals("master key should be encryption key (cached)", keyId, cachedRing.getEncryptId());
Assert.assertEquals("canonicalized key flags should be zero",
0, (long) pubRing.getPublicKey().getKeyUsage());
Assert.assertTrue("master key should be able to certify", pubRing.getPublicKey().canCertify());
Assert.assertTrue("master key should be allowed to sign", pubRing.getPublicKey().canSign());
Assert.assertTrue("master key should be able to encrypt", pubRing.getPublicKey().canEncrypt());
}
@Test public void testImportDivertToCard() throws Exception {
UncachedKeyRing sec = readRingFromResource("/test-keys/divert_to_card_sec.asc");
long keyId = sec.getMasterKeyId();
SaveKeyringResult result;
result = mProviderHelper.saveSecretKeyRing(sec, new ProgressScaler());
Assert.assertTrue("import of secret keyring should succeed", result.success());
// make sure both the CanonicalizedSecretKeyRing as well as the CachedPublicKeyRing correctly
// indicate the secret key type
CachedPublicKeyRing cachedRing = mProviderHelper.getCachedPublicKeyRing(keyId);
CanonicalizedSecretKeyRing secRing = mProviderHelper.getCanonicalizedSecretKeyRing(keyId);
Iterator<CanonicalizedSecretKey> it = secRing.secretKeyIterator().iterator();
{ // first subkey
Assert.assertTrue("keyring should have 3 subkeys (1)", it.hasNext());
CanonicalizedSecretKey key = it.next();
Assert.assertEquals("first subkey should be of type sign+certify",
KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, (int) key.getKeyUsage());
Assert.assertEquals("first subkey should be divert-to-card",
SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType());
Assert.assertTrue("canCertify() should be true", key.canCertify());
Assert.assertTrue("canSign() should be true", key.canSign());
// cached
Assert.assertEquals("all subkeys from CachedPublicKeyRing should be divert-to-key",
SecretKeyType.DIVERT_TO_CARD, cachedRing.getSecretKeyType(key.getKeyId()));
}
{ // second subkey
Assert.assertTrue("keyring should have 3 subkeys (2)", it.hasNext());
CanonicalizedSecretKey key = it.next();
Assert.assertEquals("second subkey should be of type authenticate",
KeyFlags.AUTHENTICATION, (int) key.getKeyUsage());
Assert.assertEquals("second subkey should be divert-to-card",
SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType());
Assert.assertTrue("canAuthenticate() should be true", key.canAuthenticate());
// cached
Assert.assertEquals("all subkeys from CachedPublicKeyRing should be divert-to-key",
SecretKeyType.DIVERT_TO_CARD, cachedRing.getSecretKeyType(key.getKeyId()));
}
{ // third subkey
Assert.assertTrue("keyring should have 3 subkeys (3)", it.hasNext());
CanonicalizedSecretKey key = it.next();
Assert.assertEquals("first subkey should be of type encrypt (both types)",
KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, (int) key.getKeyUsage());
Assert.assertEquals("third subkey should be divert-to-card",
SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType());
Assert.assertTrue("canEncrypt() should be true", key.canEncrypt());
// cached
Assert.assertEquals("all subkeys from CachedPublicKeyRing should be divert-to-key",
SecretKeyType.DIVERT_TO_CARD, cachedRing.getSecretKeyType(key.getKeyId()));
}
Assert.assertFalse("keyring should have 3 subkeys (4)", it.hasNext());
}
@Test public void testImportBadEncodedUserId() throws Exception {
UncachedKeyRing key = readRingFromResource("/test-keys/bad_user_id_encoding.asc");
long keyId = key.getMasterKeyId();
SaveKeyringResult result;
result = mProviderHelper.savePublicKeyRing(key, new ProgressScaler());
Assert.assertTrue("import of keyring should succeed", result.success());
CanonicalizedPublicKeyRing ring = mProviderHelper.getCanonicalizedPublicKeyRing(keyId);
boolean found = false;
byte[] badUserId = Hex.decode("436c61757320467261656e6b656c203c436c6175732e4672e46e6b656c4068616c696661782e727774682d61616368656e2e64653e");
for (byte[] rawUserId : new IterableIterator<byte[]>(
ring.getUnorderedRawUserIds().iterator())) {
if (Arrays.equals(rawUserId, badUserId)) {
found = true;
}
}
Assert.assertTrue("import of the badly encoded user id should succeed", found);
}
@Test
/** Tests a master key which may sign, but is stripped. In this case, if there is a different
* subkey available which can sign, that one should be selected.
*/
public void testImportStrippedFlags() throws Exception {
UncachedKeyRing key = readRingFromResource("/test-keys/stripped_flags.asc");
long masterKeyId = key.getMasterKeyId();
SaveKeyringResult result;
result = mProviderHelper.saveSecretKeyRing(key, new ProgressScaler());
Assert.assertTrue("import of keyring should succeed", result.success());
long signId;
{
CanonicalizedSecretKeyRing ring = mProviderHelper.getCanonicalizedSecretKeyRing(masterKeyId);
Assert.assertTrue("master key should have sign flag", ring.getPublicKey().canSign());
Assert.assertTrue("master key should have encrypt flag", ring.getPublicKey().canEncrypt());
signId = ring.getSecretSignId();
Assert.assertNotEquals("encrypt id should not be 0", 0, signId);
Assert.assertNotEquals("encrypt key should be different from master key", masterKeyId, signId);
}
{
CachedPublicKeyRing ring = mProviderHelper.getCachedPublicKeyRing(masterKeyId);
Assert.assertEquals("signing key should be same id cached as uncached", signId, ring.getSecretSignId());
}
}
UncachedKeyRing readRingFromResource(String name) throws Exception {
return UncachedKeyRing.fromStream(ProviderHelperSaveTest.class.getResourceAsStream(name)).next();
}
}

View File

@@ -0,0 +1,254 @@
/*
* Copyright (C) Art O Cathain
*
* 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.support;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.spongycastle.bcpg.ContainedPacket;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.bcpg.MPInteger;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.PublicKeyPacket;
import org.spongycastle.bcpg.PublicSubkeyPacket;
import org.spongycastle.bcpg.RSAPublicBCPGKey;
import org.spongycastle.bcpg.SignaturePacket;
import org.spongycastle.bcpg.SignatureSubpacket;
import org.spongycastle.bcpg.SignatureSubpacketInputStream;
import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.spongycastle.bcpg.UserIDPacket;
import org.spongycastle.bcpg.sig.Features;
import org.spongycastle.bcpg.sig.IssuerKeyID;
import org.spongycastle.bcpg.sig.KeyExpirationTime;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.bcpg.sig.PreferredAlgorithms;
import org.spongycastle.bcpg.sig.SignatureCreationTime;
import org.spongycastle.openpgp.PGPSignature;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Helps create correct and incorrect keyrings for tests.
*
* The original "correct" keyring was generated by GnuPG.
*/
public class KeyringBuilder {
private static final BigInteger PUBLIC_KEY_MODULUS = new BigInteger(
"cbab78d90d5f2cc0c54dd3c3953005a1e6b521f1ffa5465a102648bf7b91ec72" +
"f9c180759301587878caeb73332156209f81ca5b3b94309d96110f6972cfc56a" +
"37fd6279f61d71f19b8f64b288e338299dce133520f5b9b4253e6f4ba31ca36a" +
"fd87c2081b15f0b283e9350e370e181a23d31379101f17a23ae9192250db6540" +
"2e9cab2a275bc5867563227b197c8b136c832a94325b680e144ed864fb00b9b8" +
"b07e13f37b40d5ac27dae63cd6a470a7b40fa3c7479b5b43e634850cc680b177" +
"8dd6b1b51856f36c3520f258f104db2f96b31a53dd74f708ccfcefccbe420a90" +
"1c37f1f477a6a4b15f5ecbbfd93311a647bcc3f5f81c59dfe7252e3cd3be6e27"
, 16
);
private static final BigInteger PUBLIC_SUBKEY_MODULUS = new BigInteger(
"e8e2e2a33102649f19f8a07486fb076a1406ca888d72ae05d28f0ef372b5408e" +
"45132c69f6e5cb6a79bb8aed84634196731393a82d53e0ddd42f28f92cc15850" +
"8ce3b7ca1a9830502745aee774f86987993df984781f47c4a2910f95cf4c950c" +
"c4c6cccdc134ad408a0c5418b5e360c9781a8434d366053ea6338b975fae88f9" +
"383a10a90e7b2caa9ddb95708aa9d8a90246e29b04dbd6136613085c9a287315" +
"c6e9c7ff4012defc1713875e3ff6073333a1c93d7cd75ebeaaf16b8b853d96ba" +
"7003258779e8d2f70f1bc0bcd3ef91d7a9ccd8e225579b2d6fcae32799b0a6c0" +
"e7305fc65dc4edc849c6130a0d669c90c193b1e746c812510f9d600a208be4a5"
, 16
);
private static final Date SIGNATURE_DATE = new Date(1404566755000L);
private static final BigInteger EXPONENT = BigInteger.valueOf(0x010001);
private static final String USER_ID_STRING = "OpenKeychain User (NOT A REAL KEY) <openkeychain@example.com>";
public static final BigInteger CORRECT_SIGNATURE = new BigInteger(
"b065c071d3439d5610eb22e5b4df9e42ed78b8c94f487389e4fc98e8a75a043f" +
"14bf57d591811e8e7db2d31967022d2ee64372829183ec51d0e20c42d7a1e519" +
"e9fa22cd9db90f0fd7094fd093b78be2c0db62022193517404d749152c71edc6" +
"fd48af3416038d8842608ecddebbb11c5823a4321d2029b8993cb017fa8e5ad7" +
"8a9a618672d0217c4b34002f1a4a7625a514b6a86475e573cb87c64d7069658e" +
"627f2617874007a28d525e0f87d93ca7b15ad10dbdf10251e542afb8f9b16cbf" +
"7bebdb5fe7e867325a44e59cad0991cb239b1c859882e2ebb041b80e5cdc3b40" +
"ed259a8a27d63869754c0881ccdcb50f0564fecdc6966be4a4b87a3507a9d9be"
, 16
);
public static final BigInteger CORRECT_SUBKEY_SIGNATURE = new BigInteger(
"9c40543e646cfa6d3d1863d91a4e8f1421d0616ddb3187505df75fbbb6c59dd5" +
"3136b866f246a0320e793cb142c55c8e0e521d1e8d9ab864650f10690f5f1429" +
"2eb8402a3b1f82c01079d12f5c57c43fce524a530e6f49f6f87d984e26db67a2" +
"d469386dac87553c50147ebb6c2edd9248325405f737b815253beedaaba4f5c9" +
"3acd5d07fe6522ceda1027932d849e3ec4d316422cd43ea6e506f643936ab0be" +
"8246e546bb90d9a83613185047566864ffe894946477e939725171e0e15710b2" +
"089f78752a9cb572f5907323f1b62f14cb07671aeb02e6d7178f185467624ec5" +
"74e4a73c439a12edba200a4832106767366a1e6f63da0a42d593fa3914deee2b"
, 16
);
public static final BigInteger KEY_ID = BigInteger.valueOf(0x15130BCF071AE6BFL);
public static UncachedKeyRing correctRing() {
return convertToKeyring(correctKeyringPackets());
}
public static UncachedKeyRing ringWithExtraIncorrectSignature() {
List<ContainedPacket> packets = correctKeyringPackets();
SignaturePacket incorrectSignaturePacket = createSignaturePacket(CORRECT_SIGNATURE.subtract(BigInteger.ONE));
packets.add(2, incorrectSignaturePacket);
return convertToKeyring(packets);
}
private static UncachedKeyRing convertToKeyring(List<ContainedPacket> packets) {
try {
return UncachedKeyRing.decodeFromData(TestDataUtil.concatAll(packets));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static List<ContainedPacket> correctKeyringPackets() {
PublicKeyPacket publicKey = createPgpPublicKey(PUBLIC_KEY_MODULUS);
UserIDPacket userId = createUserId(USER_ID_STRING);
SignaturePacket signaturePacket = createSignaturePacket(CORRECT_SIGNATURE);
PublicKeyPacket subKey = createPgpPublicSubKey(PUBLIC_SUBKEY_MODULUS);
SignaturePacket subKeySignaturePacket = createSubkeySignaturePacket();
return new ArrayList<ContainedPacket>(Arrays.asList(
publicKey,
userId,
signaturePacket,
subKey,
subKeySignaturePacket
));
}
private static SignaturePacket createSignaturePacket(BigInteger signature) {
MPInteger[] signatureArray = new MPInteger[]{
new MPInteger(signature)
};
int signatureType = PGPSignature.POSITIVE_CERTIFICATION;
int keyAlgorithm = SignaturePacket.RSA_GENERAL;
int hashAlgorithm = HashAlgorithmTags.SHA1;
SignatureSubpacket[] hashedData = new SignatureSubpacket[]{
new SignatureCreationTime(false, SIGNATURE_DATE),
new KeyFlags(false, KeyFlags.CERTIFY_OTHER + KeyFlags.SIGN_DATA),
new KeyExpirationTime(false, TimeUnit.DAYS.toSeconds(2)),
new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_SYM_ALGS, false, new int[]{
SymmetricKeyAlgorithmTags.AES_256,
SymmetricKeyAlgorithmTags.AES_192,
SymmetricKeyAlgorithmTags.AES_128,
SymmetricKeyAlgorithmTags.CAST5,
SymmetricKeyAlgorithmTags.TRIPLE_DES
}),
new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_HASH_ALGS, false, new int[]{
HashAlgorithmTags.SHA256,
HashAlgorithmTags.SHA1,
HashAlgorithmTags.SHA384,
HashAlgorithmTags.SHA512,
HashAlgorithmTags.SHA224
}),
new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_COMP_ALGS, false, new int[]{
CompressionAlgorithmTags.ZLIB,
CompressionAlgorithmTags.BZIP2,
CompressionAlgorithmTags.ZIP
}),
new Features(false, Features.FEATURE_MODIFICATION_DETECTION),
createPreferencesSignatureSubpacket()
};
SignatureSubpacket[] unhashedData = new SignatureSubpacket[]{
new IssuerKeyID(false, KEY_ID.toByteArray())
};
byte[] fingerPrint = new BigInteger("522c", 16).toByteArray();
return new SignaturePacket(signatureType,
KEY_ID.longValue(),
keyAlgorithm,
hashAlgorithm,
hashedData,
unhashedData,
fingerPrint,
signatureArray);
}
/**
* There is no Preferences subpacket in BouncyCastle, so we have
* to create one manually.
*/
private static SignatureSubpacket createPreferencesSignatureSubpacket() {
SignatureSubpacket prefs;
try {
prefs = new SignatureSubpacketInputStream(new ByteArrayInputStream(
new byte[]{2, SignatureSubpacketTags.KEY_SERVER_PREFS, (byte) 0x80})
).readPacket();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return prefs;
}
private static SignaturePacket createSubkeySignaturePacket() {
int signatureType = PGPSignature.SUBKEY_BINDING;
int keyAlgorithm = SignaturePacket.RSA_GENERAL;
int hashAlgorithm = HashAlgorithmTags.SHA1;
SignatureSubpacket[] hashedData = new SignatureSubpacket[]{
new SignatureCreationTime(false, SIGNATURE_DATE),
new KeyFlags(false, KeyFlags.ENCRYPT_COMMS + KeyFlags.ENCRYPT_STORAGE),
new KeyExpirationTime(false, TimeUnit.DAYS.toSeconds(2)),
};
SignatureSubpacket[] unhashedData = new SignatureSubpacket[]{
new IssuerKeyID(false, KEY_ID.toByteArray())
};
byte[] fingerPrint = new BigInteger("234a", 16).toByteArray();
MPInteger[] signature = new MPInteger[]{
new MPInteger(CORRECT_SUBKEY_SIGNATURE)
};
return new SignaturePacket(signatureType,
KEY_ID.longValue(),
keyAlgorithm,
hashAlgorithm,
hashedData,
unhashedData,
fingerPrint,
signature);
}
private static PublicKeyPacket createPgpPublicKey(BigInteger modulus) {
return new PublicKeyPacket(PublicKeyAlgorithmTags.RSA_GENERAL, SIGNATURE_DATE, new RSAPublicBCPGKey(modulus, EXPONENT));
}
private static PublicKeyPacket createPgpPublicSubKey(BigInteger modulus) {
return new PublicSubkeyPacket(PublicKeyAlgorithmTags.RSA_GENERAL, SIGNATURE_DATE, new RSAPublicBCPGKey(modulus, EXPONENT));
}
private static UserIDPacket createUserId(String userId) {
return new UserIDPacket(userId);
}
}

View File

@@ -0,0 +1,365 @@
/*
* Copyright (C) Art O Cathain
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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.support;
import android.content.Context;
import org.spongycastle.util.Arrays;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
/** Helper methods for keyring tests. */
public class KeyringTestingHelper {
private final Context context;
public KeyringTestingHelper(Context robolectricContext) {
this.context = robolectricContext;
}
public boolean addKeyring(Collection<String> blobFiles) throws Exception {
ProviderHelper providerHelper = new ProviderHelper(context);
byte[] data = TestDataUtil.readAllFully(blobFiles);
UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data);
long masterKeyId = ring.getMasterKeyId();
// Should throw an exception; key is not yet saved
retrieveKeyAndExpectNotFound(providerHelper, masterKeyId);
SaveKeyringResult saveKeyringResult = providerHelper.savePublicKeyRing(ring, new ProgressScaler());
boolean saveSuccess = saveKeyringResult.success();
// Now re-retrieve the saved key. Should not throw an exception.
providerHelper.getCanonicalizedPublicKeyRing(masterKeyId);
// A different ID should still fail
retrieveKeyAndExpectNotFound(providerHelper, masterKeyId - 1);
return saveSuccess;
}
public static UncachedKeyRing removePacket(UncachedKeyRing ring, int position)
throws IOException, PgpGeneralException {
return UncachedKeyRing.decodeFromData(removePacket(ring.getEncoded(), position));
}
public static byte[] removePacket(byte[] ring, int position) throws IOException {
Iterator<RawPacket> it = parseKeyring(ring);
ByteArrayOutputStream out = new ByteArrayOutputStream(ring.length);
int i = 0;
while(it.hasNext()) {
// at the right position, skip the packet
if(i++ == position) {
it.next();
continue;
}
// write the old one
out.write(it.next().buf);
}
if (i <= position) {
throw new IndexOutOfBoundsException("injection index did not not occur in stream!");
}
return out.toByteArray();
}
public static UncachedKeyRing injectPacket(UncachedKeyRing ring, byte[] inject, int position)
throws IOException, PgpGeneralException {
return UncachedKeyRing.decodeFromData(injectPacket(ring.getEncoded(), inject, position));
}
public static byte[] injectPacket(byte[] ring, byte[] inject, int position) throws IOException {
Iterator<RawPacket> it = parseKeyring(ring);
ByteArrayOutputStream out = new ByteArrayOutputStream(ring.length + inject.length);
int i = 0;
while(it.hasNext()) {
// at the right position, inject the new packet
if(i++ == position) {
out.write(inject);
}
// write the old one
out.write(it.next().buf);
}
if (i <= position) {
throw new IndexOutOfBoundsException("injection index did not not occur in stream!");
}
return out.toByteArray();
}
/** This class contains a single pgp packet, together with information about its position
* in the keyring and its packet tag.
*/
public static class RawPacket {
public int position;
// packet tag for convenience, this can also be read from the header
public int tag;
public int headerLength, length;
// this buf includes the header, so its length is headerLength + length!
public byte[] buf;
@Override
public boolean equals(Object other) {
return other instanceof RawPacket && Arrays.areEqual(this.buf, ((RawPacket) other).buf);
}
@Override
public int hashCode() {
return Arrays.hashCode(buf);
}
}
/** A comparator which compares RawPackets by their position */
public static final Comparator<RawPacket> packetOrder = new Comparator<RawPacket>() {
public int compare(RawPacket left, RawPacket right) {
return Integer.compare(left.position, right.position);
}
};
/** Diff two keyrings, returning packets only present in one keyring in its associated List.
*
* Packets in the returned lists are annotated and ordered by their original order of appearance
* in their origin keyrings.
*
* @return true if keyrings differ in at least one packet
*/
public static boolean diffKeyrings(byte[] ringA, byte[] ringB,
List<RawPacket> onlyA, List<RawPacket> onlyB)
throws IOException {
Iterator<RawPacket> streamA = parseKeyring(ringA);
Iterator<RawPacket> streamB = parseKeyring(ringB);
HashSet<RawPacket> a = new HashSet<RawPacket>(), b = new HashSet<RawPacket>();
RawPacket p;
int pos = 0;
while(true) {
p = streamA.next();
if (p == null) {
break;
}
p.position = pos++;
a.add(p);
}
pos = 0;
while(true) {
p = streamB.next();
if (p == null) {
break;
}
p.position = pos++;
b.add(p);
}
onlyA.clear();
onlyB.clear();
onlyA.addAll(a);
onlyA.removeAll(b);
onlyB.addAll(b);
onlyB.removeAll(a);
Collections.sort(onlyA, packetOrder);
Collections.sort(onlyB, packetOrder);
return !onlyA.isEmpty() || !onlyB.isEmpty();
}
/** Creates an iterator of RawPackets over a binary keyring. */
public static Iterator<RawPacket> parseKeyring(byte[] ring) {
final InputStream stream = new ByteArrayInputStream(ring);
return new Iterator<RawPacket>() {
RawPacket next;
@Override
public boolean hasNext() {
if (next == null) try {
next = readPacket(stream);
} catch (IOException e) {
return false;
}
return next != null;
}
@Override
public RawPacket next() {
if (!hasNext()) {
return null;
}
try {
return next;
} finally {
next = null;
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/** Read a single (raw) pgp packet from an input stream.
*
* Note that the RawPacket.position field is NOT set here!
*
* Variable length packets are not handled here. we don't use those in our test classes, and
* otherwise rely on BouncyCastle's own unit tests to handle those correctly.
*/
private static RawPacket readPacket(InputStream in) throws IOException {
// save here. this is tag + length, max 6 bytes
in.mark(6);
int hdr = in.read();
int headerLength = 1;
if (hdr < 0) {
return null;
}
if ((hdr & 0x80) == 0) {
throw new IOException("invalid header encountered");
}
boolean newPacket = (hdr & 0x40) != 0;
int tag;
int bodyLen;
if (newPacket) {
tag = hdr & 0x3f;
int l = in.read();
headerLength += 1;
if (l < 192) {
bodyLen = l;
} else if (l <= 223) {
int b = in.read();
headerLength += 1;
bodyLen = ((l - 192) << 8) + (b) + 192;
} else if (l == 255) {
bodyLen = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
headerLength += 4;
} else {
// bodyLen = 1 << (l & 0x1f);
throw new IOException("no support for partial bodies in test classes");
}
} else {
int lengthType = hdr & 0x3;
tag = (hdr & 0x3f) >> 2;
switch (lengthType) {
case 0:
bodyLen = in.read();
headerLength += 1;
break;
case 1:
bodyLen = (in.read() << 8) | in.read();
headerLength += 2;
break;
case 2:
bodyLen = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
headerLength += 4;
break;
case 3:
// bodyLen = 1 << (l & 0x1f);
throw new IOException("no support for partial bodies in test classes");
default:
throw new IOException("unknown length type encountered");
}
}
in.reset();
// read the entire packet INCLUDING the header here
byte[] buf = new byte[headerLength+bodyLen];
if (in.read(buf) != headerLength+bodyLen) {
throw new IOException("read length mismatch!");
}
RawPacket p = new RawPacket();
p.tag = tag;
p.headerLength = headerLength;
p.length = bodyLen;
p.buf = buf;
return p;
}
public static <E> E getNth(Iterator<E> it, int position) {
while(position-- > 0) {
it.next();
}
return it.next();
}
public static long getSubkeyId(UncachedKeyRing ring, int position) {
return getNth(ring.getPublicKeys(), position).getKeyId();
}
private void retrieveKeyAndExpectNotFound(ProviderHelper providerHelper, long masterKeyId) {
try {
providerHelper.getCanonicalizedPublicKeyRing(masterKeyId);
throw new AssertionError("Was expecting the previous call to fail!");
} catch (ProviderHelper.NotFoundException expectedException) {
// good
}
}
public static <E> List<E> itToList(Iterator<E> it) {
List<E> result = new ArrayList<E>();
while(it.hasNext()) {
result.add(it.next());
}
return result;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) Art O Cathain
*
* 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.support;
import android.content.Context;
import android.net.Uri;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
class ProviderHelperStub extends ProviderHelper {
public ProviderHelperStub(Context context) {
super(context);
}
@Override
public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri id) throws NotFoundException {
byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob"));
return new CanonicalizedPublicKeyRing(data, 0);
}
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright (C) Art O Cathain
*
* 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.support;
import org.spongycastle.bcpg.ContainedPacket;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Iterator;
/**
* Misc support functions. Would just use Guava / Apache Commons but
* avoiding extra dependencies.
*/
public class TestDataUtil {
public static byte[] readFully(InputStream input) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
appendToOutput(input, output);
return output.toByteArray();
}
public static void appendToOutput(InputStream input, OutputStream output) {
byte[] buffer = new byte[8192];
int bytesRead;
try {
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static byte[] readAllFully(Collection<String> inputResources) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
for (String inputResource : inputResources) {
appendToOutput(getResourceAsStream(inputResource), output);
}
return output.toByteArray();
}
public static InputStream getResourceAsStream(String resourceName) {
return TestDataUtil.class.getResourceAsStream(resourceName);
}
/**
* Null-safe equivalent of {@code a.equals(b)}.
*/
public static boolean equals(Object a, Object b) {
return (a == null) ? (b == null) : a.equals(b);
}
public static <T> boolean iterEquals(Iterator<T> a, Iterator<T> b, EqualityChecker<T> comparator) {
while (a.hasNext()) {
T aObject = a.next();
if (!b.hasNext()) {
return false;
}
T bObject = b.next();
if (!comparator.areEquals(aObject, bObject)) {
return false;
}
}
if (b.hasNext()) {
return false;
}
return true;
}
public static <T> boolean iterEquals(Iterator<T> a, Iterator<T> b) {
return iterEquals(a, b, new EqualityChecker<T>() {
@Override
public boolean areEquals(T lhs, T rhs) {
return TestDataUtil.equals(lhs, rhs);
}
});
}
public static interface EqualityChecker<T> {
public boolean areEquals(T lhs, T rhs);
}
public static byte[] concatAll(java.util.List<ContainedPacket> packets) {
byte[][] byteArrays = new byte[packets.size()][];
try {
for (int i = 0; i < packets.size(); i++) {
byteArrays[i] = packets.get(i).getEncoded();
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return concatAll(byteArrays);
}
public static byte[] concatAll(byte[]... byteArrays) {
if (byteArrays.length == 1) {
return byteArrays[0];
} else if (byteArrays.length == 2) {
return concat(byteArrays[0], byteArrays[1]);
} else {
byte[] first = concat(byteArrays[0], byteArrays[1]);
byte[][] remainingArrays = new byte[byteArrays.length - 1][];
remainingArrays[0] = first;
System.arraycopy(byteArrays, 2, remainingArrays, 1, byteArrays.length - 2);
return concatAll(remainingArrays);
}
}
private static byte[] concat(byte[] a, byte[] b) {
int aLen = a.length;
int bLen = b.length;
byte[] c = new byte[aLen + bLen];
System.arraycopy(a, 0, c, 0, aLen);
System.arraycopy(b, 0, c, aLen, bLen);
return c;
}
}

View File

@@ -0,0 +1,102 @@
/* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.keychain.util;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.util.Iso7816TLV.Iso7816CompositeTLV;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
public class Iso7816TLVTest {
@Before
public void setUp() throws Exception {
ShadowLog.stream = System.out;
}
@Test
public void testDecode() throws Exception {
// this is an Application Related Data packet, received from my Yubikey
String input = "6e81dd4f10d27600012401020000000000000100005f520f0073000080000000000000000000007300c00af00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f030303c53c1efdb4845ca242ca6977fddb1f788094fd3b430af1114c28a08d8c5afda81191cc50ca9bf51bc99fe8e6ca03a9d4d40e7b5925cd154813df381655b2c63c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd0c5423590e5423590e5423590e9000";
byte[] data = Hex.decode(input);
Iso7816TLV tlv = Iso7816TLV.readSingle(data, true);
Assert.assertNotNull("tlv parse must succeed", tlv);
Assert.assertEquals("top packet must be 'application related data' tag", 0x6e, tlv.mT);
Assert.assertEquals("length must be correct", 221, tlv.mL);
Assert.assertTrue("top packet must be composite", tlv instanceof Iso7816CompositeTLV);
Iso7816CompositeTLV ctlv = (Iso7816CompositeTLV) tlv;
Assert.assertEquals("top packet must have 11 sub packets", 11, ctlv.mSubs.length);
Assert.assertEquals("sub packet #1 must have expected tag", 0x4f, ctlv.mSubs[0].mT);
Assert.assertEquals("sub packet #1 must have expected length", 16, ctlv.mSubs[0].mL);
Assert.assertEquals("sub packet #2 must have expected tag", 0x5f52, ctlv.mSubs[1].mT);
Assert.assertEquals("sub packet #2 must have expected length", 15, ctlv.mSubs[1].mL);
Assert.assertEquals("sub packet #3 must have expected tag", 0x73, ctlv.mSubs[2].mT);
Assert.assertEquals("sub packet #3 must have expected length", 0, ctlv.mSubs[2].mL);
Assert.assertTrue("sub packet #3 muse be composite", ctlv.mSubs[2] instanceof Iso7816CompositeTLV);
Assert.assertEquals("sub packet #4 must have expected tag", 0xc0, ctlv.mSubs[3].mT);
Assert.assertEquals("sub packet #4 must have expected length", 10, ctlv.mSubs[3].mL);
Assert.assertEquals("sub packet #5 must have expected tag", 0xc1, ctlv.mSubs[4].mT);
Assert.assertEquals("sub packet #5 must have expected length", 6, ctlv.mSubs[4].mL);
Assert.assertEquals("sub packet #6 must have expected tag", 0xc2, ctlv.mSubs[5].mT);
Assert.assertEquals("sub packet #6 must have expected length", 6, ctlv.mSubs[5].mL);
Assert.assertEquals("sub packet #7 must have expected tag", 0xc3, ctlv.mSubs[6].mT);
Assert.assertEquals("sub packet #7 must have expected length", 6, ctlv.mSubs[6].mL);
Assert.assertEquals("sub packet #8 must have expected tag", 0xc4, ctlv.mSubs[7].mT);
Assert.assertEquals("sub packet #8 must have expected length", 7, ctlv.mSubs[7].mL);
Assert.assertEquals("sub packet #9 must have expected tag", 0xc5, ctlv.mSubs[8].mT);
Assert.assertEquals("sub packet #9 must have expected length", 60, ctlv.mSubs[8].mL);
{
// this is my pubkey fingerprint
String fingerprint = "1efdb4845ca242ca6977fddb1f788094fd3b430a";
byte[] V1 = new byte[20];
System.arraycopy(ctlv.mSubs[8].mV, 0, V1, 0, 20);
Assert.assertArrayEquals("fingerprint must match", V1, Hex.decode(fingerprint));
}
Assert.assertEquals("sub packet #10 must have expected tag", 0xc6, ctlv.mSubs[9].mT);
Assert.assertEquals("sub packet #10 must have expected length", 60, ctlv.mSubs[9].mL);
Assert.assertEquals("sub packet #11 must have expected tag", 0xcd, ctlv.mSubs[10].mT);
Assert.assertEquals("sub packet #11 must have expected length", 12, ctlv.mSubs[10].mL);
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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.util;
import android.os.Bundle;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
public class ParcelableFileCacheTest {
@Before
public void setUp() throws Exception {
ShadowLog.stream = System.out;
}
@Test
public void testInputOutput() throws Exception {
ParcelableFileCache<Bundle> cache = new ParcelableFileCache<Bundle>(RuntimeEnvironment.application, "test.pcl");
ArrayList<Bundle> list = new ArrayList<Bundle>();
for (int i = 0; i < 50; i++) {
Bundle b = new Bundle();
b.putInt("key1", i);
b.putString("key2", Integer.toString(i));
list.add(b);
}
// write to cache file
cache.writeCache(list.size(), list.iterator());
// read back
IteratorWithSize<Bundle> it = cache.readCache();
Assert.assertEquals("number of entries must be correct", list.size(), it.getSize());
while (it.hasNext()) {
Bundle b = it.next();
Assert.assertEquals("input values should be equal to output values",
b.getInt("key1"), b.getInt("key1"));
Assert.assertEquals("input values should be equal to output values",
b.getString("key2"), b.getString("key2"));
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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.util;
import java.util.Random;
public class TestingUtils {
public static Passphrase genPassphrase() {
return genPassphrase(false);
}
public static Passphrase genPassphrase(boolean noEmpty) {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789!@#$%^&*()-_=";
Random r = new Random();
StringBuilder passbuilder = new StringBuilder();
// 20% chance for an empty passphrase
for(int i = 0, j = noEmpty || r.nextInt(10) > 2 ? r.nextInt(20)+1 : 0; i < j; i++) {
passbuilder.append(chars.charAt(r.nextInt(chars.length())));
}
System.out.println("Generated passphrase: '" + passbuilder.toString() + "'");
return new Passphrase(passbuilder.toString());
}
}

View File

@@ -0,0 +1,13 @@
Copyright © 2011, Stephen Paul Weber <singpolyma.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -0,0 +1,26 @@
These test files were copied from the OpenPGP Haskell project.
Original README
===============
This is an OpenPGP library inspired by my work on OpenPGP libraries in
Ruby <https://github.com/singpolyma/openpgp>,
PHP <http://github.com/singpolyma/openpgp-php>,
and Python <https://github.com/singpolyma/OpenPGP-Python>.
It defines types to represent OpenPGP messages as a series of packets
and then defines instances of Data.Binary for each to facilitate
encoding/decoding.
For performing cryptography, see
<http://hackage.haskell.org/package/openpgp-crypto-api> or
<http://hackage.haskell.org/package/openpgp-Crypto>
For dealing with ASCII armor, see
<http://hackage.haskell.org/package/openpgp-asciiarmor>
It is intended that you use qualified imports with this library.
> import qualified Data.OpenPGP as OpenPGP

View File

@@ -0,0 +1 @@
´$Test Key (RSA) <testkey@example.org>

View File

@@ -0,0 +1 @@
´$Test Key (DSA) <testkey@example.com>

View File

@@ -0,0 +1 @@
´+Test Key (DSA sign-only) <test@example.net>

View File

@@ -0,0 +1 @@
´.Test Key (RSA sign-only) <testkey@example.net>

View File

@@ -0,0 +1 @@
´$Test Key (RSA) <testkey@example.org>

View File

@@ -0,0 +1 @@
´$Test Key (DSA) <testkey@example.com>

View File

@@ -0,0 +1 @@
´+Test Key (DSA sign-only) <test@example.net>

View File

@@ -0,0 +1 @@
´.Test Key (RSA sign-only) <testkey@example.net>

Some files were not shown because too many files have changed in this diff Show More