1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
23 #include <cppuhelper/supportsservice.hxx>
24 #include <gpg/xmlsignature_gpgimpl.hxx>
26 #if defined _MSC_VER && defined __clang__
27 #pragma clang diagnostic push
28 #pragma clang diagnostic ignored "-Wundef"
31 #if defined _MSC_VER && defined __clang__
32 #pragma clang diagnostic pop
36 #include <signingresult.h>
37 #include <importresult.h>
39 #include <xmlelementwrapper_xmlsecimpl.hxx>
40 #include <xmlsec/xmlstreamio.hxx>
41 #include <xmlsec/errorcallback.hxx>
42 #include <xmlsec/xmltree.h>
43 #include <xmlsec/base64.h>
44 #include <xmlsec/xmldsig.h>
45 #include <xmlsec/xmlsec.h>
47 #include "SecurityEnvironment.hxx"
49 using namespace css::uno
;
50 using namespace css::lang
;
51 using namespace css::xml::wrapper
;
52 using namespace css::xml::crypto
;
54 XMLSignature_GpgImpl::XMLSignature_GpgImpl() {
57 XMLSignature_GpgImpl::~XMLSignature_GpgImpl() {
61 Reference
< XXMLSignatureTemplate
>
62 SAL_CALL
XMLSignature_GpgImpl::generate(
63 const Reference
< XXMLSignatureTemplate
>& aTemplate
,
64 const Reference
< XSecurityEnvironment
>& aEnvironment
67 xmlSecDSigCtxPtr pDsigCtx
= nullptr ;
68 xmlNodePtr pNode
= nullptr ;
71 throw RuntimeException() ;
73 if( !aEnvironment
.is() )
74 throw RuntimeException() ;
77 Reference
< XXMLElementWrapper
> xElement
= aTemplate
->getTemplate() ;
78 if( !xElement
.is() ) {
79 throw RuntimeException() ;
82 XMLElementWrapper_XmlSecImpl
* pElement
=
83 dynamic_cast<XMLElementWrapper_XmlSecImpl
*>(xElement
.get());
84 if( pElement
== nullptr ) {
85 throw RuntimeException() ;
88 pNode
= pElement
->getNativeElement() ;
90 //Get the stream/URI binding
91 Reference
< XUriBinding
> xUriBinding
= aTemplate
->getBinding() ;
92 if( xUriBinding
.is() ) {
93 //Register the stream input callbacks into libxml2
94 if( xmlRegisterStreamInputCallbacks( xUriBinding
) < 0 )
95 throw RuntimeException() ;
99 SecurityEnvironmentGpg
* pSecEnv
=
100 dynamic_cast<SecurityEnvironmentGpg
*>(aEnvironment
.get());
101 if( pSecEnv
== nullptr )
102 throw RuntimeException() ;
106 //Create Signature context
107 pDsigCtx
= xmlSecDSigCtxCreate( nullptr ) ;
108 if( pDsigCtx
== nullptr )
110 clearErrorRecorder();
114 // set intended operation to sign - several asserts inside libxmlsec
115 // wanting that for digest / transforms
116 pDsigCtx
->operation
= xmlSecTransformOperationSign
;
118 // we default to SHA512 for all digests - nss crypto does not have it...
119 //pDsigCtx->defDigestMethodId = xmlSecTransformSha512Id;
121 // Calculate digest for all references
122 xmlNodePtr cur
= xmlSecGetNextElementNode(pNode
->children
);
124 cur
= xmlSecGetNextElementNode(cur
->children
);
125 while( cur
!= nullptr )
127 // some of those children I suppose should be reference elements
128 if( xmlSecCheckNodeName(cur
, xmlSecNodeReference
, xmlSecDSigNs
) )
130 xmlSecDSigReferenceCtxPtr pDsigRefCtx
=
131 xmlSecDSigReferenceCtxCreate(pDsigCtx
,
132 xmlSecDSigReferenceOriginSignedInfo
);
133 if(pDsigRefCtx
== nullptr)
134 throw RuntimeException();
136 // add this one to the list
137 if( xmlSecPtrListAdd(&(pDsigCtx
->signedInfoReferences
),
140 // TODO resource handling
141 xmlSecDSigReferenceCtxDestroy(pDsigRefCtx
);
142 throw RuntimeException();
145 if( xmlSecDSigReferenceCtxProcessNode(pDsigRefCtx
, cur
) < 0 )
146 throw RuntimeException();
148 // final check - all good?
149 if(pDsigRefCtx
->status
!= xmlSecDSigStatusSucceeded
)
151 pDsigCtx
->status
= xmlSecDSigStatusInvalid
;
152 return aTemplate
; // TODO - harder error?
156 cur
= xmlSecGetNextElementNode(cur
->next
);
159 // get me a digestible buffer from the signature template!
160 // -------------------------------------------------------
162 // run the transformations over SignedInfo element (first child of
164 xmlSecNodeSetPtr nodeset
= nullptr;
165 cur
= xmlSecGetNextElementNode(pNode
->children
);
166 // TODO assert that...
167 nodeset
= xmlSecNodeSetGetChildren(pNode
->doc
, cur
, 1, 0);
168 if(nodeset
== nullptr)
169 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
171 if( xmlSecTransformCtxXmlExecute(&(pDsigCtx
->transformCtx
), nodeset
) < 0 )
172 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
174 // now extract the keyid from PGPData
175 // walk xml tree to PGPData node - go to children, first is
176 // SignedInfo, 2nd is signaturevalue, 3rd is KeyInfo
177 // 1st child is PGPData, 1st grandchild is PGPKeyID
178 cur
= xmlSecGetNextElementNode(pNode
->children
);
179 // TODO error handling
180 cur
= xmlSecGetNextElementNode(cur
->next
);
181 cur
= xmlSecGetNextElementNode(cur
->next
);
182 cur
= xmlSecGetNextElementNode(cur
->children
);
183 // check that this is now PGPData
184 if(!xmlSecCheckNodeName(cur
, xmlSecNodePGPData
, xmlSecDSigNs
))
185 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
186 // check that this is now PGPKeyID
187 cur
= xmlSecGetNextElementNode(cur
->children
);
188 static const xmlChar xmlSecNodePGPKeyID
[] = "PGPKeyID";
189 if(!xmlSecCheckNodeName(cur
, xmlSecNodePGPKeyID
, xmlSecDSigNs
))
190 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
192 GpgME::Context
& rCtx
=pSecEnv
->getGpgContext();
193 rCtx
.setKeyListMode(GPGME_KEYLIST_MODE_LOCAL
);
195 xmlChar
* pKey
=xmlNodeGetContent(cur
);
197 int nRet
= xmlSecBase64Decode_ex(pKey
, reinterpret_cast<xmlSecByte
*>(pKey
), xmlStrlen(pKey
), &nWritten
);
199 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
201 rCtx
.clearSigningKeys(); // tdf#108828 Clear keys from previous unsuccessful sessions
202 if( rCtx
.addSigningKey(
204 reinterpret_cast<char*>(pKey
), err
, true)) )
205 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
209 // good, ctx is setup now, let's sign the lot
211 reinterpret_cast<char*>(xmlSecBufferGetData(pDsigCtx
->transformCtx
.result
)),
212 xmlSecBufferGetSize(pDsigCtx
->transformCtx
.result
), false);
213 GpgME::Data data_out
;
215 SAL_INFO("xmlsecurity.xmlsec.gpg", "Generating signature for: " << xmlSecBufferGetData(pDsigCtx
->transformCtx
.result
));
217 // we base64-encode anyway
218 rCtx
.setArmor(false);
219 GpgME::SigningResult sign_res
=rCtx
.sign(data_in
, data_out
,
221 off_t result
= data_out
.seek(0,SEEK_SET
);
224 int len
=0, curr
=0; char buf
;
225 while( (curr
=data_out
.read(&buf
, 1)) )
228 if(sign_res
.error() || !len
)
229 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
231 // write signed data to xml
232 xmlChar
* signature
= static_cast<xmlChar
*>(xmlMalloc(len
+ 1));
233 if(signature
== nullptr)
234 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
235 result
= data_out
.seek(0,SEEK_SET
);
237 if( data_out
.read(signature
, len
) != len
)
238 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
240 // conversion to base64
241 xmlChar
* signatureEncoded
=nullptr;
242 if( !(signatureEncoded
=xmlSecBase64Encode(reinterpret_cast<xmlSecByte
*>(signature
), len
, 79)) )
243 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
246 // walk xml tree to sign value node - go to children, first is
247 // SignedInfo, 2nd is signaturevalue
248 cur
= xmlSecGetNextElementNode(pNode
->children
);
249 cur
= xmlSecGetNextElementNode(cur
->next
);
251 // TODO some assert would be good...
252 xmlNodeSetContentLen(cur
, signatureEncoded
, xmlStrlen(signatureEncoded
));
253 xmlFree(signatureEncoded
);
255 aTemplate
->setStatus(SecurityOperationStatus_OPERATION_SUCCEEDED
);
258 xmlSecDSigCtxDestroy( pDsigCtx
) ;
260 //Unregistered the stream/URI binding
261 if( xUriBinding
.is() )
262 xmlUnregisterStreamInputCallbacks() ;
264 clearErrorRecorder();
269 Reference
< XXMLSignatureTemplate
>
270 SAL_CALL
XMLSignature_GpgImpl::validate(
271 const Reference
< XXMLSignatureTemplate
>& aTemplate
,
272 const Reference
< XXMLSecurityContext
>& aSecurityCtx
274 xmlSecDSigCtxPtr pDsigCtx
= nullptr ;
275 xmlNodePtr pNode
= nullptr ;
277 if( !aTemplate
.is() )
278 throw RuntimeException() ;
280 if( !aSecurityCtx
.is() )
281 throw RuntimeException() ;
284 Reference
< XXMLElementWrapper
> xElement
= aTemplate
->getTemplate() ;
286 throw RuntimeException() ;
288 XMLElementWrapper_XmlSecImpl
* pElement
=
289 dynamic_cast<XMLElementWrapper_XmlSecImpl
*>(xElement
.get());
290 if( pElement
== nullptr )
291 throw RuntimeException() ;
293 pNode
= pElement
->getNativeElement() ;
295 //Get the stream/URI binding
296 Reference
< XUriBinding
> xUriBinding
= aTemplate
->getBinding() ;
297 if( xUriBinding
.is() ) {
298 //Register the stream input callbacks into libxml2
299 if( xmlRegisterStreamInputCallbacks( xUriBinding
) < 0 )
300 throw RuntimeException() ;
305 sal_Int32 nSecurityEnvironment
= aSecurityCtx
->getSecurityEnvironmentNumber();
308 for (i
=0; i
<nSecurityEnvironment
; ++i
)
310 Reference
< XSecurityEnvironment
> aEnvironment
= aSecurityCtx
->getSecurityEnvironmentByIndex(i
);
312 SecurityEnvironmentGpg
* pSecEnv
=
313 dynamic_cast<SecurityEnvironmentGpg
*>(aEnvironment
.get());
314 if( pSecEnv
== nullptr )
315 throw RuntimeException() ;
317 // TODO figure out key from pSecEnv!
318 // unclear how/where that is transported in nss impl...
320 //Create Signature context
321 pDsigCtx
= xmlSecDSigCtxCreate( nullptr ) ;
322 if( pDsigCtx
== nullptr )
324 clearErrorRecorder();
328 // set intended operation to verify - several asserts inside libxmlsec
329 // wanting that for digest / transforms
330 pDsigCtx
->operation
= xmlSecTransformOperationVerify
;
332 // reset status - to be set later
333 pDsigCtx
->status
= xmlSecDSigStatusUnknown
;
335 // get me a digestible buffer from the SignatureInfo node!
336 // -------------------------------------------------------
338 // run the transformations - first child node is required to
340 xmlSecNodeSetPtr nodeset
= nullptr;
341 xmlNodePtr cur
= xmlSecGetNextElementNode(pNode
->children
);
342 // TODO assert that...
343 nodeset
= xmlSecNodeSetGetChildren(pNode
->doc
, cur
, 1, 0);
344 if(nodeset
== nullptr)
345 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
347 // TODO assert we really have the SignatureInfo here?
348 if( xmlSecTransformCtxXmlExecute(&(pDsigCtx
->transformCtx
), nodeset
) < 0 )
349 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
351 // Validate the template via gpgme
352 GpgME::Context
& rCtx
=pSecEnv
->getGpgContext();
354 GpgME::Data
data_text(
355 reinterpret_cast<char*>(xmlSecBufferGetData(pDsigCtx
->transformCtx
.result
)),
356 xmlSecBufferGetSize(pDsigCtx
->transformCtx
.result
), false);
358 SAL_INFO("xmlsecurity.xmlsec.gpg", "Validating SignatureInfo: " << xmlSecBufferGetData(pDsigCtx
->transformCtx
.result
));
360 // walk xml tree to sign value node - go to children, first is
361 // SignedInfo, 2nd is signaturevalue
362 cur
= xmlSecGetNextElementNode(pNode
->children
);
363 cur
= xmlSecGetNextElementNode(cur
->next
);
365 if(!xmlSecCheckNodeName(cur
, xmlSecNodeSignatureValue
, xmlSecDSigNs
))
366 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
367 xmlChar
* pSignatureValue
=xmlNodeGetContent(cur
);
369 int nRet
= xmlSecBase64Decode_ex(pSignatureValue
, reinterpret_cast<xmlSecByte
*>(pSignatureValue
), xmlStrlen(pSignatureValue
), &nSigSize
);
371 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
373 GpgME::Data
data_signature(
374 reinterpret_cast<char*>(pSignatureValue
),
377 GpgME::VerificationResult verify_res
=rCtx
.verifyDetachedSignature(
378 data_signature
, data_text
);
380 // TODO: needs some more error handling, needs checking _all_ signatures
381 if( verify_res
.isNull() || verify_res
.numSignatures() == 0
382 // there is at least 1 signature and it is anything else than fully valid
383 || ( (verify_res
.numSignatures() > 0)
384 && verify_res
.signature(0).status().encodedError() > 0 ) )
386 // let's try again, but this time import the public key
387 // payload (avoiding that in a first cut for being a bit
388 // speedier. also prevents all too easy poisoning/sha1
389 // fingerprint collision attacks)
391 // walk xml tree to PGPData node - go to children, first is
392 // SignedInfo, 2nd is signaturevalue, 3rd is KeyInfo
393 // 1st child is PGPData, 1st or 2nd grandchild is PGPKeyPacket
394 cur
= xmlSecGetNextElementNode(pNode
->children
);
395 // TODO error handling
396 cur
= xmlSecGetNextElementNode(cur
->next
);
397 cur
= xmlSecGetNextElementNode(cur
->next
);
398 cur
= xmlSecGetNextElementNode(cur
->children
);
399 // check that this is now PGPData
400 if(!xmlSecCheckNodeName(cur
, xmlSecNodePGPData
, xmlSecDSigNs
))
401 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
402 // check that this is now PGPKeyPacket
403 cur
= xmlSecGetNextElementNode(cur
->children
);
404 static const xmlChar xmlSecNodePGPKeyPacket
[] = "PGPKeyPacket";
405 if(!xmlSecCheckNodeName(cur
, xmlSecNodePGPKeyPacket
, xmlSecDSigNs
))
407 // not this one, maybe the next?
408 cur
= xmlSecGetNextElementNode(cur
->next
);
409 if(!xmlSecCheckNodeName(cur
, xmlSecNodePGPKeyPacket
, xmlSecDSigNs
))
412 clearErrorRecorder();
413 xmlFree(pSignatureValue
);
419 // got a key packet, import & re-validate
420 xmlChar
* pKeyPacket
=xmlNodeGetContent(cur
);
422 nRet
= xmlSecBase64Decode_ex(pKeyPacket
, reinterpret_cast<xmlSecByte
*>(pKeyPacket
), xmlStrlen(pKeyPacket
), &nKeyLen
);
424 throw RuntimeException(u
"The GpgME library failed to initialize for the OpenPGP protocol."_ustr
);
426 GpgME::Data
data_key(
427 reinterpret_cast<char*>(pKeyPacket
),
430 rCtx
.importKeys(data_key
);
433 // and re-run (rewind text and signature streams to position 0)
434 (void)data_text
.seek(0,SEEK_SET
);
435 (void)data_signature
.seek(0,SEEK_SET
);
436 verify_res
=rCtx
.verifyDetachedSignature(data_signature
, data_text
);
438 // TODO: needs some more error handling, needs checking _all_ signatures
439 if( verify_res
.isNull() || verify_res
.numSignatures() == 0
440 // there is at least 1 signature and it is anything else than valid
441 || ( (verify_res
.numSignatures() > 0)
442 && verify_res
.signature(0).status().encodedError() > 0 ) )
444 clearErrorRecorder();
445 xmlFree(pSignatureValue
);
451 xmlFree(pSignatureValue
);
453 // now verify digest for all references
454 cur
= xmlSecGetNextElementNode(pNode
->children
);
456 cur
= xmlSecGetNextElementNode(cur
->children
);
457 while( cur
!= nullptr )
459 // some of those children I suppose should be reference elements
460 if( xmlSecCheckNodeName(cur
, xmlSecNodeReference
, xmlSecDSigNs
) )
462 xmlSecDSigReferenceCtxPtr pDsigRefCtx
=
463 xmlSecDSigReferenceCtxCreate(pDsigCtx
,
464 xmlSecDSigReferenceOriginSignedInfo
);
465 if(pDsigRefCtx
== nullptr)
466 throw RuntimeException();
468 // add this one to the list
469 if( xmlSecPtrListAdd(&(pDsigCtx
->signedInfoReferences
),
472 // TODO resource handling
473 xmlSecDSigReferenceCtxDestroy(pDsigRefCtx
);
474 throw RuntimeException();
477 if( xmlSecDSigReferenceCtxProcessNode(pDsigRefCtx
, cur
) < 0 )
478 throw RuntimeException();
480 // final check - all good?
481 if(pDsigRefCtx
->status
!= xmlSecDSigStatusSucceeded
)
483 pDsigCtx
->status
= xmlSecDSigStatusInvalid
;
484 return aTemplate
; // TODO - harder error?
488 cur
= xmlSecGetNextElementNode(cur
->next
);
491 // TODO - also verify manifest (only relevant for ooxml)?
492 aTemplate
->setStatus(SecurityOperationStatus_OPERATION_SUCCEEDED
);
495 xmlSecDSigCtxDestroy( pDsigCtx
) ;
498 //Unregistered the stream/URI binding
499 if( xUriBinding
.is() )
500 xmlUnregisterStreamInputCallbacks() ;
502 clearErrorRecorder();
507 OUString SAL_CALL
XMLSignature_GpgImpl::getImplementationName() {
508 return impl_getImplementationName() ;
512 sal_Bool SAL_CALL
XMLSignature_GpgImpl::supportsService( const OUString
& serviceName
) {
513 return cppu::supportsService(this, serviceName
);
517 Sequence
< OUString
> SAL_CALL
XMLSignature_GpgImpl::getSupportedServiceNames() {
518 return impl_getSupportedServiceNames() ;
521 //Helper for XServiceInfo
522 Sequence
< OUString
> XMLSignature_GpgImpl::impl_getSupportedServiceNames() {
523 Sequence
<OUString
> seqServiceNames
{ u
"com.sun.star.xml.crypto.XMLSignature"_ustr
};
524 return seqServiceNames
;
527 OUString
XMLSignature_GpgImpl::impl_getImplementationName() {
528 return u
"com.sun.star.xml.security.bridge.xmlsec.XMLSignature_GpgImpl"_ustr
;
531 //Helper for registry
532 Reference
< XInterface
> XMLSignature_GpgImpl::impl_createInstance( const Reference
< XMultiServiceFactory
>& ) {
533 return Reference
< XInterface
>( *new XMLSignature_GpgImpl
) ;
536 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */