factor/basis/io/sockets/secure/openssl/openssl.factor

416 lines
12 KiB
Factor

! Copyright (C) 2007, 2008, Slava Pestov, Elie CHAFTARI.
! See http://factorcode.org/license.txt for BSD license.
USING: accessors alien alien.c-types alien.data alien.enums
alien.strings assocs byte-arrays classes.struct combinators
combinators.short-circuit destructors fry io io.backend
io.binary io.buffers io.encodings.latin1 io.encodings.utf8
io.files io.pathnames io.ports io.sockets io.sockets.secure
io.timeouts kernel libc locals math math.functions math.order
math.parser memoize namespaces openssl openssl.libcrypto
openssl.libssl random sequences sets splitting unicode ;
IN: io.sockets.secure.openssl
GENERIC: ssl-method ( symbol -- method )
M: TLSv1 ssl-method drop TLSv1_method ;
M: TLSv1.1 ssl-method drop TLSv1_1_method ;
M: TLSv1.2 ssl-method drop TLSv1_2_method ;
MEMO: make-cipher-list ( -- string )
{
"ECDHE-ECDSA-AES256-GCM-SHA384"
"ECDHE-ECDSA-AES256-SHA384"
"ECDHE-ECDSA-AES128-GCM-SHA256"
"ECDHE-ECDSA-AES128-SHA256"
"ECDHE-RSA-AES256-GCM-SHA384"
"ECDHE-RSA-AES256-SHA384"
"ECDHE-RSA-AES128-GCM-SHA256"
"ECDHE-RSA-AES128-SHA256"
"ECDHE-ECDSA-AES256-CCM8"
"ECDHE-ECDSA-AES256-CCM"
"ECDHE-ECDSA-AES128-CCM8"
"ECDHE-ECDSA-AES128-CCM"
"ECDHE-ECDSA-CAMELLIA256-SHA384"
"ECDHE-RSA-CAMELLIA256-SHA384"
"ECDHE-ECDSA-CAMELLIA128-SHA256"
"ECDHE-RSA-CAMELLIA128-SHA256"
"ECDHE-RSA-CHACHA20-POLY1305"
"ECDHE-ECDSA-CHACHA20-POLY1305"
"ECDHE-PSK-CHACHA20-POLY1305"
"AES256-SHA"
"AES128-SHA256"
"AES128-SHA"
"CAMELLIA256-SHA"
"CAMELLIA128-SHA"
"IDEA-CBC-SHA"
"DES-CBC3-SHA"
} ":" join ;
TUPLE: openssl-context < secure-context aliens sessions ;
<PRIVATE
: bn-bytes-needed ( num -- bytes-required )
log2 1 + 8 / ceiling ;
PRIVATE>
: number>bn ( num -- bn )
dup bn-bytes-needed >be
dup length
f BN_bin2bn ; inline
: set-session-cache ( ctx -- )
handle>>
[ SSL_SESS_CACHE_BOTH SSL_CTX_set_session_cache_mode ssl-error ]
[ 32 random-bits >hex dup length SSL_CTX_set_session_id_context ssl-error ]
bi ;
ERROR: file-expected path ;
: ensure-exists ( path -- path )
dup exists? [ file-expected ] unless ; inline
: ssl-file-path ( path -- path' )
absolute-path ensure-exists ;
: load-certificate-chain ( ctx -- )
dup config>> key-file>> [
[ handle>> ] [ config>> key-file>> ssl-file-path ] bi
SSL_CTX_use_certificate_chain_file
ssl-error
] [ drop ] if ;
: password-callback ( -- alien )
int { void* int bool void* } cdecl
[| buf size rwflag password! |
password [ B{ 0 } password! ] unless
password strlen :> len
buf password len 1 + size min memcpy
len
] alien-callback ;
: default-pasword ( ctx -- alien )
[ config>> password>> latin1 malloc-string ] [ aliens>> ] bi
[ push ] [ drop ] 2bi ;
: set-default-password ( ctx -- )
dup config>> password>> [
[ handle>> password-callback SSL_CTX_set_default_passwd_cb ]
[
[ handle>> ] [ default-pasword ] bi
SSL_CTX_set_default_passwd_cb_userdata
] bi
] [ drop ] if ;
: use-private-key-file ( ctx -- )
dup config>> key-file>> [
[ handle>> ]
[ config>> key-file>> ssl-file-path ] bi
SSL_FILETYPE_PEM SSL_CTX_use_PrivateKey_file
ssl-error
] [ drop ] if ;
: load-verify-locations ( ctx -- )
dup config>> [ ca-file>> ] [ ca-path>> ] bi or [
[ handle>> ]
[
config>>
[ ca-file>> dup [ ssl-file-path ] when ]
[ ca-path>> dup [ ssl-file-path ] when ] bi
] bi
SSL_CTX_load_verify_locations
] [ handle>> SSL_CTX_set_default_verify_paths ] if ssl-error ;
: set-verify-depth ( ctx -- )
dup config>> verify-depth>> [
[ handle>> ] [ config>> verify-depth>> ] bi
SSL_CTX_set_verify_depth
] [ drop ] if ;
TUPLE: bio < disposable handle ;
: <bio> ( handle -- bio ) bio new-disposable swap >>handle ;
M: bio dispose* handle>> BIO_free ssl-error ;
: <file-bio> ( path -- bio )
normalize-path "r" BIO_new_file dup ssl-error <bio> ;
: load-dh-params ( ctx -- )
dup config>> dh-file>> [
[ handle>> ] [ config>> dh-file>> ] bi <file-bio> &dispose
handle>> f f f PEM_read_bio_DHparams dup ssl-error
SSL_CTX_set_tmp_dh ssl-error
] [ drop ] if ;
! Attempt to set ecdh. If it fails, ignore...?
: set-ecdh-params ( ctx -- )
handle>> SSL_CTRL_SET_ECDH_AUTO 1 f SSL_CTX_ctrl drop ;
: <openssl-context> ( config ctx -- context )
openssl-context new-disposable
swap >>handle
swap >>config
V{ } clone >>aliens
H{ } clone >>sessions ;
M: openssl <secure-context> ( config -- context )
maybe-init-ssl
[
dup method>> ssl-method SSL_CTX_new
dup ssl-error <openssl-context> |dispose
{
[ set-session-cache ]
[ load-certificate-chain ]
[ set-default-password ]
[ use-private-key-file ]
[ load-verify-locations ]
[ set-verify-depth ]
[ load-dh-params ]
[ set-ecdh-params ]
[ ]
} cleave
] with-destructors ;
M: openssl-context dispose*
[
[ aliens>> [ &free drop ] each ]
[ sessions>> values [ SSL_SESSION_free ] each ]
[ handle>> SSL_CTX_free ]
tri
] with-destructors ;
TUPLE: ssl-handle < disposable file handle connected ;
SYMBOL: default-secure-context
: current-secure-context ( -- ctx )
secure-context get [
default-secure-context [
<secure-config> <secure-context>
] initialize-alien
] unless* ;
: get-session ( addrspec -- session/f )
current-secure-context sessions>> at ;
: save-session ( session addrspec -- )
current-secure-context sessions>> set-at ;
: set-secure-cipher-list-only ( ssl -- ssl )
dup handle>> make-cipher-list SSL_set_cipher_list ssl-error ;
: <ssl-handle> ( fd -- ssl )
[
ssl-handle new-disposable |dispose
current-secure-context handle>> SSL_new
dup ssl-error >>handle
swap >>file
set-secure-cipher-list-only
] with-destructors ;
:: <ssl-socket> ( winsock hostname -- ssl )
winsock socket-handle BIO_NOCLOSE BIO_new_socket dup ssl-error :> bio
winsock <ssl-handle> :> handle
handle handle>> :> native-handle
hostname [
utf8 string>alien
native-handle swap SSL_set_tlsext_host_name ssl-error
] when*
native-handle bio bio SSL_set_bio
handle ;
! Error handling
: syscall-error ( r -- event )
ERR_get_error [
{
{ -1 [
errno ECONNRESET = [ premature-close ]
[ throw-errno ] if
] }
! OpenSSL docs say this it is an error condition for
! a server to not send a close notify, but web
! servers in the wild don't seem to do this, for
! example https://www.google.com.
{ 0 [ f ] }
} case
] [ nip (ssl-error) ] if-zero ;
: check-ssl-error ( ssl ret exra-cases/f -- event/f )
[ swap over SSL_get_error ] dip
{
{ SSL_ERROR_NONE [ drop f ] }
{ SSL_ERROR_WANT_READ [ drop +input+ ] }
{ SSL_ERROR_WANT_WRITE [ drop +output+ ] }
{ SSL_ERROR_SYSCALL [ syscall-error ] }
{ SSL_ERROR_SSL [ drop (ssl-error) ] }
} append [ [ execute( -- n ) ] dip ] assoc-map
at [ call( x -- y ) ] [ no-cond ] if* ;
! Accept
: do-ssl-accept-once ( ssl -- event/f )
dup SSL_accept {
{ SSL_ERROR_ZERO_RETURN [ (ssl-error) ] }
{ SSL_ERROR_WANT_ACCEPT [ drop +input+ ] }
} check-ssl-error ;
: do-ssl-accept ( ssl-handle -- )
dup handle>> do-ssl-accept-once
[ [ dup file>> ] dip wait-for-fd do-ssl-accept ] [ drop ] if* ;
: maybe-handshake ( ssl-handle -- )
dup connected>> [ drop ] [
[ [ do-ssl-accept ] with-timeout ]
[ t swap connected<< ] bi
] if ;
! Input ports
: do-ssl-read ( buffer ssl -- event/f )
2dup swap [ buffer-end ] [ buffer-capacity ] bi SSL_read [
{ { SSL_ERROR_ZERO_RETURN [ drop f ] } } check-ssl-error
] keep swap [ 2nip ] [ swap buffer+ f ] if* ;
M: ssl-handle refill ( port handle -- event/f )
dup maybe-handshake [ buffer>> ] [ handle>> ] bi* do-ssl-read ;
! Output ports
: do-ssl-write ( buffer ssl -- event/f )
2dup swap [ buffer@ ] [ buffer-length ] bi SSL_write
[ f check-ssl-error ] keep swap [ 2nip ] [ swap buffer-consume f ] if* ;
M: ssl-handle drain ( port handle -- event/f )
dup maybe-handshake [ buffer>> ] [ handle>> ] bi* do-ssl-write ;
! Connect
: do-ssl-connect-once ( ssl -- event/f )
dup SSL_connect f check-ssl-error ;
: do-ssl-connect ( ssl-handle -- )
dup handle>> do-ssl-connect-once
[ dupd wait-for-fd do-ssl-connect ] [ drop ] if* ;
: resume-session ( ssl-handle ssl-session -- )
[ [ handle>> ] dip SSL_set_session ssl-error ]
[ drop do-ssl-connect ]
2bi ;
: begin-session ( ssl-handle addrspec -- )
[ drop do-ssl-connect ]
[ [ handle>> SSL_get1_session ] dip save-session ]
2bi ;
: secure-connection ( client-out addrspec -- )
[ handle>> ] dip
[
'[
_ dup get-session
[ resume-session ] [ begin-session ] ?if
] with-timeout
] [ drop t >>connected drop ] 2bi ;
M: ssl-handle timeout
drop secure-socket-timeout get ;
M: ssl-handle cancel-operation
file>> cancel-operation ;
M: ssl-handle dispose*
[
! Free file>> after SSL_free
[ file>> &dispose drop ]
[ handle>> SSL_free ] bi
] with-destructors ;
: check-verify-result ( ssl-handle -- )
SSL_get_verify_result X509_V_ERROR number>enum dup X509_V_ERR_OK =
[ drop ] [ certificate-verify-error ] if ;
: x509name>string ( x509name -- string )
NID_commonName 256 <byte-array>
[ 256 X509_NAME_get_text_by_NID ] keep
swap -1 = [ drop f ] [ latin1 alien>string ] if ;
: subject-name ( certificate -- host )
X509_get_subject_name x509name>string ;
: issuer-name ( certificate -- issuer )
X509_get_issuer_name x509name>string ;
: sk-value ( stack v -- obj )
ssl-new-api? get-global [ OPENSSL_sk_value ] [ sk_value ] if ;
: sk-num ( stack -- num )
ssl-new-api? get-global [ OPENSSL_sk_num ] [ sk_num ] if ;
: name-stack>sequence ( name-stack -- seq )
dup sk-num <iota> [
sk-value GENERAL_NAME_st memory>struct
] with map ;
: alternative-dns-names ( certificate -- dns-names )
NID_subject_alt_name f f X509_get_ext_d2i
[ name-stack>sequence ] [ f ] if*
[ type>> GEN_DNS = ] filter
[ d>> dNSName>> data>> utf8 alien>string ] map ;
! *.foo.com matches: foo.com, www.foo.com, a.foo.com
! *.bar.foo.com matches: bar.foo.com, www.bar.foo.com, b.bar.foo.com
: subject-names-match? ( name pattern -- ? )
[ >lower ] bi@
"*." ?head [
{
[ tail? ]
[ [ [ CHAR: . = ] count ] bi@ - 1 <= ]
} 2&&
] [
=
] if ;
: check-subject-name ( host ssl-handle -- )
SSL_get_peer_certificate [
[ alternative-dns-names ]
[ subject-name ] bi suffix members
2dup [ subject-names-match? ] with any?
[ 2drop ] [ subject-name-verify-error ] if
] [ certificate-missing-error ] if* ;
M: openssl check-certificate ( host ssl -- )
current-secure-context config>> verify>> [
handle>>
[ nip check-verify-result ]
[ check-subject-name ]
2bi
] [ 2drop ] if ;
: check-buffer ( port -- port )
dup buffer>> buffer-empty? [ upgrade-buffers-full ] unless ;
: input/output-ports ( -- input output )
input-stream output-stream
[ get underlying-port check-buffer ] bi@
2dup [ handle>> ] bi@ eq? [ upgrade-on-non-socket ] unless ;
: make-input/output-secure ( input output -- )
dup handle>> non-ssl-socket? [ upgrade-on-non-socket ] unless
[ f <ssl-socket> ] change-handle
handle>> >>handle drop ;
: (send-secure-handshake) ( output -- )
remote-address get [ upgrade-on-non-socket ] unless*
secure-connection ;
M: openssl send-secure-handshake
input/output-ports
[ make-input/output-secure ] keep
[ (send-secure-handshake) ] keep
remote-address get dup inet? [
host>> swap handle>> check-certificate
] [ 2drop ] if ;
M: openssl accept-secure-handshake ( -- )
input/output-ports
make-input/output-secure ;
openssl secure-socket-backend set-global