Repository: wolfSSL/wolfssl
Pull request #8758: Added new ASN X509 API: `wc_GetSubjectPubKeyInfoDerFromCert`
Author: login: bigbrett
Created: 2025-05-09T18:34:55Z | Merged: 2025-05-14T19:28:46Z

# Description

Adds a new ASN API function to export the FULL public key from an X509, including subject public key info (SPKI). Currently we have `wc_GetPubKeyDerFromCert()` but this is only the raw public key DER fields and does not include SPKI headers.

Need something like this for wolfHSM cert management.

# Testing

Added test to api.c

# Checklist

 - [x] added tests
 - [x] updated/added doxygen
 - [x] updated appropriate READMEs
 - [] Updated manual and documentation



Diff:
diff --git a/tests/api.c b/tests/api.c
index e67f38f7d55..60db1b4fb4d 100644
--- a/tests/api.c
+++ b/tests/api.c
@@ -22755,6 +22755,151 @@ static int test_wc_GetPubKeyDerFromCert(void)
     return EXPECT_RESULT();
 }
 
+static int test_wc_GetSubjectPubKeyInfoDerFromCert(void)
+{
+    EXPECT_DECLS;
+#if !defined(NO_RSA) || defined(HAVE_ECC)
+    int ret;
+    word32 idx = 0;
+    byte keyDer[TWOK_BUF];  /* large enough for up to RSA 2048 */
+    word32 keyDerSz = (word32)sizeof(keyDer);
+#if !defined(NO_RSA) && defined(WOLFSSL_CERT_REQ) && !defined(NO_FILESYSTEM)
+    byte certBuf[6000]; /* for PEM and CSR, client-cert.pem is 5-6kB */
+    word32 certBufSz = sizeof(certBuf);
+#endif
+#if ((!defined(USE_CERT_BUFFERS_2048) && !defined(USE_CERT_BUFFERS_1024)) || \
+     defined(WOLFSSL_CERT_REQ)) && !defined(NO_RSA) && !defined(NO_FILESYSTEM)
+    XFILE fp = XBADFILE;
+#endif
+#ifndef NO_RSA
+    RsaKey rsaKey;
+    #if defined(USE_CERT_BUFFERS_2048)
+        byte* rsaCertDer = (byte*)client_cert_der_2048;
+        word32 rsaCertDerSz = sizeof_client_cert_der_2048;
+    #elif defined(USE_CERT_BUFFERS_1024)
+        byte* rsaCertDer = (byte*)client_cert_der_1024;
+        word32 rsaCertDerSz = sizeof_client_cert_der_1024;
+    #else
+        unsigned char rsaCertDer[TWOK_BUF];
+        word32 rsaCertDerSz;
+    #endif
+#endif
+#ifdef HAVE_ECC
+    ecc_key eccKey;
+    #if defined(USE_CERT_BUFFERS_256)
+        byte* eccCert = (byte*)cliecc_cert_der_256;
+        word32 eccCertSz = sizeof_cliecc_cert_der_256;
+    #else
+        unsigned char eccCert[ONEK_BUF];
+        word32 eccCertSz;
+        XFILE fp2 = XBADFILE;
+    #endif
+#endif
+
+#ifndef NO_RSA
+
+#if !defined(USE_CERT_BUFFERS_1024) && !defined(USE_CERT_BUFFERS_2048)
+    ExpectTrue((fp = XFOPEN("./certs/1024/client-cert.der", "rb")) != XBADFILE);
+    ExpectIntGT(rsaCertDerSz = (word32)XFREAD(rsaCertDer, 1, sizeof(rsaCertDer),
+        fp), 0);
+    if (fp != XBADFILE) {
+        XFCLOSE(fp);
+        fp = XBADFILE;
+    }
+#endif
+
+    /* good test case - RSA DER cert */
+    ExpectIntEQ(wc_GetSubjectPubKeyInfoDerFromCert(rsaCertDer, rsaCertDerSz,
+                                                   keyDer, &keyDerSz), 0);
+    ExpectIntGT(keyDerSz, 0);
+
+    /* sanity check, verify we can import DER public key */
+    ret = wc_InitRsaKey(&rsaKey, HEAP_HINT);
+    ExpectIntEQ(ret, 0);
+    ExpectIntEQ(wc_RsaPublicKeyDecode(keyDer, &idx, &rsaKey, keyDerSz), 0);
+    if (ret == 0) {
+        wc_FreeRsaKey(&rsaKey);
+    }
+
+    /* bad args: certDer */
+    keyDerSz = (word32)sizeof(keyDer);
+    ExpectIntEQ(wc_GetSubjectPubKeyInfoDerFromCert(NULL, rsaCertDerSz, keyDer,
+                                                   &keyDerSz),
+                WC_NO_ERR_TRACE(BAD_FUNC_ARG));
+
+    /* bad args: 0 sized certSz */
+    keyDerSz = (word32)sizeof(keyDer);
+    ExpectIntEQ(wc_GetSubjectPubKeyInfoDerFromCert(rsaCertDer, 0, keyDer,
+                                                   &keyDerSz),
+                WC_NO_ERR_TRACE(BAD_FUNC_ARG));
+
+    /* bad args: NULL inout size */
+    ExpectIntEQ(ret = wc_GetSubjectPubKeyInfoDerFromCert(rsaCertDer,
+                                                         rsaCertDerSz, keyDer,
+                                                         NULL),
+                WC_NO_ERR_TRACE(BAD_FUNC_ARG));
+
+    /* Certificate Request Tests */
+    #if defined(WOLFSSL_CERT_REQ) && !defined(NO_FILESYSTEM)
+    {
+        XMEMSET(certBuf, 0, sizeof(certBuf));
+        ExpectTrue((fp = XFOPEN("./certs/csr.signed.der", "rb")) != XBADFILE);
+        ExpectIntGT(certBufSz = (word32)XFREAD(certBuf, 1, certBufSz, fp), 0);
+        if (fp != XBADFILE) {
+            XFCLOSE(fp);
+        }
+
+        /* good test case - RSA DER certificate request */
+        keyDerSz = sizeof(keyDer);
+        ExpectIntEQ(ret = wc_GetSubjectPubKeyInfoDerFromCert(rsaCertDer,
+                                                             rsaCertDerSz,
+                                                             keyDer,
+                                                             &keyDerSz), 0);
+        ExpectIntGT(keyDerSz, 0);
+
+        /* sanity check, verify we can import DER public key */
+        ret = wc_InitRsaKey(&rsaKey, HEAP_HINT);
+        ExpectIntEQ(ret, 0);
+        idx = 0;
+        ExpectIntEQ(wc_RsaPublicKeyDecode(keyDer, &idx, &rsaKey, keyDerSz), 0);
+        if (ret == 0) {
+            wc_FreeRsaKey(&rsaKey);
+        }
+    }
+    #endif /* WOLFSSL_CERT_REQ */
+#endif /* NO_RSA */
+
+#ifdef HAVE_ECC
+    #ifndef USE_CERT_BUFFERS_256
+        ExpectTrue((fp2 = XFOPEN("./certs/client-ecc-cert.der", "rb")) !=
+            XBADFILE);
+        ExpectIntGT(eccCertSz = (word32)XFREAD(eccCert, 1, ONEK_BUF, fp2), 0);
+        if (fp2 != XBADFILE) {
+            XFCLOSE(fp2);
+        }
+    #endif
+
+    /* good test case - ECC */
+    XMEMSET(keyDer, 0, sizeof(keyDer));
+    keyDerSz = sizeof(keyDer);
+    ExpectIntEQ(wc_GetSubjectPubKeyInfoDerFromCert(eccCert, eccCertSz, keyDer,
+                                                   &keyDerSz), 0);
+    ExpectIntGT(keyDerSz, 0);
+
+    /* sanity check, verify we can import DER public key */
+    ret = wc_ecc_init(&eccKey);
+    ExpectIntEQ(ret, 0);
+    idx = 0; /* reset idx to 0, used above in RSA case */
+    ExpectIntEQ(wc_EccPublicKeyDecode(keyDer, &idx, &eccKey, keyDerSz), 0);
+    if (ret == 0) {
+        wc_ecc_free(&eccKey);
+    }
+
+#endif
+#endif /* !NO_RSA || HAVE_ECC */
+    return EXPECT_RESULT();
+}
+
 static int test_wc_CheckCertSigPubKey(void)
 {
     EXPECT_DECLS;
@@ -66846,6 +66991,7 @@ TEST_CASE testCases[] = {
     TEST_DECL(test_wc_PubKeyPemToDer),
     TEST_DECL(test_wc_PemPubKeyToDer),
     TEST_DECL(test_wc_GetPubKeyDerFromCert),
+    TEST_DECL(test_wc_GetSubjectPubKeyInfoDerFromCert),
     TEST_DECL(test_wc_CheckCertSigPubKey),
 
     /* wolfCrypt ASN tests */
diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c
index 66e574b2641..2d33563a987 100644
--- a/wolfcrypt/src/asn.c
+++ b/wolfcrypt/src/asn.c
@@ -22981,7 +22981,8 @@ static int CheckDate(ASNGetData *dataASN, int dateType)
  * @param [out] badDateRet       Bad date return code.
  * @param [in]  stopAtPubKey     Stop parsing before subjectPublicKeyInfo.
  * @param [in]  stopAfterPubKey  Stop parsing after subjectPublicKeyInfo.
- * @return  0 on success.
+ * @return  0 on success if of the stop arguments is not set, otherwise set to
+ *          the corresponding byte offset at which the parsing stopped.
  * @return  ASN_CRIT_EXT_E when a critical extension was not recognized.
  * @return  ASN_TIME_E when date BER tag is nor UTC or GENERALIZED time.
  * @return  ASN_DATE_SZ_E when time data is not supported.
@@ -24608,6 +24609,82 @@ int wc_CertGetPubKey(const byte* cert, word32 certSz,
     return ret;
 }
 #endif
+
+/*
+ * @brief Export the SubjectPublicKeyInfo from an X.509 certificate
+ *
+ * This function extracts the SubjectPublicKeyInfo (SPKI) section from an X.509
+ * certificate in DER format. The SPKI contains the public key algorithm and
+ * the public key itself.
+ *
+ * @param certDer      [in]  Pointer to the DER encoded certificate
+ * @param certSz       [in]  Size of the DER encoded certificate
+ * @param pubKeyDer    [out] Buffer to hold the extracted SPKI (can be NULL to
+ *                           get size)
+ * @param pubKeyDerSz  [in,out] On input, size of pubKeyDer buffer
+ *                              On output, actual size of the SPKI
+ *
+ * @return 0 on success, negative on error
+ * @return BAD_FUNC_ARG if certDer is NULL, certSz is 0, or pubKeyDerSz is NULL
+ * @return BUFFER_E if the provided buffer is too small
+ */
+WOLFSSL_API int wc_GetSubjectPubKeyInfoDerFromCert(const byte* certDer,
+                                                   word32 certDerSz,
+                                                   byte* pubKeyDer,
+                                                   word32* pubKeyDerSz)
+{
+    DecodedCert cert;
+    int         ret;
+    word32      startIdx;
+    word32      idx;
+    word32      length;
+    int         badDate;
+
+    if (certDer == NULL || certDerSz == 0 || pubKeyDerSz == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+    length = 0;
+    badDate = 0;
+
+    wc_InitDecodedCert(&cert, certDer, certDerSz, NULL);
+
+    /* Parse up to the SubjectPublicKeyInfo */
+    ret = wc_GetPubX509(&cert, 0, &badDate);
+    if (ret >= 0) {
+        /* Save the starting index of SubjectPublicKeyInfo */
+        startIdx = cert.srcIdx;
+
+        /* Get the length of the SubjectPublicKeyInfo sequence */
+        idx = startIdx;
+        ret = GetSequence(certDer, &idx, (int*)&length, certDerSz);
+        if (ret >= 0) {
+            /* Calculate total length including sequence header */
+            length += (idx - startIdx);
+
+            /* Copy the SubjectPublicKeyInfo if buffer provided */
+            if (pubKeyDer != NULL) {
+                if (*pubKeyDerSz < (word32)length) {
+                    ret = BUFFER_E;
+                }
+                else {
+                    XMEMCPY(pubKeyDer, &certDer[startIdx], length);
+                }
+            }
+        }
+    }
+
+    if (ret >= 0) {
+        ret = 0;
+    }
+
+    *pubKeyDerSz = length;
+    wc_FreeDecodedCert(&cert);
+
+    return ret;
+}
+
+
 #ifdef HAVE_OCSP
 Signer* findSignerByKeyHash(Signer *list, byte *hash)
 {
diff --git a/wolfssl/wolfcrypt/asn_public.h b/wolfssl/wolfcrypt/asn_public.h
index 00aa081834e..fff6a5ac8fe 100644
--- a/wolfssl/wolfcrypt/asn_public.h
+++ b/wolfssl/wolfcrypt/asn_public.h
@@ -879,6 +879,10 @@ WOLFSSL_API int  wc_ParseCert(
 
 WOLFSSL_API int wc_GetPubKeyDerFromCert(struct DecodedCert* cert,
                                         byte* derKey, word32* derKeySz);
+WOLFSSL_API int wc_GetSubjectPubKeyInfoDerFromCert(const byte* certDer,
+                                                   word32 certDerSz,
+                                                   byte* pubKeyDer,
+                                                   word32* pubKeyDerSz);
 
 #ifdef WOLFSSL_FPKI
 WOLFSSL_API int wc_GetUUIDFromCert(struct DecodedCert* cert,


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T18:35:01Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds a new ASN API function to export the full X509 public key including the Subject Public Key Info (SPKI) section. Key changes include:
- Declaring the new function wc_ExportX509PubKeyWithSpki in asn_public.h.
- Implementing the new API in asn.c with proper error checking and SPKI extraction.
- Adding corresponding tests in tests/api.c to verify the functionality with RSA and ECC certificates.

### Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

| File                              | Description                                                        |
| --------------------------------- | ------------------------------------------------------------------ |
| wolfssl/wolfcrypt/asn_public.h    | Added the function declaration for wc_ExportX509PubKeyWithSpki.     |
| wolfcrypt/src/asn.c               | Implemented the new API function with SPKI extraction logic.       |
| tests/api.c                       | Introduced tests for the new API function covering valid and error cases. |


<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**wolfssl/wolfcrypt/asn_public.h:882**
* [nitpick] The parameter name 'cert' in the header is inconsistent with 'certDer' used in the implementation; consider using consistent naming across header and source for clarity.
```
WOLFSSL_API int wc_ExportX509PubKeyWithSpki(const byte* cert, word32 certSz,
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T18:35:40Z
user: login: dgarske
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-12T15:52:16Z
user: login: bigbrett
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T15:59:11Z
user: login: bigbrett
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T19:44:40Z
user: login: bigbrett
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T19:44:44Z
user: login: dgarske
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-14T17:26:00Z
user: login: dgarske
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-14T17:33:26Z

Review comments:
diff_hunk: @@ -879,6 +879,8 @@ WOLFSSL_API int  wc_ParseCert(
 
 WOLFSSL_API int wc_GetPubKeyDerFromCert(struct DecodedCert* cert,
                                         byte* derKey, word32* derKeySz);
path: wolfssl/wolfcrypt/asn_public.h
user: login: Copilot
body: Consider adding a Doxygen comment for wc_ExportX509PubKeyWithSpki in the header to make the API documentation consistent with its implementation.
```suggestion
                                        byte* derKey, word32* derKeySz);
/*!
    \brief Extracts the public key and Subject Public Key Info (SPKI) from an X.509 certificate.

    This function parses an X.509 certificate to extract the public key and its associated
    Subject Public Key Info (SPKI) structure. The extracted data is written to the provided
    buffers.

    \param[in]  cert      Pointer to the X.509 certificate in DER format.
    \param[in]  certSz    Size of the certificate buffer in bytes.
    \param[out] pubKey    Buffer to hold the extracted public key and SPKI.
    \param[in,out] pubKeySz  On input, the size of the pubKey buffer. On output, the size of the
                             extracted public key and SPKI.

    \return 0 on success, or a negative error code on failure.
*/
```
created_at: 2025-05-09T18:35:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -24608,6 +24609,81 @@ int wc_CertGetPubKey(const byte* cert, word32 certSz,
     return ret;
 }
 #endif
+
+/*
+ * @brief Export the SubjectPublicKeyInfo from an X.509 certificate
+ *
+ * This function extracts the SubjectPublicKeyInfo (SPKI) section from an X.509
+ * certificate in DER format. The SPKI contains the public key algorithm and
+ * the public key itself.
+ *
+ * @param certDer      [in]  Pointer to the DER encoded certificate
+ * @param certSz       [in]  Size of the DER encoded certificate
+ * @param pubKeyDer    [out] Buffer to hold the extracted SPKI (can be NULL to
+ *                           get size)
+ * @param pubKeyDerSz  [in,out] On input, size of pubKeyDer buffer
+ *                              On output, actual size of the SPKI
+ *
+ * @return 0 on success, negative on error
+ * @return BAD_FUNC_ARG if certDer is NULL, certSz is 0, or pubKeyDerSz is NULL
+ * @return BUFFER_E if the provided buffer is too small
+ */
+WOLFSSL_API int wc_ExportX509PubKeyWithSpki(const byte* certDer, word32 certSz,
+                                            byte*   pubKeyDer,
+                                            word32* pubKeyDerSz)
+{
+    DecodedCert cert;
+    int         ret;
+    word32      startIdx;
+    word32      idx;
+    word32      length;
+    int         badDate = 0;
+
+    if (certDer == NULL || certSz == 0 || pubKeyDerSz == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+    /* Initialize decoded cert structure */
+    wc_InitDecodedCert(&cert, certDer, certSz, NULL);
+
+    /* Parse up to the SubjectPublicKeyInfo */
+    ret = wc_GetPubX509(&cert, 0, &badDate);
path: wolfcrypt/src/asn.c
user: login: dgarske
body: Please rewrite to use a single return at end... use the `if (ret >= 0)` logic?
created_at: 2025-05-12T15:50:18Z
author_association: MEMBER
diff_hunk: @@ -879,6 +879,8 @@ WOLFSSL_API int  wc_ParseCert(
 
 WOLFSSL_API int wc_GetPubKeyDerFromCert(struct DecodedCert* cert,
                                         byte* derKey, word32* derKeySz);
+WOLFSSL_API int wc_ExportX509PubKeyWithSpki(const byte* cert, word32 certSz,
path: wolfssl/wolfcrypt/asn_public.h
user: login: dgarske
body: The term SPKI isn't used anywhere else... Is a name like `wc_GetSubPubKeyDerFromCert` okay?
created_at: 2025-05-12T15:52:05Z
author_association: MEMBER
diff_hunk: @@ -879,6 +879,8 @@ WOLFSSL_API int  wc_ParseCert(
 
 WOLFSSL_API int wc_GetPubKeyDerFromCert(struct DecodedCert* cert,
                                         byte* derKey, word32* derKeySz);
+WOLFSSL_API int wc_ExportX509PubKeyWithSpki(const byte* cert, word32 certSz,
path: wolfssl/wolfcrypt/asn_public.h
user: login: bigbrett
body: Yeah, might just spell out "subject public key info" in its entirety just to make it explicitly clear what we are referring to, but that is probs better 
created_at: 2025-05-12T15:59:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -879,6 +879,8 @@ WOLFSSL_API int  wc_ParseCert(
 
 WOLFSSL_API int wc_GetPubKeyDerFromCert(struct DecodedCert* cert,
                                         byte* derKey, word32* derKeySz);
+WOLFSSL_API int wc_ExportX509PubKeyWithSpki(const byte* cert, word32 certSz,
path: wolfssl/wolfcrypt/asn_public.h
user: login: bigbrett
body: updated
created_at: 2025-05-12T19:44:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -24608,6 +24609,81 @@ int wc_CertGetPubKey(const byte* cert, word32 certSz,
     return ret;
 }
 #endif
+
+/*
+ * @brief Export the SubjectPublicKeyInfo from an X.509 certificate
+ *
+ * This function extracts the SubjectPublicKeyInfo (SPKI) section from an X.509
+ * certificate in DER format. The SPKI contains the public key algorithm and
+ * the public key itself.
+ *
+ * @param certDer      [in]  Pointer to the DER encoded certificate
+ * @param certSz       [in]  Size of the DER encoded certificate
+ * @param pubKeyDer    [out] Buffer to hold the extracted SPKI (can be NULL to
+ *                           get size)
+ * @param pubKeyDerSz  [in,out] On input, size of pubKeyDer buffer
+ *                              On output, actual size of the SPKI
+ *
+ * @return 0 on success, negative on error
+ * @return BAD_FUNC_ARG if certDer is NULL, certSz is 0, or pubKeyDerSz is NULL
+ * @return BUFFER_E if the provided buffer is too small
+ */
+WOLFSSL_API int wc_ExportX509PubKeyWithSpki(const byte* certDer, word32 certSz,
+                                            byte*   pubKeyDer,
+                                            word32* pubKeyDerSz)
+{
+    DecodedCert cert;
+    int         ret;
+    word32      startIdx;
+    word32      idx;
+    word32      length;
+    int         badDate = 0;
+
+    if (certDer == NULL || certSz == 0 || pubKeyDerSz == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+    /* Initialize decoded cert structure */
+    wc_InitDecodedCert(&cert, certDer, certSz, NULL);
+
+    /* Parse up to the SubjectPublicKeyInfo */
+    ret = wc_GetPubX509(&cert, 0, &badDate);
path: wolfcrypt/src/asn.c
user: login: bigbrett
body: done
created_at: 2025-05-12T19:44:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -879,6 +879,9 @@ WOLFSSL_API int  wc_ParseCert(
 
 WOLFSSL_API int wc_GetPubKeyDerFromCert(struct DecodedCert* cert,
                                         byte* derKey, word32* derKeySz);
+WOLFSSL_API int wc_GetSubjectPubKeyInfoDerFromCert(const byte* cert,
path: wolfssl/wolfcrypt/asn_public.h
user: login: dgarske
body: `cert` -> `certDer` and `pubKey` -> `pubKeyDer` to match function implementation.
created_at: 2025-05-14T17:25:46Z
author_association: MEMBER

PR comments:
user: login: anhu
created_at: 2025-05-09T20:50:57Z
body: I'm not sure if its me thats weird, but shouldn't it be `wc_ExportX509PubKeyAsSpki()`
author_association: MEMBER
user: login: bigbrett
created_at: 2025-05-09T21:22:01Z
body: @anhu figured it was returning the public key portion of the cert WITH SPKI as opposed to our other version of it that returns just the raw stuff. But you are probably right here 
author_association: CONTRIBUTOR
user: login: bigbrett
created_at: 2025-05-09T23:31:57Z
body: Jenkins retest this please
author_association: CONTRIBUTOR
user: login: bigbrett
created_at: 2025-05-12T15:05:07Z
body: Accepting peanut gallery comments on an appropriate name for the function, Anthony makes a good point. If a reviewer has an opinion on what makes more sense please LMK
author_association: CONTRIBUTOR
user: login: bigbrett
created_at: 2025-05-12T19:07:16Z
body: Jenkins retest this please
author_association: CONTRIBUTOR
user: login: bigbrett
created_at: 2025-05-14T18:30:18Z
body: Jenkins retest this please
author_association: CONTRIBUTOR