Brillo设备在进行pairing的时候,需要access token, 这个access token是通过/privet/v3/auth这个连接得到的。相关代码请看externl/libweaved/src/privet/privet_handler.cc@brillo-m8-dev
/privet/v3/auth这个连接需要使用https访问, 由/privet/info中可知authentication的anonymousMaxSope 为viewer, crypto 为p224_spake2, mode为anonymous与pairing, pairing使用embeddedCode:
$ curl -H "Authorization: Privt anonymous" -k https://localhost:8001/privet/info
{
"authentication": {
"anonymousMaxScope": "viewer",
"crypto": [ "p224_spake2" ],
"mode": [ "anonymous", "pairing" ],
"pairing": [ "embeddedCode" ]
},
...
}
- emulator tcp端口映射
运行在emulator时加命令行参数:
$ brilloemualtor-x86 -m 256 -- -redir tcp:8000::80 -redir tcp:8001::443
通过adb forward实现:
$ adb forward tcp:8000 tcp:80 $ adb forward tcp:8001 tcp:443
- authentication mode为anonymous: 用于request scope, 增大anonymous的作用域:
如去request owner scope会返回access denied:
$ curl -H "Authorization: Privet anonymous" -H 'Content-Type: application/json' \
-X POST \
--data '{ "mode": "anonymous", "requestedScope": "owner" }' \
-k https://localhost:8001/privet/v3/auth
{
"error": {
"code": "accessDenied",
"debugInfo": [ {
"code": "accessDenied",
"debugInfo": "HandleAuth@external/libweave/src/privet/privet_handler.cc:672",
"message": "Scope 'owner' is not allowed"
} ],
"message": "Scope 'owner' is not allowed"
}
}
而会request viewer scope则会成功(由/privet/info可知):
$ curl -H "Authorization: Privet anonymous" -H 'Content-Type: application/json' \
-X POST \
--data '{ "mode": "anonymous", "requestedScope": "viewer" }' \
-k https://localhost:8001/privet/v3/auth
{
"accessToken": "o6yq+Nhh835iGl7HmnL838RRCcER7LOi28C6g01fAJgxOjE6MTQ1MTE5ODA3Mg==",
"expiresIn": 3600,
"scope": "viewer",
"tokenType": "Privet"
}
从返回的信息中,我们可以得到accessToken, token expires time: 3600秒后过期,于是我们就可以访问scope为viewer的连接,如/privet/v3/commands/list:
$ curl -H "Authorization: Privet VZJ7xLIDQpnShNQEPElo3tFPHezhtBrqq8ZB/lWsTQ0OTk5MDcyNg==" \
-k https://localhost:8001/privet/v3/commands/list
{
"commands": [ ]
}
- authentication mode为pairing: 可以得到所有scope(viewer, user, owner)的access token
显然,如果进行pairing类型的认证,以便得到所有scope的access token需要authCode, authCode是经过base64 encode HmacSha256加密过后的session id和session key, 而session id和session key是在设备pairing后得到的。
首先进行pairing, 得到session id:
$ curl -H "Authorization: Privet anonymous" -H 'Content-Type: application/json' \
-X POST \
--data '{ "pairing": "embeddedCode", "crypto": "p224_spake2" }' \
-k https://localhost:8001/privet/v3/pairing/start
{
"deviceCommitment": "IFS6F6YgB26DSuXVLG9gZvUcuqe3vVQHXsOYIH2Gz2p3FqW4QuVPl4SRRqo0wqDUl6jgrmcaim0=",
"sessionId": "58414923-35EB-406E-8407-88DDE34CC290"
}
$ curl -H "Authorization: Privet anonymous" -H 'Content-Type: application/json' \
-X POST \
--data '{"sessionId": "58414923-35EB-406E-8407-88DDE34CC290", "clientCommitment": "ICRFzZwrH6n5c1D/LsEQrf5H6/Ap6Hb+x4ayEonnTsDW+diTwYD0nzJxut+ZxLnFifZxsZhWRIw="}'
-k https://localhost:8001/privet/v3/pairing/confirm
{
"certFingerprint": "USQs/v/5C3YcOhJtVF3kZ8V7l6vqNJtf84PefK4n7tw=",
"certSignature": "IhmKwW6SHypU3QnqGIMbBanzJgFRBErrqnWiPoEm59w="
}
NOTE: pairing confirm中的clientCommitment 并非是之前做pairing start中得到的deviceComitment, deviceCommitment只是用来验证client中的password是否与device中的password(embedded_code)一致。详见后文。
NOTE: paring start/confirm 中的access token可以任意。
这时我们假设已经知道了authCode, 下面试着去获取owner的access token:
$ curl -H "Authorization: Privet anonymous" -H 'Content-Type: application/json' \
-X POST \
--data '{ "mode": "pairing", "authCode": "I+/YzvpmdAeS0xP67wRJhA5UEQUSf6qjh+eqVdS0BeM=", "requestedScope": "owner" }' -k https://localhost:8001/privet/v3/auth
{
"accessToken": "bpl/zQW0nH6lmRfW7z7dpvu4i82plGyeqoFhjJeR8dwzOjI6MTQ1MTE5OTI2NA==",
"expiresIn": 3600,
"scope": "owner",
"tokenType": "Privet"
}
可以看到我们已经可以得到ower的access token了,我们就获得了设备的所有控制权。
下面看看如何得到authCode:
需要embedded_code, 可以从设备的system/etc/weaved/weaved.conf中得到,default: hello
int main(int argc, char *argv[])
{
if (argc != 4) return -1;
std::string password(argv[1]);
std::string session_id(argv[2]);
std::string device_commitment_base64(argv[3]);
crypto::P224EncryptedKeyExchange spake{
crypto::P224EncryptedKeyExchange::kPeerTypeClient, password};
std::string client_commitment_base64{Base64Encode(spake.GetNextMessage())};
std::vector<uint8_t> device_commitment;
if (!Base64Decode(device_commitment_base64, &device_commitment)) {
VLOG(0) << "base64 decode failed!";
return -2;
}
if (spake.ProcessMessage(std::string(device_commitment.begin(), device_commitment.end())) !=
crypto::P224EncryptedKeyExchange::kResultPending) {
VLOG(0) << "spake: process message failed: password mismatch!";
return -3;
}
const std::string& key = spake.GetUnverifiedKey();
std::vector<uint8_t> auth_code{
HmacSha256(std::vector<uint8_t>{key.begin(), key.end()},
std::vector<uint8_t>{session_id.begin(), session_id.end()})};
std::string auth_code_base64{Base64Encode(auth_code)};
VLOG(0) << "{ \"sessionId\": \"" << session_id << "\", \"clientCommitment\": \"" <<
client_commitment_base64 << "\" }";
VLOG(0) << "{ \"mode\": \"pairing\", \"authCode\": \"" << auth_code_base64 << "\" }";
return 0;
}
相关代码可以从这里下载:create-auth-code.c
关于spake, 请参考这篇文档:
http://www.di.ens.fr/~mabdalla/papers/AbPo05a-letter.pdf
编译,打包,运行emulator后,执行:
$ adb shell create-auth-code "hello" <session id> <deviceCommitment>
后可以得到confirm pairing & authentication的json 代码。
NOTE: brillo-m7-release的方法:
设备要配对需要在out/target/product/brilloemulator_x86/system/etc/weaved/weaved.conf (device/generic/brillo/brilloemulator_x86/base_product/weaved.conf) 中添加 secret字段 (代码:external/libweave/src/config.cc)。secret字段是经过base64编码的任意32字节的数据。?
如果想进行brillo设备的配对,就必须要先知道这个secret,再通过secret得到accesstoken进行pairing。?
NOTE: 这个连接也可做个参考:
https://chromium.googlesource.com/chromiumos/third_party/autotest/+/master/server/site_tests/buffet_PrivetSetupFlow/buffet_PrivetSetupFlow.py
# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import time
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import avahi_utils
from autotest_lib.client.common_lib.cros.network import interface
from autotest_lib.client.common_lib.cros.network import iw_runner
from autotest_lib.client.common_lib.cros.network import netblock
from autotest_lib.client.common_lib.cros.network import ping_runner
from autotest_lib.client.common_lib.cros.network import xmlrpc_security_types
from autotest_lib.client.common_lib.cros.tendo import peerd_config
from autotest_lib.client.common_lib.cros.tendo import buffet_config
from autotest_lib.client.common_lib.cros.tendo import privet_helper
from autotest_lib.server import site_linux_router
from autotest_lib.server import test
from autotest_lib.server.cros.network import hostap_config
from autotest_lib.server.cros.network import wifi_client
PASSPHRASE = 'chromeos'
PRIVET_AP_STARTUP_TIMEOUT_SECONDS = 30
PRIVET_MDNS_RECORD_TIMEOUT_SECONDS = 10
PRIVET_CONNECT_TIMEOUT_SECONDS = 30
POLLING_PERIOD = 0.5
class buffet_PrivetSetupFlow(test.test):
"""This test validates the privet pairing/authentication/setup flow."""
version = 1
def warmup(self, host, router_hostname=None):
self._router = None
self._shill_xmlrpc_proxy = None
config = buffet_config.BuffetConfig(
log_verbosity=3,
enable_ping=True,
disable_pairing_security=True,
device_whitelist='any',
options={'wifi_bootstrap_mode': 'automatic'})
config.restart_with_config(host=host)
self._router = site_linux_router.build_router_proxy(
test_name=self.__class__.__name__,
client_hostname=host.hostname,
router_addr=router_hostname,
enable_avahi=True)
self._shill_xmlrpc_proxy = wifi_client.get_xmlrpc_proxy(host)
# Cleans up profiles, wifi credentials, sandboxes our new credentials.
self._shill_xmlrpc_proxy.init_test_network_state()
peerd_config.PeerdConfig(verbosity_level=3).restart_with_config(
host=host)
def cleanup(self, host):
if self._shill_xmlrpc_proxy is not None:
self._shill_xmlrpc_proxy.clean_profiles()
if self._router is not None:
self._router.close()
buffet_config.naive_restart(host=host)
def run_once(self, host):
helper = privet_helper.PrivetHelper(host=host)
logging.info('Looking for privet bootstrapping network from DUT.')
scan_interface = self._router.get_wlanif(2437, 'managed')
self._router.host.run('%s link set %s up' %
(self._router.cmd_ip, scan_interface))
start_time = time.time()
privet_bss = None
while time.time() - start_time < PRIVET_AP_STARTUP_TIMEOUT_SECONDS:
bss_list = self._router.iw_runner.scan(scan_interface)
for bss in bss_list or []:
if helper.is_softap_ssid(bss.ssid):
privet_bss = bss
if privet_bss is None:
raise error.TestFail('Device did not start soft AP in time.')
self._router.release_interface(scan_interface)
# Get the netblock of the interface running the AP.
dut_iw_runner = iw_runner.IwRunner(remote_host=host)
devs = dut_iw_runner.list_interfaces(desired_if_type='AP')
if not devs:
raise error.TestFail('No AP devices on DUT?')
ap_interface = interface.Interface(devs[0].if_name, host=host)
ap_netblock = netblock.from_addr(ap_interface.ipv4_address_and_prefix)
# Set up an AP on the router in the 5Ghz range with WPA2 security.
wpa_config = xmlrpc_security_types.WPAConfig(
psk=PASSPHRASE,
wpa_mode=xmlrpc_security_types.WPAConfig.MODE_PURE_WPA2,
wpa2_ciphers=[xmlrpc_security_types.WPAConfig.CIPHER_CCMP])
router_conf = hostap_config.HostapConfig(
frequency=5240, security_config=wpa_config,
mode=hostap_config.HostapConfig.MODE_11N_PURE)
self._router.hostap_configure(router_conf)
# Connect the other interface on the router to the AP on the client
# at a hardcoded IP address.
self._router.configure_managed_station(
privet_bss.ssid, privet_bss.frequency,
ap_netblock.get_addr_in_block(200))
station_interface = self._router.get_station_interface(instance=0)
logging.debug('Set up station on %s', station_interface)
self._router.ping(ping_runner.PingConfig(ap_netblock.addr, count=3))
logging.info('Looking for privet webserver in mDNS records.')
start_time = time.time()
while time.time() - start_time < PRIVET_MDNS_RECORD_TIMEOUT_SECONDS:
all_records = avahi_utils.avahi_browse(host=self._router.host)
records = [record for record in all_records
if (record.interface == station_interface and
record.record_type == '_privet._tcp')]
if records:
break
time.sleep(POLLING_PERIOD)
if not records:
raise error.TestFail('Did not find privet mDNS records in time.')
if len(records) > 1:
raise error.TestFail('Should not see multiple privet records.')
privet_record = records[0]
# TODO(wiley) pull the HTTPs port number out of the /info API.
helper = privet_helper.PrivetdHelper(
host=self._router.host,
hostname=privet_record.address,
http_port=int(privet_record.port))
helper.ping_server()
# Now configure the client with WiFi credentials.
auth_token = helper.privet_auth()
ssid = self._router.get_ssid()
data = helper.setup_add_wifi_credentials(ssid, PASSPHRASE)
helper.setup_start(data, auth_token)
logging.info('Waiting for DUT to connect to router network.')
start_time = time.time()
# Wait for the DUT to take down the AP.
while time.time() - start_time < PRIVET_CONNECT_TIMEOUT_SECONDS:
if not dut_iw_runner.list_interfaces(desired_if_type='AP'):
break
time.sleep(POLLING_PERIOD)
else:
raise error.TestFail('Timeout waiting for DUT to take down AP.')
# But we should be able to ping the client from the router's AP.
while time.time() - start_time < PRIVET_CONNECT_TIMEOUT_SECONDS:
if dut_iw_runner.list_interfaces(desired_if_type='managed'):
break
time.sleep(POLLING_PERIOD)
else:
raise error.TestFail('Timeout waiting for DUT managerd interface.')
while time.time() - start_time < PRIVET_CONNECT_TIMEOUT_SECONDS:
devs = dut_iw_runner.list_interfaces(desired_if_type='managed')
if devs:
managed_interface = interface.Interface(devs[0].if_name,
host=host)
# Check if we have an IP yet.
if managed_interface.ipv4_address_and_prefix:
break
time.sleep(POLLING_PERIOD)
else:
raise error.TestFail('Timeout waiting for DUT managerd interface.')
managed_netblock = netblock.from_addr(
managed_interface.ipv4_address_and_prefix)
while time.time() - start_time < PRIVET_CONNECT_TIMEOUT_SECONDS:
PING_COUNT = 3
result = self._router.ping(
ping_runner.PingConfig(managed_netblock.addr,
ignore_result=True,
count=PING_COUNT))
if result.received == PING_COUNT:
break
time.sleep(POLLING_PERIOD)
else:
raise error.TestFail('Timeout before ping was successful.')
# And buffet should think it is online as well.
helper = privet_helper.PrivetdHelper(
host=host, hostname=managed_netblock.addr,
http_port=int(privet_record.port))
helper.ping_server()
if not helper.wifi_setup_was_successful(ssid, auth_token):
raise error.TestFail('Device claims to be offline, but is online.')
[2016-01-09 09:38:26] Paring需要在5分钟之内完成,access token在3600秒之后失效。并且在获取auth token之后,session id就无效了。也就是说需要获得新的auth token,就要重新paring,重新获取session id。
还要最重要的一环,Brillo系统的时间要与产生authorization token系统的时间不能相差太大(access token中包含时间信息,在调用WEB API的时候,取这个时间验证时效。
编译create-auth-code.cc时出现一个错误,我这里将create-auth-code替换成create_auth_code, 相应的Android.mk也替换了
———————————————————————————————————————————————————–
FAILED: /bin/bash -c “(true) && (mkdir -p /home/chris/brillo_example/products/CreateAuth/out/out-dragonboard/target/product/dragonboard/obj/EXECUTABLES/create_auth_code_intermediates/LINKED/) && (prebuilts/clang/host/linux-x86/3.6/bin/clang++ -pie -nostdlib -Bdynamic -Wl,-dynamic-linker,/system/bin/linker -Wl,–gc-sections -Wl,-z,nocopyreloc -L/home/chris/brillo_example/products/CreateAuth/out/out-dragonboard/target/product/dragonboard/obj/lib -Wl,-rpath-link=/home/chris/brillo_example/products/CreateAuth/out/out-dragonboard/target/product/dragonboard/obj/lib /home/chris/brillo_example/products/CreateAuth/out/out-dragonboard/target/product/dragonboard/obj/lib/crtbegin_dynamic.o -Wl,–whole-archive -Wl,–no-whole-archive /home/chris/brillo_example/products/CreateAuth/out/out-dragonboard/target/product/dragonboard/obj/STATIC_LIBRARIES/libweave-common_intermediates/libweave-common.a /home/chris/brillo_example/products/CreateAuth/out/out-dragonboard/target/product/dragonboard/obj/STATIC_LIBRARIES/libweave-external_intermediates/libweave-external.a /home/chris/brillo_example/products/CreateAuth/out/out-dragonboard/target/product/dragonboard/obj/STATIC_LIBRARIES/libgtest_intermediates/libgtest.a /home/chris/brillo_example/products/CreateAuth/out/out-dragonboard/target/product/dragonboard/obj/STATIC_LIBRARIES/libgmock_intermediates/libgmock.a /home/chris/brillo_example/products/CreateAuth/out/out-dragonboard/target/product/dragonboard/obj/STATIC_LIBRARIES/libunwind_llvm_intermediates/libunwind_llvm.a /home/chris/brillo_example/products/CreateAuth/out/out-dragonboard/target/product/dragonboard/obj/STATIC_LIBRARIES/libcompiler_rt-extras_intermediates/libcompiler_rt-extras.a prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/bin/../lib/gcc/arm-linux-androideabi/4.9.x-google/../../../../arm-linux-androideabi/lib/armv7-a/libatomic.a prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/bin/../lib/gcc/arm-linux-androideabi/4.9.x-google/armv7-a/libgcc.a -lchrome -lexpat -lcrypto -lc++ -ldl -lc -lm -o /home/chris/brillo_example/products/CreateAuth/out/out-dragonboard/target/product/dragonboard/obj/EXECUTABLES/create_auth_code_intermediates/LINKED/create_auth_code -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,–build-id=md5 -Wl,–warn-shared-textrel -Wl,–fatal-warnings -Wl,–icf=safe -Wl,–hash-style=gnu -Wl,–fix-cortex-a8 -target arm-linux-androideabi -Bprebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/arm-linux-androideabi/bin -Wl,–exclude-libs,libunwind_llvm.a -Wl,–no-undefined /home/chris/brillo_example/products/CreateAuth/out/out-dragonboard/target/product/dragonboard/obj/lib/crtend_android.o)”
/home/chris/brillo_example/products/CreateAuth/out/out-dragonboard/target/product/dragonboard/obj/lib/crtbegin_dynamic.o:crtbegin.c:function _start: error: undefined reference to ‘main’
clang: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
make: *** [ninja_wrapper] 错误 1
—————————————————————————————————————————————————–
Android.mk如下:
———————————————————————————————————————————————–
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := create_auth_code
LOCAL_MODULE_TAGS := debug
LOCAL_CPP_EXTENSION := $(libweaveCommonCppExtension)
LOCAL_CFLAGS := $(libweaveCommonCFlags)
LOCAL_CPPFLAGS := $(libweaveCommonCppFlags)
LOCAL_C_INCLUDES := \
$(libweaveCommonCIncludes) \
external/gmock/include
LOCAL_SHARED_LIBRARIES := \
$(libweaveSharedLibraries)
LOCAL_STATIC_LIBRARIES := \
libweave-common \
libweave-external \
libgtest libgmock
# libchrome_test_helpers
# libweave-test
LOCAL_CLANG := true
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
LOCAL_SRC_FILES := \
create_auth_code.cpp
include $(BUILD_EXECUTABLE)
———————————————————————————————————————————————
你需要在将create-auth-code.cc放到external/libweave文件夹中,并且将Android.mk中的内容附加到external/libweave/Android.mk中才行。
你也可以参考http://www.brobwind.com/archives/340, https://github.com/brobwind/BroDm/ 做成一个独立的app
1
1
问题已解决,谢谢!
1