hsaml2 0.1.2 → 0.2.0
raw patch · 15 files changed
+273/−128 lines, 15 filesdep +cryptondep +crypton-x509dep +silentlydep −hxt-httpdep −string-conversions
Dependencies added: crypton, crypton-x509, silently
Dependencies removed: hxt-http, string-conversions
Files
- SAML2/Bindings/HTTPRedirect.hs +0/−2
- SAML2/Core/Namespaces.hs +0/−7
- SAML2/Core/Signature.hs +11/−18
- SAML2/Metadata/Metadata.hs +16/−2
- SAML2/XML.hs +40/−8
- SAML2/XML/Canonical.hs +5/−2
- SAML2/XML/Schema/Datatypes.hs +0/−1
- SAML2/XML/Signature.hs +62/−46
- SAML2/XML/Signature/Types.hs +0/−1
- SAML2/XML/Types.hs +3/−0
- hsaml2.cabal +23/−8
- test/Metadata/Metadata.hs +10/−0
- test/XML.hs +2/−3
- test/XML/Canonical.hs +19/−4
- test/XML/Signature.hs +82/−26
SAML2/Bindings/HTTPRedirect.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TupleSections #-}@@ -23,7 +22,6 @@ import qualified Data.ByteString.Char8 as BSC import qualified Data.ByteString.Lazy as BSL import Data.Maybe (fromMaybe, maybeToList)-import Data.Monoid ((<>)) import Data.Proxy (Proxy(..)) import Network.HTTP.Types.Header (ResponseHeaders, hLocation, hCacheControl, hPragma) import Network.HTTP.Types.URI (Query, renderQuery, urlDecode)
SAML2/Core/Namespaces.hs view
@@ -8,7 +8,6 @@ , samlURNIdentifier ) where -import Data.Monoid ((<>)) import Network.URI (URI(..)) import SAML2.Core.Versioning@@ -24,9 +23,3 @@ samlURNIdentifier :: String -> (SAMLVersion, String) -> URI samlURNIdentifier t (v, n) = samlURN v [t, n]---- samlURNIdentifier :: String -> (a -> (SAMLVersion, String)) -> (a -> samlURN)--- samlURNIdentifier = (.) . uncurry . samlURNIdentifier---- xpSAMLURNIdentifier :: Identifiable URI a => String -> XP.PU a--- xpSAMLURNIdentifier = xpIdentifier XS.xpAnyURI
SAML2/Core/Signature.hs view
@@ -6,12 +6,10 @@ module SAML2.Core.Signature ( signSAMLProtocol , verifySAMLProtocol- , verifySAMLProtocol'+ , verifySAMLProtocolWithKeys ) where -import Control.Exception-import Control.Lens ((^.), (.~))-import Control.Monad (unless)+import Control.Lens ((^.), (?~)) import qualified Data.ByteString.Lazy as BSL import Data.List.NonEmpty (NonEmpty((:|))) import Network.URI (URI(uriFragment), nullURI)@@ -45,7 +43,7 @@ } , DS.signedInfoReference = r :| [] } DS.signatureSignedInfo $ SAMLP.protocolSignature p- return $ DS.signature' .~ Just s' $ m+ return $ DS.signature' ?~ s' $ m where p = m ^. SAMLP.samlProtocol' @@ -53,20 +51,15 @@ verifySAMLProtocol b = do x <- maybe (fail "invalid XML") return $ xmlToDoc b m <- either fail return $ docToSAML x- v <- DS.verifySignature mempty (DS.signedID m) x- unless (or v) $ fail "verifySAMLProtocol: invalid or missing signature"- return m+ v <- DS.verifySignatureUnenvelopedSigs mempty (DS.signedID m) x+ case v of+ Left msg -> fail $ "verifySAMLProtocol: invalid or missing signature: " ++ show msg+ Right () -> return m -- | A variant of 'verifySAMLProtocol' that is more symmetric to 'signSAMLProtocol'. The reason it -- takes an 'XmlTree' and not an @a@ is that signature verification needs both.------ TODO: Should this replace 'verifySAMLProtocol'?-verifySAMLProtocol' :: SAMLP.SAMLProtocol a => DS.PublicKeys -> XmlTree -> IO a-verifySAMLProtocol' pubkeys x = do+verifySAMLProtocolWithKeys :: SAMLP.SAMLProtocol a => DS.PublicKeys -> XmlTree -> IO a+verifySAMLProtocolWithKeys pubkeys x = do m <- either fail return $ docToSAML x- v :: Either SomeException (Maybe Bool) <- try $ DS.verifySignature pubkeys (DS.signedID m) x- case v of- Left e -> fail $ "signature verification failed: " ++ show e- Right Nothing -> fail "signature verification failed: no matching key/alg pair."- Right (Just False) -> fail "signature verification failed: verification failed."- Right (Just True) -> pure m+ v <- DS.verifySignatureUnenvelopedSigs pubkeys (DS.signedID m) x+ either (fail . show) (const $ pure m) v
SAML2/Metadata/Metadata.hs view
@@ -15,6 +15,7 @@ import qualified Network.URI as URI import qualified Text.XML.HXT.Arrow.Pickle.Schema as XPS +import qualified Text.XML.HXT.DOM.QualifiedName as HXT import SAML2.Lens import SAML2.XML import qualified Text.XML.HXT.Arrow.Pickle.Xml.Invertible as XP@@ -197,6 +198,11 @@ = Descriptor { descriptorRole :: !RoleDescriptor } -- ^§2.4.1+ | ExtendedRoleDescriptor+ { descriptorRoleExtensionType :: !(XS.String)+ , descriptorRole :: !RoleDescriptor+ , descriptorAdditionalNodes :: !Nodes+ } -- ^§2.4.1 (variant extended with use of xsi:type attribute) | IDPSSODescriptor { descriptorRole :: !RoleDescriptor , descriptorSSO :: !SSODescriptor@@ -237,15 +243,23 @@ } -- ^§2.4.6 deriving (Eq, Show) +xsiTypeQName :: HXT.QName+xsiTypeQName = HXT.mkQName "xsi" "type" "http://www.w3.org/2001/XMLSchema-instance"+ instance XP.XmlPickler Descriptor where xpickle = [XP.biCase|- Left (Left (Left (Left (Left r)))) <-> Descriptor r+ Left (Left (Left (Left (Left (Left ((t, rde), an)))))) <-> ExtendedRoleDescriptor t rde an+ Left (Left (Left (Left (Left (Right r))))) <-> Descriptor r Left (Left (Left (Left (Right (((((((ws, r), s), sso), nim), air), ap), a))))) <-> IDPSSODescriptor r s ws sso nim air ap a Left (Left (Left (Right (((((a, w), r), s), e), t)))) <-> SPSSODescriptor r s a w e t Left (Left (Right (((r, a), s), n))) <-> AuthnAuthorityDescriptor r a s n Left (Right (((((r, a), s), n), tp), t)) <-> AttributeAuthorityDescriptor r a s n tp t Right (((r, a), s), n) <-> PDPDescriptor r a s n|]- XP.>$< (xpElem "RoleDescriptor" XP.xpickle+ XP.>$< (xpElem "RoleDescriptor" + (XP.xpAttrQN xsiTypeQName XS.xpString+ XP.>*< XP.xpickle + XP.>*< XP.xpTrees)+ XP.>|< xpElem "RoleDescriptor" XP.xpickle XP.>|< xpElem "IDPSSODescriptor" (XP.xpDefault False (XP.xpAttr "WantAuthnRequestsSigned" XS.xpBoolean) XP.>*< XP.xpickle
SAML2/XML.hs view
@@ -18,11 +18,14 @@ , xpIdentifier , IdentifiedURI , samlToDoc+ , samlToDocFirstChild , samlToXML , docToSAML- , docToXML+ , docToXMLWithoutRoot+ , docToXMLWithRoot , xmlToSAML , xmlToDoc+ , xmlToDocE ) where import qualified Data.ByteString.Lazy as BSL@@ -32,9 +35,9 @@ import Data.Maybe (listToMaybe) import Network.URI (URI) import qualified Text.XML.HXT.Core as HXT-import Text.XML.HXT.Arrow.Edit (escapeXmlRefs)-import Text.XML.HXT.DOM.ShowXml (xshow')+import qualified Text.XML.HXT.DOM.ShowXml import Text.XML.HXT.DOM.XmlNode (getChildren)+import qualified Data.Tree.NTree.TypeDefs as HXT import SAML2.XML.Types import SAML2.Core.Datatypes@@ -48,7 +51,7 @@ xpTrimElemNS ns n c = XP.xpTrim $ XP.xpElemQN (mkNName ns n) (c XP.>* XP.xpWhitespace) xpXmlLang :: XP.PU XS.Language-xpXmlLang = XP.xpAttrQN (mkNName xmlNS "lang") $ XS.xpLanguage+xpXmlLang = XP.xpAttrQN (mkNName xmlNS "lang") XS.xpLanguage type IP = XS.String @@ -100,14 +103,43 @@ . HXT.runLA (HXT.processChildren $ HXT.cleanupNamespaces HXT.collectPrefixUriPairs) . XP.pickleDoc XP.xpickle -docToXML :: HXT.XmlTree -> BSL.ByteString-docToXML = xshow' cquot aquot (:) . getChildren where (cquot, aquot) = escapeXmlRefs+-- | From the input xml forest, take the first child of the first tree.+samlToDocFirstChild :: XP.XmlPickler a => a -> HXT.XmlTree+samlToDocFirstChild = head . getChildren . head+ . HXT.runLA (HXT.processChildren $ HXT.cleanupNamespaces HXT.collectPrefixUriPairs)+ . XP.pickleDoc XP.xpickle +-- | see also 'docToXMLWithRoot'+docToXMLWithoutRoot :: HXT.XmlTree -> BSL.ByteString+docToXMLWithoutRoot = BSL.concat . HXT.runLA (HXT.xshowBlob HXT.getChildren)++-- | 'docToXML' chops off the root element from the tree. 'docToXMLWithRoot' does not do+-- this. it may make sense to remove 'docToXMLWithoutRoot', but since i don't understand this+-- code enough to be confident not to break anything, i'll just leave this extra function for+-- reference.+docToXMLWithRoot :: HXT.XmlTree -> BSL.ByteString+docToXMLWithRoot = Text.XML.HXT.DOM.ShowXml.xshowBlob . (:[])+ samlToXML :: XP.XmlPickler a => a -> BSL.ByteString-samlToXML = docToXML . samlToDoc+samlToXML = docToXMLWithoutRoot . samlToDoc xmlToDoc :: BSL.ByteString -> Maybe HXT.XmlTree-xmlToDoc = listToMaybe . HXT.runLA+xmlToDoc = either (const Nothing) Just . xmlToDocE++xmlToDocE :: BSL.ByteString -> Either String HXT.XmlTree+xmlToDocE = fix . xmlToDocUnsafe+ where+ fix Nothing =+ Left "Nothing"+ fix (Just (HXT.NTree (HXT.XError num msg) shouldBeEmpty)) =+ Left $ show num ++ ": " ++ msg ++ (if null shouldBeEmpty then "" else show shouldBeEmpty)+ fix (Just good) =+ Right good++-- | Take a UTF-8 encoded bytestring and return an xml tree. This is unsafe and returns xml+-- trees containing parse errors on occasion; call 'xmlToDocE' instead.+xmlToDocUnsafe :: BSL.ByteString -> Maybe HXT.XmlTree+xmlToDocUnsafe = listToMaybe . HXT.runLA (HXT.xreadDoc HXT.>>> HXT.removeWhiteSpace HXT.>>> HXT.neg HXT.isXmlPi
SAML2/XML/Canonical.hs view
@@ -58,9 +58,12 @@ -- |Canonicalize and serialize an XML document canonicalize :: CanonicalizationAlgorithm -> Maybe InclusiveNamespaces -> Maybe String -> HXT.XmlTree -> IO BS.ByteString-canonicalize a i s =+canonicalize a i s = canonicalizeWithRoot a i s . getChildren++canonicalizeWithRoot :: CanonicalizationAlgorithm -> Maybe InclusiveNamespaces -> Maybe String -> HXT.XmlTrees -> IO BS.ByteString+canonicalizeWithRoot a i s = LibXML2.c14n (cm a) (inclusiveNamespacesPrefixList <$> i) (canonicalWithComments a) s- <=< LibXML2.fromXmlTrees . getChildren where+ <=< LibXML2.fromXmlTrees where cm CanonicalXML10{} = LibXML2.C14N_1_0 cm CanonicalXML11{} = LibXML2.C14N_1_1 cm CanonicalXMLExcl10{} = LibXML2.C14N_EXCLUSIVE_1_0
SAML2/XML/Schema/Datatypes.hs view
@@ -14,7 +14,6 @@ import Data.Char.Properties.XMLCharProps (isXmlSpaceChar, isXmlNameChar) import Data.Fixed (Pico, showFixed) import Data.List (elemIndex)-import Data.Semigroup ((<>)) import qualified Data.Time.Clock as Time import Data.Time.Format (formatTime, parseTimeM, defaultTimeLocale) import Data.Word (Word16)
SAML2/XML/Signature.hs view
@@ -1,5 +1,10 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+ -- | -- XML Signature Syntax and Processing --@@ -9,16 +14,21 @@ , generateReference , SigningKey(..) , PublicKeys(..)+ , SignatureError (..) , signingKeySignatureAlgorithm , signBase64 , verifyBase64 , generateSignature- , verifySignature+ , verifySignatureUnenvelopedSigs+ , applyCanonicalization+ , applyTransforms ) where +import GHC.Stack import Control.Applicative ((<|>))-import Control.Monad (guard, (<=<))-import Crypto.Number.Basic (numBytes)+import Control.Exception (SomeException, handle)+import Control.Monad ((<=<))+import Control.Monad.Except import Crypto.Number.Serialize (i2ospOf_, os2ip) import Crypto.Hash (hashlazy, SHA1(..), SHA256(..), SHA512(..), RIPEMD160(..)) import qualified Crypto.PubKey.DSA as DSA@@ -29,9 +39,7 @@ import qualified Data.ByteString.Base64 as Base64 import qualified Data.ByteString.Lazy as BSL import qualified Data.List.NonEmpty as NonEmpty-import Data.Maybe (isJust)-import Data.Monoid ((<>))-import qualified Data.X509 as X509+import Data.Either (isRight) import Network.URI (URI(..)) import qualified Text.XML.HXT.Core as HXT import qualified Text.XML.HXT.DOM.ShowXml as DOM@@ -71,14 +79,11 @@ applyTransforms :: Maybe Transforms -> HXT.XmlTree -> IO BSL.ByteString applyTransforms = applyTransformsXML . maybe [] (NonEmpty.toList . transforms) -asType :: a -> proxy a -> proxy a-asType _ = id- applyDigest :: DigestMethod -> BSL.ByteString -> BS.ByteString-applyDigest (DigestMethod (Identified DigestSHA1) []) = BA.convert . asType SHA1 . hashlazy-applyDigest (DigestMethod (Identified DigestSHA256) []) = BA.convert . asType SHA256 . hashlazy-applyDigest (DigestMethod (Identified DigestSHA512) []) = BA.convert . asType SHA512 . hashlazy-applyDigest (DigestMethod (Identified DigestRIPEMD160) []) = BA.convert . asType RIPEMD160 . hashlazy+applyDigest (DigestMethod (Identified DigestSHA1) []) = BA.convert . hashlazy @SHA1+applyDigest (DigestMethod (Identified DigestSHA256) []) = BA.convert . hashlazy @SHA256+applyDigest (DigestMethod (Identified DigestSHA512) []) = BA.convert . hashlazy @SHA512+applyDigest (DigestMethod (Identified DigestRIPEMD160) []) = BA.convert . hashlazy @RIPEMD160 applyDigest d = error $ "unsupported " ++ show d generateReference :: Reference -> HXT.XmlTree -> IO Reference@@ -88,13 +93,22 @@ return r { referenceDigestValue = d } -verifyReference :: Reference -> HXT.XmlTree -> IO (Maybe String)+-- | Re-compute the digest (after transforms) of a 'Reference'd subtree of an xml document and+-- compare it against the one given in the 'Reference'. If it matches, return the xml ID;+-- otherwise, return an error string.+verifyReference :: HasCallStack => Reference -> HXT.XmlTree -> IO (Either String String) verifyReference r doc = case referenceURI r of- Just URI{ uriScheme = "", uriAuthority = Nothing, uriPath = "", uriQuery = "", uriFragment = '#':xid }- | x@[_] <- HXT.runLA (getID xid) doc -> do- t <- applyTransforms (referenceTransforms r) $ DOM.mkRoot [] x- return $ xid <$ guard (applyDigest (referenceDigestMethod r) t == referenceDigestValue r)- _ -> return Nothing+ Just URI{ uriScheme = "", uriAuthority = Nothing, uriPath = "", uriQuery = "", uriFragment = '#':xid } ->+ case HXT.runLA (getID xid) doc of+ x@[_] -> do+ t :: BSL.ByteString <- applyTransforms (referenceTransforms r) $ DOM.mkRoot [] x+ let have = applyDigest (referenceDigestMethod r) t+ want = referenceDigestValue r+ return $ if have == want+ then Right xid+ else Left $ "#" <> xid <> ": digest mismatch"+ bad -> return . Left $ "#" <> xid <> ": has " <> show (length bad) <> " matches, should have 1."+ bad -> return . Left $ "unexpected referenceURI: " <> show bad data SigningKey = SigningKeyDSA DSA.KeyPair@@ -113,8 +127,7 @@ #endif instance Monoid PublicKeys where mempty = PublicKeys Nothing Nothing- PublicKeys dsa1 rsa1 `mappend` PublicKeys dsa2 rsa2 =- PublicKeys (dsa1 <|> dsa2) (rsa1 <|> rsa2)+ mappend = (<>) signingKeySignatureAlgorithm :: SigningKey -> SignatureAlgorithm signingKeySignatureAlgorithm (SigningKeyDSA _) = SignatureDSA_SHA1@@ -133,22 +146,6 @@ , rsaKeyValueExponent = e } -publicKeyValues :: KeyValue -> PublicKeys-publicKeyValues DSAKeyValue{ dsaKeyValuePQ = Just (p, q), dsaKeyValueG = Just g, dsaKeyValueY = y } = mempty- { publicKeyDSA = Just $ DSA.PublicKey- { DSA.public_params = DSA.Params- { DSA.params_p = p- , DSA.params_q = q- , DSA.params_g = g- }- , DSA.public_y = y- }- }-publicKeyValues RSAKeyValue{ rsaKeyValueModulus = n, rsaKeyValueExponent = e } = mempty- { publicKeyRSA = Just $ RSA.PublicKey (numBytes n) n e- }-publicKeyValues _ = mempty- signBytes :: SigningKey -> BS.ByteString -> IO BS.ByteString signBytes (SigningKeyDSA k) b = do s <- DSA.sign (DSA.toPrivateKey k) SHA1 b@@ -187,6 +184,7 @@ , signatureObject = [] } +-- deprecated! use 'verifySignature' instead. this is left here so it can be used for testing only. -- Exception in IO: something is syntactically wrong with the input -- Nothing: no matching key/alg pairs found -- Just False: signature verification failed || dangling refs || explicit ref is not among the signed ones@@ -206,18 +204,36 @@ let verified :: Maybe Bool verified = verifyBytes pks (signatureMethodAlgorithm $ signedInfoSignatureMethod si) (signatureValue $ signatureSignatureValue s) six valid :: Bool- valid = elem (Just xid) rl && all isJust rl+ valid = elem (Right xid) rl && all isRight rl return $ (valid &&) <$> verified where child n = HXT.runLA $ HXT.getChildren HXT.>>> isDSElem n HXT.>>> HXT.cleanupNamespaces HXT.collectPrefixUriPairs- keyinfo (KeyInfoKeyValue kv) = publicKeyValues kv- keyinfo (X509Data l) = foldMap keyx509d l- keyinfo _ = mempty- keyx509d (X509Certificate sc) = keyx509p $ X509.certPubKey $ X509.getCertificate sc- keyx509d _ = mempty- keyx509p (X509.PubKeyRSA r) = mempty{ publicKeyRSA = Just r }- keyx509p (X509.PubKeyDSA d) = mempty{ publicKeyDSA = Just d }- keyx509p _ = mempty xpathsel t = "/*[local-name()='" ++ t ++ "' and namespace-uri()='" ++ namespaceURIString ns ++ "']" xpathbase = "/*" ++ xpathsel "Signature" ++ xpathsel "SignedInfo" ++ "//" xpath = xpathbase ++ ". | " ++ xpathbase ++ "@* | " ++ xpathbase ++ "namespace::*"+++-- | It turns out sometimes we don't get envelopped signatures, but signatures that are+-- located outside the signed sub-tree. Since 'verifySiganture' doesn't support this case, if+-- you encounter it you should fall back to 'verifySignatureUnenvelopedSigs'.+verifySignatureUnenvelopedSigs :: PublicKeys -> String -> HXT.XmlTree -> IO (Either SignatureError ())+verifySignatureUnenvelopedSigs pks xid doc = catchAll $ warpResult <$> verifySignature pks xid doc+ where+ catchAll :: IO (Either SignatureError ()) -> IO (Either SignatureError ())+ catchAll = handle $ pure . Left . SignatureVerificationLegacyFailure . Left . (show @SomeException)++ warpResult (Just True) = Right ()+ warpResult bad = Left (SignatureVerificationLegacyFailure (Right bad))++data SignatureError =+ SignedElementNotFound+ | SignatureNotFoundOrEmpty+ | SignatureParseError String+ | SignatureCanonicalizationError String+ | SignatureVerifyReferenceError String+ | SignatureVerifyBadReferences String+ | SignatureVerifyInputNotReferenced String+ | SignatureVerificationCryptoUnsupported String+ | SignatureVerificationCryptoFailed String+ | SignatureVerificationLegacyFailure (Either String (Maybe Bool))+ deriving (Eq, Show)
SAML2/XML/Signature/Types.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} -- |
SAML2/XML/Types.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE QuasiQuotes #-}+ module SAML2.XML.Types where import Data.List.NonEmpty (NonEmpty(..))@@ -8,8 +9,10 @@ import qualified Text.XML.HXT.Arrow.Pickle.Xml.Invertible as XP type Node = HXT.XmlTree+ -- instance XP.XmlPickler XML.Node where xpickle = XP.xpTree type Nodes = HXT.XmlTrees+ -- instance XP.XmlPickler XML.Nodes where xpickle = XP.xpTrees type List1 a = NonEmpty a
hsaml2.cabal view
@@ -1,5 +1,5 @@ name: hsaml2-version: 0.1.2+version: 0.2.0 synopsis: OASIS Security Assertion Markup Language (SAML) V2.0 description: Direct implementation of the SAML XML standard (https://www.oasis-open.org/standards#samlv2.0), along with some related dependencies. This is currently partial, as the standard is quite extensive, but is sufficient to build a functioning SP and fully validate responses. The module layout basically follows the standard definition documentation. Its use still requires a fairly extensive understanding of SAML. license: Apache-2.0@@ -10,7 +10,6 @@ category: Security, Network, Web build-type: Simple cabal-version: >=1.10-tested-with: GHC == 8.0.2 || == 8.2.2 || == 8.4.4 || == 8.6.5 || == 8.8.3 extra-source-files: test/Metadata/metadata-idp.xml@@ -27,6 +26,10 @@ test/XML/signature-example.xml test/XML/world.txt +flag crypton+ description: Use cryton instead of cryptonite+ default: True+ source-repository head type: git location: https://github.com/dylex/hsaml2@@ -66,13 +69,20 @@ SAML2.Bindings.Internal c-sources: SAML2/XML/libxml2_stub.c+ if flag(crypton)+ build-depends:+ crypton,+ crypton-x509+ else+ build-depends:+ cryptonite,+ x509 build-depends: asn1-encoding, asn1-types >= 0.2, base >=4.8 && <5, base64-bytestring, bytestring,- cryptonite, data-default, http-types, hxt,@@ -85,11 +95,11 @@ mtl, network-uri, process,+ silently, semigroups, template-haskell, time, utf8-string,- x509, zlib pkgconfig-depends: libxml-2.0 default-language: Haskell2010@@ -109,18 +119,23 @@ XML.Signature default-language: Haskell2010 ghc-options: -Wall+ if flag(crypton)+ build-depends:+ crypton,+ crypton-x509+ else+ build-depends:+ cryptonite,+ x509 build-depends: base, base64-bytestring, bytestring,- cryptonite,+ utf8-string, hsaml2, hxt,- hxt-http, http-types, network-uri, semigroups,- string-conversions, time,- x509, HUnit
test/Metadata/Metadata.hs view
@@ -1,6 +1,8 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-} module Metadata.Metadata (tests) where +import Control.Monad.IO.Class import Data.List.NonEmpty (NonEmpty(..)) import qualified Data.X509 as X509 import qualified Test.HUnit as U@@ -490,4 +492,12 @@ [] ] []+ , canLoadMetadata "test/Metadata/metadata-idp-azure.xml" ]+ where+ canLoadMetadata :: String -> U.Test+ canLoadMetadata metadataPath = U.TestCase $ do+ [res] <- liftIO $ parseXML metadataPath + case (res :: Either String Metadata) of + Left s -> U.assertFailure s+ _ -> pure ()
test/XML.hs view
@@ -1,5 +1,6 @@ module XML ( testXML+ , parseXML , uri , pickleElem ) where@@ -7,15 +8,13 @@ import Data.Maybe (fromJust) import Network.URI (URI, parseURIReference) import qualified Test.HUnit as U- import qualified Text.XML.HXT.Core as HXT-import qualified Text.XML.HXT.HTTP as HXT (withHTTP) import qualified Text.XML.HXT.DOM.XmlNode as DOM parseXML :: HXT.XmlPickler a => String -> IO [Either String a] parseXML u = fmap (map (HXT.unpickleDoc' HXT.xpickle)) $ HXT.runX $- HXT.readDocument [HXT.withCheckNamespaces HXT.yes, HXT.withHTTP [], HXT.withRemoveWS HXT.yes] u+ HXT.readDocument [HXT.withCheckNamespaces HXT.yes, HXT.withRemoveWS HXT.yes] u HXT.>>> HXT.processBottomUp (HXT.processAttrl (HXT.none `HXT.when` HXT.isNamespaceDeclAttr)) testXML :: (Eq a, HXT.XmlPickler a, Show a) => String -> a -> U.Test
test/XML/Canonical.hs view
@@ -2,20 +2,34 @@ module XML.Canonical (tests) where import qualified Data.ByteString as BS+import qualified Data.ByteString.UTF8 as BSU+import qualified Data.ByteString.Lazy.UTF8 as BSLU import qualified Test.HUnit as U import qualified Text.XML.HXT.Core as HXT+import System.IO.Unsafe (unsafePerformIO) +import Control.Monad+import Data.Either (isRight)+import SAML2.XML import SAML2.XML.Canonical canonicalizeXML :: CanonicalizationAlgorithm -> String -> Bool -> IO [BS.ByteString]-canonicalizeXML c f ent = HXT.runX $+canonicalizeXML algo f ent = HXT.runX (HXT.readDocument [HXT.withCheckNamespaces HXT.yes, HXT.withValidate HXT.no, HXT.withCanonicalize HXT.no, HXT.withSubstDTDEntities ent] f- HXT.>>> HXT.arrIO (canonicalize c Nothing Nothing))+ HXT.>>> HXT.arrIO (canonicalize algo Nothing Nothing)) testC14N :: CanonicalizationAlgorithm -> String -> Bool -> BS.ByteString -> U.Test-testC14N c f ent s = U.TestCase $- U.assertEqual (show c ++ ' ' : f) [s] =<< canonicalizeXML c f ent+testC14N algo f ent s = U.TestCase $+ U.assertEqual (show algo ++ ' ' : f) [s] =<< canonicalizeXML algo f ent +testIdempotency :: CanonicalizationAlgorithm -> String -> U.Test+testIdempotency algo input = U.TestCase $ do+ U.assertBool (show algo ++ "[1] " ++ input) (isRight (go input))+ U.assertBool (show algo ++ "[2] " ++ input) (go input == (go >=> go) input)+ where+ go :: String -> Either String String+ go = fmap (BSU.toString . unsafePerformIO . canonicalizeWithRoot algo Nothing Nothing . (: [])) . xmlToDocE . BSLU.fromString+ tests :: U.Test tests = U.test [ testC14N (CanonicalXML10 False) "test/XML/noncanonical1.xml" False@@ -33,4 +47,5 @@ , testC14N (CanonicalXML10 False) "test/XML/noncanonical6.xml" False "<doc>\194\169</doc>" -- , testC14N (CanonicalXML10 False) "test/XML/noncanonical7.xml"+ , testIdempotency (CanonicalXMLExcl10 False) "<a>\n <b>\n\n\nwef </b></a>" ]
test/XML/Signature.hs view
@@ -1,18 +1,26 @@+{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ViewPatterns #-}+{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}+ module XML.Signature (tests) where import Control.Exception (SomeException, try)+import Control.Monad import Data.ByteString.Base64 import Data.Either (isLeft) import Data.List (isInfixOf) import Data.List.NonEmpty (NonEmpty(..))-import Data.String.Conversions+import Data.Maybe (fromMaybe) import Data.Time import qualified Data.X509 as X509 import GHC.Stack import qualified Data.ByteString as BS+import qualified Data.ByteString.Base64.Lazy as EL+import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString.Lazy as LBS import qualified SAML2.XML as HS import qualified Test.HUnit as U@@ -33,7 +41,7 @@ import XML.Keys tests :: U.Test-tests = U.test [serializationTests, signVerifyTests, counterExamples]+tests = U.test [serializationTests, signVerifyTests, verifyTests, counterExamples] ----------------------------------------------------------------------@@ -101,7 +109,7 @@ :| []) :| [])]] - , testXML "http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/signature-example-rsa.xml" $+ , testXML "test/XML/signature-example-rsa.xml" $ Signature Nothing (SignedInfo Nothing (CanonicalizationMethod@@ -138,7 +146,7 @@ : []) [] - , testXML "http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/signature-example-dsa.xml" $+ , testXML "test/XML/signature-example-dsa.xml" $ Signature Nothing (SignedInfo Nothing (CanonicalizationMethod@@ -188,36 +196,39 @@ [ U.TestCase $ do let req = somereq req' <- signSAMLProtocol privkey1 req- let reqdoc = samlToDoc req'- req'' :: AuthnRequest <- verifySAMLProtocol' pubkey1 reqdoc- U.assertEqual "AuthnRequest with verifySAMLProtocol' (matching pubkeys)" req' req''+ let reqdoc = samlToDocFirstChild req'+ req'' :: AuthnRequest <- verifySAMLProtocolWithKeys pubkey1 reqdoc+ U.assertEqual "AuthnRequest with verifySAMLProtocolWithKeys (matching pubkeys)" req' req'' , U.TestCase $ do let req = somereq req' <- signSAMLProtocol privkeyRsa req let reqdoc = samlToDoc req'- req'' :: Either SomeException AuthnRequest <- try $ verifySAMLProtocol' pubkey1 reqdoc- U.assertBool "AuthnRequest with verifySAMLProtocol' (bad pubkeys): isLeft"+ req'' :: Either SomeException AuthnRequest <- try $ verifySAMLProtocolWithKeys pubkey1 reqdoc+ U.assertBool "AuthnRequest with verifySAMLProtocolWithKeys (bad pubkeys): isLeft" $ isLeft req''- U.assertBool "AuthnRequest with verifySAMLProtocol' (bad pubkeys): error message matches"- $ "Left user error (signature verification failed: no matching key/alg pair.)" `isInfixOf` show req''+ U.assertBool "AuthnRequest with verifySAMLProtocolWithKeys (bad pubkeys): error message matches"+ $ "(SignatureVerificationLegacyFailure (Right Nothing))" `isInfixOf` show req'' , U.TestCase $ do let req = somereq req' <- signSAMLProtocol privkeyRsa req let reqdoc = samlToDoc req'- req'' :: Either SomeException AuthnRequest <- try $ verifySAMLProtocol' (pubkey1 <> dummyPubkeyRSA) reqdoc- U.assertBool "AuthnRequest with verifySAMLProtocol' (bad pubkeys): isLeft"+ req'' :: Either SomeException AuthnRequest <- try $ verifySAMLProtocolWithKeys (pubkey1 <> dummyPubkeyRSA) reqdoc+ U.assertBool "AuthnRequest with verifySAMLProtocolWithKeys (bad pubkeys): isLeft" $ isLeft req''- U.assertBool "AuthnRequest with verifySAMLProtocol' (bad pubkeys): error message matches"- $ "Left user error (signature verification failed: verification failed.)" `isInfixOf` show req''+ U.assertBool "AuthnRequest with verifySAMLProtocolWithKeys (bad pubkeys): error message matches"+ $ "(SignatureVerificationLegacyFailure (Right (Just False)))" `isInfixOf` show req'' , U.TestCase $ do let req = somereq req' <- signSAMLProtocol privkey1 req- let reqdoc = samlToDoc req'- req'' :: Either SomeException AuthnRequest <- try $ verifySAMLProtocol' pubkey2 reqdoc- U.assertBool "AuthnRequest with verifySAMLProtocol' (bad pubkeys)" $ isLeft req''+ let reqdoc = samlToDocFirstChild req'+ req'' :: Either SomeException AuthnRequest <- try $ verifySAMLProtocolWithKeys pubkey2 reqdoc+ U.assertBool "AuthnRequest with verifySAMLProtocolWithKeys (bad pubkeys)"+ $ isLeft req''+ U.assertBool "AuthnRequest with verifySAMLProtocolWithKeys (bad pubkeys): error message matches"+ $ "(SignatureVerificationLegacyFailure (Right (Just False)))" `isInfixOf` show req'' ] somereq :: AuthnRequest@@ -252,6 +263,55 @@ Just someTime = parseTimeM True defaultTimeLocale "%Y-%m-%dT%H:%M:%S%QZ" "2013-03-18T03:28:54.1Z" ----------------------------------------------------------------------+-- some real-world responses and signature verification runs++data VerifyExample+ = VerifyExample+ BSL.ByteString -- keyinfo from metadata of idp+ BSL.ByteString -- signed response (in the base64 encoded form from the multipart body)+ String -- identifier of the sub-tree of which the signature is to be verified+ (Either SignatureError ()) -- expected result+ Int -- serial number+ deriving (Eq, Show)++verifyTests :: U.Test+verifyTests = U.test $ runVerifyExample <$> examples++runVerifyExample :: VerifyExample -> U.Test+runVerifyExample (VerifyExample keys xmltree refid want examplenumber) = U.TestCase $ do+ let keys' = either (error . show) id $ parseKeyInfo keys+ let xmltree' = either (error . show) id $ (EL.decode >=> xmlToDocE) xmltree+ have <- verifySignatureUnenvelopedSigs keys' refid xmltree'+ U.assertEqual ("verify example #" ++ show examplenumber) want have++parseKeyInfo :: BSL.ByteString -> Either String PublicKeys+parseKeyInfo = getCert >=> getKeys+ where+ getCert :: BSL.ByteString -> Either String X509.SignedCertificate+ getCert raw = case xmlToSAML @KeyInfo raw of+ (Right (keyInfoElements -> X509Data (X509Certificate cert :| []) :| [])) -> Right cert+ bad -> Left $ "unsupported: " ++ show bad++ getKeys :: X509.SignedCertificate -> Either String PublicKeys+ getKeys cert = do+ case X509.certPubKey . X509.signedObject $ X509.getSigned cert of+ X509.PubKeyDSA pk -> Right $ PublicKeys (Just pk) Nothing+ X509.PubKeyRSA pk -> Right $ PublicKeys Nothing (Just pk)+ bad -> Left $ "unsupported: " ++ show bad++examples :: [VerifyExample]+examples = zipWith ($) xs [1 ..]+ where+ xs :: [Int -> VerifyExample]+ xs =+ [ VerifyExample+ "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><X509Data><X509Certificate>MIIDBTCCAe2gAwIBAgIQev76BWqjWZxChmKkGqoAfDANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE4MDIxODAwMDAwMFoXDTIwMDIxOTAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMgmGiRfLh6Fdi99XI2VA3XKHStWNRLEy5Aw/gxFxchnh2kPdk/bejFOs2swcx7yUWqxujjCNRsLBcWfaKUlTnrkY7i9x9noZlMrijgJy/Lk+HH5HX24PQCDf+twjnHHxZ9G6/8VLM2e5ZBeZm+t7M3vhuumEHG3UwloLF6cUeuPdW+exnOB1U1fHBIFOG8ns4SSIoq6zw5rdt0CSI6+l7b1DEjVvPLtJF+zyjlJ1Qp7NgBvAwdiPiRMU4l8IRVbuSVKoKYJoyJ4L3eXsjczoBSTJ6VjV2mygz96DC70MY3avccFrk7tCEC6ZlMRBfY1XPLyldT7tsR3EuzjecSa1M8CAwEAAaMhMB8wHQYDVR0OBBYEFIks1srixjpSLXeiR8zES5cTY6fBMA0GCSqGSIb3DQEBCwUAA4IBAQCKthfK4C31DMuDyQZVS3F7+4Evld3hjiwqu2uGDK+qFZas/D/eDunxsFpiwqC01RIMFFN8yvmMjHphLHiBHWxcBTS+tm7AhmAvWMdxO5lzJLS+UWAyPF5ICROe8Mu9iNJiO5JlCo0Wpui9RbB1C81Xhax1gWHK245ESL6k7YWvyMYWrGqr1NuQcNS0B/AIT1Nsj1WY7efMJQOmnMHkPUTWryVZlthijYyd7P2Gz6rY5a81DAFqhDNJl2pGIAE6HWtSzeUEh3jCsHEkoglKfm4VrGJEuXcALmfCMbdfTvtu4rlsaP2hQad+MG/KJFlenoTK34EMHeBPDCpqNDz8UVNk</X509Certificate></X509Data></KeyInfo>"+ "PHNhbWxwOlJlc3BvbnNlIElEPSJfM2FlYjMwNTQtZTg1Zi00MWZhLWEyMGYtMGYyNzhiMzI3ZjRlIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxOC0wNC0xNFQwOTo1ODo1OC40NTdaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly96YjIuemVyb2J1enoubmV0OjYwNDQzL2F1dGhyZXNwIiBJblJlc3BvbnNlVG89ImlkY2YyMjk5YWM1NTFiNDJmMWFhOWI4ODgwNGVkMzA4YzIiIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiPjxJc3N1ZXIgeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPmh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzY4MmZlYmU4LTAyMWItNGZkZS1hYzA5LWU2MDA4NWYwNTE4MS88L0lzc3Vlcj48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz48QXNzZXJ0aW9uIElEPSJfYzc5YzNlYzgtMWMyNi00NzUyLTk0NDMtMWY3NmViN2Q1ZGQ2IiBJc3N1ZUluc3RhbnQ9IjIwMTgtMDQtMTRUMDk6NTg6NTguNDQyWiIgVmVyc2lvbj0iMi4wIiB4bWxucz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+PElzc3Vlcj5odHRwczovL3N0cy53aW5kb3dzLm5ldC82ODJmZWJlOC0wMjFiLTRmZGUtYWMwOS1lNjAwODVmMDUxODEvPC9Jc3N1ZXI+PFNpZ25hdHVyZSB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PFNpZ25lZEluZm8+PENhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxkc2lnLW1vcmUjcnNhLXNoYTI1NiIvPjxSZWZlcmVuY2UgVVJJPSIjX2M3OWMzZWM4LTFjMjYtNDc1Mi05NDQzLTFmNzZlYjdkNWRkNiI+PFRyYW5zZm9ybXM+PFRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PFRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvVHJhbnNmb3Jtcz48RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2Ii8+PERpZ2VzdFZhbHVlPmxrV25SSUlBRm1IVmVXSVpWWGJhZGoxTzRhN05nNDRwL2NIQkZNOHFhYk09PC9EaWdlc3RWYWx1ZT48L1JlZmVyZW5jZT48L1NpZ25lZEluZm8+PFNpZ25hdHVyZVZhbHVlPmRhZ2VaWTV3aWdWVUpSeVg0bUZDZ0dMOVBhajRuVXpsTmFoQ2d4SkcybVU2M0RhTHpldm1qeWRITHVMWnhGR3MrNmxDRDhpb0xjNUpMckp3OFBlKzB3d1hZV0huTFAvWU53eVFSNWI2bVpUZWpOOUQvcFpORGNSdVRiQmZNeEdsTjhWVU5oaWg3OHRVL24xQmxiZE5oSGpBTmVTbGdPVUNlWWlIZWVzekRHaERYMi9RZ1p6OEJDL3FHa2ZBNUlIbHlSVHJBZkRoTmhGNFRpQ1F5N1haaVRqMXZ4WlE2ZDBBcTVGaWhFc0JtVW9qYnI1WW5KSjA2WjZ2KzRCS1lVWXNkUkY5RklWdlVtRCszckZORlJBQjdBMnp6c0RxYk82UUdham5YNURKaWtaNmtvTmFreFBsM3Jqd29lRWxwTzBDSG5oWXcyMEN1U09kMnVhK2ppeHRvdz09PC9TaWduYXR1cmVWYWx1ZT48S2V5SW5mbz48WDUwOURhdGE+PFg1MDlDZXJ0aWZpY2F0ZT5NSUlEQlRDQ0FlMmdBd0lCQWdJUWV2NzZCV3FqV1p4Q2htS2tHcW9BZkRBTkJna3Foa2lHOXcwQkFRc0ZBREF0TVNzd0tRWURWUVFERXlKaFkyTnZkVzUwY3k1aFkyTmxjM05qYjI1MGNtOXNMbmRwYm1SdmQzTXVibVYwTUI0WERURTRNREl4T0RBd01EQXdNRm9YRFRJd01ESXhPVEF3TURBd01Gb3dMVEVyTUNrR0ExVUVBeE1pWVdOamIzVnVkSE11WVdOalpYTnpZMjl1ZEhKdmJDNTNhVzVrYjNkekxtNWxkRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFNZ21HaVJmTGg2RmRpOTlYSTJWQTNYS0hTdFdOUkxFeTVBdy9neEZ4Y2huaDJrUGRrL2JlakZPczJzd2N4N3lVV3F4dWpqQ05Sc0xCY1dmYUtVbFRucmtZN2k5eDlub1psTXJpamdKeS9MaytISDVIWDI0UFFDRGYrdHdqbkhIeFo5RzYvOFZMTTJlNVpCZVptK3Q3TTN2aHV1bUVIRzNVd2xvTEY2Y1VldVBkVytleG5PQjFVMWZIQklGT0c4bnM0U1NJb3E2enc1cmR0MENTSTYrbDdiMURFalZ2UEx0SkYrenlqbEoxUXA3TmdCdkF3ZGlQaVJNVTRsOElSVmJ1U1ZLb0tZSm95SjRMM2VYc2pjem9CU1RKNlZqVjJteWd6OTZEQzcwTVkzYXZjY0Zyazd0Q0VDNlpsTVJCZlkxWFBMeWxkVDd0c1IzRXV6amVjU2ExTThDQXdFQUFhTWhNQjh3SFFZRFZSME9CQllFRklrczFzcml4anBTTFhlaVI4ekVTNWNUWTZmQk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ0t0aGZLNEMzMURNdUR5UVpWUzNGNys0RXZsZDNoaml3cXUydUdESytxRlphcy9EL2VEdW54c0ZwaXdxQzAxUklNRkZOOHl2bU1qSHBoTEhpQkhXeGNCVFMrdG03QWhtQXZXTWR4TzVsekpMUytVV0F5UEY1SUNST2U4TXU5aU5KaU81SmxDbzBXcHVpOVJiQjFDODFYaGF4MWdXSEsyNDVFU0w2azdZV3Z5TVlXckdxcjFOdVFjTlMwQi9BSVQxTnNqMVdZN2VmTUpRT21uTUhrUFVUV3J5VlpsdGhpall5ZDdQMkd6NnJZNWE4MURBRnFoRE5KbDJwR0lBRTZIV3RTemVVRWgzakNzSEVrb2dsS2ZtNFZyR0pFdVhjQUxtZkNNYmRmVHZ0dTRybHNhUDJoUWFkK01HL0tKRmxlbm9USzM0RU1IZUJQRENwcU5EejhVVk5rPC9YNTA5Q2VydGlmaWNhdGU+PC9YNTA5RGF0YT48L0tleUluZm8+PC9TaWduYXR1cmU+PFN1YmplY3Q+PE5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OnBlcnNpc3RlbnQiPnhKeGRxUzhXMlVYYXdiWlpxcEdGWEtHNHVFbU81R2ppaktEMlJrTWlwQm88L05hbWVJRD48U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBJblJlc3BvbnNlVG89ImlkY2YyMjk5YWM1NTFiNDJmMWFhOWI4ODgwNGVkMzA4YzIiIE5vdE9uT3JBZnRlcj0iMjAxOC0wNC0xNFQxMDowMzo1OC40NDJaIiBSZWNpcGllbnQ9Imh0dHBzOi8vemIyLnplcm9idXp6Lm5ldDo2MDQ0My9hdXRocmVzcCIvPjwvU3ViamVjdENvbmZpcm1hdGlvbj48L1N1YmplY3Q+PENvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE4LTA0LTE0VDA5OjUzOjU4LjQ0MloiIE5vdE9uT3JBZnRlcj0iMjAxOC0wNC0xNFQxMDo1Mzo1OC40NDJaIj48QXVkaWVuY2VSZXN0cmljdGlvbj48QXVkaWVuY2U+aHR0cHM6Ly96YjIuemVyb2J1enoubmV0OjYwNDQzL2F1dGhyZXNwPC9BdWRpZW5jZT48L0F1ZGllbmNlUmVzdHJpY3Rpb24+PC9Db25kaXRpb25zPjxBdHRyaWJ1dGVTdGF0ZW1lbnQ+PEF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL2lkZW50aXR5L2NsYWltcy90ZW5hbnRpZCI+PEF0dHJpYnV0ZVZhbHVlPjY4MmZlYmU4LTAyMWItNGZkZS1hYzA5LWU2MDA4NWYwNTE4MTwvQXR0cmlidXRlVmFsdWU+PC9BdHRyaWJ1dGU+PEF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL2lkZW50aXR5L2NsYWltcy9vYmplY3RpZGVudGlmaWVyIj48QXR0cmlidXRlVmFsdWU+Y2NmYjM3ODgtODI0MS00YWZlLTg4OTctZjMxM2YzNWY5ZTM3PC9BdHRyaWJ1dGVWYWx1ZT48L0F0dHJpYnV0ZT48QXR0cmlidXRlIE5hbWU9Imh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiPjxBdHRyaWJ1dGVWYWx1ZT5maXN4dDFAYXp1cmV3aXJlLm9ubWljcm9zb2Z0LmNvbTwvQXR0cmlidXRlVmFsdWU+PC9BdHRyaWJ1dGU+PEF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL2lkZW50aXR5L2NsYWltcy9kaXNwbGF5bmFtZSI+PEF0dHJpYnV0ZVZhbHVlPmZpc3h0MTwvQXR0cmlidXRlVmFsdWU+PC9BdHRyaWJ1dGU+PEF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL2lkZW50aXR5L2NsYWltcy9pZGVudGl0eXByb3ZpZGVyIj48QXR0cmlidXRlVmFsdWU+aHR0cHM6Ly9zdHMud2luZG93cy5uZXQvNjgyZmViZTgtMDIxYi00ZmRlLWFjMDktZTYwMDg1ZjA1MTgxLzwvQXR0cmlidXRlVmFsdWU+PC9BdHRyaWJ1dGU+PEF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL2NsYWltcy9hdXRobm1ldGhvZHNyZWZlcmVuY2VzIj48QXR0cmlidXRlVmFsdWU+aHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2F1dGhlbnRpY2F0aW9ubWV0aG9kL3Bhc3N3b3JkPC9BdHRyaWJ1dGVWYWx1ZT48L0F0dHJpYnV0ZT48L0F0dHJpYnV0ZVN0YXRlbWVudD48QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE4LTA0LTE0VDA5OjU4OjU1LjYxM1oiIFNlc3Npb25JbmRleD0iX2M3OWMzZWM4LTFjMjYtNDc1Mi05NDQzLTFmNzZlYjdkNWRkNiI+PEF1dGhuQ29udGV4dD48QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L0F1dGhuQ29udGV4dENsYXNzUmVmPjwvQXV0aG5Db250ZXh0PjwvQXV0aG5TdGF0ZW1lbnQ+PC9Bc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4K"+ "_c79c3ec8-1c26-4752-9443-1f76eb7d5dd6"+ (Right ())+ ]++---------------------------------------------------------------------- -- more counter-examples counterExamples :: U.Test@@ -261,21 +321,17 @@ U.assertEqual "canoncalization broke the input" i o ] -canonicalizeCounterExample :: HasCallStack => BS.ByteString -> IO (LBS, LBS)+canonicalizeCounterExample :: HasCallStack => BS.ByteString -> IO (LBS.ByteString, LBS.ByteString) canonicalizeCounterExample base64input = do let inbs :: LBS.ByteString- inbs = either (error "badcase") cs $ Data.ByteString.Base64.decode base64input+ inbs = either (error "badcase") BS.fromStrict $ Data.ByteString.Base64.decode base64input tree :: XmlTree- tree = maybe (error "badcase") id $ HS.xmlToDoc inbs+ tree = fromMaybe (error "badcase") $ HS.xmlToDoc inbs algo :: CanonicalizationAlgorithm algo = CanonicalXMLExcl10 {canonicalWithComments = True} - outbs :: LBS.ByteString <- cs <$> canonicalize algo Nothing Nothing (NTree (XTag (mkQName "" "" "root") []) [tree])-- -- LBS.putStr ("[" <> inbs <> "]\n")- -- LBS.putStr ("[" <> outbs <> "]\n")- -- print $ inbs == outbs+ outbs :: LBS.ByteString <- BS.fromStrict <$> canonicalize algo Nothing Nothing (NTree (XTag (mkQName "" "" "root") []) [tree]) pure (inbs, outbs)