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