mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-27 11:12:15 -05:00
Merge branch 'master' into improve-file-more
Conflicts: .gitmodules OpenKeychain/build.gradle OpenKeychain/src/main/AndroidManifest.xml OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java
This commit is contained in:
commit
eae7c711a3
3
.gitignore
vendored
3
.gitignore
vendored
@ -14,6 +14,9 @@ ant.properties
|
||||
.gradle
|
||||
build
|
||||
gradle.properties
|
||||
# this is in here because the prepare-tests thing modifies it, and we DON'T
|
||||
# want this to be commited. use git add -f to work on this file.
|
||||
settings.gradle
|
||||
|
||||
#Maven
|
||||
target
|
||||
|
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -7,9 +7,6 @@
|
||||
[submodule "extern/zxing-qr-code"]
|
||||
path = extern/zxing-qr-code
|
||||
url = https://github.com/open-keychain/zxing-qr-code.git
|
||||
[submodule "extern/AppMsg"]
|
||||
path = extern/AppMsg
|
||||
url = https://github.com/open-keychain/Android-AppMsg.git
|
||||
[submodule "extern/spongycastle"]
|
||||
path = extern/spongycastle
|
||||
url = https://github.com/open-keychain/spongycastle.git
|
||||
@ -31,9 +28,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
|
||||
url = https://github.com/singpolyma/OpenPGP-Haskell.git
|
||||
[submodule "extern/TokenAutoComplete"]
|
||||
path = extern/TokenAutoComplete
|
||||
url = https://github.com/open-keychain/TokenAutoComplete
|
||||
|
@ -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
|
||||
- ./prepare-tests.sh
|
||||
install: echo "Installation done"
|
||||
script: gradle assemble -S -q
|
||||
script:
|
||||
- gradle assemble -S -q
|
||||
- gradle --info OpenKeychain-Test:testDebug
|
||||
|
||||
|
118
OpenKeychain-Test/build.gradle
Normal file
118
OpenKeychain-Test/build.gradle
Normal file
@ -0,0 +1,118 @@
|
||||
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.novoda:gradle-android-test-plugin:0.9.9-SNAPSHOT'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'android-test'
|
||||
apply plugin: 'jacoco'
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
jacoco {
|
||||
toolVersion = "0.7.0.201403182114"
|
||||
}
|
||||
|
||||
coverageSourceDirs = [
|
||||
'../OpenKeychain/src/main/java',
|
||||
'../OpenKeychain/src/gen',
|
||||
'../OpenKeychain/build/source/apt/debug',
|
||||
'../OpenKeychain/build/source/generated/buildConfig/debug',
|
||||
'../OpenKeychain/build/source/generated/r/debug'
|
||||
]
|
||||
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled = true
|
||||
html.destination "${buildDir}/jacocoHtml"
|
||||
}
|
||||
// class R is used, but usage will not be covered, so ignore this class from report
|
||||
classDirectories = fileTree(dir: '../OpenKeychain/build/intermediates/classes/debug/org/sufficientlysecure/keychain', exclude: 'R*.class')
|
||||
additionalSourceDirs = files(coverageSourceDirs)
|
||||
executionData = files('build/jacoco/testDebug.exec')
|
||||
}
|
||||
|
||||
// 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,363 @@
|
||||
/*
|
||||
* 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.pgp.exception.PgpGeneralException;
|
||||
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.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/** Helper methods for keyring tests. */
|
||||
public class KeyringTestingHelper {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public KeyringTestingHelper(Context robolectricContext) {
|
||||
this.context = robolectricContext;
|
||||
}
|
||||
|
||||
public boolean addKeyring(Collection<String> blobFiles) throws Exception {
|
||||
|
||||
ProviderHelper providerHelper = new ProviderHelper(context);
|
||||
|
||||
byte[] data = TestDataUtil.readAllFully(blobFiles);
|
||||
UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data);
|
||||
long masterKeyId = ring.getMasterKeyId();
|
||||
|
||||
// Should throw an exception; key is not yet saved
|
||||
retrieveKeyAndExpectNotFound(providerHelper, masterKeyId);
|
||||
|
||||
OperationResults.SaveKeyringResult saveKeyringResult = providerHelper.savePublicKeyRing(ring, new NullProgressable());
|
||||
|
||||
boolean saveSuccess = saveKeyringResult.success();
|
||||
|
||||
// Now re-retrieve the saved key. Should not throw an exception.
|
||||
providerHelper.getCanonicalizedPublicKeyRing(masterKeyId);
|
||||
|
||||
// A different ID should still fail
|
||||
retrieveKeyAndExpectNotFound(providerHelper, masterKeyId - 1);
|
||||
|
||||
return saveSuccess;
|
||||
}
|
||||
|
||||
public static UncachedKeyRing removePacket(UncachedKeyRing ring, int position)
|
||||
throws IOException, PgpGeneralException {
|
||||
return UncachedKeyRing.decodeFromData(removePacket(ring.getEncoded(), position));
|
||||
}
|
||||
|
||||
public static byte[] removePacket(byte[] ring, int position) throws IOException {
|
||||
Iterator<RawPacket> it = parseKeyring(ring);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(ring.length);
|
||||
|
||||
int i = 0;
|
||||
while(it.hasNext()) {
|
||||
// at the right position, skip the packet
|
||||
if(i++ == position) {
|
||||
it.next();
|
||||
continue;
|
||||
}
|
||||
// write the old one
|
||||
out.write(it.next().buf);
|
||||
}
|
||||
|
||||
if (i <= position) {
|
||||
throw new IndexOutOfBoundsException("injection index did not not occur in stream!");
|
||||
}
|
||||
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
public static UncachedKeyRing injectPacket(UncachedKeyRing ring, byte[] inject, int position)
|
||||
throws IOException, PgpGeneralException {
|
||||
return UncachedKeyRing.decodeFromData(injectPacket(ring.getEncoded(), inject, position));
|
||||
}
|
||||
|
||||
public static byte[] injectPacket(byte[] ring, byte[] inject, int position) throws IOException {
|
||||
|
||||
Iterator<RawPacket> it = parseKeyring(ring);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(ring.length + inject.length);
|
||||
|
||||
int i = 0;
|
||||
while(it.hasNext()) {
|
||||
// at the right position, inject the new packet
|
||||
if(i++ == position) {
|
||||
out.write(inject);
|
||||
}
|
||||
// write the old one
|
||||
out.write(it.next().buf);
|
||||
}
|
||||
|
||||
if (i <= position) {
|
||||
throw new IndexOutOfBoundsException("injection index did not not occur in stream!");
|
||||
}
|
||||
|
||||
return out.toByteArray();
|
||||
|
||||
}
|
||||
|
||||
/** This class contains a single pgp packet, together with information about its position
|
||||
* in the keyring and its packet tag.
|
||||
*/
|
||||
public static class RawPacket {
|
||||
public int position;
|
||||
|
||||
// packet tag for convenience, this can also be read from the header
|
||||
public int tag;
|
||||
|
||||
public int headerLength, length;
|
||||
// this buf includes the header, so its length is headerLength + length!
|
||||
public byte[] buf;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof RawPacket && Arrays.areEqual(this.buf, ((RawPacket) other).buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(buf);
|
||||
}
|
||||
}
|
||||
|
||||
/** A comparator which compares RawPackets by their position */
|
||||
public static final Comparator<RawPacket> packetOrder = new Comparator<RawPacket>() {
|
||||
public int compare(RawPacket left, RawPacket right) {
|
||||
return Integer.compare(left.position, right.position);
|
||||
}
|
||||
};
|
||||
|
||||
/** Diff two keyrings, returning packets only present in one keyring in its associated List.
|
||||
*
|
||||
* Packets in the returned lists are annotated and ordered by their original order of appearance
|
||||
* in their origin keyrings.
|
||||
*
|
||||
* @return true if keyrings differ in at least one packet
|
||||
*/
|
||||
public static boolean diffKeyrings(byte[] ringA, byte[] ringB,
|
||||
List<RawPacket> onlyA, List<RawPacket> onlyB)
|
||||
throws IOException {
|
||||
Iterator<RawPacket> streamA = parseKeyring(ringA);
|
||||
Iterator<RawPacket> streamB = parseKeyring(ringB);
|
||||
|
||||
HashSet<RawPacket> a = new HashSet<RawPacket>(), b = new HashSet<RawPacket>();
|
||||
|
||||
RawPacket p;
|
||||
int pos = 0;
|
||||
while(true) {
|
||||
p = streamA.next();
|
||||
if (p == null) {
|
||||
break;
|
||||
}
|
||||
p.position = pos++;
|
||||
a.add(p);
|
||||
}
|
||||
pos = 0;
|
||||
while(true) {
|
||||
p = streamB.next();
|
||||
if (p == null) {
|
||||
break;
|
||||
}
|
||||
p.position = pos++;
|
||||
b.add(p);
|
||||
}
|
||||
|
||||
onlyA.clear();
|
||||
onlyB.clear();
|
||||
|
||||
onlyA.addAll(a);
|
||||
onlyA.removeAll(b);
|
||||
onlyB.addAll(b);
|
||||
onlyB.removeAll(a);
|
||||
|
||||
Collections.sort(onlyA, packetOrder);
|
||||
Collections.sort(onlyB, packetOrder);
|
||||
|
||||
return !onlyA.isEmpty() || !onlyB.isEmpty();
|
||||
}
|
||||
|
||||
/** Creates an iterator of RawPackets over a binary keyring. */
|
||||
public static Iterator<RawPacket> parseKeyring(byte[] ring) {
|
||||
|
||||
final InputStream stream = new ByteArrayInputStream(ring);
|
||||
|
||||
return new Iterator<RawPacket>() {
|
||||
RawPacket next;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (next == null) try {
|
||||
next = readPacket(stream);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
return next != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RawPacket next() {
|
||||
if (!hasNext()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return next;
|
||||
} finally {
|
||||
next = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/** Read a single (raw) pgp packet from an input stream.
|
||||
*
|
||||
* Note that the RawPacket.position field is NOT set here!
|
||||
*
|
||||
* Variable length packets are not handled here. we don't use those in our test classes, and
|
||||
* otherwise rely on BouncyCastle's own unit tests to handle those correctly.
|
||||
*/
|
||||
private static RawPacket readPacket(InputStream in) throws IOException {
|
||||
|
||||
// save here. this is tag + length, max 6 bytes
|
||||
in.mark(6);
|
||||
|
||||
int hdr = in.read();
|
||||
int headerLength = 1;
|
||||
|
||||
if (hdr < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((hdr & 0x80) == 0) {
|
||||
throw new IOException("invalid header encountered");
|
||||
}
|
||||
|
||||
boolean newPacket = (hdr & 0x40) != 0;
|
||||
int tag;
|
||||
int bodyLen;
|
||||
|
||||
if (newPacket) {
|
||||
tag = hdr & 0x3f;
|
||||
|
||||
int l = in.read();
|
||||
headerLength += 1;
|
||||
|
||||
if (l < 192) {
|
||||
bodyLen = l;
|
||||
} else if (l <= 223) {
|
||||
int b = in.read();
|
||||
headerLength += 1;
|
||||
|
||||
bodyLen = ((l - 192) << 8) + (b) + 192;
|
||||
} else if (l == 255) {
|
||||
bodyLen = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
|
||||
headerLength += 4;
|
||||
} else {
|
||||
// bodyLen = 1 << (l & 0x1f);
|
||||
throw new IOException("no support for partial bodies in test classes");
|
||||
}
|
||||
} else {
|
||||
int lengthType = hdr & 0x3;
|
||||
|
||||
tag = (hdr & 0x3f) >> 2;
|
||||
|
||||
switch (lengthType) {
|
||||
case 0:
|
||||
bodyLen = in.read();
|
||||
headerLength += 1;
|
||||
break;
|
||||
case 1:
|
||||
bodyLen = (in.read() << 8) | in.read();
|
||||
headerLength += 2;
|
||||
break;
|
||||
case 2:
|
||||
bodyLen = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
|
||||
headerLength += 4;
|
||||
break;
|
||||
case 3:
|
||||
// bodyLen = 1 << (l & 0x1f);
|
||||
throw new IOException("no support for partial bodies in test classes");
|
||||
default:
|
||||
throw new IOException("unknown length type encountered");
|
||||
}
|
||||
}
|
||||
|
||||
in.reset();
|
||||
|
||||
// read the entire packet INCLUDING the header here
|
||||
byte[] buf = new byte[headerLength+bodyLen];
|
||||
if (in.read(buf) != headerLength+bodyLen) {
|
||||
throw new IOException("read length mismatch!");
|
||||
}
|
||||
RawPacket p = new RawPacket();
|
||||
p.tag = tag;
|
||||
p.headerLength = headerLength;
|
||||
p.length = bodyLen;
|
||||
p.buf = buf;
|
||||
return p;
|
||||
|
||||
}
|
||||
|
||||
public static <E> E getNth(Iterator<E> it, int position) {
|
||||
while(position-- > 0) {
|
||||
it.next();
|
||||
}
|
||||
return it.next();
|
||||
}
|
||||
|
||||
public static long getSubkeyId(UncachedKeyRing ring, int position) {
|
||||
return getNth(ring.getPublicKeys(), position).getKeyId();
|
||||
}
|
||||
|
||||
private void retrieveKeyAndExpectNotFound(ProviderHelper providerHelper, long masterKeyId) {
|
||||
try {
|
||||
providerHelper.getCanonicalizedPublicKeyRing(masterKeyId);
|
||||
throw new AssertionError("Was expecting the previous call to fail!");
|
||||
} catch (ProviderHelper.NotFoundException expectedException) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
public static <E> List<E> itToList(Iterator<E> it) {
|
||||
List<E> result = new ArrayList<E>();
|
||||
while(it.hasNext()) {
|
||||
result.add(it.next());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@ -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.CanonicalizedPublicKeyRing;
|
||||
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 CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri id) throws NotFoundException {
|
||||
byte[] data = TestDataUtil.readFully(getClass().getResourceAsStream("/public-key-for-sample.blob"));
|
||||
return new CanonicalizedPublicKeyRing(data, 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;
|
||||
}
|
||||
|
||||
}
|
@ -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,770 @@
|
||||
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.CanonicalizedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
|
||||
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);
|
||||
|
||||
staticRing = op.createSecretKeyRing(parcel).getRing();
|
||||
|
||||
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 createSecretKeyRingTests() {
|
||||
|
||||
{
|
||||
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).getRing();
|
||||
|
||||
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).getRing();
|
||||
|
||||
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).getRing();
|
||||
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).getRing();
|
||||
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).getRing();
|
||||
Assert.assertNull("creating ring without user ids should fail", ring);
|
||||
}
|
||||
|
||||
{
|
||||
parcel.reset();
|
||||
parcel.mAddUserIds.add("shy");
|
||||
parcel.mNewPassphrase = passphrase;
|
||||
|
||||
UncachedKeyRing ring = op.createSecretKeyRing(parcel).getRing();
|
||||
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");
|
||||
ring = op.createSecretKeyRing(parcel).getRing();
|
||||
|
||||
Assert.assertEquals("the keyring should contain only the master key",
|
||||
1, KeyringTestingHelper.itToList(ring.getPublicKeys()).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, KeyringTestingHelper.itToList(ring.getPublicKeys()).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();
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
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();
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
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;
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("keyring modification with bad fingerprint should fail", modified);
|
||||
}
|
||||
|
||||
{
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mMasterKeyId = ring.getMasterKeyId();
|
||||
parcel.mFingerprint = null;
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("keyring modification with null fingerprint should fail", modified);
|
||||
}
|
||||
|
||||
{
|
||||
String badphrase = "";
|
||||
if (badphrase.equals(passphrase)) {
|
||||
badphrase = "a";
|
||||
}
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, badphrase).getRing();
|
||||
|
||||
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, new Random().nextInt(512), KeyFlags.SIGN_DATA, null));
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
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));
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
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 = KeyringTestingHelper.getSubkeyId(ring, 1);
|
||||
|
||||
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));
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
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));
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
Assert.assertNull("modifying non-existent subkey should fail", modified);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubkeyRevoke() throws Exception {
|
||||
|
||||
long keyId = KeyringTestingHelper.getSubkeyId(ring, 1);
|
||||
int flags = ring.getPublicKey(keyId).getKeyUsage();
|
||||
|
||||
UncachedKeyRing modified;
|
||||
|
||||
{
|
||||
|
||||
parcel.reset();
|
||||
parcel.mRevokeSubKeys.add(123L);
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
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;
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), false, 0);
|
||||
UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
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("");
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
UncachedKeyRing modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
Assert.assertNull("adding an empty user id should fail", modified);
|
||||
}
|
||||
|
||||
parcel.reset();
|
||||
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";
|
||||
if (parcel.mChangePrimaryUserId.equals(passphrase)) {
|
||||
parcel.mChangePrimaryUserId += "A";
|
||||
}
|
||||
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
|
||||
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());
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), false, 0);
|
||||
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
UncachedKeyRing rawModified = op.modifySecretKeyRing(secretRing, parcel, passphrase).getRing();
|
||||
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;
|
||||
}
|
||||
|
||||
CanonicalizedKeyRing modified = rawModified.canonicalize(new OperationLog(), 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.getUncachedKeyRing();
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new AssertionFailedError("error during encoding!");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifySuccess() throws Exception {
|
||||
|
||||
UncachedKeyRing expectedKeyRing = KeyringBuilder.correctRing();
|
||||
UncachedKeyRing inputKeyRing = KeyringBuilder.ringWithExtraIncorrectSignature();
|
||||
|
||||
CanonicalizedKeyRing canonicalized = inputKeyRing.canonicalize(new OperationLog(), 0);
|
||||
Assert.assertNotNull("canonicalization must succeed", canonicalized);
|
||||
|
||||
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",
|
||||
@ -79,7 +94,7 @@ public class ProviderHelperKeyringTest {
|
||||
private static Collection<String> prependResourcePath(Collection<String> files) {
|
||||
Collection<String> prependedFiles = new ArrayList<String>();
|
||||
for (String file: files) {
|
||||
prependedFiles.add("/extern/OpenPGP-Haskell/tests/data/" + file);
|
||||
prependedFiles.add("/OpenPGP-Haskell/tests/data/" + file);
|
||||
}
|
||||
return prependedFiles;
|
||||
}
|
@ -0,0 +1,627 @@
|
||||
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.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
|
||||
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.OperationResults.EditKeyResult;
|
||||
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.Date;
|
||||
import java.util.Iterator;
|
||||
|
||||
|
||||
/** Tests for the UncachedKeyring.canonicalize method.
|
||||
*
|
||||
* This is a complex and crypto-relevant method, which takes care of sanitizing keyrings.
|
||||
* Test cases are made for all its assertions.
|
||||
*/
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
|
||||
public class UncachedKeyringCanonicalizeTest {
|
||||
|
||||
static UncachedKeyRing staticRing;
|
||||
static int totalPackets;
|
||||
UncachedKeyRing ring;
|
||||
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
|
||||
ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>();
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
PGPSignatureSubpacketGenerator subHashedPacketsGen;
|
||||
PGPSecretKey secretKey;
|
||||
|
||||
@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);
|
||||
|
||||
EditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
Assert.assertTrue("initial test key creation must succeed", result.success());
|
||||
staticRing = result.getRing();
|
||||
Assert.assertNotNull("initial test key creation must succeed", staticRing);
|
||||
|
||||
// just for later reference
|
||||
totalPackets = 9;
|
||||
|
||||
// we sleep here for a second, to make sure all new certificates have different timestamps
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
@Before public void setUp() throws Exception {
|
||||
// show Log.x messages in system.out
|
||||
ShadowLog.stream = System.out;
|
||||
ring = staticRing;
|
||||
|
||||
subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
secretKey = new PGPSecretKeyRing(ring.getEncoded(), new JcaKeyFingerprintCalculator())
|
||||
.getSecretKey();
|
||||
}
|
||||
|
||||
/** Make sure the assumptions made about the generated ring packet structure are valid. */
|
||||
@Test public void testGeneratedRingStructure() throws Exception {
|
||||
|
||||
Iterator<RawPacket> it = KeyringTestingHelper.parseKeyring(ring.getEncoded());
|
||||
|
||||
Assert.assertEquals("packet #1 should be secret key",
|
||||
PacketTags.SECRET_KEY, it.next().tag);
|
||||
|
||||
Assert.assertEquals("packet #2 should be user id",
|
||||
PacketTags.USER_ID, it.next().tag);
|
||||
Assert.assertEquals("packet #3 should be signature",
|
||||
PacketTags.SIGNATURE, it.next().tag);
|
||||
|
||||
Assert.assertEquals("packet #4 should be user id",
|
||||
PacketTags.USER_ID, it.next().tag);
|
||||
Assert.assertEquals("packet #5 should be signature",
|
||||
PacketTags.SIGNATURE, it.next().tag);
|
||||
|
||||
Assert.assertEquals("packet #6 should be secret subkey",
|
||||
PacketTags.SECRET_SUBKEY, it.next().tag);
|
||||
Assert.assertEquals("packet #7 should be signature",
|
||||
PacketTags.SIGNATURE, it.next().tag);
|
||||
|
||||
Assert.assertEquals("packet #8 should be secret subkey",
|
||||
PacketTags.SECRET_SUBKEY, it.next().tag);
|
||||
Assert.assertEquals("packet #9 should be signature",
|
||||
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());
|
||||
|
||||
}
|
||||
|
||||
@Test public void testUidSignature() throws Exception {
|
||||
|
||||
UncachedPublicKey masterKey = ring.getPublicKey();
|
||||
final WrappedSignature sig = masterKey.getSignaturesForId("twi").next();
|
||||
|
||||
byte[] raw = sig.getEncoded();
|
||||
// destroy the signature
|
||||
raw[raw.length - 5] += 1;
|
||||
final WrappedSignature brokenSig = WrappedSignature.fromBytes(raw);
|
||||
|
||||
{ // bad certificates get stripped
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, brokenSig.getEncoded(), 3);
|
||||
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
|
||||
|
||||
Assert.assertTrue("canonicalized keyring with invalid extra sig must be same as original one",
|
||||
!KeyringTestingHelper.diffKeyrings(
|
||||
ring.getEncoded(), canonicalized.getEncoded(), onlyA, onlyB));
|
||||
}
|
||||
|
||||
// remove user id certificate for one user
|
||||
final UncachedKeyRing base = KeyringTestingHelper.removePacket(ring, 2);
|
||||
|
||||
{ // user id without certificate should be removed
|
||||
CanonicalizedKeyRing modified = base.canonicalize(log, 0);
|
||||
Assert.assertTrue("canonicalized keyring must differ", KeyringTestingHelper.diffKeyrings(
|
||||
ring.getEncoded(), modified.getEncoded(), onlyA, onlyB));
|
||||
|
||||
Assert.assertEquals("two packets should be stripped after canonicalization", 2, onlyA.size());
|
||||
Assert.assertEquals("no new packets after canonicalization", 0, onlyB.size());
|
||||
|
||||
Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first stripped packet must be user id", p instanceof UserIDPacket);
|
||||
Assert.assertEquals("missing user id must be the expected one",
|
||||
"twi", ((UserIDPacket) p).getID());
|
||||
|
||||
Assert.assertArrayEquals("second stripped packet must be signature we removed",
|
||||
sig.getEncoded(), onlyA.get(1).buf);
|
||||
|
||||
}
|
||||
|
||||
{ // add error to signature
|
||||
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(base, brokenSig.getEncoded(), 3);
|
||||
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
|
||||
|
||||
Assert.assertTrue("canonicalized keyring must differ", KeyringTestingHelper.diffKeyrings(
|
||||
ring.getEncoded(), canonicalized.getEncoded(), onlyA, onlyB));
|
||||
|
||||
Assert.assertEquals("two packets should be missing after canonicalization", 2, onlyA.size());
|
||||
Assert.assertEquals("no new packets after canonicalization", 0, onlyB.size());
|
||||
|
||||
Packet p = new BCPGInputStream(new ByteArrayInputStream(onlyA.get(0).buf)).readPacket();
|
||||
Assert.assertTrue("first stripped packet must be user id", p instanceof UserIDPacket);
|
||||
Assert.assertEquals("missing user id must be the expected one",
|
||||
"twi", ((UserIDPacket) p).getID());
|
||||
|
||||
Assert.assertArrayEquals("second stripped packet must be signature we removed",
|
||||
sig.getEncoded(), onlyA.get(1).buf);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test public void testUidDestroy() throws Exception {
|
||||
|
||||
// signature for "twi"
|
||||
ring = KeyringTestingHelper.removePacket(ring, 2);
|
||||
// signature for "pink"
|
||||
ring = KeyringTestingHelper.removePacket(ring, 3);
|
||||
|
||||
// canonicalization should fail, because there are no valid uids left
|
||||
CanonicalizedKeyRing canonicalized = ring.canonicalize(log, 0);
|
||||
Assert.assertNull("canonicalization of keyring with no valid uids should fail", canonicalized);
|
||||
|
||||
}
|
||||
|
||||
@Test public void testRevocationRedundant() throws Exception {
|
||||
|
||||
PGPSignature revocation = forgeSignature(
|
||||
secretKey, PGPSignature.KEY_REVOCATION, subHashedPacketsGen, secretKey.getPublicKey());
|
||||
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, revocation.getEncoded(), 1);
|
||||
|
||||
// try to add the same packet again, it should be rejected in all positions
|
||||
injectEverywhere(modified, revocation.getEncoded());
|
||||
|
||||
// an older (but different!) revocation should be rejected as well
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
|
||||
revocation = forgeSignature(
|
||||
secretKey, PGPSignature.KEY_REVOCATION, subHashedPacketsGen, secretKey.getPublicKey());
|
||||
|
||||
injectEverywhere(modified, revocation.getEncoded());
|
||||
|
||||
}
|
||||
|
||||
@Test public void testUidRedundant() throws Exception {
|
||||
|
||||
// an older uid certificate should be rejected
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
|
||||
PGPSignature revocation = forgeSignature(
|
||||
secretKey, PGPSignature.DEFAULT_CERTIFICATION, subHashedPacketsGen, "twi", secretKey.getPublicKey());
|
||||
|
||||
injectEverywhere(ring, revocation.getEncoded());
|
||||
|
||||
}
|
||||
|
||||
@Test public void testUidRevocationOutdated() throws Exception {
|
||||
// an older uid revocation cert should be rejected
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
|
||||
PGPSignature revocation = forgeSignature(
|
||||
secretKey, PGPSignature.CERTIFICATION_REVOCATION, subHashedPacketsGen, "twi", secretKey.getPublicKey());
|
||||
|
||||
injectEverywhere(ring, revocation.getEncoded());
|
||||
|
||||
}
|
||||
|
||||
@Test public void testUidRevocationRedundant() throws Exception {
|
||||
|
||||
PGPSignature revocation = forgeSignature(
|
||||
secretKey, PGPSignature.CERTIFICATION_REVOCATION, subHashedPacketsGen, "twi", secretKey.getPublicKey());
|
||||
|
||||
// add that revocation to the base, and check if the redundant one will be rejected as well
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, revocation.getEncoded(), 2);
|
||||
|
||||
injectEverywhere(modified, revocation.getEncoded());
|
||||
|
||||
// an older (but different!) uid revocation should be rejected as well
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
|
||||
revocation = forgeSignature(
|
||||
secretKey, PGPSignature.CERTIFICATION_REVOCATION, subHashedPacketsGen, "twi", secretKey.getPublicKey());
|
||||
|
||||
injectEverywhere(modified, revocation.getEncoded());
|
||||
|
||||
}
|
||||
|
||||
@Test public void testSignatureBroken() throws Exception {
|
||||
|
||||
injectEverytype(secretKey, ring, subHashedPacketsGen, true);
|
||||
|
||||
}
|
||||
|
||||
@Test public void testForeignSignature() throws Exception {
|
||||
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
parcel.mAddUserIds.add("trix");
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
UncachedKeyRing foreign = op.createSecretKeyRing(parcel).getRing();
|
||||
|
||||
Assert.assertNotNull("initial test key creation must succeed", foreign);
|
||||
PGPSecretKey foreignSecretKey =
|
||||
new PGPSecretKeyRing(foreign.getEncoded(), new JcaKeyFingerprintCalculator())
|
||||
.getSecretKey();
|
||||
|
||||
injectEverytype(foreignSecretKey, ring, subHashedPacketsGen);
|
||||
|
||||
}
|
||||
|
||||
@Test public void testSignatureFuture() throws Exception {
|
||||
|
||||
// generate future
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() + 1000 * 1000));
|
||||
|
||||
injectEverytype(secretKey, ring, subHashedPacketsGen);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test public void testSignatureLocal() throws Exception {
|
||||
|
||||
// generate future
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date());
|
||||
subHashedPacketsGen.setExportable(false, false);
|
||||
|
||||
injectEverytype(secretKey, ring, subHashedPacketsGen);
|
||||
|
||||
}
|
||||
|
||||
@Test public void testSubkeyDestroy() throws Exception {
|
||||
|
||||
// signature for second key (first subkey)
|
||||
UncachedKeyRing modified = KeyringTestingHelper.removePacket(ring, 6);
|
||||
|
||||
// canonicalization should fail, because there are no valid uids left
|
||||
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
|
||||
Assert.assertTrue("keyring with missing subkey binding sig should differ from intact one after canonicalization",
|
||||
KeyringTestingHelper.diffKeyrings(ring.getEncoded(), canonicalized.getEncoded(),
|
||||
onlyA, onlyB)
|
||||
);
|
||||
|
||||
Assert.assertEquals("canonicalized keyring should have two extra packets", 2, onlyA.size());
|
||||
Assert.assertEquals("canonicalized keyring should have no extra packets", 0, onlyB.size());
|
||||
|
||||
Assert.assertEquals("first missing packet should be the subkey",
|
||||
PacketTags.SECRET_SUBKEY, onlyA.get(0).tag);
|
||||
Assert.assertEquals("second missing packet should be subkey's signature",
|
||||
PacketTags.SIGNATURE, onlyA.get(1).tag);
|
||||
Assert.assertEquals("second missing packet should be next to subkey",
|
||||
onlyA.get(0).position + 1, onlyA.get(1).position);
|
||||
|
||||
}
|
||||
|
||||
@Test public void testSubkeyBindingNoPKB() throws Exception {
|
||||
|
||||
UncachedPublicKey pKey = KeyringTestingHelper.getNth(ring.getPublicKeys(), 1);
|
||||
Assert.assertTrue("second subkey must be able to sign", pKey.canSign());
|
||||
|
||||
PGPSignature sig;
|
||||
|
||||
subHashedPacketsGen.setKeyFlags(false, KeyFlags.SIGN_DATA);
|
||||
|
||||
{
|
||||
// forge a (newer) signature, which has the sign flag but no primary key binding sig
|
||||
PGPSignatureSubpacketGenerator unhashedSubs = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
// just add any random signature, because why not
|
||||
unhashedSubs.setEmbeddedSignature(false, forgeSignature(
|
||||
secretKey, PGPSignature.POSITIVE_CERTIFICATION, subHashedPacketsGen,
|
||||
secretKey.getPublicKey()
|
||||
)
|
||||
);
|
||||
|
||||
sig = forgeSignature(
|
||||
secretKey, PGPSignature.SUBKEY_BINDING, subHashedPacketsGen, unhashedSubs,
|
||||
secretKey.getPublicKey(), pKey.getPublicKey());
|
||||
|
||||
// inject in the right position
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig.getEncoded(), 6);
|
||||
|
||||
// canonicalize, and check if we lose the bad signature
|
||||
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
|
||||
Assert.assertFalse("subkey binding signature should be gone after canonicalization",
|
||||
KeyringTestingHelper.diffKeyrings(ring.getEncoded(), canonicalized.getEncoded(),
|
||||
onlyA, onlyB)
|
||||
);
|
||||
}
|
||||
|
||||
{ // now try one with a /bad/ primary key binding signature
|
||||
|
||||
PGPSignatureSubpacketGenerator unhashedSubs = new PGPSignatureSubpacketGenerator();
|
||||
// this one is signed by the primary key itself, not the subkey - but it IS primary binding
|
||||
unhashedSubs.setEmbeddedSignature(false, forgeSignature(
|
||||
secretKey, PGPSignature.PRIMARYKEY_BINDING, subHashedPacketsGen,
|
||||
secretKey.getPublicKey(), pKey.getPublicKey()
|
||||
)
|
||||
);
|
||||
|
||||
sig = forgeSignature(
|
||||
secretKey, PGPSignature.SUBKEY_BINDING, subHashedPacketsGen, unhashedSubs,
|
||||
secretKey.getPublicKey(), pKey.getPublicKey());
|
||||
|
||||
// inject in the right position
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig.getEncoded(), 6);
|
||||
|
||||
// canonicalize, and check if we lose the bad signature
|
||||
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
|
||||
Assert.assertFalse("subkey binding signature should be gone after canonicalization",
|
||||
KeyringTestingHelper.diffKeyrings(ring.getEncoded(), canonicalized.getEncoded(),
|
||||
onlyA, onlyB)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test public void testSubkeyBindingRedundant() throws Exception {
|
||||
|
||||
UncachedPublicKey pKey = KeyringTestingHelper.getNth(ring.getPublicKeys(), 2);
|
||||
|
||||
subHashedPacketsGen.setKeyFlags(false, KeyFlags.ENCRYPT_COMMS);
|
||||
PGPSignature sig2 = forgeSignature(
|
||||
secretKey, PGPSignature.SUBKEY_BINDING, subHashedPacketsGen,
|
||||
secretKey.getPublicKey(), pKey.getPublicKey());
|
||||
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -1000*1000));
|
||||
PGPSignature sig1 = forgeSignature(
|
||||
secretKey, PGPSignature.SUBKEY_REVOCATION, subHashedPacketsGen,
|
||||
secretKey.getPublicKey(), pKey.getPublicKey());
|
||||
|
||||
subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, new Date(new Date().getTime() -100*1000));
|
||||
PGPSignature sig3 = forgeSignature(
|
||||
secretKey, PGPSignature.SUBKEY_BINDING, subHashedPacketsGen,
|
||||
secretKey.getPublicKey(), pKey.getPublicKey());
|
||||
|
||||
UncachedKeyRing modified = KeyringTestingHelper.injectPacket(ring, sig1.getEncoded(), 8);
|
||||
modified = KeyringTestingHelper.injectPacket(modified, sig2.getEncoded(), 9);
|
||||
modified = KeyringTestingHelper.injectPacket(modified, sig1.getEncoded(), 10);
|
||||
modified = KeyringTestingHelper.injectPacket(modified, sig3.getEncoded(), 11);
|
||||
|
||||
// canonicalize, and check if we lose the bad signature
|
||||
CanonicalizedKeyRing canonicalized = modified.canonicalize(log, 0);
|
||||
Assert.assertTrue("subkey binding signature should be gone after canonicalization",
|
||||
KeyringTestingHelper.diffKeyrings(modified.getEncoded(), canonicalized.getEncoded(),
|
||||
onlyA, onlyB)
|
||||
);
|
||||
|
||||
Assert.assertEquals("canonicalized keyring should have lost two packets", 3, onlyA.size());
|
||||
Assert.assertEquals("canonicalized keyring should have no extra packets", 0, onlyB.size());
|
||||
|
||||
Assert.assertEquals("first missing packet should be the subkey",
|
||||
PacketTags.SIGNATURE, onlyA.get(0).tag);
|
||||
Assert.assertEquals("second missing packet should be a signature",
|
||||
PacketTags.SIGNATURE, onlyA.get(1).tag);
|
||||
Assert.assertEquals("second missing packet should be a signature",
|
||||
PacketTags.SIGNATURE, onlyA.get(2).tag);
|
||||
|
||||
}
|
||||
|
||||
private static final int[] sigtypes_direct = new int[] {
|
||||
PGPSignature.KEY_REVOCATION,
|
||||
PGPSignature.DIRECT_KEY,
|
||||
};
|
||||
private static final int[] sigtypes_uid = new int[] {
|
||||
PGPSignature.DEFAULT_CERTIFICATION,
|
||||
PGPSignature.NO_CERTIFICATION,
|
||||
PGPSignature.CASUAL_CERTIFICATION,
|
||||
PGPSignature.POSITIVE_CERTIFICATION,
|
||||
PGPSignature.CERTIFICATION_REVOCATION,
|
||||
};
|
||||
private static final int[] sigtypes_subkey = new int[] {
|
||||
PGPSignature.SUBKEY_BINDING,
|
||||
PGPSignature.PRIMARYKEY_BINDING,
|
||||
PGPSignature.SUBKEY_REVOCATION,
|
||||
};
|
||||
|
||||
private static void injectEverytype(PGPSecretKey secretKey,
|
||||
UncachedKeyRing ring,
|
||||
PGPSignatureSubpacketGenerator subHashedPacketsGen)
|
||||
throws Exception {
|
||||
injectEverytype(secretKey, ring, subHashedPacketsGen, false);
|
||||
}
|
||||
|
||||
private static void injectEverytype(PGPSecretKey secretKey,
|
||||
UncachedKeyRing ring,
|
||||
PGPSignatureSubpacketGenerator subHashedPacketsGen,
|
||||
boolean breakSig)
|
||||
throws Exception {
|
||||
|
||||
for (int sigtype : sigtypes_direct) {
|
||||
PGPSignature sig = forgeSignature(
|
||||
secretKey, sigtype, subHashedPacketsGen, secretKey.getPublicKey());
|
||||
byte[] encoded = sig.getEncoded();
|
||||
if (breakSig) {
|
||||
encoded[encoded.length-10] += 1;
|
||||
}
|
||||
injectEverywhere(ring, encoded);
|
||||
}
|
||||
|
||||
for (int sigtype : sigtypes_uid) {
|
||||
PGPSignature sig = forgeSignature(
|
||||
secretKey, sigtype, subHashedPacketsGen, "twi", secretKey.getPublicKey());
|
||||
|
||||
byte[] encoded = sig.getEncoded();
|
||||
if (breakSig) {
|
||||
encoded[encoded.length-10] += 1;
|
||||
}
|
||||
injectEverywhere(ring, encoded);
|
||||
}
|
||||
|
||||
for (int sigtype : sigtypes_subkey) {
|
||||
PGPSignature sig = forgeSignature(
|
||||
secretKey, sigtype, subHashedPacketsGen,
|
||||
secretKey.getPublicKey(), secretKey.getPublicKey());
|
||||
|
||||
byte[] encoded = sig.getEncoded();
|
||||
if (breakSig) {
|
||||
encoded[encoded.length-10] += 1;
|
||||
}
|
||||
injectEverywhere(ring, encoded);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void injectEverywhere(UncachedKeyRing ring, byte[] packet) throws Exception {
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
|
||||
byte[] encodedRing = ring.getEncoded();
|
||||
|
||||
for(int i = 0; i < totalPackets; i++) {
|
||||
|
||||
byte[] brokenEncoded = KeyringTestingHelper.injectPacket(encodedRing, packet, i);
|
||||
|
||||
try {
|
||||
|
||||
UncachedKeyRing brokenRing = UncachedKeyRing.decodeFromData(brokenEncoded);
|
||||
|
||||
CanonicalizedKeyRing canonicalized = brokenRing.canonicalize(log, 0);
|
||||
if (canonicalized == null) {
|
||||
System.out.println("ok, canonicalization failed.");
|
||||
continue;
|
||||
}
|
||||
|
||||
Assert.assertArrayEquals("injected bad signature must be gone after canonicalization",
|
||||
ring.getEncoded(), canonicalized.getEncoded());
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println("ok, rejected with: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static PGPSignature forgeSignature(PGPSecretKey key, int type,
|
||||
PGPSignatureSubpacketGenerator subpackets,
|
||||
PGPPublicKey publicKey)
|
||||
throws Exception {
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
PGPPrivateKey privateKey = key.extractPrivateKey(keyDecryptor);
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
publicKey.getAlgorithm(), PGPUtil.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.setHashedSubpackets(subpackets.generate());
|
||||
sGen.init(type, privateKey);
|
||||
return sGen.generateCertification(publicKey);
|
||||
|
||||
}
|
||||
|
||||
private static PGPSignature forgeSignature(PGPSecretKey key, int type,
|
||||
PGPSignatureSubpacketGenerator subpackets,
|
||||
String userId, PGPPublicKey publicKey)
|
||||
throws Exception {
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
PGPPrivateKey privateKey = key.extractPrivateKey(keyDecryptor);
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
publicKey.getAlgorithm(), PGPUtil.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.setHashedSubpackets(subpackets.generate());
|
||||
sGen.init(type, privateKey);
|
||||
return sGen.generateCertification(userId, publicKey);
|
||||
|
||||
}
|
||||
|
||||
private static PGPSignature forgeSignature(PGPSecretKey key, int type,
|
||||
PGPSignatureSubpacketGenerator subpackets,
|
||||
PGPPublicKey publicKey, PGPPublicKey signedKey)
|
||||
throws Exception {
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
PGPPrivateKey privateKey = key.extractPrivateKey(keyDecryptor);
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
publicKey.getAlgorithm(), PGPUtil.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.setHashedSubpackets(subpackets.generate());
|
||||
sGen.init(type, privateKey);
|
||||
return sGen.generateCertification(publicKey, signedKey);
|
||||
|
||||
}
|
||||
|
||||
private static PGPSignature forgeSignature(PGPSecretKey key, int type,
|
||||
PGPSignatureSubpacketGenerator hashedSubs,
|
||||
PGPSignatureSubpacketGenerator unhashedSubs,
|
||||
PGPPublicKey publicKey, PGPPublicKey signedKey)
|
||||
throws Exception {
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
|
||||
PGPPrivateKey privateKey = key.extractPrivateKey(keyDecryptor);
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
publicKey.getAlgorithm(), PGPUtil.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.setHashedSubpackets(hashedSubs.generate());
|
||||
sGen.setUnhashedSubpackets(unhashedSubs.generate());
|
||||
sGen.init(type, privateKey);
|
||||
return sGen.generateCertification(publicKey, signedKey);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,397 @@
|
||||
package org.sufficientlysecure.keychain.tests;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
import org.spongycastle.bcpg.PacketTags;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
/** Tests for the UncachedKeyring.merge method.
|
||||
*
|
||||
* This is another complex, crypto-related method. It merges information from one keyring into
|
||||
* another, keeping information from the base (ie, called object) keyring in case of conflicts.
|
||||
* The types of keys may be Public or Secret and can be mixed, For mixed types the result type
|
||||
* will be the same as the base keyring.
|
||||
*
|
||||
* Test cases:
|
||||
* - Merging keyrings with different masterKeyIds should fail
|
||||
* - Merging a key with itself should be a no-operation
|
||||
* - Merging a key with an extra revocation certificate, it should have that certificate
|
||||
* - Merging a key with an extra user id, it should have that extra user id and its certificates
|
||||
* - Merging a key with an extra user id certificate, it should have that certificate
|
||||
* - Merging a key with an extra subkey, it should have that subkey
|
||||
* - Merging a key with an extra subkey certificate, it should have that certificate
|
||||
* - All of the above operations should work regardless of the key types. This means in particular
|
||||
* that for new subkeys, an equivalent subkey of the proper type must be generated.
|
||||
* - In case of two secret keys with the same id but different S2K, the key of the base keyring
|
||||
* should be preferred (TODO or should it?)
|
||||
*
|
||||
* Note that the merge operation does not care about certificate validity, a bad certificate or
|
||||
* packet will be copied regardless. Filtering out bad packets is done with canonicalization.
|
||||
*
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
|
||||
public class UncachedKeyringMergeTest {
|
||||
|
||||
static UncachedKeyRing staticRingA, staticRingB;
|
||||
UncachedKeyRing ringA, ringB;
|
||||
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
|
||||
ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>();
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
PgpKeyOperation op;
|
||||
SaveKeyringParcel parcel;
|
||||
|
||||
@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.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();
|
||||
|
||||
EditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
staticRingA = result.getRing();
|
||||
}
|
||||
|
||||
{
|
||||
SaveKeyringParcel parcel = new SaveKeyringParcel();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.CERTIFY_OTHER, null));
|
||||
|
||||
parcel.mAddUserIds.add("shy");
|
||||
// passphrase is tested in PgpKeyOperationTest, just use empty here
|
||||
parcel.mNewPassphrase = "";
|
||||
PgpKeyOperation op = new PgpKeyOperation(null);
|
||||
|
||||
OperationResultParcel.OperationLog log = new OperationResultParcel.OperationLog();
|
||||
EditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
staticRingB = result.getRing();
|
||||
}
|
||||
|
||||
Assert.assertNotNull("initial test key creation must succeed", staticRingA);
|
||||
Assert.assertNotNull("initial test key creation must succeed", staticRingB);
|
||||
|
||||
// we sleep here for a second, to make sure all new certificates have different timestamps
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
// show Log.x messages in system.out
|
||||
ShadowLog.stream = System.out;
|
||||
ringA = staticRingA;
|
||||
ringB = staticRingB;
|
||||
|
||||
// setting up some parameters just to reduce code duplication
|
||||
op = new PgpKeyOperation(new ProgressScaler(null, 0, 100, 100));
|
||||
|
||||
// set this up, gonna need it more than once
|
||||
parcel = new SaveKeyringParcel();
|
||||
parcel.mMasterKeyId = ringA.getMasterKeyId();
|
||||
parcel.mFingerprint = ringA.getFingerprint();
|
||||
}
|
||||
|
||||
public void testSelfNoOp() throws Exception {
|
||||
|
||||
UncachedKeyRing merged = mergeWithChecks(ringA, ringA, null);
|
||||
Assert.assertArrayEquals("keyring merged with itself must be identical",
|
||||
ringA.getEncoded(), merged.getEncoded()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentMasterKeyIds() throws Exception {
|
||||
|
||||
Assert.assertNotEquals("generated key ids must be different",
|
||||
ringA.getMasterKeyId(), ringB.getMasterKeyId());
|
||||
|
||||
Assert.assertNull("merging keys with differing key ids must fail",
|
||||
ringA.merge(ringB, log, 0));
|
||||
Assert.assertNull("merging keys with differing key ids must fail",
|
||||
ringB.merge(ringA, log, 0));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddedUserId() throws Exception {
|
||||
|
||||
UncachedKeyRing modifiedA, modifiedB; {
|
||||
CanonicalizedSecretKeyRing secretRing =
|
||||
new CanonicalizedSecretKeyRing(ringA.getEncoded(), false, 0);
|
||||
|
||||
parcel.reset();
|
||||
parcel.mAddUserIds.add("flim");
|
||||
modifiedA = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
|
||||
|
||||
parcel.reset();
|
||||
parcel.mAddUserIds.add("flam");
|
||||
modifiedB = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
|
||||
}
|
||||
|
||||
{ // merge A into base
|
||||
UncachedKeyRing merged = mergeWithChecks(ringA, modifiedA);
|
||||
|
||||
Assert.assertEquals("merged keyring must have lost no packets", 0, onlyA.size());
|
||||
Assert.assertEquals("merged keyring must have gained two packets", 2, onlyB.size());
|
||||
Assert.assertTrue("merged keyring must contain new user id",
|
||||
merged.getPublicKey().getUnorderedUserIds().contains("flim"));
|
||||
}
|
||||
|
||||
{ // merge A into B
|
||||
UncachedKeyRing merged = mergeWithChecks(modifiedA, modifiedB, ringA);
|
||||
|
||||
Assert.assertEquals("merged keyring must have lost no packets", 0, onlyA.size());
|
||||
Assert.assertEquals("merged keyring must have gained four packets", 4, onlyB.size());
|
||||
Assert.assertTrue("merged keyring must contain first new user id",
|
||||
merged.getPublicKey().getUnorderedUserIds().contains("flim"));
|
||||
Assert.assertTrue("merged keyring must contain second new user id",
|
||||
merged.getPublicKey().getUnorderedUserIds().contains("flam"));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddedSubkeyId() throws Exception {
|
||||
|
||||
UncachedKeyRing modifiedA, modifiedB;
|
||||
long subKeyIdA, subKeyIdB;
|
||||
{
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ringA.getEncoded(), false, 0);
|
||||
|
||||
parcel.reset();
|
||||
parcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
||||
Constants.choice.algorithm.rsa, 1024, KeyFlags.SIGN_DATA, null));
|
||||
modifiedA = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
|
||||
modifiedB = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
|
||||
|
||||
subKeyIdA = KeyringTestingHelper.getSubkeyId(modifiedA, 2);
|
||||
subKeyIdB = KeyringTestingHelper.getSubkeyId(modifiedB, 2);
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
UncachedKeyRing merged = mergeWithChecks(ringA, modifiedA);
|
||||
|
||||
Assert.assertEquals("merged keyring must have lost no packets", 0, onlyA.size());
|
||||
Assert.assertEquals("merged keyring must have gained two packets", 2, onlyB.size());
|
||||
|
||||
long mergedKeyId = KeyringTestingHelper.getSubkeyId(merged, 2);
|
||||
Assert.assertEquals("merged keyring must contain the new subkey", subKeyIdA, mergedKeyId);
|
||||
}
|
||||
|
||||
{
|
||||
UncachedKeyRing merged = mergeWithChecks(modifiedA, modifiedB, ringA);
|
||||
|
||||
Assert.assertEquals("merged keyring must have lost no packets", 0, onlyA.size());
|
||||
Assert.assertEquals("merged keyring must have gained four packets", 4, onlyB.size());
|
||||
|
||||
Iterator<UncachedPublicKey> it = merged.getPublicKeys();
|
||||
it.next(); it.next();
|
||||
Assert.assertEquals("merged keyring must contain the new subkey",
|
||||
subKeyIdA, it.next().getKeyId());
|
||||
Assert.assertEquals("merged keyring must contain both new subkeys",
|
||||
subKeyIdB, it.next().getKeyId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddedKeySignature() throws Exception {
|
||||
|
||||
final UncachedKeyRing modified; {
|
||||
parcel.reset();
|
||||
parcel.mRevokeSubKeys.add(KeyringTestingHelper.getSubkeyId(ringA, 1));
|
||||
CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(
|
||||
ringA.getEncoded(), false, 0);
|
||||
modified = op.modifySecretKeyRing(secretRing, parcel, "").getRing();
|
||||
}
|
||||
|
||||
{
|
||||
UncachedKeyRing merged = ringA.merge(modified, log, 0);
|
||||
Assert.assertNotNull("merge must succeed", merged);
|
||||
Assert.assertFalse(
|
||||
"merging keyring with extra signatures into its base should yield that same keyring",
|
||||
KeyringTestingHelper.diffKeyrings(merged.getEncoded(), modified.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddedUserIdSignature() throws Exception {
|
||||
|
||||
final UncachedKeyRing pubRing = ringA.extractPublicKeyRing();
|
||||
|
||||
final UncachedKeyRing modified; {
|
||||
CanonicalizedPublicKeyRing publicRing = new CanonicalizedPublicKeyRing(
|
||||
pubRing.getEncoded(), 0);
|
||||
|
||||
CanonicalizedSecretKey secretKey = new CanonicalizedSecretKeyRing(
|
||||
ringB.getEncoded(), false, 0).getSecretKey();
|
||||
secretKey.unlock("");
|
||||
// sign all user ids
|
||||
modified = secretKey.certifyUserIds(publicRing, publicRing.getPublicKey().getUnorderedUserIds());
|
||||
}
|
||||
|
||||
{
|
||||
UncachedKeyRing merged = ringA.merge(modified, log, 0);
|
||||
Assert.assertNotNull("merge must succeed", merged);
|
||||
Assert.assertArrayEquals("foreign signatures should not be merged into secret key",
|
||||
ringA.getEncoded(), merged.getEncoded()
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
byte[] sig = KeyringTestingHelper.getNth(
|
||||
modified.getPublicKey().getSignaturesForId("twi"), 1).getEncoded();
|
||||
|
||||
// inject the (foreign!) signature into subkey signature position
|
||||
UncachedKeyRing moreModified = KeyringTestingHelper.injectPacket(modified, sig, 1);
|
||||
|
||||
UncachedKeyRing merged = ringA.merge(moreModified, log, 0);
|
||||
Assert.assertNotNull("merge must succeed", merged);
|
||||
Assert.assertArrayEquals("foreign signatures should not be merged into secret key",
|
||||
ringA.getEncoded(), merged.getEncoded()
|
||||
);
|
||||
|
||||
merged = pubRing.merge(moreModified, log, 0);
|
||||
Assert.assertNotNull("merge must succeed", merged);
|
||||
Assert.assertTrue(
|
||||
"merged keyring should contain new signature",
|
||||
KeyringTestingHelper.diffKeyrings(pubRing.getEncoded(), merged.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
Assert.assertEquals("merged keyring should be missing no packets", 0, onlyA.size());
|
||||
Assert.assertEquals("merged keyring should contain exactly two more packets", 2, onlyB.size());
|
||||
Assert.assertEquals("first added packet should be a signature",
|
||||
PacketTags.SIGNATURE, onlyB.get(0).tag);
|
||||
Assert.assertEquals("first added packet should be in the position we injected it at",
|
||||
1, onlyB.get(0).position);
|
||||
Assert.assertEquals("second added packet should be a signature",
|
||||
PacketTags.SIGNATURE, onlyB.get(1).tag);
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
UncachedKeyRing merged = pubRing.merge(modified, log, 0);
|
||||
Assert.assertNotNull("merge must succeed", merged);
|
||||
Assert.assertFalse(
|
||||
"merging keyring with extra signatures into its base should yield that same keyring",
|
||||
KeyringTestingHelper.diffKeyrings(merged.getEncoded(), modified.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private UncachedKeyRing mergeWithChecks(UncachedKeyRing a, UncachedKeyRing b)
|
||||
throws Exception {
|
||||
return mergeWithChecks(a, b, a);
|
||||
}
|
||||
|
||||
private UncachedKeyRing mergeWithChecks(UncachedKeyRing a, UncachedKeyRing b,
|
||||
UncachedKeyRing base)
|
||||
throws Exception {
|
||||
|
||||
Assert.assertTrue("merging keyring must be secret type", a.isSecret());
|
||||
Assert.assertTrue("merged keyring must be secret type", b.isSecret());
|
||||
|
||||
final UncachedKeyRing resultA;
|
||||
UncachedKeyRing resultB;
|
||||
|
||||
{ // sec + sec
|
||||
resultA = a.merge(b, log, 0);
|
||||
Assert.assertNotNull("merge must succeed as sec(a)+sec(b)", resultA);
|
||||
|
||||
resultB = b.merge(a, log, 0);
|
||||
Assert.assertNotNull("merge must succeed as sec(b)+sec(a)", resultB);
|
||||
|
||||
// check commutativity, if requested
|
||||
Assert.assertFalse("result of merge must be commutative",
|
||||
KeyringTestingHelper.diffKeyrings(
|
||||
resultA.getEncoded(), resultB.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
}
|
||||
|
||||
final UncachedKeyRing pubA = a.extractPublicKeyRing();
|
||||
final UncachedKeyRing pubB = b.extractPublicKeyRing();
|
||||
|
||||
{ // sec + pub, pub + sec, and pub + pub
|
||||
|
||||
try {
|
||||
resultB = a.merge(pubB, log, 0);
|
||||
Assert.assertNotNull("merge must succeed as sec(a)+pub(b)", resultA);
|
||||
|
||||
Assert.assertFalse("result of sec(a)+pub(b) must be same as sec(a)+sec(b)",
|
||||
KeyringTestingHelper.diffKeyrings(
|
||||
resultA.getEncoded(), resultB.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
} catch (RuntimeException e) {
|
||||
System.out.println("special case, dummy key generation not in yet");
|
||||
}
|
||||
|
||||
final UncachedKeyRing pubResult = resultA.extractPublicKeyRing();
|
||||
|
||||
resultB = pubA.merge(b, log, 0);
|
||||
Assert.assertNotNull("merge must succeed as pub(a)+sec(b)", resultA);
|
||||
|
||||
Assert.assertFalse("result of pub(a)+sec(b) must be same as pub(sec(a)+sec(b))",
|
||||
KeyringTestingHelper.diffKeyrings(
|
||||
pubResult.getEncoded(), resultB.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
|
||||
resultB = pubA.merge(pubB, log, 0);
|
||||
Assert.assertNotNull("merge must succeed as pub(a)+pub(b)", resultA);
|
||||
|
||||
Assert.assertFalse("result of pub(a)+pub(b) must be same as pub(sec(a)+sec(b))",
|
||||
KeyringTestingHelper.diffKeyrings(
|
||||
pubResult.getEncoded(), resultB.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if (base != null) {
|
||||
// set up onlyA and onlyB to be a diff to the base
|
||||
Assert.assertTrue("merged keyring must differ from base",
|
||||
KeyringTestingHelper.diffKeyrings(
|
||||
base.getEncoded(), resultA.getEncoded(), onlyA, onlyB)
|
||||
);
|
||||
}
|
||||
|
||||
return resultA;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
package org.sufficientlysecure.keychain.tests;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
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.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.service.OperationResultParcel;
|
||||
import org.sufficientlysecure.keychain.service.OperationResults.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@org.robolectric.annotation.Config(emulateSdk = 18) // Robolectric doesn't yet support 19
|
||||
public class UncachedKeyringTest {
|
||||
|
||||
static UncachedKeyRing staticRing, staticPubRing;
|
||||
UncachedKeyRing ring, pubRing;
|
||||
ArrayList<RawPacket> onlyA = new ArrayList<RawPacket>();
|
||||
ArrayList<RawPacket> onlyB = new ArrayList<RawPacket>();
|
||||
PgpKeyOperation op;
|
||||
SaveKeyringParcel parcel;
|
||||
|
||||
@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);
|
||||
|
||||
EditKeyResult result = op.createSecretKeyRing(parcel);
|
||||
staticRing = result.getRing();
|
||||
staticPubRing = staticRing.extractPublicKeyRing();
|
||||
|
||||
Assert.assertNotNull("initial test key creation must succeed", staticRing);
|
||||
|
||||
// we sleep here for a second, to make sure all new certificates have different timestamps
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
// show Log.x messages in system.out
|
||||
ShadowLog.stream = System.out;
|
||||
ring = staticRing;
|
||||
pubRing = staticPubRing;
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testPublicKeyItRemove() throws Exception {
|
||||
Iterator<UncachedPublicKey> it = ring.getPublicKeys();
|
||||
it.remove();
|
||||
}
|
||||
|
||||
@Test(expected = PgpGeneralException.class)
|
||||
public void testDecodeFromEmpty() throws Exception {
|
||||
UncachedKeyRing.decodeFromData(new byte[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArmorIdentity() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ring.encodeArmored(out, "OpenKeychain");
|
||||
|
||||
Assert.assertArrayEquals("armor encoded and decoded ring should be identical to original",
|
||||
ring.getEncoded(),
|
||||
UncachedKeyRing.decodeFromData(out.toByteArray()).getEncoded());
|
||||
}
|
||||
|
||||
@Test(expected = PgpGeneralException.class)
|
||||
public void testDecodeEncodeMulti() throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
// encode secret and public ring in here
|
||||
ring.encodeArmored(out, "OpenKeychain");
|
||||
pubRing.encodeArmored(out, "OpenKeychain");
|
||||
|
||||
Iterator<UncachedKeyRing> it =
|
||||
UncachedKeyRing.fromStream(new ByteArrayInputStream(out.toByteArray()));
|
||||
Assert.assertTrue("there should be two rings in the stream", it.hasNext());
|
||||
Assert.assertArrayEquals("first ring should be the first we put in",
|
||||
ring.getEncoded(), it.next().getEncoded());
|
||||
Assert.assertTrue("there should be two rings in the stream", it.hasNext());
|
||||
Assert.assertArrayEquals("second ring should be the second we put in",
|
||||
pubRing.getEncoded(), it.next().getEncoded());
|
||||
Assert.assertFalse("there should be two rings in the stream", it.hasNext());
|
||||
|
||||
// this should fail with PgpGeneralException, since it expects exactly one ring
|
||||
UncachedKeyRing.decodeFromData(out.toByteArray());
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void testPublicExtractPublic() throws Exception {
|
||||
// can't do this, either!
|
||||
pubRing.extractPublicKeyRing();
|
||||
}
|
||||
|
||||
}
|
13
OpenKeychain-Test/src/test/resources/OpenPGP-Haskell/COPYING
Normal file
13
OpenKeychain-Test/src/test/resources/OpenPGP-Haskell/COPYING
Normal file
@ -0,0 +1,13 @@
|
||||
Copyright © 2011, Stephen Paul Weber <singpolyma.net>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
26
OpenKeychain-Test/src/test/resources/OpenPGP-Haskell/README
Normal file
26
OpenKeychain-Test/src/test/resources/OpenPGP-Haskell/README
Normal file
@ -0,0 +1,26 @@
|
||||
These test files were copied from the OpenPGP Haskell project.
|
||||
|
||||
|
||||
|
||||
Original README
|
||||
===============
|
||||
|
||||
This is an OpenPGP library inspired by my work on OpenPGP libraries in
|
||||
Ruby <https://github.com/singpolyma/openpgp>,
|
||||
PHP <http://github.com/singpolyma/openpgp-php>,
|
||||
and Python <https://github.com/singpolyma/OpenPGP-Python>.
|
||||
|
||||
It defines types to represent OpenPGP messages as a series of packets
|
||||
and then defines instances of Data.Binary for each to facilitate
|
||||
encoding/decoding.
|
||||
|
||||
For performing cryptography, see
|
||||
<http://hackage.haskell.org/package/openpgp-crypto-api> or
|
||||
<http://hackage.haskell.org/package/openpgp-Crypto>
|
||||
|
||||
For dealing with ASCII armor, see
|
||||
<http://hackage.haskell.org/package/openpgp-asciiarmor>
|
||||
|
||||
It is intended that you use qualified imports with this library.
|
||||
|
||||
> import qualified Data.OpenPGP as OpenPGP
|
Binary file not shown.
@ -0,0 +1 @@
|
||||
´$Test Key (RSA) <testkey@example.org>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
´$Test Key (DSA) <testkey@example.com>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
´+Test Key (DSA sign-only) <test@example.net>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
´.Test Key (RSA sign-only) <testkey@example.net>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
´$Test Key (RSA) <testkey@example.org>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
´$Test Key (DSA) <testkey@example.com>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
´+Test Key (DSA sign-only) <test@example.net>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
´.Test Key (RSA sign-only) <testkey@example.net>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user