VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/CertificateImpl.cpp@ 86506

Last change on this file since 86506 was 84249, checked in by vboxsync, 4 years ago

Main/CertificateImpl: sha512-224WithRSAEncryption and sha512-256WithRSAEncryption (RFC-8017)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.9 KB
Line 
1/* $Id: CertificateImpl.cpp 84249 2020-05-11 11:47:02Z vboxsync $ */
2/** @file
3 * ICertificate COM class implementations.
4 */
5
6/*
7 * Copyright (C) 2008-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#define LOG_GROUP LOG_GROUP_MAIN_CERTIFICATE
19#include <iprt/err.h>
20#include <iprt/path.h>
21#include <iprt/cpp/utils.h>
22#include <VBox/com/array.h>
23#include <iprt/crypto/x509.h>
24
25#include "ProgressImpl.h"
26#include "CertificateImpl.h"
27#include "AutoCaller.h"
28#include "Global.h"
29#include "LoggingNew.h"
30
31using namespace std;
32
33
34/**
35 * Private instance data for the #Certificate class.
36 * @see Certificate::m
37 */
38struct Certificate::Data
39{
40 Data()
41 : fTrusted(false)
42 , fExpired(false)
43 , fValidX509(false)
44 {
45 RT_ZERO(X509);
46 }
47
48 ~Data()
49 {
50 if (fValidX509)
51 {
52 RTCrX509Certificate_Delete(&X509);
53 RT_ZERO(X509);
54 fValidX509 = false;
55 }
56 }
57
58 /** Whether the certificate is trusted. */
59 bool fTrusted;
60 /** Whether the certificate is trusted. */
61 bool fExpired;
62 /** Valid data in mX509. */
63 bool fValidX509;
64 /** Clone of the X.509 certificate. */
65 RTCRX509CERTIFICATE X509;
66
67private:
68 Data(const Certificate::Data &rTodo) { AssertFailed(); NOREF(rTodo); }
69 Data &operator=(const Certificate::Data &rTodo) { AssertFailed(); NOREF(rTodo); return *this; }
70};
71
72
73///////////////////////////////////////////////////////////////////////////////////
74//
75// Certificate constructor / destructor
76//
77// ////////////////////////////////////////////////////////////////////////////////
78
79DEFINE_EMPTY_CTOR_DTOR(Certificate)
80
81HRESULT Certificate::FinalConstruct()
82{
83 return BaseFinalConstruct();
84}
85
86void Certificate::FinalRelease()
87{
88 uninit();
89 BaseFinalRelease();
90}
91
92/**
93 * Initializes a certificate instance.
94 *
95 * @returns COM status code.
96 * @param a_pCert The certificate.
97 * @param a_fTrusted Whether the caller trusts the certificate or not.
98 * @param a_fExpired Whether the caller consideres the certificate to be
99 * expired.
100 */
101HRESULT Certificate::initCertificate(PCRTCRX509CERTIFICATE a_pCert, bool a_fTrusted, bool a_fExpired)
102{
103 HRESULT rc = S_OK;
104 LogFlowThisFuncEnter();
105
106 AutoInitSpan autoInitSpan(this);
107 AssertReturn(autoInitSpan.isOk(), E_FAIL);
108
109 m = new Data();
110
111 int vrc = RTCrX509Certificate_Clone(&m->X509, a_pCert, &g_RTAsn1DefaultAllocator);
112 if (RT_SUCCESS(vrc))
113 {
114 m->fValidX509 = true;
115 m->fTrusted = a_fTrusted;
116 m->fExpired = a_fExpired;
117 autoInitSpan.setSucceeded();
118 }
119 else
120 rc = Global::vboxStatusCodeToCOM(vrc);
121
122 LogFlowThisFunc(("returns rc=%Rhrc\n", rc));
123 return rc;
124}
125
126void Certificate::uninit()
127{
128 /* Enclose the state transition Ready->InUninit->NotReady */
129 AutoUninitSpan autoUninitSpan(this);
130 if (autoUninitSpan.uninitDone())
131 return;
132
133 delete m;
134 m = NULL;
135}
136
137
138/** @name Wrapped ICertificate properties
139 * @{
140 */
141
142HRESULT Certificate::getVersionNumber(CertificateVersion_T *aVersionNumber)
143{
144 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
145
146 Assert(m->fValidX509);
147 switch (m->X509.TbsCertificate.T0.Version.uValue.u)
148 {
149 case RTCRX509TBSCERTIFICATE_V1: *aVersionNumber = CertificateVersion_V1; break;
150 case RTCRX509TBSCERTIFICATE_V2: *aVersionNumber = CertificateVersion_V2; break;
151 case RTCRX509TBSCERTIFICATE_V3: *aVersionNumber = CertificateVersion_V3; break;
152 default: AssertFailed(); *aVersionNumber = CertificateVersion_Unknown; break;
153 }
154 return S_OK;
155}
156
157HRESULT Certificate::getSerialNumber(com::Utf8Str &aSerialNumber)
158{
159 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
160
161 Assert(m->fValidX509);
162
163 char szTmp[_2K];
164 int vrc = RTAsn1Integer_ToString(&m->X509.TbsCertificate.SerialNumber, szTmp, sizeof(szTmp), 0, NULL);
165 if (RT_SUCCESS(vrc))
166 aSerialNumber = szTmp;
167 else
168 return Global::vboxStatusCodeToCOM(vrc);
169
170 return S_OK;
171}
172
173HRESULT Certificate::getSignatureAlgorithmOID(com::Utf8Str &aSignatureAlgorithmOID)
174{
175 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
176
177 Assert(m->fValidX509);
178 aSignatureAlgorithmOID = m->X509.TbsCertificate.Signature.Algorithm.szObjId;
179
180 return S_OK;
181}
182
183HRESULT Certificate::getSignatureAlgorithmName(com::Utf8Str &aSignatureAlgorithmName)
184{
185 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
186
187 Assert(m->fValidX509);
188 return i_getAlgorithmName(&m->X509.TbsCertificate.Signature, aSignatureAlgorithmName);
189}
190
191HRESULT Certificate::getIssuerName(std::vector<com::Utf8Str> &aIssuerName)
192{
193 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
194
195 Assert(m->fValidX509);
196 return i_getX509Name(&m->X509.TbsCertificate.Issuer, aIssuerName);
197}
198
199HRESULT Certificate::getSubjectName(std::vector<com::Utf8Str> &aSubjectName)
200{
201 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
202
203 Assert(m->fValidX509);
204 return i_getX509Name(&m->X509.TbsCertificate.Subject, aSubjectName);
205}
206
207HRESULT Certificate::getFriendlyName(com::Utf8Str &aFriendlyName)
208{
209 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
210
211 Assert(m->fValidX509);
212
213 PCRTCRX509NAME pName = &m->X509.TbsCertificate.Subject;
214
215 /*
216 * Enumerate the subject name and pick interesting attributes we can use to
217 * form a name more friendly than the RTCrX509Name_FormatAsString output.
218 */
219 const char *pszOrg = NULL;
220 const char *pszOrgUnit = NULL;
221 const char *pszGivenName = NULL;
222 const char *pszSurname = NULL;
223 const char *pszEmail = NULL;
224 for (uint32_t i = 0; i < pName->cItems; i++)
225 {
226 PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = pName->papItems[i];
227 for (uint32_t j = 0; j < pRdn->cItems; j++)
228 {
229 PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = pRdn->papItems[j];
230 AssertContinue(pComponent->Value.enmType == RTASN1TYPE_STRING);
231
232 /* Select interesting components based on the short RDN prefix
233 string (easier to read and write than OIDs, for now). */
234 const char *pszPrefix = RTCrX509Name_GetShortRdn(&pComponent->Type);
235 if (pszPrefix)
236 {
237 const char *pszUtf8;
238 int vrc = RTAsn1String_QueryUtf8(&pComponent->Value.u.String, &pszUtf8, NULL);
239 if (RT_SUCCESS(vrc) && *pszUtf8)
240 {
241 if (!strcmp(pszPrefix, "Email"))
242 pszEmail = pszUtf8;
243 else if (!strcmp(pszPrefix, "O"))
244 pszOrg = pszUtf8;
245 else if (!strcmp(pszPrefix, "OU"))
246 pszOrgUnit = pszUtf8;
247 else if (!strcmp(pszPrefix, "S"))
248 pszSurname = pszUtf8;
249 else if (!strcmp(pszPrefix, "G"))
250 pszGivenName = pszUtf8;
251 }
252 }
253 }
254 }
255
256 if (pszGivenName && pszSurname)
257 {
258 if (pszEmail)
259 aFriendlyName = Utf8StrFmt("%s, %s <%s>", pszSurname, pszGivenName, pszEmail);
260 else if (pszOrg)
261 aFriendlyName = Utf8StrFmt("%s, %s (%s)", pszSurname, pszGivenName, pszOrg);
262 else if (pszOrgUnit)
263 aFriendlyName = Utf8StrFmt("%s, %s (%s)", pszSurname, pszGivenName, pszOrgUnit);
264 else
265 aFriendlyName = Utf8StrFmt("%s, %s", pszSurname, pszGivenName);
266 }
267 else if (pszOrg && pszOrgUnit)
268 aFriendlyName = Utf8StrFmt("%s, %s", pszOrg, pszOrgUnit);
269 else if (pszOrg)
270 aFriendlyName = Utf8StrFmt("%s", pszOrg);
271 else if (pszOrgUnit)
272 aFriendlyName = Utf8StrFmt("%s", pszOrgUnit);
273 else
274 {
275 /*
276 * Fall back on unfriendly but accurate.
277 */
278 char szTmp[_8K];
279 RT_ZERO(szTmp);
280 RTCrX509Name_FormatAsString(pName, szTmp, sizeof(szTmp) - 1, NULL);
281 aFriendlyName = szTmp;
282 }
283
284 return S_OK;
285}
286
287HRESULT Certificate::getValidityPeriodNotBefore(com::Utf8Str &aValidityPeriodNotBefore)
288{
289 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
290
291 Assert(m->fValidX509);
292 return i_getTime(&m->X509.TbsCertificate.Validity.NotBefore, aValidityPeriodNotBefore);
293}
294
295HRESULT Certificate::getValidityPeriodNotAfter(com::Utf8Str &aValidityPeriodNotAfter)
296{
297 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
298
299 Assert(m->fValidX509);
300 return i_getTime(&m->X509.TbsCertificate.Validity.NotAfter, aValidityPeriodNotAfter);
301}
302
303HRESULT Certificate::getPublicKeyAlgorithmOID(com::Utf8Str &aPublicKeyAlgorithmOID)
304{
305 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
306
307 Assert(m->fValidX509);
308 aPublicKeyAlgorithmOID = m->X509.TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm.szObjId;
309 return S_OK;
310}
311
312HRESULT Certificate::getPublicKeyAlgorithm(com::Utf8Str &aPublicKeyAlgorithm)
313{
314 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
315
316 Assert(m->fValidX509);
317 return i_getAlgorithmName(&m->X509.TbsCertificate.SubjectPublicKeyInfo.Algorithm, aPublicKeyAlgorithm);
318}
319
320HRESULT Certificate::getSubjectPublicKey(std::vector<BYTE> &aSubjectPublicKey)
321{
322
323 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Getting encoded ASN.1 bytes may make changes to X509. */
324 return i_getEncodedBytes(&m->X509.TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey.Asn1Core, aSubjectPublicKey);
325}
326
327HRESULT Certificate::getIssuerUniqueIdentifier(com::Utf8Str &aIssuerUniqueIdentifier)
328{
329 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
330
331 return i_getUniqueIdentifier(&m->X509.TbsCertificate.T1.IssuerUniqueId, aIssuerUniqueIdentifier);
332}
333
334HRESULT Certificate::getSubjectUniqueIdentifier(com::Utf8Str &aSubjectUniqueIdentifier)
335{
336 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
337
338 return i_getUniqueIdentifier(&m->X509.TbsCertificate.T2.SubjectUniqueId, aSubjectUniqueIdentifier);
339}
340
341HRESULT Certificate::getCertificateAuthority(BOOL *aCertificateAuthority)
342{
343 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
344
345 *aCertificateAuthority = m->X509.TbsCertificate.T3.pBasicConstraints
346 && m->X509.TbsCertificate.T3.pBasicConstraints->CA.fValue;
347
348 return S_OK;
349}
350
351HRESULT Certificate::getKeyUsage(ULONG *aKeyUsage)
352{
353 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
354
355 *aKeyUsage = m->X509.TbsCertificate.T3.fKeyUsage;
356 return S_OK;
357}
358
359HRESULT Certificate::getExtendedKeyUsage(std::vector<com::Utf8Str> &aExtendedKeyUsage)
360{
361 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
362 NOREF(aExtendedKeyUsage);
363 return E_NOTIMPL;
364}
365
366HRESULT Certificate::getRawCertData(std::vector<BYTE> &aRawCertData)
367{
368 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Getting encoded ASN.1 bytes may make changes to X509. */
369 return i_getEncodedBytes(&m->X509.SeqCore.Asn1Core, aRawCertData);
370}
371
372HRESULT Certificate::getSelfSigned(BOOL *aSelfSigned)
373{
374 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
375
376 Assert(m->fValidX509);
377 *aSelfSigned = RTCrX509Certificate_IsSelfSigned(&m->X509);
378
379 return S_OK;
380}
381
382HRESULT Certificate::getTrusted(BOOL *aTrusted)
383{
384 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
385
386 Assert(m->fValidX509);
387 *aTrusted = m->fTrusted;
388
389 return S_OK;
390}
391
392HRESULT Certificate::getExpired(BOOL *aExpired)
393{
394 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
395 Assert(m->fValidX509);
396 *aExpired = m->fExpired;
397 return S_OK;
398}
399
400/** @} */
401
402/** @name Wrapped ICertificate methods
403 * @{
404 */
405
406HRESULT Certificate::isCurrentlyExpired(BOOL *aResult)
407{
408 AssertReturnStmt(m->fValidX509, *aResult = TRUE, E_UNEXPECTED);
409 RTTIMESPEC Now;
410 *aResult = RTCrX509Validity_IsValidAtTimeSpec(&m->X509.TbsCertificate.Validity, RTTimeNow(&Now)) ? FALSE : TRUE;
411 return S_OK;
412}
413
414HRESULT Certificate::queryInfo(LONG aWhat, com::Utf8Str &aResult)
415{
416 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
417 /* Insurance. */
418 NOREF(aResult);
419 return setError(E_FAIL, "Unknown item %u", aWhat);
420}
421
422/** @} */
423
424
425/** @name Methods extracting COM data from the certificate object
426 * @{
427 */
428
429/**
430 * Translates an algorithm OID into a human readable string, if possible.
431 *
432 * @returns S_OK.
433 * @param a_pAlgId The algorithm.
434 * @param a_rReturn The return string value.
435 * @throws std::bad_alloc
436 */
437HRESULT Certificate::i_getAlgorithmName(PCRTCRX509ALGORITHMIDENTIFIER a_pAlgId, com::Utf8Str &a_rReturn)
438{
439 const char *pszOid = a_pAlgId->Algorithm.szObjId;
440 const char *pszName;
441 if (!pszOid) pszName = "";
442 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_RSA)) pszName = "rsaEncryption";
443 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD2_WITH_RSA)) pszName = "md2WithRSAEncryption";
444 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD4_WITH_RSA)) pszName = "md4WithRSAEncryption";
445 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD5_WITH_RSA)) pszName = "md5WithRSAEncryption";
446 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA1_WITH_RSA)) pszName = "sha1WithRSAEncryption";
447 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA224_WITH_RSA)) pszName = "sha224WithRSAEncryption";
448 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA256_WITH_RSA)) pszName = "sha256WithRSAEncryption";
449 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA384_WITH_RSA)) pszName = "sha384WithRSAEncryption";
450 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512_WITH_RSA)) pszName = "sha512WithRSAEncryption";
451 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224_WITH_RSA)) pszName = "sha512-224WithRSAEncryption";
452 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256_WITH_RSA)) pszName = "sha512-256WithRSAEncryption";
453 else
454 pszName = pszOid;
455 a_rReturn = pszName;
456 return S_OK;
457}
458
459/**
460 * Formats a X.509 name into a string array.
461 *
462 * The name is prefix with a short hand of the relative distinguished name
463 * type followed by an equal sign.
464 *
465 * @returns S_OK.
466 * @param a_pName The X.509 name.
467 * @param a_rReturn The return string array.
468 * @throws std::bad_alloc
469 */
470HRESULT Certificate::i_getX509Name(PCRTCRX509NAME a_pName, std::vector<com::Utf8Str> &a_rReturn)
471{
472 if (RTCrX509Name_IsPresent(a_pName))
473 {
474 for (uint32_t i = 0; i < a_pName->cItems; i++)
475 {
476 PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = a_pName->papItems[i];
477 for (uint32_t j = 0; j < pRdn->cItems; j++)
478 {
479 PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = pRdn->papItems[j];
480
481 AssertReturn(pComponent->Value.enmType == RTASN1TYPE_STRING,
482 setErrorVrc(VERR_CR_X509_NAME_NOT_STRING, "VERR_CR_X509_NAME_NOT_STRING"));
483
484 /* Get the prefix for this name component. */
485 const char *pszPrefix = RTCrX509Name_GetShortRdn(&pComponent->Type);
486 AssertStmt(pszPrefix, pszPrefix = pComponent->Type.szObjId);
487
488 /* Get the string. */
489 const char *pszUtf8;
490 int vrc = RTAsn1String_QueryUtf8(&pComponent->Value.u.String, &pszUtf8, NULL /*pcch*/);
491 AssertRCReturn(vrc, setErrorVrc(vrc, "RTAsn1String_QueryUtf8(%u/%u,,) -> %Rrc", i, j, vrc));
492
493 a_rReturn.push_back(Utf8StrFmt("%s=%s", pszPrefix, pszUtf8));
494 }
495 }
496 }
497 return S_OK;
498}
499
500/**
501 * Translates an ASN.1 timestamp into an ISO timestamp string.
502 *
503 * @returns S_OK.
504 * @param a_pTime The timestamp
505 * @param a_rReturn The return string value.
506 * @throws std::bad_alloc
507 */
508HRESULT Certificate::i_getTime(PCRTASN1TIME a_pTime, com::Utf8Str &a_rReturn)
509{
510 char szTmp[128];
511 if (RTTimeToString(&a_pTime->Time, szTmp, sizeof(szTmp)))
512 {
513 a_rReturn = szTmp;
514 return S_OK;
515 }
516 AssertFailed();
517 return E_FAIL;
518}
519
520/**
521 * Translates a X.509 unique identifier to a string.
522 *
523 * @returns S_OK.
524 * @param a_pUniqueId The unique identifier.
525 * @param a_rReturn The return string value.
526 * @throws std::bad_alloc
527 */
528HRESULT Certificate::i_getUniqueIdentifier(PCRTCRX509UNIQUEIDENTIFIER a_pUniqueId, com::Utf8Str &a_rReturn)
529{
530 /* The a_pUniqueId may not be present! */
531 if (RTCrX509UniqueIdentifier_IsPresent(a_pUniqueId))
532 {
533 void const *pvData = RTASN1BITSTRING_GET_BIT0_PTR(a_pUniqueId);
534 size_t const cbData = RTASN1BITSTRING_GET_BYTE_SIZE(a_pUniqueId);
535 size_t const cbFormatted = cbData * 3 - 1 + 1;
536 a_rReturn.reserve(cbFormatted); /* throws */
537 int vrc = RTStrPrintHexBytes(a_rReturn.mutableRaw(), cbFormatted, pvData, cbData, RTSTRPRINTHEXBYTES_F_SEP_COLON);
538 a_rReturn.jolt();
539 AssertRCReturn(vrc, Global::vboxStatusCodeToCOM(vrc));
540 }
541 else
542 Assert(a_rReturn.isEmpty());
543 return S_OK;
544}
545
546/**
547 * Translates any ASN.1 object into a (DER encoded) byte array.
548 *
549 * @returns S_OK.
550 * @param a_pAsn1Obj The ASN.1 object to get the DER encoded bytes for.
551 * @param a_rReturn The return byte vector.
552 * @throws std::bad_alloc
553 */
554HRESULT Certificate::i_getEncodedBytes(PRTASN1CORE a_pAsn1Obj, std::vector<BYTE> &a_rReturn)
555{
556 HRESULT hrc = S_OK;
557 Assert(a_rReturn.size() == 0);
558 if (RTAsn1Core_IsPresent(a_pAsn1Obj))
559 {
560 uint32_t cbEncoded;
561 int vrc = RTAsn1EncodePrepare(a_pAsn1Obj, 0, &cbEncoded, NULL);
562 if (RT_SUCCESS(vrc))
563 {
564 a_rReturn.resize(cbEncoded);
565 Assert(a_rReturn.size() == cbEncoded);
566 if (cbEncoded)
567 {
568 vrc = RTAsn1EncodeToBuffer(a_pAsn1Obj, 0, &a_rReturn.front(), a_rReturn.size(), NULL);
569 if (RT_FAILURE(vrc))
570 hrc = setErrorVrc(vrc, "RTAsn1EncodeToBuffer failed with %Rrc", vrc);
571 }
572 }
573 else
574 hrc = setErrorVrc(vrc, "RTAsn1EncodePrepare failed with %Rrc", vrc);
575 }
576 return hrc;
577}
578
579/** @} */
580
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use