Handling of secrets in memory

The password of the logged-in user has to survive for the whole life of the domain because it is needed for relogin (reconnect after a lost connection, domain switch, repository clean). It must never be present in the process memory in a readable form (9984).

lcbase::Secret

TahitiBase/lcsecret.h provides the container used for every long-living secret:

lcbase::Secret

holds the value obfuscated

lcbase::Secret::Clear

scoped access to the plaintext, wipes it in the destructor

lcbase::Secret::WipeGuard

wipes a foreign std::string on scope exit

lcbase::wipeString()

overwrite and release a std string

lcbase::wipeBuffer()

overwrite a raw buffer

Typical usage:

lcbase::Secret::Clear pass(pDomainCfg->getLoginPassword());
pDomainImpl->activate(user, pass.get(), mode, msg, tahitiException);

Never copy the value out of the Clear scope.

Obfuscation is done by CryptProtectMemory / CryptUnprotectMemory with the CRYPTPROTECTMEMORY_SAME_PROCESS flag, so the key is managed by the operating system and is not part of the process image. Other platforms use a key generated once per process. The value is padded to 16 bytes (PKCS#7), so the length of the password is hidden as well.

The level of protection is obfuscation. It prevents finding the password by scanning a memory dump, a crash dump or the page file. It is not a defence against code running inside the process; a debugger attached to the process can call CryptUnprotectMemory as well.

Where the password is stored

TahitiDomainConfiguration::m_loginPassword

The one long-living copy. It is intentionally not a configuration item, so it cannot be read by a generic getValue() call and can never be written into a configuration file. Set by DomainImpl::activate(), wiped by DomainImpl::deactivate().

TciolDep2::pass

Copy inside the DEP2 module. Needed because reconnect authorizes again without asking Tahiti for the configuration.

Everything else is short-living and has to be wiped:

  • Connection.Options expanded by %TAHITI_PASSWORD% (TahitiComm::initialize),

  • the ParseOptions map in the communication module (ParseOptions::wipe),

  • CString of the password controls (TahitiCommon::WipeString),

  • m_Password of TahitiCommandLineInfo.

Rules

  • Never add the password as a configuration item.

  • Never log the password, not even on the debug level, and never log an expanded Connection.Options string.

  • Keep every Secret::Clear scope as short as possible.

  • Wipe every std::string or CString which held a secret; clear() and Empty() only change the length, they do not overwrite the buffer.

  • The /p command line switch puts the password into the command line of the process, where any local process can read it. It is kept for compatibility only; do not use it in new deployments.