packages feed

hnix-store-core-0.9.0.0: src/System/Nix/Signature.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
{-|
Description : Nix-relevant interfaces to NaCl signatures.
-}

module System.Nix.Signature
  ( Signature(..)
  , signatureParser
  , parseSignature
  , signatureToText
  , NamedSignature(..)
  , narSignatureParser
  , parseNamedSignature
  , narSignatureToText
  ) where

import Crypto.Error (CryptoFailable(..))
import Data.Attoparsec.Text (Parser)
import Data.ByteString (ByteString)
import Data.Text (Text)
import GHC.Generics (Generic)
import System.Nix.Base (decodeWith, encodeWith, BaseEncoding(Base64))

import Crypto.PubKey.Ed25519 qualified as Ed25519
import Data.Attoparsec.Text qualified
import Data.ByteArray qualified
import Data.Char qualified
import Data.Text qualified

-- | An ed25519 signature.
newtype Signature = Signature Ed25519.Signature
  deriving (Eq, Generic, Show)

signatureParser :: Parser Signature
signatureParser = do
  encodedSig <-
    Data.Attoparsec.Text.takeWhile1
      (\c -> Data.Char.isAlphaNum c || c == '+' || c == '/' || c == '=')
  decodedSig <- case decodeWith Base64 encodedSig of
    Left e -> fail e
    Right decodedSig -> pure decodedSig
  sig <- case Ed25519.signature decodedSig of
    CryptoFailed e -> (fail . show) e
    CryptoPassed sig -> pure sig
  pure $ Signature sig

parseSignature :: Text -> Either String Signature
parseSignature = Data.Attoparsec.Text.parseOnly signatureParser

signatureToText :: Signature -> Text
signatureToText (Signature sig) =
  encodeWith Base64 (Data.ByteArray.convert sig :: ByteString)

-- | A detached signature attesting to a nix archive's validity.
data NamedSignature = NamedSignature
  { -- | The name of the public key used to sign the archive.
    publicKey :: !Text
  , -- | The archive's signature.
    sig :: !Signature
  }
  deriving (Eq, Generic, Ord)

instance Ord Signature where
  compare (Signature x) (Signature y) = let
    xBS = Data.ByteArray.convert x :: ByteString
    yBS = Data.ByteArray.convert y :: ByteString
    in compare xBS yBS

narSignatureParser :: Parser NamedSignature
narSignatureParser = do
  publicKey <- Data.Attoparsec.Text.takeWhile1 (/= ':')
  _ <- Data.Attoparsec.Text.string ":"
  sig <- signatureParser
  pure $ NamedSignature {..}

parseNamedSignature :: Text -> Either String NamedSignature
parseNamedSignature = Data.Attoparsec.Text.parseOnly narSignatureParser

narSignatureToText :: NamedSignature -> Text
narSignatureToText NamedSignature {..} =
  mconcat [ publicKey, ":", signatureToText sig ]

instance Show NamedSignature where
  show narSig = Data.Text.unpack (narSignatureToText narSig)