1/*
2 * Copyright (C) 2009 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
17package android.security;
18
19import android.net.LocalSocketAddress;
20import android.net.LocalSocket;
21
22import java.io.InputStream;
23import java.io.IOException;
24import java.io.OutputStream;
25import java.io.UTFDataFormatException;
26import java.nio.charset.Charsets;
27import java.nio.charset.ModifiedUtf8;
28import java.util.ArrayList;
29import java.util.Date;
30
31/**
32 * @hide This should not be made public in its present form because it
33 * assumes that private and secret key bytes are available and would
34 * preclude the use of hardware crypto.
35 */
36public class KeyStore {
37
38    // ResponseCodes
39    public static final int NO_ERROR = 1;
40    public static final int LOCKED = 2;
41    public static final int UNINITIALIZED = 3;
42    public static final int SYSTEM_ERROR = 4;
43    public static final int PROTOCOL_ERROR = 5;
44    public static final int PERMISSION_DENIED = 6;
45    public static final int KEY_NOT_FOUND = 7;
46    public static final int VALUE_CORRUPTED = 8;
47    public static final int UNDEFINED_ACTION = 9;
48    public static final int WRONG_PASSWORD = 10;
49
50    // States
51    public enum State { UNLOCKED, LOCKED, UNINITIALIZED };
52
53    private static final LocalSocketAddress sAddress = new LocalSocketAddress(
54            "keystore", LocalSocketAddress.Namespace.RESERVED);
55
56    private int mError = NO_ERROR;
57
58    private KeyStore() {}
59
60    public static KeyStore getInstance() {
61        return new KeyStore();
62    }
63
64    public State state() {
65        execute('t');
66        switch (mError) {
67            case NO_ERROR: return State.UNLOCKED;
68            case LOCKED: return State.LOCKED;
69            case UNINITIALIZED: return State.UNINITIALIZED;
70            default: throw new AssertionError(mError);
71        }
72    }
73
74    private byte[] get(byte[] key) {
75        ArrayList<byte[]> values = execute('g', key);
76        return (values == null || values.isEmpty()) ? null : values.get(0);
77    }
78
79    public byte[] get(String key) {
80        return get(getKeyBytes(key));
81    }
82
83    private boolean put(byte[] key, byte[] value) {
84        execute('i', key, value);
85        return mError == NO_ERROR;
86    }
87
88    public boolean put(String key, byte[] value) {
89        return put(getKeyBytes(key), value);
90    }
91
92    private boolean delete(byte[] key) {
93        execute('d', key);
94        return mError == NO_ERROR;
95    }
96
97    public boolean delete(String key) {
98        return delete(getKeyBytes(key));
99    }
100
101    private boolean contains(byte[] key) {
102        execute('e', key);
103        return mError == NO_ERROR;
104    }
105
106    public boolean contains(String key) {
107        return contains(getKeyBytes(key));
108    }
109
110    public byte[][] saw(byte[] prefix) {
111        ArrayList<byte[]> values = execute('s', prefix);
112        return (values == null) ? null : values.toArray(new byte[values.size()][]);
113    }
114
115    public String[] saw(String prefix) {
116        byte[][] values = saw(getKeyBytes(prefix));
117        if (values == null) {
118            return null;
119        }
120        String[] strings = new String[values.length];
121        for (int i = 0; i < values.length; ++i) {
122            strings[i] = toKeyString(values[i]);
123        }
124        return strings;
125    }
126
127    public boolean reset() {
128        execute('r');
129        return mError == NO_ERROR;
130    }
131
132    private boolean password(byte[] password) {
133        execute('p', password);
134        return mError == NO_ERROR;
135    }
136
137    public boolean password(String password) {
138        return password(getPasswordBytes(password));
139    }
140
141    public boolean lock() {
142        execute('l');
143        return mError == NO_ERROR;
144    }
145
146    private boolean unlock(byte[] password) {
147        execute('u', password);
148        return mError == NO_ERROR;
149    }
150
151    public boolean unlock(String password) {
152        return unlock(getPasswordBytes(password));
153    }
154
155    public boolean isEmpty() {
156        execute('z');
157        return mError == KEY_NOT_FOUND;
158    }
159
160    private boolean generate(byte[] key) {
161        execute('a', key);
162        return mError == NO_ERROR;
163    }
164
165    public boolean generate(String key) {
166        return generate(getKeyBytes(key));
167    }
168
169    private boolean importKey(byte[] keyName, byte[] key) {
170        execute('m', keyName, key);
171        return mError == NO_ERROR;
172    }
173
174    public boolean importKey(String keyName, byte[] key) {
175        return importKey(getKeyBytes(keyName), key);
176    }
177
178    private byte[] getPubkey(byte[] key) {
179        ArrayList<byte[]> values = execute('b', key);
180        return (values == null || values.isEmpty()) ? null : values.get(0);
181    }
182
183    public byte[] getPubkey(String key) {
184        return getPubkey(getKeyBytes(key));
185    }
186
187    private boolean delKey(byte[] key) {
188        execute('k', key);
189        return mError == NO_ERROR;
190    }
191
192    public boolean delKey(String key) {
193        return delKey(getKeyBytes(key));
194    }
195
196    private byte[] sign(byte[] keyName, byte[] data) {
197        final ArrayList<byte[]> values = execute('n', keyName, data);
198        return (values == null || values.isEmpty()) ? null : values.get(0);
199    }
200
201    public byte[] sign(String key, byte[] data) {
202        return sign(getKeyBytes(key), data);
203    }
204
205    private boolean verify(byte[] keyName, byte[] data, byte[] signature) {
206        execute('v', keyName, data, signature);
207        return mError == NO_ERROR;
208    }
209
210    public boolean verify(String key, byte[] data, byte[] signature) {
211        return verify(getKeyBytes(key), data, signature);
212    }
213
214    private boolean grant(byte[] key, byte[] uid) {
215        execute('x', key, uid);
216        return mError == NO_ERROR;
217    }
218
219    public boolean grant(String key, int uid) {
220         return grant(getKeyBytes(key), getUidBytes(uid));
221    }
222
223    private boolean ungrant(byte[] key, byte[] uid) {
224        execute('y', key, uid);
225        return mError == NO_ERROR;
226    }
227
228    public boolean ungrant(String key, int uid) {
229        return ungrant(getKeyBytes(key), getUidBytes(uid));
230    }
231
232    private long getmtime(byte[] key) {
233        final ArrayList<byte[]> values = execute('c', key);
234        if (values == null || values.isEmpty()) {
235            return -1L;
236        }
237
238        return Long.parseLong(new String(values.get(0))) * 1000L;
239    }
240
241    /**
242     * Returns the last modification time of the key in milliseconds since the
243     * epoch. Will return -1L if the key could not be found or other error.
244     */
245    public long getmtime(String key) {
246        return getmtime(getKeyBytes(key));
247    }
248
249    public int getLastError() {
250        return mError;
251    }
252
253    private ArrayList<byte[]> execute(int code, byte[]... parameters) {
254        mError = PROTOCOL_ERROR;
255
256        for (byte[] parameter : parameters) {
257            if (parameter == null || parameter.length > 65535) {
258                return null;
259            }
260        }
261
262        LocalSocket socket = new LocalSocket();
263        try {
264            socket.connect(sAddress);
265
266            OutputStream out = socket.getOutputStream();
267            out.write(code);
268            for (byte[] parameter : parameters) {
269                out.write(parameter.length >> 8);
270                out.write(parameter.length);
271                out.write(parameter);
272            }
273            out.flush();
274            socket.shutdownOutput();
275
276            InputStream in = socket.getInputStream();
277            if ((code = in.read()) != NO_ERROR) {
278                if (code != -1) {
279                    mError = code;
280                }
281                return null;
282            }
283
284            ArrayList<byte[]> values = new ArrayList<byte[]>();
285            while (true) {
286                int i, j;
287                if ((i = in.read()) == -1) {
288                    break;
289                }
290                if ((j = in.read()) == -1) {
291                    return null;
292                }
293                byte[] value = new byte[i << 8 | j];
294                for (i = 0; i < value.length; i += j) {
295                    if ((j = in.read(value, i, value.length - i)) == -1) {
296                        return null;
297                    }
298                }
299                values.add(value);
300            }
301            mError = NO_ERROR;
302            return values;
303        } catch (IOException e) {
304            // ignore
305        } finally {
306            try {
307                socket.close();
308            } catch (IOException e) {}
309        }
310        return null;
311    }
312
313    /**
314     * ModifiedUtf8 is used for key encoding to match the
315     * implementation of NativeCrypto.ENGINE_load_private_key.
316     */
317    private static byte[] getKeyBytes(String string) {
318        try {
319            int utfCount = (int) ModifiedUtf8.countBytes(string, false);
320            byte[] result = new byte[utfCount];
321            ModifiedUtf8.encode(result, 0, string);
322            return result;
323        } catch (UTFDataFormatException e) {
324            throw new RuntimeException(e);
325        }
326    }
327
328    private static String toKeyString(byte[] bytes) {
329        try {
330            return ModifiedUtf8.decode(bytes, new char[bytes.length], 0, bytes.length);
331        } catch (UTFDataFormatException e) {
332            throw new RuntimeException(e);
333        }
334    }
335
336    private static byte[] getPasswordBytes(String password) {
337        return password.getBytes(Charsets.UTF_8);
338    }
339
340    private static byte[] getUidBytes(int uid) {
341        return Integer.toString(uid).getBytes(Charsets.UTF_8);
342    }
343}
344