namshi / jose

JSON Object Signing and Encryption library for PHP.
MIT License
1.8k stars 133 forks source link

Signature verification does not work when the token was encoded with whitespace #109

Closed jshayes closed 7 years ago

jshayes commented 8 years ago

In RFC 7519 Section 7.1 it mentions

Create a JWT Claims Set containing the desired claims. Note that whitespace is explicitly allowed in the representation and no canonicalization need be performed before encoding.

Additionally, in RFC 7515 Section 3 it mentions

JWS represents digitally signed or MACed content using JSON data structures and base64url encoding. These JSON data structures MAY contain whitespace and/or line breaks before or after any JSON values or structural characters, in accordance with Section 2 of RFC 7159 [RFC7159].

However, when generating a token with whitespace or line breaks in the JSON, the token does not pass the verification. For example, Example 1 below fails verification, whereas Example 2 passes verification.

Example 1
$header = '{
    "alg": "RS256"
}';

$payload = '{
    "a": "b"
}';

$encoder = new Base64UrlSafeEncoder();
$token = sprintf('%s.%s', $encoder->encode($header), $encoder->encode($payload));
$signer = new RS256();

$privateKey = openssl_pkey_get_private('path/to/private/key');
$signature = $encoder->encode($signer->sign($token, $privateKey));
$jwsToken = sprintf('%s.%s', $token, $signature);

$jws = JWS::load($jwsToken);
$publicKey = openssl_pkey_get_public('path/to/public/key');

$jws->verify($publicKey); // Returns false
Example 2
$header = '{"alg":"RS256"}';
$payload = '{"a":"b"}';

$encoder = new Base64UrlSafeEncoder();
$token = sprintf('%s.%s', $encoder->encode($header), $encoder->encode($payload));
$signer = new RS256();

$privateKey = openssl_pkey_get_private('path/to/private/key');
$signature = $encoder->encode($signer->sign($token, $privateKey));
$jwsToken = sprintf('%s.%s', $token, $signature);

$jws = JWS::load($jwsToken);
$publicKey = openssl_pkey_get_public('path/to/public/key');

$jws->verify($publicKey); // Returns true

It looks like the verify method takes the decoded header and payload arrays, JSON encodes them, then base64 encodes them. It then uses these values to generate the input that will be checked against the signature. However, since it decodes the token, then re-encodes it, the formatting is lost and the resulting signature is different.

Maybe it should take the original header and payload and use that to check against the signature, so that any formatting changes don't impact the verification step.

odino commented 8 years ago

hey @jshayes thanks for the catch! Would you be able to send a PR for this fix?