xml_signature 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ xml_signature-*.gem
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ # uncomment this line if your project needs to run something other than `rake`:
6
+ script: bundle exec cucumber
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'rspec'
7
+ gem 'growl'
8
+ gem 'guard-bundler'
9
+ gem 'guard-cucumber'
10
+ gem 'cucumber'
11
+ end
@@ -0,0 +1,11 @@
1
+ guard 'bundler' do
2
+ watch('Gemfile')
3
+ watch(/^.+\.gemspec/)
4
+ end
5
+
6
+ guard 'cucumber' do
7
+ watch(%r{^features/.+\.feature$})
8
+ watch(%r{^features/support/.+$}) { 'features' }
9
+ watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
10
+ end
11
+
@@ -0,0 +1,54 @@
1
+ [![Build Status](https://secure.travis-ci.org/kjellm/xml_signature.png?branch=master)](http://travis-ci.org/kjellm/xml_signature)
2
+
3
+ XML Signature
4
+ =============
5
+
6
+ A (partial) Ruby implementation of the XML Signature standard.
7
+
8
+ So far it can only be used to verify signed xml documents.
9
+
10
+
11
+ Install
12
+ -------
13
+
14
+ gem install xml_signature
15
+
16
+ Usage
17
+ -----
18
+
19
+ require 'xml_signature'
20
+ foo = REXML::Document.new(...)
21
+ expected_certificate = "..."
22
+ XMLSignature.new(foo).verify(expected_certificate)
23
+
24
+ Author
25
+ ------
26
+
27
+ Kjell-Magne Øierud (kjellm AT oierud DOT net)
28
+
29
+ Bugs
30
+ ----
31
+
32
+ Report bugs to http://github.com/kjellm/xml_signature/issues
33
+
34
+ License
35
+ -------
36
+
37
+ (The MIT License)
38
+
39
+ Copyright © 2012 Kjell-Magne Øierud
40
+
41
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
42
+ associated documentation files (the ‘Software’), to deal in the Software without restriction, including
43
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
44
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to
45
+ the following conditions:
46
+
47
+ The above copyright notice and this permission notice shall be included in all copies or substantial
48
+ portions of the Software.
49
+
50
+ THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
51
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
52
+ NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
53
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
54
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,61 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rexml/document'
3
+
4
+ # With a lot of help from http://users.dcc.uchile.cl/~pcamacho/tutorial/web/xmlsec/xmlsec.html
5
+ xml = <<'EOT'
6
+ <?xml version="1.0"?>
7
+ <References>
8
+ <Book xml:id="id1">
9
+ <Author>
10
+ <FirstName>Bruce</FirstName> <LastName>Schneier</LastName>
11
+ </Author>
12
+ <Title>Applied Cryptography</Title>
13
+ </Book> <Web>
14
+ <Title>XMLSec</Title>
15
+ <Url>http://www.aleksey.com/xmlsec/</Url>
16
+ </Web>
17
+ <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
18
+ <SignedInfo>
19
+ <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
20
+ <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
21
+ <Reference URI="#id1">
22
+ <Transforms>
23
+ <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
24
+ <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
25
+ </Transforms>
26
+ <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
27
+ <DigestValue>U5zbRpFUOzL1gfjfyd73KMVb0KU=</DigestValue>
28
+ </Reference>
29
+ </SignedInfo>
30
+ <SignatureValue>
31
+ DGVFyoNcf+nepI8eBj2xr7hNFaBEvRDfCQPXPgOryt9iF8bMN1J0LgMnw+Fm3ykSVrv/p+i3PFiAhbdbdRWTtS4dyYqHY7vK4TqrOdRXLgvwAmgbDVlRtfStXzWGlddOGyj2fWzOd1slHn+MPbGGrh6L02l0/cxDshtoX2Yehd4=
32
+ </SignatureValue>
33
+ <KeyInfo>
34
+ <X509Data>
35
+ <X509Certificate>
36
+ MIIC6DCCAlGgAwIBAgICAR4wDQYJKoZIhvcNAQEFBQAwgYcxCzAJBgNVBAYTAkNMMQswCQYDVQQIEwJSTTERMA8GA1UEBxMIU2FudGlhZ28xHDAaBgNVBAoTE2xpdHRsZWNyeXB0b2dyYXBoZXIxGTAXBgNVBAMTEFBoaWxpcHBlIENhbWFjaG8xHzAdBgkqhkiG9w0BCQEWEGxvc3RpbG9zQGZyZWUuZnIwHhcNMDgwMTE5MTI1MjM3WhcNMDkwMTE4MTI1MjM3WjBuMQswCQYDVQQGEwJDTDELMAkGA1UECBMCUk0xHDAaBgNVBAoTE2xpdHRsZWNyeXB0b2dyYXBoZXIxEzARBgNVBAMTCkpvaG4gU21pdGgxHzAdBgkqhkiG9w0BCQEWEGpzbWl0aEBoZWxsby5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALwShIDVij20XFC8V3Bs8Xn6b3uRa8rnPgkMCc92LoxNc/IzCriw9gu9NGps/bwanWgZbK5va46Y27axFhHo2uNk9ZE2lj0UQegFdBGlEIOt9hlpHFSqTnmXAKraSHd2yxhVe+JqGIrtyTQluWVNPOCKXd8zubFgWqlUMXMrn8JzAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQ08GE4h2jHJZOGkDUyQE9EEPMqlDAfBgNVHSMEGDAWgBT+y1YLKOsq6cec6uU61UxVhNvUajANBgkqhkiG9w0BAQUFAAOBgQAVZMDaKVhvX2qOMlcjX7i6DESF7SDyEbjfPk+bYIDm+al45lmzixkFeYUUQcFJMG0s152AkFd/fTVMfz/j37OQYxUYwwZQlMW3dVnC+CvjtMlSrReeHThhQFQpO16i21aDitON1TFsvO8T+21YGB4kne44vry6O4JJPy8EZBsfbw==
37
+ </X509Certificate>
38
+ </X509Data>
39
+ </KeyInfo>
40
+ </Signature>
41
+ </References>
42
+ EOT
43
+
44
+ cert = 'MIIC6DCCAlGgAwIBAgICAR4wDQYJKoZIhvcNAQEFBQAwgYcxCzAJBgNVBAYTAkNMMQswCQYDVQQIEwJSTTERMA8GA1UEBxMIU2FudGlhZ28xHDAaBgNVBAoTE2xpdHRsZWNyeXB0b2dyYXBoZXIxGTAXBgNVBAMTEFBoaWxpcHBlIENhbWFjaG8xHzAdBgkqhkiG9w0BCQEWEGxvc3RpbG9zQGZyZWUuZnIwHhcNMDgwMTE5MTI1MjM3WhcNMDkwMTE4MTI1MjM3WjBuMQswCQYDVQQGEwJDTDELMAkGA1UECBMCUk0xHDAaBgNVBAoTE2xpdHRsZWNyeXB0b2dyYXBoZXIxEzARBgNVBAMTCkpvaG4gU21pdGgxHzAdBgkqhkiG9w0BCQEWEGpzbWl0aEBoZWxsby5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALwShIDVij20XFC8V3Bs8Xn6b3uRa8rnPgkMCc92LoxNc/IzCriw9gu9NGps/bwanWgZbK5va46Y27axFhHo2uNk9ZE2lj0UQegFdBGlEIOt9hlpHFSqTnmXAKraSHd2yxhVe+JqGIrtyTQluWVNPOCKXd8zubFgWqlUMXMrn8JzAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQ08GE4h2jHJZOGkDUyQE9EEPMqlDAfBgNVHSMEGDAWgBT+y1YLKOsq6cec6uU61UxVhNvUajANBgkqhkiG9w0BAQUFAAOBgQAVZMDaKVhvX2qOMlcjX7i6DESF7SDyEbjfPk+bYIDm+al45lmzixkFeYUUQcFJMG0s152AkFd/fTVMfz/j37OQYxUYwwZQlMW3dVnC+CvjtMlSrReeHThhQFQpO16i21aDitON1TFsvO8T+21YGB4kne44vry6O4JJPy8EZBsfbw=='
45
+
46
+ Given /^a signed XML document$/ do
47
+ @doc = REXML::Document.new(%q{<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_7f1bf800d7fb2c6380a81df68b07ebc0422018c1b5" Version="2.0" IssueInstant="2012-06-19T12:18:37Z" Destination="http://localhost/feide/mellon/endpoint/postResponse" InResponseTo="cda19580-9c36-012f-fc85-388d120849f6"><saml:Issuer>https://idp-test.feide.no</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status><saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_ee3a7c1ef76e7bb8a96e4480342f7171a615a92cbe" Version="2.0" IssueInstant="2012-06-19T12:18:37Z"><saml:Issuer>https://idp-test.feide.no</saml:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
48
+ <ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
49
+ <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
50
+ <ds:Reference URI="#_ee3a7c1ef76e7bb8a96e4480342f7171a615a92cbe"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>fGialLR9Z03EPTPegi8EFHVabJg=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>umIL/zWzfaid4alX5MEaET10eqMfwvaal9p0jzimj7GVicyaGB8iHGwUDOQHd3R1TO7bpqhBJIA6BPcytGu5oFtclSWMoH2y7gygkbARQxZz1WvWnEVPAsJxrn2plnvqf8qM87RjSNAE8heoCUgB97cKCXaIBlsHPsS0WcwMUmnaqVlC6jLZXNRAwuuqR/z5Ww1XKCzl2vz3D//FDX6EhtpmikBZzu0is4YtQ4aAFuJdQYOLltR33pnMrDoG9AzuoS6pgOA9CsK6sB8cak9duQX0208CdJbWFMcK5P4mbM3brRfyedUGXQcYaOTHHdtoiuZee7CJY9drndwJ8As11w==</ds:SignatureValue>
51
+ <ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIDkDCCAngCCQCoO7l98RIRDzANBgkqhkiG9w0BAQUFADCBiTELMAkGA1UEBhMCTk8xEjAQBgNVBAcTCVRyb25kaGVpbTETMBEGA1UEChMKVW5pbmV0dCBBUzEOMAwGA1UECxMFRkVJREUxGjAYBgNVBAMTEWlkcC10ZXN0LmZlaWRlLm5vMSUwIwYJKoZIhvcNAQkBFhZtb3JpYS1kcmlmdEB1bmluZXR0Lm5vMB4XDTA4MDkwNTExNTIzOFoXDTE4MDcxNTExNTIzOFowgYkxCzAJBgNVBAYTAk5PMRIwEAYDVQQHEwlUcm9uZGhlaW0xEzARBgNVBAoTClVuaW5ldHQgQVMxDjAMBgNVBAsTBUZFSURFMRowGAYDVQQDExFpZHAtdGVzdC5mZWlkZS5ubzElMCMGCSqGSIb3DQEJARYWbW9yaWEtZHJpZnRAdW5pbmV0dC5ubzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMzbnp+fdJ4nkgXS+EqnfHUqYOnbxMuJga+ZWJUoKQ/X2DAkZI1rPkJgi50K2mKk3me4JjN8+qEV3XLd326XALJnra8yf07l3gE2aDlR+3pMe1fhhSANVjEzY8x6kROJMq9bxreDQjimcjvdFX69FLgxjqtcwWoGcRyn2HZUYuuoWmvqFlX+985lOfLa/PJjaFbdy7XWtucMw6dDTrA+UWK4yjbenZaT/HHyn29kYQ4MKu4Mn0cYasrfrZrVSHG5L7fySVAaXEgaxToH/fVa40Z5ltHWOw2PiDOCsC1CcQFTmKDq1Gkhi1dMhm/CECJFlAR9ML7tG1Ort9q487kSNxUCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAhf/4EsLnq7EYrvW2gBjJobhweYjjjBng0gXx3yQ/ivHbApcWcbaBLyVIokiGTf7XFhxbeZaT9+vrS+yhKCrcjAfoaXbx/xVVlKLsSMZmOr1g8+4yq6v4ax6orPrsDsmRhutoAUL8AnsGIxbyG/FbjmzEYudnbR44vUnfLD2ffnIGjGLuJHZ0OPMFkPM2V2QPiJlyngrd1xvqBfnsWmWO5pDWlXa/WkxyOBiyIGcmXFJRAPtjJzxUo1CsE2PjdBIqt1bk5UDmuW8qxbDJo1kIKeqVonuAbihZzNXyAFEqV118S4IpCNF7QqBBmlgFE25RMDktiwFk2ymdM680WFBftw==</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature><saml:Subject><saml:NameID SPNameQualifier="urn:mace:feide.no:services:http.localhost.boklink.no.restrictedservice" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_792ba75671ac729f6878af5a478f2613c08dc646aa</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="2012-06-19T12:23:37Z" Recipient="http://localhost/feide/mellon/endpoint/postResponse" InResponseTo="cda19580-9c36-012f-fc85-388d120849f6"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2012-06-19T12:18:07Z" NotOnOrAfter="2012-06-19T12:23:37Z"><saml:AudienceRestriction><saml:Audience>urn:mace:feide.no:services:http.localhost.boklink.no.restrictedservice</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2012-06-19T12:18:36Z" SessionNotOnOrAfter="2012-06-19T20:18:37Z" SessionIndex="_6b56557e0c70873720ffd134de0a2ab22f18b377e9"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">member</saml:AttributeValue><saml:AttributeValue xsi:type="xs:string">student</saml:AttributeValue></saml:Attribute><saml:Attribute Name="eduPersonPrincipalName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">test@feide.no</saml:AttributeValue></saml:Attribute><saml:Attribute Name="cn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">FEIDE Test User (cn) øæåØÆÅ</saml:AttributeValue></saml:Attribute><saml:Attribute Name="sn" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">FEIDE Test User (sn) øæåØÆÅ</saml:AttributeValue></saml:Attribute><saml:Attribute Name="givenName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">FEIDE Test User (givenName) øæåØÆÅ</saml:AttributeValue></saml:Attribute><saml:Attribute Name="eduPersonEntitlement" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">urn:mace:feide.no:domain.no:testvalue</saml:AttributeValue><saml:AttributeValue xsi:type="xs:string">urn:mace:feide.no:GREP:testvalue</saml:AttributeValue></saml:Attribute><saml:Attribute Name="displayName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">Feide Test User (displayName) æøåÆØÅ</saml:AttributeValue></saml:Attribute><saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">support@feide.no</saml:AttributeValue></saml:Attribute><saml:Attribute Name="eduPersonOrgDN:norEduOrgNIN" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">NO968100211</saml:AttributeValue></saml:Attribute><saml:Attribute Name="eduPersonOrgDN:norEduOrgSchemaVersion" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">1.5</saml:AttributeValue></saml:Attribute><saml:Attribute Name="eduPersonOrgUnitDN:norEduOrgUnitUniqueIdentifier" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">350200</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>})
52
+ end
53
+
54
+ When /^I check it's validity$/ do #'
55
+ @sig = XMLSignature.new(@doc)
56
+ @valid = @sig.verify('MIIDkDCCAngCCQCoO7l98RIRDzANBgkqhkiG9w0BAQUFADCBiTELMAkGA1UEBhMCTk8xEjAQBgNVBAcTCVRyb25kaGVpbTETMBEGA1UEChMKVW5pbmV0dCBBUzEOMAwGA1UECxMFRkVJREUxGjAYBgNVBAMTEWlkcC10ZXN0LmZlaWRlLm5vMSUwIwYJKoZIhvcNAQkBFhZtb3JpYS1kcmlmdEB1bmluZXR0Lm5vMB4XDTA4MDkwNTExNTIzOFoXDTE4MDcxNTExNTIzOFowgYkxCzAJBgNVBAYTAk5PMRIwEAYDVQQHEwlUcm9uZGhlaW0xEzARBgNVBAoTClVuaW5ldHQgQVMxDjAMBgNVBAsTBUZFSURFMRowGAYDVQQDExFpZHAtdGVzdC5mZWlkZS5ubzElMCMGCSqGSIb3DQEJARYWbW9yaWEtZHJpZnRAdW5pbmV0dC5ubzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMzbnp+fdJ4nkgXS+EqnfHUqYOnbxMuJga+ZWJUoKQ/X2DAkZI1rPkJgi50K2mKk3me4JjN8+qEV3XLd326XALJnra8yf07l3gE2aDlR+3pMe1fhhSANVjEzY8x6kROJMq9bxreDQjimcjvdFX69FLgxjqtcwWoGcRyn2HZUYuuoWmvqFlX+985lOfLa/PJjaFbdy7XWtucMw6dDTrA+UWK4yjbenZaT/HHyn29kYQ4MKu4Mn0cYasrfrZrVSHG5L7fySVAaXEgaxToH/fVa40Z5ltHWOw2PiDOCsC1CcQFTmKDq1Gkhi1dMhm/CECJFlAR9ML7tG1Ort9q487kSNxUCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAhf/4EsLnq7EYrvW2gBjJobhweYjjjBng0gXx3yQ/ivHbApcWcbaBLyVIokiGTf7XFhxbeZaT9+vrS+yhKCrcjAfoaXbx/xVVlKLsSMZmOr1g8+4yq6v4ax6orPrsDsmRhutoAUL8AnsGIxbyG/FbjmzEYudnbR44vUnfLD2ffnIGjGLuJHZ0OPMFkPM2V2QPiJlyngrd1xvqBfnsWmWO5pDWlXa/WkxyOBiyIGcmXFJRAPtjJzxUo1CsE2PjdBIqt1bk5UDmuW8qxbDJo1kIKeqVonuAbihZzNXyAFEqV118S4IpCNF7QqBBmlgFE25RMDktiwFk2ymdM680WFBftw==')
57
+ end
58
+
59
+ Then /^it should pass$/ do
60
+ @valid.should == true
61
+ end
@@ -0,0 +1,4 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'xml_signature')
2
+
3
+ require 'rspec/expectations'
4
+ World(RSpec::Matchers)
@@ -0,0 +1,7 @@
1
+ Feature: Verifying a signed XML document
2
+
3
+ Scenario: The XML document is properly signed
4
+
5
+ Given a signed XML document
6
+ When I check it's validity
7
+ Then it should pass
@@ -0,0 +1,96 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "digest/sha1"
4
+ require "openssl"
5
+ require "xmlcanonicalizer"
6
+
7
+ class XMLSignature
8
+
9
+ # xml - A REXML document
10
+ def initialize(xml)
11
+ @xml = xml.dup
12
+ @ds_signature = xpath(@xml, '//ds:Signature')
13
+ @ds_signature.remove
14
+ end
15
+
16
+ def verify(expected_certificate)
17
+ raise "Certificate mismatch" if expected_certificate != given_certificate
18
+
19
+ REXML::XPath.each(@ds_signature, "//ds:Reference") do |ref|
20
+ raise "Digest mismatch!" if computed_digest_value(ref) != given_digest_value(ref)
21
+ end
22
+
23
+ raise "Signature mismatch" \
24
+ unless certificate.public_key.verify(signature_algorithm_class.new,
25
+ signature,
26
+ c14n(xpath(@ds_signature, "//ds:SignedInfo")))
27
+ true
28
+ end
29
+
30
+ private
31
+
32
+ def given_digest_value(ds_reference)
33
+ xpath(ds_reference, "//ds:DigestValue").text
34
+ end
35
+
36
+ def computed_digest_value(ds_reference)
37
+ signed_element_in_canonical_representation = c14n(signed_element(ds_reference))
38
+ Base64.encode64(algorithm_class(ds_reference).digest(signed_element_in_canonical_representation)).chop
39
+ end
40
+
41
+ def c14n(node)
42
+ with_comments = false
43
+ exclusive = true
44
+ XML::Util::XmlCanonicalizer.new(with_comments, exclusive).canonicalize(node)
45
+ end
46
+
47
+ def signature
48
+ Base64.decode64(xpath(@ds_signature, "//ds:SignatureValue").text.strip)
49
+ end
50
+
51
+ def certificate_text
52
+ xpath(@ds_signature, "//ds:X509Certificate").text.strip
53
+ end
54
+
55
+ alias :given_certificate :certificate_text
56
+
57
+ def certificate
58
+ OpenSSL::X509::Certificate.new(Base64.decode64(certificate_text))
59
+ end
60
+
61
+ def signed_element(ds_reference)
62
+ id = reference_uri_to_id(ds_reference.attributes['URI'])
63
+ xpath(@xml, "//[@ID='#{id}']")
64
+ end
65
+
66
+ def reference_uri_to_id(uri)
67
+ uri[1..-1] # remove the '#' prefix
68
+ end
69
+
70
+ def algorithm_class(ds_reference)
71
+ # Legal algorithm URIs: http://www.w3.org/TR/xmlsec-algorithms/#digest-method-uris
72
+ digest_method = xpath(ds_reference, "//ds:DigestMethod").attributes['Algorithm']
73
+ case digest_method
74
+ when 'http://www.w3.org/2000/09/xmldsig#sha1'
75
+ OpenSSL::Digest::SHA1
76
+ else
77
+ raise "Do not support digest method: #{digest_method}"
78
+ end
79
+ end
80
+
81
+ def signature_algorithm_class
82
+ # Legal algorithm URIs: http://www.w3.org/TR/xmlsec-algorithms/#signature-method-uris
83
+ signature_method = xpath(@ds_signature, "//ds:SignatureMethod").attributes['Algorithm']
84
+ case signature_method
85
+ when 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
86
+ OpenSSL::Digest::SHA1
87
+ else
88
+ raise "Do not support signature method: #{digest_method}"
89
+ end
90
+ end
91
+
92
+ def xpath(node, path)
93
+ REXML::XPath.first(node, path, {'ds' => 'http://www.w3.org/2000/09/xmldsig#'})
94
+ end
95
+
96
+ end
@@ -0,0 +1,3 @@
1
+ class XMLSignature
2
+ VERSION = '0.1'
3
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "xml_signature/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "xml_signature"
7
+ s.version = XMLSignature::VERSION
8
+ s.author = "Kjell-Magne Øierud"
9
+ s.email = ["kjellm@oierud.net"]
10
+ s.homepage = "https://github.com/kjellm/xml_signature"
11
+ s.license = "MIT"
12
+ s.summary = %q{A (partial) implementation of the XML Signature standard}
13
+ s.description = %q{A (partial) implementation of the XML Signature standard}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ #s.test_files = `git ls-files -- spec/*`.split("\n")
17
+ #s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.required_ruby_version = '>= 1.8.7'
21
+
22
+ s.add_runtime_dependency 'xmlcanonicalizer'
23
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xml_signature
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kjell-Magne Øierud
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: xmlcanonicalizer
16
+ requirement: &70318916634040 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70318916634040
25
+ description: A (partial) implementation of the XML Signature standard
26
+ email:
27
+ - kjellm@oierud.net
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - .gitignore
33
+ - .travis.yml
34
+ - Gemfile
35
+ - Guardfile
36
+ - README.md
37
+ - features/step_definitions/verify_steps.rb
38
+ - features/support/env.rb
39
+ - features/verify.feature
40
+ - lib/xml_signature.rb
41
+ - lib/xml_signature/version.rb
42
+ - xml_signature.gemspec
43
+ homepage: https://github.com/kjellm/xml_signature
44
+ licenses:
45
+ - MIT
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: 1.8.7
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubyforge_project:
64
+ rubygems_version: 1.8.17
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: A (partial) implementation of the XML Signature standard
68
+ test_files: []
69
+ has_rdoc: