2022-09-26

用 DNS Record 驗證 Let's Encrypt 後打包丟給 Web Server 提供下載 Part-1

本系列共三篇:

用 DNS Record 驗證 Let's Encrypt 後打包丟給 Web Server 提供下載 Part-1
用 DNS Record 驗證 Let's Encrypt 後打包丟給 Web Server 提供下載 Part-2
用 DNS Record 驗證 Let's Encrypt 後打包丟給 Web Server 提供下載 Part-3

透過 Microsoft DNS Server 上用 DNS Record 來驗證 Let's Encrypt 的 PowerShell Script 之後
需要將取得的憑證丟給目標伺服器使用,但 DNS Server 本身就不太適合再擔任其他服務腳色,容易因為有漏洞而被攻擊,影響 DNS 服務,進而影響整個 Domain 運作。
因此在取得更新的憑證後,先以 7-Zip 打包,再透過加密的方式傳輸到 Web Server 上提供給各伺服器負責人下載使用。

需要在 DNS Server 上執行 PackCert.ps1
Web Server 上的 API 接收後存起來 (API Path 建議做好 Source IP Filter)
使用者再從網頁手動下載或透過 
GetCert.ps1 下載

相關動作我將拆成三篇來寫
本篇是 DNS Server 打包丟給 Web Server 儲存的階段

PackCert.ps1 on DNS Server

API Key 必須與 Web API 相同
CertPassword 看申請 Let's Encrypt 憑證的時候用什麼密碼就填什麼

#--------------------------------------------------------------------------------

$APIKey = 'lsiMwjc80ptrSoqvBHkTCIYGA6gaxDd7';
# RandomPassword(32)
$Domain = 'contoso.com';
$CertPath = 'C:\Cert\Cert';
$PackPath = 'C:\Cert\Package';
$CertPassword = 'P@$$w0rd';
$SevenZipProgram = 'C:\Program Files\7-Zip\7z.exe';
$CompressionLevel = 7;

$URL = 'https://www.contoso.com/API/Cert/';

#----------------

Function RandomPassword {

param (
[int]$PasswordLength = 16
);

PROCESS {

return (("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".tochararray() | sort {Get-Random})[0..($PasswordLength - 1)] -join '');
};
};

#----------------

Function myEncrypt {

param (
[String]$Data,
[String]$EncryptKey
);

PROCESS {

$Encode = [System.Text.Encoding]::UTF8;
$DataBytes = $Encode.GetBytes($Data);

$AES = New-Object System.Security.Cryptography.AESManaged;
$AES.BlockSize = 128;
$AES.KeySize = 256;
$AES.Mode = [System.Security.Cryptography.CipherMode]::CBC;
$iv = RandomPassword(($AES.BlockSize / 8));
$AES.IV = $Encode.GetBytes($iv);
$AES.Key = $Encode.GetBytes($EncryptKey);

$Encryptor = $AES.CreateEncryptor();
$EncryptedBytes = $Encryptor.TransformFinalBlock($DataBytes, 0, $DataBytes.length);

return ([Convert]::ToBase64String($Encode.GetBytes($iv + ([Convert]::ToBase64String($EncryptedBytes)))));
};
};

#----------------

Function myDecrypt {

param (
[String]$Data,
[String]$EncryptKey
);

PROCESS {

$Encode = [System.Text.Encoding]::UTF8;
$DataString = $Encode.GetString([Convert]::FromBase64String($Data));

$AES = New-Object System.Security.Cryptography.AESManaged;
$AES.BlockSize = 128;
$AES.KeySize = 256;
$AES.Mode = [System.Security.Cryptography.CipherMode]::CBC;
#$AES.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
$iv = $DataString.SubString(0,($AES.BlockSize / 8));
$AES.IV = $Encode.GetBytes($iv);
$AES.Key = $Encode.GetBytes($EncryptKey);

$Decryptor = $AES.CreateDecryptor();
$DataBytes = [Convert]::FromBase64String($DataString.SubString(($AES.BlockSize / 8)));

return $Encode.GetString($Decryptor.TransformFinalBlock($DataBytes, 0, $DataBytes.Length));
};
};

#----------------

$CertFiles = Get-ChildItem -Path $CertPath;

$PackFiles = @();
$PackFilesExists = Get-ChildItem -Path $PackPath;
foreach ($PackFilesExist in $PackFilesExists) {
$PackFiles += $PackFilesExist;
};

$CertProperties = @();
foreach ($CertFile in $CertFiles) {
if ( ($CertFile.Name -like ('*' + $Domain + '*')) -and ($CertFile.Extension -eq '.pfx') ) {

Clear-Variable -Name 'CertProperties';
$CertProperties = try {
(Get-PfxData -FilePath $CertFile.FullName -Password (ConvertTo-SecureString -String $CertPassword -Force -AsPlainText) -ErrorAction SilentlyContinue).EndEntityCertificates | Select Thumbprint,NotAfter;
} catch {
(Get-PfxData -FilePath $CertFile.FullName).EndEntityCertificates | Select Thumbprint,NotAfter;
};
if ($CertProperties -eq $Null) {
continue;
};

$CertHead = ($CertFile.Name -Split '\.')[0];
if ($CertHead -eq '_') {
$PackName = 'Wildcard';
} else {
$PackName = $CertHead;
};
$PackName += '_' + $CertProperties.NotAfter.ToString("yyyyMMdd_HHmmss");

$Hit = $False;
foreach ($PackFile in $PackFiles) {
if ($PackFile.BaseName -eq $PackName) {
$Hit = $True;
break;
};
};
if ($Hit -eq $False) {
$Package = ($PackPath + '\' + $PackName + '.7z');
$Command = ('"' + $SevenZipProgram + '" a -mhe -mx=' + [String]$CompressionLevel + ' -t7z "' + $Package + '" "' + ($CertPath + '\' + $CertHead + '.' + $Domain) + '*.*"')
write-host $Command;
Invoke-Expression ('& ' + ($Command));
$PackFile = Get-item -Path $Package;
if ($PackFile -ne $Null) {
$PackFiles += $PackFile;
};
};
};
};

$FileBytes = @()
$B64String = @()
foreach ($PackFile in $PackFiles) {

Clear-Variable -Name 'FileBytes';
Clear-Variable -Name 'B64String';
$FileBytes = try {
[System.IO.File]::ReadAllBytes($PackFile.FullName);
} catch {
};
if ($FileBytes -eq $Null) {
break;
};
$POST = ([PSCustomObject]@{
Data = (myEncrypt ([System.Convert]::ToBase64String($FileBytes, [System.Base64FormattingOptions]::None)) $APIKey)
FileName = (myEncrypt $PackFile.Name $APIKey)
}) | ConvertTo-Json -Compress;

$URLRequest = Invoke-RestMethod -Method 'Post' -Uri $URL -ContentType 'application/json' -Body $POST;
write-host ($PackFile.Name + ' Upload ' + $URLRequest);
};

#--------------------------------------------------------------------------------


Web Server 接收的 API

WebRoot/API/Cert/config.php

<?php
$GetCertPublicKey = 'wEYlvurP8OCtp4AHUSe9BqiMDVXIo7Fz';

$GetCertPrivateKey = 'Txqyp7f6gV1UROorYCmILFQXcZzudiDl';

Function CloseConnection() {
header('HTTP/1.0 403 Forbidden');
// Close Connection
ob_start();
// send headers to tell the browser to close the connection
header("Content-Length: ".ob_get_length());
header('Connection: close');
ob_end_flush();
ob_flush();
flush();
if (session_id()) {
session_write_close();
};
// Close Connection
exit;
};
?>

WebRoot/API/Cert/index.php

<?php
header("HTTP/1.1 200 OK");
$Received_JsonContent = file_get_contents('php://input');

if (!function_exists('CloseConnection')) {
include('Config.php');
};

if (empty($Received_JsonContent)) {
CloseConnection();
exit;
};

ob_clean();

if (!isset($CertPackagesPath)) {
include($_SERVER['DOCUMENT_ROOT'].'/Cert/Config.php');
};

if ( (!isset($GetCertPrivateKey)) || (!isset($GetCertPublicKey)) ) {
include('Config.php');
};

$ReceivedData = json_decode($Received_JsonContent,true);

if ( (!isset($ReceivedData['FileName'])) || (!isset($ReceivedData['Data'])) ) {
CloseConnection();
exit;
};

$FileName = myDecrypt($ReceivedData['FileName'], $APIKey);
$Data = base64_decode(myDecrypt($ReceivedData['Data'], $APIKey));

if ( ($FileName != True) || ($Data != True)) {
CloseConnection();
exit;
};

$PackageTarget = $CertPackagesPath.$FileName;
if (!file_exists($PackageTarget)) {
$ifp = fopen($PackageTarget,'wb'); 
fwrite($ifp,$Data);
fclose($ifp); 
};

if (file_exists($PackageTarget)) {
echo 'ok';
};
exit;

Function myEncrypt($Data, $EncryptKey) {
$AESBlockSize = 128;
$iv = RandomPassword(($AESBlockSize/8));
$Encrypted = openssl_encrypt($Data, 'aes-256-cbc', $EncryptKey, 0, $iv);
return base64_encode($iv.$Encrypted);
};

Function myDecrypt($Data, $EncryptKey) {
$AESBlockSize = 128;
$DataDecode = base64_decode($Data);
$iv = substr($DataDecode,0,($AESBlockSize/8));
$Encrypted_data = substr($DataDecode,($AESBlockSize/8));
return openssl_decrypt($Encrypted_data, 'aes-256-cbc', $EncryptKey, 0, $iv);
};
Function RandomPassword($PasswordLength=16) {
$Alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$Password = Array();
$AlphaLength = strlen($Alphabet) - 1;
for ($i = 0; $i < $PasswordLength; $i++) {
$Password[] = $Alphabet[(rand(0, $AlphaLength))];
};
return implode($Password);
};

?>

#--------------------------------------------------------------------------------









沒有留言:

張貼留言