mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-09-10 18:59:39 +02:00
Base PGPainlessCLI on new sop-java module
* Rename pgpainless-sop -> pgpainless-cli * Introduce sop-java (implementation-independent SOP API) * Introduce sop-java-picocli (CLI frontend for sop-java) * Introduce pgpainless-sop (implementation of sop-java using PGPainless) * Rework pgpainless-cli (plugs pgpainless-sop into sop-java-picocli)
This commit is contained in:
parent
2ba782c451
commit
8cf5347b52
112 changed files with 6146 additions and 1303 deletions
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PushbackInputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||
import sop.Ready;
|
||||
import sop.enums.ArmorLabel;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Armor;
|
||||
|
||||
public class ArmorImpl implements Armor {
|
||||
|
||||
public static final byte[] ARMOR_START = "-----BEGIN PGP".getBytes(Charset.forName("UTF8"));
|
||||
|
||||
boolean allowNested = false;
|
||||
|
||||
@Override
|
||||
public Armor label(ArmorLabel label) throws SOPGPException.UnsupportedOption {
|
||||
throw new SOPGPException.UnsupportedOption();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Armor allowNested() throws SOPGPException.UnsupportedOption {
|
||||
allowNested = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ready data(InputStream data) throws SOPGPException.BadData {
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
PushbackInputStream pbIn = new PushbackInputStream(data, ARMOR_START.length);
|
||||
byte[] buffer = new byte[ARMOR_START.length];
|
||||
int read = pbIn.read(buffer);
|
||||
pbIn.unread(buffer, 0, read);
|
||||
if (!allowNested && Arrays.equals(ARMOR_START, buffer)) {
|
||||
Streams.pipeAll(pbIn, System.out);
|
||||
} else {
|
||||
ArmoredOutputStream armor = ArmoredOutputStreamFactory.get(System.out);
|
||||
Streams.pipeAll(pbIn, armor);
|
||||
armor.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import sop.Ready;
|
||||
import sop.operation.Dearmor;
|
||||
|
||||
public class DearmorImpl implements Dearmor {
|
||||
|
||||
@Override
|
||||
public Ready data(InputStream data) throws IOException {
|
||||
InputStream decoder = PGPUtil.getDecoderStream(data);
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
Streams.pipeAll(decoder, outputStream);
|
||||
decoder.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
184
pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java
Normal file
184
pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java
Normal file
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.exception.NotYetImplementedException;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import sop.DecryptionResult;
|
||||
import sop.ReadyWithResult;
|
||||
import sop.SessionKey;
|
||||
import sop.Verification;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Decrypt;
|
||||
|
||||
public class DecryptImpl implements Decrypt {
|
||||
|
||||
private final ConsumerOptions consumerOptions = new ConsumerOptions();
|
||||
|
||||
@Override
|
||||
public DecryptImpl verifyNotBefore(Date timestamp) throws SOPGPException.UnsupportedOption {
|
||||
try {
|
||||
consumerOptions.verifyNotBefore(timestamp);
|
||||
} catch (NotYetImplementedException e) {
|
||||
// throw new SOPGPException.UnsupportedOption();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecryptImpl verifyNotAfter(Date timestamp) throws SOPGPException.UnsupportedOption {
|
||||
try {
|
||||
consumerOptions.verifyNotAfter(timestamp);
|
||||
} catch (NotYetImplementedException e) {
|
||||
// throw new SOPGPException.UnsupportedOption();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecryptImpl verifyWithCert(InputStream certIn) throws SOPGPException.BadData, IOException {
|
||||
try {
|
||||
PGPPublicKeyRingCollection certs = PGPainless.readKeyRing().keyRingCollection(certIn, false)
|
||||
.getPgpPublicKeyRingCollection();
|
||||
if (certs == null) {
|
||||
throw new SOPGPException.BadData(new PGPException("No certificates provided."));
|
||||
}
|
||||
|
||||
consumerOptions.addVerificationCerts(certs);
|
||||
|
||||
} catch (PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecryptImpl withSessionKey(SessionKey sessionKey) throws SOPGPException.UnsupportedOption {
|
||||
throw new SOPGPException.UnsupportedOption();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecryptImpl withPassword(String password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
|
||||
consumerOptions.addDecryptionPassphrase(Passphrase.fromPassword(password));
|
||||
String withoutTrailingWhitespace = removeTrailingWhitespace(password);
|
||||
if (!password.equals(withoutTrailingWhitespace)) {
|
||||
consumerOptions.addDecryptionPassphrase(Passphrase.fromPassword(withoutTrailingWhitespace));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static String removeTrailingWhitespace(String passphrase) {
|
||||
int i = passphrase.length() - 1;
|
||||
// Find index of first non-whitespace character from the back
|
||||
while (i > 0 && Character.isWhitespace(passphrase.charAt(i))) {
|
||||
i--;
|
||||
}
|
||||
return passphrase.substring(0, i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecryptImpl withKey(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo {
|
||||
try {
|
||||
PGPSecretKeyRingCollection secretKeys = PGPainless.readKeyRing()
|
||||
.keyRingCollection(keyIn, true)
|
||||
.getPGPSecretKeyRingCollection();
|
||||
|
||||
for (PGPSecretKeyRing secretKey : secretKeys) {
|
||||
KeyRingInfo info = new KeyRingInfo(secretKey);
|
||||
if (!info.isFullyDecrypted()) {
|
||||
throw new SOPGPException.KeyIsProtected();
|
||||
}
|
||||
}
|
||||
|
||||
consumerOptions.addDecryptionKeys(secretKeys, SecretKeyRingProtector.unprotectedKeys());
|
||||
} catch (IOException | PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadyWithResult<DecryptionResult> ciphertext(InputStream ciphertext)
|
||||
throws SOPGPException.BadData,
|
||||
SOPGPException.MissingArg {
|
||||
|
||||
if (consumerOptions.getDecryptionKeys().isEmpty() && consumerOptions.getDecryptionPassphrases().isEmpty()) {
|
||||
throw new SOPGPException.MissingArg("Missing decryption key or passphrase.");
|
||||
}
|
||||
|
||||
DecryptionStream decryptionStream;
|
||||
try {
|
||||
decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(ciphertext)
|
||||
.withOptions(consumerOptions);
|
||||
} catch (PGPException | IOException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
|
||||
return new ReadyWithResult<DecryptionResult>() {
|
||||
@Override
|
||||
public DecryptionResult writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature {
|
||||
Streams.pipeAll(decryptionStream, outputStream);
|
||||
decryptionStream.close();
|
||||
OpenPgpMetadata metadata = decryptionStream.getResult();
|
||||
|
||||
List<Verification> verificationList = new ArrayList<>();
|
||||
for (SubkeyIdentifier verifiedSigningKey : metadata.getVerifiedSignatures().keySet()) {
|
||||
PGPSignature signature = metadata.getVerifiedSignatures().get(verifiedSigningKey);
|
||||
Date verifyNotBefore = consumerOptions.getVerifyNotBefore();
|
||||
Date verifyNotAfter = consumerOptions.getVerifyNotAfter();
|
||||
|
||||
if (verifyNotAfter == null || !signature.getCreationTime().after(verifyNotAfter)) {
|
||||
if (verifyNotBefore == null || !signature.getCreationTime().before(verifyNotBefore)) {
|
||||
verificationList.add(new Verification(
|
||||
signature.getCreationTime(),
|
||||
verifiedSigningKey.getSubkeyFingerprint().toString(),
|
||||
verifiedSigningKey.getPrimaryKeyFingerprint().toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!consumerOptions.getCertificates().isEmpty()) {
|
||||
if (verificationList.isEmpty()) {
|
||||
throw new SOPGPException.NoSignature();
|
||||
}
|
||||
}
|
||||
|
||||
return new DecryptionResult(null, verificationList);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
140
pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java
Normal file
140
pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.algorithm.StreamEncoding;
|
||||
import org.pgpainless.encryption_signing.EncryptionOptions;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.exception.WrongPassphraseException;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import sop.util.ProxyOutputStream;
|
||||
import sop.Ready;
|
||||
import sop.enums.EncryptAs;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Encrypt;
|
||||
|
||||
public class EncryptImpl implements Encrypt {
|
||||
|
||||
EncryptionOptions encryptionOptions = new EncryptionOptions();
|
||||
SigningOptions signingOptions = null;
|
||||
|
||||
private EncryptAs encryptAs = EncryptAs.Binary;
|
||||
boolean armor = true;
|
||||
|
||||
@Override
|
||||
public Encrypt noArmor() {
|
||||
armor = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Encrypt mode(EncryptAs mode) throws SOPGPException.UnsupportedOption {
|
||||
this.encryptAs = mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Encrypt signWith(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.CertCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData {
|
||||
try {
|
||||
PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn);
|
||||
|
||||
if (signingOptions == null) {
|
||||
signingOptions = SigningOptions.get();
|
||||
}
|
||||
try {
|
||||
signingOptions.addInlineSignatures(SecretKeyRingProtector.unprotectedKeys(), keys, DocumentSignatureType.BINARY_DOCUMENT);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SOPGPException.CertCannotSign();
|
||||
} catch (WrongPassphraseException e) {
|
||||
throw new SOPGPException.KeyIsProtected();
|
||||
}
|
||||
} catch (IOException | PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Encrypt withPassword(String password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
|
||||
encryptionOptions.addPassphrase(Passphrase.fromPassword(password));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Encrypt withCert(InputStream cert) throws SOPGPException.CertCannotEncrypt, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData {
|
||||
try {
|
||||
PGPPublicKeyRingCollection certificates = PGPainless.readKeyRing()
|
||||
.keyRingCollection(cert, false)
|
||||
.getPgpPublicKeyRingCollection();
|
||||
encryptionOptions.addRecipients(certificates);
|
||||
} catch (IOException | PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ready plaintext(InputStream plaintext) throws IOException {
|
||||
ProducerOptions producerOptions = signingOptions != null ?
|
||||
ProducerOptions.signAndEncrypt(encryptionOptions, signingOptions) :
|
||||
ProducerOptions.encrypt(encryptionOptions);
|
||||
producerOptions.setAsciiArmor(armor);
|
||||
producerOptions.setEncoding(encryptAsToStreamEncoding(encryptAs));
|
||||
|
||||
try {
|
||||
ProxyOutputStream proxy = new ProxyOutputStream();
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(proxy)
|
||||
.withOptions(producerOptions);
|
||||
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
proxy.replaceOutputStream(outputStream);
|
||||
Streams.pipeAll(plaintext, encryptionStream);
|
||||
encryptionStream.close();
|
||||
}
|
||||
};
|
||||
} catch (PGPException e) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private static StreamEncoding encryptAsToStreamEncoding(EncryptAs encryptAs) {
|
||||
switch (encryptAs) {
|
||||
case Binary:
|
||||
return StreamEncoding.BINARY;
|
||||
case Text:
|
||||
return StreamEncoding.TEXT;
|
||||
case MIME:
|
||||
return StreamEncoding.UTF8;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid value encountered: " + encryptAs);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.util.KeyRingUtils;
|
||||
import org.pgpainless.util.ArmorUtils;
|
||||
import sop.operation.ExtractCert;
|
||||
import sop.Ready;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
public class ExtractCertImpl implements ExtractCert {
|
||||
|
||||
private boolean armor = true;
|
||||
|
||||
@Override
|
||||
public ExtractCert noArmor() {
|
||||
armor = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ready key(InputStream keyInputStream) throws IOException, SOPGPException.BadData {
|
||||
try {
|
||||
PGPSecretKeyRing key = PGPainless.readKeyRing().secretKeyRing(keyInputStream);
|
||||
PGPPublicKeyRing cert = KeyRingUtils.publicKeyRingFrom(key);
|
||||
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
OutputStream out = armor ? ArmorUtils.createArmoredOutputStreamFor(cert, outputStream) : outputStream;
|
||||
cert.encode(out);
|
||||
|
||||
if (armor) {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditorInterface;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.ArmorUtils;
|
||||
import sop.Ready;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.GenerateKey;
|
||||
|
||||
public class GenerateKeyImpl implements GenerateKey {
|
||||
|
||||
private boolean armor = true;
|
||||
private final Set<String> userIds = new LinkedHashSet<>();
|
||||
|
||||
@Override
|
||||
public GenerateKey noArmor() {
|
||||
this.armor = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenerateKey userId(String userId) {
|
||||
this.userIds.add(userId);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo, IOException {
|
||||
Iterator<String> userIdIterator = userIds.iterator();
|
||||
if (!userIdIterator.hasNext()) {
|
||||
throw new SOPGPException.MissingArg("Missing user-id.");
|
||||
}
|
||||
|
||||
PGPSecretKeyRing key;
|
||||
try {
|
||||
key = PGPainless.generateKeyRing()
|
||||
.modernKeyRing(userIdIterator.next(), null);
|
||||
|
||||
if (userIdIterator.hasNext()) {
|
||||
SecretKeyRingEditorInterface editor = PGPainless.modifyKeyRing(key);
|
||||
|
||||
while (userIdIterator.hasNext()) {
|
||||
editor.addUserId(userIdIterator.next(), SecretKeyRingProtector.unprotectedKeys());
|
||||
}
|
||||
|
||||
key = editor.done();
|
||||
}
|
||||
|
||||
PGPSecretKeyRing finalKey = key;
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
if (armor) {
|
||||
ArmoredOutputStream armoredOutputStream = ArmorUtils.toAsciiArmoredStream(finalKey, outputStream);
|
||||
finalKey.encode(armoredOutputStream);
|
||||
armoredOutputStream.close();
|
||||
} else {
|
||||
finalKey.encode(outputStream);
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
|
||||
throw new SOPGPException.UnsupportedAsymmetricAlgo(e);
|
||||
} catch (PGPException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import org.pgpainless.sop.commands.Armor;
|
||||
import org.pgpainless.sop.commands.Dearmor;
|
||||
import org.pgpainless.sop.commands.Decrypt;
|
||||
import org.pgpainless.sop.commands.Encrypt;
|
||||
import org.pgpainless.sop.commands.ExtractCert;
|
||||
import org.pgpainless.sop.commands.GenerateKey;
|
||||
import org.pgpainless.sop.commands.Sign;
|
||||
import org.pgpainless.sop.commands.Verify;
|
||||
import org.pgpainless.sop.commands.Version;
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(exitCodeOnInvalidInput = 69,
|
||||
subcommands = {
|
||||
Armor.class,
|
||||
Dearmor.class,
|
||||
Decrypt.class,
|
||||
Encrypt.class,
|
||||
ExtractCert.class,
|
||||
GenerateKey.class,
|
||||
Sign.class,
|
||||
Verify.class,
|
||||
Version.class
|
||||
}
|
||||
)
|
||||
public class PGPainlessCLI implements Runnable {
|
||||
|
||||
public PGPainlessCLI() {
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int code = new CommandLine(new PGPainlessCLI())
|
||||
.execute(args);
|
||||
System.exit(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.util.ArmorUtils;
|
||||
|
||||
public class Print {
|
||||
|
||||
public static String toString(PGPSecretKeyRing keyRing, boolean armor) throws IOException {
|
||||
if (armor) {
|
||||
return ArmorUtils.toAsciiArmoredString(keyRing);
|
||||
} else {
|
||||
return new String(keyRing.getEncoded(), "UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
public static String toString(PGPPublicKeyRing keyRing, boolean armor) throws IOException {
|
||||
if (armor) {
|
||||
return ArmorUtils.toAsciiArmoredString(keyRing);
|
||||
} else {
|
||||
return new String(keyRing.getEncoded(), "UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
public static String toString(byte[] bytes, boolean armor) throws IOException {
|
||||
if (armor) {
|
||||
return ArmorUtils.toAsciiArmoredString(bytes);
|
||||
} else {
|
||||
return new String(bytes, "UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
public static void print_ln(String msg) {
|
||||
// CHECKSTYLE:OFF
|
||||
System.out.println(msg);
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
|
||||
public static void err_ln(String msg) {
|
||||
// CHECKSTYLE:OFF
|
||||
System.err.println(msg);
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
}
|
75
pgpainless-sop/src/main/java/org/pgpainless/sop/SOPImpl.java
Normal file
75
pgpainless-sop/src/main/java/org/pgpainless/sop/SOPImpl.java
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import sop.SOP;
|
||||
import sop.operation.Armor;
|
||||
import sop.operation.Dearmor;
|
||||
import sop.operation.Decrypt;
|
||||
import sop.operation.Encrypt;
|
||||
import sop.operation.ExtractCert;
|
||||
import sop.operation.GenerateKey;
|
||||
import sop.operation.Sign;
|
||||
import sop.operation.Verify;
|
||||
import sop.operation.Version;
|
||||
|
||||
public class SOPImpl implements SOP {
|
||||
|
||||
@Override
|
||||
public Version version() {
|
||||
return new VersionImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenerateKey generateKey() {
|
||||
return new GenerateKeyImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtractCert extractCert() {
|
||||
return new ExtractCertImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sign sign() {
|
||||
return new SignImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Verify verify() {
|
||||
return new VerifyImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Encrypt encrypt() {
|
||||
return new EncryptImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Decrypt decrypt() {
|
||||
return new DecryptImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Armor armor() {
|
||||
return new ArmorImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dearmor dearmor() {
|
||||
return new DearmorImpl();
|
||||
}
|
||||
}
|
128
pgpainless-sop/src/main/java/org/pgpainless/sop/SignImpl.java
Normal file
128
pgpainless-sop/src/main/java/org/pgpainless/sop/SignImpl.java
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.encryption_signing.EncryptionResult;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||
import sop.Ready;
|
||||
import sop.enums.SignAs;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Sign;
|
||||
|
||||
public class SignImpl implements Sign {
|
||||
|
||||
private boolean armor = true;
|
||||
private SignAs mode = SignAs.Binary;
|
||||
private List<PGPSecretKeyRing> keys = new ArrayList<>();
|
||||
private SigningOptions signingOptions = new SigningOptions();
|
||||
|
||||
@Override
|
||||
public Sign noArmor() {
|
||||
armor = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sign mode(SignAs mode) {
|
||||
this.mode = mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sign key(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
|
||||
try {
|
||||
PGPSecretKeyRing key = PGPainless.readKeyRing().secretKeyRing(keyIn);
|
||||
KeyRingInfo info = new KeyRingInfo(key);
|
||||
if (!info.isFullyDecrypted()) {
|
||||
throw new SOPGPException.KeyIsProtected();
|
||||
}
|
||||
signingOptions.addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), key, modeToSigType(mode));
|
||||
} catch (PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ready data(InputStream data) throws IOException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
try {
|
||||
EncryptionStream signingStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(buffer)
|
||||
.withOptions(ProducerOptions.sign(signingOptions)
|
||||
.setAsciiArmor(armor));
|
||||
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
|
||||
if (signingStream.isClosed()) {
|
||||
throw new IllegalStateException("EncryptionStream is already closed.");
|
||||
}
|
||||
|
||||
Streams.pipeAll(data, signingStream);
|
||||
signingStream.close();
|
||||
EncryptionResult encryptionResult = signingStream.getResult();
|
||||
|
||||
List<PGPSignature> signatures = new ArrayList<>();
|
||||
for (SubkeyIdentifier key : encryptionResult.getDetachedSignatures().keySet()) {
|
||||
signatures.addAll(encryptionResult.getDetachedSignatures().get(key));
|
||||
}
|
||||
|
||||
OutputStream out;
|
||||
if (armor) {
|
||||
out = ArmoredOutputStreamFactory.get(outputStream);
|
||||
} else {
|
||||
out = outputStream;
|
||||
}
|
||||
for (PGPSignature sig : signatures) {
|
||||
sig.encode(out);
|
||||
}
|
||||
out.close();
|
||||
outputStream.close(); // armor out does not close underlying stream
|
||||
}
|
||||
};
|
||||
|
||||
} catch (PGPException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static DocumentSignatureType modeToSigType(SignAs mode) {
|
||||
return mode == SignAs.Binary ? DocumentSignatureType.BINARY_DOCUMENT
|
||||
: DocumentSignatureType.CANONICAL_TEXT_DOCUMENT;
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.PGPainless;
|
||||
|
||||
public class SopKeyUtil {
|
||||
|
||||
public static List<PGPSecretKeyRing> loadKeysFromFiles(File... files) throws IOException, PGPException {
|
||||
List<PGPSecretKeyRing> secretKeyRings = new ArrayList<>();
|
||||
for (File file : files) {
|
||||
try (FileInputStream in = new FileInputStream(file)) {
|
||||
secretKeyRings.add(PGPainless.readKeyRing().secretKeyRing(in));
|
||||
} catch (PGPException | IOException e) {
|
||||
err_ln("Could not load secret key " + file.getName() + ": " + e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return secretKeyRings;
|
||||
}
|
||||
|
||||
public static List<PGPPublicKeyRing> loadCertificatesFromFile(File... files) throws IOException {
|
||||
List<PGPPublicKeyRing> publicKeyRings = new ArrayList<>();
|
||||
for (File file : files) {
|
||||
try (FileInputStream in = new FileInputStream(file)) {
|
||||
PGPPublicKeyRingCollection collection = PGPainless.readKeyRing()
|
||||
.keyRingCollection(in, true)
|
||||
.getPgpPublicKeyRingCollection();
|
||||
if (collection == null) {
|
||||
throw new PGPException("Provided file " + file.getName() + " does not contain a certificate.");
|
||||
}
|
||||
for (PGPPublicKeyRing keyRing : collection) {
|
||||
publicKeyRings.add(keyRing);
|
||||
}
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Could not read certificate from file " + file.getName() + ": " + e.getMessage());
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
return publicKeyRings;
|
||||
}
|
||||
}
|
124
pgpainless-sop/src/main/java/org/pgpainless/sop/VerifyImpl.java
Normal file
124
pgpainless-sop/src/main/java/org/pgpainless/sop/VerifyImpl.java
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.exception.NotYetImplementedException;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import sop.Verification;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Verify;
|
||||
|
||||
public class VerifyImpl implements Verify {
|
||||
|
||||
ConsumerOptions options = new ConsumerOptions();
|
||||
|
||||
@Override
|
||||
public Verify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption {
|
||||
try {
|
||||
options.verifyNotBefore(timestamp);
|
||||
} catch (NotYetImplementedException e) {
|
||||
// throw new SOPGPException.UnsupportedOption();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Verify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption {
|
||||
try {
|
||||
options.verifyNotAfter(timestamp);
|
||||
} catch (NotYetImplementedException e) {
|
||||
// throw new SOPGPException.UnsupportedOption();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Verify cert(InputStream cert) throws SOPGPException.BadData {
|
||||
PGPPublicKeyRingCollection certificates;
|
||||
try {
|
||||
certificates = PGPainless.readKeyRing().publicKeyRingCollection(cert);
|
||||
} catch (IOException | PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
options.addVerificationCerts(certificates);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VerifyImpl signatures(InputStream signatures) throws SOPGPException.BadData {
|
||||
try {
|
||||
options.addVerificationOfDetachedSignatures(signatures);
|
||||
} catch (IOException | PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Verification> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData {
|
||||
DecryptionStream decryptionStream;
|
||||
try {
|
||||
decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(data)
|
||||
.withOptions(options);
|
||||
|
||||
Streams.drain(decryptionStream);
|
||||
decryptionStream.close();
|
||||
|
||||
OpenPgpMetadata metadata = decryptionStream.getResult();
|
||||
List<Verification> verificationList = new ArrayList<>();
|
||||
|
||||
for (SubkeyIdentifier verifiedSigningKey : metadata.getVerifiedSignatures().keySet()) {
|
||||
PGPSignature signature = metadata.getVerifiedSignatures().get(verifiedSigningKey);
|
||||
Date verifyNotBefore = options.getVerifyNotBefore();
|
||||
Date verifyNotAfter = options.getVerifyNotAfter();
|
||||
|
||||
if (verifyNotAfter == null || !signature.getCreationTime().after(verifyNotAfter)) {
|
||||
if (verifyNotBefore == null || !signature.getCreationTime().before(verifyNotBefore)) {
|
||||
verificationList.add(new Verification(
|
||||
signature.getCreationTime(),
|
||||
verifiedSigningKey.getSubkeyFingerprint().toString(),
|
||||
verifiedSigningKey.getPrimaryKeyFingerprint().toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.getCertificates().isEmpty()) {
|
||||
if (verificationList.isEmpty()) {
|
||||
throw new SOPGPException.NoSignature();
|
||||
}
|
||||
}
|
||||
|
||||
return verificationList;
|
||||
} catch (PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,21 +13,21 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import static org.pgpainless.sop.Print.print_ln;
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import sop.operation.Version;
|
||||
|
||||
@CommandLine.Command(name = "version", description = "Display version information about the tool",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class Version implements Runnable {
|
||||
public class VersionImpl implements Version {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "PGPainless-SOP";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
public String getVersion() {
|
||||
// See https://stackoverflow.com/a/50119235
|
||||
String version;
|
||||
try {
|
||||
|
@ -37,6 +37,6 @@ public class Version implements Runnable {
|
|||
} catch (IOException e) {
|
||||
version = "DEVELOPMENT";
|
||||
}
|
||||
print_ln("PGPainlessCLI " + version);
|
||||
return version;
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PushbackInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
|
||||
@CommandLine.Command(name = "armor",
|
||||
description = "Add ASCII Armor to standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class Armor implements Runnable {
|
||||
|
||||
private static final byte[] BEGIN_ARMOR = "-----BEGIN PGP".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
private enum Label {
|
||||
auto,
|
||||
sig,
|
||||
key,
|
||||
cert,
|
||||
message
|
||||
}
|
||||
|
||||
@CommandLine.Option(names = {"--label"}, description = "Label to be used in the header and tail of the armoring.", paramLabel = "{auto|sig|key|cert|message}")
|
||||
Label label;
|
||||
|
||||
@CommandLine.Option(names = {"--allow-nested"}, description = "Allow additional armoring of already armored input")
|
||||
boolean allowNested = false;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
try (PushbackInputStream pbIn = new PushbackInputStream(System.in, BEGIN_ARMOR.length);
|
||||
ArmoredOutputStream armoredOutputStream = ArmoredOutputStreamFactory.get(System.out)) {
|
||||
|
||||
// take a peek
|
||||
byte[] firstBytes = new byte[BEGIN_ARMOR.length];
|
||||
int readByteCount = pbIn.read(firstBytes);
|
||||
if (readByteCount != -1) {
|
||||
pbIn.unread(firstBytes, 0, readByteCount);
|
||||
}
|
||||
|
||||
if (Arrays.equals(BEGIN_ARMOR, firstBytes) && !allowNested) {
|
||||
Streams.pipeAll(pbIn, System.out);
|
||||
} else {
|
||||
Streams.pipeAll(pbIn, armoredOutputStream);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
err_ln("Input data cannot be ASCII armored.");
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredInputStream;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
|
||||
@CommandLine.Command(name = "dearmor",
|
||||
description = "Remove ASCII Armor from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class Dearmor implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try (ArmoredInputStream in = new ArmoredInputStream(System.in, true)) {
|
||||
Streams.pipeAll(in, System.out);
|
||||
} catch (IOException e) {
|
||||
err_ln("Data cannot be dearmored.");
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
import static org.pgpainless.sop.SopKeyUtil.loadKeysFromFiles;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.sop.SopKeyUtil;
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(name = "decrypt",
|
||||
description = "Decrypt a message from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class Decrypt implements Runnable {
|
||||
|
||||
private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {"--session-key-out"},
|
||||
description = "Can be used to learn the session key on successful decryption",
|
||||
paramLabel = "SESSIONKEY")
|
||||
File sessionKeyOut;
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {"--with-session-key"},
|
||||
description = "Enables decryption of the \"CIPHERTEXT\" using the session key directly against the \"SEIPD\" packet",
|
||||
paramLabel = "SESSIONKEY")
|
||||
File[] withSessionKey;
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {"--with-password"},
|
||||
description = "Enables decryption based on any \"SKESK\" packets in the \"CIPHERTEXT\"",
|
||||
paramLabel = "PASSWORD")
|
||||
String[] withPassword;
|
||||
|
||||
@CommandLine.Option(names = {"--verify-out"},
|
||||
description = "Produces signature verification status to the designated file",
|
||||
paramLabel = "VERIFICATIONS")
|
||||
File verifyOut;
|
||||
|
||||
@CommandLine.Option(names = {"--verify-with"},
|
||||
description = "Certificates whose signatures would be acceptable for signatures over this message",
|
||||
paramLabel = "CERT")
|
||||
File[] certs;
|
||||
|
||||
@CommandLine.Option(names = {"--not-before"},
|
||||
description = "ISO-8601 formatted UTC date (eg. '2020-11-23T16:35Z)\n" +
|
||||
"Reject signatures with a creation date not in range.\n" +
|
||||
"Defaults to beginning of time (\"-\").",
|
||||
paramLabel = "DATE")
|
||||
String notBefore = "-";
|
||||
|
||||
@CommandLine.Option(names = {"--not-after"},
|
||||
description = "ISO-8601 formatted UTC date (eg. '2020-11-23T16:35Z)\n" +
|
||||
"Reject signatures with a creation date not in range.\n" +
|
||||
"Defaults to current system time (\"now\").\n" +
|
||||
"Accepts special value \"-\" for end of time.",
|
||||
paramLabel = "DATE")
|
||||
String notAfter = "now";
|
||||
|
||||
@CommandLine.Parameters(index = "0..*",
|
||||
description = "Secret keys to attempt decryption with",
|
||||
paramLabel = "KEY")
|
||||
File[] keys;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (verifyOut == null ^ certs == null) {
|
||||
err_ln("To enable signature verification, both --verify-out and at least one --verify-with argument must be supplied.");
|
||||
System.exit(23);
|
||||
}
|
||||
|
||||
if (sessionKeyOut != null || withSessionKey != null) {
|
||||
err_ln("session key in and out are not yet supported.");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
ConsumerOptions options = new ConsumerOptions();
|
||||
|
||||
List<PGPPublicKeyRing> verifyWith = null;
|
||||
try {
|
||||
|
||||
List<PGPSecretKeyRing> secretKeyRings = loadKeysFromFiles(keys);
|
||||
for (PGPSecretKeyRing secretKey : secretKeyRings) {
|
||||
options.addDecryptionKey(secretKey);
|
||||
}
|
||||
|
||||
if (certs != null) {
|
||||
verifyWith = SopKeyUtil.loadCertificatesFromFile(certs);
|
||||
for (PGPPublicKeyRing cert : verifyWith) {
|
||||
options.addVerificationCert(cert);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
DecryptionStream decryptionStream;
|
||||
try {
|
||||
decryptionStream = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(System.in)
|
||||
.withOptions(options);
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Error constructing decryption stream: " + e.getMessage());
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Streams.pipeAll(decryptionStream, System.out);
|
||||
System.out.flush();
|
||||
decryptionStream.close();
|
||||
} catch (IOException e) {
|
||||
err_ln("Unable to decrypt: " + e.getMessage());
|
||||
System.exit(29);
|
||||
}
|
||||
if (verifyOut == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
OpenPgpMetadata metadata = decryptionStream.getResult();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (verifyWith != null) {
|
||||
for (SubkeyIdentifier signingKey : metadata.getVerifiedSignatures().keySet()) {
|
||||
PGPSignature signature = metadata.getVerifiedSignatures().get(signingKey);
|
||||
sb.append(df.format(signature.getCreationTime())).append(' ')
|
||||
.append(signingKey.getSubkeyFingerprint()).append(' ')
|
||||
.append(signingKey.getPrimaryKeyFingerprint()).append('\n');
|
||||
}
|
||||
|
||||
try {
|
||||
verifyOut.createNewFile();
|
||||
PrintStream verifyPrinter = new PrintStream(new FileOutputStream(verifyOut));
|
||||
// CHECKSTYLE:OFF
|
||||
verifyPrinter.println(sb);
|
||||
// CHECKSTYLE:ON
|
||||
verifyPrinter.close();
|
||||
} catch (IOException e) {
|
||||
err_ln("Error writing verifications file: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.encryption_signing.EncryptionOptions;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.sop.SopKeyUtil;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(name = "encrypt",
|
||||
description = "Encrypt a message from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class Encrypt implements Runnable {
|
||||
|
||||
public enum Type {
|
||||
binary,
|
||||
text,
|
||||
mime
|
||||
}
|
||||
|
||||
@CommandLine.Option(names = "--no-armor",
|
||||
description = "ASCII armor the output",
|
||||
negatable = true)
|
||||
boolean armor = true;
|
||||
|
||||
@CommandLine.Option(names = {"--as"},
|
||||
description = "Type of the input data. Defaults to 'binary'",
|
||||
paramLabel = "{binary|text|mime}")
|
||||
Type type;
|
||||
|
||||
@CommandLine.Option(names = "--with-password",
|
||||
description = "Encrypt the message with a password",
|
||||
paramLabel = "PASSWORD")
|
||||
String[] withPassword = new String[0];
|
||||
|
||||
@CommandLine.Option(names = "--sign-with",
|
||||
description = "Sign the output with a private key",
|
||||
paramLabel = "KEY")
|
||||
File[] signWith = new File[0];
|
||||
|
||||
@CommandLine.Parameters(description = "Certificates the message gets encrypted to",
|
||||
index = "0..*",
|
||||
paramLabel = "CERTS")
|
||||
File[] certs = new File[0];
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (certs.length == 0 && withPassword.length == 0) {
|
||||
err_ln("Please either provide --with-password or at least one CERT");
|
||||
System.exit(19);
|
||||
}
|
||||
|
||||
EncryptionOptions encOpt = new EncryptionOptions();
|
||||
SigningOptions signOpt = new SigningOptions();
|
||||
|
||||
try {
|
||||
List<PGPPublicKeyRing> encryptionKeys = SopKeyUtil.loadCertificatesFromFile(certs);
|
||||
for (PGPPublicKeyRing key : encryptionKeys) {
|
||||
encOpt.addRecipient(key);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
for (String s : withPassword) {
|
||||
Passphrase passphrase = Passphrase.fromPassword(s);
|
||||
encOpt.addPassphrase(passphrase);
|
||||
}
|
||||
|
||||
SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys();
|
||||
|
||||
for (int i = 0; i < signWith.length; i++) {
|
||||
try (FileInputStream fileIn = new FileInputStream(signWith[i])) {
|
||||
PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(fileIn);
|
||||
signOpt.addInlineSignature(protector, secretKey, parseType(type));
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Cannot read secret key from file " + signWith[i].getName());
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(System.out)
|
||||
.withOptions(ProducerOptions
|
||||
.signAndEncrypt(encOpt, signOpt)
|
||||
.setAsciiArmor(armor));
|
||||
|
||||
Streams.pipeAll(System.in, encryptionStream);
|
||||
|
||||
encryptionStream.close();
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("An error happened.");
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static DocumentSignatureType parseType(Type type) {
|
||||
return type == Type.binary ? DocumentSignatureType.BINARY_DOCUMENT : DocumentSignatureType.CANONICAL_TEXT_DOCUMENT;
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
import static org.pgpainless.sop.Print.print_ln;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.util.KeyRingUtils;
|
||||
import org.pgpainless.sop.Print;
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(name = "extract-cert",
|
||||
description = "Extract a public key certificate from a secret key from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class ExtractCert implements Runnable {
|
||||
|
||||
@CommandLine.Option(names = "--no-armor",
|
||||
description = "ASCII armor the output",
|
||||
negatable = true)
|
||||
boolean armor = true;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(System.in);
|
||||
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
|
||||
|
||||
print_ln(Print.toString(publicKeys, armor));
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Error extracting certificate from keys;");
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.key.generation.KeyRingBuilderInterface;
|
||||
import org.pgpainless.key.generation.KeySpec;
|
||||
import org.pgpainless.key.generation.type.KeyType;
|
||||
import org.pgpainless.key.generation.type.eddsa.EdDSACurve;
|
||||
import org.pgpainless.key.generation.type.xdh.XDHSpec;
|
||||
import org.pgpainless.sop.Print;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
import static org.pgpainless.sop.Print.print_ln;
|
||||
|
||||
@CommandLine.Command(name = "generate-key",
|
||||
description = "Generate a secret key",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class GenerateKey implements Runnable {
|
||||
|
||||
@CommandLine.Option(names = "--no-armor",
|
||||
description = "ASCII armor the output",
|
||||
negatable = true)
|
||||
boolean armor = true;
|
||||
|
||||
@CommandLine.Parameters(description = "User-ID, eg. \"Alice <alice@example.com>\"")
|
||||
List<String> userId;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (userId.isEmpty()) {
|
||||
print_ln("At least one user-id expected.");
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
KeyRingBuilderInterface.WithAdditionalUserIdOrPassphrase builder = PGPainless.generateKeyRing()
|
||||
.withSubKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519))
|
||||
.withKeyFlags(KeyFlag.SIGN_DATA)
|
||||
.withDefaultAlgorithms())
|
||||
.withSubKey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519))
|
||||
.withKeyFlags(KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)
|
||||
.withDefaultAlgorithms())
|
||||
.withPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519))
|
||||
.withKeyFlags(KeyFlag.CERTIFY_OTHER)
|
||||
.withDefaultAlgorithms())
|
||||
.withPrimaryUserId(userId.get(0));
|
||||
|
||||
for (int i = 1; i < userId.size(); i++) {
|
||||
builder.withAdditionalUserId(userId.get(i));
|
||||
}
|
||||
|
||||
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
calendar.add(Calendar.YEAR, 3);
|
||||
Date expiration = calendar.getTime();
|
||||
|
||||
PGPSecretKeyRing secretKeys = builder.setExpirationDate(expiration)
|
||||
.withoutPassphrase()
|
||||
.build();
|
||||
|
||||
print_ln(Print.toString(secretKeys, armor));
|
||||
|
||||
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | PGPException | IOException e) {
|
||||
err_ln("Error creating OpenPGP key:");
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.encryption_signing.EncryptionResult;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.sop.Print;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
import static org.pgpainless.sop.Print.print_ln;
|
||||
|
||||
@CommandLine.Command(name = "sign",
|
||||
description = "Create a detached signature on the data from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class Sign implements Runnable {
|
||||
|
||||
public enum Type {
|
||||
binary,
|
||||
text
|
||||
}
|
||||
|
||||
@CommandLine.Option(names = "--no-armor",
|
||||
description = "ASCII armor the output",
|
||||
negatable = true)
|
||||
boolean armor = true;
|
||||
|
||||
@CommandLine.Option(names = "--as", description = "Defaults to 'binary'. If '--as=text' and the input data is not valid UTF-8, sign fails with return code 53.",
|
||||
paramLabel = "{binary|text}")
|
||||
Type type;
|
||||
|
||||
@CommandLine.Parameters(description = "Secret keys used for signing",
|
||||
paramLabel = "KEY",
|
||||
arity = "1..*")
|
||||
File[] secretKeyFile;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
PGPSecretKeyRing[] secretKeys = new PGPSecretKeyRing[secretKeyFile.length];
|
||||
for (int i = 0, secretKeyFileLength = secretKeyFile.length; i < secretKeyFileLength; i++) {
|
||||
File file = secretKeyFile[i];
|
||||
try {
|
||||
PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(new FileInputStream(file));
|
||||
secretKeys[i] = secretKey;
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Error reading secret key ring " + file.getName());
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
SigningOptions signOpt = new SigningOptions();
|
||||
for (PGPSecretKeyRing signingKey : secretKeys) {
|
||||
signOpt.addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), signingKey,
|
||||
type == Type.text ? DocumentSignatureType.CANONICAL_TEXT_DOCUMENT : DocumentSignatureType.BINARY_DOCUMENT);
|
||||
}
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(out)
|
||||
.withOptions(ProducerOptions
|
||||
.sign(signOpt)
|
||||
.setAsciiArmor(armor));
|
||||
|
||||
Streams.pipeAll(System.in, encryptionStream);
|
||||
encryptionStream.close();
|
||||
|
||||
EncryptionResult result = encryptionStream.getResult();
|
||||
for (SubkeyIdentifier signingKey : result.getDetachedSignatures().keySet()) {
|
||||
for (PGPSignature signature : result.getDetachedSignatures().get(signingKey)) {
|
||||
print_ln(Print.toString(signature.getEncoded(), armor));
|
||||
}
|
||||
}
|
||||
} catch (PGPException | IOException e) {
|
||||
err_ln("Error signing data.");
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,205 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import static org.pgpainless.sop.Print.err_ln;
|
||||
import static org.pgpainless.sop.Print.print_ln;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(name = "verify",
|
||||
description = "Verify a detached signature over the data from standard input",
|
||||
exitCodeOnInvalidInput = 37)
|
||||
public class Verify implements Runnable {
|
||||
|
||||
private static final TimeZone tz = TimeZone.getTimeZone("UTC");
|
||||
private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
|
||||
|
||||
private static final Date beginningOfTime = new Date(0);
|
||||
private static final Date endOfTime = new Date(8640000000000000L);
|
||||
|
||||
static {
|
||||
df.setTimeZone(tz);
|
||||
}
|
||||
|
||||
@CommandLine.Parameters(index = "0",
|
||||
description = "Detached signature",
|
||||
paramLabel = "SIGNATURE")
|
||||
File signature;
|
||||
|
||||
@CommandLine.Parameters(index = "1..*",
|
||||
arity = "1..*",
|
||||
description = "Public key certificates",
|
||||
paramLabel = "CERT")
|
||||
File[] certificates;
|
||||
|
||||
@CommandLine.Option(names = {"--not-before"},
|
||||
description = "ISO-8601 formatted UTC date (eg. '2020-11-23T16:35Z)\n" +
|
||||
"Reject signatures with a creation date not in range.\n" +
|
||||
"Defaults to beginning of time (\"-\").",
|
||||
paramLabel = "DATE")
|
||||
String notBefore = "-";
|
||||
|
||||
@CommandLine.Option(names = {"--not-after"},
|
||||
description = "ISO-8601 formatted UTC date (eg. '2020-11-23T16:35Z)\n" +
|
||||
"Reject signatures with a creation date not in range.\n" +
|
||||
"Defaults to current system time (\"now\").\n" +
|
||||
"Accepts special value \"-\" for end of time.",
|
||||
paramLabel = "DATE")
|
||||
String notAfter = "now";
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Date notBeforeDate = parseNotBefore();
|
||||
Date notAfterDate = parseNotAfter();
|
||||
|
||||
ConsumerOptions options = new ConsumerOptions();
|
||||
try (FileInputStream sigIn = new FileInputStream(signature)) {
|
||||
options.addVerificationOfDetachedSignatures(sigIn);
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Cannot read detached signature: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
Map<PGPPublicKeyRing, File> publicKeys = readCertificatesFromFiles();
|
||||
if (publicKeys.isEmpty()) {
|
||||
err_ln("No certificates supplied.");
|
||||
System.exit(19);
|
||||
}
|
||||
|
||||
for (PGPPublicKeyRing cert : publicKeys.keySet()) {
|
||||
options.addVerificationCert(cert);
|
||||
}
|
||||
|
||||
OpenPgpMetadata metadata;
|
||||
try {
|
||||
DecryptionStream verifier = PGPainless.decryptAndOrVerify()
|
||||
.onInputStream(System.in)
|
||||
.withOptions(options);
|
||||
|
||||
OutputStream out = new NullOutputStream();
|
||||
Streams.pipeAll(verifier, out);
|
||||
verifier.close();
|
||||
|
||||
metadata = verifier.getResult();
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Signature validation failed.");
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
Map<SubkeyIdentifier, PGPSignature> signaturesInTimeRange = new HashMap<>();
|
||||
for (SubkeyIdentifier signingKey : metadata.getVerifiedSignatures().keySet()) {
|
||||
PGPSignature signature = metadata.getVerifiedSignatures().get(signingKey);
|
||||
Date creationTime = signature.getCreationTime();
|
||||
if (!creationTime.before(notBeforeDate) && !creationTime.after(notAfterDate)) {
|
||||
signaturesInTimeRange.put(signingKey, signature);
|
||||
}
|
||||
}
|
||||
|
||||
if (signaturesInTimeRange.isEmpty()) {
|
||||
err_ln("No valid signatures found.");
|
||||
System.exit(3);
|
||||
}
|
||||
|
||||
printValidSignatures(signaturesInTimeRange, publicKeys);
|
||||
}
|
||||
|
||||
private void printValidSignatures(Map<SubkeyIdentifier, PGPSignature> validSignatures, Map<PGPPublicKeyRing, File> publicKeys) {
|
||||
for (SubkeyIdentifier signingKey : validSignatures.keySet()) {
|
||||
PGPSignature signature = validSignatures.get(signingKey);
|
||||
|
||||
for (PGPPublicKeyRing ring : publicKeys.keySet()) {
|
||||
// Search signing key ring
|
||||
File file = publicKeys.get(ring);
|
||||
if (ring.getPublicKey(signingKey.getKeyId()) == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String utcSigDate = df.format(signature.getCreationTime());
|
||||
print_ln(utcSigDate + " " + signingKey.getSubkeyFingerprint() + " " + signingKey.getPrimaryKeyFingerprint() +
|
||||
" signed by " + file.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<PGPPublicKeyRing, File> readCertificatesFromFiles() {
|
||||
Map<PGPPublicKeyRing, File> publicKeys = new HashMap<>();
|
||||
for (File cert : certificates) {
|
||||
try (FileInputStream in = new FileInputStream(cert)) {
|
||||
PGPPublicKeyRingCollection collection = PGPainless.readKeyRing().publicKeyRingCollection(in);
|
||||
for (PGPPublicKeyRing ring : collection) {
|
||||
publicKeys.put(ring, cert);
|
||||
}
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Cannot read certificate from file " + cert.getAbsolutePath() + ":");
|
||||
err_ln(e.getMessage());
|
||||
}
|
||||
}
|
||||
return publicKeys;
|
||||
}
|
||||
|
||||
private Date parseNotAfter() {
|
||||
try {
|
||||
return notAfter.equals("now") ? new Date() : notAfter.equals("-") ? endOfTime : df.parse(notAfter);
|
||||
} catch (ParseException e) {
|
||||
err_ln("Invalid date string supplied as value of --not-after.");
|
||||
System.exit(1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Date parseNotBefore() {
|
||||
try {
|
||||
return notBefore.equals("now") ? new Date() : notBefore.equals("-") ? beginningOfTime : df.parse(notBefore);
|
||||
} catch (ParseException e) {
|
||||
err_ln("Invalid date string supplied as value of --not-before.");
|
||||
System.exit(1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class NullOutputStream extends OutputStream {
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
// Nope
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Subcommands of the PGPainless SOP.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2020 Paul Schaub.
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,9 +14,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* PGPainless SOP implementing a Stateless OpenPGP Command Line Interface.
|
||||
* @see <a href="https://tools.ietf.org/html/draft-dkg-openpgp-stateless-cli-01">
|
||||
* Stateless OpenPGP Command Line Interface
|
||||
* draft-dkg-openpgp-stateless-cli-01</a>
|
||||
* Implementation of the java-sop package using pgpainless-core.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class ExitCodeTest {
|
||||
|
||||
@Test
|
||||
public void testUnknownCommand_69() {
|
||||
assertEquals(69, new CommandLine(new PGPainlessCLI()).execute("generate-kex"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandWithUnknownOption_37() {
|
||||
assertEquals(37, new CommandLine(new PGPainlessCLI()).execute("generate-key", "-k", "\"k is unknown\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successfulVersion_0 () {
|
||||
assertEquals(0, new CommandLine(new PGPainlessCLI()).execute("version"));
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Random;
|
||||
|
||||
public class TestUtils {
|
||||
|
||||
public static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
public static final String ARMOR_PRIVATE_KEY_HEADER = "-----BEGIN PGP PRIVATE KEY BLOCK-----";
|
||||
public static final byte[] ARMOR_PRIVATE_KEY_HEADER_BYTES =
|
||||
ARMOR_PRIVATE_KEY_HEADER.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
public static File createTempDirectory() throws IOException {
|
||||
String name = randomString(10);
|
||||
File dir = Files.createTempDirectory(name).toFile();
|
||||
// dir.deleteOnExit();
|
||||
return dir;
|
||||
}
|
||||
|
||||
private static String randomString(int length) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append(ALPHABET.charAt(RANDOM.nextInt(ALPHABET.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -15,13 +15,14 @@
|
|||
*/
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import picocli.CommandLine;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class DummyTest {
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class VersionTest {
|
||||
|
||||
@Test
|
||||
public void dummyTest() {
|
||||
new CommandLine(new PGPainlessCLI()).execute("generate-key", "Ed Snowden <citizen4@lavabit.com>");
|
||||
public void assertNameEqualsPGPainless() {
|
||||
assertEquals("PGPainless-SOP", new SOPImpl().version().getName());
|
||||
}
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.sop.PGPainlessCLI;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class ArmorTest {
|
||||
|
||||
private static PrintStream originalSout;
|
||||
|
||||
@BeforeEach
|
||||
public void saveSout() {
|
||||
originalSout = System.out;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void restoreSout() {
|
||||
System.setOut(originalSout);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void armorSecretKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice@pgpainless.org", null);
|
||||
byte[] bytes = secretKey.getEncoded();
|
||||
|
||||
System.setIn(new ByteArrayInputStream(bytes));
|
||||
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(armorOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("armor");
|
||||
|
||||
PGPSecretKeyRing armored = PGPainless.readKeyRing().secretKeyRing(armorOut.toString());
|
||||
assertArrayEquals(secretKey.getEncoded(), armored.getEncoded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void armorPublicKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice@pgpainless.org", null);
|
||||
PGPPublicKeyRing publicKey = PGPainless.extractCertificate(secretKey);
|
||||
byte[] bytes = publicKey.getEncoded();
|
||||
|
||||
System.setIn(new ByteArrayInputStream(bytes));
|
||||
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(armorOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("armor");
|
||||
|
||||
PGPPublicKeyRing armored = PGPainless.readKeyRing().publicKeyRing(armorOut.toString());
|
||||
assertArrayEquals(publicKey.getEncoded(), armored.getEncoded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void armorMessage() {
|
||||
String message = "Hello, World!\n";
|
||||
|
||||
System.setIn(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(armorOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("armor");
|
||||
|
||||
String armored = armorOut.toString();
|
||||
|
||||
assertTrue(armored.startsWith("-----BEGIN PGP MESSAGE-----\n"));
|
||||
assertTrue(armored.contains("SGVsbG8sIFdvcmxkIQo="));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotNestArmorByDefault() {
|
||||
String armored = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.69\n" +
|
||||
"\n" +
|
||||
"SGVsbG8sIFdvcmxkCg==\n" +
|
||||
"=fkLo\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("armor");
|
||||
|
||||
assertEquals(armored, out.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllowNested() {
|
||||
String armored = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.69\n" +
|
||||
"\n" +
|
||||
"SGVsbG8sIFdvcmxkCg==\n" +
|
||||
"=fkLo\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("armor", "--allow-nested");
|
||||
|
||||
assertNotEquals(armored, out.toString());
|
||||
assertTrue(out.toString().contains(
|
||||
"LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tClZlcnNpb246IEJDUEcgdjEuNjkK\n" +
|
||||
"ClNHVnNiRzhzSUZkdmNteGtDZz09Cj1ma0xvCi0tLS0tRU5EIFBHUCBNRVNTQUdF\n" +
|
||||
"LS0tLS0="));
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.sop.PGPainlessCLI;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class DearmorTest {
|
||||
|
||||
private PrintStream originalSout;
|
||||
|
||||
@BeforeEach
|
||||
public void saveSout() {
|
||||
this.originalSout = System.out;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void restoreSout() {
|
||||
System.setOut(originalSout);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dearmorSecretKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice@pgpainless.org", null);
|
||||
String armored = PGPainless.asciiArmor(secretKey);
|
||||
|
||||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("dearmor");
|
||||
|
||||
assertArrayEquals(secretKey.getEncoded(), out.toByteArray());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void dearmorCertificate() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice@pgpainless.org", null);
|
||||
PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKey);
|
||||
String armored = PGPainless.asciiArmor(certificate);
|
||||
|
||||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("dearmor");
|
||||
|
||||
assertArrayEquals(certificate.getEncoded(), out.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dearmorMessage() {
|
||||
String armored = "-----BEGIN PGP MESSAGE-----\n" +
|
||||
"Version: BCPG v1.69\n" +
|
||||
"\n" +
|
||||
"SGVsbG8sIFdvcmxkCg==\n" +
|
||||
"=fkLo\n" +
|
||||
"-----END PGP MESSAGE-----";
|
||||
|
||||
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("dearmor");
|
||||
|
||||
assertEquals("Hello, World\n", out.toString());
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.sop.PGPainlessCLI;
|
||||
import org.pgpainless.sop.TestUtils;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class EncryptDecryptTest {
|
||||
|
||||
private static File tempDir;
|
||||
private static PrintStream originalSout;
|
||||
|
||||
@BeforeAll
|
||||
public static void prepare() throws IOException {
|
||||
tempDir = TestUtils.createTempDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() throws IOException {
|
||||
originalSout = System.out;
|
||||
File julietKeyFile = new File(tempDir, "juliet.key");
|
||||
assertTrue(julietKeyFile.createNewFile());
|
||||
|
||||
File julietCertFile = new File(tempDir, "juliet.asc");
|
||||
assertTrue(julietCertFile.createNewFile());
|
||||
|
||||
File romeoKeyFile = new File(tempDir, "romeo.key");
|
||||
assertTrue(romeoKeyFile.createNewFile());
|
||||
|
||||
File romeoCertFile = new File(tempDir, "romeo.asc");
|
||||
assertTrue(romeoCertFile.createNewFile());
|
||||
|
||||
File msgAscFile = new File(tempDir, "msg.asc");
|
||||
assertTrue(msgAscFile.createNewFile());
|
||||
|
||||
OutputStream julietKeyOut = new FileOutputStream(julietKeyFile);
|
||||
System.setOut(new PrintStream(julietKeyOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("generate-key", "Juliet Capulet <juliet@capulet.lit>");
|
||||
julietKeyOut.close();
|
||||
|
||||
FileInputStream julietKeyIn = new FileInputStream(julietKeyFile);
|
||||
System.setIn(julietKeyIn);
|
||||
OutputStream julietCertOut = new FileOutputStream(julietCertFile);
|
||||
System.setOut(new PrintStream(julietCertOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("extract-cert");
|
||||
julietKeyIn.close();
|
||||
julietCertOut.close();
|
||||
|
||||
OutputStream romeoKeyOut = new FileOutputStream(romeoKeyFile);
|
||||
System.setOut(new PrintStream(romeoKeyOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("generate-key", "Romeo Montague <romeo@montague.lit>");
|
||||
romeoKeyOut.close();
|
||||
|
||||
FileInputStream romeoKeyIn = new FileInputStream(romeoKeyFile);
|
||||
System.setIn(romeoKeyIn);
|
||||
OutputStream romeoCertOut = new FileOutputStream(romeoCertFile);
|
||||
System.setOut(new PrintStream(romeoCertOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("extract-cert");
|
||||
romeoKeyIn.close();
|
||||
romeoCertOut.close();
|
||||
|
||||
String msg = "Hello World!\n";
|
||||
ByteArrayInputStream msgIn = new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8));
|
||||
System.setIn(msgIn);
|
||||
OutputStream msgAscOut = new FileOutputStream(msgAscFile);
|
||||
System.setOut(new PrintStream(msgAscOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("encrypt",
|
||||
"--sign-with", romeoKeyFile.getAbsolutePath(),
|
||||
julietCertFile.getAbsolutePath());
|
||||
msgAscOut.close();
|
||||
|
||||
File verifyFile = new File(tempDir, "verify.txt");
|
||||
assertTrue(verifyFile.createNewFile());
|
||||
|
||||
FileInputStream msgAscIn = new FileInputStream(msgAscFile);
|
||||
System.setIn(msgAscIn);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
PrintStream pOut = new PrintStream(out);
|
||||
System.setOut(pOut);
|
||||
new CommandLine(new PGPainlessCLI()).execute("decrypt",
|
||||
"--verify-out", verifyFile.getAbsolutePath(),
|
||||
"--verify-with", romeoCertFile.getAbsolutePath(),
|
||||
julietKeyFile.getAbsolutePath());
|
||||
msgAscIn.close();
|
||||
|
||||
assertEquals(msg, out.toString());
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void after() {
|
||||
System.setOut(originalSout);
|
||||
// CHECKSTYLE:OFF
|
||||
System.out.println(tempDir.getAbsolutePath());
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.sop.PGPainlessCLI;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class ExtractCertTest {
|
||||
|
||||
@Test
|
||||
public void testExtractCert() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
|
||||
.simpleEcKeyRing("Juliet Capulet <juliet@capulet.lit>");
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(secretKeys.getEncoded());
|
||||
System.setIn(inputStream);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
|
||||
new CommandLine(new PGPainlessCLI()).execute("extract-cert");
|
||||
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(out.toByteArray());
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(publicKeys);
|
||||
assertFalse(info.isSecretKey());
|
||||
assertTrue(info.isUserIdValid("Juliet Capulet <juliet@capulet.lit>"));
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.pgpainless.sop.TestUtils.ARMOR_PRIVATE_KEY_HEADER_BYTES;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.sop.PGPainlessCLI;
|
||||
import org.pgpainless.sop.TestUtils;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class GenerateCertTest {
|
||||
|
||||
private static File tempDir;
|
||||
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws IOException {
|
||||
tempDir = TestUtils.createTempDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyGeneration() throws IOException, PGPException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("generate-key", "--armor", "Juliet Capulet <juliet@capulet.lit>");
|
||||
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(out.toByteArray());
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
||||
assertTrue(info.isUserIdValid("Juliet Capulet <juliet@capulet.lit>"));
|
||||
|
||||
byte[] outBegin = new byte[37];
|
||||
System.arraycopy(out.toByteArray(), 0, outBegin, 0, 37);
|
||||
assertArrayEquals(outBegin, ARMOR_PRIVATE_KEY_HEADER_BYTES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoArmor() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
new CommandLine(new PGPainlessCLI()).execute("generate-key", "--no-armor", "Test <test@test.test>");
|
||||
|
||||
byte[] outBegin = new byte[37];
|
||||
System.arraycopy(out.toByteArray(), 0, outBegin, 0, 37);
|
||||
assertFalse(Arrays.equals(outBegin, ARMOR_PRIVATE_KEY_HEADER_BYTES));
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.sop.commands;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.util.KeyRingUtils;
|
||||
import org.pgpainless.sop.PGPainlessCLI;
|
||||
import org.pgpainless.sop.TestUtils;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class SignVerifyTest {
|
||||
|
||||
private static File tempDir;
|
||||
private static PrintStream originalSout;
|
||||
|
||||
private final String data = "If privacy is outlawed, only outlaws will have privacy.\n";
|
||||
|
||||
@BeforeAll
|
||||
public static void prepare() throws IOException {
|
||||
tempDir = TestUtils.createTempDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignatureCreationAndVerification() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
originalSout = System.out;
|
||||
InputStream originalIn = System.in;
|
||||
|
||||
// Write alice key to disc
|
||||
File aliceKeyFile = new File(tempDir, "alice.key");
|
||||
assertTrue(aliceKeyFile.createNewFile());
|
||||
PGPSecretKeyRing aliceKeys = PGPainless.generateKeyRing()
|
||||
.modernKeyRing("alice", null);
|
||||
OutputStream aliceKeyOut = new FileOutputStream(aliceKeyFile);
|
||||
Streams.pipeAll(new ByteArrayInputStream(aliceKeys.getEncoded()), aliceKeyOut);
|
||||
aliceKeyOut.close();
|
||||
|
||||
// Write alice pub key to disc
|
||||
File aliceCertFile = new File(tempDir, "alice.pub");
|
||||
assertTrue(aliceCertFile.createNewFile());
|
||||
PGPPublicKeyRing alicePub = KeyRingUtils.publicKeyRingFrom(aliceKeys);
|
||||
OutputStream aliceCertOut = new FileOutputStream(aliceCertFile);
|
||||
Streams.pipeAll(new ByteArrayInputStream(alicePub.getEncoded()), aliceCertOut);
|
||||
aliceCertOut.close();
|
||||
|
||||
// Write test data to disc
|
||||
File dataFile = new File(tempDir, "data");
|
||||
assertTrue(dataFile.createNewFile());
|
||||
FileOutputStream dataOut = new FileOutputStream(dataFile);
|
||||
Streams.pipeAll(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), dataOut);
|
||||
dataOut.close();
|
||||
|
||||
// Sign test data
|
||||
FileInputStream dataIn = new FileInputStream(dataFile);
|
||||
System.setIn(dataIn);
|
||||
File sigFile = new File(tempDir, "sig.asc");
|
||||
assertTrue(sigFile.createNewFile());
|
||||
FileOutputStream sigOut = new FileOutputStream(sigFile);
|
||||
System.setOut(new PrintStream(sigOut));
|
||||
new CommandLine(new PGPainlessCLI()).execute("sign", "--armor", aliceKeyFile.getAbsolutePath());
|
||||
sigOut.close();
|
||||
|
||||
// verify test data signature
|
||||
ByteArrayOutputStream verifyOut = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(verifyOut));
|
||||
dataIn = new FileInputStream(dataFile);
|
||||
System.setIn(dataIn);
|
||||
new CommandLine(new PGPainlessCLI()).execute("verify", sigFile.getAbsolutePath(), aliceCertFile.getAbsolutePath());
|
||||
dataIn.close();
|
||||
|
||||
// Test verification output
|
||||
|
||||
// [date] [signing-key-fp] [primary-key-fp] signed by [key.pub]
|
||||
String verification = verifyOut.toString();
|
||||
String[] split = verification.split(" ");
|
||||
OpenPgpV4Fingerprint primaryKeyFingerprint = new OpenPgpV4Fingerprint(aliceKeys);
|
||||
OpenPgpV4Fingerprint signingKeyFingerprint = new OpenPgpV4Fingerprint(new KeyRingInfo(alicePub, new Date()).getSigningSubkeys().get(0));
|
||||
assertEquals(signingKeyFingerprint.toString(), split[1]);
|
||||
assertEquals(primaryKeyFingerprint.toString(), split[2]);
|
||||
|
||||
System.setIn(originalIn);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void after() {
|
||||
System.setOut(originalSout);
|
||||
// CHECKSTYLE:OFF
|
||||
System.out.println(tempDir.getAbsolutePath());
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue