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