diff --git a/doc/InstanceToken.md b/doc/InstanceToken.md
new file mode 100644
index 0000000..3dc1c69
--- /dev/null
+++ b/doc/InstanceToken.md
@@ -0,0 +1,306 @@
+# Instance Token
+
+[TOC]
+
+# Generate `dls_instance_token.tok`
+
+t.b.a.
+
+# Load and Parse `liense.bin`
+
+> `self.processor.read_license_payload()` calls `LicenseFileProcessor.read_license_payload()`
+
+This [Private-Key we have](LicensingFlow.md#public-keys-private-keys-certificates) - in DB-Table
+`service_instance_artifact`, Key `service_instance.identity`
+`si_identity_private_key = self._get_si_identity_private_key(service_instance_xid=service_instance_xid)`
+
+## `LicenseFileInstallationManager`
+
+
+ `license_file_installation_manager.py`
+
+```python
+class LicenseFileInstallationManager:
+ @retry(ExceptionToCheck=InterfaceError, tries=3, delay=0.05)
+ def license_file_installation(self, event_args):
+ kwargs = self._validate_and_return_args(event_args)
+ service_instance_xid = self._validate_si_xid_header(event_args)
+
+ # ensure no upgrade job is in progress
+ self._check_migration_job(event_args, service_instance_xid)
+
+ request: LicenseFileRequest = kwargs['request']
+ try:
+ si_model = self.dal.get_service_instance(service_instance_xid)
+
+ deployment_token, payload, product_mapping_token, api_key_models, dls_certificate_token, dls_feat_display_map_token = self._read_license_file(
+ service_instance_xid=service_instance_xid, request=request
+ )
+
+ # Register SI if it is UNINITIALIZED
+ self._validate_si_state(si_model, deployment_token, event_args)
+ generated_data = {}
+ generated_data = self.dal.insert_license_file(
+ license_file_xid=payload.header.license_allocation_file_xid,
+ license_file_timestamp=payload.header.license_allocation_file_timestamp,
+ license_allocation_list=payload.license_allocation_list,
+ service_instance_xid=service_instance_xid,
+ product_mapping_list=product_mapping_token.product_mapping_info,
+ api_key_models=api_key_models,
+ dls_certificate_token=dls_certificate_token,
+ dls_feat_display_map_token=dls_feat_display_map_token,
+ si_model=si_model,
+ _license=request.license,
+ generated_data=generated_data
+ )
+ # Audit this in case of DLS
+ if self._is_dls(si_model) and generated_data is not None:
+ for value in generated_data.values():
+ if "la_xid" in value:
+ self.audit_event_processor.audit_license_server_installation_event(value["la_xid"], si_model, event_args.get('headers'))
+
+ except Exception as ex:
+ log.error(f'Error processing license allocation file for service_instance: {service_instance_xid} Error {ex}')
+ raise
+
+ def _read_license_file(self, service_instance_xid, request):
+ if not LicenseFileInstallationManager._is_dls_pre_registered():
+ # if its not pre-registered then go the standard route
+ return self._read_lf_with_si_bound_keys(service_instance_xid, request)
+
+ # X - THIS LINE IS IMPORTANT
+ si_preregistered_private_key = LicenseFileInstallationManager._get_si_preregistered_private_key()
+ try:
+ # X - THIS LINE IS IMPORTANT
+ return self.processor.read_license_payload(request.license, si_preregistered_private_key)
+ except Exception as ex:
+ # if decryption fails with SI pre-registered private key, possibility that user has acknowledged on NLP
+ # so try with SI bound instance keys
+ log.error(f"error decrypting license file with pre-registered identity key, trying SI identity private key, {ex}")
+ return self._read_lf_with_si_bound_keys(service_instance_xid, request)
+
+ def _read_lf_with_si_bound_keys(self, service_instance_xid, request):
+ si_identity_private_key = self._get_si_identity_private_key(service_instance_xid=service_instance_xid)
+ # if it fails with generic SI private key, try with SI bound key
+ deployment_token, payload, product_mapping_token, api_keys_models, dls_certificate_token, dls_feat_display_map_token = self.processor.read_license_payload(request.license, si_identity_private_key)
+ return deployment_token, payload, product_mapping_token, api_keys_models, dls_certificate_token, dls_feat_display_map_token
+
+ def _get_si_identity_private_key(self, service_instance_xid):
+ # if this fails look for global private key because it means that the incoming file has pre-registered token
+ try:
+ # Get SI Identity private key to decrypt this license file
+ si_identity_private_key = self.dal.get_si_artifact(
+ service_instance_xid,
+ si_constants.SERVICE_INSTANCE_IDENTITY_NAMESPACE,
+ si_constants.ARTIFACT_NAME_PRIVATE_KEY
+ )
+ return si_identity_private_key.value
+ except NotFoundError as ex:
+ log.error(f'Error fetching artifacts for SI attached to this license file', ex)
+ raise BadRequestError("Failed to process license allocation file")
+
+ @staticmethod
+ def _is_dls_pre_registered():
+ return os.path.exists(si_constants.SI_IS_PRE_REGISTRATION_MARKER)
+
+ @staticmethod
+ def _get_si_preregistered_private_key():
+ _global_private_key = PrivateKey.from_data(os.getenv("DLS_PRE_REGISTRATION_PRIVATE_KEY"))
+ return _global_private_key.data
+```
+
+
+
+## `LicenseFileProcessor`
+
+
+ `license_file_processor.py`
+
+```python
+class LicenseFileProcessor:
+ def build_license_payload(self, license_allocation_file_xid, license_allocation_file_timestamp,
+ license_allocation_list, public_key_string, deployment_token,
+ product_mapping_token=None,
+ api_keys_response=None,
+ dls_certificate_properties=None,
+ dls_feature_display_mapping_token=None):
+ # Generate license file container with laf and preamble
+ license_file_container = LicenseFileContainer()
+ product_mapping_token_base64 = self.generate_base64_encrypted_string(product_mapping_token, public_key_string)
+ api_keys_response_encrypted_base64 = self.get_api_key_encrypted_encoded_val(api_keys_response, public_key_string)
+
+ encrypted_dls_certificate_token = self.get_encrypted_dls_certificate_token(dls_certificate_properties, public_key_string)
+
+
+ encrypted_dls_feature_display_mapping_token = self._get_encrypted_dls_feature_display_mapping_token(dls_feature_display_mapping_token, public_key_string)
+ license_file_container.preamble = LicenseFilePreamble(
+ deployment_token=deployment_token,
+ product_mapping_token=product_mapping_token_base64,
+ api_keys_response=api_keys_response_encrypted_base64,
+ dls_certificate_token=encrypted_dls_certificate_token,
+ dls_feature_display_mapping_token=encrypted_dls_feature_display_mapping_token
+ )
+
+ # process license file payload
+ license_file_payload = LicenseFilePayload()
+ license_file_payload.header = LicenseFilePayloadHeader(
+ license_allocation_file_xid=license_allocation_file_xid,
+ license_allocation_file_timestamp=license_allocation_file_timestamp.isoformat()
+ )
+ license_file_payload.license_allocation_list = license_allocation_list
+
+ # Generate license file response
+ # need special UUIDEncoder because license_file_payload contains UUID objects
+ payload_str = json_dumps(license_file_payload.to_dict(), cls=UUIDEncoder)
+ public_key = PublicKey.from_data(public_key_string)
+ encrypted_payload_str = public_key.encrypt_aes(payload_str)
+ encrypted_payload_str = base64.b64encode(encrypted_payload_str.encode('utf-8')).decode('utf-8')
+ license_file_container.payload = encrypted_payload_str
+
+ # dump LicenseFileContainer response to JSON and base64 encode it
+ license_container_str = json_dumps(license_file_container.to_dict(), cls=UUIDEncoder)
+ license_container_str = base64.b64encode(license_container_str.encode('utf-8')).decode('utf-8')
+
+ return license_container_str
+
+
+ def read_license_payload(self, license_container_str, private_key_string):
+ try:
+ # Decode whole string object into LicenseFileContainer object
+ license_container_str = base64.b64decode(license_container_str.encode('utf-8')).decode('utf-8')
+ license_file_container = LicenseFileContainer.from_dict(JsonUtils.from_json(license_container_str))
+
+ # Decode preamble and payload
+ encrypted_payload_str = base64.b64decode(license_file_container.payload.encode('utf-8')).decode('utf-8')
+ # X - THIS LINE IS IMPORTANT
+ private_key = PrivateKey.from_data(private_key_string)
+ license_file_decoded = private_key.decrypt_aes(encrypted_payload_str)
+ payload = LicenseFilePayload.from_dict(JsonUtils.from_json(license_file_decoded))
+
+ # Decode product mapping data
+ product_mapping_token = license_file_container.preamble.product_mapping_token
+ if product_mapping_token is not None and product_mapping_token != "":
+ encrypted_product_mapping_token = base64.b64decode(license_file_container.preamble.product_mapping_token.encode('utf-8')).decode('utf-8')
+ private_key = PrivateKey.from_data(private_key_string)
+ decrypted_product_mapping_token = private_key.decrypt_aes(encrypted_product_mapping_token)
+ product_mapping_token = ProductMappingFileContainer.from_dict(JsonUtils.from_json(decrypted_product_mapping_token))
+
+ # Api Key preamble
+ api_key_models = self._get_api_key_preamble(license_file_container, private_key_string)
+
+ # Cert response preamble
+ dls_certificate_token = self._get_dls_certificate_token_preamble(license_file_container, private_key_string)
+
+ # Feature display mapping token
+ dls_feat_display_map_token = self._get_dls_feature_display_mapping_token(license_file_container, private_key_string)
+ except (UnicodeDecodeError, BinAsciiError) as be:
+ log.exception(f'Error processing license file, invalid license file: {be}')
+ raise BadRequestError('Invalid license file format') from be
+ except JSONDecodeError:
+ raise BadRequestError('Invalid license file object')
+ except ValueError as e:
+ if "Incorrect decryption" in str(e) or "Ciphertext too large" in str(e):
+ log.exception(f'Error decrypting license allocation file : {e}')
+ raise BadRequestError('Invalid license file for this service instance')
+ else:
+ raise
+ except Exception as be:
+ log.exception(f'Error processing license allocation file : {be}')
+ raise BadRequestError('Error processing license allocation file') from be
+
+ return license_file_container.preamble.deployment_token, payload, product_mapping_token, api_key_models, dls_certificate_token, dls_feat_display_map_token
+```
+
+
+
+# Other Code
+
+Interesting is that for encryption the `service_instance.deployment` **Public-Key** is used. For that one, we have no
+private key.
+
+see
+
+```diff
+public_key_string=si_deployment_public_key.value
+```
+
+
+ `return_file_export_manager.py`
+
+```python
+class ReturnFileExportManager:
+ def return_file_export_handler(self, event_args, params, dal):
+ if 'file_timestamp' not in event_args:
+ # file_timestamp not in event_args means original request on primary,
+ # so we get current time as file_timestamp
+ license_allocation_file_timestamp = datetime.utcnow()
+ # modify incoming event_args parameter to add file_timestamp,
+ # so broadcaster to sends file_timestamp to secondary
+ event_args['file_timestamp'] = license_allocation_file_timestamp
+ else:
+ # file_timestamp in event_args means replication call on secondary
+ # so we use file_timestamp from event_args
+ license_allocation_file_timestamp = event_args['file_timestamp']
+
+ license_allocation_file_xid = self.processor.get_license_file_xid()
+ log.info(f'Generating license allocation return file: {license_allocation_file_xid}')
+
+ # Generate license allocation data
+ license_allocation = LicenseAllocation()
+ license_allocation.header = LicenseAllocationHeader(params.license_allotment_xid)
+ log.info(f'Generating return for license allocation: {params.license_allotment_xid}')
+ license_allocation.object_list = self._get_object_list(params, dal)
+
+ try:
+ si_deployment_public_key = dal.get_si_artifact_for_license_allotment(
+ params.license_allotment_xid, si_constants.SERVICE_INSTANCE_DEPLOYMENT_NAMESPACE,
+ si_constants.ARTIFACT_NAME_PUBLIC_KEY
+ )
+ except NotFoundError as ex:
+ log.error(f'Error fetching artifacts for SI attached to this license allocation return file', ex)
+ raise BadRequestError("Failed to return license allocation file")
+
+ # Build license file payload string
+ encrypted_payload_str = self.processor.build_license_payload(
+ license_allocation_file_xid=license_allocation_file_xid,
+ license_allocation_file_timestamp=license_allocation_file_timestamp,
+ license_allocation_list=[license_allocation],
+ public_key_string=si_deployment_public_key.value,
+ deployment_token="")
+
+ # insert LAF record
+ dal.insert_file_creation_record(license_allocation_file_xid, license_allocation_file_timestamp,
+ params.license_allotment_xid, encrypted_payload_str)
+
+ response = ReturnFileResponse(return_license=encrypted_payload_str)
+ return response
+```
+
+
+
+
+
+ `dls_license_file_installation_dal.py`
+
+```python
+class DlsLicenseFileInstallationDal:
+ def insert_file_creation_record(self, schema, license_file_xid, license_file_timestamp, license_allotment_xid, license_allocation_file, session=None):
+ insert_file_creation_record_query = f"""
+ insert into {schema}.license_allotment_file_publication (xid, license_allotment_xid, publication_detail)
+ values (:xid, :la_xid, :publication_detail)
+ on conflict (xid) do update
+ set license_allotment_xid = :la_xid, publication_detail = :publication_detail
+ """
+ publication_detail_dict = {
+ 'timestamp': license_file_timestamp.isoformat(),
+ 'license': license_allocation_file,
+ }
+
+ publication_detail = json_dumps(publication_detail_dict)
+ session.execute(insert_file_creation_record_query, {'xid': license_file_xid, 'la_xid': license_allotment_xid, 'publication_detail': publication_detail})
+
+```
+
+
+
+
diff --git a/doc/LicensingFlow.md b/doc/LicensingFlow.md
index ae84e0b..9f678ba 100644
--- a/doc/LicensingFlow.md
+++ b/doc/LicensingFlow.md
@@ -1,5 +1,7 @@
# NLS-Instance Debug Information
+> This file describes the data exchange and shows some payloads. For Code reference see [InstanceToken.md](InstanceToken.md).
+
[TOC]
# Registration Process