| 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 |
|
| 21 |
class AuthTest extends BaseTest |
| 22 |
{ |
| 23 |
const PRIVATE_KEY_FILE = "testdata/cert.p12"; |
| 24 |
const PUBLIC_KEY_FILE_JSON = "testdata/cacert.json"; |
| 25 |
const PUBLIC_KEY_FILE = "testdata/cacert.pem"; |
| 26 |
const USER_ID = "102102479283111695822"; |
| 27 |
|
| 28 |
/** @var Google_Signer_P12 */ |
| 29 |
private $signer; |
| 30 |
|
| 31 |
/** @var string */ |
| 32 |
private $pem; |
| 33 |
|
| 34 |
/** @var Google_Verifier_Pem */ |
| 35 |
private $verifier; |
| 36 |
|
| 37 |
public function setUp() |
| 38 |
{ |
| 39 |
$this->signer = new Google_Signer_P12( |
| 40 |
file_get_contents(__DIR__.'/'.self::PRIVATE_KEY_FILE, true), |
| 41 |
"notasecret" |
| 42 |
); |
| 43 |
$this->pem = file_get_contents(__DIR__.'/'.self::PUBLIC_KEY_FILE, true); |
| 44 |
$this->verifier = new Google_Verifier_Pem($this->pem); |
| 45 |
} |
| 46 |
|
| 47 |
public function testDirectInject() |
| 48 |
{ |
| 49 |
$privateKeyString = <<<PK |
| 50 |
-----BEGIN RSA PRIVATE KEY----- |
| 51 |
MIICWwIBAAKBgQC8iqFTYTrSGxddW+Tsx6cdWbQxITdM2anRbMYcohnQpQuPG46B |
| 52 |
HO3WbUA8suC6PXqeIi4JkDrAYbI2+TN6w1FE/fh2H7WczuDVKtosBcfsoL2C5loU |
| 53 |
mOf+4jL1xx4EL6xy8wMntZhNgimVCO9LkWCix/Qh9mpqx2zbC3OV4QsSQQIDAQAB |
| 54 |
AoGASAosRCClifxB/DENko9iwisxV4haiemtIlEOjYg+luNJPGAKHjlAgyrxXX/3 |
| 55 |
sBGnlV53+r16RWHO54RmcCTLGwpC6zzVc6C4Or9KItdMDMnqBjmqiYDz3Na7tIPv |
| 56 |
vwzn8k8Uto26HZF8d1bTdoinxHrv7w1OVkDQWnHmWkQRjBUCQQDpNw8F1qiJJoYr |
| 57 |
tkkBmlObmSQRYD3mlEvRwu348e4dFb01oN2cfw/YNhh+Lt2TPHFz2GNn6VwJf1Yb |
| 58 |
qRKBqo/jAkEAzvY91ReYrkBm50pi2nqJc1Hcxm5CVP7MMnHbn8wExKrRG2rCDY9Y |
| 59 |
zOdsw7pP/x6mesdUy3tTrPYVbeWP6YPmiwJANx41Jbsa7/cz5KbbUE6qDe8+sACg |
| 60 |
AJvx42x/k8OR9DvMER2o4rDBDOeUGFZ5NbAmXCu7KrbjcrcuobDu18h44wJAQ2s5 |
| 61 |
x0HxjcoS+4Ni4nMKdZOUTNu8Jf3+vOwUNGD8qKhQiBLl9g7dSZqV9sipqJzudI6c |
| 62 |
k9Cv+GcNoggnMlWycwJAHMVgaBmNc+RVCMar/gN6i5sENjN9Itu7U1V4Qj/mG6+4 |
| 63 |
MHOXhXSKhtTe0Bqm/MssVvCmc8AraKwBMs0rkMadsA== |
| 64 |
-----END RSA PRIVATE KEY----- |
| 65 |
PK; |
| 66 |
$sign = new Google_Signer_P12($privateKeyString, null); |
| 67 |
} |
| 68 |
|
| 69 |
public function testCantOpenP12() |
| 70 |
{ |
| 71 |
try { |
| 72 |
new Google_Signer_P12( |
| 73 |
file_get_contents(__DIR__.'/'.self::PRIVATE_KEY_FILE, true), |
| 74 |
"badpassword" |
| 75 |
); |
| 76 |
$this->fail("Should have thrown"); |
| 77 |
} catch (Google_Auth_Exception $e) { |
| 78 |
$this->assertContains("mac verify failure", $e->getMessage()); |
| 79 |
} |
| 80 |
|
| 81 |
try { |
| 82 |
new Google_Signer_P12( |
| 83 |
file_get_contents(__DIR__.'/'.self::PRIVATE_KEY_FILE, true) . "foo", |
| 84 |
"badpassword" |
| 85 |
); |
| 86 |
$this->fail("Should have thrown"); |
| 87 |
} catch (Exception $e) { |
| 88 |
$this->assertContains("Unable to parse", $e->getMessage()); |
| 89 |
} |
| 90 |
} |
| 91 |
|
| 92 |
public function testVerifySignature() |
| 93 |
{ |
| 94 |
$binary_data = "\x00\x01\x02\x66\x6f\x6f"; |
| 95 |
$signature = $this->signer->sign($binary_data); |
| 96 |
$this->assertTrue($this->verifier->verify($binary_data, $signature)); |
| 97 |
|
| 98 |
$empty_string = ""; |
| 99 |
$signature = $this->signer->sign($empty_string); |
| 100 |
$this->assertTrue($this->verifier->verify($empty_string, $signature)); |
| 101 |
|
| 102 |
$text = "foobar"; |
| 103 |
$signature = $this->signer->sign($text); |
| 104 |
$this->assertTrue($this->verifier->verify($text, $signature)); |
| 105 |
|
| 106 |
$this->assertFalse($this->verifier->verify($empty_string, $signature)); |
| 107 |
} |
| 108 |
|
| 109 |
// Creates a signed JWT similar to the one created by google authentication. |
| 110 |
private function makeSignedJwt($payload) |
| 111 |
{ |
| 112 |
$header = array("typ" => "JWT", "alg" => "RS256"); |
| 113 |
$segments = array(); |
| 114 |
$segments[] = Google_Utils::urlSafeB64Encode(json_encode($header)); |
| 115 |
$segments[] = Google_Utils::urlSafeB64Encode(json_encode($payload)); |
| 116 |
$signing_input = implode(".", $segments); |
| 117 |
|
| 118 |
$signature = $this->signer->sign($signing_input); |
| 119 |
$segments[] = Google_Utils::urlSafeB64Encode($signature); |
| 120 |
|
| 121 |
return implode(".", $segments); |
| 122 |
} |
| 123 |
|
| 124 |
// Returns certificates similar to the ones used by google authentication. |
| 125 |
private function getSignonCerts() |
| 126 |
{ |
| 127 |
return array("keyid" => $this->pem); |
| 128 |
} |
| 129 |
|
| 130 |
public function testVerifySignedJwtWithCerts() |
| 131 |
{ |
| 132 |
$id_token = $this->makeSignedJwt( |
| 133 |
array( |
| 134 |
"iss" => "federated-signon@system.gserviceaccount.com", |
| 135 |
"aud" => "client_id", |
| 136 |
"sub" => self::USER_ID, |
| 137 |
"iat" => time(), |
| 138 |
"exp" => time() + 3600 |
| 139 |
) |
| 140 |
); |
| 141 |
$certs = $this->getSignonCerts(); |
| 142 |
$oauth2 = new Google_Auth_OAuth2($this->getClient()); |
| 143 |
$ticket = $oauth2->verifySignedJwtWithCerts($id_token, $certs, "client_id"); |
| 144 |
$this->assertEquals(self::USER_ID, $ticket->getUserId()); |
| 145 |
// Check that payload and envelope got filled in. |
| 146 |
$attributes = $ticket->getAttributes(); |
| 147 |
$this->assertEquals("JWT", $attributes["envelope"]["typ"]); |
| 148 |
$this->assertEquals("client_id", $attributes["payload"]["aud"]); |
| 149 |
} |
| 150 |
|
| 151 |
// Checks that the id token fails to verify with the expected message. |
| 152 |
private function checkIdTokenFailure($id_token, $msg, $issuer = null) |
| 153 |
{ |
| 154 |
$certs = $this->getSignonCerts(); |
| 155 |
$oauth2 = new Google_Auth_OAuth2($this->getClient()); |
| 156 |
try { |
| 157 |
$oauth2->verifySignedJwtWithCerts($id_token, $certs, "client_id", $issuer); |
| 158 |
$this->fail("Should have thrown for $id_token"); |
| 159 |
} catch (Google_Auth_Exception $e) { |
| 160 |
$this->assertContains($msg, $e->getMessage()); |
| 161 |
} |
| 162 |
} |
| 163 |
|
| 164 |
public function testVerifySignedJwtWithMultipleIssuers() |
| 165 |
{ |
| 166 |
$id_token = $this->makeSignedJwt( |
| 167 |
array( |
| 168 |
"iss" => "system.gserviceaccount.com", |
| 169 |
"aud" => "client_id", |
| 170 |
"sub" => self::USER_ID, |
| 171 |
"iat" => time(), |
| 172 |
"exp" => time() + 3600 |
| 173 |
) |
| 174 |
); |
| 175 |
$certs = $this->getSignonCerts(); |
| 176 |
$oauth2 = new Google_Auth_OAuth2($this->getClient()); |
| 177 |
$ticket = $oauth2->verifySignedJwtWithCerts( |
| 178 |
$id_token, |
| 179 |
$certs, |
| 180 |
"client_id", |
| 181 |
array('system.gserviceaccount.com', 'https://system.gserviceaccount.com') |
| 182 |
); |
| 183 |
$this->assertEquals(self::USER_ID, $ticket->getUserId()); |
| 184 |
// Check that payload and envelope got filled in. |
| 185 |
$attributes = $ticket->getAttributes(); |
| 186 |
$this->assertEquals("JWT", $attributes["envelope"]["typ"]); |
| 187 |
$this->assertEquals("client_id", $attributes["payload"]["aud"]); |
| 188 |
} |
| 189 |
|
| 190 |
public function testVerifySignedJwtWithBadIssuer() |
| 191 |
{ |
| 192 |
$id_token = $this->makeSignedJwt( |
| 193 |
array( |
| 194 |
"iss" => "fake.gserviceaccount.com", |
| 195 |
"aud" => "client_id", |
| 196 |
"sub" => self::USER_ID, |
| 197 |
"iat" => time(), |
| 198 |
"exp" => time() + 3600 |
| 199 |
) |
| 200 |
); |
| 201 |
|
| 202 |
$issuers = array('system.gserviceaccount.com', 'https://system.gserviceaccount.com'); |
| 203 |
$this->checkIdTokenFailure($id_token, 'Invalid issuer', $issuers[0]); |
| 204 |
$this->checkIdTokenFailure($id_token, 'Invalid issuer', $issuers); |
| 205 |
} |
| 206 |
|
| 207 |
public function testVerifySignedJwtWithBadJwt() |
| 208 |
{ |
| 209 |
$this->checkIdTokenFailure("foo", "Wrong number of segments"); |
| 210 |
$this->checkIdTokenFailure("foo.bar", "Wrong number of segments"); |
| 211 |
$this->checkIdTokenFailure( |
| 212 |
"foo.bar.baz", |
| 213 |
"Can't parse token envelope: foo" |
| 214 |
); |
| 215 |
} |
| 216 |
|
| 217 |
public function testVerifySignedJwtWithBadSignature() |
| 218 |
{ |
| 219 |
$id_token = $this->makeSignedJwt( |
| 220 |
array( |
| 221 |
"iss" => "federated-signon@system.gserviceaccount.com", |
| 222 |
"aud" => "client_id", |
| 223 |
"id" => self::USER_ID, |
| 224 |
"iat" => time(), |
| 225 |
"exp" => time() + 3600 |
| 226 |
) |
| 227 |
); |
| 228 |
$id_token = $id_token . "a"; |
| 229 |
$this->checkIdTokenFailure($id_token, "Invalid token signature"); |
| 230 |
} |
| 231 |
|
| 232 |
public function testVerifySignedJwtWithNoIssueTime() |
| 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 |
"exp" => time() + 3600 |
| 240 |
) |
| 241 |
); |
| 242 |
$this->checkIdTokenFailure($id_token, "No issue time"); |
| 243 |
} |
| 244 |
|
| 245 |
public function testVerifySignedJwtWithNoExpirationTime() |
| 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() |
| 253 |
) |
| 254 |
); |
| 255 |
$this->checkIdTokenFailure($id_token, "No expiration time"); |
| 256 |
} |
| 257 |
|
| 258 |
public function testVerifySignedJwtWithTooEarly() |
| 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() + 1800, |
| 266 |
"exp" => time() + 3600 |
| 267 |
) |
| 268 |
); |
| 269 |
$this->checkIdTokenFailure($id_token, "Token used too early"); |
| 270 |
} |
| 271 |
|
| 272 |
public function testVerifySignedJwtWithTooLate() |
| 273 |
{ |
| 274 |
$id_token = $this->makeSignedJwt( |
| 275 |
array( |
| 276 |
"iss" => "federated-signon@system.gserviceaccount.com", |
| 277 |
"aud" => "client_id", |
| 278 |
"id" => self::USER_ID, |
| 279 |
"iat" => time() - 3600, |
| 280 |
"exp" => time() - 1800 |
| 281 |
) |
| 282 |
); |
| 283 |
$this->checkIdTokenFailure($id_token, "Token used too late"); |
| 284 |
} |
| 285 |
|
| 286 |
public function testVerifySignedJwtWithLifetimeTooLong() |
| 287 |
{ |
| 288 |
$id_token = $this->makeSignedJwt( |
| 289 |
array( |
| 290 |
"iss" => "federated-signon@system.gserviceaccount.com", |
| 291 |
"aud" => "client_id", |
| 292 |
"id" => self::USER_ID, |
| 293 |
"iat" => time(), |
| 294 |
"exp" => time() + 3600 * 25 |
| 295 |
) |
| 296 |
); |
| 297 |
$this->checkIdTokenFailure($id_token, "Expiration time too far in future"); |
| 298 |
} |
| 299 |
|
| 300 |
public function testVerifySignedJwtWithBadAudience() |
| 301 |
{ |
| 302 |
$id_token = $this->makeSignedJwt( |
| 303 |
array( |
| 304 |
"iss" => "federated-signon@system.gserviceaccount.com", |
| 305 |
"aud" => "wrong_client_id", |
| 306 |
"id" => self::USER_ID, |
| 307 |
"iat" => time(), |
| 308 |
"exp" => time() + 3600 |
| 309 |
) |
| 310 |
); |
| 311 |
$this->checkIdTokenFailure($id_token, "Wrong recipient"); |
| 312 |
} |
| 313 |
|
| 314 |
public function testNoAuth() |
| 315 |
{ |
| 316 |
/** @var $noAuth Google_Auth_Simple */ |
| 317 |
$noAuth = new Google_Auth_Simple($this->getClient()); |
| 318 |
$oldAuth = $this->getClient()->getAuth(); |
| 319 |
$this->getClient()->setAuth($noAuth); |
| 320 |
$this->getClient()->setDeveloperKey(null); |
| 321 |
$req = new Google_Http_Request("http://example.com"); |
| 322 |
|
| 323 |
$resp = $noAuth->sign($req); |
| 324 |
$this->assertEquals("http://example.com", $resp->getUrl()); |
| 325 |
$this->getClient()->setAuth($oldAuth); |
| 326 |
} |
| 327 |
|
| 328 |
public function testAssertionCredentials() |
| 329 |
{ |
| 330 |
$assertion = new Google_Auth_AssertionCredentials( |
| 331 |
'name', |
| 332 |
'scope', |
| 333 |
file_get_contents(__DIR__.'/'.self::PRIVATE_KEY_FILE, true) |
| 334 |
); |
| 335 |
|
| 336 |
$token = explode(".", $assertion->generateAssertion()); |
| 337 |
$this->assertEquals('{"typ":"JWT","alg":"RS256"}', base64_decode($token[0])); |
| 338 |
|
| 339 |
$jwt = json_decode(base64_decode($token[1]), true); |
| 340 |
$this->assertEquals('https://accounts.google.com/o/oauth2/token', $jwt['aud']); |
| 341 |
$this->assertEquals('scope', $jwt['scope']); |
| 342 |
$this->assertEquals('name', $jwt['iss']); |
| 343 |
|
| 344 |
$key = $assertion->getCacheKey(); |
| 345 |
$this->assertTrue($key != false); |
| 346 |
$assertion = new Google_Auth_AssertionCredentials( |
| 347 |
'name2', |
| 348 |
'scope', |
| 349 |
file_get_contents(__DIR__.'/'.self::PRIVATE_KEY_FILE, true) |
| 350 |
); |
| 351 |
$this->assertNotEquals($key, $assertion->getCacheKey()); |
| 352 |
} |
| 353 |
|
| 354 |
public function testVerifySignedJWT() |
| 355 |
{ |
| 356 |
$assertion = new Google_Auth_AssertionCredentials( |
| 357 |
'issuer', |
| 358 |
'scope', |
| 359 |
file_get_contents(__DIR__.'/'.self::PRIVATE_KEY_FILE, true) |
| 360 |
); |
| 361 |
$client = $this->getClient(); |
| 362 |
|
| 363 |
$this->assertInstanceOf( |
| 364 |
'Google_Auth_LoginTicket', |
| 365 |
$client->verifySignedJwt( |
| 366 |
$assertion->generateAssertion(), |
| 367 |
__DIR__ . DIRECTORY_SEPARATOR . self::PUBLIC_KEY_FILE_JSON, |
| 368 |
'https://accounts.google.com/o/oauth2/token', |
| 369 |
'issuer' |
| 370 |
) |
| 371 |
); |
| 372 |
} |
| 373 |
} |
| 374 |
|