diff --git a/hnix-store-core/src/System/Nix/Signature.hs b/hnix-store-core/src/System/Nix/Signature.hs index 003668cb..3a16c52e 100644 --- a/hnix-store-core/src/System/Nix/Signature.hs +++ b/hnix-store-core/src/System/Nix/Signature.hs @@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NamedFieldPuns #-} {-| Description : Nix-relevant interfaces to NaCl signatures. -} @@ -8,22 +9,23 @@ module System.Nix.Signature , NarSignature(..) , signatureParser , parseSignature + , signatureToText ) where +import Crypto.Error (CryptoFailable(..)) +import Data.ByteString (ByteString) +import Data.Text (Text) import GHC.Generics (Generic) +import System.Nix.Base (decodeWith, encodeWith, BaseEncoding(Base64)) -import qualified Crypto.PubKey.Ed25519 -import Crypto.Error (CryptoFailable(..)) +import qualified Crypto.PubKey.Ed25519 as Ed25519 import qualified Data.Attoparsec.Text -import qualified Data.Char import qualified Data.ByteArray -import qualified Data.ByteString as BS -import Data.Text (Text) -import qualified System.Nix.Base -import System.Nix.Base (BaseEncoding(Base64)) +import qualified Data.Char +import qualified Data.Text -- | An ed25519 signature. -newtype Signature = Signature Crypto.PubKey.Ed25519.Signature +newtype Signature = Signature Ed25519.Signature deriving (Eq, Generic, Show) -- | A detached signature attesting to a nix archive's validity. @@ -33,12 +35,12 @@ data NarSignature = NarSignature , -- | The archive's signature. sig :: !Signature } - deriving (Eq, Generic, Ord, Show) + deriving (Eq, Generic, Ord) instance Ord Signature where compare (Signature x) (Signature y) = let - xBS = Data.ByteArray.convert x :: BS.ByteString - yBS = Data.ByteArray.convert y :: BS.ByteString + xBS = Data.ByteArray.convert x :: ByteString + yBS = Data.ByteArray.convert y :: ByteString in compare xBS yBS signatureParser :: Data.Attoparsec.Text.Parser NarSignature @@ -46,16 +48,21 @@ signatureParser = do publicKey <- Data.Attoparsec.Text.takeWhile1 (/= ':') _ <- Data.Attoparsec.Text.string ":" encodedSig <- Data.Attoparsec.Text.takeWhile1 (\c -> Data.Char.isAlphaNum c || c == '+' || c == '/' || c == '=') - decodedSig <- case System.Nix.Base.decodeWith Base64 encodedSig of + decodedSig <- case decodeWith Base64 encodedSig of Left e -> fail e Right decodedSig -> pure decodedSig - sig <- case Crypto.PubKey.Ed25519.signature decodedSig of + sig <- case Ed25519.signature decodedSig of CryptoFailed e -> (fail . show) e CryptoPassed sig -> pure sig pure $ NarSignature publicKey (Signature sig) -parseSignature - :: Text -> Either String NarSignature -parseSignature = - Data.Attoparsec.Text.parseOnly signatureParser +parseSignature :: Text -> Either String NarSignature +parseSignature = Data.Attoparsec.Text.parseOnly signatureParser + +signatureToText :: NarSignature -> Text +signatureToText NarSignature {publicKey, sig=Signature sig'} = let + b64Encoded = encodeWith Base64 (Data.ByteArray.convert sig' :: ByteString) + in mconcat [ publicKey, ":", b64Encoded ] +instance Show NarSignature where + show narSig = Data.Text.unpack (signatureToText narSig) diff --git a/hnix-store-tests/hnix-store-tests.cabal b/hnix-store-tests/hnix-store-tests.cabal index a161b4a2..fc860869 100644 --- a/hnix-store-tests/hnix-store-tests.cabal +++ b/hnix-store-tests/hnix-store-tests.cabal @@ -41,6 +41,7 @@ library , System.Nix.Arbitrary.Derivation , System.Nix.Arbitrary.DerivedPath , System.Nix.Arbitrary.Hash + , System.Nix.Arbitrary.Signature , System.Nix.Arbitrary.Store.Types , System.Nix.Arbitrary.StorePath , Test.Hspec.Nix @@ -67,6 +68,7 @@ test-suite props DerivationSpec DerivedPathSpec StorePathSpec + SignatureSpec hs-source-dirs: tests build-tool-depends: diff --git a/hnix-store-tests/src/System/Nix/Arbitrary.hs b/hnix-store-tests/src/System/Nix/Arbitrary.hs index 3b73f2b0..0529c518 100644 --- a/hnix-store-tests/src/System/Nix/Arbitrary.hs +++ b/hnix-store-tests/src/System/Nix/Arbitrary.hs @@ -5,5 +5,6 @@ import System.Nix.Arbitrary.ContentAddress () import System.Nix.Arbitrary.Derivation () import System.Nix.Arbitrary.DerivedPath () import System.Nix.Arbitrary.Hash () +import System.Nix.Arbitrary.Signature () import System.Nix.Arbitrary.Store.Types () import System.Nix.Arbitrary.StorePath () diff --git a/hnix-store-tests/src/System/Nix/Arbitrary/Signature.hs b/hnix-store-tests/src/System/Nix/Arbitrary/Signature.hs new file mode 100644 index 00000000..17520c3c --- /dev/null +++ b/hnix-store-tests/src/System/Nix/Arbitrary/Signature.hs @@ -0,0 +1,36 @@ +-- due to recent generic-arbitrary +{-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-} +{-# OPTIONS_GHC -Wno-orphans #-} +{-# LANGUAGE OverloadedStrings #-} +module System.Nix.Arbitrary.Signature where + +import qualified Crypto.PubKey.Ed25519 +import Crypto.Random (drgNewTest, withDRG) +import qualified Data.ByteString as BS +import qualified Data.Text as Text +import Test.QuickCheck.Arbitrary.Generic (GenericArbitrary(..)) +import Test.QuickCheck.Instances () +import Test.QuickCheck + +import System.Nix.Signature + +instance Arbitrary Crypto.PubKey.Ed25519.Signature where + arbitrary = do + seeds <- (,,,,) <$> arbitraryBoundedRandom <*> arbitraryBoundedRandom <*> arbitraryBoundedRandom <*> arbitraryBoundedRandom <*> arbitraryBoundedRandom + let drg = drgNewTest seeds + (secretKey, _) = withDRG drg Crypto.PubKey.Ed25519.generateSecretKey + publicKey = Crypto.PubKey.Ed25519.toPublic secretKey + msg :: BS.ByteString = "msg" + pure $ Crypto.PubKey.Ed25519.sign secretKey publicKey msg + +deriving via GenericArbitrary Signature + instance Arbitrary Signature + +instance Arbitrary NarSignature where + arbitrary = do + name <- Text.pack . getPrintableString <$> suchThat arbitrary (\(PrintableString str) -> validName str) + NarSignature name <$> arbitrary + +validName :: String -> Bool +validName txt = not (null txt) && not (elem ':' txt) + diff --git a/hnix-store-tests/tests/SignatureSpec.hs b/hnix-store-tests/tests/SignatureSpec.hs new file mode 100644 index 00000000..3814e5b9 --- /dev/null +++ b/hnix-store-tests/tests/SignatureSpec.hs @@ -0,0 +1,13 @@ +module SignatureSpec where + +import Test.Hspec (Spec, describe) +import Test.Hspec.Nix (roundtrips) +import Test.Hspec.QuickCheck (prop) + +import System.Nix.Signature (signatureToText, parseSignature) +import System.Nix.Arbitrary () + +spec :: Spec +spec = do + describe "Signature" $ do + prop "roundtrips" $ roundtrips signatureToText parseSignature