packages feed

KpqC-0.1.0: src/Crypto/KpqC.hs

-- | Synchronous bindings for the KpqC post-quantum algorithms.
module Crypto.KpqC
  ( Algorithm (..),
    SignatureAlgorithm (..),
    KEMAlgorithm (..),
    SignatureSizes (..),
    KEMSizes (..),
    SecretBytes,
    KeyPair (..),
    EncapsulatedSecret (..),
    Operation (..),
    KpqCError (..),
    signatureAlgorithms,
    kemAlgorithms,
    signatureSizes,
    kemSizes,
    sign,
    signWithContext,
    verify,
    verifyWithContext,
    encapsulate,
    decapsulate
  )
where

import Control.Exception (finally)
import Control.Monad (when)
import qualified Crypto.KpqC.Internal.Native.AIMer as AIMer
import qualified Crypto.KpqC.Internal.Native.HAETAE as HAETAE
import qualified Crypto.KpqC.Internal.Native.NTRUPlus as NTRUPlus
import qualified Crypto.KpqC.Internal.Native.SMAUGT as SMAUGT
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteArray as BA
import Data.Word (Word8, Word64)
import Foreign.C.Types (CInt, CSize)
import Foreign.Marshal.Alloc (alloca)
import Foreign.Marshal.Array (allocaArray)
import Foreign.Marshal.Utils (fillBytes)
import Foreign.Ptr (Ptr, castPtr)
import Foreign.Storable (peek, poke)

-- | A detached-signature parameter set.
data SignatureAlgorithm
  = AIMer128f
  | AIMer128s
  | AIMer192f
  | AIMer192s
  | AIMer256f
  | AIMer256s
  | HAETAE2
  | HAETAE3
  | HAETAE5
  deriving (Bounded, Enum, Eq, Ord, Read, Show)

-- | A KEM parameter set.
data KEMAlgorithm
  = NTRUPlus768
  | NTRUPlus864
  | NTRUPlus1152
  | SMAUGT128
  | SMAUGT192
  | SMAUGT256
  | TiMER
  deriving (Bounded, Enum, Eq, Ord, Read, Show)

-- | Fixed byte lengths for a signature parameter set.
data SignatureSizes = SignatureSizes
  { signaturePublicKeyBytes :: !Int,
    signatureSecretKeyBytes :: !Int,
    signatureBytes :: !Int
  }
  deriving (Eq, Show)

-- | Fixed byte lengths for a KEM parameter set.
data KEMSizes = KEMSizes
  { kemPublicKeyBytes :: !Int,
    kemSecretKeyBytes :: !Int,
    ciphertextBytes :: !Int,
    sharedSecretBytes :: !Int
  }
  deriving (Eq, Show)

-- | Secret material backed by memory that is scrubbed when no longer
-- referenced and compared in constant time.
type SecretBytes = BA.ScrubbedBytes

-- | A public key and its corresponding secret key.
data KeyPair = KeyPair
  { publicKey :: !ByteString,
    secretKey :: !SecretBytes
  }
  deriving (Eq)

instance Show KeyPair where
  showsPrec _ keys =
    showString "KeyPair {publicKey = <"
      . shows (BS.length (publicKey keys))
      . showString " bytes>, secretKey = <redacted>}"

-- | A ciphertext and the shared secret established for its recipient.
data EncapsulatedSecret = EncapsulatedSecret
  { ciphertext :: !ByteString,
    sharedSecret :: !SecretBytes
  }
  deriving (Eq)

instance Show EncapsulatedSecret where
  showsPrec _ value =
    showString "EncapsulatedSecret {ciphertext = <"
      . shows (BS.length (ciphertext value))
      . showString " bytes>, sharedSecret = <redacted>}"

-- | Native operation associated with a failure.
data Operation
  = KeyGeneration
  | Signing
  | Verification
  | Encapsulation
  | Decapsulation
  deriving (Eq, Show)

-- | Errors reported before or during a native operation.
data KpqCError
  = InvalidSize !String !Int !Int
  | ContextTooLong !Int
  | NativeFailure !Operation !Int
  | UnexpectedOutputSize !Operation !Int !Int
  deriving (Eq, Show)

-- | Algorithms that can generate a key pair.
class Algorithm algorithm where
  algorithmId :: algorithm -> String
  generateKeyPair :: algorithm -> IO (Either KpqCError KeyPair)

instance Algorithm SignatureAlgorithm where
  algorithmId algorithm = case algorithm of
    AIMer128f -> "aimer-128f"
    AIMer128s -> "aimer-128s"
    AIMer192f -> "aimer-192f"
    AIMer192s -> "aimer-192s"
    AIMer256f -> "aimer-256f"
    AIMer256s -> "aimer-256s"
    HAETAE2 -> "haetae-mode2"
    HAETAE3 -> "haetae-mode3"
    HAETAE5 -> "haetae-mode5"

  generateKeyPair algorithm =
    let sizes = signatureSizes algorithm
        (keyPairFunction, _, _) = signatureBackend algorithm
     in runKeyPair
          (signaturePublicKeyBytes sizes)
          (signatureSecretKeyBytes sizes)
          keyPairFunction

instance Algorithm KEMAlgorithm where
  algorithmId algorithm = case algorithm of
    NTRUPlus768 -> "NTRU+768"
    NTRUPlus864 -> "NTRU+864"
    NTRUPlus1152 -> "NTRU+1152"
    SMAUGT128 -> "SMAUG-T128"
    SMAUGT192 -> "SMAUG-T192"
    SMAUGT256 -> "SMAUG-T256"
    TiMER -> "TiMER"

  generateKeyPair algorithm =
    let sizes = kemSizes algorithm
        (keyPairFunction, _, _) = kemBackend algorithm
     in runKeyPair
          (kemPublicKeyBytes sizes)
          (kemSecretKeyBytes sizes)
          keyPairFunction

-- | Every supported signature parameter set.
signatureAlgorithms :: [SignatureAlgorithm]
signatureAlgorithms = [minBound .. maxBound]

-- | Every supported KEM parameter set.
kemAlgorithms :: [KEMAlgorithm]
kemAlgorithms = [minBound .. maxBound]

-- | Return the fixed byte lengths for a signature parameter set.
signatureSizes :: SignatureAlgorithm -> SignatureSizes
signatureSizes algorithm = case algorithm of
  AIMer128f -> SignatureSizes 32 48 6944
  AIMer128s -> SignatureSizes 32 48 4704
  AIMer192f -> SignatureSizes 48 72 15408
  AIMer192s -> SignatureSizes 48 72 10320
  AIMer256f -> SignatureSizes 64 96 31360
  AIMer256s -> SignatureSizes 64 96 20224
  HAETAE2 -> SignatureSizes 992 1408 1474
  HAETAE3 -> SignatureSizes 1472 2112 2349
  HAETAE5 -> SignatureSizes 2080 2752 2948

-- | Return the fixed byte lengths for a KEM parameter set.
kemSizes :: KEMAlgorithm -> KEMSizes
kemSizes algorithm = case algorithm of
  NTRUPlus768 -> KEMSizes 1152 2336 1152 32
  NTRUPlus864 -> KEMSizes 1296 2624 1296 32
  NTRUPlus1152 -> KEMSizes 1728 3488 1728 32
  SMAUGT128 -> KEMSizes 672 832 672 32
  SMAUGT192 -> KEMSizes 1088 1312 992 32
  SMAUGT256 -> KEMSizes 1440 1728 1376 32
  TiMER -> KEMSizes 672 832 608 32

-- | Sign a message using an empty domain-separation context.
sign :: BA.ByteArrayAccess secret => SignatureAlgorithm -> ByteString -> secret -> IO (Either KpqCError ByteString)
sign algorithm message secret = signWithContext algorithm message secret BS.empty

-- | Sign a message with a domain-separation context of at most 255 bytes.
signWithContext :: BA.ByteArrayAccess secret => SignatureAlgorithm -> ByteString -> secret -> ByteString -> IO (Either KpqCError ByteString)
signWithContext algorithm message secret context =
  let sizes = signatureSizes algorithm
      expectedSecret = signatureSecretKeyBytes sizes
      expectedSignature = signatureBytes sizes
      (_, signFunction, _) = signatureBackend algorithm
   in case validateSize "secret key" expectedSecret secret of
        Left failure -> pure (Left failure)
        Right () -> case validateContext context of
          Left failure -> pure (Left failure)
          Right () ->
            runSizedOutput Signing expectedSignature $ \signaturePointer ->
              alloca $ \lengthPointer -> do
                poke lengthPointer 0
                status <-
                  withBytes message $ \messagePointer messageLength ->
                    withBytes context $ \contextPointer contextLength ->
                      withBytes secret $ \secretPointer _ ->
                        signFunction
                          signaturePointer
                          lengthPointer
                          messagePointer
                          messageLength
                          contextPointer
                          contextLength
                          secretPointer
                actualLength <- fromIntegral <$> peek lengthPointer
                pure (status, actualLength)

-- | Verify a signature using an empty domain-separation context.
verify :: SignatureAlgorithm -> ByteString -> ByteString -> ByteString -> IO (Either KpqCError Bool)
verify algorithm message signature public =
  verifyWithContext algorithm message signature public BS.empty

-- | Verify a signature with the domain-separation context used when signing.
verifyWithContext :: SignatureAlgorithm -> ByteString -> ByteString -> ByteString -> ByteString -> IO (Either KpqCError Bool)
verifyWithContext algorithm message signature public context =
  let sizes = signatureSizes algorithm
      expectedPublic = signaturePublicKeyBytes sizes
      expectedSignature = signatureBytes sizes
      (_, _, verifyFunction) = signatureBackend algorithm
   in case validateSize "public key" expectedPublic public of
        Left failure -> pure (Left failure)
        Right () -> case validateContext context of
          Left failure -> pure (Left failure)
          Right ()
            | BS.length signature /= expectedSignature -> pure (Right False)
            | otherwise -> do
                status <-
                  withBytes signature $ \signaturePointer signatureLength ->
                    withBytes message $ \messagePointer messageLength ->
                      withBytes context $ \contextPointer contextLength ->
                        withBytes public $ \publicPointer _ ->
                          verifyFunction
                            signaturePointer
                            signatureLength
                            messagePointer
                            messageLength
                            contextPointer
                            contextLength
                            publicPointer
                pure $ case status of
                  0 -> Right True
                  -1 -> Right False
                  _ -> Left (NativeFailure Verification (fromIntegral status))

-- | Encapsulate a new shared secret to a public key.
encapsulate :: KEMAlgorithm -> ByteString -> IO (Either KpqCError EncapsulatedSecret)
encapsulate algorithm public =
  let sizes = kemSizes algorithm
      expectedPublic = kemPublicKeyBytes sizes
      expectedCiphertext = ciphertextBytes sizes
      expectedSecret = sharedSecretBytes sizes
      (_, encapsulateFunction, _) = kemBackend algorithm
   in case validateSize "public key" expectedPublic public of
        Left failure -> pure (Left failure)
        Right () ->
          runTwoOutputs Encapsulation expectedCiphertext expectedSecret EncapsulatedSecret $ \ciphertextPointer sharedSecretPointer ->
            withBytes public $ \publicPointer _ ->
              encapsulateFunction ciphertextPointer sharedSecretPointer publicPointer

-- | Decapsulate a shared secret from a ciphertext.
decapsulate :: BA.ByteArrayAccess secret => KEMAlgorithm -> ByteString -> secret -> IO (Either KpqCError SecretBytes)
decapsulate algorithm encrypted secret =
  let sizes = kemSizes algorithm
      expectedCiphertext = ciphertextBytes sizes
      expectedSecretKey = kemSecretKeyBytes sizes
      expectedSharedSecret = sharedSecretBytes sizes
      (_, _, decapsulateFunction) = kemBackend algorithm
   in case validateSize "ciphertext" expectedCiphertext encrypted of
        Left failure -> pure (Left failure)
        Right () -> case validateSize "secret key" expectedSecretKey secret of
          Left failure -> pure (Left failure)
          Right () ->
            runSecretOutput Decapsulation expectedSharedSecret $ \sharedSecretPointer ->
              withBytes encrypted $ \ciphertextPointer _ ->
                withBytes secret $ \secretPointer _ ->
                  decapsulateFunction sharedSecretPointer ciphertextPointer secretPointer

type KeyPairFunction = Ptr Word8 -> Ptr Word8 -> IO CInt

type SignFunction = Ptr Word8 -> Ptr Word64 -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt

type VerifyFunction = Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> CSize -> Ptr Word8 -> IO CInt

type EncapsulateFunction = Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt

type DecapsulateFunction = Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> IO CInt

signatureBackend :: SignatureAlgorithm -> (KeyPairFunction, SignFunction, VerifyFunction)
signatureBackend algorithm = case algorithm of
  AIMer128f -> (AIMer.aimer128fKeyPair, AIMer.aimer128fSign, AIMer.aimer128fVerify)
  AIMer128s -> (AIMer.aimer128sKeyPair, AIMer.aimer128sSign, AIMer.aimer128sVerify)
  AIMer192f -> (AIMer.aimer192fKeyPair, AIMer.aimer192fSign, AIMer.aimer192fVerify)
  AIMer192s -> (AIMer.aimer192sKeyPair, AIMer.aimer192sSign, AIMer.aimer192sVerify)
  AIMer256f -> (AIMer.aimer256fKeyPair, AIMer.aimer256fSign, AIMer.aimer256fVerify)
  AIMer256s -> (AIMer.aimer256sKeyPair, AIMer.aimer256sSign, AIMer.aimer256sVerify)
  HAETAE2 -> (HAETAE.haetae2KeyPair, HAETAE.haetae2Sign, HAETAE.haetae2Verify)
  HAETAE3 -> (HAETAE.haetae3KeyPair, HAETAE.haetae3Sign, HAETAE.haetae3Verify)
  HAETAE5 -> (HAETAE.haetae5KeyPair, HAETAE.haetae5Sign, HAETAE.haetae5Verify)

kemBackend :: KEMAlgorithm -> (KeyPairFunction, EncapsulateFunction, DecapsulateFunction)
kemBackend algorithm = case algorithm of
  NTRUPlus768 -> (NTRUPlus.ntruplus768KeyPair, NTRUPlus.ntruplus768Encapsulate, NTRUPlus.ntruplus768Decapsulate)
  NTRUPlus864 -> (NTRUPlus.ntruplus864KeyPair, NTRUPlus.ntruplus864Encapsulate, NTRUPlus.ntruplus864Decapsulate)
  NTRUPlus1152 -> (NTRUPlus.ntruplus1152KeyPair, NTRUPlus.ntruplus1152Encapsulate, NTRUPlus.ntruplus1152Decapsulate)
  SMAUGT128 -> (SMAUGT.smaugt128KeyPair, SMAUGT.smaugt128Encapsulate, SMAUGT.smaugt128Decapsulate)
  SMAUGT192 -> (SMAUGT.smaugt192KeyPair, SMAUGT.smaugt192Encapsulate, SMAUGT.smaugt192Decapsulate)
  SMAUGT256 -> (SMAUGT.smaugt256KeyPair, SMAUGT.smaugt256Encapsulate, SMAUGT.smaugt256Decapsulate)
  TiMER -> (SMAUGT.timerKeyPair, SMAUGT.timerEncapsulate, SMAUGT.timerDecapsulate)

runKeyPair :: Int -> Int -> KeyPairFunction -> IO (Either KpqCError KeyPair)
runKeyPair publicLength secretLength keyPairFunction =
  runTwoOutputs KeyGeneration publicLength secretLength KeyPair keyPairFunction

runSecretOutput :: Operation -> Int -> (Ptr Word8 -> IO CInt) -> IO (Either KpqCError SecretBytes)
runSecretOutput operation size action = do
  (status, secret) <-
    BA.allocRet size $ \pointer -> do
      fillBytes pointer 0 size
      result <- action (castPtr pointer)
      when (result /= 0) (fillBytes pointer 0 size)
      pure result
  if status == 0
    then pure (Right secret)
    else pure (Left (NativeFailure operation (fromIntegral status)))

runSizedOutput :: Operation -> Int -> (Ptr Word8 -> IO (CInt, Int)) -> IO (Either KpqCError ByteString)
runSizedOutput operation expected action =
  allocaArray expected $ \pointer ->
    ( do
        fillBytes pointer 0 expected
        (status, actual) <- action pointer
        if status /= 0
          then pure (Left (NativeFailure operation (fromIntegral status)))
          else
            if actual /= expected
              then pure (Left (UnexpectedOutputSize operation expected actual))
              else Right <$> BS.packCStringLen (castPtr pointer, expected)
    )
      `finally` fillBytes pointer 0 expected

runTwoOutputs :: Operation -> Int -> Int -> (ByteString -> SecretBytes -> value) -> (Ptr Word8 -> Ptr Word8 -> IO CInt) -> IO (Either KpqCError value)
runTwoOutputs operation firstSize secondSize constructor action =
  allocaArray firstSize $ \firstPointer ->
    ( do
        fillBytes firstPointer 0 firstSize
        (status, second) <-
          BA.allocRet secondSize $ \secondPointer -> do
            fillBytes secondPointer 0 secondSize
            result <- action firstPointer (castPtr secondPointer)
            when (result /= 0) (fillBytes secondPointer 0 secondSize)
            pure result
        if status == 0
          then do
            first <- BS.packCStringLen (castPtr firstPointer, firstSize)
            pure (Right (constructor first second))
          else pure (Left (NativeFailure operation (fromIntegral status)))
    )
      `finally` fillBytes firstPointer 0 firstSize

withBytes :: BA.ByteArrayAccess bytes => bytes -> (Ptr Word8 -> CSize -> IO value) -> IO value
withBytes bytes action =
  BA.withByteArray bytes $ \pointer ->
    action (castPtr pointer) (fromIntegral (BA.length bytes))

validateSize :: BA.ByteArrayAccess bytes => String -> Int -> bytes -> Either KpqCError ()
validateSize label expected value
  | actual == expected = Right ()
  | otherwise = Left (InvalidSize label expected actual)
  where
    actual = BA.length value

validateContext :: ByteString -> Either KpqCError ()
validateContext context
  | size <= 255 = Right ()
  | otherwise = Left (ContextTooLong size)
  where
    size = BS.length context