Safe Haskell | None |
---|---|
Language | Haskell2010 |
- Re-exports
- Key types
- Types abstracting over key types
- Key types in isolation
- Hiding key types
- Operations on keys
- Key IDs
- Signing
- Types
- Utility
- Cache layout
- Repository layout
- Repository layout
- TUF types
- Repository
- Index
- Cache
- Datatypes
- TUF types
- Construction and verification
- JSON aids
- Avoid interpreting signatures
- TUF types
Main entry point into the Hackage Security framework for clients
- module Hackage.Security.JSON
- data Ed25519
- data Key a where
- KeyEd25519 :: PublicKey -> SecretKey -> Key Ed25519
- data PublicKey a where
- data PrivateKey a where
- data KeyType typ where
- somePublicKey :: Some Key -> Some PublicKey
- somePublicKeyType :: Some PublicKey -> Some KeyType
- someKeyId :: HasKeyId key => Some key -> KeyId
- publicKey :: Key a -> PublicKey a
- privateKey :: Key a -> PrivateKey a
- createKey :: KeyType key -> IO (Key key)
- createKey' :: KeyType key -> IO (Some Key)
- newtype KeyId = KeyId {}
- class HasKeyId key where
- sign :: PrivateKey typ -> ByteString -> ByteString
- verify :: PublicKey typ -> ByteString -> ByteString -> Bool
- newtype FileLength = FileLength {
- fileLength :: Int54
- newtype Hash = Hash String
- newtype KeyThreshold = KeyThreshold Int54
- data FileInfo = FileInfo {}
- data HashFn
- newtype Hash = Hash String
- fileInfo :: ByteString -> FileInfo
- computeFileInfo :: FsRoot root => Path root -> IO FileInfo
- compareTrustedFileInfo :: FileInfo -> FileInfo -> Bool
- knownFileInfoEqual :: FileInfo -> FileInfo -> Bool
- fileInfoSHA256 :: FileInfo -> Maybe Hash
- data Int54
- module Hackage.Security.TUF.FileMap
- class HasHeader a where
- newtype FileVersion = FileVersion Int54
- newtype FileExpires = FileExpires (Maybe UTCTime)
- data Header = Header {}
- expiresInDays :: UTCTime -> Integer -> FileExpires
- expiresNever :: FileExpires
- isExpired :: UTCTime -> FileExpires -> Bool
- versionInitial :: FileVersion
- versionIncrement :: FileVersion -> FileVersion
- data CacheLayout = CacheLayout {}
- cabalCacheLayout :: CacheLayout
- data IndexLayout = IndexLayout {
- indexFileToPath :: forall dec. IndexFile dec -> IndexPath
- indexFileFromPath :: IndexPath -> Maybe (Some IndexFile)
- data IndexFile :: * -> * where
- IndexPkgMetadata :: PackageIdentifier -> IndexFile (Signed Targets)
- IndexPkgCabal :: PackageIdentifier -> IndexFile ()
- IndexPkgPrefs :: PackageName -> IndexFile ()
- hackageIndexLayout :: IndexLayout
- indexLayoutPkgMetadata :: IndexLayout -> PackageIdentifier -> IndexPath
- indexLayoutPkgCabal :: IndexLayout -> PackageIdentifier -> IndexPath
- indexLayoutPkgPrefs :: IndexLayout -> PackageName -> IndexPath
- data RepoLayout = RepoLayout {}
- hackageRepoLayout :: RepoLayout
- cabalLocalRepoLayout :: RepoLayout
- data Mirrors = Mirrors {}
- data Mirror = Mirror {}
- data MirrorContent = MirrorFull
- type MirrorDescription = String
- describeMirror :: Mirror -> MirrorDescription
- data RepoRoot
- type RepoPath = Path RepoRoot
- anchorRepoPathLocally :: Path root -> RepoPath -> Path root
- anchorRepoPathRemotely :: Path Web -> RepoPath -> Path Web
- data IndexRoot
- type IndexPath = Path IndexRoot
- data CacheRoot
- type CachePath = Path CacheRoot
- anchorCachePath :: Path root -> CachePath -> Path root
- data Root = Root {}
- data RootRoles = RootRoles {}
- data RoleSpec a = RoleSpec {}
- data Signed a = Signed {
- signed :: a
- signatures :: Signatures
- newtype Signatures = Signatures [Signature]
- data Signature = Signature {}
- unsigned :: a -> Signed a
- withSignatures :: ToJSON WriteJSON a => RepoLayout -> [Some Key] -> a -> Signed a
- withSignatures' :: ToJSON Identity a => [Some Key] -> a -> Signed a
- signRendered :: [Some Key] -> ByteString -> Signatures
- verifySignature :: ByteString -> Signature -> Bool
- signedFromJSON :: (MonadKeys m, FromJSON m a) => JSValue -> m (Signed a)
- verifySignatures :: JSValue -> Signatures -> Bool
- data UninterpretedSignatures a = UninterpretedSignatures {}
- data PreSignature = PreSignature {}
- fromPreSignature :: MonadKeys m => PreSignature -> m Signature
- fromPreSignatures :: MonadKeys m => [PreSignature] -> m Signatures
- toPreSignature :: Signature -> PreSignature
- toPreSignatures :: Signatures -> [PreSignature]
- data Snapshot = Snapshot {}
- data Targets = Targets {}
- data Delegations = Delegations {}
- data DelegationSpec = DelegationSpec {}
- data Delegation = Delegation (Pattern a) (Replacement a)
- targetsLookup :: TargetPath -> Targets -> Maybe FileInfo
- data Timestamp = Timestamp {}
Re-exports
module Hackage.Security.JSON
Key types
Types abstracting over key types
KeyEd25519 :: PublicKey -> SecretKey -> Key Ed25519 |
data PrivateKey a where Source #
SomeShow PrivateKey Source # | |
SomeEq PrivateKey Source # | |
Eq (PrivateKey typ) Source # | |
Show (PrivateKey typ) Source # | |
Key types in isolation
Hiding key types
Operations on keys
privateKey :: Key a -> PrivateKey a Source #
Key IDs
The key ID of a key, by definition, is the hexdigest of the SHA-256 hash of the canonical JSON form of the key where the private object key is excluded.
NOTE: The FromJSON and ToJSON instances for KeyId are ntentially omitted. Use writeKeyAsId instead.
Signing
sign :: PrivateKey typ -> ByteString -> ByteString Source #
Sign a bytestring and return the signature
TODO: It is unfortunate that we have to convert to a strict bytestring for ed25519
verify :: PublicKey typ -> ByteString -> ByteString -> Bool Source #
Types
newtype FileLength Source #
File length
Having verified file length information means we can protect against endless data attacks and similar.
Eq FileLength Source # | |
Ord FileLength Source # | |
Show FileLength Source # | |
ReportSchemaErrors m => FromJSON m FileLength Source # | |
Monad m => ToJSON m FileLength Source # | |
File hash
newtype KeyThreshold Source #
Key threshold
The key threshold is the minimum number of keys a document must be signed
with. Key thresholds are specified in RoleSpec
or DelegationsSpec
.
File information
This intentionally does not have an Eq
instance; see knownFileInfoEqual
and verifyFileInfo
instead.
NOTE: Throughout we compute file information always over the raw bytes.
For example, when timestamp.json
lists the hash of snapshot.json
, this
hash is computed over the actual snapshot.json
file (as opposed to the
canonical form of the embedded JSON). This brings it in line with the hash
computed over target files, where that is the only choice available.
File hash
Utility
fileInfo :: ByteString -> FileInfo Source #
Compute FileInfo
TODO: Currently this will load the entire input bytestring into memory. We need to make this incremental, by computing the length and all hashes in a single traversal over the input.
Re-exports
54-bit integer values
JavaScript can only safely represent numbers between -(2^53 - 1)
and
2^53 - 1
.
TODO: Although we introduce the type here, we don't actually do any bounds
checking and just inherit all type class instance from Int64. We should
probably define fromInteger
to do bounds checking, give different instances
for type classes such as Bounded
and FiniteBits
, etc.
Bounded Int54 Source # | |
Enum Int54 Source # | |
Eq Int54 Source # | |
Integral Int54 Source # | |
Data Int54 Source # | |
Num Int54 Source # | |
Ord Int54 Source # | |
Read Int54 Source # | |
Real Int54 Source # | |
Show Int54 Source # | |
Ix Int54 Source # | |
PrintfArg Int54 Source # | |
Storable Int54 Source # | |
Bits Int54 Source # | |
FiniteBits Int54 Source # | |
ReportSchemaErrors m => FromJSON m Int54 Source # | |
Monad m => ToJSON m Int54 Source # | |
module Hackage.Security.TUF.FileMap
class HasHeader a where Source #
fileExpires :: Lens' a FileExpires Source #
File expiry date
fileVersion :: Lens' a FileVersion Source #
File version (monotonically increasing counter)
newtype FileVersion Source #
newtype FileExpires Source #
File expiry date
A Nothing
value here means no expiry. That makes it possible to set some
files to never expire. (Note that not having the Maybe in the type here still
allows that, because you could set an expiry date 2000 years into the future.
By having the Maybe here we avoid the _need_ for such encoding issues.)
Eq FileExpires Source # | |
Ord FileExpires Source # | |
Show FileExpires Source # | |
ReportSchemaErrors m => FromJSON m FileExpires Source # | |
Monad m => ToJSON m FileExpires Source # | |
Utility
expiresInDays :: UTCTime -> Integer -> FileExpires Source #
Cache layout
data CacheLayout Source #
Location of the various files we cache
Although the generic TUF algorithms do not care how we organize the cache,
we nonetheless specity this here because as long as there are tools which
access files in the cache directly we need to define the cache layout.
See also comments for defaultCacheLayout
.
CacheLayout | |
|
cabalCacheLayout :: CacheLayout Source #
The cache layout cabal-install uses
We cache the index as cache/00-index.tar
; this is important because
`cabal-install` expects to find it there (and does not currently go through
the hackage-security library to get files from the index).
Repository layout
data IndexLayout Source #
Layout of the files within the index tarball
IndexLayout | |
|
data IndexFile :: * -> * where Source #
Files that we might request from the index
The type index tells us the type of the decoded file, if any. For files for
which the library does not support decoding this will be ()
.
NOTE: Clients should NOT rely on this type index being ()
, or they might
break if we add support for parsing additional file formats in the future.
TODO: If we wanted to support legacy Hackage, we should also have a case for the global preferred-versions file. But supporting legacy Hackage will probably require more work anyway..
IndexPkgMetadata :: PackageIdentifier -> IndexFile (Signed Targets) | |
IndexPkgCabal :: PackageIdentifier -> IndexFile () | |
IndexPkgPrefs :: PackageName -> IndexFile () |
hackageIndexLayout :: IndexLayout Source #
The layout of the index as maintained on Hackage
Utility
Repository layout
data RepoLayout Source #
Layout of a repository
RepoLayout | |
|
hackageRepoLayout :: RepoLayout Source #
The layout used on Hackage
cabalLocalRepoLayout :: RepoLayout Source #
Layout used by cabal for ("legacy") local repos
Obviously, such repos do not normally contain any of the TUF files, so their location is more or less arbitrary here.
TUF types
Definition of a mirror
NOTE: Unlike the TUF specification, we require that all mirrors must have
the same format. That is, we omit metapath
and targetspath
.
data MirrorContent Source #
Full versus partial mirrors
The TUF spec explicitly allows for partial mirrors, with the mirrors file specifying (through patterns) what is available from partial mirrors.
For now we only support full mirrors; if we wanted to add partial mirrors,
we would add a second MirrorPartial
constructor here with arguments
corresponding to TUF's metacontent
and targetscontent
fields.
Utility
type MirrorDescription = String Source #
describeMirror :: Mirror -> MirrorDescription Source #
Give a human-readable description of a particular mirror
(for use in error messages)
Repository
The root of the repository
Repository roots can be anchored at a remote URL or a local directory.
Note that even for remote repos RepoRoot
is (potentially) different from
Web
-- for a repository located at, say, http://hackage.haskell.org
they happen to coincide, but for one location at
http://example.com/some/subdirectory
they do not.
Index
Cache
anchorCachePath :: Path root -> CachePath -> Path root Source #
Anchor a cache path to the location of the cache
Datatypes
The root metadata
NOTE: We must have the invariant that ALL keys (apart from delegation keys)
must be listed in rootKeys
. (Delegation keys satisfy a similar invariant,
see Targets.)
Root | |
|
Role specification
The phantom type indicates what kind of type this role is meant to verify.
TUF types
Signed | |
|
MonadKeys m => FromJSON m (Signed Mirrors) Source # | |
(MonadKeys m, MonadReader RepoLayout m) => FromJSON m (Signed Timestamp) Source # | |
MonadKeys m => FromJSON m (Signed Targets) Source # | |
(MonadKeys m, MonadReader RepoLayout m) => FromJSON m (Signed Snapshot) Source # | |
MonadKeys m => FromJSON m (Signed Root) Source # | We give an instance for Signed Root rather than Root because the key environment from the root data is necessary to resolve the explicit sharing in the signatures. |
(Monad m, ToJSON m a) => ToJSON m (Signed a) Source # | |
newtype Signatures Source #
A list of signatures
Invariant: each signature must be made with a different key.
We enforce this invariant for incoming untrusted data (fromPreSignatures
)
but not for lists of signatures that we create in code.
MonadKeys m => FromJSON m Signatures Source # | |
Monad m => ToJSON m Signatures Source # | |
Construction and verification
withSignatures :: ToJSON WriteJSON a => RepoLayout -> [Some Key] -> a -> Signed a Source #
Sign a document
withSignatures' :: ToJSON Identity a => [Some Key] -> a -> Signed a Source #
Variation on withSignatures
that doesn't need the repo layout
signRendered :: [Some Key] -> ByteString -> Signatures Source #
Construct signatures for already rendered value
verifySignature :: ByteString -> Signature -> Bool Source #
JSON aids
signedFromJSON :: (MonadKeys m, FromJSON m a) => JSValue -> m (Signed a) Source #
General FromJSON instance for signed datatypes
We don't give a general FromJSON instance for Signed because for some datatypes we need to do something special (datatypes where we need to read key environments); for instance, see the "Signed Root" instance.
verifySignatures :: JSValue -> Signatures -> Bool Source #
Signature verification
NOTES: 1. By definition, the signature must be verified against the canonical JSON format. This means we _must_ parse and then pretty print (as we do here) because the document as stored may or may not be in canonical format. 2. However, it is important that we NOT translate from the JSValue to whatever internal datatype we are using and then back to JSValue, because that may not roundtrip: we must allow for additional fields in the JSValue that we ignore (and would therefore lose when we attempt to roundtrip). 3. We verify that all signatures are valid, but we cannot verify (here) that these signatures are signed with the right key, or that we have a sufficient number of signatures. This will be the responsibility of the calling code.
Avoid interpreting signatures
data UninterpretedSignatures a Source #
File with uninterpreted signatures
Sometimes we want to be able to read a file without interpreting the signatures (that is, resolving the key IDs) or doing any kind of checks on them. One advantage of this is that this allows us to read many file types without any key environment at all, which is sometimes useful.
(ReportSchemaErrors m, FromJSON m a) => FromJSON m (UninterpretedSignatures a) Source # | |
(Monad m, ToJSON m a) => ToJSON m (UninterpretedSignatures a) Source # | |
Show a => Show (UninterpretedSignatures a) Source # | |
data PreSignature Source #
A signature with a key ID (rather than an actual key)
This corresponds precisely to the TUF representation of a signature.
Show PreSignature Source # | |
ReportSchemaErrors m => FromJSON m PreSignature Source # | |
Monad m => ToJSON m PreSignature Source # | |
Utility
fromPreSignature :: MonadKeys m => PreSignature -> m Signature Source #
Convert a pre-signature to a signature
Verifies that the key type matches the advertised method.
fromPreSignatures :: MonadKeys m => [PreSignature] -> m Signatures Source #
Convert a list of PreSignature
s to a list of Signature
s
This verifies the invariant that all signatures are made with different keys. We do this on the presignatures rather than the signatures so that we can do the check on key IDs, rather than keys (the latter don't have an Ord instance).
toPreSignature :: Signature -> PreSignature Source #
Convert signature to pre-signature
toPreSignatures :: Signatures -> [PreSignature] Source #
Convert list of pre-signatures to a list of signatures
Snapshot | |
|
HasHeader Snapshot Source # | |
VerifyRole Snapshot Source # | |
(MonadReader RepoLayout m, MonadError DeserializationError m, ReportSchemaErrors m) => FromJSON m Snapshot Source # | |
MonadReader RepoLayout m => ToJSON m Snapshot Source # | |
(MonadKeys m, MonadReader RepoLayout m) => FromJSON m (Signed Snapshot) Source # | |
TUF types
Target metadata
Most target files do not need expiry dates because they are not subject to change (and hence attacks like freeze attacks are not a concern).
data Delegations Source #
Delegations
Much like the Root datatype, this must have an invariant that ALL used keys
(apart from the global keys, which are in the root key environment) must
be listed in delegationsKeys
.
Show Delegations Source # | |
MonadKeys m => FromJSON m Delegations Source # | |
Monad m => ToJSON m Delegations Source # | |
data DelegationSpec Source #
Delegation specification
NOTE: This is a close analogue of RoleSpec
.
Show DelegationSpec Source # | |
MonadKeys m => FromJSON m DelegationSpec Source # | |
Monad m => ToJSON m DelegationSpec Source # | |
data Delegation Source #
A delegation
A delegation is a pair of a pattern and a replacement.
See match
for an example.
Delegation (Pattern a) (Replacement a) |
Util
targetsLookup :: TargetPath -> Targets -> Maybe FileInfo Source #
HasHeader Timestamp Source # | |
VerifyRole Timestamp Source # | |
(MonadReader RepoLayout m, MonadError DeserializationError m, ReportSchemaErrors m) => FromJSON m Timestamp Source # | |
MonadReader RepoLayout m => ToJSON m Timestamp Source # | |
(MonadKeys m, MonadReader RepoLayout m) => FromJSON m (Signed Timestamp) Source # | |