VirtualBox

Ignore:
Timestamp:
Jul 8, 2023 11:10:51 AM (19 months ago)
Author:
vboxsync
Message:

IPRT,OpenSSL: Support ECDSA for verficiation purposes when IPRT links with OpenSSL. This required quite a bit of cleanups, so not entirely no-risk. bugref:10479 ticketref:21621

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Runtime/common/crypto/x509-core.cpp

    r100364 r100442  
    4545#include <iprt/string.h>
    4646#include <iprt/uni.h>
     47#include <iprt/crypto/pkix.h>
     48#ifdef RT_STRICT
     49# include <iprt/crypto/digest.h>
     50#endif
    4751
    4852#include "x509-internal.h"
     53
     54
     55/*********************************************************************************************************************************
     56*   Internal Functions                                                                                                           *
     57*********************************************************************************************************************************/
     58#ifdef RT_STRICT
     59static void rtCrX509AlgorithmIdentifier_AssertTableSanityAndMore(void);
     60#endif
    4961
    5062
     
    7385 */
    7486
    75 RTDECL(RTDIGESTTYPE) RTCrX509AlgorithmIdentifier_QueryDigestType(PCRTCRX509ALGORITHMIDENTIFIER pThis)
     87/**
     88 * String table with the encryption OIDs (used by g_aSignatureOidInfo).
     89 */
     90static const char * const g_apszEncryptionOids[] =
     91{
     92    NULL,
     93#define IDX_ENCRYPTION_NIL      0
     94    RTCR_X962_ECDSA_OID,
     95#define IDX_ENCRYPTION_ECDSA    1
     96    RTCR_PKCS1_RSA_OID,
     97#define IDX_ENCRYPTION_RSA      2
     98};
     99
     100/**
     101 * Information about an algorithm identifier.
     102 */
     103typedef struct RTCRX509ALGORITHIDENTIFIERINTERNALINFO
     104{
     105    /** The signature OID. */
     106    const char     *pszSignatureOid;
     107    /** Index into g_apszEncryptionOids of the encryption ODI.
     108     * This is IDX_ENCRYPTION_NIL for hashes. */
     109    uint8_t         idxEncryption;
     110    /** The message digest type specified by the OID.
     111     * This is set to RTDIGESTTYPE_INVALID in two cases:
     112     *      1. Pure encryption algorithm OID (cBitsDigest also zero).
     113     *      2. The hash is so esoteric that IPRT doesn't support it. */
     114    uint8_t         enmDigestType;
     115    /** The digest size in bits.
     116     * This is ZERO if the OID does not include an hash. */
     117    uint16_t        cBitsDigest;
     118} RTCRX509ALGORITHIDENTIFIERINTERNALINFO;
     119typedef RTCRX509ALGORITHIDENTIFIERINTERNALINFO const *PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO;
     120
     121/**
     122 * Signature to encryption OID.
     123 *
     124 * @note This is sorted to allow binary searching.
     125 * @note This origins in pkix-utils.cpp, which is why it uses the other set of
     126 *       OID defines.
     127 */
     128static RTCRX509ALGORITHIDENTIFIERINTERNALINFO const g_aSignatureOidInfo[] =
     129{
     130    { /*1.0.10118.3.0.55*/     RTCRX509ALGORITHMIDENTIFIERID_WHIRLPOOL, IDX_ENCRYPTION_NIL,   RTDIGESTTYPE_INVALID,    512, },
     131
     132    { /*1.2.840.10045.2.1    */     RTCR_X962_ECDSA_OID,                IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_INVALID,    0, },
     133    { /*1.2.840.10045.4.1    */     RTCR_X962_ECDSA_WITH_SHA1_OID,      IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA1,       160, },
     134    { /*1.2.840.10045.4.3.1  */     RTCR_X962_ECDSA_WITH_SHA224_OID,    IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA224,     224, },
     135    { /*1.2.840.10045.4.3.2  */     RTCR_X962_ECDSA_WITH_SHA256_OID,    IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA256,     256, },
     136    { /*1.2.840.10045.4.3.3  */     RTCR_X962_ECDSA_WITH_SHA384_OID,    IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA384,     384, },
     137    { /*1.2.840.10045.4.3.4  */     RTCR_X962_ECDSA_WITH_SHA512_OID,    IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA512,     512, },
     138
     139    { /*1.2.840.113549.1.1.1 */     RTCR_PKCS1_RSA_OID,                 IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_INVALID,    0, },
     140    { /*1.2.840.113549.1.1.11*/     RTCR_PKCS1_SHA256_WITH_RSA_OID,     IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_SHA256,     256, },
     141    { /*1.2.840.113549.1.1.12*/     RTCR_PKCS1_SHA384_WITH_RSA_OID,     IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_SHA384,     384, },
     142    { /*1.2.840.113549.1.1.13*/     RTCR_PKCS1_SHA512_WITH_RSA_OID,     IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_SHA512,     512, },
     143    { /*1.2.840.113549.1.1.14*/     RTCR_PKCS1_SHA224_WITH_RSA_OID,     IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_SHA224,     224, },
     144    { /*1.2.840.113549.1.1.15*/     RTCR_PKCS1_SHA512T224_WITH_RSA_OID, IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_SHA512T224, 224, },
     145    { /*1.2.840.113549.1.1.16*/     RTCR_PKCS1_SHA512T256_WITH_RSA_OID, IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_SHA512T256, 256, },
     146    { /*1.2.840.113549.1.1.2*/      RTCR_PKCS1_MD2_WITH_RSA_OID,        IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_MD2,        128, },
     147    { /*1.2.840.113549.1.1.3*/      RTCR_PKCS1_MD4_WITH_RSA_OID,        IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_MD4,        128, },
     148    { /*1.2.840.113549.1.1.4*/      RTCR_PKCS1_MD5_WITH_RSA_OID,        IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_MD5,        128, },
     149    { /*1.2.840.113549.1.1.5*/      RTCR_PKCS1_SHA1_WITH_RSA_OID,       IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_SHA1,       160, },
     150
     151    { /*1.2.840.113549.2.2*/        RTCRX509ALGORITHMIDENTIFIERID_MD2,  IDX_ENCRYPTION_NIL,   RTDIGESTTYPE_MD2,        128, },
     152    { /*1.2.840.113549.2.4*/        RTCRX509ALGORITHMIDENTIFIERID_MD4,  IDX_ENCRYPTION_NIL,   RTDIGESTTYPE_MD4,        128, },
     153    { /*1.2.840.113549.2.5*/        RTCRX509ALGORITHMIDENTIFIERID_MD5,  IDX_ENCRYPTION_NIL,   RTDIGESTTYPE_MD5,        128, },
     154
     155    /* oddballs for which we don't support the padding (skip?): */
     156  //{  "1.3.14.3.2.11"                                /*rsaSignature*/, IDX_ENCRYPTION_RSA/*?*/, RTDIGESTTYPE_INVALID, 0, },
     157    {  "1.3.14.3.2.14"      /*mdc2WithRSASignature w/ 9796-2 padding*/, IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_INVALID,    0, },
     158  //{  "1.3.14.3.2.15"      /*sha0WithRSASignature w/ 9796-2 padding*/, IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_INVALID,    160, },
     159    {  "1.3.14.3.2.24"       /*md2WithRSASignature w/ 9796-2 padding*/, IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_MD2,        128, },
     160    {  "1.3.14.3.2.25"       /*md5WithRSASignature w/ 9796-2 padding*/, IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_MD5,        128, },
     161    { /*1.3.14.3.2.26*/             RTCRX509ALGORITHMIDENTIFIERID_SHA1, IDX_ENCRYPTION_NIL,   RTDIGESTTYPE_SHA1,       160, },
     162    {  "1.3.14.3.2.29"           /*sha1WithRSAEncryption (obsolete?)*/, IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_SHA1,       160, },
     163
     164    { /*2.16.840.1.101.3.4.2.1*/  RTCRX509ALGORITHMIDENTIFIERID_SHA256, IDX_ENCRYPTION_NIL,   RTDIGESTTYPE_SHA256,     256, },
     165    { /*2.16.840.1.101.3.4.2.10*/ RTCRX509ALGORITHMIDENTIFIERID_SHA3_512, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA3_512,   512, },
     166    { /*2.16.840.1.101.3.4.2.2*/  RTCRX509ALGORITHMIDENTIFIERID_SHA384, IDX_ENCRYPTION_NIL,   RTDIGESTTYPE_SHA384,     384, },
     167    { /*2.16.840.1.101.3.4.2.3*/  RTCRX509ALGORITHMIDENTIFIERID_SHA512, IDX_ENCRYPTION_NIL,   RTDIGESTTYPE_SHA512,     512, },
     168    { /*2.16.840.1.101.3.4.2.4*/  RTCRX509ALGORITHMIDENTIFIERID_SHA224, IDX_ENCRYPTION_NIL,   RTDIGESTTYPE_SHA224,     224, },
     169    { /*2.16.840.1.101.3.4.2.5*/ RTCRX509ALGORITHMIDENTIFIERID_SHA512T224,IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA512T224, 224, },
     170    { /*2.16.840.1.101.3.4.2.6*/ RTCRX509ALGORITHMIDENTIFIERID_SHA512T256,IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA512T256, 256, },
     171    { /*2.16.840.1.101.3.4.2.7*/  RTCRX509ALGORITHMIDENTIFIERID_SHA3_224, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA3_224,   224, },
     172    { /*2.16.840.1.101.3.4.2.8*/  RTCRX509ALGORITHMIDENTIFIERID_SHA3_256, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA3_256,   256, },
     173    { /*2.16.840.1.101.3.4.2.9*/  RTCRX509ALGORITHMIDENTIFIERID_SHA3_384, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA3_384,   384, },
     174
     175    { /*2.16.840.1.101.3.4.3.10*/   RTCR_NIST_SHA3_256_WITH_ECDSA_OID,  IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA3_256,   256, },
     176    { /*2.16.840.1.101.3.4.3.11*/   RTCR_NIST_SHA3_384_WITH_ECDSA_OID,  IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA3_384,   384, },
     177    { /*2.16.840.1.101.3.4.3.12*/   RTCR_NIST_SHA3_512_WITH_ECDSA_OID,  IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA3_512,   512, },
     178    { /*2.16.840.1.101.3.4.3.13*/   RTCR_NIST_SHA3_224_WITH_RSA_OID,    IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_SHA3_224,   224, },
     179    { /*2.16.840.1.101.3.4.3.14*/   RTCR_NIST_SHA3_256_WITH_RSA_OID,    IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_SHA3_256,   256, },
     180    { /*2.16.840.1.101.3.4.3.15*/   RTCR_NIST_SHA3_384_WITH_RSA_OID,    IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_SHA3_384,   384, },
     181    { /*2.16.840.1.101.3.4.3.16*/   RTCR_NIST_SHA3_512_WITH_RSA_OID,    IDX_ENCRYPTION_RSA,   RTDIGESTTYPE_SHA3_512,   512, },
     182    { /*2.16.840.1.101.3.4.3.9*/    RTCR_NIST_SHA3_224_WITH_ECDSA_OID,  IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA3_224,   224, },
     183};
     184
     185/**
     186 * Encryption and digest combining.
     187 *
     188 * This is a subset of g_aSignatureOidInfo.
     189 * @todo Organize this more efficiently...
     190 */
     191typedef struct RTCRX509ALGORITHIDENTIFIERCOMBINING
     192{
     193    const char *pszDigestOid;
     194    const char *pszEncryptedDigestOid;
     195} RTCRX509ALGORITHIDENTIFIERCOMBINING;
     196typedef RTCRX509ALGORITHIDENTIFIERCOMBINING const *PCRTCRX509ALGORITHIDENTIFIERCOMBINING;
     197
     198#undef MY_COMBINE
     199#define MY_COMBINE(a_Encrypt, a_Digest) \
     200        { RTCRX509ALGORITHMIDENTIFIERID_ ## a_Digest, \
     201          RTCRX509ALGORITHMIDENTIFIERID_ ## a_Digest ## _WITH_ ## a_Encrypt }
     202
     203/** Digest and encryption combinations for ECDSA. */
     204static RTCRX509ALGORITHIDENTIFIERCOMBINING const g_aDigestAndEncryptionEcdsa[] =
     205{
     206    MY_COMBINE(ECDSA, SHA1),
     207    MY_COMBINE(ECDSA, SHA224),
     208    MY_COMBINE(ECDSA, SHA256),
     209    MY_COMBINE(ECDSA, SHA384),
     210    MY_COMBINE(ECDSA, SHA512),
     211    MY_COMBINE(ECDSA, SHA3_224),
     212    MY_COMBINE(ECDSA, SHA3_256),
     213    MY_COMBINE(ECDSA, SHA3_384),
     214    MY_COMBINE(ECDSA, SHA3_512),
     215};
     216
     217/** Digest and encryption combinations for RSA. */
     218static RTCRX509ALGORITHIDENTIFIERCOMBINING const g_aDigestAndEncryptionRsa[] =
     219{
     220    MY_COMBINE(RSA, SHA1),
     221    MY_COMBINE(RSA, SHA256),
     222    MY_COMBINE(RSA, SHA512),
     223    MY_COMBINE(RSA, SHA384),
     224    MY_COMBINE(RSA, MD5),
     225    MY_COMBINE(RSA, MD2),
     226    MY_COMBINE(RSA, MD4),
     227    MY_COMBINE(RSA, SHA224),
     228    MY_COMBINE(RSA, SHA512T224),
     229    MY_COMBINE(RSA, SHA512T256),
     230    MY_COMBINE(RSA, SHA3_224),
     231    MY_COMBINE(RSA, SHA3_256),
     232    MY_COMBINE(RSA, SHA3_384),
     233    MY_COMBINE(RSA, SHA3_512),
     234};
     235
     236#undef MY_COMBINE
     237
     238/**
     239 * Table running parallel to g_apszEncryptionOids.
     240 */
     241static struct
     242{
     243    PCRTCRX509ALGORITHIDENTIFIERCOMBINING   paCombinations;
     244    size_t                                  cCombinations;
     245} const g_aDigestAndEncryption[] =
     246{
     247    /* [IDX_ENCRYPTION_NIL]   = */ { NULL, 0 },
     248    /* [IDX_ENCRYPTION_ECDSA] = */ { &g_aDigestAndEncryptionEcdsa[0], RT_ELEMENTS(g_aDigestAndEncryptionEcdsa) },
     249    /* [IDX_ENCRYPTION_RSA]   = */ { &g_aDigestAndEncryptionRsa[0],   RT_ELEMENTS(g_aDigestAndEncryptionRsa) },
     250};
     251AssertCompile(IDX_ENCRYPTION_NIL == 0 && IDX_ENCRYPTION_ECDSA == 1 && IDX_ENCRYPTION_RSA == 2);
     252
     253
     254/**
     255 * Looks up info we've got on a algorithm identifier.
     256 */
     257static PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO rtCrX509AlgorithmIdentifier_LookupInfoByOid(const char *pszSignatureOid)
     258{
     259#ifdef RT_STRICT
     260    /*
     261     * Do internal santiy checking on first call.
     262     */
     263    static bool volatile s_fChecked = false;
     264    if (RT_LIKELY(s_fChecked))
     265    { /* likely */ }
     266    else
     267    {
     268        s_fChecked = true; /* Must be set before the call, as the callee will calls us again. */
     269        rtCrX509AlgorithmIdentifier_AssertTableSanityAndMore();
     270    }
     271#endif
     272
     273    /*
     274     * Do a binary search of g_aSignatureOidInfo.
     275     */
     276    size_t iFirst = 0;
     277    size_t iEnd   = RT_ELEMENTS(g_aSignatureOidInfo);
     278    for (;;)
     279    {
     280        size_t const i     = iFirst + (iEnd - iFirst) / 2;
     281        int const    iDiff = strcmp(pszSignatureOid, g_aSignatureOidInfo[i].pszSignatureOid);
     282        if (iDiff < 0)
     283        {
     284            if (i > iFirst)
     285                iEnd = i;
     286            else
     287                return NULL;
     288        }
     289        else if (iDiff > 0)
     290        {
     291            if (i + 1 < iEnd)
     292                iFirst = i + 1;
     293            else
     294                return NULL;
     295        }
     296        else
     297            return &g_aSignatureOidInfo[i];
     298    }
     299}
     300
     301#ifdef RT_STRICT
     302/**
     303 * Check that the g_aSignatureOidInfo and g_aDigestAndEncryption makes sense and
     304 * matches up with one another and other IPRT information sources.
     305 */
     306static void rtCrX509AlgorithmIdentifier_AssertTableSanityAndMore(void)
     307{
     308    /* Check that binary searching work and that digest info matches up: */
     309    for (size_t i = 1; i < RT_ELEMENTS(g_aSignatureOidInfo); i++)
     310        Assert(strcmp(g_aSignatureOidInfo[i].pszSignatureOid, g_aSignatureOidInfo[i - 1].pszSignatureOid) > 0);
     311    for (size_t i = 0; i < RT_ELEMENTS(g_aSignatureOidInfo); i++)
     312    {
     313        PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pInfo
     314            = rtCrX509AlgorithmIdentifier_LookupInfoByOid(g_aSignatureOidInfo[i].pszSignatureOid);
     315        Assert(pInfo && pInfo->pszSignatureOid == g_aSignatureOidInfo[i].pszSignatureOid);
     316
     317        /* If the digest type is RTDIGESTTYPE_INVALID, we must have an pure encryption entry or an obscure hash function. */
     318        if (g_aSignatureOidInfo[i].enmDigestType != RTDIGESTTYPE_INVALID)
     319            Assert(   RTCrDigestTypeToHashSize((RTDIGESTTYPE)g_aSignatureOidInfo[i].enmDigestType) * 8
     320                   == g_aSignatureOidInfo[i].cBitsDigest);
     321        else
     322            Assert(g_aSignatureOidInfo[i].cBitsDigest == 0 || g_aSignatureOidInfo[i].idxEncryption == IDX_ENCRYPTION_NIL);
     323
     324# ifdef IN_RING3
     325        /* Check with the RTCrDigestFindByObjIdString API: */
     326        RTDIGESTTYPE enmDigestType2 = (RTDIGESTTYPE)g_aSignatureOidInfo[i].enmDigestType;
     327#  if defined(IPRT_WITHOUT_DIGEST_MD2) || defined(IPRT_WITHOUT_DIGEST_MD4) || defined(IPRT_WITHOUT_DIGEST_MD5) \
     328|| defined(IPRT_WITHOUT_SHA512T224) || defined(IPRT_WITHOUT_SHA512T256) || defined(IPRT_WITHOUT_SHA3)
     329        switch (enmDigestType2)
     330        {
     331            default: break;
     332#   ifdef IPRT_WITHOUT_DIGEST_MD2
     333            case RTDIGESTTYPE_MD2:
     334#   endif
     335#   ifdef IPRT_WITHOUT_DIGEST_MD4
     336            case RTDIGESTTYPE_MD4:
     337#   endif
     338#   ifdef IPRT_WITHOUT_DIGEST_MD5
     339            case RTDIGESTTYPE_MD5:
     340#   endif
     341#   ifdef IPRT_WITHOUT_SHA512T224
     342            case RTDIGESTTYPE_SHA512T224:
     343#   endif
     344#   ifdef IPRT_WITHOUT_SHA512T256
     345            case RTDIGESTTYPE_SHA512T256:
     346#   endif
     347#   ifdef IPRT_WITHOUT_SHA3
     348            case RTDIGESTTYPE_SHA3_224:
     349            case RTDIGESTTYPE_SHA3_256:
     350            case RTDIGESTTYPE_SHA3_384:
     351            case RTDIGESTTYPE_SHA3_512:
     352#   endif
     353                enmDigestType2 = RTDIGESTTYPE_INVALID;
     354                break;
     355        }
     356#  endif
     357        PCRTCRDIGESTDESC const pDigestDesc = RTCrDigestFindByObjIdString(g_aSignatureOidInfo[i].pszSignatureOid,
     358                                                                         NULL /*ppvOpaque*/);
     359        if (pDigestDesc)
     360        {
     361            AssertMsg(pDigestDesc->enmType == enmDigestType2,
     362                      ("%s pDigestDesc=%s enmDigestType2=%s\n",  g_aSignatureOidInfo[i].pszSignatureOid,
     363                       RTCrDigestTypeToName(pDigestDesc->enmType), RTCrDigestTypeToName(enmDigestType2)));
     364            Assert(pDigestDesc->cbHash * 8 == g_aSignatureOidInfo[i].cBitsDigest);
     365        }
     366        else
     367            AssertMsg(enmDigestType2 == RTDIGESTTYPE_INVALID,
     368                      ("%s enmDigestType2=%s\n", g_aSignatureOidInfo[i].pszSignatureOid, RTCrDigestTypeToName(enmDigestType2)));
     369# endif /* IN_RING3 */
     370
     371
     372# ifdef IN_RING3
     373        /* Look it up the encryption descriptor. */
     374        const char * const pszCheckEncryptId = g_apszEncryptionOids[g_aSignatureOidInfo[i].idxEncryption];
     375        PCRTCRPKIXSIGNATUREDESC const pSigDesc = RTCrPkixSignatureFindByObjIdString(g_aSignatureOidInfo[i].pszSignatureOid,
     376                                                                                    NULL /*ppvOpaque*/);
     377        if (pSigDesc)
     378            Assert(pszCheckEncryptId && strcmp(pSigDesc->pszObjId, pszCheckEncryptId) == 0);
     379#  ifdef IPRT_WITH_OPENSSL /* No ECDSA implementation w/o OpenSSL at the moment. */
     380        else
     381            AssertMsg(!pSigDesc && pInfo->idxEncryption == IDX_ENCRYPTION_NIL, ("%s\n", g_aSignatureOidInfo[i].pszSignatureOid));
     382#  endif
     383# endif /* IN_RING3 */
     384    }
     385
     386    /*
     387     * Check that everything in g_aDigestAndEncryption is resolvable here and that the info matches up.
     388     */
     389    for (size_t idxEncryption = IDX_ENCRYPTION_NIL; idxEncryption < RT_ELEMENTS(g_aDigestAndEncryption); idxEncryption++)
     390    {
     391        PCRTCRX509ALGORITHIDENTIFIERCOMBINING const paCombinations = g_aDigestAndEncryption[idxEncryption].paCombinations;
     392        size_t const                                cCombinations  = g_aDigestAndEncryption[idxEncryption].cCombinations;
     393        for (size_t i = 0; i < cCombinations; i++)
     394        {
     395            PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pInfo
     396                = rtCrX509AlgorithmIdentifier_LookupInfoByOid(paCombinations[i].pszEncryptedDigestOid);
     397            AssertContinue(pInfo);
     398            Assert(strcmp(paCombinations[i].pszEncryptedDigestOid, pInfo->pszSignatureOid) == 0);
     399            Assert(pInfo->idxEncryption == idxEncryption);
     400            Assert(strcmp(paCombinations[i].pszDigestOid,
     401                          RTCrDigestTypeToAlgorithmOid((RTDIGESTTYPE)pInfo->enmDigestType)) == 0);
     402        }
     403    }
     404}
     405#endif /* RT_STRICT */
     406
     407
     408RTDECL(RTDIGESTTYPE) RTCrX509AlgorithmIdentifier_GetDigestType(PCRTCRX509ALGORITHMIDENTIFIER pThis, bool fPureDigestsOnly)
    76409{
    77410    AssertPtrReturn(pThis, RTDIGESTTYPE_INVALID);
    78     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_MD5))
    79         return RTDIGESTTYPE_MD5;
    80     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA1))
    81         return RTDIGESTTYPE_SHA1;
    82     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA256))
    83         return RTDIGESTTYPE_SHA256;
    84     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA512))
    85         return RTDIGESTTYPE_SHA512;
    86 
    87     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA384))
    88         return RTDIGESTTYPE_SHA384;
    89     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA224))
    90         return RTDIGESTTYPE_SHA224;
    91     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224))
    92         return RTDIGESTTYPE_SHA512T224;
    93     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256))
    94         return RTDIGESTTYPE_SHA512T256;
    95 
    96     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_224))
    97         return RTDIGESTTYPE_SHA3_224;
    98     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_256))
    99         return RTDIGESTTYPE_SHA3_256;
    100     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_384))
    101         return RTDIGESTTYPE_SHA3_384;
    102     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_512))
    103         return RTDIGESTTYPE_SHA3_512;
    104     return RTDIGESTTYPE_INVALID;
    105 }
    106 
    107 
    108 RTDECL(uint32_t) RTCrX509AlgorithmIdentifier_QueryDigestSize(PCRTCRX509ALGORITHMIDENTIFIER pThis)
     411    PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pInfo = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pThis->Algorithm.szObjId);
     412    return pInfo && (!fPureDigestsOnly || pInfo->idxEncryption == IDX_ENCRYPTION_NIL)
     413         ? (RTDIGESTTYPE)pInfo->enmDigestType : RTDIGESTTYPE_INVALID;
     414}
     415
     416
     417RTDECL(uint32_t) RTCrX509AlgorithmIdentifier_GetDigestSize(PCRTCRX509ALGORITHMIDENTIFIER pThis, bool fPureDigestsOnly)
    109418{
    110419    AssertPtrReturn(pThis, UINT32_MAX);
    111 
    112     /* common */
    113     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_MD5))
    114         return 128 / 8;
    115     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA1))
    116         return 160 / 8;
    117     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA256))
    118         return 256 / 8;
    119     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA512))
    120         return 512 / 8;
    121 
    122     /* Less common. */
    123     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_MD2))
    124         return 128 / 8;
    125     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_MD4))
    126         return 128 / 8;
    127     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA384))
    128         return 384 / 8;
    129     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA224))
    130         return 224 / 8;
    131     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224))
    132         return 224 / 8;
    133     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256))
    134         return 256 / 8;
    135     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_224))
    136         return 224 / 8;
    137     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_256))
    138         return 256 / 8;
    139     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_384))
    140         return 384 / 8;
    141     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_512))
    142         return 512 / 8;
    143     if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_WHIRLPOOL))
    144         return 512 / 8;
    145 
    146     return UINT32_MAX;
     420    PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pInfo = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pThis->Algorithm.szObjId);
     421    return pInfo && (!fPureDigestsOnly || pInfo->idxEncryption == IDX_ENCRYPTION_NIL)
     422         ? pInfo->cBitsDigest / 8 : UINT32_MAX;
    147423}
    148424
     
    157433                                                                              const char *pszEncryptedDigestOid)
    158434{
    159     /* common */
    160     if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD5))
    161     {
    162         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD5_WITH_RSA))
    163             return 0;
    164     }
    165     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA1))
    166     {
    167         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA1_WITH_RSA))
    168             return 0;
    169     }
    170     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA256))
    171     {
    172         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA256_WITH_RSA))
    173             return 0;
    174     }
    175     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512))
    176     {
    177         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512_WITH_RSA))
    178             return 0;
    179     }
    180     /* Less common. */
    181     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD2))
    182     {
    183         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD2_WITH_RSA))
    184             return 0;
    185     }
    186     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD4))
    187     {
    188         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD4_WITH_RSA))
    189             return 0;
    190     }
    191     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA384))
    192     {
    193         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA384_WITH_RSA))
    194             return 0;
    195     }
    196     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA224))
    197     {
    198         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA224_WITH_RSA))
    199             return 0;
    200     }
    201     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224))
    202     {
    203         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224_WITH_RSA))
    204             return 0;
    205     }
    206     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256))
    207     {
    208         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256_WITH_RSA))
    209             return 0;
    210     }
    211     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_224))
    212     {
    213         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_224_WITH_RSA))
    214             return 0;
    215     }
    216     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_256))
    217     {
    218         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_256_WITH_RSA))
    219             return 0;
    220     }
    221     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_384))
    222     {
    223         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_384_WITH_RSA))
    224             return 0;
    225     }
    226     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_512))
    227     {
    228         if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_512_WITH_RSA))
    229             return 0;
    230     }
    231     else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_WHIRLPOOL))
    232     {
    233         /* ?? */
    234     }
    235     else
    236         return -1;
    237     return 1;
     435    /*
     436     * Lookup the digest and encrypted digest OIDs.
     437     */
     438    PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pDigest  = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pszDigestOid);
     439    AssertMsgReturn(pDigest,                                      ("pszDigestOid=%s\n", pszDigestOid), -1);
     440    AssertMsgReturn(pDigest->idxEncryption == IDX_ENCRYPTION_NIL, ("pszDigestOid=%s\n", pszDigestOid), -1);
     441    AssertMsgReturn(pDigest->cBitsDigest   != 0,                  ("pszDigestOid=%s\n", pszDigestOid), -1);
     442
     443    PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pEncrypt = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pszEncryptedDigestOid);
     444    AssertMsgReturn(pEncrypt,                                        ("pszEncryptedDigestOid=%s\n", pszEncryptedDigestOid), 1);
     445    AssertMsgReturn(pEncrypt->idxEncryption != IDX_ENCRYPTION_NIL,   ("pszEncryptedDigestOid=%s\n", pszEncryptedDigestOid), 1);
     446    AssertMsgReturn(pEncrypt->enmDigestType != RTDIGESTTYPE_INVALID, ("pszEncryptedDigestOid=%s\n", pszEncryptedDigestOid), 1);
     447
     448    return pDigest->enmDigestType == pEncrypt->enmDigestType ? 0 : 1;
    238449}
    239450
     
    249460                                                                                  const char *pszDigestOid)
    250461{
    251     /* RSA: */
    252     if (!strcmp(pszEncryptionOid, RTCRX509ALGORITHMIDENTIFIERID_RSA))
    253     {
    254         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD5)
    255             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD5_WITH_RSA))
    256             return RTCRX509ALGORITHMIDENTIFIERID_MD5_WITH_RSA;
    257         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA1)
    258             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA1_WITH_RSA))
    259             return RTCRX509ALGORITHMIDENTIFIERID_SHA1_WITH_RSA;
    260         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA256)
    261             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA256_WITH_RSA))
    262             return RTCRX509ALGORITHMIDENTIFIERID_SHA256_WITH_RSA;
    263         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512)
    264             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512_WITH_RSA))
    265             return RTCRX509ALGORITHMIDENTIFIERID_SHA512_WITH_RSA;
    266         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD2)
    267             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD2_WITH_RSA))
    268             return RTCRX509ALGORITHMIDENTIFIERID_MD2_WITH_RSA;
    269         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD4)
    270             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD4_WITH_RSA))
    271             return RTCRX509ALGORITHMIDENTIFIERID_MD4_WITH_RSA;
    272         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA384)
    273             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA384_WITH_RSA))
    274             return RTCRX509ALGORITHMIDENTIFIERID_SHA384_WITH_RSA;
    275         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA224)
    276             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA224_WITH_RSA))
    277             return RTCRX509ALGORITHMIDENTIFIERID_SHA224_WITH_RSA;
    278         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224)
    279             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224_WITH_RSA))
    280             return RTCRX509ALGORITHMIDENTIFIERID_SHA512T224_WITH_RSA;
    281         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256)
    282             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256_WITH_RSA))
    283             return RTCRX509ALGORITHMIDENTIFIERID_SHA512T256_WITH_RSA;
    284         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_224)
    285             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_224_WITH_RSA))
    286             return RTCRX509ALGORITHMIDENTIFIERID_SHA3_224_WITH_RSA;
    287         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_256)
    288             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_256_WITH_RSA))
    289             return RTCRX509ALGORITHMIDENTIFIERID_SHA3_256_WITH_RSA;
    290         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_384)
    291             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_384_WITH_RSA))
    292             return RTCRX509ALGORITHMIDENTIFIERID_SHA3_384_WITH_RSA;
    293         if (   !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_512)
    294             || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_512_WITH_RSA))
    295             return RTCRX509ALGORITHMIDENTIFIERID_SHA3_512_WITH_RSA;
    296 
    297         /* if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_WHIRLPOOL))
    298             return ???; */
    299     }
    300     else if (RTCrX509AlgorithmIdentifier_CompareDigestOidAndEncryptedDigestOid(pszDigestOid, pszEncryptionOid) == 0)
    301         return pszEncryptionOid;
    302 
    303     AssertMsgFailed(("enc=%s hash=%s\n", pszEncryptionOid, pszDigestOid));
     462    /*
     463     * We can look up the two OIDs and see what they actually are.
     464     */
     465    /* The digest OID should be a pure hash algorithm, however we also accept
     466       the already combined algorithm. */
     467    PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pDigest  = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pszDigestOid);
     468    AssertReturn(pDigest, NULL);
     469    AssertReturn(pDigest->enmDigestType != RTDIGESTTYPE_INVALID, NULL);
     470
     471    /* The encryption OID should be a pure encryption algorithm, however we
     472       also accept the already combined algorithm. */
     473    PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pEncrypt = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pszEncryptionOid);
     474    AssertReturn(pEncrypt, NULL);
     475    uint8_t const idxEncryption = pEncrypt->idxEncryption;
     476    AssertReturn(idxEncryption != IDX_ENCRYPTION_NIL, NULL);
     477    Assert(idxEncryption < RT_ELEMENTS(g_aDigestAndEncryption));
     478
     479    /* Is the encryption OID purely encryption? */
     480    if (pEncrypt->cBitsDigest == 0)
     481    {
     482        Assert(pEncrypt->enmDigestType == RTDIGESTTYPE_INVALID);
     483
     484        /* Identify the slice of the table related to this encryption OID: */
     485        PCRTCRX509ALGORITHIDENTIFIERCOMBINING const paCombinations = g_aDigestAndEncryption[idxEncryption].paCombinations;
     486        size_t                                const cCombinations  = g_aDigestAndEncryption[idxEncryption].cCombinations;
     487
     488        /* Is the digest OID purely a digest? */
     489        if (pDigest->idxEncryption == IDX_ENCRYPTION_NIL)
     490        {
     491            for (size_t i = 0; i < cCombinations; i++)
     492                if (!strcmp(pszDigestOid, paCombinations[i].pszDigestOid))
     493                    return paCombinations[i].pszEncryptedDigestOid;
     494            AssertMsgFailed(("enc=%s hash=%s\n", pszEncryptionOid, pszDigestOid));
     495        }
     496        else
     497        {
     498            /* No, it's a combined one. */
     499            for (size_t i = 0; i < cCombinations; i++)
     500                if (!strcmp(pszDigestOid, paCombinations[i].pszEncryptedDigestOid))
     501                    return paCombinations[i].pszEncryptedDigestOid;
     502            AssertMsgFailed(("enc=%s hash+enc=%s\n", pszEncryptionOid, pszDigestOid));
     503        }
     504    }
     505    /* The digest OID purely a digest? */
     506    else if (pDigest->idxEncryption == IDX_ENCRYPTION_NIL)
     507    {
     508        /* Check that it's for the same hash before returning it. */
     509        Assert(pEncrypt->enmDigestType != RTDIGESTTYPE_INVALID);
     510        if (pEncrypt->enmDigestType == pDigest->enmDigestType)
     511            return pEncrypt->pszSignatureOid;
     512        AssertMsgFailed(("enc+hash=%s hash=%s\n", pszEncryptionOid, pszDigestOid));
     513    }
     514    /* Both the digest and encryption OIDs are combined ones, so they have to
     515       be the same entry then or they cannot be combined. */
     516    else if (pDigest == pEncrypt)
     517        return pEncrypt->pszSignatureOid;
     518    else
     519        AssertMsgFailed(("enc+hash=%s hash+enc=%s\n", pszEncryptionOid, pszDigestOid));
     520
    304521    return NULL;
    305522}
     
    311528    return RTCrX509AlgorithmIdentifier_CombineEncryptionOidAndDigestOid(pEncryption->Algorithm.szObjId,
    312529                                                                        pDigest->Algorithm.szObjId);
     530}
     531
     532
     533RTDECL(const char *) RTCrX509AlgorithmIdentifier_GetEncryptionOid(PCRTCRX509ALGORITHMIDENTIFIER pThis, bool fMustIncludeHash)
     534{
     535    AssertPtrReturn(pThis, NULL);
     536    PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pInfo = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pThis->Algorithm.szObjId);
     537    if (pInfo && (!fMustIncludeHash || pInfo->enmDigestType != RTDIGESTTYPE_INVALID))
     538        return g_apszEncryptionOids[pInfo->idxEncryption];
     539    return NULL;
     540}
     541
     542
     543RTDECL(const char *) RTCrX509AlgorithmIdentifier_GetEncryptionOidFromOid(const char *pszAlgorithmOid, bool fMustIncludeHash)
     544{
     545    AssertPtrReturn(pszAlgorithmOid, NULL);
     546    PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pInfo = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pszAlgorithmOid);
     547    if (pInfo && (!fMustIncludeHash || pInfo->enmDigestType != RTDIGESTTYPE_INVALID))
     548        return g_apszEncryptionOids[pInfo->idxEncryption];
     549    return NULL;
    313550}
    314551
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette