Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ private_key = OpenSSL::PKey::RSA.new(File.new 'private_key.pem')
certificate = OpenSSL::X509::Certificate.new(File.read 'certificate.pem')
doc.sign(private_key, certificate)
File.open('signed_doc.xml', 'w') { |file| file.puts doc.to_xml }

# Custom ID attribute
signed_document = SignedXml::Document(File.read('some_signed_doc.xml'), id_attr: "some_id")
signed_doc.is_verified?
```

Contributing
Expand Down
4 changes: 2 additions & 2 deletions lib/signed_xml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ module SignedXml
XMLDSIG_NS = "http://www.w3.org/2000/09/xmldsig#"
XML_EXC_C14N_NS = "http://www.w3.org/2001/10/xml-exc-c14n#"

def self.Document(thing)
Document.new(thing)
def self.Document(thing, id_attr: nil)
Document.new(thing, id_attr: id_attr)
end

# Logger that does nothing
Expand Down
5 changes: 3 additions & 2 deletions lib/signed_xml/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ class Document

attr_reader :doc

def initialize(thing)
def initialize(thing, id_attr: nil)
if thing.is_a? Nokogiri::XML::Document
@doc = thing
else
@doc = Nokogiri::XML(thing)
end
@id_attr = id_attr
end

def is_verifiable?
Expand Down Expand Up @@ -54,7 +55,7 @@ def signatures
def init_signatures
signatures = []
doc.xpath("//ds:Signature", ds: XMLDSIG_NS).each do |signature_node|
signatures << Signature.new(signature_node)
signatures << Signature.new(signature_node, id_attr: @id_attr)
end
signatures
end
Expand Down
8 changes: 4 additions & 4 deletions lib/signed_xml/reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Reference

attr_reader :here, :start

def initialize(here)
def initialize(here, id_attr: nil)
@here = here

uri = here['URI']
Expand All @@ -15,9 +15,9 @@ def initialize(here)
when /^#/
id = uri.split('#').last
raise ArgumentError, "XPointer expressions like #{id} are not yet supported" if id =~ /^xpointer/
# TODO: handle ID attrs with names other than 'ID'
@start = here.document.at_xpath("//*[@ID='#{id}']")
raise ArgumentError, "no match found for ID #{id}" if @start.nil?
id_attr ||= 'ID'
@start = here.document.at_xpath("//*[@#{id_attr}='#{id}']")
raise ArgumentError, "no match found for #{id_attr} #{id}" if @start.nil?
else
raise ArgumentError, "unsupported Reference URI #{uri}"
end
Expand Down
5 changes: 3 additions & 2 deletions lib/signed_xml/signature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ class Signature
attr_accessor :public_key
attr_accessor :certificate_store

def initialize(here)
def initialize(here, id_attr: nil)
@here = here
@id_attr = id_attr
end

def is_verified?
Expand Down Expand Up @@ -66,7 +67,7 @@ def init_references
references = []

here.xpath('//ds:Reference', ds: XMLDSIG_NS).each do |reference_node|
references << Reference.new(reference_node)
references << Reference.new(reference_node, id_attr: @id_attr)
end

references
Expand Down
8 changes: 8 additions & 0 deletions spec/resources/signed_saml_response_with_custom_attribute.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" CustomID="_dd2d35dad58d4ebb9e3ec8e85d3e234da2cd639962" Version="2.0" IssueInstant="2013-04-08T23:13:22Z" Destination="http://localhost:3000/saml-login" InResponseTo="83767498-2df3-4035-b0a5-8b40212b8fd7"><saml:Issuer>http://localhost/simplesaml/saml2/idp/metadata.php</saml:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#_dd2d35dad58d4ebb9e3ec8e85d3e234da2cd639962"><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></ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue></ds:SignatureValue>
<ds:KeyInfo><ds:X509Data>
<ds:X509Certificate></ds:X509Certificate>
</ds:X509Data></ds:KeyInfo></ds:Signature><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" CustomID="_6f56d840d8df3697749fc533c9efac5866d5b88dde" Version="2.0" IssueInstant="2013-04-08T23:13:22Z"><saml:Issuer>http://localhost/simplesaml/saml2/idp/metadata.php</saml:Issuer><saml:Subject><saml:NameID SPNameQualifier="http://localhost:3000/" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_3c448bd72f639960c116dc6339a4930e7a4a3e9f3c</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="2013-04-08T23:18:22Z" Recipient="http://localhost:3000/saml-login" InResponseTo="83767498-2df3-4035-b0a5-8b40212b8fd7"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2013-04-08T23:12:52Z" NotOnOrAfter="2013-04-08T23:18:22Z"><saml:AudienceRestriction><saml:Audience>http://localhost:3000/</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2013-04-08T23:12:38Z" SessionNotOnOrAfter="2013-04-09T07:13:22Z" SessionIndex="_0c21a3bb421aefe16d6278b10c7924a5d66141922b"><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="email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">[email protected]</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>
12 changes: 10 additions & 2 deletions spec/signed_xml_document_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
SignedXml::Document(File.read(File.join(resources_path, 'signed_saml_response.xml')))
end

let(:signed_doc_custom_id) do
SignedXml::Document(File.read(File.join(resources_path, 'signed_saml_response_with_custom_attribute.xml')), id_attr: 'CustomID')
end

it "knows which documents can be verified" do
unsigned_doc.is_verifiable?.should be false
signed_doc.is_verifiable?.should be true
Expand Down Expand Up @@ -57,11 +61,15 @@
signed_doc.send(:signatures).first.send(:is_signed_info_verified?).should be true
end

it "supports id attrs with names other than 'ID'" do
signed_doc_custom_id.sign(test_private_key, test_certificate).is_verified?.should be true
end

it "verifies docs with one enveloped-signature Resource element and embedded X.509 key" do
signed_doc.is_verified?.should be true
end

let(:passed_in_nokogiri_doc) do
let(:passed_in_nokogiri_doc) do
SignedXml::Document(Nokogiri::XML(File.read(File.join(resources_path, 'signed_saml_response.xml'))))
end

Expand Down Expand Up @@ -153,4 +161,4 @@
it "signs template documents" do
signed_doc_template.sign(test_private_key, test_certificate).is_verified?.should be true
end
end
end