Skip to content

Commit

Permalink
Merge branch 'trunk' into 9138-hostname-caching-policy
Browse files Browse the repository at this point in the history
  • Loading branch information
wiml committed Dec 6, 2018
2 parents 1ccc66a + 9a7ce38 commit 0ecb515
Show file tree
Hide file tree
Showing 55 changed files with 1,125 additions and 203 deletions.
Empty file modified docs/core/howto/tutorial/listings/finger/finger/__init__.py
100755 → 100644
Empty file.
Empty file modified docs/core/howto/tutorial/listings/finger/finger/finger.py
100755 → 100644
Empty file.
Empty file modified docs/historic/2003/pycon/conch/conchtalk.txt
100755 → 100644
Empty file.
Empty file modified docs/historic/2003/pycon/lore/lore-slides.html
100755 → 100644
Empty file.
2 changes: 1 addition & 1 deletion docs/web/examples/reverse-proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
from twisted.internet import reactor
from twisted.web import proxy, server

site = server.Site(proxy.ReverseProxyResource('www.yahoo.com', 80, ''))
site = server.Site(proxy.ReverseProxyResource('example.com', 80, b''))
reactor.listenTCP(8080, site)
reactor.run()
4 changes: 2 additions & 2 deletions docs/web/howto/web-in-60/dynamic-dispatch.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ for the year passed to its initializer:
def render_GET(self, request):
cal = calendar(self.year)
return (b"<!DOCTYPE html><html><head><meta charset='utf-8'>"
b"<title></title></head><body><pre>" + cal.encode('utf-8') + "</pre>")
b"<title></title></head><body><pre>" + cal.encode('utf-8') + b"</pre>")
Expand Down Expand Up @@ -138,7 +138,7 @@ basically like ``Calendar.getChild`` . Here's the full example code:
def render_GET(self, request):
cal = calendar(self.year)
return (b"<!DOCTYPE html><html><head><meta charset='utf-8'>"
b"<title></title></head><body><pre>" + cal.encode('utf-8') + "</pre>")
b"<title></title></head><body><pre>" + cal.encode('utf-8') + b"</pre>")
class Calendar(Resource):
Expand Down
1 change: 1 addition & 0 deletions src/twisted/conch/newsfragments/9515.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
twisted.conch.ssh.keys can now read private keys in the new "openssh-key-v1" format, introduced in OpenSSH 6.5 and made the default in OpenSSH 7.8.
Empty file modified src/twisted/conch/ssh/forwarding.py
100755 → 100644
Empty file.
175 changes: 163 additions & 12 deletions src/twisted/conch/ssh/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

from hashlib import md5, sha256
import base64
import struct

import bcrypt
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
Expand Down Expand Up @@ -260,8 +262,8 @@ def _fromString_PRIVATE_BLOB(cls, blob):
EC keys::
string 'ecdsa-sha2-[identifier]'
integer x
integer y
string identifier
string q
integer privateValue
identifier is the standard NIST curve name.
Expand All @@ -272,7 +274,9 @@ def _fromString_PRIVATE_BLOB(cls, blob):
@return: A new key.
@rtype: L{twisted.conch.ssh.keys.Key}
@raises BadKeyError: if the key type (the first string) is unknown.
@raises BadKeyError: if
* the key type (the first string) is unknown
* the curve name of an ECDSA key does not match the key type
"""
keyType, rest = common.getNS(blob)

Expand All @@ -282,10 +286,15 @@ def _fromString_PRIVATE_BLOB(cls, blob):
elif keyType == b'ssh-dss':
p, q, g, y, x, rest = common.getMP(rest, 5)
return cls._fromDSAComponents(y=y, g=g, p=p, q=q, x=x)
elif keyType in [curve for curve in list(_curveTable.keys())]:
x, y, privateValue, rest = common.getMP(rest, 3)
return cls._fromECComponents(x=x, y=y, curve=keyType,
privateValue=privateValue)
elif keyType in _curveTable:
curve = _curveTable[keyType]
curveName, q, rest = common.getNS(rest, 2)
if curveName != _secToNist[curve.name.encode('ascii')]:
raise BadKeyError('ECDSA curve name %r does not match key '
'type %r' % (curveName, keyType))
privateValue, rest = common.getMP(rest)
return cls._fromECEncodedPoint(
encodedPoint=q, curve=keyType, privateValue=privateValue)
else:
raise BadKeyError('unknown blob type: %s' % (keyType,))

Expand All @@ -311,14 +320,98 @@ def _fromString_PUBLIC_OPENSSH(cls, data):
blob = decodebytes(data.split()[1])
return cls._fromString_BLOB(blob)


@classmethod
def _fromString_PRIVATE_OPENSSH(cls, data, passphrase):
def _fromPrivateOpenSSH_v1(cls, data, passphrase):
"""
Return a private key object corresponding to this OpenSSH private key
string. If the key is encrypted, passphrase MUST be provided.
Providing a passphrase for an unencrypted key is an error.
string, in the "openssh-key-v1" format introduced in OpenSSH 6.5.
The format of an openssh-key-v1 private key string is::
-----BEGIN OPENSSH PRIVATE KEY-----
<base64-encoded SSH protocol string>
-----END OPENSSH PRIVATE KEY-----
The SSH protocol string is as described in
U{PROTOCOL.key<https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key>}.
@type data: L{bytes}
@param data: The key data.
@type passphrase: L{bytes} or L{None}
@param passphrase: The passphrase the key is encrypted with, or L{None}
if it is not encrypted.
@return: A new key.
@rtype: L{twisted.conch.ssh.keys.Key}
@raises BadKeyError: if
* a passphrase is provided for an unencrypted key
* the SSH protocol encoding is incorrect
@raises EncryptedKeyError: if
* a passphrase is not provided for an encrypted key
"""
lines = data.strip().splitlines()
keyList = decodebytes(b''.join(lines[1:-1]))
if not keyList.startswith(b'openssh-key-v1\0'):
raise BadKeyError('unknown OpenSSH private key format')
keyList = keyList[len(b'openssh-key-v1\0'):]
cipher, kdf, kdfOptions, rest = common.getNS(keyList, 3)
n = struct.unpack('!L', rest[:4])[0]
if n != 1:
raise BadKeyError('only OpenSSH private key files containing '
'a single key are supported')
# Ignore public key
_, encPrivKeyList, _ = common.getNS(rest[4:], 2)
if cipher != b'none':
if not passphrase:
raise EncryptedKeyError('Passphrase must be provided '
'for an encrypted key')
# Determine cipher
if cipher in (b'aes128-ctr', b'aes192-ctr', b'aes256-ctr'):
algorithmClass = algorithms.AES
blockSize = 16
keySize = int(cipher[3:6]) // 8
ivSize = blockSize
else:
raise BadKeyError('unknown encryption type %r' % (cipher,))
if kdf == b'bcrypt':
salt, rest = common.getNS(kdfOptions)
rounds = struct.unpack('!L', rest[:4])[0]
decKey = bcrypt.kdf(
passphrase, salt, keySize + ivSize, rounds,
# We can only use the number of rounds that OpenSSH used.
ignore_few_rounds=True)
else:
raise BadKeyError('unknown KDF type %r' % (kdf,))
if (len(encPrivKeyList) % blockSize) != 0:
raise BadKeyError('bad padding')
decryptor = Cipher(
algorithmClass(decKey[:keySize]),
modes.CTR(decKey[keySize:keySize + ivSize]),
backend=default_backend()
).decryptor()
privKeyList = (
decryptor.update(encPrivKeyList) + decryptor.finalize())
else:
if kdf != b'none':
raise BadKeyError('private key specifies KDF %r but no '
'cipher' % (kdf,))
privKeyList = encPrivKeyList
check1 = struct.unpack('!L', privKeyList[:4])[0]
check2 = struct.unpack('!L', privKeyList[4:8])[0]
if check1 != check2:
raise BadKeyError('check values do not match: %d != %d' %
(check1, check2))
return cls._fromString_PRIVATE_BLOB(privKeyList[8:])


The format of an OpenSSH private key string is::
@classmethod
def _fromPrivateOpenSSH_PEM(cls, data, passphrase):
"""
Return a private key object corresponding to this OpenSSH private key
string, in the old PEM-based format.
The format of a PEM-based OpenSSH private key string is::
-----BEGIN <key type> PRIVATE KEY-----
[Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,<initialization value>]
Expand Down Expand Up @@ -365,7 +458,7 @@ def _fromString_PRIVATE_OPENSSH(cls, data, passphrase):

if cipher in (b'AES-128-CBC', b'AES-256-CBC'):
algorithmClass = algorithms.AES
keySize = int(int(cipher.split(b'-')[1])/8)
keySize = int(cipher.split(b'-')[1]) // 8
if len(ivdata) != 32:
raise BadKeyError('AES encrypted key with a bad IV')
elif cipher == b'DES-EDE3-CBC':
Expand Down Expand Up @@ -447,6 +540,36 @@ def _fromString_PRIVATE_OPENSSH(cls, data, passphrase):
else:
raise BadKeyError("unknown key type %s" % (kind,))


@classmethod
def _fromString_PRIVATE_OPENSSH(cls, data, passphrase):
"""
Return a private key object corresponding to this OpenSSH private key
string. If the key is encrypted, passphrase MUST be provided.
Providing a passphrase for an unencrypted key is an error.
@type data: L{bytes}
@param data: The key data.
@type passphrase: L{bytes} or L{None}
@param passphrase: The passphrase the key is encrypted with, or L{None}
if it is not encrypted.
@return: A new key.
@rtype: L{twisted.conch.ssh.keys.Key}
@raises BadKeyError: if
* a passphrase is provided for an unencrypted key
* the encoding is incorrect
@raises EncryptedKeyError: if
* a passphrase is not provided for an encrypted key
"""
if data.strip().splitlines()[0][11:-17] == b'OPENSSH':
# New-format (openssh-key-v1) key
return cls._fromPrivateOpenSSH_v1(data, passphrase)
else:
# Old-format (PEM) key
return cls._fromPrivateOpenSSH_PEM(data, passphrase)

@classmethod
def _fromString_PUBLIC_LSH(cls, data):
"""
Expand Down Expand Up @@ -701,6 +824,34 @@ def _fromECComponents(cls, x, y, curve, privateValue=None):

return cls(keyObject)

@classmethod
def _fromECEncodedPoint(cls, encodedPoint, curve, privateValue=None):
"""
Build a key from an EC encoded point.
@param encodedPoint: The public point encoded as in SEC 1 v2.0
section 2.3.3.
@type encodedPoint: L{bytes}
@param curve: NIST name of elliptic curve.
@type curve: L{bytes}
@param privateValue: The private value.
@type privateValue: L{int}
"""

publicNumbers = ec.EllipticCurvePublicNumbers.from_encoded_point(
_curveTable[curve], encodedPoint)
if privateValue is None:
# We have public components.
keyObject = publicNumbers.public_key(default_backend())
else:
privateNumbers = ec.EllipticCurvePrivateNumbers(
private_value=privateValue, public_numbers=publicNumbers)
keyObject = privateNumbers.private_key(default_backend())

return cls(keyObject)

def __init__(self, keyObject):
"""
Initialize with a private or public
Expand Down
Empty file modified src/twisted/conch/ssh/session.py
100755 → 100644
Empty file.
3 changes: 1 addition & 2 deletions src/twisted/conch/stdio.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ def runWithProtocol(klass):
oldSettings = termios.tcgetattr(fd)
tty.setraw(fd)
try:
p = ServerProtocol(klass)
stdio.StandardIO(p)
stdio.StandardIO(ServerProtocol(klass))
reactor.run()
finally:
termios.tcsetattr(fd, termios.TCSANOW, oldSettings)
Expand Down
Loading

0 comments on commit 0ecb515

Please sign in to comment.