diff --git a/SAML2/Bindings/HTTPRedirect.hs b/SAML2/Bindings/HTTPRedirect.hs
--- a/SAML2/Bindings/HTTPRedirect.hs
+++ b/SAML2/Bindings/HTTPRedirect.hs
@@ -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)
diff --git a/SAML2/Core/Namespaces.hs b/SAML2/Core/Namespaces.hs
--- a/SAML2/Core/Namespaces.hs
+++ b/SAML2/Core/Namespaces.hs
@@ -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
diff --git a/SAML2/Core/Signature.hs b/SAML2/Core/Signature.hs
--- a/SAML2/Core/Signature.hs
+++ b/SAML2/Core/Signature.hs
@@ -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
diff --git a/SAML2/Metadata/Metadata.hs b/SAML2/Metadata/Metadata.hs
--- a/SAML2/Metadata/Metadata.hs
+++ b/SAML2/Metadata/Metadata.hs
@@ -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
diff --git a/SAML2/XML.hs b/SAML2/XML.hs
--- a/SAML2/XML.hs
+++ b/SAML2/XML.hs
@@ -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
diff --git a/SAML2/XML/Canonical.hs b/SAML2/XML/Canonical.hs
--- a/SAML2/XML/Canonical.hs
+++ b/SAML2/XML/Canonical.hs
@@ -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
diff --git a/SAML2/XML/Schema/Datatypes.hs b/SAML2/XML/Schema/Datatypes.hs
--- a/SAML2/XML/Schema/Datatypes.hs
+++ b/SAML2/XML/Schema/Datatypes.hs
@@ -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)
diff --git a/SAML2/XML/Signature.hs b/SAML2/XML/Signature.hs
--- a/SAML2/XML/Signature.hs
+++ b/SAML2/XML/Signature.hs
@@ -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)
diff --git a/SAML2/XML/Signature/Types.hs b/SAML2/XML/Signature/Types.hs
--- a/SAML2/XML/Signature/Types.hs
+++ b/SAML2/XML/Signature/Types.hs
@@ -1,5 +1,4 @@
 {-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE TypeSynonymInstances #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 -- |
diff --git a/SAML2/XML/Types.hs b/SAML2/XML/Types.hs
--- a/SAML2/XML/Types.hs
+++ b/SAML2/XML/Types.hs
@@ -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
 
diff --git a/hsaml2.cabal b/hsaml2.cabal
--- a/hsaml2.cabal
+++ b/hsaml2.cabal
@@ -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
diff --git a/test/Metadata/Metadata.hs b/test/Metadata/Metadata.hs
--- a/test/Metadata/Metadata.hs
+++ b/test/Metadata/Metadata.hs
@@ -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 ()
diff --git a/test/XML.hs b/test/XML.hs
--- a/test/XML.hs
+++ b/test/XML.hs
@@ -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
diff --git a/test/XML/Canonical.hs b/test/XML/Canonical.hs
--- a/test/XML/Canonical.hs
+++ b/test/XML/Canonical.hs
@@ -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>"
   ]
diff --git a/test/XML/Signature.hs b/test/XML/Signature.hs
--- a/test/XML/Signature.hs
+++ b/test/XML/Signature.hs
@@ -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)
