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 
11 {
22  public function __construct($APIUrl, $APIPassword,
23  $CheckForDuplicateFn, $RegisterMessageFn)
24  {
25  $this->APIUrl = $APIUrl;
26  $this->APIPassword = $APIPassword;
27  $this->CheckForDuplicateFn = $CheckForDuplicateFn;
28  $this->RegisterMessageFn = $RegisterMessageFn;
29  }
30 
37  public function DoRestCommand($Params)
38  {
39  # build an encrypted message
40  $PostData = $this->EncodeEncryptedMessage($Params);
41 
42  # set up curl to do the post
43  $Context = curl_init();
44 
45  # enable cookie handling
46  curl_setopt($Context, CURLOPT_COOKIEFILE, '');
47 
48  # use our configured endpoint
49  curl_setopt($Context, CURLOPT_URL, $this->APIUrl);
50 
51  # get results back as a string
52  curl_setopt($Context, CURLOPT_RETURNTRANSFER, TRUE);
53 
54  # send data in a POST
55  curl_setopt($Context, CURLOPT_POST, TRUE);
56 
57  # load the POST data
58  curl_setopt($Context, CURLOPT_POSTFIELDS, http_build_query($PostData));
59 
60  # fetch the data
61  $Data = curl_exec($Context);
62 
63  # attempt to parse the reply into an encrypted envelope
64  $Result = json_decode($Data, TRUE);
65  if ($Result === NULL)
66  {
67  return array(
68  "Status" => "Error",
69  "Message" => "Could not parse PostData.",
70  "Data" => $Data,
71  );
72  }
73 
74  # attempt to decode the encrypted envelope
75  $Result = $this->DecodeEncryptedMessage($Result);
76 
77  # if we decoded the envelope, return the message contents
78  if ($Result["Status"] == "OK")
79  {
80  $Result = $Result["Data"];
81  }
82 
83  return $Result;
84  }
85 
91  public function EncodeEncryptedMessage($Data)
92  {
93  # create an envelope for our message, put the provided data inside
94  $Env = array();
95  $Env["Version"] = "3";
96  $Env["Timestamp"] = time();
97  $Env["Cookie"] = base64_encode(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM));
98  $Env["Data"] = $Data;
99 
100  # generate full key from provided password
101  $FullKey = hash("sha512", $this->APIPassword, TRUE);
102 
103  # split into encryption and MAC keys
104  $EncKey = mb_substr($FullKey, 0, 32, '8bit');
105  $MacKey = mb_substr($FullKey, 32, 32, '8bit');
106 
107  # generate a random IV (initialization vector), required by
108  # AES (and for most ciphers) to provide some randomness in the
109  # data and prevent identical messages from having identical
110  # encrypted content
111 
112  # the mcrypt extension is deprecated, but unfortunately
113  # mcrypt_create_iv() is the only function available in both PHP
114  # 5.x and PHP 7.x that reliably provides randomness.
115  # see slides # 24-28 in "Using Encryption in PHP" at
116  # http://otscripts.com/wp-content/uploads/2016/10/UsingEncryption-v02.pdf
117 
118  $IV = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);
119 
120  # encrypt and base64 our payload
121  $Payload = base64_encode(openssl_encrypt(
122  json_encode($Env), "aes-256-cbc", $EncKey, OPENSSL_RAW_DATA, $IV));
123 
124  # base64 encode our IV
125  $IV = base64_encode($IV);
126 
127  # construct data we will POST
128  $PostData = array(
129  "IV" => $IV,
130  "Payload" => $Payload,
131  "MAC" => base64_encode(hash_hmac("sha256", $IV.":".$Payload, $MacKey, TRUE)),
132  );
133 
134  return $PostData;
135  }
136 
146  public function DecodeEncryptedMessage($PostData)
147  {
148  # verify that the provided POST data has the correct elements
149  if (!isset($PostData["MAC"]) || !isset($PostData["Payload"]) ||
150  !isset($PostData["IV"]))
151  {
152  return array(
153  "Status" => "Error",
154  "Message" => "PostData lacks required elements.");
155  }
156 
157  # generate full key from provided password
158  $FullKey = hash("sha512", $this->APIPassword, TRUE);
159 
160  # split into encryption and MAC keys
161  $EncKey = mb_substr($FullKey, 0, 32, '8bit');
162  $MacKey = mb_substr($FullKey, 32, 32, '8bit');
163 
164  # compute MAC
165  $MAC = hash_hmac(
166  "sha256", $PostData["IV"].":".$PostData["Payload"], $MacKey, TRUE);
167 
168  # check MAC, bail if it was not valid
169  if (!hash_equals($MAC, base64_decode($PostData["MAC"])))
170  {
171  return array(
172  "Status" => "Error",
173  "Message" => "HMAC validation failure -- message is corrupted.");
174  }
175 
176  # strip base64 encoding from payload and IV
177  $Payload = base64_decode($PostData["Payload"]);
178  $IV = base64_decode($PostData["IV"]);
179 
180  # decrypt the payload to get the envelope
181  $Env = openssl_decrypt(
182  $Payload, "aes-256-cbc", $EncKey, OPENSSL_RAW_DATA, $IV);
183 
184  # attempt to unserialize the envelope, bailing on failure
185  $Env = json_decode($Env, TRUE);
186  if ($Env === NULL)
187  {
188  return array(
189  "Status" => "Error",
190  "Message" => "Could not decode message envelope.");
191  }
192 
193  # check that the envelope contains all the required headers
194  if (!isset($Env["Version"]) || !isset($Env["Timestamp"]) ||
195  !isset($Env["Cookie"]) || !isset($Env["Data"]) )
196  {
197  return array(
198  "Status" => "Error",
199  "Message" => "Payload did not include all required parameters.");
200  }
201 
202  # check that this is an envelope in a version we understand
203  if ($Env["Version"] != "3")
204  {
205  return array(
206  "Status" => "Error",
207  "Message" => "Message was not version 3.");
208  }
209 
210  # check that this envelope isn't too old
211  if (time() - $Env["Timestamp"] > 300)
212  {
213  return array(
214  "Status" => "Error",
215  "Message" => "Message is more than 5 minutes old.");
216  }
217 
218  # check if this is a duplicate message
219  if (call_user_func($this->CheckForDuplicateFn,
220  $Env["Timestamp"], $Env["Cookie"]))
221  {
222  return array(
223  "Status" => "Error",
224  "Message" => "This is a duplicate message");
225  }
226 
227  call_user_func($this->RegisterMessageFn,
228  $Env["Timestamp"], $Env["Cookie"]);
229 
230  return array(
231  "Status" => "OK",
232  "Data" => $Env["Data"]);
233  }
234 }
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.