DocumentationDevice AppExplanationCryptography general concepts

Cryptography

This section presents general concepts about cryptography development, but also guidelines specific to the security model of the Ledger devices. It gives guidelines to:

  • Ensure a potential vulnerability in one application will not cause damages to other apps.
  • Make sure all the operations that manipulate secrets are approved by the user.
  • Restrict the use of these secrets by apps.

Never store or export secrets derived from seed

The seed allows the user to derive any key from a given path at will. The user can reconstruct this secret from a new device by reentering its seed if he were to lose the original device.

Generating secrets must be done on demand. An application must not store a secret derived from the seed, and should regenerate it instead then erased when it is not used anymore.

Even in encrypted form, those secrets must not be stored outside of the device. They can be regenerated instead.

Avoid blindly signing data

You should never allow signing of any attacker-controlled message unless it has been verified for structural validity. Importantly, you should never sign a message that might be a hash of transaction.

Rationale: If you allow an attacker to blindly sign a message, it can easily supply a hash of a valid transaction. Your signature could then be used to send an unauthorized transaction.

If you want to sign user-supplied “personal” messages, prefix them with a fixed string (which shouldn’t be a valid transaction prefix). It is also a good practice to include message length in the text to be signed. Ledger-app-eth has a good example in function handleSignPersonalMessage. Note that sometimes cryptocurrencies have a standardized way of signing such personal messages and in that case you should use the approved scheme.

Warning: If you allow signing untrusted hashes (while displaying a prompt to the user), be aware that

  1. Some users are not familiar with security and could be easily tricked. They can click through your prompt without properly checking unless you give them explicit “Warning: this is a very unusual operation. Do not continue unless you know what you are doing” warning. They might not see the message even then.
  2. A compromised host might both change hash on the screen and also data sent to device. This opens the possibility of users signing something they didn’t want to.

Signing/disclosing keys

⚠️

You must always require user approval for signing transactions/messages.

Rationale: If you do not require user consent for signing important data, an attacker can use your device as a signing black box and sign whatever they want.

ℹ️

You might also consider approvals for extracting public keys, as some users might want extended privacy.

  1. They might not want to reveal their root/account public key, only address keys
  2. They might not want to reveal address public key until it is required. (Some cryptocurrencies use addresses that are hash of public keys. It is therefore enough to send the address to the host).
ℹ️

There is a trade-off between privacy and usability here. If you want privacy, it would require a user interaction every time they want to use Ledger device, as opposed to only interaction while signing transactions. The behaviour could also be manually set in the application options.

Don’t roll your own crypto primitives

You should never roll your own crypto primitives (including encryption/derivation schemes, hashing functions, HMAC, etc.)

Rationale: It is a purpose of Ledger OS operating system to perform these in a secure manner. Importantly, writing your own crypto primitives is likely to open you to side-channel attacks or other problems. If your primitive is not supported by Ledger OS (e.g., some very new cryptography), consult with Ledger developers the possibility of including it in the OS.

Avoid Exceptions for Cryptographic Code

From the 2.0 version of the SDK every cryptographic function has a version that returns an error code instead of raising an exception. As an example cx_ecdsa_sign_no_throw performs the same computation as cx_ecdsa_sign but does not raise any exception and returns CX_OK if everything went fine.

We enforce using all _no_throw equivalents when available, as the ones raising exceptions are deprecated in a future SDK release.

Rationale: the exception mechanism is not standard C, and is difficult to use, particularly for error management. There were errors related to this exception model in every single app we reviewed in 2020.

Private Key Management

You should minimize the code that works with private (ECDSA, RSA, etc.) or secret (HMAC, AES, etc.) keys. Importantly, you should always clear the memory after you use these keys. That includes key data and key objects.

Leaving parts of private or secret keys lying around in memory is not a security issue on its own because there is no easy way to extract the RAM content on the chip. If a key is left in RAM by an app, another app will not be able to access it.

However, if the key has not properly been erased, a security issue could lead to the leak of this key, even if it is not used anymore. An attacker able to read arbitrary memory from the app, or execute arbitrary code, will be able to read the content of the stack segment, hence the parts of the key which have not been erased.

Here is an example of a common and wrong way of doing this:

uint8_t privateKeyData[64];
cx_ecfp_private_key_t privateKey;
 
os_perso_derive_node_bip32(
    tmpCtx.transactionContext.curve, tmpCtx.transactionContext.bip32Path,
    tmpCtx.transactionContext.pathLength, privateKeyData,
    NULL);
cx_ecfp_init_private_key(tmpCtx.transactionContext.curve, privateKeyData,
                         32, &privateKey);
explicit_bzero(privateKeyData, sizeof(privateKeyData));
 
// (later, after privateKey is not needed)
explicit_bzero(&privateKey, sizeof(privateKey));

In the happy path, the previous code will correctly clean the memory once the private key is initialized. Note, however, that this code fails to protect private keys in case some system call throws (for example cx_ecfp_init_private_key). Correct code should wrap the clearing in BEGIN_TRY { TRY { ... } FINALLY { explicit_bzero() } END_TRY;.

Applications where such issues were fixed include the ARK app and the Solana app.

Ledger
Copyright © Ledger SAS. All rights reserved. Ledger, Ledger Nano S, Ledger Vault, Ledger OS are registered trademarks of Ledger SAS