diff --git a/.github/workflows/haskell-ci.yml b/.github/workflows/haskell-ci.yml index 55caf438..e96f8ed1 100644 --- a/.github/workflows/haskell-ci.yml +++ b/.github/workflows/haskell-ci.yml @@ -184,6 +184,7 @@ jobs: echo "packages: $GITHUB_WORKSPACE/source/code/hsec-core" >> cabal.project echo "packages: $GITHUB_WORKSPACE/source/code/hsec-tools" >> cabal.project echo "packages: $GITHUB_WORKSPACE/source/code/cvss" >> cabal.project + echo "packages: $GITHUB_WORKSPACE/source/code/cwe" >> cabal.project echo "packages: $GITHUB_WORKSPACE/source/code/osv" >> cabal.project cat cabal.project - name: sdist diff --git a/cabal.project b/cabal.project index 3f2ec970..07cddf94 100644 --- a/cabal.project +++ b/cabal.project @@ -3,4 +3,5 @@ packages: code/*/*.cabal package hsec-core package hsec-tools package cvss +package cwe package osv 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-core/src/Security/Advisories/Core/Advisory.hs b/code/hsec-core/src/Security/Advisories/Core/Advisory.hs index 1211be2b..25414ae1 100644 --- a/code/hsec-core/src/Security/Advisories/Core/Advisory.hs +++ b/code/hsec-core/src/Security/Advisories/Core/Advisory.hs @@ -4,7 +4,7 @@ module Security.Advisories.Core.Advisory ( Advisory(..) -- * Supporting types , Affected(..) - , CWE(..) + , CWEID , Architecture(..) , AffectedVersionRange(..) , OS(..) @@ -12,6 +12,7 @@ module Security.Advisories.Core.Advisory ) where +import Security.CWE (CWEID) import Data.Text (Text) import Data.Time (ZonedTime) import Distribution.Types.Version (Version) @@ -27,7 +28,7 @@ data Advisory = Advisory { advisoryId :: HsecId , advisoryModified :: ZonedTime , advisoryPublished :: ZonedTime - , advisoryCWEs :: [CWE] + , advisoryCWEs :: [CWEID] , advisoryKeywords :: [Keyword] , advisoryAliases :: [Text] , advisoryRelated :: [Text] @@ -54,9 +55,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/hsec-tools.cabal b/code/hsec-tools/hsec-tools.cabal index 00d3bc61..dd828627 100644 --- a/code/hsec-tools/hsec-tools.cabal +++ b/code/hsec-tools/hsec-tools.cabal @@ -47,6 +47,7 @@ library , commonmark-pandoc >=0.2 && <0.3 , containers >=0.6 && <0.7 , cvss + , cwe , directory <2 , extra ^>=1.7.5 , filepath >=1.4 && <1.5 diff --git a/code/hsec-tools/src/Security/Advisories/Parse.hs b/code/hsec-tools/src/Security/Advisories/Parse.hs index fd5c5431..4ba29d34 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) @@ -223,7 +225,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] @@ -322,11 +324,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 diff --git a/flake.nix b/flake.nix index 86bb898c..b389363f 100644 --- a/flake.nix +++ b/flake.nix @@ -20,6 +20,7 @@ pkgs.haskell.lib.doJailbreak (pkgs.haskell.lib.dontCheck (pkgs.haskell.lib.unmarkBroken pkg)); cvss = pkgs.haskellPackages.callCabal2nix "cvss" ./code/cvss {}; + cwe = pkgs.haskellPackages.callCabal2nix "cwe" ./code/cwe {}; osv = pkgs.haskellPackages.callCabal2nix "osv" ./code/osv {inherit cvss;}; hsec-core = pkgs.haskellPackages.callCabal2nix "hsec-core" ./code/hsec-core { inherit cvss osv; @@ -33,10 +34,9 @@ root = ./code/hsec-tools; withHoogle = false; overrides = self: super: { - inherit cvss hsec-core osv; + inherit cvss cwe hsec-core osv; Cabal-syntax = super.Cabal-syntax_3_8_1_0; toml-parser = jailbreakUnbreak (super.callCabal2nix "toml-parser" toml-parser { }); - cwe = super.callCabal2nix "cwe" ./code/cwe {}; }; modifier = drv: