001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.decryption_verification;
006
007import static org.pgpainless.signature.consumer.SignatureValidator.signatureWasCreatedInBounds;
008
009import java.io.FilterInputStream;
010import java.io.IOException;
011import java.io.InputStream;
012import java.util.List;
013import java.util.Map;
014import javax.annotation.Nonnull;
015import javax.annotation.Nullable;
016
017import org.bouncycastle.openpgp.PGPObjectFactory;
018import org.bouncycastle.openpgp.PGPPublicKeyRing;
019import org.bouncycastle.openpgp.PGPSignature;
020import org.bouncycastle.openpgp.PGPSignatureList;
021import org.pgpainless.PGPainless;
022import org.pgpainless.exception.SignatureValidationException;
023import org.pgpainless.policy.Policy;
024import org.pgpainless.signature.consumer.CertificateValidator;
025import org.pgpainless.signature.consumer.DetachedSignatureCheck;
026import org.pgpainless.signature.consumer.OnePassSignatureCheck;
027import org.pgpainless.signature.SignatureUtils;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031public abstract class SignatureInputStream extends FilterInputStream {
032
033    protected SignatureInputStream(InputStream inputStream) {
034        super(inputStream);
035    }
036
037    public static class VerifySignatures extends SignatureInputStream {
038
039        private static final Logger LOGGER = LoggerFactory.getLogger(VerifySignatures.class);
040
041        private final PGPObjectFactory objectFactory;
042        private final List<OnePassSignatureCheck> opSignatures;
043        private final Map<Long, OnePassSignatureCheck> opSignaturesWithMissingCert;
044        private final List<DetachedSignatureCheck> detachedSignatures;
045        private final ConsumerOptions options;
046        private final OpenPgpMetadata.Builder resultBuilder;
047
048        public VerifySignatures(
049                InputStream literalDataStream,
050                @Nullable PGPObjectFactory objectFactory,
051                List<OnePassSignatureCheck> opSignatures,
052                Map<Long, OnePassSignatureCheck> onePassSignaturesWithMissingCert,
053                List<DetachedSignatureCheck> detachedSignatures,
054                ConsumerOptions options,
055                OpenPgpMetadata.Builder resultBuilder) {
056            super(literalDataStream);
057            this.objectFactory = objectFactory;
058            this.opSignatures = opSignatures;
059            this.opSignaturesWithMissingCert = onePassSignaturesWithMissingCert;
060            this.detachedSignatures = detachedSignatures;
061            this.options = options;
062            this.resultBuilder = resultBuilder;
063        }
064
065        @Override
066        public int read() throws IOException {
067            final int data = super.read();
068            final boolean endOfStream = data == -1;
069            if (endOfStream) {
070                verifyOnePassSignatures();
071                verifyDetachedSignatures();
072            } else {
073                byte b = (byte) data;
074                updateOnePassSignatures(b);
075                updateDetachedSignatures(b);
076            }
077            return data;
078        }
079
080        @Override
081        public int read(@Nonnull byte[] b, int off, int len) throws IOException {
082            int read = super.read(b, off, len);
083
084            final boolean endOfStream = read == -1;
085            if (endOfStream) {
086                parseAndCombineSignatures();
087                verifyOnePassSignatures();
088                verifyDetachedSignatures();
089            } else {
090                updateOnePassSignatures(b, off, read);
091                updateDetachedSignatures(b, off, read);
092            }
093            return read;
094        }
095
096        public void parseAndCombineSignatures() {
097            if (objectFactory == null) {
098                return;
099            }
100            // Parse signatures from message
101            PGPSignatureList signatures;
102            try {
103                signatures = parseSignatures(objectFactory);
104            } catch (IOException e) {
105                return;
106            }
107            List<PGPSignature> signatureList = SignatureUtils.toList(signatures);
108            // Set signatures as comparison sigs in OPS checks
109            for (int i = 0; i < opSignatures.size(); i++) {
110                int reversedIndex = opSignatures.size() - i - 1;
111                opSignatures.get(i).setSignature(signatureList.get(reversedIndex));
112            }
113
114            for (PGPSignature signature : signatureList) {
115                if (opSignaturesWithMissingCert.containsKey(signature.getKeyID())) {
116                    OnePassSignatureCheck check = opSignaturesWithMissingCert.remove(signature.getKeyID());
117                    check.setSignature(signature);
118
119                    resultBuilder.addInvalidInbandSignature(new SignatureVerification(signature, null),
120                            new SignatureValidationException(
121                                    "Missing verification certificate " + Long.toHexString(signature.getKeyID())));
122                }
123            }
124        }
125
126        private PGPSignatureList parseSignatures(PGPObjectFactory objectFactory) throws IOException {
127            PGPSignatureList signatureList = null;
128            Object pgpObject = objectFactory.nextObject();
129            while (pgpObject !=  null && signatureList == null) {
130                if (pgpObject instanceof PGPSignatureList) {
131                    signatureList = (PGPSignatureList) pgpObject;
132                } else {
133                    pgpObject = objectFactory.nextObject();
134                }
135            }
136
137            if (signatureList == null || signatureList.isEmpty()) {
138                throw new IOException("Verification failed - No Signatures found");
139            }
140
141            return signatureList;
142        }
143
144
145        private synchronized void verifyOnePassSignatures() {
146            Policy policy = PGPainless.getPolicy();
147            for (OnePassSignatureCheck opSignature : opSignatures) {
148                if (opSignature.getSignature() == null) {
149                    LOGGER.warn("Found OnePassSignature without respective signature packet -> skip");
150                    continue;
151                }
152
153                try {
154                    signatureWasCreatedInBounds(options.getVerifyNotBefore(),
155                            options.getVerifyNotAfter()).verify(opSignature.getSignature());
156                    CertificateValidator.validateCertificateAndVerifyOnePassSignature(opSignature, policy);
157                    resultBuilder.addVerifiedInbandSignature(
158                            new SignatureVerification(opSignature.getSignature(), opSignature.getSigningKey()));
159                } catch (SignatureValidationException e) {
160                    LOGGER.warn("One-pass-signature verification failed for signature made by key {}: {}",
161                            opSignature.getSigningKey(), e.getMessage(), e);
162                    resultBuilder.addInvalidInbandSignature(
163                            new SignatureVerification(opSignature.getSignature(), opSignature.getSigningKey()), e);
164                }
165            }
166        }
167
168        private void verifyDetachedSignatures() {
169            Policy policy = PGPainless.getPolicy();
170            for (DetachedSignatureCheck s : detachedSignatures) {
171                try {
172                    signatureWasCreatedInBounds(options.getVerifyNotBefore(),
173                            options.getVerifyNotAfter()).verify(s.getSignature());
174                    CertificateValidator.validateCertificateAndVerifyInitializedSignature(s.getSignature(),
175                            (PGPPublicKeyRing) s.getSigningKeyRing(), policy);
176                    resultBuilder.addVerifiedDetachedSignature(new SignatureVerification(s.getSignature(),
177                            s.getSigningKeyIdentifier()));
178                } catch (SignatureValidationException e) {
179                    LOGGER.warn("One-pass-signature verification failed for signature made by key {}: {}",
180                            s.getSigningKeyIdentifier(), e.getMessage(), e);
181                    resultBuilder.addInvalidDetachedSignature(new SignatureVerification(s.getSignature(),
182                            s.getSigningKeyIdentifier()), e);
183                }
184            }
185        }
186
187        private void updateOnePassSignatures(byte data) {
188            for (OnePassSignatureCheck opSignature : opSignatures) {
189                opSignature.getOnePassSignature().update(data);
190            }
191        }
192
193        private void updateOnePassSignatures(byte[] bytes, int offset, int length) {
194            for (OnePassSignatureCheck opSignature : opSignatures) {
195                opSignature.getOnePassSignature().update(bytes, offset, length);
196            }
197        }
198
199        private void updateDetachedSignatures(byte b) {
200            for (DetachedSignatureCheck detachedSignature : detachedSignatures) {
201                detachedSignature.getSignature().update(b);
202            }
203        }
204
205        private void updateDetachedSignatures(byte[] b, int off, int read) {
206            for (DetachedSignatureCheck detachedSignature : detachedSignatures) {
207                detachedSignature.getSignature().update(b, off, read);
208            }
209        }
210
211    }
212}