Merge "[Secure Channel] Resolve Ukey2 Client Init collision edge-case" into udc-dev

This commit is contained in:
Raphael Kim
2023-05-13 22:28:34 +00:00
committed by Android (Google) Code Review
2 changed files with 99 additions and 21 deletions

View File

@@ -53,8 +53,6 @@ public class SecureChannel {
private static final int VERSION = 1;
private static final int HEADER_LENGTH = 6;
private static final String HANDSHAKE_PROTOCOL = "AES_256_CBC-HMAC_SHA256";
private final InputStream mInput;
private final OutputStream mOutput;
private final Callback mCallback;
@@ -62,14 +60,16 @@ public class SecureChannel {
private final AttestationVerifier mVerifier;
private volatile boolean mStopped;
private boolean mInProgress;
private volatile boolean mInProgress;
private Role mRole;
private byte[] mClientInit;
private D2DHandshakeContext mHandshakeContext;
private D2DConnectionContextV1 mConnectionContext;
private String mAlias;
private int mVerificationResult;
private boolean mPskVerified;
/**
@@ -202,8 +202,8 @@ public class SecureChannel {
}
try {
initiateHandshake();
mInProgress = true;
initiateHandshake();
} catch (BadHandleException e) {
throw new SecureChannelException("Failed to initiate handshake protocol.", e);
}
@@ -329,12 +329,56 @@ public class SecureChannel {
mRole = Role.Initiator;
mHandshakeContext = D2DHandshakeContext.forInitiator();
mClientInit = mHandshakeContext.getNextHandshakeMessage();
// Send Client Init
if (DEBUG) {
Slog.d(TAG, "Sending Ukey2 Client Init message");
}
sendMessage(MessageType.HANDSHAKE_INIT, mHandshakeContext.getNextHandshakeMessage());
sendMessage(MessageType.HANDSHAKE_INIT, constructHandshakeInitMessage(mClientInit));
}
// In an occasion where both participants try to initiate a handshake, resolve the conflict
// with a dice roll simulated by the message byte content comparison.
// The higher value wins! (a.k.a. gets to be the initiator)
private byte[] handleHandshakeCollision(byte[] handshakeInitMessage)
throws IOException, HandshakeException, BadHandleException, CryptoException {
// First byte indicates message type; 0 = CLIENT INIT, 1 = SERVER INIT
ByteBuffer buffer = ByteBuffer.wrap(handshakeInitMessage);
boolean isClientInit = buffer.get() == 0;
byte[] handshakeMessage = new byte[buffer.remaining()];
buffer.get(handshakeMessage);
// If received message is Server Init or current role is Responder, then there was
// no collision. Return extracted handshake message.
if (mHandshakeContext == null || !isClientInit) {
return handshakeMessage;
}
Slog.w(TAG, "Detected a Ukey2 handshake role collision. Negotiating a role.");
// if received message is "larger" than the sent message, then reset the handshake context.
if (compareByteArray(mClientInit, handshakeMessage) < 0) {
Slog.d(TAG, "Assigned: Responder");
mHandshakeContext = null;
return handshakeMessage;
} else {
Slog.d(TAG, "Assigned: Initiator; Discarding received Client Init");
// Wait for another init message after discarding the client init
ByteBuffer nextInitMessage = ByteBuffer.wrap(readMessage(MessageType.HANDSHAKE_INIT));
// Throw if this message is a Client Init again; 0 = CLIENT INIT, 1 = SERVER INIT
if (nextInitMessage.get() == 0) {
// This should never happen!
throw new HandshakeException("Failed to resolve Ukey2 handshake role collision.");
}
byte[] nextHandshakeMessage = new byte[nextInitMessage.remaining()];
nextInitMessage.get(nextHandshakeMessage);
return nextHandshakeMessage;
}
}
private void exchangeHandshake()
@@ -345,8 +389,15 @@ public class SecureChannel {
}
// Waiting for message
byte[] handshakeMessage = readMessage(MessageType.HANDSHAKE_INIT);
byte[] handshakeInitMessage = readMessage(MessageType.HANDSHAKE_INIT);
// Mark "in-progress" upon receiving the first message
mInProgress = true;
// Handle a potential collision where both devices tried to initiate a connection
byte[] handshakeMessage = handleHandshakeCollision(handshakeInitMessage);
// Proceed with the rest of Ukey2 handshake
if (mHandshakeContext == null) { // Server-side logic
mRole = Role.Responder;
mHandshakeContext = D2DHandshakeContext.forResponder();
@@ -361,7 +412,8 @@ public class SecureChannel {
if (DEBUG) {
Slog.d(TAG, "Sending Ukey2 Server Init message");
}
sendMessage(MessageType.HANDSHAKE_INIT, mHandshakeContext.getNextHandshakeMessage());
sendMessage(MessageType.HANDSHAKE_INIT,
constructHandshakeInitMessage(mHandshakeContext.getNextHandshakeMessage()));
// Receive Client Finish
if (DEBUG) {
@@ -418,9 +470,9 @@ public class SecureChannel {
? Role.Responder
: Role.Initiator,
mPreSharedKey);
boolean authenticated = Arrays.equals(receivedAuthToken, expectedAuthToken);
mPskVerified = Arrays.equals(receivedAuthToken, expectedAuthToken);
if (!authenticated) {
if (!mPskVerified) {
throw new SecureChannelException("Failed to verify the hash of pre-shared key.");
}
@@ -477,10 +529,21 @@ public class SecureChannel {
}
private boolean isSecured() {
// Is ukey-2 encrypted
if (mConnectionContext == null) {
return false;
}
return mVerifier == null || mVerificationResult == RESULT_SUCCESS;
// Is authenticated
return mPskVerified || mVerificationResult == RESULT_SUCCESS;
}
// First byte indicates message type; 0 = CLIENT INIT, 1 = SERVER INIT
// This information is needed to help resolve potential role collision.
private byte[] constructHandshakeInitMessage(byte[] message) {
return ByteBuffer.allocate(1 + message.length)
.put((byte) (Role.Initiator.equals(mRole) ? 0 : 1))
.put(message)
.array();
}
private byte[] constructToken(D2DHandshakeContext.Role role, byte[] authValue)
@@ -494,6 +557,22 @@ public class SecureChannel {
.array());
}
// Arbitrary comparator
private int compareByteArray(byte[] a, byte[] b) {
if (a == b) {
return 0;
}
if (a.length != b.length) {
return a.length - b.length;
}
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return a[i] - b[i];
}
}
return 0;
}
private String generateAlias() {
String alias;
do {

View File

@@ -29,7 +29,6 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Future;
class SecureTransport extends Transport implements SecureChannel.Callback {
private final SecureChannel mSecureChannel;
@@ -70,7 +69,10 @@ class SecureTransport extends Transport implements SecureChannel.Callback {
@Override
protected void sendMessage(int message, int sequence, @NonNull byte[] data)
throws IOException {
establishSecureConnection();
// Check if channel is secured; otherwise start securing
if (!mShouldProcessRequests) {
establishSecureConnection();
}
if (DEBUG) {
Slog.d(TAG, "Queueing message 0x" + Integer.toHexString(message)
@@ -90,15 +92,12 @@ class SecureTransport extends Transport implements SecureChannel.Callback {
}
private void establishSecureConnection() {
// Check if channel is secured and start securing
if (!mShouldProcessRequests) {
Slog.d(TAG, "Establishing secure connection.");
try {
mSecureChannel.establishSecureConnection();
} catch (Exception e) {
Slog.w(TAG, "Failed to initiate secure channel handshake.", e);
onError(e);
}
Slog.d(TAG, "Establishing secure connection.");
try {
mSecureChannel.establishSecureConnection();
} catch (Exception e) {
Slog.w(TAG, "Failed to initiate secure channel handshake.", e);
onError(e);
}
}