RewardedAdsVerifier { public void verify(String rewardUrl) throws GeneralSecurityException { URI uri; try { uri = new URI(rewardUrl); } catch (URISyntaxException ex) { throw new GeneralSecurityException(ex); } String queryString = uri.getQuery(); int i = queryString.indexOf(SIGNATURE_PARAM_NAME); if (i <= 0 || queryString.charAt(i - 1) != '&') { throw new GeneralSecurityException( "signature and key id must be the last two query parameters"); } byte[] tbsData = queryString.substring(0, i - 1 ).getBytes(UTF_8); String sigAndKeyId = queryString.substring(i); i = sigAndKeyId.indexOf(KEY_ID_PARAM_NAME); if (i == -1 || sigAndKeyId.charAt(i - 1) != '&') { throw new GeneralSecurityException( "signature and key id must be the last two query parameters"); } String sig = sigAndKeyId.substring( SIGNATURE_PARAM_NAME.length(), i - 1 ); try { long keyId = Long.parseLong(sigAndKeyId.substring(i + KEY_ID_PARAM_NAME.length())); verify(tbsData, keyId, Base64.urlSafeDecode(sig)); } catch (NumberFormatException ex) { throw new GeneralSecurityException("key_id must be a long"); } } RewardedAdsVerifier(List<VerifyingPublicKeysProvider> verifyingPublicKeysProviders); private  RewardedAdsVerifier(Builder builder); void verify(String rewardUrl); static final String SIGNATURE_PARAM_NAME; static final String KEY_ID_PARAM_NAME; static final String PUBLIC_KEYS_URL_PROD; static final String PUBLIC_KEYS_URL_TEST; static final KeysDownloader KEYS_DOWNLOADER_INSTANCE_PROD; static final KeysDownloader KEYS_DOWNLOADER_INSTANCE_TEST; }
@Test public void testShouldVerify() throws Exception { RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON) .build(); verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID)); }
@Test public void testShouldVerifyIfKeyIdIsLargerThanMaxInt() throws Exception { long keyId = Integer.MAX_VALUE + 1; JSONObject trustedKeysJson = new JSONObject(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON); trustedKeysJson .getJSONArray("keys") .getJSONObject(0) .put("keyId", keyId); RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(trustedKeysJson.toString()) .build(); verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, keyId)); }
@Test public void testShouldVerifyWithEncodedUrl() throws Exception { RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON) .build(); String rewardUrl = "https: String decodedQueryString = "foo=hello world&bar=user@gmail.com"; EcdsaSignJce signer = new EcdsaSignJce( EllipticCurves.getEcPrivateKey(Base64.decode(GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64)), HashType.SHA256, EcdsaEncoding.DER); byte[] signature = signer.sign(decodedQueryString.getBytes(UTF_8)); String signedUrl = buildUrl(rewardUrl, signature, KEY_ID); verifier.verify(signedUrl); }
@Test public void testShouldDecryptV1WhenFetchingSenderVerifyingKeys() throws Exception { RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .fetchVerifyingPublicKeysWith( new KeysDownloader.Builder() .setHttpTransport( new MockHttpTransport.Builder() .setLowLevelHttpResponse( new MockLowLevelHttpResponse() .setContent(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON)) .build()) .setUrl("https: .build()) .build(); verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID)); }
@Test public void testShouldFailIfVerifyingWithDifferentKey() throws Exception { JSONObject trustedKeysJson = new JSONObject(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON); trustedKeysJson .getJSONArray("keys") .getJSONObject(0) .put("base64", ALTERNATE_PUBLIC_SIGNING_KEY); RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(trustedKeysJson.toString()) .build(); try { verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID)); fail("Expected GeneralSecurityException"); } catch (GeneralSecurityException e) { assertEquals("Invalid signature", e.getMessage()); } }
@Test public void testShouldFailIfKeyIdNotFound() throws Exception { RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON) .build(); try { verifier.verify(signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID + 1)); fail("Expected GeneralSecurityException"); } catch (GeneralSecurityException e) { assertTrue(e.getMessage().contains("cannot find verifying key with key id")); } }
@Test public void testShouldFailIfSignedMessageWasChanged() throws Exception { RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON) .build(); byte[] validSignedUrl = signUrl(REWARD_URL, GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64, KEY_ID).getBytes(UTF_8); for (int i = REWARD_HOST_AND_PATH.length(); i < REWARD_URL.length(); i++) { byte[] modifiedUrl = Arrays.copyOf(validSignedUrl, validSignedUrl.length); modifiedUrl[i] = (byte) (modifiedUrl[i] ^ 0xff); try { verifier.verify(new String(modifiedUrl, UTF_8)); fail("Expected GeneralSecurityException"); } catch (GeneralSecurityException e) { assertEquals("Invalid signature", e.getMessage()); } } }
@Test public void testShouldFailIfSignatureWasChanged() throws Exception { EcdsaSignJce signer = new EcdsaSignJce( EllipticCurves.getEcPrivateKey(Base64.decode(GOOGLE_SIGNING_PRIVATE_KEY_PKCS8_BASE64)), HashType.SHA256, EcdsaEncoding.DER); RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON) .build(); byte[] validSig = signer.sign(REWARD_URL.getBytes(UTF_8)); for (int i = 0; i < validSig.length; i++) { byte[] modifiedSig = Arrays.copyOf(validSig, validSig.length); modifiedSig[i] = (byte) (modifiedSig[i] ^ 0xff); String modifiedUrl = buildUrl(REWARD_URL, modifiedSig, KEY_ID); try { verifier.verify(modifiedUrl); fail("Expected GeneralSecurityException"); } catch (GeneralSecurityException e) { System.out.println(e); } } }
@Test public void testShouldFailWithoutSignature() throws Exception { RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON) .build(); try { verifier.verify(REWARD_URL); } catch (GeneralSecurityException e) { assertEquals("signature and key id must be the last two query parameters", e.getMessage()); } }
@Test public void testShouldFailWithoutKeyId() throws Exception { RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON) .build(); try { verifier.verify( new StringBuilder(REWARD_URL) .append("&") .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME) .append("foo") .toString()); } catch (GeneralSecurityException e) { assertEquals("signature and key id must be the last two query parameters", e.getMessage()); } }
@Test public void testShouldFailWithSignatureAndKeyIdNotTheLastParameters() throws Exception { RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON) .build(); try { verifier.verify( new StringBuilder(REWARD_HOST_AND_PATH) .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME) .toString()); } catch (GeneralSecurityException e) { assertEquals("signature and key id must be the last two query parameters", e.getMessage()); } }
@Test public void testShouldFailWithSignatureAndKeyIdNotTheLastParameters2() throws Exception { RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON) .build(); try { verifier.verify( new StringBuilder(REWARD_HOST_AND_PATH) .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME) .append("foo") .append("&") .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME) .append("123") .append("bar=baz") .toString()); } catch (GeneralSecurityException e) { assertEquals("signature and key id must be the last two query parameters", e.getMessage()); } }
@Test public void testShouldFailWithSignatureAndKeyIdNotTheLastParameters3() throws Exception { RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON) .build(); try { verifier.verify( new StringBuilder(REWARD_URL) .append("&") .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME) .append("foo") .append("&") .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME) .append("123") .append("&bar=baz") .toString()); } catch (GeneralSecurityException e) { assertEquals("key_id must be a long", e.getMessage()); } }
@Test public void testShouldFailWithSignatureAndKeyIdNoAmpersand() throws Exception { RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON) .build(); try { verifier.verify( new StringBuilder(REWARD_URL) .append("&") .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME) .append("foo") .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME) .append("123") .toString()); } catch (GeneralSecurityException e) { assertEquals("signature and key id must be the last two query parameters", e.getMessage()); } }
@Test public void testShouldFailWithKeyIdNotLong() throws Exception { RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder() .setVerifyingPublicKeys(GOOGLE_VERIFYING_PUBLIC_KEYS_JSON) .build(); try { verifier.verify( new StringBuilder(REWARD_URL) .append("&") .append(RewardedAdsVerifier.SIGNATURE_PARAM_NAME) .append("foo") .append("&") .append(RewardedAdsVerifier.KEY_ID_PARAM_NAME) .append("not_long") .toString()); } catch (GeneralSecurityException e) { assertEquals("key_id must be a long", e.getMessage()); } }