EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.tools]

COVERAGE SUMMARY FOR SOURCE FILE [ChangeFileEncryption.java]

nameclass, %method, %block, %line, %
ChangeFileEncryption.java100% (1/1)89%  (8/9)64%  (395/618)67%  (100.1/149)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ChangeFileEncryption100% (1/1)89%  (8/9)64%  (395/618)67%  (100.1/149)
copy (String, FileStore, byte []): void 0%   (0/1)0%   (0/118)0%   (0/28)
process (String): void 100% (1/1)16%  (9/56)29%  (4/14)
execute (String, String, String, char [], char [], boolean): void 100% (1/1)75%  (12/16)60%  (3/5)
getFileEncryptionKey (char []): byte [] 100% (1/1)75%  (6/8)67%  (2/3)
copy (String): void 100% (1/1)79%  (122/155)87%  (29.6/34)
process (String, String, String, char [], char [], boolean): void 100% (1/1)91%  (118/129)93%  (27.9/30)
runTool (String []): void 100% (1/1)94%  (119/127)95%  (30.5/32)
ChangeFileEncryption (): void 100% (1/1)100% (3/3)100% (1/1)
main (String []): void 100% (1/1)100% (6/6)100% (2/2)

1/*
2 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
3 * and the EPL 1.0 (http://h2database.com/html/license.html).
4 * Initial Developer: H2 Group
5 */
6package org.h2.tools;
7 
8import java.io.IOException;
9import java.io.InputStream;
10import java.io.OutputStream;
11import java.nio.channels.FileChannel;
12import java.sql.SQLException;
13import java.util.ArrayList;
14 
15import org.h2.engine.Constants;
16import org.h2.message.DbException;
17import org.h2.security.SHA256;
18import org.h2.store.FileLister;
19import org.h2.store.FileStore;
20import org.h2.store.fs.FileChannelInputStream;
21import org.h2.store.fs.FileChannelOutputStream;
22import org.h2.store.fs.FilePath;
23import org.h2.store.fs.FilePathEncrypt;
24import org.h2.store.fs.FileUtils;
25import org.h2.util.Tool;
26 
27/**
28 * Allows changing the database file encryption password or algorithm.
29 * <br />
30 * This tool can not be used to change a password of a user.
31 * The database must be closed before using this tool.
32 * @h2.resource
33 */
34public class ChangeFileEncryption extends Tool {
35 
36    private String directory;
37    private String cipherType;
38    private byte[] decrypt;
39    private byte[] encrypt;
40    private byte[] decryptKey;
41    private byte[] encryptKey;
42 
43    /**
44     * Options are case sensitive. Supported options are:
45     * <table>
46     * <tr><td>[-help] or [-?]</td>
47     * <td>Print the list of options</td></tr>
48     * <tr><td>[-cipher type]</td>
49     * <td>The encryption type (AES)</td></tr>
50     * <tr><td>[-dir &lt;dir&gt;]</td>
51     * <td>The database directory (default: .)</td></tr>
52     * <tr><td>[-db &lt;database&gt;]</td>
53     * <td>Database name (all databases if not set)</td></tr>
54     * <tr><td>[-decrypt &lt;pwd&gt;]</td>
55     * <td>The decryption password (if not set: not yet encrypted)</td></tr>
56     * <tr><td>[-encrypt &lt;pwd&gt;]</td>
57     * <td>The encryption password (if not set: do not encrypt)</td></tr>
58     * <tr><td>[-quiet]</td>
59     * <td>Do not print progress information</td></tr>
60     * </table>
61     * @h2.resource
62     *
63     * @param args the command line arguments
64     */
65    public static void main(String... args) throws SQLException {
66        new ChangeFileEncryption().runTool(args);
67    }
68 
69    @Override
70    public void runTool(String... args) throws SQLException {
71        String dir = ".";
72        String cipher = null;
73        char[] decryptPassword = null;
74        char[] encryptPassword = null;
75        String db = null;
76        boolean quiet = false;
77        for (int i = 0; args != null && i < args.length; i++) {
78            String arg = args[i];
79            if (arg.equals("-dir")) {
80                dir = args[++i];
81            } else if (arg.equals("-cipher")) {
82                cipher = args[++i];
83            } else if (arg.equals("-db")) {
84                db = args[++i];
85            } else if (arg.equals("-decrypt")) {
86                decryptPassword = args[++i].toCharArray();
87            } else if (arg.equals("-encrypt")) {
88                encryptPassword = args[++i].toCharArray();
89            } else if (arg.equals("-quiet")) {
90                quiet = true;
91            } else if (arg.equals("-help") || arg.equals("-?")) {
92                showUsage();
93                return;
94            } else {
95                showUsageAndThrowUnsupportedOption(arg);
96            }
97        }
98        if ((encryptPassword == null && decryptPassword == null) || cipher == null) {
99            showUsage();
100            throw new SQLException(
101                    "Encryption or decryption password not set, or cipher not set");
102        }
103        try {
104            process(dir, db, cipher, decryptPassword, encryptPassword, quiet);
105        } catch (Exception e) {
106            throw DbException.toSQLException(e);
107        }
108    }
109 
110    /**
111     * Get the file encryption key for a given password. The password must be
112     * supplied as char arrays and is cleaned in this method.
113     *
114     * @param password the password as a char array
115     * @return the encryption key
116     */
117    private static byte[] getFileEncryptionKey(char[] password) {
118        if (password == null) {
119            return null;
120        }
121        return SHA256.getKeyPasswordHash("file", password);
122    }
123 
124    /**
125     * Changes the password for a database. The passwords must be supplied as
126     * char arrays and are cleaned in this method. The database must be closed
127     * before calling this method.
128     *
129     * @param dir the directory (. for the current directory)
130     * @param db the database name (null for all databases)
131     * @param cipher the cipher (AES)
132     * @param decryptPassword the decryption password as a char array
133     * @param encryptPassword the encryption password as a char array
134     * @param quiet don't print progress information
135     */
136    public static void execute(String dir, String db, String cipher,
137            char[] decryptPassword, char[] encryptPassword, boolean quiet)
138            throws SQLException {
139        try {
140            new ChangeFileEncryption().process(dir, db, cipher,
141                    decryptPassword, encryptPassword, quiet);
142        } catch (Exception e) {
143            throw DbException.toSQLException(e);
144        }
145    }
146 
147    private void process(String dir, String db, String cipher,
148            char[] decryptPassword, char[] encryptPassword, boolean quiet)
149            throws SQLException {
150        dir = FileLister.getDir(dir);
151        ChangeFileEncryption change = new ChangeFileEncryption();
152        if (encryptPassword != null) {
153            for (char c : encryptPassword) {
154                if (c == ' ') {
155                    throw new SQLException("The file password may not contain spaces");
156                }
157            }
158            change.encryptKey = FilePathEncrypt.getPasswordBytes(encryptPassword);
159            change.encrypt = getFileEncryptionKey(encryptPassword);
160        }
161        if (decryptPassword != null) {
162            change.decryptKey = FilePathEncrypt.getPasswordBytes(decryptPassword);
163            change.decrypt = getFileEncryptionKey(decryptPassword);
164        }
165        change.out = out;
166        change.directory = dir;
167        change.cipherType = cipher;
168 
169        ArrayList<String> files = FileLister.getDatabaseFiles(dir, db, true);
170        FileLister.tryUnlockDatabase(files, "encryption");
171        files = FileLister.getDatabaseFiles(dir, db, false);
172        if (files.size() == 0 && !quiet) {
173            printNoDatabaseFilesFound(dir, db);
174        }
175        // first, test only if the file can be renamed
176        // (to find errors with locked files early)
177        for (String fileName : files) {
178            String temp = dir + "/temp.db";
179            FileUtils.delete(temp);
180            FileUtils.move(fileName, temp);
181            FileUtils.move(temp, fileName);
182        }
183        // if this worked, the operation will (hopefully) be successful
184        // TODO changeFileEncryption: this is a workaround!
185        // make the operation atomic (all files or none)
186        for (String fileName : files) {
187            // don't process a lob directory, just the files in the directory.
188            if (!FileUtils.isDirectory(fileName)) {
189                change.process(fileName);
190            }
191        }
192    }
193 
194    private void process(String fileName) {
195        if (fileName.endsWith(Constants.SUFFIX_MV_FILE)) {
196            try {
197                copy(fileName);
198            } catch (IOException e) {
199                throw DbException.convertIOException(e,
200                        "Error encrypting / decrypting file " + fileName);
201            }
202            return;
203        }
204        FileStore in;
205        if (decrypt == null) {
206            in = FileStore.open(null, fileName, "r");
207        } else {
208            in = FileStore.open(null, fileName, "r", cipherType, decrypt);
209        }
210        try {
211            in.init();
212            copy(fileName, in, encrypt);
213        } finally {
214            in.closeSilently();
215        }
216    }
217 
218    private void copy(String fileName) throws IOException {
219        if (FileUtils.isDirectory(fileName)) {
220            return;
221        }
222        FileChannel fileIn = FilePath.get(fileName).open("r");
223        FileChannel fileOut = null;
224        String temp = directory + "/temp.db";
225        try {
226            if (decryptKey != null) {
227                fileIn = new FilePathEncrypt.FileEncrypt(fileName, decryptKey, fileIn);
228            }
229            InputStream inStream = new FileChannelInputStream(fileIn, true);
230            FileUtils.delete(temp);
231            fileOut = FilePath.get(temp).open("rw");
232            if (encryptKey != null) {
233                fileOut = new FilePathEncrypt.FileEncrypt(temp, encryptKey, fileOut);
234            }
235            OutputStream outStream = new FileChannelOutputStream(fileOut, true);
236            byte[] buffer = new byte[4 * 1024];
237            long remaining = fileIn.size();
238            long total = remaining;
239            long time = System.currentTimeMillis();
240            while (remaining > 0) {
241                if (System.currentTimeMillis() - time > 1000) {
242                    out.println(fileName + ": " + (100 - 100 * remaining / total) + "%");
243                    time = System.currentTimeMillis();
244                }
245                int len = (int) Math.min(buffer.length, remaining);
246                len = inStream.read(buffer, 0, len);
247                outStream.write(buffer, 0, len);
248                remaining -= len;
249            }
250            inStream.close();
251            outStream.close();
252        } finally {
253            fileIn.close();
254            if (fileOut != null) {
255                fileOut.close();
256            }
257        }
258        FileUtils.delete(fileName);
259        FileUtils.move(temp, fileName);
260    }
261 
262    private void copy(String fileName, FileStore in, byte[] key) {
263        if (FileUtils.isDirectory(fileName)) {
264            return;
265        }
266        String temp = directory + "/temp.db";
267        FileUtils.delete(temp);
268        FileStore fileOut;
269        if (key == null) {
270            fileOut = FileStore.open(null, temp, "rw");
271        } else {
272            fileOut = FileStore.open(null, temp, "rw", cipherType, key);
273        }
274        fileOut.init();
275        byte[] buffer = new byte[4 * 1024];
276        long remaining = in.length() - FileStore.HEADER_LENGTH;
277        long total = remaining;
278        in.seek(FileStore.HEADER_LENGTH);
279        fileOut.seek(FileStore.HEADER_LENGTH);
280        long time = System.currentTimeMillis();
281        while (remaining > 0) {
282            if (System.currentTimeMillis() - time > 1000) {
283                out.println(fileName + ": " + (100 - 100 * remaining / total) + "%");
284                time = System.currentTimeMillis();
285            }
286            int len = (int) Math.min(buffer.length, remaining);
287            in.readFully(buffer, 0, len);
288            fileOut.write(buffer, 0, len);
289            remaining -= len;
290        }
291        in.close();
292        fileOut.close();
293        FileUtils.delete(fileName);
294        FileUtils.move(temp, fileName);
295    }
296 
297}

[all classes][org.h2.tools]
EMMA 2.0.5312 (C) Vladimir Roubtsov