That fuck shit the fascists are using
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}