mirror of
https://codeberg.org/PGPainless/wkd-java.git
synced 2024-12-03 12:42:08 +01:00
Add wkd-test-suite module
This commit is contained in:
parent
79af7ccab6
commit
049c14b779
12 changed files with 677 additions and 2 deletions
|
@ -73,6 +73,7 @@ allprojects {
|
|||
junitVersion = '5.8.2'
|
||||
slf4jVersion = '1.7.32'
|
||||
logbackVersion = '1.2.10'
|
||||
pgpainlessVersion = '1.1.1'
|
||||
rootConfigDir = new File(rootDir, 'config')
|
||||
gitCommit = getGitCommit()
|
||||
isContinuousIntegrationEnvironment = Boolean.parseBoolean(System.getenv('CI'))
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
rootProject.name = 'WKD-Java'
|
||||
|
||||
include 'wkd-java',
|
||||
'wkd-java-cli'
|
||||
'wkd-java-cli',
|
||||
'wkd-test-suite'
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ dependencies {
|
|||
testImplementation 'com.ginsberg:junit5-system-exit:1.1.2'
|
||||
testImplementation 'org.mockito:mockito-core:4.3.1'
|
||||
|
||||
implementation("org.pgpainless:pgpainless-core:1.1.0")
|
||||
implementation("org.pgpainless:pgpainless-core:$pgpainlessVersion")
|
||||
implementation project(':wkd-java')
|
||||
implementation "info.picocli:picocli:4.6.3"
|
||||
|
||||
|
|
78
wkd-test-suite/README.md
Normal file
78
wkd-test-suite/README.md
Normal file
|
@ -0,0 +1,78 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
# WKD Test Suite Generator
|
||||
|
||||
This module contains a CLI application that can be used to generate WKD test vectors.
|
||||
|
||||
```shell
|
||||
$ java -jar wkd-test-suite.jar help
|
||||
Usage: wkd-test-suite [-hV] [--json-summary[=<jsonOutputFiles>]]...
|
||||
[--xml-summary[=<xmlOutputFiles>]]... -d=<domain>
|
||||
[-m={direct|advanced}] -o=<rootDir>
|
||||
-d, --domain=<domain> Root domain
|
||||
-h, --help Show this help message and exit.
|
||||
--json-summary[=<jsonOutputFiles>]
|
||||
Write JSON summary to file
|
||||
-m, --method={direct|advanced}
|
||||
Method for key discovery
|
||||
-o, --output-dir=<rootDir>
|
||||
Output directory
|
||||
-V, --version Print version information and exit.
|
||||
--xml-summary[=<xmlOutputFiles>]
|
||||
Write XML summary to file
|
||||
```
|
||||
|
||||
Example output summary.json:
|
||||
|
||||
```json
|
||||
{
|
||||
"version" : "0.1",
|
||||
"testCases" : [ {
|
||||
"expectSuccess" : true,
|
||||
"testTitle" : "Base Case",
|
||||
"testDescription" : "Certificate has a single, valid user-id 'WKD-Test Base Case <base-case@pgpainless.github.io>'",
|
||||
"lookupMailAddress" : "base-case@pgpainless.github.io",
|
||||
"certificatePath" : ".well-known/openpgpkey/hu/6q1ubufxsqh8fjuewbachy5ocz9seanp",
|
||||
"lookupUri" : "https://pgpainless.github.io/.well-known/openpgpkey/hu/6q1ubufxsqh8fjuewbachy5ocz9seanp?l=base-case"
|
||||
}, {
|
||||
"expectSuccess" : false,
|
||||
"testTitle" : "Wrong User-ID",
|
||||
"testDescription" : "Certificate has a single, valid user-id 'WKD-Test Different User-ID <different-userid@pgpainless.github.io>', but is deposited for mail address 'wrong-userid@pgpainless.github.io'.",
|
||||
"lookupMailAddress" : "wrong-userid@pgpainless.github.io",
|
||||
"certificatePath" : ".well-known/openpgpkey/hu/87rxmyhh4paokf1apw6qiej8hk6nwuxy",
|
||||
"lookupUri" : "https://pgpainless.github.io/.well-known/openpgpkey/hu/87rxmyhh4paokf1apw6qiej8hk6nwuxy?l=wrong-userid"
|
||||
}, {
|
||||
"expectSuccess" : false,
|
||||
"testTitle" : "No User-ID",
|
||||
"testDescription" : "Certificate has no user-id, but is deposited for mail address 'absent-userid@pgpainless.github.io'.",
|
||||
"lookupMailAddress" : "absent-userid@pgpainless.github.io",
|
||||
"certificatePath" : ".well-known/openpgpkey/hu/caky1x1mawkc6gg4kge1icod96wqaeax",
|
||||
"lookupUri" : "https://pgpainless.github.io/.well-known/openpgpkey/hu/caky1x1mawkc6gg4kge1icod96wqaeax?l=absent-userid"
|
||||
}, {
|
||||
"expectSuccess" : true,
|
||||
"testTitle" : "Multi-User-ID - Primary User-ID Lookup",
|
||||
"testDescription" : "Certificate has multiple, valid user-ids. Is looked up via primary user-id 'WKD-Test Primary User-ID <primary-uid@pgpainless.github.io>' using mail address 'primary-uid@pgpainless.github.io'.",
|
||||
"lookupMailAddress" : "primary-uid@pgpainless.github.io",
|
||||
"certificatePath" : ".well-known/openpgpkey/hu/iz5jxf9oi1mbc1p45s3nxcuxn38qazkw",
|
||||
"lookupUri" : "https://pgpainless.github.io/.well-known/openpgpkey/hu/iz5jxf9oi1mbc1p45s3nxcuxn38qazkw?l=primary-uid"
|
||||
}, {
|
||||
"expectSuccess" : true,
|
||||
"testTitle" : "Multi-User-ID - Secondary User-ID Lookup",
|
||||
"testDescription" : "Certificate has multiple, valid user-ids. Is looked up via secondary user-id 'WKD-Test Secondary User-ID <secondary-uid@pgpainless.github.io>' using mail address 'secondary-uid@pgpainless.github.io'.",
|
||||
"lookupMailAddress" : "secondary-uid@pgpainless.github.io",
|
||||
"certificatePath" : ".well-known/openpgpkey/hu/34i6oasjuzeunw5uwam7yqbtit1rtmjp",
|
||||
"lookupUri" : "https://pgpainless.github.io/.well-known/openpgpkey/hu/34i6oasjuzeunw5uwam7yqbtit1rtmjp?l=secondary-uid"
|
||||
}, {
|
||||
"expectSuccess" : false,
|
||||
"testTitle" : "Secret Key Material",
|
||||
"testDescription" : "Certificate file contains secret key material.",
|
||||
"lookupMailAddress" : "test-secret-key@pgpainless.github.io",
|
||||
"certificatePath" : ".well-known/openpgpkey/hu/4uoqyth19ibwszqjaokiafhxc5sh6usu",
|
||||
"lookupUri" : "https://pgpainless.github.io/.well-known/openpgpkey/hu/4uoqyth19ibwszqjaokiafhxc5sh6usu?l=test-secret-key"
|
||||
} ]
|
||||
}
|
||||
```
|
54
wkd-test-suite/build.gradle
Normal file
54
wkd-test-suite/build.gradle
Normal file
|
@ -0,0 +1,54 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
plugins {
|
||||
id 'application'
|
||||
}
|
||||
|
||||
group 'org.pgpainless'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Testing
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||
|
||||
// Logging
|
||||
implementation "org.slf4j:slf4j-api:$slf4jVersion"
|
||||
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
|
||||
// WKD and OpenPGP
|
||||
implementation project (":wkd-java")
|
||||
implementation "org.pgpainless:pgpainless-core:$pgpainlessVersion"
|
||||
|
||||
// CLI
|
||||
implementation "info.picocli:picocli:4.6.3"
|
||||
|
||||
// Object Mapping
|
||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.13.1'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.1'
|
||||
}
|
||||
mainClassName = 'pgp.wkd.test_suite.Main'
|
||||
|
||||
jar {
|
||||
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
|
||||
manifest {
|
||||
attributes 'Main-Class': "$mainClassName"
|
||||
}
|
||||
|
||||
from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
} {
|
||||
exclude "META-INF/*.SF"
|
||||
exclude "META-INF/*.DSA"
|
||||
exclude "META-INF/*.RSA"
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
122
wkd-test-suite/src/main/java/pgp/wkd/test_suite/Main.java
Normal file
122
wkd-test-suite/src/main/java/pgp/wkd/test_suite/Main.java
Normal file
|
@ -0,0 +1,122 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.wkd.test_suite;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@CommandLine.Command(name = "wkd-test-suite", mixinStandardHelpOptions = true, version = "0.1")
|
||||
public class Main implements Runnable {
|
||||
|
||||
private static final Pattern PATTERN_DOMAIN = Pattern.compile("^[a-zA-Z0-9.-]+$");
|
||||
|
||||
@CommandLine.Option(names = {"--output-dir", "-o"},
|
||||
description = "Output directory",
|
||||
required = true)
|
||||
private File rootDir;
|
||||
|
||||
@CommandLine.Option(names = "--xml-summary",
|
||||
description = "Write XML summary to file",
|
||||
arity = "0..1")
|
||||
private List<File> xmlOutputFiles = new ArrayList<>();
|
||||
|
||||
@CommandLine.Option(names = "--json-summary",
|
||||
description = "Write JSON summary to file",
|
||||
arity = "0..1")
|
||||
private List<File> jsonOutputFiles = new ArrayList<>();
|
||||
|
||||
@CommandLine.Option(names = {"--domain", "-d"},
|
||||
description = "Root domain",
|
||||
required = true, arity = "1")
|
||||
private String domain;
|
||||
|
||||
@CommandLine.Option(names = {"--method", "-m"},
|
||||
paramLabel = "{direct|advanced}",
|
||||
description = "Method for key discovery. If absent, assume direct.")
|
||||
private TestSuiteGenerator.Method method = TestSuiteGenerator.Method.direct;
|
||||
|
||||
@CommandLine.Spec // injected by picocli
|
||||
CommandLine.Model.CommandSpec spec;
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.exit(new CommandLine(new Main()).execute(args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
validate();
|
||||
|
||||
TestSuiteGenerator generator = new TestSuiteGenerator(domain);
|
||||
try {
|
||||
TestSuite suite = generator.generateTestSuiteInDirectory(rootDir, method);
|
||||
writeSummaries(suite);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
if (missing(xmlOutputFiles) && missing(jsonOutputFiles)) {
|
||||
throw new CommandLine.ParameterException(spec.commandLine(),
|
||||
"Missing option. At least on of '--xml-summary' or '--json-summary' options must be specified.");
|
||||
}
|
||||
if (!PATTERN_DOMAIN.matcher(domain).matches()) {
|
||||
throw new CommandLine.ParameterException(spec.commandLine(),
|
||||
"Value of option '--domain' must be a valid domain string.");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean missing(List<?> list) {
|
||||
return list == null || list.isEmpty();
|
||||
}
|
||||
|
||||
private void writeSummaries(TestSuite suite) {
|
||||
ObjectMapper xmlMapper = new XmlMapper();
|
||||
xmlMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
||||
|
||||
ObjectMapper jsonMapper = new JsonMapper();
|
||||
jsonMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
||||
|
||||
ObjectWriter xmlWriter = xmlMapper.writer().withDefaultPrettyPrinter();
|
||||
for (File destination : xmlOutputFiles) {
|
||||
writeSummary(suite, destination, xmlWriter);
|
||||
}
|
||||
|
||||
ObjectWriter jsonWriter = jsonMapper.writer().withDefaultPrettyPrinter();
|
||||
for (File destination : jsonOutputFiles) {
|
||||
writeSummary(suite, destination, jsonWriter);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeSummary(TestSuite suite, File destination, ObjectWriter objWriter) {
|
||||
try {
|
||||
destination.createNewFile();
|
||||
} catch (IOException e) {
|
||||
// Skip?
|
||||
return;
|
||||
}
|
||||
|
||||
try (FileOutputStream fileOut = new FileOutputStream(destination); OutputStreamWriter osWriter = new OutputStreamWriter(fileOut, Charset.forName("UTF8"))) {
|
||||
objWriter.writeValue(osWriter, suite);
|
||||
} catch (IOException e) {
|
||||
// Skip?
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.wkd.test_suite;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class TestCase {
|
||||
|
||||
final boolean expectSuccess;
|
||||
final String testTitle;
|
||||
final String testDescription;
|
||||
final String lookupMailAddress;
|
||||
final String certificatePath;
|
||||
final URI lookupUri;
|
||||
|
||||
public TestCase(boolean expectSuccess, String testTitle, String description, String lookupMailAddress, Path certificatePath, URI lookupUri) {
|
||||
this.expectSuccess = expectSuccess;
|
||||
this.testTitle = testTitle;
|
||||
this.testDescription = description;
|
||||
this.lookupMailAddress = lookupMailAddress;
|
||||
this.certificatePath = certificatePath.toString();
|
||||
this.lookupUri = lookupUri;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.wkd.test_suite;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TestSuite {
|
||||
|
||||
final String version;
|
||||
final List<TestCase> testCases;
|
||||
|
||||
public TestSuite(String version, List<TestCase> testCases) {
|
||||
this.version = version;
|
||||
this.testCases = testCases;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.wkd.test_suite;
|
||||
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class TestSuiteGenerator {
|
||||
|
||||
public enum Method {
|
||||
direct,
|
||||
advanced
|
||||
}
|
||||
|
||||
private final String domain;
|
||||
|
||||
public TestSuiteGenerator(String domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
public TestSuite generateTestSuiteInDirectory(File directory, Method method) throws Exception {
|
||||
WkdDirectoryStructure structure = directoryStructureForMethod(directory, method);
|
||||
structure.mkdirs();
|
||||
|
||||
List<TestCase> tests = new ArrayList<>();
|
||||
tests.add(baseCase(structure));
|
||||
tests.add(wrongUserId(structure));
|
||||
tests.add(noUserId(structure));
|
||||
tests.addAll(baseCaseMultiUserIds(structure));
|
||||
tests.add(secretKeyMaterial(structure));
|
||||
|
||||
return new TestSuite("0.1", tests);
|
||||
}
|
||||
|
||||
private WkdDirectoryStructure directoryStructureForMethod(File directory, Method method) {
|
||||
WkdDirectoryStructure structure;
|
||||
if (method == Method.direct) {
|
||||
structure = new WkdDirectoryStructure.DirectMethod(directory, domain);
|
||||
} else if (method == Method.advanced) {
|
||||
structure = new WkdDirectoryStructure.AdvancedMethod(directory, domain);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid value for parameter 'method'.");
|
||||
}
|
||||
return structure;
|
||||
}
|
||||
|
||||
private PGPPublicKeyRing certificate(String userId) throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().modernKeyRing(userId, null);
|
||||
PGPPublicKeyRing publicKeys = PGPainless.extractCertificate(secretKeys);
|
||||
return publicKeys;
|
||||
}
|
||||
|
||||
private TestCase baseCase(WkdDirectoryStructure directoryStructure) throws Exception {
|
||||
String lookupMail = "base-case@" + domain;
|
||||
String userId = "WKD-Test Base Case <base-case@" + domain + ">";
|
||||
String description = "Certificate has a single, valid user-id '" + userId + "'";
|
||||
|
||||
PGPPublicKeyRing publicKeys = certificate(userId);
|
||||
URI lookupUri = directoryStructure.getAddress(lookupMail);
|
||||
Path path = directoryStructure.getRelativeCertificatePath(lookupMail);
|
||||
File file = directoryStructure.resolve(path);
|
||||
if (!file.exists() && !file.createNewFile()) {
|
||||
throw new IOException("Cannot create file " + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
try (FileOutputStream fileOut = new FileOutputStream(file)) {
|
||||
publicKeys.encode(fileOut);
|
||||
}
|
||||
|
||||
return new TestCase(true, "Base Case", description, lookupMail, path, lookupUri);
|
||||
}
|
||||
|
||||
private List<TestCase> baseCaseMultiUserIds(WkdDirectoryStructure directoryStructure) throws Exception {
|
||||
String primaryLookupMail = "primary-uid@" + domain;
|
||||
String secondaryLookupMail = "secondary-uid@" + domain;
|
||||
String primaryUserId = "WKD-Test Primary User-ID <" + primaryLookupMail + ">";
|
||||
String secondaryUserId = "WKD-Test Secondary User-ID <" + secondaryLookupMail + ">";
|
||||
String primaryDescription = "Certificate has multiple, valid user-ids. Is looked up via primary user-id '" + primaryUserId + "' using mail address '" + primaryLookupMail + "'.";
|
||||
String secondaryDescription = "Certificate has multiple, valid user-ids. Is looked up via secondary user-id '" + secondaryUserId + "' using mail address '" + secondaryLookupMail + "'.";
|
||||
|
||||
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().modernKeyRing(primaryUserId, null);
|
||||
SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys();
|
||||
secretKeys = PGPainless.modifyKeyRing(secretKeys)
|
||||
.addUserId(secondaryUserId, protector)
|
||||
.done();
|
||||
PGPPublicKeyRing publicKeys = PGPainless.extractCertificate(secretKeys);
|
||||
|
||||
Path primaryPath = directoryStructure.getRelativeCertificatePath(primaryLookupMail);
|
||||
Path secondaryPath = directoryStructure.getRelativeCertificatePath(secondaryLookupMail);
|
||||
File primaryFile = directoryStructure.resolve(primaryPath);
|
||||
File secondaryFile = directoryStructure.resolve(secondaryPath);
|
||||
|
||||
if (!primaryFile.exists() && !primaryFile.createNewFile()) {
|
||||
throw new IOException("Cannot create file " + primaryFile.getAbsolutePath());
|
||||
}
|
||||
if (!secondaryFile.exists() && !secondaryFile.createNewFile()) {
|
||||
throw new IOException("Cannot create file " + secondaryFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
try (FileOutputStream fileOut = new FileOutputStream(primaryFile)) {
|
||||
publicKeys.encode(fileOut);
|
||||
}
|
||||
try (FileOutputStream fileOut = new FileOutputStream(secondaryFile)) {
|
||||
publicKeys.encode(fileOut);
|
||||
}
|
||||
|
||||
return Arrays.asList(
|
||||
new TestCase(true, "Multi-User-ID - Primary User-ID Lookup",
|
||||
primaryDescription, primaryLookupMail, primaryPath, directoryStructure.getAddress(primaryLookupMail)),
|
||||
new TestCase(true, "Multi-User-ID - Secondary User-ID Lookup",
|
||||
secondaryDescription, secondaryLookupMail, secondaryPath, directoryStructure.getAddress(secondaryLookupMail))
|
||||
);
|
||||
}
|
||||
|
||||
private TestCase wrongUserId(WkdDirectoryStructure directoryStructure) throws Exception {
|
||||
String lookupMail = "wrong-userid@" + domain;
|
||||
String userId = "WKD-Test Different User-ID <different-userid@" + domain + ">";
|
||||
String description = "Certificate has a single, valid user-id '" + userId + "', but is deposited for mail address '" + lookupMail + "'.";
|
||||
PGPPublicKeyRing publicKeys = certificate(userId);
|
||||
Path path = directoryStructure.getRelativeCertificatePath(lookupMail);
|
||||
File file = directoryStructure.resolve(path);
|
||||
|
||||
if (!file.exists() && !file.createNewFile()) {
|
||||
throw new IOException("Cannot create file " + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
try (FileOutputStream fileOut = new FileOutputStream(file)) {
|
||||
publicKeys.encode(fileOut);
|
||||
}
|
||||
|
||||
return new TestCase(false, "Wrong User-ID", description, lookupMail, path, directoryStructure.getAddress(lookupMail));
|
||||
}
|
||||
|
||||
private TestCase noUserId(WkdDirectoryStructure directoryStructure) throws Exception {
|
||||
String lookupMail = "absent-userid@" + domain;
|
||||
String description = "Certificate has no user-id, but is deposited for mail address '" + lookupMail + "'.";
|
||||
// Generate certificate with temp user-id
|
||||
PGPPublicKeyRing publicKeys = certificate("DeleteMe");
|
||||
|
||||
// delete user-id
|
||||
List<PGPPublicKey> keys = new ArrayList<>();
|
||||
Iterator<PGPPublicKey> publicKeyIterator = publicKeys.iterator();
|
||||
PGPPublicKey primaryKey = publicKeyIterator.next();
|
||||
primaryKey = PGPPublicKey.removeCertification(primaryKey, "DeleteMe");
|
||||
keys.add(primaryKey);
|
||||
while (publicKeyIterator.hasNext()) {
|
||||
keys.add(publicKeyIterator.next());
|
||||
}
|
||||
publicKeys = new PGPPublicKeyRing(keys);
|
||||
|
||||
|
||||
Path path = directoryStructure.getRelativeCertificatePath(lookupMail);
|
||||
File file = directoryStructure.resolve(path);
|
||||
|
||||
if (!file.exists() && !file.createNewFile()) {
|
||||
throw new IOException("Cannot create file " + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
try (FileOutputStream fileOut = new FileOutputStream(file)) {
|
||||
publicKeys.encode(fileOut);
|
||||
}
|
||||
|
||||
return new TestCase(false, "No User-ID", description, lookupMail, path, directoryStructure.getAddress(lookupMail));
|
||||
}
|
||||
|
||||
private TestCase secretKeyMaterial(WkdDirectoryStructure directoryStructure) throws Exception {
|
||||
String lookupMail = "test-secret-key@" + domain;
|
||||
String description = "Certificate file contains secret key material.";
|
||||
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().modernKeyRing("WKD-Test Secret Key <" + lookupMail + ">", null);
|
||||
|
||||
Path path = directoryStructure.getRelativeCertificatePath(lookupMail);
|
||||
File file = directoryStructure.resolve(path);
|
||||
|
||||
if (!file.exists() && !file.createNewFile()) {
|
||||
throw new IOException("Cannot create file " + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
try (FileOutputStream fileOut = new FileOutputStream(file)) {
|
||||
secretKeys.encode(fileOut);
|
||||
}
|
||||
|
||||
return new TestCase(false, "Secret Key Material", description, lookupMail, path, directoryStructure.getAddress(lookupMail));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package pgp.wkd.test_suite;
|
||||
|
||||
import pgp.wkd.WKDAddress;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public abstract class WkdDirectoryStructure {
|
||||
|
||||
protected final String domain;
|
||||
protected final File rootDir;
|
||||
protected final File wellKnown;
|
||||
protected final File openpgpkey;
|
||||
|
||||
public WkdDirectoryStructure(File rootDirectory, String domain) {
|
||||
this.domain = domain;
|
||||
this.rootDir = rootDirectory;
|
||||
wellKnown = new File(rootDirectory, ".well-known");
|
||||
openpgpkey = new File(wellKnown, "openpgpkey");
|
||||
}
|
||||
|
||||
public abstract File getHu();
|
||||
|
||||
public abstract Path getRelativeCertificatePath(String mailAddress);
|
||||
|
||||
public abstract void mkdirs() throws IOException;
|
||||
|
||||
protected void mkdir(File dir) throws IOException {
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
throw new IOException("Cannot create directory '" + dir.getAbsolutePath() + "'.");
|
||||
}
|
||||
if (dir.isFile()) {
|
||||
throw new IOException("Cannot create directory '" + dir.getAbsolutePath() + "': Is a file.");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract URI getAddress(String mail);
|
||||
|
||||
public abstract File resolve(Path path);
|
||||
|
||||
public static class DirectMethod extends WkdDirectoryStructure {
|
||||
|
||||
private final File hu;
|
||||
|
||||
public DirectMethod(File rootDirectory, String domain) {
|
||||
super(rootDirectory, domain);
|
||||
this.hu = new File(openpgpkey, "hu");
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getHu() {
|
||||
return hu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getRelativeCertificatePath(String mailAddress) {
|
||||
WKDAddress address = WKDAddress.fromEmail(mailAddress);
|
||||
String path = address.getDirectMethodURI().getPath();
|
||||
String fileName = path.substring(path.lastIndexOf('/') + 1);
|
||||
return rootDir.toPath().relativize(new File(getHu(), fileName).toPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mkdirs() throws IOException {
|
||||
mkdir(rootDir);
|
||||
mkdir(wellKnown);
|
||||
mkdir(openpgpkey);
|
||||
mkdir(hu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getAddress(String mail) {
|
||||
return WKDAddress.fromEmail(mail).getDirectMethodURI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File resolve(Path path) {
|
||||
return rootDir.toPath().resolve(path).toFile();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AdvancedMethod extends WkdDirectoryStructure {
|
||||
|
||||
private final File domainFile;
|
||||
private final File hu;
|
||||
|
||||
public AdvancedMethod(File rootDir, String domain) {
|
||||
super(rootDir, domain);
|
||||
this.domainFile = new File(openpgpkey, domain);
|
||||
this.hu = new File(domainFile, "hu");
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getHu() {
|
||||
return hu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getRelativeCertificatePath(String mailAddress) {
|
||||
WKDAddress address = WKDAddress.fromEmail(mailAddress);
|
||||
String path = address.getAdvancedMethodURI().getPath();
|
||||
String fileName = path.substring(path.lastIndexOf('/') + 1);
|
||||
return rootDir.toPath().relativize(new File(getHu(), fileName).toPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mkdirs() throws IOException {
|
||||
mkdir(rootDir);
|
||||
mkdir(wellKnown);
|
||||
mkdir(openpgpkey);
|
||||
mkdir(domainFile);
|
||||
mkdir(hu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getAddress(String mail) {
|
||||
return WKDAddress.fromEmail(mail).getAdvancedMethodURI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File resolve(Path path) {
|
||||
return rootDir.toPath().resolve(path).toFile();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* WKD Test Suite Generator.
|
||||
*/
|
||||
package pgp.wkd.test_suite;
|
31
wkd-test-suite/strategy.md
Normal file
31
wkd-test-suite/strategy.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
# WKD Test Suite
|
||||
|
||||
The purpose of the WKD test suite is to generate a set of certificates which can be published to a WKD.
|
||||
The certificates cover different scenarios and edge cases and can be used to validate WKD implementations experimentally.
|
||||
|
||||
## Test Vectors
|
||||
|
||||
| Test Case | Description |
|
||||
|-----------------------------------|----------------------------------------------------------------------------------------|
|
||||
| Base Case | Certificate with a single valid user-id A identified by A |
|
||||
| Advanced Base Case | Certificate with multiple user-ids A and B identified by A, B |
|
||||
| Wrong User-ID | Certificate with a single valid user-id A identified by B |
|
||||
| Missing User-ID | Certificate without a user-id identified by A |
|
||||
| Unbound User-ID | Certificate with a single unbound user-id A identified by A |
|
||||
| Expired User-ID | Certificate with a single expired user-id A identified by A |
|
||||
| Invalidly bound User-ID | Certificate with a single user-id A with broken binding identified by A |
|
||||
| Revoked User-ID | Certificate with a single revoked user-id A identified by A |
|
||||
| Revoked Certificate | Certificate with a single user-id A, with direct-key revocation identified by A |
|
||||
| Third-Party User-ID | Certificate with an additional user-id B certified by third party, identified by B |
|
||||
| Broken Data | Certificate file contains garbage |
|
||||
| Secret Key | Certificate file contains secret key material |
|
||||
| Signatures | Certificate file contains certification signatures only |
|
||||
| Multiple Certificates | Certificate file contains multiple certificates (with valid user-id A) identified by A |
|
||||
| Armored Certificate | Certificate file contains armored certificate |
|
||||
| Duplicate mail address in user-id | Certificate contains user-id of form ".*<A>.*<B>.*" |
|
Loading…
Reference in a new issue