1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723 |
- /*
- * 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.os.Build;
- import android.text.TextUtils;
- import android.util.Base64;
- import android.util.Pair;
- import com.google.common.collect.Lists;
- import com.google.common.primitives.Bytes;
- 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.ByteArrayOutputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- 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.nio.file.Files;
- 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.InvalidParameterSpecException;
- 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.CipherOutputStream;
- 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.RequiresApi;
- 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, IOException, InvalidParameterSpecException {
- File file = new File(ocFile.getStoragePath());
- Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, encryptionKeyBytes, iv);
- return encryptFile(file, cipher);
- }
- public static EncryptedFile encryptFile(File file, Cipher cipher) throws IOException, InvalidParameterSpecException {
- File encryptedFile = new File(file.getAbsolutePath() + ".enc");
- encryptFileWithGivenCipher(file, encryptedFile, cipher);
- byte[] authenticationTag = cipher.getParameters().getParameterSpec(GCMParameterSpec.class).getIV();
- String authenticationTagString = encodeBytesToBase64String(authenticationTag);
- return new EncryptedFile(encryptedFile, authenticationTagString);
- }
- public static Cipher getCipher(int mode, byte[] encryptionKeyBytes, byte[] iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
- Cipher cipher = Cipher.getInstance(AES_CIPHER);
- Key key = new SecretKeySpec(encryptionKeyBytes, AES);
- GCMParameterSpec spec = new GCMParameterSpec(128, iv);
- cipher.init(mode, key, spec);
- return cipher;
- }
- public static void encryptFileWithGivenCipher(File inputFile, File encryptedFile, Cipher cipher) throws IOException {
- FileInputStream inputStream = new FileInputStream(inputFile);
- FileOutputStream fileOutputStream = new FileOutputStream(encryptedFile);
- CipherOutputStream outputStream = new CipherOutputStream(fileOutputStream, cipher);
- byte[] buffer = new byte[4096];
- int bytesRead;
- while ((bytesRead = inputStream.read(buffer)) != -1) {
- outputStream.write(buffer, 0, bytesRead);
- }
- outputStream.close();
- inputStream.close();
- }
- // FIXME Decryption is broken
- public static byte[] decryptFile(
- Cipher cipher,
- File file,
- byte[] authenticationTag,
- ArbitraryDataProvider arbitraryDataProvider,
- User user)
- throws BadPaddingException, IllegalBlockSizeException, IOException {
- 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);
- }
- }
|