RFC9580 says that:
Argon2 is only used with AEAD (S2K usage octet 253). An
implementation MUST NOT create and MUST reject as malformed any
secret key packet where the S2K usage octet is not AEAD (253) and
the S2K specifier type is Argon2.
Therefore, we disallow reading and writing Argon2 keys without AEAD.
And:
[The Simple and Salted S2K methods] are used only for reading in
backwards compatibility mode.
Since v6 keys don't need backwards compatibility, we also disallow
reading Simple S2K there. We still allow reading Salted S2K since the
spec says it may be used "when [the password] is high entropy".
Parsing of v5 keys, v5 signatures and AEAD-encrypted data packets now requires turning on
the corresponding config flag.
The affected entities are non-standard, and in the crypto-refresh RFC they have been superseded by
v6 keys, v6 signatures and SEIPDv2 encrypted data, respectively.
However, generation of v5 entities was supported behind config flag in OpenPGP.js v5, and some other libraries,
hence parsing them might be necessary in some cases.
The config option must be set when reading v4 private keys (e.g. those
generated in OpenPGP.js by default, without setting `config.v5Keys = true`)
which were encrypted by OpenPGP.js v5 (or older) using `config.aeadProtect = true`.
Otherwise, key parsing and/or key decryption will fail.
Additional context: OpenPGP.js up to v5 used to support encrypting v4 keys
using AEAD as specified by draft RFC4880bis
(https://www.ietf.org/archive/id/draft-ietf-openpgp-rfc4880bis-10.html#section-5.5.3-3.5).
Said AEAD mechanism was not standardized as-is, and it's been replaced in the
crypto-refresh with a new version that guarantees full key integrity on decryption.
The legacy AEAD format is incompatible, but fundamentally indistinguishable,
from that of the crypto-refresh for v4 keys. Thus, we rely on the caller to
instruct us to process the key as legacy, via the new config flag.
Co-authored-by: Daniel Huigens <d.huigens@protonmail.com>
Compared to v5 keys, v6 keys contain additional length fields to aid in
parsing the key, but omit the secret key material length field.
Additionally, unencrypted v6 secret key packets don't include the count
of the optional fields, as per the updated crypto refresh. Since they
are always absent, the count is not needed.
Finally, unencrypted v6 secret keys do not include the two-byte checksum.
In terms of API, this feature is backwards compatible, no breaking changes.
However, since a Wasm module is loaded for the Argon2 computation, browser apps
might need to make changes to their CSP policy in order to use the feature.
Newly introduced config fields:
- `config.s2kType` (defaulting to `enums.s2k.iterated`): s2k to use on
password-based encryption as well as private key encryption;
- `config.s2kArgon2Params` (defaulting to "uniformly safe settings" from Argon
RFC): parameters to use on encryption when `config.s2kType` is set to
`enums.s2k.argon2`;
Such keys are still capable of encryption and signature verification.
This change is relevant for forward compatibility of v4 keys encrypted using e.g. argon2.
The changes do not affect the public API:
`RandomBuffer` was used internally for secure randomness generation before
`crypto.getRandomValues` was made available to WebWorkers, requiring
generating randomness in the main thread.
As a result of the change, the internal `getRandomBytes()` and some functions
that use it are no longer async.
The relevant packets will be considered unsupported instead of malformed.
Hence, parsing them will succeed by default (based on
`config.ignoreUnsupportedPackets`).
Breaking change: `openpgp.encryptKey` now throws if an empty string is given as
passphrase. The operation used to succeed, but the resulting key was left in an
inconsistent state, and e.g. serialization would not be possible.
Non-breaking changes:
- `options.passphrase` in `generateKey` and `reformatKey` now defaults to
`undefined` instead of empty string. Passing an empty string does not throw for
now, but this might change in the future to align with `encryptKey`'s
behaviour.
- In TS, add `GenerateKeyOptions` as alias of `KeyOptions`, to clarify its
scope.
In several packet classes, we used to store string identifiers for public-key,
aead, cipher or hash algorithms. To make the code consistent and to avoid
having to convert to/from string values, we now always store integer values
instead, e.g. `enums.symmetric.aes128` is used instead of `'aes128'`.
This is not expected to be a breaking change for most library users. Note that
the type of `Key.getAlgorithmInfo()` and of the session key objects returned
and accepted by top-level functions remain unchanged.
Affected classes (type changes for some properties and method's arguments):
- `PublicKeyPacket`, `PublicSubkeyPacket`, `SecretKeyPacket`,
`SecretSubkeyPacket`
- `SymEncryptedIntegrityProtectedDataPacket`, `AEADEncryptedDataPacket`,
`SymmetricallyEncryptedDataPacket`
- `LiteralDataPacket`, `CompressedDataPacket`
- `PublicKeyEncryptedSessionKey`, `SymEncryptedSessionKeyPacket`
- `SignaturePacket`
Other potentially breaking changes:
- Removed property `AEADEncryptedDataPacket.aeadAlgo`, since it was redudant
given `.aeadAlgorithm`.
- Renamed `AEADEncryptedDataPacket.cipherAlgo` -> `.cipherAlgorithm`
- Make fingerprint and key ID computation async, and rely on Web Crypto
for hashing if available
- Always set fingerprint and keyID on key parsing / generation
- Introduce `*KeyPacket.computeFingerprint()` and
`*KeyPacket.computeFingerprintAndKeyID()`
- Change `getKeyID` and `getFingerprint*` functions to return the
pre-computed key ID and fingerprint, respectively
- Make `PublicKeyPacket.read` async
To encrypt/decrypt a key, the top-level functions `openpgp.encryptKey` and
`openpgp.decryptKey` should be used instead: these don't mutate the key;
instead, they either return a new encrypted/decrypted key object or throw an
error.
With `Key.prototype.encrypt` and `decrypt`, which mutated the key, it was
possible to end up in an inconsistent state if some (sub)keys could be
decrypted but others couldn't, they would both mutate the key and throw an
error, which is unexpected.
Note that the `keyID` parameter is not supported by `encryptKey`/`decryptKey`,
since partial key decryption is not recommended. If you still need to decrypt
a single subkey or primary key `k`, you can call `k.keyPacket.decrypt(...)`,
followed by `k.keyPacket.validate(...)`. Similarly, for encryption, call
`k.keyPacket.encrypt(...)`.
Additionally, `openpgp.generateKey` now requires `options.userIDs` again,
since otherwise the key is basically unusable. This was a regression from v4,
since we now allow parsing keys without user IDs (but still not using them).
When unencrypted secret key packets are serialized, a 2-byte checksum is
appended after the key material. According to rfc4880bis, these 2 bytes are
not included in the length of the key material (this encoded length is a new
addition of rfc4880bis, specific to v5 keys). We erroneously included them,
causing other implementations to fail to parse unencrypted v5 private keys
generated by OpenPGP.js.
- Use PascalCase for classes, with uppercase acronyms.
- Use camelCase for function and variables. First word/acronym is always
lowercase, otherwise acronyms are uppercase.
Also, make the packet classes' `tag` properties `static`.
* Rename `config.ignoreMdcError` to `config.allowUnauthenticatedMessages`
* Do not support creating sym. enc. messages without integrity protection
* Use `config.aeadProtect` to determine SKESK encryption mode
Refactor functions to take the configuration as a parameter.
This allows setting a config option for a single function call, whereas
setting `openpgp.config` could lead to concurrency-related issues when
multiple async function calls are made at the same time.
`openpgp.config` is used as default for unset config values in top-level
functions.
`openpgp.config` is used as default config object in low-level functions
(i.e., when calling a low-level function, it may be required to pass
`{ ...openpgp.config, modifiedConfig: modifiedValue }`).
Also,
- remove `config.rsaBlinding`: blinding is now always applied to RSA decryption
- remove `config.debug`: debugging mode can be enabled by setting
`process.env.NODE_ENV = 'development'`
- remove `config.useNative`: native crypto is always used when available
`key.isDecrypted()` now returns true if either the primary key or any subkey
is decrypted.
Additionally, implement `SecretKeyPacket.prototype.makeDummy` for encrypted
keys.
- Changes `openpgp.generateKey` to accept an explicit `type` parameter,
instead of inferring its value from the `curve` or `rsaBits` params
- Introduces `config.minRsaBits` to set minimum key size of RSA key generation
- Remove the boolean return value of various internal functions that throw on
error (the returned value was unused in most cases)
- Update and fix type definitions
- Store private and public params separately and by name in objects,
instead of as an array
- Do not keep params in MPI form, but convert them to Uint8Arrays when
generating/parsing the key
- Modify low-level crypto functions to always accept and return
Uint8Arrays instead of BigIntegers
- Move PKCS1 padding to lower level functions
Both those with a 2-byte hash (instead of SHA1 or an AEAD authentication
tag) and those without an S2K specifier (i.e., using MD5 for S2K) -
support for the latter was already broken.
Vulnerabilities can arise not just from generating keys like this, but
from using them as well (if an attacker can tamper with them), hence why
we're removing support.
Also, when generating RSA keys in JS, generate them with p < q, as per
the spec.
Also, when generating RSA keys using Web Crypto or Node crypto, swap the
generated p and q around, so that will satisfy p < q in most browsers
(but not old Microsoft Edge, 50% of the time) and so that we can use the
generated u coefficient (p^-1 mod q in OpenPGP, q^-1 mod p in RFC3447).
Then, when signing and verifying, swap p and q again, so that the key
hopefully satisfies Safari's requirement that p > q, and so that we can
keep using u again.