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 .
27 #include <comphelper/sequence.hxx>
28 #include <comphelper/servicehelper.hxx>
29 #include <cppuhelper/supportsservice.hxx>
30 #include <rtl/ref.hxx>
31 #include <rtl/ustrbuf.hxx>
32 #include <sal/log.hxx>
33 #include "x509certificate_nssimpl.hxx"
35 #include <biginteger.hxx>
36 #include <certificateextension_xmlsecimpl.hxx>
38 #include "sanextension_nssimpl.hxx"
39 #include <o3tl/string_view.hxx>
40 #include <tools/time.hxx>
41 #include <svl/sigstruct.hxx>
43 using ::css::util::DateTime
;
45 X509Certificate_NssImpl::X509Certificate_NssImpl() :
50 X509Certificate_NssImpl::~X509Certificate_NssImpl() {
51 if( m_pCert
!= nullptr ) {
52 CERT_DestroyCertificate( m_pCert
) ;
56 //Methods from XCertificate
57 sal_Int16 SAL_CALL
X509Certificate_NssImpl::getVersion() {
58 if( m_pCert
!= nullptr ) {
59 if( m_pCert
->version
.len
> 0 ) {
60 return static_cast<char>(*( m_pCert
->version
.data
)) ;
68 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getSerialNumber() {
69 if( m_pCert
!= nullptr && m_pCert
->serialNumber
.len
> 0 ) {
70 return comphelper::arrayToSequence
<sal_Int8
>(m_pCert
->serialNumber
.data
,
71 m_pCert
->serialNumber
.len
) ;
73 return css::uno::Sequence
< sal_Int8
>();
77 OUString SAL_CALL
X509Certificate_NssImpl::getIssuerName() {
78 if( m_pCert
!= nullptr ) {
79 return OUString(m_pCert
->issuerName
, PL_strlen(m_pCert
->issuerName
) , RTL_TEXTENCODING_UTF8
) ;
85 OUString SAL_CALL
X509Certificate_NssImpl::getSubjectName() {
86 if( m_pCert
!= nullptr ) {
87 return OUString(m_pCert
->subjectName
, PL_strlen(m_pCert
->subjectName
) , RTL_TEXTENCODING_UTF8
);
93 css::util::DateTime SAL_CALL
X509Certificate_NssImpl::getNotValidBefore() {
94 if( m_pCert
!= nullptr ) {
97 PRExplodedTime explTime
;
100 rv
= DER_DecodeTimeChoice( ¬Before
, &m_pCert
->validity
.notBefore
) ;
101 if( rv
!= SECStatus::SECSuccess
) {
105 //Convert the time to readable local time
106 PR_ExplodeTime( notBefore
, PR_LocalTimeParameters
, &explTime
) ;
108 dateTime
.NanoSeconds
= static_cast< sal_Int32
>( explTime
.tm_usec
* ::tools::Time::nanoPerMicro
);
109 dateTime
.Seconds
= static_cast< sal_Int16
>( explTime
.tm_sec
);
110 dateTime
.Minutes
= static_cast< sal_Int16
>( explTime
.tm_min
);
111 dateTime
.Hours
= static_cast< sal_Int16
>( explTime
.tm_hour
);
112 dateTime
.Day
= static_cast< sal_Int16
>( explTime
.tm_mday
);
113 dateTime
.Month
= static_cast< sal_Int16
>( explTime
.tm_month
+1 );
114 dateTime
.Year
= static_cast< sal_Int16
>( explTime
.tm_year
);
122 css::util::DateTime SAL_CALL
X509Certificate_NssImpl::getNotValidAfter() {
123 if( m_pCert
!= nullptr ) {
126 PRExplodedTime explTime
;
129 rv
= DER_DecodeTimeChoice( ¬After
, &m_pCert
->validity
.notAfter
) ;
130 if( rv
!= SECStatus::SECSuccess
) {
134 //Convert the time to readable local time
135 PR_ExplodeTime( notAfter
, PR_LocalTimeParameters
, &explTime
) ;
137 dateTime
.NanoSeconds
= static_cast< sal_Int16
>( explTime
.tm_usec
* ::tools::Time::nanoPerMicro
);
138 dateTime
.Seconds
= static_cast< sal_Int16
>( explTime
.tm_sec
);
139 dateTime
.Minutes
= static_cast< sal_Int16
>( explTime
.tm_min
);
140 dateTime
.Hours
= static_cast< sal_Int16
>( explTime
.tm_hour
);
141 dateTime
.Day
= static_cast< sal_Int16
>( explTime
.tm_mday
);
142 dateTime
.Month
= static_cast< sal_Int16
>( explTime
.tm_month
+1 );
143 dateTime
.Year
= static_cast< sal_Int16
>( explTime
.tm_year
);
151 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getIssuerUniqueID() {
152 if( m_pCert
!= nullptr && m_pCert
->issuerID
.len
> 0 ) {
153 return comphelper::arrayToSequence
<sal_Int8
>(m_pCert
->issuerID
.data
, m_pCert
->issuerID
.len
) ;
155 return css::uno::Sequence
< sal_Int8
>();
159 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getSubjectUniqueID() {
160 if( m_pCert
!= nullptr && m_pCert
->subjectID
.len
> 0 ) {
161 return comphelper::arrayToSequence
<sal_Int8
>(m_pCert
->subjectID
.data
,
162 m_pCert
->subjectID
.len
) ;
164 return css::uno::Sequence
< sal_Int8
>();
168 css::uno::Sequence
< css::uno::Reference
< css::security::XCertificateExtension
> > SAL_CALL
X509Certificate_NssImpl::getExtensions() {
169 if( m_pCert
!= nullptr && m_pCert
->extensions
!= nullptr ) {
170 CERTCertExtension
** extns
;
173 for( len
= 0, extns
= m_pCert
->extensions
; *extns
!= nullptr; len
++, extns
++ ) ;
174 css::uno::Sequence
< css::uno::Reference
< css::security::XCertificateExtension
> > xExtns( len
) ;
175 auto xExtnsRange
= asNonConstRange(xExtns
);
177 for( extns
= m_pCert
->extensions
, len
= 0; *extns
!= nullptr; extns
++, len
++ ) {
178 const SECItem id
= (*extns
)->id
;
179 OString
oidString(CERT_GetOidString(&id
));
182 if( (*extns
)->critical
.data
== nullptr )
185 crit
= (*extns
)->critical
.data
[0] == 0xFF;
187 // remove "OID." prefix if existing
189 constexpr std::string_view
oid("OID.");
190 if (oidString
.match(oid
))
191 objID
= oidString
.copy(oid
.size());
195 unsigned char* value
= (*extns
)->value
.data
;
196 unsigned int vlen
= (*extns
)->value
.len
;
197 unsigned char* objid
= reinterpret_cast<unsigned char *>(const_cast<char *>(objID
.getStr()));
198 unsigned int objidlen
= objID
.getLength();
200 if (objID
== "2.5.29.17")
202 rtl::Reference
<SanExtensionImpl
> pExtn
= new SanExtensionImpl
;
203 pExtn
->setCertExtn(value
, vlen
, objid
, objidlen
, crit
);
204 xExtnsRange
[len
] = pExtn
;
208 rtl::Reference
<CertificateExtension_XmlSecImpl
> pExtn
= new CertificateExtension_XmlSecImpl
;
209 pExtn
->setCertExtn(value
, vlen
, objid
, objidlen
, crit
);
210 xExtnsRange
[len
] = pExtn
;
216 return css::uno::Sequence
< css::uno::Reference
< css::security::XCertificateExtension
> > ();
220 css::uno::Reference
< css::security::XCertificateExtension
> SAL_CALL
X509Certificate_NssImpl::findCertificateExtension( const css::uno::Sequence
< sal_Int8
>& oid
) {
221 if( m_pCert
!= nullptr && m_pCert
->extensions
!= nullptr ) {
222 CERTCertExtension
** extns
;
225 idItem
.data
= reinterpret_cast<unsigned char *>(const_cast<sal_Int8
*>(oid
.getConstArray()));
226 idItem
.len
= oid
.getLength() ;
228 css::uno::Reference
<css::security::XCertificateExtension
> xExtn
;
229 for( extns
= m_pCert
->extensions
; *extns
!= nullptr; extns
++ ) {
230 if( SECITEM_CompareItem( &idItem
, &(*extns
)->id
) == SECEqual
) {
231 const SECItem id
= (*extns
)->id
;
232 OString
objId(CERT_GetOidString(&id
));
235 if( (*extns
)->critical
.data
== nullptr )
238 crit
= (*extns
)->critical
.data
[0] == 0xFF;
240 unsigned char* value
= (*extns
)->value
.data
;
241 unsigned int vlen
= (*extns
)->value
.len
;
242 unsigned char* objid
= (*extns
)->id
.data
;
243 unsigned int objidlen
= (*extns
)->id
.len
;
245 if ( objId
== "OID.2.5.29.17" )
247 rtl::Reference
<SanExtensionImpl
> xSanImpl(
248 new SanExtensionImpl
);
249 xSanImpl
->setCertExtn(value
, vlen
, objid
, objidlen
, crit
);
250 xExtn
= xSanImpl
.get();
254 rtl::Reference
<CertificateExtension_XmlSecImpl
> xSecImpl(
255 new CertificateExtension_XmlSecImpl
);
256 xSecImpl
->setCertExtn(value
, vlen
, objid
, objidlen
, crit
);
257 xExtn
= xSecImpl
.get();
270 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getEncoded() {
271 if( m_pCert
!= nullptr && m_pCert
->derCert
.len
> 0 ) {
272 return comphelper::arrayToSequence
<sal_Int8
>(m_pCert
->derCert
.data
, m_pCert
->derCert
.len
) ;
274 return css::uno::Sequence
< sal_Int8
>();
279 void X509Certificate_NssImpl::setCert( CERTCertificate
* cert
) {
280 if( m_pCert
!= nullptr ) {
281 CERT_DestroyCertificate( m_pCert
) ;
285 if( cert
!= nullptr ) {
286 m_pCert
= CERT_DupCertificate( cert
) ;
290 const CERTCertificate
* X509Certificate_NssImpl::getNssCert() const {
291 if( m_pCert
!= nullptr ) {
298 void X509Certificate_NssImpl::setRawCert( const css::uno::Sequence
< sal_Int8
>& rawCert
) {
299 CERTCertificate
* cert
;
302 certItem
.data
= reinterpret_cast<unsigned char *>(const_cast<sal_Int8
*>(rawCert
.getConstArray()));
303 certItem
.len
= rawCert
.getLength() ;
305 cert
= CERT_DecodeDERCertificate( &certItem
, PR_TRUE
, nullptr ) ;
306 if( cert
== nullptr )
307 throw css::uno::RuntimeException() ;
309 if( m_pCert
!= nullptr ) {
310 CERT_DestroyCertificate( m_pCert
) ;
317 SECKEYPrivateKey
* X509Certificate_NssImpl::getPrivateKey()
319 if (m_pCert
&& m_pCert
->slot
)
321 SECKEYPrivateKey
* pPrivateKey
= PK11_FindPrivateKeyFromCert(m_pCert
->slot
, m_pCert
, nullptr);
324 pPrivateKey
= PK11_FindKeyByDERCert(m_pCert
->slot
, m_pCert
, nullptr);
327 SAL_INFO("xmlsecurity.xmlsec", "fallback from PK11_FindPrivateKeyFromCert to PK11_FindKeyByDERCert needed");
330 SAL_WARN("xmlsecurity.xmlsec", "X509Certificate_NssImpl::getPrivateKey() cannot find private key");
335 static OUString
getAlgorithmDescription(SECAlgorithmID
const *aid
)
338 tag
= SECOID_GetAlgorithmTag(aid
);
340 const char *pDesc
= SECOID_FindOIDTagDescription(tag
);
342 return OUString::createFromAscii( pDesc
) ;
345 static css::uno::Sequence
< sal_Int8
> getThumbprint(CERTCertificate
const *pCert
, SECOidTag id
)
347 if( pCert
!= nullptr )
350 unsigned char fingerprint
[32];
358 length
= SHA1_LENGTH
;
361 length
= SHA256_LENGTH
;
367 memset(fingerprint
, 0, sizeof fingerprint
);
368 rv
= PK11_HashBuf(id
, fingerprint
, pCert
->derCert
.data
, pCert
->derCert
.len
);
369 if(rv
== SECStatus::SECSuccess
)
371 return comphelper::arrayToSequence
<sal_Int8
>(fingerprint
, length
);
374 return css::uno::Sequence
< sal_Int8
>();
377 OUString SAL_CALL
X509Certificate_NssImpl::getSubjectPublicKeyAlgorithm()
379 if( m_pCert
!= nullptr )
381 return getAlgorithmDescription(&(m_pCert
->subjectPublicKeyInfo
.algorithm
));
389 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getSubjectPublicKeyValue()
391 if( m_pCert
!= nullptr )
393 SECItem spk
= m_pCert
->subjectPublicKeyInfo
.subjectPublicKey
;
394 DER_ConvertBitString(&spk
);
398 return comphelper::arrayToSequence
<sal_Int8
>(spk
.data
, spk
.len
) ;
402 return css::uno::Sequence
< sal_Int8
>();
405 OUString SAL_CALL
X509Certificate_NssImpl::getSignatureAlgorithm()
407 if( m_pCert
!= nullptr )
409 return getAlgorithmDescription(&(m_pCert
->signature
));
417 svl::crypto::SignatureMethodAlgorithm
X509Certificate_NssImpl::getSignatureMethodAlgorithm()
419 svl::crypto::SignatureMethodAlgorithm nRet
= svl::crypto::SignatureMethodAlgorithm::RSA
;
424 SECOidTag eTag
= SECOID_GetAlgorithmTag(&m_pCert
->subjectPublicKeyInfo
.algorithm
);
425 if (eTag
== SEC_OID_ANSIX962_EC_PUBLIC_KEY
)
426 nRet
= svl::crypto::SignatureMethodAlgorithm::ECDSA
;
431 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getSHA1Thumbprint()
433 return getThumbprint(m_pCert
, SEC_OID_SHA1
);
436 css::uno::Sequence
<sal_Int8
> X509Certificate_NssImpl::getSHA256Thumbprint()
438 return getThumbprint(m_pCert
, SEC_OID_SHA256
);
441 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getMD5Thumbprint()
443 return getThumbprint(m_pCert
, SEC_OID_MD5
);
446 css::security::CertificateKind SAL_CALL
X509Certificate_NssImpl::getCertificateKind()
448 return css::security::CertificateKind_X509
;
451 sal_Int32 SAL_CALL
X509Certificate_NssImpl::getCertificateUsage( )
457 rv
= CERT_FindKeyUsageExtension(m_pCert
, &tmpitem
);
458 if ( rv
== SECStatus::SECSuccess
)
460 usage
= tmpitem
.data
[0];
461 PORT_Free(tmpitem
.data
);
462 tmpitem
.data
= nullptr;
470 * to make the nss implementation compatible with MSCrypto,
471 * the following usage is ignored
474 if ( CERT_GovtApprovedBitSet(m_pCert) )
476 usage |= KU_NS_GOVT_APPROVED;
484 OUString SAL_CALL
X509Certificate_NssImpl::getImplementationName()
486 return "com.sun.star.xml.security.gpg.XCertificate_NssImpl";
490 sal_Bool SAL_CALL
X509Certificate_NssImpl::supportsService(const OUString
& serviceName
)
492 return cppu::supportsService(this, serviceName
);
496 css::uno::Sequence
<OUString
> SAL_CALL
X509Certificate_NssImpl::getSupportedServiceNames() { return { OUString() }; }
498 namespace xmlsecurity
{
500 // based on some guesswork and:
501 // https://datatracker.ietf.org/doc/html/rfc1485
502 // https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certnametostra#CERT_X500_NAME_STR
503 // the main problem appears to be that in values " is escaped as "" vs. \"
504 static OUString
CompatDNCryptoAPI(std::u16string_view rDN
)
506 OUStringBuffer
buf(rDN
.size());
507 enum { DEFAULT
, INVALUE
, INQUOTE
} state(DEFAULT
);
508 for (size_t i
= 0; i
< rDN
.size(); ++i
)
510 if (state
== DEFAULT
)
515 if (rDN
.size() == i
+1)
519 else if (rDN
[i
+1] == '"')
521 buf
.append(rDN
[i
+1]);
531 else if (state
== INVALUE
)
533 if (rDN
[i
] == '+' || rDN
[i
] == ',' || rDN
[i
] == ';')
541 assert(state
== INQUOTE
);
544 if (rDN
.size() != i
+1 && rDN
[i
+1] == '"')
546 buf
.append(OUString::Concat("\\") + OUStringChar(rDN
[i
+1]));
561 return buf
.makeStringAndClear();
564 bool EqualDistinguishedNames(
565 std::u16string_view
const rName1
, std::u16string_view
const rName2
,
566 EqualMode
const eMode
)
568 if (eMode
== COMPAT_BOTH
&& !rName1
.empty() && rName1
== rName2
)
569 { // handle case where both need to be converted
572 CERTName
*const pName1(CERT_AsciiToName(OUStringToOString(rName1
, RTL_TEXTENCODING_UTF8
).getStr()));
573 if (pName1
== nullptr)
577 CERTName
*const pName2(CERT_AsciiToName(OUStringToOString(rName2
, RTL_TEXTENCODING_UTF8
).getStr()));
581 ret
= (CERT_CompareName(pName1
, pName2
) == SECEqual
);
582 CERT_DestroyName(pName2
);
584 if (!ret
&& eMode
== COMPAT_2ND
)
586 CERTName
*const pName2Compat(CERT_AsciiToName(OUStringToOString(
587 CompatDNCryptoAPI(rName2
), RTL_TEXTENCODING_UTF8
).getStr()));
588 if (pName2Compat
== nullptr)
590 CERT_DestroyName(pName1
);
593 ret
= CERT_CompareName(pName1
, pName2Compat
) == SECEqual
;
594 CERT_DestroyName(pName2Compat
);
596 CERT_DestroyName(pName1
);
600 } // namespace xmlsecurity
602 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */