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}