Wycheproof.hs (5499B)
1 {-# LANGUAGE BangPatterns #-} 2 {-# LANGUAGE OverloadedStrings #-} 3 {-# LANGUAGE RecordWildCards #-} 4 {-# LANGUAGE ViewPatterns #-} 5 6 module Wycheproof ( 7 Wycheproof(..) 8 , execute_group 9 ) where 10 11 import Crypto.Curve.Secp256k1 12 import Data.Aeson ((.:)) 13 import qualified Data.Aeson as A 14 import qualified Data.Attoparsec.ByteString as AT 15 import qualified Data.Bits as B 16 import qualified Data.ByteString as BS 17 import qualified Data.ByteString.Base16 as B16 18 import qualified Data.Text as T 19 import qualified Data.Text.Encoding as TE 20 import Test.Tasty (TestTree, testGroup) 21 import Test.Tasty.HUnit (assertBool, testCase) 22 23 decodeLenient :: BS.ByteString -> BS.ByteString 24 decodeLenient bs = case B16.decode bs of 25 Nothing -> error "bang" 26 Just b -> b 27 28 fi :: (Integral a, Num b) => a -> b 29 fi = fromIntegral 30 {-# INLINE fi #-} 31 32 execute_group :: Context -> SigType -> EcdsaTestGroup -> TestTree 33 execute_group tex ty EcdsaTestGroup {..} = 34 testGroup msg (fmap (execute tex ty pk_uncompressed) etg_tests) 35 where 36 msg = "wycheproof (" <> T.unpack etg_type <> ", " <> T.unpack etg_sha <> ")" 37 PublicKey {..} = etg_publicKey 38 39 execute :: Context -> SigType -> Projective -> EcdsaVerifyTest -> TestTree 40 execute tex ty pub EcdsaVerifyTest {..} = testCase report $ do 41 let msg = decodeLenient (TE.encodeUtf8 t_msg) 42 sig = toEcdsa t_sig 43 case sig of 44 Left _ -> assertBool mempty (t_result == "invalid") 45 Right s -> do 46 let ver = case ty of 47 LowS -> verify_ecdsa msg pub s && verify_ecdsa' tex msg pub s 48 Unrestricted -> verify_ecdsa_unrestricted msg pub s 49 && verify_ecdsa_unrestricted' tex msg pub s 50 if t_result == "invalid" 51 then assertBool mempty (not ver) 52 else assertBool mempty ver 53 where 54 report = "wycheproof (" <> show ty <> ") " <> show t_tcId 55 56 parse_der_sig :: AT.Parser ECDSA 57 parse_der_sig = do 58 _ <- AT.word8 0x30 59 len <- fmap fi AT.anyWord8 60 content <- AT.take len 61 etc <- AT.takeByteString 62 if BS.length content /= len || etc /= mempty 63 then fail "invalid content" 64 else case AT.parseOnly (meat len) content of 65 Left _ -> fail "invalid content" 66 Right v -> pure v 67 where 68 meat len = do 69 (lr, bs_r) <- parseAsnInt 70 (ls, bs_s) <- parseAsnInt 71 let rs = do 72 r <- roll32 bs_r 73 s <- roll32 bs_s 74 pure (r, s) 75 case rs of 76 Nothing -> fail "signature components too large" 77 Just (r, s) -> do 78 let checks = lr + ls == len 79 rest <- AT.takeByteString 80 if rest == mempty && checks 81 then pure (ECDSA r s) 82 else fail "input remaining or length mismatch" 83 84 parseAsnInt :: AT.Parser (Int, BS.ByteString) 85 parseAsnInt = do 86 _ <- AT.word8 0x02 87 len <- fmap fi AT.anyWord8 88 content <- AT.take len 89 if BS.length content /= len 90 then fail "invalid length" 91 else if len == 1 92 then pure (len + 2, content) -- + tag byt + len byt 93 else case BS.uncons content of 94 Nothing -> fail "invalid content" 95 Just (h0, t0) 96 | B.testBit h0 7 -> fail "negative value" 97 | otherwise -> case BS.uncons t0 of 98 Nothing -> fail "invalid content" 99 Just (h1, _) 100 | h0 == 0x00 && not (B.testBit h1 7) -> fail "invalid padding" 101 | otherwise -> case BS.unsnoc content of 102 Nothing -> fail "invalid content" 103 Just (_, tn) 104 | tn == 0x00 -> fail "invalid padding" 105 | otherwise -> pure (len + 2, content) 106 107 data Wycheproof = Wycheproof { 108 wp_algorithm :: !T.Text 109 , wp_generatorVersion :: !T.Text 110 , wp_numberOfTests :: !Int 111 , wp_testGroups :: ![EcdsaTestGroup] 112 } deriving Show 113 114 instance A.FromJSON Wycheproof where 115 parseJSON = A.withObject "Wycheproof" $ \m -> Wycheproof 116 <$> m .: "algorithm" 117 <*> m .: "generatorVersion" 118 <*> m .: "numberOfTests" 119 <*> m .: "testGroups" 120 121 data EcdsaTestGroup = EcdsaTestGroup { 122 etg_type :: !T.Text 123 , etg_publicKey :: !PublicKey 124 , etg_sha :: !T.Text 125 , etg_tests :: ![EcdsaVerifyTest] 126 } deriving Show 127 128 instance A.FromJSON EcdsaTestGroup where 129 parseJSON = A.withObject "EcdsaTestGroup" $ \m -> EcdsaTestGroup 130 <$> m .: "type" 131 <*> m .: "publicKey" 132 <*> m .: "sha" 133 <*> m .: "tests" 134 135 data PublicKey = PublicKey { 136 pk_type :: !T.Text 137 , pk_curve :: !T.Text 138 , pk_keySize :: !Int 139 , pk_uncompressed :: !Projective 140 } deriving Show 141 142 toProjective :: T.Text -> Projective 143 toProjective (decodeLenient . TE.encodeUtf8 -> bs) = case parse_point bs of 144 Nothing -> error "wycheproof: couldn't parse pubkey" 145 Just p -> p 146 147 instance A.FromJSON PublicKey where 148 parseJSON = A.withObject "PublicKey" $ \m -> PublicKey 149 <$> m .: "type" 150 <*> m .: "curve" 151 <*> m .: "keySize" 152 <*> fmap toProjective (m .: "uncompressed") 153 154 toEcdsa :: T.Text -> Either String ECDSA 155 toEcdsa (decodeLenient . TE.encodeUtf8 -> bs) = 156 AT.parseOnly parse_der_sig bs 157 158 data EcdsaVerifyTest = EcdsaVerifyTest { 159 t_tcId :: !Int 160 , t_comment :: !T.Text 161 , t_msg :: !T.Text 162 , t_sig :: !T.Text 163 , t_result :: !T.Text 164 } deriving Show 165 166 instance A.FromJSON EcdsaVerifyTest where 167 parseJSON = A.withObject "EcdsaVerifyTest" $ \m -> EcdsaVerifyTest 168 <$> m .: "tcId" 169 <*> m .: "comment" 170 <*> m .: "msg" 171 <*> m .: "sig" 172 <*> m .: "result" 173