mirror of
https://codeberg.org/PGPainless/sop-java.git
synced 2024-06-28 14:34:53 +02:00
Compare commits
27 commits
51a7d950e5
...
7ab65f63a4
Author | SHA1 | Date | |
---|---|---|---|
Paul Schaub | 7ab65f63a4 | ||
Paul Schaub | b8ad6d77a2 | ||
Paul Schaub | ab8f44138d | ||
Paul Schaub | 419056ba4c | ||
Paul Schaub | 312cdb69c9 | ||
Paul Schaub | c479cc8ef3 | ||
Paul Schaub | aa88904711 | ||
Paul Schaub | 7ea46a1916 | ||
Paul Schaub | 49fd7143cf | ||
Paul Schaub | 8aded17f10 | ||
Paul Schaub | 8eba099146 | ||
Paul Schaub | 0308732328 | ||
Paul Schaub | 8b8863c6df | ||
Paul Schaub | 44e6dd2180 | ||
Paul Schaub | 19d6b7e142 | ||
Paul Schaub | 226b5d99a0 | ||
Paul Schaub | e336e536a8 | ||
Paul Schaub | ed59c713eb | ||
Paul Schaub | 0aabfac695 | ||
Paul Schaub | 790d80ec29 | ||
Paul Schaub | 0fccf3051c | ||
Paul Schaub | a722e98578 | ||
Paul Schaub | aeda534f37 | ||
Paul Schaub | bb2b4e03fb | ||
Paul Schaub | 4a7c2b74da | ||
Paul Schaub | 78ecf2f554 | ||
Paul Schaub | d8cac7b9d7 |
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -6,6 +6,28 @@ SPDX-License-Identifier: Apache-2.0
|
|||
|
||||
# Changelog
|
||||
|
||||
## 6.1.0
|
||||
- `listProfiles()`: Add shortcut methods `generateKey()` and `encrypt()`
|
||||
- Add DSL for testing `Verification` results
|
||||
- `Verification`
|
||||
- Return `Optional<SignatureMode>` for `getSignatureMode()`
|
||||
- Return `Optional<String>` for `getDescription()`
|
||||
- `Profile`
|
||||
- Add support for profiles without description
|
||||
- Return `Optional<String>` for `getDescription()`
|
||||
- Add `parse(String)` method for parsing profile lines
|
||||
- `sop-java`: Add dependency on `com.google.code.findbugs:jsr305` for `@Nullable`, `@Nonnull` annotations
|
||||
- `UTCUtil`: `parseUTCDate()` is now `@Nonnull` and throws a `ParseException` for invalid inputs
|
||||
- `UTF8Util`: `decodeUTF8()` now throws `CharacterCodingException` instead of `SOPGPException.PasswordNotHumanReadable`
|
||||
- `external-sop`: Properly map error codes to new exception types (ported from `5.0.1`):
|
||||
- `UNSUPPORTED_PROFILE`
|
||||
- `INCOMPATIBLE_OPTIONS`
|
||||
|
||||
## 5.0.1
|
||||
- `external-sop`: Properly map error codes to new exception types:
|
||||
- `UNSUPPORTED_PROFILE`
|
||||
- `INCOMPATIBLE_OPTIONS`
|
||||
|
||||
## 6.0.0
|
||||
- Update implementation to [SOP Specification revision 06](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-06.html).
|
||||
- Add option `--profile=XYZ` to `encrypt` subcommand
|
||||
|
|
|
@ -267,7 +267,16 @@ public class ExternalSOP implements SOP {
|
|||
throw new SOPGPException.KeyCannotSign("External SOP backend reported error KeyCannotSign (" +
|
||||
exitCode + "):\n" + errorMessage);
|
||||
|
||||
case SOPGPException.IncompatibleOptions.EXIT_CODE:
|
||||
throw new SOPGPException.IncompatibleOptions("External SOP backend reported error IncompatibleOptions (" +
|
||||
exitCode + "):\n" + errorMessage);
|
||||
|
||||
case SOPGPException.UnsupportedProfile.EXIT_CODE:
|
||||
throw new SOPGPException.UnsupportedProfile("External SOP backend reported error UnsupportedProfile (" +
|
||||
exitCode + "):\n" + errorMessage);
|
||||
|
||||
default:
|
||||
// Did you forget to add a case for a new exception type?
|
||||
throw new RuntimeException("External SOP backend reported unknown exit code (" +
|
||||
exitCode + "):\n" + errorMessage);
|
||||
}
|
||||
|
|
|
@ -10,11 +10,10 @@ import sop.exception.SOPGPException;
|
|||
import sop.external.ExternalSOP;
|
||||
import sop.operation.Armor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Properties;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link Armor} operation using an external SOP binary.
|
||||
|
@ -37,7 +36,7 @@ public class ArmorExternal implements Armor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Ready data(InputStream data) throws SOPGPException.BadData, IOException {
|
||||
public Ready data(InputStream data) throws SOPGPException.BadData {
|
||||
return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import sop.exception.SOPGPException;
|
|||
import sop.external.ExternalSOP;
|
||||
import sop.operation.Dearmor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -30,7 +29,7 @@ public class DearmorExternal implements Dearmor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Ready data(InputStream data) throws SOPGPException.BadData, IOException {
|
||||
public Ready data(InputStream data) throws SOPGPException.BadData {
|
||||
return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ public class DetachedVerifyExternal implements DetachedVerify {
|
|||
private final List<String> commandList = new ArrayList<>();
|
||||
private final List<String> envList;
|
||||
|
||||
private Set<InputStream> certs = new HashSet<>();
|
||||
private final Set<InputStream> certs = new HashSet<>();
|
||||
private InputStream signatures;
|
||||
private int certCounter = 0;
|
||||
|
||||
|
@ -54,13 +54,13 @@ public class DetachedVerifyExternal implements DetachedVerify {
|
|||
}
|
||||
|
||||
@Override
|
||||
public DetachedVerify cert(InputStream cert) throws SOPGPException.BadData, IOException {
|
||||
public DetachedVerify cert(InputStream cert) throws SOPGPException.BadData {
|
||||
this.certs.add(cert);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VerifySignatures signatures(InputStream signatures) throws SOPGPException.BadData, IOException {
|
||||
public VerifySignatures signatures(InputStream signatures) throws SOPGPException.BadData {
|
||||
this.signatures = signatures;
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ public class EncryptExternal implements Encrypt {
|
|||
|
||||
@Override
|
||||
public Ready plaintext(InputStream plaintext)
|
||||
throws IOException, SOPGPException.KeyIsProtected {
|
||||
throws SOPGPException.KeyIsProtected {
|
||||
return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, plaintext);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ public class InlineSignExternal implements InlineSign {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Ready data(InputStream data) throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText {
|
||||
public Ready data(InputStream data) throws SOPGPException.KeyIsProtected, SOPGPException.ExpectedText {
|
||||
return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,8 +38,10 @@ public class ListProfilesExternal implements ListProfiles {
|
|||
private static List<Profile> toProfiles(String output) {
|
||||
List<Profile> profiles = new ArrayList<>();
|
||||
for (String line : output.split("\n")) {
|
||||
String[] split = line.split(": ");
|
||||
profiles.add(new Profile(split[0], split[1]));
|
||||
if (line.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
profiles.add(Profile.parse(line));
|
||||
}
|
||||
return profiles;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.io.FileOutputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.text.ParseException;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
@ -246,21 +247,36 @@ public abstract class AbstractSopCmd implements Runnable {
|
|||
}
|
||||
|
||||
public Date parseNotAfter(String notAfter) {
|
||||
Date date = notAfter.equals("now") ? new Date() : notAfter.equals("-") ? END_OF_TIME : UTCUtil.parseUTCDate(notAfter);
|
||||
if (date == null) {
|
||||
if (notAfter.equals("now")) {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
if (notAfter.equals("-")) {
|
||||
return END_OF_TIME;
|
||||
}
|
||||
|
||||
try {
|
||||
return UTCUtil.parseUTCDate(notAfter);
|
||||
} catch (ParseException e) {
|
||||
String errorMsg = getMsg("sop.error.input.malformed_not_after");
|
||||
throw new IllegalArgumentException(errorMsg);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
public Date parseNotBefore(String notBefore) {
|
||||
Date date = notBefore.equals("now") ? new Date() : notBefore.equals("-") ? BEGINNING_OF_TIME : UTCUtil.parseUTCDate(notBefore);
|
||||
if (date == null) {
|
||||
if (notBefore.equals("now")) {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
if (notBefore.equals("-")) {
|
||||
return BEGINNING_OF_TIME;
|
||||
}
|
||||
|
||||
try {
|
||||
return UTCUtil.parseUTCDate(notBefore);
|
||||
} catch (ParseException e) {
|
||||
String errorMsg = getMsg("sop.error.input.malformed_not_before");
|
||||
throw new IllegalArgumentException(errorMsg);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@ usage.header=Decrypt a message from standard input
|
|||
session-key-out=Can be used to learn the session key on successful decryption
|
||||
with-session-key.0=Symmetric message key (session key).
|
||||
with-session-key.1=Enables decryption of the "CIPHERTEXT" using the session key directly against the "SEIPD" packet.
|
||||
with-session-key.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...)
|
||||
with-session-key.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
||||
with-password.0=Symmetric passphrase to decrypt the message with.
|
||||
with-password.1=Enables decryption based on any "SKESK" packets in the "CIPHERTEXT".
|
||||
with-password.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...)
|
||||
with-password.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
||||
verify-out=Emits signature verification status to the designated output
|
||||
verify-with=Certificates for signature verification
|
||||
not-before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z)
|
||||
|
|
|
@ -5,7 +5,7 @@ usage.header=Entschl
|
|||
session-key-out=Extrahiere den Nachrichtenschlüssel nach erfolgreicher Entschlüsselung
|
||||
with-session-key.0=Symmetrischer Nachrichtenschlüssel (Sitzungsschlüssel).
|
||||
with-session-key.1=Ermöglicht direkte Entschlüsselung des im "CIPHERTEXT" enhaltenen "SEIPD" Paketes mithilfe des Nachrichtenschlüssels.
|
||||
with_session-key.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...).
|
||||
with-session-key.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...).
|
||||
with-password.0=Symmetrisches Passwort zur Entschlüsselung der Nachricht.
|
||||
with-password.1=Ermöglicht Entschlüsselung basierend auf im "CIPHERTEXT" enthaltenen "SKESK" Paketen.
|
||||
with-password.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...).
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
usage.header=Create a detached signature on the data from standard input
|
||||
no-armor=ASCII armor the output
|
||||
as.0=Specify the output format of the signed message
|
||||
as.0=Specify the output format of the signed message.
|
||||
as.1=Defaults to 'binary'.
|
||||
as.2=If '--as=text' and the input data is not valid UTF-8, sign fails with return code 53.
|
||||
with-key-password.0=Passphrase to unlock the secret key(s).
|
||||
with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
||||
micalg-out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156)
|
||||
micalg-out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156).
|
||||
KEYS[0..*]=Secret keys used for signing
|
||||
|
||||
# Generic TODO: Remove when bumping picocli to 4.7.0
|
||||
|
|
|
@ -5,10 +5,10 @@ usage.header=Verify a detached signature over the data from standard input
|
|||
not-before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z)
|
||||
not-before.1=Reject signatures with a creation date not in range.
|
||||
not-before.2=Defaults to beginning of time ("-").
|
||||
not-after.1=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z)
|
||||
not-after.2=Reject signatures with a creation date not in range.
|
||||
not-after.3=Defaults to current system time ("now").\
|
||||
not-after.4 = Accepts special value "-" for end of time.
|
||||
not-after.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z)
|
||||
not-after.1=Reject signatures with a creation date not in range.
|
||||
not-after.2=Defaults to current system time ("now").
|
||||
not-after.3=Accepts special value "-" for end of time.
|
||||
SIGNATURE[0]=Detached signature
|
||||
CERT[0..*]=Public key certificates for signature verification
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ usage.header=Encrypt a message from standard input
|
|||
no-armor=ASCII armor the output
|
||||
as=Type of the input data. Defaults to 'binary'
|
||||
with-password.0=Encrypt the message with a password.
|
||||
with-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...)
|
||||
with-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
||||
sign-with=Sign the output with a private key
|
||||
profile=Profile identifier to switch between profiles
|
||||
with-key-password.0=Passphrase to unlock the secret key(s).
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
usage.header=Create an inline-signed message from data on standard input
|
||||
no-armor=ASCII armor the output
|
||||
as.0=Specify the signature format of the signed message
|
||||
as.0=Specify the signature format of the signed message.
|
||||
as.1='text' and 'binary' will produce inline-signed messages.
|
||||
as.2='cleartextsigned' will make use of the cleartext signature framework.
|
||||
as.2='clearsigned' will make use of the cleartext signature framework.
|
||||
as.3=Defaults to 'binary'.
|
||||
as.4=If '--as=text' and the input data is not valid UTF-8, inline-sign fails with return code 53.
|
||||
with-key-password.0=Passphrase to unlock the secret key(s).
|
||||
with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
|
||||
micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156)
|
||||
micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156).
|
||||
KEYS[0..*]=Secret keys used for signing
|
||||
|
||||
# Generic TODO: Remove when bumping picocli to 4.7.0
|
||||
|
|
|
@ -5,7 +5,7 @@ usage.header=Signiere eine Nachricht von Standard-Eingabe mit eingebetteten Sign
|
|||
no-armor=Schütze Ausgabe mit ASCII Armor
|
||||
as.0=Bestimme Signaturformat der Nachricht.
|
||||
as.1='text' und 'binary' resultieren in eingebettete Signaturen.
|
||||
as.2='cleartextsigned' wird die Nachricht Klartext-signieren.
|
||||
as.2='clearsigned' wird die Nachricht Klartext-signieren.
|
||||
as.3=Standardmäßig: 'binary'.
|
||||
as.4=Ist die Standard-Eingabe nicht UTF-8 kodiert und '--as=text' gesetzt, so wird inline-sign Fehlercode 53 zurückgeben.
|
||||
with-key-password.0=Passwort zum Entsperren des privaten Schlüssels
|
||||
|
|
|
@ -47,7 +47,7 @@ sop.error.input.malformed_not_after=Invalid date string supplied as value of '--
|
|||
sop.error.input.malformed_not_before=Invalid date string supplied as value of '--not-before'.
|
||||
sop.error.input.stdin_not_a_message=Standard Input appears not to contain a valid OpenPGP message.
|
||||
sop.error.input.stdin_not_a_private_key=Standard Input appears not to contain a valid OpenPGP secret key.
|
||||
sop.error.input.stdin_not_openpgp_data=Standard Input appears not to contain valid OpenPGP data
|
||||
sop.error.input.stdin_not_openpgp_data=Standard Input appears not to contain valid OpenPGP data.
|
||||
## Indirect Data Types
|
||||
sop.error.indirect_data_type.ambiguous_filename=File name '%s' is ambiguous. File with the same name exists on the filesystem.
|
||||
sop.error.indirect_data_type.environment_variable_not_set=Environment variable '%s' not set.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
usage.header=Zeige Versionsinformationen über das Programm
|
||||
extended=Gebe erweiterte Versionsinformationen aus
|
||||
backend=Gebe Informationen über das kryptografische Backend aus
|
||||
sop-spec=Gebe die neuste Revision der SOP Spezifikation aus, welche von dieser Implementierung umgesetzt wird.
|
||||
sop-spec=Gebe die neuste Revision der SOP Spezifikation aus, welche von dieser Implementierung umgesetzt wird
|
||||
|
||||
# Generic TODO: Remove when bumping picocli to 4.7.0
|
||||
usage.synopsisHeading=Aufruf:\u0020
|
||||
|
|
|
@ -14,7 +14,7 @@ import sop.cli.picocli.commands.ArmorCmd;
|
|||
import sop.util.UTCUtil;
|
||||
|
||||
public class DateParsingTest {
|
||||
private AbstractSopCmd cmd = new ArmorCmd(); // we use ArmorCmd as a concrete implementation.
|
||||
private final AbstractSopCmd cmd = new ArmorCmd(); // we use ArmorCmd as a concrete implementation.
|
||||
|
||||
@Test
|
||||
public void parseNotAfterDashReturnsEndOfTime() {
|
||||
|
|
|
@ -31,6 +31,7 @@ import java.io.InputStream;
|
|||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.text.ParseException;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
|
@ -187,7 +188,7 @@ public class DecryptCmdTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void assertSessionKeyAndVerificationsIsProperlyWrittenToSessionKeyFile() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException {
|
||||
public void assertSessionKeyAndVerificationsIsProperlyWrittenToSessionKeyFile() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException, ParseException {
|
||||
Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z");
|
||||
String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209";
|
||||
String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B";
|
||||
|
@ -281,7 +282,7 @@ public class DecryptCmdTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void verifyOutIsProperlyWritten() throws IOException, SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData {
|
||||
public void verifyOutIsProperlyWritten() throws IOException, SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, ParseException {
|
||||
File certFile = File.createTempFile("verify-out-cert", ".asc");
|
||||
File verifyOut = new File(certFile.getParent(), "verify-out.txt");
|
||||
if (verifyOut.exists()) {
|
||||
|
|
|
@ -16,6 +16,7 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.text.ParseException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
@ -41,7 +42,7 @@ public class VerifyCmdTest {
|
|||
PrintStream originalSout;
|
||||
|
||||
@BeforeEach
|
||||
public void prepare() throws SOPGPException.UnsupportedOption, SOPGPException.BadData, SOPGPException.NoSignature, IOException {
|
||||
public void prepare() throws SOPGPException.UnsupportedOption, SOPGPException.BadData, SOPGPException.NoSignature, IOException, ParseException {
|
||||
originalSout = System.out;
|
||||
|
||||
detachedVerify = mock(DetachedVerify.class);
|
||||
|
@ -73,7 +74,7 @@ public class VerifyCmdTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void notAfter_passedDown() throws SOPGPException.UnsupportedOption {
|
||||
public void notAfter_passedDown() throws SOPGPException.UnsupportedOption, ParseException {
|
||||
Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z");
|
||||
SopCLI.main(new String[] {"verify", "--not-after", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
verify(detachedVerify, times(1)).notAfter(date);
|
||||
|
@ -100,7 +101,7 @@ public class VerifyCmdTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void notBefore_passedDown() throws SOPGPException.UnsupportedOption {
|
||||
public void notBefore_passedDown() throws SOPGPException.UnsupportedOption, ParseException {
|
||||
Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z");
|
||||
SopCLI.main(new String[] {"verify", "--not-before", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
||||
verify(detachedVerify, times(1)).notBefore(date);
|
||||
|
@ -178,7 +179,7 @@ public class VerifyCmdTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void resultIsPrintedProperly() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData {
|
||||
public void resultIsPrintedProperly() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData, ParseException {
|
||||
when(detachedVerify.data((InputStream) any())).thenReturn(Arrays.asList(
|
||||
new Verification(UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"),
|
||||
"EB85BB5FA33A75E15E944E63F231550C4F47E38E",
|
||||
|
|
|
@ -19,6 +19,10 @@ dependencies {
|
|||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||
testFixturesImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||
testFixturesImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
|
||||
|
||||
// @Nullable, @Nonnull annotations
|
||||
implementation "com.google.code.findbugs:jsr305:3.0.2"
|
||||
|
||||
}
|
||||
|
||||
test {
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
|
||||
package sop;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import sop.util.Optional;
|
||||
import sop.util.UTF8Util;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Tuple class bundling a profile name and description.
|
||||
|
@ -15,7 +19,7 @@ import java.nio.charset.Charset;
|
|||
public class Profile {
|
||||
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final Optional<String> description;
|
||||
|
||||
/**
|
||||
* Create a new {@link Profile} object.
|
||||
|
@ -24,15 +28,62 @@ public class Profile {
|
|||
* @param name profile name
|
||||
* @param description profile description
|
||||
*/
|
||||
public Profile(String name, String 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;
|
||||
this.description = description;
|
||||
|
||||
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.
|
||||
|
@ -48,6 +99,7 @@ public class Profile {
|
|||
*
|
||||
* @return name
|
||||
*/
|
||||
@Nonnull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
@ -57,17 +109,26 @@ public class Profile {
|
|||
*
|
||||
* @return description
|
||||
*/
|
||||
public String getDescription() {
|
||||
@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() {
|
||||
return getName() + ": " + getDescription();
|
||||
if (getDescription().isEmpty()) {
|
||||
return getName();
|
||||
}
|
||||
return getName() + ": " + getDescription().get();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,6 +138,6 @@ public class Profile {
|
|||
*/
|
||||
private static boolean exceeds1000CharLineLimit(Profile profile) {
|
||||
String line = profile.toString();
|
||||
return line.getBytes(Charset.forName("UTF8")).length > 1000;
|
||||
return line.getBytes(UTF8Util.UTF8).length > 1000;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ public interface SOP {
|
|||
/**
|
||||
* 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
|
||||
|
@ -66,7 +66,7 @@ public interface SOP {
|
|||
/**
|
||||
* 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
|
||||
|
@ -75,7 +75,7 @@ public interface SOP {
|
|||
|
||||
/**
|
||||
* Sign a message using inline signatures.
|
||||
*
|
||||
* <p>
|
||||
* If you need to create detached signatures, use {@link #detachedSign()} instead.
|
||||
*
|
||||
* @return builder instance
|
||||
|
@ -85,7 +85,7 @@ public interface SOP {
|
|||
/**
|
||||
* 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
|
||||
|
@ -97,7 +97,7 @@ public interface SOP {
|
|||
/**
|
||||
* 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
|
||||
|
@ -106,7 +106,7 @@ public interface SOP {
|
|||
|
||||
/**
|
||||
* 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
|
||||
|
|
|
@ -19,7 +19,7 @@ public final class SigningResult {
|
|||
* 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.
|
||||
*
|
||||
|
|
|
@ -4,11 +4,15 @@
|
|||
|
||||
package sop;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
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.
|
||||
*/
|
||||
|
@ -17,8 +21,8 @@ public class Verification {
|
|||
private final Date creationTime;
|
||||
private final String signingKeyFingerprint;
|
||||
private final String signingCertFingerprint;
|
||||
private final SignatureMode signatureMode;
|
||||
private final String description;
|
||||
private final Optional<SignatureMode> signatureMode;
|
||||
private final Optional<String> description;
|
||||
|
||||
private static final String MODE = "mode:";
|
||||
|
||||
|
@ -29,8 +33,10 @@ public class Verification {
|
|||
* @param signingKeyFingerprint fingerprint of the signing (sub-) key
|
||||
* @param signingCertFingerprint fingerprint of the certificate
|
||||
*/
|
||||
public Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint) {
|
||||
this(creationTime, signingKeyFingerprint, signingCertFingerprint, null, null);
|
||||
public Verification(@Nonnull Date creationTime,
|
||||
@Nonnull String signingKeyFingerprint,
|
||||
@Nonnull String signingCertFingerprint) {
|
||||
this(creationTime, signingKeyFingerprint, signingCertFingerprint, Optional.ofEmpty(), Optional.ofEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,15 +48,41 @@ public class Verification {
|
|||
* @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(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint, SignatureMode signatureMode, String description) {
|
||||
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 == null ? null : description.trim();
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public static Verification fromString(String toString) {
|
||||
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]'");
|
||||
|
@ -58,7 +90,7 @@ public class Verification {
|
|||
|
||||
if (split.length == 3) {
|
||||
return new Verification(
|
||||
UTCUtil.parseUTCDate(split[0]), // timestamp
|
||||
parseUTCDate(split[0]), // timestamp
|
||||
split[1], // key FP
|
||||
split[2] // cert FP
|
||||
);
|
||||
|
@ -80,7 +112,7 @@ public class Verification {
|
|||
}
|
||||
|
||||
return new Verification(
|
||||
UTCUtil.parseUTCDate(split[0]), // timestamp
|
||||
parseUTCDate(split[0]), // timestamp
|
||||
split[1], // key FP
|
||||
split[2], // cert FP
|
||||
mode, // signature mode
|
||||
|
@ -88,11 +120,20 @@ public class Verification {
|
|||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -102,6 +143,7 @@ public class Verification {
|
|||
*
|
||||
* @return signing key fingerprint
|
||||
*/
|
||||
@Nonnull
|
||||
public String getSigningKeyFingerprint() {
|
||||
return signingKeyFingerprint;
|
||||
}
|
||||
|
@ -111,6 +153,7 @@ public class Verification {
|
|||
*
|
||||
* @return signing certificate fingerprint
|
||||
*/
|
||||
@Nonnull
|
||||
public String getSigningCertFingerprint() {
|
||||
return signingCertFingerprint;
|
||||
}
|
||||
|
@ -121,7 +164,8 @@ public class Verification {
|
|||
*
|
||||
* @return signature mode
|
||||
*/
|
||||
public SignatureMode getSignatureMode() {
|
||||
@Nonnull
|
||||
public Optional<SignatureMode> getSignatureMode() {
|
||||
return signatureMode;
|
||||
}
|
||||
|
||||
|
@ -131,7 +175,8 @@ public class Verification {
|
|||
*
|
||||
* @return description
|
||||
*/
|
||||
public String getDescription() {
|
||||
@Nonnull
|
||||
public Optional<String> getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
|
@ -144,12 +189,12 @@ public class Verification {
|
|||
.append(' ')
|
||||
.append(getSigningCertFingerprint());
|
||||
|
||||
if (signatureMode != null) {
|
||||
sb.append(' ').append(MODE).append(signatureMode);
|
||||
if (signatureMode.isPresent()) {
|
||||
sb.append(' ').append(MODE).append(signatureMode.get());
|
||||
}
|
||||
|
||||
if (description != null) {
|
||||
sb.append(' ').append(description);
|
||||
if (description.isPresent()) {
|
||||
sb.append(' ').append(description.get());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
|
|
|
@ -337,7 +337,7 @@ public abstract class SOPGPException extends RuntimeException {
|
|||
/**
|
||||
* 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 {
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
package sop.operation;
|
||||
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.util.UTF8Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public interface AbstractSign<T> {
|
||||
|
||||
|
@ -67,7 +67,7 @@ public interface AbstractSign<T> {
|
|||
default T withKeyPassword(String password)
|
||||
throws SOPGPException.UnsupportedOption,
|
||||
SOPGPException.PasswordNotHumanReadable {
|
||||
return withKeyPassword(password.getBytes(Charset.forName("UTF8")));
|
||||
return withKeyPassword(password.getBytes(UTF8Util.UTF8));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,11 +8,11 @@ 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.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
|
||||
public interface Decrypt {
|
||||
|
@ -138,7 +138,7 @@ public interface Decrypt {
|
|||
default Decrypt withKeyPassword(String password)
|
||||
throws SOPGPException.UnsupportedOption,
|
||||
SOPGPException.PasswordNotHumanReadable {
|
||||
return withKeyPassword(password.getBytes(Charset.forName("UTF8")));
|
||||
return withKeyPassword(password.getBytes(UTF8Util.UTF8));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
|
||||
package sop.operation;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
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 {
|
||||
|
||||
|
@ -82,7 +82,7 @@ public interface Encrypt {
|
|||
default Encrypt withKeyPassword(String password)
|
||||
throws SOPGPException.PasswordNotHumanReadable,
|
||||
SOPGPException.UnsupportedOption {
|
||||
return withKeyPassword(password.getBytes(Charset.forName("UTF8")));
|
||||
return withKeyPassword(password.getBytes(UTF8Util.UTF8));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,7 @@ package sop.operation;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
|
||||
import sop.Profile;
|
||||
import sop.Ready;
|
||||
|
@ -54,7 +55,11 @@ public interface GenerateKey {
|
|||
default GenerateKey withKeyPassword(byte[] password)
|
||||
throws SOPGPException.PasswordNotHumanReadable,
|
||||
SOPGPException.UnsupportedOption {
|
||||
try {
|
||||
return withKeyPassword(UTF8Util.decodeUTF8(password));
|
||||
} catch (CharacterCodingException e) {
|
||||
throw new SOPGPException.PasswordNotHumanReadable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,4 +22,22 @@ public interface ListProfiles {
|
|||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ 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.
|
||||
*/
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package sop.util;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
@ -32,16 +33,24 @@ public class UTCUtil {
|
|||
*
|
||||
* @param dateString string
|
||||
* @return date
|
||||
* @throws ParseException if the date string is malformed and cannot be parsed
|
||||
*/
|
||||
public static Date parseUTCDate(String dateString) {
|
||||
@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
|
||||
}
|
||||
}
|
||||
return null;
|
||||
// No parser worked, so we throw the store exception
|
||||
throw exception;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
package sop.util;
|
||||
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
|
@ -15,7 +13,8 @@ import java.nio.charset.CodingErrorAction;
|
|||
|
||||
public class UTF8Util {
|
||||
|
||||
private static final CharsetDecoder UTF8Decoder = Charset.forName("UTF8")
|
||||
public static final Charset UTF8 = Charset.forName("UTF8");
|
||||
private static final CharsetDecoder UTF8Decoder = UTF8
|
||||
.newDecoder()
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||
.onMalformedInput(CodingErrorAction.REPORT);
|
||||
|
@ -27,14 +26,12 @@ public class UTF8Util {
|
|||
* @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) {
|
||||
public static String decodeUTF8(byte[] data)
|
||||
throws CharacterCodingException {
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(data);
|
||||
try {
|
||||
CharBuffer charBuffer = UTF8Decoder.decode(byteBuffer);
|
||||
return charBuffer.toString();
|
||||
} catch (CharacterCodingException e) {
|
||||
throw new SOPGPException.PasswordNotHumanReadable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,23 +2,23 @@
|
|||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util;
|
||||
package sop;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.util.UTCUtil;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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() {
|
||||
public void testCreationAndGetters() throws ParseException {
|
||||
byte[] bytes = "Hello, World!\n".getBytes(StandardCharsets.UTF_8);
|
||||
List<Verification> result = Collections.singletonList(
|
||||
new Verification(UTCUtil.parseUTCDate("2019-10-24T23:48:29Z"),
|
|
@ -2,19 +2,18 @@
|
|||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util;
|
||||
package sop;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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
|
100
sop-java/src/test/java/sop/ProfileTest.java
Normal file
100
sop-java/src/test/java/sop/ProfileTest.java
Normal file
|
@ -0,0 +1,100 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ProfileTest {
|
||||
|
||||
@Test
|
||||
public void toStringFull() {
|
||||
Profile profile = new Profile("default", "Use the implementers recommendations.");
|
||||
assertEquals("default: Use the implementers recommendations.", profile.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringNameOnly() {
|
||||
Profile profile = new Profile("default");
|
||||
assertEquals("default", profile.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseFull() {
|
||||
String string = "default: Use the implementers recommendations.";
|
||||
Profile profile = Profile.parse(string);
|
||||
assertEquals("default", profile.getName());
|
||||
assertTrue(profile.hasDescription());
|
||||
assertEquals("Use the implementers recommendations.", profile.getDescription().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseNameOnly() {
|
||||
String string = "rfc4880";
|
||||
Profile profile = Profile.parse(string);
|
||||
assertEquals("rfc4880", profile.getName());
|
||||
assertFalse(profile.hasDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseEmptyDescription() {
|
||||
String string = "rfc4880: ";
|
||||
Profile profile = Profile.parse(string);
|
||||
assertEquals("rfc4880", profile.getName());
|
||||
assertFalse(profile.hasDescription());
|
||||
|
||||
string = "rfc4880:";
|
||||
profile = Profile.parse(string);
|
||||
assertEquals("rfc4880", profile.getName());
|
||||
assertFalse(profile.hasDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseTooLongProfile() {
|
||||
// 1200 chars
|
||||
String string = "longDescription: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.";
|
||||
assertThrows(IllegalArgumentException.class, () -> Profile.parse(string));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructTooLongProfile() {
|
||||
// name + description = 1200 chars
|
||||
String name = "longDescription";
|
||||
String description = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.";
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile(name, description));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nameCannotBeEmpty() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile(""));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile(""), "Description Text.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nameCannotContainColons() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("default:"));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("default:", "DescriptionText"));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("rfc:4880"));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("rfc:4880", "OpenPGP Message Format"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nameCannotContainWhitespace() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("default profile"));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("default profile", "With description."));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("default\nprofile"));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("default\nprofile", "With description"));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("default\tprofile"));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("default\tprofile", "With description"));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("default\r\nprofile"));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("default\r\nprofile", "With description"));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("default\rprofile"));
|
||||
assertThrows(IllegalArgumentException.class, () -> new Profile("default\rprofile", "With description"));
|
||||
}
|
||||
}
|
|
@ -2,16 +2,15 @@
|
|||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util;
|
||||
package sop;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.Ready;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
|
||||
public class ReadyTest {
|
||||
|
|
@ -2,27 +2,26 @@
|
|||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util;
|
||||
package sop;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.util.UTCUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
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;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class ReadyWithResultTest {
|
||||
|
||||
@Test
|
||||
public void testReadyWithResult() throws SOPGPException.NoSignature, IOException {
|
||||
public void testReadyWithResult() throws SOPGPException.NoSignature, IOException, ParseException {
|
||||
byte[] data = "Hello, World!\n".getBytes(StandardCharsets.UTF_8);
|
||||
List<Verification> result = Collections.singletonList(
|
||||
new Verification(UTCUtil.parseUTCDate("2019-10-24T23:48:29Z"),
|
|
@ -2,15 +2,15 @@
|
|||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util;
|
||||
package sop;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.util.HexUtil;
|
||||
|
||||
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
|
|
@ -2,13 +2,11 @@
|
|||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
package sop;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.MicAlg;
|
||||
import sop.SigningResult;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class SigningResultTest {
|
||||
|
109
sop-java/src/test/java/sop/VerificationTest.java
Normal file
109
sop-java/src/test/java/sop/VerificationTest.java
Normal file
|
@ -0,0 +1,109 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.enums.SignatureMode;
|
||||
import sop.testsuite.assertions.VerificationAssert;
|
||||
import sop.util.UTCUtil;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class VerificationTest {
|
||||
|
||||
@Test
|
||||
public void limitedConstructorTest() throws ParseException {
|
||||
Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z");
|
||||
String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209";
|
||||
String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B";
|
||||
Verification verification = new Verification(signDate, keyFP, certFP);
|
||||
assertEquals("2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B", verification.toString());
|
||||
|
||||
VerificationAssert.assertThatVerification(verification)
|
||||
.issuedBy(certFP)
|
||||
.isBySigningKey(keyFP)
|
||||
.isCreatedAt(signDate)
|
||||
.hasMode(null)
|
||||
.hasDescription(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void limitedParsingTest() throws ParseException {
|
||||
String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B";
|
||||
Verification verification = Verification.fromString(string);
|
||||
assertEquals(string, verification.toString());
|
||||
VerificationAssert.assertThatVerification(verification)
|
||||
.isCreatedAt(UTCUtil.parseUTCDate("2022-11-07T15:01:24Z"))
|
||||
.issuedBy("F9E6F53F7201C60A87064EAB0B27F2B0760A1209", "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B")
|
||||
.hasMode(null)
|
||||
.hasDescription(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingWithModeTest() throws ParseException {
|
||||
String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:text";
|
||||
Verification verification = Verification.fromString(string);
|
||||
assertEquals(string, verification.toString());
|
||||
VerificationAssert.assertThatVerification(verification)
|
||||
.isCreatedAt(UTCUtil.parseUTCDate("2022-11-07T15:01:24Z"))
|
||||
.issuedBy("F9E6F53F7201C60A87064EAB0B27F2B0760A1209", "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B")
|
||||
.hasMode(SignatureMode.text)
|
||||
.hasDescription(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extendedConstructorTest() throws ParseException {
|
||||
Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z");
|
||||
String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209";
|
||||
String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B";
|
||||
SignatureMode mode = SignatureMode.binary;
|
||||
String description = "certificate from dkg.asc";
|
||||
Verification verification = new Verification(signDate, keyFP, certFP, mode, description);
|
||||
|
||||
assertEquals("2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:binary certificate from dkg.asc", verification.toString());
|
||||
VerificationAssert.assertThatVerification(verification)
|
||||
.isCreatedAt(signDate)
|
||||
.issuedBy(keyFP, certFP)
|
||||
.hasMode(SignatureMode.binary)
|
||||
.hasDescription(description);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extendedParsingTest() throws ParseException {
|
||||
String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:binary certificate from dkg.asc";
|
||||
Verification verification = Verification.fromString(string);
|
||||
assertEquals(string, verification.toString());
|
||||
|
||||
// no mode
|
||||
string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B certificate from dkg.asc";
|
||||
verification = Verification.fromString(string);
|
||||
assertEquals(string, verification.toString());
|
||||
VerificationAssert.assertThatVerification(verification)
|
||||
.isCreatedAt(UTCUtil.parseUTCDate("2022-11-07T15:01:24Z"))
|
||||
.issuedBy("F9E6F53F7201C60A87064EAB0B27F2B0760A1209", "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B")
|
||||
.hasMode(null)
|
||||
.hasDescription("certificate from dkg.asc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingFingerprintFails() {
|
||||
String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209";
|
||||
assertThrows(IllegalArgumentException.class, () -> Verification.fromString(string));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void malformedTimestampFails() {
|
||||
String shorter = "'99-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B";
|
||||
assertThrows(IllegalArgumentException.class, () -> Verification.fromString(shorter));
|
||||
|
||||
String longer = "'99-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:binary certificate from dkg.asc";
|
||||
assertThrows(IllegalArgumentException.class, () -> Verification.fromString(longer));
|
||||
|
||||
}
|
||||
}
|
|
@ -4,12 +4,13 @@
|
|||
|
||||
package sop.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
/**
|
||||
* Test parsing some date examples from the stateless OpenPGP CLI spec.
|
||||
|
@ -19,30 +20,29 @@ import org.junit.jupiter.api.Test;
|
|||
public class UTCUtilTest {
|
||||
|
||||
@Test
|
||||
public void parseExample1() {
|
||||
public void parseExample1() throws ParseException {
|
||||
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() {
|
||||
public void parseExample2() throws ParseException {
|
||||
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() {
|
||||
public void parseExample3() throws ParseException {
|
||||
String timestamp = "20191029T121104Z";
|
||||
Date date = UTCUtil.parseUTCDate(timestamp);
|
||||
assertEquals("2019-10-29T12:11:04Z", UTCUtil.formatUTCDate(date));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidDateReturnsNull() {
|
||||
public void invalidDateThrows() {
|
||||
String invalidTimestamp = "foobar";
|
||||
Date expectNull = UTCUtil.parseUTCDate(invalidTimestamp);
|
||||
assertNull(expectNull);
|
||||
assertThrows(ParseException.class, () -> UTCUtil.parseUTCDate(invalidTimestamp));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
package sop.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -15,7 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||
public class UTF8UtilTest {
|
||||
|
||||
@Test
|
||||
public void testValidUtf8Decoding() {
|
||||
public void testValidUtf8Decoding() throws CharacterCodingException {
|
||||
String utf8String = "Hello, World\n";
|
||||
String decoded = UTF8Util.decodeUTF8(utf8String.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
|
@ -29,11 +29,11 @@ public class UTF8UtilTest {
|
|||
*/
|
||||
@Test
|
||||
public void testInvalidUtf8StringThrows() {
|
||||
assertThrows(SOPGPException.PasswordNotHumanReadable.class,
|
||||
assertThrows(CharacterCodingException.class,
|
||||
() -> UTF8Util.decodeUTF8(new byte[] {(byte) 0xa0, (byte) 0xa1}));
|
||||
assertThrows(SOPGPException.PasswordNotHumanReadable.class,
|
||||
assertThrows(CharacterCodingException.class,
|
||||
() -> UTF8Util.decodeUTF8(new byte[] {(byte) 0xc0, (byte) 0xaf}));
|
||||
assertThrows(SOPGPException.PasswordNotHumanReadable.class,
|
||||
assertThrows(CharacterCodingException.class,
|
||||
() -> UTF8Util.decodeUTF8(new byte[] {(byte) 0x80, (byte) 0xbf}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import sop.Verification;
|
||||
import sop.enums.SignatureMode;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class VerificationTest {
|
||||
|
||||
@Test
|
||||
public void limitedConstructorTest() {
|
||||
Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z");
|
||||
String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209";
|
||||
String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B";
|
||||
Verification verification = new Verification(signDate, keyFP, certFP);
|
||||
assertEquals("2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B", verification.toString());
|
||||
}
|
||||
|
||||
public void limitedParsingTest() {
|
||||
String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B";
|
||||
Verification verification = Verification.fromString(string);
|
||||
assertEquals(string, verification.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extendedConstructorTest() {
|
||||
Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z");
|
||||
String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209";
|
||||
String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B";
|
||||
SignatureMode mode = SignatureMode.binary;
|
||||
String description = "certificate from dkg.asc";
|
||||
Verification verification = new Verification(signDate, keyFP, certFP, mode, description);
|
||||
assertEquals("2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:binary certificate from dkg.asc", verification.toString());
|
||||
assertEquals(SignatureMode.binary, verification.getSignatureMode());
|
||||
assertEquals(description, verification.getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extendedParsingTest() {
|
||||
String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:binary certificate from dkg.asc";
|
||||
Verification verification = Verification.fromString(string);
|
||||
assertEquals(string, verification.toString());
|
||||
|
||||
// no mode
|
||||
string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B certificate from dkg.asc";
|
||||
verification = Verification.fromString(string);
|
||||
assertEquals(string, verification.toString());
|
||||
}
|
||||
}
|
|
@ -4,15 +4,14 @@
|
|||
|
||||
package sop.testsuite;
|
||||
|
||||
import sop.Verification;
|
||||
import sop.util.UTCUtil;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class JUtils {
|
||||
|
@ -157,66 +156,12 @@ public class JUtils {
|
|||
return string.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static void assertSignedBy(List<Verification> verifications, String primaryFingerprint) {
|
||||
for (Verification verification : verifications) {
|
||||
if (verification.getSigningCertFingerprint().equals(primaryFingerprint)) {
|
||||
return;
|
||||
}
|
||||
public static void assertDateEquals(Date expected, Date actual) {
|
||||
assertEquals(UTCUtil.formatUTCDate(expected), UTCUtil.formatUTCDate(actual));
|
||||
}
|
||||
|
||||
if (verifications.isEmpty()) {
|
||||
fail("Verification list is empty.");
|
||||
public static boolean dateEquals(Date expected, Date actual) {
|
||||
return UTCUtil.formatUTCDate(expected).equals(UTCUtil.formatUTCDate(actual));
|
||||
}
|
||||
|
||||
fail("Verification list does not contain verification by cert " + primaryFingerprint + ":\n" +
|
||||
Arrays.toString(verifications.toArray(new Verification[0])));
|
||||
}
|
||||
|
||||
public static void assertSignedBy(List<Verification> verifications, String signingFingerprint, String primaryFingerprint) {
|
||||
for (Verification verification : verifications) {
|
||||
if (verification.getSigningCertFingerprint().equals(primaryFingerprint) && verification.getSigningKeyFingerprint().equals(signingFingerprint)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (verifications.isEmpty()) {
|
||||
fail("Verification list is empty.");
|
||||
}
|
||||
|
||||
fail("Verification list does not contain verification by key " + signingFingerprint + " on cert " + primaryFingerprint + ":\n" +
|
||||
Arrays.toString(verifications.toArray(new Verification[0])));
|
||||
}
|
||||
|
||||
public static void assertSignedBy(List<Verification> verifications, String primaryFingerprint, Date signatureDate) {
|
||||
for (Verification verification : verifications) {
|
||||
if (verification.getSigningCertFingerprint().equals(primaryFingerprint) &&
|
||||
verification.getCreationTime().equals(signatureDate)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (verifications.isEmpty()) {
|
||||
fail("Verification list is empty.");
|
||||
}
|
||||
|
||||
fail("Verification list does not contain verification by cert " + primaryFingerprint + " made at " + UTCUtil.formatUTCDate(signatureDate) + ":\n" +
|
||||
Arrays.toString(verifications.toArray(new Verification[0])));
|
||||
}
|
||||
|
||||
public static void assertSignedBy(List<Verification> verifications, String signingFingerprint, String primaryFingerprint, Date signatureDate) {
|
||||
for (Verification verification : verifications) {
|
||||
if (verification.getSigningCertFingerprint().equals(primaryFingerprint) &&
|
||||
verification.getSigningKeyFingerprint().equals(signingFingerprint) &&
|
||||
verification.getCreationTime().equals(signatureDate)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (verifications.isEmpty()) {
|
||||
fail("Verification list is empty.");
|
||||
}
|
||||
|
||||
fail("Verification list does not contain verification by key" + signingFingerprint + " on cert " + primaryFingerprint + " made at " + UTCUtil.formatUTCDate(signatureDate) + ":\n" +
|
||||
Arrays.toString(verifications.toArray(new Verification[0])));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package sop.testsuite;
|
|||
import sop.util.UTCUtil;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
||||
public class TestData {
|
||||
|
@ -59,7 +60,7 @@ public class TestData {
|
|||
"sfcfswMA\n" +
|
||||
"=RDAo\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
public static final Date ALICE_INLINE_SIGNED_MESSAGE_DATE = UTCUtil.parseUTCDate("2023-01-13T17:20:47Z");
|
||||
public static final Date ALICE_INLINE_SIGNED_MESSAGE_DATE = parseUTCDate("2023-01-13T17:20:47Z");
|
||||
// signature over PLAINTEXT
|
||||
public static final String ALICE_DETACHED_SIGNED_MESSAGE = "-----BEGIN PGP SIGNATURE-----\n" +
|
||||
"\n" +
|
||||
|
@ -68,7 +69,7 @@ public class TestData {
|
|||
"0K1UgT5roym9Fln8U5W8R03TSbfNiwE=\n" +
|
||||
"=bxPN\n" +
|
||||
"-----END PGP SIGNATURE-----";
|
||||
public static final Date ALICE_DETACHED_SIGNED_MESSAGE_DATE = UTCUtil.parseUTCDate("2023-01-13T16:57:57Z");
|
||||
public static final Date ALICE_DETACHED_SIGNED_MESSAGE_DATE = parseUTCDate("2023-01-13T16:57:57Z");
|
||||
|
||||
// 'Bob' key from draft-bre-openpgp-samples-00
|
||||
public static final String BOB_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||
|
@ -421,4 +422,12 @@ public class TestData {
|
|||
public static final byte[] BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n".getBytes(StandardCharsets.UTF_8);
|
||||
public static final byte[] END_PGP_SIGNATURE = "-----END PGP SIGNATURE-----".getBytes(StandardCharsets.UTF_8);
|
||||
public static final byte[] BEGIN_PGP_SIGNED_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----\n".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
private static Date parseUTCDate(String utcFormatted) {
|
||||
try {
|
||||
return UTCUtil.parseUTCDate(utcFormatted);
|
||||
} catch (ParseException e) {
|
||||
throw new IllegalArgumentException("Malformed UTC timestamp.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.testsuite.assertions;
|
||||
|
||||
import sop.Verification;
|
||||
import sop.enums.SignatureMode;
|
||||
import sop.testsuite.JUtils;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public final class VerificationAssert {
|
||||
|
||||
private final Verification verification;
|
||||
|
||||
public static VerificationAssert assertThatVerification(Verification verification) {
|
||||
return new VerificationAssert(verification);
|
||||
}
|
||||
|
||||
private VerificationAssert(Verification verification) {
|
||||
this.verification = verification;
|
||||
}
|
||||
|
||||
public VerificationAssert issuedBy(String signingKeyFingerprint, String primaryFingerprint) {
|
||||
return isBySigningKey(signingKeyFingerprint)
|
||||
.issuedBy(primaryFingerprint);
|
||||
}
|
||||
|
||||
public VerificationAssert issuedBy(String primaryFingerprint) {
|
||||
assertEquals(primaryFingerprint, verification.getSigningCertFingerprint());
|
||||
return this;
|
||||
}
|
||||
|
||||
public VerificationAssert isBySigningKey(String signingKeyFingerprint) {
|
||||
assertEquals(signingKeyFingerprint, verification.getSigningKeyFingerprint());
|
||||
return this;
|
||||
}
|
||||
|
||||
public VerificationAssert isCreatedAt(Date creationDate) {
|
||||
JUtils.assertDateEquals(creationDate, verification.getCreationTime());
|
||||
return this;
|
||||
}
|
||||
|
||||
public VerificationAssert hasDescription(String description) {
|
||||
assertEquals(description, verification.getDescription().get());
|
||||
return this;
|
||||
}
|
||||
|
||||
public VerificationAssert hasDescriptionOrNull(String description) {
|
||||
if (verification.getDescription().isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return hasDescription(description);
|
||||
}
|
||||
|
||||
public VerificationAssert hasMode(SignatureMode mode) {
|
||||
assertEquals(mode, verification.getSignatureMode().get());
|
||||
return this;
|
||||
}
|
||||
|
||||
public VerificationAssert hasModeOrNull(SignatureMode mode) {
|
||||
if (verification.getSignatureMode().isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
return hasMode(mode);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.testsuite.assertions;
|
||||
|
||||
import sop.Verification;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public final class VerificationListAssert {
|
||||
|
||||
private final List<Verification> verificationList = new ArrayList<>();
|
||||
|
||||
private VerificationListAssert(List<Verification> verifications) {
|
||||
this.verificationList.addAll(verifications);
|
||||
}
|
||||
|
||||
public static VerificationListAssert assertThatVerificationList(List<Verification> verifications) {
|
||||
return new VerificationListAssert(verifications);
|
||||
}
|
||||
|
||||
public VerificationListAssert isEmpty() {
|
||||
assertTrue(verificationList.isEmpty());
|
||||
return this;
|
||||
}
|
||||
|
||||
public VerificationListAssert isNotEmpty() {
|
||||
assertFalse(verificationList.isEmpty());
|
||||
return this;
|
||||
}
|
||||
|
||||
public VerificationListAssert sizeEquals(int size) {
|
||||
assertEquals(size, verificationList.size());
|
||||
return this;
|
||||
}
|
||||
|
||||
public VerificationAssert hasSingleItem() {
|
||||
sizeEquals(1);
|
||||
return VerificationAssert.assertThatVerification(verificationList.get(0));
|
||||
}
|
||||
|
||||
public VerificationListAssert containsVerificationByCert(String primaryFingerprint) {
|
||||
for (Verification verification : verificationList) {
|
||||
if (primaryFingerprint.equals(verification.getSigningCertFingerprint())) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
fail("No verification was issued by certificate " + primaryFingerprint);
|
||||
return this;
|
||||
}
|
||||
|
||||
public VerificationListAssert containsVerificationBy(String signingKeyFingerprint, String primaryFingerprint) {
|
||||
for (Verification verification : verificationList) {
|
||||
if (primaryFingerprint.equals(verification.getSigningCertFingerprint()) &&
|
||||
signingKeyFingerprint.equals(verification.getSigningKeyFingerprint())) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
fail("No verification was issued by key " + signingKeyFingerprint + " of cert " + primaryFingerprint);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* DSL for assertions on SOP objects.
|
||||
*/
|
||||
package sop.testsuite.assertions;
|
|
@ -9,6 +9,7 @@ import org.junit.jupiter.params.provider.Arguments;
|
|||
import sop.SOP;
|
||||
import sop.testsuite.SOPInstanceFactory;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -19,25 +20,30 @@ public abstract class AbstractSOPTest {
|
|||
private static final List<Arguments> backends = new ArrayList<>();
|
||||
|
||||
static {
|
||||
// populate instances list via configured test subject factory
|
||||
String factoryName = System.getenv("test.implementation");
|
||||
if (factoryName != null) {
|
||||
try {
|
||||
Class testSubjectFactoryClass = Class.forName(factoryName);
|
||||
SOPInstanceFactory factory = (SOPInstanceFactory) testSubjectFactoryClass.newInstance();
|
||||
Map<String, SOP> testSubjects = factory.provideSOPInstances();
|
||||
initBackends();
|
||||
}
|
||||
|
||||
// populate instances list via configured test subject factory
|
||||
private static void initBackends() {
|
||||
String factoryName = System.getenv("test.implementation");
|
||||
if (factoryName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SOPInstanceFactory factory;
|
||||
try {
|
||||
Class<?> testSubjectFactoryClass = Class.forName(factoryName);
|
||||
factory = (SOPInstanceFactory) testSubjectFactoryClass
|
||||
.getDeclaredConstructor().newInstance();
|
||||
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException |
|
||||
InvocationTargetException | NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
Map<String, SOP> testSubjects = factory.provideSOPInstances();
|
||||
for (String key : testSubjects.keySet()) {
|
||||
backends.add(Arguments.of(Named.of(key, testSubjects.get(key))));
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Stream<Arguments> provideBackends() {
|
||||
|
|
|
@ -11,9 +11,11 @@ import org.junit.jupiter.params.provider.MethodSource;
|
|||
import sop.SOP;
|
||||
import sop.Verification;
|
||||
import sop.enums.SignAs;
|
||||
import sop.enums.SignatureMode;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.testsuite.JUtils;
|
||||
import sop.testsuite.TestData;
|
||||
import sop.testsuite.assertions.VerificationListAssert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -21,7 +23,6 @@ import java.util.Date;
|
|||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
|
||||
|
@ -47,8 +48,11 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
|
|||
.signatures(signature)
|
||||
.data(message);
|
||||
|
||||
assertFalse(verificationList.isEmpty());
|
||||
JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT)
|
||||
.hasModeOrNull(SignatureMode.binary);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -68,8 +72,11 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
|
|||
.signatures(signature)
|
||||
.data(message);
|
||||
|
||||
assertFalse(verificationList.isEmpty());
|
||||
JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT)
|
||||
.hasModeOrNull(SignatureMode.text);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -83,8 +90,10 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
|
|||
.signatures(signature)
|
||||
.data(message);
|
||||
|
||||
assertFalse(verificationList.isEmpty());
|
||||
JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT, TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -103,8 +112,10 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
|
|||
.signatures(signature)
|
||||
.data(message);
|
||||
|
||||
assertFalse(verificationList.isEmpty());
|
||||
JUtils.assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -123,8 +134,10 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
|
|||
.signatures(signature)
|
||||
.data(message);
|
||||
|
||||
assertFalse(verificationList.isEmpty());
|
||||
JUtils.assertSignedBy(verificationList, TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -146,7 +159,10 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
|
|||
.signatures(signature)
|
||||
.data(message);
|
||||
|
||||
assertFalse(verificationList.isEmpty());
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.PASSWORD_PROTECTED_SIGNING_FINGERPRINT, TestData.PASSWORD_PROTECTED_PRIMARY_FINGERPRINT);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -170,8 +186,10 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
|
|||
.signatures(armored)
|
||||
.data(message);
|
||||
|
||||
assertFalse(verificationList.isEmpty());
|
||||
JUtils.assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -202,6 +220,21 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
|
|||
.data(message));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideInstances")
|
||||
public void signWithAliceVerifyWithBobThrowsNoSignature(SOP sop) throws IOException {
|
||||
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] signatures = sop.detachedSign()
|
||||
.key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
|
||||
.data(message)
|
||||
.toByteArrayAndResult()
|
||||
.getBytes();
|
||||
|
||||
assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify()
|
||||
.cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8))
|
||||
.signatures(signatures)
|
||||
.data(message));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideInstances")
|
||||
|
@ -229,11 +262,15 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
|
|||
.toByteArrayAndResult()
|
||||
.getBytes();
|
||||
|
||||
assertFalse(sop.verify()
|
||||
List<Verification> verificationList = sop.verify()
|
||||
.cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8))
|
||||
.signatures(signature)
|
||||
.data(message)
|
||||
.isEmpty());
|
||||
.data(message);
|
||||
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.PASSWORD_PROTECTED_SIGNING_FINGERPRINT, TestData.PASSWORD_PROTECTED_PRIMARY_FINGERPRINT);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -247,4 +284,29 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest {
|
|||
.data(message));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideInstances")
|
||||
public void signVerifyWithMultipleKeys(SOP sop) throws IOException {
|
||||
byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] signatures = sop.detachedSign()
|
||||
.key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
|
||||
.key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8))
|
||||
.data(message)
|
||||
.toByteArrayAndResult()
|
||||
.getBytes();
|
||||
|
||||
List<Verification> verificationList = sop.detachedVerify()
|
||||
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
|
||||
.cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8))
|
||||
.signatures(signatures)
|
||||
.data(message);
|
||||
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.sizeEquals(2)
|
||||
.containsVerificationBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT)
|
||||
.containsVerificationBy(TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -13,20 +13,20 @@ import sop.DecryptionResult;
|
|||
import sop.SOP;
|
||||
import sop.Verification;
|
||||
import sop.enums.EncryptAs;
|
||||
import sop.enums.SignatureMode;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.testsuite.JUtils;
|
||||
import sop.testsuite.TestData;
|
||||
import sop.testsuite.assertions.VerificationListAssert;
|
||||
import sop.util.UTCUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
|
@ -142,6 +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)
|
||||
.plaintext(message)
|
||||
.getBytes();
|
||||
|
||||
|
@ -156,9 +157,13 @@ public class EncryptDecryptTest extends AbstractSOPTest {
|
|||
|
||||
DecryptionResult result = bytesAndResult.getResult();
|
||||
assertNotNull(result.getSessionKey().get());
|
||||
|
||||
List<Verification> verificationList = result.getVerifications();
|
||||
assertEquals(1, verificationList.size());
|
||||
JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT)
|
||||
.hasModeOrNull(SignatureMode.binary);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -183,9 +188,12 @@ public class EncryptDecryptTest extends AbstractSOPTest {
|
|||
|
||||
DecryptionResult result = bytesAndResult.getResult();
|
||||
assertNotNull(result.getSessionKey().get());
|
||||
|
||||
List<Verification> verificationList = result.getVerifications();
|
||||
assertEquals(1, verificationList.size());
|
||||
JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT)
|
||||
.hasModeOrNull(SignatureMode.text);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -216,13 +224,15 @@ public class EncryptDecryptTest extends AbstractSOPTest {
|
|||
.ciphertext(ciphertext)
|
||||
.toByteArrayAndResult();
|
||||
|
||||
assertFalse(bytesAndResult.getResult().getVerifications().isEmpty());
|
||||
assertArrayEquals(message, bytesAndResult.getBytes());
|
||||
List<Verification> verifications = bytesAndResult.getResult().getVerifications();
|
||||
VerificationListAssert.assertThatVerificationList(verifications)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideInstances")
|
||||
public void decryptVerifyNotAfterTest(SOP sop) {
|
||||
public void decryptVerifyNotAfterTest(SOP sop) throws ParseException {
|
||||
byte[] message = ("-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"wV4DR2b2udXyHrYSAQdAwlOwwyxFDJta5+H9abgSj8jum9v7etUc9usdrElESmow\n" +
|
||||
|
@ -247,6 +257,7 @@ public class EncryptDecryptTest extends AbstractSOPTest {
|
|||
.ciphertext(message)
|
||||
.toByteArrayAndResult();
|
||||
|
||||
// Some implementations do not throw NoSignature and instead return an empty list.
|
||||
if (bytesAndResult.getResult().getVerifications().isEmpty()) {
|
||||
throw new SOPGPException.NoSignature("No verifiable signature found.");
|
||||
}
|
||||
|
@ -255,7 +266,7 @@ public class EncryptDecryptTest extends AbstractSOPTest {
|
|||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideInstances")
|
||||
public void decryptVerifyNotBeforeTest(SOP sop) {
|
||||
public void decryptVerifyNotBeforeTest(SOP sop) throws ParseException {
|
||||
byte[] message = ("-----BEGIN PGP MESSAGE-----\n" +
|
||||
"\n" +
|
||||
"wV4DR2b2udXyHrYSAQdAwlOwwyxFDJta5+H9abgSj8jum9v7etUc9usdrElESmow\n" +
|
||||
|
@ -280,6 +291,7 @@ public class EncryptDecryptTest extends AbstractSOPTest {
|
|||
.ciphertext(message)
|
||||
.toByteArrayAndResult();
|
||||
|
||||
// Some implementations do not throw NoSignature and instead return an empty list.
|
||||
if (bytesAndResult.getResult().getVerifications().isEmpty()) {
|
||||
throw new SOPGPException.NoSignature("No verifiable signature found.");
|
||||
}
|
||||
|
|
|
@ -13,9 +13,11 @@ import sop.ByteArrayAndResult;
|
|||
import sop.SOP;
|
||||
import sop.Verification;
|
||||
import sop.enums.InlineSignAs;
|
||||
import sop.enums.SignatureMode;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.testsuite.JUtils;
|
||||
import sop.testsuite.TestData;
|
||||
import sop.testsuite.assertions.VerificationListAssert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -51,8 +53,12 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
|
|||
.toByteArrayAndResult();
|
||||
|
||||
assertArrayEquals(message, bytesAndResult.getBytes());
|
||||
|
||||
List<Verification> verificationList = bytesAndResult.getResult();
|
||||
JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -74,8 +80,12 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
|
|||
.toByteArrayAndResult();
|
||||
|
||||
assertArrayEquals(message, bytesAndResult.getBytes());
|
||||
|
||||
List<Verification> verificationList = bytesAndResult.getResult();
|
||||
JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -97,8 +107,12 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
|
|||
.toByteArrayAndResult();
|
||||
|
||||
assertArrayEquals(message, bytesAndResult.getBytes());
|
||||
|
||||
List<Verification> verificationList = bytesAndResult.getResult();
|
||||
JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT)
|
||||
.hasModeOrNull(SignatureMode.text);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -111,8 +125,13 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
|
|||
.cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
|
||||
.data(message)
|
||||
.toByteArrayAndResult();
|
||||
|
||||
List<Verification> verificationList = bytesAndResult.getResult();
|
||||
JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT, signatureDate);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.isCreatedAt(signatureDate)
|
||||
.issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -161,8 +180,12 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
|
|||
.toByteArrayAndResult();
|
||||
|
||||
assertArrayEquals(message, bytesAndResult.getBytes());
|
||||
|
||||
List<Verification> verificationList = bytesAndResult.getResult();
|
||||
JUtils.assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -183,8 +206,12 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
|
|||
.toByteArrayAndResult();
|
||||
|
||||
assertArrayEquals(message, bytesAndResult.getBytes());
|
||||
|
||||
List<Verification> verificationList = bytesAndResult.getResult();
|
||||
JUtils.assertSignedBy(verificationList, TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.isNotEmpty()
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -205,7 +232,9 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest {
|
|||
.toByteArrayAndResult();
|
||||
|
||||
List<Verification> verificationList = bytesAndResult.getResult();
|
||||
JUtils.assertSignedBy(verificationList, TestData.PASSWORD_PROTECTED_SIGNING_FINGERPRINT, TestData.PASSWORD_PROTECTED_PRIMARY_FINGERPRINT);
|
||||
VerificationListAssert.assertThatVerificationList(verificationList)
|
||||
.hasSingleItem()
|
||||
.issuedBy(TestData.PASSWORD_PROTECTED_SIGNING_FINGERPRINT, TestData.PASSWORD_PROTECTED_PRIMARY_FINGERPRINT);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,12 +9,13 @@ import org.junit.jupiter.params.provider.Arguments;
|
|||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import sop.Profile;
|
||||
import sop.SOP;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class ListProfilesTest extends AbstractSOPTest {
|
||||
|
||||
|
@ -24,10 +25,29 @@ public class ListProfilesTest extends AbstractSOPTest {
|
|||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideInstances")
|
||||
public void listGenerateKeyProfiles(SOP sop) throws IOException {
|
||||
List<Profile> profiles = sop.listProfiles()
|
||||
.subcommand("generate-key");
|
||||
public void listGenerateKeyProfiles(SOP sop) {
|
||||
List<Profile> profiles = sop
|
||||
.listProfiles()
|
||||
.generateKey();
|
||||
|
||||
assertFalse(profiles.isEmpty());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideInstances")
|
||||
public void listEncryptProfiles(SOP sop) {
|
||||
List<Profile> profiles = sop
|
||||
.listProfiles()
|
||||
.encrypt();
|
||||
|
||||
assertFalse(profiles.isEmpty());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideInstances")
|
||||
public void listUnsupportedProfiles(SOP sop) {
|
||||
assertThrows(SOPGPException.UnsupportedProfile.class, () -> sop
|
||||
.listProfiles()
|
||||
.subcommand("invalid"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
allprojects {
|
||||
ext {
|
||||
shortVersion = '6.0.1'
|
||||
shortVersion = '6.1.1'
|
||||
isSnapshot = true
|
||||
minAndroidSdk = 10
|
||||
javaSourceCompatibility = 1.8
|
||||
|
|
Loading…
Reference in a new issue