From d77803b68707894b01c2cd95e0c45533a2240f46 Mon Sep 17 00:00:00 2001 From: Jay McCarthy Date: Sat, 20 Oct 2012 19:40:32 -0600 Subject: [PATCH] Adding in id-cookie library after 19th use in a Web app --- collects/web-server/http/id-cookie.rkt | 78 ++++++++++++++++++++++ collects/web-server/scribblings/http.scrbl | 78 +++++++++++++++++++++- 2 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 collects/web-server/http/id-cookie.rkt diff --git a/collects/web-server/http/id-cookie.rkt b/collects/web-server/http/id-cookie.rkt new file mode 100644 index 0000000000..53a8c35179 --- /dev/null +++ b/collects/web-server/http/id-cookie.rkt @@ -0,0 +1,78 @@ +#lang racket/base +(require unstable/bytes + net/base64 + net/cookie + racket/match + racket/file + racket/contract + web-server/http + web-server/stuffers/hmac-sha1) + +(define (substring* s st en) + (substring s st (+ (string-length s) en))) + +(define (mac key v) + (substring* + (bytes->string/utf-8 + (base64-encode (HMAC-SHA1 key (write/bytes v)))) + 0 -3)) + +(define (make-secret-salt/file secret-salt-path) + (unless (file-exists? secret-salt-path) + (with-output-to-file secret-salt-path + (λ () + (for ([i (in-range 128)]) + (write-byte (random 256)))))) + (file->bytes secret-salt-path)) + +(define (id-cookie? name c) + (and (client-cookie? c) + (string=? (client-cookie-name c) name))) + +(define (make-id-cookie name key data) + (define authored (current-seconds)) + (define digest + (mac key (list authored data))) + (make-cookie name + (format "~a&~a&~a" + digest authored data))) + +(define (valid-id-cookie? name key timeout c) + (and (id-cookie? name c) + (with-handlers ([exn:fail? (lambda (x) #f)]) + (match (client-cookie-value c) + [(regexp #rx"^(.+)&(.+)&(.*)$" (list _ digest authored-s data)) + (define authored (string->number authored-s)) + (define re-digest (mac key (list authored data))) + (and (string=? digest re-digest) + (<= authored timeout) + data)] + [cv + #f])))) + +(define (request-id-cookie + name + key + #:timeout [timeout +inf.0] + req) + (define cookies (request-cookies req)) + (for/or ([c (in-list cookies)]) + (valid-id-cookie? name key timeout c))) + +(define (logout-id-cookie name) + (make-cookie name "invalid format")) + +(provide + (contract-out + [make-secret-salt/file + (-> path-string? + bytes?)] + [logout-id-cookie + (-> cookie-name? cookie?)] + [request-id-cookie + (->* (cookie-name? bytes? request?) + (#:timeout number?) + (or/c false/c cookie-value?))] + [make-id-cookie + (-> cookie-name? bytes? cookie-value? + cookie?)])) diff --git a/collects/web-server/scribblings/http.scrbl b/collects/web-server/scribblings/http.scrbl index a5c61aab43..88737ebb41 100644 --- a/collects/web-server/scribblings/http.scrbl +++ b/collects/web-server/scribblings/http.scrbl @@ -265,9 +265,83 @@ transmission that the server @bold{will not catch}.} `(html (head (title "Cookie Example")) (body (h1 "You're cookie'd!")))))) ] +} - @warning{When using cookies, make sure you follow the advice of the @link["http://cookies.lcs.mit.edu/"]{MIT Cookie Eaters}, - or you will be susceptible to dangerous attacks.} +@; ------------------------------------------------------------ +@section[#:tag "id-cookie"]{Authenticated Cookies} + +@(require (for-label web-server/http/id-cookie)) +@defmodule[web-server/http/id-cookie]{ + +Cookies are useful for storing information of user's browsers and +particularly useful for storing identifying information for +authentication, sessions, etc. However, there are inherent +difficulties when using cookies as authenticators, because cookie data +is fully controlled by the user, and thus cannot be trusted. + +This module provides functions for creating and verifying +authenticated cookies that are intrinsically timestamped. It is based +on the algorithm proposed by the +@link["http://cookies.lcs.mit.edu/"]{MIT Cookie Eaters}: if you store +the data @racket[_data] at thime @racket[_authored-seconds], then the +user will receive @litchar{digest&authored-seconds&data}, where +@racket[_digest] is an HMAC-SHA1 digest of @racket[_authored-seconds] +and @racket[_data], using an arbitrary secret key. When you receive a +cookie, it will reverify this digest and check that the cookie's +@racket[_authored-seconds] is not after a timeout period, and only +then return the cookie data to the program. + +The interface represents the secret key as a byte string. The best way +to generate this is by using random bytes from something like OpenSSL +or +@tt{/dev/random}. @link["http://www.madboa.com/geek/openssl/#random-generate"]{This +FAQ} lists a few options. A convenient purely Racket-based option is +available (@racket[make-secret-salt/file]), but it will not have as +good entropy, if you care about that sort of thing. + + @defproc[(make-id-cookie + [name cookie-name?] + [secret-salt bytes?] + [value cookie-value?]) + cookie?]{ + Generates an authenticated cookie named @racket[name] containing @racket[value], signed with @racket[secret-salt]. + } + + @defproc[(request-id-cookie + [name cookie-name?] + [secret-salt bytes?] + [request request?] + [#:timeout timeout +inf.0]) + (or/c false/c cookie-value?)]{ + Extracts the first authenticated cookie named @racket[name] that was previously signed with @racket[secret-salt] before @racket[timeout] from @racket[request]. If no valid cookie is available, returns @racket[#f]. + } + + @defproc[(logout-id-cookie + [name cookie-name?]) + cookie?]{ + Generates a cookie named @racket[name] that is not validly authenticated. + + This will cause non-malicious browsers to overwrite a previously set +cookie. If you use authenticated cookies for login information, you +could send this to cause a "logout". However, malicious browsers do +not need to respect such an overwrite. Therefore, this is not an +effective way to implement timeouts or protect users on +public (i.e. possibly compromised) computers. The only way to securely +logout on the compromised computer is to have server-side state +keeping track of which cookies (sessions, etc.) are invalid. Depending +on your application, it may be better to track live sessions or dead +sessions, or never set cookies to begin with and just use +continuations, which you can revoke with @racket[send/finish]. + } + + @defproc[(make-secret-salt/file + [secret-salt-path path-string?]) + bytes?]{ + + Extracts the bytes from @racket[secret-salt-path]. If +@racket[secret-salt-path] does not exist, then it is created and +initialized with 128 random bytes. + } } @; ------------------------------------------------------------