Skip to content

Commit

Permalink
feat: update on samsung api enc algorithm
Browse files Browse the repository at this point in the history
from AES/CBC to AES/GCM

Change-Id: I1159a0ff75f2734308e0de2954c84e54a7f7a7ce
  • Loading branch information
smarcet committed May 2, 2024
1 parent b6ca75d commit 4466ebe
Show file tree
Hide file tree
Showing 10 changed files with 293 additions and 28 deletions.
1 change: 0 additions & 1 deletion app/Services/Apis/Samsung/AbstractPayload.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ abstract class AbstractPayload
function __construct(array $params)
{
if(!isset($params[PayloadParamNames::Forum]))

throw new \InvalidArgumentException("missing forum param");

if(!isset($params[PayloadParamNames::Region]))
Expand Down
29 changes: 23 additions & 6 deletions app/Services/Apis/Samsung/DecryptedListResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
**/

use App\Services\Apis\ExternalRegistrationFeeds\IExternalRegistrationFeedResponse;
use App\Utils\AES;
use App\Utils\AES256GCM;
use Illuminate\Support\Facades\Log;

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

parent::__construct($params);

if(empty($content)){
Log::warning("DecryptedListResponse::constructor empty content.");
throw new EmptyResponse("response not found.");
}

$this->position = 0;

$response = json_decode($content, true);
if(is_array($response) && !count($response))
throw new EmptyResponse("response not found");
throw new EmptyResponse("response not found.");

Log::debug(sprintf("DecryptedListResponse::constructor response %s.", json_encode($response)));

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

$dec = AES::decrypt($key, $response['data']);
if($dec->hasError())
if(empty($response['data']))
throw new EmptyResponse("response not found.");

$dec = AES256GCM::decrypt($key, $response['data']);
if($dec->hasError()) {
Log::warning(sprintf("DecryptedListResponse::constructor error %s.", $dec->getErrorMessage()));
throw new InvalidResponse($dec->getErrorMessage());
}

$data = $dec->getData();

Log::debug(sprintf("DecryptedListResponse::constructor data %s.", $data));

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

Expand Down
5 changes: 2 additions & 3 deletions app/Services/Apis/Samsung/DecryptedSingleResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/

use App\Utils\AES;
use App\Utils\AES256GCM;

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

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

Expand Down
7 changes: 5 additions & 2 deletions app/Services/Apis/Samsung/EncryptedPayload.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
* limitations under the License.
**/

use App\Utils\AES;
use App\Utils\AES256GCM;
use Illuminate\Support\Facades\Log;

/**
* Class EncryptedRequest
Expand All @@ -27,7 +28,9 @@ final class EncryptedPayload extends AbstractPayload
*/
public function __construct(string $key, AbstractPayload $request){

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

Expand Down
32 changes: 21 additions & 11 deletions app/Services/Apis/Samsung/SamsungRegistrationAPI.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,16 @@ public function userList(Summit $summit, string $region = Regions::US)
try {

$metadata = $summit->getRegistrationFeedMetadata();
Log::debug
(
sprintf
(
"SamsungRegistrationAPI::userList summit %s metadata %s",
$summit->getId(),
json_encode($metadata, JSON_UNESCAPED_UNICODE)
)
);

$metadata[PayloadParamNames::ExternalShowId] = $summit->getExternalSummitId();
$defaultTicketType = $summit->getFirstDefaultTicketType();
if(is_null($defaultTicketType))
Expand All @@ -208,17 +218,6 @@ public function userList(Summit $summit, string $region = Regions::US)

$request = new UserListRequest($metadata);

Log::debug

(
sprintf
(
"SamsungRegistrationAPI::userList POST %s payload %s",
$this->endpoint,
$request
)
);

$payload = (new EncryptedPayload($summit->getExternalRegistrationFeedApiKey(), $request))->getPayload();

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

Log::debug
(
sprintf
(

"SamsungRegistrationAPI::userList POST %s payload %s",
$this->endpoint,
json_encode($payload)
)
);

return new DecryptedListResponse
(
$summit->getExternalRegistrationFeedApiKey(),
Expand Down
2 changes: 1 addition & 1 deletion app/Services/Model/Imp/RegistrationIngestionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ public function ingestSummit(Summit $summit): void
$response = $feed->getAttendees($page, $summit->getExternalRegistrationFeedLastIngestDate());

if(is_null($response))
throw new ValidationException("Response is empty");
throw new ValidationException("Response is empty.");

if ($response->hasData()) {
$shouldMarkProcess = true;
Expand Down
2 changes: 1 addition & 1 deletion app/Utils/AES.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public static function encrypt(string $key, string $data): AES
);

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

if ($result === false) {
// Operation failed
Expand Down
206 changes: 206 additions & 0 deletions app/Utils/AES256GCM.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
<?php namespace App\Utils;
/*
* Copyright 2024 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

/**
* Class AES256GCM
* @package App\Utils
*/
final class AES256GCM
{

const CIPHER = 'AES-256-GCM';

/**
* Encoded/Decoded data
*
* @var null|string
*/
protected $data;
/**
* Initialization vector value
*
* @var string
*/
protected $iv;
/**
* Error message if operation failed
*
* @var null|string
*/
protected $errorMessage;

/**
* AesCipher constructor.
*
* @param string $iv Initialization vector value
* @param string|null $data Encoded/Decoded data
* @param string|null $errorMessage Error message if operation failed
*/
public function __construct(string $iv, string $data = null, string $errorMessage = null)
{
$this->iv = $iv;
$this->data = $data;
$this->errorMessage = $errorMessage;
}

/**
* @param string $key
* @param string $data
* @return AES256GCM
*/
public static function encrypt(string $key, string $data): AES256GCM
{
try {
// Check secret length
if (!AES256GCM::isKeyLengthValid($key)) {
throw new \InvalidArgumentException("Secret key's length must be 128, 192 or 256 bits");
}

$iv_len = openssl_cipher_iv_length(AES256GCM::CIPHER);
// Get random initialization vector
$iv = random_bytes($iv_len);
$tag = '';
// Encrypt input text
$raw = openssl_encrypt(
$data,
AES256GCM::CIPHER,
$key,
OPENSSL_RAW_DATA,
$iv,
$tag,
'',
16
);

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

if ($result === false) {
// Operation failed
return new AES256GCM($iv, null, openssl_error_string());
}

// Return successful encoded object
return new AES256GCM($iv, $result);
} catch (\Exception $e) {
// Operation failed
return new AES256GCM(isset($iv), null, $e->getMessage());
}
}

/**
* @param string $key
* @param string $data
* @return AES256GCM
*/
public static function decrypt(string $key, string $data): AES256GCM
{
try {
// Check secret length
if (!AES256GCM::isKeyLengthValid($key)) {
throw new \InvalidArgumentException("Secret key's length must be 128, 192 or 256 bits");
}

$iv = base64_decode(substr($data, 0, 16), true);
$data = base64_decode(substr($data, 16), true);
$tag = substr($data, strlen($data) - 16);
$data = substr($data, 0, strlen($data) - 16);

// Trying to get decrypted text
$decoded = openssl_decrypt(
$data,
AES256GCM::CIPHER,
$key,
OPENSSL_RAW_DATA,
$iv,
$tag
);

if ($decoded === false) {
// Operation failed
return new AES256GCM(isset($iv), null, openssl_error_string());
}

// Return successful decoded object
return new AES256GCM($iv, $decoded);
} catch (\Exception $e) {
// Operation failed
return new AES256GCM(isset($iv), null, $e->getMessage());
}
}

/**
* Check that secret password length is valid
*
* @param string $key 16/24/32 -characters secret password
*
* @return bool
*/
public static function isKeyLengthValid(string $key): bool
{
$length = strlen($key);

return $length == 16 || $length == 24 || $length == 32;
}

/**
* Get encoded/decoded data
*
* @return string|null
*/
public function getData(): ?string
{
return $this->data;
}

/**
* Get initialization vector value
*
* @return string|null
*/
public function getInitVector(): ?string
{
return $this->iv;
}

/**
* Get error message
*
* @return string|null
*/
public function getErrorMessage(): ?string
{
return $this->errorMessage;
}

/**
* Check that operation failed
*
* @return bool
*/
public function hasError(): bool
{
return $this->errorMessage !== null;
}

/**
* To string return resulting data
*
* @return null|string
*/
public function __toString(): ?string
{
return $this->getData();
}
}
3 changes: 0 additions & 3 deletions tests/ExternalRegistrationFeedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,15 @@

use App\Models\Foundation\Summit\Registration\ISummitExternalRegistrationFeedType;
use App\Services\Apis\Samsung\ForumTypes;
use App\Services\Apis\Samsung\ISamsungRegistrationAPI;
use App\Services\Model\IMemberService;
use App\Services\Model\Strategies\TicketFinder\ITicketFinderStrategyFactory;
use App\Services\Model\Strategies\TicketFinder\Strategies\TicketFinderByExternalFeedStrategy;
use App\Utils\AES;
use GuzzleHttp\ClientInterface;
use Illuminate\Support\Facades\App;
use LaravelDoctrine\ORM\Facades\EntityManager;
use Mockery;
use models\summit\ISummitAttendeeRepository;
use models\summit\ISummitAttendeeTicketRepository;
use models\summit\Summit;
use models\summit\SummitAttendeeTicket;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
Expand Down
Loading

0 comments on commit 4466ebe

Please sign in to comment.