35
36
37:-module(xmlenc,
38 [ decrypt_xml/4, 39 load_certificate_from_base64_string/2 40 ]). 41:- autoload(library(base64),[base64/2]). 42:- autoload(library(crypto),
43 [crypto_data_decrypt/6,rsa_private_decrypt/4,hex_bytes/2]). 44:- autoload(library(error),[existence_error/2,domain_error/2]). 45:- autoload(library(lists),[append/3]). 46:- autoload(library(sgml),[load_structure/3]). 47:- autoload(library(ssl),[load_certificate/2]). 48:- autoload(library(uri),[uri_components/2]). 49:- autoload(library(http/http_open),[http_open/3]). 50
51:- meta_predicate
52 decrypt_xml(+, -, 3, +). 53
62
65ssl_algorithm('http://www.w3.org/2001/04/xmlenc#tripledes-cbc', 'des3', 8).
66ssl_algorithm('http://www.w3.org/2001/04/xmlenc#aes128-cbc', 'aes-128-cbc', 16).
67ssl_algorithm('http://www.w3.org/2001/04/xmlenc#aes256-cbc', 'aes-256-cbc', 32).
68ssl_algorithm('http://www.w3.org/2001/04/xmlenc#aes192-cbc', 'aes-192-cbc', 24).
69
76
77decrypt_xml([], [], _, _):- !.
78decrypt_xml([element(ns(_, 'http://www.w3.org/2001/04/xmlenc#'):'EncryptedData',
79 Attributes, EncryptedData)|Siblings],
80 [Decrypted|NewSiblings], KeyCallback, Options) :-
81 !,
82 decrypt_element(Attributes, EncryptedData, Decrypted, KeyCallback, Options),
83 decrypt_xml(Siblings, NewSiblings, KeyCallback, Options).
84
85decrypt_xml([element(Tag, Attributes, Children)|Siblings],
86 [element(Tag, Attributes, NewChildren)|NewSiblings], KeyCallback, Options) :-
87 !,
88 decrypt_xml(Children, NewChildren, KeyCallback, Options),
89 decrypt_xml(Siblings, NewSiblings, KeyCallback, Options).
90decrypt_xml([Other|Siblings], [Other|NewSiblings], KeyCallback, Options):-
91 decrypt_xml(Siblings, NewSiblings, KeyCallback, Options).
92
103
104:-meta_predicate(decrypt_element(+, +, -, 3, +)). 105
106decrypt_element(Attributes, EncryptedData, Decrypted, KeyCallback, Options):-
107 XENC = ns(_, 'http://www.w3.org/2001/04/xmlenc#'),
108 ( memberchk(element(XENC:'CipherData', _, CipherData), EncryptedData)
109 -> true
110 ; existence_error(cipher_data, EncryptedData)
111 ),
112 113 114 ( memberchk('Type'=Type, Attributes)
115 -> true
116 ; Type = 'http://www.w3.org/2001/04/xmlenc#Content'
117 ),
118
119 120 determine_encryption_algorithm(EncryptedData, Algorithm, IVSize),
121
122 123 124 determine_key(EncryptedData, Key, KeyCallback, Options),
125
126 127 128 129 ( memberchk(element(XENC:'CipherValue', _, CipherValueElement), CipherData)
130 -> base64_element(CipherValueElement, CipherValueWithIV),
131 string_codes(CipherValueWithIV, CipherValueWithIVCodes),
132 length(IVCodes, IVSize),
133 append(IVCodes, CipherCodes, CipherValueWithIVCodes),
134 string_codes(IV, IVCodes),
135 string_codes(CipherText, CipherCodes),
136 length(CipherValueWithIVCodes, _),
137 crypto_data_decrypt(CipherText, Algorithm, Key, IV, DecryptedStringWithPadding, [padding(none), encoding(octet)])
138 ; memberchk(element(XENC:'CipherReference', CipherReferenceAttributes, CipherReference), CipherData)->
139 140 141 142 memberchk('URI'=CipherURI, CipherReferenceAttributes),
143 144 ( memberchk(element('Transforms', _, Transforms), CipherReference)
145 -> true
146 ; Transforms = []
147 ),
148 uri_components(CipherURI, uri_components(Scheme, _, _, _, _)),
149 ( ( Scheme == 'http' ; Scheme == 'https')
150 151 -> with_output_to(string(RawCipherValue),
152 setup_call_cleanup(http_open(CipherURI, HTTPStream, []),
153 copy_stream_data(HTTPStream, current_output),
154 close(HTTPStream)))
155 ; domain_error(resolvable_uri, CipherURI)
156 ),
157 apply_ciphertext_transforms(RawCipherValue, Transforms, CipherValue),
158 sub_string(CipherValue, 0, IVSize, _, IV),
159 sub_string(CipherValue, IVSize, _, 0, CipherText),
160 crypto_data_decrypt(CipherText, Algorithm, Key, IV, DecryptedStringWithPadding, [padding(none), encoding(octet)])
161 ),
162 163 164 165 xmlenc_padding(DecryptedStringWithPadding, DecryptedString),
166 167 168 ( Type == 'http://www.w3.org/2001/04/xmlenc#Element'
169 -> setup_call_cleanup(open_string(DecryptedString, StringStream),
170 load_structure(StringStream, [Decrypted], [dialect(xmlns), keep_prefix(true)]),
171 close(StringStream))
172 ; Decrypted = DecryptedString
173 ).
174
175xmlenc_padding(DecryptedStringWithPadding, DecryptedString):-
176 string_length(DecryptedStringWithPadding, _),
177 string_codes(DecryptedStringWithPadding, Codes),
178 append(_, [LastCode], Codes),
179 length(Padding, LastCode),
180 append(DecryptedCodes, Padding, Codes),
181 !,
182 string_codes(DecryptedString, DecryptedCodes).
183
184apply_ciphertext_transforms(CipherValue, [], CipherValue):- !.
185apply_ciphertext_transforms(_, [_AnythingElse|_], _):-
186 187 throw(error(implementation_missing('CipherReference transforms are not implemented', _))).
188
189:- meta_predicate determine_key(+,-,3,+). 190determine_key(EncryptedData, Key, KeyCallback, Options):-
191 DS = ns(_, 'http://www.w3.org/2000/09/xmldsig#'),
192 ( memberchk(element(DS:'KeyInfo', _, KeyInfo), EncryptedData)
193 -> true
194 ; 195 196 197 existence_error(key_info, EncryptedData)
198 ),
199 resolve_key(KeyInfo, Key, KeyCallback, Options).
200
201:- meta_predicate resolve_key(+,-,3,+). 202
203resolve_key(Info, Key, KeyCallback, Options):-
204 205 XENC = 'http://www.w3.org/2001/04/xmlenc#',
206 memberchk(element(ns(_, XENC):'EncryptedKey', _KeyAttributes, EncryptedKey), Info),
207 !,
208 209 210 211 memberchk(element(ns(_, XENC):'EncryptionMethod', MethodAttributes, EncryptionMethod), EncryptedKey),
212 memberchk('Algorithm'=Algorithm, MethodAttributes),
213
214 215 determine_key(EncryptedKey, PrivateKey, KeyCallback, Options),
216
217 memberchk(element(ns(_, XENC):'CipherData', _, CipherData), EncryptedKey),
218 memberchk(element(ns(_, XENC):'CipherValue', _, CipherValueElement), CipherData),
219 base64_element(CipherValueElement, CipherValue),
220 ( Algorithm == 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
221 -> rsa_private_decrypt(PrivateKey, CipherValue, Key, [encoding(octet), padding(pkcs1_oaep)])
222 ; Algorithm == 'http://www.w3.org/2009/xmlenc11#rsa-oaep',
223 memberchk(element(ns(_, 'http://www.w3.org/2009/xmlenc11#'):'MGF', MGFAttributes, _), EncryptionMethod),
224 memberchk('Algorithm'='http://www.w3.org/2009/xmlenc11#mgf1sha1', MGFAttributes) 225 -> rsa_private_decrypt(PrivateKey, CipherValue, Key, [encoding(octet), padding(pkcs1_oaep)])
226 ; Algorithm == 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
227 -> rsa_private_decrypt(PrivateKey, CipherValue, Key, [encoding(octet), padding(pkcs1)])
228 ; domain_error(key_transport, Algorithm)
229 ).
230resolve_key(KeyInfo, _Key, _KeyCallback, _Options):-
231 232 XENC = ns(_, 'http://www.w3.org/2001/04/xmlenc#'),
233 memberchk(element(XENC:'AgreementMethod', _KeyAttributes, _AgreementMethod), KeyInfo),
234 !,
235 throw(not_implemented).
237resolve_key(KeyInfo, Key, KeyCallback, _Options):-
238 239 DS = ns(_, 'http://www.w3.org/2000/09/xmldsig#'),
240 memberchk(element(DS:'KeyName', _KeyAttributes, [KeyName]), KeyInfo),
241 !,
242 call(KeyCallback, name, KeyName, Key).
243resolve_key(KeyInfo, _Key, _KeyCallback, _Options):-
244 245 DS = ns(_, 'http://www.w3.org/2000/09/xmldsig#'),
246 memberchk(element(DS:'RetrievalMethod', _KeyAttributes, _RetrievalMethod), KeyInfo),
247 !,
248 throw(not_implemented).
249resolve_key(KeyInfo, Key, KeyCallback, _Options):-
250 251 DS = ns(_, 'http://www.w3.org/2000/09/xmldsig#'),
252 memberchk(element(DS:'KeyValue', _KeyAttributes, KeyValue), KeyInfo),
253 !,
254 ( memberchk(element(DS:'RSAKeyValue', _, RSAKeyValue), KeyInfo)
255 -> memberchk(element(DS:'Modulus', _, [ModulusBase64]), RSAKeyValue),
256 memberchk(element(DS:'Exponent', _, [ExponentBase64]), RSAKeyValue),
257 base64_to_hex(ModulusBase64, Modulus),
258 base64_to_hex(ExponentBase64, Exponent),
259 call(KeyCallback, public_key, public_key(rsa(Modulus, Exponent, -, -, -, -, -, -)), Key)
260 ; memberchk(element(DS:'DSAKeyValue', _, _DSAKeyValue), KeyInfo)
261 -> throw(error(not_implemented(dsa_key), _)) 262 ; existence_error(usable_key_value, KeyValue)
263 ).
264resolve_key(KeyInfo, Key, KeyCallback, _Options):-
265 266 DS = ns(_, 'http://www.w3.org/2000/09/xmldsig#'),
267 memberchk(element(DS:'X509Data', _, X509Data), KeyInfo),
268 memberchk(element(DS:'X509Certificate', _, [X509Certificate]), X509Data),
269 !,
270 load_certificate_from_base64_string(X509Certificate, Certificate),
271 call(KeyCallback, certificate, Certificate, Key).
272resolve_key(KeyInfo, _Key, _KeyCallback, _Options):-
273 274 DS = ns(_, 'http://www.w3.org/2000/09/xmldsig#'),
275 memberchk(element(DS:'PGPData', _KeyAttributes, _PGPData), KeyInfo),
276 !,
277 throw(not_implemented).
278resolve_key(KeyInfo, _Key, _KeyCallback, _Options):-
279 280 DS = ns(_, 'http://www.w3.org/2000/09/xmldsig#'),
281 memberchk(element(DS:'SPKIData', _KeyAttributes, _SPKIData), KeyInfo),
282 !,
283 throw(not_implemented).
284resolve_key(KeyInfo, _Key, _KeyCallback, _Options):-
285 286 DS = ns(_, 'http://www.w3.org/2000/09/xmldsig#'),
287 memberchk(element(DS:'MgmtData', _KeyAttributes, _SPKIData), KeyInfo),
288 !,
289 throw(not_implemented).
290resolve_key(Info, _, _, _):-
291 292 293 294 existence_error(usable_key, Info).
295
296
297base64_to_hex(Base64, Hex):-
298 base64(Raw, Base64),
299 atom_codes(Raw, Codes),
300 hex_bytes(Hex0, Codes),
301 string_upper(Hex0, Hex).
302
303
304determine_encryption_algorithm(EncryptedData, Algorithm, IVSize):-
305 XENC = ns(_, 'http://www.w3.org/2001/04/xmlenc#'),
306 ( memberchk(element(XENC:'EncryptionMethod', EncryptionMethodAttributes, _), EncryptedData)
307 -> 308 memberchk('Algorithm'=XMLAlgorithm, EncryptionMethodAttributes),
309 ( ssl_algorithm(XMLAlgorithm, Algorithm, IVSize)
310 -> true
311 ; domain_error(block_cipher, XMLAlgorithm)
312 )
313 314 315 316 ; existence_error(encryption_method, EncryptedData)
317 ).
318
319base64_element([CipherValueElement], CipherValue):-
320 atom_codes(CipherValueElement, Base64Codes),
321 delete_newlines(Base64Codes, TrimmedCodes),
322 string_codes(Trimmed, TrimmedCodes),
323 base64(CipherValue, Trimmed).
324
325delete_newlines([], []):- !.
326delete_newlines([13|As], B):- !, delete_newlines(As, B).
327delete_newlines([10|As], B):- !, delete_newlines(As, B).
328delete_newlines([A|As], [A|B]):- !, delete_newlines(As, B).
329
330
331
336
337load_certificate_from_base64_string(UnnormalizedData, Certificate):-
338 normalize_space(codes(Codes), UnnormalizedData),
339 340 chunk_certificate(Codes, Chunks),
341 atomics_to_string(["-----BEGIN CERTIFICATE-----"|Chunks], '\n', CompleteCertificate),
342 setup_call_cleanup(open_string(CompleteCertificate, StringStream),
343 load_certificate(StringStream, Certificate),
344 close(StringStream)).
345
346chunk_certificate(Codes, [Chunk|Chunks]):-
347 length(ChunkCodes, 64),
348 append(ChunkCodes, Rest, Codes),
349 !,
350 string_codes(Chunk, ChunkCodes),
351 chunk_certificate(Rest, Chunks).
352chunk_certificate([], ["-----END CERTIFICATE-----\n"]):- !.
353chunk_certificate(LastCodes, [LastChunk, "-----END CERTIFICATE-----\n"]):-
354 string_codes(LastChunk, LastCodes)