示例#1
0
    def _decrypt_data(self, data, timestamp, ttl):
        current_time = int(time.time())
        if ttl is not None:
            if timestamp + ttl < current_time:
                raise InvalidToken

            if current_time + _MAX_CLOCK_SKEW < timestamp:
                raise InvalidToken

        self._verify_signature(data)

        iv = data[9:25]
        ciphertext = data[25:-32]
        decryptor = Cipher(algorithms.AES(self._encryption_key), modes.CBC(iv),
                           self._backend).decryptor()
        plaintext_padded = decryptor.update_user(ciphertext)
        try:
            plaintext_padded += decryptor.finalize()
        except ValueError:
            raise InvalidToken
        unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()

        unpadded = unpadder.update_user(plaintext_padded)
        try:
            unpadded += unpadder.finalize()
        except ValueError:
            raise InvalidToken
        return unpadded
示例#2
0
 def _read_private_key(self, tag, f, password=None):
     lines = f.readlines()
     start = 0
     beginning_of_key = "-----BEGIN " + tag + " PRIVATE KEY-----"
     while start < len(lines) and lines[start].strip() != beginning_of_key:
         start += 1
     if start >= len(lines):
         raise SSHException("not a valid " + tag + " private key file")
     # parse any headers first
     headers = {}
     start += 1
     while start < len(lines):
         l = lines[start].split(": ")
         if len(l) == 1:
             break
         headers[l[0].lower()] = l[1].strip()
         start += 1
     # find end
     end = start
     ending_of_key = "-----END " + tag + " PRIVATE KEY-----"
     while end < len(lines) and lines[end].strip() != ending_of_key:
         end += 1
     # if we trudged to the end of the file, just try to cope.
     try:
         data = decodebytes(b("".join(lines[start:end])))
     except base64.binascii.Error as e:
         raise SSHException("base64 decoding error: " + str(e))
     if "proc-type" not in headers:
         # unencryped: done
         return data
     # encrypted keyfile: will need a password
     proc_type = headers["proc-type"]
     if proc_type != "4,ENCRYPTED":
         raise SSHException(
             'Unknown private key structure "{}"'.format(proc_type)
         )
     try:
         encryption_type, saltstr = headers["dek-info"].split(",")
     except:
         raise SSHException("Can't parse DEK-info in private key file")
     if encryption_type not in self._CIPHER_TABLE:
         raise SSHException(
             'Unknown private key cipher "{}"'.format(encryption_type)
         )
     # if no password was passed in,
     # raise an exception pointing out that we need one
     if password is None:
         raise PasswordRequiredException("Private key file is encrypted")
     cipher = self._CIPHER_TABLE[encryption_type]["cipher"]
     keysize = self._CIPHER_TABLE[encryption_type]["keysize"]
     mode = self._CIPHER_TABLE[encryption_type]["mode"]
     salt = unhexlify(b(saltstr))
     key = util.generate_key_bytes(md5, salt, password, keysize)
     decryptor = Cipher(
         cipher(key), mode(salt), backend=default_backend()
     ).decryptor()
     return decryptor.update_user(data) + decryptor.finalize()
示例#3
0
    def _encrypt_from_parts(self, data, current_time, iv):
        utils._check_bytes("data", data)

        padder = padding.PKCS7(algorithms.AES.block_size).padder()
        padded_data = padder.update_user(data) + padder.finalize()
        encryptor = Cipher(algorithms.AES(self._encryption_key), modes.CBC(iv),
                           self._backend).encryptor()
        ciphertext = encryptor.update_user(padded_data) + encryptor.finalize()

        basic_parts = (b"\x80" + struct.pack(">Q", current_time) + iv +
                       ciphertext)

        h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
        h.update(basic_parts)
        hmac = h.finalize()
        return base64.urlsafe_b64encode(basic_parts + hmac)
示例#4
0
    def _parse_signing_key_data(self, data, password):
        from paramiko.transport import Transport

        # We may eventually want this to be usable for other key types, as
        # OpenSSH moves to it, but for now this is just for Ed25519 keys.
        # This format is described here:
        # https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
        # The description isn't totally complete, and I had to refer to the
        # source for a full implementation.
        message = Message(data)
        if message.get_bytes(len(OPENSSH_AUTH_MAGIC)) != OPENSSH_AUTH_MAGIC:
            raise SSHException("Invalid key")

        ciphername = message.get_text()
        kdfname = message.get_text()
        kdfoptions = message.get_binary()
        num_keys = message.get_int()

        if kdfname == "none":
            # kdfname of "none" must have an empty kdfoptions, the ciphername
            # must be "none"
            if kdfoptions or ciphername != "none":
                raise SSHException("Invalid key")
        elif kdfname == "bcrypt":
            if not password:
                raise PasswordRequiredException(
                    "Private key file is encrypted")
            kdf = Message(kdfoptions)
            bcrypt_salt = kdf.get_binary()
            bcrypt_rounds = kdf.get_int()
        else:
            raise SSHException("Invalid key")

        if ciphername != "none" and ciphername not in Transport._cipher_info:
            raise SSHException("Invalid key")

        public_keys = []
        for _ in range(num_keys):
            pubkey = Message(message.get_binary())
            if pubkey.get_text() != "ssh-ed25519":
                raise SSHException("Invalid key")
            public_keys.append(pubkey.get_binary())

        private_ciphertext = message.get_binary()
        if ciphername == "none":
            private_data = private_ciphertext
        else:
            cipher = Transport._cipher_info[ciphername]
            key = bcrypt.kdf(
                password=b(password),
                salt=bcrypt_salt,
                desired_key_bytes=cipher["key-size"] + cipher["block-size"],
                rounds=bcrypt_rounds,
                # We can't control how many rounds are on disk, so no sense
                # warning about it.
                ignore_few_rounds=True,
            )
            decryptor = Cipher(
                cipher["class"](key[:cipher["key-size"]]),
                cipher["mode"](key[cipher["key-size"]:]),
                backend=default_backend(),
            ).decryptor()
            private_data = (decryptor.update_user(private_ciphertext) +
                            decryptor.finalize())

        message = Message(unpad(private_data))
        if message.get_int() != message.get_int():
            raise SSHException("Invalid key")

        signing_keys = []
        for i in range(num_keys):
            if message.get_text() != "ssh-ed25519":
                raise SSHException("Invalid key")
            # A copy of the public key, again, ignore.
            public = message.get_binary()
            key_data = message.get_binary()
            # The second half of the key data is yet another copy of the public
            # key...
            signing_key = nacl.signing.SigningKey(key_data[:32])
            # Verify that all the public keys are the same...
            assert (signing_key.verify_key.encode() == public == public_keys[i]
                    == key_data[32:])
            signing_keys.append(signing_key)
            # Comment, ignore.
            message.get_binary()

        if len(signing_keys) != 1:
            raise SSHException("Invalid key")
        return signing_keys[0]