Skip to content

Commit 206972e

Browse files
committed
feat: update on samsung api enc algorithm
from AES/CBC to AES/GCM Change-Id: I1159a0ff75f2734308e0de2954c84e54a7f7a7ce
1 parent b6ca75d commit 206972e

10 files changed

+293
-28
lines changed

app/Services/Apis/Samsung/AbstractPayload.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ abstract class AbstractPayload
2929
function __construct(array $params)
3030
{
3131
if(!isset($params[PayloadParamNames::Forum]))
32-
3332
throw new \InvalidArgumentException("missing forum param");
3433

3534
if(!isset($params[PayloadParamNames::Region]))

app/Services/Apis/Samsung/DecryptedListResponse.php

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
**/
1414

1515
use App\Services\Apis\ExternalRegistrationFeeds\IExternalRegistrationFeedResponse;
16-
use App\Utils\AES;
16+
use App\Utils\AES256GCM;
17+
use Illuminate\Support\Facades\Log;
1718

1819
/**
1920
* Class DecryptedListResponse
@@ -35,20 +36,36 @@ public function __construct(string $key, string $content, array $params = []){
3536

3637
parent::__construct($params);
3738

39+
if(empty($content)){
40+
Log::warning("DecryptedListResponse::constructor empty content.");
41+
throw new EmptyResponse("response not found.");
42+
}
43+
3844
$this->position = 0;
3945

4046
$response = json_decode($content, true);
4147
if(is_array($response) && !count($response))
42-
throw new EmptyResponse("response not found");
48+
throw new EmptyResponse("response not found.");
49+
50+
Log::debug(sprintf("DecryptedListResponse::constructor response %s.", json_encode($response)));
4351

4452
if(!isset($response['data']))
45-
throw new InvalidResponse(sprintf("missing data field on response %s", $content));
53+
throw new InvalidResponse(sprintf("missing data field on response %s.", $content));
4654

47-
$dec = AES::decrypt($key, $response['data']);
48-
if($dec->hasError())
55+
if(empty($response['data']))
56+
throw new EmptyResponse("response not found.");
57+
58+
$dec = AES256GCM::decrypt($key, $response['data']);
59+
if($dec->hasError()) {
60+
Log::warning(sprintf("DecryptedListResponse::constructor error %s.", $dec->getErrorMessage()));
4961
throw new InvalidResponse($dec->getErrorMessage());
62+
}
63+
64+
$data = $dec->getData();
65+
66+
Log::debug(sprintf("DecryptedListResponse::constructor data %s.", $data));
5067

51-
$list = json_decode($dec->getData(), true);
68+
$list = json_decode($data, true);
5269
if(!is_array($list))
5370
throw new InvalidResponse(sprintf("invalid data field on response %s", $content));
5471

app/Services/Apis/Samsung/DecryptedSingleResponse.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
* See the License for the specific language governing permissions and
1212
* limitations under the License.
1313
**/
14-
15-
use App\Utils\AES;
14+
use App\Utils\AES256GCM;
1615

1716
/**
1817
* Class DecryptedResponse
@@ -37,7 +36,7 @@ public function __construct(string $key, string $content, array $params){
3736
if(!isset($response['data']))
3837
throw new InvalidResponse(sprintf("missing data field on response %s", $content));
3938

40-
$dec = AES::decrypt($key, $response['data']);
39+
$dec = AES256GCM::decrypt($key, $response['data']);
4140
if($dec->hasError())
4241
throw new InvalidResponse($dec->getErrorMessage());
4342

app/Services/Apis/Samsung/EncryptedPayload.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
* limitations under the License.
1313
**/
1414

15-
use App\Utils\AES;
15+
use App\Utils\AES256GCM;
16+
use Illuminate\Support\Facades\Log;
1617

1718
/**
1819
* Class EncryptedRequest
@@ -27,7 +28,9 @@ final class EncryptedPayload extends AbstractPayload
2728
*/
2829
public function __construct(string $key, AbstractPayload $request){
2930

30-
$enc = AES::encrypt($key, $request->__toString());
31+
$data =(string)$request;
32+
Log::debug(sprintf("EncryptedPayload::constructor request %s.", $data));
33+
$enc = AES256GCM::encrypt($key, $data);
3134
if($enc->hasError())
3235
throw new \Exception($enc->getErrorMessage());
3336

app/Services/Apis/Samsung/SamsungRegistrationAPI.php

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,16 @@ public function userList(Summit $summit, string $region = Regions::US)
197197
try {
198198

199199
$metadata = $summit->getRegistrationFeedMetadata();
200+
Log::debug
201+
(
202+
sprintf
203+
(
204+
"SamsungRegistrationAPI::userList summit %s metadata %s",
205+
$summit->getId(),
206+
json_encode($metadata, JSON_UNESCAPED_UNICODE)
207+
)
208+
);
209+
200210
$metadata[PayloadParamNames::ExternalShowId] = $summit->getExternalSummitId();
201211
$defaultTicketType = $summit->getFirstDefaultTicketType();
202212
if(is_null($defaultTicketType))
@@ -208,17 +218,6 @@ public function userList(Summit $summit, string $region = Regions::US)
208218

209219
$request = new UserListRequest($metadata);
210220

211-
Log::debug
212-
213-
(
214-
sprintf
215-
(
216-
"SamsungRegistrationAPI::userList POST %s payload %s",
217-
$this->endpoint,
218-
$request
219-
)
220-
);
221-
222221
$payload = (new EncryptedPayload($summit->getExternalRegistrationFeedApiKey(), $request))->getPayload();
223222

224223
// http://docs.guzzlephp.org/en/stable/request-options.html
@@ -232,6 +231,17 @@ public function userList(Summit $summit, string $region = Regions::US)
232231
]
233232
);
234233

234+
Log::debug
235+
(
236+
sprintf
237+
(
238+
239+
"SamsungRegistrationAPI::userList POST %s payload %s",
240+
$this->endpoint,
241+
json_encode($payload)
242+
)
243+
);
244+
235245
return new DecryptedListResponse
236246
(
237247
$summit->getExternalRegistrationFeedApiKey(),

app/Services/Model/Imp/RegistrationIngestionService.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ public function ingestSummit(Summit $summit): void
644644
$response = $feed->getAttendees($page, $summit->getExternalRegistrationFeedLastIngestDate());
645645

646646
if(is_null($response))
647-
throw new ValidationException("Response is empty");
647+
throw new ValidationException("Response is empty.");
648648

649649
if ($response->hasData()) {
650650
$shouldMarkProcess = true;

app/Utils/AES.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public static function encrypt(string $key, string $data): AES
8484
);
8585

8686
// Return base64-encoded string: initVector + encrypted result
87-
$result = $iv.base64_encode( $raw);
87+
$result = $iv.base64_encode($raw);
8888

8989
if ($result === false) {
9090
// Operation failed

app/Utils/AES256GCM.php

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
<?php namespace App\Utils;
2+
/*
3+
* Copyright 2024 OpenStack Foundation
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
**/
14+
15+
/**
16+
* Class AES256GCM
17+
* @package App\Utils
18+
*/
19+
final class AES256GCM
20+
{
21+
22+
const CIPHER = 'AES-256-GCM';
23+
24+
/**
25+
* Encoded/Decoded data
26+
*
27+
* @var null|string
28+
*/
29+
protected $data;
30+
/**
31+
* Initialization vector value
32+
*
33+
* @var string
34+
*/
35+
protected $iv;
36+
/**
37+
* Error message if operation failed
38+
*
39+
* @var null|string
40+
*/
41+
protected $errorMessage;
42+
43+
/**
44+
* AesCipher constructor.
45+
*
46+
* @param string $iv Initialization vector value
47+
* @param string|null $data Encoded/Decoded data
48+
* @param string|null $errorMessage Error message if operation failed
49+
*/
50+
public function __construct(string $iv, string $data = null, string $errorMessage = null)
51+
{
52+
$this->iv = $iv;
53+
$this->data = $data;
54+
$this->errorMessage = $errorMessage;
55+
}
56+
57+
/**
58+
* @param string $key
59+
* @param string $data
60+
* @return AES256GCM
61+
*/
62+
public static function encrypt(string $key, string $data): AES256GCM
63+
{
64+
try {
65+
// Check secret length
66+
if (!AES256GCM::isKeyLengthValid($key)) {
67+
throw new \InvalidArgumentException("Secret key's length must be 128, 192 or 256 bits");
68+
}
69+
70+
$iv_len = openssl_cipher_iv_length(AES256GCM::CIPHER);
71+
// Get random initialization vector
72+
$iv = random_bytes($iv_len);
73+
$tag = '';
74+
// Encrypt input text
75+
$raw = openssl_encrypt(
76+
$data,
77+
AES256GCM::CIPHER,
78+
$key,
79+
OPENSSL_RAW_DATA,
80+
$iv,
81+
$tag,
82+
'',
83+
16
84+
);
85+
86+
// Return base64-encoded string: initVector + encrypted result
87+
$result = base64_encode($iv) . base64_encode($raw . $tag);
88+
89+
if ($result === false) {
90+
// Operation failed
91+
return new AES256GCM($iv, null, openssl_error_string());
92+
}
93+
94+
// Return successful encoded object
95+
return new AES256GCM($iv, $result);
96+
} catch (\Exception $e) {
97+
// Operation failed
98+
return new AES256GCM(isset($iv), null, $e->getMessage());
99+
}
100+
}
101+
102+
/**
103+
* @param string $key
104+
* @param string $data
105+
* @return AES256GCM
106+
*/
107+
public static function decrypt(string $key, string $data): AES256GCM
108+
{
109+
try {
110+
// Check secret length
111+
if (!AES256GCM::isKeyLengthValid($key)) {
112+
throw new \InvalidArgumentException("Secret key's length must be 128, 192 or 256 bits");
113+
}
114+
115+
$iv = base64_decode(substr($data, 0, 16));
116+
$data = base64_decode(substr($data, 16));
117+
$tag = substr($data, strlen($data) - 16);
118+
$data = substr($data, 0, strlen($data) - 16);
119+
120+
// Trying to get decrypted text
121+
$decoded = openssl_decrypt(
122+
$data,
123+
AES256GCM::CIPHER,
124+
$key,
125+
OPENSSL_RAW_DATA,
126+
$iv,
127+
$tag
128+
);
129+
130+
if ($decoded === false) {
131+
// Operation failed
132+
return new AES256GCM(isset($iv), null, openssl_error_string());
133+
}
134+
135+
// Return successful decoded object
136+
return new AES256GCM($iv, $decoded);
137+
} catch (\Exception $e) {
138+
// Operation failed
139+
return new AES256GCM(isset($iv), null, $e->getMessage());
140+
}
141+
}
142+
143+
/**
144+
* Check that secret password length is valid
145+
*
146+
* @param string $key 16/24/32 -characters secret password
147+
*
148+
* @return bool
149+
*/
150+
public static function isKeyLengthValid(string $key): bool
151+
{
152+
$length = strlen($key);
153+
154+
return $length == 16 || $length == 24 || $length == 32;
155+
}
156+
157+
/**
158+
* Get encoded/decoded data
159+
*
160+
* @return string|null
161+
*/
162+
public function getData(): ?string
163+
{
164+
return $this->data;
165+
}
166+
167+
/**
168+
* Get initialization vector value
169+
*
170+
* @return string|null
171+
*/
172+
public function getInitVector(): ?string
173+
{
174+
return $this->iv;
175+
}
176+
177+
/**
178+
* Get error message
179+
*
180+
* @return string|null
181+
*/
182+
public function getErrorMessage(): ?string
183+
{
184+
return $this->errorMessage;
185+
}
186+
187+
/**
188+
* Check that operation failed
189+
*
190+
* @return bool
191+
*/
192+
public function hasError(): bool
193+
{
194+
return $this->errorMessage !== null;
195+
}
196+
197+
/**
198+
* To string return resulting data
199+
*
200+
* @return null|string
201+
*/
202+
public function __toString(): ?string
203+
{
204+
return $this->getData();
205+
}
206+
}

tests/ExternalRegistrationFeedTest.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,15 @@
1414

1515
use App\Models\Foundation\Summit\Registration\ISummitExternalRegistrationFeedType;
1616
use App\Services\Apis\Samsung\ForumTypes;
17-
use App\Services\Apis\Samsung\ISamsungRegistrationAPI;
1817
use App\Services\Model\IMemberService;
1918
use App\Services\Model\Strategies\TicketFinder\ITicketFinderStrategyFactory;
2019
use App\Services\Model\Strategies\TicketFinder\Strategies\TicketFinderByExternalFeedStrategy;
2120
use App\Utils\AES;
2221
use GuzzleHttp\ClientInterface;
2322
use Illuminate\Support\Facades\App;
24-
use LaravelDoctrine\ORM\Facades\EntityManager;
2523
use Mockery;
2624
use models\summit\ISummitAttendeeRepository;
2725
use models\summit\ISummitAttendeeTicketRepository;
28-
use models\summit\Summit;
2926
use models\summit\SummitAttendeeTicket;
3027
use Psr\Http\Message\ResponseInterface;
3128
use Psr\Http\Message\StreamInterface;

0 commit comments

Comments
 (0)