Repository navigation
security: hash refresh tokens at rest (logs everyone out once) #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
src/main/resources/db/migration/postgresql/V8__hash_refresh_tokens.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| -- | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is all this yap - bruh |
||
| -- V8: invalidate every pre-existing refresh token, because refresh_tokens.token_hash | ||
| -- used to hold the raw token rather than a hash of it. | ||
| -- | ||
| -- See the SQLite twin for the full reasoning. In short: the column named token_hash stored | ||
| -- the base64 SecureRandom value verbatim and redemption was an equality lookup against it, | ||
| -- so any read of this table yielded directly replayable 7-day sessions. The service now | ||
| -- stores sha256(pepper || plaintext) hex-encoded and looks up by that digest, matching | ||
| -- VerificationTokenService and EnrollmentTokenService. | ||
| -- | ||
| -- Deleting rather than rehashing in place is deliberate. Postgres could do the rehash with | ||
| -- pgcrypto's digest(), but that would require the extension on every deployment target and, | ||
| -- more to the point, those values have sat at rest in plaintext for the life of the table: | ||
| -- rehashing keeps any already-leaked copy valid for another 7 days. Deleting is the clean | ||
| -- cut. Nothing here can be salvaged anyway -- an old token cannot match a hash column. | ||
| -- | ||
| -- Consequence: everyone with a live session is logged out once, at deploy. Access tokens | ||
| -- (15 min) keep working until they expire; the next refresh attempt returns 401 and the | ||
| -- client re-authenticates. No user data is touched. | ||
| -- | ||
| -- No schema change is needed: token_hash is already varchar(255) not null unique, and a | ||
| -- SHA-256 hex digest is 64 chars. | ||
| -- | ||
| -- The SQLite twin of this file must stay at the same version. | ||
|
|
||
| delete from refresh_tokens; | ||
33 changes: 33 additions & 0 deletions
33
src/main/resources/db/migration/sqlite/V8__hash_refresh_tokens.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| -- | ||
| -- V8: invalidate every pre-existing refresh token, because refresh_tokens.token_hash | ||
| -- used to hold the raw token rather than a hash of it. | ||
| -- | ||
| -- RefreshTokenService generated 64 bytes of SecureRandom, base64'd it, handed it to the | ||
| -- client, and stored that exact string in a column named token_hash. Redemption was a | ||
| -- direct equality lookup against the value the client holds, so anything that could read | ||
| -- the table -- a backup, a replica, SQL injection elsewhere, ORM debug logging -- came away | ||
| -- with usable 7-day sessions needing no further work. The service now stores | ||
| -- sha256(pepper || plaintext) hex-encoded, matching VerificationTokenService and | ||
| -- EnrollmentTokenService, and looks up by that digest. | ||
| -- | ||
| -- The rows already in the table are plaintext and cannot be distinguished from hashes by | ||
| -- the lookup path -- a client presenting its old token would hash it, miss, and be rejected | ||
| -- anyway. Rather than leave dead plaintext secrets sitting in the table, they are deleted. | ||
| -- | ||
| -- We could instead have rehashed in place (update ... set token_hash = sha256(token_hash)), | ||
| -- which would have kept every session alive. We deliberately did not: neither SQLite nor a | ||
| -- stock Postgres install has SHA-256 available in plain SQL without an extension, and more | ||
| -- importantly those values have been at rest in plaintext for the life of the table. Any | ||
| -- copy already taken stays valid for up to 7 days if we preserve them. Deleting is the | ||
| -- clean cut. | ||
| -- | ||
| -- Consequence: everyone with a live session is logged out once, at deploy. Access tokens | ||
| -- (15 min) keep working until they expire; the next refresh attempt returns 401 and the | ||
| -- client re-authenticates. No user data is touched. | ||
| -- | ||
| -- No schema change is needed: token_hash is already varchar(255) not null unique, and a | ||
| -- SHA-256 hex digest is 64 chars. | ||
| -- | ||
| -- The postgres twin of this file must stay at the same version. | ||
|
|
||
| delete from refresh_tokens; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.