main
1# frozen_string_literal: true
2
3RSpec.describe Xml::Kit::Decryption do
4 describe '#decrypt_hash' do
5 let(:secret) { FFaker::Movie.title }
6 let(:password) { FFaker::Movie.title }
7
8 context 'when decrypting AES-128-CBC data' do
9 subject { described_class.new(private_keys: [private_key]) }
10
11 let(:key_pair) { generate_key_pair(password) }
12 let(:certificate_pem) { key_pair[0] }
13 let(:private_key_pem) { key_pair[1] }
14 let(:public_key) { OpenSSL::X509::Certificate.new(certificate_pem).public_key }
15 let(:private_key) { OpenSSL::PKey::RSA.new(private_key_pem, password) }
16 let(:data) do
17 cipher = OpenSSL::Cipher.new('AES-128-CBC')
18 cipher.encrypt
19 key = cipher.random_key
20 iv = cipher.random_iv
21 encrypted = cipher.update(secret) + cipher.final
22 {
23 'EncryptedData' => {
24 'xmlns:xenc' => 'http://www.w3.org/2001/04/xmlenc#',
25 'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
26 'Type' => 'http://www.w3.org/2001/04/xmlenc#Element',
27 'EncryptionMethod' => {
28 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
29 },
30 'KeyInfo' => {
31 'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
32 'EncryptedKey' => {
33 'EncryptionMethod' => {
34 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
35 },
36 'CipherData' => {
37 'CipherValue' => Base64.encode64(public_key.public_encrypt(key))
38 }
39 }
40 },
41 'CipherData' => {
42 'CipherValue' => Base64.encode64(iv + encrypted)
43 }
44 }
45 }
46 end
47
48 specify { expect(subject.decrypt_hash(data).strip).to eql(secret) }
49 end
50
51 context 'when multiple encryption keys are present' do
52 subject { described_class.new(private_keys: [other_private_key, private_key]) }
53
54 let(:key_pair) { generate_key_pair(password) }
55 let(:other_key_pair) { generate_key_pair(password) }
56 let(:certificate_pem) { key_pair[0] }
57 let(:private_key_pem) { key_pair[1] }
58 let(:public_key) { OpenSSL::X509::Certificate.new(certificate_pem).public_key }
59 let(:private_key) { OpenSSL::PKey::RSA.new(private_key_pem, password) }
60 let(:other_private_key_pem) { other_key_pair[1] }
61 let(:other_private_key) { OpenSSL::PKey::RSA.new(other_private_key_pem, password) }
62
63 let(:data) do
64 cipher = OpenSSL::Cipher.new('AES-128-CBC')
65 cipher.encrypt
66 key = cipher.random_key
67 iv = cipher.random_iv
68 encrypted = cipher.update(secret) + cipher.final
69
70 {
71 'EncryptedData' => {
72 'xmlns:xenc' => 'http://www.w3.org/2001/04/xmlenc#',
73 'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
74 'Type' => 'http://www.w3.org/2001/04/xmlenc#Element',
75 'EncryptionMethod' => {
76 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
77 },
78 'KeyInfo' => {
79 'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
80 'EncryptedKey' => {
81 'EncryptionMethod' => {
82 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
83 },
84 'CipherData' => {
85 'CipherValue' => Base64.encode64(public_key.public_encrypt(key))
86 }
87 }
88 },
89 'CipherData' => {
90 'CipherValue' => Base64.encode64(iv + encrypted)
91 }
92 }
93 }
94 end
95
96 specify { expect(subject.decrypt_hash(data).strip).to eql(secret) }
97 end
98
99 context 'when it cannot decrypt the data' do
100 subject { described_class.new(private_keys: [new_private_key]) }
101
102 let(:key_pair) { generate_key_pair(password) }
103 let(:certificate_pem) { key_pair[0] }
104 let(:public_key) { OpenSSL::X509::Certificate.new(certificate_pem).public_key }
105 let(:new_private_key_pem) { generate_key_pair(password)[1] }
106 let(:new_private_key) { OpenSSL::PKey::RSA.new(new_private_key_pem, password) }
107 let(:data) do
108 cipher = OpenSSL::Cipher.new('AES-128-CBC')
109 cipher.encrypt
110 key = cipher.random_key
111 iv = cipher.random_iv
112 encrypted = cipher.update(secret) + cipher.final
113
114 {
115 'EncryptedData' => {
116 'xmlns:xenc' => 'http://www.w3.org/2001/04/xmlenc#',
117 'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
118 'Type' => 'http://www.w3.org/2001/04/xmlenc#Element',
119 'EncryptionMethod' => {
120 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
121 },
122 'KeyInfo' => {
123 'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
124 'EncryptedKey' => {
125 'EncryptionMethod' => {
126 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
127 },
128 'CipherData' => {
129 'CipherValue' => Base64.encode64(public_key.public_encrypt(key))
130 }
131 }
132 },
133 'CipherData' => {
134 'CipherValue' => Base64.encode64(iv + encrypted)
135 }
136 }
137 }
138 end
139
140 specify { expect { subject.decrypt_hash(data) }.to raise_error(OpenSSL::PKey::RSAError) }
141 end
142 end
143
144 describe '#decrypt_document' do
145 subject { described_class.new(private_keys: [item.encryption_key_pair.private_key]) }
146
147 let(:item) { Item.new }
148 let(:document) { Nokogiri::XML(item.to_xml) }
149 let(:encrypted_node) { document.at_xpath('/x:Item/x:Encrypted/xmlenc:EncryptedData', 'xmlenc' => 'http://www.w3.org/2001/04/xmlenc#', x: 'https://www.example.org/item#') }
150
151 specify { expect(subject.decrypt_node(encrypted_node).name).to eql('EncryptMe') }
152 specify { expect(subject.decrypt_node(nil)).to be_nil }
153
154 context 'when it does not contain an EncryptedData' do
155 let(:document) { Nokogiri::XML('<hello><world></world></hello>') }
156 let(:node) { document.at_xpath('//hello/world') }
157
158 specify { expect(subject.decrypt_node(node)).to eql(node) }
159 end
160
161 context 'when the document cannot be decrypted' do
162 subject { described_class.new(private_keys: []) }
163
164 specify { expect { subject.decrypt_node(encrypted_node) }.to raise_error(Xml::Kit::DecryptionError) }
165 end
166
167 context 'when using the ruby-saml example' do
168 subject { described_class.new(cipher_registry: cipher_registry, private_keys: private_keys) }
169
170 let(:private_keys) { [OpenSSL::PKey::RSA.new(private_key_pem)] }
171 let(:private_key_pem) { IO.read('./spec/fixtures/private.txt') }
172 let(:document) { Nokogiri::XML(raw_xml) }
173 let(:encoded) { 'PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgSUQ9InBmeDUxOWI1Y2JiLWNiNmYtOTQzNS0xNjNmLWJkMzVjZTM1YzNmMCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2FwcC5tdWRhLm5vL3Nzby9jb25zdW1lIiBJblJlc3BvbnNlVG89Il9mYzRhMzRiMC03ZWZiLTAxMmUtY2FhZS03ODJiY2IxM2JiMzgiPjxzYW1sOklzc3Vlcj5odHRwczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbDI8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZng1MTliNWNiYi1jYjZmLTk0MzUtMTYzZi1iZDM1Y2UzNWMzZjAiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPjBTRFRrNXNYWjdoMW9YUWVRMm5YY3BLZnZoTT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+WENPVmk4U2c0MllRS25oMWpNTWYvV0dVcDh5Q1dFQWV4UE5taVNWT0M2dUFBUGc5WWwySUt4Um1SeGczcHpVK0o5SzlTRUVEOEJWenJERTZ4VDlxV1JUbXZ1WExqemE0TndvRmFGWllIc3ZzN0FPR3l5UEJjT3Z2R3JoM2RGWmVTUzF5U2tVc3FBWW5Wck54emRkRVFZa2trRmNxQkNqZ3dnd0Z5Vlpvbkc4PTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDR3pDQ0FZUUNDUUNOTmNRWG9tMzJWREFOQmdrcWhraUc5dzBCQVFVRkFEQlNNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0JNQ1NVNHhGVEFUQmdOVkJBY1RERWx1WkdsaGJtRndiMnhwY3pFUk1BOEdBMVVFQ2hNSVQyNWxURzluYVc0eEREQUtCZ05WQkFzVEEwVnVaekFlRncweE5EQTBNak14T0RReE1ERmFGdzB4TlRBME1qTXhPRFF4TURGYU1GSXhDekFKQmdOVkJBWVRBbFZUTVFzd0NRWURWUVFJRXdKSlRqRVZNQk1HQTFVRUJ4TU1TVzVrYVdGdVlYQnZiR2x6TVJFd0R3WURWUVFLRXdoUGJtVk1iMmRwYmpFTU1Bb0dBMVVFQ3hNRFJXNW5NSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURvNm0rUVp2WVEveEwwRWxMZ3VwSzFRRGNZTDRmNVBja3dzTmdTOXBVdlY3ZnpUcUNIazhUaEx4VGs0Mk1RMk1jSnNPZVVKVlA3MjhLaHltakZDcXhnUDRWdXdSazlycEFsMCttaHk2TVBkeWp5QTZHMTRqckRXUzY1eXNMY2hLNHQvdndwRUR6MFNRbEVvRzFrTXpsbFNtN3paUzNYcmVnQTdEak5hVVlRcXdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBTE0ydkdDaVEvdm0rYTZ2NDArVlgyemRxSEEyUS8xdkYxaWJReko1NE1KQ09WV3ZzK3ZRWGZaRmhkbTBPUE0ySXJEVTdvcXZLUHFQNnhPQWVKSzZIMHlQN000WUwzZmF0U3ZJWW1tZnlYQzlrdDNTdnovTnlySHpQaFVuSjB5ZS9zVVNYeG56UXh3Y20vOVB3QXFyUWFBM1FwUWtINTd5YkYvT29yeVBlKzJoPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgVmVyc2lvbj0iMi4wIiBJRD0icGZ4OTUxNmIwZjMtNDUzNi0xMGY2LWM2ZmEtOWRkNTIzZTE0OThjIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAub25lbG9naW4uY29tL3NhbWwyPC9zYW1sOklzc3Vlcj48c2FtbDpTdWJqZWN0PjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMzAtMDYtMDRUMDI6Mjc6MDJaIiBSZWNpcGllbnQ9InJlY2lwaWVudCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjxzYW1sOkVuY3J5cHRlZElEPjx4ZW5jOkVuY3J5cHRlZERhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIiB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMTI4LWNiYyIvPjxkc2lnOktleUluZm8geG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PHhlbmM6RW5jcnlwdGVkS2V5Pjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNyc2EtMV81Ii8+PHhlbmM6Q2lwaGVyRGF0YT48eGVuYzpDaXBoZXJWYWx1ZT5ZUkdFZGF2dWpSNlYwNUZsWERHbmxCK1VUWTFjak9DallkYlhKN2JBZHFURWxDTyt1eHl0aytnMWVTTGVuczhJcjlZaVBNNUorUWU5cXo0TkdORXdyNjV6aDM5L0ZJVXNMQ3BhaXQ3QjZXM2lFcmR4aVUrSUN1cUw3TCtNSmlGVHZiVG90NVdleWZvVkFnSE94Z1BodDRONlZSL3BhYzRDdFZEQ0ZBbDlEMjA9PC94ZW5jOkNpcGhlclZhbHVlPjwveGVuYzpDaXBoZXJEYXRhPjwveGVuYzpFbmNyeXB0ZWRLZXk+PC9kc2lnOktleUluZm8+DQogICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+dFdQZEV1dXZmSjh3WVBhOFVUQTRvR2htRENQTjFhQzVkUUFEN0g5SkhWQm5VS3Y0UkljNEQ3SnVJem12bXlyalZGWmRGNW15K3cvUGd3dWlOVGdpOUxid01iSW5adW1HbDhlSndFblBaVXBPQ0w1dDNXbEdKbU85OVVNejZQUVNLeGlGSU1DYzcrQXlRQmpjdTEzaUxWeU5TbFQyWDMxRXBOaW5jQ3FzSldvPTwveGVuYzpDaXBoZXJWYWx1ZT4NCiAgIDwveGVuYzpDaXBoZXJEYXRhPg0KPC94ZW5jOkVuY3J5cHRlZERhdGE+PC9zYW1sOkVuY3J5cHRlZElEPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDExLTA2LTA0VDAyOjE3OjAyWiIgTm90T25PckFmdGVyPSIyMDMwLTA2LTA0VDAyOjI3OjAyWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovL3NvbWVvbmUuZXhhbXBsZS5jb20vYXVkaWVuY2U8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTA2LTA0VDAyOjIyOjAyWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAzMC0wNi0wNVQwMjoyMjowMloiIFNlc3Npb25JbmRleD0iXzE2ZjU3MGZiYzAzMTUwMDdhMDM1NWRmZWE2YjNjNDZjIj48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg==' }
174 let(:raw_xml) { Base64.decode64(encoded) }
175 let(:encrypted_node) do
176 document.at_xpath(
177 '/samlp:Response/saml:Assertion/saml:Subject/saml:EncryptedID/xmlenc:EncryptedData',
178 'xmlenc' => 'http://www.w3.org/2001/04/xmlenc#',
179 'saml' => 'urn:oasis:names:tc:SAML:2.0:assertion',
180 'samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol'
181 )
182 end
183 let(:cipher_registry) do
184 Xml::Kit::Crypto.cipher_registry do |algorithm, key|
185 if algorithm == 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
186 Xml::Kit::Crypto::SymmetricCipher.new(algorithm, key, 0)
187 else
188 Xml::Kit::Crypto.cipher_for(algorithm, key)
189 end
190 end
191 end
192
193 specify do
194 expect(subject.decrypt_node(encrypted_node).to_s).to eql('<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">test@onelogin.com</saml:NameID>')
195 end
196 end
197 end
198end