diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/KeyRingValidator.java b/pgpainless-core/src/main/java/org/pgpainless/key/KeyRingValidator.java index 2a8f0ac3..fa0984f6 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/KeyRingValidator.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/KeyRingValidator.java @@ -112,7 +112,7 @@ public class KeyRingValidator { } } } catch (SignatureValidationException e) { - LOGGER.log(Level.INFO, "Rejecting user-id certification for user-id " + userId, e); + LOGGER.log(Level.FINE, "Rejecting user-id certification for user-id " + userId, e); } } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java index e425815f..d9d98bd5 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java @@ -269,7 +269,11 @@ public class KeyRingInfo { public String getPrimaryUserId() { String primaryUserId = null; Date modificationDate = null; - for (String userId : getValidUserIds()) { + List validUserIds = getValidUserIds(); + if (validUserIds.isEmpty()) { + return null; + } + for (String userId : validUserIds) { PGPSignature signature = signatures.userIdCertifications.get(userId); PrimaryUserID subpacket = SignatureSubpacketsUtil.getPrimaryUserId(signature); if (subpacket != null && subpacket.isPrimaryUserID()) { @@ -282,7 +286,7 @@ public class KeyRingInfo { } // Workaround for keys with only one user-id but no primary user-id packet. if (primaryUserId == null) { - return getValidUserIds().get(0); + return validUserIds.get(0); } return primaryUserId; diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureChainValidator.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureChainValidator.java index 3751e3fd..81f92656 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureChainValidator.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureChainValidator.java @@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; +import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.sig.SignerUserID; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; @@ -123,7 +124,7 @@ public class SignatureChainValidator { } } catch (SignatureValidationException e) { rejections.put(userIdSig, e); - LOGGER.log(Level.INFO, "Rejecting user-id signature.", e); + LOGGER.log(Level.FINE, "Rejecting user-id signature.", e); } } Collections.sort(signaturesOnUserId, new SignatureValidityComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD)); @@ -200,8 +201,18 @@ public class SignatureChainValidator { throw new SignatureValidationException("Subkey is revoked."); } - if (!KeyFlag.hasKeyFlag(SignatureSubpacketsUtil.getKeyFlags(currentSig).getFlags(), KeyFlag.SIGN_DATA)) { - throw new SignatureValidationException("Signature was made by key which is not capable of signing."); + KeyFlags keyFlags = SignatureSubpacketsUtil.getKeyFlags(currentSig); + if (keyFlags == null) { + if (directKeySignatures.isEmpty()) { + throw new SignatureValidationException("Signature was made by key which is not capable of signing (no keyflags on binding sig, no direct-key sig)."); + } + PGPSignature directKeySig = directKeySignatures.get(0); + KeyFlags directKeyFlags = SignatureSubpacketsUtil.getKeyFlags(directKeySig); + if (!KeyFlag.hasKeyFlag(directKeyFlags.getFlags(), KeyFlag.SIGN_DATA)) { + throw new SignatureValidationException("Signature was made by key which is not capable of signing (no keyflags on binding sig, no SIGN flag on direct-key sig)."); + } + } else if (!KeyFlag.hasKeyFlag(keyFlags.getFlags(), KeyFlag.SIGN_DATA)) { + throw new SignatureValidationException("Signature was made by key which is not capable of signing (no SIGN flag on binding sig)."); } } return true; diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/SopKeyUtil.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/SopKeyUtil.java index 082bdc62..4eaac5aa 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/SopKeyUtil.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/SopKeyUtil.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.pgpainless.PGPainless; @@ -41,4 +42,17 @@ public class SopKeyUtil { } return secretKeyRings; } + + public static List loadCertificatesFromFile(File... files) throws IOException { + List publicKeyRings = new ArrayList<>(); + for (File file : files) { + try (FileInputStream in = new FileInputStream(file)) { + publicKeyRings.add(PGPainless.readKeyRing().publicKeyRing(in)); + } catch (IOException e) { + err_ln("Could not read certificate from file " + file.getName() + ": " + e.getMessage()); + throw e; + } + } + return publicKeyRings; + } } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java index 36f77bf0..dda9506f 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java @@ -26,6 +26,7 @@ import java.io.PrintStream; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -40,7 +41,9 @@ import org.pgpainless.decryption_verification.DecryptionBuilderInterface; import org.pgpainless.decryption_verification.DecryptionStream; import org.pgpainless.decryption_verification.OpenPgpMetadata; import org.pgpainless.key.OpenPgpV4Fingerprint; +import org.pgpainless.sop.SopKeyUtil; import picocli.CommandLine; +import sun.text.resources.CollationData; @CommandLine.Command(name = "decrypt", description = "Decrypt a message from standard input", @@ -110,33 +113,27 @@ public class Decrypt implements Runnable { } PGPSecretKeyRingCollection secretKeys; + List verifyWith = null; + try { List secretKeyRings = loadKeysFromFiles(keys); secretKeys = new PGPSecretKeyRingCollection(secretKeyRings); - } catch (PGPException | IOException e) { + if (certs != null) { + verifyWith = SopKeyUtil.loadCertificatesFromFile(certs); + } + } catch (IOException | PGPException e) { err_ln(e.getMessage()); System.exit(1); return; } - List verifyWith = new ArrayList<>(); - if (certs != null) { - for (File f : certs) { - try { - verifyWith.add(PGPainless.readKeyRing().publicKeyRing(new FileInputStream(f))); - } catch (IOException e) { - - } - } - } - DecryptionBuilderInterface.Verify builder = PGPainless.decryptAndOrVerify() .onInputStream(System.in) .decryptWith(secretKeys); DecryptionStream decryptionStream = null; try { - if (certs != null) { + if (verifyWith != null) { decryptionStream = builder.verifyWith(new HashSet<>(verifyWith)) .ignoreMissingPublicKeys().build(); } else { @@ -144,12 +141,14 @@ public class Decrypt implements Runnable { .build(); } } 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()); @@ -161,28 +160,32 @@ public class Decrypt implements Runnable { OpenPgpMetadata metadata = decryptionStream.getResult(); StringBuilder sb = new StringBuilder(); - for (OpenPgpV4Fingerprint fingerprint : metadata.getVerifiedSignatures().keySet()) { - PGPPublicKeyRing verifier = null; - for (PGPPublicKeyRing ring : verifyWith) { - if (ring.getPublicKey(fingerprint.getKeyId()) != null) { - verifier = ring; - break; - } - } - PGPSignature signature = metadata.getVerifiedSignatures().get(fingerprint); - sb.append(df.format(signature.getCreationTime())).append(' ') - .append(fingerprint).append(' ') - .append(new OpenPgpV4Fingerprint(verifier)).append('\n'); - } - try { - verifyOut.createNewFile(); - PrintStream verifyPrinter = new PrintStream(new FileOutputStream(verifyOut)); - // CHECKSTYLE:OFF - verifyPrinter.println(sb.toString()); - // CHECKSTYLE:ON - verifyPrinter.close(); - } catch (IOException e) { + if (verifyWith != null) { + for (OpenPgpV4Fingerprint fingerprint : metadata.getVerifiedSignatures().keySet()) { + PGPPublicKeyRing verifier = null; + for (PGPPublicKeyRing ring : verifyWith) { + if (ring.getPublicKey(fingerprint.getKeyId()) != null) { + verifier = ring; + break; + } + } + PGPSignature signature = metadata.getVerifiedSignatures().get(fingerprint); + sb.append(df.format(signature.getCreationTime())).append(' ') + .append(fingerprint).append(' ') + .append(new OpenPgpV4Fingerprint(verifier)).append('\n'); + } + + try { + verifyOut.createNewFile(); + PrintStream verifyPrinter = new PrintStream(new FileOutputStream(verifyOut)); + // CHECKSTYLE:OFF + verifyPrinter.println(sb.toString()); + // CHECKSTYLE:ON + verifyPrinter.close(); + } catch (IOException e) { + err_ln("Error writing verifications file: " + e); + } } } } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Encrypt.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Encrypt.java index 2cb35ebf..979cc093 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Encrypt.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Encrypt.java @@ -16,14 +16,12 @@ 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.InputStream; -import java.util.Scanner; -import javax.annotation.Nullable; +import java.util.List; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRing; @@ -35,9 +33,8 @@ 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.OpenPgpV4Fingerprint; import org.pgpainless.key.protection.SecretKeyRingProtector; -import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider; +import org.pgpainless.sop.SopKeyUtil; import org.pgpainless.util.Passphrase; import picocli.CommandLine; @@ -87,38 +84,27 @@ public class Encrypt implements Runnable { EncryptionOptions encOpt = new EncryptionOptions(); SigningOptions signOpt = new SigningOptions(); - for (int i = 0 ; i < certs.length; i++) { - try (InputStream fileIn = new FileInputStream(certs[i])) { - PGPPublicKeyRing publicKey = PGPainless.readKeyRing().publicKeyRing(fileIn); - encOpt.addRecipient(publicKey); - } catch (IOException e) { - err_ln("Cannot read certificate " + certs[i].getName()); - err_ln(e.getMessage()); - System.exit(1); + try { + List encryptionKeys = SopKeyUtil.loadCertificatesFromFile(certs); + for (PGPPublicKeyRing key : encryptionKeys) { + encOpt.addRecipient(key); } + } catch (IOException e) { + err_ln(e.getMessage()); + System.exit(1); + return; } - for (int i = 0; i < withPassword.length; i++) { - Passphrase passphrase = Passphrase.fromPassword(withPassword[i]); + for (String s : withPassword) { + Passphrase passphrase = Passphrase.fromPassword(s); encOpt.addPassphrase(passphrase); } - final Scanner scanner = new Scanner(System.in); + 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); - SecretKeyRingProtector protector = SecretKeyRingProtector.defaultSecretKeyRingProtector( - new SecretKeyPassphraseProvider() { - @Nullable - @Override - public Passphrase getPassphraseFor(Long keyId) { - print_ln("Please provide the passphrase for key " + new OpenPgpV4Fingerprint(secretKey)); - String password = scanner.nextLine(); - Passphrase passphrase = Passphrase.fromPassword(password.trim()); - return passphrase; - } - } - ); signOpt.addInlineSignature(protector, secretKey, type == Type.text || type == Type.mime ? DocumentSignatureType.CANONICAL_TEXT_DOCUMENT : DocumentSignatureType.BINARY_DOCUMENT); diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Sign.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Sign.java index 0c5cefa2..c7cd874a 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Sign.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Sign.java @@ -21,15 +21,12 @@ 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.EncryptionBuilderInterface; -import org.pgpainless.encryption_signing.EncryptionOptions; 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.key.protection.UnprotectedKeysProtector; import org.pgpainless.sop.Print; import picocli.CommandLine; diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptTest.java index 13d7bf29..66d1ae5b 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptTest.java @@ -103,7 +103,8 @@ public class EncryptDecryptTest { FileInputStream msgAscIn = new FileInputStream(msgAscFile); System.setIn(msgAscIn); ByteArrayOutputStream out = new ByteArrayOutputStream(); - System.setOut(new PrintStream(out)); + PrintStream pOut = new PrintStream(out); + System.setOut(pOut); new CommandLine(new PGPainlessCLI()).execute("decrypt", "--verify-out", verifyFile.getAbsolutePath(), "--verify-with", romeoCertFile.getAbsolutePath(),