Initial commit

This commit is contained in:
Paul Schaub 2022-04-05 19:33:20 +02:00
commit 2f2c6a93c9
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
28 changed files with 1050 additions and 0 deletions

33
.gitignore vendored Normal file
View file

@ -0,0 +1,33 @@
# SPDX-FileCopyrightText: 2021 Paul Schaub <info@pgpainless.org>
#
# SPDX-License-Identifier: CC0-1.0
.idea
.gradle
out/
build/
bin/
libs/
*/build
*.iws
*.iml
*.ipr
*.class
*.log
*.jar
gradle.properties
!gradle-wrapper.jar
.classpath
.project
.settings/
pgpainless-core/.classpath
pgpainless-core/.project
pgpainless-core/.settings/
push_html.sh

258
build.gradle Normal file
View file

@ -0,0 +1,258 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <info@pgpainless.org>
//
// SPDX-License-Identifier: Apache-2.0
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
mavenLocal()
mavenCentral()
}
dependencies {
classpath "gradle.plugin.org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.12.0"
}
}
plugins {
id 'ru.vyarus.animalsniffer' version '1.5.3'
}
apply from: 'version.gradle'
allprojects {
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'eclipse'
apply plugin: 'jacoco'
apply plugin: 'checkstyle'
// For non-cli modules enable android api compatibility check
if (it.name.equals('vks-java')) {
// animalsniffer
apply plugin: 'ru.vyarus.animalsniffer'
dependencies {
signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:2.3.3_r2@signature"
}
animalsniffer {
sourceSets = [sourceSets.main]
}
}
// Only generate jar for submodules
// https://stackoverflow.com/a/25445035
jar {
onlyIf { !sourceSets.main.allSource.files.isEmpty() }
}
// checkstyle
checkstyle {
toolVersion = '8.18'
}
group 'org.pgpainless'
description = "Verifying Key Server Client API for Java"
version = shortVersion
sourceCompatibility = javaSourceCompatibility
repositories {
mavenCentral()
}
// Reproducible Builds
tasks.withType(AbstractArchiveTask) {
preserveFileTimestamps = false
reproducibleFileOrder = true
}
project.ext {
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'))
isReleaseVersion = !isSnapshot
signingRequired = !(isSnapshot || isContinuousIntegrationEnvironment)
sonatypeCredentialsAvailable = project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePassword')
sonatypeSnapshotUrl = 'https://oss.sonatype.org/content/repositories/snapshots'
sonatypeStagingUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2'
}
if (isSnapshot) {
version = version + '-SNAPSHOT'
}
def projectDirFile = new File("$projectDir")
if (!project.ext.isSnapshot && !'git describe --exact-match HEAD'.execute(null, projectDirFile).text.trim().equals(ext.shortVersion)) {
throw new InvalidUserDataException('Untagged version detected! Please tag every release.')
}
if (!version.endsWith('-SNAPSHOT') && version != 'git tag --points-at HEAD'.execute(null, projectDirFile).text.trim()) {
throw new InvalidUserDataException(
'Tag mismatch detected, version is ' + version + ' but should be ' +
'git tag --points-at HEAD'.execute(null, projectDirFile).text.trim())
}
jacoco {
toolVersion = "0.8.7"
}
jacocoTestReport {
dependsOn test
sourceDirectories.setFrom(project.files(sourceSets.main.allSource.srcDirs))
classDirectories.setFrom(project.files(sourceSets.main.output))
reports {
xml.enabled true
}
}
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
exceptionFormat "full"
}
}
}
subprojects {
apply plugin: 'maven-publish'
apply plugin: 'signing'
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
from sourceSets.main.allSource
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
task testsJar(type: Jar, dependsOn: testClasses) {
classifier = 'tests'
from sourceSets.test.output
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
artifact sourcesJar
artifact javadocJar
artifact testsJar
pom {
name = 'VKS for Java'
description = 'Verifying Key Server Client API for Java'
url = 'https://github.com/pgpainless/vks-java'
inceptionYear = '2022'
scm {
url = 'https://github.com/pgpainless/vks-java'
connection = 'scm:https://github.com/pgpainless/vks-java'
developerConnection = 'scm:git://github.com/pgpainless/vks-java.git'
}
licenses {
license {
name = 'The Apache Software License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
distribution = 'repo'
}
}
developers {
developer {
id = 'vanitasvitae'
name = 'Paul Schaub'
email = 'vanitasvitae@fsfe.org'
}
}
}
}
}
repositories {
if (sonatypeCredentialsAvailable) {
maven {
url isSnapshot ? sonatypeSnapshotUrl : sonatypeStagingUrl
credentials {
username = sonatypeUsername
password = sonatypePassword
}
}
}
}
}
signing {
useGpgCmd()
required { signingRequired }
sign publishing.publications.mavenJava
}
}
def getGitCommit() {
def projectDirFile = new File("$projectDir")
def dotGit = new File("$projectDir/.git")
if (!dotGit.isDirectory()) return 'non-git build'
def cmd = 'git describe --always --tags --dirty=+'
def proc = cmd.execute(null, projectDirFile)
def gitCommit = proc.text.trim()
assert !gitCommit.isEmpty()
def srCmd = 'git symbolic-ref --short HEAD'
def srProc = srCmd.execute(null, projectDirFile)
srProc.waitForOrKill(10 * 1000)
if (srProc.exitValue() == 0) {
// Only add the information if the git command was
// successful. There may be no symbolic reference for HEAD if
// e.g. in detached mode.
def symbolicReference = srProc.text.trim()
assert !symbolicReference.isEmpty()
gitCommit += "-$symbolicReference"
}
gitCommit
}
apply plugin: "com.github.kt3k.coveralls"
coveralls {
sourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs).files.absolutePath
}
task jacocoRootReport(type: JacocoReport) {
dependsOn = subprojects.jacocoTestReport
sourceDirectories.setFrom(files(subprojects.sourceSets.main.allSource.srcDirs))
classDirectories.setFrom(files(subprojects.sourceSets.main.output))
executionData.setFrom(files(subprojects.jacocoTestReport.executionData))
reports {
xml.enabled true
xml.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml")
}
// We could remove the following setOnlyIf line, but then
// jacocoRootReport would silently be SKIPPED if something with
// the projectsWithUnitTests is wrong (e.g. a project is missing
// in there).
setOnlyIf { true }
}
task javadocAll(type: Javadoc) {
def currentJavaVersion = JavaVersion.current()
if (currentJavaVersion.compareTo(JavaVersion.VERSION_1_9) >= 0) {
options.addStringOption("-release", "8");
}
source subprojects.collect {project ->
project.sourceSets.main.allJava }
destinationDir = new File(buildDir, 'javadoc')
// Might need a classpath
classpath = files(subprojects.collect {project ->
project.sourceSets.main.compileClasspath})
options.linkSource = true
options.use = true
options.links = [
"https://docs.oracle.com/javase/${sourceCompatibility.getMajorVersion()}/docs/api/",
] as String[]
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Executable file
View file

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View file

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

3
settings.gradle Normal file
View file

@ -0,0 +1,3 @@
rootProject.name = 'VKS-Java'
include 'vks-java'

12
version.gradle Normal file
View file

@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <info@pgpainless.org>
//
// SPDX-License-Identifier: CC0-1.0
allprojects {
ext {
shortVersion = '0.1.0'
isSnapshot = true
minAndroidSdk = 10
javaSourceCompatibility = 1.8
}
}

31
vks-java/build.gradle Normal file
View file

@ -0,0 +1,31 @@
plugins {
id 'java-library'
}
group 'org.pgpainless'
repositories {
mavenCentral()
}
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
api("org.pgpainless:pgp-certificate-store:0.1.0")
api("org.pgpainless:pgpainless-core:1.1.4")
// @Nonnull, @Nullable...
implementation "com.google.code.findbugs:jsr305:3.0.2"
// Lombok
compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
testCompileOnly 'org.projectlombok:lombok:1.18.22'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.22'
}
test {
useJUnitPlatform()
}

View file

@ -0,0 +1,16 @@
package pgp.vks.client;
import pgp.vks.client.exception.CertNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public interface Get {
InputStream byFingerprint(String fingerprint) throws CertNotFoundException, IOException;
InputStream byKeyId(long keyId) throws CertNotFoundException;
InputStream byEmail(String email) throws CertNotFoundException;
}

View file

@ -0,0 +1,4 @@
package pgp.vks.client;
public interface RequestVerify {
}

View file

@ -0,0 +1,11 @@
package pgp.vks.client;
import pgp.certificate_store.Certificate;
import pgp.vks.client.response.UploadResponse;
import javax.annotation.Nonnull;
public interface Upload {
UploadResponse cert(@Nonnull Certificate certificate);
}

View file

@ -0,0 +1,28 @@
package pgp.vks.client;
import javax.annotation.Nonnull;
public interface VKS {
enum Version {
v1
}
default Get get() {
return get(Version.v1);
}
Get get(@Nonnull Version version);
default Upload upload() {
return upload(Version.v1);
}
Upload upload(@Nonnull Version version);
default RequestVerify requestVerify() {
return requestVerify(Version.v1);
}
RequestVerify requestVerify(Version version);
}

View file

@ -0,0 +1,12 @@
package pgp.vks.client.exception;
public class CertNotFoundException extends Exception {
public CertNotFoundException(Throwable cause) {
super(cause);
}
public CertNotFoundException() {
}
}

View file

@ -0,0 +1,58 @@
package pgp.vks.client.impl;
import lombok.SneakyThrows;
import pgp.vks.client.Get;
import pgp.vks.client.RequestVerify;
import pgp.vks.client.Upload;
import pgp.vks.client.VKS;
import pgp.vks.client.impl.v1.GetImpl;
import pgp.vks.client.impl.v1.RequestVerifyImpl;
import pgp.vks.client.impl.v1.UploadImpl;
import pgp.vks.client.impl.v1.V1API;
import javax.annotation.Nonnull;
import java.net.URL;
public class VKSImpl implements VKS {
private final V1API api;
public VKSImpl(URL vksService) {
this.api = new V1API(vksService);
}
@SneakyThrows
public static VKS keysDotOpenPgpDotOrg() {
return new VKSImpl(new URL("https://keys.openpgp.org"));
}
@Override
public Get get(@Nonnull Version version) {
switch (version) {
case v1:
return new GetImpl(api);
default:
throw new IllegalArgumentException("Invalid version: " + version);
}
}
@Override
public Upload upload(@Nonnull Version version) {
switch (version) {
case v1:
return new UploadImpl();
default:
throw new IllegalArgumentException("Invalid version: " + version);
}
}
@Override
public RequestVerify requestVerify(Version version) {
switch (version) {
case v1:
return new RequestVerifyImpl();
default:
throw new IllegalArgumentException("Invalid version: " + version);
}
}
}

View file

@ -0,0 +1,44 @@
package pgp.vks.client.impl.v1;
import pgp.vks.client.Get;
import pgp.vks.client.exception.CertNotFoundException;
import javax.net.ssl.HttpsURLConnection;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class GetImpl implements Get {
private final V1API api;
public GetImpl(V1API api) {
this.api = api;
}
@Override
public InputStream byFingerprint(String fingerprint) throws CertNotFoundException, IOException {
URL url = api.getByFingerprint(fingerprint);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int status = connection.getResponseCode();
if (status == 200) {
return connection.getInputStream();
} else if (status == 404) {
throw new CertNotFoundException();
} else {
throw new IllegalStateException("Unhandled status code: " + status);
}
}
@Override
public InputStream byKeyId(long keyId) throws CertNotFoundException {
return null;
}
@Override
public InputStream byEmail(String email) throws CertNotFoundException {
return null;
}
}

View file

@ -0,0 +1,6 @@
package pgp.vks.client.impl.v1;
import pgp.vks.client.RequestVerify;
public class RequestVerifyImpl implements RequestVerify {
}

View file

@ -0,0 +1,14 @@
package pgp.vks.client.impl.v1;
import pgp.certificate_store.Certificate;
import pgp.vks.client.Upload;
import pgp.vks.client.response.UploadResponse;
import javax.annotation.Nonnull;
public class UploadImpl implements Upload {
@Override
public UploadResponse cert(@Nonnull Certificate certificate) {
return null;
}
}

View file

@ -0,0 +1,57 @@
package pgp.vks.client.impl.v1;
import lombok.SneakyThrows;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
/**
* URL mapper for the VKS API.
*
* @see <a href="https://keys.openpgp.org/about/api">VKS API Documentatioon</a>
*/
public class V1API {
private static final String GET_BY_FINGERPRINT = "/vks/v1/by-fingerprint/";
private static final String GET_BY_KEYID = "/vks/v1/by-keyid/";
private static final String GET_BY_EMAIL = "/vks/v1/by-email/";
private static final String POST_UPLOAD = "/vks/v1/upload";
private static final String POST_REQUEST_VERIFY = "/vks/v1/request-verify";
private final URL serviceUrl;
public V1API(URL serviceUrl) {
this.serviceUrl = serviceUrl;
}
public URL getByFingerprint(String fingerprint) {
return getUrl(GET_BY_FINGERPRINT, fingerprint.toUpperCase());
}
public URL getByKeyid(long keyId) {
return getUrl(GET_BY_KEYID, Long.toHexString(keyId).toUpperCase());
}
public URL getByEmail(String email) {
try {
return getUrl(GET_BY_EMAIL, URLEncoder.encode(email, "UTF-8"));
} catch (UnsupportedEncodingException e) {
// UTF8 is supported anywhere
throw new AssertionError(e);
}
}
public URL postUpload() {
return getUrl(POST_UPLOAD, null);
}
public URL postRequestVerify() {
return getUrl(POST_REQUEST_VERIFY, null);
}
@SneakyThrows
private URL getUrl(String path, String param) {
return new URL(serviceUrl.getProtocol(), serviceUrl.getHost(), serviceUrl.getPort(), serviceUrl.getPath() + path + (param != null ? param : ""), null);
}
}

View file

@ -0,0 +1,17 @@
package pgp.vks.client.request;
import javax.annotation.Nonnull;
import java.util.List;
public class RequestVerifyRequest {
private final String token;
private final List<String> addresses;
private final List<String> locale;
public RequestVerifyRequest(@Nonnull String token, @Nonnull List<String> addresses, @Nonnull List<String> locale) {
this.token = token;
this.addresses = addresses;
this.locale = locale;
}
}

View file

@ -0,0 +1,12 @@
package pgp.vks.client.request;
import javax.annotation.Nonnull;
public class UploadRequest {
private final String keytext;
public UploadRequest(@Nonnull String keytext) {
this.keytext = keytext;
}
}

View file

@ -0,0 +1,16 @@
package pgp.vks.client.response;
import javax.annotation.Nonnull;
public class ErrorResponse {
private final String error;
public ErrorResponse(@Nonnull String error) {
this.error = error;
}
public String getError() {
return error;
}
}

View file

@ -0,0 +1,17 @@
package pgp.vks.client.response;
import javax.annotation.Nonnull;
import java.util.Map;
public class RequestVerifyResponse {
private final String key_fpr;
private final Map<String, Status> status;
private final String token;
public RequestVerifyResponse(@Nonnull String key_fpr, @Nonnull Map<String, Status> status, @Nonnull String token) {
this.key_fpr = key_fpr;
this.status = status;
this.token = token;
}
}

View file

@ -0,0 +1,8 @@
package pgp.vks.client.response;
enum Status {
unpublished,
published,
revoked,
pending
}

View file

@ -0,0 +1,17 @@
package pgp.vks.client.response;
import java.util.Map;
public class UploadResponse {
private final String key_fpr;
private final Map<String, Status> status;
private final String token;
public UploadResponse(String key_fpr, Map<String, Status> status, String token) {
this.key_fpr = key_fpr;
this.status = status;
this.token = token;
}
}

View file

@ -0,0 +1,57 @@
package pgp.vks.client.impl.v1;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.net.MalformedURLException;
import java.net.URL;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class V1APITest {
private static V1API api;
@BeforeAll
static void prepare() throws MalformedURLException {
api = new V1API(new URL("https://keys.openpgp.org"));
}
@Test
void testGetByFingerprint() {
String fingerprint = "7F9116FEA90A5983936C7CFAA027DB2F3E1E118A";
URL url = api.getByFingerprint(fingerprint);
assertEquals("https://keys.openpgp.org/vks/v1/by-fingerprint/7F9116FEA90A5983936C7CFAA027DB2F3E1E118A", url.toString());
}
@Test
public void testGetByKeyId() {
long keyId = -6906310507597262454L;
URL url = api.getByKeyid(keyId);
assertEquals("https://keys.openpgp.org/vks/v1/by-keyid/A027DB2F3E1E118A", url.toString());
}
@Test
public void testGetByEmail() {
String email = "vanitasvitae@fsfe.org";
URL url = api.getByEmail(email);
assertEquals("https://keys.openpgp.org/vks/v1/by-email/vanitasvitae%40fsfe.org", url.toString());
}
@Test
public void testPostUpload() {
URL url = api.postUpload();
assertEquals("https://keys.openpgp.org/vks/v1/upload", url.toString());
}
@Test
public void testPostRequestVerify() {
URL url = api.postRequestVerify();
assertEquals("https://keys.openpgp.org/vks/v1/request-verify", url.toString());
}
}

View file

@ -0,0 +1,36 @@
package pgp.vks.client.impl.v1;
import lombok.SneakyThrows;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import pgp.vks.client.VKS;
import pgp.vks.client.exception.CertNotFoundException;
import pgp.vks.client.impl.VKSImpl;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class VKSTest {
private static VKS vks;
@BeforeAll
static void prepare() {
vks = VKSImpl.keysDotOpenPgpDotOrg();
}
@Test
public void testGetByFingerprint() throws CertNotFoundException, IOException {
InputStream inputStream = vks.get().byFingerprint("7F9116FEA90A5983936C7CFAA027DB2F3E1E118A");
Streams.pipeAll(inputStream, System.out);
}
@Test
public void testGetByFingerprint_inexistent() {
assertThrows(CertNotFoundException.class, () ->
vks.get().byFingerprint("0000000000000000000000000000000000000000"));
}
}

View file

@ -0,0 +1,4 @@
package pgp.vks.client.impl.v1.dummy_vks;
public class DummyVks {
}