diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2014, David Johnson
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,165 @@
+s3-signer
+======
+s3-signer is intended to be an aid in building secure cloud-based services with
+AWS. This library generates cryptographically secure URLs that
+expire at a user-defined interval. These URLs can be used to offload
+the process of uploading and downloading large files, freeing your
+webserver to focus on other things. 
+
+### Features
+ - Minimal depedencies
+ - Web framework agnostic
+ - Reduced web server load
+ - Ideal for AJAX direct-to-s3 upload scenarios
+
+### Documentation
+[S3 Query String Request Authentication](http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth)
+
+### Implementation
+
+> AWS Specification
+
+```shell
+Signature = URL-Encode( Base64( HMAC-SHA1( YourSecretAccessKeyID,UTF-8-Encoding-Of( StringToSign ) ) ) );
+```
+> Haskell Implementation
+
+```haskell
+module Network.S3.Sign  ( sign ) where
+
+import           Crypto.Hash.SHA1       (hash)
+import           Crypto.MAC.HMAC        (hmac)
+import qualified Data.ByteString.Base64 as B64
+import           Data.ByteString.UTF8   (ByteString)
+import           Network.HTTP.Types.URI (urlEncode)
+
+-- | HMAC-SHA1 Encrypted Signature
+sign :: ByteString -> ByteString -> ByteString
+sign secretKey url = urlEncode True . B64.encode $ hmac hash 64 secretKey url
+```
+
+### Use Case
+```haskell
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import           Network.S3
+
+main :: IO ()
+main = print =<< generateS3URL credentials request
+  where
+     credentials = S3Keys "<public-key-goes-here>" "<secret-key-goes-here>"
+     request     = S3Request S3GET "bucket-name" "file-name.extension" 3 -- 3 secs until expired
+```
+### Result
+```haskell
+S3URL {
+      signedRequest =
+         "https://bucket-name.s3.amazonaws.com/file-name.extension?AWSAccessKeyId=<public-key-goes-here>&Expires=1402346638&Signature=1XraY%2Bhp117I5CTKNKPc6%2BiihRA%3D"
+     }
+```
+
+### Snap integration - Downloads
+```haskell
+-- Quick and dirty example
+type FileID = ByteString
+
+makeS3URL :: FileID -> IO S3URL
+makeS3URL fileId = generateS3URL credentials request
+  where
+    credentials = S3Keys "<public-key-goes-here>" "<secret-key-goes-here>"
+    request     = S3Request S3GET "bucket-name" (fileId <> ".zip") 3 
+
+downloadFile :: Handler App (AuthManager App) ()
+downloadFile = method POST $ currentUserId >>= maybe the404 handleDownload
+  where handleDownload uid = do
+          Just fileId <- getParam "fileId"
+          -- Ensure file being requested belongs to user else 403...
+          S3URL url <- liftIO $ makeS3URL fileId
+          redirect' url 302
+```
+### Direct to S3 AJAX Uploads
+   - Configure S3 Bucket CORS Policy settings
+   - [CORS Docs](http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html#how-do-i-enable-cors)
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
+    <CORSRule>
+        <AllowedOrigin>https://my-url-goes-here.com</AllowedOrigin>
+        <AllowedMethod>PUT</AllowedMethod>
+        <AllowedHeader>*</AllowedHeader>
+    </CORSRule>
+</CORSConfiguration>
+```
+   - Retrieve PUT Request URL via AJAX 
+
+```haskell
+type FileID = ByteString
+
+makeS3URL :: FileID -> IO S3URL
+makeS3URL fileId = generateS3URL credentials request
+  where
+    credentials = S3Keys "<public-key-goes-here>" "<secret-key-goes-here>"
+    request     = S3Request S3PUT "bucket-name" (fileId <> ".zip") 3 
+
+instance ToJSON S3URL
+
+getUploadURL :: Handler App (AuthManager App) ()
+getUploadURL = method POST $ currentUserId >>= maybe the404 handleDownload
+  where handleDownload _ = do
+          Just fileId <- getParam "fileId"
+          writeJSON =<< liftIO (makeS3URL fileId)
+```
+   - Embed FileReader blob data to request
+   - Send upload request
+
+```javascript
+var xhr = new XMLHttpRequest();
+xhr.open('PUT', url /* S3-URL generated from server */);
+xhr.setRequestHeader('Content-Type', 'application/zip'); /* whatever http-content-type makes sense */
+xhr.setRequestHeader('x-amz-acl', 'public-read');
+
+/* upload completion check */
+xhr.onreadystatechange = function(e) {
+    if (this.readyState === 4 && this.status === 200) 
+          console.log('upload complete');
+};
+
+/* Amazon gives you progress information on AJAX Uploads */
+xhr.upload.addEventListener("progress", function(evt) {
+       if (evt.lengthComputable) {
+          var v = (evt.loaded / evt.total) * 100,
+          val = Math.round(v) + '%',
+          console.log('Completed: ' + val);
+      }
+}, false);
+
+/* error handling */
+xhr.upload.addEventListener("error", function(evt) {
+   console.log("There has been an error :(");
+}, false);
+
+/* Commence upload */
+xhr.send(file); // file here is a blob from the file reader API
+```
+### File Reader Info
+[How to read file data from the browser](https://developer.mozilla.org/en-US/docs/Web/API/FileReader)
+
+### Troubleshoooting
+- Why do I keep getting 403 forbidden when I attempt to upload or  download from a pre-signed URL?
+  * Ask yourself the following:
+    - Are my keys specified correctly?
+    - Did I configure the CORS settings on my bucket properly?
+    - Still trouble? [Make an issue](https://github.com/dmjio/s3-signer/issues)
+- Why are my URLs expiring faster than the specified time?
+  * Ask yourself the following:
+    - Is my server's clock synchronized with AWS? [See wiki for NTP info](https://github.com/dmjio/s3-signer/wiki/If-URLs-expire-too-quickly)
+
+### FAQ
+- Why didn't you use HMAC-SHA256?
+  * It's 30% slower, and no less secure than HMAC-SHA1
+  
+
+
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/s3-signer.cabal b/s3-signer.cabal
new file mode 100644
--- /dev/null
+++ b/s3-signer.cabal
@@ -0,0 +1,53 @@
+name:                s3-signer
+version:             0.1.0.0
+homepage:            https://github.com/dmjio/s3-signer
+bug-reports:         https://github.com/dmjio/s3-signer/issues
+synopsis:            Pre-signed Amazon S3 URLs
+description:         
+        .
+        s3-signer creates cryptographically secure Amazon S3 URLs that expire within a user-defined         
+        period. It allows uploading and downloading of content from Amazon S3. 
+        Ideal for AJAX direct-to-s3 uploads via CORS and secure downloads. 
+        Web framework agnostic with minimal dependencies.
+        .
+        > module Main where
+        > import           Network.S3
+        > main :: IO ()
+        > main = print =<< generateS3URL credentials request
+        >   where
+        >     credentials = S3Creds "<public-key-goes-here>" "<secret-key-goes-here>"
+        >     request     = S3Request S3GET "bucket-name" "file-name.extension" 3 -- three seconds until expiration
+        . 
+        Result
+        .
+        > S3URL "https://bucket-name.s3.amazonaws.com/file-name.extension?AWSAccessKeyId=<public-key-goes-here>&Expires=1402346638&Signature=1XraY%2Bhp117I5CTKNKPc6%2BiihRA%3D"
+
+license:             BSD3
+license-file:        LICENSE
+author:              David Johnson <djohnson.m@gmail.com>
+maintainer:          David Johnson <djohnson.m@gmail.com>
+copyright:           2014 David Johnson
+category:            Network
+build-type:          Simple
+cabal-version:       >=1.18
+extra-source-files:  README.md        
+
+library
+  build-depends:       base >=4.7 && <4.8
+                     , base64-bytestring
+                     , utf8-string
+                     , http-types
+                     , cryptohash
+                     , time
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  ghc-options:        -Wall
+  exposed-modules:     Network.S3
+                     , Network.S3.Types
+  other-modules:       Network.S3.Sign
+                     , Network.S3.Time
+                     , Network.S3.URL
+
+source-repository head
+  type:     git
+  location: https://github.com/dmjio/s3-signer
diff --git a/src/Network/S3.hs b/src/Network/S3.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/S3.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+
+module Network.S3
+    ( -- * Create pre-signed AWS S3 URL
+      generateS3URL
+      -- * Types
+    , module Network.S3.Types
+    ) where
+
+import           Network.S3.Sign
+import           Network.S3.Time
+import           Network.S3.Types
+import           Network.S3.URL
+
+-- | Generates a cryptographically secure URL that expires within a
+-- user defined period
+--
+-- See README for use cases and examples: <https://github.com/dmjio/s3-signer/blob/master/README.md>
+generateS3URL :: S3Keys -- ^ Amazon S3 Keys
+              -> S3Request  -- ^ Amazon S3 Request information
+              -> IO S3URL -- ^ Generated URL
+generateS3URL S3Keys{..} S3Request{..} = do
+  time <- getExpirationTimeStamp secondsToExpire
+  let url = case s3method of
+              S3GET -> getURL bucketName objectName time
+              S3PUT -> putURL bucketName objectName time
+      req = s3URL bucketName objectName publicKey time (sign secretKey url)
+  return (S3URL req)
+
diff --git a/src/Network/S3/Sign.hs b/src/Network/S3/Sign.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/S3/Sign.hs
@@ -0,0 +1,11 @@
+module Network.S3.Sign  ( sign ) where
+
+import           Crypto.Hash.SHA1       (hash)
+import           Crypto.MAC.HMAC        (hmac)
+import qualified Data.ByteString.Base64 as B64
+import           Data.ByteString.UTF8   (ByteString)
+import           Network.HTTP.Types.URI (urlEncode)
+
+-- | SHA1 Encrypted Signature
+sign :: ByteString -> ByteString -> ByteString
+sign secretKey url = urlEncode True . B64.encode $ hmac hash 64 secretKey url
diff --git a/src/Network/S3/Time.hs b/src/Network/S3/Time.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/S3/Time.hs
@@ -0,0 +1,18 @@
+module Network.S3.Time
+    ( getExpirationTimeStamp
+    , utcTimeToEpochTime
+    ) where
+
+import           Control.Applicative   ((<$>))
+import           Data.ByteString.UTF8  (ByteString, fromString)
+import           Data.Time             (UTCTime (..), getCurrentTime)
+import           Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
+
+getExpirationTimeStamp :: Integer -> IO ByteString
+getExpirationTimeStamp secs = fromString . show . (+secs) . utcTimeToEpochTime <$> getCurrentTime
+
+utcTimeToEpochTime :: UTCTime -> Integer
+utcTimeToEpochTime = fromIntegral . toSecs
+  where
+    toSecs :: UTCTime -> Int
+    toSecs = round . utcTimeToPOSIXSeconds
diff --git a/src/Network/S3/Types.hs b/src/Network/S3/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/S3/Types.hs
@@ -0,0 +1,31 @@
+{-# LANGUAGE DeriveGeneric #-}
+
+module Network.S3.Types
+    ( S3URL (..)
+    , S3Keys (..)
+    , S3Method (..)
+    , S3Request (..)
+    ) where
+
+import           Data.ByteString.UTF8 (ByteString)
+import           GHC.Generics         (Generic)
+
+newtype S3URL = S3URL {
+      signedRequest :: ByteString -- ^ Generated URL
+    } deriving (Generic, Show)
+
+data S3Keys = S3Keys {
+      publicKey :: ByteString -- ^ AWS Public Key
+    , secretKey :: ByteString  -- ^ AWS Private Key
+    } deriving (Generic, Show)
+
+data S3Method = S3GET -- ^ GET Request
+              | S3PUT -- ^ PUT Request
+    deriving (Generic, Show)
+
+data S3Request = S3Request {
+      s3method        :: S3Method -- ^ Type of HTTP Method
+    , bucketName      :: ByteString -- ^ Name of Amazon S3 Bucket
+    , objectName      :: ByteString -- ^ Name of Amazon S3 File
+    , secondsToExpire :: Integer -- ^ Number of seconds until expiration
+    } deriving (Generic, Show)
diff --git a/src/Network/S3/URL.hs b/src/Network/S3/URL.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/S3/URL.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.S3.URL
+    ( putURL
+    , getURL
+    , s3URL
+    ) where
+
+import           Data.Monoid
+import           Data.String
+
+-- | S3 Upload URL Template
+putURL :: (Monoid m, IsString m) => m -> m -> m -> m
+putURL bucket object expires =
+    mconcat [ "PUT\n\napplication/zip\n"
+            , expires
+            , "\nx-amz-acl:public-read\n/"
+            , bucket
+            , "/"
+            , object
+            ]
+
+-- | S3 Download URL Template
+getURL :: (Monoid m, IsString m) => m -> m -> m -> m
+getURL bucket object expires =
+    mconcat [ "GET\n\n\n"
+            , expires
+            , "\n/"
+            , bucket
+            , "/"
+            , object
+            ]
+
+-- | Amazon S3 URL Template
+baseUrl :: (Monoid m, IsString m) => m -> m -> m
+baseUrl bucket object =
+    mconcat [ "https://"
+            , bucket
+            , ".s3.amazonaws.com/"
+            , object
+            ]
+
+s3URL :: (Monoid m, IsString m) =>
+           m -> m -> m -> m -> m -> m
+s3URL bucket object publicKey expires sig
+    = mconcat [ baseUrl bucket object
+              , "?AWSAccessKeyId="
+              , publicKey
+              , "&Expires="
+              , expires
+              , "&Signature="
+              , sig
+              ]
