GithubHelp home page GithubHelp logo

yesodweb / clientsession Goto Github PK

View Code? Open in Web Editor NEW
24.0 24.0 15.0 108 KB

Stores web session data in a client cookie, protected with authentication and encryption.

License: MIT License

Haskell 100.00%

clientsession's People

Contributors

andreasabel avatar jmazon avatar lpsmith avatar meteficha avatar mietek avatar pbrisbin avatar snoyberg avatar softmechanics avatar sol avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

clientsession's Issues

getKey and getKeyEnv silently default.

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

Use AEAD's GCM / OCB instead of encryption+MAC.

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 ?

SIGSEGV on getKey using nixOs 19.09

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) +++

HMAC SHA256 support?

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?

Support for key rotation, new key API

Currently there are two options available to clientsession users:

  1. Use the same key forever and ever. Easy to do, convenient for users (who won't get their sessions invalidated due to key changes), but not a security best practice.
  2. Rotate keys periodically. Completely manual at this point (I don't think anyone ever implemented this on their own), invalidates all sessions at once (no support for more than one key simultaneously).

The goal is to remove both drawbacks from option 2 above:

  • Automatic key rotation implemented by the library: harder, see below.
  • Support for more than one key: this one should be easy, but does require a change in API.

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:

  • A new key gets generated a X time before new sessions start to get encrypted by it. (By whom, you may ask: a simple solution would be "by 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.)
  • The new key, together with all valid old keys, is saved on the database.
  • All clientsession servers periodically update their keyring from the one at the database.
  • After said X time have passed, all servers are assumed to have gotten the new keyring and the new key starts to be used. Old ones that are at most Y time old still are accepted.

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.

Please eliminate the crypto-aes dependency (probably by moving to cryptonite)

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.

Maybe getKey should chmod 600 the key file

...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.

Export randomKey

The randomKey function is probably more useful than initKey. Perhaps it is merely an oversight that randomKey is hidden?

Cannot install Skein on Windows

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.

Build is broken by cipher-aes-0.2.0

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'

Hide all IVs from the API

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.

can't build 0.8.2

❤ 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

Is it necessary to have a function in the Key?

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?

Check if using SipHash would improve performance

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.