This commit is contained in:
Mai Gillmann
2019-12-17 14:09:10 +01:00
parent 66e908fc8a
commit 4791d00a43
2122 changed files with 423791 additions and 0 deletions
@@ -0,0 +1,571 @@
# -*- test-case-name: twisted.conch.test.test_keys -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
# pylint: disable=I0011,C0103,W9401,W9402
"""
Data used by test_keys as well as others.
"""
from __future__ import absolute_import, division
from twisted.python.compat import long, _b64decodebytes as decodebytes
RSAData = {
'n': long('269413617238113438198661010376758399219880277968382122687862697'
'296942471209955603071120391975773283844560230371884389952067978'
'789684135947515341209478065209455427327369102356204259106807047'
'964139525310539133073743116175821417513079706301100600025815509'
'786721808719302671068052414466483676821987505720384645561708425'
'794379383191274856941628512616355437197560712892001107828247792'
'561858327085521991407807015047750218508971611590850575870321007'
'991909043252470730134547038841839367764074379439843108550888709'
'430958143271417044750314742880542002948053835745429446485015316'
'60749404403945254975473896534482849256068133525751'),
'e': long(65537),
'd': long('420335724286999695680502438485489819800002417295071059780489811'
'840828351636754206234982682752076205397047218449504537476523960'
'987613148307573487322720481066677105211155388802079519869249746'
'774085882219244493290663802569201213676433159425782937159766786'
'329742053214957933941260042101377175565683849732354700525628975'
'239000548651346620826136200952740446562751690924335365940810658'
'931238410612521441739702170503547025018016868116037053013935451'
'477930426013703886193016416453215950072147440344656137718959053'
'897268663969428680144841987624962928576808352739627262941675617'
'7724661940425316604626522633351193810751757014073'),
'p': long('152689878451107675391723141129365667732639179427453246378763774'
'448531436802867910180261906924087589684175595016060014593521649'
'964959248408388984465569934780790357826811592229318702991401054'
'226302790395714901636384511513449977061729214247279176398290513'
'085108930550446985490864812445551198848562639933888780317'),
'q': long('176444974592327996338888725079951900172097062203378367409936859'
'072670162290963119826394224277287608693818012745872307600855894'
'647300295516866118620024751601329775653542084052616260193174546'
'400544176890518564317596334518015173606460860373958663673307503'
'231977779632583864454001476729233959405710696795574874403'),
'u': long('936018002388095842969518498561007090965136403384715613439364803'
'229386793506402222847415019772053080458257034241832795210460612'
'924445085372678524176842007912276654532773301546269997020970818'
'155956828553418266110329867222673040098885651348225673298948529'
'93885224775891490070400861134282266967852120152546563278')
}
DSAData = {
'g': long("10253261326864117157640690761723586967382334319435778695"
"29171533815411392477819921538350732400350395446211982054"
"96512489289702949127531056893725702005035043292195216541"
"11525058911428414042792836395195432445511200566318251789"
"10575695836669396181746841141924498545494149998282951407"
"18645344764026044855941864175"),
'p': long("10292031726231756443208850082191198787792966516790381991"
"77502076899763751166291092085666022362525614129374702633"
"26262930887668422949051881895212412718444016917144560705"
"45675251775747156453237145919794089496168502517202869160"
"78674893099371444940800865897607102159386345313384716752"
"18590012064772045092956919481"),
'q': long(1393384845225358996250882900535419012502712821577),
'x': long(1220877188542930584999385210465204342686893855021),
'y': long("14604423062661947579790240720337570315008549983452208015"
"39426429789435409684914513123700756086453120500041882809"
"10283610277194188071619191739512379408443695946763554493"
"86398594314468629823767964702559709430618263927529765769"
"10270265745700231533660131769648708944711006508965764877"
"684264272082256183140297951")
}
ECDatanistp256 = {
'x': long('762825130203920963171185031449647317742997734817505505433829043'
'45687059013883'),
'y': long('815431978646028526322656647694416475343443758943143196810611371'
'59310646683104'),
'privateValue': long('3463874347721034170096400845565569825355565567882605'
'9678074967909361042656500'),
'curve': b'ecdsa-sha2-nistp256'
}
ECDatanistp384 = {
'privateValue': long('280814107134858470598753916394807521398239633534281633982576099083'
'35787109896602102090002196616273211495718603965098'),
'x': long('10036914308591746758780165503819213553101287571902957054148542'
'504671046744460374996612408381962208627004841444205030'),
'y': long('17337335659928075994560513699823544906448896792102247714689323'
'575406618073069185107088229463828921069465902299522926'),
'curve': b'ecdsa-sha2-nistp384'
}
ECDatanistp521 = {
'x': long('12944742826257420846659527752683763193401384271391513286022917'
'29910013082920512632908350502247952686156279140016049549948975'
'670668730618745449113644014505462'),
'y': long('10784108810271976186737587749436295782985563640368689081052886'
'16296815984553198866894145509329328086635278430266482551941240'
'591605833440825557820439734509311'),
'privateValue': long('662751235215460886290293902658128847495347691199214706697089140769'
'672273950767961331442265530524063943548846724348048614239791498442'
'5997823106818915698960565'),
'curve': b'ecdsa-sha2-nistp521'
}
privateECDSA_openssh521 = b"""-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIAjn0lSVF6QweS4bjOGP9RHwqxUiTastSE0MVuLtFvkxygZqQ712oZ
ewMvqKkxthMQgxzSpGtRBcmkL7RqZ94+18qgBwYFK4EEACOhgYkDgYYABAFpX/6B
mxxglwD+VpEvw0hcyxVzLxNnMGzxZGF7xmNj8nlF7M+TQctdlR2Xv/J+AgIeVGmB
j2p84bkV9jBzrUNJEACsJjttZw8NbUrhxjkLT/3rMNtuwjE4vLja0P7DMTE0EV8X
f09ETdku/z/1tOSSrSvRwmUcM9nQUJtHHAZlr5Q0fw==
-----END EC PRIVATE KEY-----"""
# New format introduced in OpenSSH 6.5
privateECDSA_openssh521_new = b"""-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS
1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBaV/+gZscYJcA/laRL8NIXMsVcy8T
ZzBs8WRhe8ZjY/J5RezPk0HLXZUdl7/yfgICHlRpgY9qfOG5FfYwc61DSRAArCY7bWcPDW
1K4cY5C0/96zDbbsIxOLy42tD+wzExNBFfF39PRE3ZLv8/9bTkkq0r0cJlHDPZ0FCbRxwG
Za+UNH8AAAEAeRISlnkSEpYAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ
AAAIUEAWlf/oGbHGCXAP5WkS/DSFzLFXMvE2cwbPFkYXvGY2PyeUXsz5NBy12VHZe/8n4C
Ah5UaYGPanzhuRX2MHOtQ0kQAKwmO21nDw1tSuHGOQtP/esw227CMTi8uNrQ/sMxMTQRXx
d/T0RN2S7/P/W05JKtK9HCZRwz2dBQm0ccBmWvlDR/AAAAQgCOfSVJUXpDB5LhuM4Y/1Ef
CrFSJNqy1ITQxW4u0W+THKBmpDvXahl7Ay+oqTG2ExCDHNKka1EFyaQvtGpn3j7XygAAAA
ABAg==
-----END OPENSSH PRIVATE KEY-----"""
publicECDSA_openssh521 = (
b"ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACF"
b"BAFpX/6BmxxglwD+VpEvw0hcyxVzLxNnMGzxZGF7xmNj8nlF7M+TQctdlR2Xv/J+AgIeVGmB"
b"j2p84bkV9jBzrUNJEACsJjttZw8NbUrhxjkLT/3rMNtuwjE4vLja0P7DMTE0EV8Xf09ETdku"
b"/z/1tOSSrSvRwmUcM9nQUJtHHAZlr5Q0fw== comment"
)
privateECDSA_openssh384 = b"""-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDAtAi7I8j73WCX20qUM5hhHwHuFzYWYYILs2Sh8UZ+awNkARZ/Fu2LU
LLl5RtOQpbWgBwYFK4EEACKhZANiAATU17sA9P5FRwSknKcFsjjsk0+E3CeXPYX0
Tk/M0HK3PpWQWgrO8JdRHP9eFE9O/23P8BumwFt7F/AvPlCzVd35VfraFT0o4cCW
G0RqpQ+np31aKmeJshkcYALEchnU+tQ=
-----END EC PRIVATE KEY-----"""
# New format introduced in OpenSSH 6.5
privateECDSA_openssh384_new = b"""-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS
1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQTU17sA9P5FRwSknKcFsjjsk0+E3CeX
PYX0Tk/M0HK3PpWQWgrO8JdRHP9eFE9O/23P8BumwFt7F/AvPlCzVd35VfraFT0o4cCWG0
RqpQ+np31aKmeJshkcYALEchnU+tQAAADIiktpWIpLaVgAAAATZWNkc2Etc2hhMi1uaXN0
cDM4NAAAAAhuaXN0cDM4NAAAAGEE1Ne7APT+RUcEpJynBbI47JNPhNwnlz2F9E5PzNBytz
6VkFoKzvCXURz/XhRPTv9tz/AbpsBbexfwLz5Qs1Xd+VX62hU9KOHAlhtEaqUPp6d9Wipn
ibIZHGACxHIZ1PrUAAAAMC0CLsjyPvdYJfbSpQzmGEfAe4XNhZhgguzZKHxRn5rA2QBFn8
W7YtQsuXlG05CltQAAAAA=
-----END OPENSSH PRIVATE KEY-----"""
publicECDSA_openssh384 = (
b"ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABh"
b"BNTXuwD0/kVHBKScpwWyOOyTT4TcJ5c9hfROT8zQcrc+lZBaCs7wl1Ec/14UT07/bc/wG6bA"
b"W3sX8C8+ULNV3flV+toVPSjhwJYbRGqlD6enfVoqZ4myGRxgAsRyGdT61A== comment"
)
publicECDSA_openssh = (
b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABB"
b"BKimX1DZ7+Qj0SpfePMbo1pb6yGkAb5l7duC1l855yD7tEfQfqk7bc7v46We1hLMyz6ObUBY"
b"gkN/34n42F4vpeA= comment"
)
privateECDSA_openssh = b"""-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIEyU1YOT2JxxofwbJXIjGftdNcJK55aQdNrhIt2xYQz0oAoGCCqGSM49
AwEHoUQDQgAEqKZfUNnv5CPRKl948xujWlvrIaQBvmXt24LWXznnIPu0R9B+qTtt
zu/jpZ7WEszLPo5tQFiCQ3/fifjYXi+l4A==
-----END EC PRIVATE KEY-----"""
# New format introduced in OpenSSH 6.5
privateECDSA_openssh_new = b"""-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSopl9Q2e/kI9EqX3jzG6NaW+shpAG+
Ze3bgtZfOecg+7RH0H6pO23O7+OlntYSzMs+jm1AWIJDf9+J+NheL6XgAAAAmCKU4hcilO
IXAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKimX1DZ7+Qj0Spf
ePMbo1pb6yGkAb5l7duC1l855yD7tEfQfqk7bc7v46We1hLMyz6ObUBYgkN/34n42F4vpe
AAAAAgTJTVg5PYnHGh/BslciMZ+101wkrnlpB02uEi3bFhDPQAAAAA
-----END OPENSSH PRIVATE KEY-----"""
publicRSA_openssh = (
b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDVaqx4I9bWG+wloVDEd2NQhEUBVUIUKirg"
b"0GDu1OmjrUr6OQZehFV1XwA2v2+qKj+DJjfBaS5b/fDz0n3WmM06QHjVyqgYwBGTJAkMgUyP"
b"95ztExZqpATpSXfD5FVks3loniwI66zoBC0hdwWnju9TMA2l5bs9auIJNm/9NNN9b0b/h9qp"
b"KSeq/631heY+Grh6HUqx6sBa9zDfH8Kk5O8/kUmWQNUZdy03w17snaY6RKXCpCnd1bqcPUWz"
b"xiwYZNW6Pd+rf81CrKfxGAugWBViC6QqbkPD5ASfNaNHjkbtM6Vlvbw7KW4CC1ffdOgTtDc1"
b"foNfICZgptyti8ZseZj3 comment"
)
privateRSA_openssh = b'''-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA1WqseCPW1hvsJaFQxHdjUIRFAVVCFCoq4NBg7tTpo61K+jkG
XoRVdV8ANr9vqio/gyY3wWkuW/3w89J91pjNOkB41cqoGMARkyQJDIFMj/ec7RMW
aqQE6Ul3w+RVZLN5aJ4sCOus6AQtIXcFp47vUzANpeW7PWriCTZv/TTTfW9G/4fa
qSknqv+t9YXmPhq4eh1KserAWvcw3x/CpOTvP5FJlkDVGXctN8Ne7J2mOkSlwqQp
3dW6nD1Fs8YsGGTVuj3fq3/NQqyn8RgLoFgVYgukKm5Dw+QEnzWjR45G7TOlZb28
OyluAgtX33ToE7Q3NX6DXyAmYKbcrYvGbHmY9wIDAQABAoIBACFMCGaiKNW0+44P
chuFCQC58k438BxXS+NRf54jp+Q6mFUb6ot6mB682Lqx+YkSGGCs6MwLTglaQGq6
L5n4syRghLnOaZWa+eL8H1FNJxXbKyet77RprL59EOuGR3BztACHlRU7N/nnFOeA
u2geG+bdu3NjuWfmsid/z88wm8KY/dkYNi82LvE9gXqf4QMtR9s0UWI53U/prKiL
2dbzhMQXuXGdBghCeE27xSr0w1jNVSvtvjNfBOp75gQkY/It1z0bbNWcY0MvkoiN
Pm7aGDfYDyVniR25RjReyc7Ei+2SWjMHD9+GCPmS6dvrOAg2yc3NCgFIWzk+esrG
gKnc1DkCgYEA2XAG2OK81HiRUJTUwRuJOGxGZFpRoJoHPUiPA1HMaxKOfRqxZedx
dTngMgV1jRhMr5OxSbFmX3hietEMyuZNQ7Oc9Gt95gyY3M8hYo7VLhLeBK7XJG6D
MaIVokQ9IqliJiK5su1UCp0Ig6cHDf8ZGI7Yqx3aSJwxaBGhZm3j2B0CgYEA+0QX
i6Q2vh43Haf2YWwExKrdeD4HjB4zAq4DFIeDeuWefQhnqPKqvxJwz3Kpp8cLHYjV
IP2cY8pHMFVOi8TP9H8WpJISdKEJwsRunIwz76Xl9+ArrU9cEaoahDdb/Xrqw818
sMjkH1Rjtcev3/QJp/zHJfxc6ZHXksWYHlbTsSMCgYBRr+mSn5QLSoRlPpSzO5IQ
tXS4jMnvyQ4BMvovaBKhAyauz1FoFEwmmyikAjMIX+GncJgBNHleUo7Ezza8H0tV
rOvBU4TH4WGoStSi/0ANgB8SqVDAKhh1lAwGmxZQqEvsQc177/dLyXUCaMSYuIaI
GFpD5wIzlyJkk4MMRSp87QKBgGlmN8ZA3SHFBPOwuD5HlHx2/C3rPzk8lcNDAVHE
Qpfz6Bakxu7s1EkQUDgE7jvN19DMzDJpkAegG1qf/jHNHjp+cR4ZlBpOTwzfX1LV
0Rdu7NectlWd244hX7wkiLb8r6vw76QssNyfhrADEriL4t0PwO4jPUpQ/i+4KUZY
v7YnAoGAZhb5IDTQVCW8YTGsgvvvnDUefkpVAmiVDQqTvh6/4UD6kKdUcDHpePzg
Zrcid5rr3dXSMEbK4tdeQZvPtUg1Uaol3N7bNClIIdvWdPx+5S9T95wJcLnkoHam
rXp0IjScTxfLP+Cq5V6lJ94/pX8Ppoj1FdZfNxeS4NYFSRA7kvY=
-----END RSA PRIVATE KEY-----'''
# Some versions of OpenSSH generate these (slightly different keys): the PKCS#1
# structure is wrapped in an extra ASN.1 SEQUENCE and there's an empty SEQUENCE
# following it. It is not any standard key format and was probably a bug in
# OpenSSH at some point.
privateRSA_openssh_alternate = b"""-----BEGIN RSA PRIVATE KEY-----
MIIEqTCCBKMCAQACggEBANVqrHgj1tYb7CWhUMR3Y1CERQFVQhQqKuDQYO7U6aOtSvo5Bl6EVXVf
ADa/b6oqP4MmN8FpLlv98PPSfdaYzTpAeNXKqBjAEZMkCQyBTI/3nO0TFmqkBOlJd8PkVWSzeWie
LAjrrOgELSF3BaeO71MwDaXluz1q4gk2b/00031vRv+H2qkpJ6r/rfWF5j4auHodSrHqwFr3MN8f
wqTk7z+RSZZA1Rl3LTfDXuydpjpEpcKkKd3Vupw9RbPGLBhk1bo936t/zUKsp/EYC6BYFWILpCpu
Q8PkBJ81o0eORu0zpWW9vDspbgILV9906BO0NzV+g18gJmCm3K2Lxmx5mPcCAwEAAQKCAQAhTAhm
oijVtPuOD3IbhQkAufJON/AcV0vjUX+eI6fkOphVG+qLepgevNi6sfmJEhhgrOjMC04JWkBqui+Z
+LMkYIS5zmmVmvni/B9RTScV2ysnre+0aay+fRDrhkdwc7QAh5UVOzf55xTngLtoHhvm3btzY7ln
5rInf8/PMJvCmP3ZGDYvNi7xPYF6n+EDLUfbNFFiOd1P6ayoi9nW84TEF7lxnQYIQnhNu8Uq9MNY
zVUr7b4zXwTqe+YEJGPyLdc9G2zVnGNDL5KIjT5u2hg32A8lZ4kduUY0XsnOxIvtklozBw/fhgj5
kunb6zgINsnNzQoBSFs5PnrKxoCp3NQ5AoGBANlwBtjivNR4kVCU1MEbiThsRmRaUaCaBz1IjwNR
zGsSjn0asWXncXU54DIFdY0YTK+TsUmxZl94YnrRDMrmTUOznPRrfeYMmNzPIWKO1S4S3gSu1yRu
gzGiFaJEPSKpYiYiubLtVAqdCIOnBw3/GRiO2Ksd2kicMWgRoWZt49gdAoGBAPtEF4ukNr4eNx2n
9mFsBMSq3Xg+B4weMwKuAxSHg3rlnn0IZ6jyqr8ScM9yqafHCx2I1SD9nGPKRzBVTovEz/R/FqSS
EnShCcLEbpyMM++l5ffgK61PXBGqGoQ3W/166sPNfLDI5B9UY7XHr9/0Caf8xyX8XOmR15LFmB5W
07EjAoGAUa/pkp+UC0qEZT6UszuSELV0uIzJ78kOATL6L2gSoQMmrs9RaBRMJpsopAIzCF/hp3CY
ATR5XlKOxM82vB9LVazrwVOEx+FhqErUov9ADYAfEqlQwCoYdZQMBpsWUKhL7EHNe+/3S8l1AmjE
mLiGiBhaQ+cCM5ciZJODDEUqfO0CgYBpZjfGQN0hxQTzsLg+R5R8dvwt6z85PJXDQwFRxEKX8+gW
pMbu7NRJEFA4BO47zdfQzMwyaZAHoBtan/4xzR46fnEeGZQaTk8M319S1dEXbuzXnLZVnduOIV+8
JIi2/K+r8O+kLLDcn4awAxK4i+LdD8DuIz1KUP4vuClGWL+2JwKBgQCFSxt6mxIQN54frV7a/saW
/t81a7k04haXkiYJvb1wIAOnNb0tG6DSB0cr1N6oqAcHG7gEIKcnQTxsOTnpQc7nFx3RTFy8PdIm
Jv5q1v1Icq5G+nvD0xlgRB2lE6eA9WMp1HpdBgcWXfaLPctkOuKEWk2MBi0tnRzrg0x4PXlUzjAA
-----END RSA PRIVATE KEY-----"""
# New format introduced in OpenSSH 6.5
privateRSA_openssh_new = b'''-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEA1WqseCPW1hvsJaFQxHdjUIRFAVVCFCoq4NBg7tTpo61K+jkGXoRV
dV8ANr9vqio/gyY3wWkuW/3w89J91pjNOkB41cqoGMARkyQJDIFMj/ec7RMWaqQE6Ul3w+
RVZLN5aJ4sCOus6AQtIXcFp47vUzANpeW7PWriCTZv/TTTfW9G/4faqSknqv+t9YXmPhq4
eh1KserAWvcw3x/CpOTvP5FJlkDVGXctN8Ne7J2mOkSlwqQp3dW6nD1Fs8YsGGTVuj3fq3
/NQqyn8RgLoFgVYgukKm5Dw+QEnzWjR45G7TOlZb28OyluAgtX33ToE7Q3NX6DXyAmYKbc
rYvGbHmY9wAAA7gXkBoMF5AaDAAAAAdzc2gtcnNhAAABAQDVaqx4I9bWG+wloVDEd2NQhE
UBVUIUKirg0GDu1OmjrUr6OQZehFV1XwA2v2+qKj+DJjfBaS5b/fDz0n3WmM06QHjVyqgY
wBGTJAkMgUyP95ztExZqpATpSXfD5FVks3loniwI66zoBC0hdwWnju9TMA2l5bs9auIJNm
/9NNN9b0b/h9qpKSeq/631heY+Grh6HUqx6sBa9zDfH8Kk5O8/kUmWQNUZdy03w17snaY6
RKXCpCnd1bqcPUWzxiwYZNW6Pd+rf81CrKfxGAugWBViC6QqbkPD5ASfNaNHjkbtM6Vlvb
w7KW4CC1ffdOgTtDc1foNfICZgptyti8ZseZj3AAAAAwEAAQAAAQAhTAhmoijVtPuOD3Ib
hQkAufJON/AcV0vjUX+eI6fkOphVG+qLepgevNi6sfmJEhhgrOjMC04JWkBqui+Z+LMkYI
S5zmmVmvni/B9RTScV2ysnre+0aay+fRDrhkdwc7QAh5UVOzf55xTngLtoHhvm3btzY7ln
5rInf8/PMJvCmP3ZGDYvNi7xPYF6n+EDLUfbNFFiOd1P6ayoi9nW84TEF7lxnQYIQnhNu8
Uq9MNYzVUr7b4zXwTqe+YEJGPyLdc9G2zVnGNDL5KIjT5u2hg32A8lZ4kduUY0XsnOxIvt
klozBw/fhgj5kunb6zgINsnNzQoBSFs5PnrKxoCp3NQ5AAAAgQCFSxt6mxIQN54frV7a/s
aW/t81a7k04haXkiYJvb1wIAOnNb0tG6DSB0cr1N6oqAcHG7gEIKcnQTxsOTnpQc7nFx3R
TFy8PdImJv5q1v1Icq5G+nvD0xlgRB2lE6eA9WMp1HpdBgcWXfaLPctkOuKEWk2MBi0tnR
zrg0x4PXlUzgAAAIEA2XAG2OK81HiRUJTUwRuJOGxGZFpRoJoHPUiPA1HMaxKOfRqxZedx
dTngMgV1jRhMr5OxSbFmX3hietEMyuZNQ7Oc9Gt95gyY3M8hYo7VLhLeBK7XJG6DMaIVok
Q9IqliJiK5su1UCp0Ig6cHDf8ZGI7Yqx3aSJwxaBGhZm3j2B0AAACBAPtEF4ukNr4eNx2n
9mFsBMSq3Xg+B4weMwKuAxSHg3rlnn0IZ6jyqr8ScM9yqafHCx2I1SD9nGPKRzBVTovEz/
R/FqSSEnShCcLEbpyMM++l5ffgK61PXBGqGoQ3W/166sPNfLDI5B9UY7XHr9/0Caf8xyX8
XOmR15LFmB5W07EjAAAAAAEC
-----END OPENSSH PRIVATE KEY-----'''
# Encrypted with the passphrase 'encrypted'
privateRSA_openssh_encrypted = b"""-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,FFFFFFFFFFFFFFFF
p2A1YsHLXkpMVcsEqhh/nCYb5AqL0uMzfEIqc8hpZ/Ub8PtLsypilMkqzYTnZIGS
ouyPjU/WgtR4VaDnutPWdgYaKdixSEmGhKghCtXFySZqCTJ4O8NCczsktYjUK3D4
Jtl90zL6O81WBY6xP76PBQo9lrI/heAetATeyqutc18bwQIGU+gKk32qvfo15DfS
VYiY0Ds4D7F7fd9pz+f5+UbFUCgU+tfDvBrqodYrUgmH7jKoW/CRDCHHyeEIZDbF
mcMwdcKOyw1sRLaPdihRSVx3kOMvIotHKVTkIDMp+0RTNeXzQnp5U2qzsxzTcG/M
UyJN38XXkuvq5VMj2zmmjHzx34w3NK3ZxpZcoaFUqUBlNp2C8hkCLrAa/DWobKqN
5xA1ElrQvli9XXkT/RIuy4Gc10bbGEoJjuxNRibtSxxWd5Bd1E40ocOd4l1ebI8+
w69XvMTnsmHvkBEADGF2zfRszKnMelg+W5NER1UDuNT03i+1cuhp+2AZg8z7niTO
M17XP3ScGVxrQAEYgtxPrPeIpFJvOx2j5Yt78U9Y2WlaAG6DrubbYv2RsMIibhOG
yk139vMdD8FwCey6yMkkhFAJwnBtC22MAWgjmC5c6AF3SRQSjjQXepPsJcLgpOjy
YwjhnL8w56x9kVDUNPw9A9Cqgxo2sty34ATnKrh4h59PsP83LOL6OC5WjbASgZRd
OIBD8RloQPISo+RUF7X0i4kdaHVNPlR0KyapR+3M5BwhQuvEO99IArDV2LNKGzfc
W4ssugm8iyAJlmwmb2yRXIDHXabInWY7XCdGk8J2qPFbDTvnPbiagJBimjVjgpWw
tV3sVlJYqmOqmCDP78J6he04l0vaHtiOWTDEmNCrK7oFMXIIp3XWjOZGPSOJFdPs
6Go3YB+EGWfOQxqkFM28gcqmYfVPF2sa1FbZLz0ffO11Ma/rliZxZu7WdrAXe/tc
BgIQ8etp2PwAK4jCwwVwjIO8FzqQGpS23Y9NY3rfi97ckgYXKESFtXPsMMA+drZd
ThbXvccfh4EPmaqQXKf4WghHiVJ+/yuY1kUIDEl/O0jRZWT7STgBim/Aha1m6qRs
zl1H7hkDbU4solb1GM5oPzbgGTzyBc+z0XxM9iFRM+fMzPB8+yYHTr4kPbVmKBjy
SCovjQQVsHE4YeUGTq6k/NF5cVIRKTW/RlHvzxsky1Zj31MC736jrxGw4KG7VSLZ
fP6F5jj+mXwS7m0v5to42JBZmRJdKUD88QaGE3ncyQ4yleW5bn9Lf9SuzQg1Dhao
3rSA1RuexsHlIAHvGxx/17X+pyygl8DJbt6TBfbLQk9wc707DJTfh5M/bnk9wwIX
l/Hsa1WtylAMW/2MzgiVy83MbYz4+Ss6GQ5W66okWji+NxrnrYEy6q+WgVQanp7X
D+D7oKykqE1Cdvvulvtfl5fh8wlAs8mrUnKPBBUru348u++2lfacLkxRXyT1ooqY
uSNE5nlwFt08N2Ou/bl7yq6QNRMYrRkn+UEfHWCNYDoGMHln2/i6Z1RapQzNarik
tJf7radBz5nBwBjP08YAEACNSQvpsUgdqiuYjLwX7efFXQva2RzqaQ==
-----END RSA PRIVATE KEY-----"""
# Encrypted with the passphrase 'encrypted', and using the new format
# introduced in OpenSSH 6.5
privateRSA_openssh_encrypted_new = b"""-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABD0f9WAof
DTbmwztb8pdrSeAAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQDVaqx4I9bW
G+wloVDEd2NQhEUBVUIUKirg0GDu1OmjrUr6OQZehFV1XwA2v2+qKj+DJjfBaS5b/fDz0n
3WmM06QHjVyqgYwBGTJAkMgUyP95ztExZqpATpSXfD5FVks3loniwI66zoBC0hdwWnju9T
MA2l5bs9auIJNm/9NNN9b0b/h9qpKSeq/631heY+Grh6HUqx6sBa9zDfH8Kk5O8/kUmWQN
UZdy03w17snaY6RKXCpCnd1bqcPUWzxiwYZNW6Pd+rf81CrKfxGAugWBViC6QqbkPD5ASf
NaNHjkbtM6Vlvbw7KW4CC1ffdOgTtDc1foNfICZgptyti8ZseZj3AAADwPQaac8s1xX3af
hQTQexj0vEAWDQsLYzDHN9G7W+UP5WHUu7igeu2GqAC/TOnjUXDP73I+EN3n7T3JFeDRfs
U1Z6Zqb0NKHSRVYwDIdIi8qVohFv85g6+xQ01OpaoOzz+vI34OUvCRHQGTgR6L9fQShZyC
McopYMYfbIse6KcqkfxX3KSdG1Pao6Njx/ShFRbgvmALpR/z0EaGCzHCDxpfUyAdnxm621
Jzaf+LverWdN7sfrfMptaS9//9iJb70sL67K+YIB64qhDnA/w9UOQfXGQFL+AEtdM0BPv8
thP1bs7T0yucBl+ZXdrDKVLZfaS3S/w85Jlgfu+a1DG73pOBOuag435iEJ9EnspjXiiydx
GrfSRk2C+/c4fBDZVGFscK5bfQuUUZyU1qOagekxX7WLHFKk9xajnud+nrAN070SeNwlX8
FZ2CI4KGlQfDvVUpKanYn8Kkj3fZ+YBGyx4M+19clF65FKSM0x1Rrh5tAmNT/SNDbSc28m
ASxrBhztzxUFTrIn3tp+uqkJniFLmFsUtiAUmj8fNyE9blykU7dqq+CqpLA872nQ9bOHHA
JsS1oBYmQ0n6AJz8WrYMdcepqWVld6Q8QSD1zdrY/sAWUovuBA1s4oIEXZhpXSS4ZJiMfh
PVktKBwj5bmoG/mmwYLbo0JHntK8N3TGTzTGLq5TpSBBdVvWSWo7tnfEkrFObmhi1uJSrQ
3zfPVP6BguboxBv+oxhaUBK8UOANe6ZwM4vfiu+QN+sZqWymHIfAktz7eWzwlToe4cKpdG
Uv+e3/7Lo2dyMl3nke5HsSUrlsMGPREuGkBih8+o85ii6D+cuCiVtus3f5c78Cir80zLIr
Z0wWvEAjciEvml00DWaA+JIaOrWwvXySaOzFGpCqC9SQjao379bvn9P3b7kVZsy6zBfHqm
bNEJUOuhBZaY8Okz36chh1xqh4sz7m3nsZ3GYGcvM+3mvRY72QnqsQEG0Sp1XYIn2bHa29
tqp7CG9X8J6dqMcPeoPRDWIX9gw7EPl/M0LP6xgewGJ9bgxwle6Mnr9kNITIswjAJqrLec
zx7dfixjAPc42ADqrw/tEdFQcSqxigcfJNKO1LbDBjh+Hk/cSBou2PoxbIcl0qfQfbGcqI
Dbpd695IEuiW9pYR22txNoIi+7cbMsuFHxQ/OqbrX/jCsprGNNJLAjgGsVEI1JnHWDH0db
3UbqbOHAeY3ufoYXNY1utVOIACpW3r9wBw3FjRi04d70VcKr16OXvOAHGN2G++Y+kMya84
Hl/Kt/gA==
-----END OPENSSH PRIVATE KEY-----"""
# Encrypted with the passphrase 'testxp'. NB: this key was generated by
# OpenSSH, so it doesn't use the same key data as the other keys here.
privateRSA_openssh_encrypted_aes = b"""-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,0673309A6ACCAB4B77DEE1C1E536AC26
4Ed/a9OgJWHJsne7yOGWeWMzHYKsxuP9w1v0aYcp+puS75wvhHLiUnNwxz0KDi6n
T3YkKLBsoCWS68ApR2J9yeQ6R+EyS+UQDrO9nwqo3DB5BT3Ggt8S1wE7vjNLQD0H
g/SJnlqwsECNhh8aAx+Ag0m3ZKOZiRD5mCkcDQsZET7URSmFytDKOjhFn3u6ZFVB
sXrfpYc6TJtOQlHd/52JB6aAbjt6afSv955Z7enIi+5yEJ5y7oYQTaE5zrFMP7N5
9LbfJFlKXxEddy/DErRLxEjmC+t4svHesoJKc2jjjyNPiOoGGF3kJXea62vsjdNV
gMK5Eged3TBVIk2dv8rtJUvyFeCUtjQ1UJZIebScRR47KrbsIpCmU8I4/uHWm5hW
0mOwvdx1L/mqx/BHqVU9Dw2COhOdLbFxlFI92chkovkmNk4P48ziyVnpm7ME22sE
vfCMsyirdqB1mrL4CSM7FXONv+CgfBfeYVkYW8RfJac9U1L/O+JNn7yee414O/rS
hRYw4UdWnH6Gg6niklVKWNY0ZwUZC8zgm2iqy8YCYuneS37jC+OEKP+/s6HSKuqk
2bzcl3/TcZXNSM815hnFRpz0anuyAsvwPNRyvxG2/DacJHL1f6luV4B0o6W410yf
qXQx01DLo7nuyhJqoH3UGCyyXB+/QUs0mbG2PAEn3f5dVs31JMdbt+PrxURXXjKk
4cexpUcIpqqlfpIRe3RD0sDVbH4OXsGhi2kiTfPZu7mgyFxKopRbn1KwU1qKinfY
EU9O4PoTak/tPT+5jFNhaP+HrURoi/pU8EAUNSktl7xAkHYwkN/9Cm7DeBghgf3n
8+tyCGYDsB5utPD0/Xe9yx0Qhc/kMm4xIyQDyA937dk3mUvLC9vulnAP8I+Izim0
fZ182+D1bWwykoD0997mUHG/AUChWR01V1OLwRyPv2wUtiS8VNG76Y2aqKlgqP1P
V+IvIEqR4ERvSBVFzXNF8Y6j/sVxo8+aZw+d0L1Ns/R55deErGg3B8i/2EqGd3r+
0jps9BqFHHWW87n3VyEB3jWCMj8Vi2EJIfa/7pSaViFIQn8LiBLf+zxG5LTOToK5
xkN42fReDcqi3UNfKNGnv4dsplyTR2hyx65lsj4bRKDGLKOuB1y7iB0AGb0LtcAI
dcsVlcCeUquDXtqKvRnwfIMg+ZunyjqHBhj3qgRgbXbT6zjaSdNnih569aTg0Vup
VykzZ7+n/KVcGLmvX0NesdoI7TKbq4TnEIOynuG5Sf+2GpARO5bjcWKSZeN/Ybgk
gccf8Cqf6XWqiwlWd0B7BR3SymeHIaSymC45wmbgdstrbk7Ppa2Tp9AZku8M2Y7c
8mY9b+onK075/ypiwBm4L4GRNTFLnoNQJXx0OSl4FNRWsn6ztbD+jZhu8Seu10Jw
SEJVJ+gmTKdRLYORJKyqhDet6g7kAxs4EoJ25WsOnX5nNr00rit+NkMPA7xbJT+7
CfI51GQLw7pUPeO2WNt6yZO/YkzZrqvTj5FEwybkUyBv7L0gkqu9wjfDdUw0fVHE
xEm4DxjEoaIp8dW/JOzXQ2EF+WaSOgdYsw3Ac+rnnjnNptCdOEDGP6QBkt+oXj4P
-----END RSA PRIVATE KEY-----"""
publicRSA_lsh = (
b'{KDEwOnB1YmxpYy1rZXkoMTQ6cnNhLXBrY3MxLXNoYTEoMTpuMjU3OgDVaqx4I9bWG+wloVD'
b'Ed2NQhEUBVUIUKirg0GDu1OmjrUr6OQZehFV1XwA2v2+qKj+DJjfBaS5b/fDz0n3WmM06QHj'
b'VyqgYwBGTJAkMgUyP95ztExZqpATpSXfD5FVks3loniwI66zoBC0hdwWnju9TMA2l5bs9auI'
b'JNm/9NNN9b0b/h9qpKSeq/631heY+Grh6HUqx6sBa9zDfH8Kk5O8/kUmWQNUZdy03w17snaY'
b'6RKXCpCnd1bqcPUWzxiwYZNW6Pd+rf81CrKfxGAugWBViC6QqbkPD5ASfNaNHjkbtM6Vlvbw'
b'7KW4CC1ffdOgTtDc1foNfICZgptyti8ZseZj3KSgxOmUzOgEAASkpKQ==}'
)
privateRSA_lsh = (
b"(11:private-key(9:rsa-pkcs1(1:n257:\x00\xd5j\xacx#\xd6\xd6\x1b\xec%\xa1P"
b"\xc4wcP\x84E\x01UB\x14**\xe0\xd0`\xee\xd4\xe9\xa3\xadJ\xfa9\x06^\x84Uu_"
b"\x006\xbfo\xaa*?\x83&7\xc1i.[\xfd\xf0\xf3\xd2}\xd6\x98\xcd:@x\xd5\xca"
b"\xa8\x18\xc0\x11\x93$\t\x0c\x81L\x8f\xf7\x9c\xed\x13\x16j\xa4\x04\xe9Iw"
b"\xc3\xe4Ud\xb3yh\x9e,\x08\xeb\xac\xe8\x04-!w\x05\xa7\x8e\xefS0\r\xa5\xe5"
b"\xbb=j\xe2\t6o\xfd4\xd3}oF\xff\x87\xda\xa9)'\xaa\xff\xad\xf5\x85\xe6>"
b"\x1a\xb8z\x1dJ\xb1\xea\xc0Z\xf70\xdf\x1f\xc2\xa4\xe4\xef?\x91I\x96@\xd5"
b"\x19w-7\xc3^\xec\x9d\xa6:D\xa5\xc2\xa4)\xdd\xd5\xba\x9c=E\xb3\xc6,\x18d"
b"\xd5\xba=\xdf\xab\x7f\xcdB\xac\xa7\xf1\x18\x0b\xa0X\x15b\x0b\xa4*nC\xc3"
b"\xe4\x04\x9f5\xa3G\x8eF\xed3\xa5e\xbd\xbc;)n\x02\x0bW\xdft\xe8\x13\xb475"
b"~\x83_ &`\xa6\xdc\xad\x8b\xc6ly\x98\xf7)(1:e3:\x01\x00\x01)(1:d256:!L"
b"\x08f\xa2(\xd5\xb4\xfb\x8e\x0fr\x1b\x85\t\x00\xb9\xf2N7\xf0\x1cWK\xe3Q"
b"\x7f\x9e#\xa7\xe4:\x98U\x1b\xea\x8bz\x98\x1e\xbc\xd8\xba\xb1\xf9\x89\x12"
b"\x18`\xac\xe8\xcc\x0bN\tZ@j\xba/\x99\xf8\xb3$`\x84\xb9\xcei\x95\x9a\xf9"
b"\xe2\xfc\x1fQM'\x15\xdb+'\xad\xef\xb4i\xac\xbe}\x10\xeb\x86Gps\xb4\x00"
b"\x87\x95\x15;7\xf9\xe7\x14\xe7\x80\xbbh\x1e\x1b\xe6\xdd\xbbsc\xb9g\xe6"
b"\xb2'\x7f\xcf\xcf0\x9b\xc2\x98\xfd\xd9\x186/6.\xf1=\x81z\x9f\xe1\x03-G"
b"\xdb4Qb9\xddO\xe9\xac\xa8\x8b\xd9\xd6\xf3\x84\xc4\x17\xb9q\x9d\x06\x08Bx"
b"M\xbb\xc5*\xf4\xc3X\xcdU+\xed\xbe3_\x04\xea{\xe6\x04$c\xf2-\xd7=\x1bl"
b"\xd5\x9ccC/\x92\x88\x8d>n\xda\x187\xd8\x0f%g\x89\x1d\xb9F4^\xc9\xce\xc4"
b"\x8b\xed\x92Z3\x07\x0f\xdf\x86\x08\xf9\x92\xe9\xdb\xeb8\x086\xc9\xcd\xcd"
b"\n\x01H[9>z\xca\xc6\x80\xa9\xdc\xd49)(1:p129:\x00\xfbD\x17\x8b\xa46\xbe"
b"\x1e7\x1d\xa7\xf6al\x04\xc4\xaa\xddx>\x07\x8c\x1e3\x02\xae\x03\x14\x87"
b"\x83z\xe5\x9e}\x08g\xa8\xf2\xaa\xbf\x12p\xcfr\xa9\xa7\xc7\x0b\x1d\x88"
b"\xd5 \xfd\x9cc\xcaG0UN\x8b\xc4\xcf\xf4\x7f\x16\xa4\x92\x12t\xa1\t\xc2"
b"\xc4n\x9c\x8c3\xef\xa5\xe5\xf7\xe0+\xadO\\\x11\xaa\x1a\x847[\xfdz\xea"
b"\xc3\xcd|\xb0\xc8\xe4\x1fTc\xb5\xc7\xaf\xdf\xf4\t\xa7\xfc\xc7%\xfc\\\xe9"
b"\x91\xd7\x92\xc5\x98\x1eV\xd3\xb1#)(1:q129:\x00\xd9p\x06\xd8\xe2\xbc\xd4"
b"x\x91P\x94\xd4\xc1\x1b\x898lFdZQ\xa0\x9a\x07=H\x8f\x03Q\xcck\x12\x8e}"
b"\x1a\xb1e\xe7qu9\xe02\x05u\x8d\x18L\xaf\x93\xb1I\xb1f_xbz\xd1\x0c\xca"
b"\xe6MC\xb3\x9c\xf4k}\xe6\x0c\x98\xdc\xcf!b\x8e\xd5.\x12\xde\x04\xae\xd7$"
b"n\x831\xa2\x15\xa2D=\"\xa9b&\"\xb9\xb2\xedT\n\x9d\x08\x83\xa7\x07\r\xff"
b"\x19\x18\x8e\xd8\xab\x1d\xdaH\x9c1h\x11\xa1fm\xe3\xd8\x1d)(1:a128:if7"
b"\xc6@\xdd!\xc5\x04\xf3\xb0\xb8>G\x94|v\xfc-\xeb?9<\x95\xc3C\x01Q\xc4B"
b"\x97\xf3\xe8\x16\xa4\xc6\xee\xec\xd4I\x10P8\x04\xee;\xcd\xd7\xd0\xcc\xcc"
b"2i\x90\x07\xa0\x1bZ\x9f\xfe1\xcd\x1e:~q\x1e\x19\x94\x1aNO\x0c\xdf_R\xd5"
b"\xd1\x17n\xec\xd7\x9c\xb6U\x9d\xdb\x8e!_\xbc$\x88\xb6\xfc\xaf\xab\xf0"
b"\xef\xa4,\xb0\xdc\x9f\x86\xb0\x03\x12\xb8\x8b\xe2\xdd\x0f\xc0\xee#=JP"
b"\xfe/\xb8)FX\xbf\xb6')(1:b128:Q\xaf\xe9\x92\x9f\x94\x0bJ\x84e>\x94\xb3;"
b"\x92\x10\xb5t\xb8\x8c\xc9\xef\xc9\x0e\x012\xfa/h\x12\xa1\x03&\xae\xcfQh"
b"\x14L&\x9b(\xa4\x023\x08_\xe1\xa7p\x98\x014y^R\x8e\xc4\xcf6\xbc\x1fKU"
b"\xac\xeb\xc1S\x84\xc7\xe1a\xa8J\xd4\xa2\xff@\r\x80\x1f\x12\xa9P\xc0*\x18"
b"u\x94\x0c\x06\x9b\x16P\xa8K\xecA\xcd{\xef\xf7K\xc9u\x02h\xc4\x98\xb8\x86"
b"\x88\x18ZC\xe7\x023\x97\"d\x93\x83\x0cE*|\xed)(1:c128:f\x16\xf9 4\xd0T%"
b"\xbca1\xac\x82\xfb\xef\x9c5\x1e~JU\x02h\x95\r\n\x93\xbe\x1e\xbf\xe1@\xfa"
b"\x90\xa7Tp1\xe9x\xfc\xe0f\xb7\"w\x9a\xeb\xdd\xd5\xd20F\xca\xe2\xd7^A\x9b"
b"\xcf\xb5H5Q\xaa%\xdc\xde\xdb4)H!\xdb\xd6t\xfc~\xe5/S\xf7\x9c\tp\xb9\xe4"
b"\xa0v\xa6\xadzt\"4\x9cO\x17\xcb?\xe0\xaa\xe5^\xa5\'\xde?\xa5\x7f\x0f\xa6"
b"\x88\xf5\x15\xd6_7\x17\x92\xe0\xd6\x05I\x10;\x92\xf6)))"
)
privateRSA_agentv3 = (
b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x00!L"
b"\x08f\xa2(\xd5\xb4\xfb\x8e\x0fr\x1b\x85\t\x00\xb9\xf2N7\xf0\x1cWK\xe3Q"
b"\x7f\x9e#\xa7\xe4:\x98U\x1b\xea\x8bz\x98\x1e\xbc\xd8\xba\xb1\xf9\x89\x12"
b"\x18`\xac\xe8\xcc\x0bN\tZ@j\xba/\x99\xf8\xb3$`\x84\xb9\xcei\x95\x9a\xf9"
b"\xe2\xfc\x1fQM'\x15\xdb+'\xad\xef\xb4i\xac\xbe}\x10\xeb\x86Gps\xb4\x00"
b"\x87\x95\x15;7\xf9\xe7\x14\xe7\x80\xbbh\x1e\x1b\xe6\xdd\xbbsc\xb9g\xe6"
b"\xb2'\x7f\xcf\xcf0\x9b\xc2\x98\xfd\xd9\x186/6.\xf1=\x81z\x9f\xe1\x03-G"
b"\xdb4Qb9\xddO\xe9\xac\xa8\x8b\xd9\xd6\xf3\x84\xc4\x17\xb9q\x9d\x06\x08Bx"
b"M\xbb\xc5*\xf4\xc3X\xcdU+\xed\xbe3_\x04\xea{\xe6\x04$c\xf2-\xd7=\x1bl"
b"\xd5\x9ccC/\x92\x88\x8d>n\xda\x187\xd8\x0f%g\x89\x1d\xb9F4^\xc9\xce\xc4"
b"\x8b\xed\x92Z3\x07\x0f\xdf\x86\x08\xf9\x92\xe9\xdb\xeb8\x086\xc9\xcd\xcd"
b"\n\x01H[9>z\xca\xc6\x80\xa9\xdc\xd49\x00\x00\x01\x01\x00\xd5j\xacx#\xd6"
b"\xd6\x1b\xec%\xa1P\xc4wcP\x84E\x01UB\x14**\xe0\xd0`\xee\xd4\xe9\xa3\xadJ"
b"\xfa9\x06^\x84Uu_\x006\xbfo\xaa*?\x83&7\xc1i.[\xfd\xf0\xf3\xd2}\xd6\x98"
b"\xcd:@x\xd5\xca\xa8\x18\xc0\x11\x93$\t\x0c\x81L\x8f\xf7\x9c\xed\x13\x16j"
b"\xa4\x04\xe9Iw\xc3\xe4Ud\xb3yh\x9e,\x08\xeb\xac\xe8\x04-!w\x05\xa7\x8e"
b"\xefS0\r\xa5\xe5\xbb=j\xe2\t6o\xfd4\xd3}oF\xff\x87\xda\xa9)'\xaa\xff\xad"
b"\xf5\x85\xe6>\x1a\xb8z\x1dJ\xb1\xea\xc0Z\xf70\xdf\x1f\xc2\xa4\xe4\xef?"
b"\x91I\x96@\xd5\x19w-7\xc3^\xec\x9d\xa6:D\xa5\xc2\xa4)\xdd\xd5\xba\x9c=E"
b"\xb3\xc6,\x18d\xd5\xba=\xdf\xab\x7f\xcdB\xac\xa7\xf1\x18\x0b\xa0X\x15b"
b"\x0b\xa4*nC\xc3\xe4\x04\x9f5\xa3G\x8eF\xed3\xa5e\xbd\xbc;)n\x02\x0bW\xdf"
b"t\xe8\x13\xb475~\x83_ &`\xa6\xdc\xad\x8b\xc6ly\x98\xf7\x00\x00\x00\x81"
b"\x00\x85K\x1bz\x9b\x12\x107\x9e\x1f\xad^\xda\xfe\xc6\x96\xfe\xdf5k\xb94"
b"\xe2\x16\x97\x92&\t\xbd\xbdp \x03\xa75\xbd-\x1b\xa0\xd2\x07G+\xd4\xde"
b"\xa8\xa8\x07\x07\x1b\xb8\x04 \xa7'A<l99\xe9A\xce\xe7\x17\x1d\xd1L\\\xbc="
b"\xd2&&\xfej\xd6\xfdHr\xaeF\xfa{\xc3\xd3\x19`D\x1d\xa5\x13\xa7\x80\xf5c)"
b"\xd4z]\x06\x07\x16]\xf6\x8b=\xcbd:\xe2\x84ZM\x8c\x06--\x9d\x1c\xeb\x83Lx"
b"=yT\xce\x00\x00\x00\x81\x00\xd9p\x06\xd8\xe2\xbc\xd4x\x91P\x94\xd4\xc1"
b"\x1b\x898lFdZQ\xa0\x9a\x07=H\x8f\x03Q\xcck\x12\x8e}\x1a\xb1e\xe7qu9\xe02"
b"\x05u\x8d\x18L\xaf\x93\xb1I\xb1f_xbz\xd1\x0c\xca\xe6MC\xb3\x9c\xf4k}\xe6"
b"\x0c\x98\xdc\xcf!b\x8e\xd5.\x12\xde\x04\xae\xd7$n\x831\xa2\x15\xa2D=\""
b"\xa9b&\"\xb9\xb2\xedT\n\x9d\x08\x83\xa7\x07\r\xff\x19\x18\x8e\xd8\xab"
b"\x1d\xdaH\x9c1h\x11\xa1fm\xe3\xd8\x1d\x00\x00\x00\x81\x00\xfbD\x17\x8b"
b"\xa46\xbe\x1e7\x1d\xa7\xf6al\x04\xc4\xaa\xddx>\x07\x8c\x1e3\x02\xae\x03"
b"\x14\x87\x83z\xe5\x9e}\x08g\xa8\xf2\xaa\xbf\x12p\xcfr\xa9\xa7\xc7\x0b"
b"\x1d\x88\xd5 \xfd\x9cc\xcaG0UN\x8b\xc4\xcf\xf4\x7f\x16\xa4\x92\x12t\xa1"
b"\t\xc2\xc4n\x9c\x8c3\xef\xa5\xe5\xf7\xe0+\xadO\\\x11\xaa\x1a\x847[\xfdz"
b"\xea\xc3\xcd|\xb0\xc8\xe4\x1fTc\xb5\xc7\xaf\xdf\xf4\t\xa7\xfc\xc7%\xfc\\"
b"\xe9\x91\xd7\x92\xc5\x98\x1eV\xd3\xb1#"
)
publicDSA_openssh = b"""\
ssh-dss AAAAB3NzaC1kc3MAAACBAJKQOsVERVDQIpANHH+JAAylo9\
LvFYmFFVMIuHFGlZpIL7sh3IMkqy+cssINM/lnHD3fmsAyLlUXZtt6PD9LgZRazsPOgptuH+Gu48G\
+yFuE8l0fVVUivos/MmYVJ66qT99htcZKatrTWZnpVW7gFABoqw+he2LZ0gkeU0+Sx9a5AAAAFQD0\
EYmTNaFJ8CS0+vFSF4nYcyEnSQAAAIEAkgLjxHJAE7qFWdTqf7EZngu7jAGmdB9k3YzMHe1ldMxEB\
7zNw5aOnxjhoYLtiHeoEcOk2XOyvnE+VfhIWwWAdOiKRTEZlmizkvhGbq0DCe2EPMXirjqWACI5nD\
ioQX1oEMonR8N3AEO5v9SfBqS2Q9R6OBr6lf04RvwpHZ0UGu8AAACAAhRpxGMIWEyaEh8YnjiazQT\
NEpklRZqeBGo1gotJggNmVaIQNIClGlLyCi359efEUuQcZ9SXxM59P+hecc/GU/GHakW5YWE4dP2G\
gdgMQWC7S6WFIXePGGXqNQDdWxlX8umhenvQqa1PnKrFRhDrJw8Z7GjdHxflsxCEmXPoLN8= \
comment\
"""
privateDSA_openssh = b"""\
-----BEGIN DSA PRIVATE KEY-----
MIIBvAIBAAKBgQCSkDrFREVQ0CKQDRx/iQAMpaPS7xWJhRVTCLhxRpWaSC+7IdyD
JKsvnLLCDTP5Zxw935rAMi5VF2bbejw/S4GUWs7DzoKbbh/hruPBvshbhPJdH1VV
Ir6LPzJmFSeuqk/fYbXGSmra01mZ6VVu4BQAaKsPoXti2dIJHlNPksfWuQIVAPQR
iZM1oUnwJLT68VIXidhzISdJAoGBAJIC48RyQBO6hVnU6n+xGZ4Lu4wBpnQfZN2M
zB3tZXTMRAe8zcOWjp8Y4aGC7Yh3qBHDpNlzsr5xPlX4SFsFgHToikUxGZZos5L4
Rm6tAwnthDzF4q46lgAiOZw4qEF9aBDKJ0fDdwBDub/UnwaktkPUejga+pX9OEb8
KR2dFBrvAoGAAhRpxGMIWEyaEh8YnjiazQTNEpklRZqeBGo1gotJggNmVaIQNICl
GlLyCi359efEUuQcZ9SXxM59P+hecc/GU/GHakW5YWE4dP2GgdgMQWC7S6WFIXeP
GGXqNQDdWxlX8umhenvQqa1PnKrFRhDrJw8Z7GjdHxflsxCEmXPoLN8CFQDV2gbL
czUdxCus0pfEP1bddaXRLQ==
-----END DSA PRIVATE KEY-----\
"""
privateDSA_openssh_new = b"""\
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsgAAAAdzc2gtZH
NzAAAAgQCSkDrFREVQ0CKQDRx/iQAMpaPS7xWJhRVTCLhxRpWaSC+7IdyDJKsvnLLCDTP5
Zxw935rAMi5VF2bbejw/S4GUWs7DzoKbbh/hruPBvshbhPJdH1VVIr6LPzJmFSeuqk/fYb
XGSmra01mZ6VVu4BQAaKsPoXti2dIJHlNPksfWuQAAABUA9BGJkzWhSfAktPrxUheJ2HMh
J0kAAACBAJIC48RyQBO6hVnU6n+xGZ4Lu4wBpnQfZN2MzB3tZXTMRAe8zcOWjp8Y4aGC7Y
h3qBHDpNlzsr5xPlX4SFsFgHToikUxGZZos5L4Rm6tAwnthDzF4q46lgAiOZw4qEF9aBDK
J0fDdwBDub/UnwaktkPUejga+pX9OEb8KR2dFBrvAAAAgAIUacRjCFhMmhIfGJ44ms0EzR
KZJUWangRqNYKLSYIDZlWiEDSApRpS8got+fXnxFLkHGfUl8TOfT/oXnHPxlPxh2pFuWFh
OHT9hoHYDEFgu0ulhSF3jxhl6jUA3VsZV/LpoXp70KmtT5yqxUYQ6ycPGexo3R8X5bMQhJ
lz6CzfAAAB2MVcBjzFXAY8AAAAB3NzaC1kc3MAAACBAJKQOsVERVDQIpANHH+JAAylo9Lv
FYmFFVMIuHFGlZpIL7sh3IMkqy+cssINM/lnHD3fmsAyLlUXZtt6PD9LgZRazsPOgptuH+
Gu48G+yFuE8l0fVVUivos/MmYVJ66qT99htcZKatrTWZnpVW7gFABoqw+he2LZ0gkeU0+S
x9a5AAAAFQD0EYmTNaFJ8CS0+vFSF4nYcyEnSQAAAIEAkgLjxHJAE7qFWdTqf7EZngu7jA
GmdB9k3YzMHe1ldMxEB7zNw5aOnxjhoYLtiHeoEcOk2XOyvnE+VfhIWwWAdOiKRTEZlmiz
kvhGbq0DCe2EPMXirjqWACI5nDioQX1oEMonR8N3AEO5v9SfBqS2Q9R6OBr6lf04RvwpHZ
0UGu8AAACAAhRpxGMIWEyaEh8YnjiazQTNEpklRZqeBGo1gotJggNmVaIQNIClGlLyCi35
9efEUuQcZ9SXxM59P+hecc/GU/GHakW5YWE4dP2GgdgMQWC7S6WFIXePGGXqNQDdWxlX8u
mhenvQqa1PnKrFRhDrJw8Z7GjdHxflsxCEmXPoLN8AAAAVANXaBstzNR3EK6zSl8Q/Vt11
pdEtAAAAAAE=
-----END OPENSSH PRIVATE KEY-----\
"""
publicDSA_lsh = decodebytes(b"""\
e0tERXdPbkIxWW14cFl5MXJaWGtvTXpwa2MyRW9NVHB3TVRJNU9nQ1NrRHJGUkVWUTBDS1FEUngv
aVFBTXBhUFM3eFdKaFJWVENMaHhScFdhU0MrN0lkeURKS3N2bkxMQ0RUUDVaeHc5MzVyQU1pNVZG
MmJiZWp3L1M0R1VXczdEem9LYmJoL2hydVBCdnNoYmhQSmRIMVZWSXI2TFB6Sm1GU2V1cWsvZlli
WEdTbXJhMDFtWjZWVnU0QlFBYUtzUG9YdGkyZElKSGxOUGtzZld1U2tvTVRweE1qRTZBUFFSaVpN
MW9VbndKTFQ2OFZJWGlkaHpJU2RKS1NneE9tY3hNams2QUpJQzQ4UnlRQk82aFZuVTZuK3hHWjRM
dTR3QnBuUWZaTjJNekIzdFpYVE1SQWU4emNPV2pwOFk0YUdDN1loM3FCSERwTmx6c3I1eFBsWDRT
RnNGZ0hUb2lrVXhHWlpvczVMNFJtNnRBd250aER6RjRxNDZsZ0FpT1p3NHFFRjlhQkRLSjBmRGR3
QkR1Yi9Vbndha3RrUFVlamdhK3BYOU9FYjhLUjJkRkJydktTZ3hPbmt4TWpnNkFoUnB4R01JV0V5
YUVoOFluamlhelFUTkVwa2xSWnFlQkdvMWdvdEpnZ05tVmFJUU5JQ2xHbEx5Q2kzNTllZkVVdVFj
WjlTWHhNNTlQK2hlY2MvR1UvR0hha1c1WVdFNGRQMkdnZGdNUVdDN1M2V0ZJWGVQR0dYcU5RRGRX
eGxYOHVtaGVudlFxYTFQbktyRlJoRHJKdzhaN0dqZEh4ZmxzeENFbVhQb0xOOHBLU2s9fQ==
""")
privateDSA_lsh = decodebytes(b"""\
KDExOnByaXZhdGUta2V5KDM6ZHNhKDE6cDEyOToAkpA6xURFUNAikA0cf4kADKWj0u8ViYUVUwi4
cUaVmkgvuyHcgySrL5yywg0z+WccPd+awDIuVRdm23o8P0uBlFrOw86Cm24f4a7jwb7IW4TyXR9V
VSK+iz8yZhUnrqpP32G1xkpq2tNZmelVbuAUAGirD6F7YtnSCR5TT5LH1rkpKDE6cTIxOgD0EYmT
NaFJ8CS0+vFSF4nYcyEnSSkoMTpnMTI5OgCSAuPEckATuoVZ1Op/sRmeC7uMAaZ0H2TdjMwd7WV0
zEQHvM3Dlo6fGOGhgu2Id6gRw6TZc7K+cT5V+EhbBYB06IpFMRmWaLOS+EZurQMJ7YQ8xeKuOpYA
IjmcOKhBfWgQyidHw3cAQ7m/1J8GpLZD1Ho4GvqV/ThG/CkdnRQa7ykoMTp5MTI4OgIUacRjCFhM
mhIfGJ44ms0EzRKZJUWangRqNYKLSYIDZlWiEDSApRpS8got+fXnxFLkHGfUl8TOfT/oXnHPxlPx
h2pFuWFhOHT9hoHYDEFgu0ulhSF3jxhl6jUA3VsZV/LpoXp70KmtT5yqxUYQ6ycPGexo3R8X5bMQ
hJlz6CzfKSgxOngyMToA1doGy3M1HcQrrNKXxD9W3XWl0S0pKSk=
""")
privateDSA_agentv3 = decodebytes(b"""\
AAAAB3NzaC1kc3MAAACBAJKQOsVERVDQIpANHH+JAAylo9LvFYmFFVMIuHFGlZpIL7sh3IMkqy+c
ssINM/lnHD3fmsAyLlUXZtt6PD9LgZRazsPOgptuH+Gu48G+yFuE8l0fVVUivos/MmYVJ66qT99h
tcZKatrTWZnpVW7gFABoqw+he2LZ0gkeU0+Sx9a5AAAAFQD0EYmTNaFJ8CS0+vFSF4nYcyEnSQAA
AIEAkgLjxHJAE7qFWdTqf7EZngu7jAGmdB9k3YzMHe1ldMxEB7zNw5aOnxjhoYLtiHeoEcOk2XOy
vnE+VfhIWwWAdOiKRTEZlmizkvhGbq0DCe2EPMXirjqWACI5nDioQX1oEMonR8N3AEO5v9SfBqS2
Q9R6OBr6lf04RvwpHZ0UGu8AAACAAhRpxGMIWEyaEh8YnjiazQTNEpklRZqeBGo1gotJggNmVaIQ
NIClGlLyCi359efEUuQcZ9SXxM59P+hecc/GU/GHakW5YWE4dP2GgdgMQWC7S6WFIXePGGXqNQDd
WxlX8umhenvQqa1PnKrFRhDrJw8Z7GjdHxflsxCEmXPoLN8AAAAVANXaBstzNR3EK6zSl8Q/Vt11
pdEt
""")
__all__ = ['DSAData', 'RSAData', 'privateDSA_agentv3', 'privateDSA_lsh',
'privateDSA_openssh', 'privateRSA_agentv3', 'privateRSA_lsh',
'privateRSA_openssh', 'publicDSA_lsh', 'publicDSA_openssh',
'publicRSA_lsh', 'publicRSA_openssh', 'privateRSA_openssh_alternate']
@@ -0,0 +1,28 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Loopback helper used in test_ssh and test_recvline
"""
from __future__ import division, absolute_import
from twisted.protocols import loopback
class LoopbackRelay(loopback.LoopbackRelay):
clearCall = None
def logPrefix(self):
return "LoopbackRelay(%r)" % (self.target.__class__.__name__,)
def write(self, data):
loopback.LoopbackRelay.write(self, data)
if self.clearCall is not None:
self.clearCall.cancel()
from twisted.internet import reactor
self.clearCall = reactor.callLater(0, self._clearBuffer)
def _clearBuffer(self):
self.clearCall = None
loopback.LoopbackRelay.clearBuffer(self)
@@ -0,0 +1,50 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{SSHTransportAddrress} in ssh/address.py
"""
from __future__ import division, absolute_import
from twisted.trial import unittest
from twisted.internet.address import IPv4Address
from twisted.internet.test.test_address import AddressTestCaseMixin
from twisted.conch.ssh.address import SSHTransportAddress
class SSHTransportAddressTests(unittest.TestCase, AddressTestCaseMixin):
"""
L{twisted.conch.ssh.address.SSHTransportAddress} is what Conch transports
use to represent the other side of the SSH connection. This tests the
basic functionality of that class (string representation, comparison, &c).
"""
def _stringRepresentation(self, stringFunction):
"""
The string representation of C{SSHTransportAddress} should be
"SSHTransportAddress(<stringFunction on address>)".
"""
addr = self.buildAddress()
stringValue = stringFunction(addr)
addressValue = stringFunction(addr.address)
self.assertEqual(stringValue,
"SSHTransportAddress(%s)" % addressValue)
def buildAddress(self):
"""
Create an arbitrary new C{SSHTransportAddress}. A new instance is
created for each call, but always for the same address.
"""
return SSHTransportAddress(IPv4Address("TCP", "127.0.0.1", 22))
def buildDifferentAddress(self):
"""
Like C{buildAddress}, but with a different fixed address.
"""
return SSHTransportAddress(IPv4Address("TCP", "127.0.0.2", 22))
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,567 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.conch.scripts.ckeygen}.
"""
import getpass
import sys
import subprocess
from io import BytesIO, StringIO
from twisted.python.compat import unicode, _PY3
from twisted.python.reflect import requireModule
if requireModule('cryptography') and requireModule('pyasn1'):
from twisted.conch.ssh.keys import (Key, BadKeyError,
BadFingerPrintFormat, FingerprintFormats)
from twisted.conch.scripts.ckeygen import (
changePassPhrase, displayPublicKey, printFingerprint,
_saveKey, enumrepresentation)
else:
skip = "cryptography and pyasn1 required for twisted.conch.scripts.ckeygen"
from twisted.python.filepath import FilePath
from twisted.trial.unittest import TestCase
from twisted.conch.test.keydata import (
publicRSA_openssh, privateRSA_openssh, privateRSA_openssh_encrypted, privateECDSA_openssh)
def makeGetpass(*passphrases):
"""
Return a callable to patch C{getpass.getpass}. Yields a passphrase each
time called. Use case is to provide an old, then new passphrase(s) as if
requested interactively.
@param passphrases: The list of passphrases returned, one per each call.
@return: A callable to patch C{getpass.getpass}.
"""
passphrases = iter(passphrases)
def fakeGetpass(_):
return next(passphrases)
return fakeGetpass
class KeyGenTests(TestCase):
"""
Tests for various functions used to implement the I{ckeygen} script.
"""
def setUp(self):
"""
Patch C{sys.stdout} so tests can make assertions about what's printed.
"""
if _PY3:
self.stdout = StringIO()
else:
self.stdout = BytesIO()
self.patch(sys, 'stdout', self.stdout)
def _testrun(self, keyType, keySize=None):
filename = self.mktemp()
if keySize is None:
subprocess.call(['ckeygen', '-t', keyType, '-f', filename, '--no-passphrase'])
else:
subprocess.call(['ckeygen', '-t', keyType, '-f', filename, '--no-passphrase',
'-b', keySize])
privKey = Key.fromFile(filename)
pubKey = Key.fromFile(filename + '.pub')
if keyType == 'ecdsa':
self.assertEqual(privKey.type(), 'EC')
else:
self.assertEqual(privKey.type(), keyType.upper())
self.assertTrue(pubKey.isPublic())
def test_keygeneration(self):
self._testrun('ecdsa', '384')
self._testrun('ecdsa')
self._testrun('dsa', '2048')
self._testrun('dsa')
self._testrun('rsa', '2048')
self._testrun('rsa')
def test_runBadKeytype(self):
filename = self.mktemp()
with self.assertRaises(subprocess.CalledProcessError):
subprocess.check_call(['ckeygen', '-t', 'foo', '-f', filename])
def test_enumrepresentation(self):
"""
L{enumrepresentation} takes a dictionary as input and returns a
dictionary with its attributes changed to enum representation.
"""
options = enumrepresentation({'format': 'md5-hex'})
self.assertIs(options['format'],
FingerprintFormats.MD5_HEX)
def test_enumrepresentationsha256(self):
"""
Test for format L{FingerprintFormats.SHA256-BASE64}.
"""
options = enumrepresentation({'format': 'sha256-base64'})
self.assertIs(options['format'],
FingerprintFormats.SHA256_BASE64)
def test_enumrepresentationBadFormat(self):
"""
Test for unsupported fingerprint format
"""
with self.assertRaises(BadFingerPrintFormat) as em:
enumrepresentation({'format': 'sha-base64'})
self.assertEqual('Unsupported fingerprint format: sha-base64',
em.exception.args[0])
def test_printFingerprint(self):
"""
L{printFingerprint} writes a line to standard out giving the number of
bits of the key, its fingerprint, and the basename of the file from it
was read.
"""
filename = self.mktemp()
FilePath(filename).setContent(publicRSA_openssh)
printFingerprint({'filename': filename,
'format': 'md5-hex'})
self.assertEqual(
self.stdout.getvalue(),
'2048 85:25:04:32:58:55:96:9f:57:ee:fb:a8:1a:ea:69:da temp\n')
def test_printFingerprintsha256(self):
"""
L{printFigerprint} will print key fingerprint in
L{FingerprintFormats.SHA256-BASE64} format if explicitly specified.
"""
filename = self.mktemp()
FilePath(filename).setContent(publicRSA_openssh)
printFingerprint({'filename': filename,
'format': 'sha256-base64'})
self.assertEqual(
self.stdout.getvalue(),
'2048 FBTCOoknq0mHy+kpfnY9tDdcAJuWtCpuQMaV3EsvbUI= temp\n')
def test_printFingerprintBadFingerPrintFormat(self):
"""
L{printFigerprint} raises C{keys.BadFingerprintFormat} when unsupported
formats are requested.
"""
filename = self.mktemp()
FilePath(filename).setContent(publicRSA_openssh)
with self.assertRaises(BadFingerPrintFormat) as em:
printFingerprint({'filename': filename, 'format':'sha-base64'})
self.assertEqual('Unsupported fingerprint format: sha-base64',
em.exception.args[0])
def test_saveKey(self):
"""
L{_saveKey} writes the private and public parts of a key to two
different files and writes a report of this to standard out.
"""
base = FilePath(self.mktemp())
base.makedirs()
filename = base.child('id_rsa').path
key = Key.fromString(privateRSA_openssh)
_saveKey(key, {'filename': filename, 'pass': 'passphrase',
'format': 'md5-hex'})
self.assertEqual(
self.stdout.getvalue(),
"Your identification has been saved in %s\n"
"Your public key has been saved in %s.pub\n"
"The key fingerprint in <FingerprintFormats=MD5_HEX> is:\n"
"85:25:04:32:58:55:96:9f:57:ee:fb:a8:1a:ea:69:da\n" % (
filename,
filename))
self.assertEqual(
key.fromString(
base.child('id_rsa').getContent(), None, 'passphrase'),
key)
self.assertEqual(
Key.fromString(base.child('id_rsa.pub').getContent()),
key.public())
def test_saveKeyECDSA(self):
"""
L{_saveKey} writes the private and public parts of a key to two
different files and writes a report of this to standard out.
Test with ECDSA key.
"""
base = FilePath(self.mktemp())
base.makedirs()
filename = base.child('id_ecdsa').path
key = Key.fromString(privateECDSA_openssh)
_saveKey(key, {'filename': filename, 'pass': 'passphrase',
'format': 'md5-hex'})
self.assertEqual(
self.stdout.getvalue(),
"Your identification has been saved in %s\n"
"Your public key has been saved in %s.pub\n"
"The key fingerprint in <FingerprintFormats=MD5_HEX> is:\n"
"1e:ab:83:a6:f2:04:22:99:7c:64:14:d2:ab:fa:f5:16\n" % (
filename,
filename))
self.assertEqual(
key.fromString(
base.child('id_ecdsa').getContent(), None, 'passphrase'),
key)
self.assertEqual(
Key.fromString(base.child('id_ecdsa.pub').getContent()),
key.public())
def test_saveKeysha256(self):
"""
L{_saveKey} will generate key fingerprint in
L{FingerprintFormats.SHA256-BASE64} format if explicitly specified.
"""
base = FilePath(self.mktemp())
base.makedirs()
filename = base.child('id_rsa').path
key = Key.fromString(privateRSA_openssh)
_saveKey(key, {'filename': filename, 'pass': 'passphrase',
'format': 'sha256-base64'})
self.assertEqual(
self.stdout.getvalue(),
"Your identification has been saved in %s\n"
"Your public key has been saved in %s.pub\n"
"The key fingerprint in <FingerprintFormats=SHA256_BASE64> is:\n"
"FBTCOoknq0mHy+kpfnY9tDdcAJuWtCpuQMaV3EsvbUI=\n" % (
filename,
filename))
self.assertEqual(
key.fromString(
base.child('id_rsa').getContent(), None, 'passphrase'),
key)
self.assertEqual(
Key.fromString(base.child('id_rsa.pub').getContent()),
key.public())
def test_saveKeyBadFingerPrintformat(self):
"""
L{_saveKey} raises C{keys.BadFingerprintFormat} when unsupported
formats are requested.
"""
base = FilePath(self.mktemp())
base.makedirs()
filename = base.child('id_rsa').path
key = Key.fromString(privateRSA_openssh)
with self.assertRaises(BadFingerPrintFormat) as em:
_saveKey(key, {'filename': filename, 'pass': 'passphrase',
'format': 'sha-base64'})
self.assertEqual('Unsupported fingerprint format: sha-base64',
em.exception.args[0])
def test_saveKeyEmptyPassphrase(self):
"""
L{_saveKey} will choose an empty string for the passphrase if
no-passphrase is C{True}.
"""
base = FilePath(self.mktemp())
base.makedirs()
filename = base.child('id_rsa').path
key = Key.fromString(privateRSA_openssh)
_saveKey(key, {'filename': filename, 'no-passphrase': True,
'format': 'md5-hex'})
self.assertEqual(
key.fromString(
base.child('id_rsa').getContent(), None, b''),
key)
def test_saveKeyECDSAEmptyPassphrase(self):
"""
L{_saveKey} will choose an empty string for the passphrase if
no-passphrase is C{True}.
"""
base = FilePath(self.mktemp())
base.makedirs()
filename = base.child('id_ecdsa').path
key = Key.fromString(privateECDSA_openssh)
_saveKey(key, {'filename': filename, 'no-passphrase': True,
'format': 'md5-hex'})
self.assertEqual(
key.fromString(
base.child('id_ecdsa').getContent(), None),
key)
def test_saveKeyNoFilename(self):
"""
When no path is specified, it will ask for the path used to store the
key.
"""
base = FilePath(self.mktemp())
base.makedirs()
keyPath = base.child('custom_key').path
import twisted.conch.scripts.ckeygen
self.patch(twisted.conch.scripts.ckeygen, 'raw_input', lambda _: keyPath)
key = Key.fromString(privateRSA_openssh)
_saveKey(key, {'filename': None, 'no-passphrase': True,
'format': 'md5-hex'})
persistedKeyContent = base.child('custom_key').getContent()
persistedKey = key.fromString(persistedKeyContent, None, b'')
self.assertEqual(key, persistedKey)
def test_displayPublicKey(self):
"""
L{displayPublicKey} prints out the public key associated with a given
private key.
"""
filename = self.mktemp()
pubKey = Key.fromString(publicRSA_openssh)
FilePath(filename).setContent(privateRSA_openssh)
displayPublicKey({'filename': filename})
displayed = self.stdout.getvalue().strip('\n')
if isinstance(displayed, unicode):
displayed = displayed.encode("ascii")
self.assertEqual(
displayed,
pubKey.toString('openssh'))
def test_displayPublicKeyEncrypted(self):
"""
L{displayPublicKey} prints out the public key associated with a given
private key using the given passphrase when it's encrypted.
"""
filename = self.mktemp()
pubKey = Key.fromString(publicRSA_openssh)
FilePath(filename).setContent(privateRSA_openssh_encrypted)
displayPublicKey({'filename': filename, 'pass': 'encrypted'})
displayed = self.stdout.getvalue().strip('\n')
if isinstance(displayed, unicode):
displayed = displayed.encode("ascii")
self.assertEqual(
displayed,
pubKey.toString('openssh'))
def test_displayPublicKeyEncryptedPassphrasePrompt(self):
"""
L{displayPublicKey} prints out the public key associated with a given
private key, asking for the passphrase when it's encrypted.
"""
filename = self.mktemp()
pubKey = Key.fromString(publicRSA_openssh)
FilePath(filename).setContent(privateRSA_openssh_encrypted)
self.patch(getpass, 'getpass', lambda x: 'encrypted')
displayPublicKey({'filename': filename})
displayed = self.stdout.getvalue().strip('\n')
if isinstance(displayed, unicode):
displayed = displayed.encode("ascii")
self.assertEqual(
displayed,
pubKey.toString('openssh'))
def test_displayPublicKeyWrongPassphrase(self):
"""
L{displayPublicKey} fails with a L{BadKeyError} when trying to decrypt
an encrypted key with the wrong password.
"""
filename = self.mktemp()
FilePath(filename).setContent(privateRSA_openssh_encrypted)
self.assertRaises(
BadKeyError, displayPublicKey,
{'filename': filename, 'pass': 'wrong'})
def test_changePassphrase(self):
"""
L{changePassPhrase} allows a user to change the passphrase of a
private key interactively.
"""
oldNewConfirm = makeGetpass('encrypted', 'newpass', 'newpass')
self.patch(getpass, 'getpass', oldNewConfirm)
filename = self.mktemp()
FilePath(filename).setContent(privateRSA_openssh_encrypted)
changePassPhrase({'filename': filename})
self.assertEqual(
self.stdout.getvalue().strip('\n'),
'Your identification has been saved with the new passphrase.')
self.assertNotEqual(privateRSA_openssh_encrypted,
FilePath(filename).getContent())
def test_changePassphraseWithOld(self):
"""
L{changePassPhrase} allows a user to change the passphrase of a
private key, providing the old passphrase and prompting for new one.
"""
newConfirm = makeGetpass('newpass', 'newpass')
self.patch(getpass, 'getpass', newConfirm)
filename = self.mktemp()
FilePath(filename).setContent(privateRSA_openssh_encrypted)
changePassPhrase({'filename': filename, 'pass': 'encrypted'})
self.assertEqual(
self.stdout.getvalue().strip('\n'),
'Your identification has been saved with the new passphrase.')
self.assertNotEqual(privateRSA_openssh_encrypted,
FilePath(filename).getContent())
def test_changePassphraseWithBoth(self):
"""
L{changePassPhrase} allows a user to change the passphrase of a private
key by providing both old and new passphrases without prompting.
"""
filename = self.mktemp()
FilePath(filename).setContent(privateRSA_openssh_encrypted)
changePassPhrase(
{'filename': filename, 'pass': 'encrypted',
'newpass': 'newencrypt'})
self.assertEqual(
self.stdout.getvalue().strip('\n'),
'Your identification has been saved with the new passphrase.')
self.assertNotEqual(privateRSA_openssh_encrypted,
FilePath(filename).getContent())
def test_changePassphraseWrongPassphrase(self):
"""
L{changePassPhrase} exits if passed an invalid old passphrase when
trying to change the passphrase of a private key.
"""
filename = self.mktemp()
FilePath(filename).setContent(privateRSA_openssh_encrypted)
error = self.assertRaises(
SystemExit, changePassPhrase,
{'filename': filename, 'pass': 'wrong'})
self.assertEqual('Could not change passphrase: old passphrase error',
str(error))
self.assertEqual(privateRSA_openssh_encrypted,
FilePath(filename).getContent())
def test_changePassphraseEmptyGetPass(self):
"""
L{changePassPhrase} exits if no passphrase is specified for the
C{getpass} call and the key is encrypted.
"""
self.patch(getpass, 'getpass', makeGetpass(''))
filename = self.mktemp()
FilePath(filename).setContent(privateRSA_openssh_encrypted)
error = self.assertRaises(
SystemExit, changePassPhrase, {'filename': filename})
self.assertEqual(
'Could not change passphrase: Passphrase must be provided '
'for an encrypted key',
str(error))
self.assertEqual(privateRSA_openssh_encrypted,
FilePath(filename).getContent())
def test_changePassphraseBadKey(self):
"""
L{changePassPhrase} exits if the file specified points to an invalid
key.
"""
filename = self.mktemp()
FilePath(filename).setContent(b'foobar')
error = self.assertRaises(
SystemExit, changePassPhrase, {'filename': filename})
if _PY3:
expected = "Could not change passphrase: cannot guess the type of b'foobar'"
else:
expected = "Could not change passphrase: cannot guess the type of 'foobar'"
self.assertEqual(expected, str(error))
self.assertEqual(b'foobar', FilePath(filename).getContent())
def test_changePassphraseCreateError(self):
"""
L{changePassPhrase} doesn't modify the key file if an unexpected error
happens when trying to create the key with the new passphrase.
"""
filename = self.mktemp()
FilePath(filename).setContent(privateRSA_openssh)
def toString(*args, **kwargs):
raise RuntimeError('oops')
self.patch(Key, 'toString', toString)
error = self.assertRaises(
SystemExit, changePassPhrase,
{'filename': filename,
'newpass': 'newencrypt'})
self.assertEqual(
'Could not change passphrase: oops', str(error))
self.assertEqual(privateRSA_openssh, FilePath(filename).getContent())
def test_changePassphraseEmptyStringError(self):
"""
L{changePassPhrase} doesn't modify the key file if C{toString} returns
an empty string.
"""
filename = self.mktemp()
FilePath(filename).setContent(privateRSA_openssh)
def toString(*args, **kwargs):
return ''
self.patch(Key, 'toString', toString)
error = self.assertRaises(
SystemExit, changePassPhrase,
{'filename': filename, 'newpass': 'newencrypt'})
if _PY3:
expected = (
"Could not change passphrase: cannot guess the type of b''")
else:
expected = (
"Could not change passphrase: cannot guess the type of ''")
self.assertEqual(expected, str(error))
self.assertEqual(privateRSA_openssh, FilePath(filename).getContent())
def test_changePassphrasePublicKey(self):
"""
L{changePassPhrase} exits when trying to change the passphrase on a
public key, and doesn't change the file.
"""
filename = self.mktemp()
FilePath(filename).setContent(publicRSA_openssh)
error = self.assertRaises(
SystemExit, changePassPhrase,
{'filename': filename, 'newpass': 'pass'})
self.assertEqual(
'Could not change passphrase: key not encrypted', str(error))
self.assertEqual(publicRSA_openssh, FilePath(filename).getContent())
@@ -0,0 +1,813 @@
# -*- test-case-name: twisted.conch.test.test_conch -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
import os, sys, socket
import subprocess
from itertools import count
from zope.interface import implementer
from twisted.python.reflect import requireModule
from twisted.conch.error import ConchError
from twisted.cred import portal
from twisted.internet import reactor, defer, protocol
from twisted.internet.error import ProcessExitedAlready
from twisted.internet.task import LoopingCall
from twisted.internet.utils import getProcessValue
from twisted.python import filepath, log, runtime
from twisted.python.compat import unicode, _PYPY
from twisted.trial import unittest
from twisted.conch.test.test_ssh import ConchTestRealm
from twisted.python.procutils import which
from twisted.conch.test.keydata import publicRSA_openssh, privateRSA_openssh
from twisted.conch.test.keydata import publicDSA_openssh, privateDSA_openssh
try:
from twisted.conch.test.test_ssh import ConchTestServerFactory, \
conchTestPublicKeyChecker
except ImportError:
pass
try:
import pyasn1
except ImportError:
pyasn1 = None
cryptography = requireModule("cryptography")
if cryptography:
from twisted.conch.avatar import ConchUser
from twisted.conch.ssh.session import ISession, SSHSession, wrapProtocol
else:
from twisted.conch.interfaces import ISession
class ConchUser:
pass
try:
from twisted.conch.scripts.conch import (
SSHSession as StdioInteractingSession
)
except ImportError as e:
StdioInteractingSession = None
_reason = str(e)
del e
def _has_ipv6():
""" Returns True if the system can bind an IPv6 address."""
sock = None
has_ipv6 = False
try:
sock = socket.socket(socket.AF_INET6)
sock.bind(('::1', 0))
has_ipv6 = True
except socket.error:
pass
if sock:
sock.close()
return has_ipv6
HAS_IPV6 = _has_ipv6()
class FakeStdio(object):
"""
A fake for testing L{twisted.conch.scripts.conch.SSHSession.eofReceived} and
L{twisted.conch.scripts.cftp.SSHSession.eofReceived}.
@ivar writeConnLost: A flag which records whether L{loserWriteConnection}
has been called.
"""
writeConnLost = False
def loseWriteConnection(self):
"""
Record the call to loseWriteConnection.
"""
self.writeConnLost = True
class StdioInteractingSessionTests(unittest.TestCase):
"""
Tests for L{twisted.conch.scripts.conch.SSHSession}.
"""
if StdioInteractingSession is None:
skip = _reason
def test_eofReceived(self):
"""
L{twisted.conch.scripts.conch.SSHSession.eofReceived} loses the
write half of its stdio connection.
"""
stdio = FakeStdio()
channel = StdioInteractingSession()
channel.stdio = stdio
channel.eofReceived()
self.assertTrue(stdio.writeConnLost)
class Echo(protocol.Protocol):
def connectionMade(self):
log.msg('ECHO CONNECTION MADE')
def connectionLost(self, reason):
log.msg('ECHO CONNECTION DONE')
def dataReceived(self, data):
self.transport.write(data)
if b'\n' in data:
self.transport.loseConnection()
class EchoFactory(protocol.Factory):
protocol = Echo
class ConchTestOpenSSHProcess(protocol.ProcessProtocol):
"""
Test protocol for launching an OpenSSH client process.
@ivar deferred: Set by whatever uses this object. Accessed using
L{_getDeferred}, which destroys the value so the Deferred is not
fired twice. Fires when the process is terminated.
"""
deferred = None
buf = b''
def _getDeferred(self):
d, self.deferred = self.deferred, None
return d
def outReceived(self, data):
self.buf += data
def processEnded(self, reason):
"""
Called when the process has ended.
@param reason: a Failure giving the reason for the process' end.
"""
if reason.value.exitCode != 0:
self._getDeferred().errback(
ConchError("exit code was not 0: {}".format(
reason.value.exitCode)))
else:
buf = self.buf.replace(b'\r\n', b'\n')
self._getDeferred().callback(buf)
class ConchTestForwardingProcess(protocol.ProcessProtocol):
"""
Manages a third-party process which launches a server.
Uses L{ConchTestForwardingPort} to connect to the third-party server.
Once L{ConchTestForwardingPort} has disconnected, kill the process and fire
a Deferred with the data received by the L{ConchTestForwardingPort}.
@ivar deferred: Set by whatever uses this object. Accessed using
L{_getDeferred}, which destroys the value so the Deferred is not
fired twice. Fires when the process is terminated.
"""
deferred = None
def __init__(self, port, data):
"""
@type port: L{int}
@param port: The port on which the third-party server is listening.
(it is assumed that the server is running on localhost).
@type data: L{str}
@param data: This is sent to the third-party server. Must end with '\n'
in order to trigger a disconnect.
"""
self.port = port
self.buffer = None
self.data = data
def _getDeferred(self):
d, self.deferred = self.deferred, None
return d
def connectionMade(self):
self._connect()
def _connect(self):
"""
Connect to the server, which is often a third-party process.
Tries to reconnect if it fails because we have no way of determining
exactly when the port becomes available for listening -- we can only
know when the process starts.
"""
cc = protocol.ClientCreator(reactor, ConchTestForwardingPort, self,
self.data)
d = cc.connectTCP('127.0.0.1', self.port)
d.addErrback(self._ebConnect)
return d
def _ebConnect(self, f):
reactor.callLater(.1, self._connect)
def forwardingPortDisconnected(self, buffer):
"""
The network connection has died; save the buffer of output
from the network and attempt to quit the process gracefully,
and then (after the reactor has spun) send it a KILL signal.
"""
self.buffer = buffer
self.transport.write(b'\x03')
self.transport.loseConnection()
reactor.callLater(0, self._reallyDie)
def _reallyDie(self):
try:
self.transport.signalProcess('KILL')
except ProcessExitedAlready:
pass
def processEnded(self, reason):
"""
Fire the Deferred at self.deferred with the data collected
from the L{ConchTestForwardingPort} connection, if any.
"""
self._getDeferred().callback(self.buffer)
class ConchTestForwardingPort(protocol.Protocol):
"""
Connects to server launched by a third-party process (managed by
L{ConchTestForwardingProcess}) sends data, then reports whatever it
received back to the L{ConchTestForwardingProcess} once the connection
is ended.
"""
def __init__(self, protocol, data):
"""
@type protocol: L{ConchTestForwardingProcess}
@param protocol: The L{ProcessProtocol} which made this connection.
@type data: str
@param data: The data to be sent to the third-party server.
"""
self.protocol = protocol
self.data = data
def connectionMade(self):
self.buffer = b''
self.transport.write(self.data)
def dataReceived(self, data):
self.buffer += data
def connectionLost(self, reason):
self.protocol.forwardingPortDisconnected(self.buffer)
def _makeArgs(args, mod="conch"):
start = [sys.executable, '-c'
"""
### Twisted Preamble
import sys, os
path = os.path.abspath(sys.argv[0])
while os.path.dirname(path) != path:
if os.path.basename(path).startswith('Twisted'):
sys.path.insert(0, path)
break
path = os.path.dirname(path)
from twisted.conch.scripts.%s import run
run()""" % mod]
madeArgs = []
for arg in start + list(args):
if isinstance(arg, unicode):
arg = arg.encode("utf-8")
madeArgs.append(arg)
return madeArgs
class ConchServerSetupMixin:
if not cryptography:
skip = "can't run without cryptography"
if not pyasn1:
skip = "Cannot run without PyASN1"
# FIXME: https://twistedmatrix.com/trac/ticket/8506
# This should be un-skipped on Travis after the ticket is fixed. For now
# is enabled so that we can continue with fixing other stuff using Travis.
if _PYPY:
skip = 'PyPy known_host not working yet on Travis.'
realmFactory = staticmethod(lambda: ConchTestRealm(b'testuser'))
def _createFiles(self):
for f in ['rsa_test','rsa_test.pub','dsa_test','dsa_test.pub',
'kh_test']:
if os.path.exists(f):
os.remove(f)
with open('rsa_test','wb') as f:
f.write(privateRSA_openssh)
with open('rsa_test.pub','wb') as f:
f.write(publicRSA_openssh)
with open('dsa_test.pub','wb') as f:
f.write(publicDSA_openssh)
with open('dsa_test','wb') as f:
f.write(privateDSA_openssh)
os.chmod('dsa_test', 33152)
os.chmod('rsa_test', 33152)
with open('kh_test','wb') as f:
f.write(b'127.0.0.1 '+publicRSA_openssh)
def _getFreePort(self):
s = socket.socket()
s.bind(('', 0))
port = s.getsockname()[1]
s.close()
return port
def _makeConchFactory(self):
"""
Make a L{ConchTestServerFactory}, which allows us to start a
L{ConchTestServer} -- i.e. an actually listening conch.
"""
realm = self.realmFactory()
p = portal.Portal(realm)
p.registerChecker(conchTestPublicKeyChecker())
factory = ConchTestServerFactory()
factory.portal = p
return factory
def setUp(self):
self._createFiles()
self.conchFactory = self._makeConchFactory()
self.conchFactory.expectedLoseConnection = 1
self.conchServer = reactor.listenTCP(0, self.conchFactory,
interface="127.0.0.1")
self.echoServer = reactor.listenTCP(0, EchoFactory())
self.echoPort = self.echoServer.getHost().port
if HAS_IPV6:
self.echoServerV6 = reactor.listenTCP(0, EchoFactory(), interface="::1")
self.echoPortV6 = self.echoServerV6.getHost().port
def tearDown(self):
try:
self.conchFactory.proto.done = 1
except AttributeError:
pass
else:
self.conchFactory.proto.transport.loseConnection()
deferreds = [
defer.maybeDeferred(self.conchServer.stopListening),
defer.maybeDeferred(self.echoServer.stopListening),
]
if HAS_IPV6:
deferreds.append(defer.maybeDeferred(self.echoServerV6.stopListening))
return defer.gatherResults(deferreds)
class ForwardingMixin(ConchServerSetupMixin):
"""
Template class for tests of the Conch server's ability to forward arbitrary
protocols over SSH.
These tests are integration tests, not unit tests. They launch a Conch
server, a custom TCP server (just an L{EchoProtocol}) and then call
L{execute}.
L{execute} is implemented by subclasses of L{ForwardingMixin}. It should
cause an SSH client to connect to the Conch server, asking it to forward
data to the custom TCP server.
"""
def test_exec(self):
"""
Test that we can use whatever client to send the command "echo goodbye"
to the Conch server. Make sure we receive "goodbye" back from the
server.
"""
d = self.execute('echo goodbye', ConchTestOpenSSHProcess())
return d.addCallback(self.assertEqual, b'goodbye\n')
def test_localToRemoteForwarding(self):
"""
Test that we can use whatever client to forward a local port to a
specified port on the server.
"""
localPort = self._getFreePort()
process = ConchTestForwardingProcess(localPort, b'test\n')
d = self.execute('', process,
sshArgs='-N -L%i:127.0.0.1:%i'
% (localPort, self.echoPort))
d.addCallback(self.assertEqual, b'test\n')
return d
def test_remoteToLocalForwarding(self):
"""
Test that we can use whatever client to forward a port from the server
to a port locally.
"""
localPort = self._getFreePort()
process = ConchTestForwardingProcess(localPort, b'test\n')
d = self.execute('', process,
sshArgs='-N -R %i:127.0.0.1:%i'
% (localPort, self.echoPort))
d.addCallback(self.assertEqual, b'test\n')
return d
# Conventionally there is a separate adapter object which provides ISession for
# the user, but making the user provide ISession directly works too. This isn't
# a full implementation of ISession though, just enough to make these tests
# pass.
@implementer(ISession)
class RekeyAvatar(ConchUser):
"""
This avatar implements a shell which sends 60 numbered lines to whatever
connects to it, then closes the session with a 0 exit status.
60 lines is selected as being enough to send more than 2kB of traffic, the
amount the client is configured to initiate a rekey after.
"""
def __init__(self):
ConchUser.__init__(self)
self.channelLookup[b'session'] = SSHSession
def openShell(self, transport):
"""
Write 60 lines of data to the transport, then exit.
"""
proto = protocol.Protocol()
proto.makeConnection(transport)
transport.makeConnection(wrapProtocol(proto))
# Send enough bytes to the connection so that a rekey is triggered in
# the client.
def write(counter):
i = next(counter)
if i == 60:
call.stop()
transport.session.conn.sendRequest(
transport.session, b'exit-status', b'\x00\x00\x00\x00')
transport.loseConnection()
else:
line = "line #%02d\n" % (i,)
line = line.encode("utf-8")
transport.write(line)
# The timing for this loop is an educated guess (and/or the result of
# experimentation) to exercise the case where a packet is generated
# mid-rekey. Since the other side of the connection is (so far) the
# OpenSSH command line client, there's no easy way to determine when the
# rekey has been initiated. If there were, then generating a packet
# immediately at that time would be a better way to test the
# functionality being tested here.
call = LoopingCall(write, count())
call.start(0.01)
def closed(self):
"""
Ignore the close of the session.
"""
class RekeyRealm:
"""
This realm gives out new L{RekeyAvatar} instances for any avatar request.
"""
def requestAvatar(self, avatarID, mind, *interfaces):
return interfaces[0], RekeyAvatar(), lambda: None
class RekeyTestsMixin(ConchServerSetupMixin):
"""
TestCase mixin which defines tests exercising L{SSHTransportBase}'s handling
of rekeying messages.
"""
realmFactory = RekeyRealm
def test_clientRekey(self):
"""
After a client-initiated rekey is completed, application data continues
to be passed over the SSH connection.
"""
process = ConchTestOpenSSHProcess()
d = self.execute("", process, '-o RekeyLimit=2K')
def finished(result):
expectedResult = '\n'.join(['line #%02d' % (i,) for i in range(60)]) + '\n'
expectedResult = expectedResult.encode("utf-8")
self.assertEqual(result, expectedResult)
d.addCallback(finished)
return d
class OpenSSHClientMixin:
if not which('ssh'):
skip = "no ssh command-line client available"
def execute(self, remoteCommand, process, sshArgs=''):
"""
Connects to the SSH server started in L{ConchServerSetupMixin.setUp} by
running the 'ssh' command line tool.
@type remoteCommand: str
@param remoteCommand: The command (with arguments) to run on the
remote end.
@type process: L{ConchTestOpenSSHProcess}
@type sshArgs: str
@param sshArgs: Arguments to pass to the 'ssh' process.
@return: L{defer.Deferred}
"""
# PubkeyAcceptedKeyTypes does not exist prior to OpenSSH 7.0 so we
# first need to check if we can set it. If we can, -V will just print
# the version without doing anything else; if we can't, we will get a
# configuration error.
d = getProcessValue(
which('ssh')[0], ('-o', 'PubkeyAcceptedKeyTypes=ssh-dss', '-V'))
def hasPAKT(status):
if status == 0:
opts = '-oPubkeyAcceptedKeyTypes=ssh-dss '
else:
opts = ''
process.deferred = defer.Deferred()
# Pass -F /dev/null to avoid the user's configuration file from
# being loaded, as it may contain settings that cause our tests to
# fail or hang.
cmdline = ('ssh -2 -l testuser -p %i '
'-F /dev/null '
'-oUserKnownHostsFile=kh_test '
'-oPasswordAuthentication=no '
# Always use the RSA key, since that's the one in kh_test.
'-oHostKeyAlgorithms=ssh-rsa '
'-a '
'-i dsa_test ') + opts + sshArgs + \
' 127.0.0.1 ' + remoteCommand
port = self.conchServer.getHost().port
cmds = (cmdline % port).split()
encodedCmds = []
for cmd in cmds:
if isinstance(cmd, unicode):
cmd = cmd.encode("utf-8")
encodedCmds.append(cmd)
reactor.spawnProcess(process, which('ssh')[0], encodedCmds)
return process.deferred
return d.addCallback(hasPAKT)
class OpenSSHKeyExchangeTests(ConchServerSetupMixin, OpenSSHClientMixin,
unittest.TestCase):
"""
Tests L{SSHTransportBase}'s key exchange algorithm compatibility with
OpenSSH.
"""
def assertExecuteWithKexAlgorithm(self, keyExchangeAlgo):
"""
Call execute() method of L{OpenSSHClientMixin} with an ssh option that
forces the exclusive use of the key exchange algorithm specified by
keyExchangeAlgo
@type keyExchangeAlgo: L{str}
@param keyExchangeAlgo: The key exchange algorithm to use
@return: L{defer.Deferred}
"""
kexAlgorithms = []
try:
output = subprocess.check_output([which('ssh')[0], '-Q', 'kex'],
stderr=subprocess.STDOUT)
if not isinstance(output, str):
output = output.decode("utf-8")
kexAlgorithms = output.split()
except:
pass
if keyExchangeAlgo not in kexAlgorithms:
raise unittest.SkipTest(
"{} not supported by ssh client".format(
keyExchangeAlgo))
d = self.execute('echo hello', ConchTestOpenSSHProcess(),
'-oKexAlgorithms=' + keyExchangeAlgo)
return d.addCallback(self.assertEqual, b'hello\n')
def test_ECDHSHA256(self):
"""
The ecdh-sha2-nistp256 key exchange algorithm is compatible with
OpenSSH
"""
return self.assertExecuteWithKexAlgorithm(
'ecdh-sha2-nistp256')
def test_ECDHSHA384(self):
"""
The ecdh-sha2-nistp384 key exchange algorithm is compatible with
OpenSSH
"""
return self.assertExecuteWithKexAlgorithm(
'ecdh-sha2-nistp384')
def test_ECDHSHA521(self):
"""
The ecdh-sha2-nistp521 key exchange algorithm is compatible with
OpenSSH
"""
return self.assertExecuteWithKexAlgorithm(
'ecdh-sha2-nistp521')
def test_DH_GROUP14(self):
"""
The diffie-hellman-group14-sha1 key exchange algorithm is compatible
with OpenSSH.
"""
return self.assertExecuteWithKexAlgorithm(
'diffie-hellman-group14-sha1')
def test_DH_GROUP_EXCHANGE_SHA1(self):
"""
The diffie-hellman-group-exchange-sha1 key exchange algorithm is
compatible with OpenSSH.
"""
return self.assertExecuteWithKexAlgorithm(
'diffie-hellman-group-exchange-sha1')
def test_DH_GROUP_EXCHANGE_SHA256(self):
"""
The diffie-hellman-group-exchange-sha256 key exchange algorithm is
compatible with OpenSSH.
"""
return self.assertExecuteWithKexAlgorithm(
'diffie-hellman-group-exchange-sha256')
def test_unsupported_algorithm(self):
"""
The list of key exchange algorithms supported
by OpenSSH client is obtained with C{ssh -Q kex}.
"""
self.assertRaises(unittest.SkipTest,
self.assertExecuteWithKexAlgorithm,
'unsupported-algorithm')
class OpenSSHClientForwardingTests(ForwardingMixin, OpenSSHClientMixin,
unittest.TestCase):
"""
Connection forwarding tests run against the OpenSSL command line client.
"""
def test_localToRemoteForwardingV6(self):
"""
Forwarding of arbitrary IPv6 TCP connections via SSH.
"""
localPort = self._getFreePort()
process = ConchTestForwardingProcess(localPort, b'test\n')
d = self.execute('', process,
sshArgs='-N -L%i:[::1]:%i'
% (localPort, self.echoPortV6))
d.addCallback(self.assertEqual, b'test\n')
return d
if not HAS_IPV6:
test_localToRemoteForwardingV6.skip = "Requires IPv6 support"
class OpenSSHClientRekeyTests(RekeyTestsMixin, OpenSSHClientMixin,
unittest.TestCase):
"""
Rekeying tests run against the OpenSSL command line client.
"""
class CmdLineClientTests(ForwardingMixin, unittest.TestCase):
"""
Connection forwarding tests run against the Conch command line client.
"""
if runtime.platformType == 'win32':
skip = "can't run cmdline client on win32"
def execute(self, remoteCommand, process, sshArgs='', conchArgs=None):
"""
As for L{OpenSSHClientTestCase.execute}, except it runs the 'conch'
command line tool, not 'ssh'.
"""
if conchArgs is None:
conchArgs = []
process.deferred = defer.Deferred()
port = self.conchServer.getHost().port
cmd = ('-p {} -l testuser '
'--known-hosts kh_test '
'--user-authentications publickey '
'-a '
'-i dsa_test '
'-v '.format(port) + sshArgs +
' 127.0.0.1 ' + remoteCommand)
cmds = _makeArgs(conchArgs + cmd.split())
env = os.environ.copy()
env['PYTHONPATH'] = os.pathsep.join(sys.path)
encodedCmds = []
encodedEnv = {}
for cmd in cmds:
if isinstance(cmd, unicode):
cmd = cmd.encode("utf-8")
encodedCmds.append(cmd)
for var in env:
val = env[var]
if isinstance(var, unicode):
var = var.encode("utf-8")
if isinstance(val, unicode):
val = val.encode("utf-8")
encodedEnv[var] = val
reactor.spawnProcess(process, sys.executable, encodedCmds, env=encodedEnv)
return process.deferred
def test_runWithLogFile(self):
"""
It can store logs to a local file.
"""
def cb_check_log(result):
logContent = logPath.getContent()
self.assertIn(b'Log opened.', logContent)
logPath = filepath.FilePath(self.mktemp())
d = self.execute(
remoteCommand='echo goodbye',
process=ConchTestOpenSSHProcess(),
conchArgs=['--log', '--logfile', logPath.path,
'--host-key-algorithms', 'ssh-rsa']
)
d.addCallback(self.assertEqual, b'goodbye\n')
d.addCallback(cb_check_log)
return d
def test_runWithNoHostAlgorithmsSpecified(self):
"""
Do not use --host-key-algorithms flag on command line.
"""
d = self.execute(
remoteCommand='echo goodbye',
process=ConchTestOpenSSHProcess()
)
d.addCallback(self.assertEqual, b'goodbye\n')
return d
@@ -0,0 +1,761 @@
# Copyright (c) 2007-2010 Twisted Matrix Laboratories.
# See LICENSE for details
"""
This module tests twisted.conch.ssh.connection.
"""
from __future__ import division, absolute_import
import struct
from twisted.python.reflect import requireModule
cryptography = requireModule("cryptography")
from twisted.conch import error
if cryptography:
from twisted.conch.ssh import common, connection
else:
class connection:
class SSHConnection: pass
from twisted.conch.ssh import channel
from twisted.python.compat import long
from twisted.trial import unittest
from twisted.conch.test import test_userauth
class TestChannel(channel.SSHChannel):
"""
A mocked-up version of twisted.conch.ssh.channel.SSHChannel.
@ivar gotOpen: True if channelOpen has been called.
@type gotOpen: L{bool}
@ivar specificData: the specific channel open data passed to channelOpen.
@type specificData: L{bytes}
@ivar openFailureReason: the reason passed to openFailed.
@type openFailed: C{error.ConchError}
@ivar inBuffer: a C{list} of strings received by the channel.
@type inBuffer: C{list}
@ivar extBuffer: a C{list} of 2-tuples (type, extended data) of received by
the channel.
@type extBuffer: C{list}
@ivar numberRequests: the number of requests that have been made to this
channel.
@type numberRequests: L{int}
@ivar gotEOF: True if the other side sent EOF.
@type gotEOF: L{bool}
@ivar gotOneClose: True if the other side closed the connection.
@type gotOneClose: L{bool}
@ivar gotClosed: True if the channel is closed.
@type gotClosed: L{bool}
"""
name = b"TestChannel"
gotOpen = False
gotClosed = False
def logPrefix(self):
return "TestChannel %i" % self.id
def channelOpen(self, specificData):
"""
The channel is open. Set up the instance variables.
"""
self.gotOpen = True
self.specificData = specificData
self.inBuffer = []
self.extBuffer = []
self.numberRequests = 0
self.gotEOF = False
self.gotOneClose = False
self.gotClosed = False
def openFailed(self, reason):
"""
Opening the channel failed. Store the reason why.
"""
self.openFailureReason = reason
def request_test(self, data):
"""
A test request. Return True if data is 'data'.
@type data: L{bytes}
"""
self.numberRequests += 1
return data == b'data'
def dataReceived(self, data):
"""
Data was received. Store it in the buffer.
"""
self.inBuffer.append(data)
def extReceived(self, code, data):
"""
Extended data was received. Store it in the buffer.
"""
self.extBuffer.append((code, data))
def eofReceived(self):
"""
EOF was received. Remember it.
"""
self.gotEOF = True
def closeReceived(self):
"""
Close was received. Remember it.
"""
self.gotOneClose = True
def closed(self):
"""
The channel is closed. Rembember it.
"""
self.gotClosed = True
class TestAvatar:
"""
A mocked-up version of twisted.conch.avatar.ConchUser
"""
_ARGS_ERROR_CODE = 123
def lookupChannel(self, channelType, windowSize, maxPacket, data):
"""
The server wants us to return a channel. If the requested channel is
our TestChannel, return it, otherwise return None.
"""
if channelType == TestChannel.name:
return TestChannel(remoteWindow=windowSize,
remoteMaxPacket=maxPacket,
data=data, avatar=self)
elif channelType == b"conch-error-args":
# Raise a ConchError with backwards arguments to make sure the
# connection fixes it for us. This case should be deprecated and
# deleted eventually, but only after all of Conch gets the argument
# order right.
raise error.ConchError(
self._ARGS_ERROR_CODE, "error args in wrong order")
def gotGlobalRequest(self, requestType, data):
"""
The client has made a global request. If the global request is
'TestGlobal', return True. If the global request is 'TestData',
return True and the request-specific data we received. Otherwise,
return False.
"""
if requestType == b'TestGlobal':
return True
elif requestType == b'TestData':
return True, data
else:
return False
class TestConnection(connection.SSHConnection):
"""
A subclass of SSHConnection for testing.
@ivar channel: the current channel.
@type channel. C{TestChannel}
"""
if not cryptography:
skip = "Cannot run without cryptography"
def logPrefix(self):
return "TestConnection"
def global_TestGlobal(self, data):
"""
The other side made the 'TestGlobal' global request. Return True.
"""
return True
def global_Test_Data(self, data):
"""
The other side made the 'Test-Data' global request. Return True and
the data we received.
"""
return True, data
def channel_TestChannel(self, windowSize, maxPacket, data):
"""
The other side is requesting the TestChannel. Create a C{TestChannel}
instance, store it, and return it.
"""
self.channel = TestChannel(remoteWindow=windowSize,
remoteMaxPacket=maxPacket, data=data)
return self.channel
def channel_ErrorChannel(self, windowSize, maxPacket, data):
"""
The other side is requesting the ErrorChannel. Raise an exception.
"""
raise AssertionError('no such thing')
class ConnectionTests(unittest.TestCase):
if not cryptography:
skip = "Cannot run without cryptography"
if test_userauth.transport is None:
skip = "Cannot run without both cryptography and pyasn1"
def setUp(self):
self.transport = test_userauth.FakeTransport(None)
self.transport.avatar = TestAvatar()
self.conn = TestConnection()
self.conn.transport = self.transport
self.conn.serviceStarted()
def _openChannel(self, channel):
"""
Open the channel with the default connection.
"""
self.conn.openChannel(channel)
self.transport.packets = self.transport.packets[:-1]
self.conn.ssh_CHANNEL_OPEN_CONFIRMATION(struct.pack('>2L',
channel.id, 255) + b'\x00\x02\x00\x00\x00\x00\x80\x00')
def tearDown(self):
self.conn.serviceStopped()
def test_linkAvatar(self):
"""
Test that the connection links itself to the avatar in the
transport.
"""
self.assertIs(self.transport.avatar.conn, self.conn)
def test_serviceStopped(self):
"""
Test that serviceStopped() closes any open channels.
"""
channel1 = TestChannel()
channel2 = TestChannel()
self.conn.openChannel(channel1)
self.conn.openChannel(channel2)
self.conn.ssh_CHANNEL_OPEN_CONFIRMATION(b'\x00\x00\x00\x00' * 4)
self.assertTrue(channel1.gotOpen)
self.assertFalse(channel1.gotClosed)
self.assertFalse(channel2.gotOpen)
self.assertFalse(channel2.gotClosed)
self.conn.serviceStopped()
self.assertTrue(channel1.gotClosed)
self.assertFalse(channel2.gotOpen)
self.assertFalse(channel2.gotClosed)
from twisted.internet.error import ConnectionLost
self.assertIsInstance(channel2.openFailureReason,
ConnectionLost)
def test_GLOBAL_REQUEST(self):
"""
Test that global request packets are dispatched to the global_*
methods and the return values are translated into success or failure
messages.
"""
self.conn.ssh_GLOBAL_REQUEST(common.NS(b'TestGlobal') + b'\xff')
self.assertEqual(self.transport.packets,
[(connection.MSG_REQUEST_SUCCESS, b'')])
self.transport.packets = []
self.conn.ssh_GLOBAL_REQUEST(common.NS(b'TestData') + b'\xff' +
b'test data')
self.assertEqual(self.transport.packets,
[(connection.MSG_REQUEST_SUCCESS, b'test data')])
self.transport.packets = []
self.conn.ssh_GLOBAL_REQUEST(common.NS(b'TestBad') + b'\xff')
self.assertEqual(self.transport.packets,
[(connection.MSG_REQUEST_FAILURE, b'')])
self.transport.packets = []
self.conn.ssh_GLOBAL_REQUEST(common.NS(b'TestGlobal') + b'\x00')
self.assertEqual(self.transport.packets, [])
def test_REQUEST_SUCCESS(self):
"""
Test that global request success packets cause the Deferred to be
called back.
"""
d = self.conn.sendGlobalRequest(b'request', b'data', True)
self.conn.ssh_REQUEST_SUCCESS(b'data')
def check(data):
self.assertEqual(data, b'data')
d.addCallback(check)
d.addErrback(self.fail)
return d
def test_REQUEST_FAILURE(self):
"""
Test that global request failure packets cause the Deferred to be
erred back.
"""
d = self.conn.sendGlobalRequest(b'request', b'data', True)
self.conn.ssh_REQUEST_FAILURE(b'data')
def check(f):
self.assertEqual(f.value.data, b'data')
d.addCallback(self.fail)
d.addErrback(check)
return d
def test_CHANNEL_OPEN(self):
"""
Test that open channel packets cause a channel to be created and
opened or a failure message to be returned.
"""
del self.transport.avatar
self.conn.ssh_CHANNEL_OPEN(common.NS(b'TestChannel') +
b'\x00\x00\x00\x01' * 4)
self.assertTrue(self.conn.channel.gotOpen)
self.assertEqual(self.conn.channel.conn, self.conn)
self.assertEqual(self.conn.channel.data, b'\x00\x00\x00\x01')
self.assertEqual(self.conn.channel.specificData, b'\x00\x00\x00\x01')
self.assertEqual(self.conn.channel.remoteWindowLeft, 1)
self.assertEqual(self.conn.channel.remoteMaxPacket, 1)
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_OPEN_CONFIRMATION,
b'\x00\x00\x00\x01\x00\x00\x00\x00\x00\x02\x00\x00'
b'\x00\x00\x80\x00')])
self.transport.packets = []
self.conn.ssh_CHANNEL_OPEN(common.NS(b'BadChannel') +
b'\x00\x00\x00\x02' * 4)
self.flushLoggedErrors()
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_OPEN_FAILURE,
b'\x00\x00\x00\x02\x00\x00\x00\x03' + common.NS(
b'unknown channel') + common.NS(b''))])
self.transport.packets = []
self.conn.ssh_CHANNEL_OPEN(common.NS(b'ErrorChannel') +
b'\x00\x00\x00\x02' * 4)
self.flushLoggedErrors()
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_OPEN_FAILURE,
b'\x00\x00\x00\x02\x00\x00\x00\x02' + common.NS(
b'unknown failure') + common.NS(b''))])
def _lookupChannelErrorTest(self, code):
"""
Deliver a request for a channel open which will result in an exception
being raised during channel lookup. Assert that an error response is
delivered as a result.
"""
self.transport.avatar._ARGS_ERROR_CODE = code
self.conn.ssh_CHANNEL_OPEN(
common.NS(b'conch-error-args') + b'\x00\x00\x00\x01' * 4)
errors = self.flushLoggedErrors(error.ConchError)
self.assertEqual(
len(errors), 1, "Expected one error, got: %r" % (errors,))
self.assertEqual(errors[0].value.args, (long(123), "error args in wrong order"))
self.assertEqual(
self.transport.packets,
[(connection.MSG_CHANNEL_OPEN_FAILURE,
# The response includes some bytes which identifying the
# associated request, as well as the error code (7b in hex) and
# the error message.
b'\x00\x00\x00\x01\x00\x00\x00\x7b' + common.NS(
b'error args in wrong order') + common.NS(b''))])
def test_lookupChannelError(self):
"""
If a C{lookupChannel} implementation raises L{error.ConchError} with the
arguments in the wrong order, a C{MSG_CHANNEL_OPEN} failure is still
sent in response to the message.
This is a temporary work-around until L{error.ConchError} is given
better attributes and all of the Conch code starts constructing
instances of it properly. Eventually this functionality should be
deprecated and then removed.
"""
self._lookupChannelErrorTest(123)
def test_lookupChannelErrorLongCode(self):
"""
Like L{test_lookupChannelError}, but for the case where the failure code
is represented as a L{long} instead of a L{int}.
"""
self._lookupChannelErrorTest(long(123))
def test_CHANNEL_OPEN_CONFIRMATION(self):
"""
Test that channel open confirmation packets cause the channel to be
notified that it's open.
"""
channel = TestChannel()
self.conn.openChannel(channel)
self.conn.ssh_CHANNEL_OPEN_CONFIRMATION(b'\x00\x00\x00\x00'*5)
self.assertEqual(channel.remoteWindowLeft, 0)
self.assertEqual(channel.remoteMaxPacket, 0)
self.assertEqual(channel.specificData, b'\x00\x00\x00\x00')
self.assertEqual(self.conn.channelsToRemoteChannel[channel],
0)
self.assertEqual(self.conn.localToRemoteChannel[0], 0)
def test_CHANNEL_OPEN_FAILURE(self):
"""
Test that channel open failure packets cause the channel to be
notified that its opening failed.
"""
channel = TestChannel()
self.conn.openChannel(channel)
self.conn.ssh_CHANNEL_OPEN_FAILURE(b'\x00\x00\x00\x00\x00\x00\x00'
b'\x01' + common.NS(b'failure!'))
self.assertEqual(channel.openFailureReason.args, (b'failure!', 1))
self.assertIsNone(self.conn.channels.get(channel))
def test_CHANNEL_WINDOW_ADJUST(self):
"""
Test that channel window adjust messages add bytes to the channel
window.
"""
channel = TestChannel()
self._openChannel(channel)
oldWindowSize = channel.remoteWindowLeft
self.conn.ssh_CHANNEL_WINDOW_ADJUST(b'\x00\x00\x00\x00\x00\x00\x00'
b'\x01')
self.assertEqual(channel.remoteWindowLeft, oldWindowSize + 1)
def test_CHANNEL_DATA(self):
"""
Test that channel data messages are passed up to the channel, or
cause the channel to be closed if the data is too large.
"""
channel = TestChannel(localWindow=6, localMaxPacket=5)
self._openChannel(channel)
self.conn.ssh_CHANNEL_DATA(b'\x00\x00\x00\x00' + common.NS(b'data'))
self.assertEqual(channel.inBuffer, [b'data'])
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_WINDOW_ADJUST, b'\x00\x00\x00\xff'
b'\x00\x00\x00\x04')])
self.transport.packets = []
longData = b'a' * (channel.localWindowLeft + 1)
self.conn.ssh_CHANNEL_DATA(b'\x00\x00\x00\x00' + common.NS(longData))
self.assertEqual(channel.inBuffer, [b'data'])
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_CLOSE, b'\x00\x00\x00\xff')])
channel = TestChannel()
self._openChannel(channel)
bigData = b'a' * (channel.localMaxPacket + 1)
self.transport.packets = []
self.conn.ssh_CHANNEL_DATA(b'\x00\x00\x00\x01' + common.NS(bigData))
self.assertEqual(channel.inBuffer, [])
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_CLOSE, b'\x00\x00\x00\xff')])
def test_CHANNEL_EXTENDED_DATA(self):
"""
Test that channel extended data messages are passed up to the channel,
or cause the channel to be closed if they're too big.
"""
channel = TestChannel(localWindow=6, localMaxPacket=5)
self._openChannel(channel)
self.conn.ssh_CHANNEL_EXTENDED_DATA(b'\x00\x00\x00\x00\x00\x00\x00'
b'\x00' + common.NS(b'data'))
self.assertEqual(channel.extBuffer, [(0, b'data')])
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_WINDOW_ADJUST, b'\x00\x00\x00\xff'
b'\x00\x00\x00\x04')])
self.transport.packets = []
longData = b'a' * (channel.localWindowLeft + 1)
self.conn.ssh_CHANNEL_EXTENDED_DATA(b'\x00\x00\x00\x00\x00\x00\x00'
b'\x00' + common.NS(longData))
self.assertEqual(channel.extBuffer, [(0, b'data')])
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_CLOSE, b'\x00\x00\x00\xff')])
channel = TestChannel()
self._openChannel(channel)
bigData = b'a' * (channel.localMaxPacket + 1)
self.transport.packets = []
self.conn.ssh_CHANNEL_EXTENDED_DATA(b'\x00\x00\x00\x01\x00\x00\x00'
b'\x00' + common.NS(bigData))
self.assertEqual(channel.extBuffer, [])
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_CLOSE, b'\x00\x00\x00\xff')])
def test_CHANNEL_EOF(self):
"""
Test that channel eof messages are passed up to the channel.
"""
channel = TestChannel()
self._openChannel(channel)
self.conn.ssh_CHANNEL_EOF(b'\x00\x00\x00\x00')
self.assertTrue(channel.gotEOF)
def test_CHANNEL_CLOSE(self):
"""
Test that channel close messages are passed up to the channel. Also,
test that channel.close() is called if both sides are closed when this
message is received.
"""
channel = TestChannel()
self._openChannel(channel)
self.assertTrue(channel.gotOpen)
self.assertFalse(channel.gotOneClose)
self.assertFalse(channel.gotClosed)
self.conn.sendClose(channel)
self.conn.ssh_CHANNEL_CLOSE(b'\x00\x00\x00\x00')
self.assertTrue(channel.gotOneClose)
self.assertTrue(channel.gotClosed)
def test_CHANNEL_REQUEST_success(self):
"""
Test that channel requests that succeed send MSG_CHANNEL_SUCCESS.
"""
channel = TestChannel()
self._openChannel(channel)
self.conn.ssh_CHANNEL_REQUEST(b'\x00\x00\x00\x00' + common.NS(b'test')
+ b'\x00')
self.assertEqual(channel.numberRequests, 1)
d = self.conn.ssh_CHANNEL_REQUEST(b'\x00\x00\x00\x00' + common.NS(
b'test') + b'\xff' + b'data')
def check(result):
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_SUCCESS, b'\x00\x00\x00\xff')])
d.addCallback(check)
return d
def test_CHANNEL_REQUEST_failure(self):
"""
Test that channel requests that fail send MSG_CHANNEL_FAILURE.
"""
channel = TestChannel()
self._openChannel(channel)
d = self.conn.ssh_CHANNEL_REQUEST(b'\x00\x00\x00\x00' + common.NS(
b'test') + b'\xff')
def check(result):
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_FAILURE, b'\x00\x00\x00\xff'
)])
d.addCallback(self.fail)
d.addErrback(check)
return d
def test_CHANNEL_REQUEST_SUCCESS(self):
"""
Test that channel request success messages cause the Deferred to be
called back.
"""
channel = TestChannel()
self._openChannel(channel)
d = self.conn.sendRequest(channel, b'test', b'data', True)
self.conn.ssh_CHANNEL_SUCCESS(b'\x00\x00\x00\x00')
def check(result):
self.assertTrue(result)
return d
def test_CHANNEL_REQUEST_FAILURE(self):
"""
Test that channel request failure messages cause the Deferred to be
erred back.
"""
channel = TestChannel()
self._openChannel(channel)
d = self.conn.sendRequest(channel, b'test', b'', True)
self.conn.ssh_CHANNEL_FAILURE(b'\x00\x00\x00\x00')
def check(result):
self.assertEqual(result.value.value, 'channel request failed')
d.addCallback(self.fail)
d.addErrback(check)
return d
def test_sendGlobalRequest(self):
"""
Test that global request messages are sent in the right format.
"""
d = self.conn.sendGlobalRequest(b'wantReply', b'data', True)
# must be added to prevent errbacking during teardown
d.addErrback(lambda failure: None)
self.conn.sendGlobalRequest(b'noReply', b'', False)
self.assertEqual(self.transport.packets,
[(connection.MSG_GLOBAL_REQUEST, common.NS(b'wantReply') +
b'\xffdata'),
(connection.MSG_GLOBAL_REQUEST, common.NS(b'noReply') +
b'\x00')])
self.assertEqual(self.conn.deferreds, {'global':[d]})
def test_openChannel(self):
"""
Test that open channel messages are sent in the right format.
"""
channel = TestChannel()
self.conn.openChannel(channel, b'aaaa')
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_OPEN, common.NS(b'TestChannel') +
b'\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x80\x00aaaa')])
self.assertEqual(channel.id, 0)
self.assertEqual(self.conn.localChannelID, 1)
def test_sendRequest(self):
"""
Test that channel request messages are sent in the right format.
"""
channel = TestChannel()
self._openChannel(channel)
d = self.conn.sendRequest(channel, b'test', b'test', True)
# needed to prevent errbacks during teardown.
d.addErrback(lambda failure: None)
self.conn.sendRequest(channel, b'test2', b'', False)
channel.localClosed = True # emulate sending a close message
self.conn.sendRequest(channel, b'test3', b'', True)
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_REQUEST, b'\x00\x00\x00\xff' +
common.NS(b'test') + b'\x01test'),
(connection.MSG_CHANNEL_REQUEST, b'\x00\x00\x00\xff' +
common.NS(b'test2') + b'\x00')])
self.assertEqual(self.conn.deferreds[0], [d])
def test_adjustWindow(self):
"""
Test that channel window adjust messages cause bytes to be added
to the window.
"""
channel = TestChannel(localWindow=5)
self._openChannel(channel)
channel.localWindowLeft = 0
self.conn.adjustWindow(channel, 1)
self.assertEqual(channel.localWindowLeft, 1)
channel.localClosed = True
self.conn.adjustWindow(channel, 2)
self.assertEqual(channel.localWindowLeft, 1)
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_WINDOW_ADJUST, b'\x00\x00\x00\xff'
b'\x00\x00\x00\x01')])
def test_sendData(self):
"""
Test that channel data messages are sent in the right format.
"""
channel = TestChannel()
self._openChannel(channel)
self.conn.sendData(channel, b'a')
channel.localClosed = True
self.conn.sendData(channel, b'b')
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_DATA, b'\x00\x00\x00\xff' +
common.NS(b'a'))])
def test_sendExtendedData(self):
"""
Test that channel extended data messages are sent in the right format.
"""
channel = TestChannel()
self._openChannel(channel)
self.conn.sendExtendedData(channel, 1, b'test')
channel.localClosed = True
self.conn.sendExtendedData(channel, 2, b'test2')
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_EXTENDED_DATA, b'\x00\x00\x00\xff' +
b'\x00\x00\x00\x01' + common.NS(b'test'))])
def test_sendEOF(self):
"""
Test that channel EOF messages are sent in the right format.
"""
channel = TestChannel()
self._openChannel(channel)
self.conn.sendEOF(channel)
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_EOF, b'\x00\x00\x00\xff')])
channel.localClosed = True
self.conn.sendEOF(channel)
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_EOF, b'\x00\x00\x00\xff')])
def test_sendClose(self):
"""
Test that channel close messages are sent in the right format.
"""
channel = TestChannel()
self._openChannel(channel)
self.conn.sendClose(channel)
self.assertTrue(channel.localClosed)
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_CLOSE, b'\x00\x00\x00\xff')])
self.conn.sendClose(channel)
self.assertEqual(self.transport.packets,
[(connection.MSG_CHANNEL_CLOSE, b'\x00\x00\x00\xff')])
channel2 = TestChannel()
self._openChannel(channel2)
self.assertTrue(channel2.gotOpen)
self.assertFalse(channel2.gotClosed)
channel2.remoteClosed = True
self.conn.sendClose(channel2)
self.assertTrue(channel2.gotClosed)
def test_getChannelWithAvatar(self):
"""
Test that getChannel dispatches to the avatar when an avatar is
present. Correct functioning without the avatar is verified in
test_CHANNEL_OPEN.
"""
channel = self.conn.getChannel(b'TestChannel', 50, 30, b'data')
self.assertEqual(channel.data, b'data')
self.assertEqual(channel.remoteWindowLeft, 50)
self.assertEqual(channel.remoteMaxPacket, 30)
self.assertRaises(error.ConchError, self.conn.getChannel,
b'BadChannel', 50, 30, b'data')
def test_gotGlobalRequestWithoutAvatar(self):
"""
Test that gotGlobalRequests dispatches to global_* without an avatar.
"""
del self.transport.avatar
self.assertTrue(self.conn.gotGlobalRequest(b'TestGlobal', b'data'))
self.assertEqual(self.conn.gotGlobalRequest(b'Test-Data', b'data'),
(True, b'data'))
self.assertFalse(self.conn.gotGlobalRequest(b'BadGlobal', b'data'))
def test_channelClosedCausesLeftoverChannelDeferredsToErrback(self):
"""
Whenever an SSH channel gets closed any Deferred that was returned by a
sendRequest() on its parent connection must be errbacked.
"""
channel = TestChannel()
self._openChannel(channel)
d = self.conn.sendRequest(
channel, b"dummyrequest", b"dummydata", wantReply=1)
d = self.assertFailure(d, error.ConchError)
self.conn.channelClosed(channel)
return d
class CleanConnectionShutdownTests(unittest.TestCase):
"""
Check whether correct cleanup is performed on connection shutdown.
"""
if not cryptography:
skip = "Cannot run without cryptography"
if test_userauth.transport is None:
skip = "Cannot run without both cryptography and pyasn1"
def setUp(self):
self.transport = test_userauth.FakeTransport(None)
self.transport.avatar = TestAvatar()
self.conn = TestConnection()
self.conn.transport = self.transport
def test_serviceStoppedCausesLeftoverGlobalDeferredsToErrback(self):
"""
Once the service is stopped any leftover global deferred returned by
a sendGlobalRequest() call must be errbacked.
"""
self.conn.serviceStarted()
d = self.conn.sendGlobalRequest(
b"dummyrequest", b"dummydata", wantReply=1)
d = self.assertFailure(d, error.ConchError)
self.conn.serviceStopped()
return d
@@ -0,0 +1,333 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.conch.client.default}.
"""
from __future__ import absolute_import, division
import sys
from twisted.python.reflect import requireModule
if requireModule('cryptography') and requireModule('pyasn1'):
from twisted.conch.client.agent import SSHAgentClient
from twisted.conch.client.default import SSHUserAuthClient
from twisted.conch.client.options import ConchOptions
from twisted.conch.client import default
from twisted.conch.ssh.keys import Key
skip = None
else:
skip = "cryptography and PyASN1 required for twisted.conch.client.default."
from twisted.trial.unittest import TestCase
from twisted.python.filepath import FilePath
from twisted.conch.error import ConchError
from twisted.conch.test import keydata
from twisted.test.proto_helpers import StringTransport
from twisted.python.compat import nativeString
from twisted.python.runtime import platform
if platform.isWindows():
windowsSkip = (
"genericAnswers and getPassword does not work on Windows."
" Should be fixed as part of fixing bug 6409 and 6410")
else:
windowsSkip = skip
ttySkip = None
if not sys.stdin.isatty():
ttySkip = "sys.stdin is not an interactive tty"
if not sys.stdout.isatty():
ttySkip = "sys.stdout is not an interactive tty"
class SSHUserAuthClientTests(TestCase):
"""
Tests for L{SSHUserAuthClient}.
@type rsaPublic: L{Key}
@ivar rsaPublic: A public RSA key.
"""
def setUp(self):
self.rsaPublic = Key.fromString(keydata.publicRSA_openssh)
self.tmpdir = FilePath(self.mktemp())
self.tmpdir.makedirs()
self.rsaFile = self.tmpdir.child('id_rsa')
self.rsaFile.setContent(keydata.privateRSA_openssh)
self.tmpdir.child('id_rsa.pub').setContent(keydata.publicRSA_openssh)
def test_signDataWithAgent(self):
"""
When connected to an agent, L{SSHUserAuthClient} can use it to
request signatures of particular data with a particular L{Key}.
"""
client = SSHUserAuthClient(b"user", ConchOptions(), None)
agent = SSHAgentClient()
transport = StringTransport()
agent.makeConnection(transport)
client.keyAgent = agent
cleartext = b"Sign here"
client.signData(self.rsaPublic, cleartext)
self.assertEqual(
transport.value(),
b"\x00\x00\x01\x2d\r\x00\x00\x01\x17" + self.rsaPublic.blob() +
b"\x00\x00\x00\t" + cleartext +
b"\x00\x00\x00\x00")
def test_agentGetPublicKey(self):
"""
L{SSHUserAuthClient} looks up public keys from the agent using the
L{SSHAgentClient} class. That L{SSHAgentClient.getPublicKey} returns a
L{Key} object with one of the public keys in the agent. If no more
keys are present, it returns L{None}.
"""
agent = SSHAgentClient()
agent.blobs = [self.rsaPublic.blob()]
key = agent.getPublicKey()
self.assertTrue(key.isPublic())
self.assertEqual(key, self.rsaPublic)
self.assertIsNone(agent.getPublicKey())
def test_getPublicKeyFromFile(self):
"""
L{SSHUserAuthClient.getPublicKey()} is able to get a public key from
the first file described by its options' C{identitys} list, and return
the corresponding public L{Key} object.
"""
options = ConchOptions()
options.identitys = [self.rsaFile.path]
client = SSHUserAuthClient(b"user", options, None)
key = client.getPublicKey()
self.assertTrue(key.isPublic())
self.assertEqual(key, self.rsaPublic)
def test_getPublicKeyAgentFallback(self):
"""
If an agent is present, but doesn't return a key,
L{SSHUserAuthClient.getPublicKey} continue with the normal key lookup.
"""
options = ConchOptions()
options.identitys = [self.rsaFile.path]
agent = SSHAgentClient()
client = SSHUserAuthClient(b"user", options, None)
client.keyAgent = agent
key = client.getPublicKey()
self.assertTrue(key.isPublic())
self.assertEqual(key, self.rsaPublic)
def test_getPublicKeyBadKeyError(self):
"""
If L{keys.Key.fromFile} raises a L{keys.BadKeyError}, the
L{SSHUserAuthClient.getPublicKey} tries again to get a public key by
calling itself recursively.
"""
options = ConchOptions()
self.tmpdir.child('id_dsa.pub').setContent(keydata.publicDSA_openssh)
dsaFile = self.tmpdir.child('id_dsa')
dsaFile.setContent(keydata.privateDSA_openssh)
options.identitys = [self.rsaFile.path, dsaFile.path]
self.tmpdir.child('id_rsa.pub').setContent(b'not a key!')
client = SSHUserAuthClient(b"user", options, None)
key = client.getPublicKey()
self.assertTrue(key.isPublic())
self.assertEqual(key, Key.fromString(keydata.publicDSA_openssh))
self.assertEqual(client.usedFiles, [self.rsaFile.path, dsaFile.path])
def test_getPrivateKey(self):
"""
L{SSHUserAuthClient.getPrivateKey} will load a private key from the
last used file populated by L{SSHUserAuthClient.getPublicKey}, and
return a L{Deferred} which fires with the corresponding private L{Key}.
"""
rsaPrivate = Key.fromString(keydata.privateRSA_openssh)
options = ConchOptions()
options.identitys = [self.rsaFile.path]
client = SSHUserAuthClient(b"user", options, None)
# Populate the list of used files
client.getPublicKey()
def _cbGetPrivateKey(key):
self.assertFalse(key.isPublic())
self.assertEqual(key, rsaPrivate)
return client.getPrivateKey().addCallback(_cbGetPrivateKey)
def test_getPrivateKeyPassphrase(self):
"""
L{SSHUserAuthClient} can get a private key from a file, and return a
Deferred called back with a private L{Key} object, even if the key is
encrypted.
"""
rsaPrivate = Key.fromString(keydata.privateRSA_openssh)
passphrase = b'this is the passphrase'
self.rsaFile.setContent(rsaPrivate.toString('openssh', passphrase))
options = ConchOptions()
options.identitys = [self.rsaFile.path]
client = SSHUserAuthClient(b"user", options, None)
# Populate the list of used files
client.getPublicKey()
def _getPassword(prompt):
self.assertEqual(
prompt,
"Enter passphrase for key '%s': " % (self.rsaFile.path,))
return nativeString(passphrase)
def _cbGetPrivateKey(key):
self.assertFalse(key.isPublic())
self.assertEqual(key, rsaPrivate)
self.patch(client, '_getPassword', _getPassword)
return client.getPrivateKey().addCallback(_cbGetPrivateKey)
def test_getPassword(self):
"""
Get the password using
L{twisted.conch.client.default.SSHUserAuthClient.getPassword}
"""
class FakeTransport:
def __init__(self, host):
self.transport = self
self.host = host
def getPeer(self):
return self
options = ConchOptions()
client = SSHUserAuthClient(b"user", options, None)
client.transport = FakeTransport("127.0.0.1")
def getpass(prompt):
self.assertEqual(prompt, "user@127.0.0.1's password: ")
return 'bad password'
self.patch(default.getpass, 'getpass', getpass)
d = client.getPassword()
d.addCallback(self.assertEqual, b'bad password')
return d
test_getPassword.skip = windowsSkip or ttySkip
def test_getPasswordPrompt(self):
"""
Get the password using
L{twisted.conch.client.default.SSHUserAuthClient.getPassword}
using a different prompt.
"""
options = ConchOptions()
client = SSHUserAuthClient(b"user", options, None)
prompt = b"Give up your password"
def getpass(p):
self.assertEqual(p, nativeString(prompt))
return 'bad password'
self.patch(default.getpass, 'getpass', getpass)
d = client.getPassword(prompt)
d.addCallback(self.assertEqual, b'bad password')
return d
test_getPasswordPrompt.skip = windowsSkip or ttySkip
def test_getPasswordConchError(self):
"""
Get the password using
L{twisted.conch.client.default.SSHUserAuthClient.getPassword}
and trigger a {twisted.conch.error import ConchError}.
"""
options = ConchOptions()
client = SSHUserAuthClient(b"user", options, None)
def getpass(prompt):
raise KeyboardInterrupt("User pressed CTRL-C")
self.patch(default.getpass, 'getpass', getpass)
stdout, stdin = sys.stdout, sys.stdin
d = client.getPassword(b'?')
@d.addErrback
def check_sys(fail):
self.assertEqual(
[stdout, stdin], [sys.stdout, sys.stdin])
return fail
self.assertFailure(d, ConchError)
test_getPasswordConchError.skip = windowsSkip or ttySkip
def test_getGenericAnswers(self):
"""
L{twisted.conch.client.default.SSHUserAuthClient.getGenericAnswers}
"""
options = ConchOptions()
client = SSHUserAuthClient(b"user", options, None)
def getpass(prompt):
self.assertEqual(prompt, "pass prompt")
return "getpass"
self.patch(default.getpass, 'getpass', getpass)
def raw_input(prompt):
self.assertEqual(prompt, "raw_input prompt")
return "raw_input"
self.patch(default, 'raw_input', raw_input)
d = client.getGenericAnswers(
b"Name", b"Instruction", [
(b"pass prompt", False), (b"raw_input prompt", True)])
d.addCallback(
self.assertListEqual, ["getpass", "raw_input"])
return d
test_getGenericAnswers.skip = windowsSkip or ttySkip
class ConchOptionsParsing(TestCase):
"""
Options parsing.
"""
def test_macs(self):
"""
Specify MAC algorithms.
"""
opts = ConchOptions()
e = self.assertRaises(SystemExit, opts.opt_macs, "invalid-mac")
self.assertIn("Unknown mac type", e.code)
opts = ConchOptions()
opts.opt_macs("hmac-sha2-512")
self.assertEqual(opts['macs'], [b"hmac-sha2-512"])
opts.opt_macs(b"hmac-sha2-512")
self.assertEqual(opts['macs'], [b"hmac-sha2-512"])
opts.opt_macs("hmac-sha2-256,hmac-sha1,hmac-md5")
self.assertEqual(opts['macs'], [b"hmac-sha2-256", b"hmac-sha1", b"hmac-md5"])
def test_host_key_algorithms(self):
"""
Specify host key algorithms.
"""
opts = ConchOptions()
e = self.assertRaises(SystemExit, opts.opt_host_key_algorithms, "invalid-key")
self.assertIn("Unknown host key type", e.code)
opts = ConchOptions()
opts.opt_host_key_algorithms("ssh-rsa")
self.assertEqual(opts['host-key-algorithms'], [b"ssh-rsa"])
opts.opt_host_key_algorithms(b"ssh-dss")
self.assertEqual(opts['host-key-algorithms'], [b"ssh-dss"])
opts.opt_host_key_algorithms("ssh-rsa,ssh-dss")
self.assertEqual(opts['host-key-algorithms'], [b"ssh-rsa", b"ssh-dss"])
@@ -0,0 +1,63 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.conch.ssh.forwarding}.
"""
from __future__ import division, absolute_import
from twisted.python.reflect import requireModule
cryptography = requireModule("cryptography")
if cryptography:
from twisted.conch.ssh import forwarding
from twisted.internet.address import IPv6Address
from twisted.trial import unittest
from twisted.internet.test.test_endpoints import deterministicResolvingReactor
from twisted.test.proto_helpers import MemoryReactorClock, StringTransport
class TestSSHConnectForwardingChannel(unittest.TestCase):
"""
Unit and integration tests for L{SSHConnectForwardingChannel}.
"""
if not cryptography:
skip = "Cannot run without cryptography"
def makeTCPConnection(self, reactor):
"""
Fake that connection was established for first connectTCP request made
on C{reactor}.
@param reactor: Reactor on which to fake the connection.
@type reactor: A reactor.
"""
factory = reactor.tcpClients[0][2]
connector = reactor.connectors[0]
protocol = factory.buildProtocol(None)
transport = StringTransport(peerAddress=connector.getDestination())
protocol.makeConnection(transport)
def test_channelOpenHostnameRequests(self):
"""
When a hostname is sent as part of forwarding requests, it
is resolved using HostnameEndpoint's resolver.
"""
sut = forwarding.SSHConnectForwardingChannel(
hostport=('fwd.example.org', 1234))
# Patch channel and resolver to not touch the network.
memoryReactor = MemoryReactorClock()
sut._reactor = deterministicResolvingReactor(memoryReactor, ['::1'])
sut.channelOpen(None)
self.makeTCPConnection(memoryReactor)
self.successResultOf(sut._channelOpenDeferred)
# Channel is connected using a forwarding client to the resolved
# address of the requested host.
self.assertIsInstance(sut.client, forwarding.SSHForwardingClient)
self.assertEqual(
IPv6Address('TCP', '::1', 1234), sut.client.transport.getPeer())
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,77 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for the command-line interfaces to conch.
"""
from twisted.python.reflect import requireModule
if requireModule('pyasn1'):
pyasn1Skip = None
else:
pyasn1Skip = "Cannot run without PyASN1"
if requireModule('cryptography'):
cryptoSkip = None
else:
cryptoSkip = "can't run w/o cryptography"
if requireModule('tty'):
ttySkip = None
else:
ttySkip = "can't run w/o tty"
try:
import Tkinter
except ImportError:
tkskip = "can't run w/o Tkinter"
else:
try:
Tkinter.Tk().destroy()
except Tkinter.TclError as e:
tkskip = "Can't test Tkinter: " + str(e)
else:
tkskip = None
from twisted.trial.unittest import TestCase
from twisted.scripts.test.test_scripts import ScriptTestsMixin
from twisted.python.test.test_shellcomp import ZshScriptTestMixin
class ScriptTests(TestCase, ScriptTestsMixin):
"""
Tests for the Conch scripts.
"""
skip = pyasn1Skip or cryptoSkip
def test_conch(self):
self.scriptTest("conch/conch")
test_conch.skip = ttySkip or skip
def test_cftp(self):
self.scriptTest("conch/cftp")
test_cftp.skip = ttySkip or skip
def test_ckeygen(self):
self.scriptTest("conch/ckeygen")
def test_tkconch(self):
self.scriptTest("conch/tkconch")
test_tkconch.skip = tkskip or skip
class ZshIntegrationTests(TestCase, ZshScriptTestMixin):
"""
Test that zsh completion functions are generated without error
"""
generateFor = [('conch', 'twisted.conch.scripts.conch.ClientOptions'),
('cftp', 'twisted.conch.scripts.cftp.ClientOptions'),
('ckeygen', 'twisted.conch.scripts.ckeygen.GeneralOptions'),
('tkconch', 'twisted.conch.scripts.tkconch.GeneralOptions'),
]
@@ -0,0 +1,997 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.conch.ssh}.
"""
from __future__ import division, absolute_import
import struct
from twisted.python.reflect import requireModule
cryptography = requireModule("cryptography")
pyasn1 = requireModule("pyasn1")
if cryptography:
from twisted.conch.ssh import common, forwarding, session, _kex
from twisted.conch import avatar, error
else:
class avatar:
class ConchUser: pass
from twisted.conch.test.keydata import publicRSA_openssh, privateRSA_openssh
from twisted.conch.test.keydata import publicDSA_openssh, privateDSA_openssh
from twisted.cred import portal
from twisted.cred.error import UnauthorizedLogin
from twisted.internet import defer, protocol, reactor
from twisted.internet.error import ProcessTerminated
from twisted.python import failure, log
from twisted.trial import unittest
from twisted.conch.test.loopback import LoopbackRelay
class ConchTestRealm(object):
"""
A realm which expects a particular avatarId to log in once and creates a
L{ConchTestAvatar} for that request.
@ivar expectedAvatarID: The only avatarID that this realm will produce an
avatar for.
@ivar avatar: A reference to the avatar after it is requested.
"""
avatar = None
def __init__(self, expectedAvatarID):
self.expectedAvatarID = expectedAvatarID
def requestAvatar(self, avatarID, mind, *interfaces):
"""
Return a new L{ConchTestAvatar} if the avatarID matches the expected one
and this is the first avatar request.
"""
if avatarID == self.expectedAvatarID:
if self.avatar is not None:
raise UnauthorizedLogin("Only one login allowed")
self.avatar = ConchTestAvatar()
return interfaces[0], self.avatar, self.avatar.logout
raise UnauthorizedLogin(
"Only %r may log in, not %r" % (self.expectedAvatarID, avatarID))
class ConchTestAvatar(avatar.ConchUser):
"""
An avatar against which various SSH features can be tested.
@ivar loggedOut: A flag indicating whether the avatar logout method has been
called.
"""
if not cryptography:
skip = "cannot run without cryptography"
loggedOut = False
def __init__(self):
avatar.ConchUser.__init__(self)
self.listeners = {}
self.globalRequests = {}
self.channelLookup.update(
{b'session': session.SSHSession,
b'direct-tcpip':forwarding.openConnectForwardingClient})
self.subsystemLookup.update({b'crazy': CrazySubsystem})
def global_foo(self, data):
self.globalRequests['foo'] = data
return 1
def global_foo_2(self, data):
self.globalRequests['foo_2'] = data
return 1, b'data'
def global_tcpip_forward(self, data):
host, port = forwarding.unpackGlobal_tcpip_forward(data)
try:
listener = reactor.listenTCP(
port, forwarding.SSHListenForwardingFactory(
self.conn, (host, port),
forwarding.SSHListenServerForwardingChannel),
interface=host)
except:
log.err(None, "something went wrong with remote->local forwarding")
return 0
else:
self.listeners[(host, port)] = listener
return 1
def global_cancel_tcpip_forward(self, data):
host, port = forwarding.unpackGlobal_tcpip_forward(data)
listener = self.listeners.get((host, port), None)
if not listener:
return 0
del self.listeners[(host, port)]
listener.stopListening()
return 1
def logout(self):
self.loggedOut = True
for listener in self.listeners.values():
log.msg('stopListening %s' % listener)
listener.stopListening()
class ConchSessionForTestAvatar(object):
"""
An ISession adapter for ConchTestAvatar.
"""
def __init__(self, avatar):
"""
Initialize the session and create a reference to it on the avatar for
later inspection.
"""
self.avatar = avatar
self.avatar._testSession = self
self.cmd = None
self.proto = None
self.ptyReq = False
self.eof = 0
self.onClose = defer.Deferred()
def getPty(self, term, windowSize, attrs):
log.msg('pty req')
self._terminalType = term
self._windowSize = windowSize
self.ptyReq = True
def openShell(self, proto):
log.msg('opening shell')
self.proto = proto
EchoTransport(proto)
self.cmd = b'shell'
def execCommand(self, proto, cmd):
self.cmd = cmd
self.proto = proto
f = cmd.split()[0]
if f == b'false':
t = FalseTransport(proto)
# Avoid disconnecting this immediately. If the channel is closed
# before execCommand even returns the caller gets confused.
reactor.callLater(0, t.loseConnection)
elif f == b'echo':
t = EchoTransport(proto)
t.write(cmd[5:])
t.loseConnection()
elif f == b'secho':
t = SuperEchoTransport(proto)
t.write(cmd[6:])
t.loseConnection()
elif f == b'eecho':
t = ErrEchoTransport(proto)
t.write(cmd[6:])
t.loseConnection()
else:
raise error.ConchError('bad exec')
self.avatar.conn.transport.expectedLoseConnection = 1
def eofReceived(self):
self.eof = 1
def closed(self):
log.msg('closed cmd "%s"' % self.cmd)
self.remoteWindowLeftAtClose = self.proto.session.remoteWindowLeft
self.onClose.callback(None)
from twisted.python import components
if cryptography:
components.registerAdapter(ConchSessionForTestAvatar, ConchTestAvatar,
session.ISession)
class CrazySubsystem(protocol.Protocol):
def __init__(self, *args, **kw):
pass
def connectionMade(self):
"""
good ... good
"""
class FalseTransport:
"""
False transport should act like a /bin/false execution, i.e. just exit with
nonzero status, writing nothing to the terminal.
@ivar proto: The protocol associated with this transport.
@ivar closed: A flag tracking whether C{loseConnection} has been called yet.
"""
def __init__(self, p):
"""
@type p L{twisted.conch.ssh.session.SSHSessionProcessProtocol} instance
"""
self.proto = p
p.makeConnection(self)
self.closed = 0
def loseConnection(self):
"""
Disconnect the protocol associated with this transport.
"""
if self.closed:
return
self.closed = 1
self.proto.inConnectionLost()
self.proto.outConnectionLost()
self.proto.errConnectionLost()
self.proto.processEnded(failure.Failure(ProcessTerminated(255, None, None)))
class EchoTransport:
def __init__(self, p):
self.proto = p
p.makeConnection(self)
self.closed = 0
def write(self, data):
log.msg(repr(data))
self.proto.outReceived(data)
self.proto.outReceived(b'\r\n')
if b'\x00' in data: # mimic 'exit' for the shell test
self.loseConnection()
def loseConnection(self):
if self.closed: return
self.closed = 1
self.proto.inConnectionLost()
self.proto.outConnectionLost()
self.proto.errConnectionLost()
self.proto.processEnded(failure.Failure(ProcessTerminated(0, None, None)))
class ErrEchoTransport:
def __init__(self, p):
self.proto = p
p.makeConnection(self)
self.closed = 0
def write(self, data):
self.proto.errReceived(data)
self.proto.errReceived(b'\r\n')
def loseConnection(self):
if self.closed: return
self.closed = 1
self.proto.inConnectionLost()
self.proto.outConnectionLost()
self.proto.errConnectionLost()
self.proto.processEnded(failure.Failure(ProcessTerminated(0, None, None)))
class SuperEchoTransport:
def __init__(self, p):
self.proto = p
p.makeConnection(self)
self.closed = 0
def write(self, data):
self.proto.outReceived(data)
self.proto.outReceived(b'\r\n')
self.proto.errReceived(data)
self.proto.errReceived(b'\r\n')
def loseConnection(self):
if self.closed: return
self.closed = 1
self.proto.inConnectionLost()
self.proto.outConnectionLost()
self.proto.errConnectionLost()
self.proto.processEnded(failure.Failure(ProcessTerminated(0, None, None)))
if cryptography is not None and pyasn1 is not None:
from twisted.conch import checkers
from twisted.conch.ssh import channel, connection, factory, keys
from twisted.conch.ssh import transport, userauth
class ConchTestPasswordChecker:
credentialInterfaces = checkers.IUsernamePassword,
def requestAvatarId(self, credentials):
if credentials.username == b'testuser' and credentials.password == b'testpass':
return defer.succeed(credentials.username)
return defer.fail(Exception("Bad credentials"))
class ConchTestSSHChecker(checkers.SSHProtocolChecker):
def areDone(self, avatarId):
if avatarId != b'testuser' or len(self.successfulCredentials[avatarId]) < 2:
return False
return True
class ConchTestServerFactory(factory.SSHFactory):
noisy = 0
services = {
b'ssh-userauth':userauth.SSHUserAuthServer,
b'ssh-connection':connection.SSHConnection
}
def buildProtocol(self, addr):
proto = ConchTestServer()
proto.supportedPublicKeys = self.privateKeys.keys()
proto.factory = self
if hasattr(self, 'expectedLoseConnection'):
proto.expectedLoseConnection = self.expectedLoseConnection
self.proto = proto
return proto
def getPublicKeys(self):
return {
b'ssh-rsa': keys.Key.fromString(publicRSA_openssh),
b'ssh-dss': keys.Key.fromString(publicDSA_openssh)
}
def getPrivateKeys(self):
return {
b'ssh-rsa': keys.Key.fromString(privateRSA_openssh),
b'ssh-dss': keys.Key.fromString(privateDSA_openssh)
}
def getPrimes(self):
"""
Diffie-Hellman primes that can be used for the
diffie-hellman-group-exchange-sha1 key exchange.
@return: The primes and generators.
@rtype: L{dict} mapping the key size to a C{list} of
C{(generator, prime)} tupple.
"""
# In these tests, we hardwire the prime values to those defined by
# the diffie-hellman-group14-sha1 key exchange algorithm, to avoid
# requiring a moduli file when running tests.
# See OpenSSHFactory.getPrimes.
return {
2048: [
_kex.getDHGeneratorAndPrime(
b'diffie-hellman-group14-sha1')]
}
def getService(self, trans, name):
return factory.SSHFactory.getService(self, trans, name)
class ConchTestBase:
done = 0
def connectionLost(self, reason):
if self.done:
return
if not hasattr(self, 'expectedLoseConnection'):
raise unittest.FailTest(
'unexpectedly lost connection %s\n%s' % (self, reason))
self.done = 1
def receiveError(self, reasonCode, desc):
self.expectedLoseConnection = 1
# Some versions of OpenSSH (for example, OpenSSH_5.3p1) will
# send a DISCONNECT_BY_APPLICATION error before closing the
# connection. Other, older versions (for example,
# OpenSSH_5.1p1), won't. So accept this particular error here,
# but no others.
if reasonCode != transport.DISCONNECT_BY_APPLICATION:
log.err(
Exception(
'got disconnect for %s: reason %s, desc: %s' % (
self, reasonCode, desc)))
self.loseConnection()
def receiveUnimplemented(self, seqID):
raise unittest.FailTest('got unimplemented: seqid %s' % (seqID,))
self.expectedLoseConnection = 1
self.loseConnection()
class ConchTestServer(ConchTestBase, transport.SSHServerTransport):
def connectionLost(self, reason):
ConchTestBase.connectionLost(self, reason)
transport.SSHServerTransport.connectionLost(self, reason)
class ConchTestClient(ConchTestBase, transport.SSHClientTransport):
"""
@ivar _channelFactory: A callable which accepts an SSH connection and
returns a channel which will be attached to a new channel on that
connection.
"""
def __init__(self, channelFactory):
self._channelFactory = channelFactory
def connectionLost(self, reason):
ConchTestBase.connectionLost(self, reason)
transport.SSHClientTransport.connectionLost(self, reason)
def verifyHostKey(self, key, fp):
keyMatch = key == keys.Key.fromString(publicRSA_openssh).blob()
fingerprintMatch = (
fp == b'85:25:04:32:58:55:96:9f:57:ee:fb:a8:1a:ea:69:da')
if keyMatch and fingerprintMatch:
return defer.succeed(1)
return defer.fail(Exception("Key or fingerprint mismatch"))
def connectionSecure(self):
self.requestService(ConchTestClientAuth(b'testuser',
ConchTestClientConnection(self._channelFactory)))
class ConchTestClientAuth(userauth.SSHUserAuthClient):
hasTriedNone = 0 # have we tried the 'none' auth yet?
canSucceedPublicKey = 0 # can we succeed with this yet?
canSucceedPassword = 0
def ssh_USERAUTH_SUCCESS(self, packet):
if not self.canSucceedPassword and self.canSucceedPublicKey:
raise unittest.FailTest(
'got USERAUTH_SUCCESS before password and publickey')
userauth.SSHUserAuthClient.ssh_USERAUTH_SUCCESS(self, packet)
def getPassword(self):
self.canSucceedPassword = 1
return defer.succeed(b'testpass')
def getPrivateKey(self):
self.canSucceedPublicKey = 1
return defer.succeed(keys.Key.fromString(privateDSA_openssh))
def getPublicKey(self):
return keys.Key.fromString(publicDSA_openssh)
class ConchTestClientConnection(connection.SSHConnection):
"""
@ivar _completed: A L{Deferred} which will be fired when the number of
results collected reaches C{totalResults}.
"""
name = b'ssh-connection'
results = 0
totalResults = 8
def __init__(self, channelFactory):
connection.SSHConnection.__init__(self)
self._channelFactory = channelFactory
def serviceStarted(self):
self.openChannel(self._channelFactory(conn=self))
class SSHTestChannel(channel.SSHChannel):
def __init__(self, name, opened, *args, **kwargs):
self.name = name
self._opened = opened
self.received = []
self.receivedExt = []
self.onClose = defer.Deferred()
channel.SSHChannel.__init__(self, *args, **kwargs)
def openFailed(self, reason):
self._opened.errback(reason)
def channelOpen(self, ignore):
self._opened.callback(self)
def dataReceived(self, data):
self.received.append(data)
def extReceived(self, dataType, data):
if dataType == connection.EXTENDED_DATA_STDERR:
self.receivedExt.append(data)
else:
log.msg("Unrecognized extended data: %r" % (dataType,))
def request_exit_status(self, status):
[self.status] = struct.unpack('>L', status)
def eofReceived(self):
self.eofCalled = True
def closed(self):
self.onClose.callback(None)
def conchTestPublicKeyChecker():
"""
Produces a SSHPublicKeyChecker with an in-memory key mapping with
a single use: 'testuser'
@return: L{twisted.conch.checkers.SSHPublicKeyChecker}
"""
conchTestPublicKeyDB = checkers.InMemorySSHKeyDB(
{b'testuser': [keys.Key.fromString(publicDSA_openssh)]})
return checkers.SSHPublicKeyChecker(conchTestPublicKeyDB)
class SSHProtocolTests(unittest.TestCase):
"""
Tests for communication between L{SSHServerTransport} and
L{SSHClientTransport}.
"""
if not cryptography:
skip = "can't run without cryptography"
if not pyasn1:
skip = "Cannot run without PyASN1"
def _ourServerOurClientTest(self, name=b'session', **kwargs):
"""
Create a connected SSH client and server protocol pair and return a
L{Deferred} which fires with an L{SSHTestChannel} instance connected to
a channel on that SSH connection.
"""
result = defer.Deferred()
self.realm = ConchTestRealm(b'testuser')
p = portal.Portal(self.realm)
sshpc = ConchTestSSHChecker()
sshpc.registerChecker(ConchTestPasswordChecker())
sshpc.registerChecker(conchTestPublicKeyChecker())
p.registerChecker(sshpc)
fac = ConchTestServerFactory()
fac.portal = p
fac.startFactory()
self.server = fac.buildProtocol(None)
self.clientTransport = LoopbackRelay(self.server)
self.client = ConchTestClient(
lambda conn: SSHTestChannel(name, result, conn=conn, **kwargs))
self.serverTransport = LoopbackRelay(self.client)
self.server.makeConnection(self.serverTransport)
self.client.makeConnection(self.clientTransport)
return result
def test_subsystemsAndGlobalRequests(self):
"""
Run the Conch server against the Conch client. Set up several different
channels which exercise different behaviors and wait for them to
complete. Verify that the channels with errors log them.
"""
channel = self._ourServerOurClientTest()
def cbSubsystem(channel):
self.channel = channel
return self.assertFailure(
channel.conn.sendRequest(
channel, b'subsystem', common.NS(b'not-crazy'), 1),
Exception)
channel.addCallback(cbSubsystem)
def cbNotCrazyFailed(ignored):
channel = self.channel
return channel.conn.sendRequest(
channel, b'subsystem', common.NS(b'crazy'), 1)
channel.addCallback(cbNotCrazyFailed)
def cbGlobalRequests(ignored):
channel = self.channel
d1 = channel.conn.sendGlobalRequest(b'foo', b'bar', 1)
d2 = channel.conn.sendGlobalRequest(b'foo-2', b'bar2', 1)
d2.addCallback(self.assertEqual, b'data')
d3 = self.assertFailure(
channel.conn.sendGlobalRequest(b'bar', b'foo', 1),
Exception)
return defer.gatherResults([d1, d2, d3])
channel.addCallback(cbGlobalRequests)
def disconnect(ignored):
self.assertEqual(
self.realm.avatar.globalRequests,
{"foo": b"bar", "foo_2": b"bar2"})
channel = self.channel
channel.conn.transport.expectedLoseConnection = True
channel.conn.serviceStopped()
channel.loseConnection()
channel.addCallback(disconnect)
return channel
def test_shell(self):
"""
L{SSHChannel.sendRequest} can open a shell with a I{pty-req} request,
specifying a terminal type and window size.
"""
channel = self._ourServerOurClientTest()
data = session.packRequest_pty_req(
b'conch-test-term', (24, 80, 0, 0), b'')
def cbChannel(channel):
self.channel = channel
return channel.conn.sendRequest(channel, b'pty-req', data, 1)
channel.addCallback(cbChannel)
def cbPty(ignored):
# The server-side object corresponding to our client side channel.
session = self.realm.avatar.conn.channels[0].session
self.assertIs(session.avatar, self.realm.avatar)
self.assertEqual(session._terminalType, b'conch-test-term')
self.assertEqual(session._windowSize, (24, 80, 0, 0))
self.assertTrue(session.ptyReq)
channel = self.channel
return channel.conn.sendRequest(channel, b'shell', b'', 1)
channel.addCallback(cbPty)
def cbShell(ignored):
self.channel.write(b'testing the shell!\x00')
self.channel.conn.sendEOF(self.channel)
return defer.gatherResults([
self.channel.onClose,
self.realm.avatar._testSession.onClose])
channel.addCallback(cbShell)
def cbExited(ignored):
if self.channel.status != 0:
log.msg(
'shell exit status was not 0: %i' % (self.channel.status,))
self.assertEqual(
b"".join(self.channel.received),
b'testing the shell!\x00\r\n')
self.assertTrue(self.channel.eofCalled)
self.assertTrue(
self.realm.avatar._testSession.eof)
channel.addCallback(cbExited)
return channel
def test_failedExec(self):
"""
If L{SSHChannel.sendRequest} issues an exec which the server responds to
with an error, the L{Deferred} it returns fires its errback.
"""
channel = self._ourServerOurClientTest()
def cbChannel(channel):
self.channel = channel
return self.assertFailure(
channel.conn.sendRequest(
channel, b'exec', common.NS(b'jumboliah'), 1),
Exception)
channel.addCallback(cbChannel)
def cbFailed(ignored):
# The server logs this exception when it cannot perform the
# requested exec.
errors = self.flushLoggedErrors(error.ConchError)
self.assertEqual(errors[0].value.args, ('bad exec', None))
channel.addCallback(cbFailed)
return channel
def test_falseChannel(self):
"""
When the process started by a L{SSHChannel.sendRequest} exec request
exits, the exit status is reported to the channel.
"""
channel = self._ourServerOurClientTest()
def cbChannel(channel):
self.channel = channel
return channel.conn.sendRequest(
channel, b'exec', common.NS(b'false'), 1)
channel.addCallback(cbChannel)
def cbExec(ignored):
return self.channel.onClose
channel.addCallback(cbExec)
def cbClosed(ignored):
# No data is expected
self.assertEqual(self.channel.received, [])
self.assertNotEqual(self.channel.status, 0)
channel.addCallback(cbClosed)
return channel
def test_errorChannel(self):
"""
Bytes sent over the extended channel for stderr data are delivered to
the channel's C{extReceived} method.
"""
channel = self._ourServerOurClientTest(localWindow=4, localMaxPacket=5)
def cbChannel(channel):
self.channel = channel
return channel.conn.sendRequest(
channel, b'exec', common.NS(b'eecho hello'), 1)
channel.addCallback(cbChannel)
def cbExec(ignored):
return defer.gatherResults([
self.channel.onClose,
self.realm.avatar._testSession.onClose])
channel.addCallback(cbExec)
def cbClosed(ignored):
self.assertEqual(self.channel.received, [])
self.assertEqual(b"".join(self.channel.receivedExt), b"hello\r\n")
self.assertEqual(self.channel.status, 0)
self.assertTrue(self.channel.eofCalled)
self.assertEqual(self.channel.localWindowLeft, 4)
self.assertEqual(
self.channel.localWindowLeft,
self.realm.avatar._testSession.remoteWindowLeftAtClose)
channel.addCallback(cbClosed)
return channel
def test_unknownChannel(self):
"""
When an attempt is made to open an unknown channel type, the L{Deferred}
returned by L{SSHChannel.sendRequest} fires its errback.
"""
d = self.assertFailure(
self._ourServerOurClientTest(b'crazy-unknown-channel'), Exception)
def cbFailed(ignored):
errors = self.flushLoggedErrors(error.ConchError)
self.assertEqual(errors[0].value.args, (3, 'unknown channel'))
self.assertEqual(len(errors), 1)
d.addCallback(cbFailed)
return d
def test_maxPacket(self):
"""
An L{SSHChannel} can be configured with a maximum packet size to
receive.
"""
# localWindow needs to be at least 11 otherwise the assertion about it
# in cbClosed is invalid.
channel = self._ourServerOurClientTest(
localWindow=11, localMaxPacket=1)
def cbChannel(channel):
self.channel = channel
return channel.conn.sendRequest(
channel, b'exec', common.NS(b'secho hello'), 1)
channel.addCallback(cbChannel)
def cbExec(ignored):
return self.channel.onClose
channel.addCallback(cbExec)
def cbClosed(ignored):
self.assertEqual(self.channel.status, 0)
self.assertEqual(b"".join(self.channel.received), b"hello\r\n")
self.assertEqual(b"".join(self.channel.receivedExt), b"hello\r\n")
self.assertEqual(self.channel.localWindowLeft, 11)
self.assertTrue(self.channel.eofCalled)
channel.addCallback(cbClosed)
return channel
def test_echo(self):
"""
Normal standard out bytes are sent to the channel's C{dataReceived}
method.
"""
channel = self._ourServerOurClientTest(localWindow=4, localMaxPacket=5)
def cbChannel(channel):
self.channel = channel
return channel.conn.sendRequest(
channel, b'exec', common.NS(b'echo hello'), 1)
channel.addCallback(cbChannel)
def cbEcho(ignored):
return defer.gatherResults([
self.channel.onClose,
self.realm.avatar._testSession.onClose])
channel.addCallback(cbEcho)
def cbClosed(ignored):
self.assertEqual(self.channel.status, 0)
self.assertEqual(b"".join(self.channel.received), b"hello\r\n")
self.assertEqual(self.channel.localWindowLeft, 4)
self.assertTrue(self.channel.eofCalled)
self.assertEqual(
self.channel.localWindowLeft,
self.realm.avatar._testSession.remoteWindowLeftAtClose)
channel.addCallback(cbClosed)
return channel
class SSHFactoryTests(unittest.TestCase):
if not cryptography:
skip = "can't run without cryptography"
if not pyasn1:
skip = "Cannot run without PyASN1"
def makeSSHFactory(self, primes=None):
sshFactory = factory.SSHFactory()
gpk = lambda: {'ssh-rsa' : keys.Key(None)}
sshFactory.getPrimes = lambda: primes
sshFactory.getPublicKeys = sshFactory.getPrivateKeys = gpk
sshFactory.startFactory()
return sshFactory
def test_buildProtocol(self):
"""
By default, buildProtocol() constructs an instance of
SSHServerTransport.
"""
factory = self.makeSSHFactory()
protocol = factory.buildProtocol(None)
self.assertIsInstance(protocol, transport.SSHServerTransport)
def test_buildProtocolRespectsProtocol(self):
"""
buildProtocol() calls 'self.protocol()' to construct a protocol
instance.
"""
calls = []
def makeProtocol(*args):
calls.append(args)
return transport.SSHServerTransport()
factory = self.makeSSHFactory()
factory.protocol = makeProtocol
factory.buildProtocol(None)
self.assertEqual([()], calls)
def test_buildProtocolNoPrimes(self):
"""
Group key exchanges are not supported when we don't have the primes
database.
"""
f1 = self.makeSSHFactory(primes=None)
p1 = f1.buildProtocol(None)
self.assertNotIn(
b'diffie-hellman-group-exchange-sha1', p1.supportedKeyExchanges)
self.assertNotIn(
b'diffie-hellman-group-exchange-sha256', p1.supportedKeyExchanges)
def test_buildProtocolWithPrimes(self):
"""
Group key exchanges are supported when we have the primes database.
"""
f2 = self.makeSSHFactory(primes={1:(2,3)})
p2 = f2.buildProtocol(None)
self.assertIn(
b'diffie-hellman-group-exchange-sha1', p2.supportedKeyExchanges)
self.assertIn(
b'diffie-hellman-group-exchange-sha256', p2.supportedKeyExchanges)
class MPTests(unittest.TestCase):
"""
Tests for L{common.getMP}.
@cvar getMP: a method providing a MP parser.
@type getMP: C{callable}
"""
if not cryptography:
skip = "can't run without cryptography"
if not pyasn1:
skip = "Cannot run without PyASN1"
if cryptography:
getMP = staticmethod(common.getMP)
def test_getMP(self):
"""
L{common.getMP} should parse the a multiple precision integer from a
string: a 4-byte length followed by length bytes of the integer.
"""
self.assertEqual(
self.getMP(b'\x00\x00\x00\x04\x00\x00\x00\x01'),
(1, b''))
def test_getMPBigInteger(self):
"""
L{common.getMP} should be able to parse a big enough integer
(that doesn't fit on one byte).
"""
self.assertEqual(
self.getMP(b'\x00\x00\x00\x04\x01\x02\x03\x04'),
(16909060, b''))
def test_multipleGetMP(self):
"""
L{common.getMP} has the ability to parse multiple integer in the same
string.
"""
self.assertEqual(
self.getMP(b'\x00\x00\x00\x04\x00\x00\x00\x01'
b'\x00\x00\x00\x04\x00\x00\x00\x02', 2),
(1, 2, b''))
def test_getMPRemainingData(self):
"""
When more data than needed is sent to L{common.getMP}, it should return
the remaining data.
"""
self.assertEqual(
self.getMP(b'\x00\x00\x00\x04\x00\x00\x00\x01foo'),
(1, b'foo'))
def test_notEnoughData(self):
"""
When the string passed to L{common.getMP} doesn't even make 5 bytes,
it should raise a L{struct.error}.
"""
self.assertRaises(struct.error, self.getMP, b'\x02\x00')
class GMPYInstallDeprecationTests(unittest.TestCase):
"""
Tests for the deprecation of former GMPY accidental public API.
"""
if not cryptography:
skip = "cannot run without cryptography"
def test_deprecated(self):
"""
L{twisted.conch.ssh.common.install} is deprecated.
"""
common.install()
warnings = self.flushWarnings([self.test_deprecated])
self.assertEqual(len(warnings), 1)
self.assertEqual(
warnings[0]["message"],
"twisted.conch.ssh.common.install was deprecated in Twisted 16.5.0"
)