EncryptionUtils.java 73 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723
  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.os.Build;
  24. import android.text.TextUtils;
  25. import android.util.Base64;
  26. import android.util.Pair;
  27. import com.google.common.collect.Lists;
  28. import com.google.common.primitives.Bytes;
  29. import com.google.gson.Gson;
  30. import com.google.gson.GsonBuilder;
  31. import com.google.gson.reflect.TypeToken;
  32. import com.nextcloud.client.account.User;
  33. import com.owncloud.android.R;
  34. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  35. import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
  36. import com.owncloud.android.datamodel.EncryptedFiledrop;
  37. import com.owncloud.android.datamodel.FileDataStorageManager;
  38. import com.owncloud.android.datamodel.OCFile;
  39. import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile;
  40. import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
  41. import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedMetadata;
  42. import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFile;
  43. import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1;
  44. import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
  45. import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedUser;
  46. import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFolderMetadataFile;
  47. import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedMetadata;
  48. import com.owncloud.android.lib.common.OwnCloudClient;
  49. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  50. import com.owncloud.android.lib.common.utils.Log_OC;
  51. import com.owncloud.android.lib.resources.e2ee.GetMetadataRemoteOperation;
  52. import com.owncloud.android.lib.resources.e2ee.LockFileRemoteOperation;
  53. import com.owncloud.android.lib.resources.e2ee.MetadataResponse;
  54. import com.owncloud.android.lib.resources.e2ee.StoreMetadataRemoteOperation;
  55. import com.owncloud.android.lib.resources.e2ee.StoreMetadataV2RemoteOperation;
  56. import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
  57. import com.owncloud.android.lib.resources.e2ee.UnlockFileV1RemoteOperation;
  58. import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
  59. import com.owncloud.android.lib.resources.e2ee.UpdateMetadataV2RemoteOperation;
  60. import com.owncloud.android.lib.resources.files.model.ServerFileInterface;
  61. import com.owncloud.android.lib.resources.status.E2EVersion;
  62. import com.owncloud.android.lib.resources.status.NextcloudVersion;
  63. import com.owncloud.android.lib.resources.status.OCCapability;
  64. import com.owncloud.android.lib.resources.status.Problem;
  65. import com.owncloud.android.lib.resources.status.SendClientDiagnosticRemoteOperation;
  66. import com.owncloud.android.operations.UploadException;
  67. import com.owncloud.android.utils.theme.CapabilityUtils;
  68. import org.apache.commons.httpclient.HttpStatus;
  69. import java.io.BufferedReader;
  70. import java.io.ByteArrayInputStream;
  71. import java.io.ByteArrayOutputStream;
  72. import java.io.File;
  73. import java.io.FileInputStream;
  74. import java.io.FileOutputStream;
  75. import java.io.IOException;
  76. import java.io.InputStream;
  77. import java.io.InputStreamReader;
  78. import java.io.RandomAccessFile;
  79. import java.math.BigInteger;
  80. import java.nio.charset.StandardCharsets;
  81. import java.nio.file.Files;
  82. import java.security.InvalidAlgorithmParameterException;
  83. import java.security.InvalidKeyException;
  84. import java.security.Key;
  85. import java.security.KeyFactory;
  86. import java.security.KeyPair;
  87. import java.security.KeyPairGenerator;
  88. import java.security.MessageDigest;
  89. import java.security.NoSuchAlgorithmException;
  90. import java.security.PrivateKey;
  91. import java.security.PublicKey;
  92. import java.security.SecureRandom;
  93. import java.security.cert.CertificateException;
  94. import java.security.cert.CertificateFactory;
  95. import java.security.cert.X509Certificate;
  96. import java.security.interfaces.RSAPrivateCrtKey;
  97. import java.security.interfaces.RSAPublicKey;
  98. import java.security.spec.InvalidKeySpecException;
  99. import java.security.spec.InvalidParameterSpecException;
  100. import java.security.spec.KeySpec;
  101. import java.security.spec.PKCS8EncodedKeySpec;
  102. import java.util.ArrayList;
  103. import java.util.Arrays;
  104. import java.util.Collections;
  105. import java.util.HashMap;
  106. import java.util.List;
  107. import java.util.Map;
  108. import java.util.UUID;
  109. import javax.crypto.BadPaddingException;
  110. import javax.crypto.Cipher;
  111. import javax.crypto.CipherInputStream;
  112. import javax.crypto.CipherOutputStream;
  113. import javax.crypto.IllegalBlockSizeException;
  114. import javax.crypto.KeyGenerator;
  115. import javax.crypto.NoSuchPaddingException;
  116. import javax.crypto.SecretKey;
  117. import javax.crypto.SecretKeyFactory;
  118. import javax.crypto.spec.GCMParameterSpec;
  119. import javax.crypto.spec.IvParameterSpec;
  120. import javax.crypto.spec.PBEKeySpec;
  121. import javax.crypto.spec.SecretKeySpec;
  122. import androidx.annotation.Nullable;
  123. import androidx.annotation.RequiresApi;
  124. import androidx.annotation.VisibleForTesting;
  125. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  126. /**
  127. * Utils for encryption
  128. */
  129. public final class EncryptionUtils {
  130. private static final String TAG = EncryptionUtils.class.getSimpleName();
  131. public static final String PUBLIC_KEY = "PUBLIC_KEY";
  132. public static final String PRIVATE_KEY = "PRIVATE_KEY";
  133. public static final String MNEMONIC = "MNEMONIC";
  134. public static final int ivLength = 16;
  135. public static final int saltLength = 40;
  136. public static final String ivDelimiter = "|"; // not base64 encoded
  137. public static final String ivDelimiterOld = "fA=="; // "|" base64 encoded
  138. private static final char HASH_DELIMITER = '$';
  139. private static final int iterationCount = 1024;
  140. private static final int keyStrength = 256;
  141. private static final String AES_CIPHER = "AES/GCM/NoPadding";
  142. private static final String AES = "AES";
  143. public static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
  144. public static final String RSA = "RSA";
  145. @VisibleForTesting
  146. public static final String MIGRATED_FOLDER_IDS = "MIGRATED_FOLDER_IDS";
  147. private EncryptionUtils() {
  148. // utility class -> private constructor
  149. }
  150. /*
  151. JSON
  152. */
  153. public static <T> T deserializeJSON(String json, TypeToken<T> type, boolean excludeTransient) {
  154. if (excludeTransient) {
  155. return new Gson().fromJson(json, type.getType());
  156. } else {
  157. return new GsonBuilder().excludeFieldsWithModifiers(0).create().fromJson(json, type.getType());
  158. }
  159. }
  160. public static <T> T deserializeJSON(String json, TypeToken<T> type) {
  161. return deserializeJSON(json, type, false);
  162. }
  163. public static String serializeJSON(Object data, boolean excludeTransient) {
  164. if (excludeTransient) {
  165. return new GsonBuilder()
  166. .disableHtmlEscaping()
  167. .create()
  168. .toJson(data);
  169. } else {
  170. return new GsonBuilder()
  171. .disableHtmlEscaping()
  172. .excludeFieldsWithModifiers(0)
  173. .create()
  174. .toJson(data);
  175. }
  176. }
  177. public static String serializeJSON(Object data) {
  178. return serializeJSON(data, false);
  179. }
  180. /*
  181. METADATA
  182. */
  183. /**
  184. * Encrypt folder metaData V1
  185. *
  186. * @param decryptedFolderMetadata folder metaData to encrypt
  187. * @return EncryptedFolderMetadataFile encrypted folder metadata
  188. */
  189. public static EncryptedFolderMetadataFileV1 encryptFolderMetadata(
  190. DecryptedFolderMetadataFileV1 decryptedFolderMetadata,
  191. String publicKey,
  192. long parentId,
  193. User user,
  194. ArbitraryDataProvider arbitraryDataProvider
  195. )
  196. throws NoSuchAlgorithmException, InvalidKeyException,
  197. InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
  198. IllegalBlockSizeException, CertificateException {
  199. HashMap<String, EncryptedFolderMetadataFileV1.EncryptedFile> files = new HashMap<>();
  200. HashMap<String, EncryptedFiledrop> filesdrop = new HashMap<>();
  201. EncryptedFolderMetadataFileV1 encryptedFolderMetadata = new EncryptedFolderMetadataFileV1(decryptedFolderMetadata
  202. .getMetadata(),
  203. files,
  204. filesdrop);
  205. // set new metadata key
  206. byte[] metadataKeyBytes = EncryptionUtils.generateKey();
  207. String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(
  208. EncryptionUtils.encodeBytesToBase64String(metadataKeyBytes),
  209. publicKey);
  210. encryptedFolderMetadata.getMetadata().setMetadataKey(encryptedMetadataKey);
  211. // store that this folder has been migrated
  212. addIdToMigratedIds(parentId, user, arbitraryDataProvider);
  213. // Encrypt each file in "files"
  214. for (Map.Entry<String, DecryptedFile> entry : decryptedFolderMetadata
  215. .getFiles().entrySet()) {
  216. String key = entry.getKey();
  217. DecryptedFile decryptedFile = entry.getValue();
  218. EncryptedFolderMetadataFileV1.EncryptedFile encryptedFile = new EncryptedFolderMetadataFileV1.EncryptedFile();
  219. encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
  220. encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
  221. // encrypt
  222. String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
  223. encryptedFile.setEncrypted(EncryptionUtils.encryptStringSymmetricAsString(dataJson, metadataKeyBytes));
  224. files.put(key, encryptedFile);
  225. }
  226. // set checksum
  227. String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC).trim();
  228. String checksum = EncryptionUtils.generateChecksum(decryptedFolderMetadata, mnemonic);
  229. encryptedFolderMetadata.getMetadata().setChecksum(checksum);
  230. return encryptedFolderMetadata;
  231. }
  232. /**
  233. * normally done on server only internal test
  234. */
  235. @VisibleForTesting
  236. public static void encryptFileDropFiles(DecryptedFolderMetadataFileV1 decryptedFolderMetadata,
  237. EncryptedFolderMetadataFileV1 encryptedFolderMetadata,
  238. String cert) throws NoSuchAlgorithmException, NoSuchPaddingException,
  239. InvalidKeyException, BadPaddingException, IllegalBlockSizeException, CertificateException,
  240. InvalidAlgorithmParameterException {
  241. final Map<String, EncryptedFiledrop> filesdrop = encryptedFolderMetadata.getFiledrop();
  242. for (Map.Entry<String, DecryptedFile> entry : decryptedFolderMetadata
  243. .getFiledrop().entrySet()) {
  244. String key = entry.getKey();
  245. DecryptedFile decryptedFile = entry.getValue();
  246. byte[] byt = generateKey();
  247. String metadataKey0 = encodeBytesToBase64String(byt);
  248. String enc = encryptStringAsymmetric(metadataKey0, cert);
  249. String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
  250. String encJson = encryptStringSymmetricAsString(dataJson, byt);
  251. int delimiterPosition = encJson.lastIndexOf(ivDelimiter);
  252. String encryptedInitializationVector = encJson.substring(delimiterPosition + ivDelimiter.length());
  253. String encodedCryptedBytes = encJson.substring(0, delimiterPosition);
  254. byte[] bytes = decodeStringToBase64Bytes(encodedCryptedBytes);
  255. // check authentication tag
  256. byte[] extractedAuthenticationTag = Arrays.copyOfRange(bytes,
  257. bytes.length - (128 / 8),
  258. bytes.length);
  259. String encryptedTag = encodeBytesToBase64String(extractedAuthenticationTag);
  260. EncryptedFiledrop encryptedFile = new EncryptedFiledrop(encodedCryptedBytes,
  261. decryptedFile.getInitializationVector(),
  262. decryptedFile.getAuthenticationTag(),
  263. enc,
  264. encryptedTag,
  265. encryptedInitializationVector);
  266. filesdrop.put(key, encryptedFile);
  267. }
  268. }
  269. /*
  270. * decrypt folder metaData V1 with private key
  271. */
  272. public static DecryptedFolderMetadataFileV1 decryptFolderMetaData(EncryptedFolderMetadataFileV1 encryptedFolderMetadata,
  273. String privateKey,
  274. ArbitraryDataProvider arbitraryDataProvider,
  275. User user,
  276. long remoteId)
  277. throws NoSuchAlgorithmException, InvalidKeyException,
  278. InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
  279. IllegalBlockSizeException, InvalidKeySpecException {
  280. HashMap<String, DecryptedFile> files = new HashMap<>();
  281. DecryptedFolderMetadataFileV1 decryptedFolderMetadata = new DecryptedFolderMetadataFileV1(
  282. encryptedFolderMetadata.getMetadata(), files);
  283. byte[] decryptedMetadataKey = null;
  284. String encryptedMetadataKey = decryptedFolderMetadata.getMetadata().getMetadataKey();
  285. if (encryptedMetadataKey != null) {
  286. decryptedMetadataKey = decodeStringToBase64Bytes(
  287. decryptStringAsymmetric(encryptedMetadataKey, privateKey));
  288. }
  289. if (encryptedFolderMetadata.getFiles() != null) {
  290. for (Map.Entry<String, EncryptedFolderMetadataFileV1.EncryptedFile> entry : encryptedFolderMetadata
  291. .getFiles().entrySet()) {
  292. String key = entry.getKey();
  293. EncryptedFolderMetadataFileV1.EncryptedFile encryptedFile = entry.getValue();
  294. DecryptedFile decryptedFile = new DecryptedFile();
  295. decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
  296. decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
  297. decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
  298. if (decryptedMetadataKey == null) {
  299. decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
  300. decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
  301. .getMetadataKeys().get(encryptedFile.getMetadataKey()),
  302. privateKey));
  303. }
  304. // decrypt
  305. String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
  306. decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
  307. new TypeToken<>() {
  308. }));
  309. files.put(key, decryptedFile);
  310. }
  311. }
  312. // verify checksum
  313. String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC).trim();
  314. String checksum = EncryptionUtils.generateChecksum(decryptedFolderMetadata, mnemonic);
  315. String decryptedFolderChecksum = decryptedFolderMetadata.getMetadata().getChecksum();
  316. if (TextUtils.isEmpty(decryptedFolderChecksum) &&
  317. isFolderMigrated(remoteId, user, arbitraryDataProvider)) {
  318. reportE2eError(arbitraryDataProvider, user);
  319. throw new IllegalStateException("Possible downgrade attack detected!");
  320. }
  321. if (!TextUtils.isEmpty(decryptedFolderChecksum) && !decryptedFolderChecksum.equals(checksum)) {
  322. reportE2eError(arbitraryDataProvider, user);
  323. throw new IllegalStateException("Wrong checksum!");
  324. }
  325. Map<String, EncryptedFiledrop> fileDrop = encryptedFolderMetadata.getFiledrop();
  326. if (fileDrop != null) {
  327. for (Map.Entry<String, EncryptedFiledrop> entry : fileDrop.entrySet()) {
  328. String key = entry.getKey();
  329. EncryptedFiledrop encryptedFile = entry.getValue();
  330. // decrypt key
  331. String encryptedKey = decryptStringAsymmetric(encryptedFile.getEncryptedKey(),
  332. privateKey);
  333. // decrypt encrypted blob with key
  334. String decryptedData = decryptStringSymmetricAsString(
  335. encryptedFile.getEncrypted(),
  336. decodeStringToBase64Bytes(encryptedKey),
  337. decodeStringToBase64Bytes(encryptedFile.getEncryptedInitializationVector()),
  338. decodeStringToBase64Bytes(encryptedFile.getEncryptedTag()),
  339. arbitraryDataProvider,
  340. user
  341. );
  342. DecryptedFile decryptedFile = new DecryptedFile();
  343. decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
  344. decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
  345. decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(decryptedData,
  346. new TypeToken<>() {
  347. }));
  348. files.put(key, decryptedFile);
  349. // remove from filedrop
  350. fileDrop.remove(key);
  351. }
  352. }
  353. return decryptedFolderMetadata;
  354. }
  355. /**
  356. * Download metadata (v1 or v2) for folder and decrypt it
  357. *
  358. * @return decrypted v2 metadata or null
  359. */
  360. @SuppressFBWarnings("URV")
  361. public static @Nullable
  362. Object
  363. downloadFolderMetadata(OCFile folder,
  364. OwnCloudClient client,
  365. Context context,
  366. User user
  367. ) {
  368. RemoteOperationResult<MetadataResponse> getMetadataOperationResult = new GetMetadataRemoteOperation(folder.getLocalId())
  369. .execute(client);
  370. if (!getMetadataOperationResult.isSuccess()) {
  371. return null;
  372. }
  373. OCCapability capability = CapabilityUtils.getCapability(context);
  374. // decrypt metadata
  375. EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2();
  376. String serializedEncryptedMetadata = getMetadataOperationResult.getResultData().getMetadata();
  377. E2EVersion version = determinateVersion(serializedEncryptedMetadata);
  378. switch (version) {
  379. case UNKNOWN:
  380. Log_OC.e(TAG, "Unknown e2e state");
  381. return null;
  382. case V1_0, V1_1, V1_2:
  383. ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
  384. String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
  385. String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
  386. EncryptedFolderMetadataFileV1 encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
  387. serializedEncryptedMetadata, new TypeToken<>() {
  388. });
  389. try {
  390. DecryptedFolderMetadataFileV1 v1 = decryptFolderMetaData(encryptedFolderMetadata,
  391. privateKey,
  392. arbitraryDataProvider,
  393. user,
  394. folder.getLocalId());
  395. if (capability.getEndToEndEncryptionApiVersion().compareTo(E2EVersion.V2_0) >= 0) {
  396. new EncryptionUtilsV2().migrateV1ToV2andUpload(
  397. v1,
  398. client.getUserId(),
  399. publicKey,
  400. folder,
  401. new FileDataStorageManager(user, context.getContentResolver()),
  402. client,
  403. user,
  404. context
  405. );
  406. } else {
  407. return v1;
  408. }
  409. } catch (Exception e) {
  410. // TODO do not crash, but show meaningful error
  411. Log_OC.e(TAG, "Could not decrypt metadata for " + folder.getDecryptedFileName(), e);
  412. return null;
  413. }
  414. case V2_0:
  415. return encryptionUtilsV2.parseAnyMetadata(getMetadataOperationResult.getResultData(),
  416. user,
  417. client,
  418. context,
  419. folder);
  420. }
  421. return null;
  422. }
  423. public static E2EVersion determinateVersion(String metadata) {
  424. try {
  425. EncryptedFolderMetadataFileV1 v1 = EncryptionUtils.deserializeJSON(
  426. metadata,
  427. new TypeToken<>() {
  428. });
  429. double version = v1.getMetadata().getVersion();
  430. if (version == 1.0) {
  431. return E2EVersion.V1_0;
  432. } else if (version == 1.1) {
  433. return E2EVersion.V1_1;
  434. } else if (version == 1.2) {
  435. return E2EVersion.V1_2;
  436. } else {
  437. throw new IllegalStateException("Unknown version");
  438. }
  439. } catch (Exception e) {
  440. EncryptedFolderMetadataFile v2 = EncryptionUtils.deserializeJSON(
  441. metadata,
  442. new TypeToken<>() {
  443. });
  444. if ("2.0".equals(v2.getVersion()) || "2".equals(v2.getVersion())) {
  445. return E2EVersion.V2_0;
  446. }
  447. }
  448. return E2EVersion.UNKNOWN;
  449. }
  450. /*
  451. BASE 64
  452. */
  453. @SuppressFBWarnings({"DM", "MDM"})
  454. public static byte[] encodeStringToBase64Bytes(String string) {
  455. try {
  456. return Base64.encode(string.getBytes(), Base64.NO_WRAP);
  457. } catch (Exception e) {
  458. return new byte[0];
  459. }
  460. }
  461. @SuppressFBWarnings({"DM", "MDM"})
  462. public static String decodeBase64BytesToString(byte[] bytes) {
  463. try {
  464. return new String(Base64.decode(bytes, Base64.NO_WRAP));
  465. } catch (Exception e) {
  466. return "";
  467. }
  468. }
  469. @SuppressFBWarnings({"DM", "MDM"})
  470. public static String encodeBytesToBase64String(byte[] bytes) {
  471. return Base64.encodeToString(bytes, Base64.NO_WRAP);
  472. }
  473. @SuppressFBWarnings({"DM", "MDM"})
  474. public static String encodeStringToBase64String(String string) {
  475. return Base64.encodeToString(string.getBytes(), Base64.NO_WRAP);
  476. }
  477. @SuppressFBWarnings({"DM", "MDM"})
  478. public static String decodeBase64StringToString(String string) {
  479. return new String(Base64.decode(string, Base64.NO_WRAP));
  480. }
  481. public static byte[] decodeStringToBase64Bytes(String string) {
  482. return Base64.decode(string, Base64.NO_WRAP);
  483. }
  484. public static EncryptedFile encryptFile(File file, Cipher cipher) throws InvalidParameterSpecException {
  485. File encryptedFile = new File(file.getAbsolutePath() + ".enc");
  486. encryptFileWithGivenCipher(file, encryptedFile, cipher);
  487. String authenticationTagString = getAuthenticationTag(cipher);
  488. return new EncryptedFile(encryptedFile, authenticationTagString);
  489. }
  490. public static String getAuthenticationTag(Cipher cipher) throws InvalidParameterSpecException {
  491. byte[] authenticationTag = cipher.getParameters().getParameterSpec(GCMParameterSpec.class).getIV();
  492. return encodeBytesToBase64String(authenticationTag);
  493. }
  494. public static Cipher getCipher(int mode, byte[] encryptionKeyBytes, byte[] iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
  495. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  496. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  497. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  498. cipher.init(mode, key, spec);
  499. return cipher;
  500. }
  501. public static void encryptFileWithGivenCipher(File inputFile, File encryptedFile, Cipher cipher) {
  502. try( FileInputStream inputStream = new FileInputStream(inputFile);
  503. FileOutputStream fileOutputStream = new FileOutputStream(encryptedFile);
  504. CipherOutputStream outputStream = new CipherOutputStream(fileOutputStream, cipher)) {
  505. byte[] buffer = new byte[4096];
  506. int bytesRead;
  507. while ((bytesRead = inputStream.read(buffer)) != -1) {
  508. outputStream.write(buffer, 0, bytesRead);
  509. }
  510. outputStream.close();
  511. inputStream.close();
  512. Log_OC.d(TAG, encryptedFile.getName() + "encrypted successfully");
  513. } catch (IOException exception) {
  514. Log_OC.d(TAG, "Error caught at encryptFileWithGivenCipher(): " + exception.getLocalizedMessage());
  515. }
  516. }
  517. public static void decryptFile(Cipher cipher,
  518. File encryptedFile,
  519. File decryptedFile,
  520. String authenticationTag,
  521. ArbitraryDataProvider arbitraryDataProvider,
  522. User user) {
  523. try (FileInputStream inputStream = new FileInputStream(encryptedFile);
  524. FileOutputStream outputStream = new FileOutputStream(decryptedFile)) {
  525. byte[] buffer = new byte[4096];
  526. int bytesRead;
  527. while ((bytesRead = inputStream.read(buffer)) != -1) {
  528. byte[] output = cipher.update(buffer, 0, bytesRead);
  529. if (output != null) {
  530. outputStream.write(output);
  531. }
  532. }
  533. byte[] output = cipher.doFinal();
  534. if (output != null) {
  535. outputStream.write(output);
  536. }
  537. inputStream.close();
  538. outputStream.close();
  539. if (!getAuthenticationTag(cipher).equals(authenticationTag)) {
  540. reportE2eError(arbitraryDataProvider, user);
  541. throw new SecurityException("Tag not correct");
  542. }
  543. Log_OC.d(TAG, encryptedFile.getName() + "decrypted successfully");
  544. } catch (IOException | BadPaddingException | IllegalBlockSizeException | InvalidParameterSpecException |
  545. SecurityException exception) {
  546. Log_OC.d(TAG, "Error caught at decryptFile(): " + exception.getLocalizedMessage());
  547. }
  548. }
  549. /**
  550. * Encrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
  551. * and public key
  552. *
  553. * @param string String to encrypt
  554. * @param cert contains public key in it
  555. * @return encrypted string
  556. */
  557. public static String encryptStringAsymmetric(String string, String cert)
  558. throws NoSuchAlgorithmException,
  559. NoSuchPaddingException, InvalidKeyException,
  560. BadPaddingException, IllegalBlockSizeException,
  561. CertificateException {
  562. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  563. String trimmedCert = cert.replace("-----BEGIN CERTIFICATE-----\n", "")
  564. .replace("-----END CERTIFICATE-----\n", "");
  565. byte[] encodedCert = trimmedCert.getBytes(StandardCharsets.UTF_8);
  566. byte[] decodedCert = org.apache.commons.codec.binary.Base64.decodeBase64(encodedCert);
  567. CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
  568. InputStream in = new ByteArrayInputStream(decodedCert);
  569. X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(in);
  570. PublicKey realPublicKey = certificate.getPublicKey();
  571. cipher.init(Cipher.ENCRYPT_MODE, realPublicKey);
  572. byte[] bytes = encodeStringToBase64Bytes(string);
  573. byte[] cryptedBytes = cipher.doFinal(bytes);
  574. return encodeBytesToBase64String(cryptedBytes);
  575. }
  576. public static String encryptStringAsymmetricV2(byte[] bytes, String cert)
  577. throws NoSuchAlgorithmException,
  578. NoSuchPaddingException, InvalidKeyException,
  579. BadPaddingException, IllegalBlockSizeException,
  580. CertificateException {
  581. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  582. String trimmedCert = cert.replace("-----BEGIN CERTIFICATE-----\n", "")
  583. .replace("-----END CERTIFICATE-----\n", "");
  584. byte[] encodedCert = trimmedCert.getBytes(StandardCharsets.UTF_8);
  585. byte[] decodedCert = org.apache.commons.codec.binary.Base64.decodeBase64(encodedCert);
  586. CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
  587. InputStream in = new ByteArrayInputStream(decodedCert);
  588. X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(in);
  589. PublicKey realPublicKey = certificate.getPublicKey();
  590. cipher.init(Cipher.ENCRYPT_MODE, realPublicKey);
  591. byte[] cryptedBytes = cipher.doFinal(bytes);
  592. return encodeBytesToBase64String(cryptedBytes);
  593. }
  594. public static String encryptStringAsymmetric(String string, PublicKey publicKey) throws NoSuchPaddingException,
  595. NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
  596. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  597. cipher.init(Cipher.ENCRYPT_MODE, publicKey);
  598. byte[] bytes = encodeStringToBase64Bytes(string);
  599. byte[] cryptedBytes = cipher.doFinal(bytes);
  600. return encodeBytesToBase64String(cryptedBytes);
  601. }
  602. /**
  603. * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
  604. * and public key
  605. *
  606. * @param string string to decrypt
  607. * @param privateKeyString private key
  608. * @return decrypted string
  609. */
  610. public static String decryptStringAsymmetric(String string, String privateKeyString)
  611. throws NoSuchAlgorithmException,
  612. NoSuchPaddingException, InvalidKeyException,
  613. BadPaddingException, IllegalBlockSizeException,
  614. InvalidKeySpecException {
  615. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  616. byte[] privateKeyBytes = decodeStringToBase64Bytes(privateKeyString);
  617. PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
  618. KeyFactory kf = KeyFactory.getInstance(RSA);
  619. PrivateKey privateKey = kf.generatePrivate(keySpec);
  620. cipher.init(Cipher.DECRYPT_MODE, privateKey);
  621. byte[] bytes = decodeStringToBase64Bytes(string);
  622. byte[] encodedBytes = cipher.doFinal(bytes);
  623. return decodeBase64BytesToString(encodedBytes);
  624. }
  625. public static byte[] decryptStringAsymmetricAsBytes(String string, String privateKeyString)
  626. throws NoSuchAlgorithmException,
  627. NoSuchPaddingException, InvalidKeyException,
  628. BadPaddingException, IllegalBlockSizeException,
  629. InvalidKeySpecException {
  630. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  631. byte[] privateKeyBytes = decodeStringToBase64Bytes(privateKeyString);
  632. PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
  633. KeyFactory kf = KeyFactory.getInstance(RSA);
  634. PrivateKey privateKey = kf.generatePrivate(keySpec);
  635. cipher.init(Cipher.DECRYPT_MODE, privateKey);
  636. byte[] bytes = decodeStringToBase64Bytes(string);
  637. return cipher.doFinal(bytes);
  638. }
  639. public static byte[] decryptStringAsymmetricV2(String string, String privateKeyString)
  640. throws NoSuchAlgorithmException,
  641. NoSuchPaddingException, InvalidKeyException,
  642. BadPaddingException, IllegalBlockSizeException,
  643. InvalidKeySpecException {
  644. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  645. byte[] privateKeyBytes = decodeStringToBase64Bytes(privateKeyString);
  646. PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
  647. KeyFactory kf = KeyFactory.getInstance(RSA);
  648. PrivateKey privateKey = kf.generatePrivate(keySpec);
  649. cipher.init(Cipher.DECRYPT_MODE, privateKey);
  650. byte[] bytes;
  651. try {
  652. bytes = decodeStringToBase64Bytes(string);
  653. } catch (Exception e) {
  654. bytes = encodeStringToBase64Bytes(string);
  655. }
  656. return cipher.doFinal(bytes);
  657. }
  658. /**
  659. * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
  660. * and public key
  661. *
  662. * @param string string to decrypt
  663. * @param privateKey private key
  664. * @return decrypted string
  665. */
  666. public static String decryptStringAsymmetric(String string, PrivateKey privateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
  667. Cipher cipher = Cipher.getInstance(RSA_CIPHER);
  668. cipher.init(Cipher.DECRYPT_MODE, privateKey);
  669. byte[] bytes = decodeStringToBase64Bytes(string);
  670. byte[] encodedBytes = cipher.doFinal(bytes);
  671. return decodeBase64BytesToString(encodedBytes);
  672. }
  673. /**
  674. * Decrypt string with AES/GCM/NoPadding
  675. *
  676. * @param string string to decrypt
  677. * @param encryptionKeyBytes key from metadata
  678. * @return decrypted string
  679. */
  680. public static String encryptStringSymmetricAsString(String string, byte[] encryptionKeyBytes)
  681. throws NoSuchPaddingException,
  682. InvalidKeyException,
  683. NoSuchAlgorithmException,
  684. IllegalBlockSizeException,
  685. BadPaddingException,
  686. InvalidAlgorithmParameterException {
  687. EncryptedMetadata metadata = encryptStringSymmetric(string, encryptionKeyBytes, ivDelimiter);
  688. return metadata.getCiphertext();
  689. }
  690. @VisibleForTesting
  691. public static String encryptStringSymmetricAsStringOld(String string, byte[] encryptionKeyBytes)
  692. throws NoSuchPaddingException,
  693. InvalidKeyException,
  694. NoSuchAlgorithmException,
  695. IllegalBlockSizeException,
  696. BadPaddingException,
  697. InvalidAlgorithmParameterException {
  698. EncryptedMetadata metadata = encryptStringSymmetric(string, encryptionKeyBytes, ivDelimiterOld);
  699. return metadata.getCiphertext();
  700. }
  701. // /**
  702. // * Encrypt string with AES/GCM/NoPadding
  703. // *
  704. // * @param string string to encrypt
  705. // * @param encryptionKeyBytes key from metadata
  706. // * @return decrypted string
  707. // */
  708. // private static String encryptStringSymmetric(String string,
  709. // byte[] encryptionKeyBytes,
  710. // String delimiter)
  711. // throws NoSuchAlgorithmException,
  712. // InvalidAlgorithmParameterException,
  713. // NoSuchPaddingException,
  714. // InvalidKeyException,
  715. // BadPaddingException,
  716. // IllegalBlockSizeException {
  717. //
  718. // Cipher cipher = Cipher.getInstance(AES_CIPHER);
  719. // byte[] iv = randomBytes(ivLength);
  720. //
  721. // Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  722. // GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  723. // cipher.init(Cipher.ENCRYPT_MODE, key, spec);
  724. //
  725. // byte[] bytes = encodeStringToBase64Bytes(string);
  726. // byte[] cryptedBytes = cipher.doFinal(bytes);
  727. //
  728. // String encodedCryptedBytes = encodeBytesToBase64String(cryptedBytes);
  729. // String encodedIV = encodeBytesToBase64String(iv);
  730. //
  731. // return encodedCryptedBytes + delimiter + encodedIV;
  732. // }
  733. public static String decryptStringSymmetricAsString(String string,
  734. byte[] encryptionKeyBytes,
  735. byte[] iv,
  736. byte[] authenticationTag,
  737. ArbitraryDataProvider arbitraryDataProvider,
  738. User user
  739. ) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
  740. return decryptStringSymmetricAsString(
  741. decodeStringToBase64Bytes(string),
  742. encryptionKeyBytes,
  743. iv,
  744. authenticationTag,
  745. false,
  746. arbitraryDataProvider,
  747. user);
  748. }
  749. public static String decryptStringSymmetricAsString(String string,
  750. byte[] encryptionKeyBytes,
  751. byte[] iv,
  752. byte[] authenticationTag,
  753. boolean fileDropV2,
  754. ArbitraryDataProvider arbitraryDataProvider,
  755. User user) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
  756. return decryptStringSymmetricAsString(
  757. decodeStringToBase64Bytes(string),
  758. encryptionKeyBytes,
  759. iv,
  760. authenticationTag,
  761. fileDropV2,
  762. arbitraryDataProvider,
  763. user);
  764. }
  765. public static String decryptStringSymmetricAsString(byte[] bytes,
  766. byte[] encryptionKeyBytes,
  767. byte[] iv,
  768. byte[] authenticationTag,
  769. boolean fileDropV2,
  770. ArbitraryDataProvider arbitraryDataProvider,
  771. User user)
  772. throws NoSuchPaddingException,
  773. NoSuchAlgorithmException,
  774. InvalidAlgorithmParameterException,
  775. InvalidKeyException,
  776. IllegalBlockSizeException,
  777. BadPaddingException {
  778. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  779. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  780. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  781. cipher.init(Cipher.DECRYPT_MODE, key, spec);
  782. // check authentication tag
  783. byte[] extractedAuthenticationTag = Arrays.copyOfRange(bytes,
  784. bytes.length - (128 / 8),
  785. bytes.length);
  786. if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
  787. reportE2eError(arbitraryDataProvider, user);
  788. throw new SecurityException("Tag not correct");
  789. }
  790. byte[] encodedBytes = cipher.doFinal(bytes);
  791. if (fileDropV2) {
  792. return new EncryptionUtilsV2().gZipDecompress(encodedBytes);
  793. } else {
  794. return decodeBase64BytesToString(encodedBytes);
  795. }
  796. }
  797. public static EncryptedMetadata encryptStringSymmetric(
  798. String string,
  799. byte[] encryptionKeyBytes) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
  800. return encryptStringSymmetric(string, encryptionKeyBytes, ivDelimiter);
  801. }
  802. public static EncryptedMetadata encryptStringSymmetric(
  803. String string,
  804. byte[] encryptionKeyBytes,
  805. String delimiter) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
  806. byte[] bytes = encodeStringToBase64Bytes(string);
  807. return encryptStringSymmetric(bytes, encryptionKeyBytes, delimiter);
  808. }
  809. /**
  810. * Encrypt string with AES/GCM/NoPadding
  811. *
  812. * @param bytes byte array
  813. * @param encryptionKeyBytes key from metadata
  814. * @return decrypted string
  815. */
  816. public static EncryptedMetadata encryptStringSymmetric(
  817. byte[] bytes,
  818. byte[] encryptionKeyBytes,
  819. String delimiter)
  820. throws NoSuchAlgorithmException,
  821. InvalidAlgorithmParameterException,
  822. NoSuchPaddingException,
  823. InvalidKeyException,
  824. BadPaddingException,
  825. IllegalBlockSizeException {
  826. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  827. byte[] iv = randomBytes(ivLength);
  828. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  829. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  830. cipher.init(Cipher.ENCRYPT_MODE, key, spec);
  831. byte[] cryptedBytes = cipher.doFinal(bytes);
  832. String encodedCryptedBytes = encodeBytesToBase64String(cryptedBytes);
  833. String encodedIV = encodeBytesToBase64String(iv);
  834. String authenticationTag = encodeBytesToBase64String(Arrays.copyOfRange(cryptedBytes,
  835. cryptedBytes.length - (128 / 8),
  836. cryptedBytes.length));
  837. return new EncryptedMetadata(encodedCryptedBytes + delimiter + encodedIV, encodedIV, authenticationTag);
  838. }
  839. /**
  840. * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
  841. * and public key
  842. *
  843. * @param string string to decrypt
  844. * @param encryptionKeyBytes key from metadata
  845. * @return decrypted string
  846. */
  847. public static String decryptStringSymmetric(String string, byte[] encryptionKeyBytes)
  848. throws NoSuchAlgorithmException,
  849. InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
  850. BadPaddingException, IllegalBlockSizeException {
  851. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  852. String ivString;
  853. int delimiterPosition = string.lastIndexOf(ivDelimiter);
  854. if (delimiterPosition == -1) {
  855. // backward compatibility
  856. delimiterPosition = string.lastIndexOf(ivDelimiterOld);
  857. ivString = string.substring(delimiterPosition + ivDelimiterOld.length());
  858. } else {
  859. ivString = string.substring(delimiterPosition + ivDelimiter.length());
  860. }
  861. String cipherString = string.substring(0, delimiterPosition);
  862. byte[] iv = new IvParameterSpec(decodeStringToBase64Bytes(ivString)).getIV();
  863. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  864. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  865. cipher.init(Cipher.DECRYPT_MODE, key, spec);
  866. byte[] bytes = decodeStringToBase64Bytes(cipherString);
  867. byte[] encodedBytes = cipher.doFinal(bytes);
  868. return decodeBase64BytesToString(encodedBytes);
  869. }
  870. /**
  871. * Decrypt string with AES/GCM/NoPadding
  872. *
  873. * @param string string to decrypt
  874. * @param encryptionKeyBytes key from metadata
  875. * @param authenticationTag auth tag to check
  876. * @return decrypted string
  877. */
  878. public static byte[] decryptStringSymmetric(String string,
  879. byte[] encryptionKeyBytes,
  880. String authenticationTag,
  881. String ivString)
  882. throws NoSuchAlgorithmException,
  883. InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
  884. BadPaddingException, IllegalBlockSizeException {
  885. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  886. int delimiterPosition = string.lastIndexOf(ivDelimiter);
  887. String cipherString;
  888. if (delimiterPosition == -1) {
  889. cipherString = string;
  890. } else {
  891. cipherString = string.substring(0, delimiterPosition);
  892. }
  893. byte[] iv = new IvParameterSpec(decodeStringToBase64Bytes(ivString)).getIV();
  894. Key key = new SecretKeySpec(encryptionKeyBytes, AES);
  895. GCMParameterSpec spec = new GCMParameterSpec(128, iv);
  896. cipher.init(Cipher.DECRYPT_MODE, key, spec);
  897. byte[] bytes = decodeStringToBase64Bytes(cipherString);
  898. // check authentication tag
  899. if (authenticationTag != null) {
  900. byte[] authenticationTagBytes = decodeStringToBase64Bytes(authenticationTag);
  901. byte[] extractedAuthenticationTag = Arrays.copyOfRange(bytes,
  902. bytes.length - (128 / 8),
  903. bytes.length);
  904. if (!Arrays.equals(extractedAuthenticationTag, authenticationTagBytes)) {
  905. throw new SecurityException("Tag not correct");
  906. }
  907. }
  908. return cipher.doFinal(bytes);
  909. }
  910. /**
  911. * Encrypt private key with symmetric AES encryption, GCM mode mode and no padding
  912. *
  913. * @param privateKey byte64 encoded string representation of private key
  914. * @param keyPhrase key used for encryption, e.g. 12 random words
  915. * {@link EncryptionUtils#getRandomWords(int, Context)}
  916. * @return encrypted string, bytes first encoded base64, IV separated with "|", then to string
  917. */
  918. public static String encryptPrivateKey(String privateKey, String keyPhrase)
  919. throws NoSuchPaddingException,
  920. NoSuchAlgorithmException,
  921. InvalidKeyException,
  922. BadPaddingException,
  923. IllegalBlockSizeException,
  924. InvalidKeySpecException {
  925. return encryptPrivateKey(privateKey, keyPhrase, ivDelimiter);
  926. }
  927. @VisibleForTesting
  928. public static String encryptPrivateKeyOld(String privateKey, String keyPhrase)
  929. throws NoSuchPaddingException,
  930. NoSuchAlgorithmException,
  931. InvalidKeyException,
  932. BadPaddingException,
  933. IllegalBlockSizeException,
  934. InvalidKeySpecException {
  935. return encryptPrivateKey(privateKey, keyPhrase, ivDelimiterOld);
  936. }
  937. private static String encryptPrivateKey(String privateKey, String keyPhrase, String delimiter)
  938. throws NoSuchPaddingException,
  939. NoSuchAlgorithmException,
  940. InvalidKeyException,
  941. BadPaddingException,
  942. IllegalBlockSizeException,
  943. InvalidKeySpecException {
  944. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  945. SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
  946. byte[] salt = randomBytes(saltLength);
  947. KeySpec spec = new PBEKeySpec(keyPhrase.toCharArray(), salt, iterationCount, keyStrength);
  948. SecretKey tmp = factory.generateSecret(spec);
  949. SecretKeySpec key = new SecretKeySpec(tmp.getEncoded(), AES);
  950. cipher.init(Cipher.ENCRYPT_MODE, key);
  951. byte[] bytes = encodeStringToBase64Bytes(privateKey);
  952. byte[] encrypted = cipher.doFinal(bytes);
  953. byte[] iv = cipher.getIV();
  954. String encodedIV = encodeBytesToBase64String(iv);
  955. String encodedSalt = encodeBytesToBase64String(salt);
  956. String encodedEncryptedBytes = encodeBytesToBase64String(encrypted);
  957. return encodedEncryptedBytes + delimiter + encodedIV + delimiter + encodedSalt;
  958. }
  959. /**
  960. * Decrypt private key with symmetric AES encryption, GCM mode mode and no padding
  961. *
  962. * @param privateKey byte64 encoded string representation of private key, IV separated with "|"
  963. * @param keyPhrase key used for encryption, e.g. 12 random words
  964. * {@link EncryptionUtils#getRandomWords(int, Context)}
  965. * @return decrypted string
  966. */
  967. @SuppressFBWarnings("UCPM_USE_CHARACTER_PARAMETERIZED_METHOD")
  968. public static String decryptPrivateKey(String privateKey, String keyPhrase) throws NoSuchPaddingException,
  969. NoSuchAlgorithmException, InvalidKeyException, BadPaddingException,
  970. IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException {
  971. String[] strings;
  972. // split up iv, salt
  973. if (privateKey.lastIndexOf(ivDelimiter) == -1) {
  974. // backward compatibility
  975. strings = privateKey.split(ivDelimiterOld);
  976. } else {
  977. strings = privateKey.split("\\" + ivDelimiter);
  978. }
  979. String realPrivateKey = strings[0];
  980. byte[] iv = decodeStringToBase64Bytes(strings[1]);
  981. byte[] salt = decodeStringToBase64Bytes(strings[2]);
  982. Cipher cipher = Cipher.getInstance(AES_CIPHER);
  983. SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
  984. KeySpec spec = new PBEKeySpec(keyPhrase.toCharArray(), salt, iterationCount, keyStrength);
  985. SecretKey tmp = factory.generateSecret(spec);
  986. SecretKeySpec key = new SecretKeySpec(tmp.getEncoded(), AES);
  987. cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
  988. byte[] bytes = decodeStringToBase64Bytes(realPrivateKey);
  989. byte[] decrypted = cipher.doFinal(bytes);
  990. String pemKey = decodeBase64BytesToString(decrypted);
  991. return pemKey.replaceAll("\n", "").replace("-----BEGIN PRIVATE KEY-----", "")
  992. .replace("-----END PRIVATE KEY-----", "");
  993. }
  994. public static String privateKeyToPEM(PrivateKey privateKey) {
  995. String privateKeyString = encodeBytesToBase64String(privateKey.getEncoded());
  996. return "-----BEGIN PRIVATE KEY-----\n" + privateKeyString.replaceAll("(.{65})", "$1\n")
  997. + "\n-----END PRIVATE KEY-----";
  998. }
  999. public static PrivateKey PEMtoPrivateKey(String pem) throws NoSuchAlgorithmException, InvalidKeySpecException {
  1000. byte[] privateKeyBytes = EncryptionUtils.decodeStringToBase64Bytes(pem);
  1001. PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
  1002. KeyFactory kf = KeyFactory.getInstance(EncryptionUtils.RSA);
  1003. return kf.generatePrivate(keySpec);
  1004. }
  1005. /*
  1006. Helper
  1007. */
  1008. public static ArrayList<String> getRandomWords(int count, Context context) throws IOException {
  1009. InputStream ins = context.getResources().openRawResource(R.raw.encryption_key_words);
  1010. InputStreamReader inputStreamReader = new InputStreamReader(ins);
  1011. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  1012. List<String> lines = new ArrayList<>();
  1013. String line;
  1014. while ((line = bufferedReader.readLine()) != null) {
  1015. lines.add(line);
  1016. }
  1017. SecureRandom random = new SecureRandom();
  1018. ArrayList<String> outputLines = Lists.newArrayListWithCapacity(count);
  1019. for (int i = 0; i < count; i++) {
  1020. int randomLine = random.nextInt(lines.size());
  1021. outputLines.add(lines.get(randomLine));
  1022. }
  1023. return outputLines;
  1024. }
  1025. /**
  1026. * Generates private/public key pair, used for asymmetric encryption
  1027. *
  1028. * @return KeyPair
  1029. */
  1030. public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
  1031. KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA);
  1032. keyGen.initialize(2048, new SecureRandom());
  1033. return keyGen.generateKeyPair();
  1034. }
  1035. /**
  1036. * Generates key for symmetric encryption
  1037. *
  1038. * @return byte[] byteArray of key
  1039. */
  1040. public static byte[] generateKey() {
  1041. KeyGenerator keyGenerator;
  1042. try {
  1043. keyGenerator = KeyGenerator.getInstance(AES);
  1044. keyGenerator.init(128);
  1045. return keyGenerator.generateKey().getEncoded();
  1046. } catch (NoSuchAlgorithmException e) {
  1047. Log_OC.e(TAG, e.getMessage());
  1048. }
  1049. return null;
  1050. }
  1051. /**
  1052. * Generates key for symmetric encryption
  1053. *
  1054. * @return String String base64 encoded key
  1055. */
  1056. public static String generateKeyString() {
  1057. return EncryptionUtils.encodeBytesToBase64String(generateKey());
  1058. }
  1059. public static byte[] randomBytes(int size) {
  1060. SecureRandom random = new SecureRandom();
  1061. final byte[] iv = new byte[size];
  1062. random.nextBytes(iv);
  1063. return iv;
  1064. }
  1065. /**
  1066. * Generate a SHA512 with appended salt
  1067. *
  1068. * @param token token to be hashed
  1069. * @return SHA512 with appended salt, delimiter HASH_DELIMITER
  1070. */
  1071. public static String generateSHA512(String token) {
  1072. String salt = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.randomBytes(EncryptionUtils.saltLength));
  1073. return generateSHA512(token, salt);
  1074. }
  1075. /**
  1076. * Generate a SHA512 with appended salt
  1077. *
  1078. * @param token token to be hashed
  1079. * @return SHA512 with appended salt, delimiter HASH_DELIMITER
  1080. */
  1081. public static String generateSHA512(String token, String salt) {
  1082. MessageDigest digest;
  1083. String hashedToken = "";
  1084. byte[] hash;
  1085. try {
  1086. digest = MessageDigest.getInstance("SHA-512");
  1087. digest.update(salt.getBytes());
  1088. hash = digest.digest(token.getBytes());
  1089. StringBuilder stringBuilder = new StringBuilder();
  1090. for (byte hashByte : hash) {
  1091. stringBuilder.append(Integer.toString((hashByte & 0xff) + 0x100, 16).substring(1));
  1092. }
  1093. stringBuilder.append(HASH_DELIMITER).append(salt);
  1094. hashedToken = stringBuilder.toString();
  1095. } catch (NoSuchAlgorithmException e) {
  1096. Log_OC.e(TAG, "Generating SHA512 failed", e);
  1097. }
  1098. return hashedToken;
  1099. }
  1100. public static boolean verifySHA512(String hashWithSalt, String compareToken) {
  1101. String salt = hashWithSalt.split("\\" + HASH_DELIMITER)[1];
  1102. String newHash = generateSHA512(compareToken, salt);
  1103. return hashWithSalt.equals(newHash);
  1104. }
  1105. public static String lockFolder(ServerFileInterface parentFile, OwnCloudClient client) throws UploadException {
  1106. return lockFolder(parentFile, client, -1);
  1107. }
  1108. public static String lockFolder(ServerFileInterface parentFile, OwnCloudClient client, long counter) throws UploadException {
  1109. // Lock folder
  1110. LockFileRemoteOperation lockFileOperation = new LockFileRemoteOperation(parentFile.getLocalId(),
  1111. counter);
  1112. RemoteOperationResult<String> lockFileOperationResult = lockFileOperation.execute(client);
  1113. if (lockFileOperationResult.isSuccess() &&
  1114. !TextUtils.isEmpty(lockFileOperationResult.getResultData())) {
  1115. return lockFileOperationResult.getResultData();
  1116. } else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
  1117. throw new UploadException("Forbidden! Please try again later.)");
  1118. } else {
  1119. throw new UploadException("Could not lock folder");
  1120. }
  1121. }
  1122. /**
  1123. * @param parentFile file metadata should be retrieved for
  1124. * @return Pair: boolean: true: metadata already exists, false: metadata new created
  1125. */
  1126. public static Pair<Boolean, DecryptedFolderMetadataFileV1> retrieveMetadataV1(OCFile parentFile,
  1127. OwnCloudClient client,
  1128. String privateKey,
  1129. String publicKey,
  1130. ArbitraryDataProvider arbitraryDataProvider,
  1131. User user)
  1132. throws UploadException,
  1133. InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException,
  1134. IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException {
  1135. long localId = parentFile.getLocalId();
  1136. GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(localId);
  1137. RemoteOperationResult<MetadataResponse> getMetadataOperationResult = getMetadataOperation.execute(client);
  1138. DecryptedFolderMetadataFileV1 metadata;
  1139. if (getMetadataOperationResult.isSuccess()) {
  1140. // decrypt metadata
  1141. String serializedEncryptedMetadata = getMetadataOperationResult.getResultData().getMetadata();
  1142. EncryptedFolderMetadataFileV1 encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
  1143. serializedEncryptedMetadata, new TypeToken<>() {
  1144. });
  1145. return new Pair<>(Boolean.TRUE, decryptFolderMetaData(encryptedFolderMetadata,
  1146. privateKey,
  1147. arbitraryDataProvider,
  1148. user,
  1149. localId));
  1150. } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
  1151. // TODO extract
  1152. // new metadata
  1153. metadata = new DecryptedFolderMetadataFileV1();
  1154. metadata.setMetadata(new DecryptedMetadata());
  1155. metadata.getMetadata().setVersion(Double.parseDouble(E2EVersion.V1_2.getValue()));
  1156. metadata.getMetadata().setMetadataKeys(new HashMap<>());
  1157. String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
  1158. String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
  1159. metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
  1160. return new Pair<>(Boolean.FALSE, metadata);
  1161. } else {
  1162. // TODO E2E: error
  1163. throw new UploadException("something wrong");
  1164. }
  1165. }
  1166. /**
  1167. * @param parentFile file metadata should be retrieved for
  1168. * @return Pair: boolean: true: metadata already exists, false: metadata new created
  1169. */
  1170. public static Pair<Boolean, DecryptedFolderMetadataFile> retrieveMetadata(OCFile parentFile,
  1171. OwnCloudClient client,
  1172. String privateKey,
  1173. String publicKey,
  1174. FileDataStorageManager storageManager,
  1175. User user,
  1176. Context context,
  1177. ArbitraryDataProvider arbitraryDataProvider)
  1178. throws UploadException, Throwable,
  1179. InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException,
  1180. IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException {
  1181. long localId = parentFile.getLocalId();
  1182. GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(localId);
  1183. RemoteOperationResult<MetadataResponse> getMetadataOperationResult = getMetadataOperation.execute(client);
  1184. DecryptedFolderMetadataFile metadata;
  1185. if (getMetadataOperationResult.isSuccess()) {
  1186. // decrypt metadata
  1187. String serializedEncryptedMetadata = getMetadataOperationResult.getResultData().getMetadata();
  1188. EncryptedFolderMetadataFile encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
  1189. serializedEncryptedMetadata, new TypeToken<>() {
  1190. });
  1191. return new Pair<>(Boolean.TRUE,
  1192. new EncryptionUtilsV2().decryptFolderMetadataFile(encryptedFolderMetadata,
  1193. client.getUserId(),
  1194. privateKey,
  1195. parentFile,
  1196. storageManager,
  1197. client,
  1198. parentFile.getE2eCounter(),
  1199. getMetadataOperationResult.getResultData().getSignature(),
  1200. user,
  1201. context,
  1202. arbitraryDataProvider)
  1203. );
  1204. } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND ||
  1205. getMetadataOperationResult.getHttpCode() == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
  1206. // new metadata
  1207. metadata = new DecryptedFolderMetadataFile(new com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedMetadata(),
  1208. new ArrayList<>(),
  1209. new HashMap<>(),
  1210. E2EVersion.V2_0.getValue());
  1211. metadata.getUsers().add(new DecryptedUser(client.getUserId(), publicKey));
  1212. byte[] metadataKey = EncryptionUtils.generateKey();
  1213. if (metadataKey == null) {
  1214. throw new UploadException("Could not encrypt folder!");
  1215. }
  1216. metadata.getMetadata().setMetadataKey(metadataKey);
  1217. metadata.getMetadata().getKeyChecksums().add(new EncryptionUtilsV2().hashMetadataKey(metadataKey));
  1218. return new Pair<>(Boolean.FALSE, metadata);
  1219. } else {
  1220. reportE2eError(arbitraryDataProvider, user);
  1221. throw new UploadException("something wrong");
  1222. }
  1223. }
  1224. public static void uploadMetadata(ServerFileInterface parentFile,
  1225. String serializedFolderMetadata,
  1226. String token,
  1227. OwnCloudClient client,
  1228. boolean metadataExists,
  1229. E2EVersion version,
  1230. String signature,
  1231. ArbitraryDataProvider arbitraryDataProvider,
  1232. User user) throws UploadException {
  1233. RemoteOperationResult<String> uploadMetadataOperationResult;
  1234. if (metadataExists) {
  1235. // update metadata
  1236. if (version == E2EVersion.V2_0) {
  1237. uploadMetadataOperationResult = new UpdateMetadataV2RemoteOperation(
  1238. parentFile.getRemoteId(),
  1239. serializedFolderMetadata,
  1240. token,
  1241. signature)
  1242. .execute(client);
  1243. } else {
  1244. uploadMetadataOperationResult = new UpdateMetadataRemoteOperation(
  1245. parentFile.getLocalId(),
  1246. serializedFolderMetadata,
  1247. token)
  1248. .execute(client);
  1249. }
  1250. } else {
  1251. // store metadata
  1252. if (version == E2EVersion.V2_0) {
  1253. uploadMetadataOperationResult = new StoreMetadataV2RemoteOperation(
  1254. parentFile.getRemoteId(),
  1255. serializedFolderMetadata,
  1256. token,
  1257. signature
  1258. )
  1259. .execute(client);
  1260. } else {
  1261. uploadMetadataOperationResult = new StoreMetadataRemoteOperation(
  1262. parentFile.getLocalId(),
  1263. serializedFolderMetadata
  1264. )
  1265. .execute(client);
  1266. }
  1267. }
  1268. if (!uploadMetadataOperationResult.isSuccess()) {
  1269. reportE2eError(arbitraryDataProvider, user);
  1270. throw new UploadException("Storing/updating metadata was not successful");
  1271. }
  1272. }
  1273. public static RemoteOperationResult<Void> unlockFolder(ServerFileInterface parentFolder, OwnCloudClient client, String token) {
  1274. if (token != null) {
  1275. return new UnlockFileRemoteOperation(parentFolder.getLocalId(), token).execute(client);
  1276. } else {
  1277. return new RemoteOperationResult<>(new Exception("No token available"));
  1278. }
  1279. }
  1280. public static RemoteOperationResult<Void> unlockFolderV1(ServerFileInterface parentFolder, OwnCloudClient client, String token) {
  1281. if (token != null) {
  1282. return new UnlockFileV1RemoteOperation(parentFolder.getLocalId(), token).execute(client);
  1283. } else {
  1284. return new RemoteOperationResult<>(new Exception("No token available"));
  1285. }
  1286. }
  1287. public static X509Certificate convertCertFromString(String string) throws CertificateException {
  1288. String trimmedCert = string.replace("-----BEGIN CERTIFICATE-----\n", "")
  1289. .replace("-----END CERTIFICATE-----\n", "");
  1290. byte[] encodedCert = trimmedCert.getBytes(StandardCharsets.UTF_8);
  1291. byte[] decodedCert = org.apache.commons.codec.binary.Base64.decodeBase64(encodedCert);
  1292. CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
  1293. InputStream in = new ByteArrayInputStream(decodedCert);
  1294. return (X509Certificate) certFactory.generateCertificate(in);
  1295. }
  1296. public static RSAPublicKey convertPublicKeyFromString(String string) throws CertificateException {
  1297. return (RSAPublicKey) convertCertFromString(string).getPublicKey();
  1298. }
  1299. public static void removeE2E(ArbitraryDataProvider arbitraryDataProvider, User user) {
  1300. // delete stored E2E keys and mnemonic
  1301. arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
  1302. arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
  1303. arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.MNEMONIC);
  1304. }
  1305. public static boolean isMatchingKeys(KeyPair keyPair, String publicKeyString) throws CertificateException {
  1306. // check key
  1307. RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyPair.getPrivate();
  1308. RSAPublicKey publicKey = EncryptionUtils.convertPublicKeyFromString(publicKeyString);
  1309. BigInteger modulusPublic = publicKey.getModulus();
  1310. BigInteger modulusPrivate = privateKey.getModulus();
  1311. return modulusPrivate.compareTo(modulusPublic) == 0;
  1312. }
  1313. public static boolean supportsSecureFiledrop(OCFile file, User user) {
  1314. return file.isEncrypted() &&
  1315. file.isFolder() &&
  1316. user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_26);
  1317. }
  1318. public static String generateChecksum(DecryptedFolderMetadataFileV1 metadataFile,
  1319. String mnemonic) throws NoSuchAlgorithmException {
  1320. StringBuilder stringBuilder = new StringBuilder();
  1321. stringBuilder.append(mnemonic.replaceAll(" ", ""));
  1322. ArrayList<String> keys = new ArrayList<>(metadataFile.getFiles().keySet());
  1323. Collections.sort(keys);
  1324. for (String key : keys) {
  1325. stringBuilder.append(key);
  1326. }
  1327. stringBuilder.append(metadataFile.getMetadata().getMetadataKey());
  1328. // sha256 hash-sum
  1329. return sha256(stringBuilder.toString());
  1330. }
  1331. /**
  1332. * SHA-256 hash of metadata-key
  1333. */
  1334. public static String sha256(String string) throws NoSuchAlgorithmException {
  1335. byte[] bytes = MessageDigest
  1336. .getInstance("SHA-256")
  1337. .digest(string.getBytes(StandardCharsets.UTF_8));
  1338. return bytesToHex(bytes);
  1339. }
  1340. public static String bytesToHex(byte[] bytes) {
  1341. StringBuilder result = new StringBuilder();
  1342. for (byte individualByte : bytes) {
  1343. result.append(Integer.toString((individualByte & 0xff) + 0x100, 16)
  1344. .substring(1));
  1345. }
  1346. return result.toString();
  1347. }
  1348. public static void addIdToMigratedIds(long id,
  1349. User user,
  1350. ArbitraryDataProvider arbitraryDataProvider) {
  1351. Gson gson = new Gson();
  1352. String ids = arbitraryDataProvider.getValue(user, MIGRATED_FOLDER_IDS);
  1353. ArrayList<Long> arrayList = gson.fromJson(ids, ArrayList.class);
  1354. if (arrayList == null) {
  1355. arrayList = new ArrayList<>();
  1356. }
  1357. if (arrayList.contains(id)) {
  1358. // nothing to do here
  1359. return;
  1360. }
  1361. arrayList.add(id);
  1362. String json = gson.toJson(arrayList);
  1363. arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(),
  1364. MIGRATED_FOLDER_IDS,
  1365. json);
  1366. }
  1367. public static boolean isFolderMigrated(long id,
  1368. User user,
  1369. ArbitraryDataProvider arbitraryDataProvider) {
  1370. Gson gson = new Gson();
  1371. String ids = arbitraryDataProvider.getValue(user, MIGRATED_FOLDER_IDS);
  1372. ArrayList<Long> arrayList = gson.fromJson(ids, new TypeToken<List<Long>>() {
  1373. }.getType());
  1374. if (arrayList == null) {
  1375. return false;
  1376. }
  1377. return arrayList.contains(id);
  1378. }
  1379. public static void reportE2eError(ArbitraryDataProvider arbitraryDataProvider, User user) {
  1380. arbitraryDataProvider.incrementValue(user.getAccountName(), ArbitraryDataProvider.E2E_ERRORS);
  1381. if (arbitraryDataProvider.getLongValue(user.getAccountName(),
  1382. ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP) == -1L) {
  1383. arbitraryDataProvider.storeOrUpdateKeyValue(
  1384. user.getAccountName(),
  1385. ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP,
  1386. System.currentTimeMillis() / 1000
  1387. );
  1388. }
  1389. }
  1390. @Nullable
  1391. public static Problem readE2eError(ArbitraryDataProvider arbitraryDataProvider, User user) {
  1392. int value = arbitraryDataProvider.getIntegerValue(user.getAccountName(),
  1393. ArbitraryDataProvider.E2E_ERRORS);
  1394. long timestamp = arbitraryDataProvider.getLongValue(user.getAccountName(),
  1395. ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP);
  1396. arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(),
  1397. ArbitraryDataProvider.E2E_ERRORS);
  1398. arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(),
  1399. ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP);
  1400. if (value > 0 && timestamp > 0) {
  1401. return new Problem(SendClientDiagnosticRemoteOperation.E2EE_ERRORS, value, timestamp);
  1402. } else {
  1403. return null;
  1404. }
  1405. }
  1406. public static String generateUid() {
  1407. return UUID.randomUUID().toString().replaceAll("-", "");
  1408. }
  1409. public static String retrievePublicKeyForUser(User user, Context context) {
  1410. return new ArbitraryDataProviderImpl(context).getValue(user, PUBLIC_KEY);
  1411. }
  1412. public static byte[] generateIV() {
  1413. return EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
  1414. }
  1415. public static String byteToHex(byte[] bytes) {
  1416. StringBuilder sbKey = new StringBuilder();
  1417. for (byte b : bytes) {
  1418. sbKey.append(String.format("%02X ", b));
  1419. }
  1420. return sbKey.toString();
  1421. }
  1422. public static void savePublicKey(User currentUser,
  1423. String key,
  1424. String user,
  1425. ArbitraryDataProvider arbitraryDataProvider) {
  1426. arbitraryDataProvider.storeOrUpdateKeyValue(currentUser,
  1427. ArbitraryDataProvider.PUBLIC_KEY + user,
  1428. key);
  1429. }
  1430. public static String getPublicKey(User currentUser,
  1431. String user,
  1432. ArbitraryDataProvider arbitraryDataProvider) {
  1433. return arbitraryDataProvider.getValue(currentUser, ArbitraryDataProvider.PUBLIC_KEY + user);
  1434. }
  1435. }