1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.vault;
18 
19 import android.os.ParcelFileDescriptor;
20 import android.test.AndroidTestCase;
21 import android.test.MoreAsserts;
22 import android.test.suitebuilder.annotation.MediumTest;
23 
24 import org.json.JSONObject;
25 
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.RandomAccessFile;
31 import java.nio.charset.StandardCharsets;
32 import java.security.DigestException;
33 import java.util.Arrays;
34 import java.util.concurrent.CountDownLatch;
35 import java.util.concurrent.TimeUnit;
36 
37 import javax.crypto.SecretKey;
38 import javax.crypto.spec.SecretKeySpec;
39 
40 /**
41  * Tests for {@link EncryptedDocument}.
42  */
43 @MediumTest
44 public class EncryptedDocumentTest extends AndroidTestCase {
45 
46     private File mFile;
47 
48     private SecretKey mDataKey = new SecretKeySpec(new byte[] {
49             0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
50             0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, "AES");
51 
52     private SecretKey mMacKey = new SecretKeySpec(new byte[] {
53             0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
54             0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
55             0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
56             0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }, "AES");
57 
58     @Override
setUp()59     protected void setUp() throws Exception {
60         super.setUp();
61 
62         mFile = new File(getContext().getFilesDir(), "meow");
63     }
64 
65     @Override
tearDown()66     protected void tearDown() throws Exception {
67         super.tearDown();
68 
69         for (File f : getContext().getFilesDir().listFiles()) {
70             f.delete();
71         }
72     }
73 
testEmptyFile()74     public void testEmptyFile() throws Exception {
75         mFile.createNewFile();
76         final EncryptedDocument doc = new EncryptedDocument(4, mFile, mDataKey, mMacKey);
77 
78         try {
79             doc.readMetadata();
80             fail("expected metadata to throw");
81         } catch (IOException expected) {
82         }
83 
84         try {
85             final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
86             doc.readContent(pipe[1]);
87             fail("expected content to throw");
88         } catch (IOException expected) {
89         }
90     }
91 
testNormalMetadataAndContents()92     public void testNormalMetadataAndContents() throws Exception {
93         final byte[] content = "KITTENS".getBytes(StandardCharsets.UTF_8);
94         testMetadataAndContents(content);
95     }
96 
testGiantMetadataAndContents()97     public void testGiantMetadataAndContents() throws Exception {
98         // try with content size of prime number >1MB
99         final byte[] content = new byte[1298047];
100         Arrays.fill(content, (byte) 0x42);
101         testMetadataAndContents(content);
102     }
103 
testMetadataAndContents(byte[] content)104     private void testMetadataAndContents(byte[] content) throws Exception {
105         final EncryptedDocument doc = new EncryptedDocument(4, mFile, mDataKey, mMacKey);
106         final byte[] beforeContent = content;
107 
108         final ParcelFileDescriptor[] beforePipe = ParcelFileDescriptor.createReliablePipe();
109         new Thread() {
110             @Override
111             public void run() {
112                 final FileOutputStream os = new FileOutputStream(beforePipe[1].getFileDescriptor());
113                 try {
114                     os.write(beforeContent);
115                     beforePipe[1].close();
116                 } catch (IOException e) {
117                     throw new RuntimeException(e);
118                 }
119             }
120         }.start();
121 
122         // fully write metadata and content
123         final JSONObject before = new JSONObject();
124         before.put("meow", "cake");
125         doc.writeMetadataAndContent(before, beforePipe[0]);
126 
127         // now go back and verify we can read
128         final JSONObject after = doc.readMetadata();
129         assertEquals("cake", after.getString("meow"));
130 
131         final CountDownLatch latch = new CountDownLatch(1);
132         final ParcelFileDescriptor[] afterPipe = ParcelFileDescriptor.createReliablePipe();
133         final byte[] afterContent = new byte[beforeContent.length];
134         new Thread() {
135             @Override
136             public void run() {
137                 final FileInputStream is = new FileInputStream(afterPipe[0].getFileDescriptor());
138                 try {
139                     int i = 0;
140                     while (i < afterContent.length) {
141                         int n = is.read(afterContent, i, afterContent.length - i);
142                         i += n;
143                     }
144                     afterPipe[0].close();
145                     latch.countDown();
146                 } catch (IOException e) {
147                     throw new RuntimeException(e);
148                 }
149             }
150         }.start();
151 
152         doc.readContent(afterPipe[1]);
153         latch.await(5, TimeUnit.SECONDS);
154 
155         MoreAsserts.assertEquals(beforeContent, afterContent);
156     }
157 
testNormalMetadataOnly()158     public void testNormalMetadataOnly() throws Exception {
159         final EncryptedDocument doc = new EncryptedDocument(4, mFile, mDataKey, mMacKey);
160 
161         // write only metadata
162         final JSONObject before = new JSONObject();
163         before.put("lol", "wut");
164         doc.writeMetadataAndContent(before, null);
165 
166         // verify we can read
167         final JSONObject after = doc.readMetadata();
168         assertEquals("wut", after.getString("lol"));
169 
170         final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
171         try {
172             doc.readContent(pipe[1]);
173             fail("found document content");
174         } catch (IOException expected) {
175         }
176     }
177 
testCopiedFile()178     public void testCopiedFile() throws Exception {
179         final EncryptedDocument doc1 = new EncryptedDocument(1, mFile, mDataKey, mMacKey);
180         final EncryptedDocument doc4 = new EncryptedDocument(4, mFile, mDataKey, mMacKey);
181 
182         // write values for doc1 into file
183         final JSONObject meta1 = new JSONObject();
184         meta1.put("key1", "value1");
185         doc1.writeMetadataAndContent(meta1, null);
186 
187         // now try reading as doc4, which should fail
188         try {
189             doc4.readMetadata();
190             fail("somehow read without checking docid");
191         } catch (DigestException expected) {
192         }
193     }
194 
testBitTwiddle()195     public void testBitTwiddle() throws Exception {
196         final EncryptedDocument doc = new EncryptedDocument(4, mFile, mDataKey, mMacKey);
197 
198         // write some metadata
199         final JSONObject before = new JSONObject();
200         before.put("twiddle", "twiddle");
201         doc.writeMetadataAndContent(before, null);
202 
203         final RandomAccessFile f = new RandomAccessFile(mFile, "rw");
204         f.seek(f.length() - 4);
205         f.write(0x00);
206         f.close();
207 
208         try {
209             doc.readMetadata();
210             fail("somehow passed hmac");
211         } catch (DigestException expected) {
212         }
213     }
214 
testErrorAbortsWrite()215     public void testErrorAbortsWrite() throws Exception {
216         final EncryptedDocument doc = new EncryptedDocument(4, mFile, mDataKey, mMacKey);
217 
218         // write initial metadata
219         final JSONObject init = new JSONObject();
220         init.put("color", "red");
221         doc.writeMetadataAndContent(init, null);
222 
223         // try writing with a pipe that reports failure
224         final byte[] content = "KITTENS".getBytes(StandardCharsets.UTF_8);
225         final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
226         new Thread() {
227             @Override
228             public void run() {
229                 final FileOutputStream os = new FileOutputStream(pipe[1].getFileDescriptor());
230                 try {
231                     os.write(content);
232                     pipe[1].closeWithError("ZOMG");
233                 } catch (IOException e) {
234                     throw new RuntimeException(e);
235                 }
236             }
237         }.start();
238 
239         final JSONObject second = new JSONObject();
240         second.put("color", "blue");
241         try {
242             doc.writeMetadataAndContent(second, pipe[0]);
243             fail("somehow wrote without error");
244         } catch (IOException ignored) {
245         }
246 
247         // verify that original metadata still in place
248         final JSONObject after = doc.readMetadata();
249         assertEquals("red", after.getString("color"));
250     }
251 }
252