Private/Get-GistContent.ps1
function Get-GistContent { <# .SYNOPSIS A short one-line action-based description, e.g. 'Tests if a function is valid' .DESCRIPTION A longer description of the function, its purpose, common use cases, etc. .NOTES Information or caveats about the function e.g. 'This function is not supported in Linux' .LINK Specify a URI to a help page, this will show when Get-Help -Online is used. .EXAMPLE Get-GistContent https://gist.github.com/alainQtec/34c60b903f3c1c9d51ec5f0ee3a82adb Will get contents of the first file found in the gist #> [OutputType([string])] [CmdletBinding()] param ( # File name with extention [Parameter(Mandatory = $false, Position = 0)] [string]$FileName, # url string, can be a raw_url or just a normal gist url [Parameter(Mandatory = $false)] [string]$GistUrl ) begin { enum EncryptionScope { User # The encrypted data can be decrypted with the same user on any machine. Machine # The encrypted data can only be decrypted with the same user on the same machine it was encrypted on. } enum Compression { Gzip Deflate ZLib } class GitHub { static $webSession static [string] $UserName static hidden [bool] $IsInteractive = $false static hidden [string] $TokenFile = [GitHub]::GetTokenFile() static [PSObject] createSession() { return [Github]::createSession([Github]::UserName) } static [PSObject] createSession([string]$UserName) { [GitHub]::SetToken() return [GitHub]::createSession($UserName, [GitHub]::GetToken()) } static [Psobject] createSession([string]$GitHubUserName, [securestring]$clientSecret) { [ValidateNotNullOrEmpty()][string]$GitHubUserName = $GitHubUserName [ValidateNotNullOrEmpty()][string]$GithubToken = $GithubToken = [AesCrypt]::GetString([securestring]$clientSecret) $encodedAuth = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($GitHubUserName):$($GithubToken)")) $web_session = New-Object Microsoft.PowerShell.Commands.WebRequestSession [void]$web_session.Headers.Add('Authorization', "Basic $($encodedAuth)") [void]$web_session.Headers.Add('Accept', 'application/vnd.github.v3+json') [GitHub]::webSession = $web_session return $web_session } static [void] SetToken() { [GitHub]::SetToken([AesCrypt]::GetString((Read-Host -Prompt "[GitHub] Paste/write your api token" -AsSecureString)), $(Read-Host -Prompt "[GitHub] Paste/write a Password to encrypt the token" -AsSecureString)) } static [void] SetToken([string]$token, [securestring]$password) { if (![IO.File]::Exists([GitHub]::TokenFile)) { New-Item -Type File -Path ([GitHub]::TokenFile) -Force | Out-Null } [IO.File]::WriteAllText([GitHub]::TokenFile, [convert]::ToBase64String([AesCrypt]::Encrypt([system.Text.Encoding]::UTF8.GetBytes($token), $password)), [System.Text.Encoding]::UTF8); } static [securestring] GetToken() { $sectoken = $null; $session_pass = [AesCrypt]::GetSecureString('123'); try { if ([GitHub]::IsInteractive) { if ([string]::IsNullOrWhiteSpace((Get-Content ([GitHub]::TokenFile) -ErrorAction Ignore))) { Write-Host "[GitHub] You'll need to set your api token first. This is a One-Time Process :)" -ForegroundColor Green [GitHub]::SetToken() Write-Host "[GitHub] Good, now let's use the api token :)" -ForegroundColor DarkGreen } elseif ([GitHub]::ValidateBase64String([IO.File]::ReadAllText([GitHub]::TokenFile))) { Write-Host "[GitHub] Encrypted token found in file: $([GitHub]::TokenFile)" -ForegroundColor DarkGreen } else { throw [System.Exception]::New("Unable to read token file!") } $session_pass = Read-Host -Prompt "[GitHub] Input password to use your token" -AsSecureString } else { #Fix: Temporary Workaround: Thisz a pat from one of my GitHub a/cs.It Can only read/write gists. Will expire on 1/1/2025. DoNot Abuse this or I'll take it down!! $et = "OOLqqov4ugMQAtFcWqbzRwNBD65uf9JOZ+jzx1RtcHAZtnKaq1zkIpBcuv1MQfOkvIr/V066Zgsaq5Gka+VhlbqhV8apm8zcQomYjYqLaECKAonFeeo9MqvaP1F2VLgXokrxD1M6weLwS7KC+dyvAgv10IEvLzWFMw==" [GitHub]::SetToken([convert]::ToBase64String([aescrypt]::Decrypt([convert]::FromBase64String($et), $session_pass)), $session_pass) } $sectoken = [AesCrypt]::GetSecureString([system.Text.Encoding]::UTF8.GetString( [AesCrypt]::Decrypt([Convert]::FromBase64String([IO.File]::ReadAllText([GitHub]::GetTokenFile())), $session_pass) ) ) } catch { throw $_ } return $sectoken } static [PsObject] GetUserInfo([string]$UserName) { if ([string]::IsNullOrWhiteSpace([GitHub]::userName)) { [GitHub]::createSession() } $response = Invoke-RestMethod -Uri "https://api.github.com/user/$UserName" -WebSession ([GitHub]::webSession) -Method Get -Verbose:$false return $response } static [PsObject] GetGist([uri]$Uri) { $l = [GitHub]::ParseGistUri($Uri) return [GitHub]::GetGist($l.Owner, $l.Id) } static [PsObject] GetGist([string]$UserName, [string]$GistId) { $t = [GitHub]::GetToken() if ($null -eq ([GitHub]::webSession)) { [GitHub]::webSession = $(if ($null -eq $t) { [GitHub]::createSession($UserName) } else { [GitHub]::createSession($UserName, $t) } ) } if (![GitHub]::IsConnected()) { throw [System.Net.NetworkInformation.PingException]::new("PingException, PLease check your connection!"); } if ([string]::IsNullOrWhiteSpace($GistId) -or $GistId -eq '*') { return Get-Gists -UserName $UserName -SecureToken $t } return Invoke-RestMethod -Uri "https://api.github.com/gists/$GistId" -WebSession ([GitHub]::webSession) -Method Get -Verbose:$false } Static [string] GetGistContent([string]$FileName, [uri]$GistUri) { return [GitHub]::GetGist($GistUri).files.$FileName.content } static [PsObject] CreateGist([string]$description, [array]$files) { $url = 'https://api.github.com/gists' $body = @{ description = $description files = @{} } foreach ($file in $files) { $body.files[$file.Name] = @{ content = $file.Content } } $response = Invoke-RestMethod -Uri $url -WebSession ([GitHub]::webSession) -Method Post -Body ($body | ConvertTo-Json) -Verbose:$false return $response } static [PsObject] UpdateGist([GistFile]$gist, [string]$NewContent) { return '' } static [string] GetTokenFile() { if (![IO.File]::Exists([GitHub]::TokenFile)) { [GitHub]::TokenFile = [IO.Path]::Combine([GitHub]::Get_dataPath('Github', 'clicache'), "token"); } return [GitHub]::TokenFile } static [GistFile] ParseGistUri([uri]$GistUri) { $res = $null; $ogs = $GistUri.OriginalString $IsRawUri = $ogs.Contains('/raw/') -and $ogs.Contains('gist.githubusercontent.com') $seg = $GistUri.Segments $res = $(if ($IsRawUri) { $name = $seg[-1] $rtri = 'https://gist.github.com/{0}{1}' -f $seg[1], $seg[2] $rtri = $rtri.Remove($rtri.Length - 1) $info = [GitHub]::GetGist([uri]::new($rtri)) $file = $info.files."$name" [PsCustomObject]@{ language = $file.language IsPublic = $info.IsPublic raw_url = $file.raw_url Owner = $info.owner.login type = $file.type filename = $name size = $file.size Id = $seg[2].Replace('/', '') } } else { # $info = [GitHub]::GetGist($GistUri) [PsCustomObject]@{ language = '' IsPublic = $null raw_url = '' Owner = $seg[1].Split('/')[0] type = '' filename = '' size = '' Id = $seg[-1] } } ) return [GistFile]::New($res) } static [PsObject] GetUserRepositories() { if ($null -eq [GitHub]::webSession) { [Github]::createSession() } $response = Invoke-RestMethod -Uri 'https://api.github.com/user/repos' -WebSession ([GitHub]::webSession) -Method Get -Verbose:$false return $response } static [psobject] ParseLink([string]$text, [bool]$throwOnFailure) { [ValidateNotNullOrEmpty()][string]$text = $text $uri = $text -as 'Uri'; if ($uri -isnot [Uri] -and $throwOnFailure) { throw [System.InvalidOperationException]::New("Could not create uri from text '$text'.") }; $Scheme = $uri.Scheme if ([regex]::IsMatch($text, '^(\/[a-zA-Z0-9_-]+)+|([a-zA-Z]:\\(((?![<>:"\/\\|?*]).)+\\?)*((?![<>:"\/\\|?*]).)+)$')) { if ($text.ToCharArray().Where({ $_ -in [IO.Path]::InvalidPathChars }).Count -eq 0) { $Scheme = 'file' } else { Write-Debug "'$text' has invalidPathChars in it !" -Debug } } $IsValid = $Scheme -in @('file', 'https') $IsGistUrl = [Regex]::IsMatch($text, 'https?://gist\.github\.com/\w+/[0-9a-f]+') $OutptObject = [pscustomobject]@{ FullName = $text Scheme = [PSCustomObject]@{ Name = $Scheme IsValid = $IsValid IsGistUrl = $IsGistUrl } } return $OutptObject } static [string] Get_Host_Os() { # Should return one of these: [Enum]::GetNames([System.PlatformID]) return $(if ($(Get-Variable IsWindows -Value)) { "Windows" }elseif ($(Get-Variable IsLinux -Value)) { "Linux" }elseif ($(Get-Variable IsMacOS -Value)) { "macOS" }else { "UNKNOWN" }) } static [IO.DirectoryInfo] Get_dataPath([string]$appName, [string]$SubdirName) { $_Host_OS = [GitHub]::Get_Host_Os() $dataPath = if ($_Host_OS -eq 'Windows') { [System.IO.DirectoryInfo]::new([IO.Path]::Combine($Env:HOME, "AppData", "Roaming", $appName, $SubdirName)) } elseif ($_Host_OS -in ('Linux', 'MacOs')) { [System.IO.DirectoryInfo]::new([IO.Path]::Combine((($env:PSModulePath -split [IO.Path]::PathSeparator)[0] | Split-Path | Split-Path), $appName, $SubdirName)) } elseif ($_Host_OS -eq 'Unknown') { try { [System.IO.DirectoryInfo]::new([IO.Path]::Combine((($env:PSModulePath -split [IO.Path]::PathSeparator)[0] | Split-Path | Split-Path), $appName, $SubdirName)) } catch { Write-Warning "Could not resolve chat data path" Write-Warning "HostOS = '$_Host_OS'. Could not resolve data path." [System.IO.Directory]::CreateTempSubdirectory(($SubdirName + 'Data-')) } } else { throw [InvalidOperationException]::new('Could not resolve data path. Get_Host_OS FAILED!') } if (!$dataPath.Exists) { [GitHub]::Create_Dir($dataPath) } return $dataPath } static [void] Create_Dir([string]$Path) { [GitHub]::Create_Dir([System.IO.DirectoryInfo]::new($Path)) } static [void] Create_Dir([System.IO.DirectoryInfo]$Path) { [ValidateNotNullOrEmpty()][System.IO.DirectoryInfo]$Path = $Path $nF = @(); $p = $Path; while (!$p.Exists) { $nF += $p; $p = $p.Parent } [Array]::Reverse($nF); $nF | ForEach-Object { $_.Create(); Write-Verbose "Created $_" } } static [bool] ValidateBase64String([string]$base64) { return $(try { [void][Convert]::FromBase64String($base64); $true } catch { $false }) } static [bool] IsConnected() { if (![bool]("System.Net.NetworkInformation.Ping" -as 'type')) { Add-Type -AssemblyName System.Net.NetworkInformation }; $cs = $null; $re = @{ true = @{ m = "Success"; c = "Green" }; false = @{ m = "Failed"; c = "Red" } } Write-Host "[Github] Testing Connection ... " -ForegroundColor Blue -NoNewline try { [System.Net.NetworkInformation.PingReply]$PingReply = [System.Net.NetworkInformation.Ping]::new().Send("github.com"); $cs = $PingReply.Status -eq [System.Net.NetworkInformation.IPStatus]::Success } catch [System.Net.Sockets.SocketException], [System.Net.NetworkInformation.PingException] { $cs = $false } catch { $cs = $false; Write-Error $_ } $re = $re[$cs.ToString()] Write-Host $re.m -ForegroundColor $re.c return $cs } } class GistFile { [ValidateNotNullOrEmpty()][string]$Name # with extention [string]$language [string]$raw_url [bool]$IsPublic [string]$Owner [string]$type [string]$Id [int]$size GistFile([string]$filename) { $this.Name = $filename } GistFile([psobject]$InputObject) { $this.language = $InputObject.language $this.IsPublic = $InputObject.IsPublic $this.raw_url = $InputObject.raw_url $this.Owner = $InputObject.Owner $this.type = $InputObject.type $this.Name = $InputObject.filename $this.size = $InputObject.size $this.Id = $InputObject.Id } [string] ShowFileInfo() { return "File: $($this.Name)" } } class Gist { [uri] $Uri [string] $Id [string] $Owner [string] $Description [bool] $IsPublic [GistFile[]] $Files = @() Gist() {} Gist([string]$Name) { $this.AddFile([GistFile]::new($Name)) } [psobject] Post() { $gisfiles = @() $this.Files.Foreach({ $gisfiles += @{ $_.Name = @{ content = $_.Content } } } ) $data = @{ files = $gisfiles description = $this.Description public = $this.IsPublic } | ConvertTo-Json Write-Verbose ($data | Out-String) Write-Verbose "[PROCESS] Posting to https://api.github.com/gists" $invokeParams = @{ Method = 'Post' Uri = "https://api.github.com/gists" WebSession = [GitHub]::webSession Body = $data ContentType = 'application/json' } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $r = Invoke-RestMethod @invokeParams $r = $r | Select-Object @{Name = "Url"; Expression = { $_.html_url } }, Description, Public, @{Name = "Created"; Expression = { $_.created_at -as [datetime] } } return $r } [void] AddFile([GistFile]$file) { $this.Files += $file } [string] ShowInfo() { $info = "Gist ID: $($this.Id)" $info += "`nDescription: $($this.Description)" $info += "`nFiles:" foreach ($file in $this.Files.Values) { $info += "`n - $($file.ShowFileInfo())" } return $info } } class Shuffl3r { static [Byte[]] Combine([Byte[]]$Bytes, [Byte[]]$Nonce, [securestring]$Passwod) { return [Shuffl3r]::Combine($bytes, $Nonce, [AesCrypt]::GetString($Passwod)) } static [Byte[]] Combine([Byte[]]$Bytes, [Byte[]]$Nonce, [string]$Passw0d) { # if ($Bytes.Length -lt 16) { throw [InvalidArgumentException]::New('Bytes', 'Input bytes.length should be > 16. ie: $minLength = 17, since the common $nonce length is 16') } if ($bytes.Length -lt ($Nonce.Length + 1)) { Write-Debug "Bytes.Length = $($Bytes.Length) but Nonce.Length = $($Nonce.Length)" -Debug throw [System.ArgumentOutOfRangeException]::new("Nonce", 'Make sure $Bytes.length > $Nonce.Length') } if ([string]::IsNullOrWhiteSpace($Passw0d)) { throw [System.ArgumentNullException]::new('$Passw0d') } [int[]]$Indices = [int[]]::new($Nonce.Length); Set-Variable -Name Indices -Scope local -Visibility Public -Option ReadOnly -Value ([Shuffl3r]::GenerateIndices($Nonce.Length, $Passw0d, $bytes.Length)); [Byte[]]$combined = [Byte[]]::new($bytes.Length + $Nonce.Length); for ([int]$i = 0; $i -lt $Indices.Length; $i++) { $combined[$Indices[$i]] = $Nonce[$i] } $i = 0; $ir = (0..($combined.Length - 1)) | Where-Object { $_ -NotIn $Indices }; foreach ($j in $ir) { $combined[$j] = $bytes[$i]; $i++ } return $combined } static [array] Split([Byte[]]$ShuffledBytes, [securestring]$Passwod, [int]$NonceLength) { return [Shuffl3r]::Split($ShuffledBytes, [AesCrypt]::GetString($Passwod), [int]$NonceLength); } static [array] Split([Byte[]]$ShuffledBytes, [string]$Passw0d, [int]$NonceLength) { if ($null -eq $ShuffledBytes) { throw [System.ArgumentNullException]::new('$ShuffledBytes') } if ([string]::IsNullOrWhiteSpace($Passw0d)) { throw [System.ArgumentNullException]::new('$Passw0d') } [int[]]$Indices = [int[]]::new([int]$NonceLength); Set-Variable -Name Indices -Scope local -Visibility Private -Option ReadOnly -Value ([Shuffl3r]::GenerateIndices($NonceLength, $Passw0d, ($ShuffledBytes.Length - $NonceLength))); $Nonce = [Byte[]]::new($NonceLength); $bytes = [Byte[]]$((0..($ShuffledBytes.Length - 1)) | Where-Object { $_ -NotIn $Indices } | Select-Object *, @{l = 'bytes'; e = { $ShuffledBytes[$_] } }).bytes for ($i = 0; $i -lt $NonceLength; $i++) { $Nonce[$i] = $ShuffledBytes[$Indices[$i]] }; return ($bytes, $Nonce) } static hidden [int[]] GenerateIndices([int]$Count, [string]$randomString, [int]$HighestIndex) { if ($HighestIndex -lt 3 -or $Count -ge $HighestIndex) { throw [System.ArgumentOutOfRangeException]::new('$HighestIndex >= 3 is required; and $Count should be less than $HighestIndex') } if ([string]::IsNullOrWhiteSpace($randomString)) { throw [System.ArgumentNullException]::new('$randomString') } [Byte[]]$hash = [System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes([string]$randomString)) [int[]]$indices = [int[]]::new($Count) for ($i = 0; $i -lt $Count; $i++) { [int]$nextIndex = [Convert]::ToInt32($hash[$i] % $HighestIndex) while ($indices -contains $nextIndex) { $nextIndex = ($nextIndex + 1) % $HighestIndex } $indices[$i] = $nextIndex } return $indices } } # Custom AES Galois/Counter Mode implementation class AesCrypt { static hidden [string] $caller static [ValidateNotNull()][EncryptionScope] $EncryptionScope static [byte[]] GetDerivedSalt([securestring]$password) { $rfc2898 = $null; $s4lt = $null; [byte[]]$s6lt = if ([AesCrypt]::EncryptionScope.ToString() -eq "Machine") { [System.Text.Encoding]::UTF8.GetBytes([AesCrypt]::GetUniqueMachineId()) } else { [convert]::FromBase64String("qmkmopealodukpvdiexiianpnnutirid") } Set-Variable -Name password -Scope Local -Visibility Private -Option Private -Value $password; Set-Variable -Name s4lt -Scope Local -Visibility Private -Option Private -Value $s6lt; Set-Variable -Name rfc2898 -Scope Local -Visibility Private -Option Private -Value $([System.Security.Cryptography.Rfc2898DeriveBytes]::new($password, $s6lt)); Set-Variable -Name s4lt -Scope Local -Visibility Private -Option Private -Value $($rfc2898.GetBytes(16)); return $s4lt } static [byte[]] Encrypt([byte[]]$bytes) { return [AesCrypt]::Encrypt($bytes, [AesCrypt]::GetPassword()); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password) { [byte[]]$_salt = [AesCrypt]::GetDerivedSalt($Password) return [AesCrypt]::Encrypt($bytes, $Password, $_salt); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt) { return [AesCrypt]::Encrypt($bytes, $Password, $Salt, $null, $null, 1); } static [string] Encrypt([string]$text, [SecureString]$Password, [int]$iterations) { return [convert]::ToBase64String([AesCrypt]::Encrypt([System.Text.Encoding]::UTF8.GetBytes("$text"), $Password, $iterations)); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [int]$iterations) { [byte[]]$_salt = [AesCrypt]::GetDerivedSalt($Password) return [AesCrypt]::Encrypt($bytes, $Password, $_salt, $null, $null, $iterations); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [int]$iterations) { return [AesCrypt]::Encrypt($bytes, $Password, $Salt, $null, $null, $iterations); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [int]$iterations, [string]$Compression) { [byte[]]$_salt = [AesCrypt]::GetDerivedSalt($Password) return [AesCrypt]::Encrypt($bytes, $Password, $_salt, $null, $Compression, $iterations); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [byte[]]$associatedData, [int]$iterations) { return [AesCrypt]::Encrypt($bytes, $Password, $Salt, $associatedData, $null, $iterations); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [byte[]]$associatedData) { return [AesCrypt]::Encrypt($bytes, $Password, $Salt, $associatedData, $null, 1); } static [byte[]] Encrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [byte[]]$associatedData, [string]$Compression, [int]$iterations) { [int]$IV_SIZE = 0; Set-Variable -Name IV_SIZE -Scope Local -Visibility Private -Option Private -Value 12 [int]$TAG_SIZE = 0; Set-Variable -Name TAG_SIZE -Scope Local -Visibility Private -Option Private -Value 16 [string]$Key = $null; Set-Variable -Name Key -Scope Local -Visibility Private -Option Private -Value $([convert]::ToBase64String([System.Security.Cryptography.Rfc2898DeriveBytes]::new([AesCrypt]::GetString($Password), $Salt, 10000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(32))); [System.IntPtr]$th = [System.IntPtr]::new(0); if ([string]::IsNullOrWhiteSpace([AesCrypt]::caller)) { [AesCrypt]::caller = '[AesCrypt]' } Set-Variable -Name th -Scope Local -Visibility Private -Option Private -Value $([System.Runtime.InteropServices.Marshal]::StringToHGlobalAnsi($TAG_SIZE)); try { $_bytes = $bytes; $aes = $null; Set-Variable -Name aes -Scope Local -Visibility Private -Option Private -Value $([ScriptBlock]::Create("[Security.Cryptography.AesGcm]::new([convert]::FromBase64String('$Key'))").Invoke()); for ($i = 1; $i -lt $iterations + 1; $i++) { # if ($Protect) { $_bytes = [xconvert]::ToProtected($_bytes, $Salt, [EncryptionScope]::User) } # Generate a random IV for each iteration: [byte[]]$IV = $null; Set-Variable -Name IV -Scope Local -Visibility Private -Option Private -Value ([System.Security.Cryptography.Rfc2898DeriveBytes]::new([AesCrypt]::GetString($password), $salt, 1, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes($IV_SIZE)); $tag = [byte[]]::new($TAG_SIZE); $Encrypted = [byte[]]::new($_bytes.Length); [void]$aes.Encrypt($IV, $_bytes, $Encrypted, $tag, $associatedData); $_bytes = [Shuffl3r]::Combine([Shuffl3r]::Combine($Encrypted, $IV, $Password), $tag, $Password); Write-Debug "$([AesCrypt]::caller) [+] Encryption [$i/$iterations] ... Done" } } catch { throw $_ } finally { [void][System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocAnsi($th); Remove-Variable IV_SIZE, TAG_SIZE, th -ErrorAction SilentlyContinue } if (![string]::IsNullOrWhiteSpace($Compression)) { $_bytes = [AesCrypt]::Compress($_bytes, $Compression); } return $_bytes } static [byte[]] Decrypt([byte[]]$bytes) { return [AesCrypt]::Decrypt($bytes, [AesCrypt]::GetPassword()); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password) { [byte[]]$_salt = [AesCrypt]::GetDerivedSalt($Password) return [AesCrypt]::Decrypt($bytes, $Password, $_salt); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt) { return [AesCrypt]::Decrypt($bytes, $Password, $Salt, $null, $null, 1); } static [string] Decrypt([string]$text, [SecureString]$Password, [int]$iterations) { return [System.Text.Encoding]::UTF8.GetString([AesCrypt]::Decrypt([convert]::FromBase64String($text), $Password, $iterations)); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [int]$iterations) { [byte[]]$_salt = [AesCrypt]::GetDerivedSalt($Password) return [AesCrypt]::Decrypt($bytes, $Password, $_salt, $null, $null, $iterations); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [int]$iterations) { return [AesCrypt]::Decrypt($bytes, $Password, $Salt, $null, $null, 1); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [int]$iterations, [string]$Compression) { [byte[]]$_salt = [AesCrypt]::GetDerivedSalt($Password) return [AesCrypt]::Decrypt($bytes, $Password, $_salt, $null, $Compression, $iterations); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [byte[]]$associatedData, [int]$iterations) { return [AesCrypt]::Decrypt($bytes, $Password, $Salt, $associatedData, $null, $iterations); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [byte[]]$associatedData) { return [AesCrypt]::Decrypt($bytes, $Password, $Salt, $associatedData, $null, 1); } static [byte[]] Decrypt([byte[]]$Bytes, [SecureString]$Password, [byte[]]$Salt, [byte[]]$associatedData, [string]$Compression, [int]$iterations) { [int]$IV_SIZE = 0; Set-Variable -Name IV_SIZE -Scope Local -Visibility Private -Option Private -Value 12 [int]$TAG_SIZE = 0; Set-Variable -Name TAG_SIZE -Scope Local -Visibility Private -Option Private -Value 16 [string]$Key = $null; Set-Variable -Name Key -Scope Local -Visibility Private -Option Private -Value $([convert]::ToBase64String([System.Security.Cryptography.Rfc2898DeriveBytes]::new([AesCrypt]::GetString($Password), $Salt, 10000, [System.Security.Cryptography.HashAlgorithmName]::SHA1).GetBytes(32))); [System.IntPtr]$th = [System.IntPtr]::new(0); if ([string]::IsNullOrWhiteSpace([AesCrypt]::caller)) { [AesCrypt]::caller = '[AesCrypt]' } Set-Variable -Name th -Scope Local -Visibility Private -Option Private -Value $([System.Runtime.InteropServices.Marshal]::StringToHGlobalAnsi($TAG_SIZE)); try { $_bytes = if (![string]::IsNullOrWhiteSpace($Compression)) { [AesCrypt]::DeCompress($bytes, $Compression) } else { $bytes } $aes = [ScriptBlock]::Create("[Security.Cryptography.AesGcm]::new([convert]::FromBase64String('$Key'))").Invoke() for ($i = 1; $i -lt $iterations + 1; $i++) { # if ($UnProtect) { $_bytes = [xconvert]::ToUnProtected($_bytes, $Salt, [EncryptionScope]::User) } # Split the real encrypted bytes from nonce & tags then decrypt them: ($b, $n1) = [Shuffl3r]::Split($_bytes, $Password, $TAG_SIZE); ($b, $n2) = [Shuffl3r]::Split($b, $Password, $IV_SIZE); $Decrypted = [byte[]]::new($b.Length); $aes.Decrypt($n2, $b, $n1, $Decrypted, $associatedData); $_bytes = $Decrypted; Write-Debug "$([AesCrypt]::caller) [+] Decryption [$i/$iterations] ... Done" } } catch { if ($_.FullyQualifiedErrorId -eq "AuthenticationTagMismatchException") { Write-Host "$([AesCrypt]::caller) Wrong password" -ForegroundColor Yellow } throw $_ } finally { [void][System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocAnsi($th); Remove-Variable IV_SIZE, TAG_SIZE, th -ErrorAction SilentlyContinue } return $_bytes } static [byte[]] Compress([byte[]]$Bytes) { return [AesCrypt]::Compress($Bytes, 'Gzip'); } static [string] Compress([string]$Plaintext) { return [convert]::ToBase64String([AesCrypt]::Compress([System.Text.Encoding]::UTF8.GetBytes($Plaintext))); } static [byte[]] Compress([byte[]]$Bytes, [string]$Compression) { if (("$Compression" -as 'Compression') -isnot 'Compression') { Throw [System.InvalidCastException]::new("Compression type '$Compression' is unknown! Valid values: $([Enum]::GetNames([AesCrypt]) -join ', ')"); } $outstream = [System.IO.MemoryStream]::new() $Comstream = switch ($Compression) { "Gzip" { New-Object System.IO.Compression.GzipStream($outstream, [System.IO.Compression.CompressionLevel]::Optimal) } "Deflate" { New-Object System.IO.Compression.DeflateStream($outstream, [System.IO.Compression.CompressionLevel]::Optimal) } "ZLib" { New-Object System.IO.Compression.ZLibStream($outstream, [System.IO.Compression.CompressionLevel]::Optimal) } Default { throw "Failed to Compress Bytes. Could Not resolve Compression!" } } [void]$Comstream.Write($Bytes, 0, $Bytes.Length); $Comstream.Close(); $Comstream.Dispose(); [byte[]]$OutPut = $outstream.ToArray(); $outStream.Close() return $OutPut; } static [byte[]] DeCompress([byte[]]$Bytes) { return [AesCrypt]::DeCompress($Bytes, 'Gzip'); } static [string] DeCompress([string]$Base64Text) { return [System.Text.Encoding]::UTF8.GetString([AesCrypt]::DeCompress([convert]::FromBase64String($Base64Text))); } static [byte[]] DeCompress([byte[]]$Bytes, [string]$Compression) { if (("$Compression" -as 'Compression') -isnot 'Compression') { Throw [System.InvalidCastException]::new("Compression type '$Compression' is unknown! Valid values: $([Enum]::GetNames([compression]) -join ', ')"); } $inpStream = [System.IO.MemoryStream]::new($Bytes) $ComStream = switch ($Compression) { "Gzip" { New-Object System.IO.Compression.GzipStream($inpStream, [System.IO.Compression.CompressionMode]::Decompress); } "Deflate" { New-Object System.IO.Compression.DeflateStream($inpStream, [System.IO.Compression.CompressionMode]::Decompress); } "ZLib" { New-Object System.IO.Compression.ZLibStream($inpStream, [System.IO.Compression.CompressionMode]::Decompress); } Default { throw "Failed to DeCompress Bytes. Could Not resolve Compression!" } } $outStream = [System.IO.MemoryStream]::new(); [void]$Comstream.CopyTo($outStream); $Comstream.Close(); $Comstream.Dispose(); $inpStream.Close() [byte[]]$OutPut = $outstream.ToArray(); $outStream.Close() return $OutPut; } static [string] GetUniqueMachineId() { $Id = [string]($Env:MachineId) $vp = (Get-Variable VerbosePreference).Value try { Set-Variable VerbosePreference -Value $([System.Management.Automation.ActionPreference]::SilentlyContinue) $sha256 = [System.Security.Cryptography.SHA256]::Create() $HostOS = $(if ($(Get-Variable PSVersionTable -Value).PSVersion.Major -le 5 -or $(Get-Variable IsWindows -Value)) { "Windows" }elseif ($(Get-Variable IsLinux -Value)) { "Linux" }elseif ($(Get-Variable IsMacOS -Value)) { "macOS" }else { "UNKNOWN" }); if ($HostOS -eq "Windows") { if ([string]::IsNullOrWhiteSpace($Id)) { $machineId = Get-CimInstance -ClassName Win32_ComputerSystemProduct | Select-Object -ExpandProperty UUID Set-Item -Path Env:\MachineId -Value $([convert]::ToBase64String($sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($machineId)))); } $Id = [string]($Env:MachineId) } elseif ($HostOS -eq "Linux") { # $Id = (sudo cat /sys/class/dmi/id/product_uuid).Trim() # sudo prompt is a nono # Lets use mac addresses $Id = ([string[]]$(ip link show | grep "link/ether" | awk '{print $2}') -join '-').Trim() $Id = [convert]::ToBase64String($sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Id))) } elseif ($HostOS -eq "macOS") { $Id = (system_profiler SPHardwareDataType | Select-String "UUID").Line.Split(":")[1].Trim() $Id = [convert]::ToBase64String($sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Id))) } else { throw "Error: HostOS = '$HostOS'. Could not determine the operating system." } } catch { throw $_ } finally { $sha256.Clear(); $sha256.Dispose() Set-Variable VerbosePreference -Value $vp } return $Id } static [SecureString] GetSecureString([string]$String) { $SecureString = $null; Set-Variable -Name SecureString -Scope Local -Visibility Private -Option Private -Value ([System.Security.SecureString]::new()); if (![string]::IsNullOrEmpty($String)) { $Chars = $String.toCharArray() ForEach ($Char in $Chars) { $SecureString.AppendChar($Char) } } $SecureString.MakeReadOnly(); return $SecureString } static [string] GetString([System.Security.SecureString]$SecureString) { [string]$Pstr = [string]::Empty; [IntPtr]$zero = [IntPtr]::Zero; if ($null -eq $SecureString -or $SecureString.Length -eq 0) { return [string]::Empty; } try { Set-Variable -Name zero -Scope Local -Visibility Private -Option Private -Value ([System.Runtime.InteropServices.Marshal]::SecurestringToBSTR($SecureString)); Set-Variable -Name Pstr -Scope Local -Visibility Private -Option Private -Value ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($zero)); } finally { if ($zero -ne [IntPtr]::Zero) { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($zero); } } return $Pstr; } static [securestring] GetPassword() { $_pass = if ([AesCrypt]::EncryptionScope.ToString() -eq "Machine") { [AesCrypt]::GetSecureString([AesCrypt]::GetUniqueMachineId()) } else { $password = [SecureString]::new(); [Console]::Write("Enter your password: "); while ($true) { [ConsoleKeyInfo]$key = [Console]::ReadKey($true); if ($key.Key -eq [ConsoleKey]::Enter) { break; } $password.AppendChar($key.KeyChar); [Console]::Write("*"); } $password } [Console]::WriteLine(); return $_pass } static [void] ValidateCompression([string]$Compression) { if ($Compression -notin ([Enum]::GetNames('Compression' -as 'Type'))) { Throw [System.InvalidCastException]::new("The name '$Compression' is not a valid [Compression]`$typeName.") }; } } } process { $content = [string]::Empty if ($PSBoundParameters.Keys.Count -eq 1) { if ([GitHub]::ParseLink($FileName, $false).Scheme.IsValid) { $PrsdUrl = [GitHub]::ParseLink($FileName, $false) if ($PrsdUrl.Scheme.IsGistUrl) { $res = [GitHub]::GetGist([uri]::new($PrsdUrl.FullName)) Write-Verbose "[GitHub] Selecting first file in the gist" $fn0 = ($res.files[0] | Get-Member -MemberType noteproperty).Name[0] $content = $res.files.$fn0.content } else { Write-Error "Please Provide a valid Gist Url" } } } else { $content = [GitHub]::GetGistContent($FileName, [uri]::new($GistUrl)) } } end { return $content } } |