blob: 9474bd48d911b55e46e2eff246376d61b1547506 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.car.encryptionrunner;
import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake;
/**
* An {@link EncryptionRunner} that uses Ukey2 as the underlying implementation, and generates a
* longer token for the out of band verification step.
*
* <p>To use this class:
*
* <p>1. As a client.
*
* <p>{@code
* HandshakeMessage initialClientMessage = clientRunner.initHandshake();
* sendToServer(initialClientMessage.getNextMessage());
* byte message = getServerResponse();
* HandshakeMessage message = clientRunner.continueHandshake(message);
* }
*
* <p>If it is a first-time connection,
*
* <p>{@code message.getHandshakeState()} should be OOB_VERIFICATION_NEEDED. Wait for an encrypted
* message sent from the server, and decrypt that message with an out of band key that was generated
* before the start of the handshake.
*
* <p>After confirming that the decrypted message matches the verification code, send an encrypted
* message back to the server, and call {@code HandshakeMessage lastMessage =
* clientRunner.verifyPin();} otherwise {@code clientRunner.invalidPin(); }
*
* <p>Use {@code lastMessage.getKey()} to get the key for encryption.
*
* <p>If it is a reconnection,
*
* <p>{@code message.getHandshakeState()} should be RESUMING_SESSION, PIN has been verified blindly,
* send the authentication message over to server, then authenticate the message from server.
*
* <p>{@code
* clientMessage = clientRunner.initReconnectAuthentication(previousKey)
* sendToServer(clientMessage.getNextMessage());
* HandshakeMessage lastMessage = clientRunner.authenticateReconnection(previousKey, message)
* }
*
* <p>{@code lastMessage.getHandshakeState()} should be FINISHED if reconnection handshake is done.
*
* <p>2. As a server.
*
* <p>{@code
* byte[] initialMessage = getClientMessageBytes();
* HandshakeMessage message = serverRunner.respondToInitRequest(initialMessage);
* sendToClient(message.getNextMessage());
* byte[] clientMessage = getClientResponse();
* HandshakeMessage message = serverRunner.continueHandshake(clientMessage);}
*
* <p>if it is a first-time connection,
*
* <p>{@code message.getHandshakeState()} should be OOB_VERIFICATION_NEEDED, send the verification
* code to the client, encrypted using an out of band key generated before the start of the
* handshake, and wait for a response from the client.
* If the decrypted message from the client matches the verification code, call {@code
* HandshakeMessage lastMessage = serverRunner.verifyPin}, otherwise
* {@code clientRunner.invalidPin(); }
* Use {@code lastMessage.getKey()} to get the key for encryption.
*
* <p>If it is a reconnection,
*
* <p>{@code message.getHandshakeState()} should be RESUMING_SESSION,PIN has been verified blindly,
* waiting for client message.
* After client message been received,
* {@code serverMessage = serverRunner.authenticateReconnection(previousKey, message);
* sendToClient(serverMessage.getNextMessage());}
* {@code serverMessage.getHandshakeState()} should be FINISHED if reconnection handshake is done.
*
* <p>Also see {@link EncryptionRunnerTest} for examples.
*/
public final class OobUkey2EncryptionRunner extends Ukey2EncryptionRunner {
// Choose max verification string length supported by Ukey2
private static final int VERIFICATION_STRING_LENGTH = 32;
@Override
public HandshakeMessage continueHandshake(byte[] response) throws HandshakeException {
checkInitialized();
Ukey2Handshake uKey2Client = getUkey2Client();
try {
if (uKey2Client.getHandshakeState() != Ukey2Handshake.State.IN_PROGRESS) {
throw new IllegalStateException(
"handshake is not in progress, state =" + uKey2Client.getHandshakeState());
}
uKey2Client.parseHandshakeMessage(response);
// Not obvious from ukey2 api, but getting the next message can change the state.
// calling getNext message might go from in progress to verification needed, on
// the assumption that we already send this message to the peer.
byte[] nextMessage = null;
if (uKey2Client.getHandshakeState() == Ukey2Handshake.State.IN_PROGRESS) {
nextMessage = uKey2Client.getNextHandshakeMessage();
}
byte[] verificationCode = null;
if (uKey2Client.getHandshakeState() == Ukey2Handshake.State.VERIFICATION_NEEDED) {
// getVerificationString() needs to be called before notifyPinVerified().
verificationCode = uKey2Client.getVerificationString(VERIFICATION_STRING_LENGTH);
if (isReconnect()) {
HandshakeMessage handshakeMessage = verifyPin();
return HandshakeMessage.newBuilder()
.setHandshakeState(handshakeMessage.getHandshakeState())
.setNextMessage(nextMessage)
.build();
}
}
return HandshakeMessage.newBuilder()
.setHandshakeState(HandshakeMessage.HandshakeState.OOB_VERIFICATION_NEEDED)
.setNextMessage(nextMessage)
.setOobVerificationCode(verificationCode)
.build();
} catch (com.google.security.cryptauth.lib.securegcm.HandshakeException
| Ukey2Handshake.AlertException e) {
throw new HandshakeException(e);
}
}
}