diff --git a/rdpy/core/type.py b/rdpy/core/type.py index dd838db..5c056ef 100644 --- a/rdpy/core/type.py +++ b/rdpy/core/type.py @@ -959,6 +959,13 @@ class ArrayType(Type): @param s: Stream """ s.writeType(self._array) + + def __getitem__(self, item): + """ + @summary: Magic function to be FactoryType as transparent as possible + @return: index of _value + """ + return self._array.__getitem__(item) def __sizeof__(self): """ diff --git a/rdpy/core/x509.py b/rdpy/core/x509.py new file mode 100644 index 0000000..d507b2f --- /dev/null +++ b/rdpy/core/x509.py @@ -0,0 +1,159 @@ +# +# Copyright (c) 2014 Sylvain Peyrefitte +# +# This file is part of rdpy. +# +# rdpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +""" +@see: https://github.com/filippog/pyasn1/blob/master/examples/x509.py +""" + +from pyasn1.type import tag, namedtype, namedval, univ, constraint, char, useful +from pyasn1.codec.ber import decoder, encoder +from pyasn1 import error +from error import InvalidExpectedDataException + +MAX = 64 + +class DirectoryString(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('teletexString', char.TeletexString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType('printableString', char.PrintableString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType('universalString', char.UniversalString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType('utf8String', char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType('bmpString', char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))), + namedtype.NamedType('ia5String', char.IA5String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX))) # hm, this should not be here!? XXX + ) + +class AttributeValue(DirectoryString): pass + +class AttributeType(univ.ObjectIdentifier): pass + +class AttributeTypeAndValue(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('type', AttributeType()), + namedtype.NamedType('value', AttributeValue()) + ) + +class RelativeDistinguishedName(univ.SetOf): + componentType = AttributeTypeAndValue() + +class RDNSequence(univ.SequenceOf): + componentType = RelativeDistinguishedName() + +class Name(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('', RDNSequence()) + ) + +class AlgorithmIdentifier(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('algorithm', univ.ObjectIdentifier()), + namedtype.OptionalNamedType('parameters', univ.Null()) + # XXX syntax screwed? +# namedtype.OptionalNamedType('parameters', univ.ObjectIdentifier()) + ) + +class Extension(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('extnID', univ.ObjectIdentifier()), + namedtype.DefaultedNamedType('critical', univ.Boolean('False')), + namedtype.NamedType('extnValue', univ.OctetString()) + ) + +class Extensions(univ.SequenceOf): + componentType = Extension() + sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX) + +class SubjectPublicKeyInfo(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('algorithm', AlgorithmIdentifier()), + namedtype.NamedType('subjectPublicKey', univ.BitString()) + ) + +class UniqueIdentifier(univ.BitString): pass + +class Time(univ.Choice): + componentType = namedtype.NamedTypes( + namedtype.NamedType('utcTime', useful.UTCTime()), + namedtype.NamedType('generalTime', useful.GeneralizedTime()) + ) + +class Validity(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('notBefore', Time()), + namedtype.NamedType('notAfter', Time()) + ) + +class CertificateSerialNumber(univ.Integer): pass + +class Version(univ.Integer): + namedValues = namedval.NamedValues( + ('v1', 0), ('v2', 1), ('v3', 2) + ) + +class TBSCertificate(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.DefaultedNamedType('version', Version('v1', tagSet=Version.tagSet.tagExplicitly(tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)))), + namedtype.NamedType('serialNumber', CertificateSerialNumber()), + namedtype.NamedType('signature', AlgorithmIdentifier()), + namedtype.NamedType('issuer', Name()), + namedtype.NamedType('validity', Validity()), + namedtype.NamedType('subject', Name()), + namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()), + namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), + namedtype.OptionalNamedType('extensions', Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))) + ) + +class X509Certificate(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('tbsCertificate', TBSCertificate()), + namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()), + namedtype.NamedType('signatureValue', univ.BitString()) + ) + +class RSAPublicKey(univ.Sequence): + """ + @summary: asn1 public rsa key + @see: https://tools.ietf.org/html/rfc3447 + """ + componentType = namedtype.NamedTypes( + namedtype.NamedType('modulus', univ.Integer()), + namedtype.NamedType('publicExponent', univ.Integer()), + ) + +def load(stream): + """ + @summary: load X509 certificate + @param stream: {str} + """ + return decoder.decode(stream, asn1Spec=X509Certificate())[0] + +def extractRSAKey(certificate): + """ + @summary: try to extract rsa key + @return: (modulus, public exponent) + """ + #http://www.alvestrand.no/objectid/1.2.840.113549.1.1.1.html + #if certificate.getComponentByName('tbsCertificate').getComponentByName('subjectPublicKeyInfo').getComponentByName('algorithm').getComponentByName('algorithm')._value != (1, 2, 840, 113549, 1, 1, 1): + # raise InvalidExpectedDataException("Certificate doesn't contain RSA public key") + + rsaKey = decoder.decode(encoder.encode(certificate.getComponentByName('tbsCertificate').getComponentByName('subjectPublicKeyInfo').getComponentByName('subjectPublicKey'))[3:], asn1Spec=RSAPublicKey())[0] + return rsaKey.getComponentByName('modulus')._value , rsaKey.getComponentByName('publicExponent')._value + + + \ No newline at end of file diff --git a/rdpy/protocol/rdp/ber.py b/rdpy/protocol/rdp/ber.py index 71f0c15..f416675 100644 --- a/rdpy/protocol/rdp/ber.py +++ b/rdpy/protocol/rdp/ber.py @@ -50,7 +50,7 @@ class Tag(object): def berPC(pc): """ - Return BER_CONSTRUCT if true + @summary: Return BER_CONSTRUCT if true BER_PRIMITIVE if false @param pc: boolean @return: BerPc value @@ -62,7 +62,7 @@ def berPC(pc): def readLength(s): """ - Read length of BER structure + @summary: Read length of BER structure length be on 1 2 or 3 bytes @param s: stream @return: int or Python long @@ -86,7 +86,7 @@ def readLength(s): def writeLength(size): """ - Return structure length as expected in BER specification + @summary: Return structure length as expected in BER specification @param size: int or python long @return: UInt8 or (UInt8(0x82), UInt16Be) """ @@ -97,7 +97,7 @@ def writeLength(size): def readUniversalTag(s, tag, pc): """ - Read tag of BER packet + @summary: Read tag of BER packet @param tag: Tag class attributes @param pc: boolean @return: true if tag is correctly read @@ -108,7 +108,7 @@ def readUniversalTag(s, tag, pc): def writeUniversalTag(tag, pc): """ - Return universal tag byte + @summary: Return universal tag byte @param tag: tag class attributes @param pc: boolean @return: UInt8 @@ -117,7 +117,7 @@ def writeUniversalTag(tag, pc): def readApplicationTag(s, tag): """ - Read application tag + @summary: Read application tag @param s: stream @param tag: tag class attributes @return: length of application packet @@ -138,7 +138,7 @@ def readApplicationTag(s, tag): def writeApplicationTag(tag, size): """ - Return structure that represent BER application tag + @summary: Return structure that represent BER application tag @param tag: int python that match an uint8(0xff) @param size: size to rest of packet """ @@ -149,7 +149,7 @@ def writeApplicationTag(tag, size): def readBoolean(s): """ - Return boolean + @summary: Return boolean @param s: stream @return: boolean """ @@ -164,7 +164,7 @@ def readBoolean(s): def writeBoolean(b): """ - Return structure that represent boolean in BER specification + @summary: Return structure that represent boolean in BER specification @param b: boolean @return: BER boolean block """ @@ -175,7 +175,7 @@ def writeBoolean(b): def readInteger(s): """ - Read integer structure from stream + @summary: Read integer structure from stream @param s: stream @return: int or long python """ @@ -207,7 +207,7 @@ def readInteger(s): def writeInteger(value): """ - Write integer value + @summary: Write integer value @param param: INT or Python long @return: BER integer block """ @@ -220,7 +220,7 @@ def writeInteger(value): def readOctetString(s): """ - Read BER string structure + @summary: Read BER string structure @param s: stream @return: string python """ @@ -231,7 +231,7 @@ def readOctetString(s): def writeOctetstring(value): """ - Write string in BER representation + @summary: Write string in BER representation @param value: string @return: BER octet string block """ @@ -239,7 +239,7 @@ def writeOctetstring(value): def readEnumerated(s): """ - Read enumerated structure + @summary: Read enumerated structure @param s: Stream @return: int or long """ @@ -253,7 +253,7 @@ def readEnumerated(s): def writeEnumerated(enumerated): """ - Write enumerated structure + @summary: Write enumerated structure @param s: Stream @return: BER enumerated block """ diff --git a/rdpy/protocol/rdp/gcc.py b/rdpy/protocol/rdp/gcc.py index 96cf508..ee9ad50 100644 --- a/rdpy/protocol/rdp/gcc.py +++ b/rdpy/protocol/rdp/gcc.py @@ -299,7 +299,7 @@ class ServerCertificate(CompositeType): @summary: Server certificate structure @see: http://msdn.microsoft.com/en-us/library/cc240521.aspx """ - def __init__(self, readLen, conditional): + def __init__(self, readLen = None, conditional = lambda:True): CompositeType.__init__(self, readLen = readLen, conditional = conditional) self.dwVersion = UInt32Le() @@ -307,10 +307,10 @@ class ServerCertificate(CompositeType): """ Closure for capability factory """ - for c in [ProprietaryServerCertificate]: + for c in [ProprietaryServerCertificate, X509CertificateChain]: if self.dwVersion.value & 0x7fffffff == c._TYPE_: return c() - raise InvalidExpectedDataException("unknown certificate type : %s (RDPY doesn't support x.509 format please repport a bug)"%hex(self.dwVersion.value)) + raise InvalidExpectedDataException("unknown certificate type : %s "%hex(self.dwVersion.value)) self.certData = FactoryType(CertificateFactory) @@ -331,6 +331,29 @@ class ProprietaryServerCertificate(CompositeType): self.wSignatureBlobType = UInt16Le(0x0008, constant = True) self.wSignatureBlobLen = UInt16Le(lambda:sizeof(self.SignatureBlob)) self.SignatureBlob = String(readLen = self.wSignatureBlobLen) + +class CertBlob(CompositeType): + """ + @summary: certificate blob, contain x509 data + @see: http://msdn.microsoft.com/en-us/library/cc241911.aspx + """ + def __init__(self): + CompositeType.__init__(self) + self.cbCert = UInt32Le(lambda:sizeof(self.abCert)) + self.abCert = String(readLen = self.cbCert) + +class X509CertificateChain(CompositeType): + """ + @summary: X509 certificate chain + @see: http://msdn.microsoft.com/en-us/library/cc241910.aspx + """ + _TYPE_ = CertificateType.CERT_CHAIN_VERSION_2 + + def __init__(self): + CompositeType.__init__(self) + self.NumCertBlobs = UInt32Le() + self.CertBlobArray = ArrayType(CertBlob, readLen = self.NumCertBlobs) + self.padding = String(readLen = UInt8(lambda:(8 + 4 * self.NumCertBlobs.value))) class RSAPublicKey(CompositeType): """ diff --git a/rdpy/protocol/rdp/lic.py b/rdpy/protocol/rdp/lic.py index 20cd47c..b7c4aa1 100644 --- a/rdpy/protocol/rdp/lic.py +++ b/rdpy/protocol/rdp/lic.py @@ -25,7 +25,8 @@ from rdpy.core.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof, FactoryType, ArrayType, Stream from rdpy.core.error import InvalidExpectedDataException import rdpy.core.log as log -import rc4, sec +import rc4, sec, gcc +from rdpy.core import x509 class MessageType(object): """ diff --git a/rdpy/protocol/rdp/mcs.py b/rdpy/protocol/rdp/mcs.py index b14891f..1f6d908 100644 --- a/rdpy/protocol/rdp/mcs.py +++ b/rdpy/protocol/rdp/mcs.py @@ -306,7 +306,7 @@ class Client(MCSLayer): #static virtual channel if self._nbChannelRequested < self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelCount.value: - channelId = self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray._array[self._nbChannelRequested] + channelId = self._serverSettings.getBlock(gcc.MessageType.SC_NET).channelIdArray[self._nbChannelRequested] self._nbChannelRequested += 1 self.sendChannelJoinRequest(channelId) return @@ -384,7 +384,7 @@ class Client(MCSLayer): if confirm == 0: serverNet = self._serverSettings.getBlock(gcc.MessageType.SC_NET) for i in range(0, serverNet.channelCount.value): - if channelId == serverNet.channelIdArray._array[i].value: + if channelId == serverNet.channelIdArray[i].value: self._channels[channelId] = self._virtualChannels[i][1] self.connectNextChannel() diff --git a/rdpy/protocol/rdp/sec.py b/rdpy/protocol/rdp/sec.py index 4c6a4fb..de426e5 100644 --- a/rdpy/protocol/rdp/sec.py +++ b/rdpy/protocol/rdp/sec.py @@ -26,7 +26,7 @@ import gcc, lic, tpkt from rdpy.core.type import CompositeType, Stream, UInt32Le, UInt16Le, String, sizeof, UInt8 from rdpy.core.layer import LayerAutomata, IStreamSender from rdpy.core.error import InvalidExpectedDataException -from rdpy.core import log +from rdpy.core import log, x509 class SecurityFlag(object): """ @@ -155,6 +155,9 @@ def sessionKeyBlob(secret, random1, random2): def macData(macSaltKey, data): """ @see: http://msdn.microsoft.com/en-us/library/cc241995.aspx + @param macSaltKey: {str} mac key + @param data: {str} data to sign + @return: {str} signature """ sha1Digest = sha.new() md5Digest = md5.new() @@ -179,6 +182,9 @@ def macData(macSaltKey, data): def tempKey(initialKey, currentKey): """ @see: http://msdn.microsoft.com/en-us/library/cc240792.aspx + @param initialKey: {str} key computed first time + @param currentKey: {str} key actually used + @return: {str} temp key """ sha1Digest = sha.new() md5Digest = md5.new() @@ -514,9 +520,17 @@ class Client(SecLayer): self._encryptRc4 = rc4.RC4Key(self._currentEncryptKey) #send client random encrypted with - certificate = self._transport.getGCCServerSettings().SC_SECURITY.serverCertificate.certData + certificate = self._transport.getGCCServerSettings().SC_SECURITY.serverCertificate.certData._value + if isinstance(certificate, gcc.ProprietaryServerCertificate): + modulus = bin2bn(certificate.PublicKeyBlob.modulus.value[::-1]) + publicExponent = certificate.PublicKeyBlob.pubExp.value + elif isinstance(certificate, gcc.X509CertificateChain): + modulus, publicExponent = x509.extractRSAKey(x509.load(certificate.CertBlobArray[-1].abCert.value)) + else: + raise InvalidExpectedDataException("unknown certificate type") + #reverse because bignum in little endian - serverPublicKey = rsa.PublicKey(bin2bn(certificate.PublicKeyBlob.modulus.value[::-1]), certificate.PublicKeyBlob.pubExp.value) + serverPublicKey = rsa.PublicKey(modulus, publicExponent) message = ClientSecurityExchangePDU() #reverse because bignum in little endian diff --git a/rdpy/protocol/rdp/x224.py b/rdpy/protocol/rdp/x224.py index 89cbbc9..d52562f 100644 --- a/rdpy/protocol/rdp/x224.py +++ b/rdpy/protocol/rdp/x224.py @@ -132,7 +132,7 @@ class X224Layer(LayerAutomata, IStreamSender): LayerAutomata.__init__(self, presentation) #default selectedProtocol is SSl #client requested selectedProtocol - self._requestedProtocol = Protocols.PROTOCOL_RDP + self._requestedProtocol = Protocols.PROTOCOL_RDP #server selected selectedProtocol self._selectedProtocol = Protocols.PROTOCOL_SSL