123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728 |
- /*
- * Nextcloud Android client application
- *
- * @author Tobias Kaminsky
- * Copyright (C) 2017 Tobias Kaminsky
- * Copyright (C) 2017 Nextcloud GmbH.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- package com.owncloud.android.utils;
- import android.content.Context;
- import android.text.TextUtils;
- import android.util.Base64;
- import android.util.Pair;
- import com.google.common.collect.Lists;
- import com.google.gson.Gson;
- import com.google.gson.GsonBuilder;
- import com.google.gson.reflect.TypeToken;
- import com.nextcloud.client.account.User;
- import com.owncloud.android.R;
- import com.owncloud.android.datamodel.ArbitraryDataProvider;
- import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
- import com.owncloud.android.datamodel.EncryptedFiledrop;
- import com.owncloud.android.datamodel.FileDataStorageManager;
- import com.owncloud.android.datamodel.OCFile;
- import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile;
- import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
- import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedMetadata;
- import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFile;
- import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1;
- import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
- import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedUser;
- import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedFolderMetadataFile;
- import com.owncloud.android.datamodel.e2e.v2.encrypted.EncryptedMetadata;
- import com.owncloud.android.lib.common.OwnCloudClient;
- import com.owncloud.android.lib.common.operations.RemoteOperationResult;
- import com.owncloud.android.lib.common.utils.Log_OC;
- import com.owncloud.android.lib.resources.e2ee.GetMetadataRemoteOperation;
- import com.owncloud.android.lib.resources.e2ee.LockFileRemoteOperation;
- import com.owncloud.android.lib.resources.e2ee.MetadataResponse;
- import com.owncloud.android.lib.resources.e2ee.StoreMetadataRemoteOperation;
- import com.owncloud.android.lib.resources.e2ee.StoreMetadataV2RemoteOperation;
- import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
- import com.owncloud.android.lib.resources.e2ee.UnlockFileV1RemoteOperation;
- import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
- import com.owncloud.android.lib.resources.e2ee.UpdateMetadataV2RemoteOperation;
- import com.owncloud.android.lib.resources.files.model.ServerFileInterface;
- import com.owncloud.android.lib.resources.status.E2EVersion;
- import com.owncloud.android.lib.resources.status.NextcloudVersion;
- import com.owncloud.android.lib.resources.status.OCCapability;
- import com.owncloud.android.lib.resources.status.Problem;
- import com.owncloud.android.lib.resources.status.SendClientDiagnosticRemoteOperation;
- import com.owncloud.android.operations.UploadException;
- import com.owncloud.android.utils.theme.CapabilityUtils;
- import org.apache.commons.httpclient.HttpStatus;
- import java.io.BufferedReader;
- import java.io.ByteArrayInputStream;
- import java.io.File;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.RandomAccessFile;
- import java.math.BigInteger;
- import java.nio.charset.StandardCharsets;
- import java.security.InvalidAlgorithmParameterException;
- import java.security.InvalidKeyException;
- import java.security.Key;
- import java.security.KeyFactory;
- import java.security.KeyPair;
- import java.security.KeyPairGenerator;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.security.PrivateKey;
- import java.security.PublicKey;
- import java.security.SecureRandom;
- import java.security.cert.CertificateException;
- import java.security.cert.CertificateFactory;
- import java.security.cert.X509Certificate;
- import java.security.interfaces.RSAPrivateCrtKey;
- import java.security.interfaces.RSAPublicKey;
- import java.security.spec.InvalidKeySpecException;
- import java.security.spec.KeySpec;
- import java.security.spec.PKCS8EncodedKeySpec;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.UUID;
- import javax.crypto.BadPaddingException;
- import javax.crypto.Cipher;
- import javax.crypto.IllegalBlockSizeException;
- import javax.crypto.KeyGenerator;
- import javax.crypto.NoSuchPaddingException;
- import javax.crypto.SecretKey;
- import javax.crypto.SecretKeyFactory;
- import javax.crypto.spec.GCMParameterSpec;
- import javax.crypto.spec.IvParameterSpec;
- import javax.crypto.spec.PBEKeySpec;
- import javax.crypto.spec.SecretKeySpec;
- import androidx.annotation.Nullable;
- import androidx.annotation.VisibleForTesting;
- import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
- /**
- * Utils for encryption
- */
- public final class EncryptionUtils {
- private static final String TAG = EncryptionUtils.class.getSimpleName();
- public static final String PUBLIC_KEY = "PUBLIC_KEY";
- public static final String PRIVATE_KEY = "PRIVATE_KEY";
- public static final String MNEMONIC = "MNEMONIC";
- public static final int ivLength = 16;
- public static final int saltLength = 40;
- public static final String ivDelimiter = "|"; // not base64 encoded
- public static final String ivDelimiterOld = "fA=="; // "|" base64 encoded
- private static final char HASH_DELIMITER = '$';
- private static final int iterationCount = 1024;
- private static final int keyStrength = 256;
- private static final String AES_CIPHER = "AES/GCM/NoPadding";
- private static final String AES = "AES";
- public static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
- public static final String RSA = "RSA";
- @VisibleForTesting
- public static final String MIGRATED_FOLDER_IDS = "MIGRATED_FOLDER_IDS";
- private EncryptionUtils() {
- // utility class -> private constructor
- }
- /*
- JSON
- */
- public static <T> T deserializeJSON(String json, TypeToken<T> type, boolean excludeTransient) {
- if (excludeTransient) {
- return new Gson().fromJson(json, type.getType());
- } else {
- return new GsonBuilder().excludeFieldsWithModifiers(0).create().fromJson(json, type.getType());
- }
- }
- public static <T> T deserializeJSON(String json, TypeToken<T> type) {
- return deserializeJSON(json, type, false);
- }
- public static String serializeJSON(Object data, boolean excludeTransient) {
- if (excludeTransient) {
- return new GsonBuilder()
- .disableHtmlEscaping()
- .create()
- .toJson(data);
- } else {
- return new GsonBuilder()
- .disableHtmlEscaping()
- .excludeFieldsWithModifiers(0)
- .create()
- .toJson(data);
- }
- }
- public static String serializeJSON(Object data) {
- return serializeJSON(data, false);
- }
- /*
- METADATA
- */
- /**
- * Encrypt folder metaData V1
- *
- * @param decryptedFolderMetadata folder metaData to encrypt
- * @return EncryptedFolderMetadataFile encrypted folder metadata
- */
- public static EncryptedFolderMetadataFileV1 encryptFolderMetadata(
- DecryptedFolderMetadataFileV1 decryptedFolderMetadata,
- String publicKey,
- long parentId,
- User user,
- ArbitraryDataProvider arbitraryDataProvider
- )
- throws NoSuchAlgorithmException, InvalidKeyException,
- InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
- IllegalBlockSizeException, CertificateException {
- HashMap<String, EncryptedFolderMetadataFileV1.EncryptedFile> files = new HashMap<>();
- HashMap<String, EncryptedFiledrop> filesdrop = new HashMap<>();
- EncryptedFolderMetadataFileV1 encryptedFolderMetadata = new EncryptedFolderMetadataFileV1(decryptedFolderMetadata
- .getMetadata(),
- files,
- filesdrop);
- // set new metadata key
- byte[] metadataKeyBytes = EncryptionUtils.generateKey();
- String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(
- EncryptionUtils.encodeBytesToBase64String(metadataKeyBytes),
- publicKey);
- encryptedFolderMetadata.getMetadata().setMetadataKey(encryptedMetadataKey);
- // store that this folder has been migrated
- addIdToMigratedIds(parentId, user, arbitraryDataProvider);
- // Encrypt each file in "files"
- for (Map.Entry<String, DecryptedFile> entry : decryptedFolderMetadata
- .getFiles().entrySet()) {
- String key = entry.getKey();
- DecryptedFile decryptedFile = entry.getValue();
- EncryptedFolderMetadataFileV1.EncryptedFile encryptedFile = new EncryptedFolderMetadataFileV1.EncryptedFile();
- encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
- encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
- // encrypt
- String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
- encryptedFile.setEncrypted(EncryptionUtils.encryptStringSymmetricAsString(dataJson, metadataKeyBytes));
- files.put(key, encryptedFile);
- }
- // set checksum
- String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC).trim();
- String checksum = EncryptionUtils.generateChecksum(decryptedFolderMetadata, mnemonic);
- encryptedFolderMetadata.getMetadata().setChecksum(checksum);
- return encryptedFolderMetadata;
- }
- /**
- * normally done on server only internal test
- */
- @VisibleForTesting
- public static void encryptFileDropFiles(DecryptedFolderMetadataFileV1 decryptedFolderMetadata,
- EncryptedFolderMetadataFileV1 encryptedFolderMetadata,
- String cert) throws NoSuchAlgorithmException, NoSuchPaddingException,
- InvalidKeyException, BadPaddingException, IllegalBlockSizeException, CertificateException,
- InvalidAlgorithmParameterException {
- final Map<String, EncryptedFiledrop> filesdrop = encryptedFolderMetadata.getFiledrop();
- for (Map.Entry<String, DecryptedFile> entry : decryptedFolderMetadata
- .getFiledrop().entrySet()) {
- String key = entry.getKey();
- DecryptedFile decryptedFile = entry.getValue();
- byte[] byt = generateKey();
- String metadataKey0 = encodeBytesToBase64String(byt);
- String enc = encryptStringAsymmetric(metadataKey0, cert);
- String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
- String encJson = encryptStringSymmetricAsString(dataJson, byt);
- int delimiterPosition = encJson.lastIndexOf(ivDelimiter);
- String encryptedInitializationVector = encJson.substring(delimiterPosition + ivDelimiter.length());
- String encodedCryptedBytes = encJson.substring(0, delimiterPosition);
- byte[] bytes = decodeStringToBase64Bytes(encodedCryptedBytes);
- // check authentication tag
- byte[] extractedAuthenticationTag = Arrays.copyOfRange(bytes,
- bytes.length - (128 / 8),
- bytes.length);
- String encryptedTag = encodeBytesToBase64String(extractedAuthenticationTag);
- EncryptedFiledrop encryptedFile = new EncryptedFiledrop(encodedCryptedBytes,
- decryptedFile.getInitializationVector(),
- decryptedFile.getAuthenticationTag(),
- enc,
- encryptedTag,
- encryptedInitializationVector);
- filesdrop.put(key, encryptedFile);
- }
- }
- /*
- * decrypt folder metaData V1 with private key
- */
- public static DecryptedFolderMetadataFileV1 decryptFolderMetaData(EncryptedFolderMetadataFileV1 encryptedFolderMetadata,
- String privateKey,
- ArbitraryDataProvider arbitraryDataProvider,
- User user,
- long remoteId)
- throws NoSuchAlgorithmException, InvalidKeyException,
- InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
- IllegalBlockSizeException, InvalidKeySpecException {
- HashMap<String, DecryptedFile> files = new HashMap<>();
- DecryptedFolderMetadataFileV1 decryptedFolderMetadata = new DecryptedFolderMetadataFileV1(
- encryptedFolderMetadata.getMetadata(), files);
- byte[] decryptedMetadataKey = null;
- String encryptedMetadataKey = decryptedFolderMetadata.getMetadata().getMetadataKey();
- if (encryptedMetadataKey != null) {
- decryptedMetadataKey = decodeStringToBase64Bytes(
- decryptStringAsymmetric(encryptedMetadataKey, privateKey));
- }
- if (encryptedFolderMetadata.getFiles() != null) {
- for (Map.Entry<String, EncryptedFolderMetadataFileV1.EncryptedFile> entry : encryptedFolderMetadata
- .getFiles().entrySet()) {
- String key = entry.getKey();
- EncryptedFolderMetadataFileV1.EncryptedFile encryptedFile = entry.getValue();
- DecryptedFile decryptedFile = new DecryptedFile();
- decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
- decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
- decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
- if (decryptedMetadataKey == null) {
- decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
- decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
- .getMetadataKeys().get(encryptedFile.getMetadataKey()),
- privateKey));
- }
- // decrypt
- String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
- decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
- new TypeToken<>() {
- }));
- files.put(key, decryptedFile);
- }
- }
- // verify checksum
- String mnemonic = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.MNEMONIC).trim();
- String checksum = EncryptionUtils.generateChecksum(decryptedFolderMetadata, mnemonic);
- String decryptedFolderChecksum = decryptedFolderMetadata.getMetadata().getChecksum();
- if (TextUtils.isEmpty(decryptedFolderChecksum) &&
- isFolderMigrated(remoteId, user, arbitraryDataProvider)) {
- reportE2eError(arbitraryDataProvider, user);
- throw new IllegalStateException("Possible downgrade attack detected!");
- }
- if (!TextUtils.isEmpty(decryptedFolderChecksum) && !decryptedFolderChecksum.equals(checksum)) {
- reportE2eError(arbitraryDataProvider, user);
- throw new IllegalStateException("Wrong checksum!");
- }
- Map<String, EncryptedFiledrop> fileDrop = encryptedFolderMetadata.getFiledrop();
- if (fileDrop != null) {
- for (Map.Entry<String, EncryptedFiledrop> entry : fileDrop.entrySet()) {
- String key = entry.getKey();
- EncryptedFiledrop encryptedFile = entry.getValue();
- // decrypt key
- String encryptedKey = decryptStringAsymmetric(encryptedFile.getEncryptedKey(),
- privateKey);
- // decrypt encrypted blob with key
- String decryptedData = decryptStringSymmetricAsString(
- encryptedFile.getEncrypted(),
- decodeStringToBase64Bytes(encryptedKey),
- decodeStringToBase64Bytes(encryptedFile.getEncryptedInitializationVector()),
- decodeStringToBase64Bytes(encryptedFile.getEncryptedTag()),
- arbitraryDataProvider,
- user
- );
- DecryptedFile decryptedFile = new DecryptedFile();
- decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
- decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
- decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(decryptedData,
- new TypeToken<>() {
- }));
- files.put(key, decryptedFile);
- // remove from filedrop
- fileDrop.remove(key);
- }
- }
- return decryptedFolderMetadata;
- }
- /**
- * Download metadata (v1 or v2) for folder and decrypt it
- *
- * @return decrypted v2 metadata or null
- */
- @SuppressFBWarnings("URV")
- public static @Nullable
- Object
- downloadFolderMetadata(OCFile folder,
- OwnCloudClient client,
- Context context,
- User user
- ) {
- RemoteOperationResult<MetadataResponse> getMetadataOperationResult = new GetMetadataRemoteOperation(folder.getLocalId())
- .execute(client);
- if (!getMetadataOperationResult.isSuccess()) {
- return null;
- }
- OCCapability capability = CapabilityUtils.getCapability(context);
- // decrypt metadata
- EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2();
- String serializedEncryptedMetadata = getMetadataOperationResult.getResultData().getMetadata();
- E2EVersion version = determinateVersion(serializedEncryptedMetadata);
- switch (version) {
- case UNKNOWN:
- Log_OC.e(TAG, "Unknown e2e state");
- return null;
- case V1_0, V1_1, V1_2:
- ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
- String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
- String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
- EncryptedFolderMetadataFileV1 encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
- serializedEncryptedMetadata, new TypeToken<>() {
- });
- try {
- DecryptedFolderMetadataFileV1 v1 = decryptFolderMetaData(encryptedFolderMetadata,
- privateKey,
- arbitraryDataProvider,
- user,
- folder.getLocalId());
- if (capability.getEndToEndEncryptionApiVersion().compareTo(E2EVersion.V2_0) >= 0) {
- new EncryptionUtilsV2().migrateV1ToV2andUpload(
- v1,
- client.getUserId(),
- publicKey,
- folder,
- new FileDataStorageManager(user, context.getContentResolver()),
- client,
- user,
- context
- );
- } else {
- return v1;
- }
- } catch (Exception e) {
- // TODO do not crash, but show meaningful error
- Log_OC.e(TAG, "Could not decrypt metadata for " + folder.getDecryptedFileName(), e);
- return null;
- }
- case V2_0:
- return encryptionUtilsV2.parseAnyMetadata(getMetadataOperationResult.getResultData(),
- user,
- client,
- context,
- folder);
- }
- return null;
- }
- public static E2EVersion determinateVersion(String metadata) {
- try {
- EncryptedFolderMetadataFileV1 v1 = EncryptionUtils.deserializeJSON(
- metadata,
- new TypeToken<>() {
- });
- double version = v1.getMetadata().getVersion();
- if (version == 1.0) {
- return E2EVersion.V1_0;
- } else if (version == 1.1) {
- return E2EVersion.V1_1;
- } else if (version == 1.2) {
- return E2EVersion.V1_2;
- } else {
- throw new IllegalStateException("Unknown version");
- }
- } catch (Exception e) {
- EncryptedFolderMetadataFile v2 = EncryptionUtils.deserializeJSON(
- metadata,
- new TypeToken<>() {
- });
- if ("2.0".equals(v2.getVersion()) || "2".equals(v2.getVersion())) {
- return E2EVersion.V2_0;
- }
- }
- return E2EVersion.UNKNOWN;
- }
- /*
- BASE 64
- */
- @SuppressFBWarnings({"DM", "MDM"})
- public static byte[] encodeStringToBase64Bytes(String string) {
- try {
- return Base64.encode(string.getBytes(), Base64.NO_WRAP);
- } catch (Exception e) {
- return new byte[0];
- }
- }
- @SuppressFBWarnings({"DM", "MDM"})
- public static String decodeBase64BytesToString(byte[] bytes) {
- try {
- return new String(Base64.decode(bytes, Base64.NO_WRAP));
- } catch (Exception e) {
- return "";
- }
- }
- @SuppressFBWarnings({"DM", "MDM"})
- public static String encodeBytesToBase64String(byte[] bytes) {
- return Base64.encodeToString(bytes, Base64.NO_WRAP);
- }
- @SuppressFBWarnings({"DM", "MDM"})
- public static String encodeStringToBase64String(String string) {
- return Base64.encodeToString(string.getBytes(), Base64.NO_WRAP);
- }
- @SuppressFBWarnings({"DM", "MDM"})
- public static String decodeBase64StringToString(String string) {
- return new String(Base64.decode(string, Base64.NO_WRAP));
- }
- public static byte[] decodeStringToBase64Bytes(String string) {
- return Base64.decode(string, Base64.NO_WRAP);
- }
- /*
- ENCRYPTION
- */
- /**
- * @param ocFile file do crypt
- * @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
- * @param iv initialization vector, either from metadata or
- * {@link EncryptionUtils#randomBytes(int)}
- * @return encryptedFile with encryptedBytes and authenticationTag
- */
- public static EncryptedFile encryptFile(OCFile ocFile, byte[] encryptionKeyBytes, byte[] iv)
- throws NoSuchAlgorithmException,
- InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
- BadPaddingException, IllegalBlockSizeException, IOException {
- File file = new File(ocFile.getStoragePath());
- return encryptFile(file, encryptionKeyBytes, iv);
- }
- /**
- * @param file file do crypt
- * @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
- * @param iv initialization vector, either from metadata or
- * {@link EncryptionUtils#randomBytes(int)}
- * @return encryptedFile with encryptedBytes and authenticationTag
- */
- public static EncryptedFile encryptFile(File file, byte[] encryptionKeyBytes, byte[] iv)
- throws NoSuchAlgorithmException,
- InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
- BadPaddingException, IllegalBlockSizeException, IOException {
- Cipher cipher = Cipher.getInstance(AES_CIPHER);
- Key key = new SecretKeySpec(encryptionKeyBytes, AES);
- GCMParameterSpec spec = new GCMParameterSpec(128, iv);
- cipher.init(Cipher.ENCRYPT_MODE, key, spec);
- RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
- byte[] fileBytes = new byte[(int) randomAccessFile.length()];
- randomAccessFile.readFully(fileBytes);
- byte[] cryptedBytes = cipher.doFinal(fileBytes);
- String authenticationTag = encodeBytesToBase64String(Arrays.copyOfRange(cryptedBytes,
- cryptedBytes.length - (128 / 8),
- cryptedBytes.length));
- return new EncryptedFile(cryptedBytes, authenticationTag);
- }
- /**
- * @param file encrypted file
- * @param encryptionKeyBytes key from metadata
- * @param iv initialization vector from metadata
- * @param authenticationTag authenticationTag from metadata
- * @return decrypted byte[]
- */
- public static byte[] decryptFile(File file,
- byte[] encryptionKeyBytes,
- byte[] iv,
- byte[] authenticationTag,
- ArbitraryDataProvider arbitraryDataProvider,
- User user)
- throws NoSuchAlgorithmException,
- InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
- BadPaddingException, IllegalBlockSizeException, IOException {
- Cipher cipher = Cipher.getInstance(AES_CIPHER);
- Key key = new SecretKeySpec(encryptionKeyBytes, AES);
- GCMParameterSpec spec = new GCMParameterSpec(128, iv);
- cipher.init(Cipher.DECRYPT_MODE, key, spec);
- RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
- byte[] fileBytes = new byte[(int) randomAccessFile.length()];
- randomAccessFile.readFully(fileBytes);
- // check authentication tag
- byte[] extractedAuthenticationTag = Arrays.copyOfRange(fileBytes,
- fileBytes.length - (128 / 8),
- fileBytes.length);
- if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
- reportE2eError(arbitraryDataProvider, user);
- throw new SecurityException("Tag not correct");
- }
- return cipher.doFinal(fileBytes);
- }
- /**
- * Encrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
- * and public key
- *
- * @param string String to encrypt
- * @param cert contains public key in it
- * @return encrypted string
- */
- public static String encryptStringAsymmetric(String string, String cert)
- throws NoSuchAlgorithmException,
- NoSuchPaddingException, InvalidKeyException,
- BadPaddingException, IllegalBlockSizeException,
- CertificateException {
- Cipher cipher = Cipher.getInstance(RSA_CIPHER);
- String trimmedCert = cert.replace("-----BEGIN CERTIFICATE-----\n", "")
- .replace("-----END CERTIFICATE-----\n", "");
- byte[] encodedCert = trimmedCert.getBytes(StandardCharsets.UTF_8);
- byte[] decodedCert = org.apache.commons.codec.binary.Base64.decodeBase64(encodedCert);
- CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
- InputStream in = new ByteArrayInputStream(decodedCert);
- X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(in);
- PublicKey realPublicKey = certificate.getPublicKey();
- cipher.init(Cipher.ENCRYPT_MODE, realPublicKey);
- byte[] bytes = encodeStringToBase64Bytes(string);
- byte[] cryptedBytes = cipher.doFinal(bytes);
- return encodeBytesToBase64String(cryptedBytes);
- }
- public static String encryptStringAsymmetricV2(byte[] bytes, String cert)
- throws NoSuchAlgorithmException,
- NoSuchPaddingException, InvalidKeyException,
- BadPaddingException, IllegalBlockSizeException,
- CertificateException {
- Cipher cipher = Cipher.getInstance(RSA_CIPHER);
- String trimmedCert = cert.replace("-----BEGIN CERTIFICATE-----\n", "")
- .replace("-----END CERTIFICATE-----\n", "");
- byte[] encodedCert = trimmedCert.getBytes(StandardCharsets.UTF_8);
- byte[] decodedCert = org.apache.commons.codec.binary.Base64.decodeBase64(encodedCert);
- CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
- InputStream in = new ByteArrayInputStream(decodedCert);
- X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(in);
- PublicKey realPublicKey = certificate.getPublicKey();
- cipher.init(Cipher.ENCRYPT_MODE, realPublicKey);
- byte[] cryptedBytes = cipher.doFinal(bytes);
- return encodeBytesToBase64String(cryptedBytes);
- }
- public static String encryptStringAsymmetric(String string, PublicKey publicKey) throws NoSuchPaddingException,
- NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
- Cipher cipher = Cipher.getInstance(RSA_CIPHER);
- cipher.init(Cipher.ENCRYPT_MODE, publicKey);
- byte[] bytes = encodeStringToBase64Bytes(string);
- byte[] cryptedBytes = cipher.doFinal(bytes);
- return encodeBytesToBase64String(cryptedBytes);
- }
- /**
- * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
- * and public key
- *
- * @param string string to decrypt
- * @param privateKeyString private key
- * @return decrypted string
- */
- public static String decryptStringAsymmetric(String string, String privateKeyString)
- throws NoSuchAlgorithmException,
- NoSuchPaddingException, InvalidKeyException,
- BadPaddingException, IllegalBlockSizeException,
- InvalidKeySpecException {
- Cipher cipher = Cipher.getInstance(RSA_CIPHER);
- byte[] privateKeyBytes = decodeStringToBase64Bytes(privateKeyString);
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
- KeyFactory kf = KeyFactory.getInstance(RSA);
- PrivateKey privateKey = kf.generatePrivate(keySpec);
- cipher.init(Cipher.DECRYPT_MODE, privateKey);
- byte[] bytes = decodeStringToBase64Bytes(string);
- byte[] encodedBytes = cipher.doFinal(bytes);
- return decodeBase64BytesToString(encodedBytes);
- }
- public static byte[] decryptStringAsymmetricAsBytes(String string, String privateKeyString)
- throws NoSuchAlgorithmException,
- NoSuchPaddingException, InvalidKeyException,
- BadPaddingException, IllegalBlockSizeException,
- InvalidKeySpecException {
- Cipher cipher = Cipher.getInstance(RSA_CIPHER);
- byte[] privateKeyBytes = decodeStringToBase64Bytes(privateKeyString);
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
- KeyFactory kf = KeyFactory.getInstance(RSA);
- PrivateKey privateKey = kf.generatePrivate(keySpec);
- cipher.init(Cipher.DECRYPT_MODE, privateKey);
- byte[] bytes = decodeStringToBase64Bytes(string);
- return cipher.doFinal(bytes);
- }
- public static byte[] decryptStringAsymmetricV2(String string, String privateKeyString)
- throws NoSuchAlgorithmException,
- NoSuchPaddingException, InvalidKeyException,
- BadPaddingException, IllegalBlockSizeException,
- InvalidKeySpecException {
- Cipher cipher = Cipher.getInstance(RSA_CIPHER);
- byte[] privateKeyBytes = decodeStringToBase64Bytes(privateKeyString);
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
- KeyFactory kf = KeyFactory.getInstance(RSA);
- PrivateKey privateKey = kf.generatePrivate(keySpec);
- cipher.init(Cipher.DECRYPT_MODE, privateKey);
- byte[] bytes;
- try {
- bytes = decodeStringToBase64Bytes(string);
- } catch (Exception e) {
- bytes = encodeStringToBase64Bytes(string);
- }
- return cipher.doFinal(bytes);
- }
- /**
- * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
- * and public key
- *
- * @param string string to decrypt
- * @param privateKey private key
- * @return decrypted string
- */
- public static String decryptStringAsymmetric(String string, PrivateKey privateKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
- Cipher cipher = Cipher.getInstance(RSA_CIPHER);
- cipher.init(Cipher.DECRYPT_MODE, privateKey);
- byte[] bytes = decodeStringToBase64Bytes(string);
- byte[] encodedBytes = cipher.doFinal(bytes);
- return decodeBase64BytesToString(encodedBytes);
- }
- /**
- * Decrypt string with AES/GCM/NoPadding
- *
- * @param string string to decrypt
- * @param encryptionKeyBytes key from metadata
- * @return decrypted string
- */
- public static String encryptStringSymmetricAsString(String string, byte[] encryptionKeyBytes)
- throws NoSuchPaddingException,
- InvalidKeyException,
- NoSuchAlgorithmException,
- IllegalBlockSizeException,
- BadPaddingException,
- InvalidAlgorithmParameterException {
- EncryptedMetadata metadata = encryptStringSymmetric(string, encryptionKeyBytes, ivDelimiter);
- return metadata.getCiphertext();
- }
- @VisibleForTesting
- public static String encryptStringSymmetricAsStringOld(String string, byte[] encryptionKeyBytes)
- throws NoSuchPaddingException,
- InvalidKeyException,
- NoSuchAlgorithmException,
- IllegalBlockSizeException,
- BadPaddingException,
- InvalidAlgorithmParameterException {
- EncryptedMetadata metadata = encryptStringSymmetric(string, encryptionKeyBytes, ivDelimiterOld);
- return metadata.getCiphertext();
- }
- // /**
- // * Encrypt string with AES/GCM/NoPadding
- // *
- // * @param string string to encrypt
- // * @param encryptionKeyBytes key from metadata
- // * @return decrypted string
- // */
- // private static String encryptStringSymmetric(String string,
- // byte[] encryptionKeyBytes,
- // String delimiter)
- // throws NoSuchAlgorithmException,
- // InvalidAlgorithmParameterException,
- // NoSuchPaddingException,
- // InvalidKeyException,
- // BadPaddingException,
- // IllegalBlockSizeException {
- //
- // Cipher cipher = Cipher.getInstance(AES_CIPHER);
- // byte[] iv = randomBytes(ivLength);
- //
- // Key key = new SecretKeySpec(encryptionKeyBytes, AES);
- // GCMParameterSpec spec = new GCMParameterSpec(128, iv);
- // cipher.init(Cipher.ENCRYPT_MODE, key, spec);
- //
- // byte[] bytes = encodeStringToBase64Bytes(string);
- // byte[] cryptedBytes = cipher.doFinal(bytes);
- //
- // String encodedCryptedBytes = encodeBytesToBase64String(cryptedBytes);
- // String encodedIV = encodeBytesToBase64String(iv);
- //
- // return encodedCryptedBytes + delimiter + encodedIV;
- // }
- public static String decryptStringSymmetricAsString(String string,
- byte[] encryptionKeyBytes,
- byte[] iv,
- byte[] authenticationTag,
- ArbitraryDataProvider arbitraryDataProvider,
- User user
- ) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
- return decryptStringSymmetricAsString(
- decodeStringToBase64Bytes(string),
- encryptionKeyBytes,
- iv,
- authenticationTag,
- false,
- arbitraryDataProvider,
- user);
- }
- public static String decryptStringSymmetricAsString(String string,
- byte[] encryptionKeyBytes,
- byte[] iv,
- byte[] authenticationTag,
- boolean fileDropV2,
- ArbitraryDataProvider arbitraryDataProvider,
- User user) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
- return decryptStringSymmetricAsString(
- decodeStringToBase64Bytes(string),
- encryptionKeyBytes,
- iv,
- authenticationTag,
- fileDropV2,
- arbitraryDataProvider,
- user);
- }
- public static String decryptStringSymmetricAsString(byte[] bytes,
- byte[] encryptionKeyBytes,
- byte[] iv,
- byte[] authenticationTag,
- boolean fileDropV2,
- ArbitraryDataProvider arbitraryDataProvider,
- User user)
- throws NoSuchPaddingException,
- NoSuchAlgorithmException,
- InvalidAlgorithmParameterException,
- InvalidKeyException,
- IllegalBlockSizeException,
- BadPaddingException {
- Cipher cipher = Cipher.getInstance(AES_CIPHER);
- Key key = new SecretKeySpec(encryptionKeyBytes, AES);
- GCMParameterSpec spec = new GCMParameterSpec(128, iv);
- cipher.init(Cipher.DECRYPT_MODE, key, spec);
- // check authentication tag
- byte[] extractedAuthenticationTag = Arrays.copyOfRange(bytes,
- bytes.length - (128 / 8),
- bytes.length);
- if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
- reportE2eError(arbitraryDataProvider, user);
- throw new SecurityException("Tag not correct");
- }
- byte[] encodedBytes = cipher.doFinal(bytes);
- if (fileDropV2) {
- return new EncryptionUtilsV2().gZipDecompress(encodedBytes);
- } else {
- return decodeBase64BytesToString(encodedBytes);
- }
- }
- public static EncryptedMetadata encryptStringSymmetric(
- String string,
- byte[] encryptionKeyBytes) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
- return encryptStringSymmetric(string, encryptionKeyBytes, ivDelimiter);
- }
- public static EncryptedMetadata encryptStringSymmetric(
- String string,
- byte[] encryptionKeyBytes,
- String delimiter) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
- byte[] bytes = encodeStringToBase64Bytes(string);
- return encryptStringSymmetric(bytes, encryptionKeyBytes, delimiter);
- }
- /**
- * Encrypt string with AES/GCM/NoPadding
- *
- * @param bytes byte array
- * @param encryptionKeyBytes key from metadata
- * @return decrypted string
- */
- public static EncryptedMetadata encryptStringSymmetric(
- byte[] bytes,
- byte[] encryptionKeyBytes,
- String delimiter)
- throws NoSuchAlgorithmException,
- InvalidAlgorithmParameterException,
- NoSuchPaddingException,
- InvalidKeyException,
- BadPaddingException,
- IllegalBlockSizeException {
- Cipher cipher = Cipher.getInstance(AES_CIPHER);
- byte[] iv = randomBytes(ivLength);
- Key key = new SecretKeySpec(encryptionKeyBytes, AES);
- GCMParameterSpec spec = new GCMParameterSpec(128, iv);
- cipher.init(Cipher.ENCRYPT_MODE, key, spec);
- byte[] cryptedBytes = cipher.doFinal(bytes);
- String encodedCryptedBytes = encodeBytesToBase64String(cryptedBytes);
- String encodedIV = encodeBytesToBase64String(iv);
- String authenticationTag = encodeBytesToBase64String(Arrays.copyOfRange(cryptedBytes,
- cryptedBytes.length - (128 / 8),
- cryptedBytes.length));
- return new EncryptedMetadata(encodedCryptedBytes + delimiter + encodedIV, encodedIV, authenticationTag);
- }
- /**
- * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
- * and public key
- *
- * @param string string to decrypt
- * @param encryptionKeyBytes key from metadata
- * @return decrypted string
- */
- public static String decryptStringSymmetric(String string, byte[] encryptionKeyBytes)
- throws NoSuchAlgorithmException,
- InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
- BadPaddingException, IllegalBlockSizeException {
- Cipher cipher = Cipher.getInstance(AES_CIPHER);
- String ivString;
- int delimiterPosition = string.lastIndexOf(ivDelimiter);
- if (delimiterPosition == -1) {
- // backward compatibility
- delimiterPosition = string.lastIndexOf(ivDelimiterOld);
- ivString = string.substring(delimiterPosition + ivDelimiterOld.length());
- } else {
- ivString = string.substring(delimiterPosition + ivDelimiter.length());
- }
- String cipherString = string.substring(0, delimiterPosition);
- byte[] iv = new IvParameterSpec(decodeStringToBase64Bytes(ivString)).getIV();
- Key key = new SecretKeySpec(encryptionKeyBytes, AES);
- GCMParameterSpec spec = new GCMParameterSpec(128, iv);
- cipher.init(Cipher.DECRYPT_MODE, key, spec);
- byte[] bytes = decodeStringToBase64Bytes(cipherString);
- byte[] encodedBytes = cipher.doFinal(bytes);
- return decodeBase64BytesToString(encodedBytes);
- }
- /**
- * Decrypt string with AES/GCM/NoPadding
- *
- * @param string string to decrypt
- * @param encryptionKeyBytes key from metadata
- * @param authenticationTag auth tag to check
- * @return decrypted string
- */
- public static byte[] decryptStringSymmetric(String string,
- byte[] encryptionKeyBytes,
- String authenticationTag,
- String ivString)
- throws NoSuchAlgorithmException,
- InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
- BadPaddingException, IllegalBlockSizeException {
- Cipher cipher = Cipher.getInstance(AES_CIPHER);
- int delimiterPosition = string.lastIndexOf(ivDelimiter);
- String cipherString;
- if (delimiterPosition == -1) {
- cipherString = string;
- } else {
- cipherString = string.substring(0, delimiterPosition);
- }
- byte[] iv = new IvParameterSpec(decodeStringToBase64Bytes(ivString)).getIV();
- Key key = new SecretKeySpec(encryptionKeyBytes, AES);
- GCMParameterSpec spec = new GCMParameterSpec(128, iv);
- cipher.init(Cipher.DECRYPT_MODE, key, spec);
- byte[] bytes = decodeStringToBase64Bytes(cipherString);
- // check authentication tag
- if (authenticationTag != null) {
- byte[] authenticationTagBytes = decodeStringToBase64Bytes(authenticationTag);
- byte[] extractedAuthenticationTag = Arrays.copyOfRange(bytes,
- bytes.length - (128 / 8),
- bytes.length);
- if (!Arrays.equals(extractedAuthenticationTag, authenticationTagBytes)) {
- throw new SecurityException("Tag not correct");
- }
- }
- return cipher.doFinal(bytes);
- }
- /**
- * Encrypt private key with symmetric AES encryption, GCM mode mode and no padding
- *
- * @param privateKey byte64 encoded string representation of private key
- * @param keyPhrase key used for encryption, e.g. 12 random words
- * {@link EncryptionUtils#getRandomWords(int, Context)}
- * @return encrypted string, bytes first encoded base64, IV separated with "|", then to string
- */
- public static String encryptPrivateKey(String privateKey, String keyPhrase)
- throws NoSuchPaddingException,
- NoSuchAlgorithmException,
- InvalidKeyException,
- BadPaddingException,
- IllegalBlockSizeException,
- InvalidKeySpecException {
- return encryptPrivateKey(privateKey, keyPhrase, ivDelimiter);
- }
- @VisibleForTesting
- public static String encryptPrivateKeyOld(String privateKey, String keyPhrase)
- throws NoSuchPaddingException,
- NoSuchAlgorithmException,
- InvalidKeyException,
- BadPaddingException,
- IllegalBlockSizeException,
- InvalidKeySpecException {
- return encryptPrivateKey(privateKey, keyPhrase, ivDelimiterOld);
- }
- private static String encryptPrivateKey(String privateKey, String keyPhrase, String delimiter)
- throws NoSuchPaddingException,
- NoSuchAlgorithmException,
- InvalidKeyException,
- BadPaddingException,
- IllegalBlockSizeException,
- InvalidKeySpecException {
- Cipher cipher = Cipher.getInstance(AES_CIPHER);
- SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
- byte[] salt = randomBytes(saltLength);
- KeySpec spec = new PBEKeySpec(keyPhrase.toCharArray(), salt, iterationCount, keyStrength);
- SecretKey tmp = factory.generateSecret(spec);
- SecretKeySpec key = new SecretKeySpec(tmp.getEncoded(), AES);
- cipher.init(Cipher.ENCRYPT_MODE, key);
- byte[] bytes = encodeStringToBase64Bytes(privateKey);
- byte[] encrypted = cipher.doFinal(bytes);
- byte[] iv = cipher.getIV();
- String encodedIV = encodeBytesToBase64String(iv);
- String encodedSalt = encodeBytesToBase64String(salt);
- String encodedEncryptedBytes = encodeBytesToBase64String(encrypted);
- return encodedEncryptedBytes + delimiter + encodedIV + delimiter + encodedSalt;
- }
- /**
- * Decrypt private key with symmetric AES encryption, GCM mode mode and no padding
- *
- * @param privateKey byte64 encoded string representation of private key, IV separated with "|"
- * @param keyPhrase key used for encryption, e.g. 12 random words
- * {@link EncryptionUtils#getRandomWords(int, Context)}
- * @return decrypted string
- */
- @SuppressFBWarnings("UCPM_USE_CHARACTER_PARAMETERIZED_METHOD")
- public static String decryptPrivateKey(String privateKey, String keyPhrase) throws NoSuchPaddingException,
- NoSuchAlgorithmException, InvalidKeyException, BadPaddingException,
- IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException {
- String[] strings;
- // split up iv, salt
- if (privateKey.lastIndexOf(ivDelimiter) == -1) {
- // backward compatibility
- strings = privateKey.split(ivDelimiterOld);
- } else {
- strings = privateKey.split("\\" + ivDelimiter);
- }
- String realPrivateKey = strings[0];
- byte[] iv = decodeStringToBase64Bytes(strings[1]);
- byte[] salt = decodeStringToBase64Bytes(strings[2]);
- Cipher cipher = Cipher.getInstance(AES_CIPHER);
- SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
- KeySpec spec = new PBEKeySpec(keyPhrase.toCharArray(), salt, iterationCount, keyStrength);
- SecretKey tmp = factory.generateSecret(spec);
- SecretKeySpec key = new SecretKeySpec(tmp.getEncoded(), AES);
- cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
- byte[] bytes = decodeStringToBase64Bytes(realPrivateKey);
- byte[] decrypted = cipher.doFinal(bytes);
- String pemKey = decodeBase64BytesToString(decrypted);
- return pemKey.replaceAll("\n", "").replace("-----BEGIN PRIVATE KEY-----", "")
- .replace("-----END PRIVATE KEY-----", "");
- }
- public static String privateKeyToPEM(PrivateKey privateKey) {
- String privateKeyString = encodeBytesToBase64String(privateKey.getEncoded());
- return "-----BEGIN PRIVATE KEY-----\n" + privateKeyString.replaceAll("(.{65})", "$1\n")
- + "\n-----END PRIVATE KEY-----";
- }
-
- public static PrivateKey PEMtoPrivateKey(String pem) throws NoSuchAlgorithmException, InvalidKeySpecException {
- byte[] privateKeyBytes = EncryptionUtils.decodeStringToBase64Bytes(pem);
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
- KeyFactory kf = KeyFactory.getInstance(EncryptionUtils.RSA);
- return kf.generatePrivate(keySpec);
- }
- /*
- Helper
- */
- public static ArrayList<String> getRandomWords(int count, Context context) throws IOException {
- InputStream ins = context.getResources().openRawResource(R.raw.encryption_key_words);
- InputStreamReader inputStreamReader = new InputStreamReader(ins);
- BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
- List<String> lines = new ArrayList<>();
- String line;
- while ((line = bufferedReader.readLine()) != null) {
- lines.add(line);
- }
- SecureRandom random = new SecureRandom();
- ArrayList<String> outputLines = Lists.newArrayListWithCapacity(count);
- for (int i = 0; i < count; i++) {
- int randomLine = random.nextInt(lines.size());
- outputLines.add(lines.get(randomLine));
- }
- return outputLines;
- }
- /**
- * Generates private/public key pair, used for asymmetric encryption
- *
- * @return KeyPair
- */
- public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
- KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA);
- keyGen.initialize(2048, new SecureRandom());
- return keyGen.generateKeyPair();
- }
- /**
- * Generates key for symmetric encryption
- *
- * @return byte[] byteArray of key
- */
- public static byte[] generateKey() {
- KeyGenerator keyGenerator;
- try {
- keyGenerator = KeyGenerator.getInstance(AES);
- keyGenerator.init(128);
- return keyGenerator.generateKey().getEncoded();
- } catch (NoSuchAlgorithmException e) {
- Log_OC.e(TAG, e.getMessage());
- }
- return null;
- }
- /**
- * Generates key for symmetric encryption
- *
- * @return String String base64 encoded key
- */
- public static String generateKeyString() {
- return EncryptionUtils.encodeBytesToBase64String(generateKey());
- }
- public static byte[] randomBytes(int size) {
- SecureRandom random = new SecureRandom();
- final byte[] iv = new byte[size];
- random.nextBytes(iv);
- return iv;
- }
- /**
- * Generate a SHA512 with appended salt
- *
- * @param token token to be hashed
- * @return SHA512 with appended salt, delimiter HASH_DELIMITER
- */
- public static String generateSHA512(String token) {
- String salt = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.randomBytes(EncryptionUtils.saltLength));
- return generateSHA512(token, salt);
- }
- /**
- * Generate a SHA512 with appended salt
- *
- * @param token token to be hashed
- * @return SHA512 with appended salt, delimiter HASH_DELIMITER
- */
- public static String generateSHA512(String token, String salt) {
- MessageDigest digest;
- String hashedToken = "";
- byte[] hash;
- try {
- digest = MessageDigest.getInstance("SHA-512");
- digest.update(salt.getBytes());
- hash = digest.digest(token.getBytes());
- StringBuilder stringBuilder = new StringBuilder();
- for (byte hashByte : hash) {
- stringBuilder.append(Integer.toString((hashByte & 0xff) + 0x100, 16).substring(1));
- }
- stringBuilder.append(HASH_DELIMITER).append(salt);
- hashedToken = stringBuilder.toString();
- } catch (NoSuchAlgorithmException e) {
- Log_OC.e(TAG, "Generating SHA512 failed", e);
- }
- return hashedToken;
- }
- public static boolean verifySHA512(String hashWithSalt, String compareToken) {
- String salt = hashWithSalt.split("\\" + HASH_DELIMITER)[1];
- String newHash = generateSHA512(compareToken, salt);
- return hashWithSalt.equals(newHash);
- }
- public static String lockFolder(ServerFileInterface parentFile, OwnCloudClient client) throws UploadException {
- return lockFolder(parentFile, client, -1);
- }
- public static String lockFolder(ServerFileInterface parentFile, OwnCloudClient client, long counter) throws UploadException {
- // Lock folder
- LockFileRemoteOperation lockFileOperation = new LockFileRemoteOperation(parentFile.getLocalId(),
- counter);
- RemoteOperationResult<String> lockFileOperationResult = lockFileOperation.execute(client);
- if (lockFileOperationResult.isSuccess() &&
- !TextUtils.isEmpty(lockFileOperationResult.getResultData())) {
- return lockFileOperationResult.getResultData();
- } else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
- throw new UploadException("Forbidden! Please try again later.)");
- } else {
- throw new UploadException("Could not lock folder");
- }
- }
- /**
- * @param parentFile file metadata should be retrieved for
- * @return Pair: boolean: true: metadata already exists, false: metadata new created
- */
- public static Pair<Boolean, DecryptedFolderMetadataFileV1> retrieveMetadataV1(OCFile parentFile,
- OwnCloudClient client,
- String privateKey,
- String publicKey,
- ArbitraryDataProvider arbitraryDataProvider,
- User user)
- throws UploadException,
- InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException,
- IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException {
- long localId = parentFile.getLocalId();
- GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(localId);
- RemoteOperationResult<MetadataResponse> getMetadataOperationResult = getMetadataOperation.execute(client);
- DecryptedFolderMetadataFileV1 metadata;
- if (getMetadataOperationResult.isSuccess()) {
- // decrypt metadata
- String serializedEncryptedMetadata = getMetadataOperationResult.getResultData().getMetadata();
- EncryptedFolderMetadataFileV1 encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
- serializedEncryptedMetadata, new TypeToken<>() {
- });
- return new Pair<>(Boolean.TRUE, decryptFolderMetaData(encryptedFolderMetadata,
- privateKey,
- arbitraryDataProvider,
- user,
- localId));
- } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
- // TODO extract
- // new metadata
- metadata = new DecryptedFolderMetadataFileV1();
- metadata.setMetadata(new DecryptedMetadata());
- metadata.getMetadata().setVersion(Double.parseDouble(E2EVersion.V1_2.getValue()));
- metadata.getMetadata().setMetadataKeys(new HashMap<>());
- String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
- String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
- metadata.getMetadata().setMetadataKey(encryptedMetadataKey);
- return new Pair<>(Boolean.FALSE, metadata);
- } else {
- // TODO E2E: error
- throw new UploadException("something wrong");
- }
- }
- /**
- * @param parentFile file metadata should be retrieved for
- * @return Pair: boolean: true: metadata already exists, false: metadata new created
- */
- public static Pair<Boolean, DecryptedFolderMetadataFile> retrieveMetadata(OCFile parentFile,
- OwnCloudClient client,
- String privateKey,
- String publicKey,
- FileDataStorageManager storageManager,
- User user,
- Context context,
- ArbitraryDataProvider arbitraryDataProvider)
- throws UploadException, Throwable,
- InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException,
- IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException {
- long localId = parentFile.getLocalId();
- GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(localId);
- RemoteOperationResult<MetadataResponse> getMetadataOperationResult = getMetadataOperation.execute(client);
- DecryptedFolderMetadataFile metadata;
- if (getMetadataOperationResult.isSuccess()) {
- // decrypt metadata
- String serializedEncryptedMetadata = getMetadataOperationResult.getResultData().getMetadata();
- EncryptedFolderMetadataFile encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
- serializedEncryptedMetadata, new TypeToken<>() {
- });
- return new Pair<>(Boolean.TRUE,
- new EncryptionUtilsV2().decryptFolderMetadataFile(encryptedFolderMetadata,
- client.getUserId(),
- privateKey,
- parentFile,
- storageManager,
- client,
- parentFile.getE2eCounter(),
- getMetadataOperationResult.getResultData().getSignature(),
- user,
- context,
- arbitraryDataProvider)
- );
- } else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND ||
- getMetadataOperationResult.getHttpCode() == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
- // new metadata
- metadata = new DecryptedFolderMetadataFile(new com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedMetadata(),
- new ArrayList<>(),
- new HashMap<>(),
- E2EVersion.V2_0.getValue());
- metadata.getUsers().add(new DecryptedUser(client.getUserId(), publicKey));
- byte[] metadataKey = EncryptionUtils.generateKey();
- if (metadataKey == null) {
- throw new UploadException("Could not encrypt folder!");
- }
- metadata.getMetadata().setMetadataKey(metadataKey);
- metadata.getMetadata().getKeyChecksums().add(new EncryptionUtilsV2().hashMetadataKey(metadataKey));
- return new Pair<>(Boolean.FALSE, metadata);
- } else {
- reportE2eError(arbitraryDataProvider, user);
- throw new UploadException("something wrong");
- }
- }
- public static void uploadMetadata(ServerFileInterface parentFile,
- String serializedFolderMetadata,
- String token,
- OwnCloudClient client,
- boolean metadataExists,
- E2EVersion version,
- String signature,
- ArbitraryDataProvider arbitraryDataProvider,
- User user) throws UploadException {
- RemoteOperationResult<String> uploadMetadataOperationResult;
- if (metadataExists) {
- // update metadata
- if (version == E2EVersion.V2_0) {
- uploadMetadataOperationResult = new UpdateMetadataV2RemoteOperation(
- parentFile.getRemoteId(),
- serializedFolderMetadata,
- token,
- signature)
- .execute(client);
- } else {
- uploadMetadataOperationResult = new UpdateMetadataRemoteOperation(
- parentFile.getLocalId(),
- serializedFolderMetadata,
- token)
- .execute(client);
- }
- } else {
- // store metadata
- if (version == E2EVersion.V2_0) {
- uploadMetadataOperationResult = new StoreMetadataV2RemoteOperation(
- parentFile.getRemoteId(),
- serializedFolderMetadata,
- token,
- signature
- )
- .execute(client);
- } else {
- uploadMetadataOperationResult = new StoreMetadataRemoteOperation(
- parentFile.getLocalId(),
- serializedFolderMetadata
- )
- .execute(client);
- }
- }
- if (!uploadMetadataOperationResult.isSuccess()) {
- reportE2eError(arbitraryDataProvider, user);
- throw new UploadException("Storing/updating metadata was not successful");
- }
- }
- public static RemoteOperationResult<Void> unlockFolder(ServerFileInterface parentFolder, OwnCloudClient client, String token) {
- if (token != null) {
- return new UnlockFileRemoteOperation(parentFolder.getLocalId(), token).execute(client);
- } else {
- return new RemoteOperationResult<>(new Exception("No token available"));
- }
- }
- public static RemoteOperationResult<Void> unlockFolderV1(ServerFileInterface parentFolder, OwnCloudClient client, String token) {
- if (token != null) {
- return new UnlockFileV1RemoteOperation(parentFolder.getLocalId(), token).execute(client);
- } else {
- return new RemoteOperationResult<>(new Exception("No token available"));
- }
- }
- public static X509Certificate convertCertFromString(String string) throws CertificateException {
- String trimmedCert = string.replace("-----BEGIN CERTIFICATE-----\n", "")
- .replace("-----END CERTIFICATE-----\n", "");
- byte[] encodedCert = trimmedCert.getBytes(StandardCharsets.UTF_8);
- byte[] decodedCert = org.apache.commons.codec.binary.Base64.decodeBase64(encodedCert);
- CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
- InputStream in = new ByteArrayInputStream(decodedCert);
- return (X509Certificate) certFactory.generateCertificate(in);
- }
- public static RSAPublicKey convertPublicKeyFromString(String string) throws CertificateException {
- return (RSAPublicKey) convertCertFromString(string).getPublicKey();
- }
- public static void removeE2E(ArbitraryDataProvider arbitraryDataProvider, User user) {
- // delete stored E2E keys and mnemonic
- arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
- arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
- arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(), EncryptionUtils.MNEMONIC);
- }
- public static boolean isMatchingKeys(KeyPair keyPair, String publicKeyString) throws CertificateException {
- // check key
- RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyPair.getPrivate();
- RSAPublicKey publicKey = EncryptionUtils.convertPublicKeyFromString(publicKeyString);
- BigInteger modulusPublic = publicKey.getModulus();
- BigInteger modulusPrivate = privateKey.getModulus();
- return modulusPrivate.compareTo(modulusPublic) == 0;
- }
- public static boolean supportsSecureFiledrop(OCFile file, User user) {
- return file.isEncrypted() &&
- file.isFolder() &&
- user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_26);
- }
- public static String generateChecksum(DecryptedFolderMetadataFileV1 metadataFile,
- String mnemonic) throws NoSuchAlgorithmException {
- StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append(mnemonic.replaceAll(" ", ""));
- ArrayList<String> keys = new ArrayList<>(metadataFile.getFiles().keySet());
- Collections.sort(keys);
- for (String key : keys) {
- stringBuilder.append(key);
- }
- stringBuilder.append(metadataFile.getMetadata().getMetadataKey());
- // sha256 hash-sum
- return sha256(stringBuilder.toString());
- }
- /**
- * SHA-256 hash of metadata-key
- */
- public static String sha256(String string) throws NoSuchAlgorithmException {
- byte[] bytes = MessageDigest
- .getInstance("SHA-256")
- .digest(string.getBytes(StandardCharsets.UTF_8));
- return bytesToHex(bytes);
- }
- public static String bytesToHex(byte[] bytes) {
- StringBuilder result = new StringBuilder();
- for (byte individualByte : bytes) {
- result.append(Integer.toString((individualByte & 0xff) + 0x100, 16)
- .substring(1));
- }
- return result.toString();
- }
- public static void addIdToMigratedIds(long id,
- User user,
- ArbitraryDataProvider arbitraryDataProvider) {
- Gson gson = new Gson();
- String ids = arbitraryDataProvider.getValue(user, MIGRATED_FOLDER_IDS);
- ArrayList<Long> arrayList = gson.fromJson(ids, ArrayList.class);
- if (arrayList == null) {
- arrayList = new ArrayList<>();
- }
- if (arrayList.contains(id)) {
- // nothing to do here
- return;
- }
- arrayList.add(id);
- String json = gson.toJson(arrayList);
- arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(),
- MIGRATED_FOLDER_IDS,
- json);
- }
- public static boolean isFolderMigrated(long id,
- User user,
- ArbitraryDataProvider arbitraryDataProvider) {
- Gson gson = new Gson();
- String ids = arbitraryDataProvider.getValue(user, MIGRATED_FOLDER_IDS);
- ArrayList<Long> arrayList = gson.fromJson(ids, new TypeToken<List<Long>>() {
- }.getType());
- if (arrayList == null) {
- return false;
- }
- return arrayList.contains(id);
- }
- public static void reportE2eError(ArbitraryDataProvider arbitraryDataProvider, User user) {
- arbitraryDataProvider.incrementValue(user.getAccountName(), ArbitraryDataProvider.E2E_ERRORS);
- if (arbitraryDataProvider.getLongValue(user.getAccountName(),
- ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP) == -1L) {
- arbitraryDataProvider.storeOrUpdateKeyValue(
- user.getAccountName(),
- ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP,
- System.currentTimeMillis() / 1000
- );
- }
- }
- @Nullable
- public static Problem readE2eError(ArbitraryDataProvider arbitraryDataProvider, User user) {
- int value = arbitraryDataProvider.getIntegerValue(user.getAccountName(),
- ArbitraryDataProvider.E2E_ERRORS);
- long timestamp = arbitraryDataProvider.getLongValue(user.getAccountName(),
- ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP);
- arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(),
- ArbitraryDataProvider.E2E_ERRORS);
- arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(),
- ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP);
- if (value > 0 && timestamp > 0) {
- return new Problem(SendClientDiagnosticRemoteOperation.E2EE_ERRORS, value, timestamp);
- } else {
- return null;
- }
- }
- public static String generateUid() {
- return UUID.randomUUID().toString().replaceAll("-", "");
- }
- public static String retrievePublicKeyForUser(User user, Context context) {
- return new ArbitraryDataProviderImpl(context).getValue(user, PUBLIC_KEY);
- }
- public static byte[] generateIV() {
- return EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
- }
- public static String byteToHex(byte[] bytes) {
- StringBuilder sbKey = new StringBuilder();
- for (byte b : bytes) {
- sbKey.append(String.format("%02X ", b));
- }
- return sbKey.toString();
- }
- public static void savePublicKey(User currentUser,
- String key,
- String user,
- ArbitraryDataProvider arbitraryDataProvider) {
- arbitraryDataProvider.storeOrUpdateKeyValue(currentUser,
- ArbitraryDataProvider.PUBLIC_KEY + user,
- key);
- }
- public static String getPublicKey(User currentUser,
- String user,
- ArbitraryDataProvider arbitraryDataProvider) {
- return arbitraryDataProvider.getValue(currentUser, ArbitraryDataProvider.PUBLIC_KEY + user);
- }
- }
|