java - How to validate Safety Net JWS signature from header data in Android app -


i'm using safetynet api checking if device rooted or not , using below helpful code uses android verification api validate jwt signature:

https://github.com/scottyab/safetynethelper

and want validate on client side reduce overhead of web service , besides has limitation on 10k request per day.

so after decoding jws i'm getting 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 part if perform base64 decoding becomes unreadable below signature string received in jws last element

gw09rv1abbtd4er7f5ww_3tt1mprd5youmkpkwnrxjq8xw_cxlo4428dhtjdd8tbep-iv3nrvrwt2t4ph1usr2kj9budqjuxqzouhn93r2hfk-uakuyqyhp89_wowjscg4ysvhd4jc9s1hrzlngauosocomhn4szlzn5o8bxybdxkjhwwgard4bclhcwjzmxz5izfkhdiayenrq09ceqjrx_plqay8er_oai_2idzbnigfd2kmlk_ckaevjdxuc4bzjsilvriulrvp362wwhz4r1bhh8flmhr88nk99app2jkqd2l7lpv8y5f3fn3dkhj15czhr6zbitow1futeifg 

now per google

"verify compatibility check response: extract ssl certificate chain jws message. validate ssl certificate chain , use ssl hostname matching verify leaf certificate issued hostname attest.android.com. use certificate verify signature of jws message."

i have cert string , signature how should go validating ssl certificate string , host name matching on second cert , how validate signature.

i need pointers on , code snipped helpful.

the way want validate jwt signature on device not secure. think next case:

  • the device rooted, malware application root privileges catches request google's safetynet , returns self-signed response.

  • when verify response own server service - response you've got wasn't provided google. if locally on device - same malware app catch request verify jwt signature , respond true.

anyway, can locally:

  1. you need api key google developers application.
  2. use android device verification api: android developers:

to use android device verification api:

create json message containing entire contents of jws message in following format:

{ "signedattestation": "<output of> getjwsresult()>" } 

use http post request send message content-type of "application/json" following url: https://www.googleapis.com/androidcheck/v1/attestations/verify?key=<your api key>

the service validates integrity of message, , if message valid, returns json message following contents: { “isvalidsignature”: true }

so (code safetynet helper):

/**  *  * validates result android device verification api.  *  * note: validates provided jws (json web signature) message received actual safetynet service.  * *not* verify payload data matches original compatibility check request.  * post 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 scottab on 27/05/2015.  */ public class androiddeviceverifier {      private static final string tag = androiddeviceverifier.class.getsimplename();      //used verifiy 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 trust managers url connection. default uses system defaults plus googleapistrustmanager (ssl pinning)      * @return array of trustmanager including system defaults plus googleapistrustmanager (ssl pinning)      * @throws keystoreexception      * @throws nosuchalgorithmexception      */     protected trustmanager[] gettrustmanagers() throws keystoreexception, nosuchalgorithmexception {         trustmanagerfactory trustmanagerfactory = trustmanagerfactory.getinstance(trustmanagerfactory.getdefaultalgorithm());         //init 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 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 = 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 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);             }         }     }  } 

Comments