Commit b43b040e authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

Keep caBLE state.

The change causes a state blob to be loaded and stored in Android's
preferences system. The state blob contains the long-term authenticator
keys, which are randomly generated as needed.

(Advertising and handshaking based on these keys is not yet
implemented.)

BUG=1002262

Change-Id: I2cf0f4235d5395a738c18b2f9fe1db435e3b9158
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2099236Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Commit-Queue: Adam Langley <agl@chromium.org>
Cr-Commit-Position: refs/heads/master@{#749474}
parent 8d13b361
...@@ -19,7 +19,9 @@ import android.bluetooth.le.AdvertiseData; ...@@ -19,7 +19,9 @@ import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings; import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.BluetoothLeAdvertiser; import android.bluetooth.le.BluetoothLeAdvertiser;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.util.Base64;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.Log; import org.chromium.base.Log;
...@@ -64,6 +66,11 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable { ...@@ -64,6 +66,11 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable {
// need to dump debugging information from this code. // need to dump debugging information from this code.
private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray(); private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
// The filename and key name of the SharedPreferences value that contains
// the base64-encoded state from the native code.
private static final String STATE_FILE_NAME = "cablev2_authenticator";
private static final String STATE_VALUE_NAME = "keys";
/** /**
* The pending fragments to send to each client. If present, the value is * The pending fragments to send to each client. If present, the value is
* never an empty array. * never an empty array.
...@@ -95,6 +102,19 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable { ...@@ -95,6 +102,19 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable {
* @return true if successful and false on error. * @return true if successful and false on error.
*/ */
public boolean start() { public boolean start() {
Context context = ContextUtils.getApplicationContext();
SharedPreferences prefs =
context.getSharedPreferences(STATE_FILE_NAME, Context.MODE_PRIVATE);
byte[] stateBytes = null;
try {
stateBytes = Base64.decode(prefs.getString(STATE_VALUE_NAME, ""), Base64.DEFAULT);
if (stateBytes.length == 0) {
stateBytes = null;
}
} catch (IllegalArgumentException e) {
Log.w(TAG, "Ignoring corrupt state");
}
BluetoothGattService cableService = new BluetoothGattService( BluetoothGattService cableService = new BluetoothGattService(
UUID.fromString(CABLE_UUID), BluetoothGattService.SERVICE_TYPE_PRIMARY); UUID.fromString(CABLE_UUID), BluetoothGattService.SERVICE_TYPE_PRIMARY);
cableService.addCharacteristic(new BluetoothGattCharacteristic( cableService.addCharacteristic(new BluetoothGattCharacteristic(
...@@ -119,7 +139,6 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable { ...@@ -119,7 +139,6 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable {
BluetoothGattCharacteristic.PERMISSION_READ BluetoothGattCharacteristic.PERMISSION_READ
| BluetoothGattCharacteristic.PERMISSION_WRITE)); | BluetoothGattCharacteristic.PERMISSION_WRITE));
Context context = ContextUtils.getApplicationContext();
BluetoothManager manager = BluetoothManager manager =
(BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
mServer = manager.openGattServer(context, this); mServer = manager.openGattServer(context, this);
...@@ -136,8 +155,12 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable { ...@@ -136,8 +155,12 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable {
// worrying about JNIEnv objects and references being incorrectly used // worrying about JNIEnv objects and references being incorrectly used
// across threads. // across threads.
assert mTaskRunner == null; assert mTaskRunner == null;
// TODO: in practice, this sadly appears to return the UI thread,
// despite requesting |BEST_EFFORT|.
mTaskRunner = PostTask.createSingleThreadTaskRunner(TaskTraits.BEST_EFFORT); mTaskRunner = PostTask.createSingleThreadTaskRunner(TaskTraits.BEST_EFFORT);
mTaskRunner.postTask(() -> BLEHandlerJni.get().start(this)); // Local variables passed into a lambda must be final.
final byte[] state = stateBytes;
mTaskRunner.postTask(() -> BLEHandlerJni.get().start(this, state));
return true; return true;
} }
...@@ -275,6 +298,10 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable { ...@@ -275,6 +298,10 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable {
} }
private static String hex(byte[] bytes) { private static String hex(byte[] bytes) {
if (bytes == null) {
return "(null)";
}
char[] ret = new char[bytes.length * 2]; char[] ret = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) { for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF; int v = bytes[j] & 0xFF;
...@@ -370,6 +397,7 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable { ...@@ -370,6 +397,7 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable {
*/ */
@CalledByNative @CalledByNative
public void sendBLEAdvert(byte[] dataUuidBytes) { public void sendBLEAdvert(byte[] dataUuidBytes) {
assert mTaskRunner.belongsToCurrentThread();
Log.i(TAG, "sendBLEAdvert " + dataUuidBytes.length); Log.i(TAG, "sendBLEAdvert " + dataUuidBytes.length);
maybeStopAdvertising(); maybeStopAdvertising();
...@@ -411,13 +439,30 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable { ...@@ -411,13 +439,30 @@ class BLEHandler extends BluetoothGattServerCallback implements Closeable {
advertiser.startAdvertising(settings, data, mCallback); advertiser.startAdvertising(settings, data, mCallback);
} }
/**
* Called by native code to store a new state blob.
*/
@CalledByNative
public void setState(byte[] newState) {
assert mTaskRunner.belongsToCurrentThread();
Context context = ContextUtils.getApplicationContext();
SharedPreferences prefs =
context.getSharedPreferences(STATE_FILE_NAME, Context.MODE_PRIVATE);
Log.i(TAG, "Writing updated state");
prefs.edit()
.putString(STATE_VALUE_NAME,
Base64.encodeToString(newState, Base64.NO_WRAP | Base64.NO_PADDING))
.apply();
}
@NativeMethods @NativeMethods
interface Natives { interface Natives {
/** /**
* Called to alert the C++ code to a new instance. The C++ code calls back into this object * Called to alert the C++ code to a new instance. The C++ code calls back into this object
* to send data. * to send data.
*/ */
void start(BLEHandler bleHandler); void start(BLEHandler bleHandler, byte[] stateBytes);
void stop(); void stop();
/** /**
* Called when a QR code has been scanned. * Called when a QR code has been scanned.
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "components/cbor/reader.h" #include "components/cbor/reader.h"
#include "components/cbor/writer.h"
#include "components/device_event_log/device_event_log.h" #include "components/device_event_log/device_event_log.h"
#include "crypto/aead.h" #include "crypto/aead.h"
#include "device/fido/authenticator_get_info_response.h" #include "device/fido/authenticator_get_info_response.h"
...@@ -16,6 +17,7 @@ ...@@ -16,6 +17,7 @@
#include "device/fido/cable/cable_discovery_data.h" #include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/cable/v2_handshake.h" #include "device/fido/cable/v2_handshake.h"
#include "device/fido/fido_constants.h" #include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "third_party/boringssl/src/include/openssl/aes.h" #include "third_party/boringssl/src/include/openssl/aes.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h" #include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/digest.h" #include "third_party/boringssl/src/include/openssl/digest.h"
...@@ -30,6 +32,8 @@ ...@@ -30,6 +32,8 @@
// only be included once across Chromium. // only be included once across Chromium.
#include "chrome/android/features/cablev2_authenticator/internal/jni_headers/BLEHandler_jni.h" #include "chrome/android/features/cablev2_authenticator/internal/jni_headers/BLEHandler_jni.h"
using device::fido_parsing_utils::CopyCBORBytestring;
namespace { namespace {
// Defragmenter accepts CTAP2 message fragments and reassembles them. // Defragmenter accepts CTAP2 message fragments and reassembles them.
...@@ -119,9 +123,12 @@ class Defragmenter { ...@@ -119,9 +123,12 @@ class Defragmenter {
// AuthenticatorState contains the keys for a caBLE v2 authenticator. // AuthenticatorState contains the keys for a caBLE v2 authenticator.
struct AuthenticatorState { struct AuthenticatorState {
// TODO: authenticator-global state isn't yet implemented. device::CableEidGeneratorKey eid_gen_key;
base::Optional<std::pair<std::array<uint8_t, device::kCableNonceSize>, device::CablePskGeneratorKey psk_gen_key;
std::array<uint8_t, device::kCableEphemeralIdSize>>> bssl::UniquePtr<EC_KEY> identity_key;
std::pair<std::array<uint8_t, device::kCableNonceSize>,
std::array<uint8_t, device::kCableEphemeralIdSize>>
pairing_nonce_and_eid; pairing_nonce_and_eid;
base::Optional<std::pair<std::array<uint8_t, device::kCableNonceSize>, base::Optional<std::pair<std::array<uint8_t, device::kCableNonceSize>,
std::array<uint8_t, device::kCableEphemeralIdSize>>> std::array<uint8_t, device::kCableEphemeralIdSize>>>
...@@ -343,9 +350,14 @@ class CableInterface { ...@@ -343,9 +350,14 @@ class CableInterface {
} }
void Start(JNIEnv* env, void Start(JNIEnv* env,
const base::android::JavaParamRef<jobject>& ble_handler) { const base::android::JavaParamRef<jobject>& ble_handler,
const base::android::JavaParamRef<jbyteArray>& state_bytes) {
ble_handler_.Reset(ble_handler); ble_handler_.Reset(ble_handler);
env_ = env; env_ = env;
if (!ParseState(state_bytes)) {
GenerateFreshStateAndStore();
}
} }
void Stop() { void Stop() {
...@@ -353,7 +365,6 @@ class CableInterface { ...@@ -353,7 +365,6 @@ class CableInterface {
clients_.clear(); clients_.clear();
known_mtus_.clear(); known_mtus_.clear();
auth_state_.qr_nonce_and_eid.reset(); auth_state_.qr_nonce_and_eid.reset();
auth_state_.pairing_nonce_and_eid.reset();
auth_state_.qr_discovery_data.reset(); auth_state_.qr_discovery_data.reset();
env_ = nullptr; env_ = nullptr;
} }
...@@ -460,6 +471,63 @@ class CableInterface { ...@@ -460,6 +471,63 @@ class CableInterface {
friend struct base::DefaultSingletonTraits<CableInterface>; friend struct base::DefaultSingletonTraits<CableInterface>;
CableInterface() = default; CableInterface() = default;
bssl::UniquePtr<EC_KEY> P256KeyFromSeed(base::span<const uint8_t, 32> seed) {
bssl::UniquePtr<EC_GROUP> p256(
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
return bssl::UniquePtr<EC_KEY>(
EC_KEY_derive_from_secret(p256.get(), seed.data(), seed.size()));
}
bool ParseState(const base::android::JavaParamRef<jbyteArray>& state_bytes) {
if (!state_bytes) {
return false;
}
base::span<const uint8_t> state_bytes_span(
reinterpret_cast<uint8_t*>(
env_->GetByteArrayElements(state_bytes, nullptr)),
env_->GetArrayLength(state_bytes));
base::Optional<cbor::Value> state = cbor::Reader::Read(state_bytes_span);
if (!state || !state->is_map()) {
return false;
}
const cbor::Value::MapValue& state_map(state->GetMap());
std::array<uint8_t, 32> identity_key_seed;
if (!CopyCBORBytestring(&auth_state_.eid_gen_key, state_map, 1) ||
!CopyCBORBytestring(&auth_state_.psk_gen_key, state_map, 2) ||
!CopyCBORBytestring(&identity_key_seed, state_map, 3)) {
return false;
}
auth_state_.identity_key = P256KeyFromSeed(identity_key_seed);
return true;
}
void GenerateFreshStateAndStore() {
RAND_bytes(auth_state_.eid_gen_key.data(), auth_state_.eid_gen_key.size());
RAND_bytes(auth_state_.psk_gen_key.data(), auth_state_.psk_gen_key.size());
std::array<uint8_t, 32> identity_key_seed;
RAND_bytes(identity_key_seed.data(), identity_key_seed.size());
auth_state_.identity_key = P256KeyFromSeed(identity_key_seed);
cbor::Value::MapValue map;
map.emplace(1, cbor::Value(auth_state_.eid_gen_key));
map.emplace(2, cbor::Value(auth_state_.psk_gen_key));
map.emplace(3, cbor::Value(identity_key_seed));
base::Optional<std::vector<uint8_t>> bytes =
cbor::Writer::Write(cbor::Value(std::move(map)));
CHECK(bytes.has_value());
base::android::ScopedJavaLocalRef<jbyteArray> jbytes(
env_, env_->NewByteArray(bytes->size()));
env_->SetByteArrayRegion(jbytes.obj(), 0, bytes->size(),
(jbyte*)bytes->data());
Java_BLEHandler_setState(env_, ble_handler_, jbytes);
}
JNIEnv* env_ = nullptr; JNIEnv* env_ = nullptr;
base::android::ScopedJavaGlobalRef<jobject> ble_handler_; base::android::ScopedJavaGlobalRef<jobject> ble_handler_;
AuthenticatorState auth_state_; AuthenticatorState auth_state_;
...@@ -473,8 +541,9 @@ class CableInterface { ...@@ -473,8 +541,9 @@ class CableInterface {
static void JNI_BLEHandler_Start( static void JNI_BLEHandler_Start(
JNIEnv* env, JNIEnv* env,
const base::android::JavaParamRef<jobject>& ble_handler) { const base::android::JavaParamRef<jobject>& ble_handler,
CableInterface::GetInstance()->Start(env, ble_handler); const base::android::JavaParamRef<jbyteArray>& state_bytes) {
CableInterface::GetInstance()->Start(env, ble_handler, state_bytes);
} }
static void JNI_BLEHandler_Stop(JNIEnv* env) { static void JNI_BLEHandler_Stop(JNIEnv* env) {
......
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
#include "third_party/boringssl/src/include/openssl/obj.h" #include "third_party/boringssl/src/include/openssl/obj.h"
#include "third_party/boringssl/src/include/openssl/sha.h" #include "third_party/boringssl/src/include/openssl/sha.h"
using device::fido_parsing_utils::CopyCBORBytestring;
namespace { namespace {
// Maximum value of a sequence number. Exceeding this causes all operations to // Maximum value of a sequence number. Exceeding this causes all operations to
...@@ -42,18 +44,6 @@ bool ConstructNonce(uint32_t counter, base::span<uint8_t, 12> out_nonce) { ...@@ -42,18 +44,6 @@ bool ConstructNonce(uint32_t counter, base::span<uint8_t, 12> out_nonce) {
return true; return true;
} }
template <size_t N>
bool CopyBytestring(std::array<uint8_t, N>* out,
const cbor::Value::MapValue& map,
int key) {
const auto it = map.find(cbor::Value(key));
if (it == map.end() || !it->second.is_bytestring()) {
return false;
}
const std::vector<uint8_t> bytestring = it->second.GetBytestring();
return device::fido_parsing_utils::ExtractArray(bytestring, /*pos=*/0, out);
}
CBS CBSFromSpan(base::span<const uint8_t> in) { CBS CBSFromSpan(base::span<const uint8_t> in) {
CBS cbs; CBS cbs;
CBS_init(&cbs, in.data(), in.size()); CBS_init(&cbs, in.data(), in.size());
...@@ -302,9 +292,11 @@ HandshakeInitiator::ProcessResponse(base::span<const uint8_t> response) { ...@@ -302,9 +292,11 @@ HandshakeInitiator::ProcessResponse(base::span<const uint8_t> response) {
const cbor::Value::MapValue& pairing_map(pairing->GetMap()); const cbor::Value::MapValue& pairing_map(pairing->GetMap());
const auto name_it = pairing_map.find(cbor::Value(4)); const auto name_it = pairing_map.find(cbor::Value(4));
if (!CopyBytestring(&future_discovery->v2->eid_gen_key, pairing_map, 1) || if (!CopyCBORBytestring(&future_discovery->v2->eid_gen_key, pairing_map,
!CopyBytestring(&future_discovery->v2->psk_gen_key, pairing_map, 2) || 1) ||
!CopyBytestring(&future_discovery->v2->peer_identity.value(), !CopyCBORBytestring(&future_discovery->v2->psk_gen_key, pairing_map,
2) ||
!CopyCBORBytestring(&future_discovery->v2->peer_identity.value(),
pairing_map, 3) || pairing_map, 3) ||
name_it == pairing_map.end() || !name_it->second.is_string() || name_it == pairing_map.end() || !name_it->second.is_string() ||
!EC_POINT_oct2point(group, peer_point.get(), !EC_POINT_oct2point(group, peer_point.get(),
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "base/containers/span.h" #include "base/containers/span.h"
#include "base/optional.h" #include "base/optional.h"
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "components/cbor/values.h"
#include "crypto/sha2.h" #include "crypto/sha2.h"
namespace device { namespace device {
...@@ -123,6 +124,21 @@ base::StringPiece ConvertToStringPiece(base::span<const uint8_t> data); ...@@ -123,6 +124,21 @@ base::StringPiece ConvertToStringPiece(base::span<const uint8_t> data);
COMPONENT_EXPORT(DEVICE_FIDO) COMPONENT_EXPORT(DEVICE_FIDO)
std::string ConvertBytesToUuid(base::span<const uint8_t, 16> bytes); std::string ConvertBytesToUuid(base::span<const uint8_t, 16> bytes);
// Copies the contents of the bytestring, keyed by |key|, from |map| into |out|.
// Returns true on success or false if the key if not found, the value is not a
// bytestring, or the value has the wrong length.
template <size_t N>
bool CopyCBORBytestring(std::array<uint8_t, N>* out,
const cbor::Value::MapValue& map,
int key) {
const auto it = map.find(cbor::Value(key));
if (it == map.end() || !it->second.is_bytestring()) {
return false;
}
const std::vector<uint8_t> bytestring = it->second.GetBytestring();
return ExtractArray(bytestring, /*pos=*/0, out);
}
} // namespace fido_parsing_utils } // namespace fido_parsing_utils
} // namespace device } // namespace device
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment