watfordjc / LTO-Encryption-Manager

LTO tape AES key management using SLIP-0021 symmetric key derivation from a BIP-0039 wallet mnemonic.
MIT License
3 stars 0 forks source link

Store account-level derivation keys #1

Open watfordjc opened 2 years ago

watfordjc commented 2 years ago

Feature Branch

Current feature branch for this issue: feature/issue-1/gui-2.

Progress


Background

At present, the application only takes hexadecimal entropy or a BIP-0039 mnemonic seed and calculates and displays the other, as well as the BIP-0039 derived binary seed, the derived SLIP-0021 master node (m) derivation key, and the symmetric keys for nodes m, m/"SLIP-0021", m/`"SLIP-0021"/"Master encryption key", and m/"SLIP-0021"/"Authentication key".

These tests in the UI are no longer necessary as they were converted to unit tests in commit b7d7cb4. The BIP-0039 tests use the vectors.json file linked from the bip0039 specification (using BIP-0039 passphrase TREZOR). The SLIP-0021 tests use a slip0021-vectors.json file in a similar format, that currently only contains the example in the slip0021 specification (using no BIP-0039 passphrase).

An attempt was made to clear things from memory when they are no longer needed in commits beb65b3 and 2384bcb, although some continued use of strings currently remain. I considered using PasswordBox and SecureString, but until Dictionary<string, int> supports key lookups using ReadOnlySpan<char> and type SensitiveData is available, it would be too much work to remove the use of string.

Mnemonic seeds are also too long to not display the input text, and it is unclear if PasswordRevealMode in the UWA version of PasswordBox allocates a string if the password is revealed. TextBox can have a CustomDictionary for spell check, but such dictionaries augment the system dictionaries rather than replace them - it isn't possible to force spell check to only use the BIP-0039 word list.

Until there is a suitable solution to the issues highlighted in the previous two paragraphs, the use of TextBox and string will remain, because the alternative is to spend a huge amount of time creating NotTextBox and NotDictionary (plus implementing ReadOnlySpan<char>.Split(), ReadOnlySpan<char>.Normalize(), etc.).

A lot of code was split from MainWindow.xaml.cs in commit 89ab9cb and slightly refactored into sub-namespaces and static public functions. Although it could potentially split into a separate library, watfordjc/GameChatLite#2 brought this repository back to the top of the unfinished code stack because I also intend to use SLIP-0021 derived keys in the GameChatLite server.

That brings us to the issue: retaining secret data, and doing it securely.

The Issue

LTO-Encryption-Manager needs to be able to calculate the symmetric key for a given LTO tape's barcode without requiring the user to enter their BIP-0039 mnemonic seed every time. Deriving deterministic keys using SLIP-0021 was chosen because LTO uses AES-256-GCM (256 bit keys) and SLIP-0021 uses HMAC-SHA512 to derive a 512-bit hash for a given node, with the first half defined as a 256-bit derivation key and the second half a 256-bit symmetric key. The 256-bit symmetric key for a given node can therefore be directly used as an AES-256-GCM symmetric key, and the node's derivation path can be based on the tape's label/barcode.

Similarly, it is proposed that GameChatLite's server will use 64-bit snowflake IDs for user IDs, device IDs, and state IDs. A new installation is initially identified as a new device ID (stored in a cookie signed using HMAC-SHA256 with the server's key), a state ID (passed in a query variable to the OAuth2 server, along with a signature of the state ID and device ID signed using the state ID's symmetric key), and on success a new device ID is generated and linked to a new/existing user ID given the user ID supplied by the OAuth2 server.

In order to avoid saving state and potential personal identifiers, the state ID will have a validity window and will be added to a blacklist once used, with the blacklist getting pruned as old IDs become too old to be valid. The server will only store device IDs that are linked to user IDs, with new_session device IDs having an as yet undecided validity period (5 days?) and only existing in the user's browser. Likewise, the state IDs will only exist in the user's browser as a query parameter. In order to solve the question How can a server know it generated and linked two IDs without storing either of them or anything about them? I again considered SLIP-0021. Given the limited lifetime of the IDs, HMAC-SHA256 using a 256-bit (32 byte) seemed sufficient.

So, I have two SLIP-0021 use cases, and code that can create keys for (currently hard-coded) derivation paths. Both derivation path schemes are to use a reverse hostname as the first-level label and an ASCII number at the second-level to support key rollovers (the first key would be at derivation path m/"uk.johncook.slip-0021.usage-description"/"0"). I need to store three things for each first-level label: the first-level label, the key rollover count, and the derivation key for that node.

Given Windows 11's announcement had me check my devices had built-in TPM support, my initial thoughts are to work out how to encrypt and store the derivation keys in .NET 5, using the TPM in some way. This will probably involve CNG rather than CryptoAPI.

First-Level to Account-Level

This issue has been renamed to focus on the uk.johncook.slip-0021.lto-aes256-gcm SLIP-0021 schema and progress the development of this application.

Storing the account-level derivation key (i.e. m/"uk.johncook.slip-0021.lto-aes256-gcm"/"0"/"0"/"0" rather than m/"uk.johncook.slip-0021.lto-aes256-gcm") will potentially require re-entering the recovery seed more often, but it will simplify what needs to be done to get the application to the point where it is minimally functional.

watfordjc commented 2 years ago

TPM

The usage of a TPM may or may not be that useful.

TPM Limitations

TPM 2.0, for example, only requires hashing algorithm SHA2-256, so HMAC-SHA2-512 can't be done in TPM 2.0 silicon (such as that in my Intel J4105) that hasn't opted to include optional algorithms. TPM 1.2 likewise only requires SHA1 (which is now optional in TPM 2.0).

The Secure Boot stuff (PCRs) can either be available as SHA1 registers, SHA2-256 registers, or both. Windows documentation specifically warns you not to go switching the registers currently in use, especially if you're using Bitlocker.

RSA 2048 and ECC P-256 support are required in TPM 2.0, but RSA 4096 isn't, nor is ECC P-384, nor PBKDF2, and while the most recent (June 2020) version of the algorithm registry does contain the SHA3 family of algorithms, Argon2 is absent.

On top of the dated algorithm limitation, interacting with the TPM is a bit of a nightmare.

Windows 10

The first thing I did was back up my Bitlocker recovery key to several places including my phone.

In order to enable Secure Boot I needed to flash both UEFI and my SBC's/PC's Embedded Controller, both of which were highly likely to result in a change requiring the Bitlocker recovery key. Without it being on my phone I would have needed to boot to another OS and copy it to my phone anyway (or take a picture of the open file) because Bitlocker did not want to read a recovery key from any of my drives.

"As I'm doing the TPM thing, I might as well do the TPM everything."

Bitlocker, UEFI Secure Boot, switching to PCR[7] and PCR[11] bindings, adding the Memory Integrity green check mark to Windows Security, basically turning on every consumer-targeted security feature that wasn't already on so msinfo32 looked like an SSL Labs report.

Intermingled with research about TPM 2.0, of course.

The Nightmare of OwnerAuth

There is this TPM passphrase thing called OwnerAuth. It is a thing the owner of a device knows, unless the device knows better.

When you're looking at TPM API source code, commands, and documentation, you'll come across this ownerAuth/auth variable and, in the case of TSS.NET be told in the every so helpful source code:

// If running on a real TPM, which has been provisioned by Windows, this // value will be different. An administrator can retrieve the owner // authorization value from the registry.

Nope, that isn't how Windows works these days. In order for the value to be in the registry you have to (a) change the registry or group policy to something Microsoft says is a bad idea, (b) clear the TPM, (c) fix everything (e.g. Bitlocker), and (d) do something with that TPM lockout bypass password in the registry such as sticking it in a file somehow so it can be loaded at the command line.

Microsoft have basically said this is a bad idea, you don't need to do this, you don't need that credential, if someone enters the wrong PIN 32 times do what everyone else has to do and wait 2 hours for attempt 33. As a result, Windows now takes ownership of the TPM on every boot, and that credential is destroyed almost as soon as it exists.

Maybe I need the high entropy deleted from existence passphrase, maybe I don't. I just don't know because I haven't actually been able to do much with the TPM yet.

Endorsement Key Certificate AKA the EKcert

The TPM may or may not have an EKcert. The EKcert is a public certificate from the manufacturer that says the EK's public key is from a TPM they made.

In an administrative PowerShell, the following will show the EK information:

Get-TpmEndorsementKeyInfo -hash "sha256"

IsPresent should be True if you have a TPM and it is enabled. If the value of PublicKey is System.Security.Cryptography.AsnEncodedData that means it is in ASN.1 format. You can extract the EK public key to a file with the following:

$bytedata = (Get-TpmEndorsementKeyInfo -Hash "Sha256").PublicKey.RawData
Set-Content ek_rsa.raw.pub -Value $bytedata -Encoding Byte

I should probably note here that my TPM stores the public key in ASN.1 format that doesn't say what type of key it is. openssl asn1parse -inform DER says the file created from the public key byte array consists of a SEQUENCE containing a 257 byte INTEGER (the public key) and a 3 byte INTEGER (the exponent).

ManufacturerCertificates and AdditionalCertificates are each an X509Certificate2Collection object. Basically an array of X.509 certificates in ASN1 format. My TPM has 0 certificates in ManufacturerCertificates and 1 certificate in AdditionalCertificates, the latter of which can be extracted with the following:

$bytedata = (Get-TpmEndorsementKeyInfo -Hash "Sha256").AdditionalCertificates[0].RawData
Set-Content ek_rsa.cer -Value $bytedata -Encoding Byte

You can confirm EKcert ek_rsa.cer is for EKpub ek_rsa.raw.pub by various methods, although I'd probably go with comparing the modulus of the two using openssl in WSL2 Ubuntu.

Parse the EKpub public key file using OpenSSL, print the public key modulus, then get an MD5 hash of the output:

openssl asn1parse -in ek_rsa.raw.pub -inform DER -offset 4 | head -n1 | sed 's/.*:\(.*\)/\1/' | md5sum --

Parse the EKcert public certificate file using OpenSSL, print the modulus, then get the MD5 hash of the output:

openssl x509 -noout -modulus -inform DER -in ek_rsa.cer | awk -F '=' '{print $2}' | md5sum --

You can remove the | md5sum -- bit to double-check you're not hashing the wrong data, but if you're hashing the right thing and nothing has changed openssl's output format the hashes should match. Piping to md5sum is because it easier to see a difference between the hashes than trying to compare the hexadecimal moduli byte by byte - it's also why the moduli are reformatted to the same format (uppercase, no colons, trailing newline).

If the moduli match and your public key is raw ASN.1 (i.e. it lacks the information about the key type), the certificate contains a copy of the public key that contains that information and can be extracted in PEM format:

openssl x509 -pubkey -inform DER -in ek_rsa.cer -noout > ek_rsa.pub

Windows Registry

An alternative method of getting the EKpub and EKcert is through regedt32.exe/regedit.exe or reg.exe.

The registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TPM contains a number of TPM-related registry keys.

EKcert Certificate Chain

Open the ek_rsa.cer certificate's properties in Windows Explorer and go to the tab with all the certificate's details. Look for the new-ish OID Authority Information Access and copy the value of the URL (use Ctrl+C - right-click doesn't work). Paste it in a Web browser and download the manufacturer's signing certificate that signed the EKcert.

Open the intermediate certificate's properties in Windows explorer and go to the Certification Path tab. Select the issuing certificate of the intermediate and click View Certificate (if able to - I believe Windows automatically uses AIA to fetch missing issuer certificates). Go to the Details tab and click Copy to File... choosing DER (.CER) format. Alternatively, use the process in the previous paragraph to download the certificate that signed the intermediate certificate (this method has the advantage of not needing to pick a filename).

Repeat the process until you're at the manufacturer's TPM EK Root signing certificate. In theory, this will be the same certificate linked to in the Wikipedia article on TPM.

Missing EKcert

If the TPM has an EK public key, but the EKcert is missing, you might be able to fetch the EKcert from the manufacturer. This will need a Linux-based UEFI booted operating system running "bare-metal" on the device with the TPM. If you're not multi-booting then a bootable live OS, such as Ubuntu running from a USB drive, should be OK.

Obvious disclaimer here: you need to interact with the TPM on the device by running another "bare-metal" OS on the same device. The TPM you are going to interact with is the thing you have told Windows to check hasn't been messed with before letting you access your files. Backup your Bitlocker recovery key.

OK, for the next steps you're going to want to install... something. sudo apt install tpm2-tools strongswan-pki libstrongswan-extra-plugins libengine-pkcs11-openssl jq - not sure which were needed.

The EK key should be stored in the TPM at 0x81010001. Change to a persistent directory you can write to (such as an external drive), create a new directory, and cd to it.

The first command we want is to get the EK public key out of the TPM again. The copy we already have is no good because it needs to be in a certain format:

sudo tpm2_readpublic -c 0x81010001 -o ek_rsa.tcg.pub

The next thing that's needed is to ask Intel for a certificate for your EK's public key:

sudo tpm2_getekcertificate -o ek_rsa.crt.json -u ek_rsa.tcg.pub "https://ekop.intel.com/ekcertservice/"

The URL will be different for different TPM manufacturers. I think I saw an issue asking for tpm2-tools to support auto-detection, although it doesn't seem to be in the current version available in Ubuntu Focal.

Next, get the URL-safe base64-encoding certificate from the JSON as DER format:

jq -r .certificate ek_rsa.crt.json | sed 's/-/+/g;s/_/\//g;s/%3D/=/g;s/^{.*certificate":"//g;s/"}$//g;' | base64 -d | openssl x509 -inform DER -outform DER > ek_rsa.cer

You can now verify the moduli for the public key in the certificate and the public key extracted from the TPM, and then fetch the certificate chain.

watfordjc commented 2 years ago

Bitlocker Security

Depending on the TPM's state when Bitlocker was enabled for a system drive, the PCRs used to seal the key could be one of two options:

  1. PCR[7] and PCR[11]. This is the TPM 2.0 default if Secure Boot was enabled when Bitlocker was enabled. The PCR[7] register contains the Secure Boot state, so if Secure Boot is enabled and you're booting Windows the value in PCR[7] should always be the same. The PCR[11] register contains Bitlocker access control information, such as the Bitlocker VMK (volume master key) - I assume PCR[11] only changes if the Bitlocker configuration for the drive changes, such as the encryption algorithm or key protectors.
  2. PCR[0], PCR[2], PCR[4], and PCR[11]. On Windows 7 and earlier PCR[5] was also included, but from Windows 8 Microsoft stopped including it in the policy - PCR[5] is the MBR partition table, so would change whenever a partition was resized/created/deleted. PCR[0] is the UEFI firmware and stuff, PCR[2] is any option ROM code, PCR[4] is the MBR boot code, and PCR[11] is the same as above.

PCR[7] binding requires Secure Boot, which itself requires a certain UEFI configuration, such as disabling any compatibility support module (CSM). It also requires securing against DMA attacks, either through policy (such as ignoring inserted devices unless a user is logged in) or through disabling hardware (such as Thunderbolt 3) - Microsoft's OEM guide for Kernel DMA Protection has the actual firmware requirements (e.g. OEMs must enable IOMMU by default).

There are a number of PCR registers Bitlocker's key can be sealed with when it comes to the TPM key protector, but the Secure Boot option of PCR[7] and PCR[11] is the current default as it is (probably in Microsoft's opinion) the least likely to cause Bitlocker recovery. PCR[7] will likely only change if the user changes something in the registry, and can be restored to the expected value if the user undoes their change. I assume PCR[11] uses something that can be determined within Windows whenever a Bitlocker tool changes something used to calculate PCR[11], so the key can be resealed with the PCR[7] value from the last bootup process and the new PCR[11] value without the need for Bitlocker recovery. Alternatively, suspending Bitlocker and enabling Bitlocker (without rebooting in between) will probably have the same effect.

By default (at least on my SBC PC), group policy is not configured for other TPM key protector options. It might be possible for a physical MITM attack on the TPM to reveal the Bitlocker VMK if using the default options of sealing to PCRs (AKA the TPM key protector). There are other options for using the TPM, such as the TPM and PIN key protector, which (in addition to sealing to the PCRs) requires a PIN be input on boot. Incorrect PIN, no Bitlocker key. Only one key protector using the TPM can be enabled, so adding a TPM and PIN key protector will replace a TPM key protector.

The relevant group policy setting for enabling TPM & PIN, TPM & Key, and TPM & PIN & Key, is at Computer Configuration\Administrative Templates\Windows Components\Bitlocker Drive Encryption\Operating System Drives, with the policy called Require additional authentication at startup. The policy's name is a little misleading: while you can set one (and only one) authentication method as "require" (i.e. this is the only method that can be used), the other options are "allow" and "do not allow". To enable TPM & PIN, you just need to change this group policy to Enabled and make sure Configure TPM startup PIN is set to Allow startup PIN with TPM. Also note the checkbox Allow BitLocker without a compatible TPM should probably remain checked.

The TPM & Key protector will require a flash drive with the .BEK key file for booting (i.e. something you have). The TPM & PIN protector will require a PIN to boot (i.e. something you know), as well as a pre-boot compatible USB keyboard for input. There are other group policies in Operating System Drives related to TPM & PIN, such as allowing enhanced PINs (i.e. alphanumeric PINs) and a PIN length policy. I see no point enforcing any policies as I'm both admin and user of the device. I'm going to use TPM & PIN because USB ports and drives can fail.

To switch from a TPM key protector to a TPM & PIN key protector, make sure you have an accessible copy of the recovery numeric password first. Then, at an elevated powershell prompt, type the following command (where C: is your system drive) and enter the pre-boot PIN you want to use when prompted:

manage-bde -protectors -add C: -tpmandpin

Make sure things still work by rebooting. If you're asked for your PIN and Windows continues booting after you've entered it, the TPM & PIN key protector is working. Your operating system now requires either (a) your exact computer and its TPM chip, and the PIN in your head, (b) the .BEK recovery key you created when you enabled Bitlocker (if applicable), or (c) the recovery numeric password.

The reason I'm going through this here is for the following two reasons:

  1. The OS drive being used by the application needs to be secure to minimise the risk of the derivation keys being compromised.
  2. Bitlocker recovery numeric keys and key files are a PITA to store, and I am creating an application that can create keys deterministically.

Assuming my reboot goes fine, the next step is to work out how the numeric passwords work (the documentation suggests you can have more than one at the same time, unlike TPM key protectors) and then decide how best to create a derivation path for Bitlocker numeric passwords. It looks like the key protectors are GUIDs created by Windows when creating a key protector, so I don't think there is way for the key to be derived by its GUID as it looks like an ID can only provided when deleting a key protector.

watfordjc commented 2 years ago

SLIP-0021 Derivation Paths

DRAFT - Schemas are subject to change

I am proposing three different SLIP-0021 derivation paths for LTO AES256-GCM encryption.

  1. The main path from master node to the symmetric key for a tape.
  2. A well-known path for creating a fingerprint for a derivation key.
  3. TODO: A path for Bitlocker numeric keys?

LTO AES256-GCM Derivation Path

This section has been moved to a section of the LTO Encryption Keys wiki page in the backup-policy repository.

Key Validation Derivation Paths

This section has been moved to the LTO Encryption Key Associated Data wiki page in the backup-policy repository.

TODO: Determine what fingerprint (if any) should be stored in MAM.

watfordjc commented 2 years ago

Creating/Recreating the Seed Phrase

My uncommitted code has a new startup window with title Recreate Master Seed from BIP-0039 Seed Phrase. In this comment I am going to list the current options, any UI/background logic, and then deliberate on what I need/want to do.

Window: Recreate Master Seed from BIP-0039 Seed Phrase

Current Issues

TODO

watfordjc commented 2 years ago

Securely Storing the Derivation Key

At this point, the AddSeedPhraseView view and the AddSeedPhraseViewModel view model can take a BIP-0039 seed phrase and optional password, recreate a BIP-0039 binary seed, convert the binary seed to a SLIP-0021 node using a first level label and global key rollover count (e.g. m/"uk.johncook.slip-0021.lto-aes256-gcm"/"0"), and then create and display a fingerprint of that node.

I am going to refer to the node m/"uk.johncook.slip-0021.lto-aes256-gcm"/"0" as the current global derivation node because after creating it from the binary seed we can throw away everything used to create the node (including the binary seed).

What Needs Storing

For different SLIP-0021 first level labels, and for different global key rollover counts, there will be a different current global derivation node. I am going to refer to these as global derivation nodes. The fingerprint for each global derivation node will require (a) the derivation key for the node, (b) the global key rollover count.

For storing the current global derivation node, the following would be useful:

Of these, the only thing that needs encryption is the node's derivation key. The other data can be used to identify the key.

The optional human-readable description of the node would be used in the UI when listing the global derivation nodes or indicating the current global derivation node, possibly alongside the derivation path of that node and its fingerprint.

After the refactoring towards using the MVVM pattern, a node's derivation path is now available as a string, the global key rollover count is a string, and the node's fingerprint is (after creating a validation node and calculating the fingerprint) a string? property of the validation node.

The full bytes of a node are available as a byte[], with the node's derivation key and symmetric key each being a ReadOnlySpan<byte> for a slice of the byte array.

To constructor for a node is:

Slip21Node(in byte[] nodeBytes, string globalKeyRolloverCount, string? label = null)

The current constructor could be used to create a node using just a derivation key, by setting the last 32 bytes of nodeBytes to '\0'. At present, there are no checks to check if the left or right half of nodeBytes equals 0x0.

The function Slip21Node.Clear() clears the byte[] array (and thus the ReadOnlySpan<byte> values), and nodeBytes can only be set during instantiation. There is not currently a dispose pattern for Slip21Node.

So, the data that needs to be encrypted for storage is a ReadOnlySpan<byte> variable. Its value needs to be able to be used as the key in HMAC-SHA512 operations, and nothing else.

Methods to Encrypt Credentials

Since most (all?) TPMs don't support HMAC-SHA512, TPM non-exportable storage is not an option. TPM sealing, however, is an option, if I can work out how to do it.

There are a number of options, though, so I'm going to list the options under consideration and then think about which is most suitable.

The derivation key for the current global derivation node is an important secret. All nodes derived from this key can be recreated using the derivation key and the derivation path, allowing everything encrypted with all descendant nodes' keys to be decrypted.

That is why I'm looking at how to store it first. The BIP-0039 seed phrase (or equivalent) is not stored on the computer, being stored offline (e.g. using pencil and paper, sheet steel and hammer, etc.) and only being needed to (re)create a global derivation node. Derivation keys for descendant nodes, such as the primary account's key rollover node (used to derive key nodes for LTO tapes), will also be stored, but they won't necessarily need the same level of protection.

Bound to PCRs and Sealed by TPM

This section has been moved to a wiki page because describing the method and its pitfalls briefly is not possible.

I covered some TPM issues in an earlier comment to this issue, but the main pitfall for this option is simple: no-one knows how to do it.

Data Protection API (DPAPI)

For DPAPI, I am going to briefly detail how the encryption/decryption key generation/storage works for a local user account. Things might be different for domain accounts and Microsoft accounts.

DPAPI uses PBKDF2 to derive a key using the SHA-1 hash of a Windows account's logon password (in UTF16-LE), with a 16 byte random salt and a number of iterations varying by Windows version (or registry setting). Using this key, 64 bytes (512 bits) of random data (a master key) gets encrypted.

Every master key has a GUID to identify it, and the master key gets rotated every 3 months. The current master key is used to encrypt data, with the encrypted data becoming a binary blob/file. The binary blob contains the GUID of the master key used to encrypt it, an optional description, the cryptographic and hashing algorithms, an optional salt (i.e. supplied by an application to Protect() so other applications can't decrypt without access to the salt), the ciphertext (encrypted data), and a HMAC/signature, plus a couple of other things.

Given the way DPAPI works, the SHA-1 hash of every password ever used for an account is stored in a credential history file that is encrypted with the current master key. Likewise, every encrypted master key created for an account is also stored. This allows a blob (such as a file in an encrypted folder) that was last encrypted years ago, using a really old 64 byte master key (encrypted using a key derived from the SHA-1 hash of an old password), to be decrypted.

In terms of security/authentication, DPAPI ProtectedData only offers two options: encrypt it using the SYSTEM account's credentials (LocalMachine scope), or encrypt it using the current logged on user's credentials (CurrentUser scope). In the case of CurrentUser scope, any application that knows the optional salt can decrypt the data if the user is logged on. In the case of LocalMachine scope, any application running as any user can decrypt the data if it knows the optional salt.

For these reasons, I am ruling out DPAPI and ProtectedData.Protect().

There might be a use for ProtectedMemory.Protect() after decrypting the derivation key if there is a need to keep it in memory for a time, as the available scopes are CrossProcess (any process can Unprotect()), SameLogon (only code running as same user as that which called Protect() can Unprotect()), and SameProcess (only the same process that called Protect() can call Unprotect().

Credentials Management API

The Credential Manager in Windows offers a way to manage Windows credentials (credentials stored by applications) and Web credentials (logins for Web sites).

The Credentials Management API are the (Win32) APIs that allow an application to create/modify/delete credentials.

There is a generic credential option, and such a credential is stored as a binary blob that should be portable and in a format (and endianness) defined by the application.

There are three persistent options when creating a credential:

Unlike DPAPI, there is no salt option, so if a user is logged on any application running as the user can read all of their credentials.

Key Containers

Key containers are also knows as RSA key containers.

They have the same scopes as DPAPI ProtectedData and can only store limited types of asymmetric keypairs (as of .NET 6, DSA, ECDH, ECDSA, RSA).

Azure Key Vault

Azure Key Vault is a cloud thing that requires an Azure account.

This does not seem appropriate for the use case.

watfordjc commented 2 years ago

Reducing Requirements

One of the issues with wanting to do things as secure as feasible is that the most secure way is probably not feasible. If it is feasible, it will require doing with TPM what I have done with LTO: read the specification documents, work out how the OS and hardware implements the specification, and spend ages manually implementing something myself.

Utilising the TPM to provide enhanced security (e.g. PCR binding, attestation, etc.) would be too much work at this time. If I use the high level Windows APIs, such as CNG, there is the possibility to use those harder-to-use TPM features later if Windows were to add support or someone works out how to use the low level APIs when TPM automatic provisioning is in use.

This decision will make things potentially less secure if there is malicious software on the computer, but there are levels of insecurity. Ransomware is out there, and at the most basic level it just needs read and write access to files. Protecting a backup/archiving utility against ransomware that reads the memory of other running applications should probably be less of a priority than having a backup/archive that can be restored from.

I want the backup tapes to be encrypted. This application's purpose is to turn on LTO drive encryption so that I can start archiving and backing up data. It is time to get to the point where it is minimally functional.

Not Storing First-Level Derivation Keys

This issue was renamed to focus on the original purpose of this application. Other schemas can be added later, but for now I just need to store the derivation key for the account's key rollover node (henceforce the current-account key) so that tape keys can be derived and A-KAD/U-KAD can be generated.

BIP39 Recovery Seed Generation

I don't believe I currently have a 256-bit (24 word) BIP39 recovery seed stored anywhere, and would prefer 256 bits of initial entropy over 192 or 128 bits because 256 bits will equal 128-bit strength post-quantum.

I have a WPF window for "restoring" recovery seeds (it doesn't actually save anything yet because of security), but WPF windows do not work properly on a secure desktop (they don't paint anything in the client area of the window).

I have created a WinForms window for generating a new 256-bit BIP39 binary seed, although a fair amount of refactoring needs to take place before it is committed to the repository because the current application start up process is a bit of a mess. The WinForms window currently does the following after it has opened in a secure desktop via SwitchDesktop():

  1. Check the current user's certificate store contains a certificate with a subject name containing "LTO Encryption Manager".
  2. If there is only one match for such a certificate, get the public and private RSA keys if they exist, and then set `validTpmCert` = true if the certificate has a private key, the public and private RSA keys exist, and the private key is not exportable.
  3. If there is a valid TPM-backed certificate, enable the Generate 256-Bit Seed button.
  4. On click of the seed generation button, connect to the version 2.0 TPM, perform steps 1 & 2 again, and if a valid certificate is available get 32 random bytes from the TPM and convert it to a BIP39 binary seed, convert the binary seed to a seed phrase and display it, and enable the Generate Account Node button. The status bar notes the importance of writing down the seed phrase.
  5. On click of the generate account node button, the binary seed is used to derive the global and account nodes and their fingerprints, encrypts the current-account key using the RSA public key, and then uses the RSA private key to sign (encrypted current-account key + derivation path + global key rollover count + global fingerprint + account fingerprint) where + is the ␟ (unit-separator) character 0x1F.
  6. Writes (encrypted current-account key + derivation path + global key rollover count + global fingerprint + account fingerprint ++ signature) to a file, where + is the ␟ (unit-separator) character 0x1F and ++ is the ␞ (record-separator) character 0x1E.
  7. On success, the window closes and the secure desktop returns to the default desktop.

The location of the saved file is currently %LocalAppData%\John Cook UK\LTO-Encryption-Manager\Accounts\HEX1\HEX2.blob where HEX1 is the hexadecimal representation of the UTF-8 bytes for the global fingerprint, and HEX2 is the hexadecimal representation of the UTF-8 bytes for the account fingerprint.

The secure desktop does not currently mitigate against other applications (such as keyloggers) opening the secure desktop to try and obtain the entropy, binary seed, or recovery phrase. 1Password has implemented such a mitigation by checking it is the only process using the desktop, but after several hours I couldn't work out how to get a process count of 1 so have given up on mitigating the issue for now.

The WPF window for restoring a seed will need migrating to WinForms, although I'm not too keen on doing that because converting the input validation, autocomplete word lists, and the layout, will likely be a fair amount of work.

TPM-Backed Certificate Creation

My current TPM-backed certificate was generated using certutil and an .inf file, and then signed using my internal CA's intermediate certificate (the one that signs RADIUS certificates) using a custom OpenSSL configuration:

[ usr_cert_tpm_unverified ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature, keyEncipherment, dataEncipherment
extendedKeyUsage = clientAuth
crlDistributionPoints = URI:[redacted-internal]

I also added a [ usr_cert_tpm_verified] configuration that adds nonRepudiation to keyUsage that can be further built upon if I ever work out how to do key attestation (without having to use Windows Server).

Key generation needs migrating to C#. I am also not happy with some aspects of the key/certificate, such as the key description in the password popup being blank (the key is password-protected).

watfordjc commented 2 years ago

Restricting Key Usage

[This comment has had to be recreated from scratch because a desktop switch couldn't be escaped from and I had to logout. As a result, a lot of my thoughts have been lost.]

The problem with the current test certificate is that it has extendedKeyUsage = clientAuth. The certificate is for a specific purpose and the specific purpose is incorrect: it is not meant to be a TLS client certificate, but without specifying an EKU it will be used for all purposes including client authentication. That means the certificate it is listed as an option for TLS client authentication on some Web sites, such as one of mine.

What is needed is a custom key usage object identifier.

Obtaining an OID

An OID is an Identifier for an Object. It isn't for transient things, but for anything else it could be given an OID to uniquely identify it permanently and forever. It is globally unique, and once you have one you can in theory create an unlimited number of them.

An object identifier (OID) is an extensively used identification mechanism jointly developed by ITU-T and ISO/IEC for naming any type of object, concept or "thing" with a globally unambiguous name which requires a persistent name (long life-time). It is not intended to be used for transient naming. OIDs, once allocated, should not be re-used for a different object/thing.

—What is an OID?, OID Repository

OIDs are part of a tree structure. That tree structure has a root (let's call it . as with DNS and then pretend it isn't there). Unlike DNS, the order is in the opposite direction, a bit like reverse hostnames and the bit before ip6.arpa or in-addr.arpa in reverse DNS. There are, and will only ever be, 3 branches from the root:

Once an OID is defined for a specific purpose, that purpose is fixed forever... sort of. The ITU-T and ISO/IEC members include a lot of huge government agencies, and a rule change can "prefer" that their members do things a certain way from this point forward, such as OID arc 1.2 (iso.member-body) being deprecated in 1991 in favour of 2.16 (joint-iso-itu-t.country) for assigning OIDs to private companies, resulting in the ANSI (an ISO member) and the Department of State (an ITU-T member) deciding the ANSI would stop registering private company OIDs under OID arc 1.2.840 (iso.member-body.us) and switch to OID arc 2.16.840.1 (joint-iso-itu-t.country.us.organization).

Such a change didn't invalidate existing OIDs because defining a thing as something and then changing your mind doesn't allow retrospective changes to things that relied on the thing's definition. That is why there are a lot of OIDs that start 1.2, including my own.

There are several routes to obtaining an OID. The IANA option is free but it can take a few weeks and results in your e-mail address being published. The ANSI option costs upwards of a thousand US dollars and can take a month.

OIDs 2.25 and 1.2.840.113556.1.8000.2554 can be used with a unique UUID (i.e. one you generate yourself that is guaranteed to be globally unique, such as a version 4 UUID), although they look very long. The version 5 (truncated SHA-1) namespace OID generated using the namespace DNS (6ba7b810-9dad-11d1-80b4-00c04fd430c8) and the domain I use for reverse hostnames johncook.uk is d4eb3887-3fcd-56d7-96a5-0c6d7ba8658d.

UUID d4eb3887-3fcd-56d7-96a5-0c6d7ba8658d can be converted into an OID using one of the OIDs defined for such a purpose, such as:

United Kingdom OIDs

Approximately 0.3 centuries ago the BSI (British Standards Institute) decided how OIDs would be allocated to UK companies. Any company incorporated under the Companies Act 1985 with the numeric part of their Company Number greater than 0 and less than 2^24-1, are "deemed to be assigned" an OID automatically by appending the numeric part to the OID for companies registered in that jurisdiction (i.e. {iso(1) member-body(2) gb(826) national(0) eng-ltd(1)} for companies registered in England & Wales).

I am the sole director of a company (a non-trading dormant company is still a company) that bears my name because during my negotiations for johncook.co.uk (a domain that was "for sale" that I'd wanted since 1998), and with the .uk second-level domain option down the road that would give me the first right to johncook.uk (because the .co.uk was older than the .org.uk, .me.uk, etc.), I pondered the option of owning John Cook Limited (and by extension, the right to johncook.ltd.uk). At the time there was already a company with that name.

I periodically checked the company name at Companies House and eventually noticed the company had been dissolved. After spending a lot of time looking at everything that needed to be done, I formed the company John Cook Limited with me as a sole director. I might "start a business" at some point, but until then it is pretty much a "name/brand protection" company to reserve my name. I can't do anything in the company's name that involves money because a first invoice will make the annual accounts more time consuming forever, but as a sole director I can speak as the company by merely saying I am speaking as the company.

Interpreting BS 7453 Part 1

There are four issues with the definition of OID 1.2.826.0:

  1. The definition is in a Part of a British Standard, a copy of which costs £225.00.
  2. BS 7453 has never been updated.
  3. The Companies Act 2006 has repealed most of the Companies Act 1985.
  4. Companies House has breached Company Number 2^23 and a company with Company Number 2^24 will probably be incorporated in the next 3-5 years.

The publicly available extracts of BS 7453 Part 1 with regard to the (automatic) creation of OIDs for companies specifically says Companies Act 1985. Although John Cook Limited was incorporated under the Companies Act 2006, I assume a standards body would have defined Companies Act 1985 to, at a minimum, mean Companies Act 1985 (as amended).

(7) A certificate of incorporation given in respect of an association is conclusive evidence— (a) that the requirements of this Act in respect of registration and of matters precedent and incidental to it have been complied with, and that the association is a company authorised to be registered, and is duly registered, under this Act, and (b) if the certificate contains a statement that the company is a public company, that the company is such a company.

Section 13(7), Chapter I, Part I, Companies Act 1985 (as enacted)

(4) The certificate is conclusive evidence that the requirements of this Act as to registration have been complied with and that the company is duly registered under this Act.

Section 15(4), Part 2, Companies Act 2006 (as amended)

Given "incorporated under" either Act is conclusively proven with a certificate of incorporation, and that supersedence did not change company registration numbers, and Section 1 of the Companies Act 2006 (as amended) says every company "formed and registered under the Companies Act 1985" is included in the definition of "a company formed and registered under this Act", I am going to infer that BS 7453 Part 1 says my company automatically has an OID it can use (or if it doesn't, it would do if BS 7453 were updated).

As for the 2^24-1 limit, maybe they'll update the standard to change the limit or assign another < 2^7 node under 1.2.826.0 for longer company numbers, maybe someone will contact them, maybe someone will ignore the limit, time will tell.

Obtaining a Company OID

OK, let's make this simple.

Speaking as the legal person John Cook Limited, John Cook Limited "is deemed to be assigned" OID arc 1.2.826.0.1.11484356 per BS 7453 Part 1 Section 6, with the assumed name john-cook (iso.member-body.gb.national.eng-ltd.john-cook).

OID arc 1.2.826.0.1.11484356.0 is reserved.

OID arc 1.2.826.0.1.11484356.1 has been allocated by John Cook Limited to the named individual (company director and natural person) John Cook for any purpose (no restrictions on use), with assumed name john-cook (iso.member-body.gb.national.eng-ltd.john-cook.john-cook).

It is done.

Partitioning My OID

While I was pondering this issue and looking at other OID arcs for suggestions, I noticed a couple of 1.2.826.0 OIDs had a PKI node:

While I don't have any plans on creating medical devices, I noted that DICOM place a 64 character limit on OIDs. 1.2.826.0.1.11484356.1 is 22 characters, a UUID in decimal is a maximum of 39 characters, and a . separator is 1 character. That's 62 characters, leaving 2 characters to spare. A single digit child node of 1.2.826.0.1.11484356.1 should probably be reserved for DICOM/UUIDs that follow the 2.25 definition. I've still got 10 to spare, so it isn't something I need to think much about yet.

After spending some time looking at the RFCs for {iso(1) identified-organization(3) dod(6) internet(1) security(5) mechanisms(5) id-pkix(7)} and its siblings, I considered the PKIX OID arcs to be a suitable structure for custom X509v3 extended key purpose identifiers (and other custom X509v3 OID needs).

Knowing I want an id-pkix or pkix or similar node, I then asked the question of how much of the IETF/IANA tree I want to emulate, and what the parent structure should be. Whilst trying to answer that question I pondered what X509v3 and PKI are in a broadest sense, and the answer: infosec.

Creating an infosec OID arc with id-pkix as a child node and first level child nodes of id-pkix automatically following the numbering of child nodes of {iso(1) identified-organization(3) dod(6) internet(1) security(5) mechanisms(5) id-pkix(7)} would make things simple. Information security is a very broad category that not only encompasses encryption, but it includes data backups and archiving, authentication, and identity. Active Directory, LDAP, RADIUS, Kerberos, etc. are all infosec topics.

The following OIDs have been assigned: