|
45 | 45 | import re |
46 | 46 | import shlex |
47 | 47 | import os |
| 48 | +import base64 |
48 | 49 |
|
49 | 50 | # TBD: |
50 | 51 | # Support token persistency later on and remove RedfishClient.__password |
@@ -394,6 +395,7 @@ class BMCAccessor(object): |
394 | 395 | BMC_DIR = "/host/bmc" |
395 | 396 | BMC_PASS_FILE = "bmc_pass" |
396 | 397 | BMC_TPM_HEX_FILE = "hw_mgmt_const.bin" |
| 398 | + LEGACY_PLATFORM_PATTERN = r'N5\d{3}_LD' |
397 | 399 |
|
398 | 400 | def __init__(self): |
399 | 401 | # TBD: Token persistency. |
@@ -441,80 +443,97 @@ def redfish_api_wrapper(*args, **kwargs): |
441 | 443 | err_msg = f"'{self.__class__.__name__}' object has no attribute '{name}'" |
442 | 444 | raise AttributeError(err_msg) |
443 | 445 |
|
| 446 | + def _handle_legacy_password(self): |
| 447 | + """Handle legacy password generation for juliet platforms""" |
| 448 | + pass_len = 13 |
| 449 | + attempt = 1 |
| 450 | + max_attempts = 100 |
| 451 | + max_repeat = int(3 + 0.09 * pass_len) |
| 452 | + hex_data = "1300NVOS-BMC-USER-Const" |
| 453 | + os.makedirs(self.BMC_DIR, exist_ok=True) |
| 454 | + cmd = f'echo "{hex_data}" | xxd -r -p > {self.BMC_DIR}/{self.BMC_TPM_HEX_FILE}' |
| 455 | + subprocess.run(cmd, shell=True, check=True) |
| 456 | + |
| 457 | + tpm_command = ["tpm2_createprimary", "-C", "o", "-u", f"{self.BMC_DIR}/{self.BMC_TPM_HEX_FILE}", "-G", "aes256cfb"] |
| 458 | + result = subprocess.run(tpm_command, capture_output=True, check=True, text=True) |
| 459 | + |
| 460 | + while attempt <= max_attempts: |
| 461 | + if attempt > 1: |
| 462 | + const = f"1300NVOS-BMC-USER-Const-{attempt}" |
| 463 | + mess = f"Password did not meet criteria; retrying with const: {const}" |
| 464 | + # print(mess) |
| 465 | + tpm_command = f'echo -n "{const}" | tpm2_createprimary -C o -G aes -u -' |
| 466 | + result = subprocess.run(tpm_command, shell=True, capture_output=True, check=True, text=True) |
| 467 | + |
| 468 | + symcipher_pattern = r"symcipher:\s+([\da-fA-F]+)" |
| 469 | + symcipher_match = re.search(symcipher_pattern, result.stdout) |
| 470 | + |
| 471 | + if not symcipher_match: |
| 472 | + raise Exception("Symmetric cipher not found in TPM output") |
| 473 | + |
| 474 | + # BMC dictates a password of 13 characters. Random from TPM is used with an append of A! |
| 475 | + symcipher_part = symcipher_match.group(1)[:pass_len - 2] |
| 476 | + if symcipher_part.isdigit(): |
| 477 | + symcipher_value = symcipher_part[:pass_len - 3] + 'vA!' |
| 478 | + elif symcipher_part.isalpha() and symcipher_part.islower(): |
| 479 | + symcipher_value = symcipher_part[:pass_len - 3] + '9A!' |
| 480 | + else: |
| 481 | + symcipher_value = symcipher_part + 'A!' |
| 482 | + if len(symcipher_value) != pass_len: |
| 483 | + raise Exception("Bad cipher length from TPM output") |
| 484 | + |
| 485 | + # check for monotonic |
| 486 | + monotonic_check = True |
| 487 | + for i in range(len(symcipher_value) - 3): |
| 488 | + seq = symcipher_value[i:i + 4] |
| 489 | + increments = [ord(seq[j + 1]) - ord(seq[j]) for j in range(3)] |
| 490 | + if increments == [1, 1, 1] or increments == [-1, -1, -1]: |
| 491 | + monotonic_check = False |
| 492 | + break |
| 493 | + |
| 494 | + variety_check = len(set(symcipher_value)) >= 5 |
| 495 | + repeating_pattern_check = sum(1 for i in range(pass_len - 1) if symcipher_value[i] == symcipher_value[i + 1]) <= max_repeat |
| 496 | + |
| 497 | + # check for consecutive_pairs |
| 498 | + count = 0 |
| 499 | + for i in range(11): |
| 500 | + val1 = symcipher_value[i] |
| 501 | + val2 = symcipher_value[i + 1] |
| 502 | + if val2 == "v" or val1 == "v": |
| 503 | + continue |
| 504 | + if abs(int(val2, 16) - int(val1, 16)) == 1: |
| 505 | + count += 1 |
| 506 | + consecutive_pair_check = count <= 4 |
| 507 | + |
| 508 | + if consecutive_pair_check and variety_check and repeating_pattern_check and monotonic_check: |
| 509 | + os.remove(f"{self.BMC_DIR}/{self.BMC_TPM_HEX_FILE}") |
| 510 | + return symcipher_value |
| 511 | + else: |
| 512 | + attempt += 1 |
| 513 | + raise Exception("Failed to generate a valid password after maximum retries.") |
| 514 | + |
444 | 515 | def get_login_password(self): |
445 | 516 | try: |
446 | | - pass_len = 13 |
447 | | - attempt = 1 |
448 | | - max_attempts = 100 |
449 | | - max_repeat = int(3 + 0.09 * pass_len) |
450 | | - hex_data = "1300NVOS-BMC-USER-Const" |
451 | | - os.makedirs(self.BMC_DIR, exist_ok=True) |
452 | | - cmd = f'echo "{hex_data}" | xxd -r -p > {self.BMC_DIR}/{self.BMC_TPM_HEX_FILE}' |
453 | | - subprocess.run(cmd, shell=True, check=True) |
454 | | - |
455 | | - tpm_command = ["tpm2_createprimary", "-C", "o", "-u", f"{self.BMC_DIR}/{self.BMC_TPM_HEX_FILE}", "-G", "aes256cfb"] |
456 | | - result = subprocess.run(tpm_command, capture_output=True, check=True, text=True) |
457 | | - |
458 | | - while attempt <= max_attempts: |
459 | | - if attempt > 1: |
460 | | - const = f"1300NVOS-BMC-USER-Const-{attempt}" |
461 | | - mess = f"Password did not meet criteria; retrying with const: {const}" |
462 | | - # print(mess) |
463 | | - tpm_command = f'echo -n "{const}" | tpm2_createprimary -C o -G aes -u -' |
464 | | - result = subprocess.run(tpm_command, shell=True, capture_output=True, check=True, text=True) |
465 | | - |
466 | | - symcipher_pattern = r"symcipher:\s+([\da-fA-F]+)" |
467 | | - symcipher_match = re.search(symcipher_pattern, result.stdout) |
468 | | - |
469 | | - if not symcipher_match: |
| 517 | + with open('/sys/devices/virtual/dmi/id/product_name') as f: |
| 518 | + platform_name = f.read().strip() |
| 519 | + if re.match(self.LEGACY_PLATFORM_PATTERN, platform_name.upper()): |
| 520 | + return self._handle_legacy_password() |
| 521 | + else: |
| 522 | + const = "1300NVOS-BMC-USER-Const" |
| 523 | + tpm_command = f'echo -n "{const}" | tpm2_createprimary -C o -G aes -u -' |
| 524 | + result = subprocess.run(tpm_command, shell=True, |
| 525 | + capture_output=True, check=True, |
| 526 | + text=True).stdout |
| 527 | + match = re.search(r"symcipher:\s+([\da-fA-F]+)", result) |
| 528 | + if not match: |
470 | 529 | raise Exception("Symmetric cipher not found in TPM output") |
471 | | - |
472 | | - # BMC dictates a password of 13 characters. Random from TPM is used with an append of A! |
473 | | - symcipher_part = symcipher_match.group(1)[:pass_len - 2] |
474 | | - if symcipher_part.isdigit(): |
475 | | - symcipher_value = symcipher_part[:pass_len - 3] + 'vA!' |
476 | | - elif symcipher_part.isalpha() and symcipher_part.islower(): |
477 | | - symcipher_value = symcipher_part[:pass_len - 3] + '9A!' |
478 | | - else: |
479 | | - symcipher_value = symcipher_part + 'A!' |
480 | | - if len(symcipher_value) != pass_len: |
481 | | - raise Exception("Bad cipher length from TPM output") |
482 | | - |
483 | | - # check for monotonic |
484 | | - monotonic_check = True |
485 | | - for i in range(len(symcipher_value) - 3): |
486 | | - seq = symcipher_value[i:i + 4] |
487 | | - increments = [ord(seq[j + 1]) - ord(seq[j]) for j in range(3)] |
488 | | - if increments == [1, 1, 1] or increments == [-1, -1, -1]: |
489 | | - monotonic_check = False |
490 | | - break |
491 | | - |
492 | | - variety_check = len(set(symcipher_value)) >= 5 |
493 | | - repeating_pattern_check = sum(1 for i in range(pass_len - 1) if symcipher_value[i] == symcipher_value[i + 1]) <= max_repeat |
494 | | - |
495 | | - # check for consecutive_pairs |
496 | | - count = 0 |
497 | | - for i in range(11): |
498 | | - val1 = symcipher_value[i] |
499 | | - val2 = symcipher_value[i + 1] |
500 | | - if val2 == "v" or val1 == "v": |
501 | | - continue |
502 | | - if abs(int(val2, 16) - int(val1, 16)) == 1: |
503 | | - count += 1 |
504 | | - consecutive_pair_check = count <= 4 |
505 | | - |
506 | | - if consecutive_pair_check and variety_check and repeating_pattern_check and monotonic_check: |
507 | | - os.remove(f"{self.BMC_DIR}/{self.BMC_TPM_HEX_FILE}") |
508 | | - return symcipher_value |
509 | | - else: |
510 | | - attempt += 1 |
511 | | - |
512 | | - raise Exception("Failed to generate a valid password after maximum retries.") |
513 | | - |
| 530 | + # Extract symcipher and encode to base64 |
| 531 | + return base64.b64encode(bytes.fromhex(match.group(1))).decode("ascii") |
514 | 532 | except subprocess.CalledProcessError as e: |
515 | 533 | # print(f"Error executing TPM command: {e}") |
516 | 534 | raise Exception("Failed to communicate with TPM") |
517 | | - |
| 535 | + except (FileNotFoundError, PermissionError) as e: |
| 536 | + raise Exception("no platform name found") |
518 | 537 | except Exception as e: |
519 | 538 | # print(f"Error: {e}") |
520 | 539 | raise |
|
0 commit comments