Few Updates to SSL Cert Handling

- SSL certs are no longer stored in the host database. This was
  done not only for security reasons but there is simply no need
  to do such thing anymore.

- The host will longer support multiple SSL certs and will instead
  have just a single cert for all TCP connections. This required a
  change to the client header format that simply replaced the the
  common name with padding. The host will also no longer send the
  HOST_CERT type id during session initialization. HOST_CERT was
  also removed as a type id.

- The cert and privite key are now pointed to files in the local
  file system by the environment variables: MRCI_PRIV_KEY and
  MRCI_PUB_KEY.

- The host will still create a default self-signed cert if a valid
  cert and private key is not defined in the above environmental
  vars. Since the host only support single certs now, the default
  cert needed to be expanded to include subject alternative names.
  The host will try to detect it's WAN ip address using ipify.org
  and then assign SANs for all detected local LAN interfaces.

- Since the cert is now handled by environmental vars and nothing
  related to it stored in the database, all the core commands
  related to cert management were removed.
This commit is contained in:
Maurice ONeal 2020-04-05 15:51:11 -04:00
parent 629029ebce
commit 80d493ad16
25 changed files with 229 additions and 842 deletions

View File

@ -64,8 +64,7 @@ SOURCES += src/main.cpp \
src/commands/auth.cpp \
src/commands/acct_recovery.cpp \
src/commands/table_viewer.cpp \
src/commands/fs.cpp \
src/commands/certs.cpp
src/commands/fs.cpp
HEADERS += \
src/cmd_object.h \
@ -91,8 +90,7 @@ HEADERS += \
src/commands/auth.h \
src/commands/acct_recovery.h \
src/commands/table_viewer.h \
src/commands/fs.h \
src/commands/certs.h
src/commands/fs.h
RESOURCES += \
cmd_docs.qrc

View File

@ -2,7 +2,6 @@
<qresource prefix="/">
<file>docs/intern_commands/accept_ch.md</file>
<file>docs/intern_commands/add_acct.md</file>
<file>docs/intern_commands/add_cert.md</file>
<file>docs/intern_commands/add_ch.md</file>
<file>docs/intern_commands/add_mod.md</file>
<file>docs/intern_commands/add_ranked_cmd.md</file>
@ -10,7 +9,6 @@
<file>docs/intern_commands/add_sub_ch.md</file>
<file>docs/intern_commands/auth.md</file>
<file>docs/intern_commands/cast.md</file>
<file>docs/intern_commands/cert_info.md</file>
<file>docs/intern_commands/close_host.md</file>
<file>docs/intern_commands/close_sub_ch.md</file>
<file>docs/intern_commands/decline_ch.md</file>
@ -33,7 +31,6 @@
<file>docs/intern_commands/lock_acct.md</file>
<file>docs/intern_commands/ls_act_log.md</file>
<file>docs/intern_commands/ls_auth_log.md</file>
<file>docs/intern_commands/ls_certs.md</file>
<file>docs/intern_commands/ls_ch_members.md</file>
<file>docs/intern_commands/ls_chs.md</file>
<file>docs/intern_commands/ls_dbg.md</file>
@ -60,7 +57,6 @@
<file>docs/intern_commands/request_pw_reset.md</file>
<file>docs/intern_commands/restart_host.md</file>
<file>docs/intern_commands/rm_acct.md</file>
<file>docs/intern_commands/rm_cert.md</file>
<file>docs/intern_commands/rm_ch.md</file>
<file>docs/intern_commands/rm_mod.md</file>
<file>docs/intern_commands/rm_ranked_cmd.md</file>

View File

@ -5,28 +5,37 @@
### Usage ###
```
Usage: mrci <argument>
<Arguments>
-help : display usage information about this application.
-stop : stop the current host instance if one is currently running.
-about : display versioning/warranty information about this application.
-addr {ip_address:port} : set the listening address and port for TCP clients.
-status : display status information about the host instance if it is currently running.
-reset_root : reset the root account password to the default password.
-host : start a new host instance. (this blocks).
-default_pw : show the default password.
-public_cmds : run the internal module to list it's public commands. for internal use only.
-exempt_cmds : run the internal module to list it's rank exempt commands. for internal use only.
-user_cmds : run the internal module to list it's user commands. for internal use only.
-run_cmd {command_name} : run an internal module command. for internal use only.
-help : display usage information about this application.
-stop : stop the current host instance if one is currently running.
-about : display versioning/warranty information about this application.
-addr : set the listening address and port for TCP clients.
-status : display status information about the host instance if it is currently running.
-reset_root : reset the root account password to the default password.
-host : start a new host instance. (this blocks)
-default_pw : show the default password.
-public_cmds : run the internal module to list it's public commands. for internal use only.
-exempt_cmds : run the internal module to list it's rank exempt commands. for internal use only.
-user_cmds : run the internal module to list it's user commands. for internal use only.
-run_cmd : run an internal module command. for internal use only.
-add_cert : add/update an SSL certificate for a given common name.
-rm_cert : remove an SSL certificate for a given common name.
Internal module | -public_cmds, -user_cmds, -exempt_cmds, -run_cmd |:
-pipe {pipe_name/path} : the named pipe used to establish a data connection with the session.
-mem_ses {key_name} : the shared memory key for the session object.
-mem_host {key_name} : the shared memory key for the server object.
-pipe : the named pipe used to establish a data connection with the session.
-mem_ses : the shared memory key for the session.
-mem_host : the shared memory key for the host main process.
Details:
addr - this argument takes a {ip_address:port} string. it will return an error if not formatted correctly
examples: 10.102.9.2:35516 or 0.0.0.0:35516.
run_cmd - this argument is used by the host itself, along side the internal module arguments below to run
the internal command names passed by it. this is not ment to be run directly by human input.
the executable will auto close if it fails to connect to the pipe and/or shared memory segments
```
The host can only be managed via a connected client that supports text input/output so the host application is always listening for clients while running entirely in the background. By default the host listen for clients on address 0.0.0.0 and port 35516, effectively making it reachable on any network interface of the host platform via that specific port.

View File

@ -4,9 +4,9 @@ Other than transporting data to be processed by modules, the host have a few oth
### 4.2 Host Ranks ###
Each user registered in the host must be assigned a host rank. The lower it's numeric value, the higher the level of access the user has in the host with 0 being invalid. By default, the host defines the ```root``` user with a host rank of 1; giving it the highest level of access possible in the host. The host also defines a default initial host rank of 2 for all new accounts that are created in the host. This can be re-configured at any time using the [host_config](intern_commands.md) internal command.
Each user registered in the host must be assigned a host rank. The lower it's numeric value, the higher the level of access the user has in the host with 0 being invalid. When initialized, the host defines a ```root``` user with a host rank of 1; giving it the highest level of access possible in the host. The host also defines a default initial host rank of 2 for all new accounts that are created in the host. This can be re-configured at any time using the [host_config](intern_commands.md) internal command.
Some internal commands have the ability to change the user account information of other accounts. The host will in general not allow users of a lower level of access to any user information of higher access level. For example: a user of host rank ```1``` can force change another user's email address if that user's rank is ```2``` or higher but the rank ```2``` user can't do the same in reverse.
Some internal commands have the ability to change the user account information of other accounts. The host will in general does not allow users of a lower level of access to any user information of higher access level. For example: a user of host rank ```1``` can force change another user's email address if that user's rank is ```2``` or higher but the rank ```2``` user can't do the same in reverse.
Host ranks can also be assigned to the commands themselves via the command names of specific modules. By doing this, the host can be configured to allow users of certain ranks or higher access to running certain commands. For example: if a command named ```this_cmd``` is assigned a host rank of ```6```, all users with a host rank value of ```6``` or lower will have access to running this command. All commands that don't have an assigned rank will be assumed a rank of ```1``` but all commands that define itself as rank exempt can bypass this and allow the user to run it regardless of the user's host rank. Exemptions would also disregard the assigned rank of the command.

View File

@ -6,8 +6,6 @@ The host is extendable via 3rd party modules but the host itself is an internal
* [add_acct](intern_commands/add_acct.md) - create a new host user account.
* [add_cert](intern_commands/add_cert.md) - install a new SSL/TLS cert into the host.
* [add_ch](intern_commands/add_ch.md) - create a new channel.
* [add_mod](intern_commands/add_mod.md) - add a new module to the host.
@ -22,8 +20,6 @@ The host is extendable via 3rd party modules but the host itself is an internal
* [cast](intern_commands/cast.md) - broadcast data to all sessions listening to any matching sub-channels.
* [cert_info](intern_commands/cert_info.md) - display detailed information about an installed SSL/TLS certificate.
* [ch_owner_override](intern_commands/ch_owner_override.md) - set/unset the channel owner override flag for your current session.
* [close_host](intern_commands/close_host.md) - close the host instance.
@ -70,8 +66,6 @@ The host is extendable via 3rd party modules but the host itself is an internal
* [ls_auth_log](intern_commands/ls_auth_log.md) - display the host authorization activity log.
* [ls_certs](intern_commands/ls_certs.md) - display a list of all SSL/TLS certificates installed in the host database.
* [ls_ch_members](intern_commands/ls_ch_members.md) - list all members in a channel.
* [ls_chs](intern_commands/ls_chs.md) - list all channels you are currently a member of and all pending invites.
@ -124,8 +118,6 @@ The host is extendable via 3rd party modules but the host itself is an internal
* [rm_acct](intern_commands/rm_acct.md) - delete a user account from the host database.
* [rm_cert](intern_commands/rm_cert.md) - remove the SSL/TLS cert associated with the given common name.
* [rm_ch](intern_commands/rm_ch.md) - permanently remove a channel and all of it's sub-shannels from the host.
* [rm_mod](intern_commands/rm_mod.md) - remove a module from the host.

View File

@ -1,11 +0,0 @@
### Summary ###
install a new SSL/TLS cert into the host.
### IO ###
```[-name (text) -cert (path) -priv (path) {-force}]/[text]```
### Description ###
add an SSL/TLS certificate to a common name given in -name. the common name is used by clients to request a certain certificate installed in the host. certificates usually come in seperate cert file and private key file pairs. -cert is the file path to the cert file and -priv is the path to the private key. the files must be formatted in Pem or Der (extension doesn't matter). once installed, the file pairs no longer need to exists for the host to use them. you can the pass the optional -force option to replace a common name if it already exists without asking a confirmation question.

View File

@ -1,11 +0,0 @@
### Summary ###
display detailed information about an installed SSL/TLS certificate.
### IO ###
```[-name (text)]/[text]```
### Description ###
display more detailed information about the SSL/TLS certificate given in -name. -name is the common name clients can use to request a certain certificate installed in the host.

View File

@ -1,11 +0,0 @@
### Summary ###
display a list of all SSL/TLS certificates installed in the host database.
### IO ###
```[{search_terms}]/[text]```
### Description ###
by default, all entries in the table are displayed in 50 entries per page. you can pass the column name as -common_name (text) to refine your search to specific entries.

View File

@ -1,11 +0,0 @@
### Summary ###
remove the SSL/TLS cert associated with the given common name.
### IO ###
```[-name (text) {-force}]/[text]```
### Description ###
remove the SSL/TLS certificate and private key for the common name given in -name. you can the pass the optional -force option to remove the cert without asking a confirmation question.

View File

@ -45,12 +45,12 @@ notes:
* When the session calls the module in list mode (-public_cmds, -exempt_cmds or -user_cmds), it will only respond to frame type ids: [NEW_CMD](type_ids.md) or [ERR](type_ids.md) from the module; all other data types are ignored. Modules called in run mode (-run_cmd) on the other hand will open up all frame type ids.
* When the session detects that the module successfully established a pipe connection, it will send a [HOST_VER](type_ids.md) frame to the module so the module can decide if it supports the host. If the host is not compatible or the module fails for what ever other reason, the module can send a useful error message [ERR](type_ids.md) and then terminate if needed. The error message will be added to the host debug log where it can be used by host admins to find out what went wrong. The HOST_VER frame is sent only when the module is called with the -public_cmds, -exempt_cmds or -user_cmds options.
* When the session detects that the module successfully established a pipe connection, it will send a [HOST_VER](type_ids.md) frame to the module so the module can decide if it supports the host. If the host is not compatible or the module fails for what ever other reason, the module can send a useful error message [ERR](type_ids.md) and then terminate gracefully. The error message will be added to the host debug log where it can be used by host admins to find out what went wrong. The HOST_VER frame is sent only when the module is called with the -public_cmds, -exempt_cmds or -user_cmds options.
* When the module sends a [NEW_CMD](type_ids.md) frame, the 16bit command id is needed but does not need to be valid, it just needs to be there as a place holder. The session will auto fill a valid command id before sending the data to the client. A valid NEW_CMD frame must have a minimum of 259 bytes and a valid command name. the session will ignore all NEW_CMD frames the doesn't meet these requirements. See section [6.3](shared_data.md) for what would be considered a valid command name.
* The session will call all modules with the -public_cmds when created for the first time or when the user logout so it doesn't matter if the command names returned to the session overlap with -exempt_cmd or -user_cmds. When a user is logged in, it will then call 2 instances of each module with the -exempt_cmds and -user_cmds options so the command names should not overlap when these options are active.
* Modules called with -run_cmd does not need to terminate after running the requested command, instead it must send an [IDLE](type_ids.md) frame to indicate that the command is finished. This is desired because not only it tells the client that the command is finished but it also makes it so the session doesn't need to recreate the module process on every subsequent call to the module.
* Modules called with -run_cmd does not need to terminate after running the requested command, instead it must send an [IDLE](type_ids.md) frame to indicate that the command is finished when it eventually does finish. This is desired because not only it tells the client that the command is finished but it also makes it so the session doesn't need to recreate the module process on every subsequent call to the module.
* The session will send a [KILL_CMD](type_ids.md) to the module after 2 mins of being idle (all modes) to give the module a chance to terminate gracefully; The module will have 3 seconds to do this before it is force killed.
* The session will send a [KILL_CMD](type_ids.md) to the module after 2 mins of being idle (no IPC/Pipe activity). The module will have 3 seconds to do this before it is force killed. This will also happen when the user ends the session and the module process needs to terminate. Modules must still send an [IDLE](type_ids.md) frame to indicate the command is finished if a command was running.

View File

@ -29,7 +29,7 @@ notes:
The host uses a 4 number versioning system that indicate rev numbers for the host application itself, the tcp interface and the module interface:
```
[Major][Minor][TCP_Rev][Mod_Rev]
3 . 0 . 0 . 0
3 . 2 . 1 . 0
Major - this indicate any changes to the host application that would cause
clients to need to change behaviour to maintain compatibility.
@ -58,11 +58,11 @@ Any increments to the Major resets the Minor to 0. Any 3rd party client applicat
### 1.4 Client Header ###
```
[tag][appName][coName]
[tag][appName][padding]
tag - 4bytes - 0x4D, 0x52, 0x43, 0x49 (MRCI)
appName - 134bytes - UTF16LE string (padded with 0x00)
coName - 272bytes - UTF16LE string (padded with 0x00)
padding - 272bytes - padding of 0x00 bytes reserved for future expansion
```
notes:
@ -71,8 +71,6 @@ notes:
* The **appName** is the name of the client application that is connected to the host. It can also contain the client's app version if needed because it doesn't follow any particular standard. This string is accessable to all modules so the commands themselves can be made aware of what app the user is currently using.
* The **coName** is the common name of a SSL certificate that is currently installed in the host. Depending on how the host is configured, it can contain more than one installed SSL cert so coName can be used by clients as a way to request which one of the SSL certs to use during the SSL handshake.
### 1.5 Host Header ###
```
@ -90,10 +88,9 @@ sesId - 28bytes - 224bit sha3 hash
notes:
* **reply** is a numeric value that the host returns in it's header to communicate to the client the result of it's evaluation of the client's header.
* **reply** is a numeric value that the host returns in it's header to communicate to the client if SSL need to initated or not.
* reply = 1, means the client is acceptable and it does not need to take any further action.
* reply = 2, means the client is acceptable but the host will now send it's Pem formatted SSL cert data in a ```HOST_CERT``` mrci frame just after sending it's header. After receiving the cert, the client will then need to send a STARTTLS signal using this cert.
* reply = 4, means the host was unable to find the SSL cert associated with the common name sent by the client. The session will auto close at this point.
* reply = 1, means SSL is not required so the client doesn't need to take any further action.
* reply = 2, means SSL is required to continue so the client needs to send a STARTLS signal.
* **sesId** is the session id. It is a unique 224bit sha3 hash generated against the current date and time of session creation (down to the msec) and the machine id. This can be used by the host and client to uniquely identify the current session or past sessions.
* **sesId** is the session id. It is a unique 224bit sha3 hash generated against the current date and time of session creation (down to the msec) and the machine id of the host. This can be used by the host or client to uniquely identify the current session or past sessions.

View File

@ -10,7 +10,6 @@ enum TypeID : quint8
ERR = 3,
PRIV_TEXT = 4,
IDLE = 5,
HOST_CERT = 6,
FILE_INFO = 7,
PEER_INFO = 8,
MY_INFO = 9,
@ -115,9 +114,6 @@ This type id doesn't carry any actual data, instead can be used to tell the host
```RESUME_CMD```
This is the other half of YIELD_CMD that tells the host to resume the command that was running.
```HOST_CERT```
Just as the name implies, this data type is used by the host to send the host SSL certificate while setting up an SSL connection.
```HOST_VER```
This data structure carries 4 numeric values that represent the host version as described in section [1.3](protocol.md).

View File

@ -1,267 +0,0 @@
#include "certs.h"
// This file is part of MRCI.
// MRCI is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// MRCI is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with MRCI under the LICENSE.md file. If not, see
// <http://www.gnu.org/licenses/>.
ListCerts::ListCerts(QObject *parent) : TableViewer(parent)
{
setParams(TABLE_CERT_DATA, false);
addTableColumn(TABLE_CERT_DATA, COLUMN_COMMON_NAME);
}
CertInfo::CertInfo(QObject *parent) : CmdObject(parent) {}
AddCert::AddCert(QObject *parent) : CmdObject(parent) {}
RemoveCert::RemoveCert(QObject *parent) : CmdObject(parent) {}
QString ListCerts::cmdName() {return "ls_certs";}
QString CertInfo::cmdName() {return "cert_info";}
QString AddCert::cmdName() {return "add_cert";}
QString RemoveCert::cmdName() {return "rm_cert";}
void CertInfo::procIn(const QByteArray &binIn, quint8 dType)
{
if (dType == TEXT)
{
auto args = parseArgs(binIn, 2);
auto coName = getParam("-name", args);
retCode = INVALID_PARAMS;
if (coName.isEmpty())
{
errTxt("err: The common name argument (-name) was not found or is empty.\n");
}
else if (!validCommonName(coName))
{
errTxt("err: Invalid common name. it must be less than 200 chars long and contain no spaces.\n");
}
else if (!certExists(coName))
{
errTxt("err: A cert with common name: '" + coName + "' does not exists.\n");
}
else
{
retCode = NO_ERRORS;
QString txt;
QTextStream txtOut(&txt);
Query db(this);
db.setType(Query::PULL, TABLE_CERT_DATA);
db.addColumn(COLUMN_CERT);
db.addCondition(COLUMN_COMMON_NAME, coName);
db.exec();
auto cert = toSSLCert(db.getData(COLUMN_CERT).toByteArray());
txtOut << "Self Signed: " << boolStr(cert.isSelfSigned()) << endl;
txtOut << "Black Listed: " << boolStr(cert.isBlacklisted()) << endl;
txtOut << "Effective Date: " << cert.effectiveDate().toString("MM/dd/yy") << endl;
txtOut << "Expiry Date: " << cert.expiryDate().toString("MM/dd/yy") << endl;
mainTxt(txt);
}
}
}
void AddCert::run()
{
Query db(this);
db.setType(qType, TABLE_CERT_DATA);
db.addColumn(COLUMN_COMMON_NAME, coName);
db.addColumn(COLUMN_CERT, certBa);
db.addColumn(COLUMN_PRIV_KEY, privBa);
db.exec();
flags &= ~MORE_INPUT;
}
void AddCert::ask()
{
flags |= MORE_INPUT;
promptTxt("Common name: '" + coName + "' already exists. do you want to replace it? (y/n): ");
}
void AddCert::procIn(const QByteArray &binIn, quint8 dType)
{
if ((dType == TEXT) && (flags & MORE_INPUT))
{
auto ans = fromTEXT(binIn);
if (noCaseMatch("n", ans))
{
retCode = ABORTED;
flags &= ~MORE_INPUT;
}
else if (noCaseMatch("y", ans))
{
run();
}
else
{
ask();
}
}
else if (dType == TEXT)
{
auto args = parseArgs(binIn, 7);
auto cert = getParam("-cert", args);
auto priv = getParam("-priv", args);
auto force = argExists("-force", args);
coName = getParam("-name", args);
retCode = INVALID_PARAMS;
QFile certFile(cert, this);
QFile privFile(priv, this);
if (coName.isEmpty())
{
errTxt("err: The common name (-name) argument was not found or empty.\n");
}
else if (cert.isEmpty())
{
errTxt("err: The cert file path (-cert) argument was not found or empty.\n");
}
else if (priv.isEmpty())
{
errTxt("err: The priv key file path (-priv) argument was not found or empty.\n");
}
else if (!certFile.exists())
{
errTxt("err: The given cert file: '" + cert + "' does not exists or is not a file.\n");
}
else if (!privFile.exists())
{
errTxt("err: The given priv key file: '" + priv + "' does not exists or is not a file.\n");
}
else if (!validCommonName(coName))
{
errTxt("err: The common name must be less than or equal to 136 chars long and contain no spaces.\n");
}
else if (!certFile.open(QFile::ReadOnly))
{
errTxt("err: Unable to open the cert file: '" + cert + "' for reading. reason: " + certFile.errorString() + "\n");
}
else if (!privFile.open(QFile::ReadOnly))
{
errTxt("err: Unable to open the priv key: '" + priv + "' for reading. reason: " + privFile.errorString() + "\n");
}
else if (toSSLCert(&certFile).isNull())
{
errTxt("err: The given cert file is not compatible.\n");
}
else if (toSSLKey(&privFile).isNull())
{
errTxt("err: The given private key is not compatible.\n");
}
else
{
retCode = NO_ERRORS;
certBa = certFile.readAll();
privBa = privFile.readAll();
if (certExists(coName))
{
qType = Query::UPDATE;
if (force) run();
else ask();
}
else
{
qType = Query::PUSH;
run();
}
}
certFile.close();
privFile.close();
}
}
void RemoveCert::run()
{
Query db(this);
db.setType(Query::DEL, TABLE_CERT_DATA);
db.addCondition(COLUMN_COMMON_NAME, coName);
db.exec();
flags &= ~MORE_INPUT;
}
void RemoveCert::ask()
{
flags |= MORE_INPUT;
promptTxt("Are you sure you want to remove the cert for common name: " + coName + "? (y/n): ");
}
void RemoveCert::procIn(const QByteArray &binIn, quint8 dType)
{
if ((dType == TEXT) && (flags & MORE_INPUT))
{
QString ans = fromTEXT(binIn);
if (noCaseMatch("n", ans))
{
retCode = ABORTED;
flags &= ~MORE_INPUT;
}
else if (noCaseMatch("y", ans))
{
run();
}
else
{
ask();
}
}
else if (dType == TEXT)
{
auto args = parseArgs(binIn, -1);
auto name = getParam("-name", args);
auto force = argExists("-force", args);
retCode = INVALID_PARAMS;
if (name.isEmpty())
{
errTxt("err: Common name (-name) argument not found or is empty.\n");
}
else if (!validCommonName(name))
{
errTxt("err: Invalid common name.\n");
}
else if (!certExists(name))
{
errTxt("err: The given common name '" + name + "' does not exists.\n");
}
else
{
retCode = NO_ERRORS;
coName = name;
if (force) run();
else ask();
}
}
}

View File

@ -1,98 +0,0 @@
#ifndef CERTS_H
#define CERTS_H
// This file is part of MRCI.
// MRCI is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// MRCI is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with MRCI under the LICENSE.md file. If not, see
// <http://www.gnu.org/licenses/>.
#include "../common.h"
#include "../cmd_object.h"
#include "../make_cert.h"
#include "table_viewer.h"
class ListCerts : public TableViewer
{
Q_OBJECT
public:
static QString cmdName();
explicit ListCerts(QObject *parent = nullptr);
};
//--------------------------
class CertInfo : public CmdObject
{
Q_OBJECT
public:
static QString cmdName();
void procIn(const QByteArray &binIn, quint8 dType);
explicit CertInfo(QObject *parent = nullptr);
};
//----------------------------
class AddCert : public CmdObject
{
Q_OBJECT
private:
QString coName;
QByteArray certBa;
QByteArray privBa;
Query::QueryType qType;
void run();
void ask();
public:
static QString cmdName();
void procIn(const QByteArray &binIn, quint8 dType);
explicit AddCert(QObject *parent = nullptr);
};
//-----------------------------
class RemoveCert : public CmdObject
{
Q_OBJECT
private:
QString coName;
void run();
void ask();
public:
static QString cmdName();
void procIn(const QByteArray &binIn, quint8 dType);
explicit RemoveCert(QObject *parent = nullptr);
};
#endif // CERTS_H

View File

@ -27,8 +27,7 @@ QString columnType(const QString &column)
if ((column == COLUMN_IPADDR) || (column == COLUMN_LOGENTRY) || (column == COLUMN_USERNAME) ||
(column == COLUMN_CHANNEL_NAME) || (column == COLUMN_EMAIL) || (column == COLUMN_SUB_CH_NAME) ||
(column == COLUMN_COMMAND) || (column == COLUMN_CLIENT_VER) || (column == COLUMN_COMMON_NAME) ||
(column == COLUMN_DISPLAY_NAME))
(column == COLUMN_COMMAND) || (column == COLUMN_CLIENT_VER) || (column == COLUMN_DISPLAY_NAME))
{
ret = "TEXT COLLATE NOCASE";
}
@ -58,8 +57,8 @@ QString columnType(const QString &column)
{
ret = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL";
}
else if ((column == COLUMN_HASH) || (column == COLUMN_SALT) || (column == COLUMN_CERT) ||
(column == COLUMN_PRIV_KEY) || (column == COLUMN_USER_ID) || (column == COLUMN_SESSION_ID))
else if ((column == COLUMN_HASH) || (column == COLUMN_SALT) || (column == COLUMN_USER_ID) ||
(column == COLUMN_SESSION_ID))
{
ret = "BLOB";
}

View File

@ -37,7 +37,7 @@
#include "shell.h"
#define APP_NAME "MRCI"
#define APP_VER "3.1.0.0"
#define APP_VER "3.2.1.0"
#define APP_TARGET "mrci"
#ifdef Q_OS_WIN
@ -58,7 +58,8 @@
#define ENV_DB_PATH "MRCI_DB_PATH"
#define ENV_WORK_DIR "MRCI_WORK_DIR"
#define ROOT_USER "root"
#define ENV_PRIV_KEY "MRCI_PRIV_KEY"
#define ENV_PUB_KEY "MRCI_PUB_KEY"
#define SUBJECT_SUB "%subject%"
#define MSG_SUB "%message_body%"
#define TARGET_EMAIL_SUB "%target_email%"
@ -66,7 +67,6 @@
#define TEMP_PW_SUB "%temp_pw%"
#define USERNAME_SUB "%user_name%"
#define DATE_SUB "%date%"
#define INTERN_MOD_NAME ":internal_mod"
#define DEFAULT_ROOT_USER "root"
#define DEFAULT_CONFIRM_SUBJECT "Email Verification"
#define DEFAULT_TEMP_PW_SUBJECT "Password Reset"
@ -99,7 +99,6 @@ Date requested: %date%."
#define TABLE_CMD_RANKS "command_ranks"
#define TABLE_AUTH_LOG "auth_log"
#define TABLE_PW_RECOVERY "pw_recovery"
#define TABLE_CERT_DATA "ssl_certs"
#define TABLE_DMESG "host_debug_messages"
#define TABLE_CHANNELS "channels"
#define TABLE_CH_MEMBERS "channel_members"
@ -132,8 +131,6 @@ Date requested: %date%."
#define COLUMN_ZIPBIN "archiver_executable"
#define COLUMN_ZIPEXTRACT "extract_command"
#define COLUMN_ZIPCOMPRESS "compress_command"
#define COLUMN_CERT "ssl_cert"
#define COLUMN_PRIV_KEY "private_key"
#define COLUMN_AUTH_ATTEMPT "auth_attempt"
#define COLUMN_RECOVER_ATTEMPT "recover_attempt"
#define COLUMN_COUNT "count_to_threshold"
@ -146,7 +143,6 @@ Date requested: %date%."
#define COLUMN_TEMP_PW_SUBJECT "temp_pw_email_subject"
#define COLUMN_ENABLE_PW_RESET "enable_pw_reset"
#define COLUMN_ENABLE_CONFIRM "eable_email_verify"
#define COLUMN_COMMON_NAME "common_name"
#define COLUMN_DISPLAY_NAME "display_name"
#define COLUMN_USER_ID "user_id"
#define COLUMN_CHANNEL_NAME "channel_name"

View File

@ -66,18 +66,6 @@ bool setupDb(QString *errMsg)
ret = query.exec();
}
if (ret)
{
query.setType(Query::CREATE_TABLE, TABLE_CERT_DATA);
query.addColumn(COLUMN_COMMON_NAME);
query.addColumn(COLUMN_CERT);
query.addColumn(COLUMN_PRIV_KEY);
query.setPrimary(COLUMN_COMMON_NAME);
query.addUnique(COLUMN_COMMON_NAME);
ret = query.exec();
}
if (ret)
{
query.setType(Query::CREATE_TABLE, TABLE_CHANNELS);

View File

@ -47,22 +47,30 @@ void showHelp()
txtOut << "" << endl << APP_NAME << " v" << QCoreApplication::applicationVersion() << endl << endl;
txtOut << "Usage: " << APP_TARGET << " <argument>" << endl << endl;
txtOut << "<Arguments>" << endl << endl;
txtOut << " -help : display usage information about this application." << endl;
txtOut << " -stop : stop the current host instance if one is currently running." << endl;
txtOut << " -about : display versioning/warranty information about this application." << endl;
txtOut << " -addr {ip_address:port} : set the listening address and port for TCP clients." << endl;
txtOut << " -status : display status information about the host instance if it is currently running." << endl;
txtOut << " -reset_root : reset the root account password to the default password." << endl;
txtOut << " -host : start a new host instance. (this blocks)" << endl;
txtOut << " -default_pw : show the default password." << endl;
txtOut << " -public_cmds : run the internal module to list it's public commands. for internal use only." << endl;
txtOut << " -exempt_cmds : run the internal module to list it's rank exempt commands. for internal use only." << endl;
txtOut << " -user_cmds : run the internal module to list it's user commands. for internal use only." << endl;
txtOut << " -run_cmd {command_name} : run an internal module command. for internal use only." << endl << endl;
txtOut << " -help : display usage information about this application." << endl;
txtOut << " -stop : stop the current host instance if one is currently running." << endl;
txtOut << " -about : display versioning/warranty information about this application." << endl;
txtOut << " -addr : set the listening address and port for TCP clients." << endl;
txtOut << " -status : display status information about the host instance if it is currently running." << endl;
txtOut << " -reset_root : reset the root account password to the default password." << endl;
txtOut << " -host : start a new host instance. (this blocks)" << endl;
txtOut << " -default_pw : show the default password." << endl;
txtOut << " -public_cmds : run the internal module to list it's public commands. for internal use only." << endl;
txtOut << " -exempt_cmds : run the internal module to list it's rank exempt commands. for internal use only." << endl;
txtOut << " -user_cmds : run the internal module to list it's user commands. for internal use only." << endl;
txtOut << " -run_cmd : run an internal module command. for internal use only." << endl;
txtOut << " -add_cert : add/update an SSL certificate for a given common name." << endl;
txtOut << " -rm_cert : remove an SSL certificate for a given common name." << endl << endl;
txtOut << "Internal module | -public_cmds, -user_cmds, -exempt_cmds, -run_cmd |:" << endl << endl;
txtOut << " -pipe {pipe_name/path} : the named pipe used to establish a data connection with the session." << endl;
txtOut << " -mem_ses {key_name} : the shared memory key for the session." << endl;
txtOut << " -mem_host {key_name} : the shared memory key for the host main process." << endl << endl;
txtOut << " -pipe : the named pipe used to establish a data connection with the session." << endl;
txtOut << " -mem_ses : the shared memory key for the session." << endl;
txtOut << " -mem_host : the shared memory key for the host main process." << endl << endl;
txtOut << "Details:" << endl << endl;
txtOut << "addr - this argument takes a {ip_address:port} string. it will return an error if not formatted correctly" << endl;
txtOut << " examples: 10.102.9.2:35516 or 0.0.0.0:35516." << endl << endl;
txtOut << "run_cmd - this argument is used by the host itself, along side the internal module arguments below to run" << endl;
txtOut << " the internal command names passed by it. this is not ment to be run directly by human input." << endl;
txtOut << " the executable will auto close if it fails to connect to the pipe and/or shared memory segments" << endl << endl;
}
void soeDueToDbErr(int *retCode, const QString *errMsg)
@ -110,9 +118,9 @@ int main(int argc, char *argv[])
qInstallMessageHandler(msgHandler);
//args.append("-host"); // debug
//args.append("-add_cert -name test"); // debug
if (args.contains("-help", Qt::CaseInsensitive))
if (args.contains("-help", Qt::CaseInsensitive) || args.size() == 1)
{
showHelp();
}
@ -122,68 +130,58 @@ int main(int argc, char *argv[])
QTextStream(stdout) << "Based on QT " << QT_VERSION_STR << " " << 8 * QT_POINTER_SIZE << "bit" << endl << endl;
QTextStream(stdout) << "The program is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE" << endl;
QTextStream(stdout) << "WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE." << endl << endl;
}
else if (args.contains("-default_pw", Qt::CaseInsensitive))
else if (args.contains("-stop", Qt::CaseInsensitive) || args.contains("-status", Qt::CaseInsensitive))
{
QTextStream(stdout) << "" << endl << " Root User : " << getUserName(rootUserId()) << endl;
QTextStream(stdout) << " Default Password: " << defaultPw() << endl << endl;
ret = shellToHost(args, app);
}
else if (args.contains("-addr", Qt::CaseInsensitive))
else if (setupDb(&err))
{
auto params = getParam("-addr", args);
auto addr = params.split(':');
ret = 128;
if (!setupDb(&err))
if (args.contains("-addr", Qt::CaseInsensitive))
{
soeDueToDbErr(&ret, &err);
}
else if (addr.size() != 2)
{
QTextStream(stderr) << "" << endl << "err: Address string parsing error, number of params found: " << addr.size() << endl;
}
else
{
bool pOk;
ushort port = addr[1].toUShort(&pOk);
auto params = getParam("-addr", args);
auto addr = params.split(':');
if (!pOk)
ret = 128;
if (addr.size() != 2)
{
QTextStream(stderr) << "" << endl << "err: Invalid port." << endl;
}
else if (port == 0)
{
QTextStream(stderr) << "" << endl << "err: The port cannot be 0." << endl;
}
else if (QHostAddress(addr[0]).isNull())
{
QTextStream(stderr) << "" << endl << "err: Invalid ip address." << endl;
QTextStream(stderr) << "" << endl << "err: Address string parsing error, number of params found: " << addr.size() << endl;
}
else
{
ret = 0;
bool pOk;
auto port = addr[1].toUShort(&pOk);
Query db(&app);
if (!pOk)
{
QTextStream(stderr) << "" << endl << "err: Invalid port." << endl;
}
else if (port == 0)
{
QTextStream(stderr) << "" << endl << "err: The port cannot be 0." << endl;
}
else if (QHostAddress(addr[0]).isNull())
{
QTextStream(stderr) << "" << endl << "err: Invalid ip address." << endl;
}
else
{
ret = 0;
db.setType(Query::UPDATE, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_IPADDR, addr[0]);
db.addColumn(COLUMN_PORT, port);
db.exec();
Query db(&app);
db.setType(Query::UPDATE, TABLE_SERV_SETTINGS);
db.addColumn(COLUMN_IPADDR, addr[0]);
db.addColumn(COLUMN_PORT, port);
db.exec();
}
}
}
}
else if (args.contains("-run_cmd", Qt::CaseInsensitive) ||
args.contains("-public_cmds", Qt::CaseInsensitive) ||
args.contains("-exempt_cmds", Qt::CaseInsensitive) ||
args.contains("-user_cmds", Qt::CaseInsensitive))
{
if (!setupDb(&err))
{
soeDueToDbErr(&ret, &err);
}
else
else if (args.contains("-run_cmd", Qt::CaseInsensitive) ||
args.contains("-public_cmds", Qt::CaseInsensitive) ||
args.contains("-exempt_cmds", Qt::CaseInsensitive) ||
args.contains("-user_cmds", Qt::CaseInsensitive))
{
auto *mod = new Module(&app);
@ -192,14 +190,7 @@ int main(int argc, char *argv[])
ret = QCoreApplication::exec();
}
}
}
else if (args.contains("-host", Qt::CaseInsensitive))
{
if (!setupDb(&err))
{
soeDueToDbErr(&ret, &err);
}
else
else if (args.contains("-host", Qt::CaseInsensitive))
{
auto *serv = new TCPServer(&app);
@ -208,18 +199,7 @@ int main(int argc, char *argv[])
ret = QCoreApplication::exec();
}
}
}
else if (args.contains("-stop", Qt::CaseInsensitive) || args.contains("-status", Qt::CaseInsensitive))
{
ret = shellToHost(args, app);
}
else if (args.contains("-reset_root", Qt::CaseInsensitive))
{
if (!setupDb(&err))
{
soeDueToDbErr(&ret, &err);
}
else
else if (args.contains("-reset_root", Qt::CaseInsensitive))
{
auto uId = rootUserId();
@ -232,10 +212,15 @@ int main(int argc, char *argv[])
updatePassword(uId, defaultPw(), TABLE_USERS, true);
}
else if (args.contains("-default_pw", Qt::CaseInsensitive))
{
QTextStream(stdout) << "" << endl << " Root User : " << getUserName(rootUserId()) << endl;
QTextStream(stdout) << " Default Password: " << defaultPw() << endl << endl;
}
}
else
{
showHelp();
soeDueToDbErr(&ret, &err);
}
cleanupDbConnection();

View File

@ -31,19 +31,6 @@ void Cert::cleanup()
BN_free(bne);
}
QByteArray tempFilePath(const QString &baseName)
{
return QDir::tempPath().toUtf8() + "/" + baseName.toUtf8() + "_" + QDateTime::currentDateTime().toString("YYYYMMddHHmmsszzz").toUtf8();
}
long genSerialViaDateTime()
{
QDateTime dateTime = QDateTime::currentDateTime();
QString serialStr = dateTime.toString("YYYYMMddHH");
return serialStr.toLong();
}
bool genRSAKey(Cert *cert)
{
bool ret = false;
@ -65,13 +52,29 @@ bool genRSAKey(Cert *cert)
return ret;
}
bool genX509(Cert *cert, const QString &coName)
bool genX509(Cert *cert, const QString &outsideAddr)
{
bool ret = false;
auto ret = false;
auto interfaces = QNetworkInterface::allAddresses();
if (cert->x509 && cert->pKey)
QList<QByteArray> cnNames;
if (!outsideAddr.isEmpty())
{
ASN1_INTEGER_set(X509_get_serialNumber(cert->x509), genSerialViaDateTime());
cnNames.append(outsideAddr.toUtf8());
}
for (auto&& addr : interfaces)
{
if (addr.isGlobal())
{
cnNames.append(addr.toString().toUtf8());
}
}
if (cert->x509 && cert->pKey && !cnNames.isEmpty())
{
ASN1_INTEGER_set(X509_get_serialNumber(cert->x509), QDateTime::currentDateTime().toSecsSinceEpoch());
X509_gmtime_adj(X509_get_notBefore(cert->x509), 0); // now
X509_gmtime_adj(X509_get_notAfter(cert->x509), 31536000L); // 365 days
@ -79,15 +82,31 @@ bool genX509(Cert *cert, const QString &coName)
// copy the subject name to the issuer name.
X509_NAME *name = X509_get_subject_name(cert->x509);
QByteArray orgName = QCoreApplication::organizationName().toUtf8();
auto *name = X509_get_subject_name(cert->x509);
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *) "USA", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *) orgName.data(), -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *) coName.toUtf8().data(), -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *) cnNames[0].data(), -1, -1, 0);
X509_set_issuer_name(cert->x509, name);
cnNames.removeAt(0);
QByteArray sanField;
for (int i = 0; i < cnNames.size(); ++i)
{
sanField.append("DNS:" + cnNames[i]);
if (i != cnNames.size() - 1)
{
sanField.append(", ");
}
}
if (!sanField.isEmpty())
{
addExt(cert->x509, NID_subject_alt_name, sanField.data());
}
if (X509_sign(cert->x509, cert->pKey, EVP_sha1()))
{
ret = true;
@ -97,10 +116,21 @@ bool genX509(Cert *cert, const QString &coName)
return ret;
}
void addExt(X509 *cert, int nid, char *value)
{
X509_EXTENSION *ext = X509V3_EXT_conf_nid(NULL, NULL, nid, value);
if (ext != NULL)
{
X509_add_ext(cert, ext, -1);
X509_EXTENSION_free(ext);
}
}
bool writePrivateKey(const char *path, Cert* cert)
{
bool ret = false;
FILE *file = fopen(path, "wb");
auto ret = false;
auto *file = fopen(path, "wb");
if (file)
{
@ -114,8 +144,8 @@ bool writePrivateKey(const char *path, Cert* cert)
bool writeX509(const char *path, Cert *cert)
{
bool ret = false;
FILE *file = fopen(path, "wb");
auto ret = false;
auto *file = fopen(path, "wb");
if (file)
{
@ -127,181 +157,15 @@ bool writeX509(const char *path, Cert *cert)
return ret;
}
bool getCertAndKey(const QString &coName, QByteArray &cert, QByteArray &privKey)
void genDefaultSSLFiles(const QString &outsideAddr)
{
bool ret = true;
auto *cert = new Cert();
Query db;
genRSAKey(cert);
genX509(cert, outsideAddr);
writePrivateKey(DEFAULT_PRIV_KEY_NAME, cert);
writeX509(DEFAULT_PUB_KEY_NAME, cert);
db.setType(Query::PULL, TABLE_CERT_DATA);
db.addColumn(COLUMN_CERT);
db.addColumn(COLUMN_PRIV_KEY);
db.addCondition(COLUMN_COMMON_NAME, coName);
db.exec();
if (db.rows())
{
cert = db.getData(COLUMN_CERT).toByteArray();
privKey = db.getData(COLUMN_PRIV_KEY).toByteArray();
QSslCertificate certObj(cert, QSsl::Pem);
if (certObj.isNull())
{
genNewSSLData(coName, cert, privKey, true);
qDebug() << "The cert for CN: " << coName << " was invalid, generated a new self signed cert.";
}
else if (certObj.expiryDate().isValid() && (certObj.expiryDate() < QDateTime::currentDateTime()))
{
genNewSSLData(coName, cert, privKey, true);
qDebug() << "The cert for CN: " << coName << " was expired, generated a new self signed cert.";
}
}
else if (islocalIP(coName) && !coName.isEmpty())
{
genNewSSLData(coName, cert, privKey, false);
qDebug() << "Generated a new self signed cert for CN: " << coName;
}
else
{
ret = false;
}
return ret;
}
void genNewSSLData(const QString &coName, QByteArray &cert, QByteArray &privKey, bool exists)
{
auto *newCert = new Cert();
QByteArray certPath = tempFilePath(QString(APP_NAME) + "Cert");
QByteArray privKeyPath = tempFilePath(QString(APP_NAME) + "PrivKey");
if (genRSAKey(newCert))
{
if (genX509(newCert, coName))
{
if (writePrivateKey(privKeyPath.data(), newCert) &&
writeX509(certPath.data(), newCert))
{
QFile certFile(certPath);
QFile privFile(privKeyPath);
if (certFile.open(QFile::ReadOnly) && privFile.open(QFile::ReadOnly))
{
Query db;
cert = certFile.readAll();
privKey = privFile.readAll();
if (exists) db.setType(Query::UPDATE, TABLE_CERT_DATA);
else db.setType(Query::PUSH, TABLE_CERT_DATA);
db.addColumn(COLUMN_COMMON_NAME, coName);
db.addColumn(COLUMN_CERT, cert);
db.addColumn(COLUMN_PRIV_KEY, privKey);
db.exec();
}
certFile.close();
privFile.close();
certFile.remove();
privFile.remove();
}
}
}
newCert->cleanup();
newCert->deleteLater();
}
void genNewSSLData(const QString &coName)
{
QByteArray cert;
QByteArray priv;
genNewSSLData(coName, cert, priv, false);
}
bool islocalIP(const QString &coName)
{
bool ret = false;
QList<QHostAddress> interfaces = QNetworkInterface::allAddresses();
for (auto&& addr : interfaces)
{
if ((addr.toString() == coName) && addr.isGlobal())
{
qDebug() << "Detected local address: " << addr.toString();
ret = true;
}
}
return ret;
}
bool certExists(const QString &coName)
{
Query db;
db.setType(Query::PULL, TABLE_CERT_DATA);
db.addColumn(COLUMN_COMMON_NAME);
db.addCondition(COLUMN_COMMON_NAME, coName);
db.exec();
return db.rows();
}
QSslKey toSSLKey(const QByteArray &data)
{
QSslKey ret(data, QSsl::Rsa, QSsl::Pem);
if (ret.isNull()) ret = QSslKey(data, QSsl::Dsa, QSsl::Pem);
if (ret.isNull()) ret = QSslKey(data, QSsl::Ec, QSsl::Pem);
if (ret.isNull()) ret = QSslKey(data, QSsl::Opaque, QSsl::Pem);
if (ret.isNull()) ret = QSslKey(data, QSsl::Rsa, QSsl::Der);
if (ret.isNull()) ret = QSslKey(data, QSsl::Dsa, QSsl::Der);
if (ret.isNull()) ret = QSslKey(data, QSsl::Ec, QSsl::Der);
if (ret.isNull()) ret = QSslKey(data, QSsl::Opaque, QSsl::Der);
return ret;
}
QSslKey toSSLKey(QIODevice *dev)
{
QSslKey ret(dev, QSsl::Rsa, QSsl::Pem);
if (ret.isNull()) ret = QSslKey(dev, QSsl::Dsa, QSsl::Pem);
if (ret.isNull()) ret = QSslKey(dev, QSsl::Ec, QSsl::Pem);
if (ret.isNull()) ret = QSslKey(dev, QSsl::Opaque, QSsl::Pem);
if (ret.isNull()) ret = QSslKey(dev, QSsl::Rsa, QSsl::Der);
if (ret.isNull()) ret = QSslKey(dev, QSsl::Dsa, QSsl::Der);
if (ret.isNull()) ret = QSslKey(dev, QSsl::Ec, QSsl::Der);
if (ret.isNull()) ret = QSslKey(dev, QSsl::Opaque, QSsl::Der);
return ret;
}
QSslCertificate toSSLCert(const QByteArray &data)
{
QSslCertificate ret(data, QSsl::Pem);
if (ret.isNull()) ret = QSslCertificate(data, QSsl::Der);
return ret;
}
QSslCertificate toSSLCert(QIODevice *dev)
{
QSslCertificate ret(dev, QSsl::Pem);
if (ret.isNull()) ret = QSslCertificate(dev, QSsl::Der);
return ret;
cert->cleanup();
cert->deleteLater();
}

View File

@ -20,6 +20,7 @@
#include <cstdio>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <QDateTime>
#include <QCoreApplication>
@ -33,6 +34,9 @@
#include "db.h"
#define DEFAULT_PUB_KEY_NAME "cert.pem"
#define DEFAULT_PRIV_KEY_NAME "priv.pem"
class Cert : public QObject
{
Q_OBJECT
@ -49,20 +53,11 @@ public:
explicit Cert(QObject *parent = nullptr);
};
QByteArray tempFilePath(const QString &baseName);
QSslKey toSSLKey(const QByteArray &data);
QSslKey toSSLKey(QIODevice *dev);
QSslCertificate toSSLCert(const QByteArray &data);
QSslCertificate toSSLCert(QIODevice *dev);
long genSerialViaDateTime();
bool genRSAKey(Cert *cert);
bool genX509(Cert *cert, const QString &coName);
bool writePrivateKey(const char *path, Cert *cert);
bool writeX509(const char *path, Cert *cert);
bool islocalIP(const QString &coName);
bool certExists(const QString &coName);
bool getCertAndKey(const QString &coName, QByteArray &cert, QByteArray &privKey);
void genNewSSLData(const QString &coName, QByteArray &cert, QByteArray &privKey, bool exists);
void genNewSSLData(const QString &coName);
bool genRSAKey(Cert *cert);
bool genX509(Cert *cert, const QString &outsideAddr);
bool writePrivateKey(const char *path, Cert *cert);
bool writeX509(const char *path, Cert *cert);
void genDefaultSSLFiles(const QString &outsideAddr);
void addExt(X509 *cert, int nid, char *value);
#endif // MAKE_CERT_H

View File

@ -82,10 +82,6 @@ QStringList Module::userCmdList()
ret << MakePath::cmdName();
ret << ChangeDir::cmdName();
ret << ListDBG::cmdName();
ret << ListCerts::cmdName();
ret << CertInfo::cmdName();
ret << AddCert::cmdName();
ret << RemoveCert::cmdName();
ret << ToPeer::cmdName();
ret << LsP2P::cmdName();
ret << P2POpen::cmdName();
@ -211,10 +207,6 @@ bool Module::runCmd(const QString &name)
else if (noCaseMatch(name, MakePath::cmdName())) new MakePath(this);
else if (noCaseMatch(name, ChangeDir::cmdName())) new ChangeDir(this);
else if (noCaseMatch(name, ListDBG::cmdName())) new ListDBG(this);
else if (noCaseMatch(name, ListCerts::cmdName())) new ListCerts(this);
else if (noCaseMatch(name, CertInfo::cmdName())) new CertInfo(this);
else if (noCaseMatch(name, AddCert::cmdName())) new AddCert(this);
else if (noCaseMatch(name, RemoveCert::cmdName())) new RemoveCert(this);
else if (noCaseMatch(name, ToPeer::cmdName())) new ToPeer(this);
else if (noCaseMatch(name, LsP2P::cmdName())) new LsP2P(this);
else if (noCaseMatch(name, P2POpen::cmdName())) new P2POpen(this);

View File

@ -28,7 +28,6 @@
#include "commands/cmd_ranks.h"
#include "commands/acct_recovery.h"
#include "commands/fs.h"
#include "commands/certs.h"
#include "commands/p2p.h"
#include "commands/channels.h"

View File

@ -336,8 +336,7 @@ void Session::dataFromClient()
{
wrStringToBlock(fromTEXT(tcpSocket->read(BLKSIZE_APP_NAME)).trimmed(), appName, BLKSIZE_APP_NAME);
auto coName = fromTEXT(tcpSocket->read(272)).trimmed();
auto ver = QCoreApplication::applicationVersion().split('.');
auto ver = QCoreApplication::applicationVersion().split('.');
QByteArray servHeader;
@ -352,15 +351,15 @@ void Session::dataFromClient()
if (tcpSocket->peerAddress().isLoopback())
{
// SSL encryption is optional for locally connected clients
// so sesOk() can be called right away instead of starting
// an SSL handshake.
servHeader[0] = 1;
// reply value 1 means the client needs to take no further
// action, just await a message from the ASYNC_RDY async
// command id.
servHeader[0] = 1;
// SSL encryption is optional for locally connected clients
// so sesOk() can be called right away instead of starting
// an SSL handshake.
tcpSocket->write(servHeader);
@ -368,42 +367,20 @@ void Session::dataFromClient()
}
else
{
QByteArray certBa;
QByteArray privBa;
servHeader[0] = 2;
if (getCertAndKey(coName, certBa, privBa))
{
servHeader[0] = 2;
// reply value 2 means the host will now send a STARTTLS
// signal to begin the SSL handshake. the client will
// likely have to do the same. a ASYNC_RDY async will not
// get sent until the handshake is successful.
// reply value 2 means the client version is acceptable
// but the host will now send it's Pem formatted SSL cert
// data in a HOST_CERT mrci frame just after sending it's
// header.
auto pubKey = expandEnvVariables(qEnvironmentVariable(ENV_PUB_KEY, DEFAULT_PUB_KEY_NAME));
auto privKey = expandEnvVariables(qEnvironmentVariable(ENV_PRIV_KEY, DEFAULT_PRIV_KEY_NAME));
// the client must use this cert and send a STARTTLS
// signal when ready.
tcpSocket->setLocalCertificate(toSSLCert(certBa));
tcpSocket->setPrivateKey(toSSLKey(privBa));
tcpSocket->write(servHeader);
dataToClient(ASYNC_SYS_MSG, certBa, HOST_CERT);
tcpSocket->startServerEncryption();
}
else
{
servHeader[0] = 4;
// reply value 4 means the host was unable to load the
// SSL cert associated with the common name sent by the
// client. the session will lock out and auto close at
// this point.
tcpSocket->write(servHeader);
endSession();
}
tcpSocket->setLocalCertificate(pubKey);
tcpSocket->setPrivateKey(privKey);
tcpSocket->write(servHeader);
tcpSocket->startServerEncryption();
}
}
else

View File

@ -20,6 +20,7 @@ TCPServer::TCPServer(QObject *parent) : QTcpServer(parent)
{
controlPipe = new QLocalServer(this);
hostSharedMem = new QSharedMemory(this);
qNam = new QNetworkAccessManager(this);
hostKey = createHostSharedMem(hostSharedMem);
hostLoad = static_cast<char*>(hostSharedMem->data());
controlSocket = nullptr;
@ -36,6 +37,7 @@ TCPServer::TCPServer(QObject *parent) : QTcpServer(parent)
#endif
connect(controlPipe, &QLocalServer::newConnection, this, &TCPServer::newPipeConnection);
connect(qNam, &QNetworkAccessManager::finished, this, &TCPServer::replyFromIpify);
}
void TCPServer::newPipeConnection()
@ -79,6 +81,13 @@ bool TCPServer::createPipe()
return ret;
}
void TCPServer::replyFromIpify(QNetworkReply *reply)
{
genDefaultSSLFiles(reply->readAll());
reply->deleteLater();
}
bool TCPServer::start()
{
close();
@ -92,6 +101,8 @@ bool TCPServer::start()
db.addColumn(COLUMN_MAXSESSIONS);
db.exec();
qNam->get(QNetworkRequest(QUrl("https://api.ipify.org")));
maxSessions = db.getData(COLUMN_MAXSESSIONS).toUInt();
auto ret = false;

View File

@ -30,14 +30,15 @@ class TCPServer: public QTcpServer
private:
QSharedMemory *hostSharedMem;
QLocalServer *controlPipe;
QLocalSocket *controlSocket;
char *hostLoad;
QString controlPipePath;
QString hostKey;
quint32 maxSessions;
quint32 flags;
QNetworkAccessManager *qNam;
QSharedMemory *hostSharedMem;
QLocalServer *controlPipe;
QLocalSocket *controlSocket;
char *hostLoad;
QString controlPipePath;
QString hostKey;
quint32 maxSessions;
quint32 flags;
bool servOverloaded();
bool createPipe();
@ -50,6 +51,7 @@ private slots:
void closedPipeConnection();
void sessionEnded();
void setMaxSessions(quint32 value);
void replyFromIpify(QNetworkReply *reply);
public slots: