Add image decryption

This commit is contained in:
Emi Simpson 2022-11-05 14:47:35 -04:00
parent 99422b1f0a
commit c12ceba496
Signed by: Emi
GPG Key ID: A12F2C2FFDC3D847
2 changed files with 50 additions and 10 deletions

View File

@ -27,12 +27,14 @@ to generate this file without the comments in this block.
, "foldable-traversable" , "foldable-traversable"
, "maybe" , "maybe"
, "newtype" , "newtype"
, "parallel"
, "parsing" , "parsing"
, "prelude" , "prelude"
, "protobuf" , "protobuf"
, "strings" , "strings"
, "subtlecrypto" , "subtlecrypto"
, "transformers" , "transformers"
, "web-file"
, "web-html" , "web-html"
] ]
, packages = ./packages.dhall , packages = ./packages.dhall

View File

@ -1,9 +1,11 @@
module Main where module Main where
import Prelude import Prelude
import AviaryFormat.Format (Format, Image, Index, parseIndex) as Format import AviaryFormat.Format (Format(..), Image, Index, parseIndex) as Format
import Aviary.FFI (arrayBufferToBlob)
import Control.Monad.Error.Class (try) import Control.Monad.Error.Class (try)
import Control.Parallel (parTraverse)
import Effect (Effect) import Effect (Effect)
import Effect.Aff (Aff, launchAff) import Effect.Aff (Aff, launchAff)
import Effect.Class (liftEffect) import Effect.Class (liftEffect)
@ -16,15 +18,15 @@ import Data.Either (Either(..), hush, note)
import Data.Maybe (Maybe(..)) import Data.Maybe (Maybe(..))
import Data.Newtype (unwrap) import Data.Newtype (unwrap)
import Data.ArrayBuffer.ArrayBuffer (byteLength) import Data.ArrayBuffer.ArrayBuffer (byteLength)
import Data.ArrayBuffer.Builder (execPut, putInt8) import Data.ArrayBuffer.Builder (DataBuff, execPut, putDataBuff, putInt8)
import Data.ArrayBuffer.Builder.Internal (toView) import Data.ArrayBuffer.DataView (whole)
import Data.ArrayBuffer.DataView (buffer, whole)
import Data.ArrayBuffer.Types (ArrayBuffer) import Data.ArrayBuffer.Types (ArrayBuffer)
import Data.Filterable (partitionMap) import Data.Filterable (partitionMap)
import Data.Foldable (traverse_) import Data.Foldable (traverse_)
import Data.String (drop, indexOf', indexOf, splitAt) import Data.String (drop, indexOf', indexOf, splitAt)
import Data.String.Pattern (Pattern(..)) import Data.String.Pattern (Pattern(..))
import Parsing (runParserT) import Parsing (runParserT)
import Web.File.Url (createObjectURL)
import Web.HTML (window) as HTML import Web.HTML (window) as HTML
import Web.HTML.Window (location) import Web.HTML.Window (location)
import Web.HTML.Location (hash, origin, pathname) as Location import Web.HTML.Location (hash, origin, pathname) as Location
@ -38,12 +40,19 @@ import Crypto.Subtle.Key.Types (CryptoKey, decrypt, raw)
nonceE :: Effect ArrayBuffer nonceE :: Effect ArrayBuffer
nonceE = execPut $ traverse_ putInt8 [0xd0, 0xc3, 0x75, 0x56, 0x58, 0xc1, 0x7e, 0x5f, 0xd6, 0xcc, 0xb6, 0x76] nonceE = execPut $ traverse_ putInt8 [0xd0, 0xc3, 0x75, 0x56, 0x58, 0xc1, 0x7e, 0x5f, 0xd6, 0xcc, 0xb6, 0x76]
databuffToBuffer :: DataBuff -> Effect ArrayBuffer
databuffToBuffer = execPut <<< putDataBuff
data AviaryError data AviaryError
= MalformedKey = ServerError
| UnexpectedStatusCode Int
| MalformedKey
| MalformedUrl | MalformedUrl
| DecryptFailed | DecryptFailed
| ImageDecryptFailed
| StrangeIndex | StrangeIndex
| IndexMissingField String | IndexMissingField String
| ImageNotFound
type Image = type Image =
{ key :: Aff CryptoKey { key :: Aff CryptoKey
@ -60,11 +69,15 @@ type Index =
} }
instance showAviaryError :: Show AviaryError where instance showAviaryError :: Show AviaryError where
show ServerError = "The server encountered an error"
show MalformedKey = "Malformed key!!" show MalformedKey = "Malformed key!!"
show MalformedUrl = "Malformed url!!" show MalformedUrl = "Malformed url!!"
show DecryptFailed = "Either the data provided by the server was bad, or valid but incorrect key. Expiration likely." show DecryptFailed = "Either the data provided by the server was bad, or valid but incorrect key. Expiration likely."
show ImageDecryptFailed = "The decryption of the album's index succeeded, but it contained a reference to an image whose decryption failed. This likely indicates expiration"
show StrangeIndex = "The gallery index matched with the provided key, but was in a strange format. This could indicate a severe version mismatch, or that the gallery was created by a malfunctioning client." show StrangeIndex = "The gallery index matched with the provided key, but was in a strange format. This could indicate a severe version mismatch, or that the gallery was created by a malfunctioning client."
show (IndexMissingField f) = "The gallery index was missing the crucial field " <> f <> ". This is almost certainly the result of a version mismatch or that the creator's client was not fully up-to-spec." show (IndexMissingField f) = "The gallery index was missing the crucial field " <> f <> ". This is almost certainly the result of a version mismatch or that the creator's client was not fully up-to-spec."
show ImageNotFound = "The image was not found, likely indicating that it expired"
show (UnexpectedStatusCode status) = "Server returned an unexpected status code: " <> (show status)
type UrlInfo = type UrlInfo =
{ server :: String { server :: String
@ -72,6 +85,13 @@ type UrlInfo =
, key :: String , key :: String
} }
formatToMime :: Format.Format -> String
formatToMime Format.Format_WEBP = "image/webp"
formatToMime Format.Format_AVIF = "image/avif"
formatToMime Format.Format_JPG = "image/jpeg"
formatToMime Format.Format_PNG = "image/png"
formatToMime Format.Format_GIF = "image/gif"
parseUrl :: String -> Either AviaryError UrlInfo parseUrl :: String -> Either AviaryError UrlInfo
parseUrl url = note MalformedUrl do parseUrl url = note MalformedUrl do
slashIndex <- indexOf' (Pattern "/") 8 url slashIndex <- indexOf' (Pattern "/") 8 url
@ -99,7 +119,7 @@ convertImageFromProtobuf protoimage = let protoimage' = unwrap protoimage in do
thumbUrl <- note (IndexMissingField "images[].thumb_url") protoimage'.thumb_url thumbUrl <- note (IndexMissingField "images[].thumb_url") protoimage'.thumb_url
blurhash <- note (IndexMissingField "images[].blurhash") protoimage'.blurhash blurhash <- note (IndexMissingField "images[].blurhash") protoimage'.blurhash
format <- note (IndexMissingField "images[].format") protoimage'.format format <- note (IndexMissingField "images[].format") protoimage'.format
let key = SC.importKey raw (buffer $ toView $ unwrap rawKey) (aes aesGCM) false [decrypt] let key = importKey =<< liftEffect (databuffToBuffer $ unwrap rawKey)
pure { key, fullUrl, thumbUrl, blurhash, format } pure { key, fullUrl, thumbUrl, blurhash, format }
convertIndexFromProtobuf :: Format.Index -> Either AviaryError Index convertIndexFromProtobuf :: Format.Index -> Either AviaryError Index
@ -119,8 +139,25 @@ convertIndexFromProtobuf protoindex =
parseIndex :: ArrayBuffer -> Effect (Either AviaryError Index) parseIndex :: ArrayBuffer -> Effect (Either AviaryError Index)
parseIndex rawData = do parseIndex rawData = do
parseResult <- runParserT (whole rawData) (Format.parseIndex (byteLength rawData)) parseResult <- runParserT (whole rawData) (Format.parseIndex (byteLength rawData))
_ <- liftEffect $ log $ show (parseResult)
pure $ convertIndexFromProtobuf =<< (note StrangeIndex $ hush parseResult) pure $ convertIndexFromProtobuf =<< (note StrangeIndex $ hush parseResult)
fetchImageAsBlobUrl :: ArrayBuffer -> Image -> Aff (Either AviaryError String)
fetchImageAsBlobUrl nonce image = do
cryptoKey <- image.key
{ status, arrayBuffer } <- fetch (image.fullUrl <> ".bin") {}
case status of
200 -> do
encryptedImage <- arrayBuffer
decryptedImage <- try $ decryptBlob cryptoKey encryptedImage nonce
case decryptedImage of
Left _ -> pure $ Left ImageDecryptFailed
Right rawImage -> do
url <- liftEffect $ createObjectURL $ (arrayBufferToBlob $ formatToMime image.format) rawImage
pure $ Right url
404 -> pure $ Left ImageNotFound
s -> pure $ Left $ if s / 100 == 5 then ServerError else UnexpectedStatusCode s
main_aff :: Aff Unit main_aff :: Aff Unit
main_aff = do main_aff = do
urlInfo <- liftEffect do urlInfo <- liftEffect do
@ -143,10 +180,11 @@ main_aff = do
_ <- liftEffect $ log $ message err _ <- liftEffect $ log $ message err
pure $ Left $ DecryptFailed pure $ Left $ DecryptFailed
Right serializedIndex' -> do Right serializedIndex' -> do
_ <- liftEffect $ log "BBBBBBBBB!" liftEffect $ parseIndex serializedIndex'
maybeIndex <- liftEffect $ parseIndex serializedIndex' maybeUrls <- case maybeIndex of
pure ((\i -> i.title) <$> maybeIndex) Left err -> pure $ Left err
liftEffect $ log $ show maybeIndex Right index -> Right <$> parTraverse (fetchImageAsBlobUrl urlInfo.nonce) index.images
liftEffect $ log $ show maybeUrls
main :: Effect Unit main :: Effect Unit
main = launchAff main_aff $> unit main = launchAff main_aff $> unit