diff --git a/code/cwe/src/Security/CWE.hs b/code/cwe/src/Security/CWE.hs index 4d275eaf..49c3dcd3 100644 --- a/code/cwe/src/Security/CWE.hs +++ b/code/cwe/src/Security/CWE.hs @@ -2,7 +2,7 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} -module Security.CWE (CWEID, mkCWEID, cweNames, cweIds) where +module Security.CWE (CWEID, unCWEID, mkCWEID, cweNames, cweIds) where import Security.CWE.Data import Data.Text (Text) @@ -14,6 +14,10 @@ import Data.Bits newtype CWEID = CWEID Word deriving newtype (Eq, Ord, Show) +-- | Access the underlying data. +unCWEID :: CWEID -> Word +unCWEID (CWEID cwe) = cwe + mkCWEID :: (Integral a, Bits a) => a -> Maybe CWEID mkCWEID num = CWEID <$> toIntegralSized num diff --git a/code/hsec-tools/hsec-tools.cabal b/code/hsec-tools/hsec-tools.cabal index 38a55489..faa1c642 100644 --- a/code/hsec-tools/hsec-tools.cabal +++ b/code/hsec-tools/hsec-tools.cabal @@ -49,6 +49,7 @@ library , commonmark ^>=0.2.2 , commonmark-pandoc >=0.2 && <0.3 , containers >=0.6 && <0.7 + , cwe >=0.1 && <2 , directory <2 , extra ^>=1.7.5 , filepath >=1.4 && <1.5 diff --git a/code/hsec-tools/src/Security/Advisories/Definition.hs b/code/hsec-tools/src/Security/Advisories/Definition.hs index 14ea7538..e4eb3bc2 100644 --- a/code/hsec-tools/src/Security/Advisories/Definition.hs +++ b/code/hsec-tools/src/Security/Advisories/Definition.hs @@ -4,7 +4,7 @@ module Security.Advisories.Definition ( Advisory(..) -- * Supporting types , Affected(..) - , CWE(..) + , CWEID , Architecture(..) , AffectedVersionRange(..) , OS(..) @@ -12,6 +12,7 @@ module Security.Advisories.Definition ) where +import Security.CWE (CWEID) import Data.Text (Text) import Data.Time (ZonedTime) import Distribution.Types.Version (Version) @@ -26,7 +27,7 @@ data Advisory = Advisory { advisoryId :: HsecId , advisoryModified :: ZonedTime , advisoryPublished :: ZonedTime - , advisoryCWEs :: [CWE] + , advisoryCWEs :: [CWEID] , advisoryKeywords :: [Keyword] , advisoryAliases :: [Text] , advisoryRelated :: [Text] @@ -53,9 +54,6 @@ data Affected = Affected } deriving stock (Show) -newtype CWE = CWE {unCWE :: Integer} - deriving stock (Show) - data Architecture = AArch64 | Alpha diff --git a/code/hsec-tools/src/Security/Advisories/Parse.hs b/code/hsec-tools/src/Security/Advisories/Parse.hs index f979e44f..a8983ee9 100644 --- a/code/hsec-tools/src/Security/Advisories/Parse.hs +++ b/code/hsec-tools/src/Security/Advisories/Parse.hs @@ -14,6 +14,7 @@ module Security.Advisories.Parse ) where +import qualified Security.CWE as CWE import Data.Bifunctor (first) import Data.Foldable (toList) import Data.List (intercalate) @@ -25,6 +26,7 @@ import GHC.Generics (Generic) import qualified Data.Map as Map import Data.Sequence (Seq((:<|))) import qualified Data.Text as T +import qualified Data.Text.Read as T import qualified Data.Text.Lazy as T (toStrict) import Data.Time (ZonedTime(..), LocalTime (LocalTime), midnight, utc) import Distribution.Parsec (eitherParsec) @@ -222,7 +224,7 @@ data AdvisoryMetadata = AdvisoryMetadata { amdId :: HsecId , amdModified :: Maybe ZonedTime , amdPublished :: Maybe ZonedTime - , amdCWEs :: [CWE] + , amdCWEs :: [CWE.CWEID] , amdKeywords :: [Keyword] , amdAliases :: [T.Text] , amdRelated :: [T.Text] @@ -321,11 +323,26 @@ instance Toml.FromValue HsecId where instance Toml.ToValue HsecId where toValue = Toml.toValue . printHsecId -instance Toml.FromValue CWE where - fromValue v = CWE <$> Toml.fromValue v - -instance Toml.ToValue CWE where - toValue (CWE x) = Toml.toValue x +instance Toml.FromValue CWE.CWEID where + fromValue v = case v of + -- Check if the cwe number is known + Toml.Integer int | Just cwe <- CWE.mkCWEID int, Map.member cwe CWE.cweNames -> pure cwe + -- Check if the cwe text match "number: description" + Toml.String string -> case T.breakOn ":" (T.pack string) of + (numTxt, name) -> case T.decimal numTxt of + Right (num, "") -> do + -- Value is a "num: text", now validate if it's known + cwe <- Toml.fromValue (Toml.Integer num) + case T.strip (T.drop 1 name) of + "" -> pure cwe + expectedName -> case Map.lookup cwe CWE.cweNames of + Just cweName | expectedName == cweName -> pure cwe + _ -> fail ("unexpected description, got: " <> show cwe <> ", expected: " <> show expectedName) + _ -> fail ("expected a number, got: " <> show numTxt) + _ -> fail "expected a valid number or a cwe text description" + +instance Toml.ToValue CWE.CWEID where + toValue = Toml.toValue . CWE.unCWEID instance Toml.FromValue Keyword where fromValue v = Keyword <$> Toml.fromValue v