Closed nstilt1 closed 4 months ago
I'm afraid the solution might not be as simple as this. I feel like I need to recap the purpose of "versions" for both myself, and anyone who is wondering "What is the point of versions for an ID"
Versions provide 2 functionalities:
For expiring IDs, the version is associated with an "epoch" which is essentially a reference point in time for the expiration timestamps (timestamp in ID = (expiration_time - version_epoch) >> precision_loss
. The epoch helps us achieve a smaller timestamp size, but it still requires a little extra space for the version number.
When computing the MAC of IDs, as well as private keys, a version "salt" is added in the computation. This has a similar effect of using different MAC keys and HKDF keys, but it is a little easier and more efficient since we don't need to reinitialize the MAC and HKDF when dealing with IDs with different versions. It is probably slightly less secure than using new keys for different versions, but I believe this will suffice for most use-cases, especially given that there aren't any known feasible attacks against hash functions, even with quantum computers.
Versions probably should not be opted out of for ID types, as it would add quite a bit of complexity to an already-somewhat-complex library to make them optional for ID types... but if they were to be optional, then the ID also wouldn't be able to contain a timestamp given that timestamps have a dependency on the version epoch.
As for VERSION_LIFETIME
... I intended for VERSION_LIFETIME
to be about a year long because I feel like it is logical that HKDF/MAC rekeying is something that shouldn't need to be done very often. But if a VERSION_LIFETIME
was much shorter than this, it would allow for an insanely complex (and possibly unnecessarily complex) cryptographic ID system... I will allow for low VERSION_LIFETIME
values because it is cool, but the cryptographic algorithms/libraries this library uses are strong enough to not need daily, weekly, or even monthly HKDF rekeying.
If anyone has any comments on this subject, the floor is yours, and I'll try to work on these constant parameters a little bit more to try to ensure safety and compile-time validation...
The issue
With the
VersioningConfig
, a version lifetime can be specified by a user that determines how frequently versions change. The lower bound for this is currently 600 seconds for flexibility, and IDs' constant parameters independently determine how many bits are allotted for the version number.With the current configuration, someone could make a
KeyGenerator
that switches versions every 10 minutes, and they can use multiple ID types that have a different amount of bits reserved for the version. If there's one ID that can store a 32-bit version and another ID that can store a 5-bit version... the code will kind of break after about 5.3 hours, and it would be a little challenging to prevent users from doing this without making some changes.Potential solutions
I think the solution for this is to change where the consts are defined. I want to move
const VERSION_BITS: u8
to theKeyGenerator
'sVersioningConfig
, and then add aconst USE_VERSIONS: bool
inBinaryId
. This way, a user could opt-out of having a version encoding in an ID, but if they opt-in, it will always be the same size, and it will be able to be validated at compile time. I could add another const (likeconst BREAKING_POINT_YEARS: u64
) that offers a compile-time validation that the user's chosen parameters will last for a minimum ofBREAKING_POINT_YEARS
.I also want to move
REQUIRE_EXPIRING_KEYS
from theVersioningConfig
to aBinaryId
. This way, if a user wants to require a specific ID type to expire, but not necessarily every type of key ID, then they will be able to do so.I kind of want to validate timestamp sizes as well because the timestamps will need to be able to represent at least
VERSION_LIFETIME + MAX_KEY_EXPIRATION_TIME
seconds.