1 ! Copyright (C) 2007, 2008, Slava Pestov, Elie CHAFTARI.
2 ! See https://factorcode.org/license.txt for BSD license.
3 USING: accessors alien alien.c-types alien.data alien.enums
4 alien.libraries.finder alien.strings assocs byte-arrays
5 classes.struct combinators combinators.short-circuit destructors
6 endian io io.backend io.buffers io.encodings.latin1
7 io.encodings.string io.encodings.utf8 io.files io.pathnames
8 io.ports io.sockets io.sockets.secure io.timeouts kernel libc
9 math math.functions math.order math.parser namespaces openssl
10 openssl.libcrypto openssl.libssl random sequences sets splitting
12 IN: io.sockets.secure.openssl
14 GENERIC: ssl-method ( symbol -- method )
15 M: TLS ssl-method drop TLS_method ;
16 M: TLSv1 ssl-method drop TLSv1_method ;
17 M: TLSv1.1 ssl-method drop TLSv1_1_method ;
18 M: TLSv1.2 ssl-method drop TLSv1_2_method ;
20 CONSTANT: weak-ciphers-for-compatibility
22 ! Weak 12/28/2021, included for compatibility for now
23 "ECDHE-ECDSA-AES256-SHA384"
24 "ECDHE-ECDSA-AES128-SHA256"
25 "ECDHE-RSA-AES256-GCM-SHA384"
26 "ECDHE-RSA-AES256-SHA384"
27 "ECDHE-RSA-AES128-SHA256"
28 "ECDHE-RSA-CAMELLIA256-SHA384"
29 "ECDHE-RSA-CAMELLIA128-SHA256"
30 "ECDHE-ECDSA-CAMELLIA256-SHA384"
31 "ECDHE-ECDSA-CAMELLIA128-SHA256"
41 MEMO: make-cipher-list ( -- string )
43 ! https://ciphersuite.info/cs/?security=recommended&software=openssl&singlepage=true
44 ! Recommended 2/16/2023
45 "ECDHE-ECDSA-AES256-GCM-SHA384"
46 "ECDHE-ECDSA-AES128-GCM-SHA256"
47 "ECDHE-ECDSA-CHACHA20-POLY1305"
48 "ECDHE-PSK-CHACHA20-POLY1305"
49 "DHE-DSS-AES256-GCM-SHA384"
50 "DHE-DSS-AES128-GCM-SHA256"
51 "DHE-PSK-AES256-GCM-SHA384"
52 "DHE-PSK-AES128-GCM-SHA256"
53 "DHE-PSK-CHACHA20-POLY1305"
54 "TLS_AES_128_GCM_SHA256"
55 "TLS_AES_256_GCM_SHA384"
58 "ECDHE-RSA-AES128-GCM-SHA256"
59 "ECDHE-RSA-CHACHA20-POLY1305"
60 "ECDHE-ECDSA-AES256-CCM8"
61 "ECDHE-ECDSA-AES256-CCM"
62 "ECDHE-ECDSA-AES128-CCM8"
63 "ECDHE-ECDSA-AES128-CCM"
66 weak-ciphers-for-compatibility append
69 TUPLE: openssl-context < secure-context aliens sessions ;
73 : bn-bytes-needed ( num -- bytes-required )
74 log2 1 + 8 / ceiling ;
78 : number>bn ( num -- bn )
79 dup bn-bytes-needed >be
83 : add-ctx-flag ( ctx flag -- )
85 [ [ SSL_CTX_get_options ] dip bitor ]
86 [ drop swap SSL_CTX_set_options ssl-error ] 2bi ;
88 : clear-ctx-flag ( ctx flag -- )
90 [ [ SSL_CTX_get_options ] dip bitnot bitand ]
91 [ drop swap SSL_CTX_set_options ssl-error ] 2bi ;
93 : disable-old-tls ( ctx -- )
94 SSL_OP_NO_TLSv1 SSL_OP_NO_TLSv1_1 bitor add-ctx-flag ;
96 : ignore-unexpected-eof ( ctx -- )
97 SSL_OP_IGNORE_UNEXPECTED_EOF add-ctx-flag ;
99 : set-session-cache ( ctx -- )
101 [ SSL_SESS_CACHE_BOTH SSL_CTX_set_session_cache_mode ssl-error ]
102 [ 32 random-bits >hex dup length SSL_CTX_set_session_id_context ssl-error ]
105 ERROR: file-expected path ;
107 : ensure-exists ( path -- path )
108 dup file-exists? [ file-expected ] unless ; inline
110 : ssl-file-path ( path -- path' )
111 absolute-path ensure-exists ;
113 : load-certificate-chain ( ctx -- )
114 dup config>> key-file>> [
115 [ handle>> ] [ config>> key-file>> ssl-file-path ] bi
116 SSL_CTX_use_certificate_chain_file
120 : password-callback ( -- alien )
121 int { void* int bool void* } cdecl
122 [| buf size rwflag password! |
123 password [ B{ 0 } password! ] unless
125 password strlen :> len
126 buf password len 1 + size min memcpy
130 : default-pasword ( ctx -- alien )
131 [ config>> password>> latin1 malloc-string ] [ aliens>> ] bi
132 [ push ] [ drop ] 2bi ;
134 : set-default-password ( ctx -- )
135 dup config>> password>> [
136 [ handle>> password-callback SSL_CTX_set_default_passwd_cb ]
138 [ handle>> ] [ default-pasword ] bi
139 SSL_CTX_set_default_passwd_cb_userdata
143 : use-private-key-file ( ctx -- )
144 dup config>> key-file>> [
146 [ config>> key-file>> ssl-file-path ] bi
147 SSL_FILETYPE_PEM SSL_CTX_use_PrivateKey_file
151 : load-verify-locations ( ctx -- )
152 dup config>> [ ca-file>> ] [ ca-path>> ] bi or [
156 [ ca-file>> dup [ ssl-file-path ] when ]
157 [ ca-path>> dup [ ssl-file-path ] when ] bi
159 SSL_CTX_load_verify_locations
160 ] [ handle>> SSL_CTX_set_default_verify_paths ] if ssl-error ;
162 : set-verify-depth ( ctx -- )
163 dup config>> verify-depth>> [
164 [ handle>> ] [ config>> verify-depth>> ] bi
165 SSL_CTX_set_verify_depth
168 TUPLE: bio < disposable handle ;
170 : <bio> ( handle -- bio ) bio new-disposable swap >>handle ;
172 M: bio dispose* handle>> BIO_free ssl-error ;
174 : <file-bio> ( path -- bio )
175 normalize-path "r" BIO_new_file dup ssl-error <bio> ;
177 : load-dh-params ( ctx -- )
178 dup config>> dh-file>> [
179 [ handle>> ] [ config>> dh-file>> ] bi <file-bio> &dispose
180 handle>> f f f PEM_read_bio_DHparams dup ssl-error
181 SSL_CTX_set_tmp_dh ssl-error
184 ! Attempt to set ecdh. If it fails, ignore...?
185 : set-ecdh-params ( ctx -- )
186 handle>> SSL_CTRL_SET_ECDH_AUTO 1 f SSL_CTX_ctrl drop ;
188 : <openssl-context> ( config ctx -- context )
189 openssl-context new-disposable
193 H{ } clone >>sessions ;
195 M: openssl <secure-context>
198 dup method>> ssl-method SSL_CTX_new
199 dup ssl-error <openssl-context> |dispose
201 [ set-session-cache ]
202 [ load-certificate-chain ]
203 [ set-default-password ]
204 [ use-private-key-file ]
205 [ load-verify-locations ]
209 [ os macosx? [ drop ] [ ignore-unexpected-eof ] if ]
214 M: openssl-context dispose*
216 [ aliens>> [ &free drop ] each ]
217 [ sessions>> values [ SSL_SESSION_free ] each ]
218 [ handle>> SSL_CTX_free ]
222 TUPLE: ssl-handle < disposable file handle connected terminated ;
224 SYMBOL: default-secure-context
226 : current-secure-context ( -- ctx )
228 default-secure-context [
229 <secure-config> <secure-context>
233 : get-session ( addrspec -- session/f )
234 current-secure-context sessions>> at ;
236 : save-session ( session addrspec -- )
237 current-secure-context sessions>> set-at ;
239 : set-secure-cipher-list-only ( ssl -- ssl )
240 dup handle>> make-cipher-list SSL_set_cipher_list ssl-error ;
242 : <ssl-handle> ( fd -- ssl )
244 ssl-handle new-disposable |dispose
245 current-secure-context handle>> SSL_new
246 dup ssl-error >>handle
248 set-secure-cipher-list-only
253 : alpn_select_cb_func ( -- alien )
254 [| ssl out outlen in inlen arg |
255 ! if alpn-protocols is empty return err noack
257 ! current-secure-context relies on secure-context
258 ! variable being set. if this is not set in a callback,
259 ! we need some other way of accessing it (probably
260 ! passing it as arg to SSL_CTX_set_alpn_select_cb, but
261 ! need to make sure that stays defined as long as the
262 ! callback can be called)
263 current-secure-context config>> alpn-supported-protocols>>
264 [ SSL_TLSEXT_ERR_NOACK ]
266 ! convert alpn-protocols from list of strings to
267 ! c-string in wire format and length.
268 ! see https://www.openssl.org/docs/manmaster/man3/SSL_set_alpn_protos.html
269 [ utf8 encode dup length prefix ] map
271 in inlen SSL_select_next_proto
272 ! the function returns OPENSSL_NPN_NO_OVERLAP when no
273 ! match is found, otherwise OPENSSL_NPN_NEGOTIATED
274 OPENSSL_NPN_NEGOTIATED =
275 [ ! DOUBLECHECK: The value in out is already copied
276 ! from the original, so we can just leave it and
277 ! return... otherwise this detail needs to be ironed
278 ! out, probably by finding the entry in in that out
279 ! is identical to. (out needs to point directly into
280 ! in, or a buffer that will outlive the tls
283 [ SSL_TLSEXT_ERR_ALERT_FATAL ] if
285 ] SSL_CTX_alpn_select_cb_func ;
287 : get_alpn_selected_wrapper ( ssl* -- alpn_string/f )
288 { c-string int } [ SSL_get0_alpn_selected ] with-out-parameters
289 drop ! how do we unbox the c-string?
290 ! also, the string is not null-terminated, is that problematic?
295 :: <ssl-socket> ( winsock hostname -- ssl )
296 winsock socket-handle BIO_NOCLOSE BIO_new_socket dup ssl-error :> bio
297 winsock <ssl-handle> :> handle
298 handle handle>> :> native-handle
299 current-secure-context config>> alpn-supported-protocols>>
300 [ drop native-handle ctx>> alpn_select_cb_func f SSL_CTX_set_alpn_select_cb ]
304 native-handle swap SSL_set_tlsext_host_name ssl-error
306 native-handle bio bio SSL_set_bio
309 : ssl-error-syscall ( ssl-handle -- event/f )
315 [ premature-close-error ] [ throw-errno ] if f
317 ! https://stackoverflow.com/questions/13686398/ssl-read-failing-with-ssl-error-syscall-error
322 : check-ssl-error ( ssl-handle ret -- event/f )
323 [ drop ] [ [ handle>> ] dip SSL_get_error ] 2bi
325 { SSL_ERROR_NONE [ drop f ] }
326 { SSL_ERROR_WANT_READ [ drop +input+ ] }
327 { SSL_ERROR_WANT_WRITE [ drop +output+ ] }
328 { SSL_ERROR_SYSCALL [ ssl-error-syscall ] }
329 { SSL_ERROR_SSL [ drop throw-ssl-error ] }
330 ! https://stackoverflow.com/questions/50223224/ssl-read-returns-ssl-error-zero-return-but-err-get-error-is-0
331 ! there are no more bytes to read
332 { SSL_ERROR_ZERO_RETURN [ drop f ] }
333 { SSL_ERROR_WANT_ACCEPT [ drop +input+ ] }
337 : do-ssl-accept-once ( ssl-handle -- event/f )
338 dup handle>> SSL_accept check-ssl-error ;
340 : do-ssl-accept ( ssl-handle -- )
341 dup do-ssl-accept-once
342 [ [ dup file>> ] dip wait-for-fd do-ssl-accept ] [ drop ] if* ;
344 : maybe-handshake ( ssl-handle -- ssl-handle )
345 dup [ connected>> ] [ terminated>> ] bi or [
346 [ [ do-ssl-accept ] with-timeout ]
351 : do-ssl-read ( buffer ssl-handle -- event/f )
352 2dup handle>> swap [ buffer-end ] [ buffer-capacity ] bi
353 ERR_clear_error SSL_read dup 0 >
354 [ nip swap buffer+ f ] [ check-ssl-error nip ] if ;
356 : throw-if-terminated ( ssl-handle -- ssl-handle )
357 dup terminated>> [ premature-close-error ] when ;
361 [ buffer>> ] [ maybe-handshake ] bi* do-ssl-read ;
364 : do-ssl-write ( buffer ssl-handle -- event/f )
365 2dup handle>> swap [ buffer@ ] [ buffer-length ] bi
366 ERR_clear_error SSL_write dup 0 > [
367 nip over buffer-consume buffer-empty? f +output+ ?
368 ] [ check-ssl-error nip ] if ;
372 [ buffer>> ] [ maybe-handshake ] bi* do-ssl-write ;
375 : do-ssl-connect-once ( ssl-handle -- event/f )
376 dup handle>> SSL_connect check-ssl-error ;
378 : do-ssl-connect ( ssl-handle -- )
379 dup do-ssl-connect-once
380 [ dupd wait-for-fd do-ssl-connect ] [ drop ] if* ;
382 : resume-session ( ssl-handle ssl-session -- )
383 [ [ handle>> ] dip SSL_set_session ssl-error ]
384 [ drop do-ssl-connect ]
387 : begin-session ( ssl-handle addrspec -- )
388 [ drop do-ssl-connect ]
389 [ [ handle>> SSL_get1_session ] dip save-session ]
392 : secure-connection ( client-out addrspec -- )
397 [ get-session ] [ resume-session ] [ begin-session ] ?if
399 ] [ drop t >>connected drop ] 2bi ;
401 M: ssl-handle timeout
402 drop secure-socket-timeout get ;
404 M: ssl-handle cancel-operation
405 file>> cancel-operation ;
407 M: ssl-handle dispose*
409 ! Free file>> after SSL_free
410 [ file>> &dispose drop ]
411 [ handle>> SSL_free ] bi
414 : check-verify-result ( ssl-handle -- )
415 SSL_get_verify_result X509_V_ERROR number>enum dup X509_V_ERR_OK =
416 [ drop ] [ certificate-verify-error ] if ;
418 : x509name>string ( x509name -- string )
419 NID_commonName 256 <byte-array>
420 [ 256 X509_NAME_get_text_by_NID ] keep
421 swap -1 = [ drop f ] [ latin1 alien>string ] if ;
423 : subject-name ( certificate -- host )
424 X509_get_subject_name x509name>string ;
426 : issuer-name ( certificate -- issuer )
427 X509_get_issuer_name x509name>string ;
429 : sk-value ( stack v -- obj )
430 ssl-new-api? get-global [ OPENSSL_sk_value ] [ sk_value ] if ;
432 : sk-num ( stack -- num )
433 ssl-new-api? get-global [ OPENSSL_sk_num ] [ sk_num ] if ;
435 : name-stack>sequence ( name-stack -- seq )
437 sk-value GENERAL_NAME_st memory>struct
440 : alternative-dns-names ( certificate -- dns-names )
441 NID_subject_alt_name f f X509_get_ext_d2i
442 [ name-stack>sequence ] [ f ] if*
443 [ type>> GEN_DNS = ] filter
444 [ d>> dNSName>> data>> utf8 alien>string ] map ;
446 ! *.foo.com matches: foo.com, www.foo.com, a.foo.com
447 ! *.bar.foo.com matches: bar.foo.com, www.bar.foo.com, b.bar.foo.com
448 : subject-names-match? ( name pattern -- ? )
453 [ [ [ CHAR: . = ] count ] bi@ - 1 <= ]
459 : check-subject-name ( host ssl-handle -- )
460 get-ssl-peer-certificate [
461 [ alternative-dns-names ]
462 [ subject-name ] bi suffix members
463 2dup [ subject-names-match? ] with any?
464 [ 2drop ] [ subject-name-verify-error ] if
465 ] [ certificate-missing-error ] if* ;
467 M: openssl check-certificate
468 current-secure-context config>> verify>> [
470 [ nip check-verify-result ]
471 [ check-subject-name ]
475 : check-buffer ( port -- port )
476 dup buffer>> buffer-empty? [ upgrade-buffers-full ] unless ;
478 : input/output-ports ( -- input output )
479 input-stream output-stream
480 [ get underlying-port check-buffer ] bi@
481 2dup [ handle>> ] bi@ eq? [ upgrade-on-non-socket ] unless ;
483 : make-input/output-secure ( input output -- )
484 dup handle>> non-ssl-socket? [ upgrade-on-non-socket ] unless
485 [ f <ssl-socket> ] change-handle
486 handle>> >>handle drop ;
488 : (send-secure-handshake) ( output -- )
489 remote-address get [ upgrade-on-non-socket ] unless*
492 M: openssl send-secure-handshake
494 [ make-input/output-secure ]
495 [ nip (send-secure-handshake) ]
497 nip remote-address get dup inet? [
498 host>> swap handle>> check-certificate
502 M: openssl accept-secure-handshake
504 make-input/output-secure ;
506 openssl secure-socket-backend set-global