Skip to content

Commit ef8185d

Browse files
authored
!deploy v0.2.0 - Added New-RSAKeyPair (#4)
## 0.2.0 - 2019-08-10 * Added `New-RSAKeyPair` to enable generation of RSA PEM and SSH keys directly from PowerShell * Supports password protection keys * Defaults to 4096 key length * Offers an Interactive mode using the `-Interactive` or `-i` switch to simulate `ssh-keygen` experience * Updated README with command comparisons between `openssl`, `ssh-keygen` and `New-RSAKeyPair`
2 parents 9590c79 + 905e4b0 commit ef8185d

File tree

13 files changed

+489
-28
lines changed

13 files changed

+489
-28
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,5 @@ MigrationBackup/
353353

354354
# Repo specific
355355
BuildOutput
356+
Testing
356357
!/PEMEncrypt/bin

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
* [PEMEncrypt - ChangeLog](#PEMEncrypt---ChangeLog)
1+
* [PEMEncrypt - ChangeLog](#pemencrypt---changelog)
2+
* [0.2.0 - 2019-08-10](#020---2019-08-10)
23
* [0.1.1 - 2019-07-07](#011---2019-07-07)
34
* [0.1.0 - 2019-07-07](#010---2019-07-07)
45

56
***
67

78
# PEMEncrypt - ChangeLog
89

10+
## 0.2.0 - 2019-08-10
11+
12+
* Added `New-RSAKeyPair` to enable generation of RSA PEM and SSH keys directly from PowerShell
13+
* Supports password protection keys
14+
* Defaults to 4096 key length
15+
* Offers an Interactive mode using the `-Interactive` or `-i` switch to simulate `ssh-keygen` experience
16+
* Updated README with command comparisons between `openssl`, `ssh-keygen` and `New-RSAKeyPair`
17+
918
## 0.1.1 - 2019-07-07
1019

1120
* Added CHANGELOG, CODE_OF_CONDUCT, CONTRIBUTING docs

PEMEncrypt/PEMEncrypt.psd1

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
RootModule = 'PEMEncrypt.psm1'
1313

1414
# Version number of this module.
15-
ModuleVersion = '0.1.1'
15+
ModuleVersion = '0.2.0'
1616

1717
# Supported PSEditions
1818
CompatiblePSEditions = @('Desktop','Core')
@@ -35,6 +35,7 @@ Description = 'A cross-platform PowerShell module handling string encryption and
3535
# Minimum version of the PowerShell engine required by this module
3636
PowerShellVersion = '5.1.0'
3737

38+
3839
# Name of the PowerShell host required by this module
3940
# PowerShellHostName = ''
4041

@@ -78,7 +79,7 @@ FunctionsToExport = '*'
7879
# VariablesToExport = '*'
7980

8081
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
81-
# AliasesToExport = @()
82+
AliasesToExport = @('genrsa','genssh','genkey')
8283

8384
# DSC resources to export from this module
8485
# DscResourcesToExport = @()
@@ -96,7 +97,7 @@ PrivateData = @{
9697

9798
# Tags applied to this module. These help with module discovery in online galleries.
9899
Tags = @('PSEdition_Desktop', 'PSEdition_Core', 'Windows', 'MacOS', 'Linux', 'RSA',
99-
'Crypto', 'Encrypt', 'Decrypt', 'PEM', 'Security')
100+
'Crypto', 'Encrypt', 'Decrypt', 'PEM', 'ssh-keygen', 'openssl', 'SSH', 'Security')
100101

101102
# A URL to the license for this module.
102103
# LicenseUri = ''

PEMEncrypt/PEMEncrypt.psm1

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
# Base module contents
1+
New-Alias -Name 'genrsa' -Value 'New-RSAKeyPair'
2+
New-Alias -Name 'genssh' -Value 'New-RSAKeyPair'
3+
New-Alias -Name 'genkey' -Value 'New-RSAKeyPair'
4+
Export-ModuleMember -Alias @('genrsa','genssh','genkey')
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function Get-DefaultPath {
2+
[CmdletBinding()]
3+
Param()
4+
Process {
5+
$homePath = if ($HOME) {
6+
$HOME
7+
}
8+
elseif (Test-Path "~") {
9+
(Resolve-Path "~").Path
10+
}
11+
[System.IO.Path]::Combine($homePath,".ssh","id_rsa")
12+
}
13+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
function Unprotect-SecureString {
2+
[CmdletBinding()]
3+
Param(
4+
[Parameter(Mandatory,Position = 0)]
5+
[SecureString]
6+
$SecureString
7+
)
8+
Process {
9+
[Runtime.InteropServices.Marshal]::PtrToStringAuto(
10+
[Runtime.InteropServices.Marshal]::SecureStringToBSTR(
11+
$SecureString
12+
)
13+
)
14+
}
15+
}
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
function New-RSAKeyPair {
2+
<#
3+
.SYNOPSIS
4+
Generates an RSA PEM key pair and corresponding public SSH key.
5+
6+
.DESCRIPTION
7+
Generates an RSA PEM key pair and corresponding public SSH key.
8+
9+
.PARAMETER Length
10+
Alias: [-l,-b]
11+
The bit-length of the key to generate. Defaults to 4096.
12+
13+
.PARAMETER Password
14+
Alias: -p
15+
A SecureString or plain-text String containing the password to encrypt the private key with. Exclude to create the private key without a password. For security, SecureString is recommended, although plain-text strings are allowed for broader compatibility.
16+
17+
.PARAMETER Path
18+
Alias: -out
19+
The path to save the private key to. Defaults to ~/.ssh/id_rsa
20+
21+
.PARAMETER Interactive
22+
Alias: -i
23+
If $true, prompt the user for the options to create the key with. Similar to creating a key with ssh-keygen.
24+
25+
.PARAMETER NoFile
26+
Alias: -nof
27+
If $true, do not save any keys to file. Sets PassThru to $true and returns the generated RSAKey object containing the PublicPEM, PublicSSH, and PrivatePEM as string properties.
28+
29+
.PARAMETER NoSSH
30+
Alias: -nos
31+
If $true, do not save the SSH key to file. Use when only the RSA PEM key pair is needed.
32+
33+
.PARAMETER NoPEM
34+
Alias: -nop
35+
If $true, do not save the Public PEM key to file. Use when only the SSH key pair is needed.
36+
37+
.PARAMETER PassThru
38+
Alias: -pt
39+
Returns the generated RSAKey object containing the PublicPEM, PublicSSH, and PrivatePEM as string properties.
40+
41+
.PARAMETER Force
42+
Alias: -f
43+
If the keys at the file path already exist, overwrite them.
44+
45+
.EXAMPLE
46+
New-RSAKeyPair -Interactive
47+
48+
Generating public/private RSA key pair...
49+
Enter the path to save the key to (Default: C:\Users\nate\.ssh\id_rsa): .\Testing\id_pemencrypt
50+
Enter desired key length (Default: 4096):
51+
Enter passphrase (Default: No passphrase):
52+
Saving private key to path : .\Testing\id_pemencrypt
53+
Saving public SSH key to path : .\Testing\id_pemencrypt.pub
54+
Saving public PEM key to path : .\Testing\id_pemencrypt.pem
55+
56+
.EXAMPLE
57+
New-RSAKeyPair -NoFile
58+
59+
PublicPEM
60+
---------
61+
-----BEGIN PUBLIC KEY-----...
62+
63+
.EXAMPLE
64+
New-RSAKeyPair -Length 1024 -NoFile | Select-Object -ExpandProperty PublicSSH
65+
ssh-rsa AAAAB3NzaC1yc2EAAAABAwAAAIEAo2CDoZRSy7JDJbX3ygsj3L09rMxq+46lMkWv6K33Cng3y4DokqqyUc2KCzhspBViGzVl3mJ+Y4S9O+D4bktcSDRZbEmZ0cVsFZFEAI17iEKnZHZnaqMIoIzaK2TS0rnQbkYpSDfKUAZtwSNiWB0TfMFdnOY6UJdlfLGzPeFJWTU= PEMEncrypt@User@Computer
66+
67+
.EXAMPLE
68+
New-RSAKeyPair
69+
Key already exists at desired path: C:\Users\nate\.ssh\id_rsa. Use -Force to overwrite the existing key or choose a different path.
70+
At E:\Git\PEMEncrypt\BuildOutput\PEMEncrypt\0.2.0\PEMEncrypt.psm1:177 char:21
71+
+ ... throw "Key already exists at desired path: $Path. Use -Fo ...
72+
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
73+
+ CategoryInfo : OperationStopped: (Key already exists \u2026e a different path.:String) [], RuntimeException
74+
+ FullyQualifiedErrorId : Key already exists at desired path: C:\Users\nate\.ssh\id_rsa. Use -Force to overwrite the existing key or choose a different path.
75+
#>
76+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")]
77+
[OutputType('SCRTHQ.PEMEncrypt.RSAKey')]
78+
[CmdletBinding()]
79+
Param(
80+
[Parameter(Position = 0)]
81+
[Alias('l','b')]
82+
[Int]
83+
$Length = 4096,
84+
[parameter()]
85+
[Alias('p')]
86+
[Object]
87+
$Password,
88+
[parameter()]
89+
[Alias('out')]
90+
[String]
91+
$Path = (Get-DefaultPath),
92+
[Parameter()]
93+
[Alias('i')]
94+
[Switch]
95+
$Interactive,
96+
[Parameter()]
97+
[Alias('nof')]
98+
[Switch]
99+
$NoFile,
100+
[Parameter()]
101+
[Alias('nos')]
102+
[Switch]
103+
$NoSSH,
104+
[Parameter()]
105+
[Alias('nop')]
106+
[Switch]
107+
$NoPEM,
108+
[Parameter()]
109+
[Alias('pt')]
110+
[Switch]
111+
$PassThru,
112+
[Parameter()]
113+
[Alias('f')]
114+
[Switch]
115+
$Force
116+
)
117+
Begin {
118+
Import-Assemblies
119+
if ($MyInvocation.InvocationName -eq 'genrsa') {
120+
$NoPEM = $false
121+
$NoSSH = $true
122+
}
123+
if ($MyInvocation.InvocationName -eq 'genssh') {
124+
$NoSSH = $false
125+
$NoPEM = $true
126+
}
127+
}
128+
Process {
129+
if ($Interactive) {
130+
Write-Host "Generating public/private RSA key pair..."
131+
if (-not $NoFile) {
132+
$newPath = if ($choice = Read-Host -Prompt "Enter the path to save the key to (Default: $Path)") {
133+
$choice
134+
}
135+
else {
136+
$Path
137+
}
138+
if (-not $Force -and (Test-Path $newPath)) {
139+
throw "Key already exists at desired path: $newPath. Use -Force to overwrite the existing key or choose a different path"
140+
}
141+
}
142+
$Length = if ($choice = Read-Host -Prompt "Enter desired key bit length (Default: 4096)") {
143+
$choice
144+
}
145+
else {
146+
4096
147+
}
148+
$Password = Read-Host -AsSecureString -Prompt "Enter passphrase (Default: No passphrase)"
149+
if (-not ([System.String]::IsNullOrEmpty((Unprotect-SecureString -SecureString $Password)))) {
150+
$confirmed = Read-Host -AsSecureString -Prompt "Enter the same passphrase to confirm"
151+
if ((Unprotect-SecureString -SecureString $confirmed) -ne (Unprotect-SecureString -SecureString $Password)) {
152+
Write-Warning "Passphrases provided do not match! Exiting"
153+
throw
154+
}
155+
$keys = [SCRTHQ.PEMEncrypt.RSA]::Generate(
156+
$Length,
157+
(Unprotect-SecureString -SecureString $Password)
158+
)
159+
}
160+
else {
161+
$keys = [SCRTHQ.PEMEncrypt.RSA]::Generate(
162+
$Length
163+
)
164+
}
165+
if (-not $NoFile) {
166+
Write-Host "Saving private key to path : $newPath"
167+
$keys.PrivatePEM | Set-Content -Path $newPath -Force
168+
if (-not $NoSSH) {
169+
$sshPath = "{0}.pub" -f $newPath
170+
Write-Host "Saving public SSH key to path : $sshPath"
171+
$keys.PublicSSH | Set-Content -Path $sshPath -Force
172+
}
173+
if (-not $NoPEM) {
174+
$pemPath = "{0}.pem" -f $newPath
175+
Write-Host "Saving public PEM key to path : $pemPath"
176+
$keys.PublicPEM | Set-Content -Path $pemPath -Force
177+
}
178+
}
179+
if ($PassThru -or $NoFile) {
180+
$keys
181+
}
182+
}
183+
else {
184+
$keys = if ($PSBoundParameters.ContainsKey('Password')) {
185+
[SCRTHQ.PEMEncrypt.RSA]::Generate(
186+
$Length,
187+
$(if($Password -is [SecureString]){(Unprotect-SecureString -SecureString $Password)}else{"$Password"})
188+
)
189+
}
190+
else {
191+
[SCRTHQ.PEMEncrypt.RSA]::Generate(
192+
$Length
193+
)
194+
}
195+
if (-not $NoFile) {
196+
if (-not $Force -and (Test-Path $Path)) {
197+
throw "Key already exists at desired path: $Path. Use -Force to overwrite the existing key or choose a different path."
198+
}
199+
else {
200+
Write-Host "Saving private key to path : $Path"
201+
$keys.PrivatePEM | Set-Content -Path $Path -Force
202+
if (-not $NoSSH) {
203+
$sshPath = "{0}.pub" -f $Path
204+
Write-Host "Saving public SSH key to path : $sshPath"
205+
$keys.PublicSSH | Set-Content -Path $sshPath -Force
206+
}
207+
if (-not $NoPEM) {
208+
$pemPath = "{0}.pem" -f $Path
209+
Write-Host "Saving public PEM key to path : $pemPath"
210+
$keys.PublicPEM | Set-Content -Path $pemPath -Force
211+
}
212+
}
213+
}
214+
if ($PassThru -or $NoFile) {
215+
$keys
216+
}
217+
}
218+
}
219+
}

PEMEncrypt/Public/Unprotect-PEMString.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function Unprotect-PEMString {
6363
[SCRTHQ.PEMEncrypt.Crypto]::Decrypt(
6464
$string,
6565
$PrivateKey,
66-
(New-Object PSCredential 'user',$Password).GetNetworkCredential().Password
66+
(Unprotect-SecureString -SecureString $Password)
6767
)
6868
}
6969
else {

0 commit comments

Comments
 (0)