diff options
author | Joseph Tate <jtate@dragonstrider.com> | 2013-03-18 10:43:07 -0700 |
---|---|---|
committer | Joseph Tate <jtate@dragonstrider.com> | 2013-03-18 10:43:07 -0700 |
commit | 9b644fd780854dd4800f64af5c55f9621f93aaca (patch) | |
tree | ca8f27bcbefa1a3bbd273a486a7c9db0ab63b0b2 | |
parent | fee468673ab90d8542f6613909b4e9ee3449069c (diff) | |
parent | fcbf6cfe34a797250331ebfe1a9cfb43504ef94f (diff) | |
download | cherrypy-9b644fd780854dd4800f64af5c55f9621f93aaca.tar.gz |
Merged in stpierre/cherrypy/1001-client-cert-verify (pull request #15)
Added support for client certificate verification in SSLAdapter (issue #1001)
-rw-r--r-- | cherrypy/test/ca.cert | 32 | ||||
-rw-r--r-- | cherrypy/test/ca.key | 51 | ||||
-rw-r--r-- | cherrypy/test/client.cert | 29 | ||||
-rw-r--r-- | cherrypy/test/client.key | 51 | ||||
-rw-r--r-- | cherrypy/test/client_ip.cert | 29 | ||||
-rw-r--r-- | cherrypy/test/client_wildcard.cert | 29 | ||||
-rw-r--r-- | cherrypy/test/client_wrong_ca.cert | 29 | ||||
-rw-r--r-- | cherrypy/test/client_wrong_host.cert | 29 | ||||
-rw-r--r-- | cherrypy/test/https_verifier.py | 162 | ||||
-rw-r--r-- | cherrypy/test/server.cert | 29 | ||||
-rw-r--r-- | cherrypy/test/server.key | 51 | ||||
-rw-r--r-- | cherrypy/test/server_wrong_ca.cert | 29 | ||||
-rw-r--r-- | cherrypy/test/server_wrong_host.cert | 29 | ||||
-rw-r--r-- | cherrypy/test/static/has space.html | 1 | ||||
-rw-r--r-- | cherrypy/test/test.conf | 11 | ||||
-rw-r--r-- | cherrypy/test/test_ssl.py | 207 | ||||
-rw-r--r-- | cherrypy/wsgiserver/ssl_builtin.py | 113 | ||||
-rw-r--r-- | cherrypy/wsgiserver/ssl_pyopenssl.py | 35 | ||||
-rw-r--r-- | cherrypy/wsgiserver/wsgiserver2.py | 39 | ||||
-rw-r--r-- | cherrypy/wsgiserver/wsgiserver3.py | 31 |
20 files changed, 1006 insertions, 10 deletions
diff --git a/cherrypy/test/ca.cert b/cherrypy/test/ca.cert new file mode 100644 index 00000000..3ba97d74 --- /dev/null +++ b/cherrypy/test/ca.cert @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFlzCCA3+gAwIBAgIJAPYshx+wivLoMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJYWDEMMAoGA1UEBxMDWFhYMREwDwYDVQQKEwhDaGVy +cnlQeTAeFw0xMDEwMDQxOTAxNDJaFw0yMDEwMDExOTAxNDJaMDsxCzAJBgNVBAYT +AlVTMQswCQYDVQQIEwJYWDEMMAoGA1UEBxMDWFhYMREwDwYDVQQKEwhDaGVycnlQ +eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKzWr8dvyn1Nsq2xRFpM ++Ockg99V0/OAD6SrzrI9Uf//BcSotbooDyvUJ1Z2iW9wOfIj5MQ0juNpu4AlfyTa +cPZPDrbf/wJkjWj+2UIrImZOrWVmtsRb+OJi2cH1yjibvMfn8BTxhW9P03jUb1O7 +V6Lnnp1XktJM9vZWLhtoKDwIYNzsG/S1WUX4OfBu/QofrPTeBBCFV7Q/6V3FCa9P +aBijXhuzlt3D9YMxiL4Uu18Mk/KgzReOBCDrTftatbZpdHnB0s9Oq3ZwuF1SbcT0 +lAw2MLguuA1j8RmaaqmoNNogsiyQpXY06ef3Skom86mkiXmOIgyX6IQ/BXhFoH4i +96vv3SptD43g6TFMjvWEBlJIWDBNCxGNnCbHNj70MCLJ1KeW0KUUc6WTxB2ADYif +Ifpo1UR2g0KQjHhLhdWDU4aCqj3S/n23Ho7Q4cD36VPdoknMaDJs+7UDJ4B9b+eZ +L0IYcLhqtSHPVdxxYejS1GLNXWizcwmJV3z1tA+1g0jTRA70Ee6SdMOxlnEf4ACT +IdwcC8xQWh0j4rgqT4Z1EYmP+iukjh029Ge4rASaBkbyRJ1ALESdxr5nAryq8wxj +/XVwgOMU5O4zuANggvq8GGAyrixcPJB+f8AYhdHp0yjKuUNJ2YbH1G3Dlg8hqyNA +uNc88jG8nc1EKd8/OHQnabZVAgMBAAGjgZ0wgZowHQYDVR0OBBYEFDYQnybvD5dR +D8rLKlXPZifiWPEYMGsGA1UdIwRkMGKAFDYQnybvD5dRD8rLKlXPZifiWPEYoT+k +PTA7MQswCQYDVQQGEwJVUzELMAkGA1UECBMCWFgxDDAKBgNVBAcTA1hYWDERMA8G +A1UEChMIQ2hlcnJ5UHmCCQD2LIcfsIry6DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3 +DQEBBQUAA4ICAQCSlMKC7WUxee3QvXNPW08Q86IzcwAZqKNZy2+ZyqUPUYzeaFW8 +exTDELv8yMj0C5VjprmdIrhsMKXZSA8D8/JuUZgHUruLLXn/Dc4HRFa79cVxW9yx +YM6weTgdNnBBtdi+Qz3Sc9UiIOlr5YzoZq2qTZw2u8ogLx9iPPrbg8Qiurx8WJse +rzb1iqoqSLV/RpaktKNkgQpqao4TrDkxjgtuiKyMj7DKRgTz2sWwKdpPtXLCYIz2 +Xc/jxjj1xFHbKMCHxdjgYCrGUzHBUV8LhyNrii573L1Bxedi/7Jdr2IMkiEWuyus +QthB+CGwCGUzm/nh/AVsnG2YNCRraiK1FWuq1FawudYM0eoozWIdk0lU9IR1jgNi +y8LCQD/8d3E8EJ/dkP8o4mvmOeYko7QTb7gCNdT06YierNTi8DCZ31iggQfxOH0P +eW1siKumG1wztEoPan/nbTsJchhqpfY6qN2YikH/6cQGR0b9tDJZe/BF7mdRXq8d +vF4HIIOAn6xCK1/TdUbmCp6MaUYdWHM4MoAWbf592j9oER/EIc603DYjdDBTUVO3 +pYPbVg1SWINvKzTTEFFXtOvJNt5dFxFlARMcUR034Au62EMOTvC3/7bQcy0nEPTV +G50UvRgwvNrr3D2el/jurDaHdJBQ90qUnAnI56uW7puyKvMPFXS1q2bsvQ== +-----END CERTIFICATE----- diff --git a/cherrypy/test/ca.key b/cherrypy/test/ca.key new file mode 100644 index 00000000..11b58545 --- /dev/null +++ b/cherrypy/test/ca.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/cherrypy/test/client.cert b/cherrypy/test/client.cert new file mode 100644 index 00000000..1e5c3825 --- /dev/null +++ b/cherrypy/test/client.cert @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE8DCCAtgCAQMwDQYJKoZIhvcNAQEFBQAwOzELMAkGA1UEBhMCVVMxCzAJBgNV +BAgTAlhYMQwwCgYDVQQHEwNYWFgxETAPBgNVBAoTCENoZXJyeVB5MB4XDTEwMTAw +NTE2NDkwNVoXDTIwMTAwMjE2NDkwNVowQTELMAkGA1UEBhMCVVMxCzAJBgNVBAgT +AlhYMREwDwYDVQQKEwhDaGVycnlQeTESMBAGA1UEAxMJbG9jYWxob3N0MIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAymPRfUID7S9YAy36CqGg1JEXGYnf +/u6CN/6OnTTLbWjQLzUaVy2gjqqVXXamZv9x4dVv7JLYl4RZ5fX72HGmcIP2tyDW +cRLizmoKlo7Ih8k5Z66u4qFp7ebQrEma0WZvlqKUFrX2ataaJIm51809YJXZXK3G +2gdwJyjPeCK9B8RkCqRTpiSxR38e3iSvpsqkwqqoxNcZAE2z2VV0c+p9HcwEeNN9 +3ZQEdYkA4ni6U6tnJ5v17CaJwvF7mVfxydsoHU7qH8lE5R3mBN6BgZMjZtwOWDFw +3grUFBiS9F7NiMxNvnykwr4WTn7fc6hSm/Rvls12ALElIDd5qe9GpnPXJH3OMG87 +BHxa0DRuM7e6yw/dpBlMcfzyo1um7xe9DJJnc63hwQdc1agjr5zzkZWkv4I/4j88 +gmgKjcEa4PbpmyZ8E8uNsPSO9Vy7csel+A7IwHiiArVfoel6dor34km6KYIN2uq7 +P04djTOy+KR0ry+OwCo2vTkjPcd7S5Ta7rVsfGo3wxpxysTCDtBA6lMMd0otIZiE +Hz2Z89sHS6ZebYM2yvLcxvFYp6w8id4ETcRLoITP5+TCUWedZ+tuixygZrcqfQFF +WHlpGTlBO765xvH11rzFKDZSVrXwGpo82qHcFCw0imkXnKSoFUIX+EK97TK4RtBs +kpNSll1e+d7XokECAwEAATANBgkqhkiG9w0BAQUFAAOCAgEAUdqrvx74A1pLYkHp +bhxHMuMEsg1m0eWVLrM9VN2lUTcDPySYmWmeT8ywOZWUCKyDfc7hBhRwyQbfYkoQ +FbgDebvrLQvXvTCWIv5paFq4d05oSiBvvQb0KHmw9gDsDne77dYetUUTupyI1YYV +70bIcZYk27jOSSZa7rFyZvJxO1vc1eBb+w8Wm0i6vWERUDIBI8hcSvGPiWt+/VCB +feGIYiJN6xyYg3/Yi6OgzZLGGiJxhJiHvlA7BkTR7TSlIrskbipBydWeRrU/d9TS +OOwviIlw0hGdBSsOgqzfNal1x8KXIJ87rdn6LwOp1m3k1T1QcgXYp5Xv6dYeJYYJ +/KJ8dOCnh5lRmc/y6dgV3V/U2iT7co8TQjhYANAa+0YhNMflrhbGYfv8TcarWv4v +eE1eUMgKCTAdqQix5e5yXtimOqM+ubsV1Oo64AUdguy1EyDGpQf8py5Ps0YpfQms +S2FMskUuEkYRvZQgvp4jtlYpBfc9yojFrHO320tepwfrD4+T6w6pbhEiR8T5oagu +cARuXtn4fuVFUu49sx4Hs8X1gtLS4HjPAYQveB+ZQuysn8O3JWKz9eBQ/JIVjeh1 +2Ggv9gO8PMO175g4ePAZa6uSl39tCOtNBf0YdZCi4XmJn+FzDNec7eLTv7oognCG +VCp+fSm/1V5FLqnelUk1NTMbwYg= +-----END CERTIFICATE----- diff --git a/cherrypy/test/client.key b/cherrypy/test/client.key new file mode 100644 index 00000000..8f9a04fc --- /dev/null +++ b/cherrypy/test/client.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/cherrypy/test/client_ip.cert b/cherrypy/test/client_ip.cert new file mode 100644 index 00000000..d99c4b70 --- /dev/null +++ b/cherrypy/test/client_ip.cert @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE8TCCAtkCAgRjMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlVTMQswCQYD +VQQIEwJYWDEMMAoGA1UEBxMDWFhYMREwDwYDVQQKEwhDaGVycnlQeTAeFw0xMDEw +MDgxNjQwMTRaFw0yMDEwMDUxNjQwMTRaMEExCzAJBgNVBAYTAlVTMQswCQYDVQQI +EwJYWDERMA8GA1UEChMIQ2hlcnJ5UHkxEjAQBgNVBAMTCTEyNy4wLjAuMTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMpj0X1CA+0vWAMt+gqhoNSRFxmJ +3/7ugjf+jp00y21o0C81GlctoI6qlV12pmb/ceHVb+yS2JeEWeX1+9hxpnCD9rcg +1nES4s5qCpaOyIfJOWeuruKhae3m0KxJmtFmb5ailBa19mrWmiSJudfNPWCV2Vyt +xtoHcCcoz3givQfEZAqkU6YksUd/Ht4kr6bKpMKqqMTXGQBNs9lVdHPqfR3MBHjT +fd2UBHWJAOJ4ulOrZyeb9ewmicLxe5lX8cnbKB1O6h/JROUd5gTegYGTI2bcDlgx +cN4K1BQYkvRezYjMTb58pMK+Fk5+33OoUpv0b5bNdgCxJSA3eanvRqZz1yR9zjBv +OwR8WtA0bjO3ussP3aQZTHH88qNbpu8XvQySZ3Ot4cEHXNWoI6+c85GVpL+CP+I/ +PIJoCo3BGuD26ZsmfBPLjbD0jvVcu3LHpfgOyMB4ogK1X6HpenaK9+JJuimCDdrq +uz9OHY0zsvikdK8vjsAqNr05Iz3He0uU2u61bHxqN8MaccrEwg7QQOpTDHdKLSGY +hB89mfPbB0umXm2DNsry3MbxWKesPIneBE3ES6CEz+fkwlFnnWfrboscoGa3Kn0B +RVh5aRk5QTu+ucbx9da8xSg2Ula18BqaPNqh3BQsNIppF5ykqBVCF/hCve0yuEbQ +bJKTUpZdXvne16JBAgMBAAEwDQYJKoZIhvcNAQEFBQADggIBAJpdyMHD7nsrd7dA +r1wDdUg8nXOq0ZJAcheMIakUNhjzymn4XF/o8KhPykuPdHFilOgqAT6CAM//hFDO +tzROsq74wB6ICxerPsuDIppxhbSRuVYbFlr2aUXRUQZIWNAjvTnOfLhAy+i8BVlS +8mEdQrqYLpg1RJhRWT7crSMAex4IIZzSnblFs1yo3Hx97eIgHViTBxGfsSMtvdie +ZOWbKV9ueYkgxN8t8b9qDlhiaaASoFP8f3kktmKtk+8yxBA1jiPA9sYPL5sqvdP0 +ZF0ul6mo0I6H01ITkXPF0RlNScHVS+ryouIOaWXe80mHtuGwYiAFADTab0WsmIXm +dRoUFX3yRAc1tJnUJwbHUkZWwI6rU3b6dHmfuaUq06i2MhpdW+awhycCRExKdxCm +jdTzqlYVoJj0ARXIQqV5wS5EZ+T3pFoykV3mvYaqlBbogfyt+LoPoVmPpPzXG/DX +ihxaGAmcvENatdwAfqP9ycZCcXKmfLf7amwyekS6qZ6nUgGeRBQNA34mrIxg/Yij +yzfVhnMJ7Mi7fzLxZrF59N2uIV2y/6oH6eMakJ6kniCexgTh8EoADF4JQqHGvIfX +c/HBoU3BAlXDYuzZVpL6uAhJwDbqyKMqUVvGTfnM65PSoAtjbyre1Tk6WtgcgD+q +ycDjzcNUsA9fv/zBCS1QbYPIrVyh +-----END CERTIFICATE----- diff --git a/cherrypy/test/client_wildcard.cert b/cherrypy/test/client_wildcard.cert new file mode 100644 index 00000000..671fd095 --- /dev/null +++ b/cherrypy/test/client_wildcard.cert @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE8zCCAtsCAivjMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlVTMQswCQYD +VQQIEwJYWDEMMAoGA1UEBxMDWFhYMREwDwYDVQQKEwhDaGVycnlQeTAeFw0xMDEw +MDgxNjQwMzBaFw0yMDEwMDUxNjQwMzBaMEMxCzAJBgNVBAYTAlVTMQswCQYDVQQI +EwJYWDERMA8GA1UEChMIQ2hlcnJ5UHkxFDASBgNVBAMUCyoubG9jYWxob3N0MIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAymPRfUID7S9YAy36CqGg1JEX +GYnf/u6CN/6OnTTLbWjQLzUaVy2gjqqVXXamZv9x4dVv7JLYl4RZ5fX72HGmcIP2 +tyDWcRLizmoKlo7Ih8k5Z66u4qFp7ebQrEma0WZvlqKUFrX2ataaJIm51809YJXZ +XK3G2gdwJyjPeCK9B8RkCqRTpiSxR38e3iSvpsqkwqqoxNcZAE2z2VV0c+p9HcwE +eNN93ZQEdYkA4ni6U6tnJ5v17CaJwvF7mVfxydsoHU7qH8lE5R3mBN6BgZMjZtwO +WDFw3grUFBiS9F7NiMxNvnykwr4WTn7fc6hSm/Rvls12ALElIDd5qe9GpnPXJH3O +MG87BHxa0DRuM7e6yw/dpBlMcfzyo1um7xe9DJJnc63hwQdc1agjr5zzkZWkv4I/ +4j88gmgKjcEa4PbpmyZ8E8uNsPSO9Vy7csel+A7IwHiiArVfoel6dor34km6KYIN +2uq7P04djTOy+KR0ry+OwCo2vTkjPcd7S5Ta7rVsfGo3wxpxysTCDtBA6lMMd0ot +IZiEHz2Z89sHS6ZebYM2yvLcxvFYp6w8id4ETcRLoITP5+TCUWedZ+tuixygZrcq +fQFFWHlpGTlBO765xvH11rzFKDZSVrXwGpo82qHcFCw0imkXnKSoFUIX+EK97TK4 +RtBskpNSll1e+d7XokECAwEAATANBgkqhkiG9w0BAQUFAAOCAgEAPPJULe6zpVta +ihGI3j9ALaXbfCAarHCRbzwhFu6YzcmQ76H4Ec1yq/Gt/dhE+UrUXEe1sUMUgQYm +LyBqppkHbA0m+ehvGg5Rbe/R9ZDp2Q1JzI39YLqDgY3MHIkO6TST6Ps94X0Udn6N +VxA2gQRu0vI9/e7zWxSg+RkR+X60AYkcJNy+h+PZewTFQoUyAiBWRjrqDzGqiNuC +WRCX7vE5TqRq6f62zYCC/rnUDbUae80Cf4aTsNHJWdtljijCY/bzsmvOp+mrq7j6 +XEa6uvdS/obvze8/xiFizEbCz9z8UUWTcKq2CiOEQzl8LAlBIbrLJQSKoGH6L+oM +J+Cwe4itpBe7shgRfaPfC7avwCiJkMY13yBBPfzQ9vTbl9MOg5gTlFUEdl/DHTuw +1HLmJzAWsfxyBeggt76oeuKytFmXrqOoCQhd/EF113ykrg3I9Crf1x4kjk/AYbjN +LqLVvsChNYtO7lNjW+dUsqptq4meCVWh/HY1Znk2yVsI2coBgiBkIIrBNfoTxYFX +0iBxpIp8XL+Y22DiUV6owbErPCy8qUvPmgHa7OF8G1koN4vubEHtfegZEjGLcMZG +SbIo4cZZ/EmZHSzGilyGVvpV9zYRQfs8sruHhy4BOGS2EFyrJcOVOhLHEODZFMDH +PzR1WeCLFQAYsgHr3Y7HHk0ljgBHIUY= +-----END CERTIFICATE----- diff --git a/cherrypy/test/client_wrong_ca.cert b/cherrypy/test/client_wrong_ca.cert new file mode 100644 index 00000000..450c98f6 --- /dev/null +++ b/cherrypy/test/client_wrong_ca.cert @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE4jCCAsoCAQIwDQYJKoZIhvcNAQEFBQAwLTELMAkGA1UEBhMCVVMxCzAJBgNV +BAgTAlhYMREwDwYDVQQKEwhDaGVycnlQeTAeFw0xMDEwMDQxOTExMzZaFw0yMDEw +MDExOTExMzZaMEExCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJYWDERMA8GA1UEChMI +Q2hlcnJ5UHkxEjAQBgNVBAMTCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAMpj0X1CA+0vWAMt+gqhoNSRFxmJ3/7ugjf+jp00y21o0C81 +GlctoI6qlV12pmb/ceHVb+yS2JeEWeX1+9hxpnCD9rcg1nES4s5qCpaOyIfJOWeu +ruKhae3m0KxJmtFmb5ailBa19mrWmiSJudfNPWCV2VytxtoHcCcoz3givQfEZAqk +U6YksUd/Ht4kr6bKpMKqqMTXGQBNs9lVdHPqfR3MBHjTfd2UBHWJAOJ4ulOrZyeb +9ewmicLxe5lX8cnbKB1O6h/JROUd5gTegYGTI2bcDlgxcN4K1BQYkvRezYjMTb58 +pMK+Fk5+33OoUpv0b5bNdgCxJSA3eanvRqZz1yR9zjBvOwR8WtA0bjO3ussP3aQZ +THH88qNbpu8XvQySZ3Ot4cEHXNWoI6+c85GVpL+CP+I/PIJoCo3BGuD26ZsmfBPL +jbD0jvVcu3LHpfgOyMB4ogK1X6HpenaK9+JJuimCDdrquz9OHY0zsvikdK8vjsAq +Nr05Iz3He0uU2u61bHxqN8MaccrEwg7QQOpTDHdKLSGYhB89mfPbB0umXm2DNsry +3MbxWKesPIneBE3ES6CEz+fkwlFnnWfrboscoGa3Kn0BRVh5aRk5QTu+ucbx9da8 +xSg2Ula18BqaPNqh3BQsNIppF5ykqBVCF/hCve0yuEbQbJKTUpZdXvne16JBAgMB +AAEwDQYJKoZIhvcNAQEFBQADggIBABJ6kQ5SLRMVrDiA4MSGNdZ2/5DdaGQ8U5ai +wjZ+JPu/efhiOkSSdFJCDXXJphAmGHWOTzGWc/NjgEJ2hswixsMS1nYxrP+abGjb +I+Nw0KOLynd2++hlS7JIVUzBzZsu5aE0QHQtg9bYRt2Sw94LDpiUvpmDSid10coQ +au444fNxeXVixZdyJxEyS5b5KXB0p3xHL7My4wZ8/bM/TPHyoe4qVJM5egSrw0Oz +LItyNVJ8akc9gfwOgX8SzRG75T15IuCojnhKKRiaHJMahRQO3u+JB4+gA4hGk0Rx +JQferTzQM/goYMSq3mQcKUbgsEf3wUmFWJDnkJAXgLHeZh1Oawug9VsGlCxS+IJ3 +Mj9hoPI4RYrMOGI8EUXBrXWhxhhi2A1LR+o4/+O5IrVBp298vEpO+EWHb9/lbt2F +gpU5jZcRR+M76MdVHF5X4RD+rE97vRZofR7bfhxmvJ+LeForbJk3u0arCLD6LHfG +TtUGsRWdABQuB/ZvRNYe6KWCxvuVTwTLayYNwbPr5qRCs4fHww6vRJGvDC5fqdIQ +7aqRvwrKq2w7dMIZK0BiVEtOiBoHcKhvzD3yfi/OkqaoPbIodN5C65g2prHjwyD0 +uCqynSHHjP3qycXLLaChrYSftNwvBSJKKfbCyY/MGU7lOydkpkiFvsGXW8elC7On +Dvhc259l +-----END CERTIFICATE----- diff --git a/cherrypy/test/client_wrong_host.cert b/cherrypy/test/client_wrong_host.cert new file mode 100644 index 00000000..c40048ac --- /dev/null +++ b/cherrypy/test/client_wrong_host.cert @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE9DCCAtwCAQQwDQYJKoZIhvcNAQEFBQAwOzELMAkGA1UEBhMCVVMxCzAJBgNV +BAgTAlhYMQwwCgYDVQQHEwNYWFgxETAPBgNVBAoTCENoZXJyeVB5MB4XDTEwMTAw +NDE5MTAzOVoXDTIwMTAwMTE5MTAzOVowRTELMAkGA1UEBhMCVVMxCzAJBgNVBAgT +AlhYMREwDwYDVQQKEwhDaGVycnlQeTEWMBQGA1UEAxQNbm90X2xvY2FsaG9zdDCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMpj0X1CA+0vWAMt+gqhoNSR +FxmJ3/7ugjf+jp00y21o0C81GlctoI6qlV12pmb/ceHVb+yS2JeEWeX1+9hxpnCD +9rcg1nES4s5qCpaOyIfJOWeuruKhae3m0KxJmtFmb5ailBa19mrWmiSJudfNPWCV +2VytxtoHcCcoz3givQfEZAqkU6YksUd/Ht4kr6bKpMKqqMTXGQBNs9lVdHPqfR3M +BHjTfd2UBHWJAOJ4ulOrZyeb9ewmicLxe5lX8cnbKB1O6h/JROUd5gTegYGTI2bc +DlgxcN4K1BQYkvRezYjMTb58pMK+Fk5+33OoUpv0b5bNdgCxJSA3eanvRqZz1yR9 +zjBvOwR8WtA0bjO3ussP3aQZTHH88qNbpu8XvQySZ3Ot4cEHXNWoI6+c85GVpL+C +P+I/PIJoCo3BGuD26ZsmfBPLjbD0jvVcu3LHpfgOyMB4ogK1X6HpenaK9+JJuimC +Ddrquz9OHY0zsvikdK8vjsAqNr05Iz3He0uU2u61bHxqN8MaccrEwg7QQOpTDHdK +LSGYhB89mfPbB0umXm2DNsry3MbxWKesPIneBE3ES6CEz+fkwlFnnWfrboscoGa3 +Kn0BRVh5aRk5QTu+ucbx9da8xSg2Ula18BqaPNqh3BQsNIppF5ykqBVCF/hCve0y +uEbQbJKTUpZdXvne16JBAgMBAAEwDQYJKoZIhvcNAQEFBQADggIBABOSv3BiRC7K +oZXdLuEIWsAyzt5Ir4aRDqXZxIlFhcek2hs9AB+aNOcRLM6o+FR/ynXiUjftBJYL +lHUsYzXt0xIxrxH8W2SgTgyY2ZxMXq15c1mDWZUeM+lejuBMII1qNTUblb9vy0sl +xf3JmsrIUk7Ji+0eMY2ZI7eMCYLN3akdzixI4Qb5UgyKoRBqWrTnTomB0SnCqEiv +md4BG5ifKJjGtr01xJXcTmwa80v8ANmLz1WS4no4lyau2Yf30KGxEJ2nvIiV4Nhs +2iD/cnJYPcMkqFTKodt9JVLDCiec4Gh1HkN0VWy1XyEZvZasfO2/kFcYS8Pg9xTQ +2w0/9VxckWx8JA54GYovhVYIYfEDZgIICf4EGIhljGX5K0gQ/jJc9Rx4dYjmKItx +Js/js+CDLOOErnXJ7ZexArvwyRdbVQZC2PsdK7HcWnKYvjf8x4bGFSzmB/SLVnYL +mx6ygRhdlftobhB2RnXDPtHzU28SiGGG90zzjvZoV2LdNgHttcwzjPbMU2gRXvcM +wUFdk+xvEhjuwct6ZmZG5MbBbyXllGHoU8VadvQiK1ufrhcBdla6vaNhyNebuB1t +P+N+T1Xe7jnjcVSkgQjT4W6du4gif4q3wFQcXuW4WtHua5b6JibQ5kNoOcct+sV7 +ZGrvlNC+PcYw1dFtI6I7ANWrdGmuGD9w +-----END CERTIFICATE----- diff --git a/cherrypy/test/https_verifier.py b/cherrypy/test/https_verifier.py new file mode 100644 index 00000000..87ab236c --- /dev/null +++ b/cherrypy/test/https_verifier.py @@ -0,0 +1,162 @@ +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Extensions to allow HTTPS requests with SSL certificate validation.""" + + +import httplib +import re +import socket +import urllib2 +import ssl + + +class InvalidCertificateException(httplib.HTTPException, urllib2.URLError): + """Raised when a certificate is provided with an invalid hostname.""" + + def __init__(self, host, cert, reason): + """Constructor. + + Args: + host: The hostname the connection was made to. + cert: The SSL certificate (as a dictionary) the host returned. + """ + httplib.HTTPException.__init__(self) + self.host = host + self.cert = cert + self.reason = reason + + def __str__(self): + return ('Host %s returned an invalid certificate %s %s\n' % + (self.host, self.reason, self.cert)) + +class CertValidatingHTTPSConnection(httplib.HTTPConnection): + """An HTTPConnection that connects over SSL and validates certificates.""" + + default_port = httplib.HTTPS_PORT + + def __init__(self, host, port=None, key_file=None, cert_file=None, + ca_certs=None, strict=None, **kwargs): + """Constructor. + + Args: + host: The hostname. Can be in 'host:port' form. + port: The port. Defaults to 443. + key_file: A file containing the client's private key + cert_file: A file containing the client's certificates + ca_certs: A file contianing a set of concatenated certificate + authority certs for validating the server against. + strict: When true, causes BadStatusLine to be raised if the status + line can't be parsed as a valid HTTP/1.0 or 1.1 status line. + """ + httplib.HTTPConnection.__init__(self, host, port, strict, **kwargs) + self.key_file = key_file + self.cert_file = cert_file + self.ca_certs = ca_certs + if self.ca_certs: + self.cert_reqs = ssl.CERT_REQUIRED + else: + self.cert_reqs = ssl.CERT_NONE + + def _GetValidHostsForCert(self, cert): + """Returns a list of valid host globs for an SSL certificate. + + Args: + cert: A dictionary representing an SSL certificate. + Returns: + list: A list of valid host globs. + """ + if 'subjectAltName' in cert: + return [x[1] for x in cert['subjectAltName'] + if x[0].lower() == 'dns'] + else: + return [x[0][1] for x in cert['subject'] + if x[0][0].lower() == 'commonname'] + + def _ValidateCertificateHostname(self, cert, hostname): + """Validates that a given hostname is valid for an SSL certificate. + + Args: + cert: A dictionary representing an SSL certificate. + hostname: The hostname to test. + Returns: + bool: Whether or not the hostname is valid for this certificate. + """ + hosts = self._GetValidHostsForCert(cert) + for host in hosts: + host_re = host.replace('.', '\.').replace('*', '[^.]*') + if re.search('^%s$' % (host_re,), hostname, re.I): + return True + return False + + def _possible_addresses(self, address): + name, port = address[:2] + canonical, alt_hosts, host_ips = socket.gethostbyaddr(name) + all_addrs = set([name] + [canonical] + alt_hosts + host_ips) + all_addrs.update(info[4][0] for addr in all_addrs.copy() + for info in socket.getaddrinfo(addr, port)) + return all_addrs + + def _matches(self, addr, cname): + if cname.startswith("*."): + return addr == cname[2:] or addr.endswith(cname[1:]) + else: + return addr == cname + + def _address_matches(self, address, cert): + for cname in self._GetValidHostsForCert(cert): + for possible in self._possible_addresses(address): + if self._matches(possible, cname): + return True + return False + + def connect(self): + "Connect to a host on a given (SSL) port." + sock = socket.create_connection((self.host, self.port)) + self.sock = ssl.wrap_socket(sock, keyfile = self.key_file, + certfile = self.cert_file, + cert_reqs = self.cert_reqs, + ca_certs = self.ca_certs) + if self.cert_reqs & ssl.CERT_REQUIRED: + addr = self.sock.getpeername() + cert = self.sock.getpeercert() + if not self._address_matches(addr, cert): + raise InvalidCertificateException(addr, cert, + 'hostname mismatch') + + +class VerifiedHTTPSHandler(urllib2.HTTPSHandler): + """An HTTPHandler that validates SSL certificates.""" + + def __init__(self, **kwargs): + """Constructor. Any keyword args are passed to the httplib handler.""" + urllib2.AbstractHTTPHandler.__init__(self) + self._connection_args = kwargs + + def https_open(self, req): + def http_class_wrapper(host, **kwargs): + full_kwargs = dict(self._connection_args) + full_kwargs.update(kwargs) + return CertValidatingHTTPSConnection(host, **full_kwargs) + + try: + return self.do_open(http_class_wrapper, req) + except urllib2.URLError, e: + if type(e.reason) == ssl.SSLError and e.reason.args[0] == 1: + raise InvalidCertificateException(req.host, '', + e.reason.args[1]) + raise + + https_request = urllib2.HTTPSHandler.do_request_ diff --git a/cherrypy/test/server.cert b/cherrypy/test/server.cert new file mode 100644 index 00000000..da8f3cab --- /dev/null +++ b/cherrypy/test/server.cert @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE8DCCAtgCAQEwDQYJKoZIhvcNAQEFBQAwOzELMAkGA1UEBhMCVVMxCzAJBgNV +BAgTAlhYMQwwCgYDVQQHEwNYWFgxETAPBgNVBAoTCENoZXJyeVB5MB4XDTEwMTAw +NTE2NTU0NFoXDTIwMTAwMjE2NTU0NFowQTELMAkGA1UEBhMCVVMxCzAJBgNVBAgT +AlhYMREwDwYDVQQKEwhDaGVycnlQeTESMBAGA1UEAxMJbG9jYWxob3N0MIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5LKNLY3Il4TFKigGvRqu3RXHvU/T +9wrNyD/SCkUFFP/kTophyFoFyLu1jhmw6YQQfr7Q8rpvH2wXR/l7wvlr2HzfOXZH +TA9vUsZLdAo07thYRPzLOAANKjVDmn6/dzyfeGxmpmxYlEioRtYuYDqpUoAM5V7s +6B7aoW4llWi2rrRn8h6yAGlRrvoAUc/gOugOFzCVerQ6OFvY54YDUEkK/HqhpD70 +grYh9DdNKiNbHaDBDlQ1WEON/N4tTY1KFvYcHo67U5dkxweNQmUBZQYkp2fAO6FA +FOfQahXjvO7QpoIYU00XjF61YBKNplTQhWH7x9ZAIzdSedhu2e6yZqLc/5wgw9lD +pYeNqvTGK28UF+9JjvoYoI3T5//CDxrQjTZlhiW7NF1gnapaQR/uwB9AoFierEGI +dEX5ob8Hv0V8hPhCiJUypQj0tvhGuCwAktf6jtlsWNMGrD4IpE1LEcPzhGbDZBs7 +i0J9chmXaklPed+enTpHCrLzhY7ImRzrnRnfceIkRQCL3TRvWJO+JeQ1Nahiw7m+ +S7jie63cLwiLMgFqcqMu7s0Yt7qrnA4Rg39B2KCdDMw1pAkoU4HMOY5x/8Lvsukz +3y9IJHN5i+GbPQDN+RdAA4nFTNA9au+j/YJZETw61PUmbx8YdLfnOXyr+/JL/rQ+ +VIAHHMur+dO6qp8CAwEAATANBgkqhkiG9w0BAQUFAAOCAgEAfnA62cflmXJ6fLpi +wp0egKb4CgQK8Kc8qio4U7X76Fhp3YQFQu8FZu+h2EAqwOuuYFZD4PzjdVA2DgTc ++OcIiKX0K3+KRuAzlhQ1jGxVcXjAQD6oHQdd6bFh8HbnBa3MJqtTx0U3wzEuIpXi +3Nxjb79mFkVhjWFoj9gKa1l8KPqZGePr4iJZzmmPah/GoaPGJrD6RwTu+8BSjiQi +FPmmZF7IauMoY2lKGloSNRd6FFiCSvtiTzkIvoL6pjOz2o5QFborEp5myF8z95n9 +UbrFB80JMhLDcxD6F4AInL8Mz6N8g32dgz8DgVXp93pRwWoeUlY+DQSZ7qBsIt2U +U/gK6sTu7XnYX3JFTAkyBcU9xBMEgZiLLxzTGNYdzK1BDeHm9GVabqpRtc4acsEi +diWGq6RGhe9o4VoYj0OzBHkI95ZJExK2tcO5DKpbXkpZOK1zTZaMNyW043UkO7FT +/AXy90aO3aQXGnb9OR+14zbSXk8u0vaVwhPbzHMcQ4JVxaU2acLLSK94rdz+t/oR +wIIjcST1eyvVb0y1C5TlZR6jIEfcZdRcdzGbGk2+Pi8Pv7HN1F5uoZy9UQmqqOD7 +b6qOGuymiC59o7jHmND+By3L51XqWzpPyyeAZv7WeAln93idRVjEhDYiWDfeFePV +H7+ZxbdpJ4BDgCwYBaFYyFReMcw= +-----END CERTIFICATE----- diff --git a/cherrypy/test/server.key b/cherrypy/test/server.key new file mode 100644 index 00000000..81453f31 --- /dev/null +++ b/cherrypy/test/server.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/cherrypy/test/server_wrong_ca.cert b/cherrypy/test/server_wrong_ca.cert new file mode 100644 index 00000000..aef25868 --- /dev/null +++ b/cherrypy/test/server_wrong_ca.cert @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE4jCCAsoCAQEwDQYJKoZIhvcNAQEFBQAwLTELMAkGA1UEBhMCVVMxCzAJBgNV +BAgTAlhYMREwDwYDVQQKEwhDaGVycnlQeTAeFw0xMDEwMDQxOTA2MjJaFw0yMDEw +MDExOTA2MjJaMEExCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJYWDERMA8GA1UEChMI +Q2hlcnJ5UHkxEjAQBgNVBAMTCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAOSyjS2NyJeExSooBr0art0Vx71P0/cKzcg/0gpFBRT/5E6K +YchaBci7tY4ZsOmEEH6+0PK6bx9sF0f5e8L5a9h83zl2R0wPb1LGS3QKNO7YWET8 +yzgADSo1Q5p+v3c8n3hsZqZsWJRIqEbWLmA6qVKADOVe7Oge2qFuJZVotq60Z/Ie +sgBpUa76AFHP4DroDhcwlXq0Ojhb2OeGA1BJCvx6oaQ+9IK2IfQ3TSojWx2gwQ5U +NVhDjfzeLU2NShb2HB6Ou1OXZMcHjUJlAWUGJKdnwDuhQBTn0GoV47zu0KaCGFNN +F4xetWASjaZU0IVh+8fWQCM3UnnYbtnusmai3P+cIMPZQ6WHjar0xitvFBfvSY76 +GKCN0+f/wg8a0I02ZYYluzRdYJ2qWkEf7sAfQKBYnqxBiHRF+aG/B79FfIT4QoiV +MqUI9Lb4RrgsAJLX+o7ZbFjTBqw+CKRNSxHD84Rmw2QbO4tCfXIZl2pJT3nfnp06 +Rwqy84WOyJkc650Z33HiJEUAi900b1iTviXkNTWoYsO5vku44nut3C8IizIBanKj +Lu7NGLe6q5wOEYN/QdignQzMNaQJKFOBzDmOcf/C77LpM98vSCRzeYvhmz0AzfkX +QAOJxUzQPWrvo/2CWRE8OtT1Jm8fGHS35zl8q/vyS/60PlSABxzLq/nTuqqfAgMB +AAEwDQYJKoZIhvcNAQEFBQADggIBALGpyj0/UKlJaZGfsbaQTNAPU7sWPAMqVvGT +7CuLnyCou1wFk7gHyckvAcagsbfK9nfhNuZNvmriPU5BFpFOSA2+492hu1ww+k+7 ++UB4IbsIaQ2pCoiPnWkYl4Y6tZ7wvKYY022vyS/54TqzOsB80rvUYYOuaf91U63y +BX6TTJSvajQRDC+teTHSH6A9lqY0V8HG4BdCpn1WqHGq+P7+SQ0S0hq4Xu86f8Ri +ycOljavptcEPpGPPl7mLKLkhdDUoUHHfvsYL2oKX1xzzsyALwrYDsUVDO6Ti+PUf +NLGWxYbGkRWGia7lEP/PkRSWlIeDGPkHEHNXCtBpvPU0LJc8ooTDdTY53ObHSr1/ +p+sFZGQnCarrW19jbft2h29IyEVxqXC3mbzRlx2kw/qbVXqR1tjMkm+brBNYYkxz +E6wlTIJnvHM4l4XSCXXUonkiBUVqruQMjs/StrbSOATyubdDI385ZaSwNzjPoMGI +Gfp5i0EkXLIBe5sAoHqNwG0xAcCx0/weAp6UeNZFnq1wVA9K5K+M0wCtAD3YWTzN +mWTFyZ2WKugS3uDoazd9pUdZ6RLoWzEYVKJde4YcZSnwzGpvOeOwzo9lNVB0XtKN +ta3QJMWtXCbC1bTWzGnROGB88RmyrFVM46p6QwrsBA68kZwkxYmQaJy5LnZQpUIo +Wc01Cj3L +-----END CERTIFICATE----- diff --git a/cherrypy/test/server_wrong_host.cert b/cherrypy/test/server_wrong_host.cert new file mode 100644 index 00000000..f2a4b1a4 --- /dev/null +++ b/cherrypy/test/server_wrong_host.cert @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE9DCCAtwCAQIwDQYJKoZIhvcNAQEFBQAwOzELMAkGA1UEBhMCVVMxCzAJBgNV +BAgTAlhYMQwwCgYDVQQHEwNYWFgxETAPBgNVBAoTCENoZXJyeVB5MB4XDTEwMTAw +NDE5MDczN1oXDTIwMTAwMTE5MDczN1owRTELMAkGA1UEBhMCVVMxCzAJBgNVBAgT +AlhYMREwDwYDVQQKEwhDaGVycnlQeTEWMBQGA1UEAxQNbm90X2xvY2FsaG9zdDCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOSyjS2NyJeExSooBr0art0V +x71P0/cKzcg/0gpFBRT/5E6KYchaBci7tY4ZsOmEEH6+0PK6bx9sF0f5e8L5a9h8 +3zl2R0wPb1LGS3QKNO7YWET8yzgADSo1Q5p+v3c8n3hsZqZsWJRIqEbWLmA6qVKA +DOVe7Oge2qFuJZVotq60Z/IesgBpUa76AFHP4DroDhcwlXq0Ojhb2OeGA1BJCvx6 +oaQ+9IK2IfQ3TSojWx2gwQ5UNVhDjfzeLU2NShb2HB6Ou1OXZMcHjUJlAWUGJKdn +wDuhQBTn0GoV47zu0KaCGFNNF4xetWASjaZU0IVh+8fWQCM3UnnYbtnusmai3P+c +IMPZQ6WHjar0xitvFBfvSY76GKCN0+f/wg8a0I02ZYYluzRdYJ2qWkEf7sAfQKBY +nqxBiHRF+aG/B79FfIT4QoiVMqUI9Lb4RrgsAJLX+o7ZbFjTBqw+CKRNSxHD84Rm +w2QbO4tCfXIZl2pJT3nfnp06Rwqy84WOyJkc650Z33HiJEUAi900b1iTviXkNTWo +YsO5vku44nut3C8IizIBanKjLu7NGLe6q5wOEYN/QdignQzMNaQJKFOBzDmOcf/C +77LpM98vSCRzeYvhmz0AzfkXQAOJxUzQPWrvo/2CWRE8OtT1Jm8fGHS35zl8q/vy +S/60PlSABxzLq/nTuqqfAgMBAAEwDQYJKoZIhvcNAQEFBQADggIBABhFU1U6n/KY +5wNTobrZvsHq6C23wXaVXDU3IjHdH0Dhrjbw/9tlalW5MZtus3v4/SdM5Xv1Br+w +y31x0uKoGeSUOGRg6Aa5dzb8DlMTW5Vo9CKjJ4EL5c+1fhwb25aUAFuFut92SpXm +juvu1zSN1+M8nvSGtHp5qxcnJRgqLmyDrXTYqC4Hik8gl0U04BQf3KvmD9fXRq0O +q0rawmLdLDpbcVatR6yJzwXBtm60mIavr1P6HAMtCceeCn5+9xid71BCC/45IOtX +d+QBHOAYsmD9yDf/zEpaaRK6E5S/CmAKI8bw3Xy7B47A0BorVk9iTXGTrjNmOrkn +OCGOtDJh+sOOOc7O/Zm/7YKJmGy+0k6Ws7XCb41ikz7J4ydk3A4VzypHL2zGed/N +WP4y74zftG/dvHFpRYIbUhCH1I/SbORYqQMSsoMgPMtJc22pl8rCrQ6WTaQSI7gT +2noblDfAehYQwtZF2I7cndsWWJBmkVfbuUWWuYiMZyTIZPwbkDFDePCU7v7bIKPQ +pHNRez+4rEINE89ciCV2msANGrZ9KEivu0z7eduNr/O/yUONeMIMQu1Omk9SgTaw +9mBedXmylRhmmJBk7UpeSTgy4dD1fTbp7LFVbIKJ0kIitWCKK+lokQXqOfGQKyRZ +Bxqyh6SNyo9S4lxwvlYkWHjVpw9LF8TI +-----END CERTIFICATE----- diff --git a/cherrypy/test/static/has space.html b/cherrypy/test/static/has space.html new file mode 100644 index 00000000..b9f5f097 --- /dev/null +++ b/cherrypy/test/static/has space.html @@ -0,0 +1 @@ +Hello, world
diff --git a/cherrypy/test/test.conf b/cherrypy/test/test.conf new file mode 100644 index 00000000..44660d09 --- /dev/null +++ b/cherrypy/test/test.conf @@ -0,0 +1,11 @@ +[global] +server.socket_host: '127.0.0.1' +server.socket_port: 54583 +checker.on: False +log.screen: False +log.error_file: r'/home/cstpierre/devel/cherrypy/cherrypy/test/test.error.log' +log.access_file: r'/home/cstpierre/devel/cherrypy/cherrypy/test/test.access.log' + +unsubsig: True +test_case_name: "test_signal_handler_unsubscribe" + diff --git a/cherrypy/test/test_ssl.py b/cherrypy/test/test_ssl.py new file mode 100644 index 00000000..8a15c1ea --- /dev/null +++ b/cherrypy/test/test_ssl.py @@ -0,0 +1,207 @@ +import socket +import urllib2 +import itertools +from unittest import TestCase +from os.path import abspath, dirname, join + +import cherrypy +from cherrypy.wsgiserver import SSLAdapter +from cherrypy.test.https_verifier import VerifiedHTTPSHandler + + +class HostnameTests(TestCase): + def assert_matches(self, addr, common_name): + self.assertTrue(SSLAdapter._matches(addr, common_name), + "%s doesn't match %s" % (addr, common_name)) + + def assert_not_matches(self, addr, common_name): + self.assertFalse(SSLAdapter._matches(addr, common_name), + "%s matches %s" % (addr, common_name)) + + def test_local_valid(self): + matcher = SSLAdapter.address_matches + self.assertTrue(matcher(("localhost",8080), "localhost")) + self.assertTrue(matcher(("127.0.0.1",8080), "localhost")) + self.assertTrue(matcher(("localhost",8080), "127.0.0.1")) + self.assertTrue(matcher(("127.0.0.1",8080), "127.0.0.1")) + self.assertTrue(matcher(("localhost",8080), "*.localhost")) + self.assertTrue(matcher(("127.0.0.1",8080), "*.localhost")) + + def test_local_invalid(self): + matcher = SSLAdapter.address_matches + self.assertFalse(matcher(("localhost",8080), "1.2.3.4")) + self.assertFalse(matcher(("localhost",8080), "example.com")) + self.assertFalse(matcher(("localhost",8080), "*.example.com")) + + def test_wild_matches(self): + self.assertTrue(SSLAdapter._matches("localhost", "*.localhost")) + self.assertTrue(SSLAdapter._matches("sub.localhost", "*.localhost")) + self.assertTrue(SSLAdapter._matches("a.b.localhost", "*.localhost")) + self.assertTrue(SSLAdapter._matches("example.com", "*.example.com")) + self.assertTrue(SSLAdapter._matches("sub.example.com", "*.example.com")) + self.assertTrue(SSLAdapter._matches("a.b.example.com", "*.example.com")) + + def test_wild_nonmatches(self): + self.assertFalse(SSLAdapter._matches("localhost", "localhost.*")) + self.assertFalse(SSLAdapter._matches("a.b.localhost", "a.*.localhost")) + self.assertFalse(SSLAdapter._matches("not_localhost", "*.localhost")) + self.assertFalse(SSLAdapter._matches("not_localhost", "*localhost")) + self.assertFalse(SSLAdapter._matches("example.com", "example.com.*")) + self.assertFalse(SSLAdapter._matches("example.com", "example.com*")) + self.assertFalse(SSLAdapter._matches("example.com", "example.*")) + self.assertFalse(SSLAdapter._matches("a.b.example.com", "a.*.example.com")) + self.assertFalse(SSLAdapter._matches("not_example.com", "*.example.com")) + self.assertFalse(SSLAdapter._matches("not_example.com", "*example.com")) + + +THIS_DIR = abspath(dirname(__file__)) + +CA_CERT = join(THIS_DIR, "ca.cert") + +SERVER_KEY = join(THIS_DIR, "server.key") +SERVER_CERT = join(THIS_DIR, "server.cert") +SERVER_WRONG_CA = join(THIS_DIR, "server_wrong_ca.cert") +SERVER_WRONG_HOST = join(THIS_DIR, "server_wrong_host.cert") + +CLIENT_KEY = join(THIS_DIR, "client.key") +CLIENT_CERT = join(THIS_DIR, "client.cert") +CLIENT_IP_CERT = join(THIS_DIR, "client_ip.cert") +CLIENT_WILD_CERT = join(THIS_DIR, "client_wildcard.cert") +CLIENT_WRONG_CA = join(THIS_DIR, "client_wrong_ca.cert") +CLIENT_WRONG_HOST = join(THIS_DIR, "client_wrong_host.cert") + +class Root: + @cherrypy.expose + def index(self): + return "ok" + +class HTTPSTests(object): + regular_fail = False + checked_fail = False + server_host = "localhost" + client_host = "localhost" + server_ca = CA_CERT + server_cert = SERVER_CERT + client_cert = CLIENT_CERT + server_check = "ignore" # ignore, optional, required + server_ssl = "builtin" # builtin, pyopenssl + server_check_host = True + + def setUp(self): + socket.setdefaulttimeout(1) + + cherrypy.config.update({ + "checker.on": False, + "log.screen": False, + "engine.autoreload_on": False, + + "server.socket_host": self.server_host, + "server.socket_port": 8080, + + "server.ssl_private_key": SERVER_KEY, + "server.ssl_certificate": self.server_cert, + "server.ssl_client_CA": self.server_ca, + "server.ssl_client_check": self.server_check, + "server.ssl_module": self.server_ssl, + "server.ssl_client_check_host": self.server_check_host, + }) + cherrypy.tree.mount(Root()) + cherrypy.engine.start() + cherrypy.engine.wait(cherrypy.engine.states.STARTED) + + self.opener = urllib2.build_opener(VerifiedHTTPSHandler( + ca_certs = CA_CERT, + key_file = CLIENT_KEY, + cert_file = self.client_cert + )) + + self.url = "https://" + self.client_host + ":8080/" + + def tearDown(self): + cherrypy.engine.exit() + cherrypy.engine.wait(cherrypy.engine.states.EXITING) + cherrypy.server.httpserver = None # force the ssl adaptor to reload + + def test_checked(self): + if self.checked_fail: + self.assertRaises(Exception, self.opener.open, self.url) + else: + self.assertEqual("ok", self.opener.open(self.url).read()) + + def test_regular(self): + if self.regular_fail: + self.assertRaises(urllib2.URLError, urllib2.urlopen, self.url) + else: + self.assertEqual("ok", urllib2.urlopen(self.url).read()) + + +SSL_MODULES = ["builtin", "pyopenssl"] +BAD_SERVER_CERTS = [SERVER_WRONG_CA, SERVER_WRONG_HOST] +BAD_CLIENT_CERTS = [CLIENT_WRONG_CA, CLIENT_WRONG_HOST] +GOOD_CLIENT_CERTS = [CLIENT_CERT, CLIENT_IP_CERT, CLIENT_WILD_CERT] +SERVER_HOSTS = ["localhost", "127.0.0.1", "0.0.0.0"] +if socket.getaddrinfo("localhost", 8080)[0][0] == socket.AF_INET6: + CLIENT_HOSTS = ["localhost"] +else: + CLIENT_HOSTS = ["localhost", "127.0.0.1"] + +TESTS = [ + {"regular_fail": False, + "checked_fail": False, + "settings": {"server_check": ["ignore","optional"], + "client_cert": GOOD_CLIENT_CERTS}}, + {"regular_fail": False, + "checked_fail": False, + "settings": {"server_ca": [None], + "server_check": ["ignore","optional","required"], + "client_cert": GOOD_CLIENT_CERTS + BAD_CLIENT_CERTS}}, + {"regular_fail": False, + "checked_fail": False, + "settings": {"server_check": ["ignore"], + "client_cert": GOOD_CLIENT_CERTS + BAD_CLIENT_CERTS}}, + {"regular_fail": True, + "checked_fail": False, + "settings": {"server_check": ["required"], + "client_cert": GOOD_CLIENT_CERTS}}, + {"regular_fail": True, + "checked_fail": True, + "settings": {"server_check": ["required"], + "client_cert": BAD_CLIENT_CERTS}}, + {"regular_fail": False, + "checked_fail": True, + "settings": {"server_check": ["optional"], + "client_cert": BAD_CLIENT_CERTS}}, + {"regular_fail": False, + "checked_fail": True, + "settings": {"server_check": ["ignore","optional"], + "server_cert": BAD_SERVER_CERTS}}, + {"regular_fail": True, + "checked_fail": True, + "settings": {"server_check": ["required"], + "server_cert": BAD_SERVER_CERTS}}, + {"regular_fail": True, + "checked_fail": False, + "settings": {"server_check": ["required"], + "client_cert": [CLIENT_WRONG_HOST], + "server_check_host": [False]}}, + {"regular_fail": False, + "checked_fail": False, + "settings": {"server_check": ["optional"], + "client_cert": [CLIENT_WRONG_HOST], + "server_check_host": [False]}}, +] +for ssl_mod in SSL_MODULES: + for client_host,server_host in itertools.product(CLIENT_HOSTS,SERVER_HOSTS): + for tests in TESTS: + combos = [] + for attr,vals in tests["settings"].items(): + combos.append([(attr,val) for val in vals]) + + for settings in itertools.product(*combos): + attrs = dict(settings, server_ssl = ssl_mod, + server_host = server_host, + client_host = client_host, + regular_fail = tests["regular_fail"], + checked_fail = tests["checked_fail"]) + name = "SSLClientCertTest_" + str(attrs) + globals()[name] = type(name, (HTTPSTests, TestCase), attrs) diff --git a/cherrypy/wsgiserver/ssl_builtin.py b/cherrypy/wsgiserver/ssl_builtin.py index 7148dfda..12fddc80 100644 --- a/cherrypy/wsgiserver/ssl_builtin.py +++ b/cherrypy/wsgiserver/ssl_builtin.py @@ -5,6 +5,7 @@ The ssl module must be importable for SSL functionality. To use this module, set ``CherryPyWSGIServer.ssl_adapter`` to an instance of ``BuiltinSSLAdapter``. """ +import socket, errno try: import ssl @@ -21,7 +22,42 @@ except ImportError: import sys -from cherrypy import wsgiserver +from cherrypy import wsgiserver, config + + +def decode_cert(prefix, cert): + if not cert: + return None + + key_map = {'countryName': 'C', + 'stateOrProvinceName': 'ST', + 'localityName': 'L', + 'organizationName': 'O', + 'organizationalUnitName': 'OU', + 'commonName': 'CN', + # Don't know by what key names python's ssl + # implementation uses for these fields. + #'???': 'T', + #'???': 'I', + #'???': 'G', + #'???': 'S', + #'???': 'D', + #'???': 'UID', + 'emailAddress': 'Email', + } + + DN_string = ["subject="] + cert_dict = {} + + for rdn in cert: + for key, item in rdn: + if key in key_map: + cert_dict["%s_%s" % (prefix, key_map[key])] = item + DN_string.append("%s=%s" % (key_map[key], item)) + + cert_dict[prefix] = "/".join(DN_string) + + return cert_dict class BuiltinSSLAdapter(wsgiserver.SSLAdapter): @@ -33,12 +69,26 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter): private_key = None """The filename of the server's private key file.""" - def __init__(self, certificate, private_key, certificate_chain=None): + def __init__(self, certificate, private_key, certificate_chain=None, + client_CA=None): if ssl is None: raise ImportError("You must install the ssl module to use HTTPS.") self.certificate = certificate self.private_key = private_key self.certificate_chain = certificate_chain + self.client_CA = client_CA or config.get("server.ssl_client_CA") + + self.check_host = config.get("server.ssl_client_check_host", False) + check = config.get("server.ssl_client_check", "ignore") + if check == "ignore": + self.check = ssl.CERT_NONE + elif check == "optional": + self.check = ssl.CERT_OPTIONAL + elif check == "required": + self.check = ssl.CERT_REQUIRED + else: + raise ValueError("server.ssl_client_check must be one of 'ignore'," + "'optional','required'") def bind(self, sock): """Wrap and return the given socket.""" @@ -47,9 +97,20 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter): def wrap(self, sock): """Wrap and return the given socket, plus WSGI environ entries.""" try: - s = ssl.wrap_socket(sock, do_handshake_on_connect=True, - server_side=True, certfile=self.certificate, - keyfile=self.private_key, ssl_version=ssl.PROTOCOL_SSLv23) + if self.client_CA: + s = ssl.wrap_socket(sock, do_handshake_on_connect=True, + server_side=True, + certfile=self.certificate, + keyfile=self.private_key, + ssl_version=ssl.PROTOCOL_SSLv23, + ca_certs=self.client_CA, + cert_reqs=self.check) + else: + s = ssl.wrap_socket(sock, do_handshake_on_connect=True, + server_side=True, + certfile=self.certificate, + keyfile=self.private_key, + ssl_version=ssl.PROTOCOL_SSLv23) except ssl.SSLError: e = sys.exc_info()[1] if e.errno == ssl.SSL_ERROR_EOF: @@ -80,6 +141,48 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter): ## SSL_VERSION_INTERFACE string The mod_ssl program version ## SSL_VERSION_LIBRARY string The OpenSSL program version } + + client_cert = sock.getpeercert() + if self.client_CA and (client_cert or self.check == ssl.CERT_REQUIRED): + if self.check == ssl.CERT_REQUIRED and not client_cert: + sock.close() + raise ssl.SSLError(ssl.SSL_ERROR_SSL, "certificate required but not found") + + client_cert_subject = decode_cert( "SSL_CLIENT_S_DN", client_cert["subject"] ) + if self.check_host: + try: + assert self.address_matches(sock.getpeername(), + client_cert_subject["SSL_CLIENT_S_DN_CN"]) + except: + sock.close() + raise ssl.SSLError(ssl.SSL_ERROR_SSL, "certificate commonName doesn't match client socket address") + + # Update for client environment variables + ssl_environ.update( { + #"SSL_CLIENT_M_VERSION": "", + #"SSL_CLIENT_M_SERIAL": "", + #"SSL_CLIENT_V_START": "", + "SSL_CLIENT_V_END": client_cert["notAfter"], + #"SSL_CLIENT_A_SIG": "", + #"SSL_CLIENT_A_KEY": "", + #"SSL_CLIENT_CERT": "", + #"SSL_CLIENT_CERT_CHAIN": "", + #"SSL_CLIENT_VERIFY": "", + } ) + + ssl_environ.update( client_cert_subject ) + + # Update for server environment variables + ssl_environ.update( { + #"SSL_SERVER_M_VERSION": "", + #"SSL_SERVER_M_SERIAL": "", + #"SSL_SERVER_V_START": "", + #"SSL_SERVER_V_END": "", + #"SSL_SERVER_A_SIG": "", + #"SSL_SERVER_A_KEY": "", + #"SSL_SERVER_CERT": "", + } ) + return ssl_environ if sys.version_info >= (3, 0): diff --git a/cherrypy/wsgiserver/ssl_pyopenssl.py b/cherrypy/wsgiserver/ssl_pyopenssl.py index 42745fbc..9b664f26 100644 --- a/cherrypy/wsgiserver/ssl_pyopenssl.py +++ b/cherrypy/wsgiserver/ssl_pyopenssl.py @@ -34,7 +34,7 @@ import socket import threading import time -from cherrypy import wsgiserver +from cherrypy import wsgiserver, config try: from OpenSSL import SSL @@ -168,7 +168,8 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter): This is needed for cheaper "chained root" SSL certificates, and should be left as None if not required.""" - def __init__(self, certificate, private_key, certificate_chain=None): + def __init__(self, certificate, private_key, certificate_chain=None, + client_CA=None): if SSL is None: raise ImportError("You must install pyOpenSSL to use HTTPS.") @@ -176,8 +177,21 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter): self.certificate = certificate self.private_key = private_key self.certificate_chain = certificate_chain + self.client_CA = client_CA or config.get("server.ssl_client_CA") self._environ = None + self.check_host = config.get("server.ssl_client_check_host", False) + check = config.get("server.ssl_client_check", "ignore") + if check == "ignore": + self.check = SSL.VERIFY_NONE + elif check == "optional": + self.check = SSL.VERIFY_PEER + elif check == "required": + self.check = SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT + else: + raise ValueError("server.ssl_client_check must be one of 'ignore'," + "'optional','required'") + def bind(self, sock): """Wrap and return the given socket.""" if self.context is None: @@ -198,6 +212,23 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter): if self.certificate_chain: c.load_verify_locations(self.certificate_chain) c.use_certificate_file(self.certificate) + + if self.client_CA: + c.load_client_ca(self.client_CA) + + c.set_verify_depth(2) + c.load_verify_locations(self.client_CA) + + def callback(conn, cert, errno, depth, retcode): + if retcode and depth < 1 and self.check_host: + try: + assert self.address_matches(conn.getpeername(), + cert.get_subject().commonName) + except: + return False + return retcode + + c.set_verify(self.check, callback) return c def get_environ(self): diff --git a/cherrypy/wsgiserver/wsgiserver2.py b/cherrypy/wsgiserver/wsgiserver2.py index bfb8f699..2f3c5ab6 100644 --- a/cherrypy/wsgiserver/wsgiserver2.py +++ b/cherrypy/wsgiserver/wsgiserver2.py @@ -161,6 +161,7 @@ def plat_specific_errors(*errnames): socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR") socket_errors_to_ignore = plat_specific_errors( + "EPERM", "EPIPE", "EBADF", "WSAEBADF", "ENOTSOCK", "WSAENOTSOCK", @@ -1049,7 +1050,13 @@ class CP_fileobject(socket._fileobject): buf.write(data) del data # explicit free break - assert n <= left, "recv(%d) returned %d bytes" % (left, n) + elif n > left: + # Could happen with SSL transport. Differ + # extra data read to the next call + buf.write(data[:left]) + self._rbuf.write(data[left:]) + del data + break buf.write(data) buf_len += n del data # explicit free @@ -1343,6 +1350,9 @@ class HTTPConnection(object): "The client sent a plain HTTP request, but " "this server only speaks HTTPS on this port.") self.linger = True + except TypeError: + # Python bug #9729: http://bugs.python.org/issue9729 + return except Exception: e = sys.exc_info()[1] self.server.error_log(repr(e), level=logging.ERROR, traceback=True) @@ -1602,10 +1612,12 @@ class SSLAdapter(object): * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) -> socket file object`` """ - def __init__(self, certificate, private_key, certificate_chain=None): + def __init__(self, certificate, private_key, certificate_chain=None, + client_CA=None): self.certificate = certificate self.private_key = private_key self.certificate_chain = certificate_chain + self.client_CA = client_CA def wrap(self, sock): raise NotImplemented @@ -1613,6 +1625,29 @@ class SSLAdapter(object): def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE): raise NotImplemented + @staticmethod + def possible_addresses(address): + name, port = address[:2] + canonical, alt_hosts, host_ips = socket.gethostbyaddr(name) + all_addrs = set([name] + [canonical] + alt_hosts + host_ips) + all_addrs.update(info[4][0] for addr in all_addrs.copy() + for info in socket.getaddrinfo(addr, port)) + return all_addrs + + @staticmethod + def _matches(addr, cname): + if cname.startswith("*."): + return addr == cname[2:] or addr.endswith(cname[1:]) + else: + return addr == cname + + @staticmethod + def address_matches(address, common_name): + for possible in SSLAdapter.possible_addresses(address): + if SSLAdapter._matches(possible, common_name): + return True + return False + class HTTPServer(object): """An HTTP server.""" diff --git a/cherrypy/wsgiserver/wsgiserver3.py b/cherrypy/wsgiserver/wsgiserver3.py index a7da188b..372a1c10 100644 --- a/cherrypy/wsgiserver/wsgiserver3.py +++ b/cherrypy/wsgiserver/wsgiserver3.py @@ -151,6 +151,7 @@ def plat_specific_errors(*errnames): socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR") socket_errors_to_ignore = plat_specific_errors( + "EPERM", "EPIPE", "EBADF", "WSAEBADF", "ENOTSOCK", "WSAENOTSOCK", @@ -1077,6 +1078,9 @@ class HTTPConnection(object): "The client sent a plain HTTP request, but " "this server only speaks HTTPS on this port.") self.linger = True + except TypeError: + # Python bug #9729: http://bugs.python.org/issue9729 + return except Exception: e = sys.exc_info()[1] self.server.error_log(repr(e), level=logging.ERROR, traceback=True) @@ -1336,10 +1340,12 @@ class SSLAdapter(object): * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) -> socket file object`` """ - def __init__(self, certificate, private_key, certificate_chain=None): + def __init__(self, certificate, private_key, certificate_chain=None, + client_CA=None): self.certificate = certificate self.private_key = private_key self.certificate_chain = certificate_chain + self.client_CA = client_CA def wrap(self, sock): raise NotImplemented @@ -1347,6 +1353,29 @@ class SSLAdapter(object): def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE): raise NotImplemented + @staticmethod + def possible_addresses(address): + name, port = address[:2] + canonical, alt_hosts, host_ips = socket.gethostbyaddr(name) + all_addrs = set([name] + [canonical] + alt_hosts + host_ips) + all_addrs.update(info[4][0] for addr in all_addrs.copy() + for info in socket.getaddrinfo(addr, port)) + return all_addrs + + @staticmethod + def _matches(addr, cname): + if cname.startswith("*."): + return addr == cname[2:] or addr.endswith(cname[1:]) + else: + return addr == cname + + @staticmethod + def address_matches(address, common_name): + for possible in SSLAdapter.possible_addresses(address): + if SSLAdapter._matches(possible, common_name): + return True + return False + class HTTPServer(object): """An HTTP server.""" |