diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2013, Lainepress
+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.
+
+  Neither the name of the {organization} nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+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 HOLDER 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,5 @@
+cloudfront-signer
+=================
+
+Provides functions for reading in the signing keys from file and making up
+time-limited, signed URLS for accessing CloudFront-hosted files.
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/aws-cloudfront-signer.cabal b/aws-cloudfront-signer.cabal
new file mode 100644
--- /dev/null
+++ b/aws-cloudfront-signer.cabal
@@ -0,0 +1,45 @@
+Name:               aws-cloudfront-signer
+Version:            1.0.0.0
+Synopsis:           CloudFront URL signer
+Description:        Provides functions for reading in the signing keys from file and making up time-limited, signed URLS for accessing CloudFront-hosted files.
+Homepage:           http://github.com/cdornan/cloudfront-signer
+Author:             Chris Dornan
+Maintainer:         chris@chrisdornan.com
+License:            BSD3
+License-file:       LICENSE
+Copyright:          (C) Chris Dornan
+Category:           Network
+Category:           Network, Web, AWS, Cloud, Distributed Computing
+Build-type:         Simple
+
+Cabal-version:      >= 1.14
+
+Source-repository this
+    type: git
+    location:       https://github.com/lainepress/aws-cloudfront-signer.git
+    tag:            1.0.0.0
+
+Source-repository head
+    type: git
+    location:       https://github.com/lainepress/aws-cloudfront-signer.git
+
+library
+    Hs-Source-Dirs: src
+
+    Build-depends:      
+        RSA                  >= 1.2.2.0     ,
+        asn1-types           >= 0.2.0       ,
+        asn1-encoding        >= 0.8.0       ,
+        base                 == 4.*         ,
+        base64-bytestring    == 1.0.*       ,
+        bytestring           >= 0.9         ,
+        crypto-pubkey-types  >= 0.4.0       ,
+        old-locale           >= 1           ,
+        time                 >= 1.1.4
+
+    Exposed-modules:
+        Aws.CloudFront.Signer
+   
+    Default-Language:   Haskell2010
+
+    GHC-Options:        -Wall
diff --git a/src/Aws/CloudFront/Signer.hs b/src/Aws/CloudFront/Signer.hs
new file mode 100644
--- /dev/null
+++ b/src/Aws/CloudFront/Signer.hs
@@ -0,0 +1,171 @@
+{-# LANGUAGE RecordWildCards            #-} 
+
+module Aws.CloudFront.Signer
+    ( URL
+    , JSONPOlicy
+    , CloudFrontSigningKey(..)
+    , CloudFrontPolicy(..)
+    , readCloudFrontSigningKeyFromDER
+    , parseRSAPrivateKeyDER
+    , signCannedPolicyURL
+    , signCustomPolicyURL
+    , signCustomPolicyURL_
+    , cannedPolicy
+    , customPolicy
+    , unixTime
+    ) where
+
+import qualified Data.ASN1.Encoding             as A
+import qualified Data.ASN1.BinaryEncoding       as A
+import qualified Data.ASN1.Types                as A
+import qualified Data.ByteString.Lazy.Char8     as LBS
+import qualified Data.ByteString.Base64.Lazy    as B64
+import           Data.Time
+import           Data.Maybe
+import           Codec.Crypto.RSA
+import qualified Crypto.Types.PubKey.RSA        as C
+import           Text.Printf
+import           System.Locale
+
+-- | input and output URLs
+type URL        = String
+
+-- | a JSON CloudFront policy
+type JSONPOlicy = String        
+
+-- | the CloudFront key pair identifier
+type KeyID      = String        
+
+
+-- | a CloudFront siging key has an identifier and an RSA private key
+
+data CloudFrontSigningKey
+    = CloudFrontSigningKey
+        { cfk_key_id :: KeyID
+        , cfk_key    :: PrivateKey
+        }
+    deriving (Show)
+
+-- | a CloudFront policy must identify the resource being accessed and the
+--   expiry time; a starting time and IPv4 address may also be specified
+
+data CloudFrontPolicy
+    = CloudFrontPolicy
+        { cfp_Resource        :: URL
+        , cfp_DateLessThan    :: UTCTime
+        , cfp_DateGreaterThan :: Maybe UTCTime
+        , cfp_IpAddress       :: Maybe String
+        }
+
+-- | RSA private keys can only be read from DER file for now (the OpenSSL
+--   tools can be used to convert from PEM:
+--
+--      openssl rsa -in input.pem -inform PEM -out output.der -outform DER
+--
+
+readCloudFrontSigningKeyFromDER :: KeyID -> FilePath -> IO CloudFrontSigningKey
+readCloudFrontSigningKeyFromDER ki fp = 
+ do pk_b <- LBS.readFile fp
+    case parseRSAPrivateKeyDER pk_b of
+      Left err -> error err
+      Right pk ->
+        return $
+            CloudFrontSigningKey 
+                { cfk_key_id = ki
+                , cfk_key    = pk
+                }
+
+-- | If you have the DER ByteString then you can construct a private key
+--   functionally.
+
+parseRSAPrivateKeyDER :: LBS.ByteString -> Either String C.PrivateKey
+parseRSAPrivateKeyDER bs = 
+    case A.decodeASN1 A.DER bs of
+      Left err -> Left $ show err
+      Right as ->
+        case A.fromASN1 as of
+          Left err -> Left $ show err
+          Right pr -> 
+            case pr of
+              (pk,[]) -> Right pk
+              _       -> Left "residula data"
+
+-- | In most cases only a time-limited, signed URL is needed, in which case a
+--   canned policy can be used; URLs signed with a canned policy are shorter
+--   than those signed with a custom policy.
+
+signCannedPolicyURL :: CloudFrontSigningKey -> UTCTime -> URL -> URL
+signCannedPolicyURL CloudFrontSigningKey{..} exp_utc url = 
+    printf "%s%cExpires=%s&Signature=%s&Key-Pair-Id=%s" url sep exp_eps pol_sig cfk_key_id  
+  where
+    exp_eps = unixTime exp_utc
+    pol_sig = b64 $ rsa_sha1 cfk_key pol 
+    pol     = cannedPolicy exp_utc url
+    sep     = if any (=='?') url then '&' else '?'
+
+-- | Signing a URL with a custom policy allows a start time to be specified and
+--   the IP address of the recipient(s) to be specified.
+
+signCustomPolicyURL :: CloudFrontSigningKey -> CloudFrontPolicy -> URL
+signCustomPolicyURL cfk cfp = signCustomPolicyURL_ cfk (customPolicy cfp) $ cfp_Resource cfp
+
+-- | The URL can also be signed with the custom policy in JSON format.
+--   (See the CloudFront documentation for details.)
+
+signCustomPolicyURL_ :: CloudFrontSigningKey -> JSONPOlicy -> URL -> URL
+signCustomPolicyURL_ CloudFrontSigningKey{..} pol url =
+    printf "%s%cPolicy=%s&Signature=%s&Key-Pair-Id=%s" url sep pol_b64 pol_sig cfk_key_id  
+  where
+    pol_sig = b64 $ rsa_sha1 cfk_key pol 
+    pol_b64 = b64            pol
+    sep     = if any (=='?') url then '&' else '?'
+
+-- | The JSON canned policy can be generated from the expiry time and
+--   the URL of the distributed resource.
+
+cannedPolicy :: UTCTime -> URL -> JSONPOlicy
+cannedPolicy exp_utc url =
+    concat
+        [ "{\"Statement\":[{\"Resource\":\""
+        , url    
+        , "\",\"Condition\":{\"DateLessThan\":{\"AWS:EpochTime\":"
+        , unixTime exp_utc
+        , "}}}]}"
+        ]
+
+-- | JSON custom policies provide more flexibility (allowing start times and
+--   recipient IP addresses to be specified) but generate longer signed URLs.
+
+customPolicy :: CloudFrontPolicy -> JSONPOlicy
+customPolicy CloudFrontPolicy{..} = unlines $ catMaybes
+    [ ok $          "{"
+    , ok $          "   \"Statement\": [{"
+    , ok $          "      \"Resource\":\""                             ++                      cfp_Resource     ++ "\","
+    , ok $          "      \"Condition\":{"
+    , ok $          "         \"DateLessThan\":{\"AWS:EpochTime\":"     ++ unixTime             cfp_DateLessThan ++ "},"
+    , st $ \ust ->  "         \"DateGreaterThan\":{\"AWS:EpochTime\":"  ++ unixTime             ust              ++ "},"
+    , ok $          "         \"IpAddress\":{\"AWS:SourceIp\":\""       ++ maybe "0.0.0.0/0" id cfp_IpAddress    ++"\"}"
+    , ok $          "      }"
+    , ok $          "   }]"
+    , ok $          "}"
+    ]
+  where
+    ok   = Just
+    st f = maybe Nothing (Just . f) cfp_DateGreaterThan
+
+-- | CloudFront uses Unix Epoch time (number of seconds since 1970, UTC) to
+--   specify UTC.
+
+unixTime :: UTCTime -> String
+unixTime = formatTime defaultTimeLocale "%s" 
+
+rsa_sha1 :: PrivateKey -> String -> String
+rsa_sha1 pk = LBS.unpack . rsassa_pkcs1_v1_5_sign ha_SHA1 pk . LBS.pack
+
+b64 :: String -> String
+b64 = map f . LBS.unpack . B64.encode . LBS.pack
+  where
+    f '+' = '-'
+    f '=' = '_'
+    f '/' = '~'
+    f c   = c
