add from hackage; update to support lts-23.24
This commit is contained in:
commit
6d35ffd86f
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
FROM haskell:9.8.4
|
||||||
|
|
||||||
|
WORKDIR /opt/filepath-crypto
|
||||||
|
|
||||||
|
# RUN stack install --resolver lts-24.0 --compiler ghc-9.10.1 hpack
|
||||||
|
|
||||||
|
# Add just the .cabal file to capture dependencies
|
||||||
|
# COPY ./filepath-crypto.cabal /opt/filepath-crypto/filepath-crypto.cabal
|
||||||
|
|
||||||
|
# Docker will cache this command as a layer, freeing us up to
|
||||||
|
# modify source code without re-installing dependencies
|
||||||
|
# (unless the .cabal file changes!)
|
||||||
|
|
||||||
|
# Add and Install Application Code
|
||||||
|
COPY . /opt/filepath-crypto
|
||||||
|
# RUN stack build --resolver lts-24.0 --only-dependencies -j4
|
||||||
|
RUN stack build --resolver lts-23.24 --compiler ghc-9.8.4 filepath-crypto
|
||||||
30
LICENSE
Normal file
30
LICENSE
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
Copyright (c) 2017, Gregor Kleen
|
||||||
|
|
||||||
|
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 Gregor Kleen nor the names of other
|
||||||
|
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
|
||||||
|
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.
|
||||||
15
changes.md
Normal file
15
changes.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# 0.1.0.0
|
||||||
|
- Add support for 'cryptoids-class'
|
||||||
|
|
||||||
|
# 0.0.0.3
|
||||||
|
- Got rid of `encoding`
|
||||||
|
|
||||||
|
# 0.0.0.2
|
||||||
|
- Improved documentation
|
||||||
|
|
||||||
|
# 0.0.0.1
|
||||||
|
- Improved documentation
|
||||||
|
|
||||||
|
# 0.0.0.0
|
||||||
|
|
||||||
|
First published version
|
||||||
45
filepath-crypto.cabal
Normal file
45
filepath-crypto.cabal
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
name: filepath-crypto
|
||||||
|
version: 0.2.0.0
|
||||||
|
cabal-version: >=1.10
|
||||||
|
build-type: Simple
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
maintainer: Gregor Kleen <aethoago@141.li>
|
||||||
|
synopsis: Reversable and secure encoding of object ids as filepaths
|
||||||
|
category: cryptography
|
||||||
|
author: Gregor Kleen <aethoago@141.li>
|
||||||
|
extra-source-files:
|
||||||
|
changes.md
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: https://git.rheperire.org/cryptoids/filepath-crypto
|
||||||
|
|
||||||
|
library
|
||||||
|
exposed-modules:
|
||||||
|
System.FilePath.Cryptographic
|
||||||
|
System.FilePath.Cryptographic.ImplicitNamespace
|
||||||
|
Data.Binary.SerializationLength
|
||||||
|
Data.Binary.SerializationLength.TH
|
||||||
|
build-depends:
|
||||||
|
base >=4.10.1.0 && <5,
|
||||||
|
binary >=0.8.5.1 && <0.9,
|
||||||
|
bytestring >=0.10.8.2 && <0.13,
|
||||||
|
case-insensitive >=1.2.0.10 && <2,
|
||||||
|
cryptoids >=0.6.0.0 && <0.7,
|
||||||
|
exceptions >=0.8.3 && <0.11,
|
||||||
|
filepath >=1.4.1.2 && <2,
|
||||||
|
sandi >=0.4.1 && <0.6,
|
||||||
|
template-haskell >=2.12.0.0 && <3
|
||||||
|
default-language: Haskell2010
|
||||||
|
default-extensions: KindSignatures ViewPatterns FlexibleContexts
|
||||||
|
GeneralizedNewtypeDeriving PatternGuards RecordWildCards DataKinds
|
||||||
|
DeriveDataTypeable DeriveGeneric FlexibleInstances
|
||||||
|
MultiParamTypeClasses TypeFamilies ConstraintKinds
|
||||||
|
other-extensions: ScopedTypeVariables
|
||||||
|
hs-source-dirs: src
|
||||||
|
other-modules:
|
||||||
|
Data.Binary.SerializationLength.Class
|
||||||
|
Paths_filepath_crypto
|
||||||
|
ghc-options: -Wall -fno-warn-name-shadowing
|
||||||
|
|
||||||
13
packages.yaml
Normal file
13
packages.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
name: filepath-crypto
|
||||||
|
version: 0.2.0.0
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- base >=4.10.1.0 && <5
|
||||||
|
- binary >=0.8.5.1 && <0.9
|
||||||
|
- bytestring >=0.10.8.2 && <0.13
|
||||||
|
- case-insensitive >=1.2.0.10 && <2
|
||||||
|
- cryptoids >=0.6.0.0 && <0.7
|
||||||
|
- exceptions >=0.8.3 && <0.9
|
||||||
|
- filepath >=1.4.1.2 && <2
|
||||||
|
- sandi >=0.4.1 && <0.6
|
||||||
|
- template-haskell >=2.12.0.0 && <3
|
||||||
24
src/Data/Binary/SerializationLength.hs
Normal file
24
src/Data/Binary/SerializationLength.hs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
|
||||||
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
|
module Data.Binary.SerializationLength
|
||||||
|
( HasFixedSerializationLength(..)
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Data.Binary.SerializationLength.Class
|
||||||
|
import Data.Binary.SerializationLength.TH
|
||||||
|
|
||||||
|
import Data.Int
|
||||||
|
import Data.Word
|
||||||
|
|
||||||
|
$(hasFixedSerializationLength ''Word8 1)
|
||||||
|
$(hasFixedSerializationLength ''Word16 2)
|
||||||
|
$(hasFixedSerializationLength ''Word32 4)
|
||||||
|
$(hasFixedSerializationLength ''Word64 8)
|
||||||
|
|
||||||
|
$(hasFixedSerializationLength ''Int8 1)
|
||||||
|
$(hasFixedSerializationLength ''Int16 2)
|
||||||
|
$(hasFixedSerializationLength ''Int32 4)
|
||||||
|
$(hasFixedSerializationLength ''Int64 8)
|
||||||
13
src/Data/Binary/SerializationLength/Class.hs
Normal file
13
src/Data/Binary/SerializationLength/Class.hs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
|
||||||
|
module Data.Binary.SerializationLength.Class
|
||||||
|
( HasFixedSerializationLength(..)
|
||||||
|
) where
|
||||||
|
|
||||||
|
import GHC.TypeLits
|
||||||
|
|
||||||
|
-- | The class of types for which the result of serialization with @Data.Binary@
|
||||||
|
-- is known statically to be of a certain length
|
||||||
|
class KnownNat (SerializationLength a) => HasFixedSerializationLength a where
|
||||||
|
-- | The 'SerializationLength' is given in bytes at type level
|
||||||
|
type SerializationLength a :: Nat
|
||||||
23
src/Data/Binary/SerializationLength/TH.hs
Normal file
23
src/Data/Binary/SerializationLength/TH.hs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
|
||||||
|
|
||||||
|
module Data.Binary.SerializationLength.TH
|
||||||
|
( hasFixedSerializationLength
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Language.Haskell.TH
|
||||||
|
|
||||||
|
import Data.Binary.SerializationLength.Class
|
||||||
|
|
||||||
|
|
||||||
|
hasFixedSerializationLength :: Name -> Integer -> DecsQ
|
||||||
|
-- | Shorthand for defining instances of 'HasFixedSerializationLength', morally:
|
||||||
|
--
|
||||||
|
-- > hasFixedSerializationLength typeName byteN = [d|
|
||||||
|
-- > instance HasFixedSerializiationLength $(typeName) where
|
||||||
|
-- > type SerializationLength $(typeName) = $(byteN)
|
||||||
|
-- > |]
|
||||||
|
hasFixedSerializationLength (return . ConT -> t) (return . LitT . NumTyLit -> i) =
|
||||||
|
[d|
|
||||||
|
instance HasFixedSerializationLength $(t) where
|
||||||
|
type SerializationLength $(t) = $(i)
|
||||||
|
|]
|
||||||
121
src/System/FilePath/Cryptographic.hs
Normal file
121
src/System/FilePath/Cryptographic.hs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Description: Reversably generate filepaths from arbitrary serializable types in a secure fashion
|
||||||
|
License: BSD3
|
||||||
|
|
||||||
|
Given a value of a serializable type (like 'Int') we perform serialization and
|
||||||
|
compute a cryptographic hash of the associated namespace (carried as a phantom
|
||||||
|
type of kind 'Symbol').
|
||||||
|
The serialized payload is encrypted using a symmetric cipher in CBC mode using
|
||||||
|
the hashed namespace as an initialization vector (IV).
|
||||||
|
|
||||||
|
The ciphertext is then
|
||||||
|
<https://hackage.haskell.org/package/sandi/docs/Codec-Binary-Base32.html base32>-encoded
|
||||||
|
and padding stripped.
|
||||||
|
|
||||||
|
Rather than being indicated by the amount of padding, the length of the
|
||||||
|
serialized plaintext is instead carried at the type level within
|
||||||
|
'CryptoFileName' (analogously to the namespace).
|
||||||
|
Mismatches in serialized plaintext length are checked for but are /not/
|
||||||
|
guaranteed to cause runtime errors in all cases.
|
||||||
|
|
||||||
|
Since the serialized payload is padded to the length of the next cipher block we
|
||||||
|
can detect namespace mismatches by checking that all bytes expected to have been
|
||||||
|
inserted during padding are nil.
|
||||||
|
The probability of detecting a namespace mismatch is thus \(1 - 2^{b \left \lceil \frac{l}{b} \right \rceil - l}\)
|
||||||
|
where \(l\) is the length of the serialized payload and \(b\) the length of a
|
||||||
|
ciphertext block (both in bits).
|
||||||
|
-}
|
||||||
|
module System.FilePath.Cryptographic
|
||||||
|
( CryptoFileName
|
||||||
|
, HasCryptoFileName
|
||||||
|
, module Data.Binary.SerializationLength
|
||||||
|
, encrypt
|
||||||
|
, decrypt
|
||||||
|
, module Data.CryptoID.Poly
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Data.CryptoID.Poly hiding (encrypt, decrypt)
|
||||||
|
import Data.CryptoID.ByteString (cipherBlockSize)
|
||||||
|
import qualified Data.CryptoID.Poly as Poly (encrypt, decrypt)
|
||||||
|
import Data.CryptoID.Class (HasCryptoID)
|
||||||
|
import qualified Data.CryptoID.Class as Class (HasCryptoID(..))
|
||||||
|
|
||||||
|
import System.FilePath (FilePath)
|
||||||
|
import qualified Codec.Binary.Base32 as Base32
|
||||||
|
import Data.CaseInsensitive (CI)
|
||||||
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
import Data.Binary
|
||||||
|
import Data.Binary.SerializationLength
|
||||||
|
import Data.Char (toUpper)
|
||||||
|
|
||||||
|
import Data.Ratio ((%))
|
||||||
|
import Data.List
|
||||||
|
|
||||||
|
import qualified Data.ByteString as ByteString
|
||||||
|
import qualified Data.ByteString.Char8 as ByteString.Char8
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.Catch
|
||||||
|
|
||||||
|
import Data.Proxy
|
||||||
|
import GHC.TypeLits
|
||||||
|
|
||||||
|
|
||||||
|
type CryptoFileName (namespace :: Symbol) = CryptoID namespace (CI FilePath)
|
||||||
|
type HasCryptoFileName (namespace :: Symbol) = HasCryptoID namespace (CI FilePath)
|
||||||
|
|
||||||
|
|
||||||
|
paddedLength :: Integral a => a -> a
|
||||||
|
-- | Round up to nearest multiple of 'cipherBlockSize'
|
||||||
|
paddedLength l = bs * ceiling (l % bs)
|
||||||
|
where bs = fromIntegral cipherBlockSize
|
||||||
|
|
||||||
|
-- | Encrypt an arbitrary serializable value
|
||||||
|
--
|
||||||
|
-- We only expect to fail if the given value is not serialized in such a fashion
|
||||||
|
-- that it meets the expected length given at type level.
|
||||||
|
encrypt :: forall a m namespace.
|
||||||
|
( KnownSymbol namespace
|
||||||
|
, Binary a
|
||||||
|
, MonadThrow m
|
||||||
|
, HasFixedSerializationLength a
|
||||||
|
) => CryptoIDKey -> a -> m (CryptoFileName namespace)
|
||||||
|
encrypt = Poly.encrypt determineLength $ return . encode
|
||||||
|
where
|
||||||
|
determineLength str = do
|
||||||
|
let l = ByteString.length str
|
||||||
|
unless (fromIntegral l == natVal (Proxy :: Proxy (SerializationLength a))) $
|
||||||
|
throwM $ CiphertextConversionFailed str
|
||||||
|
return . Just $ paddedLength l
|
||||||
|
encode str = CI.mk . dropWhileEnd (== '=') . ByteString.Char8.unpack $ Base32.encode str
|
||||||
|
|
||||||
|
|
||||||
|
-- | Decrypt an arbitrary serializable value
|
||||||
|
--
|
||||||
|
-- Since no integrity guarantees can be made (we do not sign the values we
|
||||||
|
-- 'encrypt') it is likely that deserialization will fail emitting
|
||||||
|
-- 'DeserializationError' or 'InvalidNamespaceDetected'.
|
||||||
|
decrypt :: forall a m namespace.
|
||||||
|
( KnownSymbol namespace
|
||||||
|
, Binary a
|
||||||
|
, MonadThrow m
|
||||||
|
, HasFixedSerializationLength a
|
||||||
|
) => CryptoIDKey -> CryptoFileName namespace -> m a
|
||||||
|
decrypt = Poly.decrypt $ (\str -> either (const . throwM $ CiphertextConversionFailed str) return $ Base32.decode str) . ByteString.Char8.pack . padding (natVal (Proxy :: Proxy (SerializationLength a))) . map toUpper . CI.original
|
||||||
|
where
|
||||||
|
padding l str = str ++ replicate (genericIndex paddingTable $ l' `mod` 5) '='
|
||||||
|
where
|
||||||
|
l' = paddedLength l
|
||||||
|
paddingTable = [0, 6, 4, 3, 1]
|
||||||
|
|
||||||
|
instance ( MonadCrypto m
|
||||||
|
, MonadCryptoKey m ~ CryptoIDKey
|
||||||
|
, KnownSymbol namespace
|
||||||
|
, Binary a
|
||||||
|
, HasFixedSerializationLength a
|
||||||
|
) => HasCryptoID namespace (CI FilePath) a m where
|
||||||
|
encrypt = cryptoIDKey . flip encrypt
|
||||||
|
decrypt = cryptoIDKey . flip decrypt
|
||||||
20
src/System/FilePath/Cryptographic/ImplicitNamespace.hs
Normal file
20
src/System/FilePath/Cryptographic/ImplicitNamespace.hs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{-|
|
||||||
|
Description: Reversably generate filepaths from arbitrary serializable types with implicit type level nonces
|
||||||
|
License: BSD3
|
||||||
|
-}
|
||||||
|
module System.FilePath.Cryptographic.ImplicitNamespace
|
||||||
|
( CryptoFileName
|
||||||
|
, HasCryptoFileName
|
||||||
|
, module System.FilePath.Cryptographic
|
||||||
|
, module Data.CryptoID.Class.ImplicitNamespace
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Data.CryptoID.Class.ImplicitNamespace
|
||||||
|
|
||||||
|
import System.FilePath.Cryptographic hiding (encrypt, decrypt, CryptoID, HasCryptoID, CryptoFileName, HasCryptoFileName)
|
||||||
|
|
||||||
|
import System.FilePath (FilePath)
|
||||||
|
import Data.CaseInsensitive (CI)
|
||||||
|
|
||||||
|
type CryptoFileName plaintext = CryptoID (CI FilePath) plaintext
|
||||||
|
type HasCryptoFileName plaintext = HasCryptoID (CI FilePath) plaintext
|
||||||
9
stack.yaml
Normal file
9
stack.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
snapshot: lts-24.0
|
||||||
|
compiler: ghc-9.10.1
|
||||||
|
|
||||||
|
packages:
|
||||||
|
- .
|
||||||
|
|
||||||
|
extra-deps:
|
||||||
|
- git: https://gitea.uniworx.systems/haskell/cryptoid.git
|
||||||
|
commit: 1d3f4843377664f1679f2a18ca3160a0d02b8b1b
|
||||||
Loading…
Reference in New Issue
Block a user