CWIS Developer Documentation
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 
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(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM));
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  # the mcrypt extension is deprecated, but unfortunately
122  # mcrypt_create_iv() is the only function available in both PHP
123  # 5.x and PHP 7.x that reliably provides randomness.
124  # see slides # 24-28 in "Using Encryption in PHP" at
125  # http://otscripts.com/wp-content/uploads/2016/10/UsingEncryption-v02.pdf
126 
127  $IV = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);
128 
129  # encrypt and base64 our payload
130  $Payload = base64_encode(openssl_encrypt(
131  json_encode($Env), "aes-256-cbc", $EncKey, OPENSSL_RAW_DATA, $IV));
132 
133  # base64 encode our IV
134  $IV = base64_encode($IV);
135 
136  # construct data we will POST
137  $PostData = array(
138  "IV" => $IV,
139  "Payload" => $Payload,
140  "MAC" => base64_encode(hash_hmac("sha256", $IV.":".$Payload, $MacKey, TRUE)),
141  );
142 
143  return $PostData;
144  }
145 
155  public function DecodeEncryptedMessage($PostData)
156  {
157  # verify that the provided POST data has the correct elements
158  if (!isset($PostData["MAC"]) || !isset($PostData["Payload"]) ||
159  !isset($PostData["IV"]))
160  {
161  return array(
162  "Status" => "Error",
163  "Message" => "PostData lacks required elements.");
164  }
165 
166  # generate full key from provided password
167  $FullKey = hash("sha512", $this->APIPassword, TRUE);
168 
169  # split into encryption and MAC keys
170  $EncKey = mb_substr($FullKey, 0, 32, '8bit');
171  $MacKey = mb_substr($FullKey, 32, 32, '8bit');
172 
173  # compute MAC
174  $MAC = hash_hmac(
175  "sha256", $PostData["IV"].":".$PostData["Payload"], $MacKey, TRUE);
176 
177  # check MAC, bail if it was not valid
178  if (!hash_equals($MAC, base64_decode($PostData["MAC"])))
179  {
180  return array(
181  "Status" => "Error",
182  "Message" => "HMAC validation failure -- message is corrupted.");
183  }
184 
185  # strip base64 encoding from payload and IV
186  $Payload = base64_decode($PostData["Payload"]);
187  $IV = base64_decode($PostData["IV"]);
188 
189  # decrypt the payload to get the envelope
190  $Env = openssl_decrypt(
191  $Payload, "aes-256-cbc", $EncKey, OPENSSL_RAW_DATA, $IV);
192 
193  # attempt to unserialize the envelope, bailing on failure
194  $Env = json_decode($Env, TRUE);
195  if ($Env === NULL)
196  {
197  return array(
198  "Status" => "Error",
199  "Message" => "Could not decode message envelope.");
200  }
201 
202  # check that the envelope contains all the required headers
203  if (!isset($Env["Version"]) || !isset($Env["Timestamp"]) ||
204  !isset($Env["Cookie"]) || !isset($Env["Data"]) )
205  {
206  return array(
207  "Status" => "Error",
208  "Message" => "Payload did not include all required parameters.");
209  }
210 
211  # check that this is an envelope in a version we understand
212  if ($Env["Version"] != "3")
213  {
214  return array(
215  "Status" => "Error",
216  "Message" => "Message was not version 3.");
217  }
218 
219  # check that this envelope isn't too old
220  if (time() - $Env["Timestamp"] > 300)
221  {
222  return array(
223  "Status" => "Error",
224  "Message" => "Message is more than 5 minutes old.");
225  }
226 
227  # check if this is a duplicate message
228  if (call_user_func($this->CheckForDuplicateFn,
229  $Env["Timestamp"], $Env["Cookie"]))
230  {
231  return array(
232  "Status" => "Error",
233  "Message" => "This is a duplicate message");
234  }
235 
236  call_user_func($this->RegisterMessageFn,
237  $Env["Timestamp"], $Env["Cookie"]);
238 
239  return array(
240  "Status" => "OK",
241  "Data" => $Env["Data"]);
242  }
243 }
EncodeEncryptedMessage($Data)
Construct an encrypted message packet from provided data.
DoRestCommand($Params)
Run a REST API command against a remote site.
__construct($APIUrl, $APIPassword, $CheckForDuplicateFn, $RegisterMessageFn)
Constructor.
DecodeEncryptedMessage($PostData)
Decrypt an encrypted message packet.
This class provides a general-purpose library for encrypted REST calls and responses.