commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-old / civicrm / vendor / civicrm / civicrm-cxn-rpc / src / AesHelper.php
1 <?php
2
3 /*
4 * This file is part of the civicrm-cxn-rpc package.
5 *
6 * Copyright (c) CiviCRM LLC <info@civicrm.org>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this package.
10 */
11
12 namespace Civi\Cxn\Rpc;
13
14 use Civi\Cxn\Rpc\Exception\InvalidMessageException;
15
16 class AesHelper {
17 /**
18 * @return string
19 * A secret, expressed in a series of printable ASCII characters.
20 */
21 public static function createSecret() {
22 return base64_encode(crypt_random_string(Constants::AES_BYTES));
23 }
24
25 /**
26 * @param $secret
27 * A secret, expressed in a series of printable ASCII characters.
28 * @return array
29 * - enc: string, raw encryption key
30 * - auth: string, raw authentication key
31 */
32 public static function deriveAesKeys($secret) {
33 $rawSecret = base64_decode($secret);
34 if (Constants::AES_BYTES != strlen($rawSecret)) {
35 throw new InvalidMessageException("Failed to derive keys from secret.");
36 }
37
38 $result = array(
39 'enc' => BinHex::hex2bin(hash_hmac('sha256', 'dearbrutus', $rawSecret)),
40 'auth' => BinHex::hex2bin(hash_hmac('sha256', 'thefaultisinourselves', $rawSecret)),
41 );
42 if (Constants::AES_BYTES != strlen($result['enc']) || Constants::AES_BYTES != strlen($result['auth'])) {
43 throw new InvalidMessageException("Failed to derive keys from secret.");
44 }
45 return $result;
46 }
47
48 /**
49 * Encrypt $plaintext with $secret, then date and sign the message.
50 *
51 * @param string $secret
52 * @param string $plaintext
53 * @return array
54 * Array(string $body, string $signature).
55 * Note that $body begins with an unencrypted envelope (ttl, iv).
56 * @throws InvalidMessageException
57 */
58 public static function encryptThenSign($secret, $plaintext) {
59 $iv = crypt_random_string(Constants::AES_BYTES);
60
61 $keys = AesHelper::deriveAesKeys($secret);
62
63 $cipher = new \Crypt_AES(CRYPT_AES_MODE_CBC);
64 $cipher->setKeyLength(Constants::AES_BYTES);
65 $cipher->setKey($keys['enc']);
66 $cipher->setIV($iv);
67
68 // JSON string; this will be signed but not encrypted
69 $jsonEnvelope = json_encode(array(
70 'ttl' => Time::getTime() + Constants::REQUEST_TTL,
71 'iv' => BinHex::bin2hex($iv),
72 ));
73 // JSON string; this will be signed and encrypted
74 $jsonEncrypted = $cipher->encrypt($plaintext);
75 $body = $jsonEnvelope . Constants::PROTOCOL_DELIM . $jsonEncrypted;
76 $signature = hash_hmac('sha256', $body, $keys['auth']);
77 return array($body, $signature);
78 }
79
80 /**
81 * Validate the signature and date of the message, then
82 * decrypt it.
83 *
84 * @param string $secret
85 * @param string $body
86 * @param string $signature
87 * @return string
88 * Plain text.
89 * @throws InvalidMessageException
90 */
91 public static function authenticateThenDecrypt($secret, $body, $signature) {
92 $keys = self::deriveAesKeys($secret);
93
94 $localHmac = hash_hmac('sha256', $body, $keys['auth']);
95 if (!self::hash_compare($signature, $localHmac)) {
96 throw new InvalidMessageException("Incorrect hash");
97 }
98
99 list ($jsonEnvelope, $jsonEncrypted) = explode(Constants::PROTOCOL_DELIM, $body, 2);
100 if (strlen($jsonEnvelope) > Constants::MAX_ENVELOPE_BYTES) {
101 throw new InvalidMessageException("Oversized envelope");
102 }
103
104 $envelope = json_decode($jsonEnvelope, TRUE);
105 if (!$envelope) {
106 throw new InvalidMessageException("Malformed envelope");
107 }
108
109 if (!is_numeric($envelope['ttl']) || Time::getTime() > $envelope['ttl']) {
110 throw new InvalidMessageException("Invalid TTL");
111 }
112
113 if (!is_string($envelope['iv']) || strlen($envelope['iv']) !== Constants::AES_BYTES * 2 || !preg_match('/^[a-f0-9]+$/', $envelope['iv'])) {
114 // AES_BYTES (32) ==> bin2hex ==> 2 hex digits (4-bit) per byte (8-bit)
115 throw new InvalidMessageException("Malformed initialization vector");
116 }
117
118 $jsonPlaintext = UserError::adapt('Civi\Cxn\Rpc\Exception\InvalidMessageException', function () use ($jsonEncrypted, $envelope, $keys) {
119 $cipher = new \Crypt_AES(CRYPT_AES_MODE_CBC);
120 $cipher->setKeyLength(Constants::AES_BYTES);
121 $cipher->setKey($keys['enc']);
122 $cipher->setIV(BinHex::hex2bin($envelope['iv']));
123 return $cipher->decrypt($jsonEncrypted);
124 });
125 return $jsonPlaintext;
126 }
127
128 /**
129 * Comparison function which resists timing attacks.
130 *
131 * @param string $a
132 * @param string $b
133 * @return bool
134 */
135 private static function hash_compare($a, $b) {
136 if (!is_string($a) || !is_string($b)) {
137 return FALSE;
138 }
139
140 $len = strlen($a);
141 if ($len !== strlen($b)) {
142 return FALSE;
143 }
144
145 $status = 0;
146 for ($i = 0; $i < $len; $i++) {
147 $status |= ord($a[$i]) ^ ord($b[$i]);
148 }
149 return $status === 0;
150 }
151
152 }