Public/WaykNowDen.ps1
. "$PSScriptRoot/../Private/PlatformHelpers.ps1" . "$PSScriptRoot/../Private/DenHelper.ps1" . "$PSScriptRoot/../Private/JsonHelper.ps1" . "$PSScriptRoot/../Private/Exceptions.ps1" . "$PSScriptRoot/../Private/Base64Url.ps1" . "$PSScriptRoot/../Private/UserAgent.ps1" . "$PSScriptRoot/../Private/RSAHelper.ps1" class WaykDenObject { [string]$DenUrl [string]$Realm [string]$DenID [string]$DenLocalPath [string]$DenGlobalPath } class WaykDenRegistrationObject { [bool]$IsRegistered } function Get-WaykNowDen { [CmdletBinding()] param( [switch]$All ) $WaykNowConfig = Get-WaykNowInfo $DenPath = $WaykNowConfig.DenPath if ((Get-IsWindows) -And (Get-Service "WaykNowService" -ErrorAction SilentlyContinue)) { $DenPath = $WaykNowConfig.DenGlobalPath } $localJson = Get-Content -Path "$DenPath/default.json" -Raw -Encoding UTF8 | ConvertFrom-Json $Realm = $localJson.realm $denJson = Get-Content -Path "$DenPath/$Realm/.state" -Raw -Encoding UTF8 | ConvertFrom-Json $settingJson = Get-Content -Path $WaykNowConfig.GlobalConfigFile -Raw -Encoding UTF8 | ConvertFrom-Json $WaykNowObject = [WaykDenObject]::New() $WaykNowObject.Realm = $Realm $WaykNowObject.DenID = $denJson.denId $WaykNowObject.DenUrl = $settingJson.DenUrl # TODO Remove this one when the Den Url will be always set in the settings file .cfg if (!($WaykNowObject.DenUrl)){ $WaykNowObject.DenUrl = "https://den.wayk.net" } $WaykNowObject.DenLocalPath = Join-Path -Path $WaykNowConfig.DenPath -ChildPath $Realm $WaykNowObject.DenGlobalPath = Join-Path -Path $WaykNowConfig.DenGlobalPath -ChildPath $Realm return $WaykNowObject } function Connect-WaykNowDen { [CmdletBinding()] param( [switch] $Force ) $WaykNowDenObject = Get-WaykNowDen $WaykDenUrl = Format-WaykDenUrl $WaykNowDenObject.DenUrl $WaykDenPath = $WaykNowDenObject.DenLocalPath $WaykDenGlobalPath = $WaykNowDenObject.DenGlobalPath $val = (Invoke-RestMethod -Uri "$WaykDenUrl/.well-known/configuration" -Method 'GET' -ContentType 'application/json') $lucidUrl = $val.lucid_uri $oauthJson = Get-WaykNowDenOauthJson $WaykDenPath #if there is aleady oauthCode in oauth.cfg if ($oauthJson.device_code -AND !($Force)) { $FormPoke = @{ client_id = $val.wayk_client_id device_code = $oauthJson.device_code grant_type = "urn:ietf:params:oauth:grant-type:device_code" } try { $result = Invoke-RestMethod -Uri "$lucidUrl/auth/token" -Method 'POST' -ContentType 'application/x-www-form-urlencoded' -Body $FormPoke $openIdConfig = Invoke-RestMethod -Uri "$lucidUrl/openid/.well-known/openid-configuration" -Method 'GET' -ContentType 'application/json' $access_token = $result.access_token $Header = @{ Authorization = "Bearer " + $access_token Accept = '*/*' } $userInfo = Invoke-RestMethod -Uri $openIdConfig.userinfo_endpoint -Method 'GET' -Headers $Header $name = '' if ($userInfo.name){ $name = $userInfo.name } else { $name = $userInfo.username } Write-Host "`"$name`" is already connected, you can use -Force to force reconnect" } catch { Write-Host "Unknown error $_" Write-Host "Try to use -Force" } } else { # if force, disconnect the current sessions if ($Force){ $_ = Disconnect-WaykNowDen } $Form = @{ client_id = $val.wayk_client_id scope = 'openid profile' auth_type = 'none' } $device_authorization = (Invoke-RestMethod -Uri "$lucidUrl/auth/device-authorization" -Method 'POST' -ContentType 'application/x-www-form-urlencoded' -Body $Form) $verificationUri = $device_authorization.verification_uri $FormPoke = @{ client_id = $val.wayk_client_id device_code = $device_authorization.device_code grant_type = "urn:ietf:params:oauth:grant-type:device_code" } Start-Process $verificationUri -ErrorAction SilentlyContinue $pokeCode = '400' while ($pokeCode -eq '400') { Start-Sleep -Seconds $device_authorization.interval -ErrorAction SilentlyContinue try { $result = Invoke-RestMethod -Uri "$lucidUrl/auth/token" -Method 'POST' -ContentType 'application/x-www-form-urlencoded' -Body $FormPoke $pokeCode = '200' $openIdConfig = Invoke-RestMethod -Uri "$lucidUrl/openid/.well-known/openid-configuration" -Method 'GET' -ContentType 'application/json' $access_token = $result.access_token $Header = @{ Authorization = "Bearer " + $access_token Accept = '*/*' } $userInfo = Invoke-RestMethod -Uri $openIdConfig.userinfo_endpoint -Method 'GET' -Headers $Header $name = '' if ($userInfo.name) { $name = $userInfo.name } else { $name = $userInfo.username } Write-Host "`"$name`" is now connected" } catch [Microsoft.PowerShell.Commands.HttpResponseException] { $pokeCode = $_.Exception.Response.StatusCode.Value__ if (!($pokeCode -eq '400')){ throw $_ } } } $oauthGlobalPath = "$WaykDenGlobalPath/oauth.cfg" $oauthPath = "$WaykDenPath/oauth.cfg" $oauthJson = Set-JsonValue $oauthJson "device_code" $device_authorization.device_code $fileValue = $oauthJson | ConvertTo-Json $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False [System.IO.File]::WriteAllLines($oauthPath , $fileValue, $Utf8NoBomEncoding) [System.IO.File]::WriteAllLines($oauthGlobalPath , $fileValue, $Utf8NoBomEncoding) } } function Disconnect-WaykNowDen { [CmdletBinding()] param() $WaykNowDenObject = Get-WaykNowDen $WaykDenUrl = Format-WaykDenUrl $WaykNowDenObject.DenUrl $WaykDenPath = $WaykNowDenObject.DenLocalPath $WaykDenGlobalPath = $WaykNowDenObject.DenGlobalPath $val = (Invoke-RestMethod -Uri "$WaykDenUrl/.well-known/configuration" -Method 'GET' -ContentType 'application/json') $lucidUrl = $val.lucid_uri $oauthDeviceCodeJson = Get-WaykNowDenOauthJson $WaykDenPath if ($oauthDeviceCodeJson.device_code) { $deviceCode = $oauthDeviceCodeJson.device_code try { $_ = Invoke-RestMethod -Uri "$lucidUrl/auth/device-logout?code=$deviceCode" -Method 'POST' -ContentType 'application/x-www-form-urlencoded' } catch { #Just hide error from here, you can try to disconnect with an device code who not work at all so // miam } $oauthGlobalPath = "$WaykDenGlobalPath/oauth.cfg" $oauthPath = "$WaykDenPath/oauth.cfg" $oauthDeviceCodeJson = Set-JsonValue $oauthDeviceCodeJson "device_code" $null $fileValue = $oauthDeviceCodeJson | ConvertTo-Json $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False [System.IO.File]::WriteAllLines($oauthPath , $fileValue, $Utf8NoBomEncoding) [System.IO.File]::WriteAllLines($oauthGlobalPath , $fileValue, $Utf8NoBomEncoding) } } function Get-WaykNowMachine { [CmdletBinding()] param() $WaykNowDenObject = Get-WaykNowDen $WaykDenUrl = Format-WaykDenUrl $WaykNowDenObject.DenUrl $WaykDenPath = $WaykNowDenObject.DenLocalPath $oauthJson = Get-WaykNowDenOauthJson $WaykDenPath if (!($oauthJson.device_code) -OR ($null -eq $oauthJson.device_code)) { throw (New-Object NotConnectedException) } $val = (Invoke-RestMethod -Uri "$WaykDenUrl/.well-known/configuration" -Method 'GET' -ContentType 'application/json') $lucidUrl = $val.lucid_uri try { $FormPoke = @{ client_id = $val.wayk_client_id device_code = $oauthJson.device_code grant_type = "urn:ietf:params:oauth:grant-type:device_code" } $getToken = Invoke-RestMethod -Uri "$lucidUrl/auth/token" -Method 'POST' -ContentType 'application/x-www-form-urlencoded' -Body $FormPoke $access_token = $getToken.access_token } catch { Write-Host "Unknown error $_" Write-Host "Try Connect-WaykNowDen -Force" return; } $Header = @{ Authorization = "Bearer " + $access_token } $machineResult = Invoke-RestMethod -Uri "$WaykDenUrl/machine" -Method 'GET' -ContentType 'application/json' -Headers $Header $MachineReport = @() foreach ($machine in $machineResult) { $PSObject = New-Object PSObject -Property @{ UserAgent = $machine.user_agent MachineName = $machine.machine_name DenID = $machine.den_id State = $machine.state } $MachineReport += $PSObject } return $MachineReport | Format-Table MachineName, DenID, State, UserAgent } function Register-WaykNowMachine { [CmdletBinding()] param() if (!(Get-IsWindows)){ throw (New-Object UnsupportedPlatformException("Windows")) } if (!(Get-Service "WaykNowService" -ErrorAction SilentlyContinue)) { throw (New-Object UnattendedNotFound) } $WaykNowDenRegistered = Get-WaykNowDenRegistration if ($WaykNowDenRegistered.IsRegistered){ throw "This machine is already registered" } $WaykNowUniqueID = Get-WaykNowUniqueID $WaykNowDenObject = Get-WaykNowDen $WaykDenPath = $WaykNowDenObject.DenGlobalPath $WaykDenUrl = Format-WaykDenUrl $WaykNowDenObject.DenUrl $DenGlobalPath = $WaykNowDenObject.DenGlobalPath $oauthJson = Get-WaykNowDenOauthJson $WaykDenPath # 5 : You are connected with Lucid if (!($oauthJson.device_code) -OR ($null -eq $oauthJson.device_code)){ throw (New-Object NotConnectedException) } # 6 : Get the AccessToken $val = (Invoke-RestMethod -Uri "$WaykDenUrl/.well-known/configuration" -Method 'GET' -ContentType 'application/json') $lucidUrl = $val.lucid_uri try { $FormPoke = @{ client_id = $val.wayk_client_id device_code = $oauthJson.device_code grant_type = "urn:ietf:params:oauth:grant-type:device_code" } $getToken = Invoke-RestMethod -Uri "$lucidUrl/auth/token" -Method 'POST' -ContentType 'application/x-www-form-urlencoded' -Body $FormPoke $access_token = $getToken.access_token } catch { Write-Host "Unknown error $_" Write-Host "Try Connect-WaykNowDen -Force" return; } # 7 : Get the CA Chain and test it $intermediateAuthority = "CN=" + $WaykNowDenObject.Realm + " Authority" $rootAuthority = "CN=" + $WaykNowDenObject.Realm + " Root CA" #Get Ca Chain from Den $contentsDen = Invoke-RestMethod -Uri "$WaykDenUrl/pki/chain" -Method 'GET' -ContentType 'text/plain' $ca_chain_from_den = @() $contentsDen | Select-String -Pattern '(?smi)^-{2,}BEGIN CERTIFICATE-{2,}.*?-{2,}END CERTIFICATE-{2,}' ` -Allmatches | ForEach-Object {$_.Matches} | ForEach-Object { $ca_chain_from_den += $_.Value } if (!($ca_chain_from_den.Count -eq 2)){ throw "Incorrect Wayk Den CA Chain" } $tempDirectory = New-TemporaryDirectory $DenChainPem = "$DenGlobalPath/$WaykNowUniqueID-ca-chain.pem" $DenIntermediateCa = "$tempDirectory/intermediate_ca.pem" $DenRootCa = "$tempDirectory/root_ca.pem" $Utf8NoBomEncoding = [System.Text.UTF8Encoding]::new($False) [System.IO.File]::WriteAllLines($DenChainPem, $contentsDen, $Utf8NoBomEncoding) [System.IO.File]::WriteAllLines($DenIntermediateCa, $ca_chain_from_den[0], $Utf8NoBomEncoding) [System.IO.File]::WriteAllLines($DenRootCa, $ca_chain_from_den[1], $Utf8NoBomEncoding) $intermediate_ca = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("$DenIntermediateCa") $root_ca = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("$DenRootCa") # Check the subject and the Isuer are the same if (!(($intermediate_ca.Subject -eq $intermediateAuthority) -AND ($intermediate_ca.Issuer -eq $rootAuthority))){ Write-Host "intermediate Subject: " + $intermediate_ca.Subject + "Must be $intermediateAuthority" Write-Host "intermediate issuer: " + $intermediate_ca.Issuer + "Must be $rootAuthority" throw "Incorrect intermediate Chain" } if (!(($root_ca.Subject -eq $rootAuthority) -AND ($root_ca.Issuer -eq $rootAuthority))){ Write-Host "root Subject: " + $root_ca.Subject + "Must be $rootAuthority" Write-Host "root issuer: " + $root_ca.Issuer+ "Must be $rootAuthority" throw "Incorrect Root Chain" } # 8 : Create CSR $key_size = 2048 $subject = "CN=$WaykNowUniqueID" $rsa_key = [System.Security.Cryptography.RSA]::Create($key_size) $certRequest = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new( $subject, $rsa_key, [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1) $csr_der = $certRequest.CreateSigningRequest() $sb = [System.Text.StringBuilder]::new() $csr_base64 = [Convert]::ToBase64String($csr_der) $offset = 0 $line_length = 64 [void]$sb.AppendLine("-----BEGIN CERTIFICATE REQUEST-----") while ($offset -lt $csr_base64.Length) { $line_end = [Math]::Min($offset + $line_length, $csr_base64.Length) [void]$sb.AppendLine($csr_base64.Substring($offset, $line_end - $offset)) $offset = $line_end } [void]$sb.AppendLine("-----END CERTIFICATE REQUEST-----") $csr_pem = $sb.ToString() $RSAParams = $rsa_key.ExportParameters($true); $privateKey = ExportPrivateKeyFromRSA $RSAParams $privateKey = $privateKey -Replace "`r`n", "`n" $DenCertificatePath = "$DenGlobalPath/$WaykNowUniqueID.crt" $DenKeyPath = "$DenGlobalPath/$WaykNowUniqueID.key" $DenCsrPath = "$DenGlobalPath/$WaykNowUniqueID.csr" [System.IO.File]::WriteAllLines($DenCsrPath, $csr_pem, $Utf8NoBomEncoding) [System.IO.File]::WriteAllLines($DenKeyPath, $privateKey, $Utf8NoBomEncoding) $csr = Get-Content "$DenCsrPath" | Out-String $csr = $csr -Replace "`r`n", "`n" # 9 : Sign CSR to Den $WaykNowUseAgent = Get-WaykNowUserAgent $ComputerName = $env:computername $headers = @{ Authorization = "Bearer " + $access_token } $payload = [PSCustomObject]@{ csr=$csr machine_id=[string]$WaykNowUniqueID machine_name=$ComputerName user_agent=$WaykNowUseAgent } | ConvertTo-Json $cert = Invoke-RestMethod -Uri "$WaykDenUrl/machine" -Method 'POST' -Headers $headers -ContentType 'application/json' -Body $payload if (!($cert.certificate)){ throw "Error with signed CSR" } [System.IO.File]::WriteAllLines($DenCertificatePath, $cert.certificate, $Utf8NoBomEncoding) $leaf_cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("$DenCertificatePath") # 10 : Check if CSR signed is correct if (!(($leaf_cert.Subject -eq "$subject") -AND ($leaf_cert.Issuer -eq $intermediateAuthority))){ Write-Host "Subject:" $leaf_cert.Subject Write-Host "Authority: " $leaf_cert.Issuer throw "Incorrect signature on certificate" } #11 load the chain and validate the leaf $chain = [System.Security.Cryptography.X509Certificates.X509Chain]::new() $chain.ChainPolicy.RevocationMode = [System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck [void]$chain.ChainPolicy.ExtraStore.Add($root_ca) [void]$chain.ChainPolicy.ExtraStore.Add($intermediate_ca) [void]$chain.Build($leaf_cert) $storeContainsCertficate = $chain.ChainPolicy.ExtraStore.Contains($chain.ChainElements[$chain.ChainElements.Count -1].Certificate) $IsUntrustedRoot = ([Linq.Enumerable]::First($chain.ChainStatus)).Status -eq [System.Security.Cryptography.X509Certificates.X509ChainStatusFlags]::UntrustedRoot if (($chain.ChainStatus.Length -gt 0) ` -AND ($IsUntrustedRoot)` -AND ($storeContainsCertficate)) { $isValidCertificate = $true } Remove-Item -Path $tempDirectory -Force -Recurse if (!($isValidCertificate )){ $_ = Unregister-WaykNowMachine throw "Invalid Certificate Chain" } return "Machine Registered: " + $WaykNowUniqueID } function Unregister-WaykNowMachine { [CmdletBinding()] param() if (!(Get-IsWindows)){ throw (New-Object UnsupportedPlatformException("Windows")) } if (!(Get-Service "WaykNowService" -ErrorAction SilentlyContinue)) { throw (New-Object UnattendedNotFound) } $WaykNowDenRegistered = Get-WaykNowDenRegistration if (!($WaykNowDenRegistered.IsRegistered)){ throw "This machine is not registered" } $WaykNowUniqueID = Get-WaykNowUniqueID $WaykNowDenObject = Get-WaykNowDen $WaykDenPath = $WaykNowDenObject.DenGlobalPath $WaykDenUrl = Format-WaykDenUrl $WaykNowDenObject.DenUrl $DenGlobalPath = $WaykNowDenObject.DenGlobalPath $oauthJson = Get-WaykNowDenOauthJson $WaykDenPath # 5 : You are connected with Lucid if (!($oauthJson.device_code) -OR ($null -eq $oauthJson.device_code)){ throw (New-Object NotConnectedException) } # 6 : Get the AccessToken $val = (Invoke-RestMethod -Uri "$WaykDenUrl/.well-known/configuration" -Method 'GET' -ContentType 'application/json') $lucidUrl = $val.lucid_uri try { $FormPoke = @{ client_id = $val.wayk_client_id device_code = $oauthJson.device_code grant_type = "urn:ietf:params:oauth:grant-type:device_code" } $getToken = Invoke-RestMethod -Uri "$lucidUrl/auth/token" -Method 'POST' -ContentType 'application/x-www-form-urlencoded' -Body $FormPoke $access_token = $getToken.access_token } catch { Write-Host "Unknown error $_" Write-Host "Try Connect-WaykNowDen -Force" return; } $headers = @{ Authorization = "Bearer " + $access_token } #7 Remove Machine, and certificats files Invoke-RestMethod -Uri "$WaykDenUrl/machine/$WaykNowUniqueID" -Method 'DELETE' -Headers $headers -ContentType 'application/json' $_ = Remove-WaykNowMachineCertificate $DenGlobalPath } function Remove-WaykNowMachineCertificate { [CmdletBinding()] param( [string] $DenGlobalPath ) $ListItem = Get-ChildItem -Path $DenGlobalPath $waykNowCRT = '[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}.crt' $waykNowCSR = '[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}.csr' $waykNowKEY = '[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}.key' $waykNowCaChainPEM = '[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}-ca-chain.pem' foreach ($item in $ListItem) { $path = "$DenGlobalPath\$item" if ($item -CMatch $waykNowCRT) { Remove-Item -Path $path -Force -Recurse continue; } if ($item -CMatch $waykNowCSR) { Remove-Item -Path $path -Force -Recurse continue; } if ($item -CMatch $waykNowKEY) { Remove-Item -Path $path -Force -Recurse continue; } if ($item -CMatch $waykNowCaChainPEM) { Remove-Item -Path $path -Force -Recurse continue; } } } function Get-WaykNowDenRegistration { [CmdletBinding()] param() $WaykNowDenObject = Get-WaykNowDen $WaykNowDenRegistration = [WaykDenRegistrationObject]::new(); $WaykNowDenRegistration.IsRegistered = $false; $DenGlobalPath = $WaykNowDenObject.DenGlobalPath $ListItem = Get-ChildItem -Path $DenGlobalPath $waykNowUniqueIDPattern = '[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}.crt' foreach ($item in $ListItem) { if ($item -CMatch $waykNowUniqueIDPattern) { #if file .crt is empty, just continue If ($Null -eq (Get-Content "$DenGlobalPath/$item")) { continue } $WaykNowDenRegistration.IsRegistered = $true; break; } } return $WaykNowDenRegistration; } Export-ModuleMember -Function Get-WaykNowDen, Connect-WaykNowDen, Disconnect-WaykNowDen, Get-WaykNowMachine, Register-WaykNowMachine, Unregister-WaykNowMachine, Get-WaykNowDenRegistration |