AuthTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. <?php
  2. /**
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing,
  14. * software distributed under the License is distributed on an
  15. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. * KIND, either express or implied. See the License for the
  17. * specific language governing permissions and limitations
  18. * under the License.
  19. */
  20. class AuthTest extends BaseTest
  21. {
  22. const PRIVATE_KEY_FILE = "testdata/cert.p12";
  23. const PUBLIC_KEY_FILE_JSON = "testdata/cacert.json";
  24. const PUBLIC_KEY_FILE = "testdata/cacert.pem";
  25. const USER_ID = "102102479283111695822";
  26. /** @var Google_Signer_P12 */
  27. private $signer;
  28. /** @var string */
  29. private $pem;
  30. /** @var Google_Verifier_Pem */
  31. private $verifier;
  32. public function setUp()
  33. {
  34. $this->signer = new Google_Signer_P12(
  35. file_get_contents(__DIR__.'/'.self::PRIVATE_KEY_FILE, true),
  36. "notasecret"
  37. );
  38. $this->pem = file_get_contents(__DIR__.'/'.self::PUBLIC_KEY_FILE, true);
  39. $this->verifier = new Google_Verifier_Pem($this->pem);
  40. }
  41. public function testDirectInject()
  42. {
  43. $privateKeyString = <<<PK
  44. -----BEGIN RSA PRIVATE KEY-----
  45. MIICWwIBAAKBgQC8iqFTYTrSGxddW+Tsx6cdWbQxITdM2anRbMYcohnQpQuPG46B
  46. HO3WbUA8suC6PXqeIi4JkDrAYbI2+TN6w1FE/fh2H7WczuDVKtosBcfsoL2C5loU
  47. mOf+4jL1xx4EL6xy8wMntZhNgimVCO9LkWCix/Qh9mpqx2zbC3OV4QsSQQIDAQAB
  48. AoGASAosRCClifxB/DENko9iwisxV4haiemtIlEOjYg+luNJPGAKHjlAgyrxXX/3
  49. sBGnlV53+r16RWHO54RmcCTLGwpC6zzVc6C4Or9KItdMDMnqBjmqiYDz3Na7tIPv
  50. vwzn8k8Uto26HZF8d1bTdoinxHrv7w1OVkDQWnHmWkQRjBUCQQDpNw8F1qiJJoYr
  51. tkkBmlObmSQRYD3mlEvRwu348e4dFb01oN2cfw/YNhh+Lt2TPHFz2GNn6VwJf1Yb
  52. qRKBqo/jAkEAzvY91ReYrkBm50pi2nqJc1Hcxm5CVP7MMnHbn8wExKrRG2rCDY9Y
  53. zOdsw7pP/x6mesdUy3tTrPYVbeWP6YPmiwJANx41Jbsa7/cz5KbbUE6qDe8+sACg
  54. AJvx42x/k8OR9DvMER2o4rDBDOeUGFZ5NbAmXCu7KrbjcrcuobDu18h44wJAQ2s5
  55. x0HxjcoS+4Ni4nMKdZOUTNu8Jf3+vOwUNGD8qKhQiBLl9g7dSZqV9sipqJzudI6c
  56. k9Cv+GcNoggnMlWycwJAHMVgaBmNc+RVCMar/gN6i5sENjN9Itu7U1V4Qj/mG6+4
  57. MHOXhXSKhtTe0Bqm/MssVvCmc8AraKwBMs0rkMadsA==
  58. -----END RSA PRIVATE KEY-----
  59. PK;
  60. $sign = new Google_Signer_P12($privateKeyString, null);
  61. }
  62. public function testCantOpenP12()
  63. {
  64. try {
  65. new Google_Signer_P12(
  66. file_get_contents(__DIR__.'/'.self::PRIVATE_KEY_FILE, true),
  67. "badpassword"
  68. );
  69. $this->fail("Should have thrown");
  70. } catch (Google_Auth_Exception $e) {
  71. $this->assertContains("mac verify failure", $e->getMessage());
  72. }
  73. try {
  74. new Google_Signer_P12(
  75. file_get_contents(__DIR__.'/'.self::PRIVATE_KEY_FILE, true) . "foo",
  76. "badpassword"
  77. );
  78. $this->fail("Should have thrown");
  79. } catch (Exception $e) {
  80. $this->assertContains("Unable to parse", $e->getMessage());
  81. }
  82. }
  83. public function testVerifySignature()
  84. {
  85. $binary_data = "\x00\x01\x02\x66\x6f\x6f";
  86. $signature = $this->signer->sign($binary_data);
  87. $this->assertTrue($this->verifier->verify($binary_data, $signature));
  88. $empty_string = "";
  89. $signature = $this->signer->sign($empty_string);
  90. $this->assertTrue($this->verifier->verify($empty_string, $signature));
  91. $text = "foobar";
  92. $signature = $this->signer->sign($text);
  93. $this->assertTrue($this->verifier->verify($text, $signature));
  94. $this->assertFalse($this->verifier->verify($empty_string, $signature));
  95. }
  96. // Creates a signed JWT similar to the one created by google authentication.
  97. private function makeSignedJwt($payload)
  98. {
  99. $header = array("typ" => "JWT", "alg" => "RS256");
  100. $segments = array();
  101. $segments[] = Google_Utils::urlSafeB64Encode(json_encode($header));
  102. $segments[] = Google_Utils::urlSafeB64Encode(json_encode($payload));
  103. $signing_input = implode(".", $segments);
  104. $signature = $this->signer->sign($signing_input);
  105. $segments[] = Google_Utils::urlSafeB64Encode($signature);
  106. return implode(".", $segments);
  107. }
  108. // Returns certificates similar to the ones used by google authentication.
  109. private function getSignonCerts()
  110. {
  111. return array("keyid" => $this->pem);
  112. }
  113. public function testVerifySignedJwtWithCerts()
  114. {
  115. $id_token = $this->makeSignedJwt(
  116. array(
  117. "iss" => "federated-signon@system.gserviceaccount.com",
  118. "aud" => "client_id",
  119. "sub" => self::USER_ID,
  120. "iat" => time(),
  121. "exp" => time() + 3600
  122. )
  123. );
  124. $certs = $this->getSignonCerts();
  125. $oauth2 = new Google_Auth_OAuth2($this->getClient());
  126. $ticket = $oauth2->verifySignedJwtWithCerts($id_token, $certs, "client_id");
  127. $this->assertEquals(self::USER_ID, $ticket->getUserId());
  128. // Check that payload and envelope got filled in.
  129. $attributes = $ticket->getAttributes();
  130. $this->assertEquals("JWT", $attributes["envelope"]["typ"]);
  131. $this->assertEquals("client_id", $attributes["payload"]["aud"]);
  132. }
  133. // Checks that the id token fails to verify with the expected message.
  134. private function checkIdTokenFailure($id_token, $msg, $issuer = null)
  135. {
  136. $certs = $this->getSignonCerts();
  137. $oauth2 = new Google_Auth_OAuth2($this->getClient());
  138. try {
  139. $oauth2->verifySignedJwtWithCerts($id_token, $certs, "client_id", $issuer);
  140. $this->fail("Should have thrown for $id_token");
  141. } catch (Google_Auth_Exception $e) {
  142. $this->assertContains($msg, $e->getMessage());
  143. }
  144. }
  145. public function testVerifySignedJwtWithMultipleIssuers()
  146. {
  147. $id_token = $this->makeSignedJwt(
  148. array(
  149. "iss" => "system.gserviceaccount.com",
  150. "aud" => "client_id",
  151. "sub" => self::USER_ID,
  152. "iat" => time(),
  153. "exp" => time() + 3600
  154. )
  155. );
  156. $certs = $this->getSignonCerts();
  157. $oauth2 = new Google_Auth_OAuth2($this->getClient());
  158. $ticket = $oauth2->verifySignedJwtWithCerts(
  159. $id_token,
  160. $certs,
  161. "client_id",
  162. array('system.gserviceaccount.com', 'https://system.gserviceaccount.com')
  163. );
  164. $this->assertEquals(self::USER_ID, $ticket->getUserId());
  165. // Check that payload and envelope got filled in.
  166. $attributes = $ticket->getAttributes();
  167. $this->assertEquals("JWT", $attributes["envelope"]["typ"]);
  168. $this->assertEquals("client_id", $attributes["payload"]["aud"]);
  169. }
  170. public function testVerifySignedJwtWithBadIssuer()
  171. {
  172. $id_token = $this->makeSignedJwt(
  173. array(
  174. "iss" => "fake.gserviceaccount.com",
  175. "aud" => "client_id",
  176. "sub" => self::USER_ID,
  177. "iat" => time(),
  178. "exp" => time() + 3600
  179. )
  180. );
  181. $issuers = array('system.gserviceaccount.com', 'https://system.gserviceaccount.com');
  182. $this->checkIdTokenFailure($id_token, 'Invalid issuer', $issuers[0]);
  183. $this->checkIdTokenFailure($id_token, 'Invalid issuer', $issuers);
  184. }
  185. public function testVerifySignedJwtWithBadJwt()
  186. {
  187. $this->checkIdTokenFailure("foo", "Wrong number of segments");
  188. $this->checkIdTokenFailure("foo.bar", "Wrong number of segments");
  189. $this->checkIdTokenFailure(
  190. "foo.bar.baz",
  191. "Can't parse token envelope: foo"
  192. );
  193. }
  194. public function testVerifySignedJwtWithBadSignature()
  195. {
  196. $id_token = $this->makeSignedJwt(
  197. array(
  198. "iss" => "federated-signon@system.gserviceaccount.com",
  199. "aud" => "client_id",
  200. "id" => self::USER_ID,
  201. "iat" => time(),
  202. "exp" => time() + 3600
  203. )
  204. );
  205. $id_token = $id_token . "a";
  206. $this->checkIdTokenFailure($id_token, "Invalid token signature");
  207. }
  208. public function testVerifySignedJwtWithNoIssueTime()
  209. {
  210. $id_token = $this->makeSignedJwt(
  211. array(
  212. "iss" => "federated-signon@system.gserviceaccount.com",
  213. "aud" => "client_id",
  214. "id" => self::USER_ID,
  215. "exp" => time() + 3600
  216. )
  217. );
  218. $this->checkIdTokenFailure($id_token, "No issue time");
  219. }
  220. public function testVerifySignedJwtWithNoExpirationTime()
  221. {
  222. $id_token = $this->makeSignedJwt(
  223. array(
  224. "iss" => "federated-signon@system.gserviceaccount.com",
  225. "aud" => "client_id",
  226. "id" => self::USER_ID,
  227. "iat" => time()
  228. )
  229. );
  230. $this->checkIdTokenFailure($id_token, "No expiration time");
  231. }
  232. public function testVerifySignedJwtWithTooEarly()
  233. {
  234. $id_token = $this->makeSignedJwt(
  235. array(
  236. "iss" => "federated-signon@system.gserviceaccount.com",
  237. "aud" => "client_id",
  238. "id" => self::USER_ID,
  239. "iat" => time() + 1800,
  240. "exp" => time() + 3600
  241. )
  242. );
  243. $this->checkIdTokenFailure($id_token, "Token used too early");
  244. }
  245. public function testVerifySignedJwtWithTooLate()
  246. {
  247. $id_token = $this->makeSignedJwt(
  248. array(
  249. "iss" => "federated-signon@system.gserviceaccount.com",
  250. "aud" => "client_id",
  251. "id" => self::USER_ID,
  252. "iat" => time() - 3600,
  253. "exp" => time() - 1800
  254. )
  255. );
  256. $this->checkIdTokenFailure($id_token, "Token used too late");
  257. }
  258. public function testVerifySignedJwtWithLifetimeTooLong()
  259. {
  260. $id_token = $this->makeSignedJwt(
  261. array(
  262. "iss" => "federated-signon@system.gserviceaccount.com",
  263. "aud" => "client_id",
  264. "id" => self::USER_ID,
  265. "iat" => time(),
  266. "exp" => time() + 3600 * 25
  267. )
  268. );
  269. $this->checkIdTokenFailure($id_token, "Expiration time too far in future");
  270. }
  271. public function testVerifySignedJwtWithBadAudience()
  272. {
  273. $id_token = $this->makeSignedJwt(
  274. array(
  275. "iss" => "federated-signon@system.gserviceaccount.com",
  276. "aud" => "wrong_client_id",
  277. "id" => self::USER_ID,
  278. "iat" => time(),
  279. "exp" => time() + 3600
  280. )
  281. );
  282. $this->checkIdTokenFailure($id_token, "Wrong recipient");
  283. }
  284. public function testNoAuth()
  285. {
  286. /** @var $noAuth Google_Auth_Simple */
  287. $noAuth = new Google_Auth_Simple($this->getClient());
  288. $oldAuth = $this->getClient()->getAuth();
  289. $this->getClient()->setAuth($noAuth);
  290. $this->getClient()->setDeveloperKey(null);
  291. $req = new Google_Http_Request("http://example.com");
  292. $resp = $noAuth->sign($req);
  293. $this->assertEquals("http://example.com", $resp->getUrl());
  294. $this->getClient()->setAuth($oldAuth);
  295. }
  296. public function testAssertionCredentials()
  297. {
  298. $assertion = new Google_Auth_AssertionCredentials(
  299. 'name',
  300. 'scope',
  301. file_get_contents(__DIR__.'/'.self::PRIVATE_KEY_FILE, true)
  302. );
  303. $token = explode(".", $assertion->generateAssertion());
  304. $this->assertEquals('{"typ":"JWT","alg":"RS256"}', base64_decode($token[0]));
  305. $jwt = json_decode(base64_decode($token[1]), true);
  306. $this->assertEquals('https://accounts.google.com/o/oauth2/token', $jwt['aud']);
  307. $this->assertEquals('scope', $jwt['scope']);
  308. $this->assertEquals('name', $jwt['iss']);
  309. $key = $assertion->getCacheKey();
  310. $this->assertTrue($key != false);
  311. $assertion = new Google_Auth_AssertionCredentials(
  312. 'name2',
  313. 'scope',
  314. file_get_contents(__DIR__.'/'.self::PRIVATE_KEY_FILE, true)
  315. );
  316. $this->assertNotEquals($key, $assertion->getCacheKey());
  317. }
  318. public function testVerifySignedJWT()
  319. {
  320. $assertion = new Google_Auth_AssertionCredentials(
  321. 'issuer',
  322. 'scope',
  323. file_get_contents(__DIR__.'/'.self::PRIVATE_KEY_FILE, true)
  324. );
  325. $client = $this->getClient();
  326. $this->assertInstanceOf(
  327. 'Google_Auth_LoginTicket',
  328. $client->verifySignedJwt(
  329. $assertion->generateAssertion(),
  330. __DIR__ . DIRECTORY_SEPARATOR . self::PUBLIC_KEY_FILE_JSON,
  331. 'https://accounts.google.com/o/oauth2/token',
  332. 'issuer'
  333. )
  334. );
  335. }
  336. }