mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-23 17:22:16 -05:00
Merge pull request #711 from open-keychain/external-test
External test + working SaveKeyringParcel
This commit is contained in:
commit
d8f941fa70
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -31,6 +31,6 @@
|
||||
[submodule "extern/minidns"]
|
||||
path = extern/minidns
|
||||
url = https://github.com/open-keychain/minidns.git
|
||||
[submodule "OpenKeychain/src/test/resources/extern/OpenPGP-Haskell"]
|
||||
path = OpenKeychain/src/test/resources/extern/OpenPGP-Haskell
|
||||
[submodule "OpenKeychain-Test/src/test/resources/extern/OpenPGP-Haskell"]
|
||||
path = OpenKeychain-Test/src/test/resources/extern/OpenPGP-Haskell
|
||||
url = https://github.com/singpolyma/OpenPGP-Haskell.git
|
||||
|
@ -1,5 +1,5 @@
|
||||
language: java
|
||||
jdk: oraclejdk7
|
||||
jdk: openjdk7
|
||||
before_install:
|
||||
# Install base Android SDK
|
||||
- sudo apt-get update -qq
|
||||
@ -12,6 +12,9 @@ before_install:
|
||||
# Install required Android components.
|
||||
#- echo "y" | android update sdk -a --filter build-tools-19.1.0,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force
|
||||
- ( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) | android update sdk --no-ui --all --force --filter build-tools-19.1.0,android-19,platform-tools,extra-android-support,extra-android-m2repository
|
||||
- ./install-custom-gradle-test-plugin.sh
|
||||
install: echo "Installation done"
|
||||
script: gradle assemble -S -q
|
||||
script:
|
||||
- gradle assemble -S -q
|
||||
- gradle --info OpenKeychain-Test:testDebug
|
||||
|
||||
|
80
OpenKeychain-Test/build.gradle
Normal file
80
OpenKeychain-Test/build.gradle
Normal file
@ -0,0 +1,80 @@
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'android-test'
|
||||
|
||||
dependencies {
|
||||
testCompile 'junit:junit:4.11'
|
||||
testCompile 'com.google.android:android:4.1.1.4'
|
||||
testCompile('com.squareup:fest-android:1.0.+') { exclude module: 'support-v4' }
|
||||
testCompile ('org.robolectric:robolectric:2.3') {
|
||||
exclude module: 'classworlds'
|
||||
exclude module: 'maven-artifact'
|
||||
exclude module: 'maven-artifact-manager'
|
||||
exclude module: 'maven-error-diagnostics'
|
||||
exclude module: 'maven-model'
|
||||
exclude module: 'maven-plugin-registry'
|
||||
exclude module: 'maven-profile'
|
||||
exclude module: 'maven-project'
|
||||
exclude module: 'maven-settings'
|
||||
exclude module: 'nekohtml'
|
||||
exclude module: 'plexus-container-default'
|
||||
exclude module: 'plexus-interpolation'
|
||||
exclude module: 'plexus-utils'
|
||||
exclude module: 'support-v4' // crazy but my android studio don't like this dependency and to fix it remove .idea and re import project
|
||||
exclude module: 'wagon-file'
|
||||
exclude module: 'wagon-http-lightweight'
|
||||
exclude module: 'wagon-http-shared'
|
||||
exclude module: 'wagon-provider-api'
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
projectUnderTest ':OpenKeychain'
|
||||
}
|
||||
|
||||
// new workaround to force add custom output dirs for android studio
|
||||
task addTest {
|
||||
def file = file(project.name + ".iml")
|
||||
doLast {
|
||||
try {
|
||||
def parsedXml = (new XmlParser()).parse(file)
|
||||
def node = parsedXml.component[1]
|
||||
def outputNode = parsedXml.component[1].output[0]
|
||||
def outputTestNode = parsedXml.component[1].'output-test'[0]
|
||||
def rewrite = false
|
||||
|
||||
new Node(node, 'sourceFolder', ['url': 'file://$MODULE_DIR$/' + "${it}", 'isTestSource': "true"])
|
||||
|
||||
if(outputNode == null) {
|
||||
new Node(node, 'output', ['url': 'file://$MODULE_DIR$/build/resources/testDebug'])
|
||||
} else {
|
||||
if(outputNode.attributes['url'] != 'file://$MODULE_DIR$/build/resources/testDebug') {
|
||||
outputNode.attributes = ['url': 'file://$MODULE_DIR$/build/resources/testDebug']
|
||||
rewrite = true
|
||||
}
|
||||
}
|
||||
|
||||
if(outputTestNode == null) {
|
||||
new Node(node, 'output-test', ['url': 'file://$MODULE_DIR$/build/test-classes/debug'])
|
||||
} else {
|
||||
if(outputTestNode.attributes['url'] != 'file://$MODULE_DIR$/build/test-classes/debug') {
|
||||
outputTestNode.attributes = ['url': 'file://$MODULE_DIR$/build/test-classes/debug']
|
||||
rewrite = true
|
||||
}
|
||||
}
|
||||
|
||||
if(rewrite) {
|
||||
def writer = new StringWriter()
|
||||
new XmlNodePrinter(new PrintWriter(writer)).print(parsedXml)
|
||||
file.text = writer.toString()
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// iml not found, common on command line only builds
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// always do the addtest on prebuild
|
||||
gradle.projectsEvaluated {
|
||||
testDebugClasses.dependsOn(addTest)
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,331 @@
|
||||
/*
|
||||
* Copyright (C) Art O Cathain, Vincent Breitmoser
|
||||
*
|
||||
* 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.NullProgressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
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);
|
||||
|
||||
OperationResults.SaveKeyringResult saveKeyringResult = providerHelper.savePublicKeyRing(ring, new NullProgressable());
|
||||
|
||||
boolean saveSuccess = saveKeyringResult.success();
|
||||
|
||||
// Now re-retrieve the saved key. Should not throw an exception.
|
||||
providerHelper.getWrappedPublicKeyRing(masterKeyId);
|
||||
|
||||
// A different ID should still fail
|
||||
retrieveKeyAndExpectNotFound(providerHelper, masterKeyId - 1);
|
||||
|
||||
return saveSuccess;
|
||||
}
|
||||
|
||||
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) {
|
||||
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 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;
|
||||
|
||||
}
|
||||
|
||||
private void retrieveKeyAndExpectNotFound(ProviderHelper providerHelper, long masterKeyId) {
|
||||
try {
|
||||
providerHelper.getWrappedPublicKeyRing(masterKeyId);
|
||||
throw new AssertionError("Was expecting the previous call to fail!");
|
||||
} catch (ProviderHelper.NotFoundException expectedException) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,20 @@
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
package org.sufficientlysecure.keychain.support;
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.WrappedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
|
||||
/**
|
||||
* Created by art on 21/06/14.
|
||||
*/
|
||||
class ProviderHelperStub extends ProviderHelper {
|
||||
public ProviderHelperStub(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri id) throws NotFoundException {
|
||||
byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob"));
|
||||
return new WrappedPublicKeyRing(data, false, 0);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,297 @@
|
||||
/*
|
||||
* 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.BCPGKey;
|
||||
import org.spongycastle.bcpg.PublicKeyPacket;
|
||||
import org.spongycastle.bcpg.SignatureSubpacket;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Created by art on 28/06/14.
|
||||
*/
|
||||
public class UncachedKeyringTestingHelper {
|
||||
|
||||
public static boolean compareRing(UncachedKeyRing keyRing1, UncachedKeyRing keyRing2) {
|
||||
OperationResultParcel.OperationLog operationLog = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing canonicalized = keyRing1.canonicalize(operationLog, 0);
|
||||
|
||||
if (canonicalized == null) {
|
||||
throw new AssertionError("Canonicalization failed; messages: [" + operationLog.toList() + "]");
|
||||
}
|
||||
|
||||
return TestDataUtil.iterEquals(canonicalized.getPublicKeys(), keyRing2.getPublicKeys(), new
|
||||
TestDataUtil.EqualityChecker<UncachedPublicKey>() {
|
||||
@Override
|
||||
public boolean areEquals(UncachedPublicKey lhs, UncachedPublicKey rhs) {
|
||||
return comparePublicKey(lhs, rhs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean comparePublicKey(UncachedPublicKey key1, UncachedPublicKey key2) {
|
||||
boolean equal = true;
|
||||
|
||||
if (key1.canAuthenticate() != key2.canAuthenticate()) {
|
||||
return false;
|
||||
}
|
||||
if (key1.canCertify() != key2.canCertify()) {
|
||||
return false;
|
||||
}
|
||||
if (key1.canEncrypt() != key2.canEncrypt()) {
|
||||
return false;
|
||||
}
|
||||
if (key1.canSign() != key2.canSign()) {
|
||||
return false;
|
||||
}
|
||||
if (key1.getAlgorithm() != key2.getAlgorithm()) {
|
||||
return false;
|
||||
}
|
||||
if (key1.getBitStrength() != key2.getBitStrength()) {
|
||||
return false;
|
||||
}
|
||||
if (!TestDataUtil.equals(key1.getCreationTime(), key2.getCreationTime())) {
|
||||
return false;
|
||||
}
|
||||
if (!TestDataUtil.equals(key1.getExpiryTime(), key2.getExpiryTime())) {
|
||||
return false;
|
||||
}
|
||||
if (!Arrays.equals(key1.getFingerprint(), key2.getFingerprint())) {
|
||||
return false;
|
||||
}
|
||||
if (key1.getKeyId() != key2.getKeyId()) {
|
||||
return false;
|
||||
}
|
||||
if (key1.getKeyUsage() != key2.getKeyUsage()) {
|
||||
return false;
|
||||
}
|
||||
if (!TestDataUtil.equals(key1.getPrimaryUserId(), key2.getPrimaryUserId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ooops, getPublicKey is due to disappear. But then how to compare?
|
||||
if (!keysAreEqual(key1.getPublicKey(), key2.getPublicKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return equal;
|
||||
}
|
||||
|
||||
public static boolean keysAreEqual(PGPPublicKey a, PGPPublicKey b) {
|
||||
|
||||
if (a.getAlgorithm() != b.getAlgorithm()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getBitStrength() != b.getBitStrength()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TestDataUtil.equals(a.getCreationTime(), b.getCreationTime())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(a.getFingerprint(), b.getFingerprint())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getKeyID() != b.getKeyID()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pubKeyPacketsAreEqual(a.getPublicKeyPacket(), b.getPublicKeyPacket())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getVersion() != b.getVersion()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getValidDays() != b.getValidDays()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getValidSeconds() != b.getValidSeconds()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(a.getTrustData(), b.getTrustData())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TestDataUtil.iterEquals(a.getUserIDs(), b.getUserIDs())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TestDataUtil.iterEquals(a.getUserAttributes(), b.getUserAttributes(),
|
||||
new TestDataUtil.EqualityChecker<PGPUserAttributeSubpacketVector>() {
|
||||
public boolean areEquals(PGPUserAttributeSubpacketVector lhs, PGPUserAttributeSubpacketVector rhs) {
|
||||
// For once, BC defines equals, so we use it implicitly.
|
||||
return TestDataUtil.equals(lhs, rhs);
|
||||
}
|
||||
}
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!TestDataUtil.iterEquals(a.getSignatures(), b.getSignatures(),
|
||||
new TestDataUtil.EqualityChecker<PGPSignature>() {
|
||||
public boolean areEquals(PGPSignature lhs, PGPSignature rhs) {
|
||||
return signaturesAreEqual(lhs, rhs);
|
||||
}
|
||||
}
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean signaturesAreEqual(PGPSignature a, PGPSignature b) {
|
||||
|
||||
if (a.getVersion() != b.getVersion()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getKeyAlgorithm() != b.getKeyAlgorithm()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getHashAlgorithm() != b.getHashAlgorithm()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getSignatureType() != b.getSignatureType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!Arrays.equals(a.getSignature(), b.getSignature())) {
|
||||
return false;
|
||||
}
|
||||
} catch (PGPException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
if (a.getKeyID() != b.getKeyID()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TestDataUtil.equals(a.getCreationTime(), b.getCreationTime())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(a.getSignatureTrailer(), b.getSignatureTrailer())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!subPacketVectorsAreEqual(a.getHashedSubPackets(), b.getHashedSubPackets())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!subPacketVectorsAreEqual(a.getUnhashedSubPackets(), b.getUnhashedSubPackets())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean subPacketVectorsAreEqual(PGPSignatureSubpacketVector aHashedSubPackets, PGPSignatureSubpacketVector bHashedSubPackets) {
|
||||
for (int i = 0; i < Byte.MAX_VALUE; i++) {
|
||||
if (!TestDataUtil.iterEquals(Arrays.asList(aHashedSubPackets.getSubpackets(i)).iterator(),
|
||||
Arrays.asList(bHashedSubPackets.getSubpackets(i)).iterator(),
|
||||
new TestDataUtil.EqualityChecker<SignatureSubpacket>() {
|
||||
@Override
|
||||
public boolean areEquals(SignatureSubpacket lhs, SignatureSubpacket rhs) {
|
||||
return signatureSubpacketsAreEqual(lhs, rhs);
|
||||
}
|
||||
}
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean signatureSubpacketsAreEqual(SignatureSubpacket lhs, SignatureSubpacket rhs) {
|
||||
if (lhs.getType() != rhs.getType()) {
|
||||
return false;
|
||||
}
|
||||
if (!Arrays.equals(lhs.getData(), rhs.getData())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean pubKeyPacketsAreEqual(PublicKeyPacket a, PublicKeyPacket b) {
|
||||
|
||||
if (a.getAlgorithm() != b.getAlgorithm()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bcpgKeysAreEqual(a.getKey(), b.getKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TestDataUtil.equals(a.getTime(), b.getTime())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getValidDays() != b.getValidDays()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.getVersion() != b.getVersion()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean bcpgKeysAreEqual(BCPGKey a, BCPGKey b) {
|
||||
|
||||
if (!TestDataUtil.equals(a.getFormat(), b.getFormat())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arrays.equals(a.getEncoded(), b.getEncoded())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void doTestCanonicalize(UncachedKeyRing inputKeyRing, UncachedKeyRing expectedKeyRing) {
|
||||
if (!compareRing(inputKeyRing, expectedKeyRing)) {
|
||||
throw new AssertionError("Expected [" + inputKeyRing + "] to match [" + expectedKeyRing + "]");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,20 @@
|
||||
/*
|
||||
* 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 tests;
|
||||
|
||||
import org.junit.Assert;
|
||||
@ -5,7 +22,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.*;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.sufficientlysecure.keychain.testsupport.PgpVerifyTestingHelper;
|
||||
import org.sufficientlysecure.keychain.support.PgpVerifyTestingHelper;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
|
@ -0,0 +1,783 @@
|
||||
package org.sufficientlysecure.keychain.tests;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.*;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
import org.spongycastle.bcpg.BCPGInputStream;
|
||||
import org.spongycastle.bcpg.Packet;
|
||||
import org.spongycastle.bcpg.PacketTags;
|
||||
import org.spongycastle.bcpg.SecretSubkeyPacket;
|
||||
import org.spongycastle.bcpg.SignaturePacket;
|
||||
import org.spongycastle.bcpg.UserIDPacket;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Constants.choice.algorithm;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
|
||||
import org.sufficientlysecure.keychain.support.KeyringBuilder;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||
import org.sufficientlysecure.keychain.support.TestDataUtil;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
|
||||
public class PgpKeyOperationTest {
|
||||
|
||||
static UncachedKeyRing staticRing;
|
||||
static String passphrase;
|
||||
|
||||
UncachedKeyRing ring;
|
||||
PgpKeyOperation op;
|
||||
SaveKeyringParcel parcel;
|
||||
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
|
||||
ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>();
|
||||
|
||||
@BeforeClass public static void setUpOnce() throws Exception {
|
||||
ShadowLog.stream = System.out;
|
||||
|
||||
{
|
||||
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789!@#$%^&*()-_=";
|
||||
Random r = new Random();
|
||||
StringBuilder passbuilder = new StringBuilder();
|
||||
// 20% chance for an empty passphrase
|
||||
for(int i = 0, j = r.nextInt(10) > 2 ? r.nextInt(20) : 0; i < j; i++) {
|
||||
passbuilder.append(chars.charAt(r.nextInt(chars.length())));
|
||||
}
|
||||
passphrase = passbuilder.toString();
|
||||
System.out.println("Passphrase is '" + passphrase + "'");
|
||||
}
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.ENCRYPT_COMMS, null));
|
||||
|
||||
parcel.mAddUserIds.add("twi");
|
||||
parcel.mAddUserIds.add("pink");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
staticRing = op.createSecretKeyRing(parcel, log, 0);
|
||||
|
||||
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;
|
||||
|
||||
// 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 = ring.getMasterKeyId();
|
||||
parcel.mFingerprint = ring.getFingerprint();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlgorithmChoice() {
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, new Random().nextInt(256)+255, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
|
||||
Assert.assertNull("creating ring with < 512 bytes keysize should fail", ring);
|
||||
}
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.elgamal, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
|
||||
Assert.assertNull("creating ring with ElGamal master key should fail", ring);
|
||||
}
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
12345, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
Assert.assertNull("creating ring with bad algorithm choice should fail", ring);
|
||||
}
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
Assert.assertNull("creating ring with non-certifying master key should fail", ring);
|
||||
}
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
Assert.assertNull("creating ring without user ids should fail", ring);
|
||||
}
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
Assert.assertNull("creating ring without subkeys should fail", ring);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
// this is a special case since the flags are in user id certificates rather than
|
||||
// subkey binding certificates
|
||||
public void testMasterFlags() throws Exception {
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, null));
|
||||
parcel.mAddUserIds.add("luna");
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
ring = op.createSecretKeyRing(parcel, log, 0);
|
||||
|
||||
Assert.assertEquals("the keyring should contain only the master key",
|
||||
1, ring.getAvailableSubkeys().size());
|
||||
Assert.assertEquals("first (master) key must have both flags",
|
||||
KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, ring.getPublicKey().getKeyUsage());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatedKey() throws Exception {
|
||||
|
||||
// an empty modification should change nothing. this also ensures the keyring
|
||||
// is constant through canonicalization.
|
||||
// applyModificationWithChecks(parcel, ring, onlyA, onlyB);
|
||||
|
||||
Assert.assertNotNull("key creation failed", ring);
|
||||
|
||||
Assert.assertNull("primary user id must be empty",
|
||||
ring.getPublicKey().getPrimaryUserId());
|
||||
|
||||
Assert.assertEquals("number of user ids must be two",
|
||||
2, ring.getPublicKey().getUnorderedUserIds().size());
|
||||
|
||||
Assert.assertEquals("number of subkeys must be three",
|
||||
3, ring.getAvailableSubkeys().size());
|
||||
|
||||
Assert.assertTrue("key ring should have been created in the last 120 seconds",
|
||||
ring.getPublicKey().getCreationTime().after(new Date(new Date().getTime()-1000*120)));
|
||||
|
||||
Assert.assertNull("key ring should not expire",
|
||||
ring.getPublicKey().getExpiryTime());
|
||||
|
||||
Iterator<UncachedPublicKey> it = ring.getPublicKeys();
|
||||
|
||||
Assert.assertEquals("first (master) key can certify",
|
||||
KeyFlags.CERTIFY_OTHER, it.next().getKeyUsage());
|
||||
|
||||
UncachedPublicKey signingKey = it.next();
|
||||
Assert.assertEquals("second key can sign",
|
||||
KeyFlags.SIGN_DATA, signingKey.getKeyUsage());
|
||||
ArrayList<WrappedSignature> sigs = signingKey.getSignatures().next().getEmbeddedSignatures();
|
||||
Assert.assertEquals("signing key signature should have one embedded signature",
|
||||
1, sigs.size());
|
||||
Assert.assertEquals("embedded signature should be of primary key binding type",
|
||||
PGPSignature.PRIMARYKEY_BINDING, sigs.get(0).getSignatureType());
|
||||
Assert.assertEquals("primary key binding signature issuer should be signing subkey",
|
||||
signingKey.getKeyId(), sigs.get(0).getKeyId());
|
||||
|
||||
Assert.assertEquals("third key can encrypt",
|
||||
KeyFlags.ENCRYPT_COMMS, it.next().getKeyUsage());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadKeyModification() throws Exception {
|
||||
|
||||
{
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
// off by one
|
||||
parcel.mMasterKeyId = ring.getMasterKeyId() -1;
|
||||
parcel.mFingerprint = ring.getFingerprint();
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
|
||||
Assert.assertNull("keyring modification with bad master key id should fail", modified);
|
||||
}
|
||||
|
||||
{
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
// off by one
|
||||
parcel.mMasterKeyId = null;
|
||||
parcel.mFingerprint = ring.getFingerprint();
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
|
||||
Assert.assertNull("keyring modification with null master key id should fail", modified);
|
||||
}
|
||||
|
||||
{
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mMasterKeyId = ring.getMasterKeyId();
|
||||
parcel.mFingerprint = ring.getFingerprint();
|
||||
// some byte, off by one
|
||||
parcel.mFingerprint[5] += 1;
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
|
||||
Assert.assertNull("keyring modification with bad fingerprint should fail", modified);
|
||||
}
|
||||
|
||||
{
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mMasterKeyId = ring.getMasterKeyId();
|
||||
parcel.mFingerprint = null;
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
|
||||
Assert.assertNull("keyring modification with null fingerprint should fail", modified);
|
||||
}
|
||||
|
||||
{
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, "bad passphrase", log, 0);
|
||||
|
||||
Assert.assertNull("keyring modification with bad passphrase should fail", modified);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubkeyAdd() throws Exception {
|
||||
|
||||
long expiry = new Date().getTime() / 1000 + 159;
|
||||
int flags = KeyFlags.SIGN_DATA;
|
||||
int bits = 1024 + new Random().nextInt(8);
|
||||
parcel.mAddSubKeys.add(new SubkeyAdd(algorithm.rsa, bits, flags, expiry));
|
||||
|
||||
UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
|
||||
|
||||
Assert.assertEquals("no extra packets in original", 0, onlyA.size());
|
||||
Assert.assertEquals("exactly two extra packets in modified", 2, onlyB.size());
|
||||
|
||||
Packet p;
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first new packet must be secret subkey", p instanceof SecretSubkeyPacket);
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(1).buf)).readPacket();
|
||||
Assert.assertTrue("second new packet must be signature", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("signature type must be subkey binding certificate",
|
||||
PGPSignature.SUBKEY_BINDING, ((SignaturePacket) p).getSignatureType());
|
||||
Assert.assertEquals("signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
// get new key from ring. it should be the last one (add a check to make sure?)
|
||||
UncachedPublicKey newKey = null;
|
||||
{
|
||||
Iterator<UncachedPublicKey> it = modified.getPublicKeys();
|
||||
while (it.hasNext()) {
|
||||
newKey = it.next();
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertNotNull("new key is not null", newKey);
|
||||
Assert.assertNotNull("added key must have an expiry date",
|
||||
newKey.getExpiryTime());
|
||||
Assert.assertEquals("added key must have expected expiry date",
|
||||
expiry, newKey.getExpiryTime().getTime()/1000);
|
||||
Assert.assertEquals("added key must have expected flags",
|
||||
flags, newKey.getKeyUsage());
|
||||
Assert.assertEquals("added key must have expected bitsize",
|
||||
bits, newKey.getBitStrength());
|
||||
|
||||
{ // bad keysize should fail
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SubkeyAdd(algorithm.rsa, 77, KeyFlags.SIGN_DATA, null));
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
|
||||
Assert.assertNull("creating a subkey with keysize < 512 should fail", modified);
|
||||
}
|
||||
|
||||
{ // a past expiry should fail
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.SIGN_DATA,
|
||||
new Date().getTime()/1000-10));
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
|
||||
Assert.assertNull("creating subkey with past expiry date should fail", modified);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubkeyModify() throws Exception {
|
||||
|
||||
long expiry = new Date().getTime()/1000 + 1024;
|
||||
long keyId;
|
||||
{
|
||||
Iterator<UncachedPublicKey> it = ring.getPublicKeys();
|
||||
it.next();
|
||||
keyId = it.next().getKeyId();
|
||||
}
|
||||
|
||||
UncachedKeyRing modified = ring;
|
||||
{
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, expiry));
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertEquals("one extra packet in original", 1, onlyA.size());
|
||||
Assert.assertEquals("one extra packet in modified", 1, onlyB.size());
|
||||
|
||||
Assert.assertEquals("old packet must be signature",
|
||||
PacketTags.SIGNATURE, onlyA.get(0).tag);
|
||||
|
||||
Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first new packet must be signature", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("signature type must be subkey binding certificate",
|
||||
PGPSignature.SUBKEY_BINDING, ((SignaturePacket) p).getSignatureType());
|
||||
Assert.assertEquals("signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
Assert.assertNotNull("modified key must have an expiry date",
|
||||
modified.getPublicKey(keyId).getExpiryTime());
|
||||
Assert.assertEquals("modified key must have expected expiry date",
|
||||
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
|
||||
Assert.assertEquals("modified key must have same flags as before",
|
||||
ring.getPublicKey(keyId).getKeyUsage(), modified.getPublicKey(keyId).getKeyUsage());
|
||||
}
|
||||
|
||||
{
|
||||
int flags = KeyFlags.SIGN_DATA | KeyFlags.ENCRYPT_COMMS;
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, flags, null));
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertEquals("old packet must be signature",
|
||||
PacketTags.SIGNATURE, onlyA.get(0).tag);
|
||||
|
||||
Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first new packet must be signature", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("signature type must be subkey binding certificate",
|
||||
PGPSignature.SUBKEY_BINDING, ((SignaturePacket) p).getSignatureType());
|
||||
Assert.assertEquals("signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
Assert.assertEquals("modified key must have expected flags",
|
||||
flags, modified.getPublicKey(keyId).getKeyUsage());
|
||||
Assert.assertNotNull("key must retain its expiry",
|
||||
modified.getPublicKey(keyId).getExpiryTime());
|
||||
Assert.assertEquals("key expiry must be unchanged",
|
||||
expiry, modified.getPublicKey(keyId).getExpiryTime().getTime()/1000);
|
||||
}
|
||||
|
||||
{ // a past expiry should fail
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, new Date().getTime()/1000-10));
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
|
||||
Assert.assertNull("setting subkey expiry to a past date should fail", modified);
|
||||
}
|
||||
|
||||
{ // modifying nonexistent keyring should fail
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(123, null, null));
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
|
||||
Assert.assertNull("modifying non-existent subkey should fail", modified);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubkeyRevoke() throws Exception {
|
||||
|
||||
long keyId;
|
||||
{
|
||||
Iterator<UncachedPublicKey> it = ring.getPublicKeys();
|
||||
it.next();
|
||||
keyId = it.next().getKeyId();
|
||||
}
|
||||
|
||||
int flags = ring.getPublicKey(keyId).getKeyUsage();
|
||||
|
||||
UncachedKeyRing modified;
|
||||
|
||||
{
|
||||
|
||||
parcel.reset();
|
||||
parcel.mRevokeSubKeys.add(123L);
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
|
||||
Assert.assertNull("revoking a nonexistent subkey should fail", otherModified);
|
||||
|
||||
}
|
||||
|
||||
{ // revoked second subkey
|
||||
|
||||
parcel.reset();
|
||||
parcel.mRevokeSubKeys.add(keyId);
|
||||
|
||||
modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
|
||||
|
||||
Assert.assertEquals("no extra packets in original", 0, onlyA.size());
|
||||
Assert.assertEquals("exactly one extra packet in modified", 1, onlyB.size());
|
||||
|
||||
Packet p;
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first new packet must be secret subkey", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("signature type must be subkey binding certificate",
|
||||
PGPSignature.SUBKEY_REVOCATION, ((SignaturePacket) p).getSignatureType());
|
||||
Assert.assertEquals("signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
Assert.assertTrue("subkey must actually be revoked",
|
||||
modified.getPublicKey(keyId).isRevoked());
|
||||
}
|
||||
|
||||
{ // re-add second subkey
|
||||
|
||||
parcel.reset();
|
||||
parcel.mChangeSubKeys.add(new SubkeyChange(keyId, null, null));
|
||||
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertEquals("exactly two outdated packets in original", 2, onlyA.size());
|
||||
Assert.assertEquals("exactly one extra packet in modified", 1, onlyB.size());
|
||||
|
||||
Packet p;
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first outdated packet must be signature", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("first outdated signature type must be subkey binding certification",
|
||||
PGPSignature.SUBKEY_BINDING, ((SignaturePacket) p).getSignatureType());
|
||||
Assert.assertEquals("first outdated signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(1).buf)).readPacket();
|
||||
Assert.assertTrue("second outdated packet must be signature", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("second outdated signature type must be subkey revocation",
|
||||
PGPSignature.SUBKEY_REVOCATION, ((SignaturePacket) p).getSignatureType());
|
||||
Assert.assertEquals("second outdated signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("new packet must be signature ", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("new signature type must be subkey binding certification",
|
||||
PGPSignature.SUBKEY_BINDING, ((SignaturePacket) p).getSignatureType());
|
||||
Assert.assertEquals("signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
Assert.assertFalse("subkey must no longer be revoked",
|
||||
modified.getPublicKey(keyId).isRevoked());
|
||||
Assert.assertEquals("subkey must have the same usage flags as before",
|
||||
flags, modified.getPublicKey(keyId).getKeyUsage());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserIdRevoke() throws Exception {
|
||||
|
||||
UncachedKeyRing modified;
|
||||
String uid = ring.getPublicKey().getUnorderedUserIds().get(1);
|
||||
|
||||
{ // revoke second user id
|
||||
|
||||
parcel.mRevokeUserIds.add(uid);
|
||||
|
||||
modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
|
||||
|
||||
Assert.assertEquals("no extra packets in original", 0, onlyA.size());
|
||||
Assert.assertEquals("exactly one extra packet in modified", 1, onlyB.size());
|
||||
|
||||
Packet p;
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first new packet must be secret subkey", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("signature type must be subkey binding certificate",
|
||||
PGPSignature.CERTIFICATION_REVOCATION, ((SignaturePacket) p).getSignatureType());
|
||||
Assert.assertEquals("signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
}
|
||||
|
||||
{ // re-add second user id
|
||||
|
||||
parcel.reset();
|
||||
parcel.mChangePrimaryUserId = uid;
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(modified.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
|
||||
Assert.assertNull("setting primary user id to a revoked user id should fail", otherModified);
|
||||
|
||||
}
|
||||
|
||||
{ // re-add second user id
|
||||
|
||||
parcel.reset();
|
||||
parcel.mAddUserIds.add(uid);
|
||||
|
||||
applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertEquals("exactly two outdated packets in original", 2, onlyA.size());
|
||||
Assert.assertEquals("exactly one extra packet in modified", 1, onlyB.size());
|
||||
|
||||
Packet p;
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first outdated packet must be signature", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("first outdated signature type must be positive certification",
|
||||
PGPSignature.POSITIVE_CERTIFICATION, ((SignaturePacket) p).getSignatureType());
|
||||
Assert.assertEquals("first outdated signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(1).buf)).readPacket();
|
||||
Assert.assertTrue("second outdated packet must be signature", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("second outdated signature type must be certificate revocation",
|
||||
PGPSignature.CERTIFICATION_REVOCATION, ((SignaturePacket) p).getSignatureType());
|
||||
Assert.assertEquals("second outdated signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("new packet must be signature ", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("new signature type must be positive certification",
|
||||
PGPSignature.POSITIVE_CERTIFICATION, ((SignaturePacket) p).getSignatureType());
|
||||
Assert.assertEquals("signature must have been created by master key",
|
||||
ring.getMasterKeyId(), ((SignaturePacket) p).getKeyID());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserIdAdd() throws Exception {
|
||||
|
||||
parcel.mAddUserIds.add("rainbow");
|
||||
|
||||
UncachedKeyRing modified = applyModificationWithChecks(parcel, ring, onlyA, onlyB);
|
||||
|
||||
Assert.assertTrue("keyring must contain added user id",
|
||||
modified.getPublicKey().getUnorderedUserIds().contains("rainbow"));
|
||||
|
||||
Assert.assertEquals("no extra packets in original", 0, onlyA.size());
|
||||
Assert.assertEquals("exactly two extra packets in modified", 2, onlyB.size());
|
||||
|
||||
Assert.assertTrue("keyring must contain added user id",
|
||||
modified.getPublicKey().getUnorderedUserIds().contains("rainbow"));
|
||||
|
||||
Packet p;
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first new packet must be user id", p instanceof UserIDPacket);
|
||||
Assert.assertEquals("user id packet must match added user id",
|
||||
"rainbow", ((UserIDPacket) p).getID());
|
||||
|
||||
p = new BCPGInputStream(new ByteArrayInputStream(onlyB.get(1).buf)).readPacket();
|
||||
Assert.assertTrue("second new packet must be signature", p instanceof SignaturePacket);
|
||||
Assert.assertEquals("signature type must be positive certification",
|
||||
PGPSignature.POSITIVE_CERTIFICATION, ((SignaturePacket) p).getSignatureType());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserIdPrimary() throws Exception {
|
||||
|
||||
UncachedKeyRing modified = ring;
|
||||
String uid = ring.getPublicKey().getUnorderedUserIds().get(1);
|
||||
|
||||
{ // first part, add new user id which is also primary
|
||||
parcel.mAddUserIds.add("jack");
|
||||
parcel.mChangePrimaryUserId = "jack";
|
||||
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertEquals("primary user id must be the one added",
|
||||
"jack", modified.getPublicKey().getPrimaryUserId());
|
||||
}
|
||||
|
||||
{ // second part, change primary to a different one
|
||||
parcel.reset();
|
||||
parcel.mChangePrimaryUserId = uid;
|
||||
|
||||
modified = applyModificationWithChecks(parcel, modified, onlyA, onlyB);
|
||||
|
||||
Assert.assertEquals("old keyring must have two outdated certificates", 2, onlyA.size());
|
||||
Assert.assertEquals("new keyring must have two new packets", 2, onlyB.size());
|
||||
|
||||
Assert.assertEquals("primary user id must be the one changed to",
|
||||
"pink", modified.getPublicKey().getPrimaryUserId());
|
||||
}
|
||||
|
||||
{ // third part, change primary to a non-existent one
|
||||
parcel.reset();
|
||||
//noinspection SpellCheckingInspection
|
||||
parcel.mChangePrimaryUserId = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
|
||||
Assert.assertNull("changing primary user id to a non-existent one should fail", modified);
|
||||
}
|
||||
|
||||
// check for revoked primary user id already done in revoke test
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static UncachedKeyRing applyModificationWithChecks(SaveKeyringParcel parcel,
|
||||
UncachedKeyRing ring,
|
||||
ArrayList<RawPacket> onlyA,
|
||||
ArrayList<RawPacket> onlyB) {
|
||||
return applyModificationWithChecks(parcel, ring, onlyA, onlyB, true, true);
|
||||
}
|
||||
|
||||
// applies a parcel modification while running some integrity checks
|
||||
private static UncachedKeyRing applyModificationWithChecks(SaveKeyringParcel parcel,
|
||||
UncachedKeyRing ring,
|
||||
ArrayList<RawPacket> onlyA,
|
||||
ArrayList<RawPacket> onlyB,
|
||||
boolean canonicalize,
|
||||
boolean constantCanonicalize) {
|
||||
try {
|
||||
|
||||
Assert.assertTrue("modified keyring must be secret", ring.isSecret());
|
||||
WrappedSecretKeyRing secretRing = new WrappedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing rawModified = op.modifySecretKeyRing(secretRing, parcel, passphrase, log, 0);
|
||||
Assert.assertNotNull("key modification failed", rawModified);
|
||||
|
||||
if (!canonicalize) {
|
||||
Assert.assertTrue("keyring must differ from original", KeyringTestingHelper.diffKeyrings(
|
||||
ring.getEncoded(), rawModified.getEncoded(), onlyA, onlyB));
|
||||
return rawModified;
|
||||
}
|
||||
|
||||
UncachedKeyRing modified = rawModified.canonicalize(log, 0);
|
||||
if (constantCanonicalize) {
|
||||
Assert.assertTrue("key must be constant through canonicalization",
|
||||
!KeyringTestingHelper.diffKeyrings(
|
||||
modified.getEncoded(), rawModified.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
}
|
||||
Assert.assertTrue("keyring must differ from original", KeyringTestingHelper.diffKeyrings(
|
||||
ring.getEncoded(), modified.getEncoded(), onlyA, onlyB));
|
||||
return modified;
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new AssertionFailedError("error during encoding!");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifySuccess() throws Exception {
|
||||
|
||||
UncachedKeyRing expectedKeyRing = KeyringBuilder.correctRing();
|
||||
UncachedKeyRing inputKeyRing = KeyringBuilder.ringWithExtraIncorrectSignature();
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing canonicalizedRing = inputKeyRing.canonicalize(log, 0);
|
||||
|
||||
if (canonicalizedRing == null) {
|
||||
throw new AssertionError("Canonicalization failed; messages: [" + log + "]");
|
||||
}
|
||||
|
||||
ArrayList onlyA = new ArrayList<RawPacket>();
|
||||
ArrayList onlyB = new ArrayList<RawPacket>();
|
||||
//noinspection unchecked
|
||||
Assert.assertTrue("keyrings differ", !KeyringTestingHelper.diffKeyrings(
|
||||
expectedKeyRing.getEncoded(), expectedKeyRing.getEncoded(), onlyA, onlyB));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Just testing my own test code. Should really be using a library for this.
|
||||
*/
|
||||
@Test
|
||||
public void testConcat() throws Exception {
|
||||
byte[] actual = TestDataUtil.concatAll(new byte[]{1}, new byte[]{2,-2}, new byte[]{5},new byte[]{3});
|
||||
byte[] expected = new byte[]{1,2,-2,5,3};
|
||||
Assert.assertEquals(java.util.Arrays.toString(expected), java.util.Arrays.toString(actual));
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,3 +1,20 @@
|
||||
/*
|
||||
* 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 tests;
|
||||
|
||||
import java.util.Collections;
|
||||
@ -9,9 +26,7 @@ import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.*;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.sufficientlysecure.keychain.testsupport.KeyringTestingHelper;
|
||||
import org.sufficientlysecure.keychain.testsupport.PgpVerifyTestingHelper;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
|
||||
@ -24,7 +39,7 @@ public class ProviderHelperKeyringTest {
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test
|
||||
public void testSavePublicKeyringRsa() throws Exception {
|
||||
Assert.assertTrue(new KeyringTestingHelper(Robolectric.application).addKeyring(prependResourcePath(Arrays.asList(
|
||||
"000001-006.public_key",
|
||||
@ -45,7 +60,7 @@ public class ProviderHelperKeyringTest {
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test
|
||||
public void testSavePublicKeyringDsa() throws Exception {
|
||||
Assert.assertTrue(new KeyringTestingHelper(Robolectric.application).addKeyring(prependResourcePath(Arrays.asList(
|
||||
"000016-006.public_key",
|
||||
@ -62,7 +77,7 @@ public class ProviderHelperKeyringTest {
|
||||
))));
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test
|
||||
public void testSavePublicKeyringDsa2() throws Exception {
|
||||
Assert.assertTrue(new KeyringTestingHelper(Robolectric.application).addKeyring(prependResourcePath(Arrays.asList(
|
||||
"000027-006.public_key",
|
@ -0,0 +1,106 @@
|
||||
package org.sufficientlysecure.keychain.tests;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.Before;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
import org.spongycastle.bcpg.BCPGInputStream;
|
||||
import org.spongycastle.bcpg.Packet;
|
||||
import org.spongycastle.bcpg.PacketTags;
|
||||
import org.spongycastle.bcpg.UserIDPacket;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
|
||||
public class UncachedKeyringTest {
|
||||
|
||||
static UncachedKeyRing staticRing;
|
||||
UncachedKeyRing ring;
|
||||
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
|
||||
ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>();
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpOnce() throws Exception {
|
||||
ShadowLog.stream = System.out;
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.ENCRYPT_COMMS, null));
|
||||
|
||||
parcel.mAddUserIds.add("twi");
|
||||
parcel.mAddUserIds.add("pink");
|
||||
// passphrase is tested in PgpKeyOperationTest, just use empty here
|
||||
parcel.mNewPassphrase = "";
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
staticRing = op.createSecretKeyRing(parcel, log, 0);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Test public void testGeneratedRingStructure() throws Exception {
|
||||
|
||||
Iterator<RawPacket> it = KeyringTestingHelper.parseKeyring(ring.getEncoded());
|
||||
|
||||
Assert.assertEquals("packet #1 should be secret key",
|
||||
PacketTags.SECRET_KEY, it.next().tag);
|
||||
|
||||
Assert.assertEquals("packet #2 should be secret key",
|
||||
PacketTags.USER_ID, it.next().tag);
|
||||
Assert.assertEquals("packet #3 should be secret key",
|
||||
PacketTags.SIGNATURE, it.next().tag);
|
||||
|
||||
Assert.assertEquals("packet #4 should be secret key",
|
||||
PacketTags.USER_ID, it.next().tag);
|
||||
Assert.assertEquals("packet #5 should be secret key",
|
||||
PacketTags.SIGNATURE, it.next().tag);
|
||||
|
||||
Assert.assertEquals("packet #6 should be secret key",
|
||||
PacketTags.SECRET_SUBKEY, it.next().tag);
|
||||
Assert.assertEquals("packet #7 should be secret key",
|
||||
PacketTags.SIGNATURE, it.next().tag);
|
||||
|
||||
Assert.assertEquals("packet #8 should be secret key",
|
||||
PacketTags.SECRET_SUBKEY, it.next().tag);
|
||||
Assert.assertEquals("packet #9 should be secret key",
|
||||
PacketTags.SIGNATURE, it.next().tag);
|
||||
|
||||
Assert.assertFalse("exactly 9 packets total", it.hasNext());
|
||||
|
||||
Assert.assertArrayEquals("created keyring should be constant through canonicalization",
|
||||
ring.getEncoded(), ring.canonicalize(log, 0).getEncoded());
|
||||
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
@ -21,13 +21,6 @@ dependencies {
|
||||
compile project(':extern:minidns')
|
||||
compile project(':extern:KeybaseLib:Lib')
|
||||
|
||||
|
||||
// Unit tests are run with Robolectric
|
||||
testCompile 'junit:junit:4.11'
|
||||
testCompile 'org.robolectric:robolectric:2.3'
|
||||
testCompile 'com.squareup:fest-android:1.0.8'
|
||||
testCompile 'com.google.android:android:4.1.1.4'
|
||||
// compile dependencies are automatically also included in testCompile
|
||||
}
|
||||
|
||||
android {
|
||||
@ -86,7 +79,7 @@ android {
|
||||
|
||||
// NOTE: This disables Lint!
|
||||
tasks.whenTaskAdded { task ->
|
||||
if (task.name.equals("lint")) {
|
||||
if (task.name.contains("lint")) {
|
||||
task.enabled = false
|
||||
}
|
||||
}
|
||||
|
@ -65,10 +65,8 @@ import java.security.NoSuchProviderException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* This class is the single place where ALL operations that actually modify a PGP public or secret
|
||||
@ -104,11 +102,12 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
/** Creates new secret key. */
|
||||
private PGPKeyPair createKey(int algorithmChoice, int keySize) throws PgpGeneralMsgIdException {
|
||||
private PGPKeyPair createKey(int algorithmChoice, int keySize, OperationLog log, int indent) {
|
||||
|
||||
try {
|
||||
if (keySize < 512) {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_key_size_minimum512bit);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_KEYSIZE_512, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
int algorithm;
|
||||
@ -143,7 +142,8 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_unknown_algorithm_choice);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,7 +157,9 @@ public class PgpKeyOperation {
|
||||
} catch(InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch(PGPException e) {
|
||||
throw new PgpGeneralMsgIdException(R.string.msg_mf_error_pgp, e);
|
||||
Log.e(Constants.TAG, "internal pgp error", e);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,20 +168,36 @@ public class PgpKeyOperation {
|
||||
|
||||
try {
|
||||
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_KEYID, indent);
|
||||
log.add(LogLevel.START, LogType.MSG_CR, indent);
|
||||
indent += 1;
|
||||
updateProgress(R.string.progress_building_key, 0, 100);
|
||||
|
||||
if (saveParcel.addSubKeys == null || saveParcel.addSubKeys.isEmpty()) {
|
||||
if (saveParcel.mAddSubKeys.isEmpty()) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_MASTER, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
SubkeyAdd add = saveParcel.addSubKeys.remove(0);
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize);
|
||||
if (saveParcel.mAddUserIds.isEmpty()) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_USER_ID, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
SubkeyAdd add = saveParcel.mAddSubKeys.remove(0);
|
||||
if ((add.mFlags & KeyFlags.CERTIFY_OTHER) != KeyFlags.CERTIFY_OTHER) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_NO_CERTIFY, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (add.mAlgorithm == Constants.choice.algorithm.elgamal) {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal);
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_MASTER_ELGAMAL, indent);
|
||||
return null;
|
||||
}
|
||||
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent);
|
||||
|
||||
// return null if this failed (an error will already have been logged by createKey)
|
||||
if (keyPair == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// define hashing and signing algos
|
||||
@ -195,17 +213,15 @@ public class PgpKeyOperation {
|
||||
PGPSecretKeyRing sKR = new PGPSecretKeyRing(
|
||||
masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator());
|
||||
|
||||
return internal(sKR, masterSecretKey, saveParcel, "", log, indent);
|
||||
return internal(sKR, masterSecretKey, add.mFlags, saveParcel, "", log, indent);
|
||||
|
||||
} catch (PGPException e) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
|
||||
Log.e(Constants.TAG, "pgp error encoding key", e);
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "io error encoding key", e);
|
||||
return null;
|
||||
} catch (PgpGeneralMsgIdException e) {
|
||||
Log.e(Constants.TAG, "pgp msg id error", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -257,11 +273,16 @@ public class PgpKeyOperation {
|
||||
return null;
|
||||
}
|
||||
|
||||
return internal(sKR, masterSecretKey, saveParcel, passphrase, log, indent);
|
||||
// read masterKeyFlags, and use the same as before.
|
||||
// since this is the master key, this contains at least CERTIFY_OTHER
|
||||
int masterKeyFlags = readKeyFlags(masterSecretKey.getPublicKey()) | KeyFlags.CERTIFY_OTHER;
|
||||
|
||||
return internal(sKR, masterSecretKey, masterKeyFlags, saveParcel, passphrase, log, indent);
|
||||
|
||||
}
|
||||
|
||||
private UncachedKeyRing internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
|
||||
int masterKeyFlags,
|
||||
SaveKeyringParcel saveParcel, String passphrase,
|
||||
OperationLog log, int indent) {
|
||||
|
||||
@ -289,7 +310,7 @@ public class PgpKeyOperation {
|
||||
PGPPublicKey modifiedPublicKey = masterPublicKey;
|
||||
|
||||
// 2a. Add certificates for new user ids
|
||||
for (String userId : saveParcel.addUserIds) {
|
||||
for (String userId : saveParcel.mAddUserIds) {
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_UID_ADD, indent);
|
||||
|
||||
// this operation supersedes all previous binding and revocation certificates,
|
||||
@ -298,9 +319,10 @@ public class PgpKeyOperation {
|
||||
Iterator<PGPSignature> it = modifiedPublicKey.getSignaturesForID(userId);
|
||||
if (it != null) {
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(it)) {
|
||||
// if it's not a self cert, never mind
|
||||
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
|
||||
continue;
|
||||
// foreign certificate?! error error error
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
|
||||
return null;
|
||||
}
|
||||
if (cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION
|
||||
|| cert.getSignatureType() == PGPSignature.NO_CERTIFICATION
|
||||
@ -314,27 +336,31 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
// if it's supposed to be primary, we can do that here as well
|
||||
boolean isPrimary = saveParcel.changePrimaryUserId != null
|
||||
&& userId.equals(saveParcel.changePrimaryUserId);
|
||||
boolean isPrimary = saveParcel.mChangePrimaryUserId != null
|
||||
&& userId.equals(saveParcel.mChangePrimaryUserId);
|
||||
// generate and add new certificate
|
||||
PGPSignature cert = generateUserIdSignature(masterPrivateKey,
|
||||
masterPublicKey, userId, isPrimary);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
|
||||
masterPublicKey, userId, isPrimary, masterKeyFlags);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
}
|
||||
|
||||
// 2b. Add revocations for revoked user ids
|
||||
for (String userId : saveParcel.revokeUserIds) {
|
||||
for (String userId : saveParcel.mRevokeUserIds) {
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent);
|
||||
// a duplicate revocatin will be removed during canonicalization, so no need to
|
||||
// a duplicate revocation will be removed during canonicalization, so no need to
|
||||
// take care of that here.
|
||||
PGPSignature cert = generateRevocationSignature(masterPrivateKey,
|
||||
masterPublicKey, userId);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
|
||||
}
|
||||
|
||||
// 3. If primary user id changed, generate new certificates for both old and new
|
||||
if (saveParcel.changePrimaryUserId != null) {
|
||||
if (saveParcel.mChangePrimaryUserId != null) {
|
||||
|
||||
// keep track if we actually changed one
|
||||
boolean ok = false;
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_UID_PRIMARY, indent);
|
||||
indent += 1;
|
||||
|
||||
// we work on the modifiedPublicKey here, to respect new or newly revoked uids
|
||||
// noinspection unchecked
|
||||
@ -343,10 +369,11 @@ public class PgpKeyOperation {
|
||||
PGPSignature currentCert = null;
|
||||
// noinspection unchecked
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(
|
||||
masterPublicKey.getSignaturesForID(userId))) {
|
||||
// if it's not a self cert, never mind
|
||||
modifiedPublicKey.getSignaturesForID(userId))) {
|
||||
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
|
||||
continue;
|
||||
// foreign certificate?! error error error
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_INTEGRITY, indent);
|
||||
return null;
|
||||
}
|
||||
// we know from canonicalization that if there is any revocation here, it
|
||||
// is valid and not superseded by a newer certification.
|
||||
@ -373,7 +400,7 @@ public class PgpKeyOperation {
|
||||
// we definitely should not update certifications of revoked keys, so just leave it.
|
||||
if (isRevoked) {
|
||||
// revoked user ids cannot be primary!
|
||||
if (userId.equals(saveParcel.changePrimaryUserId)) {
|
||||
if (userId.equals(saveParcel.mChangePrimaryUserId)) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_REVOKED_PRIMARY, indent);
|
||||
return null;
|
||||
}
|
||||
@ -383,14 +410,16 @@ public class PgpKeyOperation {
|
||||
// if this is~ the/a primary user id
|
||||
if (currentCert.hasSubpackets() && currentCert.getHashedSubPackets().isPrimaryUserID()) {
|
||||
// if it's the one we want, just leave it as is
|
||||
if (userId.equals(saveParcel.changePrimaryUserId)) {
|
||||
if (userId.equals(saveParcel.mChangePrimaryUserId)) {
|
||||
ok = true;
|
||||
continue;
|
||||
}
|
||||
// otherwise, generate new non-primary certification
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent);
|
||||
modifiedPublicKey = PGPPublicKey.removeCertification(
|
||||
modifiedPublicKey, userId, currentCert);
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
masterPrivateKey, masterPublicKey, userId, false);
|
||||
masterPrivateKey, masterPublicKey, userId, false, masterKeyFlags);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
continue;
|
||||
@ -399,20 +428,28 @@ public class PgpKeyOperation {
|
||||
// if we are here, this is not currently a primary user id
|
||||
|
||||
// if it should be
|
||||
if (userId.equals(saveParcel.changePrimaryUserId)) {
|
||||
if (userId.equals(saveParcel.mChangePrimaryUserId)) {
|
||||
// add shiny new primary user id certificate
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_PRIMARY_NEW, indent);
|
||||
modifiedPublicKey = PGPPublicKey.removeCertification(
|
||||
modifiedPublicKey, userId, currentCert);
|
||||
PGPSignature newCert = generateUserIdSignature(
|
||||
masterPrivateKey, masterPublicKey, userId, true);
|
||||
masterPrivateKey, masterPublicKey, userId, true, masterKeyFlags);
|
||||
modifiedPublicKey = PGPPublicKey.addCertification(
|
||||
modifiedPublicKey, userId, newCert);
|
||||
ok = true;
|
||||
}
|
||||
|
||||
// user id is not primary and is not supposed to be - nothing to do here.
|
||||
|
||||
}
|
||||
|
||||
indent -= 1;
|
||||
|
||||
if (!ok) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_NOEXIST_PRIMARY, indent);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the secret key ring
|
||||
@ -423,9 +460,16 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
// 4a. For each subkey change, generate new subkey binding certificate
|
||||
for (SaveKeyringParcel.SubkeyChange change : saveParcel.changeSubKeys) {
|
||||
for (SaveKeyringParcel.SubkeyChange change : saveParcel.mChangeSubKeys) {
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE,
|
||||
indent, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
|
||||
|
||||
// TODO allow changes in master key? this implies generating new user id certs...
|
||||
if (change.mKeyId == masterPublicKey.getKeyID()) {
|
||||
Log.e(Constants.TAG, "changing the master key not supported");
|
||||
return null;
|
||||
}
|
||||
|
||||
PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);
|
||||
if (sKey == null) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING,
|
||||
@ -434,22 +478,45 @@ public class PgpKeyOperation {
|
||||
}
|
||||
PGPPublicKey pKey = sKey.getPublicKey();
|
||||
|
||||
if (change.mExpiry != null && new Date(change.mExpiry).before(new Date())) {
|
||||
// expiry must not be in the past
|
||||
if (change.mExpiry != null && new Date(change.mExpiry*1000).before(new Date())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY,
|
||||
indent + 1, PgpKeyHelper.convertKeyIdToHex(change.mKeyId));
|
||||
return null;
|
||||
}
|
||||
|
||||
// generate and add new signature. we can be sloppy here and just leave the old one,
|
||||
// it will be removed during canonicalization
|
||||
// keep old flags, or replace with new ones
|
||||
int flags = change.mFlags == null ? readKeyFlags(pKey) : change.mFlags;
|
||||
long expiry;
|
||||
if (change.mExpiry == null) {
|
||||
long valid = pKey.getValidSeconds();
|
||||
expiry = valid == 0
|
||||
? 0
|
||||
: pKey.getCreationTime().getTime() / 1000 + pKey.getValidSeconds();
|
||||
} else {
|
||||
expiry = change.mExpiry;
|
||||
}
|
||||
|
||||
// drop all old signatures, they will be superseded by the new one
|
||||
//noinspection unchecked
|
||||
for (PGPSignature sig : new IterableIterator<PGPSignature>(pKey.getSignatures())) {
|
||||
// special case: if there is a revocation, don't use expiry from before
|
||||
if (change.mExpiry == null
|
||||
&& sig.getSignatureType() == PGPSignature.SUBKEY_REVOCATION) {
|
||||
expiry = 0;
|
||||
}
|
||||
pKey = PGPPublicKey.removeCertification(pKey, sig);
|
||||
}
|
||||
|
||||
// generate and add new signature
|
||||
PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
|
||||
sKey, pKey, change.mFlags, change.mExpiry, passphrase);
|
||||
sKey, pKey, flags, expiry, passphrase);
|
||||
pKey = PGPPublicKey.addCertification(pKey, sig);
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
|
||||
}
|
||||
|
||||
// 4b. For each subkey revocation, generate new subkey revocation certificate
|
||||
for (long revocation : saveParcel.revokeSubKeys) {
|
||||
for (long revocation : saveParcel.mRevokeSubKeys) {
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_REVOKE,
|
||||
indent, PgpKeyHelper.convertKeyIdToHex(revocation));
|
||||
PGPSecretKey sKey = sKR.getSecretKey(revocation);
|
||||
@ -468,52 +535,51 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
// 5. Generate and add new subkeys
|
||||
for (SaveKeyringParcel.SubkeyAdd add : saveParcel.addSubKeys) {
|
||||
try {
|
||||
for (SaveKeyringParcel.SubkeyAdd add : saveParcel.mAddSubKeys) {
|
||||
|
||||
if (add.mExpiry != null && new Date(add.mExpiry).before(new Date())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent);
|
||||
|
||||
// generate a new secret key (privkey only for now)
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize);
|
||||
|
||||
// add subkey binding signature (making this a sub rather than master key)
|
||||
PGPPublicKey pKey = keyPair.getPublicKey();
|
||||
PGPSignature cert = generateSubkeyBindingSignature(
|
||||
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
|
||||
add.mFlags, add.mExpiry);
|
||||
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
|
||||
|
||||
PGPSecretKey sKey; {
|
||||
// define hashing and signing algos
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.build().get(HashAlgorithmTags.SHA1);
|
||||
|
||||
// Build key encrypter and decrypter based on passphrase
|
||||
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
|
||||
sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey,
|
||||
sha1Calc, false, keyEncryptor);
|
||||
}
|
||||
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID,
|
||||
indent+1, PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()));
|
||||
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||
|
||||
} catch (PgpGeneralMsgIdException e) {
|
||||
if (add.mExpiry != null && new Date(add.mExpiry*1000).before(new Date())) {
|
||||
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1);
|
||||
return null;
|
||||
}
|
||||
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent);
|
||||
|
||||
// generate a new secret key (privkey only for now)
|
||||
PGPKeyPair keyPair = createKey(add.mAlgorithm, add.mKeysize, log, indent);
|
||||
if(keyPair == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// add subkey binding signature (making this a sub rather than master key)
|
||||
PGPPublicKey pKey = keyPair.getPublicKey();
|
||||
PGPSignature cert = generateSubkeyBindingSignature(
|
||||
masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
|
||||
add.mFlags, add.mExpiry == null ? 0 : add.mExpiry);
|
||||
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
|
||||
|
||||
PGPSecretKey sKey; {
|
||||
// define hashing and signing algos
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.build().get(HashAlgorithmTags.SHA1);
|
||||
|
||||
// Build key encrypter and decrypter based on passphrase
|
||||
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
|
||||
sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey,
|
||||
sha1Calc, false, keyEncryptor);
|
||||
}
|
||||
|
||||
log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID,
|
||||
indent+1, PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()));
|
||||
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||
|
||||
}
|
||||
|
||||
// 6. If requested, change passphrase
|
||||
if (saveParcel.newPassphrase != null) {
|
||||
if (saveParcel.mNewPassphrase != null) {
|
||||
log.add(LogLevel.INFO, LogType.MSG_MF_PASSPHRASE, indent);
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build()
|
||||
.get(HashAlgorithmTags.SHA1);
|
||||
@ -523,7 +589,7 @@ public class PgpKeyOperation {
|
||||
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
saveParcel.newPassphrase.toCharArray());
|
||||
saveParcel.mNewPassphrase.toCharArray());
|
||||
|
||||
sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew);
|
||||
}
|
||||
@ -546,7 +612,7 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
private static PGPSignature generateUserIdSignature(
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary)
|
||||
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary, int flags)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
pKey.getAlgorithm(), PGPUtil.SHA1)
|
||||
@ -558,6 +624,7 @@ public class PgpKeyOperation {
|
||||
subHashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
|
||||
subHashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
|
||||
subHashedPacketsGen.setPrimaryUserID(false, primary);
|
||||
subHashedPacketsGen.setKeyFlags(false, flags);
|
||||
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
|
||||
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
|
||||
return sGen.generateCertification(userId, pKey);
|
||||
@ -599,7 +666,7 @@ public class PgpKeyOperation {
|
||||
|
||||
private static PGPSignature generateSubkeyBindingSignature(
|
||||
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
|
||||
PGPSecretKey sKey, PGPPublicKey pKey, int flags, Long expiry, String passphrase)
|
||||
PGPSecretKey sKey, PGPPublicKey pKey, int flags, long expiry, String passphrase)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
@ -611,7 +678,7 @@ public class PgpKeyOperation {
|
||||
|
||||
private static PGPSignature generateSubkeyBindingSignature(
|
||||
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
|
||||
PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, Long expiry)
|
||||
PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry)
|
||||
throws IOException, PGPException, SignatureException {
|
||||
|
||||
// date for signing
|
||||
@ -640,17 +707,9 @@ public class PgpKeyOperation {
|
||||
hashedPacketsGen.setKeyFlags(false, flags);
|
||||
}
|
||||
|
||||
if (expiry != null) {
|
||||
Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
creationDate.setTime(pKey.getCreationTime());
|
||||
|
||||
// (Just making sure there's no programming error here, this MUST have been checked above!)
|
||||
if (new Date(expiry).before(todayDate)) {
|
||||
throw new RuntimeException("Bad subkey creation date, this is a bug!");
|
||||
}
|
||||
hashedPacketsGen.setKeyExpirationTime(false, expiry - creationDate.getTimeInMillis());
|
||||
} else {
|
||||
hashedPacketsGen.setKeyExpirationTime(false, 0);
|
||||
if (expiry > 0) {
|
||||
long creationTime = pKey.getCreationTime().getTime() / 1000;
|
||||
hashedPacketsGen.setKeyExpirationTime(false, expiry - creationTime);
|
||||
}
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
@ -665,4 +724,22 @@ public class PgpKeyOperation {
|
||||
|
||||
}
|
||||
|
||||
/** Returns all flags valid for this key.
|
||||
*
|
||||
* This method does not do any validity checks on the signature, so it should not be used on
|
||||
* a non-canonicalized key!
|
||||
*
|
||||
*/
|
||||
private static int readKeyFlags(PGPPublicKey key) {
|
||||
int flags = 0;
|
||||
//noinspection unchecked
|
||||
for(PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
|
||||
if (!sig.hasSubpackets()) {
|
||||
continue;
|
||||
}
|
||||
flags |= sig.getHashedSubPackets().getKeyFlags();
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -81,6 +81,10 @@ public class UncachedKeyRing {
|
||||
return new UncachedPublicKey(mRing.getPublicKey());
|
||||
}
|
||||
|
||||
public UncachedPublicKey getPublicKey(long keyId) {
|
||||
return new UncachedPublicKey(mRing.getPublicKey(keyId));
|
||||
}
|
||||
|
||||
public Iterator<UncachedPublicKey> getPublicKeys() {
|
||||
final Iterator<PGPPublicKey> it = mRing.getPublicKeys();
|
||||
return new Iterator<UncachedPublicKey>() {
|
||||
@ -558,7 +562,7 @@ public class UncachedKeyRing {
|
||||
// make sure the certificate checks out
|
||||
try {
|
||||
cert.init(masterKey);
|
||||
if (!cert.verifySignature(key)) {
|
||||
if (!cert.verifySignature(masterKey, key)) {
|
||||
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD, indent);
|
||||
badCerts += 1;
|
||||
continue;
|
||||
|
@ -9,6 +9,7 @@ import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.security.SignatureException;
|
||||
import java.util.ArrayList;
|
||||
@ -44,14 +45,19 @@ public class UncachedPublicKey {
|
||||
}
|
||||
|
||||
public Date getExpiryTime() {
|
||||
Date creationDate = getCreationTime();
|
||||
if (mPublicKey.getValidDays() == 0) {
|
||||
long seconds = mPublicKey.getValidSeconds();
|
||||
if (seconds > Integer.MAX_VALUE) {
|
||||
Log.e(Constants.TAG, "error, expiry time too large");
|
||||
return null;
|
||||
}
|
||||
if (seconds == 0) {
|
||||
// no expiry
|
||||
return null;
|
||||
}
|
||||
Date creationDate = getCreationTime();
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
calendar.setTime(creationDate);
|
||||
calendar.add(Calendar.DATE, mPublicKey.getValidDays());
|
||||
calendar.add(Calendar.SECOND, (int) seconds);
|
||||
|
||||
return calendar.getTime();
|
||||
}
|
||||
@ -77,26 +83,65 @@ public class UncachedPublicKey {
|
||||
return mPublicKey.getBitStrength();
|
||||
}
|
||||
|
||||
/** Returns the primary user id, as indicated by the public key's self certificates.
|
||||
*
|
||||
* This is an expensive operation, since potentially a lot of certificates (and revocations)
|
||||
* have to be checked, and even then the result is NOT guaranteed to be constant through a
|
||||
* canonicalization operation.
|
||||
*
|
||||
* Returns null if there is no primary user id (as indicated by certificates)
|
||||
*
|
||||
*/
|
||||
public String getPrimaryUserId() {
|
||||
String found = null;
|
||||
PGPSignature foundSig = null;
|
||||
for (String userId : new IterableIterator<String>(mPublicKey.getUserIDs())) {
|
||||
PGPSignature revocation = null;
|
||||
|
||||
for (PGPSignature sig : new IterableIterator<PGPSignature>(mPublicKey.getSignaturesForID(userId))) {
|
||||
if (sig.getHashedSubPackets() != null
|
||||
&& sig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.PRIMARY_USER_ID)) {
|
||||
try {
|
||||
try {
|
||||
|
||||
// if this is a revocation, this is not the user id
|
||||
if (sig.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION) {
|
||||
// make sure it's actually valid
|
||||
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME), mPublicKey);
|
||||
if (!sig.verifyCertification(userId, mPublicKey)) {
|
||||
continue;
|
||||
}
|
||||
if (found != null && found.equals(userId)) {
|
||||
found = null;
|
||||
}
|
||||
revocation = sig;
|
||||
// this revocation may still be overridden by a newer cert
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sig.getHashedSubPackets() != null && sig.getHashedSubPackets().isPrimaryUserID()) {
|
||||
if (foundSig != null && sig.getCreationTime().before(foundSig.getCreationTime())) {
|
||||
continue;
|
||||
}
|
||||
// ignore if there is a newer revocation for this user id
|
||||
if (revocation != null && sig.getCreationTime().before(revocation.getCreationTime())) {
|
||||
continue;
|
||||
}
|
||||
// make sure it's actually valid
|
||||
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME), mPublicKey);
|
||||
if (sig.verifyCertification(userId, mPublicKey)) {
|
||||
return userId;
|
||||
found = userId;
|
||||
foundSig = sig;
|
||||
// this one can't be relevant anymore at this point
|
||||
revocation = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// nothing bad happens, the key is just not considered the primary key id
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// nothing bad happens, the key is just not considered the primary key id
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return found;
|
||||
}
|
||||
|
||||
public ArrayList<String> getUnorderedUserIds() {
|
||||
@ -186,6 +231,21 @@ public class UncachedPublicKey {
|
||||
return mPublicKey;
|
||||
}
|
||||
|
||||
public Iterator<WrappedSignature> getSignatures() {
|
||||
final Iterator<PGPSignature> it = mPublicKey.getSignatures();
|
||||
return new Iterator<WrappedSignature>() {
|
||||
public void remove() {
|
||||
it.remove();
|
||||
}
|
||||
public WrappedSignature next() {
|
||||
return new WrappedSignature(it.next());
|
||||
}
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Iterator<WrappedSignature> getSignaturesForId(String userId) {
|
||||
final Iterator<PGPSignature> it = mPublicKey.getSignaturesForID(userId);
|
||||
return new Iterator<WrappedSignature>() {
|
||||
|
@ -101,8 +101,8 @@ public abstract class WrappedKeyRing extends KeyRing {
|
||||
|
||||
abstract public IterableIterator<WrappedPublicKey> publicKeyIterator();
|
||||
|
||||
public UncachedKeyRing getUncached() {
|
||||
return new UncachedKeyRing(getRing());
|
||||
public byte[] getEncoded() throws IOException {
|
||||
return getRing().getEncoded();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
/** OpenKeychain wrapper around PGPSignature objects.
|
||||
@ -55,6 +56,31 @@ public class WrappedSignature {
|
||||
return mSig.getCreationTime();
|
||||
}
|
||||
|
||||
public ArrayList<WrappedSignature> getEmbeddedSignatures() {
|
||||
ArrayList<WrappedSignature> sigs = new ArrayList<WrappedSignature>();
|
||||
if (!mSig.hasSubpackets()) {
|
||||
return sigs;
|
||||
}
|
||||
try {
|
||||
PGPSignatureList list;
|
||||
list = mSig.getHashedSubPackets().getEmbeddedSignatures();
|
||||
for(int i = 0; i < list.size(); i++) {
|
||||
sigs.add(new WrappedSignature(list.get(i)));
|
||||
}
|
||||
list = mSig.getUnhashedSubPackets().getEmbeddedSignatures();
|
||||
for(int i = 0; i < list.size(); i++) {
|
||||
sigs.add(new WrappedSignature(list.get(i)));
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
// no matter
|
||||
Log.e(Constants.TAG, "exception reading embedded signatures", e);
|
||||
} catch (IOException e) {
|
||||
// no matter
|
||||
Log.e(Constants.TAG, "exception reading embedded signatures", e);
|
||||
}
|
||||
return sigs;
|
||||
}
|
||||
|
||||
public byte[] getEncoded() throws IOException {
|
||||
return mSig.getEncoded();
|
||||
}
|
||||
|
@ -450,8 +450,7 @@ public class ProviderHelper {
|
||||
if (cert.verifySignature(masterKey, userId)) {
|
||||
item.trustedCerts.add(cert);
|
||||
log(LogLevel.INFO, LogType.MSG_IP_UID_CERT_GOOD,
|
||||
PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId()),
|
||||
trustedKey.getPrimaryUserId()
|
||||
PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId())
|
||||
);
|
||||
} else {
|
||||
log(LogLevel.WARN, LogType.MSG_IP_UID_CERT_BAD);
|
||||
@ -670,7 +669,7 @@ public class ProviderHelper {
|
||||
|
||||
// If there is an old keyring, merge it
|
||||
try {
|
||||
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached();
|
||||
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new public ring into the old one
|
||||
publicRing = oldPublicRing.merge(publicRing, mLog, mIndent);
|
||||
@ -706,7 +705,7 @@ public class ProviderHelper {
|
||||
// If there is a secret key, merge new data (if any) and save the key for later
|
||||
UncachedKeyRing secretRing;
|
||||
try {
|
||||
secretRing = getWrappedSecretKeyRing(publicRing.getMasterKeyId()).getUncached();
|
||||
secretRing = getWrappedSecretKeyRing(publicRing.getMasterKeyId()).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new public ring into secret one
|
||||
secretRing = secretRing.merge(publicRing, mLog, mIndent);
|
||||
@ -754,7 +753,7 @@ public class ProviderHelper {
|
||||
|
||||
// If there is an old secret key, merge it.
|
||||
try {
|
||||
UncachedKeyRing oldSecretRing = getWrappedSecretKeyRing(masterKeyId).getUncached();
|
||||
UncachedKeyRing oldSecretRing = getWrappedSecretKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new secret ring into old one
|
||||
secretRing = oldSecretRing.merge(secretRing, mLog, mIndent);
|
||||
@ -791,7 +790,7 @@ public class ProviderHelper {
|
||||
// Merge new data into public keyring as well, if there is any
|
||||
UncachedKeyRing publicRing;
|
||||
try {
|
||||
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached();
|
||||
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncachedKeyRing();
|
||||
|
||||
// Merge data from new public ring into secret one
|
||||
publicRing = oldPublicRing.merge(secretRing, mLog, mIndent);
|
||||
|
@ -350,9 +350,9 @@ public class KeychainIntentService extends IntentService
|
||||
providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 10, 95, 100));
|
||||
|
||||
// cache new passphrase
|
||||
if (saveParcel.newPassphrase != null) {
|
||||
if (saveParcel.mNewPassphrase != null) {
|
||||
PassphraseCacheService.addCachedPassphrase(this, ring.getMasterKeyId(),
|
||||
saveParcel.newPassphrase, ring.getPublicKey().getPrimaryUserId());
|
||||
saveParcel.mNewPassphrase, ring.getPublicKey().getPrimaryUserId());
|
||||
}
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
sendErrorToHandler(e);
|
||||
|
@ -9,6 +9,7 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@ -70,9 +71,6 @@ public class OperationResultParcel implements Parcelable {
|
||||
mParameters = parameters;
|
||||
mIndent = indent;
|
||||
}
|
||||
public LogEntryParcel(LogLevel level, LogType type, Object... parameters) {
|
||||
this(level, type, 0, parameters);
|
||||
}
|
||||
|
||||
public LogEntryParcel(Parcel source) {
|
||||
mLevel = LogLevel.values()[source.readInt()];
|
||||
@ -104,6 +102,15 @@ public class OperationResultParcel implements Parcelable {
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LogEntryParcel{" +
|
||||
"mLevel=" + mLevel +
|
||||
", mType=" + mType +
|
||||
", mParameters=" + Arrays.toString(mParameters) +
|
||||
", mIndent=" + mIndent +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
/** This is an enum of all possible log events.
|
||||
@ -237,7 +244,14 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_MG_FOUND_NEW (R.string.msg_mg_found_new),
|
||||
|
||||
// secret key create
|
||||
MSG_CR_ERROR_NO_MASTER (R.string.msg_mr),
|
||||
MSG_CR (R.string.msg_cr),
|
||||
MSG_CR_ERROR_NO_MASTER (R.string.msg_cr_error_no_master),
|
||||
MSG_CR_ERROR_NO_USER_ID (R.string.msg_cr_error_no_user_id),
|
||||
MSG_CR_ERROR_NO_CERTIFY (R.string.msg_cr_error_no_certify),
|
||||
MSG_CR_ERROR_KEYSIZE_512 (R.string.msg_cr_error_keysize_512),
|
||||
MSG_CR_ERROR_UNKNOWN_ALGO (R.string.msg_cr_error_unknown_algo),
|
||||
MSG_CR_ERROR_INTERNAL_PGP (R.string.msg_cr_error_internal_pgp),
|
||||
MSG_CR_ERROR_MASTER_ELGAMAL (R.string.msg_cr_error_master_elgamal),
|
||||
|
||||
// secret key modify
|
||||
MSG_MF (R.string.msg_mr),
|
||||
@ -245,10 +259,13 @@ public class OperationResultParcel implements Parcelable {
|
||||
MSG_MF_ERROR_FINGERPRINT (R.string.msg_mf_error_fingerprint),
|
||||
MSG_MF_ERROR_KEYID (R.string.msg_mf_error_keyid),
|
||||
MSG_MF_ERROR_INTEGRITY (R.string.msg_mf_error_integrity),
|
||||
MSG_MF_ERROR_NOEXIST_PRIMARY (R.string.msg_mf_error_noexist_primary),
|
||||
MSG_MF_ERROR_REVOKED_PRIMARY (R.string.msg_mf_error_revoked_primary),
|
||||
MSG_MF_ERROR_PGP (R.string.msg_mf_error_pgp),
|
||||
MSG_MF_ERROR_SIG (R.string.msg_mf_error_sig),
|
||||
MSG_MF_PASSPHRASE (R.string.msg_mf_passphrase),
|
||||
MSG_MF_PRIMARY_REPLACE_OLD (R.string.msg_mf_primary_replace_old),
|
||||
MSG_MF_PRIMARY_NEW (R.string.msg_mf_primary_new),
|
||||
MSG_MF_SUBKEY_CHANGE (R.string.msg_mf_subkey_change),
|
||||
MSG_MF_SUBKEY_MISSING (R.string.msg_mf_subkey_missing),
|
||||
MSG_MF_SUBKEY_NEW_ID (R.string.msg_mf_subkey_new_id),
|
||||
@ -314,6 +331,7 @@ public class OperationResultParcel implements Parcelable {
|
||||
}
|
||||
|
||||
public void add(LogLevel level, LogType type, int indent) {
|
||||
Log.d(Constants.TAG, type.toString());
|
||||
mParcels.add(new OperationResultParcel.LogEntryParcel(level, type, indent, (Object[]) null));
|
||||
}
|
||||
|
||||
|
@ -27,23 +27,19 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
// the key fingerprint, for safety. MUST be null for a new key.
|
||||
public byte[] mFingerprint;
|
||||
|
||||
public String newPassphrase;
|
||||
public String mNewPassphrase;
|
||||
|
||||
public ArrayList<String> addUserIds;
|
||||
public ArrayList<SubkeyAdd> addSubKeys;
|
||||
public ArrayList<String> mAddUserIds;
|
||||
public ArrayList<SubkeyAdd> mAddSubKeys;
|
||||
|
||||
public ArrayList<SubkeyChange> changeSubKeys;
|
||||
public String changePrimaryUserId;
|
||||
public ArrayList<SubkeyChange> mChangeSubKeys;
|
||||
public String mChangePrimaryUserId;
|
||||
|
||||
public ArrayList<String> revokeUserIds;
|
||||
public ArrayList<Long> revokeSubKeys;
|
||||
public ArrayList<String> mRevokeUserIds;
|
||||
public ArrayList<Long> mRevokeSubKeys;
|
||||
|
||||
public SaveKeyringParcel() {
|
||||
addUserIds = new ArrayList<String>();
|
||||
addSubKeys = new ArrayList<SubkeyAdd>();
|
||||
changeSubKeys = new ArrayList<SubkeyChange>();
|
||||
revokeUserIds = new ArrayList<String>();
|
||||
revokeSubKeys = new ArrayList<Long>();
|
||||
reset();
|
||||
}
|
||||
|
||||
public SaveKeyringParcel(long masterKeyId, byte[] fingerprint) {
|
||||
@ -52,6 +48,16 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
mFingerprint = fingerprint;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mNewPassphrase = null;
|
||||
mAddUserIds = new ArrayList<String>();
|
||||
mAddSubKeys = new ArrayList<SubkeyAdd>();
|
||||
mChangePrimaryUserId = null;
|
||||
mChangeSubKeys = new ArrayList<SubkeyChange>();
|
||||
mRevokeUserIds = new ArrayList<String>();
|
||||
mRevokeSubKeys = new ArrayList<Long>();
|
||||
}
|
||||
|
||||
// performance gain for using Parcelable here would probably be negligible,
|
||||
// use Serializable instead.
|
||||
public static class SubkeyAdd implements Serializable {
|
||||
@ -70,6 +76,7 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
public static class SubkeyChange implements Serializable {
|
||||
public long mKeyId;
|
||||
public Integer mFlags;
|
||||
// this is a long unix timestamp, in seconds (NOT MILLISECONDS!)
|
||||
public Long mExpiry;
|
||||
public SubkeyChange(long keyId, Integer flags, Long expiry) {
|
||||
mKeyId = keyId;
|
||||
@ -82,16 +89,16 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
|
||||
mFingerprint = source.createByteArray();
|
||||
|
||||
newPassphrase = source.readString();
|
||||
mNewPassphrase = source.readString();
|
||||
|
||||
addUserIds = source.createStringArrayList();
|
||||
addSubKeys = (ArrayList<SubkeyAdd>) source.readSerializable();
|
||||
mAddUserIds = source.createStringArrayList();
|
||||
mAddSubKeys = (ArrayList<SubkeyAdd>) source.readSerializable();
|
||||
|
||||
changeSubKeys = (ArrayList<SubkeyChange>) source.readSerializable();
|
||||
changePrimaryUserId = source.readString();
|
||||
mChangeSubKeys = (ArrayList<SubkeyChange>) source.readSerializable();
|
||||
mChangePrimaryUserId = source.readString();
|
||||
|
||||
revokeUserIds = source.createStringArrayList();
|
||||
revokeSubKeys = (ArrayList<Long>) source.readSerializable();
|
||||
mRevokeUserIds = source.createStringArrayList();
|
||||
mRevokeSubKeys = (ArrayList<Long>) source.readSerializable();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -102,16 +109,16 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
}
|
||||
destination.writeByteArray(mFingerprint);
|
||||
|
||||
destination.writeString(newPassphrase);
|
||||
destination.writeString(mNewPassphrase);
|
||||
|
||||
destination.writeStringList(addUserIds);
|
||||
destination.writeSerializable(addSubKeys);
|
||||
destination.writeStringList(mAddUserIds);
|
||||
destination.writeSerializable(mAddSubKeys);
|
||||
|
||||
destination.writeSerializable(changeSubKeys);
|
||||
destination.writeString(changePrimaryUserId);
|
||||
destination.writeSerializable(mChangeSubKeys);
|
||||
destination.writeString(mChangePrimaryUserId);
|
||||
|
||||
destination.writeStringList(revokeUserIds);
|
||||
destination.writeSerializable(revokeSubKeys);
|
||||
destination.writeStringList(mRevokeUserIds);
|
||||
destination.writeSerializable(mRevokeSubKeys);
|
||||
}
|
||||
|
||||
public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() {
|
||||
|
@ -1,56 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.pgp.NullProgressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Helper for tests of the Keyring import in ProviderHelper.
|
||||
*/
|
||||
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);
|
||||
|
||||
OperationResults.SaveKeyringResult saveKeyringResult = providerHelper.savePublicKeyRing(ring, new NullProgressable());
|
||||
|
||||
boolean saveSuccess = saveKeyringResult.success();
|
||||
|
||||
// Now re-retrieve the saved key. Should not throw an exception.
|
||||
providerHelper.getWrappedPublicKeyRing(masterKeyId);
|
||||
|
||||
// A different ID should still fail
|
||||
retrieveKeyAndExpectNotFound(providerHelper, masterKeyId - 1);
|
||||
|
||||
return saveSuccess;
|
||||
}
|
||||
|
||||
|
||||
private void retrieveKeyAndExpectNotFound(ProviderHelper providerHelper, long masterKeyId) {
|
||||
try {
|
||||
providerHelper.getWrappedPublicKeyRing(masterKeyId);
|
||||
throw new AssertionError("Was expecting the previous call to fail!");
|
||||
} catch (ProviderHelper.NotFoundException expectedException) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
|
||||
/**
|
||||
* Created by art on 21/06/14.
|
||||
*/
|
||||
class ProviderHelperStub extends ProviderHelper {
|
||||
public ProviderHelperStub(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri id) throws NotFoundException {
|
||||
byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob"));
|
||||
return new WrappedPublicKeyRing(data, false, 0);
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
private static void appendToOutput(InputStream input, ByteArrayOutputStream 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);
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Test support classes.
|
||||
* This is only in main code because of gradle-Android Studio-robolectric issues. Having
|
||||
* classes in main code means IDE autocomplete, class detection, etc., all works.
|
||||
* TODO Move into test package when possible
|
||||
*/
|
||||
package org.sufficientlysecure.keychain.testsupport;
|
@ -556,7 +556,7 @@ public class EditKeyActivityOld extends ActionBarActivity implements EditorListe
|
||||
saveParams.deletedKeys = mKeysView.getDeletedKeys();
|
||||
saveParams.keysExpiryDates = getKeysExpiryDates(mKeysView);
|
||||
saveParams.keysUsages = getKeysUsages(mKeysView);
|
||||
saveParams.newPassphrase = mNewPassphrase;
|
||||
saveParams.mNewPassphrase = mNewPassphrase;
|
||||
saveParams.oldPassphrase = mCurrentPassphrase;
|
||||
saveParams.newKeys = toPrimitiveArray(mKeysView.getNewKeysArray());
|
||||
saveParams.keys = getKeys(mKeysView);
|
||||
|
@ -228,7 +228,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
});
|
||||
|
||||
mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.addSubKeys);
|
||||
mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys);
|
||||
mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter);
|
||||
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
@ -298,7 +298,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
Bundle data = message.getData();
|
||||
|
||||
// cache new returned passphrase!
|
||||
mSaveKeyringParcel.newPassphrase = data
|
||||
mSaveKeyringParcel.mNewPassphrase = data
|
||||
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
|
||||
}
|
||||
}
|
||||
@ -320,19 +320,19 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
switch (message.what) {
|
||||
case EditUserIdDialogFragment.MESSAGE_CHANGE_PRIMARY_USER_ID:
|
||||
// toggle
|
||||
if (mSaveKeyringParcel.changePrimaryUserId != null
|
||||
&& mSaveKeyringParcel.changePrimaryUserId.equals(userId)) {
|
||||
mSaveKeyringParcel.changePrimaryUserId = null;
|
||||
if (mSaveKeyringParcel.mChangePrimaryUserId != null
|
||||
&& mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) {
|
||||
mSaveKeyringParcel.mChangePrimaryUserId = null;
|
||||
} else {
|
||||
mSaveKeyringParcel.changePrimaryUserId = userId;
|
||||
mSaveKeyringParcel.mChangePrimaryUserId = userId;
|
||||
}
|
||||
break;
|
||||
case EditUserIdDialogFragment.MESSAGE_REVOKE:
|
||||
// toggle
|
||||
if (mSaveKeyringParcel.revokeUserIds.contains(userId)) {
|
||||
mSaveKeyringParcel.revokeUserIds.remove(userId);
|
||||
if (mSaveKeyringParcel.mRevokeUserIds.contains(userId)) {
|
||||
mSaveKeyringParcel.mRevokeUserIds.remove(userId);
|
||||
} else {
|
||||
mSaveKeyringParcel.revokeUserIds.add(userId);
|
||||
mSaveKeyringParcel.mRevokeUserIds.add(userId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -363,10 +363,10 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
break;
|
||||
case EditSubkeyDialogFragment.MESSAGE_REVOKE:
|
||||
// toggle
|
||||
if (mSaveKeyringParcel.revokeSubKeys.contains(keyId)) {
|
||||
mSaveKeyringParcel.revokeSubKeys.remove(keyId);
|
||||
if (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId)) {
|
||||
mSaveKeyringParcel.mRevokeSubKeys.remove(keyId);
|
||||
} else {
|
||||
mSaveKeyringParcel.revokeSubKeys.add(keyId);
|
||||
mSaveKeyringParcel.mRevokeSubKeys.add(keyId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -451,9 +451,9 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
|
||||
private void save(String passphrase) {
|
||||
Log.d(Constants.TAG, "add userids to parcel: " + mUserIdsAddedAdapter.getDataAsStringList());
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel.newPassphrase: " + mSaveKeyringParcel.newPassphrase);
|
||||
Log.d(Constants.TAG, "mSaveKeyringParcel.mNewPassphrase: " + mSaveKeyringParcel.mNewPassphrase);
|
||||
|
||||
mSaveKeyringParcel.addUserIds = mUserIdsAddedAdapter.getDataAsStringList();
|
||||
mSaveKeyringParcel.mAddUserIds = mUserIdsAddedAdapter.getDataAsStringList();
|
||||
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
@ -509,4 +509,4 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,8 +32,6 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Constants.choice.algorithm;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
@ -43,7 +41,6 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class KeyListActivity extends DrawerActivity {
|
||||
|
||||
@ -153,10 +150,10 @@ public class KeyListActivity extends DrawerActivity {
|
||||
Bundle data = new Bundle();
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.addSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.addSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
|
||||
parcel.addUserIds.add("swagerinho");
|
||||
parcel.newPassphrase = "swag";
|
||||
parcel.mAddSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddSubKeys.add(new SubkeyAdd(algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
|
||||
parcel.mAddUserIds.add("swagerinho");
|
||||
parcel.mNewPassphrase = "swag";
|
||||
|
||||
// get selected key entries
|
||||
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, parcel);
|
||||
|
@ -328,7 +328,7 @@ public class WizardActivity extends ActionBarActivity {
|
||||
&& isEditTextNotEmpty(this, passphraseEdit)) {
|
||||
|
||||
// SaveKeyringParcel newKey = new SaveKeyringParcel();
|
||||
// newKey.addUserIds.add(nameEdit.getText().toString() + " <"
|
||||
// newKey.mAddUserIds.add(nameEdit.getText().toString() + " <"
|
||||
// + emailEdit.getText().toString() + ">");
|
||||
|
||||
|
||||
|
@ -143,7 +143,7 @@ public class SubkeysAdapter extends CursorAdapter {
|
||||
|
||||
// for edit key
|
||||
if (mSaveKeyringParcel != null) {
|
||||
boolean revokeThisSubkey = (mSaveKeyringParcel.revokeSubKeys.contains(keyId));
|
||||
boolean revokeThisSubkey = (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId));
|
||||
|
||||
if (revokeThisSubkey) {
|
||||
if (!isRevoked) {
|
||||
|
@ -131,10 +131,10 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
|
||||
|
||||
// for edit key
|
||||
if (mSaveKeyringParcel != null) {
|
||||
boolean changeAnyPrimaryUserId = (mSaveKeyringParcel.changePrimaryUserId != null);
|
||||
boolean changeThisPrimaryUserId = (mSaveKeyringParcel.changePrimaryUserId != null
|
||||
&& mSaveKeyringParcel.changePrimaryUserId.equals(userId));
|
||||
boolean revokeThisUserId = (mSaveKeyringParcel.revokeUserIds.contains(userId));
|
||||
boolean changeAnyPrimaryUserId = (mSaveKeyringParcel.mChangePrimaryUserId != null);
|
||||
boolean changeThisPrimaryUserId = (mSaveKeyringParcel.mChangePrimaryUserId != null
|
||||
&& mSaveKeyringParcel.mChangePrimaryUserId.equals(userId));
|
||||
boolean revokeThisUserId = (mSaveKeyringParcel.mRevokeUserIds.contains(userId));
|
||||
|
||||
// only if primary user id will be changed
|
||||
// (this is not triggered if the user id is currently the primary one)
|
||||
|
@ -39,15 +39,21 @@ public class ProgressScaler implements Progressable {
|
||||
* Set progress of ProgressDialog by sending message to handler on UI thread
|
||||
*/
|
||||
public void setProgress(String message, int progress, int max) {
|
||||
mWrapped.setProgress(message, mFrom + progress * (mTo - mFrom) / max, mMax);
|
||||
if (mWrapped != null) {
|
||||
mWrapped.setProgress(message, mFrom + progress * (mTo - mFrom) / max, mMax);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(int resourceId, int progress, int max) {
|
||||
mWrapped.setProgress(resourceId, progress, mMax);
|
||||
if (mWrapped != null) {
|
||||
mWrapped.setProgress(resourceId, progress, mMax);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(int progress, int max) {
|
||||
mWrapped.setProgress(progress, max);
|
||||
if (mWrapped != null) {
|
||||
mWrapped.setProgress(progress, max);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -510,7 +510,7 @@
|
||||
<string name="cert_casual">casual</string>
|
||||
<string name="cert_positive">positive</string>
|
||||
<string name="cert_revoke">revoked</string>
|
||||
<string name="cert_verify_ok">ok</string>
|
||||
<string name="cert_verify_ok">OK</string>
|
||||
<string name="cert_verify_failed">failed!</string>
|
||||
<string name="cert_verify_error">error!</string>
|
||||
<string name="cert_verify_unavailable">key unavailable</string>
|
||||
@ -555,7 +555,7 @@
|
||||
<string name="msg_ip_reinsert_secret">Re-inserting secret key</string>
|
||||
<string name="msg_ip_uid_cert_bad">Encountered bad certificate!</string>
|
||||
<string name="msg_ip_uid_cert_error">Error processing certificate!</string>
|
||||
<string name="msg_ip_uid_cert_good">User id is certified by %1$s (%2$s)</string>
|
||||
<string name="msg_ip_uid_cert_good">User id is certified by %1$s</string>
|
||||
<plurals name="msg_ip_uid_certs_unknown">
|
||||
<item quantity="one">Ignoring one certificate issued by an unknown public key</item>
|
||||
<item quantity="other">Ignoring %s certificates issued by unknown public keys</item>
|
||||
@ -637,16 +637,29 @@
|
||||
<string name="msg_mg_new_subkey">Adding new subkey %s</string>
|
||||
<string name="msg_mg_found_new">Found %s new certificates in keyring</string>
|
||||
|
||||
<!-- createSecretKeyRing -->
|
||||
<string name="msg_cr">Generating new master key</string>
|
||||
<string name="msg_cr_error_no_master">No master key options specified!</string>
|
||||
<string name="msg_cr_error_no_user_id">Keyrings must be created with at least one user id!</string>
|
||||
<string name="msg_cr_error_no_certify">Master key must have certify flag!</string>
|
||||
<string name="msg_cr_error_keysize_512">Key size must be greater or equal 512!</string>
|
||||
<string name="msg_cr_error_internal_pgp">Internal PGP error!</string>
|
||||
<string name="msg_cr_error_unknown_algo">Bad algorithm choice!</string>
|
||||
<string name="msg_cr_error_master_elgamal">Master key must not be of type ElGamal!</string>
|
||||
|
||||
<!-- modifySecretKeyRing -->
|
||||
<string name="msg_mr">Modifying keyring %s</string>
|
||||
<string name="msg_mf_error_encode">Encoding exception!</string>
|
||||
<string name="msg_mf_error_fingerprint">Actual key fingerprint does not match the expected one!</string>
|
||||
<string name="msg_mf_error_keyid">No key ID. This is an internal error, please file a bug report!</string>
|
||||
<string name="msg_mf_error_integrity">Internal error, integrity check failed!</string>
|
||||
<string name="msg_mf_error_noexist_primary">Bad primary user id specified!</string>
|
||||
<string name="msg_mf_error_revoked_primary">Revoked user ids cannot be primary!</string>
|
||||
<string name="msg_mf_error_pgp">PGP internal exception!</string>
|
||||
<string name="msg_mf_error_sig">Signature exception!</string>
|
||||
<string name="msg_mf_passphrase">Changing passphrase</string>
|
||||
<string name="msg_mf_primary_replace_old">Replacing certificate of previous primary user id</string>
|
||||
<string name="msg_mf_primary_new">Generating new certificate for new primary user id</string>
|
||||
<string name="msg_mf_subkey_change">Modifying subkey %s</string>
|
||||
<string name="msg_mf_subkey_missing">Tried to operate on missing subkey %s!</string>
|
||||
<string name="msg_mf_subkey_new">Generating new %1$s bit %2$s subkey</string>
|
||||
|
10
build.gradle
10
build.gradle
@ -1,12 +1,16 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
// need this for com.novoda:gradle-android-test-plugin:0.9.9-SNAPSHOT below (0.9.3 in repos doesn't work!)
|
||||
// run ./install-custom-gradle-test-plugin.sh to pull the thing into the local repository
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
|
||||
classpath 'com.android.tools.build:gradle:0.12.0'
|
||||
classpath 'org.robolectric:robolectric-gradle-plugin:0.11.0'
|
||||
classpath 'com.novoda:gradle-android-test-plugin:0.9.9-SNAPSHOT'
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,3 +23,9 @@ allprojects {
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '1.12'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
tasks.withType(Test) {
|
||||
maxParallelForks = 1
|
||||
}
|
||||
}
|
||||
|
15
install-custom-gradle-test-plugin.sh
Executable file
15
install-custom-gradle-test-plugin.sh
Executable file
@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
mkdir temp
|
||||
cd temp
|
||||
|
||||
git clone https://github.com/nenick/gradle-android-test-plugin.git
|
||||
cd gradle-android-test-plugin
|
||||
|
||||
echo "rootProject.name = 'gradle-android-test-plugin-parent'" > settings.gradle
|
||||
echo "include ':gradle-android-test-plugin'" >> settings.gradle
|
||||
|
||||
./gradlew :gradle-android-test-plugin:install
|
||||
|
||||
cd ..
|
||||
cd ..
|
@ -1,4 +1,5 @@
|
||||
include ':OpenKeychain'
|
||||
include ':OpenKeychain-Test'
|
||||
include ':extern:openpgp-api-lib'
|
||||
include ':extern:openkeychain-api-lib'
|
||||
include ':extern:html-textview'
|
||||
|
Loading…
Reference in New Issue
Block a user