EncryptionUtils.java 74 KB

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