diff --git a/Cargo.toml b/Cargo.toml index 431053833..94a530eb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,5 @@ opt-level = 3 [patch.crates-io] # REDOX START, Uncommment when you want to compile/check with redoxer -# # following patches are just waiting on a new version to be released to crates.io -# socket2 = { git = "https://github.com/alexcrichton/socket2-rs", branch = "v0.3.x" } # REDOX END diff --git a/Lib/test/keycert.passwd.pem b/Lib/test/keycert.passwd.pem new file mode 100644 index 000000000..0ad696055 --- /dev/null +++ b/Lib/test/keycert.passwd.pem @@ -0,0 +1,50 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,E74528136B90D2DD + +WRHVD2PJXPqjFSHg92HURIsUzvsTE4a9oi0SC5yMBFKNWA5Z933gK3XTifp6jul5 +zpNYi8jBXZ2EqJJBxCuVcefmXSxL0q7CMej25TdIC4BVAFJVveeprHPUFkNB0IM1 +go5Lg4YofYqTCg3OE3k7WvfR3Zg1cRYxksDKO+WNZgWyKBex5X4vjOiyUqDl3GKt +kQXnkg1VgPV2Vrx93S9XNdELNRTguwf+XG0fkhtYhp/zCto8uKTgy5elK2P/ulGp +7fe6uj7h/uN9L7EOC6CjRkitywfeBUER739mOcGT4imSFJ9G27TCqPzj2ea3uuaf +/v1xhkQ4M6lNY/gcRfgVpCXhW43aAQV8XXQRMJTqLmz5Y5hYTKn+Ugq5vJ/ngyRM +lu1gUJnYYaemBTb4hbm6aBvnYK9mORa891Pmf+vxU9rYuQIdVAhvvXh4KBreSEBI +1AFy6dFKXl8ZKs6Wrq5wPefmFFkRmZ8OBiiq0fp2ApCRGZw6LsjCgwrRM38JiY7d +3OdsJpKvRYufgUyuuzUE0xA+E4yMvD48M9pPq2fC8O5giuGL1uEekQWXJuq+6ZRI +XYKIeSkuQALbX3RAzCPXTUEMtCYXKm/gxrrwJ+Bet4ob2amf3MX0uvWwOuAq++Fk +J0HFSBxrwvIWOhyQXOPuJdAN8PXA7dWOXfOgOMF0hQYqZCl3T4TiVZJbwVQtg1sN +dO7oAD5ZPHiKzvveZuB6k1FlBG8j0TyAC+44ChxkPDD3jF4dd6zGe62sDf85p4/d +W80gxJeD3xnDxG0ePPns+GuKUpUaWS7WvHtDpeFW1JEhvOqf8p1Li9a7RzWVo8ML +mGTdQgBIYIf6/fk69pFKl0nKtBU75KaunZz4nAmd9bNED4naDurMBg44u5TvODbJ +vgYIYXIYjNvONbskJatVrrTS8zch2NwVIjCi8L/hecwBXbIXzo1pECpc6BU7sQT8 ++i9sDKBeJcRipzfKZNHvnO19mUZaPCY8+a/f9c21DgKXz+bgLcJbohpSaeGM8Gfc +aZd3Vp9n3OJ3g2zQR1++HO9v1vR/wLELu6MeydkvMduHLmOPCn54gZ9z51ZNPAwa +qfFIsH+mLh9ks0H74ssF59uIlstkgB9zmZHv/Q0dK9ZfG/VEH6rSgdETWhZxhoMQ +Z92jXBEFT0zhI3rrIPNY+XS7eJCQIc1wc84Ea3cRk7SP+S1og3JtAxX56ykUwtkM +LQ/Dwwa6h1aqD0l2d5x1/BSdavtTuSegISRWQ4iOmSvEdlFP7H4g6RZk/okbLzMD +Evq5gNc7vlXhVawoQU8JCanJ5ZbbWnIRZfiXxBQS4lpYPKvJt4ML9z/x+82XxcXv +Z93N2Wep7wWW5OwS2LcQcOgZRDSIPompwo/0pMFGOS+5oort0ZDRHdmmGLjvBcCb +1KQmKQ4+8brI/3rjRzts6uDLjTGNxSCieNsnqhwHUv9Mg9WDSWupcGa+x27L89x3 +rObf6+3umcFLSjIzU8wuv1hx/e/y98Kv7BDBNYpAr6kVMrLnzYjAfJbBmqlxkzkQ +IgQzgrk2QZoTdgwR+S374NAMO0AE5IlO+/qa6qp2SORGTDX64I3UNw== +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDWTCCAkGgAwIBAgIJAPm6B21bar2bMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV +BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xODAx +MTkxOTA5MDZaFw0yODAxMTcxOTA5MDZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQH +DA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5k +YXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAKvvsX2gEti4shve3iYMc+jE4Se7WHs1Bol2f21H8qNboDOFdeb1 +RKHjmq3exHpajywOUEgne9nKHJY/3f2phR4Y5klqG6liLgiSpVyRlcBGbeT2qEAj +9oLiLFUXLGfGDds2mTwivQDLJBWi51j7ff5k2Pr58fN5ugYMn24T9FNyn0moT+qj +SFoBNm58l9jrdkJSlgWfqPlbiMa+mqDn/SFtrwLF2Trbfzu42Sd9UdIzMaSSrzbN +sGm53pNhCh8KndWUQ8GPP2IsLPoUU4qAtmZuTxCx2S1cXrN9EkmT69tlOH84YfSn +96Ih9bWRc7M5y5bfVdEVM+fKQl3hBRf05qMCAwEAAaMYMBYwFAYDVR0RBA0wC4IJ +bG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQAtQ8f37cCEk7/rAcbYR53ce3iK +Vpihb0U2ni1QjG9Tg9UIExkIGkwTiCm7kwQL+GEStBu9AG/QVrOjeTriRiddhWkk +ze8kRaI3AC/63t6Vh9Q1x6PESgeE4OtAO9JpJCf4GILglA789Y/b/GF8zJZQxR13 +qpB4ZwWw7gCBhdEW59u6CFeBmfDa58hM8lWvuVoRrTi7bjUeC6PAn5HVMzZSykhu +4HaUfBp6bKFjuym2+h/VvM1n8C3chjVSmutsLb6ELdD8IK0vPV/yf5+LN256zSsS +dyUZYd8XwQaioEMKdbhLvnehyzHiWfQIUR3BdhONxoIJhHv/EAo8eCkHHYIF +-----END CERTIFICATE----- diff --git a/Lib/test/keycert.pem b/Lib/test/keycert.pem new file mode 100644 index 000000000..9545dcf4b --- /dev/null +++ b/Lib/test/keycert.pem @@ -0,0 +1,48 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCr77F9oBLYuLIb +3t4mDHPoxOEnu1h7NQaJdn9tR/KjW6AzhXXm9USh45qt3sR6Wo8sDlBIJ3vZyhyW +P939qYUeGOZJahupYi4IkqVckZXARm3k9qhAI/aC4ixVFyxnxg3bNpk8Ir0AyyQV +oudY+33+ZNj6+fHzeboGDJ9uE/RTcp9JqE/qo0haATZufJfY63ZCUpYFn6j5W4jG +vpqg5/0hba8Cxdk62387uNknfVHSMzGkkq82zbBpud6TYQofCp3VlEPBjz9iLCz6 +FFOKgLZmbk8QsdktXF6zfRJJk+vbZTh/OGH0p/eiIfW1kXOzOcuW31XRFTPnykJd +4QUX9OajAgMBAAECggEAHppmXDbuw9Z0FVPg9KLIysioTtsgz6VLiZIm8juZK4x2 +glUh/D7xvWL2uDXrgN+3lh7iGUW13LkFx5SMncbbo9TIwI57Z/XKvcnkVwquve+L +RfLFVc1Q5lD9lROv2rS86KTaN4LzYz3FKXi6dvMkpPAsUtfEQhMLkmISypQQq/1z +EJaqo7r85OjN7e0wKazlKZpOzJEa5FQLMVRjTRFhLFNbHXX/tAet2jw+umATKbw8 +hYgiuZ44TwSEd9JeIV/oSYWfI/3HetuYW0ru3caiztRF2NySNu8lcsWgNC7fIku9 +mcHjtSNzs91QN1Qlu7GQvvhpt6OWDirNDCW+49WGaQKBgQDg9SDhfF0jRYslgYbH +cqO4ggaFdHjrAAYpwnAgvanhFZL/zEqm5G1E7l/e2fCkJ9VOSFO0A208chvwMcr+ +dCjHE2tVdE81aQ2v/Eo83VdS1RcOV4Y75yPH48rMhxPaHvxWD/FFDbf0/P2mtPB7 +SZ3kIeZMkE1wxdaO3AKUbQoozwKBgQDDqYgg7kVtygyICE1mB8Hwp6nUxFTczG7y +4XcsDqMIrKmw+PbQluvkoHoStxeVrsTloDhkTjIrpmYLyAiazg+PUJdkd6xrfLSj +VV6X93W0S/1egEb1F1CGFxtk8v/PWH4K76EPL2vxXdxjywz3GWlrL9yDYaB2szzS +DqgwVMqx7QKBgDCD7UF0Bsoyl13RX3XoPXLvZ+SkR+e2q52Z94C4JskKVBeiwX7Y +yNAS8M4pBoMArDoj0xmBm69rlKbqtjLGbnzwrTdSzDpim7cWnBQgUFLm7gAD1Elb +AhZ8BCK0Bw4FnLoa2hfga4oEfdfUMgEE0W5/+SEOBgWKRUmuHUhRc911AoGAY2EN +YmSDYSM5wDIvVb5k9B3EtevOiqNPSw/XnsoEZtiEC/44JnQxdltIBY93bDBrk5IQ +cmoBM4h91kgQjshQwOMXMhFSwvmBKmCm/hrTbvMVytTutXfVD3ZXFKwT4DW7N0TF +ElhsxBh/YzRz7mG62JVjtFt2zDN3ld2Z8YpvtXUCgYEA4EJ4ObS5YyvcXAKHJFo6 +Fxmavyrf8LSm3MFA65uSnFvWukMVqqRMReQc5jvpxHKCis+XvnHzyOfL0gW9ZTi7 +tWGGbBi0TRJCa8BkvgngUZxOxUlMfg/7cVxOIB0TPoUSgxFd/+qVz4GZMvr0dPu7 +eAF7J/8ECVvb0wSPTUI1N3c= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDWTCCAkGgAwIBAgIJAPm6B21bar2bMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV +BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xODAx +MTkxOTA5MDZaFw0yODAxMTcxOTA5MDZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQH +DA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5k +YXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAKvvsX2gEti4shve3iYMc+jE4Se7WHs1Bol2f21H8qNboDOFdeb1 +RKHjmq3exHpajywOUEgne9nKHJY/3f2phR4Y5klqG6liLgiSpVyRlcBGbeT2qEAj +9oLiLFUXLGfGDds2mTwivQDLJBWi51j7ff5k2Pr58fN5ugYMn24T9FNyn0moT+qj +SFoBNm58l9jrdkJSlgWfqPlbiMa+mqDn/SFtrwLF2Trbfzu42Sd9UdIzMaSSrzbN +sGm53pNhCh8KndWUQ8GPP2IsLPoUU4qAtmZuTxCx2S1cXrN9EkmT69tlOH84YfSn +96Ih9bWRc7M5y5bfVdEVM+fKQl3hBRf05qMCAwEAAaMYMBYwFAYDVR0RBA0wC4IJ +bG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQAtQ8f37cCEk7/rAcbYR53ce3iK +Vpihb0U2ni1QjG9Tg9UIExkIGkwTiCm7kwQL+GEStBu9AG/QVrOjeTriRiddhWkk +ze8kRaI3AC/63t6Vh9Q1x6PESgeE4OtAO9JpJCf4GILglA789Y/b/GF8zJZQxR13 +qpB4ZwWw7gCBhdEW59u6CFeBmfDa58hM8lWvuVoRrTi7bjUeC6PAn5HVMzZSykhu +4HaUfBp6bKFjuym2+h/VvM1n8C3chjVSmutsLb6ELdD8IK0vPV/yf5+LN256zSsS +dyUZYd8XwQaioEMKdbhLvnehyzHiWfQIUR3BdhONxoIJhHv/EAo8eCkHHYIF +-----END CERTIFICATE----- diff --git a/Lib/test/keycert2.pem b/Lib/test/keycert2.pem new file mode 100644 index 000000000..bb5fa65a8 --- /dev/null +++ b/Lib/test/keycert2.pem @@ -0,0 +1,49 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3ulRNfhbOAey/ +B+wIVYx+d5az7EV4riR6yi/qE6G+bxbTvay2pqySHtDweuaYSh2cVmcasBKKIFJm +rCD1zR8UmLb5i2XFIina1t3eePCuBZMrvZZwkzlQUSM1AZtjGOO/W0I3FwO6y645 +9xA5PduKI7SMYkH/VL3zE5W1JwMovv6bvNiT+GU5l6mB9ylCTgLpmUqoQhRqz/35 +zCzVyoh+ppDvVcpWYfvXywsXsgQwbAF0QJm8SSFi0TZm5ykv4WE16afQp08yuZS0 +3U4K3MJCa4rxO58edcxBopWYfQ29K3iINM8enRfr5q+u5mAAbALAEEvyFjgLWl/u +7arxn7bJAgMBAAECggEBAJfMt8KfHzBunrDnVrk8FayYGkfmOzAOkc1yKEx6k/TH +zFB+Mqlm5MaF95P5t3S0J+r36JBAUdEWC38RUNpF9BwMYYGlDxzlsTdCuGYL/q+J +o6NMLXQt7/jQUQqGnWAvPFzqhbcGqOo5R2ZVH25sEWv9PDuRI35XAepIkDTwWsfa +P6UcJJoP+4v9B++fb3sSL4zNwp1BqS4wxR8YTR0t1zQqOxJ5BGPw1J8aBMs1sq5t +qyosAQAT63kLrdqWotHaM26QxjqEQUMlh12XMWb5GdBXUxbvyGtEabsqskGa/f8B +RdHE437J8D8l+jxb2mZLzrlaH3dq2tbFGCe1rT8qLRECgYEA5CWIvoD/YnQydLGA +OlEhCSocqURuqcotg9Ev0nt/C60jkr/NHFLGppz9lhqjIDjixt3sIMGZMFzxRtwM +pSYal3XiR7rZuHau9iM35yDhpuytEiGbYy1ADakJRzY5jq/Qa8RfPP9Atua5xAeP +q6DiSnq9vhHv9G+O4MxzHBmrw9sCgYEAziiJWFthcwvuXn3Jv9xFYKEb/06puZAx +EgQCz/3rPzv5fmGD/sKVo1U/K4z/eA82DNeKG8QRTFJCxT8TCNRxOmGV7HdCYo/B +4BTNNvbKcdi3l0j75kKoADg+nt5CD5lz6gLG0GrUEnVO1y5HVfCTb3BEAfa36C85 +9i0sfQGiwysCgYEAuus9k8cgdct5oz3iLuVVSark/JGCkT2B+OOkaLChsDFUWeEm +7TOsaclpwldkmvvAYOplkZjMJ2GelE2pVo1XcAw3LkmaI5WpVyQXoxe/iQGT8qzy +IFlsh0Scw2lb0tmcyw6CcPk4TiHOxRrkzNrtS9QwLM+JZx0XVHptPPKTVc0CgYAu +j/VFYY5G/8Dc0qhIjyWUR48dQNUQtkJ/ASzpcT46z/7vznKTjbtiYpSb74KbyUO5 +7sygrM4DYOj3x+Eys1jHiNbly6HQxQtS4x/edCsRP5NntfI+9XsgYZOzKhvdjhki +F3J0DEzNxnUCIM+311hVaRPTJbgv1srOkTFlIoNydQKBgQC6/OHGaC/OewQqRlRK +Mg5KZm01/pk4iKrpA5nG7OTAeoa70NzXNtG8J3WnaJ4mWanNwNUOyRMAMrsUAy9q +EeGqHM5mMFpY4TeVuNLL21lu/x3KYw6mKL3Ctinn+JLAoYoqEy8deZnEA5/tjYlz +YhFBchnUicjoUN1chdpM6SpV2Q== +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDYjCCAkqgAwIBAgIJALJXRr8qF6oIMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNV +BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpb24xFTATBgNVBAMMDGZha2Vob3N0bmFtZTAeFw0x +ODAxMTkxOTA5MDZaFw0yODAxMTcxOTA5MDZaMGIxCzAJBgNVBAYTAlhZMRcwFQYD +VQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZv +dW5kYXRpb24xFTATBgNVBAMMDGZha2Vob3N0bmFtZTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBALe6VE1+Fs4B7L8H7AhVjH53lrPsRXiuJHrKL+oTob5v +FtO9rLamrJIe0PB65phKHZxWZxqwEoogUmasIPXNHxSYtvmLZcUiKdrW3d548K4F +kyu9lnCTOVBRIzUBm2MY479bQjcXA7rLrjn3EDk924ojtIxiQf9UvfMTlbUnAyi+ +/pu82JP4ZTmXqYH3KUJOAumZSqhCFGrP/fnMLNXKiH6mkO9VylZh+9fLCxeyBDBs +AXRAmbxJIWLRNmbnKS/hYTXpp9CnTzK5lLTdTgrcwkJrivE7nx51zEGilZh9Db0r +eIg0zx6dF+vmr67mYABsAsAQS/IWOAtaX+7tqvGftskCAwEAAaMbMBkwFwYDVR0R +BBAwDoIMZmFrZWhvc3RuYW1lMA0GCSqGSIb3DQEBCwUAA4IBAQCZhHhGItpkqhEq +ntMRd6Hv0GoOJixNvgeMwK4NJSRT/no3OirtUTzccn46h+SWibSa2eVssAV+pAVJ +HbzkN/DH27A1mMx1zJL1ekcOKA1AF6MXhUnrUGXMqW36YNtzHfXJLrwvpLJ13OQg +/Kxo4Nw68bGzM+PyRtKU/mpgYyfcvwR+ZSeIDh1fvUZK/IEVCf8ub42GPVs5wPfv +M+k5aHxWTxeif3K1byTRzxHupYNG2yWO4XEdnBGOuOwzzN4/iQyNcsuQKeuKHGrt +YvIlG/ri04CQ7xISZCj74yjTZ+/A2bXre2mQXAHqKPumHL7cl34+erzbUaxYxbTE +u5FcOmLQ +-----END CERTIFICATE----- diff --git a/Lib/test/keycert3.pem b/Lib/test/keycert3.pem new file mode 100644 index 000000000..621eb08bb --- /dev/null +++ b/Lib/test/keycert3.pem @@ -0,0 +1,132 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDgV4G+Zzf2DT5n +oAisIGFhn/bz7Vn5WiXUqbDsxROJOh/7BtOlduZka0pPhFylGbnxS8l1kEWHRI2T +6hOoWzumB6ItKiN+T5J30lAvSyo7iwdFoAQ/S5nPXQfhNARQe/NEOhRtpcuNdyx4 +BWdPdPuJQA1ASNJCLwcLOoRxaLbKLvb2V5T5FCAkeNPtRvPuT4gKQItMmiHfAhoV +C8MZWF/GC0RukHINys5MwqeFexam8CznmQPMYrLdhmKTj3DTivCPoh97EDIFGlgZ +SCaaYDVQA+aqlo/q2pi52PFwC1KzhNEA7EeOqSwC1NQjwjHuhcnf9WbxrgTq2zh3 +rv5YEW2ZAgMBAAECggEAPfSMtTumPcJskIumuXp7yk02EyliZrWZqwBuBwVqHsS5 +nkbFXnXWrLbgn9MrDsFrE5NdgKUmPnQVMVs8sIr5jyGejSCNCs4I4iRn1pfIgwcj +K/xEEALd6GGF0pDd/CgvB5GOoLVf4KKf2kmLvWrOKJpSzoUN5A8+v8AaYYOMr4sC +czbvfGomzEIewEG+Rw9zOVUDlmwyEKPQZ47E7PQ+EEA7oeFdR+1Zj6eT9ndegf8B +54frySYCLRUCk/sHCpWhaJBtBrcpht7Y8CfY7hiH/7x866fvuLnYPz4YALtUb0wN +7zUCNS9ol3n4LbjFFKfZtiRjKaCBRzMjK0rz6ydFcQKBgQDyLI3oGbnW73vqcDe/ +6eR0w++fiCAVhfMs3AO/gOaJi2la2JHlJ5u+cIHQIOFwEhn6Zq0AtdmnFx1TS5IQ +C0MdXI0XoQQw7rEF8EJcvfe85Z0QxENVhzydtdb8QpJfnQGfBfLyQlaaRYzRRHB6 +VdYUHF3EIPVIhbjbghal+Qep/QKBgQDtJlRPHkWwTMevu0J0fYbWN1ywtVTFUR// +k7VyORSf8yuuSnaQRop4cbcqONxmDKH6Or1fl3NYBsAxtXkkOK1E2OZNo2sfQdRa +wpA7o7mPHRhztQFpT5vflp+8P6+PEFat8D04eBOhNwrwwfhiPjD4gv5KvN4XutRW +VWv/2pnmzQKBgHPvHGg2mJ7quvm6ixXW1MWJX1eSBToIjCe3lBvDi5nhIaiZ8Q4w +7gA3QA3xD7tlDwauzLeAVxgEmsdbcCs6GQEfY3QiYy1Bt4FOSZa4YrcNfSmfq1Rw +j3Y4rRjKjeQz96i3YlzToT3tecJc7zPBj+DEy6au2H3Fdn+vQURneWHJAoGBANG7 +XES8mRVaUh/wlM1BVsaNH8SIGfiHzqzRjV7/bGYpQTBbWpAuUrhCmaMVtpXqBjav +TFwGLVRkZAWSYRjPpy2ERenT5SE3rv61o6mbGrifGsj6A82HQmtzYsGx8SmtYXtj +REF0sKebbmmOooUAS379GrguYJzL9o6D7YfRZNrhAoGAVfb/tiFU4S67DSpYpQey +ULhgfsFpDByICY6Potsg67gVFf9jIaB83NPTx3u/r6sHFgxFw7lQsuZcgSuWMu7t +glzOXVIP11Y5sl5CJ5OsfeK1/0umMZF5MWPyAQCx/qrPlZL86vXjt24Y/VaOxsAi +CZYdyJsjgOrJrWoMbo5ta54= +-----END PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 82:ed:bf:41:c8:80:91:9c + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server + Validity + Not Before: Jan 19 19:09:06 2018 GMT + Not After : Nov 28 19:09:06 2027 GMT + Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:e0:57:81:be:67:37:f6:0d:3e:67:a0:08:ac:20: + 61:61:9f:f6:f3:ed:59:f9:5a:25:d4:a9:b0:ec:c5: + 13:89:3a:1f:fb:06:d3:a5:76:e6:64:6b:4a:4f:84: + 5c:a5:19:b9:f1:4b:c9:75:90:45:87:44:8d:93:ea: + 13:a8:5b:3b:a6:07:a2:2d:2a:23:7e:4f:92:77:d2: + 50:2f:4b:2a:3b:8b:07:45:a0:04:3f:4b:99:cf:5d: + 07:e1:34:04:50:7b:f3:44:3a:14:6d:a5:cb:8d:77: + 2c:78:05:67:4f:74:fb:89:40:0d:40:48:d2:42:2f: + 07:0b:3a:84:71:68:b6:ca:2e:f6:f6:57:94:f9:14: + 20:24:78:d3:ed:46:f3:ee:4f:88:0a:40:8b:4c:9a: + 21:df:02:1a:15:0b:c3:19:58:5f:c6:0b:44:6e:90: + 72:0d:ca:ce:4c:c2:a7:85:7b:16:a6:f0:2c:e7:99: + 03:cc:62:b2:dd:86:62:93:8f:70:d3:8a:f0:8f:a2: + 1f:7b:10:32:05:1a:58:19:48:26:9a:60:35:50:03: + e6:aa:96:8f:ea:da:98:b9:d8:f1:70:0b:52:b3:84: + d1:00:ec:47:8e:a9:2c:02:d4:d4:23:c2:31:ee:85: + c9:df:f5:66:f1:ae:04:ea:db:38:77:ae:fe:58:11: + 6d:99 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:localhost + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 85:11:BE:16:47:04:D1:30:EE:86:8A:18:70:BE:A8:28:6F:82:3D:CE + X509v3 Authority Key Identifier: + keyid:9A:CF:CF:6E:EB:71:3D:DB:3C:F1:AE:88:6B:56:72:03:CB:08:A7:48 + DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server + serial:82:ED:BF:41:C8:80:91:9B + + Authority Information Access: + CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer + OCSP - URI:http://testca.pythontest.net/testca/ocsp/ + + X509v3 CRL Distribution Points: + + Full Name: + URI:http://testca.pythontest.net/testca/revocation.crl + + Signature Algorithm: sha1WithRSAEncryption + 7f:a1:7e:3e:68:01:b0:32:b8:57:b8:03:68:13:13:b3:e3:f4: + 70:2f:15:e5:0f:87:b9:fd:e0:12:e3:16:f2:91:53:c7:4e:25: + af:ca:cb:a7:d9:9d:57:4d:bf:a2:80:d4:78:aa:04:31:fd:6d: + cc:6d:82:43:e9:62:16:0d:0e:26:8b:e7:f1:3d:57:5c:68:02: + 9c:2b:b6:c9:fd:62:2f:10:85:88:cc:44:a5:e7:a2:3e:89:f2: + 1f:02:6a:3f:d0:3c:6c:24:2d:bc:51:62:7a:ec:25:c5:86:87: + 77:35:8f:f9:7e:d0:17:3d:77:56:bf:1a:0c:be:09:78:ee:ea: + 73:97:65:60:94:91:35:b3:5c:46:8a:5e:6d:94:52:de:48:b7: + 1f:6c:28:79:7f:ff:08:8d:e4:7d:d0:b9:0b:7c:ae:c4:1d:2a: + a1:b3:50:11:82:03:5e:6c:e7:26:fa:05:32:39:07:83:49:b9: + a2:fa:04:da:0d:e5:ff:4c:db:97:d0:c3:a7:43:37:4c:16:de: + 3c:b5:e9:7e:82:d4:b3:10:df:d1:c1:66:72:9c:15:67:19:3b: + 7b:91:0a:82:07:67:c5:06:03:5f:80:54:08:81:8a:b1:5c:7c: + 4c:d2:07:38:92:eb:12:f5:71:ae:de:05:15:c8:e1:33:f0:e4: + 96:0f:0f:1e +-----BEGIN CERTIFICATE----- +MIIE8TCCA9mgAwIBAgIJAILtv0HIgJGcMA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV +BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW +MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODAxMTkxOTA5MDZaFw0yNzExMjgx +OTA5MDZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj +MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxv +Y2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOBXgb5nN/YN +PmegCKwgYWGf9vPtWflaJdSpsOzFE4k6H/sG06V25mRrSk+EXKUZufFLyXWQRYdE +jZPqE6hbO6YHoi0qI35PknfSUC9LKjuLB0WgBD9Lmc9dB+E0BFB780Q6FG2ly413 +LHgFZ090+4lADUBI0kIvBws6hHFotsou9vZXlPkUICR40+1G8+5PiApAi0yaId8C +GhULwxlYX8YLRG6Qcg3KzkzCp4V7FqbwLOeZA8xist2GYpOPcNOK8I+iH3sQMgUa +WBlIJppgNVAD5qqWj+ramLnY8XALUrOE0QDsR46pLALU1CPCMe6Fyd/1ZvGuBOrb +OHeu/lgRbZkCAwEAAaOCAcAwggG8MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDAOBgNV +HQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud +EwEB/wQCMAAwHQYDVR0OBBYEFIURvhZHBNEw7oaKGHC+qChvgj3OMH0GA1UdIwR2 +MHSAFJrPz27rcT3bPPGuiGtWcgPLCKdIoVGkTzBNMQswCQYDVQQGEwJYWTEmMCQG +A1UECgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNVBAMMDW91 +ci1jYS1zZXJ2ZXKCCQCC7b9ByICRmzCBgwYIKwYBBQUHAQEEdzB1MDwGCCsGAQUF +BzAChjBodHRwOi8vdGVzdGNhLnB5dGhvbnRlc3QubmV0L3Rlc3RjYS9weWNhY2Vy +dC5jZXIwNQYIKwYBBQUHMAGGKWh0dHA6Ly90ZXN0Y2EucHl0aG9udGVzdC5uZXQv +dGVzdGNhL29jc3AvMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly90ZXN0Y2EucHl0 +aG9udGVzdC5uZXQvdGVzdGNhL3Jldm9jYXRpb24uY3JsMA0GCSqGSIb3DQEBBQUA +A4IBAQB/oX4+aAGwMrhXuANoExOz4/RwLxXlD4e5/eAS4xbykVPHTiWvysun2Z1X +Tb+igNR4qgQx/W3MbYJD6WIWDQ4mi+fxPVdcaAKcK7bJ/WIvEIWIzESl56I+ifIf +Amo/0DxsJC28UWJ67CXFhod3NY/5ftAXPXdWvxoMvgl47upzl2VglJE1s1xGil5t +lFLeSLcfbCh5f/8IjeR90LkLfK7EHSqhs1ARggNebOcm+gUyOQeDSbmi+gTaDeX/ +TNuX0MOnQzdMFt48tel+gtSzEN/RwWZynBVnGTt7kQqCB2fFBgNfgFQIgYqxXHxM +0gc4kusS9XGu3gUVyOEz8OSWDw8e +-----END CERTIFICATE----- diff --git a/Lib/test/keycert4.pem b/Lib/test/keycert4.pem new file mode 100644 index 000000000..b7df7f3f2 --- /dev/null +++ b/Lib/test/keycert4.pem @@ -0,0 +1,132 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDH/76hZAZH4cSV +CmVZa5HEqKCjCKrcPwBECs9BS+3ibwN4x9NnFNP+tCeFGgJXl7WGFoeXgg3oK+1p +FsOWpsRHuF3BdqkCnShSydmT8bLaGHwKeL0cPxJP5T/uW7ezPKW2VWXGMwmwRaRJ +9dj2VCUu20vDZWSGFr9zjnjoJczBtH3RsVUgpK7euEHuQ5pIM9QSOaCo+5FPR7s7 +1nU7YqbFWtd+NhC8Og1G497B31DQlHciF6BRm6/cNGAmHaAErKUGBFdkGtFPHBn4 +vktoEg9fwxJAZLvGpoTZWrB4HRsRwVTmFdGvK+JXK225xF23AXRXp/snhSuSFeLj +E5cpyJJ7AgMBAAECggEAQOv527X2e/sDr0XSpHZQuT/r9UBpBlnFIlFH+fBF5k0X +GWv0ae/O6U1dzs0kmX57xG0n0ry6+vTXeleTYiH8cTOd66EzN9AAOO+hG29IGZf9 +HAEZkkO/FARc/mjzdtFnEYsjIHWM3ZWdwQx3Q28JKu6w51rQiN51g3NqOCGdF/uF +rE5XPKsKndn+nLHvsNuApFgUYZEwdrozgUueEgRaPTUCNhzotcA9eWoBdA24XNhk +x8Cm/bZWabXm7gBO75zl3Cu2F21ay+EuwyOZTsx6lZi6YX9/zo1mkO81Zi3tQk50 +NMEI0feLNwsdxTbmOcVJadjOgd+QVghlFyr5HGBWMQKBgQD3AH3rhnAo6tOyNkGN ++IzIU1MhUS452O7IavykUYO9sM24BVChpRtlI9Dpev4yE/q3BAO3+oWT3cJrN7/3 +iyo1dzAkpGvI65XWfElXFM4nLjEiZzx4W9fiPN91Oucpr0ED6+BZXTtz4gVm0TP/ +TUc2xvTB6EKvIyWmKOYEi0snxQKBgQDPSOjbz9jWOrC9XY7PmtLB6QJDDz7XSGVK +wzD+gDAPpAwhk58BEokdOhBx2Lwl8zMJi0CRHgH2vNvkRyhvUQ4UFzisrqann/Tw +klp5sw3iWC6ERC8z9zL7GfHs7sK3mOVeAdK6ffowPM3JrZ2vPusVBdr0MN3oZwki +CtNXqbY1PwKBgGheQNbAW6wubX0kB9chavtKmhm937Z5v4vYCSC1gOEqUAKt3EAx +L74wwBmn6rjmUE382EVpCgBM99WuHONQXmlxD1qsTw763LlgkuzE0cckcYaD8L06 +saHa7uDuHrcyYlpx1L5t8q0ol/e19i6uTKUMtGcq6OJwC3yGU4sgAIWxAoGBAMVq +qiQXm2vFL+jafxYoXUvDMJ1PmskMsTP4HOR2j8+FrOwZnVk3HxGP6HOVOPRn4JbZ +YiAT1Uj6a+7I+rCyINdvmlGUcTK6fFzW9oZryvBkjcD483/pkktmVWwTpa2YV/Ml +h16IdsyUTGYlDUYHhXtbPUJOfDpIT4F1j/0wrFGfAoGAO82BcUsehEUQE0xvQLIn +7QaFtUI5z19WW730jVuEobiYlh9Ka4DPbKMvka8MwyOxEwhk39gZQavmfG6+wZm+ +kjERU23LhHziJGWS2Um4yIhC7myKbWaLzjHEq72dszLpQku4BzE5fT60fxI7cURD +WGm/Z3Q2weS3ZGIoMj1RNPI= +-----END PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 82:ed:bf:41:c8:80:91:9d + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server + Validity + Not Before: Jan 19 19:09:06 2018 GMT + Not After : Nov 28 19:09:06 2027 GMT + Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=fakehostname + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:c7:ff:be:a1:64:06:47:e1:c4:95:0a:65:59:6b: + 91:c4:a8:a0:a3:08:aa:dc:3f:00:44:0a:cf:41:4b: + ed:e2:6f:03:78:c7:d3:67:14:d3:fe:b4:27:85:1a: + 02:57:97:b5:86:16:87:97:82:0d:e8:2b:ed:69:16: + c3:96:a6:c4:47:b8:5d:c1:76:a9:02:9d:28:52:c9: + d9:93:f1:b2:da:18:7c:0a:78:bd:1c:3f:12:4f:e5: + 3f:ee:5b:b7:b3:3c:a5:b6:55:65:c6:33:09:b0:45: + a4:49:f5:d8:f6:54:25:2e:db:4b:c3:65:64:86:16: + bf:73:8e:78:e8:25:cc:c1:b4:7d:d1:b1:55:20:a4: + ae:de:b8:41:ee:43:9a:48:33:d4:12:39:a0:a8:fb: + 91:4f:47:bb:3b:d6:75:3b:62:a6:c5:5a:d7:7e:36: + 10:bc:3a:0d:46:e3:de:c1:df:50:d0:94:77:22:17: + a0:51:9b:af:dc:34:60:26:1d:a0:04:ac:a5:06:04: + 57:64:1a:d1:4f:1c:19:f8:be:4b:68:12:0f:5f:c3: + 12:40:64:bb:c6:a6:84:d9:5a:b0:78:1d:1b:11:c1: + 54:e6:15:d1:af:2b:e2:57:2b:6d:b9:c4:5d:b7:01: + 74:57:a7:fb:27:85:2b:92:15:e2:e3:13:97:29:c8: + 92:7b + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:fakehostname + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + F8:76:79:CB:11:85:F0:46:E5:95:E6:7E:69:CB:12:5E:4E:AA:EC:4D + X509v3 Authority Key Identifier: + keyid:9A:CF:CF:6E:EB:71:3D:DB:3C:F1:AE:88:6B:56:72:03:CB:08:A7:48 + DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server + serial:82:ED:BF:41:C8:80:91:9B + + Authority Information Access: + CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer + OCSP - URI:http://testca.pythontest.net/testca/ocsp/ + + X509v3 CRL Distribution Points: + + Full Name: + URI:http://testca.pythontest.net/testca/revocation.crl + + Signature Algorithm: sha1WithRSAEncryption + 6d:50:8d:fb:ee:4e:93:8b:eb:47:56:ba:38:cc:80:e1:9d:c7: + e1:9e:1f:9c:22:0c:d2:08:9b:ed:bf:31:d9:00:ee:af:8c:56: + 78:92:d1:7c:ba:4e:81:7f:82:1f:f4:68:99:86:91:c6:cb:57: + d3:b9:41:12:fa:75:53:fd:22:32:21:50:af:6b:4c:b1:34:36: + d1:a8:25:0a:d0:f0:f8:81:7d:69:58:6e:af:e3:d2:c4:32:87: + 79:d7:cd:ad:0c:56:f3:15:27:10:0c:f9:57:59:53:00:ed:af: + 5d:4d:07:86:7a:e5:f3:97:88:bc:86:b4:f1:17:46:33:55:28: + 66:7b:70:d3:a5:12:b9:4f:c7:ed:e6:13:20:2d:f0:9e:ec:17: + 64:cf:fd:13:14:1b:76:ba:64:ac:c5:51:b6:cd:13:0a:93:b1: + fd:43:09:a0:0b:44:6c:77:45:43:0b:e5:ed:70:b2:76:dc:08: + 4a:5b:73:5f:c1:fc:7f:63:70:f8:b9:ca:3c:98:06:5f:fd:98: + d1:e4:e6:61:5f:09:8f:6c:18:86:98:9c:cb:3f:73:7b:3f:38: + f5:a7:09:20:ee:a5:63:1c:ff:8b:a6:d1:8c:e8:f4:84:3d:99: + 38:0f:cc:e0:52:03:f9:18:05:23:76:39:de:52:ce:8e:fb:a6: + 6e:f5:4f:c3 +-----BEGIN CERTIFICATE----- +MIIE9zCCA9+gAwIBAgIJAILtv0HIgJGdMA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV +BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW +MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODAxMTkxOTA5MDZaFw0yNzExMjgx +OTA5MDZaMGIxCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj +MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xFTATBgNVBAMMDGZh +a2Vob3N0bmFtZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMf/vqFk +BkfhxJUKZVlrkcSooKMIqtw/AEQKz0FL7eJvA3jH02cU0/60J4UaAleXtYYWh5eC +Degr7WkWw5amxEe4XcF2qQKdKFLJ2ZPxstoYfAp4vRw/Ek/lP+5bt7M8pbZVZcYz +CbBFpEn12PZUJS7bS8NlZIYWv3OOeOglzMG0fdGxVSCkrt64Qe5Dmkgz1BI5oKj7 +kU9HuzvWdTtipsVa1342ELw6DUbj3sHfUNCUdyIXoFGbr9w0YCYdoASspQYEV2Qa +0U8cGfi+S2gSD1/DEkBku8amhNlasHgdGxHBVOYV0a8r4lcrbbnEXbcBdFen+yeF +K5IV4uMTlynIknsCAwEAAaOCAcMwggG/MBcGA1UdEQQQMA6CDGZha2Vob3N0bmFt +ZTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPh2ecsRhfBG5ZXmfmnLEl5OquxNMH0G +A1UdIwR2MHSAFJrPz27rcT3bPPGuiGtWcgPLCKdIoVGkTzBNMQswCQYDVQQGEwJY +WTEmMCQGA1UECgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNV +BAMMDW91ci1jYS1zZXJ2ZXKCCQCC7b9ByICRmzCBgwYIKwYBBQUHAQEEdzB1MDwG +CCsGAQUFBzAChjBodHRwOi8vdGVzdGNhLnB5dGhvbnRlc3QubmV0L3Rlc3RjYS9w +eWNhY2VydC5jZXIwNQYIKwYBBQUHMAGGKWh0dHA6Ly90ZXN0Y2EucHl0aG9udGVz +dC5uZXQvdGVzdGNhL29jc3AvMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly90ZXN0 +Y2EucHl0aG9udGVzdC5uZXQvdGVzdGNhL3Jldm9jYXRpb24uY3JsMA0GCSqGSIb3 +DQEBBQUAA4IBAQBtUI377k6Ti+tHVro4zIDhncfhnh+cIgzSCJvtvzHZAO6vjFZ4 +ktF8uk6Bf4If9GiZhpHGy1fTuUES+nVT/SIyIVCva0yxNDbRqCUK0PD4gX1pWG6v +49LEMod5182tDFbzFScQDPlXWVMA7a9dTQeGeuXzl4i8hrTxF0YzVShme3DTpRK5 +T8ft5hMgLfCe7Bdkz/0TFBt2umSsxVG2zRMKk7H9QwmgC0Rsd0VDC+XtcLJ23AhK +W3Nfwfx/Y3D4uco8mAZf/ZjR5OZhXwmPbBiGmJzLP3N7Pzj1pwkg7qVjHP+LptGM +6PSEPZk4D8zgUgP5GAUjdjneUs6O+6Zu9U/D +-----END CERTIFICATE----- diff --git a/Lib/test/keycertecc.pem b/Lib/test/keycertecc.pem new file mode 100644 index 000000000..deb484f99 --- /dev/null +++ b/Lib/test/keycertecc.pem @@ -0,0 +1,96 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDe3QWmhZX07HZbntz4 +CFqAOaoYMdYwD7Z3WPNIc2zR7p4D6BMOa7NAWjLV5A7CUw6hZANiAAQ5IVKzLLz4 +LCfcpy6fMOp+jk5KwywsU3upPtjA6E3UetxPcfnnv+gghRyDAYLN2OVqZgLMEmUo +F1j1SM1QrbhHIuNcVxI9gPPMdumcNFSz/hqxrBRtA/8Z2gywczdNLjc= +-----END PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 82:ed:bf:41:c8:80:91:9e + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=XY, O=Python Software Foundation CA, CN=our-ca-server + Validity + Not Before: Jan 19 19:09:06 2018 GMT + Not After : Nov 28 19:09:06 2027 GMT + Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=localhost-ecc + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (384 bit) + pub: + 04:39:21:52:b3:2c:bc:f8:2c:27:dc:a7:2e:9f:30: + ea:7e:8e:4e:4a:c3:2c:2c:53:7b:a9:3e:d8:c0:e8: + 4d:d4:7a:dc:4f:71:f9:e7:bf:e8:20:85:1c:83:01: + 82:cd:d8:e5:6a:66:02:cc:12:65:28:17:58:f5:48: + cd:50:ad:b8:47:22:e3:5c:57:12:3d:80:f3:cc:76: + e9:9c:34:54:b3:fe:1a:b1:ac:14:6d:03:ff:19:da: + 0c:b0:73:37:4d:2e:37 + ASN1 OID: secp384r1 + NIST CURVE: P-384 + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:localhost-ecc + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 33:23:0E:15:04:83:2E:3D:BF:DA:81:6D:10:38:80:C3:C2:B0:A4:74 + X509v3 Authority Key Identifier: + keyid:9A:CF:CF:6E:EB:71:3D:DB:3C:F1:AE:88:6B:56:72:03:CB:08:A7:48 + DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server + serial:82:ED:BF:41:C8:80:91:9B + + Authority Information Access: + CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer + OCSP - URI:http://testca.pythontest.net/testca/ocsp/ + + X509v3 CRL Distribution Points: + + Full Name: + URI:http://testca.pythontest.net/testca/revocation.crl + + Signature Algorithm: sha1WithRSAEncryption + 3b:6f:97:af:7e:5f:e0:14:34:ed:57:7e:de:ce:c4:85:1e:aa: + 84:52:94:7c:e5:ce:e9:9c:88:8b:ad:b5:4d:16:ac:af:81:ea: + b8:a2:e2:50:2e:cb:e9:11:bd:1b:a6:3f:0c:a2:d7:7b:67:72: + b3:43:16:ad:c6:87:ac:6e:ac:47:78:ef:2f:8c:86:e8:9b:d1: + 43:8c:c1:7a:91:30:e9:14:d6:9f:41:8b:9b:0b:24:9b:78:86: + 11:8a:fc:2b:cd:c9:13:ee:90:4f:14:33:51:a3:c4:9e:d6:06: + 48:f5:41:12:af:f0:f2:71:40:78:f5:96:c2:5d:cf:e1:38:ff: + bf:10:eb:74:2f:c2:23:21:3e:27:f5:f1:f2:af:2c:62:82:31: + 00:c8:96:1b:c3:7e:8d:71:89:e7:40:b5:67:1a:33:fb:c0:8b: + 96:0c:36:78:25:27:82:d8:27:27:52:0f:f7:69:cd:ff:2b:92: + 10:d3:d2:0a:db:65:ed:af:90:eb:db:76:f3:8a:7a:13:9e:c6: + 33:57:15:42:06:13:d6:54:49:fa:84:a7:0e:1d:14:72:ca:19: + 8e:2b:aa:a4:02:54:3c:f6:1c:23:81:7a:59:54:b0:92:65:72: + c8:e5:ba:9f:03:4e:30:f2:4d:45:85:e3:35:a8:b1:68:58:b9: + 3b:20:a3:eb +-----BEGIN CERTIFICATE----- +MIIESzCCAzOgAwIBAgIJAILtv0HIgJGeMA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV +BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW +MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODAxMTkxOTA5MDZaFw0yNzExMjgx +OTA5MDZaMGMxCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj +MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xFjAUBgNVBAMMDWxv +Y2FsaG9zdC1lY2MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQ5IVKzLLz4LCfcpy6f +MOp+jk5KwywsU3upPtjA6E3UetxPcfnnv+gghRyDAYLN2OVqZgLMEmUoF1j1SM1Q +rbhHIuNcVxI9gPPMdumcNFSz/hqxrBRtA/8Z2gywczdNLjejggHEMIIBwDAYBgNV +HREEETAPgg1sb2NhbGhvc3QtZWNjMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAU +BggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUMyMO +FQSDLj2/2oFtEDiAw8KwpHQwfQYDVR0jBHYwdIAUms/PbutxPds88a6Ia1ZyA8sI +p0ihUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUg +Rm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcoIJAILtv0HIgJGb +MIGDBggrBgEFBQcBAQR3MHUwPAYIKwYBBQUHMAKGMGh0dHA6Ly90ZXN0Y2EucHl0 +aG9udGVzdC5uZXQvdGVzdGNhL3B5Y2FjZXJ0LmNlcjA1BggrBgEFBQcwAYYpaHR0 +cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2Evb2NzcC8wQwYDVR0fBDww +OjA4oDagNIYyaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2EvcmV2 +b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQEFBQADggEBADtvl69+X+AUNO1Xft7OxIUe +qoRSlHzlzumciIuttU0WrK+B6rii4lAuy+kRvRumPwyi13tncrNDFq3Gh6xurEd4 +7y+Mhuib0UOMwXqRMOkU1p9Bi5sLJJt4hhGK/CvNyRPukE8UM1GjxJ7WBkj1QRKv +8PJxQHj1lsJdz+E4/78Q63QvwiMhPif18fKvLGKCMQDIlhvDfo1xiedAtWcaM/vA +i5YMNnglJ4LYJydSD/dpzf8rkhDT0grbZe2vkOvbdvOKehOexjNXFUIGE9ZUSfqE +pw4dFHLKGY4rqqQCVDz2HCOBellUsJJlcsjlup8DTjDyTUWF4zWosWhYuTsgo+s= +-----END CERTIFICATE----- diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py new file mode 100644 index 000000000..d02695360 --- /dev/null +++ b/Lib/test/test_ftplib.py @@ -0,0 +1,1103 @@ +"""Test script for ftplib module.""" + +# Modified by Giampaolo Rodola' to test FTP class, IPv6 and TLS +# environment + +import unittest +import ftplib +import asyncore +import asynchat +import socket +import io +import errno +import os +import threading +import time +try: + import ssl +except ImportError: + ssl = None + +from unittest import TestCase, skipUnless +from test import support +from test.support import HOST, HOSTv6 + +import sys +if sys.platform == 'win32': + raise unittest.SkipTest("test_ftplib not working on windows") + +TIMEOUT = 3 +# the dummy data returned by server over the data channel when +# RETR, LIST, NLST, MLSD commands are issued +RETR_DATA = 'abcde12345\r\n' * 1000 +LIST_DATA = 'foo\r\nbar\r\n' +NLST_DATA = 'foo\r\nbar\r\n' +MLSD_DATA = ("type=cdir;perm=el;unique==keVO1+ZF4; test\r\n" + "type=pdir;perm=e;unique==keVO1+d?3; ..\r\n" + "type=OS.unix=slink:/foobar;perm=;unique==keVO1+4G4; foobar\r\n" + "type=OS.unix=chr-13/29;perm=;unique==keVO1+5G4; device\r\n" + "type=OS.unix=blk-11/108;perm=;unique==keVO1+6G4; block\r\n" + "type=file;perm=awr;unique==keVO1+8G4; writable\r\n" + "type=dir;perm=cpmel;unique==keVO1+7G4; promiscuous\r\n" + "type=dir;perm=;unique==keVO1+1t2; no-exec\r\n" + "type=file;perm=r;unique==keVO1+EG4; two words\r\n" + "type=file;perm=r;unique==keVO1+IH4; leading space\r\n" + "type=file;perm=r;unique==keVO1+1G4; file1\r\n" + "type=dir;perm=cpmel;unique==keVO1+7G4; incoming\r\n" + "type=file;perm=r;unique==keVO1+1G4; file2\r\n" + "type=file;perm=r;unique==keVO1+1G4; file3\r\n" + "type=file;perm=r;unique==keVO1+1G4; file4\r\n") + + +class DummyDTPHandler(asynchat.async_chat): + dtp_conn_closed = False + + def __init__(self, conn, baseclass): + asynchat.async_chat.__init__(self, conn) + self.baseclass = baseclass + self.baseclass.last_received_data = '' + + def handle_read(self): + self.baseclass.last_received_data += self.recv(1024).decode('ascii') + + def handle_close(self): + # XXX: this method can be called many times in a row for a single + # connection, including in clear-text (non-TLS) mode. + # (behaviour witnessed with test_data_connection) + if not self.dtp_conn_closed: + self.baseclass.push('226 transfer complete') + self.close() + self.dtp_conn_closed = True + + def push(self, what): + if self.baseclass.next_data is not None: + what = self.baseclass.next_data + self.baseclass.next_data = None + if not what: + return self.close_when_done() + super(DummyDTPHandler, self).push(what.encode('ascii')) + + def handle_error(self): + raise Exception + + +class DummyFTPHandler(asynchat.async_chat): + + dtp_handler = DummyDTPHandler + + def __init__(self, conn): + asynchat.async_chat.__init__(self, conn) + # tells the socket to handle urgent data inline (ABOR command) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_OOBINLINE, 1) + self.set_terminator(b"\r\n") + self.in_buffer = [] + self.dtp = None + self.last_received_cmd = None + self.last_received_data = '' + self.next_response = '' + self.next_data = None + self.rest = None + self.next_retr_data = RETR_DATA + self.push('220 welcome') + + def collect_incoming_data(self, data): + self.in_buffer.append(data) + + def found_terminator(self): + line = b''.join(self.in_buffer).decode('ascii') + self.in_buffer = [] + if self.next_response: + self.push(self.next_response) + self.next_response = '' + cmd = line.split(' ')[0].lower() + self.last_received_cmd = cmd + space = line.find(' ') + if space != -1: + arg = line[space + 1:] + else: + arg = "" + if hasattr(self, 'cmd_' + cmd): + method = getattr(self, 'cmd_' + cmd) + method(arg) + else: + self.push('550 command "%s" not understood.' %cmd) + + def handle_error(self): + raise Exception + + def push(self, data): + asynchat.async_chat.push(self, data.encode('ascii') + b'\r\n') + + def cmd_port(self, arg): + addr = list(map(int, arg.split(','))) + ip = '%d.%d.%d.%d' %tuple(addr[:4]) + port = (addr[4] * 256) + addr[5] + s = socket.create_connection((ip, port), timeout=TIMEOUT) + self.dtp = self.dtp_handler(s, baseclass=self) + self.push('200 active data connection established') + + def cmd_pasv(self, arg): + with socket.create_server((self.socket.getsockname()[0], 0)) as sock: + sock.settimeout(TIMEOUT) + ip, port = sock.getsockname()[:2] + ip = ip.replace('.', ','); p1 = port / 256; p2 = port % 256 + self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2)) + conn, addr = sock.accept() + self.dtp = self.dtp_handler(conn, baseclass=self) + + def cmd_eprt(self, arg): + af, ip, port = arg.split(arg[0])[1:-1] + port = int(port) + s = socket.create_connection((ip, port), timeout=TIMEOUT) + self.dtp = self.dtp_handler(s, baseclass=self) + self.push('200 active data connection established') + + def cmd_epsv(self, arg): + with socket.create_server((self.socket.getsockname()[0], 0), + family=socket.AF_INET6) as sock: + sock.settimeout(TIMEOUT) + port = sock.getsockname()[1] + self.push('229 entering extended passive mode (|||%d|)' %port) + conn, addr = sock.accept() + self.dtp = self.dtp_handler(conn, baseclass=self) + + def cmd_echo(self, arg): + # sends back the received string (used by the test suite) + self.push(arg) + + def cmd_noop(self, arg): + self.push('200 noop ok') + + def cmd_user(self, arg): + self.push('331 username ok') + + def cmd_pass(self, arg): + self.push('230 password ok') + + def cmd_acct(self, arg): + self.push('230 acct ok') + + def cmd_rnfr(self, arg): + self.push('350 rnfr ok') + + def cmd_rnto(self, arg): + self.push('250 rnto ok') + + def cmd_dele(self, arg): + self.push('250 dele ok') + + def cmd_cwd(self, arg): + self.push('250 cwd ok') + + def cmd_size(self, arg): + self.push('250 1000') + + def cmd_mkd(self, arg): + self.push('257 "%s"' %arg) + + def cmd_rmd(self, arg): + self.push('250 rmd ok') + + def cmd_pwd(self, arg): + self.push('257 "pwd ok"') + + def cmd_type(self, arg): + self.push('200 type ok') + + def cmd_quit(self, arg): + self.push('221 quit ok') + self.close() + + def cmd_abor(self, arg): + self.push('226 abor ok') + + def cmd_stor(self, arg): + self.push('125 stor ok') + + def cmd_rest(self, arg): + self.rest = arg + self.push('350 rest ok') + + def cmd_retr(self, arg): + self.push('125 retr ok') + if self.rest is not None: + offset = int(self.rest) + else: + offset = 0 + self.dtp.push(self.next_retr_data[offset:]) + self.dtp.close_when_done() + self.rest = None + + def cmd_list(self, arg): + self.push('125 list ok') + self.dtp.push(LIST_DATA) + self.dtp.close_when_done() + + def cmd_nlst(self, arg): + self.push('125 nlst ok') + self.dtp.push(NLST_DATA) + self.dtp.close_when_done() + + def cmd_opts(self, arg): + self.push('200 opts ok') + + def cmd_mlsd(self, arg): + self.push('125 mlsd ok') + self.dtp.push(MLSD_DATA) + self.dtp.close_when_done() + + def cmd_setlongretr(self, arg): + # For testing. Next RETR will return long line. + self.next_retr_data = 'x' * int(arg) + self.push('125 setlongretr ok') + + +class DummyFTPServer(asyncore.dispatcher, threading.Thread): + + handler = DummyFTPHandler + + def __init__(self, address, af=socket.AF_INET): + threading.Thread.__init__(self) + asyncore.dispatcher.__init__(self) + self.daemon = True + self.create_socket(af, socket.SOCK_STREAM) + self.bind(address) + self.listen(5) + self.active = False + self.active_lock = threading.Lock() + self.host, self.port = self.socket.getsockname()[:2] + self.handler_instance = None + + def start(self): + assert not self.active + self.__flag = threading.Event() + threading.Thread.start(self) + self.__flag.wait() + + def run(self): + self.active = True + self.__flag.set() + while self.active and asyncore.socket_map: + self.active_lock.acquire() + asyncore.loop(timeout=0.1, count=1) + self.active_lock.release() + asyncore.close_all(ignore_all=True) + + def stop(self): + assert self.active + self.active = False + self.join() + + def handle_accepted(self, conn, addr): + self.handler_instance = self.handler(conn) + + def handle_connect(self): + self.close() + handle_read = handle_connect + + def writable(self): + return 0 + + def handle_error(self): + raise Exception + + +if ssl is not None: + + CERTFILE = os.path.join(os.path.dirname(__file__), "keycert3.pem") + CAFILE = os.path.join(os.path.dirname(__file__), "pycacert.pem") + + class SSLConnection(asyncore.dispatcher): + """An asyncore.dispatcher subclass supporting TLS/SSL.""" + + _ssl_accepting = False + _ssl_closing = False + + def secure_connection(self): + context = ssl.SSLContext() + context.load_cert_chain(CERTFILE) + socket = context.wrap_socket(self.socket, + suppress_ragged_eofs=False, + server_side=True, + do_handshake_on_connect=False) + self.del_channel() + self.set_socket(socket) + self._ssl_accepting = True + + def _do_ssl_handshake(self): + try: + self.socket.do_handshake() + except ssl.SSLError as err: + if err.args[0] in (ssl.SSL_ERROR_WANT_READ, + ssl.SSL_ERROR_WANT_WRITE): + return + elif err.args[0] == ssl.SSL_ERROR_EOF: + return self.handle_close() + # TODO: SSLError does not expose alert information + elif "SSLV3_ALERT_BAD_CERTIFICATE" in err.args[1]: + return self.handle_close() + raise + except OSError as err: + if err.args[0] == errno.ECONNABORTED: + return self.handle_close() + else: + self._ssl_accepting = False + + def _do_ssl_shutdown(self): + self._ssl_closing = True + try: + self.socket = self.socket.unwrap() + except ssl.SSLError as err: + if err.args[0] in (ssl.SSL_ERROR_WANT_READ, + ssl.SSL_ERROR_WANT_WRITE): + return + except OSError as err: + # Any "socket error" corresponds to a SSL_ERROR_SYSCALL return + # from OpenSSL's SSL_shutdown(), corresponding to a + # closed socket condition. See also: + # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html + pass + self._ssl_closing = False + if getattr(self, '_ccc', False) is False: + super(SSLConnection, self).close() + else: + pass + + def handle_read_event(self): + if self._ssl_accepting: + self._do_ssl_handshake() + elif self._ssl_closing: + self._do_ssl_shutdown() + else: + super(SSLConnection, self).handle_read_event() + + def handle_write_event(self): + if self._ssl_accepting: + self._do_ssl_handshake() + elif self._ssl_closing: + self._do_ssl_shutdown() + else: + super(SSLConnection, self).handle_write_event() + + def send(self, data): + try: + return super(SSLConnection, self).send(data) + except ssl.SSLError as err: + if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN, + ssl.SSL_ERROR_WANT_READ, + ssl.SSL_ERROR_WANT_WRITE): + return 0 + raise + + def recv(self, buffer_size): + try: + return super(SSLConnection, self).recv(buffer_size) + except ssl.SSLError as err: + if err.args[0] in (ssl.SSL_ERROR_WANT_READ, + ssl.SSL_ERROR_WANT_WRITE): + return b'' + if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN): + self.handle_close() + return b'' + raise + + def handle_error(self): + raise Exception + + def close(self): + if (isinstance(self.socket, ssl.SSLSocket) and + self.socket._sslobj is not None): + self._do_ssl_shutdown() + else: + super(SSLConnection, self).close() + + + class DummyTLS_DTPHandler(SSLConnection, DummyDTPHandler): + """A DummyDTPHandler subclass supporting TLS/SSL.""" + + def __init__(self, conn, baseclass): + DummyDTPHandler.__init__(self, conn, baseclass) + if self.baseclass.secure_data_channel: + self.secure_connection() + + + class DummyTLS_FTPHandler(SSLConnection, DummyFTPHandler): + """A DummyFTPHandler subclass supporting TLS/SSL.""" + + dtp_handler = DummyTLS_DTPHandler + + def __init__(self, conn): + DummyFTPHandler.__init__(self, conn) + self.secure_data_channel = False + self._ccc = False + + def cmd_auth(self, line): + """Set up secure control channel.""" + self.push('234 AUTH TLS successful') + self.secure_connection() + + def cmd_ccc(self, line): + self.push('220 Reverting back to clear-text') + self._ccc = True + self._do_ssl_shutdown() + + def cmd_pbsz(self, line): + """Negotiate size of buffer for secure data transfer. + For TLS/SSL the only valid value for the parameter is '0'. + Any other value is accepted but ignored. + """ + self.push('200 PBSZ=0 successful.') + + def cmd_prot(self, line): + """Setup un/secure data channel.""" + arg = line.upper() + if arg == 'C': + self.push('200 Protection set to Clear') + self.secure_data_channel = False + elif arg == 'P': + self.push('200 Protection set to Private') + self.secure_data_channel = True + else: + self.push("502 Unrecognized PROT type (use C or P).") + + + class DummyTLS_FTPServer(DummyFTPServer): + handler = DummyTLS_FTPHandler + + +class TestFTPClass(TestCase): + + def setUp(self): + self.server = DummyFTPServer((HOST, 0)) + self.server.start() + self.client = ftplib.FTP(timeout=TIMEOUT) + self.client.connect(self.server.host, self.server.port) + + def tearDown(self): + self.client.close() + self.server.stop() + # Explicitly clear the attribute to prevent dangling thread + self.server = None + asyncore.close_all(ignore_all=True) + + def check_data(self, received, expected): + self.assertEqual(len(received), len(expected)) + self.assertEqual(received, expected) + + def test_getwelcome(self): + self.assertEqual(self.client.getwelcome(), '220 welcome') + + def test_sanitize(self): + self.assertEqual(self.client.sanitize('foo'), repr('foo')) + self.assertEqual(self.client.sanitize('pass 12345'), repr('pass *****')) + self.assertEqual(self.client.sanitize('PASS 12345'), repr('PASS *****')) + + def test_exceptions(self): + self.assertRaises(ValueError, self.client.sendcmd, 'echo 40\r\n0') + self.assertRaises(ValueError, self.client.sendcmd, 'echo 40\n0') + self.assertRaises(ValueError, self.client.sendcmd, 'echo 40\r0') + self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 400') + self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 499') + self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 500') + self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 599') + self.assertRaises(ftplib.error_proto, self.client.sendcmd, 'echo 999') + + def test_all_errors(self): + exceptions = (ftplib.error_reply, ftplib.error_temp, ftplib.error_perm, + ftplib.error_proto, ftplib.Error, OSError, + EOFError) + for x in exceptions: + try: + raise x('exception not included in all_errors set') + except ftplib.all_errors: + pass + + def test_set_pasv(self): + # passive mode is supposed to be enabled by default + self.assertTrue(self.client.passiveserver) + self.client.set_pasv(True) + self.assertTrue(self.client.passiveserver) + self.client.set_pasv(False) + self.assertFalse(self.client.passiveserver) + + def test_voidcmd(self): + self.client.voidcmd('echo 200') + self.client.voidcmd('echo 299') + self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 199') + self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 300') + + def test_login(self): + self.client.login() + + def test_acct(self): + self.client.acct('passwd') + + def test_rename(self): + self.client.rename('a', 'b') + self.server.handler_instance.next_response = '200' + self.assertRaises(ftplib.error_reply, self.client.rename, 'a', 'b') + + def test_delete(self): + self.client.delete('foo') + self.server.handler_instance.next_response = '199' + self.assertRaises(ftplib.error_reply, self.client.delete, 'foo') + + def test_size(self): + self.client.size('foo') + + def test_mkd(self): + dir = self.client.mkd('/foo') + self.assertEqual(dir, '/foo') + + def test_rmd(self): + self.client.rmd('foo') + + def test_cwd(self): + dir = self.client.cwd('/foo') + self.assertEqual(dir, '250 cwd ok') + + def test_pwd(self): + dir = self.client.pwd() + self.assertEqual(dir, 'pwd ok') + + def test_quit(self): + self.assertEqual(self.client.quit(), '221 quit ok') + # Ensure the connection gets closed; sock attribute should be None + self.assertEqual(self.client.sock, None) + + def test_abort(self): + self.client.abort() + + def test_retrbinary(self): + def callback(data): + received.append(data.decode('ascii')) + received = [] + self.client.retrbinary('retr', callback) + self.check_data(''.join(received), RETR_DATA) + + def test_retrbinary_rest(self): + def callback(data): + received.append(data.decode('ascii')) + for rest in (0, 10, 20): + received = [] + self.client.retrbinary('retr', callback, rest=rest) + self.check_data(''.join(received), RETR_DATA[rest:]) + + def test_retrlines(self): + received = [] + self.client.retrlines('retr', received.append) + self.check_data(''.join(received), RETR_DATA.replace('\r\n', '')) + + @unittest.skip("TODO: RUSTPYTHON; weird limiting to 8192, something w/ buffering?") + def test_storbinary(self): + f = io.BytesIO(RETR_DATA.encode('ascii')) + self.client.storbinary('stor', f) + self.check_data(self.server.handler_instance.last_received_data, RETR_DATA) + # test new callback arg + flag = [] + f.seek(0) + self.client.storbinary('stor', f, callback=lambda x: flag.append(None)) + self.assertTrue(flag) + + def test_storbinary_rest(self): + f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii')) + for r in (30, '30'): + f.seek(0) + self.client.storbinary('stor', f, rest=r) + self.assertEqual(self.server.handler_instance.rest, str(r)) + + def test_storlines(self): + f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii')) + self.client.storlines('stor', f) + self.check_data(self.server.handler_instance.last_received_data, RETR_DATA) + # test new callback arg + flag = [] + f.seek(0) + self.client.storlines('stor foo', f, callback=lambda x: flag.append(None)) + self.assertTrue(flag) + + f = io.StringIO(RETR_DATA.replace('\r\n', '\n')) + # storlines() expects a binary file, not a text file + with support.check_warnings(('', BytesWarning), quiet=True): + self.assertRaises(TypeError, self.client.storlines, 'stor foo', f) + + def test_nlst(self): + self.client.nlst() + self.assertEqual(self.client.nlst(), NLST_DATA.split('\r\n')[:-1]) + + def test_dir(self): + l = [] + self.client.dir(lambda x: l.append(x)) + self.assertEqual(''.join(l), LIST_DATA.replace('\r\n', '')) + + def test_mlsd(self): + list(self.client.mlsd()) + list(self.client.mlsd(path='/')) + list(self.client.mlsd(path='/', facts=['size', 'type'])) + + ls = list(self.client.mlsd()) + for name, facts in ls: + self.assertIsInstance(name, str) + self.assertIsInstance(facts, dict) + self.assertTrue(name) + self.assertIn('type', facts) + self.assertIn('perm', facts) + self.assertIn('unique', facts) + + def set_data(data): + self.server.handler_instance.next_data = data + + def test_entry(line, type=None, perm=None, unique=None, name=None): + type = 'type' if type is None else type + perm = 'perm' if perm is None else perm + unique = 'unique' if unique is None else unique + name = 'name' if name is None else name + set_data(line) + _name, facts = next(self.client.mlsd()) + self.assertEqual(_name, name) + self.assertEqual(facts['type'], type) + self.assertEqual(facts['perm'], perm) + self.assertEqual(facts['unique'], unique) + + # plain + test_entry('type=type;perm=perm;unique=unique; name\r\n') + # "=" in fact value + test_entry('type=ty=pe;perm=perm;unique=unique; name\r\n', type="ty=pe") + test_entry('type==type;perm=perm;unique=unique; name\r\n', type="=type") + test_entry('type=t=y=pe;perm=perm;unique=unique; name\r\n', type="t=y=pe") + test_entry('type=====;perm=perm;unique=unique; name\r\n', type="====") + # spaces in name + test_entry('type=type;perm=perm;unique=unique; na me\r\n', name="na me") + test_entry('type=type;perm=perm;unique=unique; name \r\n', name="name ") + test_entry('type=type;perm=perm;unique=unique; name\r\n', name=" name") + test_entry('type=type;perm=perm;unique=unique; n am e\r\n', name="n am e") + # ";" in name + test_entry('type=type;perm=perm;unique=unique; na;me\r\n', name="na;me") + test_entry('type=type;perm=perm;unique=unique; ;name\r\n', name=";name") + test_entry('type=type;perm=perm;unique=unique; ;name;\r\n', name=";name;") + test_entry('type=type;perm=perm;unique=unique; ;;;;\r\n', name=";;;;") + # case sensitiveness + set_data('Type=type;TyPe=perm;UNIQUE=unique; name\r\n') + _name, facts = next(self.client.mlsd()) + for x in facts: + self.assertTrue(x.islower()) + # no data (directory empty) + set_data('') + self.assertRaises(StopIteration, next, self.client.mlsd()) + set_data('') + for x in self.client.mlsd(): + self.fail("unexpected data %s" % x) + + def test_makeport(self): + with self.client.makeport(): + # IPv4 is in use, just make sure send_eprt has not been used + self.assertEqual(self.server.handler_instance.last_received_cmd, + 'port') + + def test_makepasv(self): + host, port = self.client.makepasv() + conn = socket.create_connection((host, port), timeout=TIMEOUT) + conn.close() + # IPv4 is in use, just make sure send_epsv has not been used + self.assertEqual(self.server.handler_instance.last_received_cmd, 'pasv') + + def test_with_statement(self): + self.client.quit() + + def is_client_connected(): + if self.client.sock is None: + return False + try: + self.client.sendcmd('noop') + except (OSError, EOFError): + return False + return True + + # base test + with ftplib.FTP(timeout=TIMEOUT) as self.client: + self.client.connect(self.server.host, self.server.port) + self.client.sendcmd('noop') + self.assertTrue(is_client_connected()) + self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit') + self.assertFalse(is_client_connected()) + + # QUIT sent inside the with block + with ftplib.FTP(timeout=TIMEOUT) as self.client: + self.client.connect(self.server.host, self.server.port) + self.client.sendcmd('noop') + self.client.quit() + self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit') + self.assertFalse(is_client_connected()) + + # force a wrong response code to be sent on QUIT: error_perm + # is expected and the connection is supposed to be closed + try: + with ftplib.FTP(timeout=TIMEOUT) as self.client: + self.client.connect(self.server.host, self.server.port) + self.client.sendcmd('noop') + self.server.handler_instance.next_response = '550 error on quit' + except ftplib.error_perm as err: + self.assertEqual(str(err), '550 error on quit') + else: + self.fail('Exception not raised') + # needed to give the threaded server some time to set the attribute + # which otherwise would still be == 'noop' + time.sleep(0.1) + self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit') + self.assertFalse(is_client_connected()) + + def test_source_address(self): + self.client.quit() + port = support.find_unused_port() + try: + self.client.connect(self.server.host, self.server.port, + source_address=(HOST, port)) + self.assertEqual(self.client.sock.getsockname()[1], port) + self.client.quit() + except OSError as e: + if e.errno == errno.EADDRINUSE: + self.skipTest("couldn't bind to port %d" % port) + raise + + def test_source_address_passive_connection(self): + port = support.find_unused_port() + self.client.source_address = (HOST, port) + try: + with self.client.transfercmd('list') as sock: + self.assertEqual(sock.getsockname()[1], port) + except OSError as e: + if e.errno == errno.EADDRINUSE: + self.skipTest("couldn't bind to port %d" % port) + raise + + def test_parse257(self): + self.assertEqual(ftplib.parse257('257 "/foo/bar"'), '/foo/bar') + self.assertEqual(ftplib.parse257('257 "/foo/bar" created'), '/foo/bar') + self.assertEqual(ftplib.parse257('257 ""'), '') + self.assertEqual(ftplib.parse257('257 "" created'), '') + self.assertRaises(ftplib.error_reply, ftplib.parse257, '250 "/foo/bar"') + # The 257 response is supposed to include the directory + # name and in case it contains embedded double-quotes + # they must be doubled (see RFC-959, chapter 7, appendix 2). + self.assertEqual(ftplib.parse257('257 "/foo/b""ar"'), '/foo/b"ar') + self.assertEqual(ftplib.parse257('257 "/foo/b""ar" created'), '/foo/b"ar') + + def test_line_too_long(self): + self.assertRaises(ftplib.Error, self.client.sendcmd, + 'x' * self.client.maxline * 2) + + def test_retrlines_too_long(self): + self.client.sendcmd('SETLONGRETR %d' % (self.client.maxline * 2)) + received = [] + self.assertRaises(ftplib.Error, + self.client.retrlines, 'retr', received.append) + + def test_storlines_too_long(self): + f = io.BytesIO(b'x' * self.client.maxline * 2) + self.assertRaises(ftplib.Error, self.client.storlines, 'stor', f) + + +@skipUnless(support.IPV6_ENABLED, "IPv6 not enabled") +class TestIPv6Environment(TestCase): + + def setUp(self): + self.server = DummyFTPServer((HOSTv6, 0), af=socket.AF_INET6) + self.server.start() + self.client = ftplib.FTP(timeout=TIMEOUT) + self.client.connect(self.server.host, self.server.port) + + def tearDown(self): + self.client.close() + self.server.stop() + # Explicitly clear the attribute to prevent dangling thread + self.server = None + asyncore.close_all(ignore_all=True) + + def test_af(self): + self.assertEqual(self.client.af, socket.AF_INET6) + + def test_makeport(self): + with self.client.makeport(): + self.assertEqual(self.server.handler_instance.last_received_cmd, + 'eprt') + + def test_makepasv(self): + host, port = self.client.makepasv() + conn = socket.create_connection((host, port), timeout=TIMEOUT) + conn.close() + self.assertEqual(self.server.handler_instance.last_received_cmd, 'epsv') + + def test_transfer(self): + def retr(): + def callback(data): + received.append(data.decode('ascii')) + received = [] + self.client.retrbinary('retr', callback) + self.assertEqual(len(''.join(received)), len(RETR_DATA)) + self.assertEqual(''.join(received), RETR_DATA) + self.client.set_pasv(True) + retr() + self.client.set_pasv(False) + retr() + + +@skipUnless(ssl, "SSL not available") +@unittest.skip("TODO: RUSTPYTHON; figure out why do_handshake() is throwing 'ssl session has been shut down'. SslSession object?") +class TestTLS_FTPClassMixin(TestFTPClass): + """Repeat TestFTPClass tests starting the TLS layer for both control + and data connections first. + """ + + def setUp(self): + self.server = DummyTLS_FTPServer((HOST, 0)) + self.server.start() + self.client = ftplib.FTP_TLS(timeout=TIMEOUT) + self.client.connect(self.server.host, self.server.port) + # enable TLS + self.client.auth() + self.client.prot_p() + + +@skipUnless(ssl, "SSL not available") +@unittest.skip("TODO: RUSTPYTHON; fix ssl") +class TestTLS_FTPClass(TestCase): + """Specific TLS_FTP class tests.""" + + def setUp(self): + self.server = DummyTLS_FTPServer((HOST, 0)) + self.server.start() + self.client = ftplib.FTP_TLS(timeout=TIMEOUT) + self.client.connect(self.server.host, self.server.port) + + def tearDown(self): + self.client.close() + self.server.stop() + # Explicitly clear the attribute to prevent dangling thread + self.server = None + asyncore.close_all(ignore_all=True) + + def test_control_connection(self): + self.assertNotIsInstance(self.client.sock, ssl.SSLSocket) + self.client.auth() + self.assertIsInstance(self.client.sock, ssl.SSLSocket) + + def test_data_connection(self): + # clear text + with self.client.transfercmd('list') as sock: + self.assertNotIsInstance(sock, ssl.SSLSocket) + self.assertEqual(sock.recv(1024), LIST_DATA.encode('ascii')) + self.assertEqual(self.client.voidresp(), "226 transfer complete") + + # secured, after PROT P + self.client.prot_p() + with self.client.transfercmd('list') as sock: + self.assertIsInstance(sock, ssl.SSLSocket) + # consume from SSL socket to finalize handshake and avoid + # "SSLError [SSL] shutdown while in init" + self.assertEqual(sock.recv(1024), LIST_DATA.encode('ascii')) + self.assertEqual(self.client.voidresp(), "226 transfer complete") + + # PROT C is issued, the connection must be in cleartext again + self.client.prot_c() + with self.client.transfercmd('list') as sock: + self.assertNotIsInstance(sock, ssl.SSLSocket) + self.assertEqual(sock.recv(1024), LIST_DATA.encode('ascii')) + self.assertEqual(self.client.voidresp(), "226 transfer complete") + + def test_login(self): + # login() is supposed to implicitly secure the control connection + self.assertNotIsInstance(self.client.sock, ssl.SSLSocket) + self.client.login() + self.assertIsInstance(self.client.sock, ssl.SSLSocket) + # make sure that AUTH TLS doesn't get issued again + self.client.login() + + def test_auth_issued_twice(self): + self.client.auth() + self.assertRaises(ValueError, self.client.auth) + + def test_context(self): + self.client.quit() + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + self.assertRaises(ValueError, ftplib.FTP_TLS, keyfile=CERTFILE, + context=ctx) + self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE, + context=ctx) + self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE, + keyfile=CERTFILE, context=ctx) + + self.client = ftplib.FTP_TLS(context=ctx, timeout=TIMEOUT) + self.client.connect(self.server.host, self.server.port) + self.assertNotIsInstance(self.client.sock, ssl.SSLSocket) + self.client.auth() + self.assertIs(self.client.sock.context, ctx) + self.assertIsInstance(self.client.sock, ssl.SSLSocket) + + self.client.prot_p() + with self.client.transfercmd('list') as sock: + self.assertIs(sock.context, ctx) + self.assertIsInstance(sock, ssl.SSLSocket) + + def test_ccc(self): + self.assertRaises(ValueError, self.client.ccc) + self.client.login(secure=True) + self.assertIsInstance(self.client.sock, ssl.SSLSocket) + self.client.ccc() + self.assertRaises(ValueError, self.client.sock.unwrap) + + @skipUnless(False, "FIXME: bpo-32706") + def test_check_hostname(self): + self.client.quit() + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + self.assertEqual(ctx.check_hostname, True) + ctx.load_verify_locations(CAFILE) + self.client = ftplib.FTP_TLS(context=ctx, timeout=TIMEOUT) + + # 127.0.0.1 doesn't match SAN + self.client.connect(self.server.host, self.server.port) + with self.assertRaises(ssl.CertificateError): + self.client.auth() + # exception quits connection + + self.client.connect(self.server.host, self.server.port) + self.client.prot_p() + with self.assertRaises(ssl.CertificateError): + with self.client.transfercmd("list") as sock: + pass + self.client.quit() + + self.client.connect("localhost", self.server.port) + self.client.auth() + self.client.quit() + + self.client.connect("localhost", self.server.port) + self.client.prot_p() + with self.client.transfercmd("list") as sock: + pass + + +class TestTimeouts(TestCase): + + def setUp(self): + self.evt = threading.Event() + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.settimeout(20) + self.port = support.bind_port(self.sock) + self.server_thread = threading.Thread(target=self.server) + self.server_thread.daemon = True + self.server_thread.start() + # Wait for the server to be ready. + self.evt.wait() + self.evt.clear() + self.old_port = ftplib.FTP.port + ftplib.FTP.port = self.port + + def tearDown(self): + ftplib.FTP.port = self.old_port + self.server_thread.join() + # Explicitly clear the attribute to prevent dangling thread + self.server_thread = None + + def server(self): + # This method sets the evt 3 times: + # 1) when the connection is ready to be accepted. + # 2) when it is safe for the caller to close the connection + # 3) when we have closed the socket + self.sock.listen() + # (1) Signal the caller that we are ready to accept the connection. + self.evt.set() + try: + conn, addr = self.sock.accept() + except socket.timeout: + pass + else: + conn.sendall(b"1 Hola mundo\n") + conn.shutdown(socket.SHUT_WR) + # (2) Signal the caller that it is safe to close the socket. + self.evt.set() + conn.close() + finally: + self.sock.close() + + @unittest.skip("TODO: RUSTPYTHON; socket.{get,set}timeout") + def testTimeoutDefault(self): + # default -- use global socket timeout + self.assertIsNone(socket.getdefaulttimeout()) + socket.setdefaulttimeout(30) + try: + ftp = ftplib.FTP(HOST) + finally: + socket.setdefaulttimeout(None) + self.assertEqual(ftp.sock.gettimeout(), 30) + self.evt.wait() + ftp.close() + + @unittest.skip("TODO: RUSTPYTHON; socket.{get,set}timeout") + def testTimeoutNone(self): + # no timeout -- do not use global socket timeout + self.assertIsNone(socket.getdefaulttimeout()) + socket.setdefaulttimeout(30) + try: + ftp = ftplib.FTP(HOST, timeout=None) + finally: + socket.setdefaulttimeout(None) + self.assertIsNone(ftp.sock.gettimeout()) + self.evt.wait() + ftp.close() + + def testTimeoutValue(self): + # a value + ftp = ftplib.FTP(HOST, timeout=30) + self.assertEqual(ftp.sock.gettimeout(), 30) + self.evt.wait() + ftp.close() + + def testTimeoutConnect(self): + ftp = ftplib.FTP() + ftp.connect(HOST, timeout=30) + self.assertEqual(ftp.sock.gettimeout(), 30) + self.evt.wait() + ftp.close() + + def testTimeoutDifferentOrder(self): + ftp = ftplib.FTP(timeout=30) + ftp.connect(HOST) + self.assertEqual(ftp.sock.gettimeout(), 30) + self.evt.wait() + ftp.close() + + def testTimeoutDirectAccess(self): + ftp = ftplib.FTP() + ftp.timeout = 30 + ftp.connect(HOST) + self.assertEqual(ftp.sock.gettimeout(), 30) + self.evt.wait() + ftp.close() + + +class MiscTestCase(TestCase): + def test__all__(self): + blacklist = {'MSG_OOB', 'FTP_PORT', 'MAXLINE', 'CRLF', 'B_CRLF', + 'Error', 'parse150', 'parse227', 'parse229', 'parse257', + 'print_line', 'ftpcp', 'test'} + support.check__all__(self, ftplib, blacklist=blacklist) + + +def test_main(): + tests = [TestFTPClass, TestTimeouts, + TestIPv6Environment, + TestTLS_FTPClassMixin, TestTLS_FTPClass, + MiscTestCase] + + thread_info = support.threading_setup() + try: + support.run_unittest(*tests) + finally: + support.threading_cleanup(*thread_info) + + +if __name__ == '__main__': + test_main() diff --git a/vm/Cargo.toml b/vm/Cargo.toml index d5cb9632a..2088b1f03 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -106,7 +106,7 @@ uname = "0.1.1" crc32fast = "1.2.0" adler32 = "1.0.3" gethostname = "0.2.0" -socket2 = "0.3" +socket2 = "0.3.19" rustyline = "6.0" openssl = { version = "0.10", features = ["vendored"], optional = true } openssl-sys = { version = "0.9", optional = true } diff --git a/vm/src/py_io.rs b/vm/src/py_io.rs index 17c007772..3974dac5b 100644 --- a/vm/src/py_io.rs +++ b/vm/src/py_io.rs @@ -30,6 +30,25 @@ impl Write for PyWriter<'_> { } } +pub fn write_all( + mut buf: &[u8], + mut write: impl FnMut(&[u8]) -> io::Result, +) -> io::Result<()> { + while !buf.is_empty() { + match write(buf) { + Ok(0) => { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "failed to write whole buffer", + )) + } + Ok(n) => buf = &buf[n..], + Err(e) => return Err(e), + } + } + Ok(()) +} + pub fn file_readline(obj: &PyObjectRef, size: Option, vm: &VirtualMachine) -> PyResult { let args = size.map_or_else(Vec::new, |size| vec![vm.ctx.new_int(size)]); let ret = vm.call_method(obj, "readline", args)?; diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 545142b25..dcb61b7d7 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -170,6 +170,11 @@ fn make_path<'a>( } impl IntoPyException for io::Error { + fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef { + (&self).into_pyexception(vm) + } +} +impl IntoPyException for &'_ io::Error { fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef { #[allow(unreachable_patterns)] // some errors are just aliases of each other let exc_type = match self.kind() { diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index ca7a6962c..23e876afe 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -4,7 +4,7 @@ use gethostname::gethostname; use nix::unistd::sethostname; use socket2::{Domain, Protocol, Socket, Type as SocketType}; use std::convert::TryFrom; -use std::io::{self, prelude::*}; +use std::io; use std::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, ToSocketAddrs}; use std::time::Duration; @@ -20,7 +20,7 @@ use crate::pyobject::{ BorrowValue, Either, IntoPyObject, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, StaticType, TryFromObject, }; -use crate::vm::VirtualMachine; +use crate::{py_io, VirtualMachine}; #[cfg(unix)] type RawSocket = std::os::unix::io::RawFd; @@ -48,7 +48,8 @@ mod c { pub use winapi::shared::ws2def::*; pub use winapi::um::winsock2::{ SD_BOTH as SHUT_RDWR, SD_RECEIVE as SHUT_RD, SD_SEND as SHUT_WR, SOCK_DGRAM, SOCK_RAW, - SOCK_RDM, SOCK_STREAM, SOL_SOCKET, SO_BROADCAST, SO_REUSEADDR, SO_TYPE, *, + SOCK_RDM, SOCK_STREAM, SOL_SOCKET, SO_BROADCAST, SO_ERROR, SO_OOBINLINE, SO_REUSEADDR, + SO_TYPE, *, }; } @@ -71,7 +72,7 @@ pub type PySocketRef = PyRef; #[pyimpl(flags(BASETYPE))] impl PySocket { - fn sock(&self) -> PyRwLockReadGuard<'_, Socket> { + pub fn sock(&self) -> PyRwLockReadGuard<'_, Socket> { self.sock.read() } @@ -167,52 +168,90 @@ impl PySocket { } #[pymethod] - fn recv(&self, bufsize: usize, vm: &VirtualMachine) -> PyResult> { + fn recv( + &self, + bufsize: usize, + flags: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult> { + let flags = flags.unwrap_or(0); let mut buffer = vec![0u8; bufsize]; let n = self .sock() - .recv(&mut buffer) + .recv_with_flags(&mut buffer, flags) .map_err(|err| convert_sock_error(vm, err))?; buffer.truncate(n); Ok(buffer) } #[pymethod] - fn recv_into(&self, buf: PyRwBytesLike, vm: &VirtualMachine) -> PyResult { - buf.with_ref(|buf| self.sock().recv(buf)) + fn recv_into( + &self, + buf: PyRwBytesLike, + flags: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let flags = flags.unwrap_or(0); + buf.with_ref(|buf| self.sock().recv_with_flags(buf, flags)) .map_err(|err| convert_sock_error(vm, err)) } #[pymethod] - fn recvfrom(&self, bufsize: usize, vm: &VirtualMachine) -> PyResult<(Vec, AddrTuple)> { + fn recvfrom( + &self, + bufsize: usize, + flags: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult<(Vec, AddrTuple)> { + let flags = flags.unwrap_or(0); let mut buffer = vec![0u8; bufsize]; let (n, addr) = self .sock() - .recv_from(&mut buffer) + .recv_from_with_flags(&mut buffer, flags) .map_err(|err| convert_sock_error(vm, err))?; buffer.truncate(n); Ok((buffer, get_addr_tuple(addr))) } #[pymethod] - fn send(&self, bytes: PyBytesLike, vm: &VirtualMachine) -> PyResult { + fn send( + &self, + bytes: PyBytesLike, + flags: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let flags = flags.unwrap_or(0); bytes - .with_ref(|b| self.sock().send(b)) + .with_ref(|b| self.sock().send_with_flags(b, flags)) .map_err(|err| convert_sock_error(vm, err)) } #[pymethod] - fn sendall(&self, bytes: PyBytesLike, vm: &VirtualMachine) -> PyResult<()> { + fn sendall( + &self, + bytes: PyBytesLike, + flags: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult<()> { + let flags = flags.unwrap_or(0); + let sock = self.sock(); bytes - .with_ref(|b| self.sock_mut().write_all(b)) + .with_ref(|buf| py_io::write_all(buf, |b| sock.send_with_flags(b, flags))) .map_err(|err| convert_sock_error(vm, err)) } #[pymethod] - fn sendto(&self, bytes: PyBytesLike, address: Address, vm: &VirtualMachine) -> PyResult<()> { + fn sendto( + &self, + bytes: PyBytesLike, + address: Address, + flags: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult<()> { + let flags = flags.unwrap_or(0); let addr = get_addr(vm, address, Some(self.family.load()))?; bytes - .with_ref(|b| self.sock().send_to(b, &addr)) + .with_ref(|b| self.sock().send_to_with_flags(b, &addr, flags)) .map_err(|err| convert_sock_error(vm, err))?; Ok(()) } @@ -411,15 +450,15 @@ impl PySocket { impl io::Read for PySocketRef { fn read(&mut self, buf: &mut [u8]) -> io::Result { - ::read(&mut self.sock_mut(), buf) + <&Socket as io::Read>::read(&mut &*self.sock(), buf) } } impl io::Write for PySocketRef { fn write(&mut self, buf: &[u8]) -> io::Result { - ::write(&mut self.sock_mut(), buf) + <&Socket as io::Write>::write(&mut &*self.sock(), buf) } fn flush(&mut self) -> io::Result<()> { - ::flush(&mut self.sock_mut()) + <&Socket as io::Write>::flush(&mut &*self.sock()) } } @@ -850,7 +889,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "SO_REUSEADDR" => ctx.new_int(c::SO_REUSEADDR), "SO_TYPE" => ctx.new_int(c::SO_TYPE), "SO_BROADCAST" => ctx.new_int(c::SO_BROADCAST), - // "SO_EXCLUSIVEADDRUSE" => ctx.new_int(c::SO_EXCLUSIVEADDRUSE), + "SO_OOBINLINE" => ctx.new_int(c::SO_OOBINLINE), + "SO_ERROR" => ctx.new_int(c::SO_ERROR), "TCP_NODELAY" => ctx.new_int(c::TCP_NODELAY), "AI_ALL" => ctx.new_int(c::AI_ALL), "AI_PASSIVE" => ctx.new_int(c::AI_PASSIVE), diff --git a/vm/src/stdlib/ssl.rs b/vm/src/stdlib/ssl.rs index 192edf9d3..991517c40 100644 --- a/vm/src/stdlib/ssl.rs +++ b/vm/src/stdlib/ssl.rs @@ -1,18 +1,19 @@ +use super::os::PyPathLike; use super::socket::PySocketRef; -use crate::builtins::bytearray::PyByteArrayRef; use crate::builtins::pystr::PyStrRef; use crate::builtins::{pytype::PyTypeRef, weakref::PyWeak}; -use crate::byteslike::PyBytesLike; -use crate::common::lock::{PyRwLock, PyRwLockWriteGuard}; +use crate::byteslike::{PyBytesLike, PyRwBytesLike}; +use crate::common::lock::{PyRwLock, PyRwLockReadGuard, PyRwLockWriteGuard}; use crate::exceptions::{IntoPyException, PyBaseExceptionRef}; -use crate::function::OptionalArg; +use crate::function::{OptionalArg, OptionalOption}; use crate::pyobject::{ - BorrowValue, Either, IntoPyObject, ItemProtocol, PyClassImpl, PyObjectRef, PyRef, PyResult, - PyValue, StaticType, + BorrowValue, Either, IntoPyObject, ItemProtocol, PyCallable, PyClassImpl, PyObjectRef, PyRef, + PyResult, PyValue, StaticType, }; use crate::types::create_simple_type; use crate::VirtualMachine; +use crossbeam_utils::atomic::AtomicCell; use foreign_types_shared::{ForeignType, ForeignTypeRef}; use openssl::{ asn1::{Asn1Object, Asn1ObjectRef}, @@ -24,6 +25,7 @@ use openssl::{ use std::convert::TryFrom; use std::ffi::{CStr, CString}; use std::fmt; +use std::time::Instant; mod sys { #![allow(non_camel_case_types, unused)] @@ -231,7 +233,7 @@ fn _ssl_rand_pseudo_bytes(n: i32, vm: &VirtualMachine) -> PyResult<(Vec, boo #[pyclass(module = "ssl", name = "_SSLContext")] struct PySslContext { ctx: PyRwLock, - check_hostname: bool, + check_hostname: AtomicCell, } impl fmt::Debug for PySslContext { @@ -246,6 +248,10 @@ impl PyValue for PySslContext { } } +fn builder_as_ctx(x: &SslContextBuilder) -> &ssl::SslContextRef { + unsafe { ssl::SslContextRef::from_ptr(x.as_ptr()) } +} + #[pyimpl(flags(BASETYPE))] impl PySslContext { fn builder(&self) -> PyRwLockWriteGuard<'_, SslContextBuilder> { @@ -256,7 +262,7 @@ impl PySslContext { F: Fn(&ssl::SslContextRef) -> R, { let c = self.ctx.read(); - func(unsafe { &**(&*c as *const SslContextBuilder as *const ssl::SslContext) }) + func(builder_as_ctx(&c)) } fn ptr(&self) -> *mut sys::SSL_CTX { (*self.ctx.write()).as_ptr() @@ -309,7 +315,7 @@ impl PySslContext { PySslContext { ctx: PyRwLock::new(builder), - check_hostname, + check_hostname: AtomicCell::new(check_hostname), } .into_ref_with_type(vm, cls) } @@ -338,12 +344,22 @@ impl PySslContext { unreachable!() } } + #[pyproperty] + fn options(&self) -> libc::c_ulong { + self.ctx.read().options().bits() + } + #[pyproperty(setter)] + fn set_options(&self, opts: libc::c_ulong) { + self.builder() + .set_options(SslOptions::from_bits_truncate(opts)); + } #[pyproperty(setter)] fn set_verify_mode(&self, cert: i32, vm: &VirtualMachine) -> PyResult<()> { + let mut ctx = self.builder(); let cert_req = CertRequirements::try_from(cert) .map_err(|_| vm.new_value_error("invalid value for verify_mode".to_owned()))?; let mode = match cert_req { - CertRequirements::None if self.check_hostname => { + CertRequirements::None if self.check_hostname.load() => { return Err(vm.new_value_error( "Cannot set verify_mode to CERT_NONE when check_hostname is enabled." .to_owned(), @@ -353,9 +369,21 @@ impl PySslContext { CertRequirements::Optional => SslVerifyMode::PEER, CertRequirements::Required => SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT, }; - self.builder().set_verify(mode); + ctx.set_verify(mode); Ok(()) } + #[pyproperty] + fn check_hostname(&self) -> bool { + self.check_hostname.load() + } + #[pyproperty(setter)] + fn set_check_hostname(&self, ch: bool) { + let mut ctx = self.builder(); + if ch && builder_as_ctx(&ctx).verify_mode() == SslVerifyMode::NONE { + ctx.set_verify(SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT); + } + self.check_hostname.store(ch); + } #[pymethod] fn set_default_verify_paths(&self, vm: &VirtualMachine) -> PyResult<()> { @@ -442,6 +470,30 @@ impl PySslContext { Ok(vm.ctx.new_list(certs)) } + #[pymethod] + fn load_cert_chain( + &self, + certfile: PyPathLike, + keyfile: OptionalArg, + password: OptionalOption>, + vm: &VirtualMachine, + ) -> PyResult<()> { + // TODO: requires passing a callback to C + if password.flatten().is_some() { + return Err(vm.new_not_implemented_error("password arg not yet supported".to_owned())); + } + let mut ctx = self.builder(); + ctx.set_certificate_chain_file(&certfile) + .and_then(|()| { + ctx.set_private_key_file( + keyfile.as_ref().unwrap_or(&certfile), + ssl::SslFiletype::PEM, + ) + }) + .and_then(|()| ctx.check_private_key()) + .map_err(|e| convert_openssl_error(vm, e)) + } + #[pymethod] fn _wrap_socket( zelf: PyRef, @@ -471,7 +523,7 @@ impl PySslContext { Ok(PySslSocket { ctx: zelf, - stream: PyRwLock::new(Some(stream)), + stream: PyRwLock::new(stream), socket_type, server_hostname: args.server_hostname, owner: PyRwLock::new(args.owner.as_ref().map(PyWeak::downgrade)), @@ -507,7 +559,7 @@ struct LoadVerifyLocationsArgs { #[pyclass(module = "ssl", name = "_SSLSocket")] struct PySslSocket { ctx: PyRef, - stream: PyRwLock>>, + stream: PyRwLock>, socket_type: SslServerOrClient, server_hostname: Option, owner: PyRwLock>, @@ -527,20 +579,19 @@ impl PyValue for PySslSocket { #[pyimpl] impl PySslSocket { - fn stream_builder(&self) -> ssl::SslStreamBuilder { - std::mem::replace(&mut *self.stream.write(), None).unwrap() - } - fn exec_stream(&self, func: F) -> R - where - F: Fn(&mut ssl::SslStream) -> R, - { - let mut b = self.stream.write(); - func(unsafe { - &mut *(b.as_mut().unwrap() as *mut ssl::SslStreamBuilder<_> as *mut ssl::SslStream<_>) + fn stream(&self) -> impl std::ops::Deref> + '_ { + let s = self.stream.read(); + // SAFETY: SslStreamBuilder is just a wrapper around SslStream + PyRwLockReadGuard::map(s, |s| unsafe { + &*(s as *const _ as *const ssl::SslStream<_>) }) } - fn set_stream(&self, stream: ssl::SslStream) { - *self.stream.write() = Some(unsafe { std::mem::transmute(stream) }); + fn stream_mut(&self) -> impl std::ops::DerefMut> + '_ { + let s = self.stream.write(); + // SAFETY: SslStreamBuilder is just a wrapper around SslStream + PyRwLockWriteGuard::map(s, |s| unsafe { + &mut *(s as *mut _ as *mut ssl::SslStream<_>) + }) } #[pyproperty] @@ -571,35 +622,58 @@ impl PySslSocket { vm: &VirtualMachine, ) -> PyResult> { let binary = binary.unwrap_or(false); - if !self.exec_stream(|stream| stream.ssl().is_init_finished()) { + let stream = self.stream(); + if !stream.ssl().is_init_finished() { return Err(vm.new_value_error("handshake not done yet".to_owned())); } - self.exec_stream(|stream| stream.ssl().peer_certificate()) + stream + .ssl() + .peer_certificate() .map(|cert| cert_to_py(vm, &cert, binary)) .transpose() } #[pymethod] fn do_handshake(&self, vm: &VirtualMachine) -> PyResult<()> { - // Either a stream builder or a mid-handshake stream from WANT_READ or WANT_WRITE - let mut handshaker: Either<_, ssl::MidHandshakeSslStream<_>> = - Either::A(self.stream_builder()); + let stream_builder = self.stream.write(); + let timeout = stream_builder + .get_ref() + .sock() + .read_timeout() + .ok() + .flatten() + .map(|dur| (dur, Instant::now())); + let mut stream = unsafe { std::ptr::read(&*stream_builder) }; loop { - let handshake_result = match handshaker { - Either::A(s) => s.handshake(), - Either::B(s) => s.handshake(), - }; - match handshake_result { - Ok(stream) => { - self.set_stream(stream); + match stream.handshake() { + Ok(s) => { + // s and stream_builder are the same thing + std::mem::forget(s); return Ok(()); } - Err(ssl::HandshakeError::SetupFailure(e)) => { - return Err(convert_openssl_error(vm, e)) + Err(ssl::HandshakeError::SetupFailure(_e)) => { + // handshake() error handling code never constructs this + unreachable!(); + // return Err(convert_openssl_error(vm, e)) + } + Err(ssl::HandshakeError::WouldBlock(s)) => { + std::mem::forget(s); + stream = unsafe { std::ptr::read(&*stream_builder) }; } - Err(ssl::HandshakeError::WouldBlock(s)) => handshaker = Either::B(s), Err(ssl::HandshakeError::Failure(s)) => { - return Err(convert_ssl_error(vm, s.into_error())) + let err = convert_ssl_error(vm, s.error()); + std::mem::forget(s); + return Err(err); + } + } + if let Some((timeout, ref start)) = timeout { + if start.elapsed() >= timeout { + std::mem::forget(stream); + let socket_timeout = vm.class("_socket", "timeout"); + return Err(vm.new_exception_msg( + socket_timeout, + "The handshake operation timed out".to_owned(), + )); } } } @@ -607,25 +681,39 @@ impl PySslSocket { #[pymethod] fn write(&self, data: PyBytesLike, vm: &VirtualMachine) -> PyResult { - data.with_ref(|b| self.exec_stream(|stream| stream.ssl_write(b))) + let mut stream = self.stream_mut(); + data.with_ref(|b| stream.ssl_write(b)) .map_err(|e| convert_ssl_error(vm, e)) } #[pymethod] - fn read(&self, n: usize, buffer: OptionalArg, vm: &VirtualMachine) -> PyResult { - if let OptionalArg::Present(buffer) = buffer { - let n = self - .exec_stream(|stream| { - let mut buf = buffer.borrow_value_mut(); - stream.ssl_read(&mut buf.elements) - }) - .map_err(|e| convert_ssl_error(vm, e))?; - Ok(vm.ctx.new_int(n)) + fn read(&self, n: usize, buffer: OptionalArg, vm: &VirtualMachine) -> PyResult { + let mut stream = self.stream_mut(); + let ret_nread = buffer.is_present(); + let ssl_res = if let OptionalArg::Present(buffer) = buffer { + buffer.with_ref(|buf| stream.ssl_read(buf).map(|n| vm.ctx.new_int(n))) } else { let mut buf = vec![0u8; n]; - buf.truncate(n); - Ok(vm.ctx.new_bytes(buf)) - } + stream.ssl_read(&mut buf).map(|n| { + buf.truncate(n); + vm.ctx.new_bytes(buf) + }) + }; + ssl_res.or_else(|e| { + if e.code() == ssl::ErrorCode::ZERO_RETURN + && stream.get_shutdown() == ssl::ShutdownState::RECEIVED + { + Ok(if ret_nread { + vm.ctx.new_int(0) + } else { + vm.ctx.new_bytes(vec![]) + }) + } else { + Err(convert_ssl_error(vm, e)) + } + }) + + // .map_err(|e| convert_ssl_error(vm, e))?; } } @@ -645,15 +733,28 @@ fn convert_openssl_error(vm: &VirtualMachine, err: ErrorStack) -> PyBaseExceptio // ); // TODO: map the error codes to code names, e.g. "CERTIFICATE_VERIFY_FAILED", just requires a big hashmap/dict let msg = e.to_string(); - vm.new_exception_msg(cls, msg) + vm.new_exception(cls, vec![vm.ctx.new_int(e.code()), vm.ctx.new_str(msg)]) } None => vm.new_exception_empty(cls), } } -fn convert_ssl_error(vm: &VirtualMachine, e: ssl::Error) -> PyBaseExceptionRef { - match e.into_io_error() { - Ok(io_err) => io_err.into_pyexception(vm), - Err(e) => convert_openssl_error(vm, e.ssl_error().unwrap().clone()), +fn convert_ssl_error( + vm: &VirtualMachine, + e: impl std::borrow::Borrow, +) -> PyBaseExceptionRef { + let e = e.borrow(); + match e.io_error() { + Some(io_err) => io_err.into_pyexception(vm), + None => match e.ssl_error() { + Some(e) => convert_openssl_error(vm, e.clone()), + None => vm.new_exception( + ssl_error(vm), + vec![ + vm.ctx.new_int(e.code().as_raw()), + vm.ctx.new_str(e.to_string()), + ], + ), + }, } } @@ -805,7 +906,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "SSL_ERROR_SYSCALL" => ctx.new_int(sys::SSL_ERROR_SYSCALL), "SSL_ERROR_SSL" => ctx.new_int(sys::SSL_ERROR_SSL), "SSL_ERROR_WANT_CONNECT" => ctx.new_int(sys::SSL_ERROR_WANT_CONNECT), - // "SSL_ERROR_EOF" => ctx.new_int(sys::SSL_ERROR_EOF), + "SSL_ERROR_EOF" => ctx.new_int(8), // custom for python // "SSL_ERROR_INVALID_ERROR_CODE" => ctx.new_int(sys::SSL_ERROR_INVALID_ERROR_CODE), // TODO: so many more of these "ALERT_DESCRIPTION_DECODE_ERROR" => ctx.new_int(sys::SSL_AD_DECODE_ERROR),