Merge "[Secure Channel] Resolve Ukey2 Client Init collision edge-case" into udc-dev am: 0dda4e11e6
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23214159 Change-Id: I486c3dd799f62a59bc0e55354e81ab7c2e98bdbd Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -53,8 +53,6 @@ public class SecureChannel {
|
|||||||
private static final int VERSION = 1;
|
private static final int VERSION = 1;
|
||||||
private static final int HEADER_LENGTH = 6;
|
private static final int HEADER_LENGTH = 6;
|
||||||
|
|
||||||
private static final String HANDSHAKE_PROTOCOL = "AES_256_CBC-HMAC_SHA256";
|
|
||||||
|
|
||||||
private final InputStream mInput;
|
private final InputStream mInput;
|
||||||
private final OutputStream mOutput;
|
private final OutputStream mOutput;
|
||||||
private final Callback mCallback;
|
private final Callback mCallback;
|
||||||
@@ -62,14 +60,16 @@ public class SecureChannel {
|
|||||||
private final AttestationVerifier mVerifier;
|
private final AttestationVerifier mVerifier;
|
||||||
|
|
||||||
private volatile boolean mStopped;
|
private volatile boolean mStopped;
|
||||||
private boolean mInProgress;
|
private volatile boolean mInProgress;
|
||||||
|
|
||||||
private Role mRole;
|
private Role mRole;
|
||||||
|
private byte[] mClientInit;
|
||||||
private D2DHandshakeContext mHandshakeContext;
|
private D2DHandshakeContext mHandshakeContext;
|
||||||
private D2DConnectionContextV1 mConnectionContext;
|
private D2DConnectionContextV1 mConnectionContext;
|
||||||
|
|
||||||
private String mAlias;
|
private String mAlias;
|
||||||
private int mVerificationResult;
|
private int mVerificationResult;
|
||||||
|
private boolean mPskVerified;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -202,8 +202,8 @@ public class SecureChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
initiateHandshake();
|
|
||||||
mInProgress = true;
|
mInProgress = true;
|
||||||
|
initiateHandshake();
|
||||||
} catch (BadHandleException e) {
|
} catch (BadHandleException e) {
|
||||||
throw new SecureChannelException("Failed to initiate handshake protocol.", e);
|
throw new SecureChannelException("Failed to initiate handshake protocol.", e);
|
||||||
}
|
}
|
||||||
@@ -329,12 +329,56 @@ public class SecureChannel {
|
|||||||
|
|
||||||
mRole = Role.Initiator;
|
mRole = Role.Initiator;
|
||||||
mHandshakeContext = D2DHandshakeContext.forInitiator();
|
mHandshakeContext = D2DHandshakeContext.forInitiator();
|
||||||
|
mClientInit = mHandshakeContext.getNextHandshakeMessage();
|
||||||
|
|
||||||
// Send Client Init
|
// Send Client Init
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.d(TAG, "Sending Ukey2 Client Init message");
|
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()
|
private void exchangeHandshake()
|
||||||
@@ -345,8 +389,15 @@ public class SecureChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Waiting for message
|
// 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
|
if (mHandshakeContext == null) { // Server-side logic
|
||||||
mRole = Role.Responder;
|
mRole = Role.Responder;
|
||||||
mHandshakeContext = D2DHandshakeContext.forResponder();
|
mHandshakeContext = D2DHandshakeContext.forResponder();
|
||||||
@@ -361,7 +412,8 @@ public class SecureChannel {
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.d(TAG, "Sending Ukey2 Server Init message");
|
Slog.d(TAG, "Sending Ukey2 Server Init message");
|
||||||
}
|
}
|
||||||
sendMessage(MessageType.HANDSHAKE_INIT, mHandshakeContext.getNextHandshakeMessage());
|
sendMessage(MessageType.HANDSHAKE_INIT,
|
||||||
|
constructHandshakeInitMessage(mHandshakeContext.getNextHandshakeMessage()));
|
||||||
|
|
||||||
// Receive Client Finish
|
// Receive Client Finish
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@@ -418,9 +470,9 @@ public class SecureChannel {
|
|||||||
? Role.Responder
|
? Role.Responder
|
||||||
: Role.Initiator,
|
: Role.Initiator,
|
||||||
mPreSharedKey);
|
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.");
|
throw new SecureChannelException("Failed to verify the hash of pre-shared key.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,10 +529,21 @@ public class SecureChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSecured() {
|
private boolean isSecured() {
|
||||||
|
// Is ukey-2 encrypted
|
||||||
if (mConnectionContext == null) {
|
if (mConnectionContext == null) {
|
||||||
return false;
|
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)
|
private byte[] constructToken(D2DHandshakeContext.Role role, byte[] authValue)
|
||||||
@@ -494,6 +557,22 @@ public class SecureChannel {
|
|||||||
.array());
|
.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() {
|
private String generateAlias() {
|
||||||
String alias;
|
String alias;
|
||||||
do {
|
do {
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import java.io.IOException;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.Future;
|
|
||||||
|
|
||||||
class SecureTransport extends Transport implements SecureChannel.Callback {
|
class SecureTransport extends Transport implements SecureChannel.Callback {
|
||||||
private final SecureChannel mSecureChannel;
|
private final SecureChannel mSecureChannel;
|
||||||
@@ -70,7 +69,10 @@ class SecureTransport extends Transport implements SecureChannel.Callback {
|
|||||||
@Override
|
@Override
|
||||||
protected void sendMessage(int message, int sequence, @NonNull byte[] data)
|
protected void sendMessage(int message, int sequence, @NonNull byte[] data)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
establishSecureConnection();
|
// Check if channel is secured; otherwise start securing
|
||||||
|
if (!mShouldProcessRequests) {
|
||||||
|
establishSecureConnection();
|
||||||
|
}
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.d(TAG, "Queueing message 0x" + Integer.toHexString(message)
|
Slog.d(TAG, "Queueing message 0x" + Integer.toHexString(message)
|
||||||
@@ -90,15 +92,12 @@ class SecureTransport extends Transport implements SecureChannel.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void establishSecureConnection() {
|
private void establishSecureConnection() {
|
||||||
// Check if channel is secured and start securing
|
Slog.d(TAG, "Establishing secure connection.");
|
||||||
if (!mShouldProcessRequests) {
|
try {
|
||||||
Slog.d(TAG, "Establishing secure connection.");
|
mSecureChannel.establishSecureConnection();
|
||||||
try {
|
} catch (Exception e) {
|
||||||
mSecureChannel.establishSecureConnection();
|
Slog.w(TAG, "Failed to initiate secure channel handshake.", e);
|
||||||
} catch (Exception e) {
|
onError(e);
|
||||||
Slog.w(TAG, "Failed to initiate secure channel handshake.", e);
|
|
||||||
onError(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user