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