2022-09-26

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

本系列共三篇:

用 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

承上一篇 用 DNS Record 驗證 Let's Encrypt 後打包丟給 Web Server 提供下載 Part-2

本篇是以 PowerShell 向 Web Server 自動下載憑證的階段 (雙向加密傳輸)

WebRoot/AAPI/Cert/GetCert.php on Web Server

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

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

if (!function_exists('CloseConnection')) {
include($_SERVER['DOCUMENT_ROOT'].'/API/Cert/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($_SERVER['DOCUMENT_ROOT'].'/API/Cert/Config.php');
};

$ReceivedData = json_decode($Received_JsonContent,true);
$Request = strtolower(myDecrypt($ReceivedData['Name'],$GetCertPublicKey));

$Auth = strtolower(myDecrypt(Substr(myDecrypt($ReceivedData['Auth'],$GetCertPublicKey),16),$GetCertPrivateKey));

if ($Request != $Auth) {
CloseConnection();
exit;
};

$CertPackages = array_diff(scandir($CertPackagesPath), array('.', '..'));

$LastPackages = array();
foreach ($CertPackages as $CertPackage) {
if (substr($CertPackage, -3) != '.7z') {
Continue;
};
$Cert = explode('_',$CertPackage);
if (strtolower($Cert[0]) != $Auth) {
Continue;
};

$CertExpireDateTime = substr($Cert[1],0,4).'-'.substr($Cert[1],4,2).'-'.substr($Cert[1],6,2).' '.substr($Cert[2],0,2).':'.substr($Cert[2],2,2).':'.substr($Cert[2],4,2);
$CertExpireTimestamp = strtotime(substr($Cert[1],0,4).'-'.substr($Cert[1],4,2).'-'.substr($Cert[1],6,2).' '.substr($Cert[2],0,2).':'.substr($Cert[2],2,2).':'.substr($Cert[2],4,2));
if (isset($LastPackages[$Cert[0]]['ExpireDateTime'])) {
if ($CertExpireTimestamp > $LastPackages[$Cert[0]]['ExpireDateTime']) {
$LastPackages[$Cert[0]]['FileName'] = $CertPackage;
$LastPackages[$Cert[0]]['CertExpireDateTime'] = $CertExpireDateTime;
$LastPackages[$Cert[0]]['ExpireDateTimestamp'] = $CertExpireTimestamp ;
};
} else {
$LastPackages[$Cert[0]]['FileName'] = $CertPackage;
$LastPackages[$Cert[0]]['CertExpireDateTime'] = $CertExpireDateTime;
$LastPackages[$Cert[0]]['ExpireDateTimestamp'] = $CertExpireTimestamp ;
};
};

$LastPackages = array_change_key_case_recursive($LastPackages,CASE_LOWER);
$LastPackages = array_map('nestedLowercase', $LastPackages);

if (Empty($LastPackages)) {
CloseConnection();
exit;
};
if (Count($LastPackages) != 1) {
CloseConnection();
exit;
};

$FP = fopen($CertPackagesPath.$LastPackages[$Auth]['filename'],"rb");
$FileSize = filesize($CertPackagesPath.$LastPackages[$Auth]['filename']);
$Data = base64_encode(fread($FP, $FileSize));
fclose($FP);

$SendData['FileName'] = myEncrypt($LastPackages[$Auth]['filename'], $GetCertPublicKey);
$SendData['Data'] = myEncrypt($Data, $GetCertPublicKey);

echo json_encode($SendData, JSON_UNESCAPED_UNICODE);

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);
};
?>

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


GetCert.ps1 on Client

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

#Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force

# input cert path (.pem .pfx)
# ADD '\' at the end of path like: 'C:\Temp\'
$GetCertPath = 'C:\Cert\Cert\';

# input 7zip program fullpath
# https://www.7-zip.org/download.html
$SevenZipProgram = 'C:\Program Files\7-Zip\7z.exe';

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

# Do not change this section
# initial array
$DNSNames = @();
$Auths = @();
# Do not change this section

# when www.contoso.com, input www
$DNSNames += 'www';
# input AuthCode got from https://www.contoso.com/Cert/
$Auths += 'oooxxx==';

#---------------- #Do not change the section below ----------------

$URL = 'https://www.contoso.com/AAPI/Cert/GetCert.php';
$PublicKey = 'wEYlvurP8OCtp4AHUSe9BqiMDVXIo7Fz';

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

$DateTimeString = Get-Date -format yyyyMMdd_HHmmss
$TranscriptLog = (Get-Item -Path ".\" -Verbose).FullName + "\Logs\" + ("GetCert_" + $DateTimeString + ".log")
start-transcript -path $TranscriptLog

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));
};
};

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

$KeySize = 256;
$EncryptKey = RandomPassword(($KeySize / 8))

for ($i=0; $i -lt $DNSNames.Length; $i++) {

$POST = ([PSCustomObject]@{
Name = (myEncrypt $DNSNames[$i] $PublicKey);
Auth = (myEncrypt ((RandomPassword(16)) + [string]$Auths[$i]) $PublicKey);
}) | ConvertTo-Json -Compress;

$ReceivedData = (Invoke-RestMethod -Method 'Post' -Uri $URL -ContentType 'application/json' -Body $POST);

write-host $ReceivedData

$FileName = ($GetCertPath + (myDecrypt ($ReceivedData.FileName) $PublicKey));

[System.IO.File]::WriteAllBytes($FileName, [Convert]::FromBase64String((myDecrypt ($ReceivedData.Data) $PublicKey)))
};

$CommandLine = """" + $SevenZipProgram + """ x """ + $FileName + """ -o""" + $GetCertPath + """ -y"
$CommandLine

cmd /c $CommandLine

Remove-Item -Path $FileName -Force

Stop-Transcript

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

註: Apache 路徑設定 Source IP Filter 範例 (httpd-ssl.conf)

<Directory "${SITEROOT}/www.contoso.com/API">
Require all granted
order deny,allow
deny from all
allow from 192.168.1.1/32
allow from 192.168.10.0/24
</Directory>

<Directory "${SITEROOT}/www.contoso.com/AAPI">
Require all granted
order deny,allow
allow from all
allow from 192.168.10.0/24
</Directory>

沒有留言:

張貼留言