| /******************************************************************************* |
| * Copyright (c) 2006, 2012 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.osgi.internal.signedcontent; |
| |
| import java.io.FilterInputStream; |
| import java.io.IOException; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import org.eclipse.osgi.signedcontent.InvalidContentException; |
| import org.eclipse.osgi.signedcontent.SignerInfo; |
| import org.eclipse.osgi.storage.bundlefile.BundleEntry; |
| import org.eclipse.osgi.storage.bundlefile.BundleFile; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * This InputStream will calculate the digest of bytes as they are read. At the |
| * end of the InputStream, it will calculate the digests and throw an exception |
| * if the calculated digest do not match the expected digests. |
| */ |
| class DigestedInputStream extends FilterInputStream { |
| private final MessageDigest digests[]; |
| private final byte result[][]; |
| private final BundleEntry entry; |
| private final BundleFile bundleFile; |
| private long remaining; |
| |
| /** |
| * Constructs an InputStream that uses another InputStream as a source and |
| * calculates the digest. At the end of the stream an exception will be |
| * thrown if the calculated digest doesn't match the passed digest. |
| * |
| * @param in the stream to use as an input source. |
| * @param signerInfos the signers. |
| * @param results the expected digest. |
| * @throws IOException |
| * @throws NoSuchAlgorithmException |
| */ |
| DigestedInputStream(BundleEntry entry, BundleFile bundleFile, SignerInfo[] signerInfos, byte results[][], long size) throws IOException, NoSuchAlgorithmException { |
| super(entry.getInputStream()); |
| this.entry = entry; |
| this.bundleFile = bundleFile; |
| this.remaining = size; |
| this.digests = new MessageDigest[signerInfos.length]; |
| for (int i = 0; i < signerInfos.length; i++) |
| this.digests[i] = MessageDigest.getInstance(signerInfos[i].getMessageDigestAlgorithm()); |
| this.result = results; |
| } |
| |
| /** |
| * Not supported. |
| */ |
| @Override |
| public synchronized void mark(int readlimit) { |
| // Noop, we don't want to support this |
| } |
| |
| /** |
| * Always returns false. |
| */ |
| @Override |
| public boolean markSupported() { |
| return false; |
| } |
| |
| /** |
| * Read a byte from the InputStream. Digests are calculated on reads. At the |
| * end of the stream the calculated digests must match the expected digests. |
| * |
| * @return the character read or -1 at end of stream. |
| * @throws IOException if there was an problem reading the byte or at the |
| * end of the stream the calculated digests do not match the |
| * expected digests. |
| * @see java.io.InputStream#read() |
| */ |
| @Override |
| public int read() throws IOException { |
| if (remaining <= 0) |
| return -1; |
| int c = super.read(); |
| if (c != -1) { |
| for (MessageDigest digest : digests) { |
| digest.update((byte) c); |
| } |
| remaining--; |
| } else { |
| // We hit eof so set remaining to zero |
| remaining = 0; |
| } |
| if (remaining == 0) |
| verifyDigests(); |
| return c; |
| } |
| |
| private void verifyDigests() throws InvalidContentException { |
| // Check the digest at end of file |
| for (int i = 0; i < digests.length; i++) { |
| byte rc[] = digests[i].digest(); |
| if (!MessageDigest.isEqual(result[i], rc)) |
| throw new InvalidContentException(NLS.bind(SignedContentMessages.File_In_Jar_Is_Tampered, entry.getName(), bundleFile.getBaseFile()), null); |
| } |
| } |
| |
| /** |
| * Read bytes from the InputStream. Digests are calculated on reads. At the |
| * end of the stream the calculated digests must match the expected digests. |
| * |
| * @return the number of characters read or -1 at end of stream. |
| * @throws IOException if there was an problem reading or at the |
| * end of the stream the calculated digests do not match the |
| * expected digests. |
| * @see java.io.InputStream#read() |
| */ |
| @Override |
| public int read(byte[] b, int off, int len) throws IOException { |
| if (remaining <= 0) |
| return -1; |
| int rc = super.read(b, off, len); |
| if (rc != -1) { |
| for (MessageDigest digest : digests) { |
| digest.update(b, off, rc); |
| } |
| remaining -= rc; |
| } else { |
| // We hit eof so set remaining to zero |
| remaining = 0; |
| } |
| if (remaining <= 0) |
| verifyDigests(); |
| return rc; |
| } |
| |
| /** |
| * Not supported. |
| * |
| * @throws IOException always thrown if this method is called since mark/reset is not supported. |
| * @see java.io.InputStream#reset() |
| */ |
| @Override |
| public synchronized void reset() throws IOException { |
| // Throw IOException, we don't want to support this |
| throw new IOException("Reset not supported"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * This method is implemented as a read into a bitbucket. |
| */ |
| @Override |
| public long skip(long n) throws IOException { |
| byte buffer[] = new byte[4096]; |
| long count = 0; |
| while (n - count > 0) { |
| int rc = (n - count) > buffer.length ? buffer.length : (int) (n - count); |
| rc = read(buffer, 0, rc); |
| if (rc == -1) |
| break; |
| count += rc; |
| n -= rc; |
| } |
| return count; |
| } |
| } |