/* $Id: CertificateImpl.cpp $ */ /** @file * ICertificate COM class implementations. */ /* * Copyright (C) 2008-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ #define LOG_GROUP LOG_GROUP_MAIN_CERTIFICATE #include #include #include #include #include #include "ProgressImpl.h" #include "CertificateImpl.h" #include "AutoCaller.h" #include "Global.h" #include "LoggingNew.h" using namespace std; /** * Private instance data for the #Certificate class. * @see Certificate::m */ struct Certificate::Data { Data() : fTrusted(false) , fExpired(false) , fValidX509(false) { RT_ZERO(X509); } ~Data() { if (fValidX509) { RTCrX509Certificate_Delete(&X509); RT_ZERO(X509); fValidX509 = false; } } /** Whether the certificate is trusted. */ bool fTrusted; /** Whether the certificate is trusted. */ bool fExpired; /** Valid data in mX509. */ bool fValidX509; /** Clone of the X.509 certificate. */ RTCRX509CERTIFICATE X509; private: Data(const Certificate::Data &rTodo) { AssertFailed(); NOREF(rTodo); } Data &operator=(const Certificate::Data &rTodo) { AssertFailed(); NOREF(rTodo); return *this; } }; /////////////////////////////////////////////////////////////////////////////////// // // Certificate constructor / destructor // // //////////////////////////////////////////////////////////////////////////////// DEFINE_EMPTY_CTOR_DTOR(Certificate) HRESULT Certificate::FinalConstruct() { return BaseFinalConstruct(); } void Certificate::FinalRelease() { uninit(); BaseFinalRelease(); } /** * Initializes a certificate instance. * * @returns COM status code. * @param a_pCert The certificate. * @param a_fTrusted Whether the caller trusts the certificate or not. * @param a_fExpired Whether the caller consideres the certificate to be * expired. */ HRESULT Certificate::initCertificate(PCRTCRX509CERTIFICATE a_pCert, bool a_fTrusted, bool a_fExpired) { HRESULT hrc = S_OK; LogFlowThisFuncEnter(); AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), E_FAIL); m = new Data(); int vrc = RTCrX509Certificate_Clone(&m->X509, a_pCert, &g_RTAsn1DefaultAllocator); if (RT_SUCCESS(vrc)) { m->fValidX509 = true; m->fTrusted = a_fTrusted; m->fExpired = a_fExpired; autoInitSpan.setSucceeded(); } else hrc = Global::vboxStatusCodeToCOM(vrc); LogFlowThisFunc(("returns hrc=%Rhrc\n", hrc)); return hrc; } void Certificate::uninit() { /* Enclose the state transition Ready->InUninit->NotReady */ AutoUninitSpan autoUninitSpan(this); if (autoUninitSpan.uninitDone()) return; delete m; m = NULL; } /** @name Wrapped ICertificate properties * @{ */ HRESULT Certificate::getVersionNumber(CertificateVersion_T *aVersionNumber) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); switch (m->X509.TbsCertificate.T0.Version.uValue.u) { case RTCRX509TBSCERTIFICATE_V1: *aVersionNumber = CertificateVersion_V1; break; case RTCRX509TBSCERTIFICATE_V2: *aVersionNumber = CertificateVersion_V2; break; case RTCRX509TBSCERTIFICATE_V3: *aVersionNumber = CertificateVersion_V3; break; default: AssertFailed(); *aVersionNumber = CertificateVersion_Unknown; break; } return S_OK; } HRESULT Certificate::getSerialNumber(com::Utf8Str &aSerialNumber) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); char szTmp[_2K]; int vrc = RTAsn1Integer_ToString(&m->X509.TbsCertificate.SerialNumber, szTmp, sizeof(szTmp), 0, NULL); if (RT_SUCCESS(vrc)) aSerialNumber = szTmp; else return Global::vboxStatusCodeToCOM(vrc); return S_OK; } HRESULT Certificate::getSignatureAlgorithmOID(com::Utf8Str &aSignatureAlgorithmOID) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); aSignatureAlgorithmOID = m->X509.TbsCertificate.Signature.Algorithm.szObjId; return S_OK; } HRESULT Certificate::getSignatureAlgorithmName(com::Utf8Str &aSignatureAlgorithmName) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); return i_getAlgorithmName(&m->X509.TbsCertificate.Signature, aSignatureAlgorithmName); } HRESULT Certificate::getIssuerName(std::vector &aIssuerName) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); return i_getX509Name(&m->X509.TbsCertificate.Issuer, aIssuerName); } HRESULT Certificate::getSubjectName(std::vector &aSubjectName) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); return i_getX509Name(&m->X509.TbsCertificate.Subject, aSubjectName); } HRESULT Certificate::getFriendlyName(com::Utf8Str &aFriendlyName) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); PCRTCRX509NAME pName = &m->X509.TbsCertificate.Subject; /* * Enumerate the subject name and pick interesting attributes we can use to * form a name more friendly than the RTCrX509Name_FormatAsString output. */ const char *pszOrg = NULL; const char *pszOrgUnit = NULL; const char *pszGivenName = NULL; const char *pszSurname = NULL; const char *pszEmail = NULL; for (uint32_t i = 0; i < pName->cItems; i++) { PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = pName->papItems[i]; for (uint32_t j = 0; j < pRdn->cItems; j++) { PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = pRdn->papItems[j]; AssertContinue(pComponent->Value.enmType == RTASN1TYPE_STRING); /* Select interesting components based on the short RDN prefix string (easier to read and write than OIDs, for now). */ const char *pszPrefix = RTCrX509Name_GetShortRdn(&pComponent->Type); if (pszPrefix) { const char *pszUtf8; int vrc = RTAsn1String_QueryUtf8(&pComponent->Value.u.String, &pszUtf8, NULL); if (RT_SUCCESS(vrc) && *pszUtf8) { if (!strcmp(pszPrefix, "Email")) pszEmail = pszUtf8; else if (!strcmp(pszPrefix, "O")) pszOrg = pszUtf8; else if (!strcmp(pszPrefix, "OU")) pszOrgUnit = pszUtf8; else if (!strcmp(pszPrefix, "S")) pszSurname = pszUtf8; else if (!strcmp(pszPrefix, "G")) pszGivenName = pszUtf8; } } } } if (pszGivenName && pszSurname) { if (pszEmail) aFriendlyName = Utf8StrFmt("%s, %s <%s>", pszSurname, pszGivenName, pszEmail); else if (pszOrg) aFriendlyName = Utf8StrFmt("%s, %s (%s)", pszSurname, pszGivenName, pszOrg); else if (pszOrgUnit) aFriendlyName = Utf8StrFmt("%s, %s (%s)", pszSurname, pszGivenName, pszOrgUnit); else aFriendlyName = Utf8StrFmt("%s, %s", pszSurname, pszGivenName); } else if (pszOrg && pszOrgUnit) aFriendlyName = Utf8StrFmt("%s, %s", pszOrg, pszOrgUnit); else if (pszOrg) aFriendlyName = Utf8StrFmt("%s", pszOrg); else if (pszOrgUnit) aFriendlyName = Utf8StrFmt("%s", pszOrgUnit); else { /* * Fall back on unfriendly but accurate. */ char szTmp[_8K]; RT_ZERO(szTmp); RTCrX509Name_FormatAsString(pName, szTmp, sizeof(szTmp) - 1, NULL); aFriendlyName = szTmp; } return S_OK; } HRESULT Certificate::getValidityPeriodNotBefore(com::Utf8Str &aValidityPeriodNotBefore) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); return i_getTime(&m->X509.TbsCertificate.Validity.NotBefore, aValidityPeriodNotBefore); } HRESULT Certificate::getValidityPeriodNotAfter(com::Utf8Str &aValidityPeriodNotAfter) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); return i_getTime(&m->X509.TbsCertificate.Validity.NotAfter, aValidityPeriodNotAfter); } HRESULT Certificate::getPublicKeyAlgorithmOID(com::Utf8Str &aPublicKeyAlgorithmOID) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); aPublicKeyAlgorithmOID = m->X509.TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm.szObjId; return S_OK; } HRESULT Certificate::getPublicKeyAlgorithm(com::Utf8Str &aPublicKeyAlgorithm) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); return i_getAlgorithmName(&m->X509.TbsCertificate.SubjectPublicKeyInfo.Algorithm, aPublicKeyAlgorithm); } HRESULT Certificate::getSubjectPublicKey(std::vector &aSubjectPublicKey) { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Getting encoded ASN.1 bytes may make changes to X509. */ return i_getEncodedBytes(&m->X509.TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey.Asn1Core, aSubjectPublicKey); } HRESULT Certificate::getIssuerUniqueIdentifier(com::Utf8Str &aIssuerUniqueIdentifier) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); return i_getUniqueIdentifier(&m->X509.TbsCertificate.T1.IssuerUniqueId, aIssuerUniqueIdentifier); } HRESULT Certificate::getSubjectUniqueIdentifier(com::Utf8Str &aSubjectUniqueIdentifier) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); return i_getUniqueIdentifier(&m->X509.TbsCertificate.T2.SubjectUniqueId, aSubjectUniqueIdentifier); } HRESULT Certificate::getCertificateAuthority(BOOL *aCertificateAuthority) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); *aCertificateAuthority = m->X509.TbsCertificate.T3.pBasicConstraints && m->X509.TbsCertificate.T3.pBasicConstraints->CA.fValue; return S_OK; } HRESULT Certificate::getKeyUsage(ULONG *aKeyUsage) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); *aKeyUsage = m->X509.TbsCertificate.T3.fKeyUsage; return S_OK; } HRESULT Certificate::getExtendedKeyUsage(std::vector &aExtendedKeyUsage) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); NOREF(aExtendedKeyUsage); return E_NOTIMPL; } HRESULT Certificate::getRawCertData(std::vector &aRawCertData) { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Getting encoded ASN.1 bytes may make changes to X509. */ return i_getEncodedBytes(&m->X509.SeqCore.Asn1Core, aRawCertData); } HRESULT Certificate::getSelfSigned(BOOL *aSelfSigned) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); *aSelfSigned = RTCrX509Certificate_IsSelfSigned(&m->X509); return S_OK; } HRESULT Certificate::getTrusted(BOOL *aTrusted) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); *aTrusted = m->fTrusted; return S_OK; } HRESULT Certificate::getExpired(BOOL *aExpired) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Assert(m->fValidX509); *aExpired = m->fExpired; return S_OK; } /** @} */ /** @name Wrapped ICertificate methods * @{ */ HRESULT Certificate::isCurrentlyExpired(BOOL *aResult) { AssertReturnStmt(m->fValidX509, *aResult = TRUE, E_UNEXPECTED); RTTIMESPEC Now; *aResult = RTCrX509Validity_IsValidAtTimeSpec(&m->X509.TbsCertificate.Validity, RTTimeNow(&Now)) ? FALSE : TRUE; return S_OK; } HRESULT Certificate::queryInfo(LONG aWhat, com::Utf8Str &aResult) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); /* Insurance. */ NOREF(aResult); return setError(E_FAIL, tr("Unknown item %u"), aWhat); } /** @} */ /** @name Methods extracting COM data from the certificate object * @{ */ /** * Translates an algorithm OID into a human readable string, if possible. * * @returns S_OK. * @param a_pAlgId The algorithm. * @param a_rReturn The return string value. * @throws std::bad_alloc */ HRESULT Certificate::i_getAlgorithmName(PCRTCRX509ALGORITHMIDENTIFIER a_pAlgId, com::Utf8Str &a_rReturn) { const char *pszOid = a_pAlgId->Algorithm.szObjId; const char *pszName; if (!pszOid) pszName = ""; else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_RSA)) pszName = "rsaEncryption"; else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD2_WITH_RSA)) pszName = "md2WithRSAEncryption"; else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD4_WITH_RSA)) pszName = "md4WithRSAEncryption"; else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD5_WITH_RSA)) pszName = "md5WithRSAEncryption"; else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA1_WITH_RSA)) pszName = "sha1WithRSAEncryption"; else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA224_WITH_RSA)) pszName = "sha224WithRSAEncryption"; else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA256_WITH_RSA)) pszName = "sha256WithRSAEncryption"; else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA384_WITH_RSA)) pszName = "sha384WithRSAEncryption"; else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512_WITH_RSA)) pszName = "sha512WithRSAEncryption"; else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224_WITH_RSA)) pszName = "sha512-224WithRSAEncryption"; else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256_WITH_RSA)) pszName = "sha512-256WithRSAEncryption"; else pszName = pszOid; a_rReturn = pszName; return S_OK; } /** * Formats a X.509 name into a string array. * * The name is prefix with a short hand of the relative distinguished name * type followed by an equal sign. * * @returns S_OK. * @param a_pName The X.509 name. * @param a_rReturn The return string array. * @throws std::bad_alloc */ HRESULT Certificate::i_getX509Name(PCRTCRX509NAME a_pName, std::vector &a_rReturn) { if (RTCrX509Name_IsPresent(a_pName)) { for (uint32_t i = 0; i < a_pName->cItems; i++) { PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = a_pName->papItems[i]; for (uint32_t j = 0; j < pRdn->cItems; j++) { PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = pRdn->papItems[j]; AssertReturn(pComponent->Value.enmType == RTASN1TYPE_STRING, setErrorVrc(VERR_CR_X509_NAME_NOT_STRING, "VERR_CR_X509_NAME_NOT_STRING")); /* Get the prefix for this name component. */ const char *pszPrefix = RTCrX509Name_GetShortRdn(&pComponent->Type); AssertStmt(pszPrefix, pszPrefix = pComponent->Type.szObjId); /* Get the string. */ const char *pszUtf8; int vrc = RTAsn1String_QueryUtf8(&pComponent->Value.u.String, &pszUtf8, NULL /*pcch*/); AssertRCReturn(vrc, setErrorVrc(vrc, "RTAsn1String_QueryUtf8(%u/%u,,) -> %Rrc", i, j, vrc)); a_rReturn.push_back(Utf8StrFmt("%s=%s", pszPrefix, pszUtf8)); } } } return S_OK; } /** * Translates an ASN.1 timestamp into an ISO timestamp string. * * @returns S_OK. * @param a_pTime The timestamp * @param a_rReturn The return string value. * @throws std::bad_alloc */ HRESULT Certificate::i_getTime(PCRTASN1TIME a_pTime, com::Utf8Str &a_rReturn) { char szTmp[128]; if (RTTimeToString(&a_pTime->Time, szTmp, sizeof(szTmp))) { a_rReturn = szTmp; return S_OK; } AssertFailed(); return E_FAIL; } /** * Translates a X.509 unique identifier to a string. * * @returns S_OK. * @param a_pUniqueId The unique identifier. * @param a_rReturn The return string value. * @throws std::bad_alloc */ HRESULT Certificate::i_getUniqueIdentifier(PCRTCRX509UNIQUEIDENTIFIER a_pUniqueId, com::Utf8Str &a_rReturn) { /* The a_pUniqueId may not be present! */ if (RTCrX509UniqueIdentifier_IsPresent(a_pUniqueId)) { void const *pvData = RTASN1BITSTRING_GET_BIT0_PTR(a_pUniqueId); size_t const cbData = RTASN1BITSTRING_GET_BYTE_SIZE(a_pUniqueId); size_t const cbFormatted = cbData * 3 - 1 + 1; a_rReturn.reserve(cbFormatted); /* throws */ int vrc = RTStrPrintHexBytes(a_rReturn.mutableRaw(), cbFormatted, pvData, cbData, RTSTRPRINTHEXBYTES_F_SEP_COLON); a_rReturn.jolt(); AssertRCReturn(vrc, Global::vboxStatusCodeToCOM(vrc)); } else Assert(a_rReturn.isEmpty()); return S_OK; } /** * Translates any ASN.1 object into a (DER encoded) byte array. * * @returns S_OK. * @param a_pAsn1Obj The ASN.1 object to get the DER encoded bytes for. * @param a_rReturn The return byte vector. * @throws std::bad_alloc */ HRESULT Certificate::i_getEncodedBytes(PRTASN1CORE a_pAsn1Obj, std::vector &a_rReturn) { HRESULT hrc = S_OK; Assert(a_rReturn.size() == 0); if (RTAsn1Core_IsPresent(a_pAsn1Obj)) { uint32_t cbEncoded; int vrc = RTAsn1EncodePrepare(a_pAsn1Obj, 0, &cbEncoded, NULL); if (RT_SUCCESS(vrc)) { a_rReturn.resize(cbEncoded); Assert(a_rReturn.size() == cbEncoded); if (cbEncoded) { vrc = RTAsn1EncodeToBuffer(a_pAsn1Obj, 0, &a_rReturn.front(), a_rReturn.size(), NULL); if (RT_FAILURE(vrc)) hrc = setErrorVrc(vrc, tr("RTAsn1EncodeToBuffer failed with %Rrc"), vrc); } } else hrc = setErrorVrc(vrc, tr("RTAsn1EncodePrepare failed with %Rrc"), vrc); } return hrc; } /** @} */