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 <cppuhelper/supportsservice.hxx>
29 #include <rtl/ref.hxx>
30 #include <rtl/ustrbuf.hxx>
31 #include <sal/log.hxx>
32 #include "x509certificate_nssimpl.hxx"
34 #include <biginteger.hxx>
35 #include <certificateextension_xmlsecimpl.hxx>
37 #include "sanextension_nssimpl.hxx"
38 #include <tools/time.hxx>
39 #include <svl/sigstruct.hxx>
41 using ::css::util::DateTime
;
43 X509Certificate_NssImpl::X509Certificate_NssImpl() :
48 X509Certificate_NssImpl::~X509Certificate_NssImpl() {
49 if( m_pCert
!= nullptr ) {
50 CERT_DestroyCertificate( m_pCert
) ;
54 //Methods from XCertificate
55 sal_Int16 SAL_CALL
X509Certificate_NssImpl::getVersion() {
56 if( m_pCert
!= nullptr ) {
57 if( m_pCert
->version
.len
> 0 ) {
58 return static_cast<char>(*( m_pCert
->version
.data
)) ;
66 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getSerialNumber() {
67 if( m_pCert
!= nullptr && m_pCert
->serialNumber
.len
> 0 ) {
68 return comphelper::arrayToSequence
<sal_Int8
>(m_pCert
->serialNumber
.data
,
69 m_pCert
->serialNumber
.len
) ;
71 return css::uno::Sequence
< sal_Int8
>();
75 OUString SAL_CALL
X509Certificate_NssImpl::getIssuerName() {
76 if( m_pCert
!= nullptr ) {
77 return OUString(m_pCert
->issuerName
, PL_strlen(m_pCert
->issuerName
) , RTL_TEXTENCODING_UTF8
) ;
83 OUString SAL_CALL
X509Certificate_NssImpl::getSubjectName() {
84 if( m_pCert
!= nullptr ) {
85 return OUString(m_pCert
->subjectName
, PL_strlen(m_pCert
->subjectName
) , RTL_TEXTENCODING_UTF8
);
91 css::util::DateTime SAL_CALL
X509Certificate_NssImpl::getNotValidBefore() {
92 if( m_pCert
!= nullptr ) {
95 PRExplodedTime explTime
;
98 rv
= DER_DecodeTimeChoice( ¬Before
, &m_pCert
->validity
.notBefore
) ;
99 if( rv
!= SECStatus::SECSuccess
) {
103 //Convert the time to readable local time
104 PR_ExplodeTime( notBefore
, PR_LocalTimeParameters
, &explTime
) ;
106 dateTime
.NanoSeconds
= static_cast< sal_Int32
>( explTime
.tm_usec
* ::tools::Time::nanoPerMicro
);
107 dateTime
.Seconds
= static_cast< sal_Int16
>( explTime
.tm_sec
);
108 dateTime
.Minutes
= static_cast< sal_Int16
>( explTime
.tm_min
);
109 dateTime
.Hours
= static_cast< sal_Int16
>( explTime
.tm_hour
);
110 dateTime
.Day
= static_cast< sal_Int16
>( explTime
.tm_mday
);
111 dateTime
.Month
= static_cast< sal_Int16
>( explTime
.tm_month
+1 );
112 dateTime
.Year
= static_cast< sal_Int16
>( explTime
.tm_year
);
120 css::util::DateTime SAL_CALL
X509Certificate_NssImpl::getNotValidAfter() {
121 if( m_pCert
!= nullptr ) {
124 PRExplodedTime explTime
;
127 rv
= DER_DecodeTimeChoice( ¬After
, &m_pCert
->validity
.notAfter
) ;
128 if( rv
!= SECStatus::SECSuccess
) {
132 //Convert the time to readable local time
133 PR_ExplodeTime( notAfter
, PR_LocalTimeParameters
, &explTime
) ;
135 dateTime
.NanoSeconds
= static_cast< sal_Int16
>( explTime
.tm_usec
* ::tools::Time::nanoPerMicro
);
136 dateTime
.Seconds
= static_cast< sal_Int16
>( explTime
.tm_sec
);
137 dateTime
.Minutes
= static_cast< sal_Int16
>( explTime
.tm_min
);
138 dateTime
.Hours
= static_cast< sal_Int16
>( explTime
.tm_hour
);
139 dateTime
.Day
= static_cast< sal_Int16
>( explTime
.tm_mday
);
140 dateTime
.Month
= static_cast< sal_Int16
>( explTime
.tm_month
+1 );
141 dateTime
.Year
= static_cast< sal_Int16
>( explTime
.tm_year
);
149 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getIssuerUniqueID() {
150 if( m_pCert
!= nullptr && m_pCert
->issuerID
.len
> 0 ) {
151 return comphelper::arrayToSequence
<sal_Int8
>(m_pCert
->issuerID
.data
, m_pCert
->issuerID
.len
) ;
153 return css::uno::Sequence
< sal_Int8
>();
157 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getSubjectUniqueID() {
158 if( m_pCert
!= nullptr && m_pCert
->subjectID
.len
> 0 ) {
159 return comphelper::arrayToSequence
<sal_Int8
>(m_pCert
->subjectID
.data
,
160 m_pCert
->subjectID
.len
) ;
162 return css::uno::Sequence
< sal_Int8
>();
166 css::uno::Sequence
< css::uno::Reference
< css::security::XCertificateExtension
> > SAL_CALL
X509Certificate_NssImpl::getExtensions() {
167 if( m_pCert
!= nullptr && m_pCert
->extensions
!= nullptr ) {
168 CERTCertExtension
** extns
;
171 for( len
= 0, extns
= m_pCert
->extensions
; *extns
!= nullptr; len
++, extns
++ ) ;
172 css::uno::Sequence
< css::uno::Reference
< css::security::XCertificateExtension
> > xExtns( len
) ;
173 auto xExtnsRange
= asNonConstRange(xExtns
);
175 for( extns
= m_pCert
->extensions
, len
= 0; *extns
!= nullptr; extns
++, len
++ ) {
176 const SECItem id
= (*extns
)->id
;
177 OString
oidString(CERT_GetOidString(&id
));
180 if( (*extns
)->critical
.data
== nullptr )
183 crit
= (*extns
)->critical
.data
[0] == 0xFF;
185 // remove "OID." prefix if existing
187 static constexpr std::string_view
oid("OID.");
188 if (oidString
.match(oid
))
189 objID
= oidString
.copy(oid
.size());
193 unsigned char* value
= (*extns
)->value
.data
;
194 unsigned int vlen
= (*extns
)->value
.len
;
195 unsigned char* objid
= reinterpret_cast<unsigned char *>(const_cast<char *>(objID
.getStr()));
196 unsigned int objidlen
= objID
.getLength();
198 if (objID
== "2.5.29.17")
200 rtl::Reference
<SanExtensionImpl
> pExtn
= new SanExtensionImpl
;
201 pExtn
->setCertExtn(value
, vlen
, objid
, objidlen
, crit
);
202 xExtnsRange
[len
] = pExtn
;
206 rtl::Reference
<CertificateExtension_XmlSecImpl
> pExtn
= new CertificateExtension_XmlSecImpl
;
207 pExtn
->setCertExtn(value
, vlen
, objid
, objidlen
, crit
);
208 xExtnsRange
[len
] = pExtn
;
214 return css::uno::Sequence
< css::uno::Reference
< css::security::XCertificateExtension
> > ();
218 css::uno::Reference
< css::security::XCertificateExtension
> SAL_CALL
X509Certificate_NssImpl::findCertificateExtension( const css::uno::Sequence
< sal_Int8
>& oid
) {
219 if( m_pCert
!= nullptr && m_pCert
->extensions
!= nullptr ) {
220 CERTCertExtension
** extns
;
223 idItem
.data
= reinterpret_cast<unsigned char *>(const_cast<sal_Int8
*>(oid
.getConstArray()));
224 idItem
.len
= oid
.getLength() ;
226 css::uno::Reference
<css::security::XCertificateExtension
> xExtn
;
227 for( extns
= m_pCert
->extensions
; *extns
!= nullptr; extns
++ ) {
228 if( SECITEM_CompareItem( &idItem
, &(*extns
)->id
) == SECEqual
) {
229 const SECItem id
= (*extns
)->id
;
230 OString
objId(CERT_GetOidString(&id
));
233 if( (*extns
)->critical
.data
== nullptr )
236 crit
= (*extns
)->critical
.data
[0] == 0xFF;
238 unsigned char* value
= (*extns
)->value
.data
;
239 unsigned int vlen
= (*extns
)->value
.len
;
240 unsigned char* objid
= (*extns
)->id
.data
;
241 unsigned int objidlen
= (*extns
)->id
.len
;
243 if ( objId
== "OID.2.5.29.17" )
245 rtl::Reference
<SanExtensionImpl
> xSanImpl(
246 new SanExtensionImpl
);
247 xSanImpl
->setCertExtn(value
, vlen
, objid
, objidlen
, crit
);
248 xExtn
= xSanImpl
.get();
252 rtl::Reference
<CertificateExtension_XmlSecImpl
> xSecImpl(
253 new CertificateExtension_XmlSecImpl
);
254 xSecImpl
->setCertExtn(value
, vlen
, objid
, objidlen
, crit
);
255 xExtn
= xSecImpl
.get();
268 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getEncoded() {
269 if( m_pCert
!= nullptr && m_pCert
->derCert
.len
> 0 ) {
270 return comphelper::arrayToSequence
<sal_Int8
>(m_pCert
->derCert
.data
, m_pCert
->derCert
.len
) ;
272 return css::uno::Sequence
< sal_Int8
>();
277 void X509Certificate_NssImpl::setCert( CERTCertificate
* cert
) {
278 if( m_pCert
!= nullptr ) {
279 CERT_DestroyCertificate( m_pCert
) ;
283 if( cert
!= nullptr ) {
284 m_pCert
= CERT_DupCertificate( cert
) ;
288 const CERTCertificate
* X509Certificate_NssImpl::getNssCert() const {
289 if( m_pCert
!= nullptr ) {
296 void X509Certificate_NssImpl::setRawCert( const css::uno::Sequence
< sal_Int8
>& rawCert
) {
297 CERTCertificate
* cert
;
300 certItem
.data
= reinterpret_cast<unsigned char *>(const_cast<sal_Int8
*>(rawCert
.getConstArray()));
301 certItem
.len
= rawCert
.getLength() ;
303 cert
= CERT_DecodeDERCertificate( &certItem
, PR_TRUE
, nullptr ) ;
304 if( cert
== nullptr )
305 throw css::uno::RuntimeException() ;
307 if( m_pCert
!= nullptr ) {
308 CERT_DestroyCertificate( m_pCert
) ;
315 SECKEYPrivateKey
* X509Certificate_NssImpl::getPrivateKey()
317 if (m_pCert
&& m_pCert
->slot
)
319 SECKEYPrivateKey
* pPrivateKey
= PK11_FindPrivateKeyFromCert(m_pCert
->slot
, m_pCert
, nullptr);
322 pPrivateKey
= PK11_FindKeyByDERCert(m_pCert
->slot
, m_pCert
, nullptr);
325 SAL_INFO("xmlsecurity.xmlsec", "fallback from PK11_FindPrivateKeyFromCert to PK11_FindKeyByDERCert needed");
328 SAL_WARN("xmlsecurity.xmlsec", "X509Certificate_NssImpl::getPrivateKey() cannot find private key");
333 static OUString
getAlgorithmDescription(SECAlgorithmID
const *aid
)
336 tag
= SECOID_GetAlgorithmTag(aid
);
338 const char *pDesc
= SECOID_FindOIDTagDescription(tag
);
340 return OUString::createFromAscii( pDesc
) ;
343 static css::uno::Sequence
< sal_Int8
> getThumbprint(CERTCertificate
const *pCert
, SECOidTag id
)
345 if( pCert
!= nullptr )
348 unsigned char fingerprint
[32];
356 length
= SHA1_LENGTH
;
359 length
= SHA256_LENGTH
;
365 memset(fingerprint
, 0, sizeof fingerprint
);
366 rv
= PK11_HashBuf(id
, fingerprint
, pCert
->derCert
.data
, pCert
->derCert
.len
);
367 if(rv
== SECStatus::SECSuccess
)
369 return comphelper::arrayToSequence
<sal_Int8
>(fingerprint
, length
);
372 return css::uno::Sequence
< sal_Int8
>();
375 OUString SAL_CALL
X509Certificate_NssImpl::getSubjectPublicKeyAlgorithm()
377 if( m_pCert
!= nullptr )
379 return getAlgorithmDescription(&(m_pCert
->subjectPublicKeyInfo
.algorithm
));
387 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getSubjectPublicKeyValue()
389 if( m_pCert
!= nullptr )
391 SECItem spk
= m_pCert
->subjectPublicKeyInfo
.subjectPublicKey
;
392 DER_ConvertBitString(&spk
);
396 return comphelper::arrayToSequence
<sal_Int8
>(spk
.data
, spk
.len
) ;
400 return css::uno::Sequence
< sal_Int8
>();
403 OUString SAL_CALL
X509Certificate_NssImpl::getSignatureAlgorithm()
405 if( m_pCert
!= nullptr )
407 return getAlgorithmDescription(&(m_pCert
->signature
));
415 svl::crypto::SignatureMethodAlgorithm
X509Certificate_NssImpl::getSignatureMethodAlgorithm()
417 svl::crypto::SignatureMethodAlgorithm nRet
= svl::crypto::SignatureMethodAlgorithm::RSA
;
422 SECOidTag eTag
= SECOID_GetAlgorithmTag(&m_pCert
->subjectPublicKeyInfo
.algorithm
);
423 if (eTag
== SEC_OID_ANSIX962_EC_PUBLIC_KEY
)
424 nRet
= svl::crypto::SignatureMethodAlgorithm::ECDSA
;
429 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getSHA1Thumbprint()
431 return getThumbprint(m_pCert
, SEC_OID_SHA1
);
434 css::uno::Sequence
<sal_Int8
> X509Certificate_NssImpl::getSHA256Thumbprint()
436 return getThumbprint(m_pCert
, SEC_OID_SHA256
);
439 css::uno::Sequence
< sal_Int8
> SAL_CALL
X509Certificate_NssImpl::getMD5Thumbprint()
441 return getThumbprint(m_pCert
, SEC_OID_MD5
);
444 css::security::CertificateKind SAL_CALL
X509Certificate_NssImpl::getCertificateKind()
446 return css::security::CertificateKind_X509
;
449 sal_Int32 SAL_CALL
X509Certificate_NssImpl::getCertificateUsage( )
455 rv
= CERT_FindKeyUsageExtension(m_pCert
, &tmpitem
);
456 if ( rv
== SECStatus::SECSuccess
)
458 usage
= tmpitem
.data
[0];
459 PORT_Free(tmpitem
.data
);
460 tmpitem
.data
= nullptr;
468 * to make the nss implementation compatible with MSCrypto,
469 * the following usage is ignored
472 if ( CERT_GovtApprovedBitSet(m_pCert) )
474 usage |= KU_NS_GOVT_APPROVED;
482 OUString SAL_CALL
X509Certificate_NssImpl::getImplementationName()
484 return u
"com.sun.star.xml.security.gpg.XCertificate_NssImpl"_ustr
;
488 sal_Bool SAL_CALL
X509Certificate_NssImpl::supportsService(const OUString
& serviceName
)
490 return cppu::supportsService(this, serviceName
);
494 css::uno::Sequence
<OUString
> SAL_CALL
X509Certificate_NssImpl::getSupportedServiceNames() { return { OUString() }; }
496 namespace xmlsecurity
{
498 // based on some guesswork and:
499 // https://datatracker.ietf.org/doc/html/rfc1485
500 // https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certnametostra#CERT_X500_NAME_STR
501 // the main problem appears to be that in values " is escaped as "" vs. \"
502 static OUString
CompatDNCryptoAPI(std::u16string_view rDN
)
504 OUStringBuffer
buf(rDN
.size());
505 enum { DEFAULT
, INVALUE
, INQUOTE
} state(DEFAULT
);
506 for (size_t i
= 0; i
< rDN
.size(); ++i
)
508 if (state
== DEFAULT
)
513 if (rDN
.size() == i
+1)
517 else if (rDN
[i
+1] == '"')
519 buf
.append(rDN
[i
+1]);
529 else if (state
== INVALUE
)
531 if (rDN
[i
] == '+' || rDN
[i
] == ',' || rDN
[i
] == ';')
539 assert(state
== INQUOTE
);
542 if (rDN
.size() != i
+1 && rDN
[i
+1] == '"')
544 buf
.append(OUString::Concat("\\") + OUStringChar(rDN
[i
+1]));
559 return buf
.makeStringAndClear();
562 bool EqualDistinguishedNames(
563 std::u16string_view
const rName1
, std::u16string_view
const rName2
,
564 EqualMode
const eMode
)
566 if (eMode
== COMPAT_BOTH
&& !rName1
.empty() && rName1
== rName2
)
567 { // handle case where both need to be converted
570 CERTName
*const pName1(CERT_AsciiToName(OUStringToOString(rName1
, RTL_TEXTENCODING_UTF8
).getStr()));
571 if (pName1
== nullptr)
575 CERTName
*const pName2(CERT_AsciiToName(OUStringToOString(rName2
, RTL_TEXTENCODING_UTF8
).getStr()));
579 ret
= (CERT_CompareName(pName1
, pName2
) == SECEqual
);
580 CERT_DestroyName(pName2
);
582 if (!ret
&& eMode
== COMPAT_2ND
)
584 CERTName
*const pName2Compat(CERT_AsciiToName(OUStringToOString(
585 CompatDNCryptoAPI(rName2
), RTL_TEXTENCODING_UTF8
).getStr()));
586 if (pName2Compat
== nullptr)
588 CERT_DestroyName(pName1
);
591 ret
= CERT_CompareName(pName1
, pName2Compat
) == SECEqual
;
592 CERT_DestroyName(pName2Compat
);
594 CERT_DestroyName(pName1
);
598 } // namespace xmlsecurity
600 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */