packages feed

crypton 2.1.6 → 2.1.7

raw patch · 9 files changed

+99/−43 lines, 9 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,5 +1,23 @@ # CHANGELOG for crypton +## 2.1.7++One fix: RSA-PSS verification was accepting a signature RFC 8017 says to+refuse.  It is a conformance fault rather than a forgery -- producing such a+signature takes the private key, since it is the signer who chooses the+encoding, and a third party holding a valid signature cannot turn it into+one of these.++* fix(pss): RSA-PSS verification refuses an encoding with a bit set outside+  `emBits`, as RFC 8017 9.1.2 step 6 requires.  Step 9 clears those bits in+  DB and crypton did that; clearing is not checking, so an encoding the+  standard calls inconsistent verified as though it were sound -- the bit+  that made it wrong was thrown away before anything looked at it.  Only the+  signer can produce such a signature, since it takes the private key to+  sign a chosen encoding, so this is conformance rather than forgery.  Found+  with tlsfuzzer, in the `xor 0x80 at 0` case of+  `test-tls13-certificate-verify.py`, while testing hs-tls+ ## 2.1.6  Two things a caller could walk into, the licence field saying what the tree
Crypto/PubKey/ECC/P256.hs view
@@ -413,8 +413,6 @@ foreign import ccall "crypton_p256e_scalar_invert"     ccrypton_p256e_scalar_invert :: Ptr P256Scalar -> Ptr P256Scalar -> IO () --- foreign import ccall "crypton_p256_modinv"---    ccrypton_p256_modinv :: Ptr P256Scalar -> Ptr P256Scalar -> Ptr P256Scalar -> IO () foreign import ccall "crypton_p256_modinv_vartime"     ccrypton_p256_modinv_vartime         :: Ptr P256Scalar -> Ptr P256Scalar -> Ptr P256Scalar -> IO ()
Crypto/PubKey/RSA/PSS.hs view
@@ -28,7 +28,7 @@ import Crypto.PubKey.RSA.Prim import Crypto.PubKey.RSA.Types import Crypto.Random.Types-import Data.Bits (shiftR, xor, (.&.))+import Data.Bits (complement, shiftR, xor, (.&.)) import Data.Word  import Crypto.Internal.ByteArray (ByteArray, ByteArrayAccess)@@ -233,6 +233,7 @@     | B.length s /= k = False     | os2ip s >= public_n pk = False     | B.any (/= 0) pre = False+    | B.any (\x -> x .&. topBits /= 0) (B.take 1 maskedDB) = False     | B.last em /= pssTrailerField params = False     | B.any (/= 0) ps0 = False     | b1 /= B.singleton 1 = False@@ -246,6 +247,13 @@     emLen = if emTruncate pubBits then k - 1 else k     dbLen = emLen - hashLen - 1     pubBits = numBits (public_n pk)+    -- RFC 8017 9.1.2 step 6: the leftmost 8*emLen - emBits bits of the+    -- leftmost octet of maskedDB have to be zero already.  Step 9 clears+    -- them in DB, which is what normalizeToKeySize does below, and clearing+    -- is not checking: without this an encoding with the top bit set -- one+    -- the standard calls inconsistent -- verifies as though it were sound,+    -- because the bit that made it wrong is thrown away before it is read.+    topBits = complement (normalizeMask pubBits)     -- unmarshall fields     (pre, em) = B.splitAt (k - emLen) (ep pk s) -- drop 0..1 byte     maskedDB = B.take dbLen em@@ -263,7 +271,12 @@  normalizeToKeySize :: Int -> [Word8] -> [Word8] normalizeToKeySize _ [] = [] -- very unlikely-normalizeToKeySize bits (x : xs) = x .&. mask : xs+normalizeToKeySize bits (x : xs) = x .&. normalizeMask bits : xs++-- | The bits of the leftmost octet that belong to the encoding: the low+-- @emBits `mod` 8@ of them, or all eight when that is zero.  Its complement+-- is the bits RFC 8017 requires to be zero.+normalizeMask :: Int -> Word8+normalizeMask bits = if sh > 0 then 0xff `shiftR` (8 - sh) else 0xff   where-    mask = if sh > 0 then 0xff `shiftR` (8 - sh) else 0xff     sh = (bits - 1) .&. 0x7
cbits/include32/p256/p256.h view
@@ -83,16 +83,9 @@     const crypton_p256_int* b,     crypton_p256_int* c); -// b := 1 / a % MOD-// MOD best be SECP256r1_n-void crypton_p256_modinv(-    const crypton_p256_int* MOD,-    const crypton_p256_int* a,-    crypton_p256_int* b);--// b := 1 / a % MOD+// b := 1 / a % MOD, in time that depends on a // MOD best be SECP256r1_n-// Faster than crypton_p256_modinv()+// Answers zero for an a that has no inverse, which is zero and MOD void crypton_p256_modinv_vartime(     const crypton_p256_int* MOD,     const crypton_p256_int* a,@@ -130,13 +123,6 @@ void crypton_p256_base_point_mul(const crypton_p256_int *n,                          crypton_p256_int *out_x,                          crypton_p256_int *out_y);--// {out_x,out_y} := n{in_x,in_y}-void crypton_p256_point_mul(const crypton_p256_int *n,-                    const crypton_p256_int *in_x,-                    const crypton_p256_int *in_y,-                    crypton_p256_int *out_x,-                    crypton_p256_int *out_y);  // {out_x,out_y} := n1G + n2{in_x,in_y} void crypton_p256_points_mul_vartime(
cbits/include64/p256/p256.h view
@@ -83,16 +83,9 @@     const crypton_p256_int* b,     crypton_p256_int* c); -// b := 1 / a % MOD-// MOD best be SECP256r1_n-void crypton_p256_modinv(-    const crypton_p256_int* MOD,-    const crypton_p256_int* a,-    crypton_p256_int* b);--// b := 1 / a % MOD+// b := 1 / a % MOD, in time that depends on a // MOD best be SECP256r1_n-// Faster than crypton_p256_modinv()+// Answers zero for an a that has no inverse, which is zero and MOD void crypton_p256_modinv_vartime(     const crypton_p256_int* MOD,     const crypton_p256_int* a,@@ -130,13 +123,6 @@ void crypton_p256_base_point_mul(const crypton_p256_int *n,                          crypton_p256_int *out_x,                          crypton_p256_int *out_y);--// {out_x,out_y} := n{in_x,in_y}-void crypton_p256_point_mul(const crypton_p256_int *n,-                    const crypton_p256_int *in_x,-                    const crypton_p256_int *in_y,-                    crypton_p256_int *out_x,-                    crypton_p256_int *out_y);  // {out_x,out_y} := n1G + n2{in_x,in_y} void crypton_p256_points_mul_vartime(
cbits/p256/p256.c view
@@ -334,8 +334,8 @@      branch both U and V have to be odd to reach.  The other input without an      inverse is MOD itself -- 2*MOD does not fit in 256 bits, so there is no      third -- and that one already leaves here as zero, which is also what-     crypton_p256_modinv's constant-time counterpart returns.  Answer the same-     for zero rather than not answering.+     Crypto.PubKey.ECC.P256's scalarInvSafe answers for both.  Answer the+     same for zero rather than not answering.       Reachable: Crypto.PubKey.ECC.P256 exports scalarInv, and scalarFromBinary      accepts any 256 bits.  A hang inside a foreign call cannot be interrupted
cbits/tests/width/p256_width.c view
@@ -7,9 +7,8 @@ #include <string.h> #include "p256/p256.h" -/* The header declares crypton_p256_point_mul and crypton_p256_modinv, and-   nothing defines them.  What exists is this family, which has no header at-   all -- the Haskell side declares it through the FFI. */+/* This family has no header at all -- the Haskell side declares it through+   the FFI -- so it is declared here. */ void crypton_p256e_point_mul(const crypton_p256_int *n,     const crypton_p256_int *in_x, const crypton_p256_int *in_y,     crypton_p256_int *out_x, crypton_p256_int *out_y);
crypton.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               crypton-version:            2.1.6+version:            2.1.7 -- crypton's own code is BSD-3-Clause.  The parts of -- cbits/aes/gcm_fused_x86.c that follow picotls's fusion are MIT, and the -- vendored s2n-bignum assembly in cbits/s2n is taken under ISC; each has
tests/PubKey/PSSSpec.hs view
@@ -2,9 +2,14 @@  module PubKey.PSSSpec (spec) where +import Crypto.Number.Basic (numBits) import Crypto.Number.Serialize (i2ospOf_, os2ip) import Crypto.PubKey.RSA+import Crypto.PubKey.RSA.Prim (dp, ep) import qualified Crypto.PubKey.RSA.PSS as PSS+import qualified Data.ByteString as B+import qualified Data.Bits as Bits+import Data.Word (Word8)  import Imports @@ -494,10 +499,61 @@             , os2ip sg + modulus < 2 ^ (8 * k)             ] +-- | RFC 8017 9.1.2 step 6: the leftmost @8*emLen - emBits@ bits of the+-- leftmost octet of maskedDB have to be zero.  Step 9 clears them in DB,+-- and clearing is not checking -- an encoding with one of them set used to+-- verify as though it were sound, because the bit that made it wrong was+-- thrown away before anything looked at it.+--+-- Only the signer can produce such a thing, since it takes the private key+-- to sign a chosen encoding, so this is conformance rather than forgery.+-- The vectors are walked for one whose altered encoding stays below the+-- modulus, as the signature range tests above do, because an encoding at or+-- past it says nothing.+step6Tests :: Spec+step6Tests = describe "an encoding with a bit outside emBits set" $ do+    it "the honest signature verifies, key 1024" $+        verifies rsaKey1 (fst (altered rsaKey1 vectorsKey1)) `shouldBe` True+    it "and the altered one does not, key 1024" $+        verifies rsaKey1 (snd (altered rsaKey1 vectorsKey1)) `shouldBe` False+    it "the honest signature verifies, key 1026" $+        verifies rsaKey3 (fst (altered rsaKey3 vectorsKey3)) `shouldBe` True+    it "and the altered one does not, key 1026" $+        verifies rsaKey3 (snd (altered rsaKey3 vectorsKey3)) `shouldBe` False+    it "key 1025 has no bits outside emBits to set" $+        forbidden (numBits (public_n (private_pub rsaKey2))) `shouldBe` 0+  where+    verifies key (v, sg) =+        PSS.verify PSS.defaultPSSParamsSHA1 (private_pub key) (message v) sg++    -- the bits of the leftmost octet the standard requires to be zero+    forbidden bits = Bits.complement mask+      where+        mask = if sh > 0 then 0xff `Bits.shiftR` (8 - sh) else 0xff :: Word8+        sh = (bits - 1) Bits..&. 0x7++    -- the first vector whose encoding, with a forbidden bit set, is still+    -- below the modulus, paired as (honest, altered)+    altered key vs = firstVector+        [ ((v, signature v), (v, dp Nothing key em'))+        | v <- vs+        , let pub = private_pub key+              em = ep pub (signature v)+              bit = lowestSet (forbidden (numBits (public_n pub)))+              em' = B.cons (B.head em Bits..|. bit) (B.tail em)+        , B.head em Bits..&. forbidden (numBits (public_n pub)) == 0+        , os2ip em' < public_n pub+        ]++    -- the forbidden bit worth setting is the lowest of them: it is the one+    -- that adds least, and an encoding at or past the modulus proves nothing+    lowestSet w = minimum ([2 ^ i | i <- [0 .. 7 :: Int], Bits.testBit w i])+ spec :: Spec spec =     describe "RSA-PSS" $ do         signatureRangeTests+        step6Tests         describe "signature internal" $ do             doSignTest rsaKeyInt katZero vectorInt         describe "verify internal" $ do