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