Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow and add warning for HTTP anchor-data #979

Merged
merged 1 commit into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Cardano.CLI.EraBased.Commands.Governance.Actions
import qualified Cardano.CLI.EraBased.Commands.Governance.Actions as Cmd
import Cardano.CLI.Json.Friendly
import Cardano.CLI.Read
import Cardano.CLI.Run.Hash (getByteStringFromURL, httpsAndIpfsSchemas)
import Cardano.CLI.Run.Hash (getByteStringFromURL, httpsAndIpfsSchemes)
import Cardano.CLI.Types.Common
import Cardano.CLI.Types.Errors.GovernanceActionsError
import Cardano.CLI.Types.Errors.HashCmdError (FetchURLError)
Expand Down Expand Up @@ -534,7 +534,7 @@ carryHashChecks checkHash anchor checkType =
L.AnchorData
<$> fetchURLErrorToGovernanceActionError
checkType
(getByteStringFromURL httpsAndIpfsSchemas $ L.urlToText $ L.anchorUrl anchor)
(getByteStringFromURL httpsAndIpfsSchemes $ L.urlToText $ L.anchorUrl anchor)
let hash = L.hashAnchorData anchorData
when (hash /= L.anchorDataHash anchor) $
left $
Expand Down
4 changes: 2 additions & 2 deletions cardano-cli/src/Cardano/CLI/EraBased/Run/Governance/DRep.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import qualified Cardano.Api.Ledger as L

import qualified Cardano.CLI.Commands.Hash as Cmd
import qualified Cardano.CLI.EraBased.Commands.Governance.DRep as Cmd
import Cardano.CLI.Run.Hash (allSchemas, carryHashChecks, getByteStringFromURL)
import Cardano.CLI.Run.Hash (allSchemes, carryHashChecks, getByteStringFromURL)
import qualified Cardano.CLI.Run.Key as Key
import Cardano.CLI.Types.Common
import Cardano.CLI.Types.Errors.CmdError
Expand Down Expand Up @@ -187,7 +187,7 @@ runGovernanceDRepMetadataHashCmd
Cmd.DrepMetadataFileIn metadataFile ->
firstExceptT ReadFileError . newExceptT $ readByteStringFile metadataFile
Cmd.DrepMetadataURL urlText ->
fetchURLToGovernanceCmdError $ getByteStringFromURL allSchemas $ L.urlToText urlText
fetchURLToGovernanceCmdError $ getByteStringFromURL allSchemes $ L.urlToText urlText
palas marked this conversation as resolved.
Show resolved Hide resolved
let (_metadata, metadataHash) = hashDRepMetadata metadataBytes
case hashGoal of
Cmd.CheckHash expectedHash
Expand Down
9 changes: 6 additions & 3 deletions cardano-cli/src/Cardano/CLI/EraBased/Run/StakePool.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import Cardano.Api.Shelley
import qualified Cardano.CLI.Commands.Hash as Cmd
import Cardano.CLI.EraBased.Commands.StakePool
import qualified Cardano.CLI.EraBased.Commands.StakePool as Cmd
import Cardano.CLI.Run.Hash (allSchemas, getByteStringFromURL, httpsAndIpfsSchemas)
import Cardano.CLI.Run.Hash (allSchemes, getByteStringFromURL, httpsAndIpfsSchemes)
import Cardano.CLI.Types.Common
import Cardano.CLI.Types.Errors.HashCmdError (FetchURLError (..))
import Cardano.CLI.Types.Errors.StakePoolCmdError
Expand Down Expand Up @@ -235,7 +235,7 @@ runStakePoolMetadataHashCmd
. newExceptT
$ readByteStringFile poolMetadataFile
StakePoolMetadataURL urlText ->
fetchURLToStakePoolCmdError $ getByteStringFromURL allSchemas $ L.urlToText urlText
fetchURLToStakePoolCmdError $ getByteStringFromURL allSchemes $ L.urlToText urlText

(_metadata, metadataHash) <-
firstExceptT StakePoolCmdMetadataValidationError
Expand Down Expand Up @@ -275,7 +275,10 @@ carryHashChecks potentiallyCheckedAnchor =
metadataBytes <-
withExceptT
StakePoolCmdFetchURLError
(getByteStringFromURL httpsAndIpfsSchemas urlText)
( getByteStringFromURL
httpsAndIpfsSchemes
urlText
)

let expectedHash = stakePoolMetadataHash anchor

Expand Down
17 changes: 17 additions & 0 deletions cardano-cli/src/Cardano/CLI/Parser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Cardano.CLI.Parser
, readURIOfMaxLength
, subParser
, eDNSName
, stringToAnchorScheme
)
where

Expand All @@ -24,6 +25,7 @@ import Cardano.CLI.Types.Common
import qualified Data.Attoparsec.ByteString.Char8 as Atto
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BSC
import Data.Char (toLower)
import Data.Foldable
import Data.Ratio ((%))
import Data.Text (Text)
Expand Down Expand Up @@ -125,6 +127,21 @@ subParser :: String -> Opt.ParserInfo a -> Opt.Parser a
subParser availableCommand pInfo =
Opt.hsubparser $ Opt.command availableCommand pInfo <> Opt.metavar availableCommand

-- | Converts a string to an 'AnchorScheme' if it is a valid scheme and is in the
-- 'SupportedScheme' list, otherwise it returns 'Left'.
stringToAnchorScheme :: SupportedSchemes -> String -> Either String AnchorScheme
stringToAnchorScheme supportedSchemes schemaString = do
case convertToAnchorScheme $ map toLower schemaString of
Just scheme | scheme `elem` supportedSchemes -> pure scheme
_ -> Left $ "Unsupported URL scheme: " <> schemaString
where
convertToAnchorScheme :: String -> Maybe AnchorScheme
convertToAnchorScheme "file:" = Just FileScheme
convertToAnchorScheme "http:" = Just HttpScheme
convertToAnchorScheme "https:" = Just HttpsScheme
convertToAnchorScheme "ipfs:" = Just IpfsScheme
convertToAnchorScheme _ = Nothing

eDNSName :: String -> Either String ByteString
eDNSName str =
-- We're using 'Shelley.textToDns' to validate the string.
Expand Down
69 changes: 41 additions & 28 deletions cardano-cli/src/Cardano/CLI/Run/Hash.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,23 @@
module Cardano.CLI.Run.Hash
( runHashCmds
, getByteStringFromURL
, SupportedSchemas (..)
, allSchemas
, httpsAndIpfsSchemas
, carryHashChecks
, allSchemes
, httpsAndIpfsSchemes
)
where

import Cardano.Api
import qualified Cardano.Api.Ledger as L

import qualified Cardano.CLI.Commands.Hash as Cmd
import Cardano.CLI.Parser (stringToAnchorScheme)
import Cardano.CLI.Read
import Cardano.CLI.Types.Common (MustCheckHash (..), PotentiallyCheckedAnchor (..))
import Cardano.CLI.Types.Common (AnchorScheme (..), MustCheckHash (..),
PotentiallyCheckedAnchor (..), SupportedSchemes)
import Cardano.CLI.Types.Errors.HashCmdError
import Cardano.Crypto.Hash (hashToTextAsHex)
import Cardano.Prelude (first)

import Control.Exception (throw)
import Control.Monad (when)
Expand All @@ -32,7 +34,6 @@ import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Lazy.Char8 as BSL8
import Data.Char (toLower)
import Data.Function
import Data.List (intercalate)
import Data.Text (Text)
Expand Down Expand Up @@ -71,7 +72,7 @@ runHashAnchorDataCmd Cmd.HashAnchorDataCmdArgs{toHash, hashGoal} = do
return $ Text.encodeUtf8 text
Cmd.AnchorDataHashSourceText text -> return $ Text.encodeUtf8 text
Cmd.AnchorDataHashSourceURL urlText ->
fetchURLToHashCmdError $ getByteStringFromURL allSchemas $ L.urlToText urlText
fetchURLToHashCmdError $ getByteStringFromURL allSchemes $ L.urlToText urlText
let hash = L.hashAnchorData anchorData
case hashGoal of
Cmd.CheckHash expectedHash
Expand All @@ -94,30 +95,42 @@ runHashAnchorDataCmd Cmd.HashAnchorDataCmdArgs{toHash, hashGoal} = do
:: ExceptT FetchURLError IO BS8.ByteString -> ExceptT HashCmdError IO BS8.ByteString
fetchURLToHashCmdError = withExceptT HashFetchURLError

data SupportedSchemas = FileSchema | HttpSchema | HttpsSchema | IpfsSchema
deriving (Show, Eq)

allSchemas :: [SupportedSchemas]
allSchemas = [FileSchema, HttpSchema, HttpsSchema, IpfsSchema]

httpsAndIpfsSchemas :: [SupportedSchemas]
httpsAndIpfsSchemas = [HttpsSchema, IpfsSchema]

getByteStringFromURL :: [SupportedSchemas] -> Text -> ExceptT FetchURLError IO BS.ByteString
getByteStringFromURL supportedSchemas urlText = do
-- | All the supported schemes are allowed.
allSchemes :: SupportedSchemes
allSchemes = [FileScheme, HttpScheme, HttpsScheme, IpfsScheme]

-- | Only HTTPS and IPFS schemes are allowed. We also allow HTTP for testing purposes
-- but it is discouraged, because it can lead to security vulnerabilities.
-- For example: If a user checks the anchor-data through a web browser and through the
-- `cardano-cli` independently, one of them could easily get spoofed, and the user would
-- not notice that the anchor-data being verified in the browser is not the same.
httpsAndIpfsSchemes :: SupportedSchemes
httpsAndIpfsSchemes =
[ HttpScheme -- Insecure, only for testing purposes
, HttpsScheme
, IpfsScheme
]

-- | Fetches the content of a URL as a 'ByteString'.
-- The URL must be an absolute URL. The supported schemes are specified in the 'SupportedSchemes' argument.
-- If the scheme is not supported, an error is thrown.
getByteStringFromURL :: SupportedSchemes -> Text -> ExceptT FetchURLError IO BS.ByteString
palas marked this conversation as resolved.
Show resolved Hide resolved
getByteStringFromURL supportedSchemes urlText = do
let urlString = Text.unpack urlText
uri <- hoistMaybe (FetchURLInvalidURLError urlString) $ parseAbsoluteURI urlString
case map toLower $ uriScheme uri of
"file:"
| FileSchema `elem` supportedSchemas ->
let path = uriPathToFilePath (pathSegments uri)
in handleIOExceptT (FetchURLReadFileError path) $ BS.readFile path
"http:" | HttpSchema `elem` supportedSchemas -> getFileFromHttp uri
"https:" | HttpsSchema `elem` supportedSchemas -> getFileFromHttp uri
"ipfs:" | IpfsSchema `elem` supportedSchemas -> do
uri@URI{uriScheme} <- hoistMaybe (FetchURLInvalidURLError urlString) $ parseAbsoluteURI urlString
scheme <-
hoistEither $
first FetchURLUnsupportedURLSchemeError $
stringToAnchorScheme supportedSchemes uriScheme
case scheme of
FileScheme ->
let path = uriPathToFilePath (pathSegments uri)
in handleIOExceptT (FetchURLReadFileError path) $ BS.readFile path
HttpScheme -> getFileFromHttp uri
HttpsScheme -> getFileFromHttp uri
IpfsScheme -> do
httpUri <- convertToHttp uri
getFileFromHttp httpUri
unsupportedScheme -> left $ FetchURLUnsupportedURLSchemeError unsupportedScheme
where
uriPathToFilePath :: [String] -> FilePath
uriPathToFilePath allPath@(letter : path) =
Expand Down Expand Up @@ -196,7 +209,7 @@ carryHashChecks potentiallyCheckedAnchor =
L.AnchorData
<$> withExceptT
FetchURLError
(getByteStringFromURL httpsAndIpfsSchemas $ L.urlToText $ L.anchorUrl anchor)
(getByteStringFromURL httpsAndIpfsSchemes $ L.urlToText $ L.anchorUrl anchor)
let hash = L.hashAnchorData anchorData
when (hash /= L.anchorDataHash anchor) $
left $
Expand Down
9 changes: 9 additions & 0 deletions cardano-cli/src/Cardano/CLI/Types/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
module Cardano.CLI.Types.Common
( AllOrOnly (..)
, AddressKeyType (..)
, AnchorScheme (..)
, AnyPlutusScriptVersion (..)
, BalanceTxExecUnits (..)
, BlockId (..)
Expand Down Expand Up @@ -65,6 +66,7 @@ module Cardano.CLI.Types.Common
, SomeKeyFile (..)
, StakeDelegators (..)
, StakePoolMetadataFile
, SupportedSchemes
, TransferDirection (..)
, TxBodyFile
, TxBuildOutputOptions (..)
Expand Down Expand Up @@ -136,6 +138,13 @@ newtype ProposalUrl = ProposalUrl
}
deriving (Eq, Show)

-- | Specifies the schemes that are allowed to fetch anchor data.
type SupportedSchemes = [AnchorScheme]

-- | The different schemes that can be used to fetch anchor data.
data AnchorScheme = FileScheme | HttpScheme | HttpsScheme | IpfsScheme
deriving (Show, Eq)

-- | Tag for tracking proposals submitted as 'Bytestring'
data ProposalBinary

Expand Down
2 changes: 1 addition & 1 deletion cardano-cli/src/Cardano/CLI/Types/Errors/HashCmdError.hs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ instance Exception FetchURLError where
displayException (FetchURLUnsupportedURLSchemeError text) = "Unsupported URL scheme: " <> text
displayException (FetchURLReadEnvVarError exc) = "Cannot read environment variable: " <> displayException exc
displayException (FetchURLGetFileFromHttpError err) = displayException err
displayException FetchURLIpfsGatewayNotSetError = "IPFS schema requires IPFS_GATEWAY_URI environment variable to be set."
displayException FetchURLIpfsGatewayNotSetError = "IPFS scheme requires IPFS_GATEWAY_URI environment variable to be set."

data HttpRequestError
= BadStatusCodeHRE !Int !String
Expand Down
Loading