def verify_ssh_sig(self, data, msg): if len(msg.asbytes()) == 40: # spies.com bug: signature has no header sig = msg.asbytes() else: kind = msg.get_text() if kind != 'ssh-dss': return 0 sig = msg.get_binary() # pull out (r, s) which are NOT encoded as mpints sigR = util.inflate_long(sig[:20], 1) sigS = util.inflate_long(sig[20:], 1) signature = encode_dss_signature(sigR, sigS) key = dsa.DSAPublicNumbers( y=self.y, parameter_numbers=dsa.DSAParameterNumbers( p=self.p, q=self.q, g=self.g ) ).public_key(backend=default_backend()) verifier = key.verifier(signature, hashes.SHA1()) verifier.update(data) try: verifier.verify() except InvalidSignature: return False else: return True
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None, validate_point=True): self.verifying_key = None self.signing_key = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.signing_key, self.verifying_key = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != 'ecdsa-sha2-nistp256': raise SSHException('Invalid key') curvename = msg.get_text() if curvename != 'nistp256': raise SSHException("Can't handle curve of type %s" % curvename) pointinfo = msg.get_binary() if pointinfo[0:1] != four_byte: raise SSHException('Point compression is being used: %s' % binascii.hexlify(pointinfo)) curve = ec.SECP256R1() numbers = ec.EllipticCurvePublicNumbers( x=inflate_long(pointinfo[1:1 + curve.key_size // 8], always_positive=True), y=inflate_long(pointinfo[1 + curve.key_size // 8:], always_positive=True), curve=curve ) self.verifying_key = numbers.public_key(backend=default_backend()) self.size = 256
def verify_ssh_sig(self, data, msg): if msg.get_text() != 'ssh-rsa': return False sig = util.inflate_long(msg.get_binary(), True) # verify the signature by SHA'ing the data and encrypting it using the # public key. some wackiness ensues where we "pkcs1imify" the 20-byte # hash into a string as long as the RSA key. hash_obj = util.inflate_long(self._pkcs1imify(SHA.new(data).digest()), True) rsa = RSA.construct((long(self.n), long(self.e))) return rsa.verify(hash_obj, (sig,))
def get_mpint(self): """ Fetch a long int (mpint) from the stream. :return: an arbitrary-length integer (`long`). """ return util.inflate_long(self.get_binary())
def verify_ssh_sig(self, data, msg): if len(msg.asbytes()) == 40: # spies.com bug: signature has no header sig = msg.asbytes() else: kind = msg.get_text() if kind != 'ssh-dss': return 0 sig = msg.get_binary() # pull out (r, s) which are NOT encoded as mpints sigR = util.inflate_long(sig[:20], 1) sigS = util.inflate_long(sig[20:], 1) sigM = util.inflate_long(sha1(data).digest(), 1) dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q))) return dss.verify(sigM, (sigR, sigS))
def verify_ssh_sig(self, data, msg): if len(bytes(msg)) == 40: # spies.com bug: signature has no header sig = bytes(msg) else: kind = msg.get_string() if kind != b'ssh-dss': return 0 sig = msg.get_string() # pull out (r, s) which are NOT encoded as mpints sigR = util.inflate_long(sig[:20], 1) sigS = util.inflate_long(sig[20:], 1) sigM = util.inflate_long(SHA.new(data).digest(), 1) dss = DSA.construct((self.y, self.g, self.p, self.q)) return dss.verify(sigM, (sigR, sigS))
def get_mpint(self): """ Fetch a long int (mpint) from the stream. @return: an arbitrary-length integer. @rtype: long """ return util.inflate_long(self.get_string())
def decode_next(self): if self.idx >= len(self.content): return None ident = byte_ord(self.content[self.idx]) self.idx += 1 if (ident & 31) == 31: # identifier > 30 ident = 0 while self.idx < len(self.content): t = byte_ord(self.content[self.idx]) self.idx += 1 ident = (ident << 7) | (t & 0x7f) if not (t & 0x80): break if self.idx >= len(self.content): return None # now fetch length size = byte_ord(self.content[self.idx]) self.idx += 1 if size & 0x80: # more complimicated... # FIXME: theoretically should handle indefinite-length (0x80) t = size & 0x7f if self.idx + t > len(self.content): return None size = util.inflate_long( self.content[self.idx : self.idx + t], True ) self.idx += t if self.idx + size > len(self.content): # can't fit return None data = self.content[self.idx : self.idx + size] self.idx += size # now switch on id if ident == 0x30: # sequence return self.decode_sequence(data) elif ident == 2: # int return util.inflate_long(data) else: # 1: boolean (00 false, otherwise true) msg = "Unknown ber encoding type {:d} (robey is lazy)" raise BERException(msg.format(ident))
def get_adaptive_int(self): """ Fetch an int from the stream. :return: a 32-bit unsigned `int`. """ byte = self.get_bytes(1) if byte == max_byte: return util.inflate_long(self.get_binary()) byte += self.get_bytes(3) return struct.unpack('>I', byte)[0]
def sign_ssh_data(self, data): digest = sha1(data).digest() dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q), long(self.x))) # generate a suitable k qsize = len(util.deflate_long(self.q, 0)) while True: k = util.inflate_long(os.urandom(qsize), 1) if (k > 2) and (k < self.q): break r, s = dss.sign(util.inflate_long(digest, 1), k) m = Message() m.add_string('ssh-dss') # apparently, in rare cases, r or s may be shorter than 20 bytes! rstr = util.deflate_long(r, 0) sstr = util.deflate_long(s, 0) if len(rstr) < 20: rstr = zero_byte * (20 - len(rstr)) + rstr if len(sstr) < 20: sstr = zero_byte * (20 - len(sstr)) + sstr m.add_string(rstr + sstr) return m
def get_int(self): """ Fetch an int from the stream. @return: a 32-bit unsigned integer. @rtype: int """ byte = self.get_bytes(1) if byte == max_byte: return util.inflate_long(self.get_binary()) byte += self.get_bytes(3) return struct.unpack('>I', byte)[0]
def _generate_x(self): # generate an "x" (1 < x < q), where q is (p-1)/2. # p is a 128-byte (1024-bit) number, where the first 64 bits are 1. # therefore q can be approximated as a 2^1023. we drop the subset of # potential x where the first 63 bits are 1, because some of those will be # larger than q (but this is a tiny tiny subset of potential x). while 1: x_bytes = os.urandom(128) x_bytes = byte_mask(x_bytes[0], 0x7F) + x_bytes[1:] if x_bytes[:8] != b7fffffffffffffff and x_bytes[:8] != b0000000000000000: break self.x = util.inflate_long(x_bytes)
def sign_ssh_data(self, rng, data): digest = SHA.new(data).digest() dss = DSA.construct((self.y, self.g, self.p, self.q, self.x)) # generate a suitable k qsize = len(util.deflate_long(self.q, 0)) while True: k = util.inflate_long(rng.read(qsize), 1) if (k > 2) and (k < self.q): break r, s = dss.sign(util.inflate_long(digest, 1), k) m = Message() m.add_string(b'ssh-dss') # apparently, in rare cases, r or s may be shorter than 20 bytes! rstr = util.deflate_long(r, 0) sstr = util.deflate_long(s, 0) if len(rstr) < 20: rstr = b'\x00' * (20 - len(rstr)) + rstr if len(sstr) < 20: sstr = b'\x00' * (20 - len(sstr)) + sstr m.add_string(rstr + sstr) return m
def _generate_x(self): # generate an "x" (1 < x < q), where q is (p-1)/2. # p is a 128-byte (1024-bit) number, where the first 64 bits are 1. # therefore q can be approximated as a 2^1023. we drop the subset of # potential x where the first 63 bits are 1, because some of those will be # larger than q (but this is a tiny tiny subset of potential x). while 1: x_bytes = self.transport.rng.read(128) x_bytes = chr(ord(x_bytes[0]) & 0x7f) + x_bytes[1:] if (x_bytes[:8] != '\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF') and \ (x_bytes[:8] != '\x00\x00\x00\x00\x00\x00\x00\x00'): break self.x = util.inflate_long(x_bytes)
def _generate_x(self): # generate an "x" (1 < x < q), where q is (p-1)/2. # p is a 128-byte (1024-bit) number, where the first 64 bits are 1. # therefore q can be approximated as a 2^1023. we drop the subset of # potential x where the first 63 bits are 1, because some of those will be # larger than q (but this is a tiny tiny subset of potential x). while 1: x_bytes = os.urandom(128) x_bytes = byte_mask(x_bytes[0], 0x7f) + x_bytes[1:] if (x_bytes[:8] != b7fffffffffffffff and x_bytes[:8] != b0000000000000000): break self.x = util.inflate_long(x_bytes)
def _generate_x(self): # generate an "x" (1 < x < q), where q is (p-1)/2. # p is a 128-byte (1024-bit) number, where the first 64 bits are 1. # therefore q can be approximated as a 2^1023. we drop the subset of # potential x where the first 63 bits are 1, because some of those will be # larger than q (but this is a tiny tiny subset of potential x). while 1: self.transport.randpool.stir() x_bytes = self.transport.randpool.get_bytes(128) x_bytes = chr(ord(x_bytes[0]) & 0x7f) + x_bytes[1:] if (x_bytes[:8] != '\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF') and \ (x_bytes[:8] != '\x00\x00\x00\x00\x00\x00\x00\x00'): break self.x = util.inflate_long(x_bytes)
def _generate_x(self): """ generate an "x" (1 < x < q), where q is (p-1)/2. p is a 128-byte (1024-bit) number, where the first 64 bits are 1. therefore q can be approximated as a 2^1023. we drop the subset of potential x where the first 63 bits are 1, because some of those will be larger than q (but this is a tiny tiny subset of potential x). """ while 1: x_bytes = self.transport.rng.read(128) x_bytes = byte_mask(x_bytes[0], 0x7f) + x_bytes[1:] if (x_bytes[:8] != self.b7fffffffffffffff) and \ (x_bytes[:8] != self.b0000000000000000): break self.x = util.inflate_long(x_bytes)
def _generate_prime(bits, randpool): "primtive attempt at prime generation" hbyte_mask = pow(2, bits % 8) - 1 while True: # loop catches the case where we increment n into a higher bit-range x = randpool.get_bytes((bits+7) // 8) if hbyte_mask > 0: x = chr(ord(x[0]) & hbyte_mask) + x[1:] n = util.inflate_long(x, 1) n |= 1 n |= (1 << (bits - 1)) while not number.isPrime(n): n += 2 if util.bit_length(n) == bits: return n
def _generate_prime(bits, randpool): "primtive attempt at prime generation" hbyte_mask = pow(2, bits % 8) - 1 while True: # loop catches the case where we increment n into a higher bit-range x = randpool.get_bytes((bits + 7) // 8) if hbyte_mask > 0: x = chr(ord(x[0]) & hbyte_mask) + x[1:] n = util.inflate_long(x, 1) n |= 1 n |= (1 << (bits - 1)) while not number.isPrime(n): n += 2 if util.bit_length(n) == bits: return n
def _generate_x(self): """ generate an "x" (1 < x < q), where q is (p-1)/2. p is a 128-byte (1024-bit) number, where the first 64 bits are 1. therefore q can be approximated as a 2^1023. we drop the subset of potential x where the first 63 bits are 1, because some of those will be larger than q (but this is a tiny tiny subset of potential x). """ while 1: x_bytes = os.urandom(128) x_bytes = byte_mask(x_bytes[0], 0x7f) + x_bytes[1:] first = x_bytes[:8] if first not in (self.b7fffffffffffffff, self.b0000000000000000): break self.x = util.inflate_long(x_bytes)
def _uint32_cstruct_unpack(self, data, strformat): """ Used to read new OpenSSH private key format. Unpacks a c data structure containing a mix of 32-bit uints and variable length strings prefixed by 32-bit uint size field, according to the specified format. Returns the unpacked vars in a tuple. Format strings: s - denotes a string i - denotes a long integer, encoded as a byte string u - denotes a 32-bit unsigned integer r - the remainder of the input string, returned as a string """ arr = [] idx = 0 try: for f in strformat: if f == "s": # string s_size = struct.unpack(">L", data[idx:idx + 4])[0] idx += 4 s = data[idx:idx + s_size] idx += s_size arr.append(s) if f == "i": # long integer s_size = struct.unpack(">L", data[idx:idx + 4])[0] idx += 4 s = data[idx:idx + s_size] idx += s_size i = util.inflate_long(s, True) arr.append(i) elif f == "u": # 32-bit unsigned int u = struct.unpack(">L", data[idx:idx + 4])[0] idx += 4 arr.append(u) elif f == "r": # remainder as string s = data[idx:] arr.append(s) break except Exception as e: # PKey-consuming code frequently wants to save-and-skip-over issues # with loading keys, and uses SSHException as the (really friggin # awful) signal for this. So for now...we do this. raise SSHException(str(e)) return tuple(arr)
def _generate_prime(bits, rng): """primtive attempt at prime generation""" hbyte_mask = pow(2, bits % 8) - 1 while True: # loop catches the case where we increment n into a higher bit-range x = rng.read((bits + 7) // 8) if hbyte_mask > 0: x = byte_mask(x[0], hbyte_mask) + x[1:] n = util.inflate_long(x, 1) n |= 1 n |= (1 << (bits - 1)) while not number.isPrime(n): n += 2 if util.bit_length(n) == bits: break return n
def _generate_x(self): # generate an "x" (1 < x < (p-1)/2). q = (self.p - 1) // 2 qnorm = util.deflate_long(q, 0) qhbyte = ord(qnorm[0]) bytes = len(qnorm) qmask = 0xff while not (qhbyte & 0x80): qhbyte <<= 1 qmask >>= 1 while True: x_bytes = self.transport.rng.read(bytes) x_bytes = chr(ord(x_bytes[0]) & qmask) + x_bytes[1:] x = util.inflate_long(x_bytes, 1) if (x > 1) and (x < q): break self.x = x
def _generate_x(self): # generate an "x" (1 < x < (p-1)/2). q = (self.p - 1) // 2 qnorm = util.deflate_long(q, 0) qhbyte = qnorm[0] bytes = len(qnorm) qmask = 0xff while not (qhbyte & 0x80): qhbyte <<= 1 qmask >>= 1 while True: x_bytes = bytearray(self.transport.rng.read(bytes)) x_bytes[0] &= qmask x = util.inflate_long(x_bytes, 1) if (x > 1) and (x < q): break self.x = x
def _generate_x(self): # generate an "x" (1 < x < (p-1)/2). q = (self.p - 1) // 2 qnorm = util.deflate_long(q, 0) qhbyte = byte_ord(qnorm[0]) byte_count = len(qnorm) qmask = 0xff while not (qhbyte & 0x80): qhbyte <<= 1 qmask >>= 1 while True: x_bytes = os.urandom(byte_count) x_bytes = byte_mask(x_bytes[0], qmask) + x_bytes[1:] x = util.inflate_long(x_bytes, 1) if (x > 1) and (x < q): break self.x = x
def _generate_x(self): # generate an "x" (1 < x < (p-1)/2). q = (self.p - 1) // 2 qnorm = util.deflate_long(q, 0) qhbyte = ord(qnorm[0]) bytes = len(qnorm) qmask = 0xff while not (qhbyte & 0x80): qhbyte <<= 1 qmask >>= 1 while True: self.transport.randpool.stir() x_bytes = self.transport.randpool.get_bytes(bytes) x_bytes = chr(ord(x_bytes[0]) & qmask) + x_bytes[1:] x = util.inflate_long(x_bytes, 1) if (x > 1) and (x < q): break self.x = x
def _roll_random(rpool, n): "returns a random # from 0 to N-1" bits = util.bit_length(n - 1) bytes = (bits + 7) // 8 hbyte_mask = pow(2, bits % 8) - 1 # so here's the plan: # we fetch as many random bits as we'd need to fit N-1, and if the # generated number is >= N, we try again. in the worst case (N-1 is a # power of 2), we have slightly better than 50% odds of getting one that # fits, so i can't guarantee that this loop will ever finish, but the odds # of it looping forever should be infinitesimal. while True: x = rpool.get_bytes(bytes) if hbyte_mask > 0: x = chr(ord(x[0]) & hbyte_mask) + x[1:] num = util.inflate_long(x, 1) if num < n: return num
def _roll_random(rpool, n): "returns a random # from 0 to N-1" bits = util.bit_length(n-1) bytes = (bits + 7) // 8 hbyte_mask = pow(2, bits % 8) - 1 # so here's the plan: # we fetch as many random bits as we'd need to fit N-1, and if the # generated number is >= N, we try again. in the worst case (N-1 is a # power of 2), we have slightly better than 50% odds of getting one that # fits, so i can't guarantee that this loop will ever finish, but the odds # of it looping forever should be infinitesimal. while True: x = rpool.get_bytes(bytes) if hbyte_mask > 0: x = chr(ord(x[0]) & hbyte_mask) + x[1:] num = util.inflate_long(x, 1) if num < n: return num