mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-25 22:02:05 +01:00
Update SOP implementation to the latest spec version
See https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-03
This commit is contained in:
parent
5e0ca369bf
commit
1cb49f4b12
21 changed files with 348 additions and 112 deletions
|
@ -5,13 +5,16 @@
|
||||||
package org.pgpainless.cli.commands;
|
package org.pgpainless.cli.commands;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
@ -77,6 +80,9 @@ public class SignVerifyTest {
|
||||||
Streams.pipeAll(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), dataOut);
|
Streams.pipeAll(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), dataOut);
|
||||||
dataOut.close();
|
dataOut.close();
|
||||||
|
|
||||||
|
// Define micalg output file
|
||||||
|
File micalgOut = new File(tempDir, "micalg");
|
||||||
|
|
||||||
// Sign test data
|
// Sign test data
|
||||||
FileInputStream dataIn = new FileInputStream(dataFile);
|
FileInputStream dataIn = new FileInputStream(dataFile);
|
||||||
System.setIn(dataIn);
|
System.setIn(dataIn);
|
||||||
|
@ -84,7 +90,7 @@ public class SignVerifyTest {
|
||||||
assertTrue(sigFile.createNewFile());
|
assertTrue(sigFile.createNewFile());
|
||||||
FileOutputStream sigOut = new FileOutputStream(sigFile);
|
FileOutputStream sigOut = new FileOutputStream(sigFile);
|
||||||
System.setOut(new PrintStream(sigOut));
|
System.setOut(new PrintStream(sigOut));
|
||||||
PGPainlessCLI.execute("sign", "--armor", aliceKeyFile.getAbsolutePath());
|
PGPainlessCLI.execute("sign", "--armor", "--micalg-out", micalgOut.getAbsolutePath(), aliceKeyFile.getAbsolutePath());
|
||||||
sigOut.close();
|
sigOut.close();
|
||||||
|
|
||||||
// verify test data signature
|
// verify test data signature
|
||||||
|
@ -105,6 +111,15 @@ public class SignVerifyTest {
|
||||||
assertEquals(signingKeyFingerprint.toString(), split[1].trim());
|
assertEquals(signingKeyFingerprint.toString(), split[1].trim());
|
||||||
assertEquals(primaryKeyFingerprint.toString(), split[2].trim());
|
assertEquals(primaryKeyFingerprint.toString(), split[2].trim());
|
||||||
|
|
||||||
|
// Test micalg output
|
||||||
|
assertTrue(micalgOut.exists());
|
||||||
|
FileReader fileReader = new FileReader(micalgOut);
|
||||||
|
BufferedReader bufferedReader = new BufferedReader(fileReader);
|
||||||
|
String line = bufferedReader.readLine();
|
||||||
|
assertNull(bufferedReader.readLine());
|
||||||
|
bufferedReader.close();
|
||||||
|
assertEquals("pgp-sha512", line);
|
||||||
|
|
||||||
System.setIn(originalIn);
|
System.setIn(originalIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,10 +100,6 @@ public class DecryptImpl implements Decrypt {
|
||||||
PGPSecretKeyRingCollection secretKeys = PGPainless.readKeyRing()
|
PGPSecretKeyRingCollection secretKeys = PGPainless.readKeyRing()
|
||||||
.secretKeyRingCollection(keyIn);
|
.secretKeyRingCollection(keyIn);
|
||||||
|
|
||||||
if (secretKeys.size() != 1) {
|
|
||||||
throw new SOPGPException.BadData(new AssertionError("Exactly one single secret key expected. Got " + secretKeys.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (PGPSecretKeyRing secretKey : secretKeys) {
|
for (PGPSecretKeyRing secretKey : secretKeys) {
|
||||||
KeyRingInfo info = new KeyRingInfo(secretKey);
|
KeyRingInfo info = new KeyRingInfo(secretKey);
|
||||||
if (!info.isFullyDecrypted()) {
|
if (!info.isFullyDecrypted()) {
|
||||||
|
|
|
@ -49,7 +49,7 @@ public class EncryptImpl implements Encrypt {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Encrypt signWith(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.CertCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData {
|
public Encrypt signWith(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.KeyCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData {
|
||||||
try {
|
try {
|
||||||
PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn);
|
PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn);
|
||||||
if (keys.size() != 1) {
|
if (keys.size() != 1) {
|
||||||
|
@ -62,7 +62,7 @@ public class EncryptImpl implements Encrypt {
|
||||||
try {
|
try {
|
||||||
signingOptions.addInlineSignatures(SecretKeyRingProtector.unprotectedKeys(), keys, DocumentSignatureType.BINARY_DOCUMENT);
|
signingOptions.addInlineSignatures(SecretKeyRingProtector.unprotectedKeys(), keys, DocumentSignatureType.BINARY_DOCUMENT);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new SOPGPException.CertCannotSign();
|
throw new SOPGPException.KeyCannotSign();
|
||||||
} catch (WrongPassphraseException e) {
|
} catch (WrongPassphraseException e) {
|
||||||
throw new SOPGPException.KeyIsProtected();
|
throw new SOPGPException.KeyIsProtected();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,18 @@ package org.pgpainless.sop;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.key.util.KeyRingUtils;
|
|
||||||
import org.pgpainless.util.ArmorUtils;
|
import org.pgpainless.util.ArmorUtils;
|
||||||
import sop.operation.ExtractCert;
|
|
||||||
import sop.Ready;
|
import sop.Ready;
|
||||||
import sop.exception.SOPGPException;
|
import sop.exception.SOPGPException;
|
||||||
|
import sop.operation.ExtractCert;
|
||||||
|
|
||||||
public class ExtractCertImpl implements ExtractCert {
|
public class ExtractCertImpl implements ExtractCert {
|
||||||
|
|
||||||
|
@ -30,21 +32,34 @@ public class ExtractCertImpl implements ExtractCert {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Ready key(InputStream keyInputStream) throws IOException, SOPGPException.BadData {
|
public Ready key(InputStream keyInputStream) throws IOException, SOPGPException.BadData {
|
||||||
PGPSecretKeyRing key = PGPainless.readKeyRing().secretKeyRing(keyInputStream);
|
PGPSecretKeyRingCollection keys;
|
||||||
if (key == null) {
|
try {
|
||||||
|
keys = PGPainless.readKeyRing().secretKeyRingCollection(keyInputStream);
|
||||||
|
} catch (PGPException e) {
|
||||||
|
throw new IOException("Cannot read keys.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys == null || keys.size() == 0) {
|
||||||
throw new SOPGPException.BadData(new PGPException("No key data found."));
|
throw new SOPGPException.BadData(new PGPException("No key data found."));
|
||||||
}
|
}
|
||||||
|
|
||||||
PGPPublicKeyRing cert = KeyRingUtils.publicKeyRingFrom(key);
|
List<PGPPublicKeyRing> certs = new ArrayList<>();
|
||||||
|
for (PGPSecretKeyRing key : keys) {
|
||||||
|
PGPPublicKeyRing cert = PGPainless.extractCertificate(key);
|
||||||
|
certs.add(cert);
|
||||||
|
}
|
||||||
|
|
||||||
return new Ready() {
|
return new Ready() {
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(OutputStream outputStream) throws IOException {
|
public void writeTo(OutputStream outputStream) throws IOException {
|
||||||
OutputStream out = armor ? ArmorUtils.createArmoredOutputStreamFor(cert, outputStream) : outputStream;
|
|
||||||
cert.encode(out);
|
|
||||||
|
|
||||||
if (armor) {
|
for (PGPPublicKeyRing cert : certs) {
|
||||||
out.close();
|
OutputStream out = armor ? ArmorUtils.createArmoredOutputStreamFor(cert, outputStream) : outputStream;
|
||||||
|
cert.encode(out);
|
||||||
|
|
||||||
|
if (armor) {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,8 @@ import org.pgpainless.key.SubkeyIdentifier;
|
||||||
import org.pgpainless.key.info.KeyRingInfo;
|
import org.pgpainless.key.info.KeyRingInfo;
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||||
import sop.Ready;
|
import sop.MicAlg;
|
||||||
|
import sop.ReadyWithResult;
|
||||||
import sop.enums.SignAs;
|
import sop.enums.SignAs;
|
||||||
import sop.exception.SOPGPException;
|
import sop.exception.SOPGPException;
|
||||||
import sop.operation.Sign;
|
import sop.operation.Sign;
|
||||||
|
@ -53,16 +54,14 @@ public class SignImpl implements Sign {
|
||||||
public Sign key(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
|
public Sign key(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
|
||||||
try {
|
try {
|
||||||
PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn);
|
PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn);
|
||||||
if (keys.size() != 1) {
|
|
||||||
throw new SOPGPException.BadData(new AssertionError("Exactly one secret key at a time expected. Got " + keys.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
PGPSecretKeyRing key = keys.iterator().next();
|
for (PGPSecretKeyRing key : keys) {
|
||||||
KeyRingInfo info = new KeyRingInfo(key);
|
KeyRingInfo info = new KeyRingInfo(key);
|
||||||
if (!info.isFullyDecrypted()) {
|
if (!info.isFullyDecrypted()) {
|
||||||
throw new SOPGPException.KeyIsProtected();
|
throw new SOPGPException.KeyIsProtected();
|
||||||
|
}
|
||||||
|
signingOptions.addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), key, modeToSigType(mode));
|
||||||
}
|
}
|
||||||
signingOptions.addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), key, modeToSigType(mode));
|
|
||||||
} catch (PGPException e) {
|
} catch (PGPException e) {
|
||||||
throw new SOPGPException.BadData(e);
|
throw new SOPGPException.BadData(e);
|
||||||
}
|
}
|
||||||
|
@ -70,7 +69,7 @@ public class SignImpl implements Sign {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Ready data(InputStream data) throws IOException {
|
public ReadyWithResult<MicAlg> data(InputStream data) throws IOException {
|
||||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
try {
|
try {
|
||||||
EncryptionStream signingStream = PGPainless.encryptAndOrSign()
|
EncryptionStream signingStream = PGPainless.encryptAndOrSign()
|
||||||
|
@ -78,9 +77,9 @@ public class SignImpl implements Sign {
|
||||||
.withOptions(ProducerOptions.sign(signingOptions)
|
.withOptions(ProducerOptions.sign(signingOptions)
|
||||||
.setAsciiArmor(armor));
|
.setAsciiArmor(armor));
|
||||||
|
|
||||||
return new Ready() {
|
return new ReadyWithResult<MicAlg>() {
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(OutputStream outputStream) throws IOException {
|
public MicAlg writeTo(OutputStream outputStream) throws IOException {
|
||||||
|
|
||||||
if (signingStream.isClosed()) {
|
if (signingStream.isClosed()) {
|
||||||
throw new IllegalStateException("EncryptionStream is already closed.");
|
throw new IllegalStateException("EncryptionStream is already closed.");
|
||||||
|
@ -106,6 +105,8 @@ public class SignImpl implements Sign {
|
||||||
}
|
}
|
||||||
out.close();
|
out.close();
|
||||||
outputStream.close(); // armor out does not close underlying stream
|
outputStream.close(); // armor out does not close underlying stream
|
||||||
|
|
||||||
|
return micAlgFromSignatures(signatures);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -115,6 +116,19 @@ public class SignImpl implements Sign {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MicAlg micAlgFromSignatures(Iterable<PGPSignature> signatures) {
|
||||||
|
int algorithmId = 0;
|
||||||
|
for (PGPSignature signature : signatures) {
|
||||||
|
int sigAlg = signature.getHashAlgorithm();
|
||||||
|
if (algorithmId == 0 || algorithmId == sigAlg) {
|
||||||
|
algorithmId = sigAlg;
|
||||||
|
} else {
|
||||||
|
return MicAlg.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return algorithmId == 0 ? MicAlg.empty() : MicAlg.fromHashAlgorithmId(algorithmId);
|
||||||
|
}
|
||||||
|
|
||||||
private static DocumentSignatureType modeToSigType(SignAs mode) {
|
private static DocumentSignatureType modeToSigType(SignAs mode) {
|
||||||
return mode == SignAs.Binary ? DocumentSignatureType.BINARY_DOCUMENT
|
return mode == SignAs.Binary ? DocumentSignatureType.BINARY_DOCUMENT
|
||||||
: DocumentSignatureType.CANONICAL_TEXT_DOCUMENT;
|
: DocumentSignatureType.CANONICAL_TEXT_DOCUMENT;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import sop.operation.Version;
|
import sop.operation.Version;
|
||||||
|
|
||||||
public class VersionImpl implements Version {
|
public class VersionImpl implements Version {
|
||||||
|
@ -33,4 +34,20 @@ public class VersionImpl implements Version {
|
||||||
}
|
}
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBackendVersion() {
|
||||||
|
double bcVersion = new BouncyCastleProvider().getVersion();
|
||||||
|
return String.format("Bouncycastle %,.2f", bcVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getExtendedVersion() {
|
||||||
|
return getName() + " " + getVersion() + "\n" +
|
||||||
|
"Based on PGPainless " + getVersion() + "\n" +
|
||||||
|
"Using " + getBackendVersion() + "\n" +
|
||||||
|
"See https://pgpainless.org\n" +
|
||||||
|
"Implementing Stateless OpenPGP Protocol Version 3\n" +
|
||||||
|
"See https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-03";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ public class EncryptDecryptRoundTripTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void basicRoundTripWithKey() throws IOException, SOPGPException.CertCannotSign {
|
public void basicRoundTripWithKey() throws IOException, SOPGPException.KeyCannotSign {
|
||||||
byte[] encrypted = sop.encrypt()
|
byte[] encrypted = sop.encrypt()
|
||||||
.signWith(aliceKey)
|
.signWith(aliceKey)
|
||||||
.withCert(aliceCert)
|
.withCert(aliceCert)
|
||||||
|
@ -74,7 +74,7 @@ public class EncryptDecryptRoundTripTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void basicRoundTripWithoutArmorUsingKey() throws IOException, SOPGPException.CertCannotSign {
|
public void basicRoundTripWithoutArmorUsingKey() throws IOException, SOPGPException.KeyCannotSign {
|
||||||
byte[] aliceKeyNoArmor = sop.generateKey()
|
byte[] aliceKeyNoArmor = sop.generateKey()
|
||||||
.userId("Alice <alice@unarmored.org>")
|
.userId("Alice <alice@unarmored.org>")
|
||||||
.noArmor()
|
.noArmor()
|
||||||
|
@ -189,16 +189,6 @@ public class EncryptDecryptRoundTripTest {
|
||||||
.toByteArrayAndResult());
|
.toByteArrayAndResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void decrypt_withKeyWithMultipleKeysFails() {
|
|
||||||
byte[] keys = new byte[aliceKey.length + bobKey.length];
|
|
||||||
System.arraycopy(aliceKey, 0, keys, 0 , aliceKey.length);
|
|
||||||
System.arraycopy(bobKey, 0, keys, aliceKey.length, bobKey.length);
|
|
||||||
|
|
||||||
assertThrows(SOPGPException.BadData.class, () -> sop.decrypt()
|
|
||||||
.withKey(keys));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decrypt_withKeyWithPasswordProtectionFails() {
|
public void decrypt_withKeyWithPasswordProtectionFails() {
|
||||||
String passwordProtectedKey = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
String passwordProtectedKey = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||||
|
|
|
@ -13,13 +13,11 @@ import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -56,7 +54,7 @@ public class SignTest {
|
||||||
byte[] signature = sop.sign()
|
byte[] signature = sop.sign()
|
||||||
.key(key)
|
.key(key)
|
||||||
.data(data)
|
.data(data)
|
||||||
.getBytes();
|
.toByteArrayAndResult().getBytes();
|
||||||
|
|
||||||
assertTrue(new String(signature).startsWith("-----BEGIN PGP SIGNATURE-----"));
|
assertTrue(new String(signature).startsWith("-----BEGIN PGP SIGNATURE-----"));
|
||||||
|
|
||||||
|
@ -76,7 +74,7 @@ public class SignTest {
|
||||||
.key(key)
|
.key(key)
|
||||||
.noArmor()
|
.noArmor()
|
||||||
.data(data)
|
.data(data)
|
||||||
.getBytes();
|
.toByteArrayAndResult().getBytes();
|
||||||
|
|
||||||
assertFalse(new String(signature).startsWith("-----BEGIN PGP SIGNATURE-----"));
|
assertFalse(new String(signature).startsWith("-----BEGIN PGP SIGNATURE-----"));
|
||||||
|
|
||||||
|
@ -95,7 +93,7 @@ public class SignTest {
|
||||||
byte[] signature = sop.sign()
|
byte[] signature = sop.sign()
|
||||||
.key(key)
|
.key(key)
|
||||||
.data(data)
|
.data(data)
|
||||||
.getBytes();
|
.toByteArrayAndResult().getBytes();
|
||||||
|
|
||||||
assertThrows(SOPGPException.NoSignature.class, () -> sop.verify()
|
assertThrows(SOPGPException.NoSignature.class, () -> sop.verify()
|
||||||
.cert(cert)
|
.cert(cert)
|
||||||
|
@ -109,7 +107,7 @@ public class SignTest {
|
||||||
byte[] signature = sop.sign()
|
byte[] signature = sop.sign()
|
||||||
.key(key)
|
.key(key)
|
||||||
.data(data)
|
.data(data)
|
||||||
.getBytes();
|
.toByteArrayAndResult().getBytes();
|
||||||
|
|
||||||
assertThrows(SOPGPException.NoSignature.class, () -> sop.verify()
|
assertThrows(SOPGPException.NoSignature.class, () -> sop.verify()
|
||||||
.cert(cert)
|
.cert(cert)
|
||||||
|
@ -124,22 +122,12 @@ public class SignTest {
|
||||||
.mode(SignAs.Text)
|
.mode(SignAs.Text)
|
||||||
.key(key)
|
.key(key)
|
||||||
.data(data)
|
.data(data)
|
||||||
.getBytes();
|
.toByteArrayAndResult().getBytes();
|
||||||
|
|
||||||
PGPSignature sig = SignatureUtils.readSignatures(signature).get(0);
|
PGPSignature sig = SignatureUtils.readSignatures(signature).get(0);
|
||||||
assertEquals(SignatureType.CANONICAL_TEXT_DOCUMENT.getCode(), sig.getSignatureType());
|
assertEquals(SignatureType.CANONICAL_TEXT_DOCUMENT.getCode(), sig.getSignatureType());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void rejectKeyRingCollection() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
|
||||||
PGPSecretKeyRing key1 = PGPainless.generateKeyRing().modernKeyRing("Alice", null);
|
|
||||||
PGPSecretKeyRing key2 = PGPainless.generateKeyRing().modernKeyRing("Bob", null);
|
|
||||||
PGPSecretKeyRingCollection collection = new PGPSecretKeyRingCollection(Arrays.asList(key1, key2));
|
|
||||||
byte[] keys = collection.getEncoded();
|
|
||||||
|
|
||||||
assertThrows(SOPGPException.BadData.class, () -> sop.sign().key(keys));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rejectEncryptedKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
public void rejectEncryptedKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||||
PGPSecretKeyRing key = PGPainless.generateKeyRing()
|
PGPSecretKeyRing key = PGPainless.generateKeyRing()
|
||||||
|
|
|
@ -5,19 +5,48 @@
|
||||||
package org.pgpainless.sop;
|
package org.pgpainless.sop;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
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.assertNotNull;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import sop.SOP;
|
||||||
|
|
||||||
public class VersionTest {
|
public class VersionTest {
|
||||||
|
|
||||||
|
private static SOP sop;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setup() {
|
||||||
|
sop = new SOPImpl();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetVersion() {
|
public void testGetVersion() {
|
||||||
assertNotNull(new SOPImpl().version().getVersion());
|
String version = sop.version().getVersion();
|
||||||
|
assertNotNull(version);
|
||||||
|
assertFalse(version.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void assertNameEqualsPGPainless() {
|
public void assertNameEqualsPGPainless() {
|
||||||
assertEquals("PGPainless-SOP", new SOPImpl().version().getName());
|
assertEquals("PGPainless-SOP", sop.version().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetBackendVersion() {
|
||||||
|
String backendVersion = sop.version().getBackendVersion();
|
||||||
|
assertNotNull(backendVersion);
|
||||||
|
assertFalse(backendVersion.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetExtendedVersion() {
|
||||||
|
String extendedVersion = sop.version().getExtendedVersion();
|
||||||
|
assertNotNull(extendedVersion);
|
||||||
|
assertFalse(extendedVersion.isEmpty());
|
||||||
|
|
||||||
|
String firstLine = extendedVersion.split("\n")[0];
|
||||||
|
assertEquals(sop.version().getName() + " " + sop.version().getVersion(), firstLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,8 +82,8 @@ public class EncryptCmd implements Runnable {
|
||||||
throw new SOPGPException.KeyIsProtected("Key from " + keyFile.getAbsolutePath() + " is password protected.", keyIsProtected);
|
throw new SOPGPException.KeyIsProtected("Key from " + keyFile.getAbsolutePath() + " is password protected.", keyIsProtected);
|
||||||
} catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) {
|
} catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) {
|
||||||
throw new SOPGPException.UnsupportedAsymmetricAlgo("Key from " + keyFile.getAbsolutePath() + " has unsupported asymmetric algorithm.", unsupportedAsymmetricAlgo);
|
throw new SOPGPException.UnsupportedAsymmetricAlgo("Key from " + keyFile.getAbsolutePath() + " has unsupported asymmetric algorithm.", unsupportedAsymmetricAlgo);
|
||||||
} catch (SOPGPException.CertCannotSign certCannotSign) {
|
} catch (SOPGPException.KeyCannotSign keyCannotSign) {
|
||||||
throw new RuntimeException("Key from " + keyFile.getAbsolutePath() + " cannot sign.", certCannotSign);
|
throw new SOPGPException.KeyCannotSign("Key from " + keyFile.getAbsolutePath() + " cannot sign.", keyCannotSign);
|
||||||
} catch (SOPGPException.BadData badData) {
|
} catch (SOPGPException.BadData badData) {
|
||||||
throw new SOPGPException.BadData("Key file " + keyFile.getAbsolutePath() + " does not contain a valid OpenPGP private key.", badData);
|
throw new SOPGPException.BadData("Key file " + keyFile.getAbsolutePath() + " does not contain a valid OpenPGP private key.", badData);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,14 @@ package sop.cli.picocli.commands;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import picocli.CommandLine;
|
import picocli.CommandLine;
|
||||||
import sop.Ready;
|
import sop.MicAlg;
|
||||||
|
import sop.ReadyWithResult;
|
||||||
import sop.cli.picocli.Print;
|
import sop.cli.picocli.Print;
|
||||||
import sop.cli.picocli.SopCLI;
|
import sop.cli.picocli.SopCLI;
|
||||||
import sop.enums.SignAs;
|
import sop.enums.SignAs;
|
||||||
|
@ -34,9 +36,13 @@ public class SignCmd implements Runnable {
|
||||||
SignAs type;
|
SignAs type;
|
||||||
|
|
||||||
@CommandLine.Parameters(description = "Secret keys used for signing",
|
@CommandLine.Parameters(description = "Secret keys used for signing",
|
||||||
paramLabel = "KEY")
|
paramLabel = "KEYS")
|
||||||
List<File> secretKeyFile = new ArrayList<>();
|
List<File> secretKeyFile = new ArrayList<>();
|
||||||
|
|
||||||
|
@CommandLine.Option(names = "--micalg-out", description = "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)",
|
||||||
|
paramLabel = "MICALG")
|
||||||
|
File micAlgOut;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Sign sign = SopCLI.getSop().sign();
|
Sign sign = SopCLI.getSop().sign();
|
||||||
|
@ -51,8 +57,12 @@ public class SignCmd implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (micAlgOut != null && micAlgOut.exists()) {
|
||||||
|
throw new SOPGPException.OutputExists(String.format("Target %s of option %s already exists.", micAlgOut.getAbsolutePath(), "--micalg-out"));
|
||||||
|
}
|
||||||
|
|
||||||
if (secretKeyFile.isEmpty()) {
|
if (secretKeyFile.isEmpty()) {
|
||||||
Print.errln("Missing required parameter 'KEY'.");
|
Print.errln("Missing required parameter 'KEYS'.");
|
||||||
System.exit(19);
|
System.exit(19);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,8 +93,16 @@ public class SignCmd implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Ready ready = sign.data(System.in);
|
ReadyWithResult<MicAlg> ready = sign.data(System.in);
|
||||||
ready.writeTo(System.out);
|
MicAlg micAlg = ready.writeTo(System.out);
|
||||||
|
|
||||||
|
if (micAlgOut != null) {
|
||||||
|
// Write micalg out
|
||||||
|
micAlgOut.createNewFile();
|
||||||
|
FileOutputStream micAlgOutStream = new FileOutputStream(micAlgOut);
|
||||||
|
micAlg.writeTo(micAlgOutStream);
|
||||||
|
micAlgOutStream.close();
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Print.errln("IO Error.");
|
Print.errln("IO Error.");
|
||||||
Print.trace(e);
|
Print.trace(e);
|
||||||
|
|
|
@ -91,7 +91,7 @@ public class EncryptCmdTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void signWith_multipleTimesGetPassedDown() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotSign, SOPGPException.BadData {
|
public void signWith_multipleTimesGetPassedDown() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData {
|
||||||
File keyFile1 = File.createTempFile("sign-with-1-", ".asc");
|
File keyFile1 = File.createTempFile("sign-with-1-", ".asc");
|
||||||
File keyFile2 = File.createTempFile("sign-with-2-", ".asc");
|
File keyFile2 = File.createTempFile("sign-with-2-", ".asc");
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ public class EncryptCmdTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ExpectSystemExitWithStatus(67)
|
@ExpectSystemExitWithStatus(67)
|
||||||
public void signWith_keyIsProtectedCausesExit67() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotSign, SOPGPException.BadData, IOException {
|
public void signWith_keyIsProtectedCausesExit67() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
|
||||||
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected());
|
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected());
|
||||||
File keyFile = File.createTempFile("sign-with", ".asc");
|
File keyFile = File.createTempFile("sign-with", ".asc");
|
||||||
SopCLI.main(new String[] {"encrypt", "--sign-with", keyFile.getAbsolutePath(), "--with-password", "starship"});
|
SopCLI.main(new String[] {"encrypt", "--sign-with", keyFile.getAbsolutePath(), "--with-password", "starship"});
|
||||||
|
@ -115,23 +115,23 @@ public class EncryptCmdTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ExpectSystemExitWithStatus(13)
|
@ExpectSystemExitWithStatus(13)
|
||||||
public void signWith_unsupportedAsymmetricAlgoCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotSign, SOPGPException.BadData, IOException {
|
public void signWith_unsupportedAsymmetricAlgoCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
|
||||||
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception()));
|
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception()));
|
||||||
File keyFile = File.createTempFile("sign-with", ".asc");
|
File keyFile = File.createTempFile("sign-with", ".asc");
|
||||||
SopCLI.main(new String[] {"encrypt", "--with-password", "123456", "--sign-with", keyFile.getAbsolutePath()});
|
SopCLI.main(new String[] {"encrypt", "--with-password", "123456", "--sign-with", keyFile.getAbsolutePath()});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ExpectSystemExitWithStatus(1)
|
@ExpectSystemExitWithStatus(79)
|
||||||
public void signWith_certCannotSignCausesExit1() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotSign, SOPGPException.BadData {
|
public void signWith_certCannotSignCausesExit1() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData {
|
||||||
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.CertCannotSign());
|
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.KeyCannotSign());
|
||||||
File keyFile = File.createTempFile("sign-with", ".asc");
|
File keyFile = File.createTempFile("sign-with", ".asc");
|
||||||
SopCLI.main(new String[] {"encrypt", "--with-password", "dragon", "--sign-with", keyFile.getAbsolutePath()});
|
SopCLI.main(new String[] {"encrypt", "--with-password", "dragon", "--sign-with", keyFile.getAbsolutePath()});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ExpectSystemExitWithStatus(41)
|
@ExpectSystemExitWithStatus(41)
|
||||||
public void signWith_badDataCausesExit41() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotSign, SOPGPException.BadData, IOException {
|
public void signWith_badDataCausesExit41() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
|
||||||
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
||||||
File keyFile = File.createTempFile("sign-with", ".asc");
|
File keyFile = File.createTempFile("sign-with", ".asc");
|
||||||
SopCLI.main(new String[] {"encrypt", "--with-password", "orange", "--sign-with", keyFile.getAbsolutePath()});
|
SopCLI.main(new String[] {"encrypt", "--with-password", "orange", "--sign-with", keyFile.getAbsolutePath()});
|
||||||
|
|
|
@ -19,7 +19,8 @@ import java.io.OutputStream;
|
||||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import sop.Ready;
|
import sop.MicAlg;
|
||||||
|
import sop.ReadyWithResult;
|
||||||
import sop.SOP;
|
import sop.SOP;
|
||||||
import sop.cli.picocli.SopCLI;
|
import sop.cli.picocli.SopCLI;
|
||||||
import sop.exception.SOPGPException;
|
import sop.exception.SOPGPException;
|
||||||
|
@ -33,10 +34,10 @@ public class SignCmdTest {
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void mockComponents() throws IOException, SOPGPException.ExpectedText {
|
public void mockComponents() throws IOException, SOPGPException.ExpectedText {
|
||||||
sign = mock(Sign.class);
|
sign = mock(Sign.class);
|
||||||
when(sign.data((InputStream) any())).thenReturn(new Ready() {
|
when(sign.data((InputStream) any())).thenReturn(new ReadyWithResult<MicAlg>() {
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(OutputStream outputStream) {
|
public MicAlg writeTo(OutputStream outputStream) {
|
||||||
|
return MicAlg.fromHashAlgorithmId(10);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -109,9 +110,9 @@ public class SignCmdTest {
|
||||||
@Test
|
@Test
|
||||||
@ExpectSystemExitWithStatus(1)
|
@ExpectSystemExitWithStatus(1)
|
||||||
public void data_ioExceptionCausesExit1() throws IOException, SOPGPException.ExpectedText {
|
public void data_ioExceptionCausesExit1() throws IOException, SOPGPException.ExpectedText {
|
||||||
when(sign.data((InputStream) any())).thenReturn(new Ready() {
|
when(sign.data((InputStream) any())).thenReturn(new ReadyWithResult<MicAlg>() {
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(OutputStream outputStream) throws IOException {
|
public MicAlg writeTo(OutputStream outputStream) throws IOException {
|
||||||
throw new IOException();
|
throw new IOException();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
55
sop-java/src/main/java/sop/MicAlg.java
Normal file
55
sop-java/src/main/java/sop/MicAlg.java
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package sop;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
public class MicAlg {
|
||||||
|
|
||||||
|
private final String micAlg;
|
||||||
|
|
||||||
|
public MicAlg(String micAlg) {
|
||||||
|
if (micAlg == null) {
|
||||||
|
throw new IllegalArgumentException("MicAlg String cannot be null.");
|
||||||
|
}
|
||||||
|
this.micAlg = micAlg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MicAlg empty() {
|
||||||
|
return new MicAlg("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MicAlg fromHashAlgorithmId(int id) {
|
||||||
|
switch (id) {
|
||||||
|
case 1:
|
||||||
|
return new MicAlg("pgp-md5");
|
||||||
|
case 2:
|
||||||
|
return new MicAlg("pgp-sha1");
|
||||||
|
case 3:
|
||||||
|
return new MicAlg("pgp-ripemd160");
|
||||||
|
case 8:
|
||||||
|
return new MicAlg("pgp-sha256");
|
||||||
|
case 9:
|
||||||
|
return new MicAlg("pgp-sha384");
|
||||||
|
case 10:
|
||||||
|
return new MicAlg("pgp-sha512");
|
||||||
|
case 11:
|
||||||
|
return new MicAlg("pgp-sha224");
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported hash algorithm ID: " + id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMicAlg() {
|
||||||
|
return micAlg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeTo(OutputStream outputStream) {
|
||||||
|
PrintWriter pw = new PrintWriter(outputStream);
|
||||||
|
pw.write(getMicAlg());
|
||||||
|
pw.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
|
|
||||||
public abstract int getExitCode();
|
public abstract int getExitCode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No acceptable signatures found (sop verify).
|
||||||
|
*/
|
||||||
public static class NoSignature extends SOPGPException {
|
public static class NoSignature extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 3;
|
public static final int EXIT_CODE = 3;
|
||||||
|
@ -38,6 +41,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asymmetric algorithm unsupported (sop encrypt).
|
||||||
|
*/
|
||||||
public static class UnsupportedAsymmetricAlgo extends SOPGPException {
|
public static class UnsupportedAsymmetricAlgo extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 13;
|
public static final int EXIT_CODE = 13;
|
||||||
|
@ -56,6 +62,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Certificate not encryption capable (e,g, expired, revoked, unacceptable usage).
|
||||||
|
*/
|
||||||
public static class CertCannotEncrypt extends SOPGPException {
|
public static class CertCannotEncrypt extends SOPGPException {
|
||||||
public static final int EXIT_CODE = 17;
|
public static final int EXIT_CODE = 17;
|
||||||
|
|
||||||
|
@ -69,10 +78,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CertCannotSign extends Exception {
|
/**
|
||||||
|
* Missing required argument.
|
||||||
}
|
*/
|
||||||
|
|
||||||
public static class MissingArg extends SOPGPException {
|
public static class MissingArg extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 19;
|
public static final int EXIT_CODE = 19;
|
||||||
|
@ -87,6 +95,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Incomplete verification instructions (sop decrypt).
|
||||||
|
*/
|
||||||
public static class IncompleteVerification extends SOPGPException {
|
public static class IncompleteVerification extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 23;
|
public static final int EXIT_CODE = 23;
|
||||||
|
@ -101,6 +112,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unable to decrypt (sop decrypt).
|
||||||
|
*/
|
||||||
public static class CannotDecrypt extends SOPGPException {
|
public static class CannotDecrypt extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 29;
|
public static final int EXIT_CODE = 29;
|
||||||
|
@ -111,6 +125,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-UTF-8 or otherwise unreliable password (sop encrypt).
|
||||||
|
*/
|
||||||
public static class PasswordNotHumanReadable extends SOPGPException {
|
public static class PasswordNotHumanReadable extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 31;
|
public static final int EXIT_CODE = 31;
|
||||||
|
@ -121,6 +138,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsupported option.
|
||||||
|
*/
|
||||||
public static class UnsupportedOption extends SOPGPException {
|
public static class UnsupportedOption extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 37;
|
public static final int EXIT_CODE = 37;
|
||||||
|
@ -139,6 +159,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalid data type (no secret key where KEYS expected, etc.).
|
||||||
|
*/
|
||||||
public static class BadData extends SOPGPException {
|
public static class BadData extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 41;
|
public static final int EXIT_CODE = 41;
|
||||||
|
@ -157,6 +180,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-Text input where text expected.
|
||||||
|
*/
|
||||||
public static class ExpectedText extends SOPGPException {
|
public static class ExpectedText extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 53;
|
public static final int EXIT_CODE = 53;
|
||||||
|
@ -167,6 +193,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output file already exists.
|
||||||
|
*/
|
||||||
public static class OutputExists extends SOPGPException {
|
public static class OutputExists extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 59;
|
public static final int EXIT_CODE = 59;
|
||||||
|
@ -181,6 +210,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input file does not exist.
|
||||||
|
*/
|
||||||
public static class MissingInput extends SOPGPException {
|
public static class MissingInput extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 61;
|
public static final int EXIT_CODE = 61;
|
||||||
|
@ -195,6 +227,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A KEYS input is protected (locked) with a password, and sop cannot unlock it.
|
||||||
|
*/
|
||||||
public static class KeyIsProtected extends SOPGPException {
|
public static class KeyIsProtected extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 67;
|
public static final int EXIT_CODE = 67;
|
||||||
|
@ -213,6 +248,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsupported subcommand.
|
||||||
|
*/
|
||||||
public static class UnsupportedSubcommand extends SOPGPException {
|
public static class UnsupportedSubcommand extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 69;
|
public static final int EXIT_CODE = 69;
|
||||||
|
@ -227,6 +265,9 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An indirect parameter is a special designator (it starts with @), but sop does not know how to handle the prefix.
|
||||||
|
*/
|
||||||
public static class UnsupportedSpecialPrefix extends SOPGPException {
|
public static class UnsupportedSpecialPrefix extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 71;
|
public static final int EXIT_CODE = 71;
|
||||||
|
@ -237,7 +278,10 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A indirect input parameter is a special designator (it starts with @),
|
||||||
|
* and a filename matching the designator is actually present.
|
||||||
|
*/
|
||||||
public static class AmbiguousInput extends SOPGPException {
|
public static class AmbiguousInput extends SOPGPException {
|
||||||
|
|
||||||
public static final int EXIT_CODE = 73;
|
public static final int EXIT_CODE = 73;
|
||||||
|
@ -251,4 +295,30 @@ public abstract class SOPGPException extends RuntimeException {
|
||||||
return EXIT_CODE;
|
return EXIT_CODE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key not signature-capable (e.g. expired, revoked, unacceptable usage flags)
|
||||||
|
* (sop sign and sop encrypt with --sign-with).
|
||||||
|
*/
|
||||||
|
public static class KeyCannotSign extends SOPGPException {
|
||||||
|
|
||||||
|
public static final int EXIT_CODE = 79;
|
||||||
|
|
||||||
|
public KeyCannotSign() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyCannotSign(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyCannotSign(String s, KeyCannotSign keyCannotSign) {
|
||||||
|
super(s, keyCannotSign);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getExitCode() {
|
||||||
|
return EXIT_CODE;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,9 +35,9 @@ public interface Decrypt {
|
||||||
throws SOPGPException.UnsupportedOption;
|
throws SOPGPException.UnsupportedOption;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the verification cert.
|
* Adds one or more verification cert.
|
||||||
*
|
*
|
||||||
* @param cert input stream containing the cert
|
* @param cert input stream containing the cert(s)
|
||||||
* @return builder instance
|
* @return builder instance
|
||||||
*/
|
*/
|
||||||
Decrypt verifyWithCert(InputStream cert)
|
Decrypt verifyWithCert(InputStream cert)
|
||||||
|
@ -45,9 +45,9 @@ public interface Decrypt {
|
||||||
IOException;
|
IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the verification cert.
|
* Adds one or more verification cert.
|
||||||
*
|
*
|
||||||
* @param cert byte array containing the cert
|
* @param cert byte array containing the cert(s)
|
||||||
* @return builder instance
|
* @return builder instance
|
||||||
*/
|
*/
|
||||||
default Decrypt verifyWithCert(byte[] cert)
|
default Decrypt verifyWithCert(byte[] cert)
|
||||||
|
@ -75,9 +75,9 @@ public interface Decrypt {
|
||||||
SOPGPException.UnsupportedOption;
|
SOPGPException.UnsupportedOption;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the decryption key.
|
* Adds one or more decryption key.
|
||||||
*
|
*
|
||||||
* @param key input stream containing the key
|
* @param key input stream containing the key(s)
|
||||||
* @return builder instance
|
* @return builder instance
|
||||||
*/
|
*/
|
||||||
Decrypt withKey(InputStream key)
|
Decrypt withKey(InputStream key)
|
||||||
|
@ -86,9 +86,9 @@ public interface Decrypt {
|
||||||
SOPGPException.UnsupportedAsymmetricAlgo;
|
SOPGPException.UnsupportedAsymmetricAlgo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the decryption key.
|
* Adds one or more decryption key.
|
||||||
*
|
*
|
||||||
* @param key byte array containing the key
|
* @param key byte array containing the key(s)
|
||||||
* @return builder instance
|
* @return builder instance
|
||||||
*/
|
*/
|
||||||
default Decrypt withKey(byte[] key)
|
default Decrypt withKey(byte[] key)
|
||||||
|
|
|
@ -38,7 +38,7 @@ public interface Encrypt {
|
||||||
*/
|
*/
|
||||||
Encrypt signWith(InputStream key)
|
Encrypt signWith(InputStream key)
|
||||||
throws SOPGPException.KeyIsProtected,
|
throws SOPGPException.KeyIsProtected,
|
||||||
SOPGPException.CertCannotSign,
|
SOPGPException.KeyCannotSign,
|
||||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||||
SOPGPException.BadData;
|
SOPGPException.BadData;
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ public interface Encrypt {
|
||||||
*/
|
*/
|
||||||
default Encrypt signWith(byte[] key)
|
default Encrypt signWith(byte[] key)
|
||||||
throws SOPGPException.KeyIsProtected,
|
throws SOPGPException.KeyIsProtected,
|
||||||
SOPGPException.CertCannotSign,
|
SOPGPException.KeyCannotSign,
|
||||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
SOPGPException.UnsupportedAsymmetricAlgo,
|
||||||
SOPGPException.BadData {
|
SOPGPException.BadData {
|
||||||
return signWith(new ByteArrayInputStream(key));
|
return signWith(new ByteArrayInputStream(key));
|
||||||
|
|
|
@ -21,18 +21,18 @@ public interface ExtractCert {
|
||||||
ExtractCert noArmor();
|
ExtractCert noArmor();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the cert from the provided key.
|
* Extract the cert(s) from the provided key(s).
|
||||||
*
|
*
|
||||||
* @param keyInputStream input stream containing the encoding of an OpenPGP key
|
* @param keyInputStream input stream containing the encoding of one or more OpenPGP keys
|
||||||
* @return result containing the encoding of the keys cert
|
* @return result containing the encoding of the keys certs
|
||||||
*/
|
*/
|
||||||
Ready key(InputStream keyInputStream) throws IOException, SOPGPException.BadData;
|
Ready key(InputStream keyInputStream) throws IOException, SOPGPException.BadData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the cert from the provided key.
|
* Extract the cert(s) from the provided key(s).
|
||||||
*
|
*
|
||||||
* @param key byte array containing the encoding of an OpenPGP key
|
* @param key byte array containing the encoding of one or more OpenPGP key
|
||||||
* @return result containing the encoding of the keys cert
|
* @return result containing the encoding of the keys certs
|
||||||
*/
|
*/
|
||||||
default Ready key(byte[] key) throws IOException, SOPGPException.BadData {
|
default Ready key(byte[] key) throws IOException, SOPGPException.BadData {
|
||||||
return key(new ByteArrayInputStream(key));
|
return key(new ByteArrayInputStream(key));
|
||||||
|
|
|
@ -8,7 +8,8 @@ import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import sop.Ready;
|
import sop.MicAlg;
|
||||||
|
import sop.ReadyWithResult;
|
||||||
import sop.enums.SignAs;
|
import sop.enums.SignAs;
|
||||||
import sop.exception.SOPGPException;
|
import sop.exception.SOPGPException;
|
||||||
|
|
||||||
|
@ -31,17 +32,17 @@ public interface Sign {
|
||||||
Sign mode(SignAs mode) throws SOPGPException.UnsupportedOption;
|
Sign mode(SignAs mode) throws SOPGPException.UnsupportedOption;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the signer key.
|
* Add one or more signing keys.
|
||||||
*
|
*
|
||||||
* @param key input stream containing encoded key
|
* @param key input stream containing encoded keys
|
||||||
* @return builder instance
|
* @return builder instance
|
||||||
*/
|
*/
|
||||||
Sign key(InputStream key) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException;
|
Sign key(InputStream key) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the signer key.
|
* Add one or more signing keys.
|
||||||
*
|
*
|
||||||
* @param key byte array containing encoded key
|
* @param key byte array containing encoded keys
|
||||||
* @return builder instance
|
* @return builder instance
|
||||||
*/
|
*/
|
||||||
default Sign key(byte[] key) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
|
default Sign key(byte[] key) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
|
||||||
|
@ -54,7 +55,7 @@ public interface Sign {
|
||||||
* @param data input stream containing data
|
* @param data input stream containing data
|
||||||
* @return ready
|
* @return ready
|
||||||
*/
|
*/
|
||||||
Ready data(InputStream data) throws IOException, SOPGPException.ExpectedText;
|
ReadyWithResult<MicAlg> data(InputStream data) throws IOException, SOPGPException.ExpectedText;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs data.
|
* Signs data.
|
||||||
|
@ -62,7 +63,7 @@ public interface Sign {
|
||||||
* @param data byte array containing data
|
* @param data byte array containing data
|
||||||
* @return ready
|
* @return ready
|
||||||
*/
|
*/
|
||||||
default Ready data(byte[] data) throws IOException, SOPGPException.ExpectedText {
|
default ReadyWithResult<MicAlg> data(byte[] data) throws IOException, SOPGPException.ExpectedText {
|
||||||
return data(new ByteArrayInputStream(data));
|
return data(new ByteArrayInputStream(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,17 +29,17 @@ public interface Verify extends VerifySignatures {
|
||||||
Verify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption;
|
Verify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the verification cert.
|
* Add one or more verification cert.
|
||||||
*
|
*
|
||||||
* @param cert input stream containing the encoded cert
|
* @param cert input stream containing the encoded certs
|
||||||
* @return builder instance
|
* @return builder instance
|
||||||
*/
|
*/
|
||||||
Verify cert(InputStream cert) throws SOPGPException.BadData;
|
Verify cert(InputStream cert) throws SOPGPException.BadData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the verification cert.
|
* Add one or more verification cert.
|
||||||
*
|
*
|
||||||
* @param cert byte array containing the encoded cert
|
* @param cert byte array containing the encoded certs
|
||||||
* @return builder instance
|
* @return builder instance
|
||||||
*/
|
*/
|
||||||
default Verify cert(byte[] cert) throws SOPGPException.BadData {
|
default Verify cert(byte[] cert) throws SOPGPException.BadData {
|
||||||
|
|
|
@ -8,15 +8,42 @@ public interface Version {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the implementations name.
|
* Return the implementations name.
|
||||||
|
* e.g. "SOP",
|
||||||
*
|
*
|
||||||
* @return implementation name
|
* @return implementation name
|
||||||
*/
|
*/
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the implementations version string.
|
* Return the implementations short version string.
|
||||||
|
* e.g. "1.0"
|
||||||
*
|
*
|
||||||
* @return version string
|
* @return version string
|
||||||
*/
|
*/
|
||||||
String getVersion();
|
String getVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return version information about the used OpenPGP backend.
|
||||||
|
* e.g. "Bouncycastle 1.70"
|
||||||
|
*
|
||||||
|
* @return backend version string
|
||||||
|
*/
|
||||||
|
String getBackendVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an extended version string containing multiple lines of version information.
|
||||||
|
* The first line MUST match the information produced by {@link #getName()} and {@link #getVersion()}, but the rest of the text
|
||||||
|
* has no defined structure.
|
||||||
|
* Example:
|
||||||
|
* <pre>
|
||||||
|
* "SOP 1.0
|
||||||
|
* Awesome PGP!
|
||||||
|
* Using Bouncycastle 1.70
|
||||||
|
* LibFoo 1.2.2
|
||||||
|
* See https://pgp.example.org/sop/ for more information"
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @return extended version string
|
||||||
|
*/
|
||||||
|
String getExtendedVersion();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue