#!/opt/splunk/bin/python3.9
# MIT License
#
#    Copyright (c) 2025 Aplura
#
#    Permission is hereby granted, free of charge, to any person obtaining a copy
#    of this software and associated documentation files (the "Software"), to deal
#    in the Software without restriction, including without limitation the rights
#    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#    copies of the Software, and to permit persons to whom the Software is
#    furnished to do so, subject to the following conditions:
#
#    The above copyright notice and this permission notice shall be included in all
#    copies or substantial portions of the Software.
#
#    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#    SOFTWARE.

# /opt/splunk/bin/splunk cmd python3 convert-aob-cred.py <passwords_conf_location> <credential_name> <new_password>
# OR
# /opt/splunk/bin/splunk cmd python3 convert-aob-cred.py <passwords_conf_location> <credential_name> - < <new_password_file>
# OR
# echo "password" | /opt/splunk/bin/splunk cmd python3 convert-aob-cred.py <passwords_conf_location> <credential_name> -

# EXAMPLE: splunk cmd python3 ./convert-aob-cred.py passwords.conf Splunk_TA_Google_Workspace#configs/conf-splunk_ta_google_workspace_account:Sample
import configparser
import os
from base64 import b64decode, b64encode
from datetime import datetime
import sys
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from splunk.appserver.mrsparkle.lib.util import make_splunkhome_path

etc_auth_dir = make_splunkhome_path(["etc", "auth"])
config = configparser.ConfigParser()
passwords_conf = sys.argv[1]
credential_name = sys.argv[2]
new_password = None
if len(sys.argv) > 3:
    new_password = sys.argv[3]
    if new_password == "-":
        new_password = input()
config.read(passwords_conf)
section_names = []
# Special Thanks to HurricaneLabs for the knowledge of this to encode and decode!
# https://github.com/HurricaneLabs/splunksecrets/blob/master/splunksecrets/splunk.py
def decode(secret_string, string_decode, section_name):
    string_decode = b64decode(string_decode[3:])
    section_names.append(section_name)
    config.remove_section(section_name)
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=b"disk-encryption",
        iterations=1,
        backend=default_backend(),
    )
    key = kdf.derive(secret_string[:254])

    iv = string_decode[:16]
    tag = string_decode[-16:]
    string_decode = string_decode[16:-16]

    algorithm = algorithms.AES(key)
    cipher = Cipher(algorithm, mode=modes.GCM(iv, tag), backend=default_backend())
    decryptor = cipher.decryptor()
    plaintext = decryptor.update(string_decode).decode()
    return plaintext

def encode(secret_string, string_encode, iv=None):
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=b"disk-encryption",
        iterations=1,
        backend=default_backend(),
    )
    key = kdf.derive(secret_string[:254])

    if iv is None:
        iv = os.urandom(16)

    algorithm = algorithms.AES(key)
    cipher = Cipher(algorithm, mode=modes.GCM(iv), backend=default_backend())
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(string_encode.encode()) + encryptor.finalize()
    payload = b64encode(b"%s%s%s" % (iv, ciphertext, encryptor.tag)).decode()

    return f"$7${payload}"

def log(msg):
    print(f"{datetime.now()}: {msg}")

with open(os.path.join(etc_auth_dir, "splunk.secret"), "r") as f:
    secret = f.read()
    if isinstance(secret, str):
        secret = secret.encode()
    secret = secret.ljust(254, b"\0")
log(f"Loaded Secret")
sections = [decode(secret, config.get(x, "password"), x) for x in config.sections() if x.find(credential_name) != -1]
spl_sep = sections[-1]
sections = sections[:-1]
new_sections = [encode(secret, "".join(sections)) if new_password is None else encode(secret, new_password), encode(secret, spl_sep)]
idx = 0
for section in new_sections:
    idx += 1
    new_section_name = f"credential:__REST_CREDENTIAL__#{credential_name}``splunk_cred_sep``{idx}:"
    if not config.has_section(new_section_name):
        config.add_section(new_section_name)
    config.set(new_section_name, "password", section)
config.write(open(passwords_conf, "w"))
