mirror of
https://github.com/haskell-nix/hnix-store.git
synced 2024-09-11 18:05:56 +03:00
Merge pull request #264 from squalus/fingerprint
core: add fingerprints
This commit is contained in:
commit
d8e337513f
@ -62,6 +62,7 @@ library
|
||||
, System.Nix.ContentAddress
|
||||
, System.Nix.Derivation
|
||||
, System.Nix.DerivedPath
|
||||
, System.Nix.Fingerprint
|
||||
, System.Nix.Hash
|
||||
, System.Nix.Hash.Truncation
|
||||
, System.Nix.Signature
|
||||
@ -98,6 +99,7 @@ test-suite core
|
||||
main-is: Driver.hs
|
||||
other-modules:
|
||||
Derivation
|
||||
Fingerprint
|
||||
Hash
|
||||
Signature
|
||||
hs-source-dirs:
|
||||
@ -111,6 +113,7 @@ test-suite core
|
||||
, base16-bytestring
|
||||
, base64-bytestring
|
||||
, bytestring
|
||||
, containers
|
||||
, crypton
|
||||
, data-default-class
|
||||
, hspec
|
||||
@ -118,3 +121,5 @@ test-suite core
|
||||
, tasty-golden
|
||||
, tasty-hspec
|
||||
, text
|
||||
, time
|
||||
, unordered-containers
|
||||
|
46
hnix-store-core/src/System/Nix/Fingerprint.hs
Normal file
46
hnix-store-core/src/System/Nix/Fingerprint.hs
Normal file
@ -0,0 +1,46 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
{-|
|
||||
Description : Fingerprint of Nix store path metadata used for signature verification
|
||||
-}
|
||||
module System.Nix.Fingerprint
|
||||
( fingerprint
|
||||
, metadataFingerprint
|
||||
) where
|
||||
|
||||
import Crypto.Hash (Digest)
|
||||
import Data.Dependent.Sum (DSum)
|
||||
import Data.List (sort)
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Text (Text)
|
||||
import Data.Text.Lazy (toStrict)
|
||||
import Data.Text.Lazy.Builder (toLazyText)
|
||||
import Data.Word (Word64)
|
||||
import System.Nix.Hash (HashAlgo, algoDigestBuilder)
|
||||
import System.Nix.StorePath
|
||||
import System.Nix.StorePath.Metadata (Metadata(..))
|
||||
|
||||
import qualified Data.HashSet as HashSet
|
||||
import qualified Data.Text as Text
|
||||
|
||||
-- | Produce the message signed by a NAR signature
|
||||
metadataFingerprint :: StoreDir -> StorePath -> Metadata StorePath -> Text
|
||||
metadataFingerprint storeDir storePath Metadata{..} = let
|
||||
narSize = fromMaybe 0 narBytes
|
||||
in fingerprint storeDir storePath narHash narSize (HashSet.toList references)
|
||||
|
||||
-- | Produce the message signed by a NAR signature
|
||||
fingerprint :: StoreDir
|
||||
-> StorePath
|
||||
-> DSum HashAlgo Digest -- ^ NAR hash
|
||||
-> Word64 -- ^ NAR size, in bytes
|
||||
-> [StorePath] -- ^ References
|
||||
-> Text
|
||||
fingerprint storeDir storePath narHash narSize refs = let
|
||||
encodedStorePath = storePathToText storeDir storePath
|
||||
encodedNarHash = (toStrict . toLazyText . algoDigestBuilder) narHash
|
||||
encodedNarSize = (Text.pack . show) narSize
|
||||
sortedRefs = sort (storePathToText storeDir <$> refs)
|
||||
encodedRefs = Text.intercalate "," sortedRefs
|
||||
in Text.intercalate ";" [ "1", encodedStorePath, encodedNarHash, encodedNarSize, encodedRefs]
|
||||
|
69
hnix-store-core/tests/Fingerprint.hs
Normal file
69
hnix-store-core/tests/Fingerprint.hs
Normal file
@ -0,0 +1,69 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
-- Test case from https://code.tvl.fyi/commit/tvix/nix-compat/src/narinfo/fingerprint.rs?id=a834966efd64c1b2306241c3ef20f4258f6b9c4e
|
||||
|
||||
module Fingerprint where
|
||||
|
||||
import Crypto.Error (CryptoFailable(..))
|
||||
import Data.Default.Class
|
||||
import System.Nix.Base (decodeWith, BaseEncoding(Base64))
|
||||
import System.Nix.Fingerprint
|
||||
import System.Nix.Signature
|
||||
import System.Nix.StorePath
|
||||
import System.Nix.StorePath.Metadata
|
||||
import System.Nix.Hash (mkNamedDigest)
|
||||
import Data.Text (Text)
|
||||
import Data.Time.Clock (UTCTime(..))
|
||||
import Data.Time.Calendar.OrdinalDate (fromOrdinalDate)
|
||||
import Test.Hspec
|
||||
|
||||
import qualified Crypto.PubKey.Ed25519 as Ed25519
|
||||
import qualified Data.HashSet as HashSet
|
||||
import qualified Data.Set as Set
|
||||
import qualified Data.Text.Encoding as Text
|
||||
|
||||
spec_fingerprint :: Spec
|
||||
spec_fingerprint = do
|
||||
|
||||
describe "fingerprint" $ do
|
||||
|
||||
it "is valid for example metadata" $
|
||||
metadataFingerprint def exampleStorePath exampleMetadata `shouldBe` exampleFingerprint
|
||||
|
||||
it "allows a successful signature verification" $ do
|
||||
let msg = Text.encodeUtf8 $ metadataFingerprint def exampleStorePath exampleMetadata
|
||||
Signature sig' = head $ sig <$> filter (\(NarSignature publicKey _) -> publicKey == "cache.nixos.org-1") (Set.toList (sigs exampleMetadata))
|
||||
sig' `shouldSatisfy` Ed25519.verify pubkey msg
|
||||
|
||||
exampleFingerprint :: Text
|
||||
exampleFingerprint = "1;/nix/store/syd87l2rxw8cbsxmxl853h0r6pdwhwjr-curl-7.82.0-bin;sha256:1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0;196040;/nix/store/0jqd0rlxzra1rs38rdxl43yh6rxchgc6-curl-7.82.0,/nix/store/6w8g7njm4mck5dmjxws0z1xnrxvl81xa-glibc-2.34-115,/nix/store/j5jxw3iy7bbz4a57fh9g2xm2gxmyal8h-zlib-1.2.12,/nix/store/yxvjs9drzsphm9pcf42a4byzj1kb9m7k-openssl-1.1.1n";
|
||||
|
||||
exampleStorePath :: StorePath
|
||||
exampleStorePath = forceRight $ parsePath def "/nix/store/syd87l2rxw8cbsxmxl853h0r6pdwhwjr-curl-7.82.0-bin"
|
||||
|
||||
exampleMetadata :: Metadata StorePath
|
||||
exampleMetadata = Metadata
|
||||
{ deriverPath = Just $ forceRight $ parsePath def "/nix/store/5rwxzi7pal3qhpsyfc16gzkh939q1np6-curl-7.82.0.drv"
|
||||
, narHash = forceRight $ mkNamedDigest "sha256" "1b4sb93wp679q4zx9k1ignby1yna3z7c4c2ri3wphylbc2dwsys0"
|
||||
, references = HashSet.fromList $ forceRight . parsePath def <$> ["/nix/store/0jqd0rlxzra1rs38rdxl43yh6rxchgc6-curl-7.82.0","/nix/store/6w8g7njm4mck5dmjxws0z1xnrxvl81xa-glibc-2.34-115","/nix/store/j5jxw3iy7bbz4a57fh9g2xm2gxmyal8h-zlib-1.2.12","/nix/store/yxvjs9drzsphm9pcf42a4byzj1kb9m7k-openssl-1.1.1n"]
|
||||
, registrationTime = UTCTime (fromOrdinalDate 0 0) 0
|
||||
, narBytes = Just 196040
|
||||
, trust = BuiltElsewhere
|
||||
, sigs = Set.fromList $ forceRight . parseSignature <$> ["cache.nixos.org-1:TsTTb3WGTZKphvYdBHXwo6weVILmTytUjLB+vcX89fOjjRicCHmKA4RCPMVLkj6TMJ4GMX3HPVWRdD1hkeKZBQ==", "test1:519iiVLx/c4Rdt5DNt6Y2Jm6hcWE9+XY69ygiWSZCNGVcmOcyL64uVAJ3cV8vaTusIZdbTnYo9Y7vDNeTmmMBQ=="]
|
||||
, contentAddress = Nothing
|
||||
}
|
||||
|
||||
pubkey :: Ed25519.PublicKey
|
||||
pubkey = forceDecodeB64Pubkey "6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
|
||||
|
||||
forceDecodeB64Pubkey :: Text -> Ed25519.PublicKey
|
||||
forceDecodeB64Pubkey b64EncodedPubkey = let
|
||||
decoded = forceRight $ decodeWith Base64 b64EncodedPubkey
|
||||
in case Ed25519.publicKey decoded of
|
||||
CryptoFailed err -> (error . show) err
|
||||
CryptoPassed x -> x
|
||||
|
||||
forceRight :: Either a b -> b
|
||||
forceRight = \case
|
||||
Right x -> x
|
||||
_ -> error "fromRight failed"
|
||||
|
Loading…
Reference in New Issue
Block a user