Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions PowerShell/JumpCloud Module/Private/Console/Clear-Console.ps1
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)
}
23 changes: 23 additions & 0 deletions PowerShell/JumpCloud Module/Private/Console/Confim-Console.ps1
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 }
Comment thread
lucasmendes-jc marked this conversation as resolved.
}
[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]
}
22 changes: 22 additions & 0 deletions PowerShell/JumpCloud Module/Private/Vault/Get-KeyFromVault.ps1
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
Comment thread
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
}
30 changes: 30 additions & 0 deletions PowerShell/JumpCloud Module/Private/Vault/Get-VaultKeys.ps1
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
}
30 changes: 30 additions & 0 deletions PowerShell/JumpCloud Module/Private/Vault/Remove-FromVault.ps1
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: $_"
}
Comment thread
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: $_"
}
Comment thread
lucasmendes-jc marked this conversation as resolved.
} else {
throw "Unsupported OS."
}
}
13 changes: 13 additions & 0 deletions PowerShell/JumpCloud Module/Private/Vault/Request-NewKey.ps1
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
}
Comment thread
cursor[bot] marked this conversation as resolved.
38 changes: 38 additions & 0 deletions PowerShell/JumpCloud Module/Private/Vault/Set-ToVault.ps1
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,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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:

  • OrgName.api.jc
  • AnotherOrgName.api.jc
  • LastOrg.serviceTotken.jc (assuming we add support for service token credentialing later)

Reference Code:
https://github.com/TheJumpCloud/support/pull/744/changes#diff-2283fb1a2772f5d917313a140539f71ffb3860db63e32f4d1c0b4e1fec3549a1R11
https://github.com/TheJumpCloud/support/pull/744/changes#diff-2283fb1a2772f5d917313a140539f71ffb3860db63e32f4d1c0b4e1fec3549a1R20

[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: $_"
}
Comment thread
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."
}
}
115 changes: 115 additions & 0 deletions PowerShell/JumpCloud Module/Private/Vault/Unlock-Platform.ps1
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;
}
Comment thread
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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Credential blob size includes null terminator causing authentication failures

High Severity

SetCreds sets credentialBlobSize to (secret.Length + 1) * 2, which includes the null terminator in the stored byte count. When GetCreds later reads the credential using Marshal.PtrToStringUni(cred.credentialBlob, cred.credentialBlobSize / 2), the length-based overload copies exactly that many characters — including the null terminator as a real character in the C# string. The returned API key will have a trailing \0 appended, causing it to differ from the original secret and likely causing authentication failures.

Additional Locations (1)
Fix in Cursor Fix in Web

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
}
Loading