1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-09 18:29:39 +02:00

Moved sop-java and sop-java-picocli to its own repositories

See https://github.com/pgpainless/sop-java
See https://codeberg.org/PGPainless/sop-java
This commit is contained in:
Paul Schaub 2022-01-11 15:18:34 +01:00
parent 9800ca8bd4
commit c6bc8f9774
82 changed files with 11 additions and 5226 deletions

View file

@ -1,80 +1 @@
<!--
SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
SPDX-License-Identifier: Apache-2.0
-->
# SOP-Java
[![Spec Revision: 3](https://img.shields.io/badge/Spec%20Revision-3-blue)](https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-03)
[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/sop-java)](https://search.maven.org/artifact/org.pgpainless/sop-java)
[![JavaDoc](https://badgen.net/badge/javadoc/yes/green)](https://pgpainless.org/releases/latest/javadoc/sop/SOP.html)
[![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/pgpainless)](https://api.reuse.software/info/github.com/pgpainless/pgpainless)
Stateless OpenPGP Protocol for Java.
This module contains interfaces that model the API described by the
[Stateless OpenPGP Command Line Interface](https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-03) specification.
This module is not a command line application! For that, see `sop-java-picocli`.
## Usage Examples
The API defined by `sop-java` is super straight forward:
```java
SOP sop = ... // e.g. new org.pgpainless.sop.SOPImpl();
// Generate an OpenPGP key
byte[] key = sop.generateKey()
.userId("Alice <alice@example.org>")
.generate()
.getBytes();
// Extract the certificate (public key)
byte[] cert = sop.extractCert()
.key(key)
.getBytes();
// Encrypt a message
byte[] message = ...
byte[] encrypted = sop.encrypt()
.withCert(cert)
.signWith(key)
.plaintext(message)
.getBytes();
// Decrypt a message
ByteArrayAndResult<DecryptionResult> messageAndVerifications = sop.decrypt()
.verifyWith(cert)
.withKey(key)
.ciphertext(encrypted)
.toByteArrayAndResult();
byte[] decrypted = messageAndVerifications.getBytes();
// Signature Verifications
DecryptionResult messageInfo = messageAndVerifications.getResult();
List<Verification> signatureVerifications = messageInfo.getVerifications();
```
Furthermore, the API is capable of signing messages and verifying unencrypted signed data, as well as adding and removing ASCII armor.
### Limitations
As per the spec, sop-java does not (yet) deal with encrypted OpenPGP keys.
## Why should I use this?
If you need to use OpenPGP functionality like encrypting/decrypting messages, or creating/verifying
signatures inside your application, you probably don't want to start from scratch and instead reuse some library.
Instead of locking yourselves in by depending hard on that one library, you can simply depend on the interfaces from
`sop-java` and plug in a library (such as `pgpainless-sop`) that implements said interfaces.
That way you don't make yourself dependent from a single OpenPGP library and stay flexible.
Should another library emerge, that better suits your needs (and implements `sop-java`), you can easily switch
by swapping out the dependency with minimal changes to your code.
## Why should I *implement* this?
Did you create an [OpenPGP](https://datatracker.ietf.org/doc/html/rfc4880) implementation that can be used in the Java ecosystem?
By implementing the `sop-java` interface, you can turn your library into a command line interface (see `sop-java-picocli`).
This allows you to plug your library into the [OpenPGP interoperability test suite](https://tests.sequoia-pgp.org/)
of the [Sequoia-PGP](https://sequoia-pgp.org/) project.
# [MOVED](https://github.com/pgpainless/sop-java/tree/master/sop-java)

View file

@ -1,22 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
plugins {
id 'java'
}
group 'org.pgpainless'
repositories {
mavenCentral()
}
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
}
test {
useJUnitPlatform()
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -1,95 +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.Dearmor;
import sop.operation.Decrypt;
import sop.operation.DetachInbandSignatureAndMessage;
import sop.operation.Encrypt;
import sop.operation.ExtractCert;
import sop.operation.GenerateKey;
import sop.operation.Sign;
import sop.operation.Verify;
import sop.operation.Version;
/**
* Stateless OpenPGP Interface.
*/
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 Sign}.
*
* @return builder instance
*/
Sign sign();
/**
* Verify detached signatures.
* Customize the operation using the builder {@link Verify}.
*
* @return builder instance
*/
Verify verify();
/**
* 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();
DetachInbandSignatureAndMessage detachInbandSignatureAndMessage();
}

View file

@ -1,79 +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-fA-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) {
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 "" + (int) getAlgorithm() + ':' + HexUtil.bytesToHex(sessionKey);
}
}

View file

@ -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;
}

View file

@ -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].
*
* 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;
}
}
}

View file

@ -1,58 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop;
import java.util.Date;
import sop.util.UTCUtil;
public class Verification {
private final Date creationTime;
private final String signingKeyFingerprint;
private final String signingCertFingerprint;
public Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint) {
this.creationTime = creationTime;
this.signingKeyFingerprint = signingKeyFingerprint;
this.signingCertFingerprint = signingCertFingerprint;
}
/**
* Return the signatures' creation time.
*
* @return signature creation time
*/
public Date getCreationTime() {
return creationTime;
}
/**
* Return the fingerprint of the signing (sub)key.
*
* @return signing key fingerprint
*/
public String getSigningKeyFingerprint() {
return signingKeyFingerprint;
}
/**
* Return the fingerprint fo the signing certificate.
*
* @return signing certificate fingerprint
*/
public String getSigningCertFingerprint() {
return signingCertFingerprint;
}
@Override
public String toString() {
return UTCUtil.formatUTCDate(getCreationTime()) +
' ' +
getSigningKeyFingerprint() +
' ' +
getSigningCertFingerprint();
}
}

View file

@ -1,13 +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
}

View file

@ -1,11 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.enums;
public enum EncryptAs {
Binary,
Text,
MIME
}

View file

@ -1,10 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.enums;
public enum SignAs {
Binary,
Text
}

View file

@ -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;

View file

@ -1,316 +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).
*/
public static class NoSignature extends SOPGPException {
public static final int EXIT_CODE = 3;
public NoSignature() {
super("No verifiable signature found.");
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
}
/**
* Asymmetric algorithm unsupported (sop encrypt).
*/
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);
}
@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(String s) {
super(s);
}
@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;
@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;
@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(Throwable e) {
super(e);
}
public BadData(String message, BadData badData) {
super(message, badData);
}
@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;
@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);
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
}
/**
* A KEYS input is protected (locked) with a password, and sop cannot unlock it.
*/
public static class KeyIsProtected extends SOPGPException {
public static final int EXIT_CODE = 67;
public KeyIsProtected() {
super();
}
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;
@Override
public int getExitCode() {
return EXIT_CODE;
}
}
/**
* A indirect input parameter is a special designator (it starts with @),
* and a filename matching the designator is actually present.
*/
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)
* (sop sign and sop encrypt with --sign-with).
*/
public static class KeyCannotSign extends SOPGPException {
public static final int EXIT_CODE = 79;
public KeyCannotSign() {
super();
}
public KeyCannotSign(String s, KeyCannotSign keyCannotSign) {
super(s, keyCannotSign);
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
}
}

View file

@ -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;

View file

@ -1,41 +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.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
*/
Ready data(InputStream data) throws SOPGPException.BadData;
/**
* Armor the provided data.
*
* @param data unarmored OpenPGP data
* @return armored data
*/
default Ready data(byte[] data) throws SOPGPException.BadData {
return data(new ByteArrayInputStream(data));
}
}

View file

@ -1,33 +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 Dearmor {
/**
* Dearmor armored OpenPGP data.
*
* @param data armored OpenPGP data
* @return input stream of unarmored data
*/
Ready data(InputStream data) throws SOPGPException.BadData, IOException;
/**
* Dearmor armored OpenPGP data.
*
* @param data armored OpenPGP data
* @return input stream of unarmored data
*/
default Ready data(byte[] data) throws SOPGPException.BadData, IOException {
return data(new ByteArrayInputStream(data));
}
}

View file

@ -1,118 +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.Date;
import sop.DecryptionResult;
import sop.ReadyWithResult;
import sop.SessionKey;
import sop.exception.SOPGPException;
public interface Decrypt {
/**
* Makes the SOP consider signatures before this date invalid.
*
* @param timestamp timestamp
* @return builder instance
*/
Decrypt verifyNotBefore(Date timestamp)
throws SOPGPException.UnsupportedOption;
/**
* Makes the SOP consider signatures after this date invalid.
*
* @param timestamp timestamp
* @return builder instance
*/
Decrypt verifyNotAfter(Date timestamp)
throws SOPGPException.UnsupportedOption;
/**
* Adds one or more verification cert.
*
* @param cert input stream containing the cert(s)
* @return builder instance
*/
Decrypt verifyWithCert(InputStream cert)
throws SOPGPException.BadData,
IOException;
/**
* Adds one or more verification cert.
*
* @param cert byte array containing the cert(s)
* @return builder instance
*/
default Decrypt verifyWithCert(byte[] cert)
throws SOPGPException.BadData, IOException {
return verifyWithCert(new ByteArrayInputStream(cert));
}
/**
* Tries to decrypt with the given session key.
*
* @param sessionKey session key
* @return builder instance
*/
Decrypt withSessionKey(SessionKey sessionKey)
throws SOPGPException.UnsupportedOption;
/**
* Tries to decrypt with the given password.
*
* @param password password
* @return builder instance
*/
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
*/
Decrypt withKey(InputStream key)
throws SOPGPException.KeyIsProtected,
SOPGPException.BadData,
SOPGPException.UnsupportedAsymmetricAlgo;
/**
* Adds one or more decryption key.
*
* @param key byte array containing the key(s)
* @return builder instance
*/
default Decrypt withKey(byte[] key)
throws SOPGPException.KeyIsProtected,
SOPGPException.BadData,
SOPGPException.UnsupportedAsymmetricAlgo {
return withKey(new ByteArrayInputStream(key));
}
/**
* Decrypts the given ciphertext, returning verification results and plaintext.
* @param ciphertext ciphertext
* @return ready with result
*/
ReadyWithResult<DecryptionResult> ciphertext(InputStream ciphertext)
throws SOPGPException.BadData, SOPGPException.MissingArg, SOPGPException.CannotDecrypt;
/**
* Decrypts the given ciphertext, returning verification results and plaintext.
* @param ciphertext ciphertext
* @return ready with result
*/
default ReadyWithResult<DecryptionResult> ciphertext(byte[] ciphertext)
throws SOPGPException.BadData, SOPGPException.MissingArg, SOPGPException.CannotDecrypt {
return ciphertext(new ByteArrayInputStream(ciphertext));
}
}

View file

@ -1,44 +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;
/**
* Split cleartext signed messages up into data and signatures.
*/
public interface DetachInbandSignatureAndMessage {
/**
* Do not wrap the signatures in ASCII armor.
* @return builder
*/
DetachInbandSignatureAndMessage noArmor();
/**
* Detach the provided cleartext 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
*/
ReadyWithResult<Signatures> message(InputStream messageInputStream) throws IOException;
/**
* 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
*/
default ReadyWithResult<Signatures> message(byte[] message) throws IOException {
return message(new ByteArrayInputStream(message));
}
}

View file

@ -1,109 +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.EncryptAs;
import sop.exception.SOPGPException;
public interface Encrypt {
/**
* Disable ASCII armor encoding.
*
* @return builder instance
*/
Encrypt noArmor();
/**
* Sets encryption mode.
*
* @param mode mode
* @return builder instance
*/
Encrypt mode(EncryptAs mode)
throws SOPGPException.UnsupportedOption;
/**
* Adds the signer key.
*
* @param key input stream containing the encoded signer key
* @return builder instance
*/
Encrypt signWith(InputStream key)
throws SOPGPException.KeyIsProtected,
SOPGPException.KeyCannotSign,
SOPGPException.UnsupportedAsymmetricAlgo,
SOPGPException.BadData;
/**
* Adds the signer key.
*
* @param key byte array containing the encoded signer key
* @return builder instance
*/
default Encrypt signWith(byte[] key)
throws SOPGPException.KeyIsProtected,
SOPGPException.KeyCannotSign,
SOPGPException.UnsupportedAsymmetricAlgo,
SOPGPException.BadData {
return signWith(new ByteArrayInputStream(key));
}
/**
* Encrypt with the given password.
*
* @param password password
* @return builder instance
*/
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
*/
Encrypt withCert(InputStream cert)
throws SOPGPException.CertCannotEncrypt,
SOPGPException.UnsupportedAsymmetricAlgo,
SOPGPException.BadData;
/**
* Encrypt with the given cert.
*
* @param cert byte array containing the encoded cert.
* @return builder instance
*/
default Encrypt withCert(byte[] cert)
throws SOPGPException.CertCannotEncrypt,
SOPGPException.UnsupportedAsymmetricAlgo,
SOPGPException.BadData {
return withCert(new ByteArrayInputStream(cert));
}
/**
* Encrypt the given data yielding the ciphertext.
* @param plaintext plaintext
* @return input stream containing the ciphertext
*/
Ready plaintext(InputStream plaintext)
throws IOException;
/**
* Encrypt the given data yielding the ciphertext.
* @param plaintext plaintext
* @return input stream containing the ciphertext
*/
default Ready plaintext(byte[] plaintext) throws IOException {
return plaintext(new ByteArrayInputStream(plaintext));
}
}

View file

@ -1,40 +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
*/
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
*/
default Ready key(byte[] key) throws IOException, SOPGPException.BadData {
return key(new ByteArrayInputStream(key));
}
}

View file

@ -1,36 +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 sop.Ready;
import sop.exception.SOPGPException;
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);
/**
* Generate the OpenPGP key and return it encoded as an {@link InputStream}.
*
* @return key
*/
Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo, IOException;
}

View file

@ -1,69 +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.SigningResult;
import sop.enums.SignAs;
import sop.exception.SOPGPException;
public interface Sign {
/**
* Disable ASCII armor encoding.
*
* @return builder instance
*/
Sign noArmor();
/**
* Sets the signature mode.
* Note: This method has to be called before {@link #key(InputStream)} is called.
*
* @param mode signature mode
* @return builder instance
*/
Sign mode(SignAs mode) throws SOPGPException.UnsupportedOption;
/**
* Add one or more signing keys.
*
* @param key input stream containing encoded keys
* @return builder instance
*/
Sign key(InputStream key) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException;
/**
* Add one or more signing keys.
*
* @param key byte array containing encoded keys
* @return builder instance
*/
default Sign key(byte[] key) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
return key(new ByteArrayInputStream(key));
}
/**
* Signs data.
*
* @param data input stream containing data
* @return ready
*/
ReadyWithResult<SigningResult> data(InputStream data) throws IOException, SOPGPException.ExpectedText;
/**
* Signs data.
*
* @param data byte array containing data
* @return ready
*/
default ReadyWithResult<SigningResult> data(byte[] data) throws IOException, SOPGPException.ExpectedText {
return data(new ByteArrayInputStream(data));
}
}

View file

@ -1,67 +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.InputStream;
import java.util.Date;
import sop.exception.SOPGPException;
public interface Verify extends VerifySignatures {
/**
* Makes the SOP implementation consider signatures before this date invalid.
*
* @param timestamp timestamp
* @return builder instance
*/
Verify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption;
/**
* Makes the SOP implementation consider signatures after this date invalid.
*
* @param timestamp timestamp
* @return builder instance
*/
Verify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption;
/**
* Add one or more verification cert.
*
* @param cert input stream containing the encoded certs
* @return builder instance
*/
Verify cert(InputStream cert) throws SOPGPException.BadData;
/**
* Add one or more verification cert.
*
* @param cert byte array containing the encoded certs
* @return builder instance
*/
default Verify cert(byte[] cert) throws SOPGPException.BadData {
return cert(new ByteArrayInputStream(cert));
}
/**
* Provides the signatures.
* @param signatures input stream containing encoded, detached signatures.
*
* @return builder instance
*/
VerifySignatures signatures(InputStream signatures) throws SOPGPException.BadData;
/**
* Provides the signatures.
* @param signatures byte array containing encoded, detached signatures.
*
* @return builder instance
*/
default VerifySignatures signatures(byte[] signatures) throws SOPGPException.BadData {
return signatures(new ByteArrayInputStream(signatures));
}
}

View file

@ -1,40 +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 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 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));
}
}

View file

@ -1,49 +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();
}

View file

@ -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;

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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}.
*
* 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);
}
}
}

View file

@ -1,56 +0,0 @@
// SPDX-FileCopyrightText: 2021 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.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
*/
public static Date parseUTCDate(String dateString) {
for (SimpleDateFormat parser : UTC_PARSERS) {
try {
return parser.parse(dateString);
} catch (ParseException e) {
// Try next parser
}
}
return null;
}
/**
* 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);
}
}

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* Utility classes.
*/
package sop.util;

View file

@ -1,33 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.util;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import sop.ByteArrayAndResult;
import sop.Verification;
public class ByteArrayAndResultTest {
@Test
public void testCreationAndGetters() {
byte[] bytes = "Hello, World!\n".getBytes(StandardCharsets.UTF_8);
List<Verification> result = Collections.singletonList(
new Verification(UTCUtil.parseUTCDate("2019-10-24T23:48:29Z"),
"C90E6D36200A1B922A1509E77618196529AE5FF8",
"C4BC2DDB38CCE96485EBE9C2F20691179038E5C6")
);
ByteArrayAndResult<List<Verification>> bytesAndResult = new ByteArrayAndResult<>(bytes, result);
assertArrayEquals(bytes, bytesAndResult.getBytes());
assertEquals(result, bytesAndResult.getResult());
}
}

View file

@ -1,63 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.util;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.nio.charset.Charset;
import org.junit.jupiter.api.Test;
/**
* Test using some test vectors from RFC4648.
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-10">RFC-4648 §10: Test Vectors</a>
*/
public class HexUtilTest {
@SuppressWarnings("CharsetObjectCanBeUsed")
private static final Charset ASCII = Charset.forName("US-ASCII");
@Test
public void emptyHexEncodeTest() {
assertHexEquals("", "");
}
@Test
public void encodeF() {
assertHexEquals("66", "f");
}
@Test
public void encodeFo() {
assertHexEquals("666F", "fo");
}
@Test
public void encodeFoo() {
assertHexEquals("666F6F", "foo");
}
@Test
public void encodeFoob() {
assertHexEquals("666F6F62", "foob");
}
@Test
public void encodeFooba() {
assertHexEquals("666F6F6261", "fooba");
}
@Test
public void encodeFoobar() {
assertHexEquals("666F6F626172", "foobar");
}
private void assertHexEquals(String hex, String ascii) {
assertEquals(hex, HexUtil.bytesToHex(ascii.getBytes(ASCII)));
assertArrayEquals(ascii.getBytes(ASCII), HexUtil.hexToBytes(hex));
}
}

View file

@ -1,53 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import sop.MicAlg;
public class MicAlgTest {
@Test
public void constructorNullArgThrows() {
assertThrows(IllegalArgumentException.class, () -> new MicAlg(null));
}
@Test
public void emptyMicAlgIsEmptyString() {
MicAlg empty = MicAlg.empty();
assertNotNull(empty.getMicAlg());
assertTrue(empty.getMicAlg().isEmpty());
}
@Test
public void fromInvalidAlgorithmIdThrows() {
assertThrows(IllegalArgumentException.class, () -> MicAlg.fromHashAlgorithmId(-1));
}
@Test
public void fromHashAlgorithmIdsKnownAlgsMatch() {
Map<Integer, String> knownAlgorithmMicalgs = new HashMap<>();
knownAlgorithmMicalgs.put(1, "pgp-md5");
knownAlgorithmMicalgs.put(2, "pgp-sha1");
knownAlgorithmMicalgs.put(3, "pgp-ripemd160");
knownAlgorithmMicalgs.put(8, "pgp-sha256");
knownAlgorithmMicalgs.put(9, "pgp-sha384");
knownAlgorithmMicalgs.put(10, "pgp-sha512");
knownAlgorithmMicalgs.put(11, "pgp-sha224");
for (Integer id : knownAlgorithmMicalgs.keySet()) {
MicAlg micAlg = MicAlg.fromHashAlgorithmId(id);
assertEquals(knownAlgorithmMicalgs.get(id), micAlg.getMicAlg());
}
}
}

View file

@ -1,78 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
public class OptionalTest {
@Test
public void testEmpty() {
Optional<String> optional = new Optional<>();
assertEmpty(optional);
}
@Test
public void testArg() {
String string = "foo";
Optional<String> optional = new Optional<>(string);
assertFalse(optional.isEmpty());
assertTrue(optional.isPresent());
assertEquals(string, optional.get());
}
@Test
public void testOfEmpty() {
Optional<String> optional = Optional.ofEmpty();
assertEmpty(optional);
}
@Test
public void testNullArg() {
Optional<String> optional = new Optional<>(null);
assertEmpty(optional);
}
@Test
public void testOfWithNullArgThrows() {
assertThrows(NullPointerException.class, () -> Optional.of(null));
}
@Test
public void testOf() {
String string = "Hello, World!";
Optional<String> optional = Optional.of(string);
assertFalse(optional.isEmpty());
assertTrue(optional.isPresent());
assertEquals(string, optional.get());
}
@Test
public void testOfNullableWithNull() {
Optional<String> optional = Optional.ofNullable(null);
assertEmpty(optional);
}
@Test
public void testOfNullableWithArg() {
Optional<String> optional = Optional.ofNullable("bar");
assertEquals("bar", optional.get());
assertFalse(optional.isEmpty());
assertTrue(optional.isPresent());
}
private <T> void assertEmpty(Optional<T> optional) {
assertTrue(optional.isEmpty());
assertFalse(optional.isPresent());
assertNull(optional.get());
}
}

View file

@ -1,40 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
public class ProxyOutputStreamTest {
@Test
public void replaceOutputStreamThrowsNPEForNull() {
ProxyOutputStream proxy = new ProxyOutputStream();
assertThrows(NullPointerException.class, () -> proxy.replaceOutputStream(null));
}
@Test
public void testSwappingStreamPreservesWrittenBytes() throws IOException {
byte[] firstSection = "Foo\nBar\n".getBytes(StandardCharsets.UTF_8);
byte[] secondSection = "Baz\n".getBytes(StandardCharsets.UTF_8);
ProxyOutputStream proxy = new ProxyOutputStream();
proxy.write(firstSection);
ByteArrayOutputStream swappedStream = new ByteArrayOutputStream();
proxy.replaceOutputStream(swappedStream);
proxy.write(secondSection);
proxy.close();
assertEquals("Foo\nBar\nBaz\n", swappedStream.toString());
}
}

View file

@ -1,30 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.util;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
import sop.Ready;
public class ReadyTest {
@Test
public void readyTest() throws IOException {
byte[] data = "Hello, World!\n".getBytes(StandardCharsets.UTF_8);
Ready ready = new Ready() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
outputStream.write(data);
}
};
assertArrayEquals(data, ready.getBytes());
}
}

View file

@ -1,44 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.util;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import sop.ByteArrayAndResult;
import sop.ReadyWithResult;
import sop.Verification;
import sop.exception.SOPGPException;
public class ReadyWithResultTest {
@Test
public void testReadyWithResult() throws SOPGPException.NoSignature, IOException {
byte[] data = "Hello, World!\n".getBytes(StandardCharsets.UTF_8);
List<Verification> result = Collections.singletonList(
new Verification(UTCUtil.parseUTCDate("2019-10-24T23:48:29Z"),
"C90E6D36200A1B922A1509E77618196529AE5FF8",
"C4BC2DDB38CCE96485EBE9C2F20691179038E5C6")
);
ReadyWithResult<List<Verification>> readyWithResult = new ReadyWithResult<List<Verification>>() {
@Override
public List<Verification> writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature {
outputStream.write(data);
return result;
}
};
ByteArrayAndResult<List<Verification>> bytesAndResult = readyWithResult.toByteArrayAndResult();
assertArrayEquals(data, bytesAndResult.getBytes());
assertEquals(result, bytesAndResult.getResult());
}
}

View file

@ -1,61 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
import sop.SessionKey;
public class SessionKeyTest {
@Test
public void fromStringTest() {
String string = "9:FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD";
SessionKey sessionKey = SessionKey.fromString(string);
assertEquals(string, sessionKey.toString());
}
@Test
public void toStringTest() {
SessionKey sessionKey = new SessionKey((byte) 9, HexUtil.hexToBytes("FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"));
assertEquals("9:FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD", sessionKey.toString());
}
@Test
public void equalsTest() {
SessionKey s1 = new SessionKey((byte) 9, HexUtil.hexToBytes("FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"));
SessionKey s2 = new SessionKey((byte) 9, HexUtil.hexToBytes("FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"));
SessionKey s3 = new SessionKey((byte) 4, HexUtil.hexToBytes("FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"));
SessionKey s4 = new SessionKey((byte) 9, HexUtil.hexToBytes("19125CD57392BAB7037C7078359FCA4BEAF687F4025CBF9F7BCD8059CACC14FB"));
SessionKey s5 = new SessionKey((byte) 4, HexUtil.hexToBytes("19125CD57392BAB7037C7078359FCA4BEAF687F4025CBF9F7BCD8059CACC14FB"));
assertEquals(s1, s1);
assertEquals(s1, s2);
assertEquals(s1.hashCode(), s2.hashCode());
assertNotEquals(s1, s3);
assertNotEquals(s1.hashCode(), s3.hashCode());
assertNotEquals(s1, s4);
assertNotEquals(s1.hashCode(), s4.hashCode());
assertNotEquals(s4, s5);
assertNotEquals(s4.hashCode(), s5.hashCode());
assertNotEquals(s1, null);
assertNotEquals(s1, "FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD");
}
@Test
public void fromString_missingAlgorithmIdThrows() {
String missingAlgorithId = "FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD";
assertThrows(IllegalArgumentException.class, () -> SessionKey.fromString(missingAlgorithId));
}
@Test
public void fromString_wrongDivider() {
String semicolonDivider = "9;FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD";
assertThrows(IllegalArgumentException.class, () -> SessionKey.fromString(semicolonDivider));
}
}

View file

@ -1,23 +0,0 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import sop.MicAlg;
import sop.SigningResult;
public class SigningResultTest {
@Test
public void basicBuilderTest() {
SigningResult result = SigningResult.builder()
.setMicAlg(MicAlg.fromHashAlgorithmId(10))
.build();
assertEquals("pgp-sha512", result.getMicAlg().getMicAlg());
}
}

View file

@ -1,48 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.Date;
import org.junit.jupiter.api.Test;
/**
* Test parsing some date examples from the stateless OpenPGP CLI spec.
*
* @see <a href="https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-01#section-4.1">OpenPGP Stateless CLI §4.1. Date</a>
*/
public class UTCUtilTest {
@Test
public void parseExample1() {
String timestamp = "2019-10-29T12:11:04+00:00";
Date date = UTCUtil.parseUTCDate(timestamp);
assertEquals("2019-10-29T12:11:04Z", UTCUtil.formatUTCDate(date));
}
@Test
public void parseExample2() {
String timestamp = "2019-10-24T23:48:29Z";
Date date = UTCUtil.parseUTCDate(timestamp);
assertEquals("2019-10-24T23:48:29Z", UTCUtil.formatUTCDate(date));
}
@Test
public void parseExample3() {
String timestamp = "20191029T121104Z";
Date date = UTCUtil.parseUTCDate(timestamp);
assertEquals("2019-10-29T12:11:04Z", UTCUtil.formatUTCDate(date));
}
@Test
public void invalidDateReturnsNull() {
String invalidTimestamp = "foobar";
Date expectNull = UTCUtil.parseUTCDate(invalidTimestamp);
assertNull(expectNull);
}
}