That fuck shit the fascists are using
at master 170 lines 5.4 kB view raw
1/** 2 * Copyright (C) 2011 Whisper Systems 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17package org.tm.archive.crypto; 18 19import androidx.annotation.NonNull; 20 21import org.signal.core.util.logging.Log; 22import org.tm.archive.util.LimitedInputStream; 23import org.tm.archive.util.Util; 24 25import java.io.File; 26import java.io.FileInputStream; 27import java.io.IOException; 28import java.io.InputStream; 29import java.security.InvalidAlgorithmParameterException; 30import java.security.InvalidKeyException; 31import java.security.MessageDigest; 32import java.security.NoSuchAlgorithmException; 33 34import javax.crypto.Cipher; 35import javax.crypto.CipherInputStream; 36import javax.crypto.Mac; 37import javax.crypto.NoSuchPaddingException; 38import javax.crypto.spec.IvParameterSpec; 39import javax.crypto.spec.SecretKeySpec; 40 41public class ClassicDecryptingPartInputStream { 42 43 private static final String TAG = Log.tag(ClassicDecryptingPartInputStream.class); 44 45 private static final int IV_LENGTH = 16; 46 private static final int MAC_LENGTH = 20; 47 48 public static InputStream createFor(@NonNull AttachmentSecret attachmentSecret, @NonNull File file) 49 throws IOException 50 { 51 try { 52 if (file.length() <= IV_LENGTH + MAC_LENGTH) { 53 throw new IOException("File too short"); 54 } 55 56 verifyMac(attachmentSecret, file); 57 58 FileInputStream fileStream = new FileInputStream(file); 59 byte[] ivBytes = new byte[IV_LENGTH]; 60 readFully(fileStream, ivBytes); 61 62 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 63 IvParameterSpec iv = new IvParameterSpec(ivBytes); 64 cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(attachmentSecret.getClassicCipherKey(), "AES"), iv); 65 66 return new CipherInputStreamWrapper(new LimitedInputStream(fileStream, file.length() - MAC_LENGTH - IV_LENGTH), cipher); 67 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) { 68 throw new AssertionError(e); 69 } 70 } 71 72 private static void verifyMac(AttachmentSecret attachmentSecret, File file) throws IOException { 73 Mac mac = initializeMac(new SecretKeySpec(attachmentSecret.getClassicMacKey(), "HmacSHA1")); 74 FileInputStream macStream = new FileInputStream(file); 75 InputStream dataStream = new LimitedInputStream(new FileInputStream(file), file.length() - MAC_LENGTH); 76 byte[] theirMac = new byte[MAC_LENGTH]; 77 78 if (macStream.skip(file.length() - MAC_LENGTH) != file.length() - MAC_LENGTH) { 79 throw new IOException("Unable to seek"); 80 } 81 82 readFully(macStream, theirMac); 83 84 byte[] buffer = new byte[4096]; 85 int read; 86 87 while ((read = dataStream.read(buffer)) != -1) { 88 mac.update(buffer, 0, read); 89 } 90 91 byte[] ourMac = mac.doFinal(); 92 93 if (!MessageDigest.isEqual(ourMac, theirMac)) { 94 throw new IOException("Bad MAC"); 95 } 96 97 macStream.close(); 98 dataStream.close(); 99 } 100 101 private static Mac initializeMac(SecretKeySpec key) { 102 try { 103 Mac hmac = Mac.getInstance("HmacSHA1"); 104 hmac.init(key); 105 106 return hmac; 107 } catch (NoSuchAlgorithmException | InvalidKeyException e) { 108 throw new AssertionError(e); 109 } 110 } 111 112 private static void readFully(InputStream in, byte[] buffer) throws IOException { 113 int offset = 0; 114 115 for (;;) { 116 int read = in.read(buffer, offset, buffer.length-offset); 117 118 if (read + offset < buffer.length) offset += read; 119 else return; 120 } 121 } 122 123 // Note (4/3/17) -- Older versions of Android have a busted OpenSSL provider that 124 // throws a RuntimeException on a BadPaddingException, so we have to catch 125 // that here in case someone calls close() before reaching the end of the 126 // stream (since close() implicitly calls doFinal()) 127 // 128 // See Signal-Android Issue #6477 129 // Android: https://android-review.googlesource.com/#/c/65321/ 130 private static class CipherInputStreamWrapper extends CipherInputStream { 131 132 CipherInputStreamWrapper(InputStream is, Cipher c) { 133 super(is, c); 134 } 135 136 @Override 137 public void close() throws IOException { 138 try { 139 super.close(); 140 } catch (Throwable t) { 141 Log.w(TAG, t); 142 } 143 } 144 145 @Override 146 public long skip(long skipAmount) 147 throws IOException 148 { 149 long remaining = skipAmount; 150 151 if (skipAmount <= 0) { 152 return 0; 153 } 154 155 byte[] skipBuffer = new byte[4092]; 156 157 while (remaining > 0) { 158 int read = super.read(skipBuffer, 0, Util.toIntExact(Math.min(skipBuffer.length, remaining))); 159 160 if (read < 0) { 161 break; 162 } 163 164 remaining -= read; 165 } 166 167 return skipAmount - remaining; 168 } 169 } 170}