diff --git a/src/mezz/boot-files.r b/src/mezz/boot-files.r index 272b52609c..b5c2a58c75 100644 --- a/src/mezz/boot-files.r +++ b/src/mezz/boot-files.r @@ -50,16 +50,20 @@ REBOL [ %mezz-date.r ; Internet date support ;%mezz-tag.r ; build-tag %mezz-tail.r - %codec-json.r %codec-unixtime.r + ;- cryptographic %codec-utc-time.r %codec-pkix.r %codec-der.r %codec-crt.r + %codec-ssh-key.r + ;- compression %codec-gzip.r - %codec-xml.r %codec-zip.r %codec-tar.r + ;- other + %codec-json.r + %codec-xml.r %codec-html-entities.r ; optional files added in make-boot.r per os and product ;%codec-wav.r diff --git a/src/mezz/codec-ssh-key.r b/src/mezz/codec-ssh-key.r new file mode 100644 index 0000000000..4dc7c8ad3b --- /dev/null +++ b/src/mezz/codec-ssh-key.r @@ -0,0 +1,144 @@ +REBOL [ + Title: "REBOL 3 codec: Secure Shell Key" + Author: "Oldes" + Rights: "Copyright (C) 2018 Oldes. All rights reserved." + License: "BSD-3" + Test: %tests/units/crypt-test.r3 + Note: { + * it extract (and inits) only RSA keys so far + * encrypted keys only with `AES-128-CBC` encryption + } +] + +wrap [ + init-from-ssh2-key: function [data][ + try [ + binary/read data [ + v: UI32BYTES + e: UI32BYTES + n: UI32BYTES + ] + v: to string! v + if v = "ssh-rsa" [ + return rsa-init n e + ] + ] + print ["Not RSA key! (" v ")"] + none + ] + + register-codec [ + name: 'ssh-key + title: "Secure Shell Key" + ; not using suffixes as there is no standard! + + decode: function [ + "Decodes and initilize SSH key" + key [binary! string! file!] + /password p [string! binary!] "Optional password" + ][ + case [ + file? key [ key: read key ] + string? key [ key: to binary! key ] + ] + ; try to load key as a PKIX structure + try [ pkix: codecs/pkix/decode key ] + if none? pkix [ + ; if failed to load as PKIX, try to treat it as a *.PUB file + return either parse key [ + "ssh-rsa " copy data to [#" " | end] to end + ][ init-from-ssh2-key debase data 64 + ][ init-from-ssh2-key key ] + ] + if "4,ENCRYPTED" = select pkix/header "Proc-Type" [ + print "ENCRYPTED key!" + try/except [ + dek-info: select pkix/header "DEK-Info" + ;probe dek-info + parse dek-info [ + "AES-128-CBC" #"," copy iv to end + ] + iv: debase iv 16 + unless password [p: ask/hide "Pasword: "] + p: checksum/method + join to binary! p copy/part iv 8 + 'md5 + d: aes/key/decrypt p iv + pkix/binary: aes/stream d pkix/binary + ][ return none ] + ] + + switch pkix/label [ + "SSH2 PUBLIC KEY" [ + return init-from-ssh2-key pkix/binary + ] + ] + ; decode DER structure from decoded PKIX binary + try/except [ + data: codecs/der/decode pkix/binary + ][ + print "Failed to decode DER day for RSA key!" + probe system/state/last-error + return none + ] + + switch pkix/label [ + "PUBLIC KEY" [ + ; resolve RSA public data from the DER structure (PKCS#1) + all [ + parse data [ + 'SEQUENCE into [ + 'SEQUENCE set v block! ; AlgorithmIdentifier + 'BIT_STRING set data binary! ; PublicKey + ( + data: codecs/der/decode data + ) + ] + ] + v/OBJECT_IDENTIFIER = #{2A864886F70D010101} ;= rsaEncryption + parse data [ + 'SEQUENCE into [ + 'INTEGER set n binary! ;modulus + 'INTEGER set e binary! ;publicExponent + ] + ] + ] + ; resolve RSA handle from parsed data + return rsa-init n e + ] + "RSA PUBLIC KEY" [ + ; resolve RSA public data from the DER structure (PKCS#1) + parse data [ + 'SEQUENCE into [ + 'INTEGER set n binary! ;modulus + 'INTEGER set e binary! ;publicExponent + ] + ] + ; resolve RSA handle from parsed data + return rsa-init n e + ] + "RSA PRIVATE KEY" [ + ; resolve RSA private data from the DER structure (PKCS#1) + parse data [ + 'SEQUENCE into [ + 'INTEGER set v binary! ;version + 'INTEGER set n binary! ;modulus + 'INTEGER set e binary! ;publicExponent + 'INTEGER set d binary! ;privateExponent + 'INTEGER set p binary! ;prime1 + 'INTEGER set q binary! ;prime2 + 'INTEGER set dp binary! ;exponent1 d mod (p-1) + 'INTEGER set dq binary! ;exponent2 d mod (q-1) + 'INTEGER set inv binary! ;coefficient (inverse of q) mod p + to end + ] + to end + ] + ; resolve RSA handle from parsed data + return rsa-init/private n e d p q dp dq inv + ] + ] + none ; no success! + ] + ] +] \ No newline at end of file diff --git a/src/tests/units/crypt-test.r3 b/src/tests/units/crypt-test.r3 index fa92815dd6..06e70be6c8 100644 --- a/src/tests/units/crypt-test.r3 +++ b/src/tests/units/crypt-test.r3 @@ -176,6 +176,8 @@ sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV --assert block? Load-PKIX pkix --assert binary? Load-PKIX/binary pkix + --assert error? try [decode 'ssh-key pkix] ;- because it contains unsupported ssh-dss + --test-- "SSH-public-key-3" pkix: @@ -217,4 +219,10 @@ sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV ===end-group=== + +===start-group=== "SSH-key codec" + --test-- "Init RSA key from file" + --assert handle? try [key: decode 'ssh-key read %units/files/rebol-public.ppk] + rsa key none ; release it, as it is not GCed yet. + ~~~end-file~~~ \ No newline at end of file diff --git a/src/tests/units/files/rebol-public.ppk b/src/tests/units/files/rebol-public.ppk new file mode 100644 index 0000000000..ed0ee2693c --- /dev/null +++ b/src/tests/units/files/rebol-public.ppk @@ -0,0 +1,9 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "rsa-key-rebol-test" +AAAAB3NzaC1yc2EAAAABJQAAAQEA8mjwC6ZCwpQCnDXqU7g2tyvMFoWpJV+myfBz +9zbhw7rHxUmFubMtEwCKQhYccQxbPCcWu/KIg5TOhmcf9RK1xIuUrOUiUdM8uOwR +S+e5kitTUgux/wjyMNlpK5laIS1hFHiFhCxecN7Won3bsPDMm5Wi3dBMv1+1jK1r +lDtJYxDDcJE2T59m1UI4AN/Pm7dndr11yCmUdKy6VACf5V7u4OX5uC3fsTEuCV1P +2WwLBqtZMYG3jjzuRTana+s8n2TXk5D9lXoPMc7Tj4/aSw50UUAYhmVpie/8vtVw +A7MhOCA4us6q3+Mx07vq8EmzbLOGtP9taXQkDF6JR6wY4IEXBw== +---- END SSH2 PUBLIC KEY ----