Commit 3356b890 by Mohammad Izzat Johari

refector

parent 0e71f5c2
{ {
"name": "3fresources/hsmcrypto", "name": "integration/hsmcrypto",
"description": "Laravel HSM Encryption/Decryption via PKCS#11", "description": "Laravel HSM Encryption/Decryption via PKCS#11",
"type": "library", "type": "library",
"license": "MIT", "license": "MIT",
"authors": [ "authors": [
{ {
"name": "Zee Zaco", "name": "Zee Zaco",
"email": "izzat@3fresources.com" "email": "izzatjohari@gmail.com"
} }
], ],
"autoload": { "autoload": {
......
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('hsm_log', function (Blueprint $table) {
$table->id();
$table->string('event', 100)->nullable();
$table->string('subject', 100)->nullable();
$table->string('response', 100)->nullable();
$table->integer('causer')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('hsm_log');
}
};
...@@ -9,8 +9,8 @@ class HsmCryptoServiceProvider extends ServiceProvider ...@@ -9,8 +9,8 @@ class HsmCryptoServiceProvider extends ServiceProvider
public function register() public function register()
{ {
$this->mergeConfigFrom( $this->mergeConfigFrom(
__DIR__ . '/../config/hsmcrypto.php', __DIR__ . '/../config/hsm.php',
'hsmcrypto' 'hsm'
); );
$this->app->singleton('hsm-crypto', function () { $this->app->singleton('hsm-crypto', function () {
...@@ -20,8 +20,16 @@ class HsmCryptoServiceProvider extends ServiceProvider ...@@ -20,8 +20,16 @@ class HsmCryptoServiceProvider extends ServiceProvider
public function boot() public function boot()
{ {
// Auto-load migrations
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
// Optionally publish migrations
$this->publishes([
__DIR__ . '/../database/migrations/' => database_path('migrations'),
], 'hsm-crypto-migrations');
$this->publishes([ $this->publishes([
__DIR__ . '/../config/hsmcrypto.php' => config_path('hsmcrypto.php'), __DIR__ . '/../config/hsm.php' => config_path('hsm.php'),
], 'config'); ], 'config');
} }
} }
<?php <?php
namespace App\Helpers; namespace integration\HsmCrypto;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use App\Events\Logger;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Workbench\Apim\Model\AttachmentFile; use integration\HsmCrypto\Models\HsmLog;
use setasign\Fpdi\Fpdi;
class Pkcs11Command class Pkcs11Command
{ {
...@@ -52,6 +51,16 @@ class Pkcs11Command ...@@ -52,6 +51,16 @@ class Pkcs11Command
return implode("\n", $output); return implode("\n", $output);
} }
protected function logger(string $event, string $subject, string $response){
$hsm_log = new HsmLog();
$hsm_log->event = $event;
$hsm_log->subject = $subject;
$hsm_log->response = $response;
$hsm_log->causer = auth()->user()->id;
$hsm_log->save();
}
/** /**
* Check if keypair exists in HSM by listing objects. * Check if keypair exists in HSM by listing objects.
*/ */
...@@ -64,6 +73,8 @@ class Pkcs11Command ...@@ -64,6 +73,8 @@ class Pkcs11Command
. ' --pin "' . $this->pin . '"' . ' --pin "' . $this->pin . '"'
. ' --list-objects'; . ' --list-objects';
$this->logger('Retrieve Key', '', 'Retrieve Key Successful');
return $this->runShellCommand($cmd, 'Failed to list objects in HSM'); return $this->runShellCommand($cmd, 'Failed to list objects in HSM');
} }
...@@ -106,7 +117,7 @@ class Pkcs11Command ...@@ -106,7 +117,7 @@ class Pkcs11Command
$all_keys = $this->runShellCommand($cmd, 'Failed to list objects in HSM'); $all_keys = $this->runShellCommand($cmd, 'Failed to list objects in HSM');
preg_match_all('/ID:\s*([0-9A-Fa-f]+)/', $all_keys, $matches); preg_match_all('/^\s*ID:\s*([0-9A-Fa-f]+)/m', $all_keys, $matches);
$usedIds = collect($matches[1])->unique()->sort()->values(); $usedIds = collect($matches[1])->unique()->sort()->values();
$nextId = $usedIds->last() ? hexdec($usedIds->last()) + 1 : 1; $nextId = $usedIds->last() ? hexdec($usedIds->last()) + 1 : 1;
return dechex($nextId); return dechex($nextId);
...@@ -127,9 +138,10 @@ class Pkcs11Command ...@@ -127,9 +138,10 @@ class Pkcs11Command
$all_keys = $this->runShellCommand($cmd, 'Failed to list objects in HSM'); $all_keys = $this->runShellCommand($cmd, 'Failed to list objects in HSM');
preg_match_all('/ID:\s*([0-9A-Fa-f]+)/', $all_keys, $matches); preg_match_all('/^\s*ID:\s*([0-9A-Fa-f]+)/m', $all_keys, $matches);
$usedIds = collect($matches[1])->unique()->sort()->values(); $usedIds = collect($matches[1])->unique()->sort()->values();
$nextId = hexdec($usedIds->last()); $nextId = hexdec($usedIds->last());
return dechex($nextId); return dechex($nextId);
} }
...@@ -149,6 +161,8 @@ class Pkcs11Command ...@@ -149,6 +161,8 @@ class Pkcs11Command
. ' --id ' . $id . ' --id ' . $id
. ' --label "' . $label . '"'; . ' --label "' . $label . '"';
$this->logger('Generate Key', $label, 'Generate Key Successful');
return $this->runShellCommand($cmd, 'Failed to generate RSA keypair'); return $this->runShellCommand($cmd, 'Failed to generate RSA keypair');
} }
...@@ -168,6 +182,8 @@ class Pkcs11Command ...@@ -168,6 +182,8 @@ class Pkcs11Command
Log::info('Generated AES 256 key', ['key' => base64_encode($aesKey)]); Log::info('Generated AES 256 key', ['key' => base64_encode($aesKey)]);
$this->logger('Generate AES 256 Key', '', 'Generate AES 256 Key Successful');
// Generate IV // Generate IV
$ivLength = openssl_cipher_iv_length('aes-256-gcm'); $ivLength = openssl_cipher_iv_length('aes-256-gcm');
if ($ivLength === false || $ivLength <= 0) { if ($ivLength === false || $ivLength <= 0) {
...@@ -199,7 +215,7 @@ class Pkcs11Command ...@@ -199,7 +215,7 @@ class Pkcs11Command
} }
// Save encrypted PDF // Save encrypted PDF
$storagePath = public_path("storage/uploads/encrypt"); $storagePath = public_path("storage/hsm/encrypt");
if (!File::exists($storagePath)) { if (!File::exists($storagePath)) {
File::makeDirectory($storagePath, 0777, true); File::makeDirectory($storagePath, 0777, true);
} }
...@@ -211,36 +227,29 @@ class Pkcs11Command ...@@ -211,36 +227,29 @@ class Pkcs11Command
Log::info('Encrypted PDF saved', ['path' => $encryptedPdf]); Log::info('Encrypted PDF saved', ['path' => $encryptedPdf]);
// Encrypt AES Key with HSM (proc_open needed for piping) $this->logger('File Encrypted', $filename, 'File Encrypted Successful');
$encryptedKey = $this->encryptAesKey($aesKey, $filename);
// Save metadata
$attachment = new AttachmentFile();
$attachment->nama_fail = $filename;
$attachment->dir_key = $encryptedKey;
$attachment->dir_file = $encryptedPdf;
$attachment->dir_iv = base64_encode($iv);
$attachment->dir_tag = base64_encode($tag);
$attachment->save();
Event::dispatch(new Logger("encrypt", $filename, 'success', 'File encrypted successfully', $encryptedPdf)); $encryptedKey = $this->encryptAesKey($aesKey, $filename);
unset($aesKey); // Zeroize key unset($aesKey);
return [ $response = [
'status' => 'success', 'status' => 'success',
'message' => 'File encrypted successfully', 'message' => 'File Encrypted Successful',
'file' => $encryptedPdf, 'nama_fail' => $filename,
'key' => $encryptedKey 'dir_key' => $encryptedKey,
'dir_file' => $encryptedPdf,
'dir_iv' => base64_encode($iv),
'dir_tag' => base64_encode($tag)
]; ];
return json_encode($response);
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Encryption failed', [ Log::error('Encryption failed', [
'file' => $filename, 'file' => $filename,
'error' => $e->getMessage() 'error' => $e->getMessage()
]); ]);
Event::dispatch(new Logger("encrypt", $filename, 'failed', 'Encryption failed', $e->getMessage()));
throw $e; throw $e;
} }
} }
...@@ -251,7 +260,8 @@ class Pkcs11Command ...@@ -251,7 +260,8 @@ class Pkcs11Command
protected function encryptAesKey(string $aesKey, string $filename) protected function encryptAesKey(string $aesKey, string $filename)
{ {
$id_key = $this->getkeypairIdByLabel($this->keyLabel); $id_key = $this->getkeypairIdByLabel($this->keyLabel);
$storagePath = public_path("storage/uploads/encryptkey");
$storagePath = public_path("storage/hsm/encryptkey");
if (!File::exists($storagePath)) { if (!File::exists($storagePath)) {
File::makeDirectory($storagePath, 0777, true); File::makeDirectory($storagePath, 0777, true);
} }
...@@ -296,11 +306,15 @@ class Pkcs11Command ...@@ -296,11 +306,15 @@ class Pkcs11Command
'code' => $returnCode, 'code' => $returnCode,
]); ]);
$this->logger('AES Key Encrypted', '', 'AES Key Encrypted Failed');
throw new \RuntimeException("Failed to encrypt AES key: $stderr"); throw new \RuntimeException("Failed to encrypt AES key: $stderr");
} }
Log::info('AES key encrypted', ['path' => $encryptedKeyPath]); Log::info('AES key encrypted', ['path' => $encryptedKeyPath]);
$this->logger('AES Key Encrypted', '', 'AES Key Encrypted Successful');
return $encryptedKeyPath; return $encryptedKeyPath;
} }
...@@ -314,7 +328,7 @@ class Pkcs11Command ...@@ -314,7 +328,7 @@ class Pkcs11Command
try { try {
// Decrypt AES Key first // Decrypt AES Key first
$storagePath = public_path("storage/uploads/tempkey"); $storagePath = public_path("storage/hsm/tempkey");
if (!File::exists($storagePath)) { if (!File::exists($storagePath)) {
File::makeDirectory($storagePath, 0777, true); File::makeDirectory($storagePath, 0777, true);
} }
...@@ -335,6 +349,8 @@ class Pkcs11Command ...@@ -335,6 +349,8 @@ class Pkcs11Command
// Use runShellCommand for consistent error handling // Use runShellCommand for consistent error handling
$this->runShellCommand($cmd, 'Failed to decrypt AES key'); $this->runShellCommand($cmd, 'Failed to decrypt AES key');
$this->logger('AES Key Decrypted', '', 'AES Key Decrypted Successful');
$aesKey = file_get_contents($decryptedKeyFile); $aesKey = file_get_contents($decryptedKeyFile);
if ($aesKey === false) { if ($aesKey === false) {
throw new \Exception('Failed to read decrypted AES key.'); throw new \Exception('Failed to read decrypted AES key.');
...@@ -361,31 +377,58 @@ class Pkcs11Command ...@@ -361,31 +377,58 @@ class Pkcs11Command
$tag $tag
); );
$this->logger('File Decrypted', $filename, 'File Decrypted Successful');
if ($decryptedPdf === false) { if ($decryptedPdf === false) {
throw new \Exception('Failed to decrypt PDF content.'); throw new \Exception('Failed to decrypt PDF content.');
} }
// Save decrypted PDF // Save decrypted PDF
$decryptPath = public_path("storage/uploads/decrypt"); $decryptPath = public_path("storage/hsm/decrypt");
if (!File::exists($decryptPath)) { if (!File::exists($decryptPath)) {
File::makeDirectory($decryptPath, 0777, true); File::makeDirectory($decryptPath, 0777, true);
} }
$decryptedFile = $decryptPath . '/' . $filename; $fpdi = new Fpdi();
if (file_put_contents($decryptedFile, $decryptedPdf) === false) { // Write decrypted bytes to a memory stream
throw new \Exception('Failed to save decrypted PDF.'); $pdfStream = fopen('php://memory', 'r+');
fwrite($pdfStream, $decryptedPdf);
rewind($pdfStream);
// Count pages
$pageCount = $fpdi->setSourceFile($pdfStream);
// 3️⃣ Add watermark & rebuild PDF in memory
for ($i = 1; $i <= $pageCount; $i++) {
$tplIdx = $fpdi->importPage($i);
$size = $fpdi->getTemplateSize($tplIdx);
$orientation = ($size['width'] > $size['height']) ? 'L' : 'P';
$fpdi->AddPage($orientation, [$size['width'], $size['height']]);
$fpdi->useTemplate($tplIdx);
// Watermark text
$fpdi->SetFont('Arial', '', 12);
$fpdi->SetTextColor(150, 150, 150);
$fpdi->SetXY(20, $size['height'] - 20);
$fpdi->Write(0, 'Confidential — ' .
('Guest') .
' — ' . now()->toDateTimeString());
} }
Log::info('PDF decrypted successfully', ['file' => $decryptedFile]); // Output to string (S = string)
$watermarkedPdf = $fpdi->Output('S');
Event::dispatch(new Logger("decrypt", $filename, 'success', 'File decrypted successfully', $decryptedFile)); $response = response($watermarkedPdf, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="'.$filename.'"',
'Cache-Control' => 'no-store, no-cache, must-revalidate',
'Pragma' => 'no-cache',
'X-Content-Type-Options' => 'nosniff',
]);
return [ // 4️⃣ Stream PDF to browser (inline)
'status' => 'success', return $response;
'command' => 'Decryption File successful',
'file' => $decryptedFile,
];
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Decryption failed', [ Log::error('Decryption failed', [
...@@ -393,8 +436,6 @@ class Pkcs11Command ...@@ -393,8 +436,6 @@ class Pkcs11Command
'error' => $e->getMessage() 'error' => $e->getMessage()
]); ]);
Event::dispatch(new Logger("decrypt", $filename, 'failed', 'Decryption failed', $e->getMessage()));
throw $e; throw $e;
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment