Skip to content

Commit 9623823

Browse files
committed
FEAT: added ppk (PuTTY Private Key) codec (so far only RSA keys)
1 parent 2b23527 commit 9623823

7 files changed

+212
-4
lines changed

src/mezz/boot-files.r

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ REBOL [
5656
%codec-pkix.r
5757
%codec-der.r
5858
%codec-crt.r
59+
%codec-ppk.r
5960
%codec-ssh-key.r
6061
;- compression
6162
%codec-gzip.r

src/mezz/codec-ppk.r

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
REBOL [
2+
Title: "REBOL 3 codec: PuTTY Private Key"
3+
Author: "Oldes"
4+
Rights: "Copyright (C) 2020 Oldes. All rights reserved."
5+
License: "BSD-3"
6+
Test: %tests/units/rsa-test.r3
7+
Note: {
8+
* it extract (and inits) only RSA keys so far
9+
}
10+
]
11+
12+
register-codec [
13+
name: 'ppk
14+
title: "PuTTY Private Key"
15+
suffixes: [%.ppk]
16+
17+
decode: function [
18+
"Decodes PuTTY key file"
19+
data [binary! string! file!]
20+
/password pass [string! binary!] "Optional password for encrypted keys"
21+
/local type encr comm line pmac
22+
;return: [handle! none!] "RSA private key handle on success"
23+
][
24+
if file? data [data: read/string data]
25+
if binary? data [data: to string! data]
26+
sp: charset " ^-^/^M"
27+
!sp: complement sp
28+
!crlf: complement charset "^M^/"
29+
try/except [
30+
parse data [
31+
"PuTTY-User-Key-File-" ["1:" (vers: 1) | "2:" (vers: 2)]
32+
any sp copy type some !sp some sp
33+
"Encryption:"
34+
any sp copy encr some !sp some sp
35+
"Comment: "
36+
any sp copy comm some !crlf some sp
37+
"Public-Lines:"
38+
any sp copy num some !sp some sp
39+
(
40+
num: to integer! to string! num
41+
pub: make binary! 64 * num
42+
)
43+
num [ copy line any !crlf some sp (append pub line) ]
44+
"Private-Lines:"
45+
any sp copy num some !sp some sp
46+
(
47+
num: to integer! to string! num
48+
pri: make binary! 64 * num
49+
)
50+
num [ copy line any !crlf some sp (append pri line) ]
51+
["Private-MAC:" (mac?: true) | "Private-Hash:" (mac?: false)]
52+
any sp copy pmac some !sp any sp
53+
54+
|
55+
56+
"---- BEGIN SSH2 PUBLIC KEY ----" to end (
57+
return codecs/ssh-key/decode/password data pass
58+
)
59+
60+
]
61+
pub: debase pub 64
62+
pri: debase pri 64
63+
64+
if encr = "aes256-cbc" [
65+
try/except [
66+
pass: either password [copy pass][
67+
ask/hide ajoin ["Key password for " mold comm ": "]
68+
]
69+
key: join checksum/secure join #{00000000} pass
70+
checksum/secure join #{00000001} pass
71+
key: aes/decrypt/key copy/part key 32 none
72+
pri: aes/decrypt/stream key pri
73+
][
74+
;clean pass data in memory
75+
forall pass [pass/1: random 255]
76+
print "Failed to decrypt private key!"
77+
return none
78+
]
79+
]
80+
81+
macdata: either vers = 1 [ pri ][
82+
select binary/write 800 [
83+
UI32BYTES :type
84+
UI32BYTES :encr
85+
UI32BYTES :comm
86+
UI32BYTES :pub
87+
UI32BYTES :pri
88+
] 'buffer
89+
]
90+
mackey: checksum/secure join "putty-private-key-file-mac-key" any [pass ""]
91+
if pass [forall pass [pass/1: random 255]]
92+
if pmac <> form either mac? [
93+
checksum/secure/key macdata mackey
94+
][ checksum/secure macdata ] [
95+
print either key ["Wrong password!"]["MAC failed!"]
96+
return none
97+
]
98+
binary/read pub [
99+
t: UI32BYTES
100+
e: UI32BYTES ;exponent
101+
n: UI32BYTES ;modulus
102+
]
103+
binary/read pri [
104+
d: UI32BYTES
105+
p: UI32BYTES
106+
q: UI32BYTES
107+
i: UI32BYTES
108+
]
109+
if "ssh-rsa" = to string! t [
110+
return rsa-init/private n e d p q none none i
111+
]
112+
][
113+
print system/state/last-error
114+
]
115+
none
116+
]
117+
]

src/mezz/codec-ssh-key.r

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
REBOL [
22
Title: "REBOL 3 codec: Secure Shell Key"
33
Author: "Oldes"
4-
Rights: "Copyright (C) 2018 Oldes. All rights reserved."
4+
Rights: "Copyright (C) 2020 Oldes. All rights reserved."
55
License: "BSD-3"
66
Test: %tests/units/crypt-test.r3
77
Note: {
@@ -35,7 +35,7 @@ wrap [
3535
decode: function [
3636
"Decodes and initilize SSH key"
3737
key [binary! string! file!]
38-
/password p [string! binary!] "Optional password"
38+
/password p [string! binary! none!] "Optional password"
3939
][
4040
case [
4141
file? key [ key: read key ]
@@ -59,7 +59,7 @@ wrap [
5959
"AES-128-CBC" #"," copy iv to end
6060
]
6161
iv: debase iv 16
62-
unless password [p: ask/hide "Pasword: "]
62+
unless p [p: ask/hide "Pasword: "]
6363
p: checksum/method
6464
join to binary! p copy/part iv 8
6565
'md5

src/tests/units/crypt-test.r3

+17-1
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,24 @@ sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV
221221

222222

223223
===start-group=== "SSH-key codec"
224-
--test-- "Init RSA key from file"
224+
--test-- "Init RSA key from file"
225225
--assert handle? try [key: decode 'ssh-key read %units/files/rebol-public.ppk]
226226
rsa key none ; release it, as it is not GCed yet.
227+
===end-group===
228+
229+
===start-group=== "PPK codec"
230+
--test-- "Init RSA key from PPK file"
231+
--assert handle? try [key: load %units/files/rebol-private-no-pass.ppk]
232+
rsa key none ; release it, as it is not GCed yet.
233+
234+
--test-- "Init RSA key from encrypted PPK file"
235+
--assert handle? try [key: codecs/ppk/decode/password %units/files/rebol-private.ppk "Rebol"]
236+
rsa key none ; release it, as it is not GCed yet.
237+
238+
--test-- "Init public RSA key from SSH2 PPK file"
239+
--assert handle? try [key: load %units/files/rebol-public.ppk]
240+
rsa key none ; release it, as it is not GCed yet.
241+
===end-group===
242+
227243

228244
~~~end-file~~~
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
PuTTY-User-Key-File-2: ssh-rsa
2+
Encryption: none
3+
Comment: rsa-key-rebol-test
4+
Public-Lines: 6
5+
AAAAB3NzaC1yc2EAAAABJQAAAQEA8mjwC6ZCwpQCnDXqU7g2tyvMFoWpJV+myfBz
6+
9zbhw7rHxUmFubMtEwCKQhYccQxbPCcWu/KIg5TOhmcf9RK1xIuUrOUiUdM8uOwR
7+
S+e5kitTUgux/wjyMNlpK5laIS1hFHiFhCxecN7Won3bsPDMm5Wi3dBMv1+1jK1r
8+
lDtJYxDDcJE2T59m1UI4AN/Pm7dndr11yCmUdKy6VACf5V7u4OX5uC3fsTEuCV1P
9+
2WwLBqtZMYG3jjzuRTana+s8n2TXk5D9lXoPMc7Tj4/aSw50UUAYhmVpie/8vtVw
10+
A7MhOCA4us6q3+Mx07vq8EmzbLOGtP9taXQkDF6JR6wY4IEXBw==
11+
Private-Lines: 14
12+
AAABABo02647fNbD5JtEGVUoq/ggaRcxC138gLvipMDHqbRLizfsRc7i8B263oOv
13+
XQVNcaWjXGdYfXYCP9ctvkQCBdAPFv3vQfsCFGcEw5mA1cqc5mm8Ez4qewwzLfbg
14+
JWte2hANB4PpHvdyCV2svc3whNKMt6lG84pPiT+kC6FShHlpYre9nv6jbGuAb0tJ
15+
h1pO9KZVBs/Sn5nS+SwmN+lYDOo66owLK+JmhSZ8fIpxueyB1lWVgIV1SlcEst/a
16+
WmYtdgYW9bqRIDqB1RSL+SG8PaV1nptlebYOBAYwh23ZfZB3dXAMyE2cqv/io1kM
17+
2Unhj2UrcSO8J2Xai3m4Vp6XcU0AAACBAP2AxslebLmhbVLNFikTYVUUr3nhRsVb
18+
C/V0Tem0LG4WejVYDdjeZFYkZ5h7PM36PVfMpzSsSZMHhl1kCgRtgJYxMndHTPEc
19+
UxaAmFnbt/fKzRCY9QO01tk79JGyz3JobiaiL5RCvxGVHzzay8BecuAsPDqTJVUu
20+
Juo9IPTYotK1AAAAgQD0zDBuPcsCUXDfu2GChPu7X3t85IaKysFEB1ExAsQJU8rK
21+
0Liw5JiIQPY3XZNFQL0CzTOeD3ZrmMTTcT10AL+9qgolUG6z+ErKEOi/90lCS/MM
22+
0AXAARd+SZ/5mMflV2ETzCjZzzryf6xHHccZIto4IPFcS8JicBIU5KICzOVsSwAA
23+
AIEA4aa3eBSozdeS9yCRYz4vFK7kAY7pWTIuMIyUjzxNCyFiPqWxVZPFYIQts0KZ
24+
5diK42wdwFfT1CguqvkkEAwN/J1L9Mq4g3WYKPyefjw8Ub2Vx/vaI0MqAH26Jbbd
25+
0YBrAFm+aVO5Z6bbFYlkKaVZNi/kA4L1NgFFvT8OsSURgLU=
26+
Private-MAC: ae8cac65d4d96599886f6efaaa703176960f3983
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
PuTTY-User-Key-File-2: ssh-rsa
2+
Encryption: aes256-cbc
3+
Comment: rsa-key-rebol-test
4+
Public-Lines: 6
5+
AAAAB3NzaC1yc2EAAAABJQAAAQEA8mjwC6ZCwpQCnDXqU7g2tyvMFoWpJV+myfBz
6+
9zbhw7rHxUmFubMtEwCKQhYccQxbPCcWu/KIg5TOhmcf9RK1xIuUrOUiUdM8uOwR
7+
S+e5kitTUgux/wjyMNlpK5laIS1hFHiFhCxecN7Won3bsPDMm5Wi3dBMv1+1jK1r
8+
lDtJYxDDcJE2T59m1UI4AN/Pm7dndr11yCmUdKy6VACf5V7u4OX5uC3fsTEuCV1P
9+
2WwLBqtZMYG3jjzuRTana+s8n2TXk5D9lXoPMc7Tj4/aSw50UUAYhmVpie/8vtVw
10+
A7MhOCA4us6q3+Mx07vq8EmzbLOGtP9taXQkDF6JR6wY4IEXBw==
11+
Private-Lines: 14
12+
/Gzphw/DanBX5LxOzabPGUGokcJ1CenfNcHJfzfdnESNf2JWdWppZR0b/vgh0wgd
13+
3brFou3jMlVee6NSPXhOn4+rL+ptjdnlPnF3RZU5ckcRngAWRVX987Ufzj426enc
14+
8IeWyCLQbHCQJ1xsG3CLc3hd4ke10yri4IipD0O4olL6n3Bt5w9wyuZQq/liCcxJ
15+
Y5iaHrUWphGsmEBtAnpAgAXbgqm3fKZEYgyTWo3X0u2QOYQsI/xJD4dfJ+4QbM/F
16+
PDCBMe7kGz8cbjdKAa7JywUuw28N/Srhg7aWlGRnUgwB7mdhc5GQlw0O13FvfGfU
17+
JgD3QIdzOv4cDFtLtYujuw0pf0ZST4aoxbF5lPkTHyRdgtT8DBa6AawWbr1zIYfn
18+
6B28w0cHt86Cy7nUGy5D8Kl1ez514DzNo+RsvvApzmeTRR+K2RZSf4KN/Kmj9Lsq
19+
VXW/xIK7PvxTSBdBVMNEDVw0s+PpNOePou7/jRi1GPMCqFG9aPGvk+48ACPrmX0g
20+
EqHYub3ksMw0EItZ8VdZgVqo2QOfTIuLqS9IvtniDH4V64QAS+ZskB2XTInaT+w+
21+
upTMyejdKMp1Oyg6JB/n2BpPWvSohT7D9Bav+NDepe1araIMlhVM48P1cGVvUTBB
22+
Q6iuj83Whk9jDSKWQ/wG8cjuDFgCu3SRSb+y2qcdNJrLeizafjS15OMM1ZUZzpWd
23+
qxl7UYFGiYr/J5ceO8DastXu7X6tJtOy2PjeATJoYU6aKFE7rdtn/Ia3Cn/jopJ9
24+
CicXyZRtq+k2X8Caij5Qw3AjWY0RwgZV5w4tsgoBTA1Q25LDDIdo8Ycu8Iapo7Fq
25+
CQiGrQGCwAOzYOS9rqPc+1LuykqS31bY12aP7wsQZBq/t4gZZ9Obvpj6JsJao7ds
26+
Private-MAC: 30325c1c3e2eebad4a079e37394e83633143b84d

src/tests/units/rsa-test.r3

+22
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,26 @@ Rebol [
9999

100100
===end-group===
101101

102+
103+
===start-group=== "RSA initialization using codecs"
104+
--test-- "RSA sign/verify using external file"
105+
; Bob has data, which wants to sign, so it's clear, that nobody modifies them
106+
data: "Hello!"
107+
; As RSA is slow, it is used on hashes.. for example SHA1
108+
hash: checksum/secure data
109+
; Bob uses private key which keeps secret...
110+
--assert handle? try [private-key: load %units/files/rebol-private-no-pass.ppk]
111+
; .. to sign the hash..
112+
--assert binary? signed-hash: rsa/sign private-key hash
113+
114+
; than sends data, and signed hash to Eve, who have his public key
115+
--assert handle? try [public-key: load %units/files/rebol-public.ppk]
116+
; Eve uses the key to verify the checksum...
117+
--assert (checksum/secure data) = rsa/verify public-key signed-hash
118+
; so she know, that data were not modified
119+
120+
; cleanup:
121+
rsa public-key none
122+
rsa private-key none
123+
102124
~~~end-file~~~

0 commit comments

Comments
 (0)