yesodweb / clientsession Goto Github PK
View Code? Open in Web Editor NEWStores web session data in a client cookie, protected with authentication and encryption.
License: MIT License
Stores web session data in a client cookie, protected with authentication and encryption.
License: MIT License
Caught by a CI run: https://github.com/haskell-hvr/http-io-streams/actions/runs/5599585796/jobs/10240745967?pr=12#step:17:503
Building library for clientsession-0.9.2.0..
src/Web/ClientSession.hs:65:8:
Could not find module ‘Data.Bifunctor’
Perhaps you meant Data.Functor (from base)
There is a problem with the getKey and getKeyEnv from clientsession: they both silently default to generating a new key when one is missing.
Can we change these functions (actually add new ones) that essentially return an Either? Left would be a default, and in that case a warning can be logged for the user.
I would suggest as new function names keyFromFile
and keyFromEnv
.
However, this points to the fact that some kind of generic key generator is needed. Also, Either may not be descriptive enough.
data KeyGenerator = KeyGenerator { gatherKey :: IO ByteString, writeKey :: ByteString -> IO () }
data KeyInit = KeyGenerated Text Key -- ^ no key was found, generated and recorded a new one
| KeyFound Key -- ^ found the existing key
keyFrom :: KeyGenerator -> KeyInit
keyFromDef :: KeyGenerator -> Key
The AES-CTR+Skein-MAC method used by clientsession could be replaced by AES-GCM or AES-OCB, and in theory it would be much faster (practically current version have lot of room for optimisations). Provided I send a PR for this, does clientsession need to remain backward compatible, and is that a good idea to provide different choosable alternatives to the user ?
I am using Nixos 19.09 on an Intel i9900k.
When running following Snippet we run into a SIGSEGV and ghci quits.
store <- fmap clientsessionStore (ClientSession.getKey "Config/client_session_key.aes")
let sessionMiddleware :: Middleware = withSession store "SESSION" (def { Web.Cookie.setCookiePath = Just "/", Web.Cookie.setCookieMaxAge = Just (fromIntegral (60 * 60 * 24 * 30)) }) session
Warp.run 8000 $ sessionMiddleWare $ application
strace shows following trace on ghci-iserv:
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, [INT QUIT], [], 8) = 0
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, {tv_sec=31, tv_nsec=589699342}) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, [INT QUIT], [], 8) = 0
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, {tv_sec=31, tv_nsec=590509168}) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, [INT QUIT], [], 8) = 0
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, {tv_sec=31, tv_nsec=591303930}) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
brk(0x10f6a000) = 0x10f6a000
rt_sigprocmask(SIG_BLOCK, [INT QUIT], [], 8) = 0
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, {tv_sec=31, tv_nsec=592342887}) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
poll([{fd=12, events=POLLOUT}], 1, 0) = 1 ([{fd=12, revents=POLLOUT}])
write(12, "\0\0\0\0\0\0003\310\0\0\0\0\0\0\3\360\0\0\0\0\0\0\3\347\0\0\0\0\0\0\3\357"..., 4080) = 4080
poll([{fd=12, events=POLLOUT}], 1, 0) = 1 ([{fd=12, revents=POLLOUT}])
write(12, "\0\0\0\0\0\0\0\304\0\0\0\0\0\0\0\303\0\0\0\0\0\0\0\302\0\0\0\0\0\0\0\301"..., 32752) = 32752
poll([{fd=12, events=POLLOUT}], 1, 0) = 1 ([{fd=12, revents=POLLOUT}])
write(12, "\0\0\0\0\0\0\24N\0\0\0\0\0\0\24O\0\0\0\0\0\0\24P\0\0\0\0\0\0\24Q"..., 32752) = 32752
poll([{fd=12, events=POLLOUT}], 1, 0) = 1 ([{fd=12, revents=POLLOUT}])
write(12, "\0\0\0\0\0\0$L\0\0\0\0\0\0$M\0\0\0\0\0\0$N\0\0\0\0\0\0$O"..., 32752) = 32752
poll([{fd=12, events=POLLOUT}], 1, 0) = 1 ([{fd=12, revents=POLLOUT}])
write(12, "\0\0\0\0\0\0004J\0\0\0\0\0\0004K\0\0\0\0\0\0004L\0\0\0\0\0\0004M"..., 3720) = 3720
poll([{fd=13, events=POLLIN}], 1, 0) = 0 (Timeout)
epoll_ctl(4, EPOLL_CTL_MOD, 13, {EPOLLIN|EPOLLONESHOT, {u32=13, u64=2203180783894541}}) = 0
futex(0x1c28688, FUTEX_WAIT_PRIVATE, 0, NULL) = 0
futex(0x1c28690, FUTEX_WAKE_PRIVATE, 1) = 0
futex(0x1c287a8, FUTEX_WAKE_PRIVATE, 1) = 1
futex(0x1c287b0, FUTEX_WAKE_PRIVATE, 1) = 1
futex(0x16e8808, FUTEX_WAKE_PRIVATE, 1) = 1
read(13, "\r\0\0\0\0\0\0'\364\0\0\0\0\0\0\3\360\0\0\0\0\0\0\3\357\0\0\0\0\0\0\3"..., 32768) = 32768
poll([{fd=13, events=POLLIN}], 1, 0) = 1 ([{fd=13, revents=POLLIN}])
read(13, "\27\0\0\0\0\0\0\26\30\0\0\0\0\0\0\26\31\0\0\0\0\0\0\26\32\0\0\0\0\0\0\26"..., 32768) = 4057
poll([{fd=13, events=POLLIN}], 1, 0) = 1 ([{fd=13, revents=POLLIN}])
read(13, "\0\0\0\0\0\0\30>\0\0\0\0\0\0\30?\0\0\0\0\0\0\30A\0\0\0\0\0\0\30B"..., 32768) = 32752
rt_sigprocmask(SIG_BLOCK, [INT QUIT], [], 8) = 0
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, {tv_sec=31, tv_nsec=593419183}) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
poll([{fd=13, events=POLLIN}], 1, 0) = 1 ([{fd=13, revents=POLLIN}])
read(13, "\0\0\0\0\0\0/p\0\0\0\0\0\0/q\0\0\0\0\0\0/r\0\0\0\0\0\0/s"..., 32768) = 12256
poll([{fd=13, events=POLLIN}], 1, 0) = 0 (Timeout)
epoll_ctl(4, EPOLL_CTL_MOD, 13, {EPOLLIN|EPOLLONESHOT, {u32=13, u64=103095986951290893}}) = 0
futex(0x1c2868c, FUTEX_WAIT_PRIVATE, 0, NULL) = 0
futex(0x1c28690, FUTEX_WAKE_PRIVATE, 1) = 0
futex(0x1c287ac, FUTEX_WAKE_PRIVATE, 1) = 1
read(13, "\2\0\0\0\0\0\0\0\35base_GHCziBase_bindIO_c"..., 32768) = 38
poll([{fd=12, events=POLLOUT}], 1, 0) = 1 ([{fd=12, revents=POLLOUT}])
futex(0x1c287a8, FUTEX_WAKE_PRIVATE, 1) = 1
write(12, "\1\0\0\0\0@Y\4P", 9) = 9
poll([{fd=13, events=POLLIN}], 1, 0) = 0 (Timeout)
epoll_ctl(4, EPOLL_CTL_MOD, 13, {EPOLLIN|EPOLLONESHOT, {u32=13, u64=458561288688107533}}) = 0
futex(0x1c287ac, FUTEX_WAKE_PRIVATE, 1) = 1
futex(0x1c28688, FUTEX_WAIT_PRIVATE, 0, NULL) = 0
futex(0x1c28690, FUTEX_WAKE_PRIVATE, 1) = 0
futex(0x1c287a8, FUTEX_WAKE_PRIVATE, 1) = 1
futex(0x1c287b0, FUTEX_WAKE_PRIVATE, 1) = 1
futex(0x16e8808, FUTEX_WAKE_PRIVATE, 1) = 1
read(13, "\f\0\0\0\0\0\0\0\1\0\0\0\0\0\0\1\245\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0"..., 32768) = 438
poll([{fd=12, events=POLLOUT}], 1, 0) = 1 ([{fd=12, revents=POLLOUT}])
write(12, "\0\0\0\0\0\0\0\1\0\0\0\0\0\0006\30", 16) = 16
poll([{fd=13, events=POLLIN}], 1, 0) = 0 (Timeout)
epoll_ctl(4, EPOLL_CTL_MOD, 13, {EPOLLIN|EPOLLONESHOT, {u32=13, u64=13}}) = 0
futex(0x1c2868c, FUTEX_WAIT_PRIVATE, 0, NULL) = 0
futex(0x1c28690, FUTEX_WAKE_PRIVATE, 1) = 0
futex(0x1c287ac, FUTEX_WAKE_PRIVATE, 1) = 1
futex(0x1c287b0, FUTEX_WAKE_PRIVATE, 1) = 1
futex(0x16e8808, FUTEX_WAKE_PRIVATE, 1) = 1
read(13, "\23\1\0\0\0\1\0\0\0\0\0\0\0\0 \0\0\0\0\0\0\0006\30", 32768) = 24
futex(0x1c289dc, FUTEX_WAKE_PRIVATE, 1) = 1
futex(0x1c28688, FUTEX_WAIT_PRIVATE, 0, NULL) = ?
+++ killed by SIGSEGV (core dumped) +++
I need a Haskell and Clojure application to be able to encrypt & decrypt each others' sessions.
Haskell uses Skein, but there isn't a well established or verified implementation of Skein for Java.
HMAC SHA256 is well established for both though.
I'm currently looking at having to reimplement the entire session encryption/decryption suite as well as Yesod integration thereof because I can't swap out the auth for clientsession
. Is making the auth algo pluggable something you'd be open to?
Currently there are two options available to clientsession
users:
The goal is to remove both drawbacks from option 2 above:
The biggest problem in automatic key rotation is propagating the new key to all frontend servers. Of course this is trivial if you have only one server. Here's a simple solution, just to point out that the problem is solvable without much engineering:
clientsession
users themselves", and the only small problem is not creating a bunch of new keys, which is solvable by manually increasing key ids and using DB uniqueness constraints.)clientsession
servers periodically update their keyring from the one at the database.In order to sanely implement these features, we could make a big revamp of the key API. Currently it sucks and everyone knows it. Here's the idea:
-- Not shown: a better way of generating keys.
-- | A collection of 'Key's used for encoding and decoding sessions.
data Keyring =
Keyring
{ activeKey :: Key
-- ^ The key that should be used when encoding sessions.
, otherValidKeys :: [Key]
-- ^ Other keys that may be used when decoding sessions.
-- Doesn't include the active key.
} deriving (...)
-- | A keyring backend, responsible for updating the keyring whenever
-- necessary.
newtype KeyringBackend =
KeyringBackend
{ fetchKeyring :: IO Keyring }
-- | Trivial keyring backend: uses a single key, never rotates it.
-- Same behavior as `clientsession` up to version 0.9.
trivialKeyringBackend :: Key -> KeyringBackend
trivialKeyringBackend = return . flip Keyring []
-- | Keyring backend that fetches the `Key` from the given environment
-- variable. If the environment variable doesn't exist, a new key is
-- generated and printed to stdout.
envKeyringBackend :: Text -> IO KeyringBackend
envKeyringBackend = ...
-- | Keyring backend that fetches the `Key` from the given file.
-- If the file doesn't exist, a new key is generated and saved.
-- Even though rotating keys would be possible using a file,
-- synchronizing the file between frontend servers is not trivial
-- so this feature is not implemented here.
simpleFileKeyringBackend :: FilePath -> IO KeyringBackend
simpleFileKeyringBackend = ...
This would allow us to create a clientsession-persistent
package with a keyring backend that encodes the process I've outlined above. The end result being that users of persistent, including the Yesod scaffold, would get a one-liner that does the right thing without any hassle, while still allowing users the flexibility of using other processes for key rotation.
For whatever reason (GHC upgrade?), crypto-aes has become incompatible with my CPU. As the package has been self-advertising as deprecated for years, it's probably not reasonable to patch it.
There are at least two PRs (#32, #36) to address this. Could we please get back to the conversation and move forward?
(Disclaimer: I'm the author of #36. I hadn't noticed the other one. Which, FWIW, now has conflicts. I don't care which one is chosen, or if we have to write yet another one with SystemDRG instead. Let's please just get this fixed.)
FWIW I've come to patch this because it breaks hledger-web. Other yesod apps probably impacted as well.
...and whatever is the corresponding thing on Windows systems?
I realize it's a deployment issue to ensure such files are secured, but "why risk it" when we could enforce the sensible default.
The cabal file refers to the package's homepage at http://github.com/snoyberg/clientsession/tree/master, but that page doesn't exist.
The Cabal file says BSD3
, but the license file contains a BSD2 license.
The randomKey
function is probably more useful than initKey
. Perhaps it is merely an oversight that randomKey
is hidden?
Using Haskell Platform 2013.2.0.0, GHC7.6.3, and cabal 1.18.
(this is referred to from snap-core). Skein is required by Snap. After cabal install snap
, I am getting
$ cabal install snap
Resolving dependencies...
Configuring skein-1.0.9...
cabal.exe: Missing dependency on a foreign library:
* Missing (or bad) header file: skein.h
This problem can usually be solved by installing the system package that
provides this library (you may need the "-dev" version). If the library is
already installed but in a non-standard location then you can use the flags
--extra-include-dirs= and --extra-lib-dirs= to specify where it is.
If the header file does exist, it may contain errors that are caught by the C
compiler at the preprocessing stage. In this case you can re-run configure
with the verbosity flag -v3 to see the error messages.
Failed to install skein-1.0.9
cabal.exe: Error: some packages failed to install:
clientsession-0.9.0.3 depends on skein-1.0.9 which failed to install.
skein-1.0.9 failed during the configure step. The exception was:
ExitFailure 1
snap-0.13.2.4 depends on skein-1.0.9 which failed to install.
There are no installation instructions for Windows on Skein.
A complete build log is at http://hydra.cryp.to:8080/build/7479/nixlog/1/raw. The relevant part is:
[1 of 1] Compiling Web.ClientSession ( src/Web/ClientSession.hs, dist/build/Web/ClientSession.o )
src/Web/ClientSession.hs:103:29:
Not in scope: type constructor or class `A.Key'
src/Web/ClientSession.hs:125:17:
Not in scope: type constructor or class `A.IV'
src/Web/ClientSession.hs:128:22:
Not in scope: data constructor `A.IV'
src/Web/ClientSession.hs:131:11:
Not in scope: data constructor `A.IV'
src/Web/ClientSession.hs:225:18:
Not in scope: data constructor `A.IV'
src/Web/ClientSession.hs:227:45:
Not in scope: data constructor `A.IV'
src/Web/ClientSession.hs:246:42:
Not in scope: data constructor `A.IV'
IV
, randomIV
, mkIV
and encrypt
should all be hidden inside an Internal
module. Only encryptIO
should be exposed by the API.
The reason being that we should provide an easy to use interface that makes it harder for one to shoot themselves in the foot.
❤ cabal-dev update
Downloading the latest package list from hackage.haskell.org
❤ cabal-dev install clientsession-0.8.0.2
Resolving dependencies...
Configuring clientsession-0.8.0.2...
Building clientsession-0.8.0.2...
Preprocessing library clientsession-0.8.0.2...
[1 of 1] Compiling Web.ClientSession ( src/Web/ClientSession.hs, dist/build/Web/ClientSession.o )
src/Web/ClientSession.hs:102:28:
Not in scope: type constructor or class `A.AES256'
src/Web/ClientSession.hs:124:20:
Not in scope: type constructor or class `A.AES256'
Failed to install clientsession-0.8.0.2
cabal: Error: some packages failed to install:
clientsession-0.8.0.2 failed during the building phase. The exception was:
ExitFailure 1
I'd like to be able to put the Key
in acid-state, but the current definition doesn't allow this because of the macKey
field being a function. Is that necessary? I suspect the reasoning is to make it harder to compromise the actual key from the Key
value?
First of all, we need to check how good SipHash is for MACs (I suppose it's as good as Skein, but I don't know). Then a benchmark should be done comparing clientsession with each one of them.
Besides the algorithm itself, using siphash has the advantage of not going through the FFI, which may remove some overheads as well. OTOH, GHC may generate less optimal code than GCC, which means that indeed benchmarks need to be done.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.