-
Notifications
You must be signed in to change notification settings - Fork 165
CUT-5090: Secure credential reference in settings #744
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # Clear a specified number of lines from the console | ||
| function Clear-Console { | ||
| param( | ||
| [Parameter(Mandatory=$true)] | ||
| [int]$LinesToClear | ||
| ) | ||
| $currentLine = $Host.UI.RawUI.CursorPosition.Y | ||
| $bufferWidth = $Host.UI.RawUI.BufferSize.Width | ||
| $startLine = $currentLine - $LinesToClear | ||
| if ($startLine -lt 0) { $startLine = 0 } | ||
|
|
||
| for ($i = $startLine; $i -le $currentLine; $i++) { | ||
| [Console]::SetCursorPosition(0, $i) | ||
| [Console]::Write(" " * $bufferWidth) | ||
| } | ||
| [Console]::SetCursorPosition(0, $startLine) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| function Confirm-Console { | ||
| param( | ||
| [Parameter(Mandatory=$False)] | ||
| [string]$Message, | ||
| [Parameter(Mandatory=$False)] | ||
| [scriptblock]$YesAction | ||
| ) | ||
| $LinesToClear = 1 | ||
| if($Message) { | ||
| Write-Host $Message -ForegroundColor Yellow | ||
| # $LinesToClear += ($Message.Split("`n").Count) | ||
| } | ||
| $res = Find-Interactive -choices @("No", "Yes"); | ||
| if($res -eq "Yes" -and $YesAction) { | ||
| Invoke-Command -ScriptBlock $YesAction | ||
| $temp = $true | ||
| } else { | ||
| $temp = $false | ||
| } | ||
|
|
||
| Clear-Console -LinesToClear $LinesToClear | ||
| return $temp | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # Interactive function to display choices and handle user input for selection | ||
| function Find-Interactive() { | ||
| param ( | ||
| [string[]]$choices, | ||
| [scriptblock]$Callback | ||
| ) | ||
| $index = 0 | ||
| $running = $true | ||
| $trigger = $false | ||
| [Console]::CursorVisible = $false | ||
| while ($running) { | ||
| for ($i = 0; $i -lt $choices.Count; $i++) { | ||
| if ($i -eq $index) { | ||
| Write-Host " > $($choices[$i])" -ForegroundColor Cyan -BackgroundColor DarkGray | ||
| } else { | ||
| Write-Host " $($choices[$i])" | ||
| } | ||
| } | ||
| $key = [Console]::ReadKey($true) | ||
| switch ($key.Key) { | ||
| 'UpArrow' { $index = if ($index -gt 0) { $index - 1 } else { $choices.Count - 1 } } | ||
| 'DownArrow' { $index = if ($index -lt $choices.Count - 1) { $index + 1 } else { 0 } } | ||
| 'Enter' { $running = $false ; $trigger = $false;} | ||
| 'Backspace' { if($Callback) { $running = $false; $trigger = $true; } } | ||
| 'Escape' { [Console]::CursorVisible = $true; return $null } | ||
| } | ||
| [Console]::SetCursorPosition(0, [Console]::CursorTop - ($choices.Count)) | ||
| } | ||
| [Console]::CursorVisible = $true | ||
| # Clear the choices from the console | ||
| for ($i = 0; $i -lt $choices.Count; $i++) { | ||
| Write-Host (" " * ([Console]::WindowWidth - 1)) | ||
| } | ||
| [Console]::SetCursorPosition(0, [Console]::CursorTop - ($choices.Count)) | ||
| if($trigger -and $Callback) { | ||
| $temp = Invoke-Command -ScriptBlock $Callback -ArgumentList $choices[$index] | ||
| return $false | ||
| } | ||
| return $choices[$index] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Retrieve a key from the vault based on the provided key name | ||
| function Get-KeyFromVault() { | ||
| param( | ||
| [Parameter(Mandatory=$true)] | ||
| [string]$Key | ||
| ) | ||
| Unlock-Platform | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| $plat = $env:CONSOLE_PLATFORM | ||
| if ($plat -eq "MacOS") { | ||
| $serviceKey = security find-generic-password -s $Key -w | ||
| if ($LASTEXITCODE -ne 0) { | ||
| return $null | ||
| } | ||
| } elseif ($plat -eq "Windows") { | ||
| $serviceKey = [CredManager]::GetCreds($Key) | ||
| } else { | ||
| throw "Unsupported OS." | ||
| } | ||
|
|
||
| Write-Host "Retrieved key from Credential Manager: $key" -ForegroundColor Green | ||
| return $serviceKey | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| function Get-VaultKeys() { | ||
| param( | ||
| [Parameter(Mandatory=$true)] | ||
| [string]$sufix | ||
| ) | ||
|
|
||
| $plat = $env:CONSOLE_PLATFORM | ||
| If($plat -eq "Windows") { | ||
| # $username = $env:USERNAME.Trim().ToLower().Replace(" ", "_") | ||
| try { | ||
| $keys = [CredManager]::GetTargetList().Where({ $_.EndsWith($sufix) }) | ||
| } catch { | ||
| Write-Host "Error retrieving keys: $_" -ForegroundColor Red | ||
| return $null | ||
| } | ||
| } ElseIf($plat -eq "MacOS") { | ||
| $keys = security dump-keychain | ForEach-Object { | ||
| if ($_ -match '0x00000007\s+<blob>="(.+?)"' -or $_ -match '"svce"<.+?>="(.+?'+$sufix+')"') { | ||
| $found = $matches[1] | ||
| if ($found.EndsWith($sufix)){return $found} | ||
| } | ||
| } | Select-Object -Unique | ||
| } ElseIf($plat -eq "Linux") { | ||
| throw "Unsupported OS." | ||
| } Else { | ||
| throw "Unsupported OS." | ||
| } | ||
|
|
||
| return $keys | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # Remove a key from the vault based on the provided key name | ||
| function Remove-FromVault() { | ||
| param( | ||
| [Parameter(Mandatory=$true)] | ||
| [string]$Key | ||
| ) | ||
|
|
||
| $plat = $env:CONSOLE_PLATFORM | ||
| if ($plat -eq "MacOS") { | ||
| try { | ||
| security delete-generic-password -s $Key 2>$null | ||
| if ($LASTEXITCODE -ne 0) { | ||
| throw "Failed to delete key from Keychain." | ||
| } | ||
| } catch { | ||
| throw "Error deleting key from Keychain: $_" | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| } elseif ($plat -eq "Windows") { | ||
| try { | ||
| cmdkey /delete:($Key) 2>$null | ||
| if ($LASTEXITCODE -ne 0) { | ||
| throw "Failed to delete key from Credential Manager." | ||
| } | ||
| } catch { | ||
| throw "Error deleting key from Credential Manager: $_" | ||
| } | ||
|
lucasmendes-jc marked this conversation as resolved.
|
||
| } else { | ||
| throw "Unsupported OS." | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| function Request-NewKey() { | ||
| param( | ||
| [Parameter(Mandatory=$false)] | ||
| [string]$sufix_ = ".api.jc" | ||
| ) | ||
| Set-ToVault -Value ( | ||
| [System.Net.NetworkCredential]::new("", (Read-Host -Prompt "Type the api key" -AsSecureString)).Password | ||
| ) -Key ( | ||
| Read-Host -Prompt "Type the name to be saved" | ||
| ) -sufix $sufix_ | ||
| $LinesToClear = $keys.Count + 2 | ||
| Clear-Console -LinesToClear $LinesToClear | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # Aux functions for interactive behavior and vault management | ||
| function Set-ToVault() { | ||
| param( | ||
| [Parameter(Mandatory=$true)] | ||
| [string]$Key, | ||
| [Parameter(Mandatory=$false)] | ||
| [string]$sufix, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious about the choice of suffix here over prefix. For my reference, keys are gathered and parsed to see if they end with some suffix. I think that results in better looking keys as you are selecting them like:
Reference Code: |
||
| [Parameter(Mandatory=$true)] | ||
| [string]$Value | ||
| ) | ||
| if(!$sufix) { | ||
| $sufix = "" | ||
| } | ||
| $key_ = $Key.ToLower().Replace(" ", "_").Trim() | ||
| $plat = $env:CONSOLE_PLATFORM | ||
| $value = $Value.Trim() | ||
| if ($plat -eq "MacOS") { | ||
| try { | ||
| # Not necessary to use same approach as MacOs | ||
| # MacOs is safe to use security command as above | ||
| security add-generic-password -a $env:USER -s ($key_+$sufix) -w $value -T "" 2>$null | ||
| if ($LASTEXITCODE -ne 0) { | ||
| throw "Failed to add key to Keychain." | ||
| } | ||
| } catch { | ||
| throw "Error adding key to Keychain: $_" | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| } elseif ($plat -eq "Windows") { | ||
| try { | ||
| Unlock-Platform | ||
| [CredManager]::SetCreds(($key_ + $sufix), $env:USERNAME, $value) | ||
| } catch { | ||
| throw "Error adding key to Credential Manager: $_" | ||
| } | ||
| } else { | ||
| throw "Unsupported OS." | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| function Unlock-Platform() { | ||
| if([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows)) { | ||
| $plat = "Windows" | ||
| $Definition = @" | ||
| using System; | ||
| using System.ComponentModel; | ||
| using System.Runtime.InteropServices; | ||
| using System.Collections.Generic; | ||
|
|
||
| public class CredManager { | ||
| private const int CRED_TYPE_GENERIC = 1; | ||
| private const int CRED_PERSIST_LOCAL_MACHINE = 2; | ||
|
|
||
| [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | ||
| public static extern bool CredRead(string target, int type, int reservedFlag, out IntPtr credentialPtr); | ||
|
|
||
| [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | ||
| public static extern bool CredWrite(ref PCREDENTIAL userCredential, uint flags); | ||
|
|
||
| [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | ||
| public static extern bool CredEnumerate(string filter, int flag, out int count, out IntPtr pCredentials); | ||
|
|
||
| [DllImport("advapi32.dll", SetLastError = true)] | ||
| public static extern void CredFree(IntPtr pBuffer); | ||
|
|
||
| [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | ||
| public struct PCREDENTIAL { | ||
| public int flags; | ||
| public int type; | ||
| public string targetName; | ||
| public string comment; | ||
| public long lastWritten; | ||
| public int credentialBlobSize; | ||
| public IntPtr credentialBlob; | ||
| public int persist; | ||
| public int attributeCount; | ||
| public IntPtr attributes; | ||
| public string targetAlias; | ||
| public string userName; | ||
| } | ||
|
|
||
| public static string[] GetTargetList() { | ||
| int count; | ||
| IntPtr pCredentials; | ||
| List<string> targets = new List<string>(); | ||
|
|
||
| // Filtro null traz todas as credenciais genéricas (type 1) | ||
| if (CredEnumerate(null, 0, out count, out pCredentials)) { | ||
| for (int i = 0; i < count; i++) { | ||
| // Calcula o endereço de cada item no array de ponteiros | ||
| IntPtr pCurrent = Marshal.ReadIntPtr(pCredentials, i * IntPtr.Size); | ||
| PCREDENTIAL cred = (PCREDENTIAL)Marshal.PtrToStructure(pCurrent, typeof(PCREDENTIAL)); | ||
| targets.Add(cred.targetName); | ||
| } | ||
| CredFree(pCredentials); | ||
| } | ||
| return targets.ToArray(); | ||
| } | ||
|
|
||
| public static string GetCreds(string target) { | ||
| IntPtr credPtr; | ||
| if (CredRead(target, CRED_TYPE_GENERIC, 0, out credPtr)) { | ||
| try { | ||
| PCREDENTIAL cred = (PCREDENTIAL)Marshal.PtrToStructure(credPtr, typeof(PCREDENTIAL)); | ||
| return Marshal.PtrToStringUni(cred.credentialBlob, cred.credentialBlobSize / 2); | ||
| } finally { | ||
| CredFree(credPtr); | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| public static void SetCreds(string target, string userName, string secret) { | ||
| IntPtr blobPtr = Marshal.StringToCoTaskMemUni(secret); | ||
| try { | ||
| PCREDENTIAL cred = new PCREDENTIAL(); | ||
| cred.flags = 0; | ||
| cred.type = CRED_TYPE_GENERIC; | ||
| cred.targetName = target; | ||
| cred.comment = null; | ||
| cred.lastWritten = 0; | ||
| cred.credentialBlobSize = (secret.Length + 1) * 2; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Credential blob size includes null terminator causing authentication failuresHigh Severity
Additional Locations (1)
Reviewed by Cursor Bugbot for commit e5c5a99. Configure here. |
||
| cred.credentialBlob = blobPtr; | ||
| cred.persist = CRED_PERSIST_LOCAL_MACHINE; | ||
| cred.attributeCount = 0; | ||
| cred.attributes = IntPtr.Zero; | ||
| cred.targetAlias = null; | ||
| cred.userName = userName; | ||
|
|
||
| if (!CredWrite(ref cred, 0)) { | ||
| throw new Win32Exception(Marshal.GetLastWin32Error()); | ||
| } | ||
| } finally { | ||
| Marshal.FreeCoTaskMem(blobPtr); | ||
| } | ||
| } | ||
| } | ||
| "@ | ||
| if (-not ("CredManager" -as [type])) { Add-Type -TypeDefinition $Definition -Language CSharp } | ||
| } elseif([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::OSX)) { | ||
| $plat = "MacOS" | ||
| # Will be asked all times, sadly too | ||
| # try { | ||
| # security unlock-keychain | ||
| # } catch { | ||
| # throw "Error unlocking keychain: $_" | ||
| # } | ||
| } elseif([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)) { | ||
| $plat = "Linux" | ||
| } else { | ||
| $plat = "Unknown" | ||
| } | ||
|
|
||
| $env:CONSOLE_PLATFORM = $plat | ||
| } | ||


Uh oh!
There was an error while loading. Please reload this page.