EncryptionUtils.java 74 KB

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