Skip to content

Commit

Permalink
Parse assertion signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
fumieval committed Aug 5, 2024
1 parent db3829c commit c3af161
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 60 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- Replaced `x509Certificate` with `x509Certificates` in `IDPSSODescriptor` so that it may have more than one certificate ([#65](https://github.com/mbg/wai-saml2/pull/65) by [@fumieval](https://github.com/fumieval))
- Added `attributeValues` to `AssertionAttribute` in order to handle multiple attribute values with the same name ([#67](https://github.com/mbg/wai-saml2/pull/67) by [@fumieval](https://github.com/fumieval))
- Support signed assertions, not just signed responses by ([#45](https://github.com/mbg/wai-saml2/pull/45) by [@fumieval](https://github.com/mbg/wai-saml2))

## 0.6

Expand Down
9 changes: 7 additions & 2 deletions src/Network/Wai/SAML2/Assertion.hs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import Data.Time
import Text.XML.Cursor

import Network.Wai.SAML2.NameIDFormat
import Network.Wai.SAML2.Signature
import Network.Wai.SAML2.XML

--------------------------------------------------------------------------------
Expand Down Expand Up @@ -278,7 +279,9 @@ data Assertion = Assertion {
-- | The authentication statement included in the assertion.
assertionAuthnStatement :: !AuthnStatement,
-- | The assertion's attribute statement.
assertionAttributeStatement :: !AttributeStatement
assertionAttributeStatement :: !AttributeStatement,
-- | The assertion's signature.
assertionSignature :: !(Maybe Signature)
} deriving (Eq, Show)

-- Reference [Assertion]
Expand Down Expand Up @@ -306,7 +309,9 @@ instance FromXML Assertion where
assertionAuthnStatement = authnStatement,
assertionAttributeStatement =
cursor $/ element (saml2Name "AttributeStatement")
>=> parseAttributeStatement
>=> parseAttributeStatement,
assertionSignature = listToMaybe $
cursor $/ element (dsName "Signature") >=> parseXML
}

--------------------------------------------------------------------------------
Expand Down
8 changes: 3 additions & 5 deletions src/Network/Wai/SAML2/Response.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ data Response = Response {
-- | The status of the response.
responseStatusCode :: !StatusCode,
-- | The response signature.
responseSignature :: !Signature,
responseSignature :: !(Maybe Signature),
-- | The unencrypted assertion.
--
-- @since 0.4
Expand Down Expand Up @@ -88,9 +88,6 @@ instance FromXML Response where
$/ element (saml2Name "EncryptedAssertion")
) >>= parseXML

signature <- oneOrFail "Signature is required" (
cursor $/ element (dsName "Signature") ) >>= parseXML

pure Response{
responseDestination = T.concat $ attribute "Destination" cursor,
responseId = T.concat $ attribute "ID" cursor,
Expand All @@ -100,7 +97,8 @@ instance FromXML Response where
responseIssuer = T.concat $
cursor $/ element (saml2Name "Issuer") &/ content,
responseStatusCode = statusCode,
responseSignature = signature,
responseSignature = listToMaybe $
(cursor $/ element (dsName "Signature")) >>= parseXML,
responseAssertion = assertion,
responseEncryptedAssertion = encAssertion
}
Expand Down
12 changes: 7 additions & 5 deletions src/Network/Wai/SAML2/Validation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ validateSAMLResponse cfg responseXmlDoc samlResponse now = do
Left err -> throwError $ CanonicalisationFailure err
Right result -> pure result

signature <- case responseSignature samlResponse of
Just sig -> pure sig
Nothing -> throwError $ InvalidResponse $ userError "Response Signature is required"

-- 2. At this point we should dereference all elements identified by
-- Reference elements inside the SignedInfo element. However, we do
-- not currently do that and instead just assume that there is only
Expand All @@ -149,8 +153,7 @@ validateSAMLResponse cfg responseXmlDoc samlResponse now = do
let documentId = responseId samlResponse
let referenceId = referenceURI
$ signedInfoReference
$ signatureInfo
$ responseSignature samlResponse
$ signatureInfo signature

if documentId /= referenceId
then throwError $ UnexpectedReference referenceId
Expand Down Expand Up @@ -180,8 +183,7 @@ validateSAMLResponse cfg responseXmlDoc samlResponse now = do
$ BS.decodeLenient
$ referenceDigestValue
$ signedInfoReference
$ signatureInfo
$ responseSignature samlResponse
$ signatureInfo signature

if Just documentHash /= referenceHash
then throwError InvalidDigest
Expand All @@ -191,7 +193,7 @@ validateSAMLResponse cfg responseXmlDoc samlResponse now = do
-- We need to check that the SignedInfo element has not been tampered
-- with, which we do by checking the signature contained in the response;
-- first: extract the signature data from the response
let sig = BS.decodeLenient $ signatureValue $ responseSignature samlResponse
let sig = BS.decodeLenient $ signatureValue signature

-- using the IdP's public key and the canonicalised SignedInfo element,
-- check that the signature is correct
Expand Down
34 changes: 18 additions & 16 deletions tests/data/google.xml.expected
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,23 @@ Response
MkStatusCode
{ statusCodeValue = Success , statusCodeSubordinate = Nothing }
, responseSignature =
Signature
{ signatureInfo =
SignedInfo
{ signedInfoCanonicalisationMethod = C14N_EXC_1_0
, signedInfoSignatureMethod = RSA_SHA256
, signedInfoReference =
Reference
{ referenceURI = "_b9917f3478ff3f776cb351547379d0bc"
, referenceDigestMethod = DigestSHA256
, referenceDigestValue =
"z3YXe+aIiyAYbSGqxRiYPwTO38JNZad2WBgqinjiX8g="
}
}
, signatureValue =
"S6IAb+4yYyNVIfnqM7ZIrFSKbzi0RgVqk6qNL0ehzFZhcaArqN/8S0oCDrrc+0B2ygCkpbDKt03/\nvNIxq862VYa1Y39n8w0/eIKc+m3beNS6e+n316o3LCtSTPvgC2kE96Jh44MOUX9z/KIYxXiPGgdi\nnnlxK6L1dZGrMKKBxEFiBJMhFI42gWADyrUz311PDImM+rVhINJT9NyD1i1G++PILLer/BlGwg5n\nwNQHmjt4pV3m+8zMMIrfLjZT/YX8s7+1Xn1Y3bMzo6PnsbvS7OX1YGuN4cPn9f4MLu5Vcn9lWo/r\n+zuZay/pS0mEqTmeOOMGk4BOseiU/hAra5NDgQ=="
}
Just
Signature
{ signatureInfo =
SignedInfo
{ signedInfoCanonicalisationMethod = C14N_EXC_1_0
, signedInfoSignatureMethod = RSA_SHA256
, signedInfoReference =
Reference
{ referenceURI = "_b9917f3478ff3f776cb351547379d0bc"
, referenceDigestMethod = DigestSHA256
, referenceDigestValue =
"z3YXe+aIiyAYbSGqxRiYPwTO38JNZad2WBgqinjiX8g="
}
}
, signatureValue =
"S6IAb+4yYyNVIfnqM7ZIrFSKbzi0RgVqk6qNL0ehzFZhcaArqN/8S0oCDrrc+0B2ygCkpbDKt03/\nvNIxq862VYa1Y39n8w0/eIKc+m3beNS6e+n316o3LCtSTPvgC2kE96Jh44MOUX9z/KIYxXiPGgdi\nnnlxK6L1dZGrMKKBxEFiBJMhFI42gWADyrUz311PDImM+rVhINJT9NyD1i1G++PILLer/BlGwg5n\nwNQHmjt4pV3m+8zMMIrfLjZT/YX8s7+1Xn1Y3bMzo6PnsbvS7OX1YGuN4cPn9f4MLu5Vcn9lWo/r\n+zuZay/pS0mEqTmeOOMGk4BOseiU/hAra5NDgQ=="
}
, responseAssertion =
Just
Assertion
Expand Down Expand Up @@ -72,6 +73,7 @@ Response
, authnStatementLocality = ""
}
, assertionAttributeStatement = []
, assertionSignature = Nothing
}
, responseEncryptedAssertion = Nothing
}
33 changes: 17 additions & 16 deletions tests/data/keycloak.xml.expected
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,23 @@ Response
MkStatusCode
{ statusCodeValue = Success , statusCodeSubordinate = Nothing }
, responseSignature =
Signature
{ signatureInfo =
SignedInfo
{ signedInfoCanonicalisationMethod = C14N_EXC_1_0
, signedInfoSignatureMethod = RSA_SHA256
, signedInfoReference =
Reference
{ referenceURI = "ID_5b1d000b-3a5e-4dfe-aa4e-b7bf1e3efbfd"
, referenceDigestMethod = DigestSHA256
, referenceDigestValue =
"/U47P3hsUf+tUyyglYF8M1u6lbVnHimCthtxusju4mo="
}
}
, signatureValue =
"b9vgIBQ1yNvUYgNmfAyuQJXOJ68PMfRvNAZEa93tnzZXHPEsf7/F49xI6/mlYI/T9pDxYnFcfl7kPMxgz4ssvMjwUEgAR3G3ZrNv4gPMUPmbZnXe0KG8yU9AskK90ya/T11kQfI21cSlA8FrLPTGP2X97yErR10mIDvEJ/m5dWra95cGLx/ntjaSIqNJpVgpHhRxieS4Lw+zeWe/nVuznXQnb8VRhCq18ikL/u23+YhYT3ws3iXQssJ2BosX9JJt0O+X31sIHJIWHsxbI69NLJ782bVDDkI1PNF8MKoa8gSEiLsNSmp3SyXtMPzaRIBguksl9xbnmYmsJDQg6kFVlQ=="
}
Just
Signature
{ signatureInfo =
SignedInfo
{ signedInfoCanonicalisationMethod = C14N_EXC_1_0
, signedInfoSignatureMethod = RSA_SHA256
, signedInfoReference =
Reference
{ referenceURI = "ID_5b1d000b-3a5e-4dfe-aa4e-b7bf1e3efbfd"
, referenceDigestMethod = DigestSHA256
, referenceDigestValue =
"/U47P3hsUf+tUyyglYF8M1u6lbVnHimCthtxusju4mo="
}
}
, signatureValue =
"b9vgIBQ1yNvUYgNmfAyuQJXOJ68PMfRvNAZEa93tnzZXHPEsf7/F49xI6/mlYI/T9pDxYnFcfl7kPMxgz4ssvMjwUEgAR3G3ZrNv4gPMUPmbZnXe0KG8yU9AskK90ya/T11kQfI21cSlA8FrLPTGP2X97yErR10mIDvEJ/m5dWra95cGLx/ntjaSIqNJpVgpHhRxieS4Lw+zeWe/nVuznXQnb8VRhCq18ikL/u23+YhYT3ws3iXQssJ2BosX9JJt0O+X31sIHJIWHsxbI69NLJ782bVDDkI1PNF8MKoa8gSEiLsNSmp3SyXtMPzaRIBguksl9xbnmYmsJDQg6kFVlQ=="
}
, responseAssertion = Nothing
, responseEncryptedAssertion =
Just
Expand Down
33 changes: 17 additions & 16 deletions tests/data/okta.xml.expected
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,23 @@ Response
MkStatusCode
{ statusCodeValue = Success , statusCodeSubordinate = Nothing }
, responseSignature =
Signature
{ signatureInfo =
SignedInfo
{ signedInfoCanonicalisationMethod = C14N_EXC_1_0
, signedInfoSignatureMethod = RSA_SHA256
, signedInfoReference =
Reference
{ referenceURI = "id36905492230634463108368061"
, referenceDigestMethod = DigestSHA256
, referenceDigestValue =
"i300U58mYoywmpgMkq7AHOplar4B6ZwW++Ri3PUvGlc="
}
}
, signatureValue =
"eKymDiZIxFd+oJCIhdHQpI+s2nQYFBVH7TBoZaqXizJdNnNPpNlKM5wds33zP6a72GWOL/2n8WRtW+xSnPTmw5PumEuUhWbO2EizfU/SdBL4HKxxJt0nntkDRhdIoHBi+9Gy80So4NGr82B2clnhpj74GW0Ko8A0bt2sHzdQ9NaJ5ru4Qvx0Fk/UCBAGAYobryjAc9Tb5qxxgHGMmPUHG5CprRYTINKZlGF1Jl88VSdTbOYmGjJ4b6GY8pLR2qLt4LErVPUSI4vEUuPFU2f/9/i8D39hzWtJOCzuaUZ5tdKU3Fyjsgoa7ooDfZVzb4howWhQ9zPUgwjZEd9tbW2EwA=="
}
Just
Signature
{ signatureInfo =
SignedInfo
{ signedInfoCanonicalisationMethod = C14N_EXC_1_0
, signedInfoSignatureMethod = RSA_SHA256
, signedInfoReference =
Reference
{ referenceURI = "id36905492230634463108368061"
, referenceDigestMethod = DigestSHA256
, referenceDigestValue =
"i300U58mYoywmpgMkq7AHOplar4B6ZwW++Ri3PUvGlc="
}
}
, signatureValue =
"eKymDiZIxFd+oJCIhdHQpI+s2nQYFBVH7TBoZaqXizJdNnNPpNlKM5wds33zP6a72GWOL/2n8WRtW+xSnPTmw5PumEuUhWbO2EizfU/SdBL4HKxxJt0nntkDRhdIoHBi+9Gy80So4NGr82B2clnhpj74GW0Ko8A0bt2sHzdQ9NaJ5ru4Qvx0Fk/UCBAGAYobryjAc9Tb5qxxgHGMmPUHG5CprRYTINKZlGF1Jl88VSdTbOYmGjJ4b6GY8pLR2qLt4LErVPUSI4vEUuPFU2f/9/i8D39hzWtJOCzuaUZ5tdKU3Fyjsgoa7ooDfZVzb4howWhQ9zPUgwjZEd9tbW2EwA=="
}
, responseAssertion = Nothing
, responseEncryptedAssertion =
Just
Expand Down

0 comments on commit c3af161

Please sign in to comment.