Skip to content

Commit

Permalink
(dev/core#2258) CryptoToken - Change notation
Browse files Browse the repository at this point in the history
This is pre-merge change to the notation in the token. Two things:

* Use only one control character instead of multiple.
* Use URL-style key-value notation. It should be easier to skim and to tweak.
  • Loading branch information
totten committed Dec 19, 2020
1 parent 4228925 commit 5b394c8
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 18 deletions.
41 changes: 29 additions & 12 deletions Civi/Crypto/CryptoToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,25 @@
*
* - Plain text: Any string which does not begin with chr(2)
* - Encrypted text: A string in the format:
* TOKEN := DLM + VERSION + DLM + KEY_ID + DLM + CIPHERTEXT
* DLM := ASCII CHAR #2
* VERSION := String, 4-digit, alphanumeric (as in "CTK0")
* KEY_ID := String, alphanumeric and symbols "_-.,:;=+/\"
* TOKEN := DLM + FMT + QUERY
* DLM := ASCII char #2
* FMT := String, 4-digit, alphanumeric (as in "CTK?")
* QUERY := String, URL-encoded key-value pairs,
* "k", the key ID (alphanumeric and symbols "_-.,:;=+/\")
* "t", the text (base64-encoded ciphertext)
*
* @package Civi\Crypto
*/
class CryptoToken {

const VERSION_1 = 'CTK0';
/**
* Format identification code
*/
const FMT_QUERY = 'CTK?';

/**
* @var string
*/
protected $delim;

/**
Expand Down Expand Up @@ -90,7 +98,11 @@ public function encrypt($plainText, $keyIdOrTag) {
/** @var \Civi\Crypto\CipherSuiteInterface $cipherSuite */
$cipherSuite = $registry->findSuite($key['suite']);
$cipherText = $cipherSuite->encrypt($plainText, $key);
return $this->delim . self::VERSION_1 . $this->delim . $key['id'] . $this->delim . base64_encode($cipherText);

return $this->delim . self::FMT_QUERY . \http_build_query([
'k' => $key['id'],
't' => \CRM_Utils_String::base64UrlEncode($cipherText),
]);
}

/**
Expand Down Expand Up @@ -118,16 +130,21 @@ public function decrypt($token, $keyIdOrTag = '*') {
/** @var CryptoRegistry $registry */
$registry = \Civi::service('crypto.registry');

$parts = explode($this->delim, $token, 4);
if (count($parts) !== 4 || $parts[1] !== self::VERSION_1) {
throw new CryptoException("Cannot decrypt token. Invalid format.");
$fmt = substr($token, 1, 4);
switch ($fmt) {
case self::FMT_QUERY:
parse_str(substr($token, 5), $tokenData);
$keyId = $tokenData['k'];
$cipherText = \CRM_Utils_String::base64UrlDecode($tokenData['t']);
break;

default:
throw new CryptoException("Cannot decrypt token. Invalid format.");
}
$keyId = $parts[2];
$cipherText = base64_decode($parts[3]);

$key = $registry->findKey($keyId);
if (!in_array('*', $keyIdOrTag) && !in_array($keyId, $keyIdOrTag) && empty(array_intersect($keyIdOrTag, $key['tags']))) {
throw new CryptoException("Cannot decrypt token. Unexpected key: $keyId");
throw new CryptoException("Cannot decrypt token. Unexpected key: {$keyId}");
}

/** @var \Civi\Crypto\CipherSuiteInterface $cipherSuite */
Expand Down
13 changes: 7 additions & 6 deletions tests/phpunit/Civi/Crypto/CryptoTokenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ public function testDecryptInvalid() {
$this->assertEquals('mess with me', $cryptoToken->decrypt($goodExample));

try {
$badExample = preg_replace(';CTK0;', 'ctk9', $goodExample);
$badExample = preg_replace(';CTK\?;', 'ctk9', $goodExample);
$this->assertTrue($badExample !== $goodExample);
$cryptoToken->decrypt($badExample);
$this->fail("Expected CryptoException");
}
Expand All @@ -64,11 +65,11 @@ public function getExampleTokens() {
return [
// [ 'Plain text', 'Encryption Key ID', 'expectTokenRegex', 'expectTokenLen', 'expectPlain' ]
['hello world. can you see me', 'plain', '/^hello world. can you see me/', 27, TRUE],
['hello world. i am secret.', 'UNIT-TEST', '/^.CTK0.asdf-key-1./', 81, FALSE],
['hello world. we b secret.', 'asdf-key-0', '/^.CTK0.asdf-key-0./', 81, FALSE],
['hello world. u ur secret.', 'asdf-key-1', '/^.CTK0.asdf-key-1./', 81, FALSE],
['hello world. he z secret.', 'asdf-key-2', '/^.CTK0.asdf-key-2./', 73, FALSE],
['hello world. whos secret.', 'asdf-key-3', '/^.CTK0.asdf-key-3./', 125, FALSE],
['hello world. i am secret.', 'UNIT-TEST', '/^.CTK\?k=asdf-key-1&/', 84, FALSE],
['hello world. we b secret.', 'asdf-key-0', '/^.CTK\?k=asdf-key-0&/', 84, FALSE],
['hello world. u ur secret.', 'asdf-key-1', '/^.CTK\?k=asdf-key-1&/', 84, FALSE],
['hello world. he z secret.', 'asdf-key-2', '/^.CTK\?k=asdf-key-2&/', 75, FALSE],
['hello world. whos secret.', 'asdf-key-3', '/^.CTK\?k=asdf-key-3&/', 127, FALSE],
];
}

Expand Down

0 comments on commit 5b394c8

Please sign in to comment.