mirror of
https://codeberg.org/PGPainless/sop-java.git
synced 2024-12-22 04:57:56 +01:00
Merge pull request #21 from pgpainless/kotlin
Rewrite sop-java in Kotlin
This commit is contained in:
commit
a8829350a8
90 changed files with 2397 additions and 3230 deletions
17
build.gradle
17
build.gradle
|
@ -19,6 +19,8 @@ buildscript {
|
|||
|
||||
plugins {
|
||||
id 'ru.vyarus.animalsniffer' version '1.5.3'
|
||||
id 'org.jetbrains.kotlin.jvm' version "1.8.10"
|
||||
id 'com.diffplug.spotless' version '6.22.0' apply false
|
||||
}
|
||||
|
||||
apply from: 'version.gradle'
|
||||
|
@ -29,6 +31,8 @@ allprojects {
|
|||
apply plugin: 'eclipse'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'checkstyle'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'com.diffplug.spotless'
|
||||
|
||||
// For non-cli modules enable android api compatibility check
|
||||
if (it.name.equals('sop-java')) {
|
||||
|
@ -53,6 +57,12 @@ allprojects {
|
|||
toolVersion = '8.18'
|
||||
}
|
||||
|
||||
spotless {
|
||||
kotlin {
|
||||
ktfmt().dropboxStyle()
|
||||
}
|
||||
}
|
||||
|
||||
group 'org.pgpainless'
|
||||
description = "Stateless OpenPGP Protocol API for Java"
|
||||
version = shortVersion
|
||||
|
@ -69,6 +79,13 @@ allprojects {
|
|||
reproducibleFileOrder = true
|
||||
}
|
||||
|
||||
// Compatibility of default implementations in kotlin interfaces with Java implementations.
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs += ["-Xjvm-default=all-compatibility"]
|
||||
}
|
||||
}
|
||||
|
||||
project.ext {
|
||||
rootConfigDir = new File(rootDir, 'config')
|
||||
gitCommit = getGitCommit()
|
||||
|
|
|
@ -38,6 +38,7 @@ application {
|
|||
|
||||
jar {
|
||||
dependsOn(":sop-java:jar")
|
||||
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
|
||||
|
||||
manifest {
|
||||
attributes 'Main-Class': "$mainClassName"
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Tuple of a byte array and associated result object.
|
||||
* @param <T> type of result
|
||||
*/
|
||||
public class ByteArrayAndResult<T> {
|
||||
|
||||
private final byte[] bytes;
|
||||
private final T result;
|
||||
|
||||
public ByteArrayAndResult(byte[] bytes, T result) {
|
||||
this.bytes = bytes;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the byte array part.
|
||||
*
|
||||
* @return bytes
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result part.
|
||||
*
|
||||
* @return result
|
||||
*/
|
||||
public T getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the byte array part as an {@link InputStream}.
|
||||
*
|
||||
* @return input stream
|
||||
*/
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(getBytes());
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import sop.util.Optional;
|
||||
|
||||
public class DecryptionResult {
|
||||
|
||||
private final Optional<SessionKey> sessionKey;
|
||||
private final List<Verification> verifications;
|
||||
|
||||
public DecryptionResult(SessionKey sessionKey, List<Verification> verifications) {
|
||||
this.sessionKey = Optional.ofNullable(sessionKey);
|
||||
this.verifications = Collections.unmodifiableList(verifications);
|
||||
}
|
||||
|
||||
public Optional<SessionKey> getSessionKey() {
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
public List<Verification> getVerifications() {
|
||||
return new ArrayList<>(verifications);
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
public class MicAlg {
|
||||
|
||||
private final String micAlg;
|
||||
|
||||
public MicAlg(String micAlg) {
|
||||
if (micAlg == null) {
|
||||
throw new IllegalArgumentException("MicAlg String cannot be null.");
|
||||
}
|
||||
this.micAlg = micAlg;
|
||||
}
|
||||
|
||||
public static MicAlg empty() {
|
||||
return new MicAlg("");
|
||||
}
|
||||
|
||||
public static MicAlg fromHashAlgorithmId(int id) {
|
||||
switch (id) {
|
||||
case 1:
|
||||
return new MicAlg("pgp-md5");
|
||||
case 2:
|
||||
return new MicAlg("pgp-sha1");
|
||||
case 3:
|
||||
return new MicAlg("pgp-ripemd160");
|
||||
case 8:
|
||||
return new MicAlg("pgp-sha256");
|
||||
case 9:
|
||||
return new MicAlg("pgp-sha384");
|
||||
case 10:
|
||||
return new MicAlg("pgp-sha512");
|
||||
case 11:
|
||||
return new MicAlg("pgp-sha224");
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported hash algorithm ID: " + id);
|
||||
}
|
||||
}
|
||||
|
||||
public String getMicAlg() {
|
||||
return micAlg;
|
||||
}
|
||||
|
||||
public void writeTo(OutputStream outputStream) {
|
||||
PrintWriter pw = new PrintWriter(outputStream);
|
||||
pw.write(getMicAlg());
|
||||
pw.close();
|
||||
}
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
import sop.util.Optional;
|
||||
import sop.util.UTF8Util;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Tuple class bundling a profile name and description.
|
||||
*
|
||||
* @see <a href="https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-05.html#name-profile">
|
||||
* SOP Spec - Profile</a>
|
||||
*/
|
||||
public class Profile {
|
||||
|
||||
private final String name;
|
||||
private final Optional<String> description;
|
||||
|
||||
/**
|
||||
* Create a new {@link Profile} object.
|
||||
* The {@link #toString()} representation MUST NOT exceed a length of 1000 bytes.
|
||||
*
|
||||
* @param name profile name
|
||||
* @param description profile description
|
||||
*/
|
||||
public Profile(@Nonnull String name, @Nullable String description) {
|
||||
if (name.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Name cannot be empty.");
|
||||
}
|
||||
if (name.contains(":")) {
|
||||
throw new IllegalArgumentException("Name cannot contain ':'.");
|
||||
}
|
||||
if (name.contains(" ") || name.contains("\n") || name.contains("\t") || name.contains("\r")) {
|
||||
throw new IllegalArgumentException("Name cannot contain whitespace characters.");
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
|
||||
if (description == null) {
|
||||
this.description = Optional.ofEmpty();
|
||||
} else {
|
||||
String trimmedDescription = description.trim();
|
||||
if (trimmedDescription.isEmpty()) {
|
||||
this.description = Optional.ofEmpty();
|
||||
} else {
|
||||
this.description = Optional.of(trimmedDescription);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceeds1000CharLineLimit(this)) {
|
||||
throw new IllegalArgumentException("The line representation of a profile MUST NOT exceed 1000 bytes.");
|
||||
}
|
||||
}
|
||||
|
||||
public Profile(String name) {
|
||||
this(name, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a {@link Profile} from its string representation.
|
||||
*
|
||||
* @param string string representation
|
||||
* @return profile
|
||||
*/
|
||||
public static Profile parse(String string) {
|
||||
if (string.contains(": ")) {
|
||||
// description after colon, e.g. "default: Use implementers recommendations."
|
||||
String name = string.substring(0, string.indexOf(": "));
|
||||
String description = string.substring(string.indexOf(": ") + 2);
|
||||
return new Profile(name, description.trim());
|
||||
}
|
||||
|
||||
if (string.endsWith(":")) {
|
||||
// empty description, e.g. "default:"
|
||||
return new Profile(string.substring(0, string.length() - 1));
|
||||
}
|
||||
|
||||
// no description
|
||||
return new Profile(string.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name (also known as identifier) of the profile.
|
||||
* A profile name is a UTF-8 string that has no whitespace in it.
|
||||
* Similar to OpenPGP Notation names, profile names are divided into two namespaces:
|
||||
* The IETF namespace and the user namespace.
|
||||
* A profile name in the user namespace ends with the <pre>@</pre> character (0x40) followed by a DNS domain name.
|
||||
* A profile name in the IETF namespace does not have an <pre>@</pre> character.
|
||||
* A profile name in the user space is owned and controlled by the owner of the domain in the suffix.
|
||||
* A profile name in the IETF namespace that begins with the string <pre>rfc</pre> should have semantics that hew as
|
||||
* closely as possible to the referenced RFC.
|
||||
* Similarly, a profile name in the IETF namespace that begins with the string <pre>draft-</pre> should have
|
||||
* semantics that hew as closely as possible to the referenced Internet Draft.
|
||||
*
|
||||
* @return name
|
||||
*/
|
||||
@Nonnull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a free-form description of the profile.
|
||||
*
|
||||
* @return description
|
||||
*/
|
||||
@Nonnull
|
||||
public Optional<String> getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean hasDescription() {
|
||||
return description.isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the profile into a String for displaying.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if (getDescription().isEmpty()) {
|
||||
return getName();
|
||||
}
|
||||
return getName() + ": " + getDescription().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the string representation of the profile exceeds the limit of 1000 bytes length.
|
||||
* @param profile profile
|
||||
* @return <pre>true</pre> if the profile exceeds 1000 bytes, <pre>false</pre> otherwise.
|
||||
*/
|
||||
private static boolean exceeds1000CharLineLimit(Profile profile) {
|
||||
String line = profile.toString();
|
||||
return line.getBytes(UTF8Util.UTF8).length > 1000;
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public abstract class Ready {
|
||||
|
||||
/**
|
||||
* Write the data to the provided output stream.
|
||||
*
|
||||
* @param outputStream output stream
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
public abstract void writeTo(OutputStream outputStream) throws IOException;
|
||||
|
||||
/**
|
||||
* Return the data as a byte array by writing it to a {@link ByteArrayOutputStream} first and then returning
|
||||
* the array.
|
||||
*
|
||||
* @return data as byte array
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
public byte[] getBytes() throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
writeTo(bytes);
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an input stream containing the data.
|
||||
*
|
||||
* @return input stream
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return new ByteArrayInputStream(getBytes());
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public abstract class ReadyWithResult<T> {
|
||||
|
||||
/**
|
||||
* Write the data e.g. decrypted plaintext to the provided output stream and return the result of the
|
||||
* processing operation.
|
||||
*
|
||||
* @param outputStream output stream
|
||||
* @return result, eg. signatures
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws SOPGPException.NoSignature if there are no valid signatures found
|
||||
*/
|
||||
public abstract T writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature;
|
||||
|
||||
/**
|
||||
* Return the data as a {@link ByteArrayAndResult}.
|
||||
* Calling {@link ByteArrayAndResult#getBytes()} will give you access to the data as byte array, while
|
||||
* {@link ByteArrayAndResult#getResult()} will grant access to the appended result.
|
||||
*
|
||||
* @return byte array and result
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws SOPGPException.NoSignature if there are no valid signatures found
|
||||
*/
|
||||
public ByteArrayAndResult<T> toByteArrayAndResult() throws IOException, SOPGPException.NoSignature {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
T result = writeTo(bytes);
|
||||
return new ByteArrayAndResult<>(bytes.toByteArray(), result);
|
||||
}
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
import sop.operation.Armor;
|
||||
import sop.operation.ChangeKeyPassword;
|
||||
import sop.operation.Dearmor;
|
||||
import sop.operation.Decrypt;
|
||||
import sop.operation.Encrypt;
|
||||
import sop.operation.ExtractCert;
|
||||
import sop.operation.GenerateKey;
|
||||
import sop.operation.InlineDetach;
|
||||
import sop.operation.InlineSign;
|
||||
import sop.operation.InlineVerify;
|
||||
import sop.operation.DetachedSign;
|
||||
import sop.operation.DetachedVerify;
|
||||
import sop.operation.ListProfiles;
|
||||
import sop.operation.RevokeKey;
|
||||
import sop.operation.Version;
|
||||
|
||||
/**
|
||||
* Stateless OpenPGP Interface.
|
||||
* This class provides a stateless interface to various OpenPGP related operations.
|
||||
* <br>
|
||||
* Note: Subcommand objects acquired by calling any method of this interface are not intended for reuse.
|
||||
* If you for example need to generate multiple keys, make a dedicated call to {@link #generateKey()} once per
|
||||
* key generation.
|
||||
*/
|
||||
public interface SOP {
|
||||
|
||||
/**
|
||||
* Get information about the implementations name and version.
|
||||
*
|
||||
* @return version
|
||||
*/
|
||||
Version version();
|
||||
|
||||
/**
|
||||
* Generate a secret key.
|
||||
* Customize the operation using the builder {@link GenerateKey}.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
GenerateKey generateKey();
|
||||
|
||||
/**
|
||||
* Extract a certificate (public key) from a secret key.
|
||||
* Customize the operation using the builder {@link ExtractCert}.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
ExtractCert extractCert();
|
||||
|
||||
/**
|
||||
* Create detached signatures.
|
||||
* Customize the operation using the builder {@link DetachedSign}.
|
||||
* <p>
|
||||
* If you want to sign a message inline, use {@link #inlineSign()} instead.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
default DetachedSign sign() {
|
||||
return detachedSign();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create detached signatures.
|
||||
* Customize the operation using the builder {@link DetachedSign}.
|
||||
* <p>
|
||||
* If you want to sign a message inline, use {@link #inlineSign()} instead.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
DetachedSign detachedSign();
|
||||
|
||||
/**
|
||||
* Sign a message using inline signatures.
|
||||
* <p>
|
||||
* If you need to create detached signatures, use {@link #detachedSign()} instead.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
InlineSign inlineSign();
|
||||
|
||||
/**
|
||||
* Verify detached signatures.
|
||||
* Customize the operation using the builder {@link DetachedVerify}.
|
||||
* <p>
|
||||
* If you need to verify an inline-signed message, use {@link #inlineVerify()} instead.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
default DetachedVerify verify() {
|
||||
return detachedVerify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify detached signatures.
|
||||
* Customize the operation using the builder {@link DetachedVerify}.
|
||||
* <p>
|
||||
* If you need to verify an inline-signed message, use {@link #inlineVerify()} instead.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
DetachedVerify detachedVerify();
|
||||
|
||||
/**
|
||||
* Verify signatures of an inline-signed message.
|
||||
* <p>
|
||||
* If you need to verify detached signatures over a message, use {@link #detachedVerify()} instead.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
InlineVerify inlineVerify();
|
||||
|
||||
/**
|
||||
* Detach signatures from an inline signed message.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
InlineDetach inlineDetach();
|
||||
|
||||
/**
|
||||
* Encrypt a message.
|
||||
* Customize the operation using the builder {@link Encrypt}.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
Encrypt encrypt();
|
||||
|
||||
/**
|
||||
* Decrypt a message.
|
||||
* Customize the operation using the builder {@link Decrypt}.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
Decrypt decrypt();
|
||||
|
||||
/**
|
||||
* Convert binary OpenPGP data to ASCII.
|
||||
* Customize the operation using the builder {@link Armor}.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
Armor armor();
|
||||
|
||||
/**
|
||||
* Converts ASCII armored OpenPGP data to binary.
|
||||
* Customize the operation using the builder {@link Dearmor}.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
Dearmor dearmor();
|
||||
|
||||
/**
|
||||
* List supported {@link Profile Profiles} of a subcommand.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
ListProfiles listProfiles();
|
||||
|
||||
/**
|
||||
* Revoke one or more secret keys.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
RevokeKey revokeKey();
|
||||
|
||||
/**
|
||||
* Update a key's password.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
ChangeKeyPassword changeKeyPassword();
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import sop.util.HexUtil;
|
||||
|
||||
public class SessionKey {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("^(\\d):([0-9A-F]+)$");
|
||||
|
||||
private final byte algorithm;
|
||||
private final byte[] sessionKey;
|
||||
|
||||
public SessionKey(byte algorithm, byte[] sessionKey) {
|
||||
this.algorithm = algorithm;
|
||||
this.sessionKey = sessionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the symmetric algorithm octet.
|
||||
*
|
||||
* @return algorithm id
|
||||
*/
|
||||
public byte getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the session key.
|
||||
*
|
||||
* @return session key
|
||||
*/
|
||||
public byte[] getKey() {
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getAlgorithm() * 17 + Arrays.hashCode(getKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof SessionKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SessionKey otherKey = (SessionKey) other;
|
||||
return getAlgorithm() == otherKey.getAlgorithm() && Arrays.equals(getKey(), otherKey.getKey());
|
||||
}
|
||||
|
||||
public static SessionKey fromString(String string) {
|
||||
string = string.trim().toUpperCase().replace("\n", "");
|
||||
Matcher matcher = PATTERN.matcher(string);
|
||||
if (!matcher.matches()) {
|
||||
throw new IllegalArgumentException("Provided session key does not match expected format.");
|
||||
}
|
||||
byte algorithm = Byte.parseByte(matcher.group(1));
|
||||
String key = matcher.group(2);
|
||||
|
||||
return new SessionKey(algorithm, HexUtil.hexToBytes(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Integer.toString(getAlgorithm()) + ':' + HexUtil.bytesToHex(sessionKey);
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public abstract class Signatures extends Ready {
|
||||
|
||||
/**
|
||||
* Write OpenPGP signatures to the provided output stream.
|
||||
*
|
||||
* @param signatureOutputStream output stream
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Override
|
||||
public abstract void writeTo(OutputStream signatureOutputStream) throws IOException;
|
||||
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
/**
|
||||
* This class contains various information about a signed message.
|
||||
*/
|
||||
public final class SigningResult {
|
||||
|
||||
private final MicAlg micAlg;
|
||||
|
||||
private SigningResult(MicAlg micAlg) {
|
||||
this.micAlg = micAlg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string identifying the digest mechanism used to create the signed message.
|
||||
* This is useful for setting the micalg= parameter for the multipart/signed
|
||||
* content type of a PGP/MIME object as described in section 5 of [RFC3156].
|
||||
* <p>
|
||||
* If more than one signature was generated and different digest mechanisms were used,
|
||||
* the value of the micalg object is an empty string.
|
||||
*
|
||||
* @return micalg
|
||||
*/
|
||||
public MicAlg getMicAlg() {
|
||||
return micAlg;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private MicAlg micAlg;
|
||||
|
||||
public Builder setMicAlg(MicAlg micAlg) {
|
||||
this.micAlg = micAlg;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SigningResult build() {
|
||||
SigningResult signingResult = new SigningResult(micAlg);
|
||||
return signingResult;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
import sop.enums.SignatureMode;
|
||||
import sop.util.Optional;
|
||||
import sop.util.UTCUtil;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Class bundling information about a verified signature.
|
||||
*/
|
||||
public class Verification {
|
||||
|
||||
private final Date creationTime;
|
||||
private final String signingKeyFingerprint;
|
||||
private final String signingCertFingerprint;
|
||||
private final Optional<SignatureMode> signatureMode;
|
||||
private final Optional<String> description;
|
||||
|
||||
private static final String MODE = "mode:";
|
||||
|
||||
/**
|
||||
* Create a new {@link Verification} without mode and description.
|
||||
*
|
||||
* @param creationTime signature creation time
|
||||
* @param signingKeyFingerprint fingerprint of the signing (sub-) key
|
||||
* @param signingCertFingerprint fingerprint of the certificate
|
||||
*/
|
||||
public Verification(@Nonnull Date creationTime,
|
||||
@Nonnull String signingKeyFingerprint,
|
||||
@Nonnull String signingCertFingerprint) {
|
||||
this(creationTime, signingKeyFingerprint, signingCertFingerprint, Optional.ofEmpty(), Optional.ofEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Verification}.
|
||||
*
|
||||
* @param creationTime signature creation time
|
||||
* @param signingKeyFingerprint fingerprint of the signing (sub-) key
|
||||
* @param signingCertFingerprint fingerprint of the certificate
|
||||
* @param signatureMode signature mode (optional, may be <pre>null</pre>)
|
||||
* @param description free-form description, e.g. <pre>certificate from dkg.asc</pre> (optional, may be <pre>null</pre>)
|
||||
*/
|
||||
public Verification(@Nonnull Date creationTime,
|
||||
@Nonnull String signingKeyFingerprint,
|
||||
@Nonnull String signingCertFingerprint,
|
||||
@Nullable SignatureMode signatureMode,
|
||||
@Nullable String description) {
|
||||
this(
|
||||
creationTime,
|
||||
signingKeyFingerprint,
|
||||
signingCertFingerprint,
|
||||
Optional.ofNullable(signatureMode),
|
||||
Optional.ofNullable(nullSafeTrim(description))
|
||||
);
|
||||
}
|
||||
|
||||
private Verification(@Nonnull Date creationTime,
|
||||
@Nonnull String signingKeyFingerprint,
|
||||
@Nonnull String signingCertFingerprint,
|
||||
@Nonnull Optional<SignatureMode> signatureMode,
|
||||
@Nonnull Optional<String> description) {
|
||||
this.creationTime = creationTime;
|
||||
this.signingKeyFingerprint = signingKeyFingerprint;
|
||||
this.signingCertFingerprint = signingCertFingerprint;
|
||||
this.signatureMode = signatureMode;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
private static String nullSafeTrim(@Nullable String string) {
|
||||
if (string == null) {
|
||||
return null;
|
||||
}
|
||||
return string.trim();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static Verification fromString(@Nonnull String toString) {
|
||||
String[] split = toString.trim().split(" ");
|
||||
if (split.length < 3) {
|
||||
throw new IllegalArgumentException("Verification must be of the format 'UTC-DATE OpenPGPFingerprint OpenPGPFingerprint [mode] [info]'");
|
||||
}
|
||||
|
||||
if (split.length == 3) {
|
||||
return new Verification(
|
||||
parseUTCDate(split[0]), // timestamp
|
||||
split[1], // key FP
|
||||
split[2] // cert FP
|
||||
);
|
||||
}
|
||||
|
||||
SignatureMode mode = null;
|
||||
int index = 3;
|
||||
if (split[index].startsWith(MODE)) {
|
||||
mode = SignatureMode.valueOf(split[3].substring(MODE.length()));
|
||||
index++;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = index; i < split.length; i++) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(split[i]);
|
||||
}
|
||||
|
||||
return new Verification(
|
||||
parseUTCDate(split[0]), // timestamp
|
||||
split[1], // key FP
|
||||
split[2], // cert FP
|
||||
mode, // signature mode
|
||||
sb.length() != 0 ? sb.toString() : null // description
|
||||
);
|
||||
}
|
||||
|
||||
private static Date parseUTCDate(String utcFormatted) {
|
||||
try {
|
||||
return UTCUtil.parseUTCDate(utcFormatted);
|
||||
} catch (ParseException e) {
|
||||
throw new IllegalArgumentException("Malformed UTC timestamp.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the signatures' creation time.
|
||||
*
|
||||
* @return signature creation time
|
||||
*/
|
||||
@Nonnull
|
||||
public Date getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fingerprint of the signing (sub)key.
|
||||
*
|
||||
* @return signing key fingerprint
|
||||
*/
|
||||
@Nonnull
|
||||
public String getSigningKeyFingerprint() {
|
||||
return signingKeyFingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fingerprint fo the signing certificate.
|
||||
*
|
||||
* @return signing certificate fingerprint
|
||||
*/
|
||||
@Nonnull
|
||||
public String getSigningCertFingerprint() {
|
||||
return signingCertFingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mode of the signature.
|
||||
* Optional, may return <pre>null</pre>.
|
||||
*
|
||||
* @return signature mode
|
||||
*/
|
||||
@Nonnull
|
||||
public Optional<SignatureMode> getSignatureMode() {
|
||||
return signatureMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an optional description.
|
||||
* Optional, may return <pre>null</pre>.
|
||||
*
|
||||
* @return description
|
||||
*/
|
||||
@Nonnull
|
||||
public Optional<String> getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder()
|
||||
.append(UTCUtil.formatUTCDate(getCreationTime()))
|
||||
.append(' ')
|
||||
.append(getSigningKeyFingerprint())
|
||||
.append(' ')
|
||||
.append(getSigningCertFingerprint());
|
||||
|
||||
if (signatureMode.isPresent()) {
|
||||
sb.append(' ').append(MODE).append(signatureMode.get());
|
||||
}
|
||||
|
||||
if (description.isPresent()) {
|
||||
sb.append(' ').append(description.get());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof Verification)) {
|
||||
return false;
|
||||
}
|
||||
Verification other = (Verification) obj;
|
||||
return toString().equals(other.toString());
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.enums;
|
||||
|
||||
public enum ArmorLabel {
|
||||
Auto,
|
||||
Sig,
|
||||
Key,
|
||||
Cert,
|
||||
Message,
|
||||
;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().toLowerCase();
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.enums;
|
||||
|
||||
public enum EncryptAs {
|
||||
Binary,
|
||||
Text,
|
||||
;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().toLowerCase();
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.enums;
|
||||
|
||||
public enum InlineSignAs {
|
||||
|
||||
/**
|
||||
* Signature is made over the binary message.
|
||||
*/
|
||||
binary,
|
||||
|
||||
/**
|
||||
* Signature is made over the message in text mode.
|
||||
*/
|
||||
text,
|
||||
|
||||
/**
|
||||
* Signature is made using the Cleartext Signature Framework.
|
||||
*/
|
||||
clearsigned,
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.enums;
|
||||
|
||||
public enum SignAs {
|
||||
/**
|
||||
* Signature is made over the binary message.
|
||||
*/
|
||||
Binary,
|
||||
|
||||
/**
|
||||
* Signature is made over the message in text mode.
|
||||
*/
|
||||
Text,
|
||||
;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().toLowerCase();
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.enums;
|
||||
|
||||
/**
|
||||
* Enum referencing relevant signature types.
|
||||
*
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.2.1">
|
||||
* RFC4880 §5.2.1 - Signature Types</a>
|
||||
*/
|
||||
public enum SignatureMode {
|
||||
/**
|
||||
* Signature of a binary document (<pre>0x00</pre>).
|
||||
*/
|
||||
binary,
|
||||
|
||||
/**
|
||||
* Signature of a canonical text document (<pre>0x01</pre>).
|
||||
*/
|
||||
text
|
||||
|
||||
// Other Signature Types are irrelevant.
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Stateless OpenPGP Interface for Java.
|
||||
* Enumerations.
|
||||
*/
|
||||
package sop.enums;
|
|
@ -1,473 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.exception;
|
||||
|
||||
public abstract class SOPGPException extends RuntimeException {
|
||||
|
||||
public SOPGPException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SOPGPException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SOPGPException(Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public SOPGPException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public abstract int getExitCode();
|
||||
|
||||
/**
|
||||
* No acceptable signatures found (sop verify, inline-verify).
|
||||
*/
|
||||
public static class NoSignature extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 3;
|
||||
|
||||
public NoSignature() {
|
||||
this("No verifiable signature found.");
|
||||
}
|
||||
|
||||
public NoSignature(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NoSignature(String errorMsg, NoSignature e) {
|
||||
super(errorMsg, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asymmetric algorithm unsupported (sop encrypt, sign, inline-sign).
|
||||
*/
|
||||
public static class UnsupportedAsymmetricAlgo extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 13;
|
||||
|
||||
public UnsupportedAsymmetricAlgo(String message, Throwable e) {
|
||||
super(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Certificate not encryption capable (e,g, expired, revoked, unacceptable usage).
|
||||
*/
|
||||
public static class CertCannotEncrypt extends SOPGPException {
|
||||
public static final int EXIT_CODE = 17;
|
||||
|
||||
public CertCannotEncrypt(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public CertCannotEncrypt(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Missing required argument.
|
||||
*/
|
||||
public static class MissingArg extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 19;
|
||||
|
||||
public MissingArg() {
|
||||
|
||||
}
|
||||
|
||||
public MissingArg(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Incomplete verification instructions (sop decrypt).
|
||||
*/
|
||||
public static class IncompleteVerification extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 23;
|
||||
|
||||
public IncompleteVerification(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unable to decrypt (sop decrypt).
|
||||
*/
|
||||
public static class CannotDecrypt extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 29;
|
||||
|
||||
public CannotDecrypt() {
|
||||
|
||||
}
|
||||
|
||||
public CannotDecrypt(String errorMsg, Throwable e) {
|
||||
super(errorMsg, e);
|
||||
}
|
||||
|
||||
public CannotDecrypt(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-UTF-8 or otherwise unreliable password (sop encrypt).
|
||||
*/
|
||||
public static class PasswordNotHumanReadable extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 31;
|
||||
|
||||
public PasswordNotHumanReadable() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PasswordNotHumanReadable(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsupported option.
|
||||
*/
|
||||
public static class UnsupportedOption extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 37;
|
||||
|
||||
public UnsupportedOption(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UnsupportedOption(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalid data type (no secret key where KEYS expected, etc.).
|
||||
*/
|
||||
public static class BadData extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 41;
|
||||
|
||||
public BadData(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BadData(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public BadData(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-Text input where text expected.
|
||||
*/
|
||||
public static class ExpectedText extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 53;
|
||||
|
||||
public ExpectedText() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ExpectedText(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output file already exists.
|
||||
*/
|
||||
public static class OutputExists extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 59;
|
||||
|
||||
public OutputExists(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Input file does not exist.
|
||||
*/
|
||||
public static class MissingInput extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 61;
|
||||
|
||||
public MissingInput(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MissingInput(String errorMsg) {
|
||||
super(errorMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A KEYS input is protected (locked) with a password and sop failed to unlock it.
|
||||
*/
|
||||
public static class KeyIsProtected extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 67;
|
||||
|
||||
public KeyIsProtected() {
|
||||
super();
|
||||
}
|
||||
|
||||
public KeyIsProtected(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public KeyIsProtected(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsupported subcommand.
|
||||
*/
|
||||
public static class UnsupportedSubcommand extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 69;
|
||||
|
||||
public UnsupportedSubcommand(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An indirect parameter is a special designator (it starts with @), but sop does not know how to handle the prefix.
|
||||
*/
|
||||
public static class UnsupportedSpecialPrefix extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 71;
|
||||
|
||||
public UnsupportedSpecialPrefix(String errorMsg) {
|
||||
super(errorMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception that gets thrown if a special designator (starting with @) is given, but the filesystem contains
|
||||
* a file matching the designator.
|
||||
* <p>
|
||||
* E.g. <pre>@ENV:FOO</pre> is given, but <pre>./@ENV:FOO</pre> exists on the filesystem.
|
||||
*/
|
||||
public static class AmbiguousInput extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 73;
|
||||
|
||||
public AmbiguousInput(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key not signature-capable (e.g. expired, revoked, unacceptable usage flags).
|
||||
*/
|
||||
public static class KeyCannotSign extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 79;
|
||||
|
||||
public KeyCannotSign() {
|
||||
super();
|
||||
}
|
||||
|
||||
public KeyCannotSign(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public KeyCannotSign(String s, Throwable throwable) {
|
||||
super(s, throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User provided incompatible options (e.g. "--as=clearsigned --no-armor").
|
||||
*/
|
||||
public static class IncompatibleOptions extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 83;
|
||||
|
||||
public IncompatibleOptions() {
|
||||
super();
|
||||
}
|
||||
|
||||
public IncompatibleOptions(String errorMsg) {
|
||||
super(errorMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user provided a subcommand with an unsupported profile ("--profile=XYZ"),
|
||||
* or the user tried to list profiles of a subcommand that does not support profiles at all.
|
||||
*/
|
||||
public static class UnsupportedProfile extends SOPGPException {
|
||||
|
||||
public static final int EXIT_CODE = 89;
|
||||
|
||||
private final String subcommand;
|
||||
private final String profile;
|
||||
|
||||
/**
|
||||
* Create an exception signalling a subcommand that does not support any profiles.
|
||||
*
|
||||
* @param subcommand subcommand
|
||||
*/
|
||||
public UnsupportedProfile(String subcommand) {
|
||||
super("Subcommand '" + subcommand + "' does not support any profiles.");
|
||||
this.subcommand = subcommand;
|
||||
this.profile = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an exception signalling a subcommand does not support a specific profile.
|
||||
*
|
||||
* @param subcommand subcommand
|
||||
* @param profile unsupported profile
|
||||
*/
|
||||
public UnsupportedProfile(String subcommand, String profile) {
|
||||
super("Subcommand '" + subcommand + "' does not support profile '" + profile + "'.");
|
||||
this.subcommand = subcommand;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an exception into another instance with a possibly translated error message.
|
||||
*
|
||||
* @param errorMsg error message
|
||||
* @param e exception
|
||||
*/
|
||||
public UnsupportedProfile(String errorMsg, UnsupportedProfile e) {
|
||||
super(errorMsg, e);
|
||||
this.subcommand = e.getSubcommand();
|
||||
this.profile = e.getProfile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the subcommand name.
|
||||
*
|
||||
* @return subcommand
|
||||
*/
|
||||
public String getSubcommand() {
|
||||
return subcommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the profile name.
|
||||
* May return <pre>null</pre>.
|
||||
*
|
||||
* @return profile name
|
||||
*/
|
||||
public String getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExitCode() {
|
||||
return EXIT_CODE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Stateless OpenPGP Interface for Java.
|
||||
* Exception classes.
|
||||
*/
|
||||
package sop.exception;
|
|
@ -1,85 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.util.UTF8Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface AbstractSign<T> {
|
||||
|
||||
/**
|
||||
* Disable ASCII armor encoding.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
T noArmor();
|
||||
|
||||
/**
|
||||
* Add one or more signing keys.
|
||||
*
|
||||
* @param key input stream containing encoded keys
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.KeyCannotSign if the key cannot be used for signing
|
||||
* @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not contain an OpenPGP key
|
||||
* @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
T key(InputStream key)
|
||||
throws SOPGPException.KeyCannotSign,
|
||||
SOPGPException.BadData,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||
IOException;
|
||||
|
||||
/**
|
||||
* Add one or more signing keys.
|
||||
*
|
||||
* @param key byte array containing encoded keys
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.KeyCannotSign if the key cannot be used for signing
|
||||
* @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP key
|
||||
* @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
default T key(byte[] key)
|
||||
throws SOPGPException.KeyCannotSign,
|
||||
SOPGPException.BadData,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||
IOException {
|
||||
return key(new ByteArrayInputStream(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the password for the secret key used for signing.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if key passwords are not supported
|
||||
* @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the provided passphrase is not human-readable
|
||||
*/
|
||||
default T withKeyPassword(String password)
|
||||
throws SOPGPException.UnsupportedOption,
|
||||
SOPGPException.PasswordNotHumanReadable {
|
||||
return withKeyPassword(password.getBytes(UTF8Util.UTF8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the password for the secret key used for signing.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if key passwords are not supported
|
||||
* @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the provided passphrase is not human-readable
|
||||
*/
|
||||
T withKeyPassword(byte[] password)
|
||||
throws SOPGPException.UnsupportedOption,
|
||||
SOPGPException.PasswordNotHumanReadable;
|
||||
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Common API methods shared between verification of inline signatures ({@link InlineVerify})
|
||||
* and verification of detached signatures ({@link DetachedVerify}).
|
||||
*
|
||||
* @param <T> Builder type ({@link DetachedVerify}, {@link InlineVerify})
|
||||
*/
|
||||
public interface AbstractVerify<T> {
|
||||
|
||||
/**
|
||||
* Makes the SOP implementation consider signatures before this date invalid.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return builder instance
|
||||
*/
|
||||
T notBefore(Date timestamp)
|
||||
throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Makes the SOP implementation consider signatures after this date invalid.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return builder instance
|
||||
*/
|
||||
T notAfter(Date timestamp)
|
||||
throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Add one or more verification cert.
|
||||
*
|
||||
* @param cert input stream containing the encoded certs
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.BadData if the input stream does not contain an OpenPGP certificate
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
T cert(InputStream cert)
|
||||
throws SOPGPException.BadData,
|
||||
IOException;
|
||||
|
||||
/**
|
||||
* Add one or more verification cert.
|
||||
*
|
||||
* @param cert byte array containing the encoded certs
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP certificate
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
default T cert(byte[] cert)
|
||||
throws SOPGPException.BadData,
|
||||
IOException {
|
||||
return cert(new ByteArrayInputStream(cert));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import sop.Ready;
|
||||
import sop.enums.ArmorLabel;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public interface Armor {
|
||||
|
||||
/**
|
||||
* Overrides automatic detection of label.
|
||||
*
|
||||
* @param label armor label
|
||||
* @return builder instance
|
||||
*/
|
||||
Armor label(ArmorLabel label)
|
||||
throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Armor the provided data.
|
||||
*
|
||||
* @param data input stream of unarmored OpenPGP data
|
||||
* @return armored data
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.BadData if the data appears to be OpenPGP packets, but those are broken
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
Ready data(InputStream data)
|
||||
throws SOPGPException.BadData,
|
||||
IOException;
|
||||
|
||||
/**
|
||||
* Armor the provided data.
|
||||
*
|
||||
* @param data unarmored OpenPGP data
|
||||
* @return armored data
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.BadData if the data appears to be OpenPGP packets, but those are broken
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
default Ready data(byte[] data)
|
||||
throws SOPGPException.BadData,
|
||||
IOException {
|
||||
return data(new ByteArrayInputStream(data));
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import sop.Ready;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.util.UTF8Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
|
||||
public interface ChangeKeyPassword {
|
||||
|
||||
/**
|
||||
* Disable ASCII armoring of the output.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
ChangeKeyPassword noArmor();
|
||||
|
||||
default ChangeKeyPassword oldKeyPassphrase(byte[] password) {
|
||||
try {
|
||||
return oldKeyPassphrase(UTF8Util.decodeUTF8(password));
|
||||
} catch (CharacterCodingException e) {
|
||||
throw new SOPGPException.PasswordNotHumanReadable("Password MUST be a valid UTF8 string.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a passphrase to unlock the secret key.
|
||||
* This method can be provided multiple times to provide separate passphrases that are tried as a
|
||||
* means to unlock any secret key material encountered.
|
||||
*
|
||||
* @param oldPassphrase old passphrase
|
||||
* @return builder instance
|
||||
*/
|
||||
ChangeKeyPassword oldKeyPassphrase(String oldPassphrase);
|
||||
|
||||
/**
|
||||
* Provide a passphrase to re-lock the secret key with.
|
||||
* This method can only be used once, and all key material encountered will be encrypted with the given passphrase.
|
||||
* If this method is not called, the key material will not be protected.
|
||||
*
|
||||
* @param newPassphrase new passphrase
|
||||
* @return builder instance
|
||||
*/
|
||||
default ChangeKeyPassword newKeyPassphrase(byte[] newPassphrase) {
|
||||
try {
|
||||
return newKeyPassphrase(UTF8Util.decodeUTF8(newPassphrase));
|
||||
} catch (CharacterCodingException e) {
|
||||
throw new SOPGPException.PasswordNotHumanReadable("Password MUST be a valid UTF8 string.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a passphrase to re-lock the secret key with.
|
||||
* This method can only be used once, and all key material encountered will be encrypted with the given passphrase.
|
||||
* If this method is not called, the key material will not be protected.
|
||||
*
|
||||
* @param newPassphrase new passphrase
|
||||
* @return builder instance
|
||||
*/
|
||||
ChangeKeyPassword newKeyPassphrase(String newPassphrase);
|
||||
|
||||
default Ready keys(byte[] keys) throws SOPGPException.KeyIsProtected, SOPGPException.BadData {
|
||||
return keys(new ByteArrayInputStream(keys));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the key material.
|
||||
*
|
||||
* @param inputStream input stream of secret key material
|
||||
* @return ready
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.KeyIsProtected if any (sub-) key encountered cannot be unlocked.
|
||||
* @throws sop.exception.SOPGPException.BadData if the key material is malformed
|
||||
*/
|
||||
Ready keys(InputStream inputStream) throws SOPGPException.KeyIsProtected, SOPGPException.BadData;
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import sop.Ready;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.util.UTF8Util;
|
||||
|
||||
public interface Dearmor {
|
||||
|
||||
/**
|
||||
* Dearmor armored OpenPGP data.
|
||||
*
|
||||
* @param data armored OpenPGP data
|
||||
* @return input stream of unarmored data
|
||||
*
|
||||
* @throws SOPGPException.BadData in case of non-OpenPGP data
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
Ready data(InputStream data)
|
||||
throws SOPGPException.BadData,
|
||||
IOException;
|
||||
|
||||
/**
|
||||
* Dearmor armored OpenPGP data.
|
||||
*
|
||||
* @param data armored OpenPGP data
|
||||
* @return input stream of unarmored data
|
||||
*
|
||||
* @throws SOPGPException.BadData in case of non-OpenPGP data
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
default Ready data(byte[] data)
|
||||
throws SOPGPException.BadData,
|
||||
IOException {
|
||||
return data(new ByteArrayInputStream(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dearmor amored OpenPGP data.
|
||||
*
|
||||
* @param data armored OpenPGP data
|
||||
* @return input stream of unarmored data
|
||||
*
|
||||
* @throws SOPGPException.BadData in case of non-OpenPGP data
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
default Ready data(String data)
|
||||
throws SOPGPException.BadData,
|
||||
IOException {
|
||||
return data(data.getBytes(UTF8Util.UTF8));
|
||||
}
|
||||
}
|
|
@ -1,193 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import sop.DecryptionResult;
|
||||
import sop.ReadyWithResult;
|
||||
import sop.SessionKey;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.util.UTF8Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
|
||||
public interface Decrypt {
|
||||
|
||||
/**
|
||||
* Makes the SOP consider signatures before this date invalid.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported
|
||||
*/
|
||||
Decrypt verifyNotBefore(Date timestamp)
|
||||
throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Makes the SOP consider signatures after this date invalid.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported
|
||||
*/
|
||||
Decrypt verifyNotAfter(Date timestamp)
|
||||
throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Adds one or more verification cert.
|
||||
*
|
||||
* @param cert input stream containing the cert(s)
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.BadData if the {@link InputStream} doesn't provide an OpenPGP certificate
|
||||
* @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the cert uses an unsupported asymmetric algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
Decrypt verifyWithCert(InputStream cert)
|
||||
throws SOPGPException.BadData,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||
IOException;
|
||||
|
||||
/**
|
||||
* Adds one or more verification cert.
|
||||
*
|
||||
* @param cert byte array containing the cert(s)
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.BadData if the byte array doesn't contain an OpenPGP certificate
|
||||
* @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the cert uses an unsupported asymmetric algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
default Decrypt verifyWithCert(byte[] cert)
|
||||
throws SOPGPException.BadData,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||
IOException {
|
||||
return verifyWithCert(new ByteArrayInputStream(cert));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to decrypt with the given session key.
|
||||
*
|
||||
* @param sessionKey session key
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported
|
||||
*/
|
||||
Decrypt withSessionKey(SessionKey sessionKey)
|
||||
throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Tries to decrypt with the given password.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported
|
||||
*/
|
||||
Decrypt withPassword(String password)
|
||||
throws SOPGPException.PasswordNotHumanReadable,
|
||||
SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Adds one or more decryption key.
|
||||
*
|
||||
* @param key input stream containing the key(s)
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not provide an OpenPGP key
|
||||
* @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
Decrypt withKey(InputStream key)
|
||||
throws SOPGPException.BadData,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||
IOException;
|
||||
|
||||
/**
|
||||
* Adds one or more decryption key.
|
||||
*
|
||||
* @param key byte array containing the key(s)
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP key
|
||||
* @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
default Decrypt withKey(byte[] key)
|
||||
throws SOPGPException.BadData,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||
IOException {
|
||||
return withKey(new ByteArrayInputStream(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the decryption password for the secret key.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if the implementation does not support key passwords
|
||||
* @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable
|
||||
*/
|
||||
default Decrypt withKeyPassword(String password)
|
||||
throws SOPGPException.UnsupportedOption,
|
||||
SOPGPException.PasswordNotHumanReadable {
|
||||
return withKeyPassword(password.getBytes(UTF8Util.UTF8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the decryption password for the secret key.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if the implementation does not support key passwords
|
||||
* @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable
|
||||
*/
|
||||
Decrypt withKeyPassword(byte[] password)
|
||||
throws SOPGPException.UnsupportedOption,
|
||||
SOPGPException.PasswordNotHumanReadable;
|
||||
|
||||
/**
|
||||
* Decrypts the given ciphertext, returning verification results and plaintext.
|
||||
* @param ciphertext ciphertext
|
||||
* @return ready with result
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not provide an OpenPGP message
|
||||
* @throws sop.exception.SOPGPException.MissingArg if an argument required for decryption was not provided
|
||||
* @throws sop.exception.SOPGPException.CannotDecrypt in case decryption fails for some reason
|
||||
* @throws sop.exception.SOPGPException.KeyIsProtected if the decryption key cannot be unlocked (e.g. missing passphrase)
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
ReadyWithResult<DecryptionResult> ciphertext(InputStream ciphertext)
|
||||
throws SOPGPException.BadData,
|
||||
SOPGPException.MissingArg,
|
||||
SOPGPException.CannotDecrypt,
|
||||
SOPGPException.KeyIsProtected,
|
||||
IOException;
|
||||
|
||||
/**
|
||||
* Decrypts the given ciphertext, returning verification results and plaintext.
|
||||
* @param ciphertext ciphertext
|
||||
* @return ready with result
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.BadData if the byte array does not contain an encrypted OpenPGP message
|
||||
* @throws sop.exception.SOPGPException.MissingArg in case of missing decryption method (password or key required)
|
||||
* @throws sop.exception.SOPGPException.CannotDecrypt in case decryption fails for some reason
|
||||
* @throws sop.exception.SOPGPException.KeyIsProtected if the decryption key cannot be unlocked (e.g. missing passphrase)
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
default ReadyWithResult<DecryptionResult> ciphertext(byte[] ciphertext)
|
||||
throws SOPGPException.BadData,
|
||||
SOPGPException.MissingArg,
|
||||
SOPGPException.CannotDecrypt,
|
||||
SOPGPException.KeyIsProtected,
|
||||
IOException {
|
||||
return ciphertext(new ByteArrayInputStream(ciphertext));
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import sop.ReadyWithResult;
|
||||
import sop.SigningResult;
|
||||
import sop.enums.SignAs;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface DetachedSign extends AbstractSign<DetachedSign> {
|
||||
|
||||
/**
|
||||
* Sets the signature mode.
|
||||
* Note: This method has to be called before {@link #key(InputStream)} is called.
|
||||
*
|
||||
* @param mode signature mode
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported
|
||||
*/
|
||||
DetachedSign mode(SignAs mode)
|
||||
throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Signs data.
|
||||
*
|
||||
* @param data input stream containing data
|
||||
* @return ready
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked
|
||||
* @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data was encountered
|
||||
*/
|
||||
ReadyWithResult<SigningResult> data(InputStream data)
|
||||
throws IOException,
|
||||
SOPGPException.KeyIsProtected,
|
||||
SOPGPException.ExpectedText;
|
||||
|
||||
/**
|
||||
* Signs data.
|
||||
*
|
||||
* @param data byte array containing data
|
||||
* @return ready
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked
|
||||
* @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data was encountered
|
||||
*/
|
||||
default ReadyWithResult<SigningResult> data(byte[] data)
|
||||
throws IOException,
|
||||
SOPGPException.KeyIsProtected,
|
||||
SOPGPException.ExpectedText {
|
||||
return data(new ByteArrayInputStream(data));
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* API for verifying detached signatures.
|
||||
*/
|
||||
public interface DetachedVerify extends AbstractVerify<DetachedVerify>, VerifySignatures {
|
||||
|
||||
/**
|
||||
* Provides the detached signatures.
|
||||
* @param signatures input stream containing encoded, detached signatures.
|
||||
*
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.BadData if the input stream does not contain OpenPGP signatures
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
VerifySignatures signatures(InputStream signatures)
|
||||
throws SOPGPException.BadData,
|
||||
IOException;
|
||||
|
||||
/**
|
||||
* Provides the detached signatures.
|
||||
* @param signatures byte array containing encoded, detached signatures.
|
||||
*
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.BadData if the byte array does not contain OpenPGP signatures
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
default VerifySignatures signatures(byte[] signatures)
|
||||
throws SOPGPException.BadData,
|
||||
IOException {
|
||||
return signatures(new ByteArrayInputStream(signatures));
|
||||
}
|
||||
}
|
|
@ -1,193 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import sop.Profile;
|
||||
import sop.Ready;
|
||||
import sop.enums.EncryptAs;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.util.UTF8Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface Encrypt {
|
||||
|
||||
/**
|
||||
* Disable ASCII armor encoding.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
Encrypt noArmor();
|
||||
|
||||
/**
|
||||
* Sets encryption mode.
|
||||
*
|
||||
* @param mode mode
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported
|
||||
*/
|
||||
Encrypt mode(EncryptAs mode)
|
||||
throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Adds the signer key.
|
||||
*
|
||||
* @param key input stream containing the encoded signer key
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.KeyCannotSign if the key cannot be used for signing
|
||||
* @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm
|
||||
* @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not contain an OpenPGP key
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
Encrypt signWith(InputStream key)
|
||||
throws SOPGPException.KeyCannotSign,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||
SOPGPException.BadData,
|
||||
IOException;
|
||||
|
||||
/**
|
||||
* Adds the signer key.
|
||||
*
|
||||
* @param key byte array containing the encoded signer key
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.KeyCannotSign if the key cannot be used for signing
|
||||
* @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm
|
||||
* @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP key
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
default Encrypt signWith(byte[] key)
|
||||
throws SOPGPException.KeyCannotSign,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||
SOPGPException.BadData,
|
||||
IOException {
|
||||
return signWith(new ByteArrayInputStream(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the password for the secret key used for signing.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if key password are not supported
|
||||
*/
|
||||
default Encrypt withKeyPassword(String password)
|
||||
throws SOPGPException.PasswordNotHumanReadable,
|
||||
SOPGPException.UnsupportedOption {
|
||||
return withKeyPassword(password.getBytes(UTF8Util.UTF8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the password for the secret key used for signing.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if key password are not supported
|
||||
*/
|
||||
Encrypt withKeyPassword(byte[] password)
|
||||
throws SOPGPException.PasswordNotHumanReadable,
|
||||
SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Encrypt with the given password.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported
|
||||
*/
|
||||
Encrypt withPassword(String password)
|
||||
throws SOPGPException.PasswordNotHumanReadable,
|
||||
SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Encrypt with the given cert.
|
||||
*
|
||||
* @param cert input stream containing the encoded cert.
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.CertCannotEncrypt if the certificate is not encryption capable
|
||||
* @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the certificate uses an unsupported asymmetric algorithm
|
||||
* @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not contain an OpenPGP certificate
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
Encrypt withCert(InputStream cert)
|
||||
throws SOPGPException.CertCannotEncrypt,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||
SOPGPException.BadData,
|
||||
IOException;
|
||||
|
||||
/**
|
||||
* Encrypt with the given cert.
|
||||
*
|
||||
* @param cert byte array containing the encoded cert.
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.CertCannotEncrypt if the certificate is not encryption capable
|
||||
* @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the certificate uses an unsupported asymmetric algorithm
|
||||
* @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP certificate
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
default Encrypt withCert(byte[] cert)
|
||||
throws SOPGPException.CertCannotEncrypt,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||
SOPGPException.BadData,
|
||||
IOException {
|
||||
return withCert(new ByteArrayInputStream(cert));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass in a profile.
|
||||
*
|
||||
* @param profile profile
|
||||
* @return builder instance
|
||||
*/
|
||||
default Encrypt profile(Profile profile) {
|
||||
return profile(profile.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass in a profile identifier.
|
||||
*
|
||||
* @param profileName profile identifier
|
||||
* @return builder instance
|
||||
*/
|
||||
Encrypt profile(String profileName);
|
||||
|
||||
/**
|
||||
* Encrypt the given data yielding the ciphertext.
|
||||
* @param plaintext plaintext
|
||||
* @return input stream containing the ciphertext
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked
|
||||
*/
|
||||
Ready plaintext(InputStream plaintext)
|
||||
throws IOException,
|
||||
SOPGPException.KeyIsProtected;
|
||||
|
||||
/**
|
||||
* Encrypt the given data yielding the ciphertext.
|
||||
* @param plaintext plaintext
|
||||
* @return input stream containing the ciphertext
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked
|
||||
*/
|
||||
default Ready plaintext(byte[] plaintext)
|
||||
throws IOException,
|
||||
SOPGPException.KeyIsProtected {
|
||||
return plaintext(new ByteArrayInputStream(plaintext));
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import sop.Ready;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public interface ExtractCert {
|
||||
|
||||
/**
|
||||
* Disable ASCII armor encoding.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
ExtractCert noArmor();
|
||||
|
||||
/**
|
||||
* Extract the cert(s) from the provided key(s).
|
||||
*
|
||||
* @param keyInputStream input stream containing the encoding of one or more OpenPGP keys
|
||||
* @return result containing the encoding of the keys certs
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not contain an OpenPGP key
|
||||
*/
|
||||
Ready key(InputStream keyInputStream)
|
||||
throws IOException,
|
||||
SOPGPException.BadData;
|
||||
|
||||
/**
|
||||
* Extract the cert(s) from the provided key(s).
|
||||
*
|
||||
* @param key byte array containing the encoding of one or more OpenPGP key
|
||||
* @return result containing the encoding of the keys certs
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP key
|
||||
*/
|
||||
default Ready key(byte[] key)
|
||||
throws IOException,
|
||||
SOPGPException.BadData {
|
||||
return key(new ByteArrayInputStream(key));
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
|
||||
import sop.Profile;
|
||||
import sop.Ready;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.util.UTF8Util;
|
||||
|
||||
public interface GenerateKey {
|
||||
|
||||
/**
|
||||
* Disable ASCII armor encoding.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
GenerateKey noArmor();
|
||||
|
||||
/**
|
||||
* Adds a user-id.
|
||||
*
|
||||
* @param userId user-id
|
||||
* @return builder instance
|
||||
*/
|
||||
GenerateKey userId(String userId);
|
||||
|
||||
/**
|
||||
* Set a password for the key.
|
||||
*
|
||||
* @param password password to protect the key
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if key passwords are not supported
|
||||
* @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable
|
||||
*/
|
||||
GenerateKey withKeyPassword(String password)
|
||||
throws SOPGPException.PasswordNotHumanReadable,
|
||||
SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Set a password for the key.
|
||||
*
|
||||
* @param password password to protect the key
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if key passwords are not supported
|
||||
*/
|
||||
default GenerateKey withKeyPassword(byte[] password)
|
||||
throws SOPGPException.PasswordNotHumanReadable,
|
||||
SOPGPException.UnsupportedOption {
|
||||
try {
|
||||
return withKeyPassword(UTF8Util.decodeUTF8(password));
|
||||
} catch (CharacterCodingException e) {
|
||||
throw new SOPGPException.PasswordNotHumanReadable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass in a profile.
|
||||
*
|
||||
* @param profile profile
|
||||
* @return builder instance
|
||||
*/
|
||||
default GenerateKey profile(Profile profile) {
|
||||
return profile(profile.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass in a profile identifier.
|
||||
*
|
||||
* @param profile profile identifier
|
||||
* @return builder instance
|
||||
*/
|
||||
GenerateKey profile(String profile);
|
||||
|
||||
/**
|
||||
* If this options is set, the generated key will not be capable of encryption / decryption.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
GenerateKey signingOnly();
|
||||
|
||||
/**
|
||||
* Generate the OpenPGP key and return it encoded as an {@link InputStream}.
|
||||
*
|
||||
* @return key
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.MissingArg if no user-id was provided
|
||||
* @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the generated key uses an unsupported asymmetric algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
Ready generate()
|
||||
throws SOPGPException.MissingArg,
|
||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||
IOException;
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import sop.ReadyWithResult;
|
||||
import sop.Signatures;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
/**
|
||||
* Split cleartext signed messages up into data and signatures.
|
||||
*/
|
||||
public interface InlineDetach {
|
||||
|
||||
/**
|
||||
* Do not wrap the signatures in ASCII armor.
|
||||
* @return builder
|
||||
*/
|
||||
InlineDetach noArmor();
|
||||
|
||||
/**
|
||||
* Detach the provided signed message from its signatures.
|
||||
*
|
||||
* @param messageInputStream input stream containing the signed message
|
||||
* @return result containing the detached message
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws sop.exception.SOPGPException.BadData if the input stream does not contain a signed message
|
||||
*/
|
||||
ReadyWithResult<Signatures> message(InputStream messageInputStream)
|
||||
throws IOException,
|
||||
SOPGPException.BadData;
|
||||
|
||||
/**
|
||||
* Detach the provided cleartext signed message from its signatures.
|
||||
*
|
||||
* @param message byte array containing the signed message
|
||||
* @return result containing the detached message
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws sop.exception.SOPGPException.BadData if the byte array does not contain a signed message
|
||||
*/
|
||||
default ReadyWithResult<Signatures> message(byte[] message)
|
||||
throws IOException,
|
||||
SOPGPException.BadData {
|
||||
return message(new ByteArrayInputStream(message));
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import sop.Ready;
|
||||
import sop.enums.InlineSignAs;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface InlineSign extends AbstractSign<InlineSign> {
|
||||
|
||||
/**
|
||||
* Sets the signature mode.
|
||||
* Note: This method has to be called before {@link #key(InputStream)} is called.
|
||||
*
|
||||
* @param mode signature mode
|
||||
* @return builder instance
|
||||
*
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported
|
||||
*/
|
||||
InlineSign mode(InlineSignAs mode)
|
||||
throws SOPGPException.UnsupportedOption;
|
||||
|
||||
/**
|
||||
* Signs data.
|
||||
*
|
||||
* @param data input stream containing data
|
||||
* @return ready
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked
|
||||
* @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data was encountered
|
||||
*/
|
||||
Ready data(InputStream data)
|
||||
throws IOException,
|
||||
SOPGPException.KeyIsProtected,
|
||||
SOPGPException.ExpectedText;
|
||||
|
||||
/**
|
||||
* Signs data.
|
||||
*
|
||||
* @param data byte array containing data
|
||||
* @return ready
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked
|
||||
* @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data was encountered
|
||||
*/
|
||||
default Ready data(byte[] data)
|
||||
throws IOException,
|
||||
SOPGPException.KeyIsProtected,
|
||||
SOPGPException.ExpectedText {
|
||||
return data(new ByteArrayInputStream(data));
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import sop.ReadyWithResult;
|
||||
import sop.Verification;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* API for verification of inline-signed messages.
|
||||
*/
|
||||
public interface InlineVerify extends AbstractVerify<InlineVerify> {
|
||||
|
||||
/**
|
||||
* Provide the inline-signed data.
|
||||
* The result can be used to write the plaintext message out and to get the verifications.
|
||||
*
|
||||
* @param data signed data
|
||||
* @return list of signature verifications
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws SOPGPException.NoSignature when no signature is found
|
||||
* @throws SOPGPException.BadData when the data is invalid OpenPGP data
|
||||
*/
|
||||
ReadyWithResult<List<Verification>> data(InputStream data)
|
||||
throws IOException,
|
||||
SOPGPException.NoSignature,
|
||||
SOPGPException.BadData;
|
||||
|
||||
/**
|
||||
* Provide the inline-signed data.
|
||||
* The result can be used to write the plaintext message out and to get the verifications.
|
||||
*
|
||||
* @param data signed data
|
||||
* @return list of signature verifications
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws SOPGPException.NoSignature when no signature is found
|
||||
* @throws SOPGPException.BadData when the data is invalid OpenPGP data
|
||||
*/
|
||||
default ReadyWithResult<List<Verification>> data(byte[] data)
|
||||
throws IOException,
|
||||
SOPGPException.NoSignature,
|
||||
SOPGPException.BadData {
|
||||
return data(new ByteArrayInputStream(data));
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import sop.Profile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Subcommand to list supported profiles of other subcommands.
|
||||
*/
|
||||
public interface ListProfiles {
|
||||
|
||||
/**
|
||||
* Provide the name of the subcommand for which profiles shall be listed.
|
||||
* The returned list of profiles MUST NOT contain more than 4 entries.
|
||||
*
|
||||
* @param command command name (e.g. <pre>generate-key</pre>)
|
||||
* @return list of profiles.
|
||||
*/
|
||||
List<Profile> subcommand(String command);
|
||||
|
||||
/**
|
||||
* Return a list of {@link Profile Profiles} supported by the {@link GenerateKey} implementation.
|
||||
*
|
||||
* @return profiles
|
||||
*/
|
||||
default List<Profile> generateKey() {
|
||||
return subcommand("generate-key");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of {@link Profile Profiles} supported by the {@link Encrypt} implementation.
|
||||
*
|
||||
* @return profiles
|
||||
*/
|
||||
default List<Profile> encrypt() {
|
||||
return subcommand("encrypt");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import sop.Ready;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.util.UTF8Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface RevokeKey {
|
||||
|
||||
/**
|
||||
* Disable ASCII armor encoding.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
RevokeKey noArmor();
|
||||
|
||||
/**
|
||||
* Provide the decryption password for the secret key.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws SOPGPException.UnsupportedOption if the implementation does not support key passwords
|
||||
* @throws SOPGPException.PasswordNotHumanReadable if the password is not human-readable
|
||||
*/
|
||||
default RevokeKey withKeyPassword(String password)
|
||||
throws SOPGPException.UnsupportedOption,
|
||||
SOPGPException.PasswordNotHumanReadable {
|
||||
return withKeyPassword(password.getBytes(UTF8Util.UTF8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the decryption password for the secret key.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws SOPGPException.UnsupportedOption if the implementation does not support key passwords
|
||||
* @throws SOPGPException.PasswordNotHumanReadable if the password is not human-readable
|
||||
*/
|
||||
RevokeKey withKeyPassword(byte[] password)
|
||||
throws SOPGPException.UnsupportedOption,
|
||||
SOPGPException.PasswordNotHumanReadable;
|
||||
|
||||
default Ready keys(byte[] bytes) {
|
||||
return keys(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
|
||||
Ready keys(InputStream keys);
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import sop.Verification;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public interface VerifySignatures {
|
||||
|
||||
/**
|
||||
* Provide the signed data (without signatures).
|
||||
*
|
||||
* @param data signed data
|
||||
* @return list of signature verifications
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws SOPGPException.NoSignature when no valid signature is found
|
||||
* @throws SOPGPException.BadData when the data is invalid OpenPGP data
|
||||
*/
|
||||
List<Verification> data(InputStream data)
|
||||
throws IOException,
|
||||
SOPGPException.NoSignature,
|
||||
SOPGPException.BadData;
|
||||
|
||||
/**
|
||||
* Provide the signed data (without signatures).
|
||||
*
|
||||
* @param data signed data
|
||||
* @return list of signature verifications
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws SOPGPException.NoSignature when no valid signature is found
|
||||
* @throws SOPGPException.BadData when the data is invalid OpenPGP data
|
||||
*/
|
||||
default List<Verification> data(byte[] data)
|
||||
throws IOException,
|
||||
SOPGPException.NoSignature,
|
||||
SOPGPException.BadData {
|
||||
return data(new ByteArrayInputStream(data));
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation;
|
||||
|
||||
public interface Version {
|
||||
|
||||
/**
|
||||
* Return the implementations name.
|
||||
* e.g. "SOP",
|
||||
*
|
||||
* @return implementation name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Return the implementations short version string.
|
||||
* e.g. "1.0"
|
||||
*
|
||||
* @return version string
|
||||
*/
|
||||
String getVersion();
|
||||
|
||||
/**
|
||||
* Return version information about the used OpenPGP backend.
|
||||
* e.g. "Bouncycastle 1.70"
|
||||
*
|
||||
* @return backend version string
|
||||
*/
|
||||
String getBackendVersion();
|
||||
|
||||
/**
|
||||
* Return an extended version string containing multiple lines of version information.
|
||||
* The first line MUST match the information produced by {@link #getName()} and {@link #getVersion()}, but the rest of the text
|
||||
* has no defined structure.
|
||||
* Example:
|
||||
* <pre>
|
||||
* "SOP 1.0
|
||||
* Awesome PGP!
|
||||
* Using Bouncycastle 1.70
|
||||
* LibFoo 1.2.2
|
||||
* See https://pgp.example.org/sop/ for more information"
|
||||
* </pre>
|
||||
*
|
||||
* @return extended version string
|
||||
*/
|
||||
String getExtendedVersion();
|
||||
|
||||
/**
|
||||
* Return the revision of the SOP specification that this implementation is implementing, for example,
|
||||
* <pre>draft-dkg-openpgp-stateless-cli-06</pre>.
|
||||
* If the implementation targets a specific draft but the implementer knows the implementation is incomplete,
|
||||
* it should prefix the draft title with a "~" (TILDE, U+007E), for example:
|
||||
* <pre>~draft-dkg-openpgp-stateless-cli-06</pre>.
|
||||
* The implementation MAY emit additional text about its relationship to the targeted draft on the lines following
|
||||
* the versioned title.
|
||||
*
|
||||
* @return implemented SOP spec version
|
||||
*/
|
||||
default String getSopSpecVersion() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (isSopSpecImplementationIncomplete()) {
|
||||
sb.append('~');
|
||||
}
|
||||
|
||||
sb.append(getSopSpecRevisionName());
|
||||
|
||||
if (getSopSpecImplementationRemarks() != null) {
|
||||
sb.append('\n')
|
||||
.append('\n')
|
||||
.append(getSopSpecImplementationRemarks());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the version number of the latest targeted SOP spec revision.
|
||||
*
|
||||
* @return SOP spec revision number
|
||||
*/
|
||||
int getSopSpecRevisionNumber();
|
||||
|
||||
/**
|
||||
* Return the name of the latest targeted revision of the SOP spec.
|
||||
*
|
||||
* @return SOP spec revision string
|
||||
*/
|
||||
default String getSopSpecRevisionName() {
|
||||
return "draft-dkg-openpgp-stateless-cli-" + String.format("%02d", getSopSpecRevisionNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return <pre>true</pre>, if this implementation of the SOP spec is known to be incomplete or defective.
|
||||
*
|
||||
* @return true if incomplete, false otherwise
|
||||
*/
|
||||
boolean isSopSpecImplementationIncomplete();
|
||||
|
||||
/**
|
||||
* Return free-form text containing remarks about the completeness of the SOP implementation.
|
||||
* If there are no remarks, this method returns <pre>null</pre>.
|
||||
*
|
||||
* @return remarks or null
|
||||
*/
|
||||
String getSopSpecImplementationRemarks();
|
||||
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Stateless OpenPGP Interface for Java.
|
||||
* Different cryptographic operations.
|
||||
*/
|
||||
package sop.operation;
|
|
@ -1,8 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Stateless OpenPGP Interface for Java.
|
||||
*/
|
||||
package sop;
|
|
@ -1,47 +0,0 @@
|
|||
// Copyright 2021 Paul Schaub, @maybeWeCouldStealAVan, @Dave L.
|
||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util;
|
||||
|
||||
public class HexUtil {
|
||||
|
||||
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
/**
|
||||
* Encode a byte array to a hex string.
|
||||
*
|
||||
* @see <a href="https://stackoverflow.com/a/9855338">
|
||||
* How to convert a byte array to a hex string in Java?</a>
|
||||
* @param bytes bytes
|
||||
* @return hex encoding
|
||||
*/
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a hex string into a byte array.
|
||||
*
|
||||
* @see <a href="https://stackoverflow.com/a/140861">
|
||||
* Convert a string representation of a hex dump to a byte array using Java?</a>
|
||||
* @param s hex string
|
||||
* @return decoded byte array
|
||||
*/
|
||||
public static byte[] hexToBytes(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i + 1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util;
|
||||
|
||||
/**
|
||||
* Backport of java.util.Optional for older Android versions.
|
||||
*
|
||||
* @param <T> item type
|
||||
*/
|
||||
public class Optional<T> {
|
||||
|
||||
private final T item;
|
||||
|
||||
public Optional() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public Optional(T item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public static <T> Optional<T> of(T item) {
|
||||
if (item == null) {
|
||||
throw new NullPointerException("Item cannot be null.");
|
||||
}
|
||||
return new Optional<>(item);
|
||||
}
|
||||
|
||||
public static <T> Optional<T> ofNullable(T item) {
|
||||
return new Optional<>(item);
|
||||
}
|
||||
|
||||
public static <T> Optional<T> ofEmpty() {
|
||||
return new Optional<>(null);
|
||||
}
|
||||
|
||||
public T get() {
|
||||
return item;
|
||||
}
|
||||
|
||||
public boolean isPresent() {
|
||||
return item != null;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return item == null;
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* {@link OutputStream} that buffers data being written into it, until its underlying output stream is being replaced.
|
||||
* At that point, first all the buffered data is being written to the underlying stream, followed by any successive
|
||||
* data that may get written to the {@link ProxyOutputStream}.
|
||||
* <p>
|
||||
* This class is useful if we need to provide an {@link OutputStream} at one point in time when the final
|
||||
* target output stream is not yet known.
|
||||
*/
|
||||
public class ProxyOutputStream extends OutputStream {
|
||||
|
||||
private final ByteArrayOutputStream buffer;
|
||||
private OutputStream swapped;
|
||||
|
||||
public ProxyOutputStream() {
|
||||
this.buffer = new ByteArrayOutputStream();
|
||||
}
|
||||
|
||||
public synchronized void replaceOutputStream(OutputStream underlying) throws IOException {
|
||||
if (underlying == null) {
|
||||
throw new NullPointerException("Underlying OutputStream cannot be null.");
|
||||
}
|
||||
this.swapped = underlying;
|
||||
|
||||
byte[] bufferBytes = buffer.toByteArray();
|
||||
swapped.write(bufferBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] b) throws IOException {
|
||||
if (swapped == null) {
|
||||
buffer.write(b);
|
||||
} else {
|
||||
swapped.write(b);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte[] b, int off, int len) throws IOException {
|
||||
if (swapped == null) {
|
||||
buffer.write(b, off, len);
|
||||
} else {
|
||||
swapped.write(b, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void flush() throws IOException {
|
||||
buffer.flush();
|
||||
if (swapped != null) {
|
||||
swapped.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
buffer.close();
|
||||
if (swapped != null) {
|
||||
swapped.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(int i) throws IOException {
|
||||
if (swapped == null) {
|
||||
buffer.write(i);
|
||||
} else {
|
||||
swapped.write(i);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Utility class to parse and format dates as ISO-8601 UTC timestamps.
|
||||
*/
|
||||
public class UTCUtil {
|
||||
|
||||
public static final SimpleDateFormat UTC_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
public static final SimpleDateFormat[] UTC_PARSERS = new SimpleDateFormat[] {
|
||||
UTC_FORMATTER,
|
||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"),
|
||||
new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"),
|
||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")
|
||||
};
|
||||
|
||||
static {
|
||||
for (SimpleDateFormat f : UTC_PARSERS) {
|
||||
f.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parse an ISO-8601 UTC timestamp from a string.
|
||||
*
|
||||
* @param dateString string
|
||||
* @return date
|
||||
* @throws ParseException if the date string is malformed and cannot be parsed
|
||||
*/
|
||||
@Nonnull
|
||||
public static Date parseUTCDate(String dateString) throws ParseException {
|
||||
ParseException exception = null;
|
||||
for (SimpleDateFormat parser : UTC_PARSERS) {
|
||||
try {
|
||||
return parser.parse(dateString);
|
||||
} catch (ParseException e) {
|
||||
// Store first exception (that of UTC_FORMATTER) to throw if we fail to parse the date
|
||||
if (exception == null) {
|
||||
exception = e;
|
||||
}
|
||||
// Try next parser
|
||||
}
|
||||
}
|
||||
// No parser worked, so we throw the store exception
|
||||
throw exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date as ISO-8601 UTC timestamp.
|
||||
*
|
||||
* @param date date
|
||||
* @return timestamp string
|
||||
*/
|
||||
public static String formatUTCDate(Date date) {
|
||||
return UTC_FORMATTER.format(date);
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
|
||||
public class UTF8Util {
|
||||
|
||||
public static final Charset UTF8 = Charset.forName("UTF8");
|
||||
private static final CharsetDecoder UTF8Decoder = UTF8
|
||||
.newDecoder()
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||
.onMalformedInput(CodingErrorAction.REPORT);
|
||||
|
||||
/**
|
||||
* Detect non-valid UTF8 data.
|
||||
*
|
||||
* @see <a href="https://stackoverflow.com/a/1471193">ante on StackOverflow</a>
|
||||
* @param data utf-8 encoded bytes
|
||||
*
|
||||
* @return decoded string
|
||||
* @throws CharacterCodingException if the input data does not resemble UTF8
|
||||
*/
|
||||
public static String decodeUTF8(byte[] data)
|
||||
throws CharacterCodingException {
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(data);
|
||||
CharBuffer charBuffer = UTF8Decoder.decode(byteBuffer);
|
||||
return charBuffer.toString();
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Utility classes.
|
||||
*/
|
||||
package sop.util;
|
43
sop-java/src/main/kotlin/sop/ByteArrayAndResult.kt
Normal file
43
sop-java/src/main/kotlin/sop/ByteArrayAndResult.kt
Normal file
|
@ -0,0 +1,43 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop
|
||||
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Tuple of a [ByteArray] and associated result object.
|
||||
*
|
||||
* @param bytes byte array
|
||||
* @param result result object
|
||||
* @param <T> type of result
|
||||
*/
|
||||
data class ByteArrayAndResult<T>(val bytes: ByteArray, val result: T) {
|
||||
|
||||
/**
|
||||
* [InputStream] returning the contents of [bytes].
|
||||
*
|
||||
* @return input stream
|
||||
*/
|
||||
val inputStream: InputStream
|
||||
get() = bytes.inputStream()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as ByteArrayAndResult<*>
|
||||
|
||||
if (!bytes.contentEquals(other.bytes)) return false
|
||||
if (result != other.result) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var hashCode = bytes.contentHashCode()
|
||||
hashCode = 31 * hashCode + (result?.hashCode() ?: 0)
|
||||
return hashCode
|
||||
}
|
||||
}
|
15
sop-java/src/main/kotlin/sop/DecryptionResult.kt
Normal file
15
sop-java/src/main/kotlin/sop/DecryptionResult.kt
Normal file
|
@ -0,0 +1,15 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop
|
||||
|
||||
import sop.util.Optional
|
||||
|
||||
class DecryptionResult(sessionKey: SessionKey?, val verifications: List<Verification>) {
|
||||
val sessionKey: Optional<SessionKey>
|
||||
|
||||
init {
|
||||
this.sessionKey = Optional.ofNullable(sessionKey)
|
||||
}
|
||||
}
|
34
sop-java/src/main/kotlin/sop/MicAlg.kt
Normal file
34
sop-java/src/main/kotlin/sop/MicAlg.kt
Normal file
|
@ -0,0 +1,34 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop
|
||||
|
||||
import java.io.OutputStream
|
||||
import java.io.PrintWriter
|
||||
|
||||
data class MicAlg(val micAlg: String) {
|
||||
|
||||
fun writeTo(outputStream: OutputStream) {
|
||||
PrintWriter(outputStream).use { it.write(micAlg) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun empty() = MicAlg("")
|
||||
|
||||
@JvmStatic
|
||||
fun fromHashAlgorithmId(id: Int) =
|
||||
when (id) {
|
||||
1 -> "pgp-md5"
|
||||
2 -> "pgp-sha1"
|
||||
3 -> "pgp-ripemd160"
|
||||
8 -> "pgp-sha256"
|
||||
9 -> "pgp-sha384"
|
||||
10 -> "pgp-sha512"
|
||||
11 -> "pgp-sha224"
|
||||
12 -> "pgp-sha3-256"
|
||||
14 -> "pgp-sha3-512"
|
||||
else -> throw IllegalArgumentException("Unsupported hash algorithm ID: $id")
|
||||
}.let { MicAlg(it) }
|
||||
}
|
||||
}
|
87
sop-java/src/main/kotlin/sop/Profile.kt
Normal file
87
sop-java/src/main/kotlin/sop/Profile.kt
Normal file
|
@ -0,0 +1,87 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop
|
||||
|
||||
import sop.util.Optional
|
||||
import sop.util.UTF8Util
|
||||
|
||||
/**
|
||||
* Tuple class bundling a profile name and description.
|
||||
*
|
||||
* @param name profile name. A profile name is a UTF-8 string that has no whitespace in it. Similar
|
||||
* to OpenPGP Notation names, profile names are divided into two namespaces: The IETF namespace
|
||||
* and the user namespace. A profile name in the user namespace ends with the `@` character (0x40)
|
||||
* followed by a DNS domain name. A profile name in the IETF namespace does not have an `@`
|
||||
* character. A profile name in the user space is owned and controlled by the owner of the domain
|
||||
* in the suffix. A profile name in the IETF namespace that begins with the string `rfc` should
|
||||
* have semantics that hew as closely as possible to the referenced RFC. Similarly, a profile name
|
||||
* in the IETF namespace that begins with the string `draft-` should have semantics that hew as
|
||||
* closely as possible to the referenced Internet Draft.
|
||||
* @param description a free-form description of the profile.
|
||||
* @see <a
|
||||
* href="https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-05.html#name-profile">
|
||||
* SOP Spec - Profile</a>
|
||||
*/
|
||||
data class Profile(val name: String, val description: Optional<String>) {
|
||||
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
name: String,
|
||||
description: String? = null
|
||||
) : this(name, Optional.ofNullable(description?.trim()?.ifBlank { null }))
|
||||
|
||||
init {
|
||||
require(name.trim().isNotBlank()) { "Name cannot be empty." }
|
||||
require(!name.contains(":")) { "Name cannot contain ':'." }
|
||||
require(listOf(" ", "\n", "\t", "\r").none { name.contains(it) }) {
|
||||
"Name cannot contain whitespace characters."
|
||||
}
|
||||
require(!exceeds1000CharLineLimit(this)) {
|
||||
"The line representation of a profile MUST NOT exceed 1000 bytes."
|
||||
}
|
||||
}
|
||||
|
||||
fun hasDescription() = description.isPresent
|
||||
|
||||
/**
|
||||
* Convert the profile into a String for displaying.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
override fun toString(): String =
|
||||
if (description.isEmpty) name else "$name: ${description.get()}"
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Parse a [Profile] from its string representation.
|
||||
*
|
||||
* @param string string representation
|
||||
* @return profile
|
||||
*/
|
||||
@JvmStatic
|
||||
fun parse(string: String): Profile {
|
||||
return if (string.contains(": ")) {
|
||||
Profile(
|
||||
string.substring(0, string.indexOf(": ")),
|
||||
string.substring(string.indexOf(": ") + 2).trim())
|
||||
} else if (string.endsWith(":")) {
|
||||
Profile(string.substring(0, string.length - 1))
|
||||
} else {
|
||||
Profile(string.trim())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the string representation of the profile exceeds the limit of 1000 bytes length.
|
||||
*
|
||||
* @param profile profile
|
||||
* @return `true` if the profile exceeds 1000 bytes, `false` otherwise.
|
||||
*/
|
||||
@JvmStatic
|
||||
private fun exceeds1000CharLineLimit(profile: Profile): Boolean =
|
||||
profile.toString().toByteArray(UTF8Util.UTF8).size > 1000
|
||||
}
|
||||
}
|
49
sop-java/src/main/kotlin/sop/Ready.kt
Normal file
49
sop-java/src/main/kotlin/sop/Ready.kt
Normal file
|
@ -0,0 +1,49 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
/** Abstract class that encapsulates output data, waiting to be consumed. */
|
||||
abstract class Ready {
|
||||
|
||||
/**
|
||||
* Write the data to the provided output stream.
|
||||
*
|
||||
* @param outputStream output stream
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(IOException::class) abstract fun writeTo(outputStream: OutputStream)
|
||||
|
||||
/**
|
||||
* Return the data as a byte array by writing it to a [ByteArrayOutputStream] first and then
|
||||
* returning the array.
|
||||
*
|
||||
* @return data as byte array
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
val bytes: ByteArray
|
||||
@Throws(IOException::class)
|
||||
get() =
|
||||
ByteArrayOutputStream()
|
||||
.let {
|
||||
writeTo(it)
|
||||
it
|
||||
}
|
||||
.toByteArray()
|
||||
|
||||
/**
|
||||
* Return an input stream containing the data.
|
||||
*
|
||||
* @return input stream
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
val inputStream: InputStream
|
||||
@Throws(IOException::class) get() = ByteArrayInputStream(bytes)
|
||||
}
|
41
sop-java/src/main/kotlin/sop/ReadyWithResult.kt
Normal file
41
sop-java/src/main/kotlin/sop/ReadyWithResult.kt
Normal file
|
@ -0,0 +1,41 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import sop.exception.SOPGPException
|
||||
|
||||
abstract class ReadyWithResult<T> {
|
||||
|
||||
/**
|
||||
* Write the data e.g. decrypted plaintext to the provided output stream and return the result
|
||||
* of the processing operation.
|
||||
*
|
||||
* @param outputStream output stream
|
||||
* @return result, eg. signatures
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws SOPGPException in case of a SOP protocol error
|
||||
*/
|
||||
@Throws(IOException::class, SOPGPException::class)
|
||||
abstract fun writeTo(outputStream: OutputStream): T
|
||||
|
||||
/**
|
||||
* Return the data as a [ByteArrayAndResult]. Calling [ByteArrayAndResult.bytes] will give you
|
||||
* access to the data as byte array, while [ByteArrayAndResult.result] will grant access to the
|
||||
* appended result.
|
||||
*
|
||||
* @return byte array and result
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws SOPGPException.NoSignature if there are no valid signatures found
|
||||
*/
|
||||
@Throws(IOException::class, SOPGPException::class)
|
||||
fun toByteArrayAndResult() =
|
||||
ByteArrayOutputStream().let {
|
||||
val result = writeTo(it)
|
||||
ByteArrayAndResult(it.toByteArray(), result)
|
||||
}
|
||||
}
|
97
sop-java/src/main/kotlin/sop/SOP.kt
Normal file
97
sop-java/src/main/kotlin/sop/SOP.kt
Normal file
|
@ -0,0 +1,97 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop
|
||||
|
||||
import sop.operation.Armor
|
||||
import sop.operation.ChangeKeyPassword
|
||||
import sop.operation.Dearmor
|
||||
import sop.operation.Decrypt
|
||||
import sop.operation.DetachedSign
|
||||
import sop.operation.DetachedVerify
|
||||
import sop.operation.Encrypt
|
||||
import sop.operation.ExtractCert
|
||||
import sop.operation.GenerateKey
|
||||
import sop.operation.InlineDetach
|
||||
import sop.operation.InlineSign
|
||||
import sop.operation.InlineVerify
|
||||
import sop.operation.ListProfiles
|
||||
import sop.operation.RevokeKey
|
||||
import sop.operation.Version
|
||||
|
||||
/**
|
||||
* Stateless OpenPGP Interface. This class provides a stateless interface to various OpenPGP related
|
||||
* operations. Note: Subcommand objects acquired by calling any method of this interface are not
|
||||
* intended for reuse. If you for example need to generate multiple keys, make a dedicated call to
|
||||
* [generateKey] once per key generation.
|
||||
*/
|
||||
interface SOP {
|
||||
|
||||
/** Get information about the implementations name and version. */
|
||||
fun version(): Version
|
||||
|
||||
/** Generate a secret key. */
|
||||
fun generateKey(): GenerateKey
|
||||
|
||||
/** Extract a certificate (public key) from a secret key. */
|
||||
fun extractCert(): ExtractCert
|
||||
|
||||
/**
|
||||
* Create detached signatures. If you want to sign a message inline, use [inlineSign] instead.
|
||||
*/
|
||||
fun sign(): DetachedSign = detachedSign()
|
||||
|
||||
/**
|
||||
* Create detached signatures. If you want to sign a message inline, use [inlineSign] instead.
|
||||
*/
|
||||
fun detachedSign(): DetachedSign
|
||||
|
||||
/**
|
||||
* Sign a message using inline signatures. If you need to create detached signatures, use
|
||||
* [detachedSign] instead.
|
||||
*/
|
||||
fun inlineSign(): InlineSign
|
||||
|
||||
/**
|
||||
* Verify detached signatures. If you need to verify an inline-signed message, use
|
||||
* [inlineVerify] instead.
|
||||
*/
|
||||
fun verify(): DetachedVerify = detachedVerify()
|
||||
|
||||
/**
|
||||
* Verify detached signatures. If you need to verify an inline-signed message, use
|
||||
* [inlineVerify] instead.
|
||||
*/
|
||||
fun detachedVerify(): DetachedVerify
|
||||
|
||||
/**
|
||||
* Verify signatures of an inline-signed message. If you need to verify detached signatures over
|
||||
* a message, use [detachedVerify] instead.
|
||||
*/
|
||||
fun inlineVerify(): InlineVerify
|
||||
|
||||
/** Detach signatures from an inline signed message. */
|
||||
fun inlineDetach(): InlineDetach
|
||||
|
||||
/** Encrypt a message. */
|
||||
fun encrypt(): Encrypt
|
||||
|
||||
/** Decrypt a message. */
|
||||
fun decrypt(): Decrypt
|
||||
|
||||
/** Convert binary OpenPGP data to ASCII. */
|
||||
fun armor(): Armor
|
||||
|
||||
/** Converts ASCII armored OpenPGP data to binary. */
|
||||
fun dearmor(): Dearmor
|
||||
|
||||
/** List supported [Profiles][Profile] of a subcommand. */
|
||||
fun listProfiles(): ListProfiles
|
||||
|
||||
/** Revoke one or more secret keys. */
|
||||
fun revokeKey(): RevokeKey
|
||||
|
||||
/** Update a key's password. */
|
||||
fun changeKeyPassword(): ChangeKeyPassword
|
||||
}
|
48
sop-java/src/main/kotlin/sop/SessionKey.kt
Normal file
48
sop-java/src/main/kotlin/sop/SessionKey.kt
Normal file
|
@ -0,0 +1,48 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop
|
||||
|
||||
import sop.util.HexUtil
|
||||
|
||||
/**
|
||||
* Class representing a symmetric session key.
|
||||
*
|
||||
* @param algorithm symmetric key algorithm ID
|
||||
* @param key [ByteArray] containing the session key
|
||||
*/
|
||||
data class SessionKey(val algorithm: Byte, val key: ByteArray) {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as SessionKey
|
||||
|
||||
if (algorithm != other.algorithm) return false
|
||||
if (!key.contentEquals(other.key)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var hashCode = algorithm.toInt()
|
||||
hashCode = 31 * hashCode + key.contentHashCode()
|
||||
return hashCode
|
||||
}
|
||||
|
||||
override fun toString(): String = "$algorithm:${HexUtil.bytesToHex(key)}"
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic private val PATTERN = "^(\\d):([0-9A-F]+)$".toPattern()
|
||||
|
||||
@JvmStatic
|
||||
fun fromString(string: String): SessionKey {
|
||||
val matcher = PATTERN.matcher(string.trim().uppercase().replace("\n", ""))
|
||||
require(matcher.matches()) { "Provided session key does not match expected format." }
|
||||
return SessionKey(matcher.group(1).toByte(), HexUtil.hexToBytes(matcher.group(2)))
|
||||
}
|
||||
}
|
||||
}
|
19
sop-java/src/main/kotlin/sop/Signatures.kt
Normal file
19
sop-java/src/main/kotlin/sop/Signatures.kt
Normal file
|
@ -0,0 +1,19 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
|
||||
abstract class Signatures : Ready() {
|
||||
|
||||
/**
|
||||
* Write OpenPGP signatures to the provided output stream.
|
||||
*
|
||||
* @param outputStream signature output stream
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(IOException::class) abstract override fun writeTo(outputStream: OutputStream)
|
||||
}
|
28
sop-java/src/main/kotlin/sop/SigningResult.kt
Normal file
28
sop-java/src/main/kotlin/sop/SigningResult.kt
Normal file
|
@ -0,0 +1,28 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop
|
||||
|
||||
/**
|
||||
* This class contains various information about a signed message.
|
||||
*
|
||||
* @param micAlg string identifying the digest mechanism used to create the signed message. This is
|
||||
* useful for setting the `micalg=` parameter for the multipart/signed content-type of a PGP/MIME
|
||||
* object as described in section 5 of [RFC3156]. If more than one signature was generated and
|
||||
* different digest mechanisms were used, the value of the micalg object is an empty string.
|
||||
*/
|
||||
data class SigningResult(val micAlg: MicAlg) {
|
||||
|
||||
class Builder internal constructor() {
|
||||
private var micAlg = MicAlg.empty()
|
||||
|
||||
fun setMicAlg(micAlg: MicAlg) = apply { this.micAlg = micAlg }
|
||||
|
||||
fun build() = SigningResult(micAlg)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
}
|
76
sop-java/src/main/kotlin/sop/Verification.kt
Normal file
76
sop-java/src/main/kotlin/sop/Verification.kt
Normal file
|
@ -0,0 +1,76 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop
|
||||
|
||||
import java.text.ParseException
|
||||
import java.util.Date
|
||||
import sop.enums.SignatureMode
|
||||
import sop.util.Optional
|
||||
import sop.util.UTCUtil
|
||||
|
||||
data class Verification(
|
||||
val creationTime: Date,
|
||||
val signingKeyFingerprint: String,
|
||||
val signingCertFingerprint: String,
|
||||
val signatureMode: Optional<SignatureMode>,
|
||||
val description: Optional<String>
|
||||
) {
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
creationTime: Date,
|
||||
signingKeyFingerprint: String,
|
||||
signingCertFingerprint: String,
|
||||
signatureMode: SignatureMode? = null,
|
||||
description: String? = null
|
||||
) : this(
|
||||
creationTime,
|
||||
signingKeyFingerprint,
|
||||
signingCertFingerprint,
|
||||
Optional.ofNullable(signatureMode),
|
||||
Optional.ofNullable(description?.trim()))
|
||||
|
||||
override fun toString(): String =
|
||||
"${UTCUtil.formatUTCDate(creationTime)} $signingKeyFingerprint $signingCertFingerprint" +
|
||||
(if (signatureMode.isPresent) " mode:${signatureMode.get()}" else "") +
|
||||
(if (description.isPresent) " ${description.get()}" else "")
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromString(string: String): Verification {
|
||||
val split = string.trim().split(" ")
|
||||
require(split.size >= 3) {
|
||||
"Verification must be of the format 'UTC-DATE OpenPGPFingerprint OpenPGPFingerprint [mode] [info]'."
|
||||
}
|
||||
if (split.size == 3) {
|
||||
return Verification(parseUTCDate(split[0]), split[1], split[2])
|
||||
}
|
||||
|
||||
var index = 3
|
||||
val mode =
|
||||
if (split[3].startsWith("mode:")) {
|
||||
index += 1
|
||||
SignatureMode.valueOf(split[3].substring("mode:".length))
|
||||
} else null
|
||||
|
||||
val description = split.subList(index, split.size).joinToString(" ").ifBlank { null }
|
||||
|
||||
return Verification(
|
||||
parseUTCDate(split[0]),
|
||||
split[1],
|
||||
split[2],
|
||||
Optional.ofNullable(mode),
|
||||
Optional.ofNullable(description))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun parseUTCDate(string: String): Date {
|
||||
return try {
|
||||
UTCUtil.parseUTCDate(string)
|
||||
} catch (e: ParseException) {
|
||||
throw IllegalArgumentException("Malformed UTC timestamp.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt
Normal file
14
sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt
Normal file
|
@ -0,0 +1,14 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.enums
|
||||
|
||||
@Deprecated("Use of armor labels is deprecated.")
|
||||
enum class ArmorLabel {
|
||||
auto,
|
||||
sig,
|
||||
key,
|
||||
cert,
|
||||
message
|
||||
}
|
10
sop-java/src/main/kotlin/sop/enums/EncryptAs.kt
Normal file
10
sop-java/src/main/kotlin/sop/enums/EncryptAs.kt
Normal file
|
@ -0,0 +1,10 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.enums
|
||||
|
||||
enum class EncryptAs {
|
||||
binary,
|
||||
text
|
||||
}
|
17
sop-java/src/main/kotlin/sop/enums/InlineSignAs.kt
Normal file
17
sop-java/src/main/kotlin/sop/enums/InlineSignAs.kt
Normal file
|
@ -0,0 +1,17 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.enums
|
||||
|
||||
enum class InlineSignAs {
|
||||
|
||||
/** Signature is made over the binary message. */
|
||||
binary,
|
||||
|
||||
/** Signature is made over the message in text mode. */
|
||||
text,
|
||||
|
||||
/** Signature is made using the Cleartext Signature Framework. */
|
||||
clearsigned
|
||||
}
|
12
sop-java/src/main/kotlin/sop/enums/SignAs.kt
Normal file
12
sop-java/src/main/kotlin/sop/enums/SignAs.kt
Normal file
|
@ -0,0 +1,12 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.enums
|
||||
|
||||
enum class SignAs {
|
||||
/** Signature is made over the binary message. */
|
||||
binary,
|
||||
/** Signature is made over the message in text mode. */
|
||||
text
|
||||
}
|
18
sop-java/src/main/kotlin/sop/enums/SignatureMode.kt
Normal file
18
sop-java/src/main/kotlin/sop/enums/SignatureMode.kt
Normal file
|
@ -0,0 +1,18 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.enums
|
||||
|
||||
/**
|
||||
* Enum referencing relevant signature types.
|
||||
*
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.2.1"> RFC4880 §5.2.1 - Signature
|
||||
* Types</a>
|
||||
*/
|
||||
enum class SignatureMode {
|
||||
/** Signature of a binary document (type `0x00`). */
|
||||
binary,
|
||||
/** Signature of a canonical text document (type `0x01`). */
|
||||
text
|
||||
}
|
308
sop-java/src/main/kotlin/sop/exception/SOPGPException.kt
Normal file
308
sop-java/src/main/kotlin/sop/exception/SOPGPException.kt
Normal file
|
@ -0,0 +1,308 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.exception
|
||||
|
||||
abstract class SOPGPException : RuntimeException {
|
||||
|
||||
constructor() : super()
|
||||
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
constructor(cause: Throwable) : super(cause)
|
||||
|
||||
constructor(message: String, cause: Throwable) : super(message, cause)
|
||||
|
||||
abstract fun getExitCode(): Int
|
||||
|
||||
/** No acceptable signatures found (sop verify, inline-verify). */
|
||||
class NoSignature : SOPGPException {
|
||||
@JvmOverloads
|
||||
constructor(message: String = "No verifiable signature found.") : super(message)
|
||||
|
||||
constructor(errorMsg: String, e: NoSignature) : super(errorMsg, e)
|
||||
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 3
|
||||
}
|
||||
}
|
||||
|
||||
/** Asymmetric algorithm unsupported (sop encrypt, sign, inline-sign). */
|
||||
class UnsupportedAsymmetricAlgo(message: String, e: Throwable) : SOPGPException(message, e) {
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 13
|
||||
}
|
||||
}
|
||||
|
||||
/** Certificate not encryption capable (e,g, expired, revoked, unacceptable usage). */
|
||||
class CertCannotEncrypt : SOPGPException {
|
||||
constructor(message: String, cause: Throwable) : super(message, cause)
|
||||
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 17
|
||||
}
|
||||
}
|
||||
|
||||
/** Missing required argument. */
|
||||
class MissingArg : SOPGPException {
|
||||
constructor()
|
||||
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 19
|
||||
}
|
||||
}
|
||||
|
||||
/** Incomplete verification instructions (sop decrypt). */
|
||||
class IncompleteVerification(message: String) : SOPGPException(message) {
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 23
|
||||
}
|
||||
}
|
||||
|
||||
/** Unable to decrypt (sop decrypt). */
|
||||
class CannotDecrypt : SOPGPException {
|
||||
constructor()
|
||||
|
||||
constructor(errorMsg: String, e: Throwable) : super(errorMsg, e)
|
||||
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 29
|
||||
}
|
||||
}
|
||||
|
||||
/** Non-UTF-8 or otherwise unreliable password (sop encrypt). */
|
||||
class PasswordNotHumanReadable : SOPGPException {
|
||||
constructor() : super()
|
||||
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 31
|
||||
}
|
||||
}
|
||||
|
||||
/** Unsupported option. */
|
||||
class UnsupportedOption : SOPGPException {
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
constructor(message: String, cause: Throwable) : super(message, cause)
|
||||
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 37
|
||||
}
|
||||
}
|
||||
|
||||
/** Invalid data type (no secret key where KEYS expected, etc.). */
|
||||
class BadData : SOPGPException {
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
constructor(throwable: Throwable) : super(throwable)
|
||||
|
||||
constructor(message: String, throwable: Throwable) : super(message, throwable)
|
||||
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 41
|
||||
}
|
||||
}
|
||||
|
||||
/** Non-Text input where text expected. */
|
||||
class ExpectedText : SOPGPException {
|
||||
constructor() : super()
|
||||
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 53
|
||||
}
|
||||
}
|
||||
|
||||
/** Output file already exists. */
|
||||
class OutputExists(message: String) : SOPGPException(message) {
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 59
|
||||
}
|
||||
}
|
||||
|
||||
/** Input file does not exist. */
|
||||
class MissingInput : SOPGPException {
|
||||
constructor(message: String, cause: Throwable) : super(message, cause)
|
||||
|
||||
constructor(errorMsg: String) : super(errorMsg)
|
||||
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 61
|
||||
}
|
||||
}
|
||||
|
||||
/** A KEYS input is protected (locked) with a password and sop failed to unlock it. */
|
||||
class KeyIsProtected : SOPGPException {
|
||||
constructor() : super()
|
||||
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
constructor(message: String, cause: Throwable) : super(message, cause)
|
||||
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 67
|
||||
}
|
||||
}
|
||||
|
||||
/** Unsupported subcommand. */
|
||||
class UnsupportedSubcommand(message: String) : SOPGPException(message) {
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 69
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An indirect parameter is a special designator (it starts with @), but sop does not know how
|
||||
* to handle the prefix.
|
||||
*/
|
||||
class UnsupportedSpecialPrefix(errorMsg: String) : SOPGPException(errorMsg) {
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 71
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception that gets thrown if a special designator (starting with @) is given, but the
|
||||
* filesystem contains a file matching the designator.
|
||||
*
|
||||
* E.g. <pre>@ENV:FOO</pre> is given, but <pre>./@ENV:FOO</pre> exists on the filesystem.
|
||||
*/
|
||||
class AmbiguousInput(message: String) : SOPGPException(message) {
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 73
|
||||
}
|
||||
}
|
||||
|
||||
/** Key not signature-capable (e.g. expired, revoked, unacceptable usage flags). */
|
||||
class KeyCannotSign : SOPGPException {
|
||||
constructor() : super()
|
||||
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
constructor(s: String, throwable: Throwable) : super(s, throwable)
|
||||
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 79
|
||||
}
|
||||
}
|
||||
|
||||
/** User provided incompatible options (e.g. "--as=clearsigned --no-armor"). */
|
||||
class IncompatibleOptions : SOPGPException {
|
||||
constructor() : super()
|
||||
|
||||
constructor(errorMsg: String) : super(errorMsg)
|
||||
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 83
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The user provided a subcommand with an unsupported profile ("--profile=XYZ"), or the user
|
||||
* tried to list profiles of a subcommand that does not support profiles at all.
|
||||
*/
|
||||
class UnsupportedProfile : SOPGPException {
|
||||
/**
|
||||
* Return the subcommand name.
|
||||
*
|
||||
* @return subcommand
|
||||
*/
|
||||
val subcommand: String
|
||||
|
||||
/**
|
||||
* Return the profile name. May return `null`.
|
||||
*
|
||||
* @return profile name
|
||||
*/
|
||||
val profile: String?
|
||||
|
||||
/**
|
||||
* Create an exception signalling a subcommand that does not support any profiles.
|
||||
*
|
||||
* @param subcommand subcommand
|
||||
*/
|
||||
constructor(
|
||||
subcommand: String
|
||||
) : super("Subcommand '$subcommand' does not support any profiles.") {
|
||||
this.subcommand = subcommand
|
||||
profile = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an exception signalling a subcommand does not support a specific profile.
|
||||
*
|
||||
* @param subcommand subcommand
|
||||
* @param profile unsupported profile
|
||||
*/
|
||||
constructor(
|
||||
subcommand: String,
|
||||
profile: String
|
||||
) : super("Subcommand '$subcommand' does not support profile '$profile'.") {
|
||||
this.subcommand = subcommand
|
||||
this.profile = profile
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an exception into another instance with a possibly translated error message.
|
||||
*
|
||||
* @param errorMsg error message
|
||||
* @param e exception
|
||||
*/
|
||||
constructor(errorMsg: String, e: UnsupportedProfile) : super(errorMsg, e) {
|
||||
subcommand = e.subcommand
|
||||
profile = e.profile
|
||||
}
|
||||
|
||||
override fun getExitCode(): Int = EXIT_CODE
|
||||
|
||||
companion object {
|
||||
const val EXIT_CODE = 89
|
||||
}
|
||||
}
|
||||
}
|
79
sop-java/src/main/kotlin/sop/operation/AbstractSign.kt
Normal file
79
sop-java/src/main/kotlin/sop/operation/AbstractSign.kt
Normal file
|
@ -0,0 +1,79 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import sop.exception.SOPGPException.BadData
|
||||
import sop.exception.SOPGPException.KeyCannotSign
|
||||
import sop.exception.SOPGPException.PasswordNotHumanReadable
|
||||
import sop.exception.SOPGPException.UnsupportedAsymmetricAlgo
|
||||
import sop.exception.SOPGPException.UnsupportedOption
|
||||
import sop.util.UTF8Util
|
||||
|
||||
/**
|
||||
* Interface for signing operations.
|
||||
*
|
||||
* @param <T> builder subclass
|
||||
*/
|
||||
interface AbstractSign<T> {
|
||||
|
||||
/**
|
||||
* Disable ASCII armor encoding.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
fun noArmor(): T
|
||||
|
||||
/**
|
||||
* Add one or more signing keys.
|
||||
*
|
||||
* @param key input stream containing encoded keys
|
||||
* @return builder instance
|
||||
* @throws KeyCannotSign if the key cannot be used for signing
|
||||
* @throws BadData if the [InputStream] does not contain an OpenPGP key
|
||||
* @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(
|
||||
KeyCannotSign::class, BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class)
|
||||
fun key(key: InputStream): T
|
||||
|
||||
/**
|
||||
* Add one or more signing keys.
|
||||
*
|
||||
* @param key byte array containing encoded keys
|
||||
* @return builder instance
|
||||
* @throws KeyCannotSign if the key cannot be used for signing
|
||||
* @throws BadData if the byte array does not contain an OpenPGP key
|
||||
* @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(
|
||||
KeyCannotSign::class, BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class)
|
||||
fun key(key: ByteArray): T = key(key.inputStream())
|
||||
|
||||
/**
|
||||
* Provide the password for the secret key used for signing.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws UnsupportedOption if key passwords are not supported
|
||||
* @throws PasswordNotHumanReadable if the provided passphrase is not human-readable
|
||||
*/
|
||||
@Throws(UnsupportedOption::class, PasswordNotHumanReadable::class)
|
||||
fun withKeyPassword(password: String): T = withKeyPassword(password.toByteArray(UTF8Util.UTF8))
|
||||
|
||||
/**
|
||||
* Provide the password for the secret key used for signing.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws UnsupportedOption if key passwords are not supported
|
||||
* @throws PasswordNotHumanReadable if the provided passphrase is not human-readable
|
||||
*/
|
||||
@Throws(UnsupportedOption::class, PasswordNotHumanReadable::class)
|
||||
fun withKeyPassword(password: ByteArray): T
|
||||
}
|
57
sop-java/src/main/kotlin/sop/operation/AbstractVerify.kt
Normal file
57
sop-java/src/main/kotlin/sop/operation/AbstractVerify.kt
Normal file
|
@ -0,0 +1,57 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import sop.exception.SOPGPException.BadData
|
||||
import sop.exception.SOPGPException.UnsupportedOption
|
||||
|
||||
/**
|
||||
* Common API methods shared between verification of inline signatures ([InlineVerify]) and
|
||||
* verification of detached signatures ([DetachedVerify]).
|
||||
*
|
||||
* @param <T> Builder type ([DetachedVerify], [InlineVerify])
|
||||
*/
|
||||
interface AbstractVerify<T> {
|
||||
|
||||
/**
|
||||
* Makes the SOP implementation consider signatures before this date invalid.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return builder instance
|
||||
*/
|
||||
@Throws(UnsupportedOption::class) fun notBefore(timestamp: Date): T
|
||||
|
||||
/**
|
||||
* Makes the SOP implementation consider signatures after this date invalid.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return builder instance
|
||||
*/
|
||||
@Throws(UnsupportedOption::class) fun notAfter(timestamp: Date): T
|
||||
|
||||
/**
|
||||
* Add one or more verification cert.
|
||||
*
|
||||
* @param cert input stream containing the encoded certs
|
||||
* @return builder instance
|
||||
* @throws BadData if the input stream does not contain an OpenPGP certificate
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(BadData::class, IOException::class) fun cert(cert: InputStream): T
|
||||
|
||||
/**
|
||||
* Add one or more verification cert.
|
||||
*
|
||||
* @param cert byte array containing the encoded certs
|
||||
* @return builder instance
|
||||
* @throws BadData if the byte array does not contain an OpenPGP certificate
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(BadData::class, IOException::class)
|
||||
fun cert(cert: ByteArray): T = cert(cert.inputStream())
|
||||
}
|
46
sop-java/src/main/kotlin/sop/operation/Armor.kt
Normal file
46
sop-java/src/main/kotlin/sop/operation/Armor.kt
Normal file
|
@ -0,0 +1,46 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import sop.Ready
|
||||
import sop.enums.ArmorLabel
|
||||
import sop.exception.SOPGPException.BadData
|
||||
import sop.exception.SOPGPException.UnsupportedOption
|
||||
|
||||
interface Armor {
|
||||
|
||||
/**
|
||||
* Overrides automatic detection of label.
|
||||
*
|
||||
* @param label armor label
|
||||
* @return builder instance
|
||||
*/
|
||||
@Deprecated("Use of armor labels is deprecated and will be removed in a future release.")
|
||||
@Throws(UnsupportedOption::class)
|
||||
fun label(label: ArmorLabel): Armor
|
||||
|
||||
/**
|
||||
* Armor the provided data.
|
||||
*
|
||||
* @param data input stream of unarmored OpenPGP data
|
||||
* @return armored data
|
||||
* @throws BadData if the data appears to be OpenPGP packets, but those are broken
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(BadData::class, IOException::class) fun data(data: InputStream): Ready
|
||||
|
||||
/**
|
||||
* Armor the provided data.
|
||||
*
|
||||
* @param data unarmored OpenPGP data
|
||||
* @return armored data
|
||||
* @throws BadData if the data appears to be OpenPGP packets, but those are broken
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(BadData::class, IOException::class)
|
||||
fun data(data: ByteArray): Ready = data(data.inputStream())
|
||||
}
|
95
sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt
Normal file
95
sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt
Normal file
|
@ -0,0 +1,95 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.InputStream
|
||||
import sop.Ready
|
||||
import sop.exception.SOPGPException.BadData
|
||||
import sop.exception.SOPGPException.KeyIsProtected
|
||||
import sop.exception.SOPGPException.PasswordNotHumanReadable
|
||||
import sop.util.UTF8Util
|
||||
|
||||
interface ChangeKeyPassword {
|
||||
|
||||
/**
|
||||
* Disable ASCII armoring of the output.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
fun noArmor(): ChangeKeyPassword
|
||||
|
||||
/**
|
||||
* Provide a passphrase to unlock the secret key. This method can be provided multiple times to
|
||||
* provide separate passphrases that are tried as a means to unlock any secret key material
|
||||
* encountered.
|
||||
*
|
||||
* @param oldPassphrase old passphrase
|
||||
* @return builder instance
|
||||
*/
|
||||
@Throws(PasswordNotHumanReadable::class)
|
||||
fun oldKeyPassphrase(oldPassphrase: ByteArray): ChangeKeyPassword =
|
||||
try {
|
||||
oldKeyPassphrase(UTF8Util.decodeUTF8(oldPassphrase))
|
||||
} catch (e: CharacterCodingException) {
|
||||
throw PasswordNotHumanReadable("Password MUST be a valid UTF8 string.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a passphrase to unlock the secret key. This method can be provided multiple times to
|
||||
* provide separate passphrases that are tried as a means to unlock any secret key material
|
||||
* encountered.
|
||||
*
|
||||
* @param oldPassphrase old passphrase
|
||||
* @return builder instance
|
||||
*/
|
||||
fun oldKeyPassphrase(oldPassphrase: String): ChangeKeyPassword
|
||||
|
||||
/**
|
||||
* Provide a passphrase to re-lock the secret key with. This method can only be used once, and
|
||||
* all key material encountered will be encrypted with the given passphrase. If this method is
|
||||
* not called, the key material will not be protected.
|
||||
*
|
||||
* @param newPassphrase new passphrase
|
||||
* @return builder instance
|
||||
*/
|
||||
@Throws(PasswordNotHumanReadable::class)
|
||||
fun newKeyPassphrase(newPassphrase: ByteArray): ChangeKeyPassword =
|
||||
try {
|
||||
newKeyPassphrase(UTF8Util.decodeUTF8(newPassphrase))
|
||||
} catch (e: CharacterCodingException) {
|
||||
throw PasswordNotHumanReadable("Password MUST be a valid UTF8 string.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a passphrase to re-lock the secret key with. This method can only be used once, and
|
||||
* all key material encountered will be encrypted with the given passphrase. If this method is
|
||||
* not called, the key material will not be protected.
|
||||
*
|
||||
* @param newPassphrase new passphrase
|
||||
* @return builder instance
|
||||
*/
|
||||
fun newKeyPassphrase(newPassphrase: String): ChangeKeyPassword
|
||||
|
||||
/**
|
||||
* Provide the key material.
|
||||
*
|
||||
* @param keys input stream of secret key material
|
||||
* @return ready
|
||||
* @throws KeyIsProtected if any (sub-) key encountered cannot be unlocked.
|
||||
* @throws BadData if the key material is malformed
|
||||
*/
|
||||
@Throws(KeyIsProtected::class, BadData::class)
|
||||
fun keys(keys: ByteArray): Ready = keys(keys.inputStream())
|
||||
|
||||
/**
|
||||
* Provide the key material.
|
||||
*
|
||||
* @param keys input stream of secret key material
|
||||
* @return ready
|
||||
* @throws KeyIsProtected if any (sub-) key encountered cannot be unlocked.
|
||||
* @throws BadData if the key material is malformed
|
||||
*/
|
||||
@Throws(KeyIsProtected::class, BadData::class) fun keys(keys: InputStream): Ready
|
||||
}
|
46
sop-java/src/main/kotlin/sop/operation/Dearmor.kt
Normal file
46
sop-java/src/main/kotlin/sop/operation/Dearmor.kt
Normal file
|
@ -0,0 +1,46 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import sop.Ready
|
||||
import sop.exception.SOPGPException.BadData
|
||||
import sop.util.UTF8Util
|
||||
|
||||
interface Dearmor {
|
||||
|
||||
/**
|
||||
* Dearmor armored OpenPGP data.
|
||||
*
|
||||
* @param data armored OpenPGP data
|
||||
* @return input stream of unarmored data
|
||||
* @throws BadData in case of non-OpenPGP data
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(BadData::class, IOException::class) fun data(data: InputStream): Ready
|
||||
|
||||
/**
|
||||
* Dearmor armored OpenPGP data.
|
||||
*
|
||||
* @param data armored OpenPGP data
|
||||
* @return input stream of unarmored data
|
||||
* @throws BadData in case of non-OpenPGP data
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(BadData::class, IOException::class)
|
||||
fun data(data: ByteArray): Ready = data(data.inputStream())
|
||||
|
||||
/**
|
||||
* Dearmor amored OpenPGP data.
|
||||
*
|
||||
* @param data armored OpenPGP data
|
||||
* @return input stream of unarmored data
|
||||
* @throws BadData in case of non-OpenPGP data
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(BadData::class, IOException::class)
|
||||
fun data(data: String): Ready = data(data.toByteArray(UTF8Util.UTF8))
|
||||
}
|
165
sop-java/src/main/kotlin/sop/operation/Decrypt.kt
Normal file
165
sop-java/src/main/kotlin/sop/operation/Decrypt.kt
Normal file
|
@ -0,0 +1,165 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import sop.DecryptionResult
|
||||
import sop.ReadyWithResult
|
||||
import sop.SessionKey
|
||||
import sop.exception.SOPGPException.*
|
||||
import sop.util.UTF8Util
|
||||
|
||||
interface Decrypt {
|
||||
|
||||
/**
|
||||
* Makes the SOP consider signatures before this date invalid.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return builder instance
|
||||
* @throws UnsupportedOption if this option is not supported
|
||||
*/
|
||||
@Throws(UnsupportedOption::class) fun verifyNotBefore(timestamp: Date): Decrypt
|
||||
|
||||
/**
|
||||
* Makes the SOP consider signatures after this date invalid.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return builder instance
|
||||
* @throws UnsupportedOption if this option is not supported
|
||||
*/
|
||||
@Throws(UnsupportedOption::class) fun verifyNotAfter(timestamp: Date): Decrypt
|
||||
|
||||
/**
|
||||
* Adds one or more verification cert.
|
||||
*
|
||||
* @param cert input stream containing the cert(s)
|
||||
* @return builder instance
|
||||
* @throws BadData if the [InputStream] doesn't provide an OpenPGP certificate
|
||||
* @throws UnsupportedAsymmetricAlgo if the cert uses an unsupported asymmetric algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class)
|
||||
fun verifyWithCert(cert: InputStream): Decrypt
|
||||
|
||||
/**
|
||||
* Adds one or more verification cert.
|
||||
*
|
||||
* @param cert byte array containing the cert(s)
|
||||
* @return builder instance
|
||||
* @throws BadData if the byte array doesn't contain an OpenPGP certificate
|
||||
* @throws UnsupportedAsymmetricAlgo if the cert uses an unsupported asymmetric algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class)
|
||||
fun verifyWithCert(cert: ByteArray): Decrypt = verifyWithCert(cert.inputStream())
|
||||
|
||||
/**
|
||||
* Tries to decrypt with the given session key.
|
||||
*
|
||||
* @param sessionKey session key
|
||||
* @return builder instance
|
||||
* @throws UnsupportedOption if this option is not supported
|
||||
*/
|
||||
@Throws(UnsupportedOption::class) fun withSessionKey(sessionKey: SessionKey): Decrypt
|
||||
|
||||
/**
|
||||
* Tries to decrypt with the given password.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws PasswordNotHumanReadable if the password is not human-readable
|
||||
* @throws UnsupportedOption if this option is not supported
|
||||
*/
|
||||
@Throws(PasswordNotHumanReadable::class, UnsupportedOption::class)
|
||||
fun withPassword(password: String): Decrypt
|
||||
|
||||
/**
|
||||
* Adds one or more decryption key.
|
||||
*
|
||||
* @param key input stream containing the key(s)
|
||||
* @return builder instance
|
||||
* @throws BadData if the [InputStream] does not provide an OpenPGP key
|
||||
* @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class)
|
||||
fun withKey(key: InputStream): Decrypt
|
||||
|
||||
/**
|
||||
* Adds one or more decryption key.
|
||||
*
|
||||
* @param key byte array containing the key(s)
|
||||
* @return builder instance
|
||||
* @throws BadData if the byte array does not contain an OpenPGP key
|
||||
* @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class)
|
||||
fun withKey(key: ByteArray): Decrypt = withKey(key.inputStream())
|
||||
|
||||
/**
|
||||
* Provide the decryption password for the secret key.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws UnsupportedOption if the implementation does not support key passwords
|
||||
* @throws PasswordNotHumanReadable if the password is not human-readable
|
||||
*/
|
||||
@Throws(UnsupportedOption::class, PasswordNotHumanReadable::class)
|
||||
fun withKeyPassword(password: String): Decrypt =
|
||||
withKeyPassword(password.toByteArray(UTF8Util.UTF8))
|
||||
|
||||
/**
|
||||
* Provide the decryption password for the secret key.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws UnsupportedOption if the implementation does not support key passwords
|
||||
* @throws PasswordNotHumanReadable if the password is not human-readable
|
||||
*/
|
||||
@Throws(UnsupportedOption::class, PasswordNotHumanReadable::class)
|
||||
fun withKeyPassword(password: ByteArray): Decrypt
|
||||
|
||||
/**
|
||||
* Decrypts the given ciphertext, returning verification results and plaintext.
|
||||
*
|
||||
* @param ciphertext ciphertext
|
||||
* @return ready with result
|
||||
* @throws BadData if the [InputStream] does not provide an OpenPGP message
|
||||
* @throws MissingArg if an argument required for decryption was not provided
|
||||
* @throws CannotDecrypt in case decryption fails for some reason
|
||||
* @throws KeyIsProtected if the decryption key cannot be unlocked (e.g. missing passphrase)
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(
|
||||
BadData::class,
|
||||
MissingArg::class,
|
||||
CannotDecrypt::class,
|
||||
KeyIsProtected::class,
|
||||
IOException::class)
|
||||
fun ciphertext(ciphertext: InputStream): ReadyWithResult<DecryptionResult>
|
||||
|
||||
/**
|
||||
* Decrypts the given ciphertext, returning verification results and plaintext.
|
||||
*
|
||||
* @param ciphertext ciphertext
|
||||
* @return ready with result
|
||||
* @throws BadData if the byte array does not contain an encrypted OpenPGP message
|
||||
* @throws MissingArg in case of missing decryption method (password or key required)
|
||||
* @throws CannotDecrypt in case decryption fails for some reason
|
||||
* @throws KeyIsProtected if the decryption key cannot be unlocked (e.g. missing passphrase)
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(
|
||||
BadData::class,
|
||||
MissingArg::class,
|
||||
CannotDecrypt::class,
|
||||
KeyIsProtected::class,
|
||||
IOException::class)
|
||||
fun ciphertext(ciphertext: ByteArray): ReadyWithResult<DecryptionResult> =
|
||||
ciphertext(ciphertext.inputStream())
|
||||
}
|
50
sop-java/src/main/kotlin/sop/operation/DetachedSign.kt
Normal file
50
sop-java/src/main/kotlin/sop/operation/DetachedSign.kt
Normal file
|
@ -0,0 +1,50 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import sop.ReadyWithResult
|
||||
import sop.SigningResult
|
||||
import sop.enums.SignAs
|
||||
import sop.exception.SOPGPException.*
|
||||
|
||||
interface DetachedSign : AbstractSign<DetachedSign> {
|
||||
|
||||
/**
|
||||
* Sets the signature mode. Note: This method has to be called before [key] is called.
|
||||
*
|
||||
* @param mode signature mode
|
||||
* @return builder instance
|
||||
* @throws UnsupportedOption if this option is not supported
|
||||
*/
|
||||
@Throws(UnsupportedOption::class) fun mode(mode: SignAs): DetachedSign
|
||||
|
||||
/**
|
||||
* Signs data.
|
||||
*
|
||||
* @param data input stream containing data
|
||||
* @return ready
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws KeyIsProtected if at least one signing key cannot be unlocked
|
||||
* @throws ExpectedText if text data was expected, but binary data was encountered
|
||||
*/
|
||||
@Throws(IOException::class, KeyIsProtected::class, ExpectedText::class)
|
||||
fun data(data: InputStream): ReadyWithResult<SigningResult>
|
||||
|
||||
/**
|
||||
* Signs data.
|
||||
*
|
||||
* @param data byte array containing data
|
||||
* @return ready
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be
|
||||
* unlocked
|
||||
* @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data
|
||||
* was encountered
|
||||
*/
|
||||
@Throws(IOException::class, KeyIsProtected::class, ExpectedText::class)
|
||||
fun data(data: ByteArray): ReadyWithResult<SigningResult> = data(data.inputStream())
|
||||
}
|
34
sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt
Normal file
34
sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt
Normal file
|
@ -0,0 +1,34 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import sop.exception.SOPGPException.BadData
|
||||
|
||||
interface DetachedVerify : AbstractVerify<DetachedVerify>, VerifySignatures {
|
||||
|
||||
/**
|
||||
* Provides the detached signatures.
|
||||
*
|
||||
* @param signatures input stream containing encoded, detached signatures.
|
||||
* @return builder instance
|
||||
* @throws BadData if the input stream does not contain OpenPGP signatures
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(BadData::class, IOException::class)
|
||||
fun signatures(signatures: InputStream): VerifySignatures
|
||||
|
||||
/**
|
||||
* Provides the detached signatures.
|
||||
*
|
||||
* @param signatures byte array containing encoded, detached signatures.
|
||||
* @return builder instance
|
||||
* @throws BadData if the byte array does not contain OpenPGP signatures
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(BadData::class, IOException::class)
|
||||
fun signatures(signatures: ByteArray): VerifySignatures = signatures(signatures.inputStream())
|
||||
}
|
165
sop-java/src/main/kotlin/sop/operation/Encrypt.kt
Normal file
165
sop-java/src/main/kotlin/sop/operation/Encrypt.kt
Normal file
|
@ -0,0 +1,165 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import sop.Profile
|
||||
import sop.Ready
|
||||
import sop.enums.EncryptAs
|
||||
import sop.exception.SOPGPException.*
|
||||
import sop.util.UTF8Util
|
||||
|
||||
interface Encrypt {
|
||||
|
||||
/**
|
||||
* Disable ASCII armor encoding.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
fun noArmor(): Encrypt
|
||||
|
||||
/**
|
||||
* Sets encryption mode.
|
||||
*
|
||||
* @param mode mode
|
||||
* @return builder instance
|
||||
* @throws UnsupportedOption if this option is not supported
|
||||
*/
|
||||
@Throws(UnsupportedOption::class) fun mode(mode: EncryptAs): Encrypt
|
||||
|
||||
/**
|
||||
* Adds the signer key.
|
||||
*
|
||||
* @param key input stream containing the encoded signer key
|
||||
* @return builder instance
|
||||
* @throws KeyCannotSign if the key cannot be used for signing
|
||||
* @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm
|
||||
* @throws BadData if the [InputStream] does not contain an OpenPGP key
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(
|
||||
KeyCannotSign::class, UnsupportedAsymmetricAlgo::class, BadData::class, IOException::class)
|
||||
fun signWith(key: InputStream): Encrypt
|
||||
|
||||
/**
|
||||
* Adds the signer key.
|
||||
*
|
||||
* @param key byte array containing the encoded signer key
|
||||
* @return builder instance
|
||||
* @throws KeyCannotSign if the key cannot be used for signing
|
||||
* @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm
|
||||
* @throws BadData if the byte array does not contain an OpenPGP key
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(
|
||||
KeyCannotSign::class, UnsupportedAsymmetricAlgo::class, BadData::class, IOException::class)
|
||||
fun signWith(key: ByteArray): Encrypt = signWith(key.inputStream())
|
||||
|
||||
/**
|
||||
* Provide the password for the secret key used for signing.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws PasswordNotHumanReadable if the password is not human-readable
|
||||
* @throws UnsupportedOption if key password are not supported
|
||||
*/
|
||||
@Throws(PasswordNotHumanReadable::class, UnsupportedOption::class)
|
||||
fun withKeyPassword(password: String): Encrypt =
|
||||
withKeyPassword(password.toByteArray(UTF8Util.UTF8))
|
||||
|
||||
/**
|
||||
* Provide the password for the secret key used for signing.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws PasswordNotHumanReadable if the password is not human-readable
|
||||
* @throws UnsupportedOption if key password are not supported
|
||||
*/
|
||||
@Throws(PasswordNotHumanReadable::class, UnsupportedOption::class)
|
||||
fun withKeyPassword(password: ByteArray): Encrypt
|
||||
|
||||
/**
|
||||
* Encrypt with the given password.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws PasswordNotHumanReadable if the password is not human-readable
|
||||
* @throws UnsupportedOption if this option is not supported
|
||||
*/
|
||||
@Throws(PasswordNotHumanReadable::class, UnsupportedOption::class)
|
||||
fun withPassword(password: String): Encrypt
|
||||
|
||||
/**
|
||||
* Encrypt with the given cert.
|
||||
*
|
||||
* @param cert input stream containing the encoded cert.
|
||||
* @return builder instance
|
||||
* @throws CertCannotEncrypt if the certificate is not encryption capable
|
||||
* @throws UnsupportedAsymmetricAlgo if the certificate uses an unsupported asymmetric algorithm
|
||||
* @throws BadData if the [InputStream] does not contain an OpenPGP certificate
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(
|
||||
CertCannotEncrypt::class,
|
||||
UnsupportedAsymmetricAlgo::class,
|
||||
BadData::class,
|
||||
IOException::class)
|
||||
fun withCert(cert: InputStream): Encrypt
|
||||
|
||||
/**
|
||||
* Encrypt with the given cert.
|
||||
*
|
||||
* @param cert byte array containing the encoded cert.
|
||||
* @return builder instance
|
||||
* @throws CertCannotEncrypt if the certificate is not encryption capable
|
||||
* @throws UnsupportedAsymmetricAlgo if the certificate uses an unsupported asymmetric algorithm
|
||||
* @throws BadData if the byte array does not contain an OpenPGP certificate
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(
|
||||
CertCannotEncrypt::class,
|
||||
UnsupportedAsymmetricAlgo::class,
|
||||
BadData::class,
|
||||
IOException::class)
|
||||
fun withCert(cert: ByteArray): Encrypt = withCert(cert.inputStream())
|
||||
|
||||
/**
|
||||
* Pass in a profile.
|
||||
*
|
||||
* @param profile profile
|
||||
* @return builder instance
|
||||
*/
|
||||
fun profile(profile: Profile): Encrypt = profile(profile.name)
|
||||
|
||||
/**
|
||||
* Pass in a profile identifier.
|
||||
*
|
||||
* @param profileName profile identifier
|
||||
* @return builder instance
|
||||
*/
|
||||
fun profile(profileName: String): Encrypt
|
||||
|
||||
/**
|
||||
* Encrypt the given data yielding the ciphertext.
|
||||
*
|
||||
* @param plaintext plaintext
|
||||
* @return input stream containing the ciphertext
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws KeyIsProtected if at least one signing key cannot be unlocked
|
||||
*/
|
||||
@Throws(IOException::class, KeyIsProtected::class) fun plaintext(plaintext: InputStream): Ready
|
||||
|
||||
/**
|
||||
* Encrypt the given data yielding the ciphertext.
|
||||
*
|
||||
* @param plaintext plaintext
|
||||
* @return input stream containing the ciphertext
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws KeyIsProtected if at least one signing key cannot be unlocked
|
||||
*/
|
||||
@Throws(IOException::class, KeyIsProtected::class)
|
||||
fun plaintext(plaintext: ByteArray): Ready = plaintext(plaintext.inputStream())
|
||||
}
|
41
sop-java/src/main/kotlin/sop/operation/ExtractCert.kt
Normal file
41
sop-java/src/main/kotlin/sop/operation/ExtractCert.kt
Normal file
|
@ -0,0 +1,41 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import sop.Ready
|
||||
import sop.exception.SOPGPException.BadData
|
||||
|
||||
interface ExtractCert {
|
||||
|
||||
/**
|
||||
* Disable ASCII armor encoding.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
fun noArmor(): ExtractCert
|
||||
|
||||
/**
|
||||
* Extract the cert(s) from the provided key(s).
|
||||
*
|
||||
* @param keyInputStream input stream containing the encoding of one or more OpenPGP keys
|
||||
* @return result containing the encoding of the keys certs
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws BadData if the [InputStream] does not contain an OpenPGP key
|
||||
*/
|
||||
@Throws(IOException::class, BadData::class) fun key(keyInputStream: InputStream): Ready
|
||||
|
||||
/**
|
||||
* Extract the cert(s) from the provided key(s).
|
||||
*
|
||||
* @param key byte array containing the encoding of one or more OpenPGP key
|
||||
* @return result containing the encoding of the keys certs
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws BadData if the byte array does not contain an OpenPGP key
|
||||
*/
|
||||
@Throws(IOException::class, BadData::class)
|
||||
fun key(key: ByteArray): Ready = key(key.inputStream())
|
||||
}
|
91
sop-java/src/main/kotlin/sop/operation/GenerateKey.kt
Normal file
91
sop-java/src/main/kotlin/sop/operation/GenerateKey.kt
Normal file
|
@ -0,0 +1,91 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import sop.Profile
|
||||
import sop.Ready
|
||||
import sop.exception.SOPGPException.*
|
||||
import sop.util.UTF8Util
|
||||
|
||||
interface GenerateKey {
|
||||
|
||||
/**
|
||||
* Disable ASCII armor encoding.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
fun noArmor(): GenerateKey
|
||||
|
||||
/**
|
||||
* Adds a user-id.
|
||||
*
|
||||
* @param userId user-id
|
||||
* @return builder instance
|
||||
*/
|
||||
fun userId(userId: String): GenerateKey
|
||||
|
||||
/**
|
||||
* Set a password for the key.
|
||||
*
|
||||
* @param password password to protect the key
|
||||
* @return builder instance
|
||||
* @throws UnsupportedOption if key passwords are not supported
|
||||
* @throws PasswordNotHumanReadable if the password is not human-readable
|
||||
*/
|
||||
@Throws(PasswordNotHumanReadable::class, UnsupportedOption::class)
|
||||
fun withKeyPassword(password: String): GenerateKey
|
||||
|
||||
/**
|
||||
* Set a password for the key.
|
||||
*
|
||||
* @param password password to protect the key
|
||||
* @return builder instance
|
||||
* @throws PasswordNotHumanReadable if the password is not human-readable
|
||||
* @throws UnsupportedOption if key passwords are not supported
|
||||
*/
|
||||
@Throws(PasswordNotHumanReadable::class, UnsupportedOption::class)
|
||||
fun withKeyPassword(password: ByteArray): GenerateKey =
|
||||
try {
|
||||
withKeyPassword(UTF8Util.decodeUTF8(password))
|
||||
} catch (e: CharacterCodingException) {
|
||||
throw PasswordNotHumanReadable()
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass in a profile.
|
||||
*
|
||||
* @param profile profile
|
||||
* @return builder instance
|
||||
*/
|
||||
fun profile(profile: Profile): GenerateKey = profile(profile.name)
|
||||
|
||||
/**
|
||||
* Pass in a profile identifier.
|
||||
*
|
||||
* @param profile profile identifier
|
||||
* @return builder instance
|
||||
*/
|
||||
fun profile(profile: String): GenerateKey
|
||||
|
||||
/**
|
||||
* If this options is set, the generated key will not be capable of encryption / decryption.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
fun signingOnly(): GenerateKey
|
||||
|
||||
/**
|
||||
* Generate the OpenPGP key and return it encoded as an [InputStream].
|
||||
*
|
||||
* @return key
|
||||
* @throws MissingArg if no user-id was provided
|
||||
* @throws UnsupportedAsymmetricAlgo if the generated key uses an unsupported asymmetric
|
||||
* algorithm
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@Throws(MissingArg::class, UnsupportedAsymmetricAlgo::class, IOException::class)
|
||||
fun generate(): Ready
|
||||
}
|
43
sop-java/src/main/kotlin/sop/operation/InlineDetach.kt
Normal file
43
sop-java/src/main/kotlin/sop/operation/InlineDetach.kt
Normal file
|
@ -0,0 +1,43 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import sop.ReadyWithResult
|
||||
import sop.Signatures
|
||||
import sop.exception.SOPGPException.BadData
|
||||
|
||||
interface InlineDetach {
|
||||
|
||||
/**
|
||||
* Do not wrap the signatures in ASCII armor.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
fun noArmor(): InlineDetach
|
||||
|
||||
/**
|
||||
* Detach the provided signed message from its signatures.
|
||||
*
|
||||
* @param messageInputStream input stream containing the signed message
|
||||
* @return result containing the detached message
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws BadData if the input stream does not contain a signed message
|
||||
*/
|
||||
@Throws(IOException::class, BadData::class)
|
||||
fun message(messageInputStream: InputStream): ReadyWithResult<Signatures>
|
||||
|
||||
/**
|
||||
* Detach the provided cleartext signed message from its signatures.
|
||||
*
|
||||
* @param message byte array containing the signed message
|
||||
* @return result containing the detached message
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws BadData if the byte array does not contain a signed message
|
||||
*/
|
||||
@Throws(IOException::class, BadData::class)
|
||||
fun message(message: ByteArray): ReadyWithResult<Signatures> = message(message.inputStream())
|
||||
}
|
47
sop-java/src/main/kotlin/sop/operation/InlineSign.kt
Normal file
47
sop-java/src/main/kotlin/sop/operation/InlineSign.kt
Normal file
|
@ -0,0 +1,47 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import sop.Ready
|
||||
import sop.enums.InlineSignAs
|
||||
import sop.exception.SOPGPException.*
|
||||
|
||||
interface InlineSign : AbstractSign<InlineSign> {
|
||||
|
||||
/**
|
||||
* Sets the signature mode. Note: This method has to be called before [.key] is called.
|
||||
*
|
||||
* @param mode signature mode
|
||||
* @return builder instance
|
||||
* @throws UnsupportedOption if this option is not supported
|
||||
*/
|
||||
@Throws(UnsupportedOption::class) fun mode(mode: InlineSignAs): InlineSign
|
||||
|
||||
/**
|
||||
* Signs data.
|
||||
*
|
||||
* @param data input stream containing data
|
||||
* @return ready
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws KeyIsProtected if at least one signing key cannot be unlocked
|
||||
* @throws ExpectedText if text data was expected, but binary data was encountered
|
||||
*/
|
||||
@Throws(IOException::class, KeyIsProtected::class, ExpectedText::class)
|
||||
fun data(data: InputStream): Ready
|
||||
|
||||
/**
|
||||
* Signs data.
|
||||
*
|
||||
* @param data byte array containing data
|
||||
* @return ready
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws KeyIsProtected if at least one signing key cannot be unlocked
|
||||
* @throws ExpectedText if text data was expected, but binary data was encountered
|
||||
*/
|
||||
@Throws(IOException::class, KeyIsProtected::class, ExpectedText::class)
|
||||
fun data(data: ByteArray): Ready = data(data.inputStream())
|
||||
}
|
42
sop-java/src/main/kotlin/sop/operation/InlineVerify.kt
Normal file
42
sop-java/src/main/kotlin/sop/operation/InlineVerify.kt
Normal file
|
@ -0,0 +1,42 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import sop.ReadyWithResult
|
||||
import sop.Verification
|
||||
import sop.exception.SOPGPException.BadData
|
||||
import sop.exception.SOPGPException.NoSignature
|
||||
|
||||
/** API for verification of inline-signed messages. */
|
||||
interface InlineVerify : AbstractVerify<InlineVerify> {
|
||||
|
||||
/**
|
||||
* Provide the inline-signed data. The result can be used to write the plaintext message out and
|
||||
* to get the verifications.
|
||||
*
|
||||
* @param data signed data
|
||||
* @return list of signature verifications
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws NoSignature when no signature is found
|
||||
* @throws BadData when the data is invalid OpenPGP data
|
||||
*/
|
||||
@Throws(IOException::class, NoSignature::class, BadData::class)
|
||||
fun data(data: InputStream): ReadyWithResult<List<Verification>>
|
||||
|
||||
/**
|
||||
* Provide the inline-signed data. The result can be used to write the plaintext message out and
|
||||
* to get the verifications.
|
||||
*
|
||||
* @param data signed data
|
||||
* @return list of signature verifications
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws NoSignature when no signature is found
|
||||
* @throws BadData when the data is invalid OpenPGP data
|
||||
*/
|
||||
@Throws(IOException::class, NoSignature::class, BadData::class)
|
||||
fun data(data: ByteArray): ReadyWithResult<List<Verification>> = data(data.inputStream())
|
||||
}
|
34
sop-java/src/main/kotlin/sop/operation/ListProfiles.kt
Normal file
34
sop-java/src/main/kotlin/sop/operation/ListProfiles.kt
Normal file
|
@ -0,0 +1,34 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import sop.Profile
|
||||
|
||||
/** Subcommand to list supported profiles of other subcommands. */
|
||||
interface ListProfiles {
|
||||
|
||||
/**
|
||||
* Provide the name of the subcommand for which profiles shall be listed. The returned list of
|
||||
* profiles MUST NOT contain more than 4 entries.
|
||||
*
|
||||
* @param command command name (e.g. `generate-key`)
|
||||
* @return list of profiles.
|
||||
*/
|
||||
fun subcommand(command: String): List<Profile>
|
||||
|
||||
/**
|
||||
* Return a list of [Profiles][Profile] supported by the [GenerateKey] implementation.
|
||||
*
|
||||
* @return profiles
|
||||
*/
|
||||
fun generateKey(): List<Profile> = subcommand("generate-key")
|
||||
|
||||
/**
|
||||
* Return a list of [Profiles][Profile] supported by the [Encrypt] implementation.
|
||||
*
|
||||
* @return profiles
|
||||
*/
|
||||
fun encrypt(): List<Profile> = subcommand("encrypt")
|
||||
}
|
48
sop-java/src/main/kotlin/sop/operation/RevokeKey.kt
Normal file
48
sop-java/src/main/kotlin/sop/operation/RevokeKey.kt
Normal file
|
@ -0,0 +1,48 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.InputStream
|
||||
import sop.Ready
|
||||
import sop.exception.SOPGPException.PasswordNotHumanReadable
|
||||
import sop.exception.SOPGPException.UnsupportedOption
|
||||
import sop.util.UTF8Util
|
||||
|
||||
interface RevokeKey {
|
||||
|
||||
/**
|
||||
* Disable ASCII armor encoding.
|
||||
*
|
||||
* @return builder instance
|
||||
*/
|
||||
fun noArmor(): RevokeKey
|
||||
|
||||
/**
|
||||
* Provide the decryption password for the secret key.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws UnsupportedOption if the implementation does not support key passwords
|
||||
* @throws PasswordNotHumanReadable if the password is not human-readable
|
||||
*/
|
||||
@Throws(UnsupportedOption::class, PasswordNotHumanReadable::class)
|
||||
fun withKeyPassword(password: String): RevokeKey =
|
||||
withKeyPassword(password.toByteArray(UTF8Util.UTF8))
|
||||
|
||||
/**
|
||||
* Provide the decryption password for the secret key.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws UnsupportedOption if the implementation does not support key passwords
|
||||
* @throws PasswordNotHumanReadable if the password is not human-readable
|
||||
*/
|
||||
@Throws(UnsupportedOption::class, PasswordNotHumanReadable::class)
|
||||
fun withKeyPassword(password: ByteArray): RevokeKey
|
||||
|
||||
fun keys(bytes: ByteArray): Ready = keys(bytes.inputStream())
|
||||
|
||||
fun keys(keys: InputStream): Ready
|
||||
}
|
38
sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt
Normal file
38
sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt
Normal file
|
@ -0,0 +1,38 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import sop.Verification
|
||||
import sop.exception.SOPGPException.BadData
|
||||
import sop.exception.SOPGPException.NoSignature
|
||||
|
||||
interface VerifySignatures {
|
||||
|
||||
/**
|
||||
* Provide the signed data (without signatures).
|
||||
*
|
||||
* @param data signed data
|
||||
* @return list of signature verifications
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws NoSignature when no valid signature is found
|
||||
* @throws BadData when the data is invalid OpenPGP data
|
||||
*/
|
||||
@Throws(IOException::class, NoSignature::class, BadData::class)
|
||||
fun data(data: InputStream): List<Verification>
|
||||
|
||||
/**
|
||||
* Provide the signed data (without signatures).
|
||||
*
|
||||
* @param data signed data
|
||||
* @return list of signature verifications
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws NoSignature when no valid signature is found
|
||||
* @throws BadData when the data is invalid OpenPGP data
|
||||
*/
|
||||
@Throws(IOException::class, NoSignature::class, BadData::class)
|
||||
fun data(data: ByteArray): List<Verification> = data(data.inputStream())
|
||||
}
|
100
sop-java/src/main/kotlin/sop/operation/Version.kt
Normal file
100
sop-java/src/main/kotlin/sop/operation/Version.kt
Normal file
|
@ -0,0 +1,100 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.operation
|
||||
|
||||
interface Version {
|
||||
|
||||
/**
|
||||
* Return the implementations name. e.g. `SOP`,
|
||||
*
|
||||
* @return implementation name
|
||||
*/
|
||||
fun getName(): String
|
||||
|
||||
/**
|
||||
* Return the implementations short version string. e.g. `1.0`
|
||||
*
|
||||
* @return version string
|
||||
*/
|
||||
fun getVersion(): String
|
||||
|
||||
/**
|
||||
* Return version information about the used OpenPGP backend. e.g. `Bouncycastle 1.70`
|
||||
*
|
||||
* @return backend version string
|
||||
*/
|
||||
fun getBackendVersion(): String
|
||||
|
||||
/**
|
||||
* Return an extended version string containing multiple lines of version information. The first
|
||||
* line MUST match the information produced by [getName] and [getVersion], but the rest of the
|
||||
* text has no defined structure. Example:
|
||||
* ```
|
||||
* "SOP 1.0
|
||||
* Awesome PGP!
|
||||
* Using Bouncycastle 1.70
|
||||
* LibFoo 1.2.2
|
||||
* See https://pgp.example.org/sop/ for more information"
|
||||
* ```
|
||||
*
|
||||
* @return extended version string
|
||||
*/
|
||||
fun getExtendedVersion(): String
|
||||
|
||||
/**
|
||||
* Return the revision of the SOP specification that this implementation is implementing, for
|
||||
* example, `draft-dkg-openpgp-stateless-cli-06`. If the implementation targets a specific draft
|
||||
* but the implementer knows the implementation is incomplete, it should prefix the draft title
|
||||
* with a `~` (TILDE, U+007E), for example: `~draft-dkg-openpgp-stateless-cli-06`. The
|
||||
* implementation MAY emit additional text about its relationship to the targeted draft on the
|
||||
* lines following the versioned title.
|
||||
*
|
||||
* @return implemented SOP spec version
|
||||
*/
|
||||
fun getSopSpecVersion(): String {
|
||||
return buildString {
|
||||
if (isSopSpecImplementationIncomplete()) append('~')
|
||||
append(getSopSpecRevisionName())
|
||||
if (getSopSpecImplementationRemarks() != null) {
|
||||
append('\n')
|
||||
append('\n')
|
||||
append(getSopSpecImplementationRemarks())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the version number of the latest targeted SOP spec revision.
|
||||
*
|
||||
* @return SOP spec revision number
|
||||
*/
|
||||
fun getSopSpecRevisionNumber(): Int
|
||||
|
||||
/**
|
||||
* Return the name of the latest targeted revision of the SOP spec.
|
||||
*
|
||||
* @return SOP spec revision string
|
||||
*/
|
||||
fun getSopSpecRevisionName(): String = buildString {
|
||||
append("draft-dkg-openpgp-stateless-cli-")
|
||||
append(String.format("%02d", getSopSpecRevisionNumber()))
|
||||
}
|
||||
|
||||
/**
|
||||
* Return <pre>true</pre>, if this implementation of the SOP spec is known to be incomplete or
|
||||
* defective.
|
||||
*
|
||||
* @return true if incomplete, false otherwise
|
||||
*/
|
||||
fun isSopSpecImplementationIncomplete(): Boolean
|
||||
|
||||
/**
|
||||
* Return free-form text containing remarks about the completeness of the SOP implementation. If
|
||||
* there are no remarks, this method returns <pre>null</pre>.
|
||||
*
|
||||
* @return remarks or null
|
||||
*/
|
||||
fun getSopSpecImplementationRemarks(): String?
|
||||
}
|
38
sop-java/src/main/kotlin/sop/util/HexUtil.kt
Normal file
38
sop-java/src/main/kotlin/sop/util/HexUtil.kt
Normal file
|
@ -0,0 +1,38 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util
|
||||
|
||||
class HexUtil {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Encode a byte array to a hex string.
|
||||
*
|
||||
* @param bytes bytes
|
||||
* @return hex encoding
|
||||
* @see
|
||||
* [Convert Byte Arrays to Hex Strings in Kotlin](https://www.baeldung.com/kotlin/byte-arrays-to-hex-strings)
|
||||
*/
|
||||
@JvmStatic fun bytesToHex(bytes: ByteArray): String = bytes.toHex()
|
||||
|
||||
/**
|
||||
* Decode a hex string into a byte array.
|
||||
*
|
||||
* @param s hex string
|
||||
* @return decoded byte array
|
||||
* @see
|
||||
* [Kotlin convert hex string to ByteArray](https://stackoverflow.com/a/66614516/11150851)
|
||||
*/
|
||||
@JvmStatic fun hexToBytes(s: String): ByteArray = s.decodeHex()
|
||||
}
|
||||
}
|
||||
|
||||
fun String.decodeHex(): ByteArray {
|
||||
check(length % 2 == 0) { "Hex encoding must have even number of digits." }
|
||||
|
||||
return chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
}
|
||||
|
||||
fun ByteArray.toHex(): String = joinToString(separator = "") { eachByte -> "%02X".format(eachByte) }
|
26
sop-java/src/main/kotlin/sop/util/Optional.kt
Normal file
26
sop-java/src/main/kotlin/sop/util/Optional.kt
Normal file
|
@ -0,0 +1,26 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util
|
||||
|
||||
/**
|
||||
* Backport of java.util.Optional for older Android versions.
|
||||
*
|
||||
* @param <T> item type
|
||||
*/
|
||||
data class Optional<T>(val item: T? = null) {
|
||||
|
||||
val isPresent: Boolean = item != null
|
||||
val isEmpty: Boolean = item == null
|
||||
|
||||
fun get() = item
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun <T> of(item: T) = Optional(item!!)
|
||||
|
||||
@JvmStatic fun <T> ofNullable(item: T?) = Optional(item)
|
||||
|
||||
@JvmStatic fun <T> ofEmpty() = Optional(null as T?)
|
||||
}
|
||||
}
|
75
sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt
Normal file
75
sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt
Normal file
|
@ -0,0 +1,75 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* [OutputStream] that buffers data being written into it, until its underlying output stream is
|
||||
* being replaced. At that point, first all the buffered data is being written to the underlying
|
||||
* stream, followed by any successive data that may get written to the [ProxyOutputStream]. This
|
||||
* class is useful if we need to provide an [OutputStream] at one point in time when the final
|
||||
* target output stream is not yet known.
|
||||
*/
|
||||
class ProxyOutputStream {
|
||||
private val buffer = ByteArrayOutputStream()
|
||||
private var swapped: OutputStream? = null
|
||||
|
||||
@Synchronized
|
||||
fun replaceOutputStream(underlying: OutputStream) {
|
||||
this.swapped = underlying
|
||||
swapped!!.write(buffer.toByteArray())
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@Throws(IOException::class)
|
||||
fun write(b: ByteArray) {
|
||||
if (swapped == null) {
|
||||
buffer.write(b)
|
||||
} else {
|
||||
swapped!!.write(b)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@Throws(IOException::class)
|
||||
fun write(b: ByteArray, off: Int, len: Int) {
|
||||
if (swapped == null) {
|
||||
buffer.write(b, off, len)
|
||||
} else {
|
||||
swapped!!.write(b, off, len)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@Throws(IOException::class)
|
||||
fun flush() {
|
||||
buffer.flush()
|
||||
if (swapped != null) {
|
||||
swapped!!.flush()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@Throws(IOException::class)
|
||||
fun close() {
|
||||
buffer.close()
|
||||
if (swapped != null) {
|
||||
swapped!!.close()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@Throws(IOException::class)
|
||||
fun write(i: Int) {
|
||||
if (swapped == null) {
|
||||
buffer.write(i)
|
||||
} else {
|
||||
swapped!!.write(i)
|
||||
}
|
||||
}
|
||||
}
|
62
sop-java/src/main/kotlin/sop/util/UTCUtil.kt
Normal file
62
sop-java/src/main/kotlin/sop/util/UTCUtil.kt
Normal file
|
@ -0,0 +1,62 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util
|
||||
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class UTCUtil {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmField val UTC_FORMATTER = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
||||
@JvmField
|
||||
val UTC_PARSERS =
|
||||
arrayOf(
|
||||
UTC_FORMATTER,
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"),
|
||||
SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"),
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"))
|
||||
.onEach { fmt -> fmt.timeZone = TimeZone.getTimeZone("UTC") }
|
||||
|
||||
/**
|
||||
* Parse an ISO-8601 UTC timestamp from a string.
|
||||
*
|
||||
* @param dateString string
|
||||
* @return date
|
||||
* @throws ParseException if the date string is malformed and cannot be parsed
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(ParseException::class)
|
||||
fun parseUTCDate(dateString: String): Date {
|
||||
var exception: ParseException? = null
|
||||
for (parser in UTC_PARSERS) {
|
||||
try {
|
||||
return parser.parse(dateString)
|
||||
} catch (e: ParseException) {
|
||||
// Store first exception (that of UTC_FORMATTER) to throw if we fail to parse
|
||||
// the date
|
||||
if (exception == null) {
|
||||
exception = e
|
||||
}
|
||||
// Try next parser
|
||||
}
|
||||
}
|
||||
throw exception!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date as ISO-8601 UTC timestamp.
|
||||
*
|
||||
* @param date date
|
||||
* @return timestamp string
|
||||
*/
|
||||
@JvmStatic
|
||||
fun formatUTCDate(date: Date): String {
|
||||
return UTC_FORMATTER.format(date)
|
||||
}
|
||||
}
|
||||
}
|
37
sop-java/src/main/kotlin/sop/util/UTF8Util.kt
Normal file
37
sop-java/src/main/kotlin/sop/util/UTF8Util.kt
Normal file
|
@ -0,0 +1,37 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.charset.CodingErrorAction
|
||||
|
||||
class UTF8Util {
|
||||
companion object {
|
||||
@JvmField val UTF8: Charset = Charset.forName("UTF8")
|
||||
|
||||
@JvmStatic
|
||||
private val UTF8Decoder =
|
||||
UTF8.newDecoder()
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||
.onMalformedInput(CodingErrorAction.REPORT)
|
||||
|
||||
/**
|
||||
* Detect non-valid UTF8 data.
|
||||
*
|
||||
* @param data utf-8 encoded bytes
|
||||
* @return decoded string
|
||||
* @throws CharacterCodingException if the input data does not resemble UTF8
|
||||
* @see [ante on StackOverflow](https://stackoverflow.com/a/1471193)
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(CharacterCodingException::class)
|
||||
fun decodeUTF8(data: ByteArray): String {
|
||||
val byteBuffer = ByteBuffer.wrap(data)
|
||||
val charBuffer = UTF8Decoder.decode(byteBuffer)
|
||||
return charBuffer.toString()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ public class MicAlgTest {
|
|||
|
||||
@Test
|
||||
public void constructorNullArgThrows() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new MicAlg(null));
|
||||
assertThrows(NullPointerException.class, () -> new MicAlg(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -62,7 +62,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
|
|||
|
||||
byte[] signature = sop.detachedSign()
|
||||
.key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
|
||||
.mode(SignAs.Text)
|
||||
.mode(SignAs.text)
|
||||
.data(message)
|
||||
.toByteArrayAndResult()
|
||||
.getBytes();
|
||||
|
|
|
@ -142,7 +142,7 @@ public class EncryptDecryptTest extends AbstractSOPTest {
|
|||
byte[] ciphertext = sop.encrypt()
|
||||
.withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
|
||||
.signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
|
||||
.mode(EncryptAs.Binary)
|
||||
.mode(EncryptAs.binary)
|
||||
.plaintext(message)
|
||||
.getBytes();
|
||||
|
||||
|
@ -173,7 +173,7 @@ public class EncryptDecryptTest extends AbstractSOPTest {
|
|||
byte[] ciphertext = sop.encrypt()
|
||||
.withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
|
||||
.signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
|
||||
.mode(EncryptAs.Text)
|
||||
.mode(EncryptAs.text)
|
||||
.plaintext(message)
|
||||
.getBytes();
|
||||
|
||||
|
|
Loading…
Reference in a new issue