Commits

Joseph Tate committed 35b7ceb Merge

Merged in stpierre/cherrypy/1001-client-cert-verify (pull request #15)

Added support for client certificate verification in SSLAdapter (issue #1001)

Comments (0)

Files changed (20)

cherrypy/test/ca.cert

+-----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-----

cherrypy/test/ca.key

+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----

cherrypy/test/client.cert

+-----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-----

cherrypy/test/client.key

+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----

cherrypy/test/client_ip.cert

+-----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-----

cherrypy/test/client_wildcard.cert

+-----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-----

cherrypy/test/client_wrong_ca.cert

+-----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-----

cherrypy/test/client_wrong_host.cert

+-----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-----

cherrypy/test/https_verifier.py

+# 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_

cherrypy/test/server.cert

+-----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-----

cherrypy/test/server.key

+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEA5LKNLY3Il4TFKigGvRqu3RXHvU/T9wrNyD/SCkUFFP/kToph
+yFoFyLu1jhmw6YQQfr7Q8rpvH2wXR/l7wvlr2HzfOXZHTA9vUsZLdAo07thYRPzL
+OAANKjVDmn6/dzyfeGxmpmxYlEioRtYuYDqpUoAM5V7s6B7aoW4llWi2rrRn8h6y
+AGlRrvoAUc/gOugOFzCVerQ6OFvY54YDUEkK/HqhpD70grYh9DdNKiNbHaDBDlQ1
+WEON/N4tTY1KFvYcHo67U5dkxweNQmUBZQYkp2fAO6FAFOfQahXjvO7QpoIYU00X
+jF61YBKNplTQhWH7x9ZAIzdSedhu2e6yZqLc/5wgw9lDpYeNqvTGK28UF+9JjvoY
+oI3T5//CDxrQjTZlhiW7NF1gnapaQR/uwB9AoFierEGIdEX5ob8Hv0V8hPhCiJUy
+pQj0tvhGuCwAktf6jtlsWNMGrD4IpE1LEcPzhGbDZBs7i0J9chmXaklPed+enTpH
+CrLzhY7ImRzrnRnfceIkRQCL3TRvWJO+JeQ1Nahiw7m+S7jie63cLwiLMgFqcqMu
+7s0Yt7qrnA4Rg39B2KCdDMw1pAkoU4HMOY5x/8Lvsukz3y9IJHN5i+GbPQDN+RdA
+A4nFTNA9au+j/YJZETw61PUmbx8YdLfnOXyr+/JL/rQ+VIAHHMur+dO6qp8CAwEA
+AQKCAgEAyBUykM0/1tgxC03Tf3St0f0xL/58SuFn4i972sJBzPqHyvMk0313HASl
+tbniXprNN6ZH9mSHvez6fVzXG2DOKqwtO/+wJupGEhwsfUxEvUYIC+tC/C6HVgsd
+pzgG2RHvzxK/yBB4etsKZlcSYdxQsT4YikA/cmE0FBHizdG8KiLp4hla0CNUdIqC
+5xDAc6j8UuuNi7nMSeyJWx2THpWZCAVeD+2ITCd+k0QivaALImO3I4sm1J7dxYK4
+DeZ0EJynQ1DKsTp9z+dafeESlEkInnGV7FWKU//wBjA6e9xQLa0aDR8gYA2oD3KL
+/R6tBFUSS+a1XFoVTUa+zOoZqNQKFev4bIRJAHqbPkcRF5z+KBmMwnPk8On44nsz
+iEfWH/unQGaimwSanhcr8L+4Fkg8ORWfvzMj3oSPek73oLUVbajR8CQzE8j6qGuT
+duwVkAhOzaq7DKRmAFnfxWi6XWkOxvfANARIStRHoAsd6uR/yPX0FWP8GGbAh3QT
+G/o5aM6a+vG08OPE/EMYN04pMN3aMEbDlxw42hl7JQ3nZj5Ojj8PWZt5gQyf0N01
+Kml9JAOVsdxtEUQhYcPYz9hkRcPP5VDMR81V9TZPIwIfnLLt9PYFyiSL76QRC7pP
+rIyz1wijD1ycsQ9yQRHgai0m5vh2q6uLrHYYd+syRudzWj3nxBECggEBAPkmwmsf
+imuh3n5CCAXQ2ohS67N0ysScyZtxU/dfr/fOzhq19KFXsmbkotaDJBVTI7wHSBfO
+w88RPi6GpXoGuKyoqNCsziwnE/pFjO7PVU71r6YDbpWBu4HyZO7qOdnWhEtfXp35
+aURJ5uKR/k46wJJZJEhZnB1N7FculBhJ+/tAIjdWPCeTabRm2EkDod88+d+p1Ta1
+K+abjEwjwMWhNI9JP7/CiMqXS7QhG8W80Nq4/b3o0ffK1Sy0OQmdowgAfQDecPGu
+IyUFnK12vwrUV4RZz2wV2hluIBgkKFYTbey7dZF0J2reR5FSTHPAqd2VFxGYpwsD
+E2X4kqNeXl1iCrcCggEBAOr73FwsGDMijk6BUovIApaakq6EWHMcqHmsz8A8KGAM
+EVzMXlp3bexa3GYJQz/tdD7KXfUW0p978px3QwCBlhmfoSJohfbIOl3lcKkFMuH6
+IAllo/9iQ12rKEUWBo+o0QS+jAMxZMVZP3POORooWWO3RBugdyqpMRg6roz6BP0n
+prp0XoGxS4axTkC77AgrsvWkxgaOa56NChp/+OZweQQlFIhjtkhLwcpWTMzUxzai
+UywKCCYJMIBi2kg6q0uRM5o6wpP65e6xRSg0BEtRyfOd4siQtG3S4fZ/UoUPeZD0
+Nl/8R5bwMMlXdwhAByJn+q/9/7NRXTR1/4+2QoNFl1kCggEAeCMhYigORcICl9zd
+I3jGty9MqfaqA04axJJLy59fKV2V8jlEoTu5MXYTst3/Wy0AsRzNvXUc487LrgGM
+7x8ok1CsPhrlO1MIhghdYZWl6/H55VIIU5z/vjQUSUO1631Nw05UQFZQVPZRl3SQ
+LEaWLrs/DIfxCAxX5t312RRrpYYSOZ6iWO7y7GMe7W4L8qnjTt8EiWGoroTYjKo8
+vM7zmhfW3m8+KHn1rgC89IcMKjtDVvxZqmMqkWjBjApv317k57NawJ6YIbToDJiS
+m/Ux0gbARrzuso5weVOOA5tQyPLHSQC6NQTDe6Y0aShAgDDws7Jo8Z1lEClhA9dw
+JxUsVwKCAQA+3zqbktS6wdEvohxy+AXxQ9RhS1nbGtzdCWbguXfYEwKGQMwyCmhn
+5/u2dV3/+cmzjzPKgtvB/kjwolxUA1gk7PgRG8RUPkiXnO8i2hg+LE1sFjzl9OWv
+Zz80FOuWfoXGbjFKHWon/3QhyRiwLgGU05EujyWzTWesYu8XG0JAdd5/Xul2a/iY
+RlJ4sY86tG6CmR6+3FZhKr18T6dKCOfoOv+eaod4GoT0XVjZKUlQDeiRDPIXMzg9
+8bUTxFbWtjgHWRd8vpHM5rNpSVmoyW5ud10ZatZL2DPLX0+1+Gj/ZsgxsG97LDLx
+fCyGEZOXGhkSA7TpxqhYhAXn7s0nEHSpAoIBAE6eAyEXMWuc7M9l5sBWRKR1tov4
+4IWXDohvYXch1qYcr3Ua7vKZMeeKealhV8wqAVfh1xHLNo/5HKnU4bNS5Z/ABN9a
+O1jkK/hU5IakqbgxFDqkiFx7WOwEu23f7RwqyS4fjlRr55xsX26ZTZo0ybIS5NJG
+8oYBPlLISo7ztC23WaIcPCBAhiYSRheUrZCoF3aeaGrQ8EGAMNU36XQNk3pnoG1C
+aprV91cMitbD3g/W26Z7jCuEDszrWbAMG5MqHQJA8G9l0NL/30nQpFxN/KLET7UU
+hwxbbCpPBsv8vNaO06osUMNJh59uA7UBVkUjIMPGNTgYVugRjuHmYaCy/4A=
+-----END RSA PRIVATE KEY-----

cherrypy/test/server_wrong_ca.cert

+-----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-----

cherrypy/test/server_wrong_host.cert

+-----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-----

cherrypy/test/static/has space.html

+Hello, world

cherrypy/test/test.conf

+[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"
+

cherrypy/test/test_ssl.py

+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)

cherrypy/wsgiserver/ssl_builtin.py

 To use this module, set ``CherryPyWSGIServer.ssl_adapter`` to an instance of
 ``BuiltinSSLAdapter``.
 """
+import socket, errno
 
 try:
     import ssl
 
 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):
     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."""
     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:
 ##            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):

cherrypy/wsgiserver/ssl_pyopenssl.py

 import threading
 import time
 
-from cherrypy import wsgiserver
+from cherrypy import wsgiserver, config
 
 try:
     from OpenSSL import SSL
     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.")
 
         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:
         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):

cherrypy/wsgiserver/wsgiserver2.py

 socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR")
 
 socket_errors_to_ignore = plat_specific_errors(
+    "EPERM",
     "EPIPE",
     "EBADF", "WSAEBADF",
     "ENOTSOCK", "WSAENOTSOCK",
                         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
                     "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)
         * ``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
     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."""

cherrypy/wsgiserver/wsgiserver3.py

 socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR")
 
 socket_errors_to_ignore = plat_specific_errors(
+    "EPERM",
     "EPIPE",
     "EBADF", "WSAEBADF",
     "ENOTSOCK", "WSAENOTSOCK",
                     "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)
         * ``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
     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."""
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.