001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org> 002// 003// SPDX-License-Identifier: Apache-2.0 004 005package org.pgpainless.sop; 006 007import java.io.IOException; 008import java.io.InputStream; 009import java.io.OutputStream; 010import java.util.ArrayList; 011import java.util.Date; 012import java.util.List; 013 014import org.bouncycastle.openpgp.PGPException; 015import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; 016import org.bouncycastle.openpgp.PGPSecretKeyRing; 017import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; 018import org.bouncycastle.openpgp.PGPSignature; 019import org.bouncycastle.util.io.Streams; 020import org.pgpainless.PGPainless; 021import org.pgpainless.algorithm.SymmetricKeyAlgorithm; 022import org.pgpainless.decryption_verification.ConsumerOptions; 023import org.pgpainless.decryption_verification.DecryptionStream; 024import org.pgpainless.decryption_verification.OpenPgpMetadata; 025import org.pgpainless.key.SubkeyIdentifier; 026import org.pgpainless.key.info.KeyRingInfo; 027import org.pgpainless.key.protection.SecretKeyRingProtector; 028import org.pgpainless.util.Passphrase; 029import sop.DecryptionResult; 030import sop.ReadyWithResult; 031import sop.SessionKey; 032import sop.Verification; 033import sop.exception.SOPGPException; 034import sop.operation.Decrypt; 035 036public class DecryptImpl implements Decrypt { 037 038 private final ConsumerOptions consumerOptions = new ConsumerOptions(); 039 040 @Override 041 public DecryptImpl verifyNotBefore(Date timestamp) throws SOPGPException.UnsupportedOption { 042 consumerOptions.verifyNotBefore(timestamp); 043 return this; 044 } 045 046 @Override 047 public DecryptImpl verifyNotAfter(Date timestamp) throws SOPGPException.UnsupportedOption { 048 consumerOptions.verifyNotAfter(timestamp); 049 return this; 050 } 051 052 @Override 053 public DecryptImpl verifyWithCert(InputStream certIn) throws SOPGPException.BadData, IOException { 054 try { 055 PGPPublicKeyRingCollection certs = PGPainless.readKeyRing().keyRingCollection(certIn, false) 056 .getPgpPublicKeyRingCollection(); 057 if (certs.size() == 0) { 058 throw new SOPGPException.BadData(new PGPException("No certificates provided.")); 059 } 060 061 consumerOptions.addVerificationCerts(certs); 062 063 } catch (PGPException e) { 064 throw new SOPGPException.BadData(e); 065 } 066 return this; 067 } 068 069 @Override 070 public DecryptImpl withSessionKey(SessionKey sessionKey) throws SOPGPException.UnsupportedOption { 071 consumerOptions.setSessionKey( 072 new org.pgpainless.util.SessionKey( 073 SymmetricKeyAlgorithm.fromId(sessionKey.getAlgorithm()), 074 sessionKey.getKey())); 075 return this; 076 } 077 078 @Override 079 public DecryptImpl withPassword(String password) { 080 consumerOptions.addDecryptionPassphrase(Passphrase.fromPassword(password)); 081 String withoutTrailingWhitespace = removeTrailingWhitespace(password); 082 if (!password.equals(withoutTrailingWhitespace)) { 083 consumerOptions.addDecryptionPassphrase(Passphrase.fromPassword(withoutTrailingWhitespace)); 084 } 085 return this; 086 } 087 088 private static String removeTrailingWhitespace(String passphrase) { 089 int i = passphrase.length() - 1; 090 // Find index of first non-whitespace character from the back 091 while (i > 0 && Character.isWhitespace(passphrase.charAt(i))) { 092 i--; 093 } 094 return passphrase.substring(0, i); 095 } 096 097 @Override 098 public DecryptImpl withKey(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo { 099 try { 100 PGPSecretKeyRingCollection secretKeys = PGPainless.readKeyRing() 101 .secretKeyRingCollection(keyIn); 102 103 if (secretKeys.size() != 1) { 104 throw new SOPGPException.BadData(new AssertionError("Exactly one single secret key expected. Got " + secretKeys.size())); 105 } 106 107 for (PGPSecretKeyRing secretKey : secretKeys) { 108 KeyRingInfo info = new KeyRingInfo(secretKey); 109 if (!info.isFullyDecrypted()) { 110 throw new SOPGPException.KeyIsProtected(); 111 } 112 } 113 114 consumerOptions.addDecryptionKeys(secretKeys, SecretKeyRingProtector.unprotectedKeys()); 115 } catch (IOException | PGPException e) { 116 throw new SOPGPException.BadData(e); 117 } 118 return this; 119 } 120 121 @Override 122 public ReadyWithResult<DecryptionResult> ciphertext(InputStream ciphertext) 123 throws SOPGPException.BadData, 124 SOPGPException.MissingArg { 125 126 if (consumerOptions.getDecryptionKeys().isEmpty() && consumerOptions.getDecryptionPassphrases().isEmpty() && consumerOptions.getSessionKey() == null) { 127 throw new SOPGPException.MissingArg("Missing decryption key, passphrase or session key."); 128 } 129 130 DecryptionStream decryptionStream; 131 try { 132 decryptionStream = PGPainless.decryptAndOrVerify() 133 .onInputStream(ciphertext) 134 .withOptions(consumerOptions); 135 } catch (PGPException | IOException e) { 136 throw new SOPGPException.BadData(e); 137 } 138 139 return new ReadyWithResult<DecryptionResult>() { 140 @Override 141 public DecryptionResult writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature { 142 Streams.pipeAll(decryptionStream, outputStream); 143 decryptionStream.close(); 144 OpenPgpMetadata metadata = decryptionStream.getResult(); 145 146 List<Verification> verificationList = new ArrayList<>(); 147 for (SubkeyIdentifier verifiedSigningKey : metadata.getVerifiedSignatures().keySet()) { 148 PGPSignature signature = metadata.getVerifiedSignatures().get(verifiedSigningKey); 149 verificationList.add(new Verification( 150 signature.getCreationTime(), 151 verifiedSigningKey.getSubkeyFingerprint().toString(), 152 verifiedSigningKey.getPrimaryKeyFingerprint().toString())); 153 } 154 155 if (!consumerOptions.getCertificates().isEmpty()) { 156 if (verificationList.isEmpty()) { 157 throw new SOPGPException.NoSignature(); 158 } 159 } 160 161 SessionKey sessionKey = null; 162 if (metadata.getSessionKey() != null) { 163 org.pgpainless.util.SessionKey sk = metadata.getSessionKey(); 164 sessionKey = new SessionKey( 165 (byte) sk.getAlgorithm().getAlgorithmId(), 166 sk.getKey() 167 ); 168 } 169 170 return new DecryptionResult(sessionKey, verificationList); 171 } 172 }; 173 } 174}