1 /*
2  * Copyright (C) 2020 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 android.car.encryptionrunner;
18 
19 import android.annotation.IntDef;
20 
21 import com.android.internal.annotations.VisibleForTesting;
22 
23 import java.lang.annotation.Retention;
24 import java.lang.annotation.RetentionPolicy;
25 
26 /**
27  * An encryption runner that doesn't actually do encryption. Useful for debugging. Do not use in
28  * production environments.
29  */
30 @VisibleForTesting
31 public class DummyEncryptionRunner implements EncryptionRunner {
32 
33     private static final String KEY = "key";
34     private static final byte[] DUMMY_MESSAGE = "Dummy Message".getBytes();
35     @VisibleForTesting
36     public static final String INIT = "init";
37     @VisibleForTesting
38     public static final String INIT_RESPONSE = "initResponse";
39     @VisibleForTesting
40     public static final String CLIENT_RESPONSE = "clientResponse";
41     public static final String VERIFICATION_CODE = "1234";
42 
43     @Retention(RetentionPolicy.SOURCE)
44     @IntDef({Mode.UNKNOWN, Mode.CLIENT, Mode.SERVER})
45     private @interface Mode {
46 
47         int UNKNOWN = 0;
48         int CLIENT = 1;
49         int SERVER = 2;
50     }
51 
52     private boolean mIsReconnect;
53     private boolean mInitReconnectVerification;
54     private Key mCurrentDummyKey;
55     @Mode
56     private int mMode;
57     @HandshakeMessage.HandshakeState
58     private int mState;
59 
60     @Override
initHandshake()61     public HandshakeMessage initHandshake() {
62         checkRunnerIsNew();
63         mMode = Mode.CLIENT;
64         mState = HandshakeMessage.HandshakeState.IN_PROGRESS;
65         return HandshakeMessage.newBuilder()
66                 .setHandshakeState(mState)
67                 .setNextMessage(INIT.getBytes())
68                 .build();
69     }
70 
71     @Override
respondToInitRequest(byte[] initializationRequest)72     public HandshakeMessage respondToInitRequest(byte[] initializationRequest)
73             throws HandshakeException {
74         checkRunnerIsNew();
75         mMode = Mode.SERVER;
76         if (!new String(initializationRequest).equals(INIT)) {
77             throw new HandshakeException("Unexpected initialization request");
78         }
79         mState = HandshakeMessage.HandshakeState.IN_PROGRESS;
80         return HandshakeMessage.newBuilder()
81                 .setHandshakeState(HandshakeMessage.HandshakeState.IN_PROGRESS)
82                 .setNextMessage(INIT_RESPONSE.getBytes())
83                 .build();
84     }
85 
checkRunnerIsNew()86     private void checkRunnerIsNew() {
87         if (mState != HandshakeMessage.HandshakeState.UNKNOWN) {
88             throw new IllegalStateException("runner already initialized.");
89         }
90     }
91 
92     @Override
continueHandshake(byte[] response)93     public HandshakeMessage continueHandshake(byte[] response) throws HandshakeException {
94         if (mState != HandshakeMessage.HandshakeState.IN_PROGRESS) {
95             throw new HandshakeException("not waiting for response but got one");
96         }
97         switch (mMode) {
98             case Mode.SERVER:
99                 if (!CLIENT_RESPONSE.equals(new String(response))) {
100                     throw new HandshakeException("unexpected response: " + new String(response));
101                 }
102                 mState = HandshakeMessage.HandshakeState.VERIFICATION_NEEDED;
103                 if (mIsReconnect) {
104                     verifyPin();
105                     mState = HandshakeMessage.HandshakeState.RESUMING_SESSION;
106                 }
107                 return HandshakeMessage.newBuilder()
108                         .setVerificationCode(VERIFICATION_CODE)
109                         .setHandshakeState(mState)
110                         .build();
111             case Mode.CLIENT:
112                 if (!INIT_RESPONSE.equals(new String(response))) {
113                     throw new HandshakeException("unexpected response: " + new String(response));
114                 }
115                 mState = HandshakeMessage.HandshakeState.VERIFICATION_NEEDED;
116                 if (mIsReconnect) {
117                     verifyPin();
118                     mState = HandshakeMessage.HandshakeState.RESUMING_SESSION;
119                 }
120                 return HandshakeMessage.newBuilder()
121                         .setHandshakeState(mState)
122                         .setNextMessage(CLIENT_RESPONSE.getBytes())
123                         .setVerificationCode(VERIFICATION_CODE)
124                         .build();
125             default:
126                 throw new IllegalStateException("unexpected role: " + mMode);
127         }
128     }
129 
130     @Override
authenticateReconnection(byte[] message, byte[] previousKey)131     public HandshakeMessage authenticateReconnection(byte[] message, byte[] previousKey)
132             throws HandshakeException {
133         mCurrentDummyKey = new DummyKey();
134         // Blindly verify the reconnection because this is a dummy encryption runner.
135         return HandshakeMessage.newBuilder()
136                 .setHandshakeState(HandshakeMessage.HandshakeState.FINISHED)
137                 .setKey(mCurrentDummyKey)
138                 .setNextMessage(mInitReconnectVerification ? null : DUMMY_MESSAGE)
139                 .build();
140     }
141 
142     @Override
initReconnectAuthentication(byte[] previousKey)143     public HandshakeMessage initReconnectAuthentication(byte[] previousKey)
144             throws HandshakeException {
145         mInitReconnectVerification = true;
146         mState = HandshakeMessage.HandshakeState.RESUMING_SESSION;
147         return HandshakeMessage.newBuilder()
148                 .setHandshakeState(mState)
149                 .setNextMessage(DUMMY_MESSAGE)
150                 .build();
151     }
152 
153     @Override
keyOf(byte[] serialized)154     public Key keyOf(byte[] serialized) {
155         return new DummyKey();
156     }
157 
158     @Override
verifyPin()159     public HandshakeMessage verifyPin() throws HandshakeException {
160         if (mState != HandshakeMessage.HandshakeState.VERIFICATION_NEEDED) {
161             throw new IllegalStateException("asking to verify pin, state = " + mState);
162         }
163         mState = HandshakeMessage.HandshakeState.FINISHED;
164         return HandshakeMessage.newBuilder().setKey(new DummyKey()).setHandshakeState(
165                 mState).build();
166     }
167 
168     @Override
invalidPin()169     public void invalidPin() {
170         mState = HandshakeMessage.HandshakeState.INVALID;
171     }
172 
173     @Override
setIsReconnect(boolean isReconnect)174     public void setIsReconnect(boolean isReconnect) {
175         mIsReconnect = isReconnect;
176     }
177 
178     /** Method to throw a HandshakeException for testing. */
throwHandshakeException(String message)179     public static void throwHandshakeException(String message) throws HandshakeException {
180         throw new HandshakeException(message);
181     }
182 
183     private class DummyKey implements Key {
184         @Override
asBytes()185         public byte[] asBytes() {
186             return KEY.getBytes();
187         }
188 
189         @Override
encryptData(byte[] data)190         public byte[] encryptData(byte[] data) {
191             return data;
192         }
193 
194         @Override
decryptData(byte[] encryptedData)195         public byte[] decryptData(byte[] encryptedData) {
196             return encryptedData;
197         }
198 
199         @Override
getUniqueSession()200         public byte[] getUniqueSession() {
201             return KEY.getBytes();
202         }
203     }
204 }
205