問題描述
我正在使用 SafetyNet API 來檢查設備是否已植根并使用以下有用的代碼,但這使用了 Android 驗證 API驗證 JWT 簽名:
https://github.com/scottyab/safetynethelper
我想在客戶端進行驗證只是為了減少另一個 Web 服務的開銷,此外它還限制每天只有 10k 個請求.
所以在解碼 JWS 后,我得到以下信息
示例 JWS 消息響應
xxxx.yyy.zzzz
標題數據
{"alg":"RS256","x5c":["<certificate1 string>","<certificate2 string>"]}
有效載荷數據
{"nonce":"<nounce>",時間戳":1472794339527,"apkPackageName":"<apkPackageName>","apkDigestSha256":"",ctsProfileMatch":真,"extension":"<擴展字符串>","apkCertificateDigestSha256":["<apkCertificateDigestSha256 字符串>"],"basicIntegrity":true}
簽名在這部分中,如果執行 Base64 解碼,它將變得不可讀,因此下面是 JWS 最后一個元素中收到的簽名字符串
<預> <代碼> Gw09rv1aBbtd4Er7F5ww_3TT1mPRD5YouMkPkwnRXJq8XW_cxlO4428DHTJdD8Tbep-Iv3nrVRWt2t4pH1uSr2kJ9budQJuXqzOUhN93r2Hfk-UAKUYQYhp89_wOWjSCG4ySVHD4jc9S1HrZlngaUosocOmhN4SzLZN5o8BXyBdXkjhWwgArd4bcLhCWJzmxz5iZfkhDiAyeNRq09CeqjRx_plqAy8eR_OaI_2idZBNIGfd2KmLK_CKaeVjDxuC4BzJsIlVRiuLrvP362Wwhz4r1bHh8flmHr88nK99apP2jkQD2l7lPv8y5F3FN3DKhJ15CzHR6ZbiTOw1fUteifg現在根據谷歌
<塊引用>"驗證兼容性檢查響應:提取 SSL 證書來自 JWS 消息的鏈.驗證 SSL 證書鏈并使用SSL 主機名匹配以驗證葉證書是否已頒發到主機名 attest.android.com.使用證書來驗證JWS 消息的簽名."
我確實有證書字符串和簽名我應該如何去驗證 SSL 證書,它是字符串和主機名匹配的第二個證書和如何驗證簽名.
我需要這方面的指針,并且代碼被剪斷會很有幫助.
您要在設備上驗證 JWT 簽名的方式不安全.考慮下一個案例:
設備已root,具有root權限的惡意軟件應用程序捕獲您對 Google 的 SafetyNet 的請求并返回自簽名回復.
當您使用自己的服務器服務驗證響應時 - 您會發現您收到的響應不是由 Google 提供的.如果您在設備本地執行此操作 - 同一惡意軟件應用程序可能會捕獲您驗證 JWT 簽名的請求并以
true
進行響應.
無論如何,您可以在本地執行此操作:
- 您需要從 Google 開發人員處為您的應用獲取 API 密鑰.
- 使用 Android 設備驗證 API:
來自 Android 開發者:
<塊引用>注意:驗證響應消息的 API 方法的固定速率限制為每個項目每天 10,000 個請求.您應該僅在初始開發階段使用 verify() 方法進行測試.您不應在生產場景中調用該方法.
[...]
要使用 Android 設備驗證 API:
創建包含 JWS 全部內容的 JSON 消息消息格式如下:
{ "signedAttestation": "<輸出>getJwsResult()>"}
使用 HTTP POST 請求發送帶有"application/json"
的 Content-Type 到以下 URL:https://www.googleapis.com/androidcheck/v1/attestations/verify?key=<你的 API 密鑰>
服務驗證消息的完整性,如果消息有效,它返回一個 JSON 消息,內容如下內容:{ isValidSignature": true }
實際上(代碼來自 SafetyNet Helper):
/**** 使用 Android 設備驗證 API 驗證結果.** 注意:這僅驗證提供的 JWS(JSON Web 簽名)消息是從實際的 SafetyNet 服務接收到的.* 它*不*驗證負載數據是否與您的原始兼容性檢查請求相匹配.* 發布到 https://www.googleapis.com/androidcheck/v1/attestations/verify?key=<你的 API 密鑰>** 更多信息請參閱 {link https://developer.android.com/google/play/safetynet/start.html#verify-compat-check}** 由 scottab 于 27/05/2015 創建.*/公共類 AndroidDeviceVerifier {私有靜態最終字符串 TAG = AndroidDeviceVerifier.class.getSimpleName();//用于驗證安全網響應 - 10,000 個請求/天免費私有靜態最終字符串 GOOGLE_VERIFICATION_URL = "https://www.googleapis.com/androidcheck/v1/attestations/verify?key=";私有最終字符串 apiKey;私有最終字符串簽名驗證;私有 AndroidDeviceVerifierCallback 回調;公共接口 AndroidDeviceVerifierCallback{無效錯誤(字符串 s);無效成功(布爾 isValidSignature);}public AndroidDeviceVerifier(@NonNull String apiKey, @NonNull String signatureToVerify) {this.apiKey = apiKey;this.signatureToVerify = signatureToVerify;}公共無效驗證(AndroidDeviceVerifierCallback androidDeviceVerifierCallback){回調 = androidDeviceVerifierCallback;AndroidDeviceVerifierTask 任務 = new AndroidDeviceVerifierTask();任務.執行();}/*** 為 URL 連接提供信任管理器.默認情況下,這使用系統默認值加上 GoogleApisTrustManager (SSL pinning)* @return 包括系統默認值和 GoogleApisTrustManager(SSL pinning)的 TrustManager 數組* @throws KeyStoreException* @throws NoSuchAlgorithmException*/受保護的 TrustManager[] getTrustManagers() 拋出 KeyStoreException,NoSuchAlgorithmException {TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());//使用默認的系統信任管理器初始化trustManagerFactory.init((KeyStore)null);TrustManager[] defaultTrustManagers = trustManagerFactory.getTrustManagers();TrustManager[] trustManagers = Arrays.copyOf(defaultTrustManagers, defaultTrustManagers.length + 1);//添加我們的谷歌APIs pinning TrustManager以獲得額外的安全性trustManagers[defaultTrustManagers.length] = new GoogleApisTrustManager();返回信任管理器;}私有類 AndroidDeviceVerifierTask 擴展 AsyncTask<Void, Void, Boolean>{私人異常錯誤;@覆蓋protected Boolean doInBackground(Void...params) {//Log.d(TAG, "signatureToVerify:" + signatureToVerify);嘗試 {URL verifyApiUrl = 新 URL(GOOGLE_VERIFICATION_URL + apiKey);SSLContext sslContext = SSLContext.getInstance("TLS");sslContext.init(null, getTrustManagers(), null);HttpsURLConnection urlConnection = (HttpsURLConnection) verifyApiUrl.openConnection();urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());urlConnection.setRequestMethod("POST");urlConnection.setRequestProperty("Content-Type", "application/json");//build post body { "signedAttestation": "<getJwsResult() 的輸出>"}字符串 requestJsonBody = "{ "signedAttestation": ""+signatureToVerify+""}";byte[] outputInBytes = requestJsonBody.getBytes("UTF-8");輸出流 os = urlConnection.getOutputStream();os.write(outputInBytes);os.close();urlConnection.connect();//resp ={ isValidSignature": true }InputStream = urlConnection.getInputStream();StringBuilder sb = new StringBuilder();BufferedReader rd = new BufferedReader(new InputStreamReader(is));字符串線;while ((line = rd.readLine()) != null) {sb.append(line);}字符串響應 = sb.toString();JSONObject responseRoot = new JSONObject(response);if(responseRoot.has("isValidSignature")){返回 responseRoot.getBoolean("isValidSignature");}}catch (異常 e){//請求驗證 JWS 消息時出錯錯誤 = e;Log.e(TAG, "驗證 JWS 消息時出現問題:" + e.getMessage(), e);返回假;}返回假;}@覆蓋受保護的無效 onPostExecute(Boolean aBoolean) {如果(錯誤!=空){callback.error(error.getMessage());}別的 {callback.success(aBoolean);}}}}
I'm using SafetyNet API for checking if device is rooted or not and using the below helpful code but this uses Android verification API to validate the JWT signature:
https://github.com/scottyab/safetynethelper
And I want to validate on client side only to reduce the overhead of another web service all and besides it has limitation on only 10k request per day.
So after decoding the JWS i'm getting the below info
Sample JWS message response
xxxx.yyy.zzzz
Header data
{"alg":"RS256","x5c":["<certificate1 string>","<certificate2 string>"]}
Payload data
{"nonce":"<nounce>",
"timestampMs":1472794339527,
"apkPackageName":"<apkPackageName>",
"apkDigestSha256":"<sha digest string>",
"ctsProfileMatch":true,
"extension":"<extension string>",
"apkCertificateDigestSha256":["<apkCertificateDigestSha256 string>"],"basicIntegrity":true}
Signature in this part if perform Base64 decoding it becomes unreadable so below is the Signature string as received in JWS last element
Gw09rv1aBbtd4Er7F5ww_3TT1mPRD5YouMkPkwnRXJq8XW_cxlO4428DHTJdD8Tbep-Iv3nrVRWt2t4pH1uSr2kJ9budQJuXqzOUhN93r2Hfk-UAKUYQYhp89_wOWjSCG4ySVHD4jc9S1HrZlngaUosocOmhN4SzLZN5o8BXyBdXkjhWwgArd4bcLhCWJzmxz5iZfkhDiAyeNRq09CeqjRx_plqAy8eR_OaI_2idZBNIGfd2KmLK_CKaeVjDxuC4BzJsIlVRiuLrvP362Wwhz4r1bHh8flmHr88nK99apP2jkQD2l7lPv8y5F3FN3DKhJ15CzHR6ZbiTOw1fUteifg
Now as per google
"Verify the compatibility check response: Extract the SSL certificate chain from the JWS message. Validate the SSL certificate chain and use SSL hostname matching to verify that the leaf certificate was issued to the hostname attest.android.com. Use the certificate to verify the signature of the JWS message."
I do have the cert string and signature how should I go about validating SSL certificate which is string and host name matching on second cert and how to validate signature.
I need pointers on this and code snipped would be very helpful.
The way you want to validate JWT signature on the device is not secure. Think about next case:
the device is rooted, malware application with root privileges catches your request to Google's SafetyNet and returns self-signed response.
When you verify the response with your own server service - you will get that the response you've got wasn't provided by Google. If you do this locally on the device - the same malware app could catch you request to verify JWT signature and respond with
true
.
Anyway, you can do this locally:
- You need to get API key from Google developers for your application.
- Use the Android Device Verification API:
From Android Developers:
Note: The API method to verify response messages has a fixed rate limit of 10,000 requests per day, per project. You should use the verify() method only for testing during the initial development stage. You shouldn't call the method in a production scenario.
[...]
To use the Android Device Verification API:
Create a JSON message containing the entire contents of the JWS message in the following format:
{ "signedAttestation": "<output of> getJwsResult()>" }
Use an HTTP POST request to send the message with a Content-Type of
"application/json"
to the following URL: https://www.googleapis.com/androidcheck/v1/attestations/verify?key=<your API key>
The service validates the integrity of the message, and if the message is valid, it returns a JSON message with the following contents:
{ "isValidSignature": true }
So actually (code from SafetyNet Helper):
/**
*
* Validates the result with Android Device Verification API.
*
* Note: This only validates that the provided JWS (JSON Web Signature) message was received from the actual SafetyNet service.
* It does *not* verify that the payload data matches your original compatibility check request.
* POST to https://www.googleapis.com/androidcheck/v1/attestations/verify?key=<your API key>
*
* More info see {link https://developer.android.com/google/play/safetynet/start.html#verify-compat-check}
*
* Created by scottab on 27/05/2015.
*/
public class AndroidDeviceVerifier {
private static final String TAG = AndroidDeviceVerifier.class.getSimpleName();
//used to verifiy the safety net response - 10,000 requests/day free
private static final String GOOGLE_VERIFICATION_URL = "https://www.googleapis.com/androidcheck/v1/attestations/verify?key=";
private final String apiKey;
private final String signatureToVerify;
private AndroidDeviceVerifierCallback callback;
public interface AndroidDeviceVerifierCallback{
void error(String s);
void success(boolean isValidSignature);
}
public AndroidDeviceVerifier(@NonNull String apiKey, @NonNull String signatureToVerify) {
this.apiKey = apiKey;
this.signatureToVerify = signatureToVerify;
}
public void verify(AndroidDeviceVerifierCallback androidDeviceVerifierCallback){
callback = androidDeviceVerifierCallback;
AndroidDeviceVerifierTask task = new AndroidDeviceVerifierTask();
task.execute();
}
/**
* Provide the trust managers for the URL connection. By Default this uses the system defaults plus the GoogleApisTrustManager (SSL pinning)
* @return array of TrustManager including system defaults plus the GoogleApisTrustManager (SSL pinning)
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
*/
protected TrustManager[] getTrustManagers() throws KeyStoreException, NoSuchAlgorithmException {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
//init with the default system trustmanagers
trustManagerFactory.init((KeyStore)null);
TrustManager[] defaultTrustManagers = trustManagerFactory.getTrustManagers();
TrustManager[] trustManagers = Arrays.copyOf(defaultTrustManagers, defaultTrustManagers.length + 1);
//add our Google APIs pinning TrustManager for extra security
trustManagers[defaultTrustManagers.length] = new GoogleApisTrustManager();
return trustManagers;
}
private class AndroidDeviceVerifierTask extends AsyncTask<Void, Void, Boolean>{
private Exception error;
@Override
protected Boolean doInBackground(Void... params) {
//Log.d(TAG, "signatureToVerify:" + signatureToVerify);
try {
URL verifyApiUrl = new URL(GOOGLE_VERIFICATION_URL + apiKey);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, getTrustManagers(), null);
HttpsURLConnection urlConnection = (HttpsURLConnection) verifyApiUrl.openConnection();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Content-Type", "application/json");
//build post body { "signedAttestation": "<output of getJwsResult()>" }
String requestJsonBody = "{ "signedAttestation": ""+signatureToVerify+""}";
byte[] outputInBytes = requestJsonBody.getBytes("UTF-8");
OutputStream os = urlConnection.getOutputStream();
os.write(outputInBytes);
os.close();
urlConnection.connect();
//resp ={ "isValidSignature": true }
InputStream is = urlConnection.getInputStream();
StringBuilder sb = new StringBuilder();
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = rd.readLine()) != null) {
sb.append(line);
}
String response = sb.toString();
JSONObject responseRoot = new JSONObject(response);
if(responseRoot.has("isValidSignature")){
return responseRoot.getBoolean("isValidSignature");
}
}catch (Exception e){
//something went wrong requesting validation of the JWS Message
error = e;
Log.e(TAG, "problem validating JWS Message :" + e.getMessage(), e);
return false;
}
return false;
}
@Override
protected void onPostExecute(Boolean aBoolean) {
if(error!=null){
callback.error(error.getMessage());
}else {
callback.success(aBoolean);
}
}
}
}
這篇關于如何從 Android 應用程序中的標頭數據驗證 Safety Net JWS 簽名的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!