诡异的问题:和朋友共用一台服务器,在访问我的网站时, nginx 同时发送了他的证书 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fourstring
V2EX    NGINX

诡异的问题:和朋友共用一台服务器,在访问我的网站时, nginx 同时发送了他的证书

  •  
  •   fourstring 2017-02-05 22:54:54 +08:00 6309 次点击
    这是一个创建于 3244 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我们的服务器用 Docker 建立 nginx 环境,所以 ssl 直接丢在了 root 下,也没有太大关系。

    服务端软件为: nginx-1.11.8 , openssl-1.1.0c (已排除软件版本因素)

    我的域名为 a.com ,他的域名为 b.com

    以下是我能提供的所有信息,症状如标题,请大神赐教,谢谢!

    Dockerfile 如下:

    FROM ubuntu:latest COPY crontabs /root RUN apt update && \ apt-get install -y -q libpcre3 libpcre3-dev zlib1g zlib1g-dev git wget gcc make cmake cpp autoconf automake cron && \ cd /usr/local/src && \ wget http://nginx.org/download/nginx-1.11.8.tar.gz && \ tar xzf nginx-1.11.8.tar.gz && \ wget https://www.openssl.org/source/openssl-1.1.0d.tar.gz && \ tar xzf openssl-1.1.0d.tar.gz && \ groupadd www && \ useradd www -g www -s /sbin/nologin && \ git clone https://github.com/grahamedgecombe/nginx-ct && \ cd /usr/local/src/nginx-1.11.8 && \ ./configure --user=www --group=www --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_gzip_static_module --with-http_v2_module --with-openssl=../openssl-1.1.0d --add-module=../nginx-ct && \ make -j2 && make install && \ ln -s /usr/local/nginx/sbin/nginx /usr/local/bin/nginx && \ rm -rf /usr/local/src/* && \ apt remove -y -q git wget gcc make cmake cpp autoconf automake && \ apt autoremove -y && \ apt purge -y -q git wget gcc make cmake cpp autoconf automake && \ mkdir -p /usr/local/nginx/conf/vhost && \ mkdir -p /home/wwwroot && \ crontab /root/crontabs COPY ["nginx.conf","fastcgi.conf","/usr/local/nginx/conf/"] COPY ["a.com.conf","b.com.conf","/usr/local/nginx/conf/vhost/"] ADD ssl.tar.gz /root EXPOSE 80 EXPOSE 443 CMD ["nginx","-g","daemon off;"] 

    a.com.conf:

    server { server_name a.com www.a.com; listen 80; location ^~ /.well-known/acme-challenge/ { alias /home/wwwroot/challenges/; try_files $uri =404; } location / { rewrite ^/(.*)$ https://a.com/$1 permanent; } } server { server_name a.com www.a.com; listen 443 ssl http2 reuseport fastopen=3; root /home/wwwroot/hexo; server_tokens off; ssl_ct on; ssl_certificate /root/ssl/a/a.com.rsa.pem; ssl_certificate_key /root/ssl/a/a.com.rsa.key; ssl_ct_static_scts /root/ssl/a/scts/rsa; ssl_certificate /root/ssl/a/a.ecc.pem; ssl_certificate_key /root/ssl/a/a.ecc.key; ssl_ct_static_scts /root/ssl/a/scts/ecc; ssl_dhparam /root/ssl/a/dhparams.pem; ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5; ssl_prefer_server_ciphers on; ssl_ecdh_curve secp384r1; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_session_cache shared:SSL:50m; ssl_session_timeout 1d; ssl_session_tickets on; ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 10s; add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload'; add_header Public-Key-Pins 'pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=";pin-sha256="Fbs+o+IxVNTHBpjNQYfX/TBnxPC+OWLYxQLEtqkrAfM=";max-age=2592000; includeSubDomains'; index index.html; location / { expires 120s; } location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|ico)$ { expires 30d; access_log off; } location ~ .*\.(js|css)?$ { expires 7d; access_log off; } } 

    b.conf:

    server { server_name b.com,www.b.com; location ^~ /.well-known/acme-challenge/ { alias /home/wwwroot/challenges/; try_files $uri =404; } location / { rewrite ^/(.*)$ https://b.com/$1 permanent; } } server { server_name b.com,www.b.com; listen 443 ssl http2; index index.php; root /home/wwwroot/b; if (!-e $request_filename) { rewrite ^(.*)$ /index.php$1 last; } location ~ .*\.php(\/.*)*$ { include fastcgi.conf; fastcgi_pass cgi:9001; } server_tokens off; ssl_ct on; ssl_certificate /root/ssl/b/b.com.rsa.pem; ssl_certificate_key /root/ssl/b/b.com.rsa.key; ssl_ct_static_scts /root/ssl/b/scts/rsa; ssl_certificate /root/ssl/b/b.com.ecc.pem; ssl_certificate_key /root/ssl/b/b.com.ecc.key; ssl_ct_static_scts /root/ssl/b/scts/ecc; ssl_dhparam /root/ssl/b/dhparams.pem; ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5; ssl_prefer_server_ciphers on; ssl_ecdh_curve secp384r1; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_session_cache shared:SSL:50m; ssl_session_timeout 1d; ssl_session_tickets on; ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 10s; add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload'; add_header Public-Key-Pins 'pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=";pin-sha256="Fbs+o+IxVNTHBpjNQYfX/TBnxPC+OWLYxQLEtqkrAfM=";max-age=2592000; includeSubDomains'; } error_log /root/b_error.log crit; 

    /root/ssl 目录的结构图:

    /root/ssl |-- b.com.ecc.pem |-- a | |-- account.key | |-- acme_tiny.py | |-- ct-submit-1.0.0 | | |-- LICENSE | | |-- README.markdown | | |-- ct-submit | | `-- ct-submit.go | |-- ct-submit.zip | |-- dhparams.pem | |-- intermediate.pem | |-- renew_ecc.sh | |-- renew_rsa.sh | |-- root.pem | |-- scts | | |-- ecc | | | |-- b.com.ecc.sct | | | `-- a.com.ecc.sct | | `-- rsa | | `-- a.com.rsa.sct | |-- signed_ecc.crt | |-- signed_rsa.crt | |-- a.com.ecc.csr | |-- a.com.ecc.key | |-- a.com.ecc.pem | |-- a.com.rsa.csr | |-- a.com.rsa.key | `-- a.com.rsa.pem |-- intermediate.pem |-- root.pem |-- signed_ecc.crt `-- b |-- account.key |-- acme_tiny.py |-- b.com.ecc.csr |-- b.com.ecc.key |-- b.com.ecc.pem |-- b.com.rsa.csr |-- b.com.rsa.key |-- b.com.rsa.pem |-- ct-submit-1.0.0 | |-- LICENSE | |-- README.markdown | |-- ct-submit | `-- ct-submit.go |-- dhparams.pem |-- intermediate.pem |-- renew_ecc.sh |-- renew_rsa.sh |-- root.pem |-- scts | |-- ecc | | `-- b.com.ecc.sct | `-- rsa | `-- b.com.rsa.sct |-- signed_ecc.crt `-- signed_rsa.crt 

    *.ecc.pem表示该域名的 ECC 证书,*.rsa.pem表示该域名的 RSA 证书,signed_*.crt表示 Let ‘ s encrypt 签发但还未进行证书链合并的证书;*.ecc.key表示对应域名 ECC 证书私钥,*.rsa.key表示对应域名 RSA 证书私钥;*.ecc.sct表示该域名 ECC 证书的 SCT 文件,*.rsa.sct表示该域名 RSA 证书的 SCT 文件。account.key为用户密钥。

    renew_*.sh为对应证书更新脚本。 renew_ecc.sh:

    #!/bin/bash cd /root/ssl/a python acme_tiny.py --account-key ./account.key --csr ./a.com.ecc.csr --acme-dir /home/wwwroot/challenges/ > ./signed_ecc.crt wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem wget -O - https://letsencrypt.org/certs/isrgrootx1.pem > root.pem cat signed_ecc.crt intermediate.pem > a.com.ecc.pem ./ct-submit-1.0.0/ct-submit ct.googleapis.com/pilot </root/ssl/a/a.com.ecc.pem >/root/ssl/a/scts/ecc/a.com.ecc.sct kill -HUP `cat /usr/local/nginx/logs/nginx.pid` 

    renew_rsa.sh:

    #!/bin/bash cd /root/ssl/a python acme_tiny.py --account-key ./account.key --csr ./a.com.rsa.csr --acme-dir /home/wwwroot/challenges/ > ./signed_rsa.crt wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem wget -O - https://letsencrypt.org/certs/isrgrootx1.pem > root.pem cat signed_rsa.crt intermediate.pem > a.com.rsa.pem ./ct-submit-1.0.0/ct-submit ct.googleapis.com/pilot </root/ssl/a/a.com.rsa.pem >/root/ssl/a/scts/rsa/a.com.rsa.sct kill -HUP `cat /usr/local/nginx/logs/nginx.pid` 
    40 条回复    2017-02-07 07:11:59 +08:00
    feiyunfirst
        1
    feiyunfirst  
       2017-02-05 23:04:57 +08:00 via iPhone
    应该是 Nginx 没有开启 TLS 吧。参考 https://www.i7i8.com/2016/11/nginx-ip-https/
    SourceMan
        2
    SourceMan  
       2017-02-05 23:09:39 +08:00 via iPhone
    $ nginx -V
    TLS SNI support disabled
    参考楼上开启它即可
    fourstring
        3
    fourstring  
    OP
       2017-02-05 23:09:41 +08:00
    @feiyunfirst #1 SNI 已开启
    fourstring
        4
    fourstring  
    OP
       2017-02-05 23:09:56 +08:00
    @SourceMan #2 SNI 已开启……
    wql
        5
    wql  
       2017-02-06 01:50:31 +08:00 via Android
    同时配上两个单一域名的证书导致的。
    根据 cipher 中 ecdsa 在 rsa 前,如果 b.com 这一个因为一些原因匹配不上(我估计 sct 搞的鬼),那么优先返回默认 ecdsa 证书,正好有一个 a.com.ecc.crt 。
    顺带说一句,@qgy18 的配置可能对于这种情有 bug ,自己也碰到过。
    lhbc
        6
    lhbc  
       2017-02-06 03:41:59 +08:00   2
    配置里两处写错了
    server_name b.com,www.b.com;
    应该为
    server_name b.com www.b.com;
    Systemd
        7
    Systemd  
       2017-02-06 04:26:12 +08:00 via Android
    试试注释掉 ssl_session_cache
    wql
        8
    wql  
       2017-02-06 08:53:07 +08:00 via Android   1
    六楼 @lhbc 正解。
    逗号那个 nginx 读不出来可能就变成 default 了。
    HLT
        9
    HLT  
       2017-02-06 09:30:18 +08:00   1
    server_name b.com,www.b.com;
    fourstring
        10
    fourstring  
    OP
       2017-02-06 10:23:30 +08:00
    @wql #8
    @wql #5
    @lhbc #6
    @HLT #9 谢谢,但是目前的情况是,访问我的( a.com )会返回两个域名的四个证书,访问他的( b.com )只会返回他的证书。而且我用 F12 看了一下,访问我的不会返回 sct ,但是他的可以返回 sct 。

    参照各位的解决方案,修改了配置文件,删除多余的 sct ,并且启用 Mozilla 的 cipher 配置之后,这个问题仍然存在……
    fourstring
        11
    fourstring  
    OP
       2017-02-06 10:25:29 +08:00
    @fourstring #10 补充一下,我选用 Mozilla 的 modern 配置,结果访问我的域名只返回了 3 个证书,两个域名的 ECC ,以及我自己的 RSA

    Cipher :
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256;
    lhbc
        12
    lhbc  
       2017-02-06 10:26:29 +08:00 via iPhone
    nginx -V
    贴上来
    fourstring
        13
    fourstring  
    OP
       2017-02-06 10:27:01 +08:00
    @lhbc #12 nginx version: nginx/1.11.9
    built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4)
    built with OpenSSL 1.1.0d 26 Jan 2017
    TLS SNI support enabled
    configure arguments: --user=www --group=www --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_gzip_static_module --with-http_v2_module --with-openssl=../openssl-1.1.0d --add-module=../nginx-ct
    lhbc
        14
    lhbc  
       2017-02-06 10:30:58 +08:00 via iPhone
    去掉 ct 所有配置,或者去掉 nginx-ct 模块编译试下。
    fourstring
        15
    fourstring  
    OP
       2017-02-06 10:34:04 +08:00
    @lhbc #14 好的
    fourstring
        16
    fourstring  
    OP
       2017-02-06 10:36:23 +08:00
    @lhbc #14 已关闭所有 ct 配置,仍然无效……
    fourstring
        17
    fourstring  
    OP
       2017-02-06 10:38:55 +08:00
    @lhbc #14 而且现在又有另一个诡异的问题,就是我的 session_cache 配置失效了,我怀疑是返回了其他域名的证书所致
    wql
        18
    wql  
       2017-02-06 10:57:33 +08:00   1
    @fourstring 这是双证书本身的缺点。
    你的 cipher 是这样的:
    EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3ES:!MD5
    或者是 ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
    这都是强行把 ECDHE-ECDSA 这种套件提前的设置方式( EECDH 在我看来基本等价于 ECDHE )。
    所以,在大部分浏览器的套件选择默认以 ECDSA 为先的情况下,你又开了 ssl_prefer_server_ciphers on ,那无疑在取交集时,会优先选择 EECDH+ECDSA+CHACHA20 或 EECDH+ECDSA+AES128 ;因而这是必然:返回证书前两个一定都会是 ECDSA 证书了。
    解决方案其实只能是保守的:使用 SAN 证书。在这种双证书的特殊情况下, SNI 扩展不是主要矛盾,套件和证书才是。
    fourstring
        19
    fourstring  
    OP
       2017-02-06 11:04:16 +08:00
    @wql #18 谢谢,这是我的理解:浏览器取交集最后剩下 ECDSA 的 cipher ,但是为什么会一次性返回两个 ECC 证书?而不是返回本域名的一个 ECC 证书呢?

    另外 ququ 的 csr 生成命令中似乎就默认开启了 SAN 扩展,您这里的 SAN 证书还有什么需要额外注意的吗?
    wql
        20
    wql  
       2017-02-06 11:06:27 +08:00
    @fourstring 按照我的个人理解,这个问题还是在于 Nginx 在实现的时候把所有的 ECDSA 证书全返回去了。
    我的 SAN 证书的 CSR 就是按照 @qgy18 的方式生成的
    fourstring
        21
    fourstring  
    OP
       2017-02-06 11:10:09 +08:00
    @wql #20
    那就是说不能双站点共存?
    wql
        22
    wql  
       2017-02-06 12:08:45 +08:00
    @fourstring 我目前部署下来只发现两个解决方案:
    1.放弃给每个站点单独设双证书
    2.统一使用 SAN 来部署双证书
    fourstring
        23
    fourstring  
    OP
       2017-02-06 13:20:43 +08:00
    @wql #22 统一使用 SAN ?具体怎么操作呢?
    我对 SAN 的了解仅限于指定备用名称???
    wql
        24
    wql  
       2017-02-06 13:42:03 +08:00 via Android
    @fourstring 我可能表达不清楚,就是分别签发 ECDSA 和 RSA 这两张证书,这两张证书要把四个域名都填上。就是备用名称。
    fourstring
        25
    fourstring  
    OP
       2017-02-06 13:44:42 +08:00
    @wql #24 啊, SAN 还能填非本域名的域名?
    lhbc
        26
    lhbc  
       2017-02-06 13:49:18 +08:00
    @fourstring SAN 可以包含多个顶级域名
    你看下淘宝和天猫的证书
    fourstring
        27
    fourstring  
    OP
       2017-02-06 13:54:31 +08:00
    @lhbc #26 涨姿势了,谢谢!
    fourstring
        28
    fourstring  
    OP
       2017-02-06 18:35:36 +08:00
    @lhbc #26
    @wql #24 这个问题已经解决,但是 CT 策略失效了,是因为 SAN 有多个顶级域名的原因吗?
    wql
        29
    wql  
       2017-02-06 18:38:19 +08:00 via Android
    @fourstring 你是否知道,每签发一次证书,必须重新获取一次 SCT?
    fourstring
        30
    fourstring  
    OP
       2017-02-06 18:39:03 +08:00
    @wql #29 我的脚本里都是这么做的,签发一次就获取一次
    lightening
        31
    lightening  
       2017-02-06 18:43:22 +08:00
    你还有一个选择,试试我的 https://github.com/SteveLTN/https-portal
    wql
        32
    wql  
       2017-02-06 18:46:57 +08:00 via Android
    @fourstring 证书已经同一,那么 sct 必然是同一组啊。
    fourstring
        33
    fourstring  
    OP
       2017-02-06 18:47:32 +08:00
    @wql #32 是的,然并卵……
    chromee
        34
    chromee  
       2017-02-06 19:05:39 +08:00 via Android   2
    @fourstring 看这个 issue https://github.com/grahamedgecombe/nginx-ct/issues/13
    nginx-ct 用 OpenSSL 1.1.0 并用 SNI 的话不会对非默认的 vhost 返回 ct 信息
    我之前也遇到了这个问题 我觉得用 OpenSSL 1.0.2 的最新版就行 没必要上 1.1.0
    fourstring
        35
    fourstring  
    OP
       2017-02-06 19:17:14 +08:00
    @chromee #34 这样啊,谢谢!
    bobylive
        36
    bobylive  
       2017-02-06 19:45:59 +08:00 via Android
    之前情,用 default_server 解,也研究出什好方法,因以前就有一 IP 一的法。
    fourstring
        37
    fourstring  
    OP
       2017-02-06 19:48:29 +08:00   1
    @bobylive #36 已经解决。方案如下:用
    openssl req -new -sha256 -key double.ecc.key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:a.com,DNS:www.a.com,DNS:b.com,DNS:www.b.com")) > double.ecc.csr
    生成带 SAN 的 CSR ,然后提交给 let ‘ s encrypt (如果是别家那得支持签发 SAN 证书)即可。 SAN 上限 100 个域名,这样一个证书可以给多个顶级域名使用。
    如果用 nginx-ct 开启 CT ,不要使用 openssl1.1.0
    evlos
        38
    evlos  
       2017-02-06 20:21:23 +08:00 via iPhone
    另外我会单独加一个 default ,这样就算写错配置也不会很尴尬地进到其他 host 里
    bobylive
        39
    bobylive  
       2017-02-06 22:47:17 +08:00 via Android
    @fourstring 感,等我
    Showfom
        40
    Showfom  
    PRO
       2017-02-07 07:11:59 +08:00 via iPhone
    @bobylive 同意 楼主没有加 default_server 的锅
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1028 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 18:56 PVG 02:56 LAX 10:56 JFK 13:56
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86