This README gives an introduction to the C++ and Python code examples in this folder on how to login/logout to/from a Visionary camera with a certain user level. Further this document describes how change the password for a certain user level.
Note
|
Remember to adjust the command line arguments like IP address ( |
C++
Either build and run the samples from the top level directory as described in Getting Started or build and run the samples from the sample subdirectory using its CmakeLists.txt file.
Userlevels
cd build/
./login_logout -i192.168.1.10 -tVisionary-S
Change password
cd build/
./password_change -i192.168.1.10 -tVisionary-S
Python
Note
|
Make sure you followed the prerequisite steps in Getting Started |
To run the Python samples, execute the following command from the top-level directory:
Userlevels
python3 -m userlevels_and_passwords.python.login_logout -i192.168.1.10 -tVisionary-S
Change password
python3 -m userlevels_and_passwords.python.password_change -i192.168.1.10 -tVisionary-S
Whether a variable can be written or a method can be executed by a user depends on the required user level. The required user level can be found in the variable overview or the method overview under Write-Access and Invocation Access respectively. For this it is required to lookup the variable or method definition in the respective telegram listing. The table below shows all available user levels.
The samples login_logout.py
and login_logout.cpp
demonstrate how to login and logout to/from the device with a user level. To showcase how the user level affects the access to the device’s variables, the SysTemperatureWarningMargin
variable is temporarily changed. In addition, the code demonstrates how write access errors can be detected by accessing the variable without suitable access rights.
Variable definition: SysTemperatureWarningMargin
Looking at the variable definition we note:
-
The Read-Access is Always
-
The Write-Access is Service
-
The data is
Int
-
Physical Unit is °C
The first step is to create a camera control object specifing the right VisionaryType
.
The VisionaryControl
class provides an interface for controlling a Visionary Camera, including managing the connection, logging in and out, and controlling data acquisition.
C++
if (!visionaryControl.open(ipAddress))
{
std::fprintf(stderr, "Failed to open control connection to device.\n");
return ExitCode::eCommunicationError;
}
std::fprintf(stdout, "Opened connection to the Visionary device %s\n", ipAddress.c_str());
Python
deviceControl = Control(ip_address, cola_protocol, control_port)
deviceControl.open()
In the next step we read the variable SysTemperatureWarningMargin
and store its original value. A login is not required since the Read-Access is Always.
C++
CoLaCommand getWarnMargin =
CoLaParameterWriter(CoLaCommandType::READ_VARIABLE, "SysTemperatureWarningMargin").build();
CoLaCommand getWarnMarginResponse = visionaryControl.sendCommand(getWarnMargin);
Python
sysTemperatureWarningMarginResponse = deviceControl.readVariable(
b'SysTemperatureWarningMargin')
sysTemperatureWarningMargin = struct.unpack(
'>h', sysTemperatureWarningMarginResponse)[0]
print(
f"Current SysTemperatureWarningMargin: {sysTemperatureWarningMargin} degree Celsius")
After reading the variable we try to set a new value without login. The code demonstrates what would happen if there is an attempt to write a variable which needs SERVICE access without any login. It is expected, that a CoLaError
is reported, namely VARIABLE_WRITE_ACCESS_DENIED
.
C++
CoLaCommand setWarnMargin = CoLaParameterWriter(CoLaCommandType::WRITE_VARIABLE, "SysTemperatureWarningMargin")
.parameterInt(originalWarnMargin - 1)
.build();
CoLaCommand setWarnMarginResponse = visionaryControl.sendCommand(setWarnMargin);
Python
deviceControl.writeVariable(b'SysTemperatureWarningMargin', struct.pack(
'>h', sysTemperatureWarningMargin))
print("Successfully written new value to variable SysTemperatureWarningMargin")
print(
f"Current SysTemperatureWarningMargin: {sysTemperatureWarningMargin} degree Celsius")
Since writing the variable requires the Userlevel Service
, we invoke the method login
of the control object and specify the two arguments, userlevel and password:
Userlevel | Password |
---|---|
Maintenance |
MAIN |
Authorized Client |
CLIENT |
Service |
CUST_SERV |
C++
const std::string defaultSecret = "CUST_SERV";
if (!visionaryControl.login(IAuthentication::UserLevel::SERVICE, defaultSecret))
{
std::fprintf(stderr, "Failed to login - maybe the default password for SERVICE was changed\n");
return ExitCode::eAuthenticationError;
}
Python
deviceControl.login(Control.USERLEVEL_SERVICE, "CUST_SERV")
print("\nLogin with user level SERVICE was successful")
After the login we try to write the variable again. Writing to SysTemperatureWarningMargin
succeeds.
Changing the password for a given user level requires a few steps which differ based on the Secure User Level (SUL) version. We distingush between SUL 1 for the Visionary-S CX and SUL 2 for the Visionary-T Mini CX.
The first step is to create a camera control object specifing the right VisionaryType
.
The VisionaryControl
class provides an interface for controlling a Visionary Camera, including managing the connection, logging in and out, and controlling data acquisition.
C++
std::shared_ptr<VisionaryControl> visionaryControl = std::make_shared<VisionaryControl>(visionaryType);
if (!visionaryControl->open(ipAddress))
Python
device_control = Control(ip_address, cola_protocol, control_port)
device_control.open()
The next step involves signing into the device using the user level for which we wish to modify the password. Alternatively, we can also log in using a user level that possesses superior access rights.
C++
if (!visionaryControl->login(IAuthentication::UserLevel::SERVICE, oldPassword))
Python
device_control.login(user_level.value, old_password)
To change the password we call the function changePasswordForUserLevel
which takes as arguments the Control-object
, the old_password
, the new_password
and the device_type
.
If the device_type`
is Visionary-S CX which uses SUL1, the function will call the changePasswordForUserLevelLegacy
and changePasswordForUserLevelSecure
functions. If the device_type
is Visionary-T Mini CX (SUL2) it will only call the `changePasswordForUserLevelSecure`function.
First we change the password. See Step 3.1: Invoke the SetPassword CoLa command (SUL1/Visionary-S only) and Step 3.2: Invoke the ChangePassword CoLa command for more details.
C++
if (!changePasswordForUserLevel(visionaryControl, userLevel, oldPassword, newPassword, visionaryType))
{
std::fprintf(stderr, "Failed to change device password for Userlvl %s\n", userLevel.c_str());
return ExitCode::eAuthenticationError;
}
visionaryControl->logout();
Python
if changePasswordForUserLevel(device_control, user_level, old_password, new_password, device_type):
device_control.logout()
else:
print("Failed to change password.")
device_control.close()
sys.exit()
Now after we changed the password and logged out, we try to login to the user_level
using the old_password
. Since we just changed the password this will lead to an error and we won’t be able to login.
C++
std::fprintf(stdout, "1. Login with old password.\n");
if (!visionaryControl->login(IAuthentication::UserLevel::SERVICE, oldPassword))
{
std::fprintf(stderr, "Failed login: Userlvl: %s | Password %s\n\n", userLevel.c_str(), oldPassword.c_str());
}
Python
try:
print("\n1. Login with old password.")
device_control.login(user_level.value, old_password)
except Exception as e:
print(
f"Failed login: Userlvl:{user_level.name} | Password:{old_password}\n")
The login with the new_password
will succeed.
C++
std::fprintf(stdout, "2. Login with new password.\n");
if (!visionaryControl->login(IAuthentication::UserLevel::SERVICE, newPassword))
{
std::fprintf(stderr, "Failed login: Userlvl: %s | Password %s\n", userLevel.c_str(), newPassword.c_str());
return ExitCode::eAuthenticationError;
}
std::fprintf(stdout, "Successful login: Userlvl: %s | Password %s\n\n", userLevel.c_str(), newPassword.c_str());
Python
print("2. Login with new password.")
device_control.login(user_level.value, new_password)
print(
f"Successful login: Userlvl:{user_level.name} | Password:{new_password}\n")
For safety reasons we reset the password back to the default password.
Note
|
Password changes are permanent. Keep your passwords in a safe place. Otherwise you risk to loose acess to the modified user level. |
C++
std::fprintf(stdout, "Resetting password.\n");
if (changePasswordForUserLevel(visionaryControl, "Service", newPassword, oldPassword, visionaryType))
{
std::fprintf(stdout, "Successfully reset password.\n\n");
}
else
{
std::fprintf(stderr, "Failed to reset password.\n");
return ExitCode::eAuthenticationError;
}
Python
print("Reset to default password:")
if changePasswordForUserLevel(device_control, user_level, new_password, old_password, device_type):
print("Resetting password succeeded\n")
else:
print("Failed to reset password.")
The function changePasswordForUserLevelLegacy
will create and send a CoLa command to invoke the Method SetPassword
. See the method description below. It takes the user level and the new password as input.
This method call will update the password hash.
C++
// Build the cola message for SetPassword method invocation
CoLaParameterWriter getChangePasswordBuilder = CoLaParameterWriter(CoLaCommandType::METHOD_INVOCATION, "SetPassword");
// add the UserLevel
getChangePasswordBuilder.parameterUSInt(static_cast<uint8_t>(IAuthentication::UserLevel::SERVICE));
// add the MD5 hash to the password
getChangePasswordBuilder.parameterPasswordMD5(newPassword);
CoLaCommand getChangePasswordCommand = getChangePasswordBuilder.build();
CoLaCommand getChangePasswordResponse = visionaryControl->sendCommand(getChangePasswordCommand);
// 1 == SUCCESS see SetPassword method documentation
uint8_t result = CoLaParameterReader(getChangePasswordResponse).readUSInt();
if (getChangePasswordResponse.getError() == CoLaError::OK && result == 1)
{
std::fprintf(
stdout, "Changed legacy password hash for user level %s. PASSWORD: %s\n", userLevel.c_str(), newPassword.c_str());
return true;
}
Python
device_control.changeUserLevelPassword(user_level.value, new_password)
print(
f"Changed legacy password hash for user level {user_level.name}. PASSWORD: {new_password}")
return True
Important
|
You need to be logged into the required user level to invoke the CoLa command. Login via the SetAccessMode or the login-function provided by the Python/C++ api.
|
The function changePasswordForUserLevelSecure
will create and send a CoLa command to invoke the Method ChangePassword
. See the method description above.
The CoLa command for the method invocation needs three parameters:
-
the
encryptedMessage
as an array of typeUSInt
-
the
length
of the encrypted message arrayUInt
-
the
user level
of typeEnum8
The function completes the following steps to create the encryptedMessage
:
-
Create the
oldPwStr
-
(SUL1)
oldPwStr
=UserLevelName + ':SICK Sensor:' + oldPassword
. -
(SUL2)
oldPwStr
=UserLevelName + ':SICK Sensor:' + oldPassword + ':' + oldSalt
.
-
-
Create the
newPwStr
-
(SUL1)
newPwStr
=UserLevelName + ':SICK Sensor:' + newPassword
. -
(SUL2)
newPwStr
=UserLevelName + ':SICK Sensor:' + newPassword + ':' + newSalt
.
-
Note
|
UserLevelName is the string of the name of the desired UserLevel e.g. 'AuthorizedClient'. oldSalt is returned by the GetChallenge CoLa command in addition to the challenge bytes, newSalt is a 16byte random string generated by the client.
|
-
Calculate
oldPwHash
assha256(oldPsStr)
-
Calculate
newPwHash
assha256(newPwStr)
Note
|
oldPwHash and newPwHash are a 32x 8bit byte arrays.
|
-
Calculate
encryptedNewPwdHash
-
(SUL1) encryptedNewPwdHash =
AES128CBC(key, iv, newPwHashPKCS7padded)
-
(SUL2) encryptedNewPwdHash =
AES128CBC(key, iv, newPwdHash)
-
Note
|
key is the first 16 bytes of oldPwHash and iv is set to be 16 bytes of random data. Set newPwHashPKCS7padded to newPwHash + 16 * "\x10" . 16 * "\x10" means 16 times the 0x10 byte thus the newPwHashPKCS7padded has a length of 48 bytes in total.
|
-
Set
hmacData
toiv + encryptedNewPwdHash
. -
Calculate
generatedHMAC
asHMACsha256(oldPwdHash, hmacData)
.oldPwdHash
is the key for the HMAC. -
Set
encryptedMessage
toiv + encryptedNewPwdHash + generatedHMAC
.
C++
// get challenge from device
ChallengeRequest challengeRequest = getChallengeFromDevice(visionaryControl, SUL);
// create an encrypted message from the old password, the user Level, the new password and the old salt
auto encryptedMessage =
createEncryptedMessage(userLevel,
oldPassword,
newPassword,
std::vector<uint8_t>(challengeRequest.salt.begin(), challengeRequest.salt.end()));
// Build the COLA command for changing the password
CoLaParameterWriter getChangePasswordBuilder =
CoLaParameterWriter(CoLaCommandType::METHOD_INVOCATION, "ChangePassword");
getChangePasswordBuilder.parameterUInt(encryptedMessage.size()); // add length of the encrypted message array
// add the encrypted message to the cola command
for (auto byte : encryptedMessage)
{
getChangePasswordBuilder.parameterUSInt(byte);
}
// last parameter in the cola command is the UserLevel
CoLaCommand getChangePasswordCommand =
getChangePasswordBuilder.parameterUSInt(static_cast<uint8_t>(IAuthentication::UserLevel::SERVICE)).build();
// send the cola command
CoLaCommand getChangePasswordResponse = visionaryControl->sendCommand(getChangePasswordCommand);
// 0 == SUCCESS see ChangePassword documentation
uint8_t result = CoLaParameterReader(getChangePasswordResponse).readUSInt(); // 0 == SUCCESS
if (getChangePasswordResponse.getError() == CoLaError::OK && result == 0)
{
std::fprintf(
stdout, "Changed secure hash for user level %s. PASSWORD: %s\n", userLevel.c_str(), newPassword.c_str());
return true;
}
Python
# get challenge from device
challenge, salt = getChallenge(
device_control, sul_version, user_level.value)
# create the old salt string as specified in the documentation
old_salt_str = '' if not salt else ":" + bytes(salt).decode("latin-1")
# create an encrypted message from the old password, the new password, the user level and the old salt
encrypted_message = createEncryptedMessage(user_level.name, old_password,
new_password, old_salt_str, sul_version)
try:
# Write the packed byte struct and call the Method ChangePassword
device_control.invokeMethod(b'ChangePassword', struct.pack(">H", len(
encrypted_message)) + encrypted_message + struct.pack('>B', user_level.value))
print(
f"Changed secure hash for user level {user_level.name}. PASSWORD: {new_password}")
return True