ch.cyberduck.core.cryptomator.impl.CryptoVaultTest.java Source code

Java tutorial

Introduction

Here is the source code for ch.cyberduck.core.cryptomator.impl.CryptoVaultTest.java

Source

package ch.cyberduck.core.cryptomator.impl;

/*
 * Copyright (c) 2002-2016 iterate GmbH. All rights reserved.
 * https://cyberduck.io/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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 General Public License for more details.
 */

import ch.cyberduck.core.ConnectionCallback;
import ch.cyberduck.core.Credentials;
import ch.cyberduck.core.DisabledPasswordCallback;
import ch.cyberduck.core.DisabledPasswordStore;
import ch.cyberduck.core.Host;
import ch.cyberduck.core.LoginOptions;
import ch.cyberduck.core.NullSession;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.SerializerFactory;
import ch.cyberduck.core.Session;
import ch.cyberduck.core.TestProtocol;
import ch.cyberduck.core.cryptomator.CryptoInvalidFilesizeException;
import ch.cyberduck.core.cryptomator.CryptoVault;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.exception.LoginCanceledException;
import ch.cyberduck.core.features.Directory;
import ch.cyberduck.core.features.Read;
import ch.cyberduck.core.features.Vault;
import ch.cyberduck.core.features.Write;
import ch.cyberduck.core.serializer.PathDictionary;
import ch.cyberduck.core.transfer.TransferStatus;
import ch.cyberduck.core.vault.DefaultVaultRegistry;
import ch.cyberduck.core.vault.LoadingVaultLookupListener;
import ch.cyberduck.core.vault.VaultCredentials;
import ch.cyberduck.core.vault.VaultUnlockCancelException;

import org.apache.commons.io.IOUtils;
import org.junit.Test;

import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.Assert.*;

public class CryptoVaultTest {

    @Test
    public void testLoad() throws Exception {
        final NullSession session = new NullSession(new Host(new TestProtocol())) {
            @Override
            @SuppressWarnings("unchecked")
            public <T> T _getFeature(final Class<T> type) {
                if (type == Read.class) {
                    return (T) new Read() {
                        @Override
                        public InputStream read(final Path file, final TransferStatus status,
                                final ConnectionCallback callback) throws BackgroundException {
                            final String masterKey = "{\n" + "  \"scryptSalt\": \"NrC7QGG/ouc=\",\n"
                                    + "  \"scryptCostParam\": 16384,\n" + "  \"scryptBlockSize\": 8,\n"
                                    + "  \"primaryMasterKey\": \"Q7pGo1l0jmZssoQh9rXFPKJE9NIXvPbL+HcnVSR9CHdkeR8AwgFtcw==\",\n"
                                    + "  \"hmacMasterKey\": \"xzBqT4/7uEcQbhHFLC0YmMy4ykVKbuvJEA46p1Xm25mJNuTc20nCbw==\",\n"
                                    + "  \"versionMac\": \"hlNr3dz/CmuVajhaiGyCem9lcVIUjDfSMLhjppcXOrM=\",\n"
                                    + "  \"version\": 5\n" + "}";
                            return IOUtils.toInputStream(masterKey, Charset.defaultCharset());
                        }

                        @Override
                        public boolean offset(final Path file) throws BackgroundException {
                            return false;
                        }
                    };
                }
                return super._getFeature(type);
            }
        };
        final Path home = new Path("/", EnumSet.of((Path.Type.directory)));
        final CryptoVault vault = new CryptoVault(home, new DisabledPasswordStore());
        vault.load(session, new DisabledPasswordCallback() {
            @Override
            public Credentials prompt(final Host bookmark, final String title, final String reason,
                    final LoginOptions options) throws LoginCanceledException {
                return new VaultCredentials("vault");
            }
        });
        assertEquals(Vault.State.open, vault.getState());
        assertNotSame(home, vault.encrypt(session, home));
        assertEquals(vault.encrypt(session, home), vault.encrypt(session, home));
        final Path directory = new Path(home, "dir", EnumSet.of(Path.Type.directory));
        assertNull(directory.attributes().getVault());
        assertEquals(home, vault.encrypt(session, directory).attributes().getVault());
        assertEquals(home, directory.attributes().getVault());
        assertEquals(vault.encrypt(session, directory), vault.encrypt(session, directory));
        assertEquals(new Path(home, directory.getName(), EnumSet.of(Path.Type.directory, Path.Type.decrypted)),
                vault.decrypt(session, vault.encrypt(session, directory, true)));
        final Path placeholder = new Path(home, "placeholder",
                EnumSet.of(Path.Type.directory, Path.Type.placeholder));
        assertTrue(vault.encrypt(session, placeholder, true).getType().contains(Path.Type.placeholder));
        assertTrue(vault.decrypt(session, vault.encrypt(session, placeholder, true)).getType()
                .contains(Path.Type.placeholder));
        assertEquals(
                new Path(home, placeholder.getName(),
                        EnumSet.of(Path.Type.directory, Path.Type.placeholder, Path.Type.decrypted)),
                vault.decrypt(session, vault.encrypt(session, placeholder, true)));
        assertNotEquals(vault.encrypt(session, directory), vault.encrypt(session, directory, true));
        assertEquals(vault.encrypt(session, directory).attributes().getDirectoryId(),
                vault.encrypt(session, directory).attributes().getDirectoryId());
        assertEquals(vault.encrypt(session, vault.encrypt(session, directory)).attributes().getDirectoryId(),
                vault.encrypt(session, vault.encrypt(session, directory)).attributes().getDirectoryId());
        assertNull(vault.encrypt(session, directory, true).attributes().getDirectoryId());
        assertNull(vault.encrypt(session, vault.encrypt(session, directory), true).attributes().getDirectoryId());
        assertNotEquals(vault.encrypt(session, directory).attributes().getDirectoryId(),
                vault.encrypt(session, directory, true).attributes().getDirectoryId());

        vault.close();
        assertEquals(Vault.State.closed, vault.getState());
    }

    @Test
    public void testFind() throws Exception {
        final NullSession session = new NullSession(new Host(new TestProtocol())) {
            @Override
            @SuppressWarnings("unchecked")
            public <T> T _getFeature(final Class<T> type) {
                if (type == Read.class) {
                    return (T) new Read() {
                        @Override
                        public InputStream read(final Path file, final TransferStatus status,
                                final ConnectionCallback callback) throws BackgroundException {
                            final String masterKey = "{\n" + "  \"scryptSalt\": \"NrC7QGG/ouc=\",\n"
                                    + "  \"scryptCostParam\": 16384,\n" + "  \"scryptBlockSize\": 8,\n"
                                    + "  \"primaryMasterKey\": \"Q7pGo1l0jmZssoQh9rXFPKJE9NIXvPbL+HcnVSR9CHdkeR8AwgFtcw==\",\n"
                                    + "  \"hmacMasterKey\": \"xzBqT4/7uEcQbhHFLC0YmMy4ykVKbuvJEA46p1Xm25mJNuTc20nCbw==\",\n"
                                    + "  \"versionMac\": \"hlNr3dz/CmuVajhaiGyCem9lcVIUjDfSMLhjppcXOrM=\",\n"
                                    + "  \"version\": 5\n" + "}";
                            return IOUtils.toInputStream(masterKey, Charset.defaultCharset());
                        }

                        @Override
                        public boolean offset(final Path file) throws BackgroundException {
                            return false;
                        }
                    };
                }
                return super._getFeature(type);
            }
        };
        final Path home = new Path("/", EnumSet.of((Path.Type.directory)));
        final CryptoVault vault = new CryptoVault(home, new DisabledPasswordStore());
        assertEquals(home, vault.load(session, new DisabledPasswordCallback() {
            @Override
            public Credentials prompt(final Host bookmark, final String title, final String reason,
                    final LoginOptions options) throws LoginCanceledException {
                return new VaultCredentials("vault");
            }
        }).getHome());
        assertEquals(Vault.State.open, vault.getState());
        final AtomicBoolean found = new AtomicBoolean();
        assertEquals(vault, new DefaultVaultRegistry(new DisabledPasswordCallback()) {
            protected Vault find(final Session<?> session, final Path directory,
                    final LoadingVaultLookupListener listener) throws VaultUnlockCancelException {
                found.set(true);
                return vault;
            }
        }.find(session, home));
        assertTrue(found.get());
        vault.close();
    }

    @Test
    public void testSerializeVaultHome() throws Exception {
        final NullSession session = new NullSession(new Host(new TestProtocol())) {
            @Override
            @SuppressWarnings("unchecked")
            public <T> T _getFeature(final Class<T> type) {
                if (type == Read.class) {
                    return (T) new Read() {
                        @Override
                        public InputStream read(final Path file, final TransferStatus status,
                                final ConnectionCallback callback) throws BackgroundException {
                            final String masterKey = "{\n" + "  \"scryptSalt\": \"NrC7QGG/ouc=\",\n"
                                    + "  \"scryptCostParam\": 16384,\n" + "  \"scryptBlockSize\": 8,\n"
                                    + "  \"primaryMasterKey\": \"Q7pGo1l0jmZssoQh9rXFPKJE9NIXvPbL+HcnVSR9CHdkeR8AwgFtcw==\",\n"
                                    + "  \"hmacMasterKey\": \"xzBqT4/7uEcQbhHFLC0YmMy4ykVKbuvJEA46p1Xm25mJNuTc20nCbw==\",\n"
                                    + "  \"versionMac\": \"hlNr3dz/CmuVajhaiGyCem9lcVIUjDfSMLhjppcXOrM=\",\n"
                                    + "  \"version\": 5\n" + "}";
                            return IOUtils.toInputStream(masterKey, Charset.defaultCharset());
                        }

                        @Override
                        public boolean offset(final Path file) throws BackgroundException {
                            return false;
                        }
                    };
                }
                return super._getFeature(type);
            }
        };
        final Path home = new Path("/", EnumSet.of((Path.Type.directory)));
        final CryptoVault vault = new CryptoVault(home, new DisabledPasswordStore());
        assertEquals(home, vault.load(session, new DisabledPasswordCallback() {
            @Override
            public Credentials prompt(final Host bookmark, final String title, final String reason,
                    final LoginOptions options) throws LoginCanceledException {
                return new VaultCredentials("vault");
            }
        }).getHome());
        assertEquals(Vault.State.open, vault.getState());
        assertEquals(home, new PathDictionary().deserialize(home.serialize(SerializerFactory.get())));
        vault.close();
    }

    @Test
    public void testLoadInvalidPassphrase() throws Exception {
        final NullSession session = new NullSession(new Host(new TestProtocol())) {
            @Override
            @SuppressWarnings("unchecked")
            public <T> T _getFeature(final Class<T> type) {
                if (type == Read.class) {
                    return (T) new Read() {
                        @Override
                        public InputStream read(final Path file, final TransferStatus status,
                                final ConnectionCallback callback) throws BackgroundException {
                            final String masterKey = "{\n" + "  \"scryptSalt\": \"NrC7QGG/ouc=\",\n"
                                    + "  \"scryptCostParam\": 16384,\n" + "  \"scryptBlockSize\": 8,\n"
                                    + "  \"primaryMasterKey\": \"Q7pGo1l0jmZssoQh9rXFPKJE9NIXvPbL+HcnVSR9CHdkeR8AwgFtcw==\",\n"
                                    + "  \"hmacMasterKey\": \"xzBqT4/7uEcQbhHFLC0YmMy4ykVKbuvJEA46p1Xm25mJNuTc20nCbw==\",\n"
                                    + "  \"versionMac\": \"hlNr3dz/CmuVajhaiGyCem9lcVIUjDfSMLhjppcXOrM=\",\n"
                                    + "  \"version\": 5\n" + "}";
                            return IOUtils.toInputStream(masterKey, Charset.defaultCharset());
                        }

                        @Override
                        public boolean offset(final Path file) throws BackgroundException {
                            return false;
                        }
                    };
                }
                return super._getFeature(type);
            }
        };
        final AtomicBoolean prompt = new AtomicBoolean();
        final CryptoVault vault = new CryptoVault(new Path("/", EnumSet.of(Path.Type.directory)),
                new DisabledPasswordStore());
        try {
            vault.load(session, new DisabledPasswordCallback() {
                @Override
                public Credentials prompt(final Host bookmark, final String title, final String reason,
                        final LoginOptions options) throws LoginCanceledException {
                    if (!prompt.get()) {
                        assertEquals("Provide your passphrase to unlock the Cryptomator Vault /", reason);
                        prompt.set(true);
                        return new VaultCredentials("null");
                    } else {
                        assertEquals(
                                "Failure to decrypt master key file. Provide your passphrase to unlock the Cryptomator Vault /.",
                                reason);
                        throw new LoginCanceledException();
                    }
                }
            });
            fail();
        } catch (LoginCanceledException e) {
            //
        }
        assertTrue(prompt.get());
    }

    @Test
    public void testLoadCancel() throws Exception {
        final NullSession session = new NullSession(new Host(new TestProtocol())) {
            @Override
            @SuppressWarnings("unchecked")
            public <T> T _getFeature(final Class<T> type) {
                if (type == Read.class) {
                    return (T) new Read() {
                        @Override
                        public InputStream read(final Path file, final TransferStatus status,
                                final ConnectionCallback callback) throws BackgroundException {
                            final String masterKey = "{\n" + "  \"scryptSalt\": \"NrC7QGG/ouc=\",\n"
                                    + "  \"scryptCostParam\": 16384,\n" + "  \"scryptBlockSize\": 8,\n"
                                    + "  \"primaryMasterKey\": \"Q7pGo1l0jmZssoQh9rXFPKJE9NIXvPbL+HcnVSR9CHdkeR8AwgFtcw==\",\n"
                                    + "  \"hmacMasterKey\": \"xzBqT4/7uEcQbhHFLC0YmMy4ykVKbuvJEA46p1Xm25mJNuTc20nCbw==\",\n"
                                    + "  \"versionMac\": \"hlNr3dz/CmuVajhaiGyCem9lcVIUjDfSMLhjppcXOrM=\",\n"
                                    + "  \"version\": 5\n" + "}";
                            return IOUtils.toInputStream(masterKey, Charset.defaultCharset());
                        }

                        @Override
                        public boolean offset(final Path file) throws BackgroundException {
                            return false;
                        }
                    };
                }
                return super._getFeature(type);
            }
        };
        final CryptoVault vault = new CryptoVault(new Path("/", EnumSet.of(Path.Type.directory)),
                new DisabledPasswordStore());
        try {
            vault.load(session, new DisabledPasswordCallback() {
                @Override
                public Credentials prompt(final Host bookmark, final String title, final String reason,
                        final LoginOptions options) throws LoginCanceledException {
                    throw new LoginCanceledException();
                }
            });
            fail();
        } catch (LoginCanceledException e) {
            //
        }
    }

    @Test
    public void testLoadEmptyPassword() throws Exception {
        final NullSession session = new NullSession(new Host(new TestProtocol())) {
            @Override
            @SuppressWarnings("unchecked")
            public <T> T _getFeature(final Class<T> type) {
                if (type == Read.class) {
                    return (T) new Read() {
                        @Override
                        public InputStream read(final Path file, final TransferStatus status,
                                final ConnectionCallback callback) throws BackgroundException {
                            final String masterKey = "{\n" + "  \"scryptSalt\": \"NrC7QGG/ouc=\",\n"
                                    + "  \"scryptCostParam\": 16384,\n" + "  \"scryptBlockSize\": 8,\n"
                                    + "  \"primaryMasterKey\": \"Q7pGo1l0jmZssoQh9rXFPKJE9NIXvPbL+HcnVSR9CHdkeR8AwgFtcw==\",\n"
                                    + "  \"hmacMasterKey\": \"xzBqT4/7uEcQbhHFLC0YmMy4ykVKbuvJEA46p1Xm25mJNuTc20nCbw==\",\n"
                                    + "  \"versionMac\": \"hlNr3dz/CmuVajhaiGyCem9lcVIUjDfSMLhjppcXOrM=\",\n"
                                    + "  \"version\": 5\n" + "}";
                            return IOUtils.toInputStream(masterKey, Charset.defaultCharset());
                        }

                        @Override
                        public boolean offset(final Path file) throws BackgroundException {
                            return false;
                        }
                    };
                }
                return super._getFeature(type);
            }
        };
        final CryptoVault vault = new CryptoVault(new Path("/", EnumSet.of(Path.Type.directory)),
                new DisabledPasswordStore());
        try {
            vault.load(session, new DisabledPasswordCallback() {
                @Override
                public Credentials prompt(final Host bookmark, final String title, final String reason,
                        final LoginOptions options) throws LoginCanceledException {
                    return new VaultCredentials(null);
                }
            });
            fail();
        } catch (LoginCanceledException e) {
            //
        }
    }

    @Test
    public void testCreate() throws Exception {
        final Path home = new Path("/vault", EnumSet.of(Path.Type.directory));
        final NullSession session = new NullSession(new Host(new TestProtocol())) {
            @Override
            @SuppressWarnings("unchecked")
            public <T> T _getFeature(final Class<T> type) {
                if (type == Directory.class) {
                    return (T) new Directory() {

                        @Override
                        public Path mkdir(final Path folder, final String region, final TransferStatus status)
                                throws BackgroundException {
                            assertTrue(folder.equals(home) || folder.isChild(home));
                            return folder;
                        }

                        @Override
                        public boolean isSupported(final Path workdir) {
                            return true;
                        }

                        @Override
                        public Directory withWriter(final Write writer) {
                            return this;
                        }
                    };
                }
                return super._getFeature(type);
            }
        };
        final CryptoVault vault = new CryptoVault(home, new DisabledPasswordStore());
        vault.create(session, null, new VaultCredentials("test"));
    }

    @Test
    public void testCleartextSize() throws Exception {
        final Path home = new Path("/vault", EnumSet.of(Path.Type.directory));
        final NullSession session = new NullSession(new Host(new TestProtocol())) {
            @Override
            @SuppressWarnings("unchecked")
            public <T> T _getFeature(final Class<T> type) {
                if (type == Directory.class) {
                    return (T) new Directory() {

                        @Override
                        public Path mkdir(final Path folder, final String region, final TransferStatus status)
                                throws BackgroundException {
                            assertTrue(folder.equals(home) || folder.isChild(home));
                            return folder;
                        }

                        @Override
                        public boolean isSupported(final Path workdir) {
                            throw new UnsupportedOperationException();
                        }

                        @Override
                        public Directory withWriter(final Write writer) {
                            return this;
                        }
                    };
                }
                return super._getFeature(type);
            }
        };
        final CryptoVault vault = new CryptoVault(home, new DisabledPasswordStore());
        vault.create(session, null, new VaultCredentials("test"));
        // zero ciphertextFileSize
        try {
            vault.toCleartextSize(0);
            fail();
        } catch (CryptoInvalidFilesizeException e) {
        }
        // ciphertextFileSize == headerSize
        assertEquals(0L, vault.toCleartextSize(vault.getCryptor().fileHeaderCryptor().headerSize()));
        // ciphertextFileSize == headerSize + 1
        try {
            vault.toCleartextSize(vault.toCleartextSize(vault.getCryptor().fileHeaderCryptor().headerSize()) + 1);
            fail();
        } catch (CryptoInvalidFilesizeException e) {
        }
        // ciphertextFileSize == headerSize + chunkHeaderSize + 1
        assertEquals(1L, vault.toCleartextSize(vault.getCryptor().fileHeaderCryptor().headerSize() + 48 + 1));
        // ciphertextFileSize == headerSize + (32768 + chunkHeaderSize) + (1 + chunkHeaderSize) + 1
        assertEquals(32769L, vault
                .toCleartextSize(vault.getCryptor().fileHeaderCryptor().headerSize() + (32768 + 48) + (1 + 48)));
    }
}