1 /*
2  * Copyright 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 com.google.security.cryptauth.lib.securegcm.Ukey2Handshake;
20 
21 /**
22  * An {@link EncryptionRunner} that uses Ukey2 as the underlying implementation, and generates a
23  * longer token for the out of band verification step.
24  *
25  * <p>To use this class:
26  *
27  * <p>1. As a client.
28  *
29  * <p>{@code
30  * HandshakeMessage initialClientMessage = clientRunner.initHandshake();
31  * sendToServer(initialClientMessage.getNextMessage());
32  * byte message = getServerResponse();
33  * HandshakeMessage message = clientRunner.continueHandshake(message);
34  * }
35  *
36  * <p>If it is a first-time connection,
37  *
38  * <p>{@code message.getHandshakeState()} should be OOB_VERIFICATION_NEEDED. Wait for an encrypted
39  * message sent from the server, and decrypt that message with an out of band key that was generated
40  * before the start of the handshake.
41  *
42  * <p>After confirming that the decrypted message matches the verification code, send an encrypted
43  * message back to the server, and call {@code HandshakeMessage lastMessage =
44  * clientRunner.verifyPin();} otherwise {@code clientRunner.invalidPin(); }
45  *
46  * <p>Use {@code lastMessage.getKey()} to get the key for encryption.
47  *
48  * <p>If it is a reconnection,
49  *
50  * <p>{@code message.getHandshakeState()} should be RESUMING_SESSION, PIN has been verified blindly,
51  * send the authentication message over to server, then authenticate the message from server.
52  *
53  * <p>{@code
54  * clientMessage = clientRunner.initReconnectAuthentication(previousKey)
55  * sendToServer(clientMessage.getNextMessage());
56  * HandshakeMessage lastMessage = clientRunner.authenticateReconnection(previousKey, message)
57  * }
58  *
59  * <p>{@code lastMessage.getHandshakeState()} should be FINISHED if reconnection handshake is done.
60  *
61  * <p>2. As a server.
62  *
63  * <p>{@code
64  * byte[] initialMessage = getClientMessageBytes();
65  * HandshakeMessage message = serverRunner.respondToInitRequest(initialMessage);
66  * sendToClient(message.getNextMessage());
67  * byte[] clientMessage = getClientResponse();
68  * HandshakeMessage message = serverRunner.continueHandshake(clientMessage);}
69  *
70  * <p>if it is a first-time connection,
71  *
72  * <p>{@code message.getHandshakeState()} should be OOB_VERIFICATION_NEEDED, send the verification
73  * code to the client, encrypted using an out of band key generated before the start of the
74  * handshake, and wait for a response from the client.
75  * If the decrypted message from the client matches the verification code, call {@code
76  * HandshakeMessage lastMessage = serverRunner.verifyPin}, otherwise
77  * {@code clientRunner.invalidPin(); }
78  * Use {@code lastMessage.getKey()} to get the key for encryption.
79  *
80  * <p>If it is a reconnection,
81  *
82  * <p>{@code message.getHandshakeState()} should be RESUMING_SESSION,PIN has been verified blindly,
83  * waiting for client message.
84  * After client message been received,
85  * {@code serverMessage = serverRunner.authenticateReconnection(previousKey, message);
86  * sendToClient(serverMessage.getNextMessage());}
87  * {@code serverMessage.getHandshakeState()} should be FINISHED if reconnection handshake is done.
88  *
89  * <p>Also see {@link EncryptionRunnerTest} for examples.
90  */
91 public final class OobUkey2EncryptionRunner extends Ukey2EncryptionRunner {
92     // Choose max verification string length supported by Ukey2
93     private static final int VERIFICATION_STRING_LENGTH = 32;
94 
95     @Override
continueHandshake(byte[] response)96     public HandshakeMessage continueHandshake(byte[] response) throws HandshakeException {
97         checkInitialized();
98 
99         Ukey2Handshake uKey2Client = getUkey2Client();
100 
101         try {
102             if (uKey2Client.getHandshakeState() != Ukey2Handshake.State.IN_PROGRESS) {
103                 throw new IllegalStateException(
104                         "handshake is not in progress, state =" + uKey2Client.getHandshakeState());
105             }
106             uKey2Client.parseHandshakeMessage(response);
107 
108             // Not obvious from ukey2 api, but getting the next message can change the state.
109             // calling getNext message might go from in progress to verification needed, on
110             // the assumption that we already send this message to the peer.
111             byte[] nextMessage = null;
112             if (uKey2Client.getHandshakeState() == Ukey2Handshake.State.IN_PROGRESS) {
113                 nextMessage = uKey2Client.getNextHandshakeMessage();
114             }
115 
116             byte[] verificationCode = null;
117             if (uKey2Client.getHandshakeState() == Ukey2Handshake.State.VERIFICATION_NEEDED) {
118                 // getVerificationString() needs to be called before notifyPinVerified().
119                 verificationCode = uKey2Client.getVerificationString(VERIFICATION_STRING_LENGTH);
120                 if (isReconnect()) {
121                     HandshakeMessage handshakeMessage = verifyPin();
122                     return HandshakeMessage.newBuilder()
123                             .setHandshakeState(handshakeMessage.getHandshakeState())
124                             .setNextMessage(nextMessage)
125                             .build();
126                 }
127             }
128 
129             return HandshakeMessage.newBuilder()
130                     .setHandshakeState(HandshakeMessage.HandshakeState.OOB_VERIFICATION_NEEDED)
131                     .setNextMessage(nextMessage)
132                     .setOobVerificationCode(verificationCode)
133                     .build();
134         } catch (com.google.security.cryptauth.lib.securegcm.HandshakeException
135                 | Ukey2Handshake.AlertException e) {
136             throw new HandshakeException(e);
137         }
138     }
139 }
140