CWIS Developer Documentation

- Home
- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

RestAPIHelper.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: RestAPIHelper
4 #
5 # Part of the Collection Workflow Integration System (CWIS)
6 # Copyright 2017 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu/cwis/
8 #
9
19 class RestAPIHelper
20 {
31 public function __construct($APIUrl, $APIPassword,
32 $CheckForDuplicateFn, $RegisterMessageFn)
33 {
34 $this->APIUrl = $APIUrl;
35 $this->APIPassword = $APIPassword;
36 $this->CheckForDuplicateFn = $CheckForDuplicateFn;
37 $this->RegisterMessageFn = $RegisterMessageFn;
38 }
39
46 public function DoRestCommand($Params)
47 {
48 # build an encrypted message
49 $PostData = $this->EncodeEncryptedMessage($Params);
50
51 # set up curl to do the post
52 $Context = curl_init();
53
54 # enable cookie handling
55 curl_setopt($Context, CURLOPT_COOKIEFILE, '');
56
57 # use our configured endpoint
58 curl_setopt($Context, CURLOPT_URL, $this->APIUrl);
59
60 # get results back as a string
61 curl_setopt($Context, CURLOPT_RETURNTRANSFER, TRUE);
62
63 # send data in a POST
64 curl_setopt($Context, CURLOPT_POST, TRUE);
65
66 # load the POST data
67 curl_setopt($Context, CURLOPT_POSTFIELDS, http_build_query($PostData));
68
69 # fetch the data
70 $Data = curl_exec($Context);
71
72 # attempt to parse the reply into an encrypted envelope
73 $Result = json_decode($Data, TRUE);
74 if ($Result === NULL)
75 {
76 return array(
77 "Status" => "Error",
78 "Message" => "Could not parse PostData.",
79 "Data" => $Data,
80 );
81 }
82
83 # attempt to decode the encrypted envelope
84 $Result = $this->DecodeEncryptedMessage($Result);
85
86 # if we decoded the envelope, return the message contents
87 if ($Result["Status"] == "OK")
88 {
89 $Result = $Result["Data"];
90 }
91
92 return $Result;
93 }
94
100 public function EncodeEncryptedMessage($Data)
101 {
102 # create an envelope for our message, put the provided data inside
103 $Env = array();
104 $Env["Version"] = "3";
105 $Env["Timestamp"] = time();
106 $Env["Cookie"] = base64_encode(random_bytes(16));
107 $Env["Data"] = $Data;
108
109 # generate full key from provided password
110 $FullKey = hash("sha512", $this->APIPassword, TRUE);
111
112 # split into encryption and MAC keys
113 $EncKey = mb_substr($FullKey, 0, 32, '8bit');
114 $MacKey = mb_substr($FullKey, 32, 32, '8bit');
115
116 # generate a random IV (initialization vector), required by
117 # AES (and for most ciphers) to provide some randomness in the
118 # data and prevent identical messages from having identical
119 # encrypted content
120
121 $IV = random_bytes(16);
122
123 # encrypt and base64 our payload
124 $Payload = base64_encode(openssl_encrypt(
125 json_encode($Env), "aes-256-cbc", $EncKey, OPENSSL_RAW_DATA, $IV));
126
127 # base64 encode our IV
128 $IV = base64_encode($IV);
129
130 # construct data we will POST
131 $PostData = array(
132 "IV" => $IV,
133 "Payload" => $Payload,
134 "MAC" => base64_encode(hash_hmac("sha256", $IV.":".$Payload, $MacKey, TRUE)),
135 );
136
137 return $PostData;
138 }
139
149 public function DecodeEncryptedMessage($PostData)
150 {
151 # verify that the provided POST data has the correct elements
152 if (!isset($PostData["MAC"]) || !isset($PostData["Payload"]) ||
153 !isset($PostData["IV"]))
154 {
155 return array(
156 "Status" => "Error",
157 "Message" => "PostData lacks required elements.");
158 }
159
160 # generate full key from provided password
161 $FullKey = hash("sha512", $this->APIPassword, TRUE);
162
163 # split into encryption and MAC keys
164 $EncKey = mb_substr($FullKey, 0, 32, '8bit');
165 $MacKey = mb_substr($FullKey, 32, 32, '8bit');
166
167 # compute MAC
168 $MAC = hash_hmac(
169 "sha256", $PostData["IV"].":".$PostData["Payload"], $MacKey, TRUE);
170
171 # check MAC, bail if it was not valid
172 if (!hash_equals($MAC, base64_decode($PostData["MAC"])))
173 {
174 return array(
175 "Status" => "Error",
176 "Message" => "HMAC validation failure -- message is corrupted.");
177 }
178
179 # strip base64 encoding from payload and IV
180 $Payload = base64_decode($PostData["Payload"]);
181 $IV = base64_decode($PostData["IV"]);
182
183 # decrypt the payload to get the envelope
184 $Env = openssl_decrypt(
185 $Payload, "aes-256-cbc", $EncKey, OPENSSL_RAW_DATA, $IV);
186
187 # attempt to unserialize the envelope, bailing on failure
188 $Env = json_decode($Env, TRUE);
189 if ($Env === NULL)
190 {
191 return array(
192 "Status" => "Error",
193 "Message" => "Could not decode message envelope.");
194 }
195
196 # check that the envelope contains all the required headers
197 if (!isset($Env["Version"]) || !isset($Env["Timestamp"]) ||
198 !isset($Env["Cookie"]) || !isset($Env["Data"]) )
199 {
200 return array(
201 "Status" => "Error",
202 "Message" => "Payload did not include all required parameters.");
203 }
204
205 # check that this is an envelope in a version we understand
206 if ($Env["Version"] != "3")
207 {
208 return array(
209 "Status" => "Error",
210 "Message" => "Message was not version 3.");
211 }
212
213 # check that this envelope isn't too old
214 if (time() - $Env["Timestamp"] > 300)
215 {
216 return array(
217 "Status" => "Error",
218 "Message" => "Message is more than 5 minutes old.");
219 }
220
221 # check if this is a duplicate message
222 if (call_user_func($this->CheckForDuplicateFn,
223 $Env["Timestamp"], $Env["Cookie"]))
224 {
225 return array(
226 "Status" => "Error",
227 "Message" => "This is a duplicate message");
228 }
229
230 call_user_func($this->RegisterMessageFn,
231 $Env["Timestamp"], $Env["Cookie"]);
232
233 return array(
234 "Status" => "OK",
235 "Data" => $Env["Data"]);
236 }
237
238 private $APIUrl;
239 private $APIPassword;
240 private $CheckForDuplicateFn;
241 private $RegisterMessageFn;
242 }
RestAPIHelper\EncodeEncryptedMessage
EncodeEncryptedMessage($Data)
Construct an encrypted message packet from provided data.
Definition: RestAPIHelper.php:100
RestAPIHelper\DoRestCommand
DoRestCommand($Params)
Run a REST API command against a remote site.
Definition: RestAPIHelper.php:46
RestAPIHelper\__construct
__construct($APIUrl, $APIPassword, $CheckForDuplicateFn, $RegisterMessageFn)
Constructor.
Definition: RestAPIHelper.php:31
PHP
PHP
Definition: RSSClient--Test.php:12
RestAPIHelper\DecodeEncryptedMessage
DecodeEncryptedMessage($PostData)
Decrypt an encrypted message packet.
Definition: RestAPIHelper.php:149
RestAPIHelper
This class provides a general-purpose library for encrypted REST calls and responses.
Definition: RestAPIHelper.php:19