1/*
2 * Copyright (C) 2017 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 com.android.server;
18
19import android.os.IBinder;
20import android.os.RemoteException;
21import android.service.gatekeeper.GateKeeperResponse;
22import android.service.gatekeeper.IGateKeeperService;
23import android.util.ArrayMap;
24
25import junit.framework.AssertionFailedError;
26
27import java.nio.ByteBuffer;
28import java.util.Arrays;
29import java.util.Random;
30
31public class MockGateKeeperService implements IGateKeeperService {
32    static class VerifyHandle {
33        public byte[] password;
34        public long sid;
35
36        public VerifyHandle(byte[] password, long sid) {
37            this.password = password;
38            this.sid = sid;
39        }
40
41        public VerifyHandle(byte[] handle) {
42            ByteBuffer buffer = ByteBuffer.allocate(handle.length);
43            buffer.put(handle, 0, handle.length);
44            buffer.flip();
45            int version = buffer.get();
46            sid = buffer.getLong();
47            password = new byte[buffer.remaining()];
48            buffer.get(password);
49        }
50
51        public byte[] toBytes() {
52            ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + password.length);
53            buffer.put((byte)0);
54            buffer.putLong(sid);
55            buffer.put(password);
56            return buffer.array();
57        }
58    }
59
60    static class AuthToken {
61        public long challenge;
62        public long sid;
63
64        public AuthToken(long challenge, long sid) {
65            this.challenge = challenge;
66            this.sid = sid;
67        }
68
69        public AuthToken(byte[] handle) {
70            ByteBuffer buffer = ByteBuffer.allocate(handle.length);
71            buffer.put(handle, 0, handle.length);
72            buffer.flip();
73            int version = buffer.get();
74            challenge = buffer.getLong();
75            sid = buffer.getLong();
76        }
77
78        public byte[] toBytes() {
79            ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + Long.BYTES);
80            buffer.put((byte)0);
81            buffer.putLong(challenge);
82            buffer.putLong(sid);
83            return buffer.array();
84        }
85    }
86
87    private ArrayMap<Integer, Long> sidMap = new ArrayMap<>();
88    private ArrayMap<Integer, AuthToken> authTokenMap = new ArrayMap<>();
89
90    private ArrayMap<Integer, byte[]> handleMap = new ArrayMap<>();
91
92    @Override
93    public GateKeeperResponse enroll(int uid, byte[] currentPasswordHandle, byte[] currentPassword,
94            byte[] desiredPassword) throws android.os.RemoteException {
95
96        if (currentPasswordHandle != null) {
97            VerifyHandle handle = new VerifyHandle(currentPasswordHandle);
98            if (Arrays.equals(currentPassword, handle.password)) {
99                // Trusted enroll
100                VerifyHandle newHandle = new VerifyHandle(desiredPassword, handle.sid);
101                refreshSid(uid, handle.sid, false);
102                handleMap.put(uid, newHandle.toBytes());
103                return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
104            } else {
105                return null;
106            }
107        } else {
108            // Untrusted enroll
109            long newSid = new Random().nextLong();
110            VerifyHandle newHandle = new VerifyHandle(desiredPassword, newSid);
111            refreshSid(uid, newSid, true);
112            handleMap.put(uid, newHandle.toBytes());
113            return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
114        }
115    }
116
117    @Override
118    public GateKeeperResponse verify(int uid, byte[] enrolledPasswordHandle,
119            byte[] providedPassword) throws android.os.RemoteException {
120        return verifyChallenge(uid, 0, enrolledPasswordHandle, providedPassword);
121    }
122
123    @Override
124    public GateKeeperResponse verifyChallenge(int uid, long challenge,
125            byte[] enrolledPasswordHandle, byte[] providedPassword) throws RemoteException {
126
127        VerifyHandle handle = new VerifyHandle(enrolledPasswordHandle);
128        if (Arrays.equals(handle.password, providedPassword)) {
129            byte[] knownHandle = handleMap.get(uid);
130            if (knownHandle != null) {
131                if (!Arrays.equals(knownHandle, enrolledPasswordHandle)) {
132                    throw new AssertionFailedError("Got correct but obsolete handle");
133                }
134            }
135            refreshSid(uid, handle.sid, false);
136            AuthToken token = new AuthToken(challenge, handle.sid);
137            refreshAuthToken(uid, token);
138            return GateKeeperResponse.createOkResponse(token.toBytes(), false);
139        } else {
140            return GateKeeperResponse.createGenericResponse(GateKeeperResponse.RESPONSE_ERROR);
141        }
142    }
143
144    private void refreshAuthToken(int uid, AuthToken token) {
145        authTokenMap.put(uid, token);
146    }
147
148    public AuthToken getAuthToken(int uid) {
149        return authTokenMap.get(uid);
150    }
151
152    public AuthToken getAuthTokenForSid(long sid) {
153        for(AuthToken token : authTokenMap.values()) {
154            if (token.sid == sid) {
155                return token;
156            }
157        }
158        return null;
159    }
160
161    public void clearAuthToken(int uid) {
162        authTokenMap.remove(uid);
163    }
164
165    @Override
166    public IBinder asBinder() {
167        throw new UnsupportedOperationException();
168    }
169
170    @Override
171    public void clearSecureUserId(int userId) throws RemoteException {
172        sidMap.remove(userId);
173    }
174
175    @Override
176    public long getSecureUserId(int userId) throws RemoteException {
177        if (sidMap.containsKey(userId)) {
178            return sidMap.get(userId);
179        } else {
180            return 0L;
181        }
182    }
183
184    private void refreshSid(int uid, long sid, boolean force) {
185        if (!sidMap.containsKey(uid) || force) {
186            sidMap.put(uid, sid);
187        } else{
188            if (sidMap.get(uid) != sid) {
189                throw new AssertionFailedError("Inconsistent SID");
190            }
191        }
192    }
193
194}
195