350 lines
12 KiB
JavaScript
350 lines
12 KiB
JavaScript
|
|
// Copyright (c) 2022, 2023, Oracle and/or its affiliates.
|
||
|
|
|
||
|
|
//-----------------------------------------------------------------------------
|
||
|
|
//
|
||
|
|
// This software is dual-licensed to you under the Universal Permissive License
|
||
|
|
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
||
|
|
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
||
|
|
// either license.
|
||
|
|
//
|
||
|
|
// If you elect to accept the software under the Apache License, Version 2.0,
|
||
|
|
// the following applies:
|
||
|
|
//
|
||
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
|
// you may not use this file except in compliance with the License.
|
||
|
|
// You may obtain a copy of the License at
|
||
|
|
//
|
||
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
//
|
||
|
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
|
// See the License for the specific language governing permissions and
|
||
|
|
// limitations under the License.
|
||
|
|
//
|
||
|
|
//-----------------------------------------------------------------------------
|
||
|
|
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
const { Buffer } = require('buffer');
|
||
|
|
const constants = require("../constants.js");
|
||
|
|
const errors = require("../../../errors.js");
|
||
|
|
const process = require("process");
|
||
|
|
const ED = require("../encryptDecrypt.js");
|
||
|
|
const Message = require("./base.js");
|
||
|
|
const util = require("../../util.js");
|
||
|
|
const cInfo = util.CLIENT_INFO;
|
||
|
|
const crypto = require('crypto');
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Executes OSESSKEY and OAUTH RPC functions
|
||
|
|
*
|
||
|
|
* @class AuthMessage
|
||
|
|
* @extends {Message}
|
||
|
|
*/
|
||
|
|
class AuthMessage extends Message {
|
||
|
|
/**
|
||
|
|
* Represents the data required for OAUTH and OSESSKEY rpc.
|
||
|
|
*
|
||
|
|
* @param {object} conn Connection object
|
||
|
|
* @param {object} config Dynamic Configuration like change password config after connection is established
|
||
|
|
*/
|
||
|
|
constructor(conn, config) {
|
||
|
|
super(conn);
|
||
|
|
this.functionCode = constants.TNS_FUNC_AUTH_PHASE_ONE;
|
||
|
|
this.messageType = constants.TNS_MSG_TYPE_FUNCTION;
|
||
|
|
this.sessionData = {};
|
||
|
|
this.conn = conn;
|
||
|
|
this.sessionKey = "";
|
||
|
|
this.encodedPassword = "";
|
||
|
|
this.changePassword = false;
|
||
|
|
Object.defineProperty(this, 'password', {
|
||
|
|
enumerable: false,
|
||
|
|
value: config.password,
|
||
|
|
});
|
||
|
|
this.username = config.user;
|
||
|
|
if (this.username === undefined) {
|
||
|
|
this.username = "";
|
||
|
|
} else {
|
||
|
|
// trim leading and trailing spaces
|
||
|
|
this.username = this.username.trim();
|
||
|
|
}
|
||
|
|
this.schemaUser = '';
|
||
|
|
this.proxyUser = '';
|
||
|
|
this.proxyStatus = -1;
|
||
|
|
if (this.username.length !== 0) {
|
||
|
|
this.proxyStatusObj = util.checkProxyUserValidity(this.username);
|
||
|
|
if (this.proxyStatusObj.status === 0) {
|
||
|
|
this.proxyStatus = 0;
|
||
|
|
this.proxyUser = this.proxyStatusObj.proxyUser;
|
||
|
|
this.schemaUser = this.proxyStatusObj.schemaUser;
|
||
|
|
this.username = this.proxyUser;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this.newPassword = config.newPassword;
|
||
|
|
if (config.changePassword) {
|
||
|
|
// ChangePassword issued after login would use the same comboKey
|
||
|
|
// used in initial Login. We issue only OAUTH.
|
||
|
|
this.changePassword = true;
|
||
|
|
this.functionCode = constants.TNS_FUNC_AUTH_PHASE_TWO;
|
||
|
|
}
|
||
|
|
if (this.username) {
|
||
|
|
this.userByteLen = Buffer.byteLength(this.username); // Get utf8 encoded number of bytes
|
||
|
|
} else {
|
||
|
|
this.userByteLen = 0;
|
||
|
|
}
|
||
|
|
this.token = config.token;
|
||
|
|
if (this.token)
|
||
|
|
this.functionCode = constants.TNS_FUNC_AUTH_PHASE_TWO;
|
||
|
|
this.privateKey = config.privateKey;
|
||
|
|
if (this.privateKey) {
|
||
|
|
this.privateKey = util.normalizePrivateKey(this.privateKey);
|
||
|
|
}
|
||
|
|
this.serviceName = this.conn.serviceName;
|
||
|
|
this.remoteAddress = this.conn.remoteAddress;
|
||
|
|
this.setAuthMode(config);
|
||
|
|
}
|
||
|
|
|
||
|
|
setAuthMode(config) {
|
||
|
|
if (!this.newPassword) {
|
||
|
|
this.authMode = constants.TNS_AUTH_MODE_LOGON;
|
||
|
|
}
|
||
|
|
if (config.privilege & constants.SYSDBA) {
|
||
|
|
this.authMode |= constants.TNS_AUTH_MODE_SYSDBA;
|
||
|
|
}
|
||
|
|
if (config.privilege & constants.SYSOPER) {
|
||
|
|
this.authMode |= constants.TNS_AUTH_MODE_SYSOPER;
|
||
|
|
}
|
||
|
|
if (config.privilege & constants.SYSASM) {
|
||
|
|
this.authMode |= constants.TNS_AUTH_MODE_SYSASM;
|
||
|
|
}
|
||
|
|
if (config.privilege & constants.SYSBKP) {
|
||
|
|
this.authMode |= constants.TNS_AUTH_MODE_SYSBKP;
|
||
|
|
}
|
||
|
|
if (config.privilege & constants.SYSDG) {
|
||
|
|
this.authMode |= constants.TNS_AUTH_MODE_SYSDGD;
|
||
|
|
}
|
||
|
|
if (config.privilege & constants.SYSKM) {
|
||
|
|
this.authMode |= constants.TNS_AUTH_MODE_SYSKMT;
|
||
|
|
}
|
||
|
|
if (config.privilege & constants.SYSRAC) {
|
||
|
|
this.authMode |= constants.TNS_AUTH_MODE_SYSRAC;
|
||
|
|
}
|
||
|
|
if (this.privateKey) {
|
||
|
|
this.authMode |= constants.TNS_AUTH_MODE_IAM_TOKEN;
|
||
|
|
}
|
||
|
|
if (this.newPassword) {
|
||
|
|
this.authMode |= constants.TNS_AUTH_MODE_CHANGE_PASSWORD;
|
||
|
|
}
|
||
|
|
if (!this.token) {
|
||
|
|
this.authMode |= constants.TNS_AUTH_MODE_WITH_PASSWORD;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
getAlterTimezoneStatement() {
|
||
|
|
let sign, tzRepr;
|
||
|
|
if (process.env.ORA_SDTZ) {
|
||
|
|
tzRepr = process.env.ORA_SDTZ;
|
||
|
|
} else {
|
||
|
|
const date = new Date();
|
||
|
|
const timezoneMinutes = date.getTimezoneOffset();
|
||
|
|
let tzHour = Math.trunc(timezoneMinutes / 60);
|
||
|
|
const tzMinutes = Math.abs((timezoneMinutes - tzHour * 60) % 60);
|
||
|
|
if (tzHour < 0) {
|
||
|
|
sign = '+'; // getTimezoneOffset() = localtime - timeUTC
|
||
|
|
tzHour = -tzHour;
|
||
|
|
} else {
|
||
|
|
sign = '-';
|
||
|
|
}
|
||
|
|
tzHour = tzHour.toLocaleString('en-US', {minimumIntegerDigits: 2});
|
||
|
|
tzRepr = `${sign}${tzHour}:${tzMinutes}`;
|
||
|
|
}
|
||
|
|
return `ALTER SESSION SET TIME_ZONE ='${tzRepr}'\x00`;
|
||
|
|
}
|
||
|
|
|
||
|
|
encode(buf) {
|
||
|
|
let verifier11G = false;
|
||
|
|
this.writeFunctionHeader(buf);
|
||
|
|
if (this.userByteLen > 0) {
|
||
|
|
buf.writeUInt8(1);
|
||
|
|
} else {
|
||
|
|
buf.writeUInt8(0);
|
||
|
|
}
|
||
|
|
buf.writeUB4(this.userByteLen);
|
||
|
|
buf.writeUB4(this.authMode);
|
||
|
|
|
||
|
|
if (this.functionCode === constants.TNS_FUNC_AUTH_PHASE_ONE) {
|
||
|
|
buf.writeUInt8(1);
|
||
|
|
buf.writeUB4(5);
|
||
|
|
buf.writeUInt8(0);
|
||
|
|
buf.writeUInt8(1);
|
||
|
|
if (this.userByteLen > 0) {
|
||
|
|
buf.writeBytesWithLength(Buffer.from(this.username));
|
||
|
|
}
|
||
|
|
buf.writeKeyValue("AUTH_TERMINAL", cInfo.terminal);
|
||
|
|
buf.writeKeyValue("AUTH_PROGRAM_NM", cInfo.program);
|
||
|
|
buf.writeKeyValue("AUTH_MACHINE", cInfo.hostName);
|
||
|
|
buf.writeKeyValue("AUTH_PID", cInfo.pid);
|
||
|
|
buf.writeKeyValue("AUTH_SID", cInfo.userName);
|
||
|
|
} else {
|
||
|
|
let numPairs = 0;
|
||
|
|
|
||
|
|
if (this.changePassword) {
|
||
|
|
ED.updatePasswordsWithComboKey(this.password, this.newPassword, this.conn.comboKey, this);
|
||
|
|
numPairs = 2;
|
||
|
|
} else {
|
||
|
|
numPairs = 4;
|
||
|
|
if (this.token) {
|
||
|
|
numPairs += 1;
|
||
|
|
} else {
|
||
|
|
numPairs += 2;
|
||
|
|
if (this.verifierType === constants.TNS_VERIFIER_TYPE_11G_1 ||
|
||
|
|
this.verifierType === constants.TNS_VERIFIER_TYPE_11G_2) {
|
||
|
|
verifier11G = true;
|
||
|
|
} else if (this.verifierType !== constants.TNS_VERIFIER_TYPE_12C) {
|
||
|
|
errors.throwErr(errors.ERR_UNSUPPORTED_VERIFIER_TYPE,
|
||
|
|
this.verifierType.toString(16));
|
||
|
|
} else {
|
||
|
|
numPairs += 1;
|
||
|
|
}
|
||
|
|
ED.updateVerifierData(this.sessionData, this.password, this.newPassword, verifier11G, this);
|
||
|
|
|
||
|
|
// The comboKey is cached inside the conn which is used
|
||
|
|
// for changePassword issued on the connection
|
||
|
|
this.conn.comboKey = this.comboKey;
|
||
|
|
if (this.newPassword) {
|
||
|
|
numPairs += 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.privateKey) {
|
||
|
|
numPairs += 2;
|
||
|
|
}
|
||
|
|
if (this.conn.connectionClass) {
|
||
|
|
numPairs += 1;
|
||
|
|
}
|
||
|
|
if (this.conn.purity) {
|
||
|
|
numPairs += 1;
|
||
|
|
}
|
||
|
|
if (this.conn.jdwpData) {
|
||
|
|
this.encryptedJDWPData = ED.getEncryptedJSWPData(this.sessionKey, this.conn.jdwpData);
|
||
|
|
numPairs += 1;
|
||
|
|
}
|
||
|
|
if (this.schemaUser.length !== 0) {
|
||
|
|
numPairs += 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
buf.writeUInt8(1);
|
||
|
|
buf.writeUB4(numPairs);
|
||
|
|
buf.writeUInt8(1);
|
||
|
|
buf.writeUInt8(1);
|
||
|
|
if (this.userByteLen > 0)
|
||
|
|
buf.writeBytesWithLength(Buffer.from(this.username));
|
||
|
|
if (this.token) {
|
||
|
|
buf.writeKeyValue("AUTH_TOKEN", this.token);
|
||
|
|
} else {
|
||
|
|
if (!this.changePassword) {
|
||
|
|
buf.writeKeyValue("AUTH_SESSKEY", this.sessionKey, 1);
|
||
|
|
if (!verifier11G) {
|
||
|
|
buf.writeKeyValue("AUTH_PBKDF2_SPEEDY_KEY", this.speedyKey);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!this.changePassword) {
|
||
|
|
buf.writeKeyValue("SESSION_CLIENT_CHARSET", "873");
|
||
|
|
buf.writeKeyValue("SESSION_CLIENT_DRIVER_NAME", constants.DRIVER_NAME);
|
||
|
|
buf.writeKeyValue("SESSION_CLIENT_VERSION",
|
||
|
|
constants.CLIENT_VERSION.toString());
|
||
|
|
buf.writeKeyValue("AUTH_ALTER_SESSION", this.getAlterTimezoneStatement(), 1);
|
||
|
|
}
|
||
|
|
if (this.encodedPassword) {
|
||
|
|
buf.writeKeyValue("AUTH_PASSWORD", this.encodedPassword);
|
||
|
|
}
|
||
|
|
if (this.proxyStatus === 0) {
|
||
|
|
buf.writeKeyValue("PROXY_CLIENT_NAME", this.schemaUser);
|
||
|
|
}
|
||
|
|
if (this.encodedNewPassword) {
|
||
|
|
buf.writeKeyValue("AUTH_NEWPASSWORD", this.encodedNewPassword);
|
||
|
|
}
|
||
|
|
if (this.conn.connectionClass) {
|
||
|
|
buf.writeKeyValue("AUTH_KPPL_CONN_CLASS", this.conn.connectionClass);
|
||
|
|
}
|
||
|
|
if (this.conn.purity) {
|
||
|
|
buf.writeKeyValue("AUTH_KPPL_PURITY", '' + this.conn.purity);
|
||
|
|
}
|
||
|
|
if (this.privateKey) {
|
||
|
|
const currentDate = new Date();
|
||
|
|
const currentDateFormatted = currentDate.toGMTString();
|
||
|
|
|
||
|
|
const header = "date: " + currentDateFormatted + "\n" +
|
||
|
|
"(request-target): " + this.serviceName + "\n" +
|
||
|
|
"host: " + this.remoteAddress;
|
||
|
|
|
||
|
|
const signature = crypto.createSign('RSA-SHA256')
|
||
|
|
.update(header)
|
||
|
|
.sign(this.privateKey, 'base64');
|
||
|
|
|
||
|
|
buf.writeKeyValue("AUTH_HEADER", header);
|
||
|
|
buf.writeKeyValue("AUTH_SIGNATURE", signature);
|
||
|
|
}
|
||
|
|
if (this.conn.jdwpData) {
|
||
|
|
buf.writeKeyValue("AUTH_ORA_DEBUG_JDWP", this.encryptedJDWPData);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
processReturnParameter(buf) {
|
||
|
|
const numParams = buf.readUB2();
|
||
|
|
for (let i = 0; i < numParams;i++) {
|
||
|
|
buf.skipUB4();
|
||
|
|
const key = buf.readStr(constants.CSFRM_IMPLICIT);
|
||
|
|
let value = "";
|
||
|
|
const numBytes = buf.readUB4();
|
||
|
|
if (numBytes > 0) {
|
||
|
|
value = buf.readStr(constants.CSFRM_IMPLICIT);
|
||
|
|
}
|
||
|
|
const flag = buf.readUB4();
|
||
|
|
if (key === "AUTH_VFR_DATA") {
|
||
|
|
this.verifierType = flag;
|
||
|
|
}
|
||
|
|
this.sessionData[key] = value;
|
||
|
|
|
||
|
|
}
|
||
|
|
if (this.functionCode === constants.TNS_FUNC_AUTH_PHASE_ONE) {
|
||
|
|
this.functionCode = constants.TNS_FUNC_AUTH_PHASE_TWO;
|
||
|
|
} else {
|
||
|
|
let releaseNum;
|
||
|
|
let updateNum;
|
||
|
|
let portReleaseNum;
|
||
|
|
let portUpdateNum;
|
||
|
|
|
||
|
|
this.conn.dbDomain = this.sessionData['AUTH_SC_DB_DOMAIN'];
|
||
|
|
this.conn.dbName = this.sessionData['AUTH_SC_DBUNIQUE_NAME'];
|
||
|
|
this.conn.maxOpenCursors = Number(this.sessionData['AUTH_MAX_OPEN_CURSORS'] || 0);
|
||
|
|
this.conn.serviceName = this.sessionData['AUTH_SC_SERVICE_NAME'];
|
||
|
|
this.conn.instanceName = this.sessionData['AUTH_INSTANCENAME'];
|
||
|
|
const fullVersionNum = Number(this.sessionData['AUTH_VERSION_NO']);
|
||
|
|
const versionNum = (fullVersionNum >> 24) & 0xFF;
|
||
|
|
this.conn.warning = this.warning;
|
||
|
|
if (buf.caps.ttcFieldVersion >= constants.TNS_CCAP_FIELD_VERSION_18_1_EXT_1) {
|
||
|
|
releaseNum = (fullVersionNum >> 16) & 0xFF;
|
||
|
|
updateNum = (fullVersionNum >> 12) & 0x0F;
|
||
|
|
portReleaseNum = (fullVersionNum >> 4) & 0xFF;
|
||
|
|
portUpdateNum = fullVersionNum & 0x0F;
|
||
|
|
} else {
|
||
|
|
releaseNum = (fullVersionNum >> 20) & 0x0F;
|
||
|
|
updateNum = (fullVersionNum >> 12) & 0xFF;
|
||
|
|
portReleaseNum = (fullVersionNum >> 8) & 0x0F;
|
||
|
|
portUpdateNum = fullVersionNum & 0xFF;
|
||
|
|
}
|
||
|
|
this.conn.serverVersionString = versionNum + '.' + releaseNum + '.' + updateNum + '.' + portReleaseNum + '.' + portUpdateNum;
|
||
|
|
this.conn.serverVersion = versionNum * 100000000 + releaseNum * 1000000 + updateNum * 10000 + portReleaseNum * 100 + portUpdateNum * 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = AuthMessage;
|