diff --git a/sdk/python/core/README.md b/sdk/python/core/README.md index 34c71042..331af213 100644 --- a/sdk/python/core/README.md +++ b/sdk/python/core/README.md @@ -4,8 +4,11 @@ For more information see our official documentation page https://docs.keeper.io/ # Change Log +## 16.6.6 +* KSM-552 - Stop generating UIDs that start with "-" + ## 16.6.5 -* KSM-529 - Hande broken encryption in records and files +* KSM-529 - Handle broken encryption in records and files ## 16.6.4 * KSM-488 - Remove unused package dependencies diff --git a/sdk/python/core/keeper_secrets_manager_core/core.py b/sdk/python/core/keeper_secrets_manager_core/core.py index b18ffaed..e1498e7d 100644 --- a/sdk/python/core/keeper_secrets_manager_core/core.py +++ b/sdk/python/core/keeper_secrets_manager_core/core.py @@ -23,17 +23,22 @@ from keeper_secrets_manager_core import utils, helpers from keeper_secrets_manager_core.configkeys import ConfigKeys from keeper_secrets_manager_core.crypto import CryptoUtils -from keeper_secrets_manager_core.dto.dtos import Folder, Record, RecordCreate, SecretsManagerResponse, AppData, \ +from keeper_secrets_manager_core.dto.dtos import Folder, Record, \ + RecordCreate, SecretsManagerResponse, AppData, \ KeeperFileUpload, KeeperFile, KeeperFolder -from keeper_secrets_manager_core.dto.payload import CompleteTransactionPayload, GetPayload, UpdatePayload, TransmissionKey, \ - EncryptedPayload, KSMHttpResponse, CreatePayload, FileUploadPayload, DeletePayload, \ - CreateFolderPayload, UpdateFolderPayload, DeleteFolderPayload, CreateOptions, QueryOptions +from keeper_secrets_manager_core.dto.payload import GetPayload, \ + CompleteTransactionPayload, UpdatePayload, TransmissionKey, \ + EncryptedPayload, KSMHttpResponse, CreatePayload, FileUploadPayload, \ + DeletePayload, CreateFolderPayload, UpdateFolderPayload, \ + DeleteFolderPayload, CreateOptions, QueryOptions from keeper_secrets_manager_core.exceptions import KeeperError -from keeper_secrets_manager_core.keeper_globals import keeper_secrets_manager_sdk_client_id, keeper_public_keys, \ - logger_name, keeper_servers -from keeper_secrets_manager_core.storage import FileKeyValueStorage, KeyValueStorage, InMemoryKeyValueStorage +from keeper_secrets_manager_core.keeper_globals import keeper_public_keys, \ + keeper_secrets_manager_sdk_client_id, logger_name, keeper_servers +from keeper_secrets_manager_core.storage import FileKeyValueStorage, \ + KeyValueStorage, InMemoryKeyValueStorage from keeper_secrets_manager_core.utils import base64_to_bytes, dict_to_json, \ - url_safe_str_to_bytes, bytes_to_base64, generate_random_bytes, now_milliseconds, string_to_bytes, json_to_dict, \ + url_safe_str_to_bytes, bytes_to_base64, generate_random_bytes, \ + generate_uid_bytes, now_milliseconds, string_to_bytes, json_to_dict, \ bytes_to_string, strtobool @@ -42,6 +47,7 @@ def find_secrets_by_title(record_title, records): records = records or [] return [x for x in records if x.title == record_title] + def find_secret_by_title(record_title, records): # Find first record with specified title records = records or [] @@ -60,6 +66,7 @@ def __init__(self, section: str): self.index1: Optional[Tuple[str, str]] = None # numeric index [N] or [] self.index2: Optional[Tuple[str, str]] = None # property index - ex. field/name[0][middle] + class SecretsManager: notation_prefix = "keeper" @@ -362,7 +369,7 @@ def prepare_create_payload(storage, create_options: CreateOptions, record_data_j raise KeeperError('Unable to create record - folder key for ' + create_options.folder_uid + ' is missing') record_key = generate_random_bytes(32) - record_uid = generate_random_bytes(16) + record_uid = generate_uid_bytes() record_data_bytes = utils.string_to_bytes(record_data_json_str) record_data_encrypted = CryptoUtils.encrypt_aes(record_data_bytes, record_key) @@ -496,15 +503,15 @@ def prepare_create_folder_payload(storage, create_options, folder_name, shared_f payload.sharedFolderUid = create_options.folder_uid payload.parentUid = create_options.subfolder_uid - folder_uid = utils.generate_random_bytes(16) + folder_uid = generate_uid_bytes() payload.folderUid = CryptoUtils.bytes_to_url_safe_str(folder_uid) - folder_key = utils.generate_random_bytes(32) + folder_key = generate_random_bytes(32) encrypted_folder_key = CryptoUtils.encrypt_aes_cbc(folder_key, shared_folder_key) payload.sharedFolderKey = CryptoUtils.bytes_to_url_safe_str(encrypted_folder_key) folder_json = dict_to_json({"name": folder_name}) - folder_data_bytes = utils.string_to_bytes(folder_json) + folder_data_bytes = string_to_bytes(folder_json) encrypted_folder_data = CryptoUtils.encrypt_aes_cbc(folder_data_bytes, folder_key) payload.data = CryptoUtils.bytes_to_url_safe_str(encrypted_folder_data) diff --git a/sdk/python/core/keeper_secrets_manager_core/crypto.py b/sdk/python/core/keeper_secrets_manager_core/crypto.py index fac5dedd..b58d95d6 100644 --- a/sdk/python/core/keeper_secrets_manager_core/crypto.py +++ b/sdk/python/core/keeper_secrets_manager_core/crypto.py @@ -36,6 +36,7 @@ def unpad_data(data): # type: (bytes) -> bytes unpadder = PKCS7(16*8).unpadder() return unpadder.update(data) + unpadder.finalize() + class CryptoUtils: BS = 16 diff --git a/sdk/python/core/keeper_secrets_manager_core/dto/dtos.py b/sdk/python/core/keeper_secrets_manager_core/dto/dtos.py index 8d586454..07dade67 100644 --- a/sdk/python/core/keeper_secrets_manager_core/dto/dtos.py +++ b/sdk/python/core/keeper_secrets_manager_core/dto/dtos.py @@ -454,11 +454,17 @@ def __init__(self): self.type = None -VALID_RECORD_FIELDS = ['login', 'password', 'url', 'fileRef', 'oneTimeCode', 'otp', 'name', 'birthDate', 'date', - 'expirationDate', 'text', 'securityQuestion', 'multiline', 'email', 'cardRef', 'addressRef', - 'pinCode', 'phone', 'secret', 'note', 'accountNumber', 'paymentCard', 'bankAccount', 'keyPair', - 'host', 'address', 'licenseNumber', 'recordRef', 'schedule', 'directoryType', 'databaseType', - 'pamHostname', 'pamResources', 'checkbox', 'passkey', 'script'] +VALID_RECORD_FIELDS = [ + 'accountNumber', 'address', 'addressRef', 'appFiller', 'bankAccount', + 'birthDate', 'cardRef', 'checkbox', 'databaseType', 'date', + 'directoryType', 'dropdown', 'email', 'expirationDate', 'fileRef', 'host', + 'isSSIDHidden', 'keyPair', 'licenseNumber', 'login', 'multiline', 'name', + 'note', 'oneTimeCode', 'otp', 'pamHostname', 'pamRemoteBrowserSettings', + 'pamResources', 'pamSettings', 'passkey', 'password', 'paymentCard', + 'phone', 'pinCode', 'rbiUrl', 'recordRef', 'schedule', 'script', 'secret', + 'securityQuestion', 'text', 'trafficEncryptionSeed', 'url', + 'wifiEncryption' +] class RecordField: diff --git a/sdk/python/core/keeper_secrets_manager_core/utils.py b/sdk/python/core/keeper_secrets_manager_core/utils.py index 873b0186..2b396032 100644 --- a/sdk/python/core/keeper_secrets_manager_core/utils.py +++ b/sdk/python/core/keeper_secrets_manager_core/utils.py @@ -109,6 +109,18 @@ def generate_random_bytes(length): return os.urandom(length) +def generate_uid_bytes(): + dash = bytes(b'\xf8\x7f') # 11111000, 0b01111111 + uid_bytes = bytes() + for _ in range(8): + uid_bytes = generate_random_bytes(16) + if dash[0] & uid_bytes[0] != dash[0]: + break + if dash[0] & uid_bytes[0] == dash[0]: + uid_bytes = bytes([uid_bytes[0] & dash[1]]) + uid_bytes[1:] + return uid_bytes + + def dict_to_json(dictionary): return json.dumps(dictionary, indent=4) diff --git a/sdk/python/core/setup.py b/sdk/python/core/setup.py index d984aaba..2560c711 100644 --- a/sdk/python/core/setup.py +++ b/sdk/python/core/setup.py @@ -18,7 +18,7 @@ setup( name="keeper-secrets-manager-core", - version="16.6.5", + version="16.6.6", description="Keeper Secrets Manager for Python 3", long_description=long_description, long_description_content_type="text/markdown", diff --git a/sdk/python/helper/keeper_secrets_manager_helper/v3/field_type.py b/sdk/python/helper/keeper_secrets_manager_helper/v3/field_type.py index 937dd1cf..d09f2af5 100644 --- a/sdk/python/helper/keeper_secrets_manager_helper/v3/field_type.py +++ b/sdk/python/helper/keeper_secrets_manager_helper/v3/field_type.py @@ -437,6 +437,7 @@ class Multiline(FieldType): name = "multiline" +# "file" - obsolete and removed legacy field - "fldt_file": { key: 'file_or_photo', default: "File or Photo" }, class FileRef(FieldType): name = "fileRef" # The validation checks to see if value is a Record UID. @@ -678,16 +679,12 @@ class Host(FieldType): } } -# AppFiller? - class LicenseNumber(FieldType): name = "licenseNumber" schema = {"value_type": str, "desc": "License Number"} -# privateKey? - class SecureNote(FieldType): name = "note" schema = {"value_type": str, "desc": "Secret Note"} @@ -705,7 +702,10 @@ class Schedule(FieldType): "value_type": dict, "schema": { "type": {"value_type": str, "desc": "Type"}, - "utcTime": {"value_type": str, "desc": "UTC Timestamp"}, + "cron": {"value_type": str, "desc": "Crontab format string"}, + # "utcTime" - replaced by time and tz + "time": {"value_type": str, "desc": "Time"}, + "tz": {"value_type": str, "desc": "Time zone"}, "weekday": {"value_type": str, "desc": "Day of the Week"}, "intervalCount": {"value_type": int, "desc": "Interval Count"} } @@ -740,7 +740,16 @@ class PamResources(FieldType): "schema": { "controllerUid": {"value_type": str, "desc": "Record UID of the Controller Record"}, "folderUid": {"value_type": str, "desc": "Folder UID"}, - "resourceRef": {"value_type": list, "desc": "List with UIDs of resource records"} + "resourceRef": {"value_type": list, "desc": "List with UIDs of resource records"}, + "allowedSettings": { + "value_type": dict, "desc": "List with allowed settings flags", + "schema": { + "connections": {"value_type": bool}, + "portForwards": {"value_type": bool}, + "rotation": {"value_type": bool}, + "sessionRecording": {"value_type": bool}, + "typescriptRecording": {"value_type": bool} + }}, } } @@ -774,6 +783,7 @@ class Passkey(FieldType): } } + class Script(FieldType): name = "script" schema = { @@ -784,3 +794,99 @@ class Script(FieldType): "recordRef": {"value_type": str, "validate": UID_REGEX, "desc": "Record UID of the referenced record."} } } + + +class IsSSIDHidden(FieldType): + name = "isSSIDHidden" + schema = {"value_type": bool, "desc": "Is SSID Hidden"} + + +class WifiEncryption(FieldType): + name = "wifiEncryption" + + +class Dropdown(FieldType): + name = "dropdown" + + +class RbiUrl(FieldType): + name = "rbiUrl" + + +class AppFiller(FieldType): + name = "appFiller" + schema = { + "value_type": dict, + "schema": { + "applicationTitle": {"value_type": str, "desc": "Application Title"}, + "contentFilter": {"value_type": str, "desc": "Content Filter"}, + "macroSequence": {"value_type": str, "desc": "Macro Sequence"}, + } + } + + +class PamRemoteBrowserSettings(FieldType): + name = "pamRemoteBrowserSettings" + schema = { + "value_type": dict, + "schema": { + "connection": { + "value_type": dict, + "desc": "Connection details", + "schema": { + "protocol": {"value_type": str}, + "userRecords": {"value_type": list}, + "allowUrlManipulation": {"value_type": bool}, + "allowedUrlPatterns": {"value_type": str}, + "allowedResourceUrlPatterns": {"value_type": str}, + "httpCredentialsUid": {"value_type": str}, + "autofillConfiguration": {"value_type": str} + } + }, + } + } + + +class PamSettings(FieldType): + name = "pamSettings" + schema = { + "value_type": dict, + "schema": { + "connection": { + "value_type": dict, + "desc": "PAM Settings", + "schema": { + "portForward": { + "value_type": list, + "desc": "Port Settings", + "schema": { + "reusePort": {"value_type": bool}, + "port": {"value_type": str}, + } + }, + "connection": { + "value_type": list, + "desc": "Connection Settings", + "schema": { + "protocol": {"value_type": str}, + "userRecords": {"value_type": list}, + "security": {"value_type": str}, + "ignoreCert": {"value_type": bool}, + "resizeMethod": {"value_type": str}, + "colorScheme": {"value_type": str} + } + }, + } + }, + } + } + + +class TrafficEncryptionSeed(FieldType): + name = "trafficEncryptionSeed" + + +# List of retired field types: +# trafficEncryptionKey - replaced by trafficEncryptionSeed +# pamProvider - deprecated for legacy/internal use only +# controller - deprecated for legacy/internal use only diff --git a/sdk/python/helper/requirements.txt b/sdk/python/helper/requirements.txt index 7cb09f23..f7f946f2 100644 --- a/sdk/python/helper/requirements.txt +++ b/sdk/python/helper/requirements.txt @@ -1,3 +1,3 @@ -keeper-secrets-manager-core>=16.2.2 +keeper-secrets-manager-core>=16.6.6 pyyaml>=6.0.1 iso8601 \ No newline at end of file diff --git a/sdk/python/helper/setup.py b/sdk/python/helper/setup.py index d851286c..2c2d05cc 100644 --- a/sdk/python/helper/setup.py +++ b/sdk/python/helper/setup.py @@ -15,7 +15,7 @@ setup( name="keeper-secrets-manager-helper", - version="1.0.4", + version="1.0.5", description="Keeper Secrets Manager SDK helper for managing records.", long_description=long_description, long_description_content_type="text/markdown", diff --git a/sdk/python/helper/tests/v3/v3_field_type_all_fields_test.py b/sdk/python/helper/tests/v3/v3_field_type_all_fields_test.py index 1b81eb4f..d3e790c9 100644 --- a/sdk/python/helper/tests/v3/v3_field_type_all_fields_test.py +++ b/sdk/python/helper/tests/v3/v3_field_type_all_fields_test.py @@ -124,7 +124,7 @@ def test_address(self): a.street2 = "Apt B" a.city = "Gotham" a.zip = "11111-2222" - a.country = "CA" + a.country = CountryEnum.CA self._check_dict(a, value={ "street1": "North Main Street", "street2": "Apt B", @@ -315,10 +315,11 @@ def test_record_ref(self): def test_schedule(self): ft = Schedule() ft.type = "WEEKLY" - ft.utcTime = "00:00" + ft.time = "00:00:00" + ft.tz = "America/Chicago" ft.weekday = "WEDNESDAY" ft.intervalCount = 1 - self._check_dict(ft, value={"type": "WEEKLY", "utcTime": "00:00", "weekday": "WEDNESDAY", "intervalCount": 1}) + self._check_dict(ft, value={"type": "WEEKLY", "time": "00:00:00", "tz": "America/Chicago", "weekday": "WEDNESDAY", "intervalCount": 1}) def test_directory_type(self): ft = DirectoryType() @@ -341,7 +342,25 @@ def test_pam_resources(self): ft.controllerUid = "OlLZ6JLjnyMOS3CiIPHBjw" ft.folderUid = "so5ja6A46Zmr9J1QyCc06g" ft.resourceRef = ["hUrGHrcM0PI3Y6Ch5wCrAQ"] - self._check_dict(ft, value={"controllerUid": "OlLZ6JLjnyMOS3CiIPHBjw", "folderUid": "so5ja6A46Zmr9J1QyCc06g", "resourceRef": ["hUrGHrcM0PI3Y6Ch5wCrAQ"]}) + ft.allowedSettings = { + "connections": True, + "portForwards": True, + "rotation": True, + "sessionRecording": True, + "typescriptRecording": True + } + self._check_dict(ft, value={ + "controllerUid": "OlLZ6JLjnyMOS3CiIPHBjw", + "folderUid": "so5ja6A46Zmr9J1QyCc06g", + "resourceRef": ["hUrGHrcM0PI3Y6Ch5wCrAQ"], + "allowedSettings": { + "connections": True, + "portForwards": True, + "rotation": True, + "sessionRecording": True, + "typescriptRecording": True + } + }) def test_checkbox(self): ft = Checkbox() @@ -367,7 +386,7 @@ def test_passkey(self): ft.createdDate = 1625140800000 self._check_dict(ft, value={ "privateKey": { - "crv":"CRV", + "crv": "CRV", "d": "DDDDD", "ext": False, "key_ops": [], @@ -382,7 +401,7 @@ def test_passkey(self): "username": "user1", "createdDate": 1625140800000}) - def test_scrpt(self): + def test_script(self): ft = Script() ft.fileRef = "OlLZ6JLjnyMOS3CiIPHBjw" ft.command = "/bin/zsh" diff --git a/sdk/python/helper/tests/v3/v3_field_type_test.py b/sdk/python/helper/tests/v3/v3_field_type_test.py index 8205f9bd..70856435 100644 --- a/sdk/python/helper/tests/v3/v3_field_type_test.py +++ b/sdk/python/helper/tests/v3/v3_field_type_test.py @@ -53,8 +53,11 @@ def test_password_filter(self): def test_load_map(self): get_field_type_map() - # Nice test to make sure we loaded all the fields, if we add more fields this will fail ... but in a good way. - self.assertEqual(39, len(field_map.keys())) + # Nice test to make sure we loaded all the fields, + # if we add more fields this will fail ... but in a good way. + # All the fields include: VALID_RECORD_FIELDS and some subtypes: + # bankAccountItem, paymentCardItem, phoneItem + self.assertEqual(47, len(field_map.keys())) # Check if we get a Login class self.assertEqual(get_class_by_type("login"), Login)