EncryptionUtils.java 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Tobias Kaminsky
  5. * Copyright (C) 2017 Tobias Kaminsky
  6. * Copyright (C) 2017 Nextcloud GmbH.
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. package com.owncloud.android.utils;
  22. import android.content.Context;
  23. import android.text.TextUtils;
  24. import android.util.Base64;
  25. import android.util.Pair;
  26. import com.google.common.collect.Lists;
  27. import com.google.gson.Gson;
  28. import com.google.gson.GsonBuilder;
  29. import com.google.gson.reflect.TypeToken;
  30. import com.nextcloud.client.account.User;
  31. import com.owncloud.android.R;
  32. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  33. import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
  34. import com.owncloud.android.datamodel.DecryptedFolderMetadata;
  35. import com.owncloud.android.datamodel.EncryptedFiledrop;
  36. import com.owncloud.android.datamodel.EncryptedFolderMetadata;
  37. import com.owncloud.android.datamodel.OCFile;
  38. import com.owncloud.android.lib.common.OwnCloudClient;
  39. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  40. import com.owncloud.android.lib.common.utils.Log_OC;
  41. import com.owncloud.android.lib.resources.e2ee.GetMetadataRemoteOperation;
  42. import com.owncloud.android.lib.resources.e2ee.LockFileRemoteOperation;
  43. import com.owncloud.android.lib.resources.e2ee.StoreMetadataRemoteOperation;
  44. import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
  45. import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
  46. import com.owncloud.android.lib.resources.status.NextcloudVersion;
  47. import com.owncloud.android.lib.resources.status.Problem;
  48. import com.owncloud.android.lib.resources.status.SendClientDiagnosticRemoteOperation;
  49. import com.owncloud.android.operations.UploadException;
  50. import org.apache.commons.httpclient.HttpStatus;
  51. import java.io.BufferedReader;
  52. import java.io.ByteArrayInputStream;
  53. import java.io.File;
  54. import java.io.IOException;
  55. import java.io.InputStream;
  56. import java.io.InputStreamReader;
  57. import java.io.RandomAccessFile;
  58. import java.math.BigInteger;
  59. import java.nio.charset.StandardCharsets;
  60. import java.security.InvalidAlgorithmParameterException;
  61. import java.security.InvalidKeyException;
  62. import java.security.Key;
  63. import java.security.KeyFactory;
  64. import java.security.KeyPair;
  65. import java.security.KeyPairGenerator;
  66. import java.security.MessageDigest;
  67. import java.security.NoSuchAlgorithmException;
  68. import java.security.PrivateKey;
  69. import java.security.PublicKey;
  70. import java.security.SecureRandom;
  71. import java.security.cert.CertificateException;
  72. import java.security.cert.CertificateFactory;
  73. import java.security.cert.X509Certificate;
  74. import java.security.interfaces.RSAPrivateCrtKey;
  75. import java.security.interfaces.RSAPublicKey;
  76. import java.security.spec.InvalidKeySpecException;
  77. import java.security.spec.KeySpec;
  78. import java.security.spec.PKCS8EncodedKeySpec;
  79. import java.util.ArrayList;
  80. import java.util.Arrays;
  81. import java.util.Collections;
  82. import java.util.HashMap;
  83. import java.util.List;
  84. import java.util.Map;
  85. import javax.crypto.BadPaddingException;
  86. import javax.crypto.Cipher;
  87. import javax.crypto.IllegalBlockSizeException;
  88. import javax.crypto.KeyGenerator;
  89. import javax.crypto.NoSuchPaddingException;
  90. import javax.crypto.SecretKey;
  91. import javax.crypto.SecretKeyFactory;
  92. import javax.crypto.spec.GCMParameterSpec;
  93. import javax.crypto.spec.IvParameterSpec;
  94. import javax.crypto.spec.PBEKeySpec;
  95. import javax.crypto.spec.SecretKeySpec;
  96. import androidx.annotation.Nullable;
  97. import androidx.annotation.VisibleForTesting;
  98. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  99. /**
  100. * Utils for encryption
  101. */
  102. public final class EncryptionUtils {
  103. private static final String TAG = EncryptionUtils.class.getSimpleName();
  104. public static final String PUBLIC_KEY = "PUBLIC_KEY";
  105. public static final String PRIVATE_KEY = "PRIVATE_KEY";
  106. public static final String MNEMONIC = "MNEMONIC";
  107. public static final int ivLength = 16;
  108. public static final int saltLength = 40;
  109. public static final String ivDelimiter = "|"; // not base64 encoded
  110. public static final String ivDelimiterOld = "fA=="; // "|" base64 encoded
  111. private static final char HASH_DELIMITER = '$';
  112. private static final int iterationCount = 1024;
  113. private static final int keyStrength = 256;
  114. private static final String AES_CIPHER = "AES/GCM/NoPadding";
  115. private static final String AES = "AES";
  116. private static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
  117. private static final String RSA = "RSA";
  118. @VisibleForTesting
  119. public static final String MIGRATED_FOLDER_IDS = "MIGRATED_FOLDER_IDS";
  120. private EncryptionUtils() {
  121. // utility class -> private constructor
  122. }
  123. /*
  124. JSON
  125. */
  126. public static <T> T deserializeJSON(String json, TypeToken<T> type, boolean excludeTransient) {
  127. if (excludeTransient) {
  128. return new Gson().fromJson(json, type.getType());
  129. } else {
  130. return new GsonBuilder().excludeFieldsWithModifiers(0).create().fromJson(json, type.getType());
  131. }
  132. }
  133. public static <T> T deserializeJSON(String json, TypeToken<T> type) {
  134. return deserializeJSON(json, type, false);
  135. }
  136. public static String serializeJSON(Object data, boolean excludeTransient) {
  137. if (excludeTransient) {
  138. return new Gson().toJson(data);
  139. } else {
  140. return new GsonBuilder().excludeFieldsWithModifiers(0).create().toJson(data);
  141. }
  142. }
  143. public static String serializeJSON(Object data) {
  144. return serializeJSON(data, false);
  145. }
  146. /*
  147. METADATA
  148. */
  149. /**
  150. * Encrypt folder metaData
  151. *
  152. * @param decryptedFolderMetadata folder metaData to encrypt
  153. * @return EncryptedFolderMetadata encrypted folder metadata
  154. */
  155. public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata,
  156. String publicKey,
  157. ArbitraryDataProvider arbitraryDataProvider,
  158. User user,
  159. long parentId
  160. )
  161. throws NoSuchAlgorithmException, InvalidKeyException,
  162. InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
  163. IllegalBlockSizeException, InvalidKeySpecException, CertificateException {
  164. HashMap<String, EncryptedFolderMetadata.EncryptedFile> files = new HashMap<>();
  165. HashMap<String, EncryptedFiledrop> filesdrop = new HashMap<>();
  166. EncryptedFolderMetadata encryptedFolderMetadata = new EncryptedFolderMetadata(decryptedFolderMetadata
  167. .getMetadata(),
  168. files,
  169. filesdrop);
  170. // set new metadata key
  171. byte[] metadataKeyBytes = EncryptionUtils.generateKey();
  172. String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(
  173. EncryptionUtils.encodeBytesToBase64String(metadataKeyBytes),
  174. publicKey);
  175. encryptedFolderMetadata.getMetadata().setMetadataKey(encryptedMetadataKey);
  176. // store that this folder has been migrated
  177. addIdToMigratedIds(parentId, user, arbitraryDataProvider);
  178. // Encrypt each file in "files"
  179. for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
  180. .getFiles().entrySet()) {
  181. String key = entry.getKey();
  182. DecryptedFolderMetadata.DecryptedFile decryptedFile = entry.getValue();
  183. EncryptedFolderMetadata.EncryptedFile encryptedFile = new EncryptedFolderMetadata.EncryptedFile();
  184. encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
  185. encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
  186. // encrypt
  187. String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
  188. encryptedFile.setEncrypted(EncryptionUtils.encryptStringSymmetric(dataJson, metadataKeyBytes));
  189. files.put(key, encryptedFile);
  190. }
  191. // set checksum
  192. String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC);
  193. String checksum = EncryptionUtils.generateChecksum(decryptedFolderMetadata, mnemonic);
  194. encryptedFolderMetadata.getMetadata().setChecksum(checksum);
  195. return encryptedFolderMetadata;
  196. }
  197. /**
  198. * normally done on server only internal test
  199. */
  200. @VisibleForTesting
  201. public static void encryptFileDropFiles(DecryptedFolderMetadata decryptedFolderMetadata,
  202. EncryptedFolderMetadata encryptedFolderMetadata,
  203. String cert) throws NoSuchPaddingException, IllegalBlockSizeException, CertificateException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
  204. final Map<String, EncryptedFiledrop> filesdrop = encryptedFolderMetadata.getFiledrop();
  205. for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
  206. .getFiledrop().entrySet()) {
  207. String key = entry.getKey();
  208. DecryptedFolderMetadata.DecryptedFile decryptedFile = entry.getValue();
  209. byte[] byt = generateKey();
  210. String metadataKey0 = encodeBytesToBase64String(byt);
  211. String enc = encryptStringAsymmetric(metadataKey0, cert);
  212. String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
  213. String encJson = encryptStringSymmetric(dataJson, byt);
  214. int delimiterPosition = encJson.lastIndexOf(ivDelimiter);
  215. String encryptedInitializationVector = encJson.substring(delimiterPosition + ivDelimiter.length());
  216. String encodedCryptedBytes = encJson.substring(0, delimiterPosition);
  217. byte[] bytes = decodeStringToBase64Bytes(encodedCryptedBytes);
  218. // check authentication tag
  219. byte[] extractedAuthenticationTag = Arrays.copyOfRange(bytes,
  220. bytes.length - (128 / 8),
  221. bytes.length);
  222. String encryptedTag = encodeBytesToBase64String(extractedAuthenticationTag);
  223. EncryptedFiledrop encryptedFile = new EncryptedFiledrop(encodedCryptedBytes,
  224. decryptedFile.getInitializationVector(),
  225. decryptedFile.getAuthenticationTag(),
  226. enc,
  227. encryptedTag,
  228. encryptedInitializationVector);
  229. filesdrop.put(key, encryptedFile);
  230. }
  231. }
  232. /*
  233. * decrypt folder metaData with private key
  234. */
  235. public static DecryptedFolderMetadata decryptFolderMetaData(EncryptedFolderMetadata encryptedFolderMetadata,
  236. String privateKey,
  237. ArbitraryDataProvider arbitraryDataProvider,
  238. User user,
  239. long remoteId)
  240. throws NoSuchAlgorithmException, InvalidKeyException,
  241. InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
  242. IllegalBlockSizeException, InvalidKeySpecException {
  243. HashMap<String, DecryptedFolderMetadata.DecryptedFile> files = new HashMap<>();
  244. DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
  245. encryptedFolderMetadata.getMetadata(), files);
  246. byte[] decryptedMetadataKey = null;
  247. String encryptedMetadataKey = decryptedFolderMetadata.getMetadata().getMetadataKey();
  248. if (encryptedMetadataKey != null) {
  249. decryptedMetadataKey = decodeStringToBase64Bytes(
  250. decryptStringAsymmetric(encryptedMetadataKey, privateKey));
  251. }
  252. if (encryptedFolderMetadata.getFiles() != null) {
  253. for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
  254. .getFiles().entrySet()) {
  255. String key = entry.getKey();
  256. EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
  257. DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
  258. decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
  259. decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
  260. decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
  261. if (decryptedMetadataKey == null) {
  262. decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
  263. decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
  264. .getMetadataKeys().get(encryptedFile.getMetadataKey()),
  265. privateKey));
  266. }
  267. // decrypt
  268. String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
  269. decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
  270. new TypeToken<DecryptedFolderMetadata.Data>() {
  271. }));
  272. files.put(key, decryptedFile);
  273. }
  274. }
  275. // verify checksum
  276. String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC);
  277. String checksum = EncryptionUtils.generateChecksum(decryptedFolderMetadata, mnemonic);
  278. String decryptedFolderChecksum = decryptedFolderMetadata.getMetadata().getChecksum();
  279. if (TextUtils.isEmpty(decryptedFolderChecksum) &&
  280. isFolderMigrated(remoteId, user, arbitraryDataProvider)) {
  281. reportE2eError(arbitraryDataProvider, user);
  282. throw new IllegalStateException("Possible downgrade attack detected!");
  283. }
  284. if (!TextUtils.isEmpty(decryptedFolderChecksum) && !decryptedFolderChecksum.equals(checksum)) {
  285. reportE2eError(arbitraryDataProvider, user);
  286. throw new IllegalStateException("Wrong checksum!");
  287. }
  288. Map<String, EncryptedFiledrop> fileDrop = encryptedFolderMetadata.getFiledrop();
  289. if (fileDrop != null) {
  290. for (Map.Entry<String, EncryptedFiledrop> entry : fileDrop.entrySet()) {
  291. String key = entry.getKey();
  292. EncryptedFiledrop encryptedFile = entry.getValue();
  293. // decrypt key
  294. String encryptedKey = decryptStringAsymmetric(encryptedFile.getEncryptedKey(),
  295. privateKey);
  296. // decrypt encrypted blob with key
  297. String decryptedData = decryptStringSymmetric(
  298. encryptedFile.getEncrypted(),
  299. decodeStringToBase64Bytes(encryptedKey),
  300. decodeStringToBase64Bytes(encryptedFile.getEncryptedInitializationVector()),
  301. decodeStringToBase64Bytes(encryptedFile.getEncryptedTag()),
  302. arbitraryDataProvider,
  303. user
  304. );
  305. DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
  306. decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
  307. decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
  308. decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(decryptedData,
  309. new TypeToken<DecryptedFolderMetadata.Data>() {
  310. }));
  311. files.put(key, decryptedFile);
  312. // remove from filedrop
  313. fileDrop.remove(key);
  314. }
  315. }
  316. return decryptedFolderMetadata;
  317. }
  318. /**
  319. * Download metadata for folder and decrypt it
  320. *
  321. * @return decrypted metadata or null
  322. */
  323. public static @Nullable
  324. DecryptedFolderMetadata downloadFolderMetadata(OCFile folder,
  325. OwnCloudClient client,
  326. Context context,
  327. User user) {
  328. RemoteOperationResult getMetadataOperationResult = new GetMetadataRemoteOperation(folder.getLocalId())
  329. .execute(client);
  330. if (!getMetadataOperationResult.isSuccess()) {
  331. return null;
  332. }
  333. // decrypt metadata
  334. ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
  335. String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
  336. String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
  337. String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
  338. EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
  339. serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
  340. });
  341. try {
  342. int filesDropCountBefore = 0;
  343. if (encryptedFolderMetadata.getFiledrop() != null) {
  344. filesDropCountBefore = encryptedFolderMetadata.getFiledrop().size();
  345. }
  346. DecryptedFolderMetadata decryptedFolderMetadata = EncryptionUtils.decryptFolderMetaData(
  347. encryptedFolderMetadata,
  348. privateKey,
  349. arbitraryDataProvider,
  350. user,
  351. folder.getLocalId());
  352. boolean transferredFiledrop = filesDropCountBefore > 0 && decryptedFolderMetadata.getFiles().size() ==
  353. encryptedFolderMetadata.getFiles().size() + filesDropCountBefore;
  354. if (transferredFiledrop) {
  355. // lock folder
  356. String token = EncryptionUtils.lockFolder(folder, client);
  357. // upload metadata
  358. EncryptedFolderMetadata encryptedFolderMetadataNew = encryptFolderMetadata(decryptedFolderMetadata,
  359. publicKey,
  360. arbitraryDataProvider,
  361. user,
  362. folder.getLocalId());
  363. String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadataNew);
  364. EncryptionUtils.uploadMetadata(folder,
  365. serializedFolderMetadata,
  366. token,
  367. client,
  368. true,
  369. arbitraryDataProvider,
  370. user);
  371. // unlock folder
  372. RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(folder, client, token);
  373. if (!unlockFolderResult.isSuccess()) {
  374. Log_OC.e(TAG, unlockFolderResult.getMessage());
  375. return null;
  376. }
  377. }
  378. return decryptedFolderMetadata;
  379. } catch (Exception e) {
  380. Log_OC.e(TAG, e.getMessage());
  381. return null;
  382. }
  383. }
  384. /*
  385. BASE 64
  386. */
  387. public static byte[] encodeStringToBase64Bytes(String string) {
  388. try {
  389. return Base64.encode(string.getBytes(), Base64.NO_WRAP);
  390. } catch (Exception e) {
  391. return new byte[0];
  392. }
  393. }
  394. public static String decodeBase64BytesToString(byte[] bytes) {
  395. try {
  396. return new String(Base64.decode(bytes, Base64.NO_WRAP));
  397. } catch (Exception e) {
  398. return "";
  399. }
  400. }
  401. public static String encodeBytesToBase64String(byte[] bytes) {
  402. return Base64.encodeToString(bytes, Base64.NO_WRAP);
  403. }
  404. public static byte[] decodeStringToBase64Bytes(String string) {
  405. return Base64.decode(string, Base64.NO_WRAP);
  406. }
  407. /*
  408. ENCRYPTION
  409. */
  410. /**
  411. * @param ocFile file do crypt
  412. * @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
  413. * @param iv initialization vector, either from metadata or
  414. * {@link EncryptionUtils#randomBytes(int)}
  415. * @return encryptedFile with encryptedBytes and authenticationTag
  416. */
  417. public static EncryptedFile encryptFile(OCFile ocFile, byte[] encryptionKeyBytes, byte[] iv)
  418. throws NoSuchAlgorithmException,
  419. InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
  420. BadPaddingException, IllegalBlockSizeException, IOException {
  421. File file = new File(ocFile.getStoragePath());
  422. return encryptFile(file, encryptionKeyBytes, iv);
  423. }
  424. /**
  425. * @param file file do crypt
  426. * @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
  427. * @param iv initialization vector, either from metadata or
  428. * {@link EncryptionUtils#randomBytes(int)}
  429. * @return encryptedFile with encryptedBytes and authenticationTag
  430. */
  431. public static EncryptedFile encryptFile(File file, byte[] encryptionKeyBytes, byte[] iv)
  432. throws NoSuchAlgorithmException,
  433. InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
  434. BadPaddingException, IllegalBlockSizeException, IOException {
  435. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  436. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  437. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  438. cipher.init(Cipher.ENCRYPT_MODE, key, spec);
  439. RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
  440. byte[] fileBytes = new byte[(int) randomAccessFile.length()];
  441. randomAccessFile.readFully(fileBytes);
  442. byte[] cryptedBytes = cipher.doFinal(fileBytes);
  443. String authenticationTag = encodeBytesToBase64String(Arrays.copyOfRange(cryptedBytes,
  444. cryptedBytes.length - (128 / 8), cryptedBytes.length));
  445. return new EncryptedFile(cryptedBytes, authenticationTag);
  446. }
  447. /**
  448. * @param file encrypted file
  449. * @param encryptionKeyBytes key from metadata
  450. * @param iv initialization vector from metadata
  451. * @param authenticationTag authenticationTag from metadata
  452. * @return decrypted byte[]
  453. */
  454. public static byte[] decryptFile(File file,
  455. byte[] encryptionKeyBytes,
  456. byte[] iv,
  457. byte[] authenticationTag,
  458. ArbitraryDataProvider arbitraryDataProvider,
  459. User user)
  460. throws NoSuchAlgorithmException,
  461. InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
  462. BadPaddingException, IllegalBlockSizeException, IOException {
  463. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  464. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  465. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  466. cipher.init(Cipher.DECRYPT_MODE, key, spec);
  467. RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
  468. byte[] fileBytes = new byte[(int) randomAccessFile.length()];
  469. randomAccessFile.readFully(fileBytes);
  470. // check authentication tag
  471. byte[] extractedAuthenticationTag = Arrays.copyOfRange(fileBytes,
  472. fileBytes.length - (128 / 8), fileBytes.length);
  473. if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
  474. reportE2eError(arbitraryDataProvider, user);
  475. throw new SecurityException("Tag not correct");
  476. }
  477. return cipher.doFinal(fileBytes);
  478. }
  479. public static class EncryptedFile {
  480. public byte[] encryptedBytes;
  481. public String authenticationTag;
  482. public EncryptedFile(byte[] encryptedBytes, String authenticationTag) {
  483. this.encryptedBytes = encryptedBytes;
  484. this.authenticationTag = authenticationTag;
  485. }
  486. }
  487. /**
  488. * Encrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
  489. * and public key
  490. *
  491. * @param string String to encrypt
  492. * @param cert contains public key in it
  493. * @return encrypted string
  494. */
  495. public static String encryptStringAsymmetric(String string, String cert)
  496. throws NoSuchAlgorithmException,
  497. NoSuchPaddingException, InvalidKeyException,
  498. BadPaddingException, IllegalBlockSizeException,
  499. CertificateException {
  500. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  501. String trimmedCert = cert.replace("-----BEGIN CERTIFICATE-----\n", "")
  502. .replace("-----END CERTIFICATE-----\n", "");
  503. byte[] encodedCert = trimmedCert.getBytes(StandardCharsets.UTF_8);
  504. byte[] decodedCert = org.apache.commons.codec.binary.Base64.decodeBase64(encodedCert);
  505. CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
  506. InputStream in = new ByteArrayInputStream(decodedCert);
  507. X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(in);
  508. PublicKey realPublicKey = certificate.getPublicKey();
  509. cipher.init(Cipher.ENCRYPT_MODE, realPublicKey);
  510. byte[] bytes = encodeStringToBase64Bytes(string);
  511. byte[] cryptedBytes = cipher.doFinal(bytes);
  512. return encodeBytesToBase64String(cryptedBytes);
  513. }
  514. public static String encryptStringAsymmetric(String string, PublicKey publicKey) throws NoSuchPaddingException,
  515. NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
  516. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  517. cipher.init(Cipher.ENCRYPT_MODE, publicKey);
  518. byte[] bytes = encodeStringToBase64Bytes(string);
  519. byte[] cryptedBytes = cipher.doFinal(bytes);
  520. return encodeBytesToBase64String(cryptedBytes);
  521. }
  522. /**
  523. * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
  524. * and public key
  525. *
  526. * @param string string to decrypt
  527. * @param privateKeyString private key
  528. * @return decrypted string
  529. */
  530. public static String decryptStringAsymmetric(String string, String privateKeyString)
  531. throws NoSuchAlgorithmException,
  532. NoSuchPaddingException, InvalidKeyException,
  533. BadPaddingException, IllegalBlockSizeException,
  534. InvalidKeySpecException {
  535. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  536. byte[] privateKeyBytes = decodeStringToBase64Bytes(privateKeyString);
  537. PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
  538. KeyFactory kf = KeyFactory.getInstance(RSA);
  539. PrivateKey privateKey = kf.generatePrivate(keySpec);
  540. cipher.init(Cipher.DECRYPT_MODE, privateKey);
  541. byte[] bytes = decodeStringToBase64Bytes(string);
  542. byte[] encodedBytes = cipher.doFinal(bytes);
  543. return decodeBase64BytesToString(encodedBytes);
  544. }
  545. public static String decryptStringAsymmetric(String string, PrivateKey privateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
  546. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  547. cipher.init(Cipher.DECRYPT_MODE, privateKey);
  548. byte[] bytes = decodeStringToBase64Bytes(string);
  549. byte[] encodedBytes = cipher.doFinal(bytes);
  550. return decodeBase64BytesToString(encodedBytes);
  551. }
  552. /**
  553. * Encrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
  554. * and public key
  555. *
  556. * @param string String to encrypt
  557. * @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
  558. * @return encrypted string
  559. */
  560. public static String encryptStringSymmetric(String string, byte[] encryptionKeyBytes)
  561. throws NoSuchPaddingException,
  562. InvalidKeyException,
  563. NoSuchAlgorithmException,
  564. IllegalBlockSizeException,
  565. BadPaddingException,
  566. InvalidAlgorithmParameterException {
  567. return encryptStringSymmetric(string, encryptionKeyBytes, ivDelimiter);
  568. }
  569. @VisibleForTesting
  570. public static String encryptStringSymmetricOld(String string, byte[] encryptionKeyBytes)
  571. throws NoSuchPaddingException,
  572. InvalidKeyException,
  573. NoSuchAlgorithmException,
  574. IllegalBlockSizeException,
  575. BadPaddingException,
  576. InvalidAlgorithmParameterException {
  577. return encryptStringSymmetric(string, encryptionKeyBytes, ivDelimiterOld);
  578. }
  579. private static String encryptStringSymmetric(String string,
  580. byte[] encryptionKeyBytes,
  581. String delimiter)
  582. throws NoSuchAlgorithmException,
  583. InvalidAlgorithmParameterException,
  584. NoSuchPaddingException,
  585. InvalidKeyException,
  586. BadPaddingException,
  587. IllegalBlockSizeException {
  588. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  589. byte[] iv = randomBytes(ivLength);
  590. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  591. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  592. cipher.init(Cipher.ENCRYPT_MODE, key, spec);
  593. byte[] bytes = encodeStringToBase64Bytes(string);
  594. byte[] cryptedBytes = cipher.doFinal(bytes);
  595. String encodedCryptedBytes = encodeBytesToBase64String(cryptedBytes);
  596. String encodedIV = encodeBytesToBase64String(iv);
  597. return encodedCryptedBytes + delimiter + encodedIV;
  598. }
  599. public static String decryptStringSymmetric(String string,
  600. byte[] encryptionKeyBytes,
  601. byte[] iv,
  602. byte[] authenticationTag,
  603. ArbitraryDataProvider arbitraryDataProvider,
  604. User user)
  605. throws NoSuchPaddingException,
  606. NoSuchAlgorithmException,
  607. InvalidAlgorithmParameterException,
  608. InvalidKeyException,
  609. IllegalBlockSizeException,
  610. BadPaddingException {
  611. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  612. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  613. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  614. cipher.init(Cipher.DECRYPT_MODE, key, spec);
  615. byte[] bytes = decodeStringToBase64Bytes(string);
  616. // check authentication tag
  617. byte[] extractedAuthenticationTag = Arrays.copyOfRange(bytes,
  618. bytes.length - (128 / 8),
  619. bytes.length);
  620. if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
  621. reportE2eError(arbitraryDataProvider, user);
  622. throw new SecurityException("Tag not correct");
  623. }
  624. byte[] encodedBytes = cipher.doFinal(bytes);
  625. return decodeBase64BytesToString(encodedBytes);
  626. }
  627. /**
  628. * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
  629. * and public key
  630. *
  631. * @param string string to decrypt
  632. * @param encryptionKeyBytes key from metadata
  633. * @return decrypted string
  634. */
  635. public static String decryptStringSymmetric(String string, byte[] encryptionKeyBytes)
  636. throws NoSuchAlgorithmException,
  637. InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
  638. BadPaddingException, IllegalBlockSizeException {
  639. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  640. String ivString;
  641. int delimiterPosition = string.lastIndexOf(ivDelimiter);
  642. if (delimiterPosition == -1) {
  643. // backward compatibility
  644. delimiterPosition = string.lastIndexOf(ivDelimiterOld);
  645. ivString = string.substring(delimiterPosition + ivDelimiterOld.length());
  646. } else {
  647. ivString = string.substring(delimiterPosition + ivDelimiter.length());
  648. }
  649. String cipherString = string.substring(0, delimiterPosition);
  650. byte[] iv = new IvParameterSpec(decodeStringToBase64Bytes(ivString)).getIV();
  651. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  652. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  653. cipher.init(Cipher.DECRYPT_MODE, key, spec);
  654. byte[] bytes = decodeStringToBase64Bytes(cipherString);
  655. byte[] encodedBytes = cipher.doFinal(bytes);
  656. return decodeBase64BytesToString(encodedBytes);
  657. }
  658. /**
  659. * Encrypt private key with symmetric AES encryption, GCM mode mode and no padding
  660. *
  661. * @param privateKey byte64 encoded string representation of private key
  662. * @param keyPhrase key used for encryption, e.g. 12 random words
  663. * {@link EncryptionUtils#getRandomWords(int, Context)}
  664. * @return encrypted string, bytes first encoded base64, IV separated with "|", then to string
  665. */
  666. public static String encryptPrivateKey(String privateKey, String keyPhrase)
  667. throws NoSuchPaddingException,
  668. NoSuchAlgorithmException,
  669. InvalidKeyException,
  670. BadPaddingException,
  671. IllegalBlockSizeException,
  672. InvalidKeySpecException {
  673. return encryptPrivateKey(privateKey, keyPhrase, ivDelimiter);
  674. }
  675. @VisibleForTesting
  676. public static String encryptPrivateKeyOld(String privateKey, String keyPhrase)
  677. throws NoSuchPaddingException,
  678. NoSuchAlgorithmException,
  679. InvalidKeyException,
  680. BadPaddingException,
  681. IllegalBlockSizeException,
  682. InvalidKeySpecException {
  683. return encryptPrivateKey(privateKey, keyPhrase, ivDelimiterOld);
  684. }
  685. private static String encryptPrivateKey(String privateKey, String keyPhrase, String delimiter)
  686. throws NoSuchPaddingException,
  687. NoSuchAlgorithmException,
  688. InvalidKeyException,
  689. BadPaddingException,
  690. IllegalBlockSizeException,
  691. InvalidKeySpecException {
  692. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  693. SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
  694. byte[] salt = randomBytes(saltLength);
  695. KeySpec spec = new PBEKeySpec(keyPhrase.toCharArray(), salt, iterationCount, keyStrength);
  696. SecretKey tmp = factory.generateSecret(spec);
  697. SecretKeySpec key = new SecretKeySpec(tmp.getEncoded(), AES);
  698. cipher.init(Cipher.ENCRYPT_MODE, key);
  699. byte[] bytes = encodeStringToBase64Bytes(privateKey);
  700. byte[] encrypted = cipher.doFinal(bytes);
  701. byte[] iv = cipher.getIV();
  702. String encodedIV = encodeBytesToBase64String(iv);
  703. String encodedSalt = encodeBytesToBase64String(salt);
  704. String encodedEncryptedBytes = encodeBytesToBase64String(encrypted);
  705. return encodedEncryptedBytes + delimiter + encodedIV + delimiter + encodedSalt;
  706. }
  707. /**
  708. * Decrypt private key with symmetric AES encryption, GCM mode mode and no padding
  709. *
  710. * @param privateKey byte64 encoded string representation of private key, IV separated with "|"
  711. * @param keyPhrase key used for encryption, e.g. 12 random words
  712. * {@link EncryptionUtils#getRandomWords(int, Context)}
  713. * @return decrypted string
  714. */
  715. @SuppressFBWarnings("UCPM_USE_CHARACTER_PARAMETERIZED_METHOD")
  716. public static String decryptPrivateKey(String privateKey, String keyPhrase) throws NoSuchPaddingException,
  717. NoSuchAlgorithmException, InvalidKeyException, BadPaddingException,
  718. IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException {
  719. String[] strings;
  720. // split up iv, salt
  721. if (privateKey.lastIndexOf(ivDelimiter) == -1) {
  722. // backward compatibility
  723. strings = privateKey.split(ivDelimiterOld);
  724. } else {
  725. strings = privateKey.split("\\" + ivDelimiter);
  726. }
  727. String realPrivateKey = strings[0];
  728. byte[] iv = decodeStringToBase64Bytes(strings[1]);
  729. byte[] salt = decodeStringToBase64Bytes(strings[2]);
  730. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  731. SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
  732. KeySpec spec = new PBEKeySpec(keyPhrase.toCharArray(), salt, iterationCount, keyStrength);
  733. SecretKey tmp = factory.generateSecret(spec);
  734. SecretKeySpec key = new SecretKeySpec(tmp.getEncoded(), AES);
  735. cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
  736. byte[] bytes = decodeStringToBase64Bytes(realPrivateKey);
  737. byte[] decrypted = cipher.doFinal(bytes);
  738. String pemKey = decodeBase64BytesToString(decrypted);
  739. return pemKey.replaceAll("\n", "").replace("-----BEGIN PRIVATE KEY-----", "")
  740. .replace("-----END PRIVATE KEY-----", "");
  741. }
  742. public static String privateKeyToPEM(PrivateKey privateKey) {
  743. String privateKeyString = encodeBytesToBase64String(privateKey.getEncoded());
  744. return "-----BEGIN PRIVATE KEY-----\n" + privateKeyString.replaceAll("(.{65})", "$1\n")
  745. + "\n-----END PRIVATE KEY-----";
  746. }
  747. /*
  748. Helper
  749. */
  750. public static ArrayList<String> getRandomWords(int count, Context context) throws IOException {
  751. InputStream ins = context.getResources().openRawResource(R.raw.encryption_key_words);
  752. InputStreamReader inputStreamReader = new InputStreamReader(ins);
  753. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  754. List<String> lines = new ArrayList<>();
  755. String line;
  756. while ((line = bufferedReader.readLine()) != null) {
  757. lines.add(line);
  758. }
  759. SecureRandom random = new SecureRandom();
  760. ArrayList<String> outputLines = Lists.newArrayListWithCapacity(count);
  761. for (int i = 0; i < count; i++) {
  762. int randomLine = random.nextInt(lines.size());
  763. outputLines.add(lines.get(randomLine));
  764. }
  765. return outputLines;
  766. }
  767. public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
  768. KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA);
  769. keyGen.initialize(2048, new SecureRandom());
  770. return keyGen.generateKeyPair();
  771. }
  772. public static byte[] generateKey() {
  773. KeyGenerator keyGenerator;
  774. try {
  775. keyGenerator = KeyGenerator.getInstance(AES);
  776. keyGenerator.init(128);
  777. return keyGenerator.generateKey().getEncoded();
  778. } catch (NoSuchAlgorithmException e) {
  779. Log_OC.e(TAG, e.getMessage());
  780. }
  781. return null;
  782. }
  783. public static byte[] randomBytes(int size) {
  784. SecureRandom random = new SecureRandom();
  785. final byte[] iv = new byte[size];
  786. random.nextBytes(iv);
  787. return iv;
  788. }
  789. /**
  790. * Generate a SHA512 with appended salt
  791. *
  792. * @param token token to be hashed
  793. * @return SHA512 with appended salt, delimiter HASH_DELIMITER
  794. */
  795. public static String generateSHA512(String token) {
  796. String salt = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.randomBytes(EncryptionUtils.saltLength));
  797. return generateSHA512(token, salt);
  798. }
  799. /**
  800. * Generate a SHA512 with appended salt
  801. *
  802. * @param token token to be hashed
  803. * @return SHA512 with appended salt, delimiter HASH_DELIMITER
  804. */
  805. public static String generateSHA512(String token, String salt) {
  806. MessageDigest digest;
  807. String hashedToken = "";
  808. byte[] hash;
  809. try {
  810. digest = MessageDigest.getInstance("SHA-512");
  811. digest.update(salt.getBytes());
  812. hash = digest.digest(token.getBytes());
  813. StringBuilder stringBuilder = new StringBuilder();
  814. for (byte hashByte : hash) {
  815. stringBuilder.append(Integer.toString((hashByte & 0xff) + 0x100, 16).substring(1));
  816. }
  817. stringBuilder.append(HASH_DELIMITER).append(salt);
  818. hashedToken = stringBuilder.toString();
  819. } catch (NoSuchAlgorithmException e) {
  820. Log_OC.e(TAG, "Generating SHA512 failed", e);
  821. }
  822. return hashedToken;
  823. }
  824. public static boolean verifySHA512(String hashWithSalt, String compareToken) {
  825. String salt = hashWithSalt.split("\\" + HASH_DELIMITER)[1];
  826. String newHash = generateSHA512(compareToken, salt);
  827. return hashWithSalt.equals(newHash);
  828. }
  829. public static String lockFolder(OCFile parentFile, OwnCloudClient client) throws UploadException {
  830. // Lock folder
  831. LockFileRemoteOperation lockFileOperation = new LockFileRemoteOperation(parentFile.getLocalId());
  832. RemoteOperationResult lockFileOperationResult = lockFileOperation.execute(client);
  833. if (lockFileOperationResult.isSuccess() &&
  834. !TextUtils.isEmpty((String) lockFileOperationResult.getData().get(0))) {
  835. return (String) lockFileOperationResult.getData().get(0);
  836. } else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
  837. throw new UploadException("Forbidden! Please try again later.)");
  838. } else {
  839. throw new UploadException("Could not lock folder");
  840. }
  841. }
  842. /**
  843. * @param parentFile file metadata should be retrieved for
  844. * @return Pair: boolean: true: metadata already exists, false: metadata new created
  845. */
  846. public static Pair<Boolean, DecryptedFolderMetadata> retrieveMetadata(OCFile parentFile,
  847. OwnCloudClient client,
  848. String privateKey,
  849. String publicKey,
  850. ArbitraryDataProvider arbitraryDataProvider,
  851. User user)
  852. throws UploadException,
  853. InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException,
  854. IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException {
  855. long localId = parentFile.getLocalId();
  856. GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(localId);
  857. RemoteOperationResult getMetadataOperationResult = getMetadataOperation.execute(client);
  858. DecryptedFolderMetadata metadata;
  859. if (getMetadataOperationResult.isSuccess()) {
  860. // decrypt metadata
  861. String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
  862. EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
  863. serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
  864. });
  865. return new Pair<>(Boolean.TRUE, decryptFolderMetaData(encryptedFolderMetadata,
  866. privateKey,
  867. arbitraryDataProvider,
  868. user,
  869. localId));
  870. } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
  871. // new metadata
  872. metadata = new DecryptedFolderMetadata();
  873. metadata.setMetadata(new DecryptedFolderMetadata.Metadata());
  874. metadata.getMetadata().setMetadataKeys(new HashMap<>());
  875. String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
  876. String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
  877. metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
  878. return new Pair<>(Boolean.FALSE, metadata);
  879. } else {
  880. reportE2eError(arbitraryDataProvider, user);
  881. throw new UploadException("something wrong");
  882. }
  883. }
  884. public static void uploadMetadata(OCFile parentFile,
  885. String serializedFolderMetadata,
  886. String token,
  887. OwnCloudClient client,
  888. boolean metadataExists,
  889. ArbitraryDataProvider arbitraryDataProvider,
  890. User user) throws UploadException {
  891. RemoteOperationResult uploadMetadataOperationResult;
  892. if (metadataExists) {
  893. // update metadata
  894. UpdateMetadataRemoteOperation storeMetadataOperation = new UpdateMetadataRemoteOperation(
  895. parentFile.getLocalId(), serializedFolderMetadata, token);
  896. uploadMetadataOperationResult = storeMetadataOperation.execute(client);
  897. } else {
  898. // store metadata
  899. StoreMetadataRemoteOperation storeMetadataOperation = new StoreMetadataRemoteOperation(
  900. parentFile.getLocalId(), serializedFolderMetadata);
  901. uploadMetadataOperationResult = storeMetadataOperation.execute(client);
  902. }
  903. if (!uploadMetadataOperationResult.isSuccess()) {
  904. reportE2eError(arbitraryDataProvider, user);
  905. throw new UploadException("Storing/updating metadata was not successful");
  906. }
  907. }
  908. public static RemoteOperationResult unlockFolder(OCFile parentFolder, OwnCloudClient client, String token) {
  909. if (token != null) {
  910. return new UnlockFileRemoteOperation(parentFolder.getLocalId(), token).execute(client);
  911. } else {
  912. return new RemoteOperationResult(new Exception("No token available"));
  913. }
  914. }
  915. public static RSAPublicKey convertPublicKeyFromString(String string) throws CertificateException {
  916. String trimmedCert = string.replace("-----BEGIN CERTIFICATE-----\n", "")
  917. .replace("-----END CERTIFICATE-----\n", "");
  918. byte[] encodedCert = trimmedCert.getBytes(StandardCharsets.UTF_8);
  919. byte[] decodedCert = org.apache.commons.codec.binary.Base64.decodeBase64(encodedCert);
  920. CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
  921. InputStream in = new ByteArrayInputStream(decodedCert);
  922. X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(in);
  923. return (RSAPublicKey) certificate.getPublicKey();
  924. }
  925. public static void removeE2E(ArbitraryDataProvider arbitraryDataProvider, User user) {
  926. // delete stored E2E keys and mnemonic
  927. arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
  928. arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
  929. arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.MNEMONIC);
  930. }
  931. public static boolean isMatchingKeys(KeyPair keyPair, String publicKeyString) throws CertificateException {
  932. // check key
  933. RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyPair.getPrivate();
  934. RSAPublicKey publicKey = EncryptionUtils.convertPublicKeyFromString(publicKeyString);
  935. BigInteger modulusPublic = publicKey.getModulus();
  936. BigInteger modulusPrivate = privateKey.getModulus();
  937. return modulusPrivate.compareTo(modulusPublic) == 0;
  938. }
  939. public static boolean supportsSecureFiledrop(OCFile file, User user) {
  940. return file.isEncrypted() &&
  941. file.isFolder() &&
  942. user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_26);
  943. }
  944. public static String generateChecksum(DecryptedFolderMetadata metadata,
  945. String mnemonic) throws NoSuchAlgorithmException {
  946. StringBuilder stringBuilder = new StringBuilder();
  947. stringBuilder.append(mnemonic.replaceAll(" ", ""));
  948. ArrayList<String> keys = new ArrayList<>(metadata.getFiles().keySet());
  949. Collections.sort(keys);
  950. for (String key : keys) {
  951. stringBuilder.append(key);
  952. }
  953. stringBuilder.append(metadata.getMetadata().getMetadataKey());
  954. // sha256 hash-sum
  955. return sha256(stringBuilder.toString());
  956. }
  957. /**
  958. * SHA-256 hash of metadata-key
  959. */
  960. public static String sha256(String string) throws NoSuchAlgorithmException {
  961. byte[] bytes = MessageDigest
  962. .getInstance("SHA-256")
  963. .digest(string.getBytes(StandardCharsets.UTF_8));
  964. return bytesToHex(bytes);
  965. }
  966. public static String bytesToHex(byte[] bytes) {
  967. StringBuilder result = new StringBuilder();
  968. for (byte individualByte : bytes) {
  969. result.append(Integer.toString((individualByte & 0xff) + 0x100, 16)
  970. .substring(1));
  971. }
  972. return result.toString();
  973. }
  974. public static void addIdToMigratedIds(long id,
  975. User user,
  976. ArbitraryDataProvider arbitraryDataProvider) {
  977. Gson gson = new Gson();
  978. String ids = arbitraryDataProvider.getValue(user, MIGRATED_FOLDER_IDS);
  979. ArrayList<Long> arrayList = gson.fromJson(ids, ArrayList.class);
  980. if (arrayList == null) {
  981. arrayList = new ArrayList<>();
  982. }
  983. if (arrayList.contains(id)) {
  984. // nothing to do here
  985. return;
  986. }
  987. arrayList.add(id);
  988. String json = gson.toJson(arrayList);
  989. arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(),
  990. MIGRATED_FOLDER_IDS,
  991. json);
  992. }
  993. public static boolean isFolderMigrated(long id,
  994. User user,
  995. ArbitraryDataProvider arbitraryDataProvider) {
  996. Gson gson = new Gson();
  997. String ids = arbitraryDataProvider.getValue(user, MIGRATED_FOLDER_IDS);
  998. ArrayList<Long> arrayList = gson.fromJson(ids, new TypeToken<List<Long>>() {
  999. }.getType());
  1000. if (arrayList == null) {
  1001. return false;
  1002. }
  1003. return arrayList.contains(id);
  1004. }
  1005. public static void reportE2eError(ArbitraryDataProvider arbitraryDataProvider, User user) {
  1006. arbitraryDataProvider.incrementValue(user.getAccountName(), ArbitraryDataProvider.E2E_ERRORS);
  1007. if (arbitraryDataProvider.getLongValue(user.getAccountName(),
  1008. ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP) == -1L) {
  1009. arbitraryDataProvider.storeOrUpdateKeyValue(
  1010. user.getAccountName(),
  1011. ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP,
  1012. System.currentTimeMillis() / 1000
  1013. );
  1014. }
  1015. }
  1016. @Nullable
  1017. public static Problem readE2eError(ArbitraryDataProvider arbitraryDataProvider, User user) {
  1018. int value = arbitraryDataProvider.getIntegerValue(user.getAccountName(),
  1019. ArbitraryDataProvider.E2E_ERRORS);
  1020. long timestamp = arbitraryDataProvider.getLongValue(user.getAccountName(),
  1021. ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP);
  1022. arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(),
  1023. ArbitraryDataProvider.E2E_ERRORS);
  1024. arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(),
  1025. ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP);
  1026. if (value > 0 && timestamp > 0) {
  1027. return new Problem(SendClientDiagnosticRemoteOperation.E2E_ERRORS, value, timestamp);
  1028. } else {
  1029. return null;
  1030. }
  1031. }
  1032. }