Skip to content

Commit 0b6b254

Browse files
sholeksandracoifmannvidia
authored andcommitted
hw-mgmt: scripts: change BMC password to be platform based
Change BMC password to be platform based Why I did it: Strengthen the bmc credentials without breaking backward compatibility How I did it: the TPM generation password now depends on platform number Issue: 4410461 Signed-off-by: Vladi Tarnavsky <[email protected]>
1 parent b7aea2c commit 0b6b254

File tree

1 file changed

+87
-68
lines changed

1 file changed

+87
-68
lines changed

usr/usr/bin/hw_management_redfish_client.py

Lines changed: 87 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import re
4646
import shlex
4747
import os
48+
import base64
4849

4950
# TBD:
5051
# Support token persistency later on and remove RedfishClient.__password
@@ -394,6 +395,7 @@ class BMCAccessor(object):
394395
BMC_DIR = "/host/bmc"
395396
BMC_PASS_FILE = "bmc_pass"
396397
BMC_TPM_HEX_FILE = "hw_mgmt_const.bin"
398+
LEGACY_PLATFORM_PATTERN = r'N5\d{3}_LD'
397399

398400
def __init__(self):
399401
# TBD: Token persistency.
@@ -441,80 +443,97 @@ def redfish_api_wrapper(*args, **kwargs):
441443
err_msg = f"'{self.__class__.__name__}' object has no attribute '{name}'"
442444
raise AttributeError(err_msg)
443445

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+
444515
def get_login_password(self):
445516
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:
470529
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")
514532
except subprocess.CalledProcessError as e:
515533
# print(f"Error executing TPM command: {e}")
516534
raise Exception("Failed to communicate with TPM")
517-
535+
except (FileNotFoundError, PermissionError) as e:
536+
raise Exception("no platform name found")
518537
except Exception as e:
519538
# print(f"Error: {e}")
520539
raise

0 commit comments

Comments
 (0)