Skip to content

Latest commit

 

History

History
523 lines (391 loc) · 17.8 KB

File metadata and controls

523 lines (391 loc) · 17.8 KB

User level and password configuration

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.

How to run the samples

Note

Remember to adjust the command line arguments like IP address (-i) and device type (-d) to match your specific device.

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

User levels: Login and logout

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.

userlevels

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

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

Establish a connection

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()

Read the current value

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}°C")

Write variable without sufficient access rights

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}°C")

Login and write with sufficient access rights

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-passwords

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.

Final Step: Logout and close the connection

Finish by logging out the current user from the device and closing the connection to the device.

C++

visionaryControl.logout();
  visionaryControl.close();

Python

deviceControl.logout()
    deviceControl.close()

Changing the password

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.

Step 1: Establish a connection

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()

Step 2: Login to the user level with the old password

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)

Step 3: Change the 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.

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.")

Step 3.1: Invoke the SetPassword CoLa command (SUL1/Visionary-S only)

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.

setPassword

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.

Step 3.2: Invoke the ChangePassword CoLa command

The function changePasswordForUserLevelSecure will create and send a CoLa command to invoke the Method ChangePassword. See the method description above.

changePassword

The CoLa command for the method invocation needs three parameters:

  1. the encryptedMessage as an array of type USInt

  2. the length of the encrypted message array UInt

  3. the user level of type Enum8

The function completes the following steps to create the encryptedMessage:

  1. Create the oldPwStr

    1. (SUL1) oldPwStr = UserLevelName + ':SICK Sensor:' + oldPassword.

    2. (SUL2) oldPwStr = UserLevelName + ':SICK Sensor:' + oldPassword + ':' + oldSalt.

  2. Create the newPwStr

    1. (SUL1) newPwStr = UserLevelName + ':SICK Sensor:' + newPassword.

    2. (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.
  1. Calculate oldPwHash as sha256(oldPsStr)

  2. Calculate newPwHash as sha256(newPwStr)

Note
oldPwHash and newPwHash are a 32x 8bit byte arrays.
  1. Calculate encryptedNewPwdHash

    1. (SUL1) encryptedNewPwdHash = AES128CBC(key, iv, newPwHashPKCS7padded)

    2. (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.
  1. Set hmacData to iv + encryptedNewPwdHash.

  2. Calculate generatedHMAC as HMACsha256(oldPwdHash, hmacData). oldPwdHash is the key for the HMAC.

  3. Set encryptedMessage to iv + 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

Final Step: Logout and Close the connection

Finally we disconnect from the control channel and logout from the user level.

C++

visionaryControl->logout();
  visionaryControl->close();

Python

device_control.logout()
    device_control.close()