FIDO U2F應用開發(二)-編程接口 1. U2F JS API 2. 接口定義 3. 編程接口實例探究 4. 瀏覽器兼容測試 5 應用demo 6. 參考文獻

1. U2F JS API

  FIDO U2F定義了JavaScript API供開發者開發支持U2F設備的在線服務網站。U2F JS API分爲兩類:底層基於消息端口的API和上層應用API。在FIDO的規格文檔中介紹底層API用於與U2F設備進行消息通訊(使用MessagePort Object),發送和接收消息。本文重點關注屏蔽了通訊細節的上層API接口。

2. 接口定義

2.1. u2f接口

  使用WebIDL定義的u2f接口定義如下:

interface u2f {
    void register (DOMString appId, sequence<RegisterRequest> registerRequests, sequence<RegisteredKey> registeredKeys, function(RegisterResponse or Error) callback, optional unsigned long? opt_timeoutSeconds);
    void sign (DOMString appId, DOMString challenge, sequence<RegisteredKey> registeredKeys, function(SignResponse or Error) callback, optional unsigned long? opt_timeoutSeconds);
};

2.2. register方法

2.2.1. 請求參數

  register方法中各參數描述如下:

參數名稱 類型 可否爲空 是否可選 描述
appId DOMString 請求中的應用ID
registerRequests sequence<RegisterRequest> 註冊請求序列
registeredKeys sequence<RegisteredKey> 已經註冊到U2F設備的信息
callback function(RegisterResponse or Error) 註冊請求回調函數
opt_timeoutSeconds unsigned long 客戶端等待請求處理的超時時間

2.2.2. 返回值

  register方法成功返回的數據(callback的參數)使用RegisterResponse結構。

dictionary RegisterResponse {
    DOMString version;
    DOMString registrationData;
    DOMString clientData;
};

  其中各屬性含義如下:

  • version:U2F協議版本,如“U2F_V2”
  • registrationData:使用websafe-base64編碼後的註冊數據,數據格式參看《FIDO U2F設備應用與開發(一)-原理與協議》3.2節。
  • clientData:使用websafe-base64編碼後的clientData,數據格式參看《FIDO U2F設備應用與開發(一)-原理與協議》3.5節。

2.3. sign方法

2.3.1. 請求參數

  sign方法中各參數描述如下:

參數名稱 類型 可否爲空 是否可選 描述
appId DOMString 請求中的應用ID
challenge DOMString 使用WEBSAFE-BASE64編碼的挑戰值
registeredKeys sequence<RegisteredKey> 待簽名用戶的註冊信息
callback function(SignResponse or Error) 簽名請求回調函數
opt_timeoutSeconds unsigned long 客戶端等待請求處理的超時時間

2.3.2. 返回值

  sign方法成功返回的數據(callback的參數)使用SignResponse結構。

dictionary SignResponse {
    DOMString keyHandle;
    DOMString signatureData;
    DOMString clientData;
};

  其中各屬性含義如下:

2.4. 錯誤碼

  register和sign方法失敗時返回的錯誤碼定義如下:

interface ErrorCode {
    const short OK = 0;
    const short OTHER_ERROR = 1;
    const short BAD_REQUEST = 2;
    const short CONFIGURATION_UNSUPPORTED = 3;
    const short DEVICE_INELIGIBLE = 4;
    const short TIMEOUT = 5;
};

2.5. 接口中的數據結構

2.5.1. RegisterRequest

  使用WebIDL定義的RegisterRequest結構如下:

dictionary RegisterRequest {
    DOMString version;
    DOMString challenge;
};

屬性含義如下:

  • version:U2F協議版本,如“U2F_V2”
  • challenge:使用websafe-base64編碼的挑戰值

2.5.2. RegisteredKey

  使用WebIDL定義的RegisteredKey結構如下:

dictionary RegisteredKey {
    DOMString   version;
    DOMString   keyHandle;
    Transports? transports;
    DOMString?  appId;
};

各屬性含義如下:

  • version:U2F協議版本,如“U2F_V2”
  • keyHandle:用於簽名用戶的key handle
  • transports:傳輸方式,可選參數
  • appId:在線服務網站應用Id

3. 編程接口實例探究

3.1. 註冊過程

  讓我們來到yubico的U2F設備測試網站(https://demo.yubico.com/u2f), 使用yubico的安全key,看看register方法和sign方法如何使用。
  兩個U2F Key,如圖1所示,其中一個帶藍牙功能。


  首先測試註冊過程,如圖2所示。

  通過跟蹤網站客戶端與服務端的交互消息,我們發現開始註冊過程後,客戶端向服務端的提交了兩次請求,對應《FIDO U2F設備應用與開發(一)-原理與協議》第3節描述的3個階段中的第1和第3階段。
  客戶端第一次向服務端提交請求後,參數包含用戶名和口令,如圖3所示。

  在客戶端JS腳本對服務端返回的註冊請求進行register函數調用後,將register的註冊數據提交給服務端,提交的表單數據如圖4所示。



  由圖4提交的數據可以看到register返回成功,提交的表單數據爲:

名稱
mode bind
username zhangkai
password zhangkai
enroll-data {"challenge": "OlyOzHxaxx6LUXX5chXsxj4GfspKTskBANNOtQ_UwcA", "version": "U2F_V2", "appId": "https://demo.yubico.com"}
data(u2f設備接口調用返回數據) {"registrationData":"BQSns2lmNJhJPSFbiDioTABT5xd2OZQpmpZFREJpbiaQC8zssXg0jLaxz8_gMioQQILSE5lsbH5BqpJwWR4rJoI1QMxn5LhVlLKhs_W-F7x4ppkw9K57h7dsTCDsikFv9BnpfSvj8XYhEHV-KEoBg8sNXq_I6-PRQ5_Z6yDkAFzrlBQwggJKMIIBMqADAgECAgQSSnL-MA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNVBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgwMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjAsMSowKAYDVQQDDCFZdWJpY28gVTJGIEVFIFNlcmlhbCAyNDk0MTQ5NzIxNTgwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ9ixu9L8v2CG4QdHFgFGhIQVPBxtO0topehV5uQHV-4ivNiYi_O-_XzfIcsL9dehUNhEr-mBA8bGYH2fquKHwCozswOTAiBgkrBgEEAYLECgIEFTEuMy42LjEuNC4xLjQxNDgyLjEuMTATBgsrBgEEAYLlHAIBAQQEAwIFIDANBgkqhkiG9w0BAQsFAAOCAQEAoU8e6gB29rhHahCivnLmDQJxu0ZbLfv8fBvRLTUZiZFwMmMdeV0Jf6MKJqMlY06FchvC0BqGMD9rwHXlmXMZ4SIUiwSW7sjR9PlM9BEN5ibCiUQ9Hw9buyOcoT6B0dWqnfWvjjYSZHW_wjrwYoMVclJ2L_aIebzw71eNVdZ_lRtPMrY8iupbD5nGfX2BSn_1pvUt-D6JSjpdnIuC5_i8ja9MgBdf-Jcv2nkzPsRl2AbqzJSPG6siBFqVVYpIwgIm2sAD1B-8ngXqKKa7XhCkneBgoKT2omdqNNaMSr6MYYdDVbkCfoKMqeBksALWLo2M8HRJIXU9NePIfF1XeUU-dzBFAiAtXTkSxA8NFX8RU-qNtKdzBkuVSk-rIFjhkCJRALTIBwIhAKjY3XT8vJgjgyOyGhEyxGF8zQonpWvdOwFoTe77cOv-","version":"U2F_V2","challenge":"OlyOzHxaxx6LUXX5chXsxj4GfspKTskBANNOtQ_UwcA","attestation":"direct","clientData":"eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZmluaXNoRW5yb2xsbWVudCIsImNoYWxsZW5nZSI6Ik9seU96SHhheHg2TFVYWDVjaFhzeGo0R2ZzcEtUc2tCQU5OT3RRX1V3Y0EiLCJvcmlnaW4iOiJodHRwczovL2RlbW8ueXViaWNvLmNvbSIsImNpZF9wdWJrZXkiOiJ1bnVzZWQifQ"}

3.2. 鑑權過程

  註冊成功後,可執行鑑權(login)過程,如圖5所示。



  通過跟蹤網站客戶端與服務端的交互消息,第一次客戶端請求時攜帶了用戶名和密碼,如圖6所示。



  在客戶端JS腳本對服務端返回的簽名請求進行sign函數調用後, U2F設備產生簽名後,客戶端將簽名數據提交到服務端,如圖7所示。

  提交的表單數據爲:

名稱
mode verify
rup
username zhangkai
password zhangkai
sign-data {"challenge": "UGZj34u9u3KVWe3jFrcInm7ZcrPWaX_j9tohZ-34FT0", "version": "U2F_V2", "keyHandle": "3sHb84XcS8HfFaQJ_nhf4aRlWe_wYRKcg5wKelF51hOiP4iNJtGPbsfe5InJmGfoxUSjtqT46HBwG7jkFtc01Q", "appId": "https://demo.yubico.com"}
data(u2f設備接口返回數據) {"keyHandle":"3sHb84XcS8HfFaQJ_nhf4aRlWe_wYRKcg5wKelF51hOiP4iNJtGPbsfe5InJmGfoxUSjtqT46HBwG7jkFtc01Q","clientData":"eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwiY2hhbGxlbmdlIjoiVUdaajM0dTl1M0tWV2UzakZyY0lubTdaY3JQV2FYX2o5dG9oWi0zNEZUMCIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIiwiY2lkX3B1YmtleSI6InVudXNlZCJ9","signatureData":"AQAAAAEwRQIhAMCCFSBV7V8kr07XDY2bT3aPI9siDiOFdFBIm8FVTRq1AiBVaYi06GWIHw6uHE_3MFkjrbSY13k5ukPU9_xnNAo_xQ"}

  服務端 驗證簽名後,返回驗證成功信息。

3.3. 異常處理

  實驗過程中,如果在交互時不按U2F設備的按鈕和不插入設備,register和sign函數都會返回錯誤碼。錯誤碼的定義可參看2.4節。

3.4. u2f-api.js

  u2f-api.js是yubico提供的U2F js api,封裝了第2節接口規範中描述的接口。可從地址: https://demo.yubico.com/js/u2f-api.js 處獲取。u2f-api.js中的主要定義如下:

var u2f = u2f || {};
u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds)
u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds)

  請注意在這個腳本中將register和sign操作的超時時間定義爲30秒:

u2f.EXTENSION_TIMEOUT_SEC = 30;

  在執行3.1節的註冊過程時,通過跟蹤瀏覽器消息,可以看到第一次向服務器請求後返回的頁面中包含如下JS代碼:

setTimeout(function() {
  var request = {"challenge": "OlyOzHxaxx6LUXX5chXsxj4GfspKTskBANNOtQ_UwcA", "version": "U2F_V2", "appId": "https://demo.yubico.com"};
  console.log("Register: ", request);
  var appId = request.appId;
  var registerRequests = [{version: request.version, challenge: request.challenge, attestation: 'direct'}];
  $('#promptModal').modal('show');
  console.log(appId, registerRequests);
  u2f.register(appId, registerRequests, [], function(data) {
    console.log("Register callback", data);
    $('#promptModal').modal('hide');
    $('#bind-data').val(JSON.stringify(data));
    $('#bind-form').submit();
  });
}, 1000);

  這段代碼中,使用U2F的上層函數register進行了註冊,讀者可以與2.2節的函數參數做一下比對,在這段代碼中registeredKeys參數使用是空數組“[]”。
  仔細閱讀u2f-api.js,會發現腳本使用了EXTENSION_ID爲“kmendfapggjehodndflmmgagdbamhnfd”的chrome內置擴展完成與U2F設備的通訊。
  在執行3.2節的鑑權過程時,通過跟蹤瀏覽器消息,可以看到第一次向服務器請求後返回的頁面中包含如下JS代碼:

setTimeout(function() {
  var request = {"challenge": "UGZj34u9u3KVWe3jFrcInm7ZcrPWaX_j9tohZ-34FT0", "version": "U2F_V2", "keyHandle": "3sHb84XcS8HfFaQJ_nhf4aRlWe_wYRKcg5wKelF51hOiP4iNJtGPbsfe5InJmGfoxUSjtqT46HBwG7jkFtc01Q", "appId": "https://demo.yubico.com"};
  console.log("sign: ", request);
  var appId = request.appId;
  var challenge = request.challenge;
  var registeredKeys = [{version: request.version, keyHandle: request.keyHandle}];
  $('#promptModal').modal('show');
  u2f.sign(appId, challenge, registeredKeys, function(data) {
    $('#promptModal').modal('hide');
    $('#verify-data').val(JSON.stringify(data));
    $('#verify-form').submit();
  });
}, 1000);

  這段代碼中,使用U2F的上層函數sign進行了註冊。

4. 瀏覽器兼容測試

  使用購買的U2F設備在PC上對chrome、firefox、IE瀏覽器進行了測試。其中chrome版本爲69,firefox版本爲firefox quantum 62, 這兩種瀏覽器目前都支持U2F設備。
  在firefox中使用U2F需要打開一個開關(默認沒有打開),如圖8所示。


  這裏有個有趣的問題,測試時使用的網站仍然是https://demo.yubico.com/u2f ,JS腳本仍然使用的是u2f-api.js,前面提到這個腳本是針對chrome內置擴展應用與USB口通信的,在firefox中怎麼也能正常使用呢?
  原因就是firefox實現了自己的u2f對象,而且這個u2f對象的所有屬性都是隻讀的,u2f-api.js沒有改寫這個對象,從控制檯如下輸出可以看到:

TypeError: setting getter-only property "u2f"

IE瀏覽器使用的版本爲11,不支持U2F設備。
  在安卓手機上安裝了chrome app,使用U2F設備的BLE模式(藍牙功能)測試了U2F的支持,沒有成功,在chrome調用register函數時,U2F設備沒有閃爍。

5 應用demo

  參看FIDO U2F應用開發(三)-開發支持U2F的站點

6. 參考文獻

  1. https://fidoalliance.org/how-fido-works/
  2. https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/FIDO-U2F-COMPLETE-v1.2-ps-20
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章