EncryptionUtils.java 51 KB

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