module Ecluse.Core.Package.Hash (
Hash,
hashAlg,
hashValue,
mkHash,
mkSriHashes,
HashAlg (..),
renderHashAlg,
parseHashAlg,
sriPrefix,
sriBody,
sriAlgorithm,
computeDigest,
isComputable,
) where
import Crypto.Hash (Blake2b_512, Digest, MD5, SHA1, SHA256, SHA384, SHA512, digestFromByteString, hashlazy)
import Data.ByteArray (convert)
import Data.ByteArray.Encoding (Base (Base16, Base64), convertFromBase)
import Data.Text qualified as T
import Data.Universe.Class (Universe (..))
import Data.Universe.Generic (universeGeneric)
data HashAlg
= SHA1
| SHA256
| SHA384
| SHA512
| MD5
| Blake2b
|
SRI
deriving stock (HashAlg -> HashAlg -> Bool
(HashAlg -> HashAlg -> Bool)
-> (HashAlg -> HashAlg -> Bool) -> Eq HashAlg
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: HashAlg -> HashAlg -> Bool
== :: HashAlg -> HashAlg -> Bool
$c/= :: HashAlg -> HashAlg -> Bool
/= :: HashAlg -> HashAlg -> Bool
Eq, (forall x. HashAlg -> Rep HashAlg x)
-> (forall x. Rep HashAlg x -> HashAlg) -> Generic HashAlg
forall x. Rep HashAlg x -> HashAlg
forall x. HashAlg -> Rep HashAlg x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. HashAlg -> Rep HashAlg x
from :: forall x. HashAlg -> Rep HashAlg x
$cto :: forall x. Rep HashAlg x -> HashAlg
to :: forall x. Rep HashAlg x -> HashAlg
Generic, Int -> HashAlg -> ShowS
[HashAlg] -> ShowS
HashAlg -> String
(Int -> HashAlg -> ShowS)
-> (HashAlg -> String) -> ([HashAlg] -> ShowS) -> Show HashAlg
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> HashAlg -> ShowS
showsPrec :: Int -> HashAlg -> ShowS
$cshow :: HashAlg -> String
show :: HashAlg -> String
$cshowList :: [HashAlg] -> ShowS
showList :: [HashAlg] -> ShowS
Show)
instance Universe HashAlg where universe :: [HashAlg]
universe = [HashAlg]
forall a. (Generic a, GUniverse (Rep a)) => [a]
universeGeneric
instance Ord HashAlg where
compare :: HashAlg -> HashAlg -> Ordering
compare HashAlg
a HashAlg
b = Int -> Int -> Ordering
forall a. Ord a => a -> a -> Ordering
compare (HashAlg -> Int
hashAlgRank HashAlg
a) (HashAlg -> Int
hashAlgRank HashAlg
b)
hashAlgRank :: HashAlg -> Int
hashAlgRank :: HashAlg -> Int
hashAlgRank = \case
HashAlg
SRI -> Int
0
HashAlg
MD5 -> Int
10
HashAlg
SHA1 -> Int
20
HashAlg
SHA256 -> Int
30
HashAlg
SHA384 -> Int
40
HashAlg
Blake2b -> Int
50
HashAlg
SHA512 -> Int
60
data Hash = Hash
{ Hash -> HashAlg
hashAlg :: HashAlg
, Hash -> Text
hashValue :: Text
}
deriving stock (Hash -> Hash -> Bool
(Hash -> Hash -> Bool) -> (Hash -> Hash -> Bool) -> Eq Hash
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Hash -> Hash -> Bool
== :: Hash -> Hash -> Bool
$c/= :: Hash -> Hash -> Bool
/= :: Hash -> Hash -> Bool
Eq, Int -> Hash -> ShowS
[Hash] -> ShowS
Hash -> String
(Int -> Hash -> ShowS)
-> (Hash -> String) -> ([Hash] -> ShowS) -> Show Hash
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Hash -> ShowS
showsPrec :: Int -> Hash -> ShowS
$cshow :: Hash -> String
show :: Hash -> String
$cshowList :: [Hash] -> ShowS
showList :: [Hash] -> ShowS
Show)
mkHash :: HashAlg -> Text -> Either Text Hash
mkHash :: HashAlg -> Text -> Either Text Hash
mkHash HashAlg
alg Text
value
| HashAlg -> Text -> Bool
wellFormed HashAlg
alg Text
value = Hash -> Either Text Hash
forall a b. b -> Either a b
Right (HashAlg -> Text -> Hash
Hash HashAlg
alg Text
value)
| Bool
otherwise = Text -> Either Text Hash
forall a b. a -> Either a b
Left (Text
"malformed " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> HashAlg -> Text
renderHashAlg HashAlg
alg Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" digest")
mkSriHashes :: Text -> Either Text (NonEmpty Hash)
mkSriHashes :: Text -> Either Text (NonEmpty Hash)
mkSriHashes Text
wire = case [Text] -> Maybe (NonEmpty Text)
forall a. [a] -> Maybe (NonEmpty a)
nonEmpty (Text -> [Text]
T.words Text
wire) of
Maybe (NonEmpty Text)
Nothing -> Text -> Either Text (NonEmpty Hash)
forall a b. a -> Either a b
Left Text
"malformed sri digest"
Just NonEmpty Text
comps -> (Text -> Either Text Hash)
-> NonEmpty Text -> Either Text (NonEmpty Hash)
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
forall (f :: * -> *) a b.
Applicative f =>
(a -> f b) -> NonEmpty a -> f (NonEmpty b)
traverse (HashAlg -> Text -> Either Text Hash
mkHash HashAlg
SRI) NonEmpty Text
comps
wellFormed :: HashAlg -> Text -> Bool
wellFormed :: HashAlg -> Text -> Bool
wellFormed = \case
HashAlg
SRI -> Text -> Bool
wellFormedSri
HashAlg
alg -> HashAlg -> Text -> Bool
wellFormedHex HashAlg
alg
wellFormedHex :: HashAlg -> Text -> Bool
wellFormedHex :: HashAlg -> Text -> Bool
wellFormedHex HashAlg
alg Text
t =
case Base -> ByteString -> Either String ByteString
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
Base -> bin -> Either String bout
convertFromBase Base
Base16 (Text -> ByteString
forall a b. ConvertUtf8 a b => a -> b
encodeUtf8 (Text -> Text
T.toLower Text
t) :: ByteString) :: Either String ByteString of
Left String
_ -> Bool
False
Right ByteString
bytes -> HashAlg -> ByteString -> Bool
hexDigestOk HashAlg
alg ByteString
bytes
hexDigestOk :: HashAlg -> ByteString -> Bool
hexDigestOk :: HashAlg -> ByteString -> Bool
hexDigestOk HashAlg
alg ByteString
bytes = case HashAlg
alg of
HashAlg
SHA1 -> Maybe (Digest SHA1) -> Bool
forall a. Maybe a -> Bool
isJust (forall a ba.
(HashAlgorithm a, ByteArrayAccess ba) =>
ba -> Maybe (Digest a)
digestFromByteString @SHA1 ByteString
bytes)
HashAlg
SHA256 -> Maybe (Digest SHA256) -> Bool
forall a. Maybe a -> Bool
isJust (forall a ba.
(HashAlgorithm a, ByteArrayAccess ba) =>
ba -> Maybe (Digest a)
digestFromByteString @SHA256 ByteString
bytes)
HashAlg
SHA384 -> Maybe (Digest SHA384) -> Bool
forall a. Maybe a -> Bool
isJust (forall a ba.
(HashAlgorithm a, ByteArrayAccess ba) =>
ba -> Maybe (Digest a)
digestFromByteString @SHA384 ByteString
bytes)
HashAlg
SHA512 -> Maybe (Digest SHA512) -> Bool
forall a. Maybe a -> Bool
isJust (forall a ba.
(HashAlgorithm a, ByteArrayAccess ba) =>
ba -> Maybe (Digest a)
digestFromByteString @SHA512 ByteString
bytes)
HashAlg
MD5 -> Maybe (Digest MD5) -> Bool
forall a. Maybe a -> Bool
isJust (forall a ba.
(HashAlgorithm a, ByteArrayAccess ba) =>
ba -> Maybe (Digest a)
digestFromByteString @MD5 ByteString
bytes)
HashAlg
Blake2b -> Maybe (Digest Blake2b_512) -> Bool
forall a. Maybe a -> Bool
isJust (forall a ba.
(HashAlgorithm a, ByteArrayAccess ba) =>
ba -> Maybe (Digest a)
digestFromByteString @Blake2b_512 ByteString
bytes)
HashAlg
SRI -> Bool
False
computeDigest :: HashAlg -> Maybe (LByteString -> ByteString)
computeDigest :: HashAlg -> Maybe (LByteString -> ByteString)
computeDigest = \case
HashAlg
SHA1 -> (LByteString -> ByteString) -> Maybe (LByteString -> ByteString)
forall a. a -> Maybe a
Just (Digest SHA1 -> ByteString
forall a. Digest a -> ByteString
digestBytes (Digest SHA1 -> ByteString)
-> (LByteString -> Digest SHA1) -> LByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. HashAlgorithm a => LByteString -> Digest a
hashlazy @SHA1)
HashAlg
SHA256 -> (LByteString -> ByteString) -> Maybe (LByteString -> ByteString)
forall a. a -> Maybe a
Just (Digest SHA256 -> ByteString
forall a. Digest a -> ByteString
digestBytes (Digest SHA256 -> ByteString)
-> (LByteString -> Digest SHA256) -> LByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. HashAlgorithm a => LByteString -> Digest a
hashlazy @SHA256)
HashAlg
SHA384 -> (LByteString -> ByteString) -> Maybe (LByteString -> ByteString)
forall a. a -> Maybe a
Just (Digest SHA384 -> ByteString
forall a. Digest a -> ByteString
digestBytes (Digest SHA384 -> ByteString)
-> (LByteString -> Digest SHA384) -> LByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. HashAlgorithm a => LByteString -> Digest a
hashlazy @SHA384)
HashAlg
SHA512 -> (LByteString -> ByteString) -> Maybe (LByteString -> ByteString)
forall a. a -> Maybe a
Just (Digest SHA512 -> ByteString
forall a. Digest a -> ByteString
digestBytes (Digest SHA512 -> ByteString)
-> (LByteString -> Digest SHA512) -> LByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. HashAlgorithm a => LByteString -> Digest a
hashlazy @SHA512)
HashAlg
Blake2b -> (LByteString -> ByteString) -> Maybe (LByteString -> ByteString)
forall a. a -> Maybe a
Just (Digest Blake2b_512 -> ByteString
forall a. Digest a -> ByteString
digestBytes (Digest Blake2b_512 -> ByteString)
-> (LByteString -> Digest Blake2b_512) -> LByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. HashAlgorithm a => LByteString -> Digest a
hashlazy @Blake2b_512)
HashAlg
MD5 -> Maybe (LByteString -> ByteString)
forall a. Maybe a
Nothing
HashAlg
SRI -> Maybe (LByteString -> ByteString)
forall a. Maybe a
Nothing
where
digestBytes :: Digest a -> ByteString
digestBytes :: forall a. Digest a -> ByteString
digestBytes = Digest a -> ByteString
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
bin -> bout
convert
isComputable :: HashAlg -> Bool
isComputable :: HashAlg -> Bool
isComputable = Maybe (LByteString -> ByteString) -> Bool
forall a. Maybe a -> Bool
isJust (Maybe (LByteString -> ByteString) -> Bool)
-> (HashAlg -> Maybe (LByteString -> ByteString))
-> HashAlg
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HashAlg -> Maybe (LByteString -> ByteString)
computeDigest
wellFormedSri :: Text -> Bool
wellFormedSri :: Text -> Bool
wellFormedSri Text
t = case Text -> [Text]
T.words Text
t of
[Text
comp] -> Text
comp Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
t Bool -> Bool -> Bool
&& Text -> Bool
wellFormedSriComponent Text
comp
[Text]
_ -> Bool
False
wellFormedSriComponent :: Text -> Bool
wellFormedSriComponent :: Text -> Bool
wellFormedSriComponent Text
comp
| Text -> Bool
T.null (Text -> Text
sriBody Text
comp) = Bool
False
| Bool
otherwise = Text -> Text -> Bool
sriBodyOk (Text -> Text
sriPrefix Text
comp) (Text -> Text
sriBody Text
comp)
sriBodyOk :: Text -> Text -> Bool
sriBodyOk :: Text -> Text -> Bool
sriBodyOk Text
algName Text
body =
case Base -> ByteString -> Either String ByteString
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
Base -> bin -> Either String bout
convertFromBase Base
Base64 (Text -> ByteString
forall a b. ConvertUtf8 a b => a -> b
encodeUtf8 Text
body :: ByteString) :: Either String ByteString of
Left String
_ -> Bool
False
Right ByteString
bytes -> case Text
algName of
Text
"sha256" -> Maybe (Digest SHA256) -> Bool
forall a. Maybe a -> Bool
isJust (forall a ba.
(HashAlgorithm a, ByteArrayAccess ba) =>
ba -> Maybe (Digest a)
digestFromByteString @SHA256 ByteString
bytes)
Text
"sha384" -> Maybe (Digest SHA384) -> Bool
forall a. Maybe a -> Bool
isJust (forall a ba.
(HashAlgorithm a, ByteArrayAccess ba) =>
ba -> Maybe (Digest a)
digestFromByteString @SHA384 ByteString
bytes)
Text
"sha512" -> Maybe (Digest SHA512) -> Bool
forall a. Maybe a -> Bool
isJust (forall a ba.
(HashAlgorithm a, ByteArrayAccess ba) =>
ba -> Maybe (Digest a)
digestFromByteString @SHA512 ByteString
bytes)
Text
_ -> Bool
False
renderHashAlg :: HashAlg -> Text
renderHashAlg :: HashAlg -> Text
renderHashAlg = \case
HashAlg
MD5 -> Text
"md5"
HashAlg
SHA1 -> Text
"sha1"
HashAlg
SHA256 -> Text
"sha256"
HashAlg
SHA384 -> Text
"sha384"
HashAlg
SHA512 -> Text
"sha512"
HashAlg
Blake2b -> Text
"blake2b"
HashAlg
SRI -> Text
"sri"
parseHashAlg :: Text -> Either Text HashAlg
parseHashAlg :: Text -> Either Text HashAlg
parseHashAlg Text
raw = case Text -> Text
T.toLower (Text -> Text
T.strip Text
raw) of
Text
"md5" -> HashAlg -> Either Text HashAlg
forall a b. b -> Either a b
Right HashAlg
MD5
Text
"sha1" -> HashAlg -> Either Text HashAlg
forall a b. b -> Either a b
Right HashAlg
SHA1
Text
"sha-1" -> HashAlg -> Either Text HashAlg
forall a b. b -> Either a b
Right HashAlg
SHA1
Text
"sha256" -> HashAlg -> Either Text HashAlg
forall a b. b -> Either a b
Right HashAlg
SHA256
Text
"sha-256" -> HashAlg -> Either Text HashAlg
forall a b. b -> Either a b
Right HashAlg
SHA256
Text
"sha384" -> HashAlg -> Either Text HashAlg
forall a b. b -> Either a b
Right HashAlg
SHA384
Text
"sha-384" -> HashAlg -> Either Text HashAlg
forall a b. b -> Either a b
Right HashAlg
SHA384
Text
"sha512" -> HashAlg -> Either Text HashAlg
forall a b. b -> Either a b
Right HashAlg
SHA512
Text
"sha-512" -> HashAlg -> Either Text HashAlg
forall a b. b -> Either a b
Right HashAlg
SHA512
Text
"blake2b" -> HashAlg -> Either Text HashAlg
forall a b. b -> Either a b
Right HashAlg
Blake2b
Text
_ -> Text -> Either Text HashAlg
forall a b. a -> Either a b
Left (Text
"unknown integrity algorithm: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
raw)
sriPrefix :: Text -> Text
sriPrefix :: Text -> Text
sriPrefix = (Text, Text) -> Text
forall a b. (a, b) -> a
fst ((Text, Text) -> Text) -> (Text -> (Text, Text)) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOn Text
"-"
sriBody :: Text -> Text
sriBody :: Text -> Text
sriBody = Int -> Text -> Text
T.drop Int
1 (Text -> Text) -> (Text -> Text) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text, Text) -> Text
forall a b. (a, b) -> b
snd ((Text, Text) -> Text) -> (Text -> (Text, Text)) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOn Text
"-"
sriAlgorithm :: Text -> Maybe HashAlg
sriAlgorithm :: Text -> Maybe HashAlg
sriAlgorithm Text
sri = case Text -> Text
sriPrefix Text
sri of
Text
"sha256" -> HashAlg -> Maybe HashAlg
forall a. a -> Maybe a
Just HashAlg
SHA256
Text
"sha384" -> HashAlg -> Maybe HashAlg
forall a. a -> Maybe a
Just HashAlg
SHA384
Text
"sha512" -> HashAlg -> Maybe HashAlg
forall a. a -> Maybe a
Just HashAlg
SHA512
Text
_ -> Maybe HashAlg
forall a. Maybe a
Nothing