#!/bin/bash

#
#Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
#
#This software is dual-licensed to you under the MIT License (MIT) and the Universal Permissive License (UPL).  See the LICENSE file in the root directory for license terms.  You may choose
#either license, or both.
#

help_msg() 
{
    printf "\nusage:"
    printf "\nprovisioner.sh [option...] <URI> <ID> <Secret> [<password>] [Icd_ID Icd_Secret]"
    printf "\nprovisioner.sh -l <provisioned file> [<password>]"
    printf "\noptions:"
    printf "\n-e : to indicate <ID> is an enterprise integration id"
    printf "\n-h : help"
    printf "\n-l : to list the contents of the provisioned file"
    printf "\n<URI> is entered as [<scheme>://]<host>[:<port>]"
    printf "\n<ID> is an activation ID"
    printf "\n<Secret> is the shared secret from registering the device on the sever or from creating the enterprise integration."
    printf "\n<password> is a passphrase used to protect the integrity of the trusted assets store"
    printf "\n<Icd_ID Icd_Secret> is a indirect connected device data contained its activation id and shared secret"
    printf "\n"
}

initialize_values()
{
    ARGS_NUMBER=1
    ARGS_COUNT=$#
    E_FLAG_USED=0
    while [ "$1" != "" ]; do
        VALUE=$(echo "$1" | awk -F= '{print $1}')
        #check options
        case $1 in
            -l)
                list_trusted_assets_file "$@"
                exit
                ;;
            -h)
                help_msg
                exit
                ;;
            -e)
                printf "INFO: -e parameter was skipped because only activation id expected for C client library.\n"
                E_FLAG_USED=1
                shift
                continue
                ;;
            *)
                #check positioned variables
                if [ "$ARGS_NUMBER" -eq 1 ]; then
                    URI_STR=$VALUE;
                    ARGS_NUMBER=2;
                    shift;
                elif [ $ARGS_NUMBER -eq 2 ]; then
                    CLIENT_ID=$VALUE;
                    ARGS_NUMBER=3;
                    shift;
                elif [ $ARGS_NUMBER -eq 3 ]; then
                    SHARED_SECRET=$VALUE;
                    ARGS_NUMBER=4;
                    shift;
                elif [ $ARGS_NUMBER -eq 4 ]; then
                    ARGS_NUMBER=5;
                    #if args count is even then it means that PASSPHRASE was provided; otherwise wasn't
                    if [ $(((ARGS_COUNT-E_FLAG_USED)%2)) -eq 0 ]; then
                        PASSPHRASE=$VALUE;
                        shift;
                    else
                        echo -n "Enter the passphrase:";
                        read -r PASSPHRASE;
                    fi;
                    #compute the PKCS#5 PBKDF2 derivation of the password
                    ASSETS_KEY=$(echo -n 00000001 | xxd -r -p | openssl dgst -sha1 -mac HMAC -macopt key:"$PASSPHRASE" -hex -r | head -c 32)
                else
                    ICD_DATA_COUNT=$((ICD_DATA_COUNT+1));
                    if [ $((ICD_DATA_COUNT%2)) -eq 1 ]; then
                        printf -v ICD_STRING "%sicd.client.id=%s\n" "$ICD_STRING" "$VALUE";
                    else
                        #use this key to encrypt the secret
                        ENCRYPTED_ICD_SECRET=$(echo -n "$VALUE" | openssl enc -aes-128-cbc -K "$ASSETS_KEY" -iv 0000000000000000 -nosalt -a -A)
                        printf -v ICD_STRING "%sicd.shared.secret=%s\n" "$ICD_STRING" "$ENCRYPTED_ICD_SECRET";
                    fi;
                    shift;
                fi
                ;;
        esac
    done

    #check that icd data was fully presented
    if [ $((ICD_DATA_COUNT%2)) -eq 1 ]; then
        printf "ERROR: indirect connected devices data wasn't fully presented.\n";
        help_msg;
        exit 1;
    fi

    #check that uri parameter is set
    if [ -z "$URI_STR" ]; then
        printf "ERROR: missing \"<URI>\" option\n";
        help_msg;
        exit 1;
    fi
    #check that id parameter is set
    if [ -z "$CLIENT_ID" ]; then
        printf "ERROR: missing \"<ID>\" option\n";
        help_msg;
        exit 1;
    fi
    #check that secret parameter is set
    if [ -z "$SHARED_SECRET" ]; then
        printf "ERROR: missing \"<SECRET>\" option\n";
        help_msg;
        exit 1;
    fi
}

initialize_values_interactive()
{
    #check that uri parameter is set
    while [ -z "$URI_STR" ]; do
        echo -n "Enter Cloud Service URI:";
        read -r URI_STR;
    done
    #check that id parameter is set
    while [ -z "$CLIENT_ID" ]; do
        echo -n "Enter the ID:";
        read -r CLIENT_ID;
    done
    #check that secret parameter is set
    while [ -z "$SHARED_SECRET" ]; do
        echo -n "Enter the shared secret:";
        read -r SHARED_SECRET;
    done
    #check that password parameter is set
    while [ -z "$PASSPHRASE" ]; do
        echo -n "Enter the passphrase:";
        read -r PASSPHRASE;
    done
}

parse_uri_parameter()
{
    NEXT="$URI_STR"

    #get server scheme
    SERVER_SCHEME=$(echo "$NEXT" | awk -F'://' '{printf "%s",$1}')
    if [ -z "$SERVER_SCHEME" ]; then 
        SERVER_SCHEME=https;
    fi
    if [ "$SERVER_SCHEME" != "https" ] && [ "$SERVER_SCHEME" != "mqtts" ]; then
        printf "ERROR: unsupported scheme in <URI> - '%s'. Only 'https' or 'mqtts' are expected !\n" "$SERVER_SCHEME"; >&2
        exit 1;
    fi

    NEXT=$(echo "$URI_STR" | awk -F'://' '{printf "%s",$2}');

    #get host name
    HOST_NAME=$(echo "$NEXT" | awk -F':' '{printf "%s",$1}')
    if [ -z "$HOST_NAME" ]; then 
        printf "ERROR: incorrect \"<URI>\" option. Host name cannot be parsed.\n";
        help_msg;
        exit 1;
    fi

    #get host port
    PORT=$(echo "$NEXT" | awk -F':' '{printf "%s",$2}')
    if [ -z "$PORT" ]; then 
        printf "ERROR: incorrect \"<URI>\" option. Host port cannot be parsed.\n";
        help_msg;
        exit 1;
    fi
    if [ "$PORT" -gt 65535 ] || [ "$PORT" -lt 0 ]; then
        printf "ERROR: illegal server port value \"%s\"\n" "$PORT";
        help_msg;
        exit 1
    fi
}

get_certificate()
{
    # if SERVER_ROOT_CERTIFICATE variable is specified then file with that name must exist
    if [ ! -z "${SERVER_ROOT_CERTIFICATE:-}" ]; then
        if [ -f "$SERVER_ROOT_CERTIFICATE" ]; then
            return 0;
        else
            printf "ERROR: SERVER_ROOT_CERTIFICATE %s does not exist or not a regular file\n" "$SERVER_ROOT_CERTIFICATE"; >&2
            exit 1
        fi
    fi

    # oraclecloud.com servers do not send a root of the cert chain,
    # however they are signed by public CA that systems should already trust.
    # Don't even try to get the certs or check connectivity
    if [[ "$HOST_NAME" =~ "oraclecloud.com"$ ]] ; then
        SERVER_ROOT_CERTIFICATE="SYSTEM_DEFAULT_CA"
        return 0
    fi

    # SERVER_ROOT_CERTIFICATE variable isn't set - set a default file name
    SERVER_ROOT_CERTIFICATE="iotca.pem"

    # remove existing file and try to get root cert from server
    printf "INFO: Try to get new server certificates from server %s...\n" "$HOST_NAME"
    rm -rf "$SERVER_ROOT_CERTIFICATE"
    CERTS=$(echo -n | openssl s_client -connect $HOST_NAME:$PORT -showcerts | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p')
    echo "$CERTS" | awk -v RS="-----BEGIN CERTIFICATE-----" 'NR > 1 { printf RS $0 > "'$SERVER_ROOT_CERTIFICATE'"; close("'$SERVER_ROOT_CERTIFICATE'") }'

    if [ -f "$SERVER_ROOT_CERTIFICATE" ] && [ -s "$SERVER_ROOT_CERTIFICATE" ]; then
        printf "INFO: %s certificate is created !\n" "$SERVER_ROOT_CERTIFICATE";
    else
        printf "ERROR: %s certificate creation is failed !\n" "$SERVER_ROOT_CERTIFICATE"; >&2
        exit 1;
    fi
}

generate_truststore()
{
    FILENAME=./trusted_assets_store
    rm -rf $FILENAME

    #use this key to encrypt the secret
    ENCRYPTED_SHARED_SECRET=$(echo -n "$SHARED_SECRET" | openssl enc -aes-128-cbc -K "$ASSETS_KEY" -iv 0000000000000000 -nosalt -a -A)

    #build the file
    printf "client.id=%s\n" "$CLIENT_ID" > $FILENAME
    printf "client.secret=%s\n" "$ENCRYPTED_SHARED_SECRET" >> $FILENAME
    if [ "$SERVER_ROOT_CERTIFICATE" = "SYSTEM_DEFAULT_CA" ]; then
        printf "trustAnchors=SYSTEM_DEFAULT_CA\n" >> $FILENAME
    else
        printf "trustAnchors=IN_PLACE_CA\n" >> $FILENAME
        cat "$SERVER_ROOT_CERTIFICATE" | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sed '$d' >> $FILENAME
        printf "%s\n" "-----END CERTIFICATE-----" >> $FILENAME
    fi
    printf "server.host=%s\n" "$HOST_NAME" >> $FILENAME
    printf "server.port=%s\n" "$PORT" >> $FILENAME
    printf "server.scheme=$SERVER_SCHEME\n" >> $FILENAME

    if [ ! $ICD_DATA_COUNT -eq 0 ]; then
        printf "%s" "$ICD_STRING" >> $FILENAME
    fi

    #compute the hmac of the file
    ASSETS_HMAC=$(cat $FILENAME | openssl dgst -sha256 -mac HMAC -macopt hexkey:"$ASSETS_KEY" -hex -r | head -c 64)
    printf "signature=%s\n" "$ASSETS_HMAC" >> $FILENAME

    if [ -f "$FILENAME" ] && [ -s $FILENAME ]; then
            printf "INFO: Trusted assets store is created !\n"
    else
        printf "ERROR: Trusted assets store creation is failed !\n";
        exit 2;
    fi
}

list_trusted_assets_file()
{
    ARGS_NUMBER=1
    while [ "$1" != "" ]; do
        VALUE=$(echo $1)
        #check positioned variables
        if [ "$ARGS_NUMBER" -eq 1 ]; then
            if ! [ "$1" = "-l" ]; then
                printf "ERROR: first parameter isn't \"-l\" option\n"
                help_msg;
                exit 2
            fi
            ARGS_NUMBER=2;
            shift;
        elif [ $ARGS_NUMBER -eq 2 ]; then
            TAS_FILENAME=$VALUE;
            ARGS_NUMBER=3;
            shift;
        elif [ $ARGS_NUMBER -eq 3 ]; then
            PASSPHRASE=$VALUE;
            ARGS_NUMBER=4;
            shift;
        else
            printf "ERROR: unexpected parameter \"%s\"\n" "$VALUE";
            help_msg;
            exit 1;
        fi
    done
    #check that tas filename parameter is set
    if [ -z "$TAS_FILENAME" ]; then
        printf "ERROR: missing \"<provisioned file>\" option\n";
        help_msg;
        exit 1;
    fi

    #check that passphrase parameter is set
    if [ -z "$PASSPHRASE" ]; then
        echo -n "Enter the passphrase:";
        read PASSPHRASE;
    fi

    read -r FIRST_LINE < $TAS_FILENAME;
    if echo $FIRST_LINE | grep -q "!"; then
        printf "ERROR: Trusted assets store created by REST API can't be printed by deprecated provisioner tool.\n";
        exit 3;
    fi

    LINE_COUNT=1;
    ICD_COUNT=0;

    #TRUSTED_ASSETS_STORE:
    #1)client.id=value
    #2)client.secret=encrypted value
    #3)truststore=truststore type (system default or in-place certificate)
    #4)OPTIONAL in-place certificate
    #5)server.host=host name
    #6)server.port=port value
    #7)server.scheme=https/mqtts
    #8 to n)indirect connected devices data
    #n+1)signature=trusted assets store signature

    #The while is loop executed in a subshell. So any changes you do for the variable will not be available once the subshell exits.
    #We are going to support /bin/bash and /bin/sh. There are no way to redirect subshell (like while read -r LINE do ... done <<< `cat file` in /bin/bash) in /bin/sh.
    #It's a reason why we print lines in the same order as them positioned in tas.

    cat $TAS_FILENAME | while read -r LINE
    do
        if [ "$LINE_COUNT" -eq 1 ]; then
            TAS_ID=$(echo "$LINE" | awk -F'client.id=' '{print $2}');
            if ! [ -z "$TAS_ID" ]; then
                printf "Client ID: %s\n" "$TAS_ID";
            else
                printf "ERROR: Incorect trusted assets store\n";
                exit 2;
            fi;
            LINE_COUNT=2;
        elif [ $LINE_COUNT -eq 2 ]; then
            TAS_ENCRYPTED_SECRET=$(echo "$LINE" | awk -F'client.secret=' '{print $2}')

            #compute the PKCS#5 PBKDF2 derivation of the password
            ASSETS_KEY=$(echo -n 00000001 | xxd -r -p | openssl dgst -sha1 -mac HMAC -macopt key:"$PASSPHRASE" -hex -r | head -c 32)
            DECRYPTED_SHARED_SECRET=$(echo -n "$TAS_ENCRYPTED_SECRET" | openssl enc -aes-128-cbc -K "$ASSETS_KEY" -iv 0000000000000000 -nosalt -d -a -A)

            if ! [ -z "$DECRYPTED_SHARED_SECRET" ]; then
                printf "Client secret: %s\n" "$DECRYPTED_SHARED_SECRET";
            else
                printf "ERROR: Incorect trusted assets store\n";
                exit 2;
            fi;
            LINE_COUNT=3;
        elif [ $LINE_COUNT -eq 3 ]; then
            TAS_CA_TYPE=$(echo "$LINE" | awk -F'trustAnchors=' '{print $2}');
            if [ "$TAS_CA_TYPE" = "IN_PLACE_CA" ]; then
                LINE_COUNT=4;
            else
                LINE_COUNT=5;
            fi;
        elif [ $LINE_COUNT -eq 4 ]; then
            TAS_HOST_PREFIX=$(echo "$LINE" | awk -F'=' '{print $1}')
            if [ "$TAS_HOST_PREFIX" = "server.host" ]; then
                TAS_HOST=$(echo "$LINE" | awk -F'server.host=' '{print $2}')
                if ! [ -z "$TAS_HOST" ]; then
                    printf "Server host: %s\n" "$TAS_HOST";
                else
                    printf "ERROR: Incorect trusted assets store\n";
                    exit 2;
                fi;
                LINE_COUNT=6;
                continue;
            else
                printf "%s\n" "$LINE";
            fi;
        elif [ $LINE_COUNT -eq 5 ]; then
            TAS_HOST=$(echo "$LINE" | awk -F'server.host=' '{print $2}')
            if ! [ -z "$TAS_HOST" ]; then
                printf "Server host: %s\n" "$TAS_HOST";
            else
                printf "ERROR: Incorect trusted assets store\n";
                exit 2;
            fi;
            LINE_COUNT=6;
        elif [ $LINE_COUNT -eq 6 ]; then
            TAS_PORT=$(echo "$LINE" | awk -F'server.port=' '{print $2}')
            if ! [ -z "$TAS_PORT" ]; then
                printf "Server port: %s\n" "$TAS_PORT";
            else
                printf "ERROR: Incorect trusted assets store\n";
                exit 2;
            fi;
            LINE_COUNT=7;
        elif [ $LINE_COUNT -eq 7 ]; then
            TAS_SCHEME=$(echo "$LINE" | awk -F'server.scheme=' '{print $2}')
            if ! [ -z "$TAS_SCHEME" ]; then
                printf "Server scheme: %s\n" "$TAS_SCHEME";
            else
                printf "ERROR: Incorect trusted assets store\n";
                exit 2;
            fi;
            LINE_COUNT=8;
        else
            ICD_COUNT=$((ICD_COUNT+1));
            if [ $((ICD_COUNT%2)) -eq 1 ]; then
                ICD_DATA=$(echo "$LINE" | awk -F'icd.client.id=' '{print $2}');
                if ! [ -z "$ICD_DATA" ]; then
                    printf "Indirect connected device (%s) ID: %s\n" "$((ICD_COUNT/2 + 1))" "$ICD_DATA";
                fi;
            else
                ICD_DATA=$(echo "$LINE" | awk -F'icd.shared.secret=' '{print $2}');
                if ! [ -z "$ICD_DATA" ]; then
                    printf "Indirect connected device (%s) shared secret: %s\n" "$((ICD_COUNT/2))" "$ICD_DATA";
                else
                    printf "ERROR: Incorect indirect connected device shared secret\n";
                    exit 2;
                fi;
            fi;
        fi
    done

    exit 1;
}

printf "WARNING: This tool has been deprecated. Please follow documentation to download provisioning file generated by the Oracle IoT Cloud Service.\n"

URI_STR=""
CLIENT_ID=""
SHARED_SECRET=""
PASSPHRASE=""
HOST_NAME=""
PORT=""
SERVER_SCHEME=""
ICD_STRING=""
ICD_DATA_COUNT=0

#parse parameters
if [ "$#" -eq 0 ]; then
    initialize_values_interactive
else
    initialize_values "$@"
fi

#parse URI parameter
parse_uri_parameter

#get certificate
get_certificate

#generate trusted assets store
generate_truststore