#!/bin/bash
# Copyright (c) 2024 Broadcom
# Remove ssoserver cert from vecs store, causing install breakage in VC 8 U3 and other problems in earlier versions
# Get list of all certificates in vecs and alias, subject, and entitlement details

# Command line syntax:
#     clean [-anysso]
#         Scan ssoserver certificate via exact subjectname match and prompt for removal if found
#         -anysso - target any non-CA cert with ssoserver anywhere in the subject
#     list
#         List certificates in VECS store with alias, subject name, and entitlements
#     delete <certificate alias> [-y]
#         Delete the specified certificate
#         -y - suppress confirmation prompt
#     help | --help | --? | /?
#         Display command line help

# ** CONSTANTS **
# Definition must be lower case
readonly Parameter_Mode_Clean="clean"
readonly Parameter_Mode_List="list"
readonly Parameter_Mode_Delete="delete"
readonly Parameter_Mode_Help_Type1="help"
readonly Parameter_Mode_Help_Type2="--help"
readonly Parameter_Mode_Help_Type3="--?"
readonly Parameter_Mode_Help_Type4="/?"
readonly Parameter_Clean_AnySso="-anysso"
readonly Parameter_Delete_NoPrompt="-y"
readonly Mode_Clean=0
readonly Mode_List=1
readonly Mode_Delete=2
readonly Mode_Help=3

# ** FUNCTIONS **
# Gets a list of certs from vecs, storing the alias, subject, and key usage in the global arrays certAlias, certSubject, certKeyUsage, and certIsAuthority
GetCertificateList() {
    mapfile -t vecsCertResultRawLines < <( /usr/lib/vmware-vmafd/bin/vecs-cli entry list --store TRUSTED_ROOTS --text | egrep 'Alias|Subject\:\s|Key Usage' -A 1 | egrep -v 'Entry type|--')
    # Sample output
    # Alias : 2823c5d97dc7a350cc13ba4bea9bc20a85a322a1
    #         Subject: CN=CA, DC=vsphere, DC=local, C=US, ST=California, O=sc2-10-212-19-16.nimbus.eng.vmware.com, OU=VMware Engineering
    #         Subject Public Key Info:
    #             X509v3 Key Usage: critical
    #                 Certificate Sign, CRL Sign
    # Alias : ssocert
    #         Subject: CN=ssoserver,dc=vsphere,dc=local, C=US
    #         Subject Public Key Info:
    #             X509v3 Key Usage:
    #                 Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment
    # Alias : ssocert2
    #         Subject: CN=ssoserver
    #         Subject Public Key Info:
    #             X509v3 Key Usage:
    #                 Digital Signature, Non Repudiation, Key Encipherment

    # Locate Alias lines, the start of each cert information block
    aliasLineNo=()
    for ((currLineNo = 0 ; currLineNo < ${#vecsCertResultRawLines[@]} ; currLineNo++ ));
    do
        if [[ "${vecsCertResultRawLines[${currLineNo}],,}" == *"alias :"* ]]; then
            aliasLineNo+=(${currLineNo})
        fi
    done

    # Parse all lines in each alias block
    certAlias=()
    certSubject=()
    certKeyUsage=()
    certIsAuthority=()
    lastAliasNo=$(( ${#aliasLineNo[@]} - 1 ))
    for ((currAliasNo = 0 ; currAliasNo < ${#aliasLineNo[@]} ; currAliasNo++ ));
    do
        # Locate the start and end of this block
        aliasBlockStartLineNo=${aliasLineNo[${currAliasNo}]}
        if [[ $currAliasNo -ne ${lastAliasNo} ]]; then
            nextAliasNo=$(( $currAliasNo + 1 ))
            nextAliasBlockStartLineNo=${aliasLineNo[${nextAliasNo}]}
            aliasBlockEndLineNo=$(( $nextAliasBlockStartLineNo - 1 ))
        else
            lastLineNoTest=$(( ${#vecsCertResultRawLines[@]} ))
            lastLineNo=$(( ${#vecsCertResultRawLines[@]} - 1 ))
            aliasBlockEndLineNo=$lastLineNo
        fi

        # Parse the lines
        currAliasName=
        currSubject=
        currKeyUsage=

        # Parse alias, always on first line of block.  Example:
        #   Alias : 2823c5d97dc7a350cc13ba4bea9bc20a85a322a1
        # VC BASH_REMATCH works only first time
        currAliasName=$(echo "${vecsCertResultRawLines[${aliasBlockStartLineNo}]}" | sed -n "s/Alias\s\+\:\s\+\([A-Za-z0-9]\+\)/\1/pi")
        currAliasName=$(echo "${currAliasName}" | xargs)
        # Parse Subject, always second line of block.  Example
        #   Subject: CN=ssoserver,dc=vsphere,dc=local, C=US
        subjectLineNo=$(( $aliasBlockStartLineNo + 1 ))
        currSubjectName=$(echo "${vecsCertResultRawLines[${subjectLineNo}]}" | sed -n "s/Subject\:\s\+\(CN\)/\1/pi")
        currSubjectName=$(echo "${currSubjectName}" | xargs)
        # Key usage (entitlements) is entire final line
        currKeyUsage="${vecsCertResultRawLines[${aliasBlockEndLineNo}]}"
        currKeyUsage=$(echo "${currKeyUsage}" | xargs)

        certAlias+=($currAliasName)
        certSubject+=($currSubjectName)
        certKeyUsage+=($currKeyUsage)
        if [[ "${currKeyUsage,,}" == *"certificate sign"* ]]; then
            certIsAuthority+=("true")
        else
            certIsAuthority+=("false")
        fi
    done    
}

# To test command line parsing, revise and uncomment the following line:
#set -- "clean" "${Parameter_Clean_AnySso}"

# Parse command line
if [[ $# -ge 1 ]]; then
    if [[ "${1,,}" = "${Parameter_Mode_Help_Type1}" ]] || [[ "${1,,}" = "${Parameter_Mode_Help_Type2}" ]]  || [[ "${1,,}" = "${Parameter_Mode_Help_Type3}" ]] || [[ "${1,,}" = "${Parameter_Mode_Help_Type4}" ]]; then
        if [[ $# -eq 1 ]]; then
            mode=Mode_Help
            validCommandLine=true
            # For command line help, ignore any extra parameters, consider anything starting with help to be valid
        fi
    elif [[ "${1,,}" = "${Parameter_Mode_Clean}" ]]; then
        mode=Mode_Clean
        if [[ $# -eq 1 ]]; then
            validCommandLine=true
        elif [[ $# -eq 2 ]]; then
            if [[ "${2,,}" -eq "${Parameter_Clean_AnySso}" ]]; then
                cleanAnySso=true
                validCommandLine=true
            else
                echo "ERROR:  Unrecognized parameter ${2} for clean!"
            fi
        elif [[ $# -gt 2 ]]; then
            echo ERROR:  Too many parameters specified for clean!
        fi
    elif [[ "${1,,}" = "${Parameter_Mode_List}" ]]; then
        mode=Mode_List
        if [[ $# -eq 1 ]]; then
            validCommandLine=true
        else
            echo ERROR:  Too many parameters specified for list!
        fi
    elif [[ "${1,,}" = "${Parameter_Mode_Delete}" ]]; then
        mode=Mode_Delete
        if [[ $# -ge 2 ]]; then
            deleteTargetAlias="${2}"
            if [[ $# -eq 2 ]]; then
                validCommandLine=true
            elif [[ $# -eq 3 ]]; then
                if [[ "${3,,}" = "${Parameter_Delete_NoPrompt}" ]]; then
                    deleteNoPrompt=true
                    validCommandLine=true
                else
                    echo "ERROR:  Unrecognized parameter ${3} for delete!"
                fi
            else
                echo "ERROR:  Too many parameters for delete!"
            fi
        else
            echo "ERROR:  No target certificate alias specified for delete!"
        fi
    else
        echo "ERROR:  Unrecognized command ${1}!"
    fi
elif [[ $# -eq 0 ]]; then
    # Consider no parameters to be a valid help request
    validCommandLine=true
    mode=Mode_Help
fi
# Exit if invalid command line
if [[ ! $validCommandLine ]]; then
    exit 1;
fi

# Initialize
declare -a -g certAlias=()
declare -a -g certSubject=()
declare -a -g certKeyUsage=()
declare -a -g certIsAuthority=()
# Disable use of space as an element separator when assigning strings to array elements.  By default, bash assumes an rvalue with spaces
# is multiple elements instead of a single element.
IFS=""

if [[ "${mode}" -eq Mode_Clean ]]; then
    # Get the list of certs from VECS
    GetCertificateList

    if [[ $cleanAnySso ]]; then
        echo Targeting any TRUSTED_ROOTS CA certificate with "ssoserver" anywhere in the subject.
    fi
    echo

    # Target only the first certificate found if there are multiple (multiple should never happen)
    ssoCertificateFound=
    for ((currCertificateNo = 0 ; currCertificateNo < ${#certAlias[@]} ; currCertificateNo++ ));
    do
        # Is this a CA certificate with subject meeting criteria?
        targetCertificateNo=
        if [[ "${certIsAuthority[$currCertificateNo],,}" -eq "true" ]]; then
            if [[ $cleanAnySso ]]; then
                if [[ ${certSubject[$currCertificateNo],,} == *"ssoserver"* ]]; then
                    # Yes, delete it
                    targetCertificateNo=$currCertificateNo
                fi
            else
                if [[ ${certSubject[$currCertificateNo],,} == "cn=ssoserver" ]]; then
                    # Yes, delete it
                    targetCertificateNo=$currCertificateNo
                fi
            fi
        fi

        # Was a target certificate found?
        if [[ ${targetCertificateNo} ]]; then
            ssoCertificateFound=${targetCertificateNo}
            echo "Located ssoserver certificate as alias ${certAlias[$targetCertificateNo]}"
            /usr/lib/vmware-vmafd/bin/vecs-cli entry delete --store TRUSTED_ROOTS --alias "${certAlias[$targetCertificateNo]}"
        fi
    done
    if [[ ! $ssoCertificateFound ]]; then
        echo No ssoserver certificate found in VECS TRUSTED_ROOTS store.
    fi
elif [[ "${mode}" -eq Mode_List ]]; then
    # Get the list of certs from VECS
    GetCertificateList

    if [[ ${#certAlias[@]} -gt 0 ]]; then
        echo "Found ${#certAlias[@]} certificates in the VECS TRUSTED_ROOTS store."
        echo
        for ((currCertificateNo = 0 ; currCertificateNo < ${#certAlias[@]} ; currCertificateNo++ ));
        do
            echo "${currCertificateNo}:  ${certSubject[${currCertificateNo}]}"
            echo "    Alias ${certAlias[${currCertificateNo}]}"
            echo "    Entitlements:  ${certKeyUsage[${currCertificateNo}]}"
            if [[ ${certIsAuthority[$currCertificateNo]} ]]; then
                echo "        This is a Certificate Authority"
            fi
        done
    else
        echo "No certificates found in the VECS TRUSTED_ROOTS store."
    fi
elif [[ "${mode}" -eq Mode_Delete ]]; then
    if [[ ! $deleteNoPrompt ]]; then
        /usr/lib/vmware-vmafd/bin/vecs-cli entry delete --store TRUSTED_ROOTS --alias "${deleteTargetAlias}"
    else
        /usr/lib/vmware-vmafd/bin/vecs-cli entry delete --store TRUSTED_ROOTS --alias "${deleteTargetAlias}" -y
    fi
elif [[ "${mode}" -eq Mode_Help ]]; then
    echo "Command line syntax:"
    echo "(clean [-anysso]) | list | delete <targetCertificateAlias> [-y] | --help)"
    echo "    clean - scans for ssoserver certificate in VECS store and removes if found"
    echo "        -anysso - include certificates with ssoserver anywhere in subject, instead of cn=ssoserver"
    echo "    list - lists certificates in VECS store with alias, subject, and entitlements"
    echo "    delete - deletes the specified certificate in VECS (only)"
    echo "        <target certificate alias> - required identifier of certificate within store"
    echo "        -y - delete withOUT confirmation prompt"
    echo "    help - display command line help"
    exit 0
fi