Пример #1
0
def decrypt_device_line(patient_id, key, data):
    """ config is expected to be 3 colon separated values.
        value 1 is the symmetric key, encrypted with the patient's public key.
        value 2 is the initialization vector for the AES CBC cipher.
        value 3 is the config, encrypted using AES CBC, with the provided key and iv. """
    iv, data = data.split(":")
    iv = decode_base64(
        iv.encode("utf-8"))  #handle non-ascii encoding garbage...
    data = decode_base64(data.encode("utf-8"))
    if not data:
        raise InvalidData()
    if not iv:
        raise InvalidIV
    try:
        decrypted = AES.new(key, mode=AES.MODE_CBC, IV=iv).decrypt(data)
    except Exception:
        if iv is None: len_iv = "None"
        else: len_iv = len(iv)
        if data is None: len_data = "None"
        else: len_data = len(data)
        if key is None: len_key = "None"
        else: len_key = len(key)
        # these print statements cause problems in getting encryption errors because the print
        # statement will print to an ascii formatted log file on the server, which causes
        # ascii encoding error.  Enable them for debugging only.
        # print("length iv: %s, length data: %s, length key: %s" % (len_iv, len_data, len_key))
        # print('%s %s %s' % (patient_id, key, data))
        raise
    return remove_PKCS5_padding(decrypted)
Пример #2
0
def decrypt_device_line(patient_id, key, data):
    """ config is expected to be 3 colon separated values.
        value 1 is the symmetric key, encrypted with the patient's public key.
        value 2 is the initialization vector for the AES CBC cipher.
        value 3 is the config, encrypted using AES CBC, with the provided key and iv. """
    iv, data = data.split(":")
    iv = decode_base64( iv.encode( "utf-8" ) ) #handle non-ascii encoding garbage...
    data = decode_base64( data.encode( "utf-8" ) )
    if not data:
        raise InvalidData()
    if not iv:
        raise InvalidIV
    try:
        decrypted = AES.new(key, mode=AES.MODE_CBC, IV=iv).decrypt( data )
    except Exception:
        if iv is None: len_iv = "None"
        else: len_iv = len(iv)
        if data is None: len_data = "None"
        else: len_data = len(data)
        if key is None: len_key = "None"
        else: len_key = len(key)
        print "length iv: %s, length data: %s, length key: %s" % (len_iv, len_data, len_key)
        print patient_id, key, data
        raise
    return remove_PKCS5_padding( decrypted )
Пример #3
0
def decrypt_device_file(patient_id, original_data, private_key, user):
    """ Runs the line-by-line decryption of a file encrypted by a device.
    This function is a special handler for iOS file uploads. """
    def create_line_error_db_entry(error_type):
        # declaring this inside decrypt device file to access its function-global variables
        # TODO @Eli consider enabling this on prod as well
        if IS_STAGING:
            LineEncryptionError.objects.create(
                type=error_type,
                base64_decryption_key=private_key.decrypt(decoded_key),
                line=encode_base64(line),
                prev_line=encode_base64(file_data[i - 1] if i > 0 else ''),
                next_line=encode_base64(file_data[i +
                                                  1] if i < len(file_data) -
                                        1 else ''),
                participant=user,
            )

    def create_decryption_key_error(traceback):
        DecryptionKeyError.objects.create(
            file_path=request.values['file_name'],
            contents=original_data,
            traceback=traceback,
            participant=user,
        )

    bad_lines = []
    error_types = []
    error_count = 0
    return_data = ""
    file_data = [line for line in original_data.split('\n') if line != ""]

    if not file_data:
        raise HandledError(
            "The file had no data in it.  Return 200 to delete file from device."
        )

    # The following code is strange because of an unfortunate design design decision made quite
    # some time ago: the decryption key is encoded as base64 twice, once wrapping the output of the
    # RSA encryption, and once wrapping the AES decryption key.
    # The second of the two except blocks likely means that the device failed to write the encryption
    # key as the first line of the file, but it may be a valid (but undecryptable) line of the  file.
    try:
        decoded_key = decode_base64(file_data[0].encode("utf-8"))
    except (TypeError, IndexError, PaddingException) as e:
        create_decryption_key_error(traceback.format_exc())
        raise DecryptionKeyInvalidError("invalid decryption key. %s" %
                                        e.message)

    try:
        decrypted_key = decode_base64(private_key.decrypt(decoded_key))
    except (TypeError, IndexError, PaddingException) as e:
        create_decryption_key_error(traceback.format_exc())
        raise DecryptionKeyInvalidError("invalid decryption key. %s" %
                                        e.message)

    for i, line in enumerate(file_data):
        #we need to skip the first line (the decryption key), but need real index values in i
        if i == 0: continue

        if line is None:
            #this case causes weird behavior inside decrypt_device_line, so we test for it instead.
            error_count += 1
            create_line_error_db_entry(LineEncryptionError.LINE_IS_NONE)
            error_types.append(LineEncryptionError.LINE_IS_NONE)
            bad_lines.append(line)
            print("encountered empty line of data, ignoring.")
            continue

        try:
            return_data += decrypt_device_line(patient_id, decrypted_key,
                                               line) + "\n"
        except Exception as e:
            error_count += 1

            error_message = "There was an error in user decryption: "
            if isinstance(e, IndexError):
                error_message += "Something is wrong with data padding:\n\tline: %s" % line
                log_error(e, error_message)
                create_line_error_db_entry(LineEncryptionError.PADDING_ERROR)
                error_types.append(LineEncryptionError.PADDING_ERROR)
                bad_lines.append(line)
                continue

            if isinstance(e, TypeError) and decrypted_key is None:
                error_message += "The key was empty:\n\tline: %s" % line
                log_error(e, error_message)
                create_line_error_db_entry(LineEncryptionError.EMPTY_KEY)
                error_types.append(LineEncryptionError.EMPTY_KEY)
                bad_lines.append(line)
                continue

            ################### skip these errors ##############################
            if "unpack" in e.message:
                error_message += "malformed line of config, dropping it and continuing."
                log_error(e, error_message)
                create_line_error_db_entry(
                    LineEncryptionError.MALFORMED_CONFIG)
                error_types.append(LineEncryptionError.MALFORMED_CONFIG)
                bad_lines.append(line)
                #the config is not colon separated correctly, this is a single
                # line error, we can just drop it.
                # implies an interrupted write operation (or read)
                continue

            if "Input strings must be a multiple of 16 in length" in e.message:
                error_message += "Line was of incorrect length, dropping it and continuing."
                log_error(e, error_message)
                create_line_error_db_entry(LineEncryptionError.INVALID_LENGTH)
                error_types.append(LineEncryptionError.INVALID_LENGTH)
                bad_lines.append(line)
                continue

            if isinstance(e, InvalidData):
                error_message += "Line contained no data, skipping: " + str(
                    line)
                log_error(e, error_message)
                create_line_error_db_entry(LineEncryptionError.LINE_EMPTY)
                error_types.append(LineEncryptionError.LINE_EMPTY)
                bad_lines.append(line)
                continue

            if isinstance(e, InvalidIV):
                error_message += "Line contained no iv, skipping: " + str(line)
                log_error(e, error_message)
                create_line_error_db_entry(LineEncryptionError.IV_MISSING)
                error_types.append(LineEncryptionError.IV_MISSING)
                bad_lines.append(line)
                continue

            ##################### flip out on these errors #####################
            if 'AES key' in e.message:
                error_message += "AES key has bad length."
                create_line_error_db_entry(
                    LineEncryptionError.AES_KEY_BAD_LENGTH)
                error_types.append(LineEncryptionError.AES_KEY_BAD_LENGTH)
                bad_lines.append(line)
            elif 'IV must be' in e.message:
                error_message += "iv has bad length."
                create_line_error_db_entry(LineEncryptionError.IV_BAD_LENGTH)
                error_types.append(LineEncryptionError.IV_BAD_LENGTH)
                bad_lines.append(line)
            elif 'Incorrect padding' in e.message:
                error_message += "base64 padding error, config is truncated."
                create_line_error_db_entry(LineEncryptionError.MP4_PADDING)
                error_types.append(LineEncryptionError.MP4_PADDING)
                bad_lines.append(line)
                # this is only seen in mp4 files. possibilities:
                #  upload during write operation.
                #  broken base64 conversion in the app
                #  some unanticipated error in the file upload
            else:
                raise  #If none of the above errors happened, raise the error.
            raise HandledError(error_message)
            # if any of them did happen, raise a HandledError to cease execution.

    if error_count:
        EncryptionErrorMetadata.objects.create(
            file_name=request.values['file_name'],
            total_lines=len(file_data),
            number_errors=error_count,
            error_lines=json.dumps(bad_lines),
            error_types=json.dumps(error_types),
            participant=user,
        )

    return return_data
Пример #4
0
def decrypt_device_file(patient_id, original_data, private_key, user):
    """ Runs the line-by-line decryption of a file encrypted by a device.
    This function is a special handler for iOS file uploads. """
    
    def create_line_error_db_entry(error_type):
        # declaring this inside decrypt device file to access its function-global variables
        if IS_STAGING:
            LineEncryptionError.create( {
                "type": error_type,
                "line": encode_base64(line),
                "base64_decryption_key": encode_base64(private_key.decrypt(decoded_key)),
                "prev_line": encode_base64(file_data[i - 1]) if i > 0 else None,
                "next_line": encode_base64(file_data[i + 1]) if i < len(file_data) - 1 else None },
                random_id=True
            )
            
    bad_lines = []
    error_types = []
    error_count = 0
    return_data = ""
    file_data = [line for line in original_data.split('\n') if line != ""]
    
    if not file_data:
        raise HandledError("The file had no data in it.  Return 200 to delete file from device.")
        
    try: #get the decryption key from the file.
        decoded_key = decode_base64(file_data[0].encode("utf-8"))
        decrypted_key = decode_base64(private_key.decrypt( decoded_key ) )
    except (TypeError, IndexError) as e:
        DecryptionKeyError.create( {
            "file_path": request.values['file_name'],
            "contents": original_data,
            "user_id": user._id },
            random_id=True
        )
        raise DecryptionKeyInvalidError("invalid decryption key. %s" % e.message)
    
    # (we have an inefficiency in this encryption process, this might not need to be doubly
    # encoded in base64.  This is probably never going to be changed.)
    # The following is all error catching code for bugs we encountered (and solved) in development.
    # print "length decrypted key", len(decrypted_key)
    
    for i, line in enumerate(file_data):
        #we need to skip the first line (the decryption key), but need real index values in i
        if i==0: continue
        
        if line is None:
            #this case causes weird behavior inside decrypt_device_line, so we test for it instead.
            error_count += 1
            create_line_error_db_entry(LINE_IS_NONE)
            error_types.append(LINE_IS_NONE)
            bad_lines.append(line)
            print "encountered empty line of data, ignoring."
            continue
            
        try:
            return_data += decrypt_device_line(patient_id, decrypted_key, line) + "\n"
        except Exception as e:
            error_count += 1
            
            error_message = "There was an error in user decryption: "
            if isinstance(e, IndexError):
                error_message += "Something is wrong with data padding:\n\tline: %s" % line
                log_error(e, error_message)
                create_line_error_db_entry(PADDING_ERROR)
                error_types.append(PADDING_ERROR)
                bad_lines.append(line)
                continue

            if isinstance(e, TypeError) and decrypted_key is None:
                error_message += "The key was empty:\n\tline: %s" % line
                log_error(e, error_message)
                create_line_error_db_entry(EMPTY_KEY)
                error_types.append(EMPTY_KEY)
                bad_lines.append(line)
                continue

            ################### skip these errors ##############################
            if "unpack" in e.message:
                error_message += "malformed line of config, dropping it and continuing."
                log_error(e, error_message)
                create_line_error_db_entry(MALFORMED_CONFIG)
                error_types.append(MALFORMED_CONFIG)
                bad_lines.append(line)
                #the config is not colon separated correctly, this is a single
                # line error, we can just drop it.
                # implies an interrupted write operation (or read)
                continue
                
            if "Input strings must be a multiple of 16 in length" in e.message:
                error_message += "Line was of incorrect length, dropping it and continuing."
                log_error(e, error_message)
                create_line_error_db_entry(INVALID_LENGTH)
                error_types.append(INVALID_LENGTH)
                bad_lines.append(line)
                continue
                
            if isinstance(e, InvalidData):
                error_message += "Line contained no data, skipping: " + str(line)
                log_error(e, error_message)
                create_line_error_db_entry(LINE_EMPTY)
                error_types.append(LINE_EMPTY)
                bad_lines.append(line)
                continue
                
            if isinstance(e, InvalidIV):
                error_message += "Line contained no iv, skipping: " + str(line)
                log_error(e, error_message)
                create_line_error_db_entry(IV_MISSING)
                error_types.append(IV_MISSING)
                bad_lines.append(line)
                continue
                
            ##################### flip out on these errors #####################
            if 'AES key' in e.message:
                error_message += "AES key has bad length."
                create_line_error_db_entry(AES_KEY_BAD_LENGTH)
                error_types.append(AES_KEY_BAD_LENGTH)
                bad_lines.append(line)
            elif 'IV must be' in e.message:
                error_message += "iv has bad length."
                create_line_error_db_entry(IV_BAD_LENGTH)
                error_types.append(IV_BAD_LENGTH)
                bad_lines.append(line)
            elif 'Incorrect padding' in e.message:
                error_message += "base64 padding error, config is truncated."
                create_line_error_db_entry(MP4_PADDING)
                error_types.append(MP4_PADDING)
                bad_lines.append(line)
                # this is only seen in mp4 files. possibilities:
                #  upload during write operation.
                #  broken base64 conversion in the app
                #  some unanticipated error in the file upload
            else:
                raise #If none of the above errors happened, raise the error.
            raise HandledError(error_message)
            # if any of them did happen, raise a HandledError to cease execution.
    
    if error_count:
        EncryptionErrorMetadata.create( {
            "file_name": request.values['file_name'],
            "total_lines": len(file_data),
            "number_errors": error_count,
            "errors_lines": bad_lines,
            "error_types": error_types},
            random_id=True
        )
        
    return return_data