Skip to content

Commit

Permalink
Fix ECDSA private blob format
Browse files Browse the repository at this point in the history
https://tools.ietf.org/html/draft-miller-ssh-agent-02 (expired
Internet-Draft, but documents the de-facto implementation in OpenSSH)
describes the payload for an SSH_AGENTC_ADD_IDENTITY message as follows:

       string                  key type
       string                  ecdsa_curve_name
       string                  Q
       mpint                   d
       string                  comment
       constraint[]            constraints

This didn't match Key._fromString_PRIVATE_BLOB, which instead expected
the affine x and y components as separate mpints.  As far as I can tell,
the private blob deserialisation code is only currently used for the
agent protocol, so it seems to make most sense to consider the previous
blob format as a mistake and use the format from the above I-D.
  • Loading branch information
cjwatson committed Sep 3, 2018
1 parent 2a18aba commit ce1b014
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 9 deletions.
49 changes: 42 additions & 7 deletions src/twisted/conch/ssh/keys.py
Expand Up @@ -260,8 +260,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 +272,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 +284,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 Down Expand Up @@ -701,6 +708,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
8 changes: 6 additions & 2 deletions src/twisted/conch/test/test_keys.py
Expand Up @@ -772,10 +772,14 @@ def test_fromPrivateBlobECDSA(self):
"""
A private EC key is correctly generated from a private key blob.
"""
from cryptography.hazmat.primitives.asymmetric import ec
publicNumbers = ec.EllipticCurvePublicNumbers(
x=keydata.ECDatanistp256['x'], y=keydata.ECDatanistp256['y'],
curve=ec.SECP256R1())
ecblob = (
common.NS(keydata.ECDatanistp256['curve']) +
common.MP(keydata.ECDatanistp256['x']) +
common.MP(keydata.ECDatanistp256['y']) +
common.NS(keydata.ECDatanistp256['curve'][-8:]) +
common.NS(publicNumbers.encode_point()) +
common.MP(keydata.ECDatanistp256['privateValue'])
)

Expand Down

0 comments on commit ce1b014

Please sign in to comment.