diff --git a/common/errorCodes.js b/common/errorCodes.js index 071f4dd0..6ad74822 100644 --- a/common/errorCodes.js +++ b/common/errorCodes.js @@ -2,4 +2,5 @@ module.exports = { ERR_MISSING_PASSPHRASE: 'ERR_MISSING_PASSPHRASE', ERR_OSSL_BAD_DECRYPT: 'ERR_OSSL_BAD_DECRYPT', ERR_INVALID_USERNAME: '390144', + ERR_INVALID_KEY_FILE: 'ERR_INVALID_KEY_FILE', }; diff --git a/common/errorMessages.js b/common/errorMessages.js index f8fe38d2..13e8a06c 100644 --- a/common/errorMessages.js +++ b/common/errorMessages.js @@ -7,4 +7,6 @@ module.exports = { 'Native Okta auth doesn\'t support MFA. Please, use the "Identity Provider SSO (via external browser)" auth instead', KEY_PAIR_PASSPHRASE_ERROR: 'Please check that the passphrase is provided and matches the key.', KEY_PAIR_USERNAME_ERROR: 'Incorrect username was specified.', + KEY_PAIR_INVALID_FILE_ERROR: + "The selected file is not a valid key. Please choose a valid key file, check it's passphrase and try again.", }; diff --git a/common/getKeyPairConnectionErrorMessageByCode.js b/common/getKeyPairConnectionErrorMessageByCode.js index 8c26de0b..b5f553bf 100644 --- a/common/getKeyPairConnectionErrorMessageByCode.js +++ b/common/getKeyPairConnectionErrorMessageByCode.js @@ -5,6 +5,7 @@ const ERROR_CODE_TO_MESSAGE = { [errorCodes.ERR_MISSING_PASSPHRASE]: errorMessages.KEY_PAIR_PASSPHRASE_ERROR, [errorCodes.ERR_OSSL_BAD_DECRYPT]: errorMessages.KEY_PAIR_PASSPHRASE_ERROR, [errorCodes.ERR_INVALID_USERNAME]: errorMessages.KEY_PAIR_USERNAME_ERROR, + [errorCodes.ERR_INVALID_KEY_FILE]: errorMessages.KEY_PAIR_INVALID_FILE_ERROR, }; const getKeyPairConnectionErrorMessageByCode = code => ERROR_CODE_TO_MESSAGE[code]; diff --git a/reverse_engineering/connection_settings_modal/connectionSettingsModalConfig.json b/reverse_engineering/connection_settings_modal/connectionSettingsModalConfig.json index 409224ac..eb2552af 100644 --- a/reverse_engineering/connection_settings_modal/connectionSettingsModalConfig.json +++ b/reverse_engineering/connection_settings_modal/connectionSettingsModalConfig.json @@ -171,7 +171,7 @@ "inputKeyword": "privateKeyPath", "inputType": "file", "inputPlaceholder": "Private Key", - "extensions": ["p8"], + "extensions": ["p8", "pem", "cert", "crt"], "dependency": { "key": "authType", "value": ["keyPair"] diff --git a/reverse_engineering/helpers/connections/connectionError.js b/reverse_engineering/helpers/connections/connectionError.js new file mode 100644 index 00000000..46eeada0 --- /dev/null +++ b/reverse_engineering/helpers/connections/connectionError.js @@ -0,0 +1,12 @@ +class ConnectionError extends Error { + code; + + constructor(message, code) { + super(message); + this.code = code; + } +} + +module.exports = { + ConnectionError, +}; diff --git a/reverse_engineering/helpers/connections/keyPairConnection.js b/reverse_engineering/helpers/connections/keyPairConnection.js index 2bd3b278..45df4f65 100644 --- a/reverse_engineering/helpers/connections/keyPairConnection.js +++ b/reverse_engineering/helpers/connections/keyPairConnection.js @@ -1,6 +1,21 @@ +const fs = require('fs'); +const crypto = require('crypto'); const { connectWithTimeout } = require('./connection.js'); +const errorCodes = require('../../../common/errorCodes.js'); +const errorMessages = require('../../../common/errorMessages.js'); +const { ConnectionError } = require('./connectionError.js'); + +const authByKeyPair = ({ account, role, timeout, username, privateKeyPath, privateKeyPass, logger }) => { + if ( + !isValidPrivateKey({ + privateKeyPath, + privateKeyPass, + logger, + }) + ) { + throw new ConnectionError(errorMessages.KEY_PAIR_INVALID_FILE_ERROR, errorCodes.ERR_INVALID_KEY_FILE); + } -const authByKeyPair = ({ account, role, timeout, username, privateKeyPath, privateKeyPass }) => { return connectWithTimeout({ account, role, @@ -12,6 +27,24 @@ const authByKeyPair = ({ account, role, timeout, username, privateKeyPath, priva }); }; +const isValidPrivateKey = ({ privateKeyPath, privateKeyPass, logger }) => { + const textFilePrivateKeyFormat = 'pem'; + const fileContent = fs.readFileSync(privateKeyPath, 'utf8'); + + try { + crypto.createPrivateKey({ + key: fileContent, + format: textFilePrivateKeyFormat, + passphrase: privateKeyPass, + }); + + return true; + } catch (error) { + logger.log('error', { error }, 'Connection error'); + return false; + } +}; + module.exports = { authByKeyPair, }; diff --git a/reverse_engineering/helpers/snowflakeHelper.js b/reverse_engineering/helpers/snowflakeHelper.js index ca51d265..f611800b 100644 --- a/reverse_engineering/helpers/snowflakeHelper.js +++ b/reverse_engineering/helpers/snowflakeHelper.js @@ -129,7 +129,7 @@ const connect = async ( logger, }); } else if (authType === 'keyPair') { - authPromise = authByKeyPair({ account, role, timeout, username, privateKeyPath, privateKeyPass }); + authPromise = authByKeyPair({ account, role, timeout, username, privateKeyPath, privateKeyPass, logger }); } else { authPromise = authByCredentials({ account, username, password, role, warehouse, timeout }); }