From 34c49e6c6cda548bf9b1da8a099ec3d39efdb2c2 Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Tue, 17 Feb 2026 00:44:12 +0900
Subject: [PATCH 1/5] Add const qualifiers for OpenSSL 4.0 compatibility

OpenSSL's master branch is changing functions to return const pointers
where the returned objects are not meant to be modified by the caller.

Update ossl_*_new() to take const pointers accordingly. Unfortunately,
*_dup() in older versions of OpenSSL and in LibreSSL/AWS-LC take
non-const pointers, so const casts are required.
---
 ext/openssl/ossl_ocsp.c        |  5 ++---
 ext/openssl/ossl_ts.c          |  2 +-
 ext/openssl/ossl_x509.h        | 12 ++++++------
 ext/openssl/ossl_x509attr.c    |  9 +++++----
 ext/openssl/ossl_x509cert.c    | 12 ++++++------
 ext/openssl/ossl_x509crl.c     | 10 +++++-----
 ext/openssl/ossl_x509ext.c     | 19 ++++++++++++++-----
 ext/openssl/ossl_x509name.c    |  5 +++--
 ext/openssl/ossl_x509req.c     |  4 ++--
 ext/openssl/ossl_x509revoked.c |  7 ++++---
 ext/openssl/ossl_x509store.c   | 10 ++++------
 11 files changed, 52 insertions(+), 43 deletions(-)

diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c
index ddb67fcf0..9dd4b466d 100644
--- a/ext/openssl/ossl_ocsp.c
+++ b/ext/openssl/ossl_ocsp.c
@@ -922,7 +922,7 @@ ossl_ocspbres_get_status(VALUE self)
         VALUE ext = rb_ary_new();
         int ext_count = OCSP_SINGLERESP_get_ext_count(single);
         for (int j = 0; j < ext_count; j++) {
-            X509_EXTENSION *x509ext = OCSP_SINGLERESP_get_ext(single, j);
+            const X509_EXTENSION *x509ext = OCSP_SINGLERESP_get_ext(single, j);
             rb_ary_push(ext, ossl_x509ext_new(x509ext));
         }
         rb_ary_push(ary, ext);
@@ -1341,7 +1341,6 @@ static VALUE
 ossl_ocspsres_get_extensions(VALUE self)
 {
     OCSP_SINGLERESP *sres;
-    X509_EXTENSION *ext;
     int count, i;
     VALUE ary;
 
@@ -1350,7 +1349,7 @@ ossl_ocspsres_get_extensions(VALUE self)
     count = OCSP_SINGLERESP_get_ext_count(sres);
     ary = rb_ary_new2(count);
     for (i = 0; i < count; i++) {
-        ext = OCSP_SINGLERESP_get_ext(sres, i);
+        const X509_EXTENSION *ext = OCSP_SINGLERESP_get_ext(sres, i);
         rb_ary_push(ary, ossl_x509ext_new(ext)); /* will dup */
     }
 
diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c
index b31a854a6..393e08acf 100644
--- a/ext/openssl/ossl_ts.c
+++ b/ext/openssl/ossl_ts.c
@@ -706,7 +706,7 @@ ossl_ts_resp_get_tsa_certificate(VALUE self)
     TS_RESP *resp;
     PKCS7 *p7;
     PKCS7_SIGNER_INFO *ts_info;
-    X509 *cert;
+    const X509 *cert;
 
     GetTSResponse(self, resp);
     if (!(p7 = TS_RESP_get_token(resp)))
diff --git a/ext/openssl/ossl_x509.h b/ext/openssl/ossl_x509.h
index d25167ee7..71932ef1a 100644
--- a/ext/openssl/ossl_x509.h
+++ b/ext/openssl/ossl_x509.h
@@ -29,7 +29,7 @@ void Init_ossl_x509(void);
  */
 extern VALUE cX509Attr;
 
-VALUE ossl_x509attr_new(X509_ATTRIBUTE *);
+VALUE ossl_x509attr_new(const X509_ATTRIBUTE *);
 X509_ATTRIBUTE *GetX509AttrPtr(VALUE);
 void Init_ossl_x509attr(void);
 
@@ -38,7 +38,7 @@ void Init_ossl_x509attr(void);
  */
 extern VALUE cX509Cert;
 
-VALUE ossl_x509_new(X509 *);
+VALUE ossl_x509_new(const X509 *);
 X509 *GetX509CertPtr(VALUE);
 X509 *DupX509CertPtr(VALUE);
 void Init_ossl_x509cert(void);
@@ -46,7 +46,7 @@ void Init_ossl_x509cert(void);
 /*
  * X509CRL
  */
-VALUE ossl_x509crl_new(X509_CRL *);
+VALUE ossl_x509crl_new(const X509_CRL *);
 X509_CRL *GetX509CRLPtr(VALUE);
 void Init_ossl_x509crl(void);
 
@@ -55,14 +55,14 @@ void Init_ossl_x509crl(void);
  */
 extern VALUE cX509Ext;
 
-VALUE ossl_x509ext_new(X509_EXTENSION *);
+VALUE ossl_x509ext_new(const X509_EXTENSION *);
 X509_EXTENSION *GetX509ExtPtr(VALUE);
 void Init_ossl_x509ext(void);
 
 /*
  * X509Name
  */
-VALUE ossl_x509name_new(X509_NAME *);
+VALUE ossl_x509name_new(const X509_NAME *);
 X509_NAME *GetX509NamePtr(VALUE);
 void Init_ossl_x509name(void);
 
@@ -77,7 +77,7 @@ void Init_ossl_x509req(void);
  */
 extern VALUE cX509Rev;
 
-VALUE ossl_x509revoked_new(X509_REVOKED *);
+VALUE ossl_x509revoked_new(const X509_REVOKED *);
 X509_REVOKED *DupX509RevokedPtr(VALUE);
 void Init_ossl_x509revoked(void);
 
diff --git a/ext/openssl/ossl_x509attr.c b/ext/openssl/ossl_x509attr.c
index 4769e56e1..b0773e7a7 100644
--- a/ext/openssl/ossl_x509attr.c
+++ b/ext/openssl/ossl_x509attr.c
@@ -48,13 +48,14 @@ static const rb_data_type_t ossl_x509attr_type = {
  * Public
  */
 VALUE
-ossl_x509attr_new(X509_ATTRIBUTE *attr)
+ossl_x509attr_new(const X509_ATTRIBUTE *attr)
 {
     X509_ATTRIBUTE *new;
     VALUE obj;
 
     obj = NewX509Attr(cX509Attr);
-    new = X509_ATTRIBUTE_dup(attr);
+    /* OpenSSL 1.1.1 takes a non-const pointer */
+    new = X509_ATTRIBUTE_dup((X509_ATTRIBUTE *)attr);
     if (!new)
         ossl_raise(eX509AttrError, "X509_ATTRIBUTE_dup");
     SetX509Attr(obj, new);
@@ -196,7 +197,7 @@ ossl_x509attr_set_value(VALUE self, VALUE value)
         ossl_raise(eX509AttrError, "attribute value must be ASN1::Set");
 
     if (X509_ATTRIBUTE_count(attr)) { /* populated, reset first */
-        ASN1_OBJECT *obj = X509_ATTRIBUTE_get0_object(attr);
+        const ASN1_OBJECT *obj = X509_ATTRIBUTE_get0_object(attr);
         X509_ATTRIBUTE *new_attr = X509_ATTRIBUTE_create_by_OBJ(NULL, obj, 0, NULL, -1);
         if (!new_attr) {
             sk_ASN1_TYPE_pop_free(sk, ASN1_TYPE_free);
@@ -240,7 +241,7 @@ ossl_x509attr_get_value(VALUE self)
 
     count = X509_ATTRIBUTE_count(attr);
     for (i = 0; i < count; i++)
-        sk_ASN1_TYPE_push(sk, X509_ATTRIBUTE_get0_type(attr, i));
+        sk_ASN1_TYPE_push(sk, (ASN1_TYPE *)X509_ATTRIBUTE_get0_type(attr, i));
 
     if ((len = i2d_ASN1_SET_ANY(sk, NULL)) <= 0) {
         sk_ASN1_TYPE_free(sk);
diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c
index 95679c7d2..de246759a 100644
--- a/ext/openssl/ossl_x509cert.c
+++ b/ext/openssl/ossl_x509cert.c
@@ -48,13 +48,14 @@ static const rb_data_type_t ossl_x509_type = {
  * Public
  */
 VALUE
-ossl_x509_new(X509 *x509)
+ossl_x509_new(const X509 *x509)
 {
     X509 *new;
     VALUE obj;
 
     obj = NewX509(cX509Cert);
-    new = X509_dup(x509);
+    /* OpenSSL 1.1.1 takes a non-const pointer */
+    new = X509_dup((X509 *)x509);
     if (!new)
         ossl_raise(eX509CertError, "X509_dup");
     SetX509(obj, new);
@@ -345,7 +346,7 @@ static VALUE
 ossl_x509_get_subject(VALUE self)
 {
     X509 *x509;
-    X509_NAME *name;
+    const X509_NAME *name;
 
     GetX509(self, x509);
     if (!(name = X509_get_subject_name(x509))) { /* NO DUP - don't free! */
@@ -380,7 +381,7 @@ static VALUE
 ossl_x509_get_issuer(VALUE self)
 {
     X509 *x509;
-    X509_NAME *name;
+    const X509_NAME *name;
 
     GetX509(self, x509);
     if(!(name = X509_get_issuer_name(x509))) { /* NO DUP - don't free! */
@@ -603,14 +604,13 @@ ossl_x509_get_extensions(VALUE self)
 {
     X509 *x509;
     int count, i;
-    X509_EXTENSION *ext;
     VALUE ary;
 
     GetX509(self, x509);
     count = X509_get_ext_count(x509);
     ary = rb_ary_new_capa(count);
     for (i=0; i<count; i++) {
-        ext = X509_get_ext(x509, i); /* NO DUP - don't free! */
+        const X509_EXTENSION *ext = X509_get_ext(x509, i);
         rb_ary_push(ary, ossl_x509ext_new(ext));
     }
 
diff --git a/ext/openssl/ossl_x509crl.c b/ext/openssl/ossl_x509crl.c
index a221429c3..9b59bda9e 100644
--- a/ext/openssl/ossl_x509crl.c
+++ b/ext/openssl/ossl_x509crl.c
@@ -58,13 +58,14 @@ GetX509CRLPtr(VALUE obj)
 }
 
 VALUE
-ossl_x509crl_new(X509_CRL *crl)
+ossl_x509crl_new(const X509_CRL *crl)
 {
     X509_CRL *tmp;
     VALUE obj;
 
     obj = NewX509CRL(cX509CRL);
-    tmp = X509_CRL_dup(crl);
+    /* OpenSSL 1.1.1 takes a non-const pointer */
+    tmp = X509_CRL_dup((X509_CRL *)crl);
     if (!tmp)
         ossl_raise(eX509CRLError, "X509_CRL_dup");
     SetX509CRL(obj, tmp);
@@ -289,7 +290,7 @@ ossl_x509crl_get_revoked(VALUE self)
     num = sk_X509_REVOKED_num(sk);
     ary = rb_ary_new_capa(num);
     for(i=0; i<num; i++) {
-        X509_REVOKED *rev = sk_X509_REVOKED_value(sk, i);
+        const X509_REVOKED *rev = sk_X509_REVOKED_value(sk, i);
         rb_ary_push(ary, ossl_x509revoked_new(rev));
     }
 
@@ -443,14 +444,13 @@ ossl_x509crl_get_extensions(VALUE self)
 {
     X509_CRL *crl;
     int count, i;
-    X509_EXTENSION *ext;
     VALUE ary;
 
     GetX509CRL(self, crl);
     count = X509_CRL_get_ext_count(crl);
     ary = rb_ary_new_capa(count);
     for (i=0; i<count; i++) {
-        ext = X509_CRL_get_ext(crl, i); /* NO DUP - don't free! */
+        const X509_EXTENSION *ext = X509_CRL_get_ext(crl, i);
         rb_ary_push(ary, ossl_x509ext_new(ext));
     }
 
diff --git a/ext/openssl/ossl_x509ext.c b/ext/openssl/ossl_x509ext.c
index ef66ecc3f..1fe727d3f 100644
--- a/ext/openssl/ossl_x509ext.c
+++ b/ext/openssl/ossl_x509ext.c
@@ -62,13 +62,14 @@ static const rb_data_type_t ossl_x509ext_type = {
  * Public
  */
 VALUE
-ossl_x509ext_new(X509_EXTENSION *ext)
+ossl_x509ext_new(const X509_EXTENSION *ext)
 {
     X509_EXTENSION *new;
     VALUE obj;
 
     obj = NewX509Ext(cX509Ext);
-    new = X509_EXTENSION_dup(ext);
+    /* OpenSSL 1.1.1 takes a non-const pointer */
+    new = X509_EXTENSION_dup((X509_EXTENSION *)ext);
     if (!new)
         ossl_raise(eX509ExtError, "X509_EXTENSION_dup");
     SetX509Ext(obj, new);
@@ -338,12 +339,20 @@ ossl_x509ext_set_value(VALUE self, VALUE data)
     GetX509Ext(self, ext);
     data = ossl_to_der_if_possible(data);
     StringValue(data);
-    asn1s = X509_EXTENSION_get_data(ext);
 
+    asn1s = ASN1_OCTET_STRING_new();
+    if (!asn1s)
+        ossl_raise(eX509ExtError, "ASN1_OCTET_STRING_new");
     if (!ASN1_OCTET_STRING_set(asn1s, (unsigned char *)RSTRING_PTR(data),
                                RSTRING_LENINT(data))) {
+        ASN1_OCTET_STRING_free(asn1s);
         ossl_raise(eX509ExtError, "ASN1_OCTET_STRING_set");
     }
+    if (!X509_EXTENSION_set_data(ext, asn1s)) {
+        ASN1_OCTET_STRING_free(asn1s);
+        ossl_raise(eX509ExtError, "X509_EXTENSION_set_data");
+    }
+    ASN1_OCTET_STRING_free(asn1s);
 
     return data;
 }
@@ -386,7 +395,7 @@ ossl_x509ext_get_value(VALUE obj)
     if (!(out = BIO_new(BIO_s_mem())))
         ossl_raise(eX509ExtError, NULL);
     if (!X509V3_EXT_print(out, ext, 0, 0))
-        ASN1_STRING_print(out, (ASN1_STRING *)X509_EXTENSION_get_data(ext));
+        ASN1_STRING_print(out, X509_EXTENSION_get_data(ext));
     ret = ossl_membio2str(out);
 
     return ret;
@@ -396,7 +405,7 @@ static VALUE
 ossl_x509ext_get_value_der(VALUE obj)
 {
     X509_EXTENSION *ext;
-    ASN1_OCTET_STRING *value;
+    const ASN1_OCTET_STRING *value;
 
     GetX509Ext(obj, ext);
     if ((value = X509_EXTENSION_get_data(ext)) == NULL)
diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c
index 5b3c3f726..2b66a4a09 100644
--- a/ext/openssl/ossl_x509name.c
+++ b/ext/openssl/ossl_x509name.c
@@ -53,13 +53,14 @@ static const rb_data_type_t ossl_x509name_type = {
  * Public
  */
 VALUE
-ossl_x509name_new(X509_NAME *name)
+ossl_x509name_new(const X509_NAME *name)
 {
     X509_NAME *new;
     VALUE obj;
 
     obj = NewX509Name(cX509Name);
-    new = X509_NAME_dup(name);
+    /* OpenSSL 1.1.1 takes a non-const pointer */
+    new = X509_NAME_dup((X509_NAME *)name);
     if (!new)
         ossl_raise(eX509NameError, "X509_NAME_dup");
     SetX509Name(obj, new);
diff --git a/ext/openssl/ossl_x509req.c b/ext/openssl/ossl_x509req.c
index 433cc461a..ad5dd0803 100644
--- a/ext/openssl/ossl_x509req.c
+++ b/ext/openssl/ossl_x509req.c
@@ -231,7 +231,7 @@ static VALUE
 ossl_x509req_get_subject(VALUE self)
 {
     X509_REQ *req;
-    X509_NAME *name;
+    const X509_NAME *name;
 
     GetX509Req(self, req);
     if (!(name = X509_REQ_get_subject_name(req))) { /* NO DUP - don't free */
@@ -351,7 +351,7 @@ ossl_x509req_get_attributes(VALUE self)
 {
     X509_REQ *req;
     int count, i;
-    X509_ATTRIBUTE *attr;
+    const X509_ATTRIBUTE *attr;
     VALUE ary;
 
     GetX509Req(self, req);
diff --git a/ext/openssl/ossl_x509revoked.c b/ext/openssl/ossl_x509revoked.c
index b88c390c7..0151961e9 100644
--- a/ext/openssl/ossl_x509revoked.c
+++ b/ext/openssl/ossl_x509revoked.c
@@ -48,13 +48,14 @@ static const rb_data_type_t ossl_x509rev_type = {
  * PUBLIC
  */
 VALUE
-ossl_x509revoked_new(X509_REVOKED *rev)
+ossl_x509revoked_new(const X509_REVOKED *rev)
 {
     X509_REVOKED *new;
     VALUE obj;
 
     obj = NewX509Rev(cX509Rev);
-    new = X509_REVOKED_dup(rev);
+    /* OpenSSL 1.1.1 takes a non-const pointer */
+    new = X509_REVOKED_dup((X509_REVOKED *)rev);
     if (!new)
         ossl_raise(eX509RevError, "X509_REVOKED_dup");
     SetX509Rev(obj, new);
@@ -185,7 +186,7 @@ ossl_x509revoked_get_extensions(VALUE self)
 {
     X509_REVOKED *rev;
     int count, i;
-    X509_EXTENSION *ext;
+    const X509_EXTENSION *ext;
     VALUE ary;
 
     GetX509Rev(self, rev);
diff --git a/ext/openssl/ossl_x509store.c b/ext/openssl/ossl_x509store.c
index be1458cec..408e18c6c 100644
--- a/ext/openssl/ossl_x509store.c
+++ b/ext/openssl/ossl_x509store.c
@@ -512,10 +512,8 @@ static void
 ossl_x509stctx_free(void *ptr)
 {
     X509_STORE_CTX *ctx = ptr;
-    if (X509_STORE_CTX_get0_untrusted(ctx))
-        sk_X509_pop_free(X509_STORE_CTX_get0_untrusted(ctx), X509_free);
-    if (X509_STORE_CTX_get0_cert(ctx))
-        X509_free(X509_STORE_CTX_get0_cert(ctx));
+    sk_X509_pop_free(X509_STORE_CTX_get0_untrusted(ctx), X509_free);
+    X509_free((X509 *)X509_STORE_CTX_get0_cert(ctx));
     X509_STORE_CTX_free(ctx);
 }
 
@@ -736,7 +734,7 @@ static VALUE
 ossl_x509stctx_get_curr_cert(VALUE self)
 {
     X509_STORE_CTX *ctx;
-    X509 *x509;
+    const X509 *x509;
 
     GetX509StCtx(self, ctx);
     x509 = X509_STORE_CTX_get_current_cert(ctx);
@@ -758,7 +756,7 @@ static VALUE
 ossl_x509stctx_get_curr_crl(VALUE self)
 {
     X509_STORE_CTX *ctx;
-    X509_CRL *crl;
+    const X509_CRL *crl;
 
     GetX509StCtx(self, ctx);
     crl = X509_STORE_CTX_get0_current_crl(ctx);

From faad7a081173c23f41f0621bf01e39f38b7c3068 Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Sun, 15 Mar 2026 03:24:45 +0900
Subject: [PATCH 2/5] pkey: remove unnecessary prototype from ossl_pkey.h

ossl_ec_new() was removed in commit 94aeab2f265d (pkey: simplify
ossl_pkey_new(), 2017-03-16), but it forgot to remove the declaration
while doing so.
---
 ext/openssl/ossl_pkey.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/ext/openssl/ossl_pkey.h b/ext/openssl/ossl_pkey.h
index 023361b90..efba33b75 100644
--- a/ext/openssl/ossl_pkey.h
+++ b/ext/openssl/ossl_pkey.h
@@ -71,7 +71,6 @@ void Init_ossl_dh(void);
  * EC
  */
 extern VALUE cEC;
-VALUE ossl_ec_new(EVP_PKEY *);
 void Init_ossl_ec(void);
 
 #define OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, _name, _get)          \

From ebb505f21704d2337d7ba5b77b1963f4bfc0ce69 Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Thu, 26 Feb 2026 21:33:19 +0900
Subject: [PATCH 3/5] asn1: use new ASN1_BIT_STRING accessor functions with
 OpenSSL 4.0

ASN1_STRING has been made opaque in OpenSSL's master branch. Use the
new accessor functions instead of accessing fields directly.

Other uses of ASN1_STRING fields were already updated in
<https://github.com/ruby/openssl/pull/978>. This patch converts the
remaining ones, which require the new functions added in OpenSSL 4.0
and were not available at that time.
---
 ext/openssl/extconf.rb        |  3 +++
 ext/openssl/openssl_missing.h | 23 ++++++++++++++++++++++
 ext/openssl/ossl_asn1.c       | 37 +++++++++++++++++++----------------
 3 files changed, 46 insertions(+), 17 deletions(-)

diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb
index a897c86b6..06ed4f6ac 100644
--- a/ext/openssl/extconf.rb
+++ b/ext/openssl/extconf.rb
@@ -169,6 +169,9 @@ def find_openssl_library
 # added in 3.5.0
 have_func("SSL_get0_peer_signature_name(NULL, NULL)", ssl_h)
 
+# added in 4.0.0
+have_func("ASN1_BIT_STRING_set1(NULL, NULL, 0, 0)", "openssl/asn1.h")
+
 Logging::message "=== Checking done. ===\n"
 
 # Append flags from environment variables.
diff --git a/ext/openssl/openssl_missing.h b/ext/openssl/openssl_missing.h
index 6592f9cce..ed3b5b7c0 100644
--- a/ext/openssl/openssl_missing.h
+++ b/ext/openssl/openssl_missing.h
@@ -29,4 +29,27 @@
 #  define EVP_PKEY_eq(a, b) EVP_PKEY_cmp(a, b)
 #endif
 
+/* added in 4.0.0 */
+#ifndef HAVE_ASN1_BIT_STRING_SET1
+static inline int
+ASN1_BIT_STRING_set1(ASN1_BIT_STRING *bitstr, const uint8_t *data,
+                     size_t length, int unused_bits)
+{
+    if (length > INT_MAX || !ASN1_STRING_set(bitstr, data, (int)length))
+        return 0;
+    bitstr->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT | 0x07);
+    bitstr->flags |= ASN1_STRING_FLAG_BITS_LEFT | unused_bits;
+    return 1;
+}
+
+static inline int
+ASN1_BIT_STRING_get_length(const ASN1_BIT_STRING *bitstr, size_t *length,
+                           int *unused_bits)
+{
+    *length = bitstr->length;
+    *unused_bits = bitstr->flags & 0x07;
+    return 1;
+}
+#endif
+
 #endif /* _OSSL_OPENSSL_MISSING_H_ */
diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c
index 18fa8edeb..67c03b7f9 100644
--- a/ext/openssl/ossl_asn1.c
+++ b/ext/openssl/ossl_asn1.c
@@ -228,7 +228,7 @@ obj_to_asn1int(VALUE obj)
 }
 
 static ASN1_BIT_STRING*
-obj_to_asn1bstr(VALUE obj, long unused_bits)
+obj_to_asn1bstr(VALUE obj, int unused_bits)
 {
     ASN1_BIT_STRING *bstr;
 
@@ -236,11 +236,11 @@ obj_to_asn1bstr(VALUE obj, long unused_bits)
         ossl_raise(eASN1Error, "unused_bits for a bitstring value must be in "\
                    "the range 0 to 7");
     StringValue(obj);
-    if(!(bstr = ASN1_BIT_STRING_new()))
-        ossl_raise(eASN1Error, NULL);
-    ASN1_BIT_STRING_set(bstr, (unsigned char *)RSTRING_PTR(obj), RSTRING_LENINT(obj));
-    bstr->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT|0x07); /* clear */
-    bstr->flags |= ASN1_STRING_FLAG_BITS_LEFT | unused_bits;
+    if (!(bstr = ASN1_BIT_STRING_new()))
+        ossl_raise(eASN1Error, "ASN1_BIT_STRING_new");
+    if (!ASN1_BIT_STRING_set1(bstr, (uint8_t *)RSTRING_PTR(obj),
+                              RSTRING_LEN(obj), unused_bits))
+        ossl_raise(eASN1Error, "ASN1_BIT_STRING_set1");
 
     return bstr;
 }
@@ -364,22 +364,25 @@ decode_int(unsigned char* der, long length)
 }
 
 static VALUE
-decode_bstr(unsigned char* der, long length, long *unused_bits)
+decode_bstr(unsigned char* der, long length, int *unused_bits)
 {
     ASN1_BIT_STRING *bstr;
     const unsigned char *p;
-    long len;
+    size_t len;
     VALUE ret;
+    int state;
 
     p = der;
-    if(!(bstr = d2i_ASN1_BIT_STRING(NULL, &p, length)))
-        ossl_raise(eASN1Error, NULL);
-    len = bstr->length;
-    *unused_bits = 0;
-    if(bstr->flags & ASN1_STRING_FLAG_BITS_LEFT)
-        *unused_bits = bstr->flags & 0x07;
-    ret = rb_str_new((const char *)bstr->data, len);
+    if (!(bstr = d2i_ASN1_BIT_STRING(NULL, &p, length)))
+        ossl_raise(eASN1Error, "d2i_ASN1_BIT_STRING");
+    if (!ASN1_BIT_STRING_get_length(bstr, &len, unused_bits)) {
+        ASN1_BIT_STRING_free(bstr);
+        ossl_raise(eASN1Error, "ASN1_BIT_STRING_get_length");
+    }
+    ret = ossl_str_new((const char *)ASN1_STRING_get0_data(bstr), len, &state);
     ASN1_BIT_STRING_free(bstr);
+    if (state)
+        rb_jump_tag(state);
 
     return ret;
 }
@@ -763,7 +766,7 @@ int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag,
 {
     VALUE value, asn1data;
     unsigned char *p;
-    long flag = 0;
+    int flag = 0;
 
     p = *pp;
 
@@ -820,7 +823,7 @@ int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag,
         asn1data = rb_obj_alloc(klass);
         ossl_asn1_initialize(4, args, asn1data);
         if(tag == V_ASN1_BIT_STRING){
-            rb_ivar_set(asn1data, sivUNUSED_BITS, LONG2NUM(flag));
+            rb_ivar_set(asn1data, sivUNUSED_BITS, INT2NUM(flag));
         }
     }
     else {

From 3e01c802c9cf96ab386c5d57f42ecebc053d6b08 Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Tue, 17 Feb 2026 16:16:54 +0900
Subject: [PATCH 4/5] ssl: fix test_tmp_dh and test_tmp_dh_callback with
 OpenSSL 4.0

OpenSSL master added support for RFC 7919 groups in TLS 1.2. They are
preferred over SSLContext#tmp_dh= or #tmp_dh_callback= values if the
client advertises them in the supported_groups extension.
---
 test/openssl/test_ssl.rb | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb
index ce1b2c1e9..e4fd58107 100644
--- a/test/openssl/test_ssl.rb
+++ b/test/openssl/test_ssl.rb
@@ -1909,7 +1909,9 @@ def test_tmp_dh_callback
       }
     }
     start_server(ctx_proc: ctx_proc) do |port|
-      server_connect(port) { |ssl|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.groups = "P-256" # Exclude RFC 7919 groups
+      server_connect(port, ctx) { |ssl|
         assert called, "dh callback should be called"
         assert_equal dh.to_der, ssl.tmp_key.to_der
       }
@@ -2172,7 +2174,9 @@ def test_tmp_dh
       ctx.tmp_dh = dh
     }
     start_server(ctx_proc: ctx_proc) do |port|
-      server_connect(port) { |ssl|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.groups = "P-256" # Exclude RFC 7919 groups
+      server_connect(port, ctx) { |ssl|
         assert_equal dh.to_der, ssl.tmp_key.to_der
       }
     end

From 69f8cd1af184d312a9ecba950b63cb2ef3d41d36 Mon Sep 17 00:00:00 2001
From: Jun Aruga <jaruga@redhat.com>
Date: Thu, 26 Mar 2026 14:42:07 +0000
Subject: [PATCH 5/5] test_pkey_rsa.rb: Fix test_private_encoding_encrypted in
 OpenSSL 4.0 FIPS

OpenSSL 4.0.0 added a check for Password-Based Key Derivation Function 2
(PBKDF2) to require the minimal password length 8 in FIPS by the following
commit.
https://github.com/openssl/openssl/commit/71ed0fc8b3cdb33cd06059416686f8972ede0248

This commit fixes the following test failure in OpenSSL 4.0 FIPS by changing
testing password length from 6 to 8..

```
1) Error: test_private_encoding_encrypted(OpenSSL::TestPKeyRSA): OpenSSL::PKey::PKeyError: i2d_PKCS8PrivateKey_bio: encrypt error
/home/runner/work/openssl/openssl/test/openssl/test_pkey_rsa.rb:465:in `private_to_der'
/home/runner/work/openssl/openssl/test/openssl/test_pkey_rsa.rb:465:in `test_private_encoding_encrypted'
     462:
     463:   def test_private_encoding_encrypted
     464:     rsa = Fixtures.pkey("rsa2048")
  => 465:     encoded = rsa.private_to_der("aes-128-cbc", "abcdef")
     466:     asn1 = OpenSSL::ASN1.decode(encoded) # PKCS #8 EncryptedPrivateKeyInfo
     467:     assert_kind_of OpenSSL::ASN1::Sequence, asn1
     468:     assert_equal 2, asn1.value.size
Error: OpenSSL::PKey::PKeyError: i2d_PKCS8PrivateKey_bio: encrypt error
```
---
 test/openssl/test_pkey_rsa.rb | 74 +++++++++++++++++------------------
 1 file changed, 37 insertions(+), 37 deletions(-)

diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb
index 86f51cf43..1716aef38 100644
--- a/test/openssl/test_pkey_rsa.rb
+++ b/test/openssl/test_pkey_rsa.rb
@@ -462,54 +462,54 @@ def test_private_encoding
 
   def test_private_encoding_encrypted
     rsa = Fixtures.pkey("rsa2048")
-    encoded = rsa.private_to_der("aes-128-cbc", "abcdef")
+    encoded = rsa.private_to_der("aes-128-cbc", "abcdefgh")
     asn1 = OpenSSL::ASN1.decode(encoded) # PKCS #8 EncryptedPrivateKeyInfo
     assert_kind_of OpenSSL::ASN1::Sequence, asn1
     assert_equal 2, asn1.value.size
     assert_not_equal rsa.private_to_der, encoded
-    assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdef")
-    assert_same_rsa rsa, OpenSSL::PKey.read(encoded) { "abcdef" }
+    assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdefgh")
+    assert_same_rsa rsa, OpenSSL::PKey.read(encoded) { "abcdefgh" }
     assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(encoded, "abcxyz") }
 
-    encoded = rsa.private_to_pem("aes-128-cbc", "abcdef")
+    encoded = rsa.private_to_pem("aes-128-cbc", "abcdefgh")
     assert_match (/BEGIN ENCRYPTED PRIVATE KEY/), encoded.lines[0]
-    assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdef")
+    assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdefgh")
 
     # Use openssl instead of certtool due to https://gitlab.com/gnutls/gnutls/-/issues/1632
-    # openssl pkcs8 -in test/openssl/fixtures/pkey/rsa2048.pem -topk8 -v2 aes-128-cbc -passout pass:abcdef
+    # openssl pkcs8 -in test/openssl/fixtures/pkey/rsa2048.pem -topk8 -v2 aes-128-cbc -passout pass:abcdefgh
     pem = <<~EOF
-    -----BEGIN ENCRYPTED PRIVATE KEY-----
-    MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIay5V8CDQi5oCAggA
-    MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBB6eyagcbsvdQlM1kPcH7kiBIIE
-    0Ng1apIyoPAZ4BfC4kMNeSmeAv3XspxqYi3uWzXiNyTcoE6390swrwM6WvdpXvLI
-    /n/V06krxPZ9X4fBG2kLUzXt5f09lEvmQU1HW1wJGU5Sq3bNeXBrlJF4DzJE4WWd
-    whVVvNMm44ghdzN/jGSw3z+6d717N+waa7vrpBDsHjhsPNwxpyzUvcFPFysTazxx
-    kN/dziIBF6SRKi6w8VaJEMQ8czGu5T3jOc2e/1p3/AYhHLPS4NHhLR5OUh0TKqLK
-    tANAqI9YqCAjhqcYCmN3mMQXY52VfOqG9hlX1x9ZQyqiH7l102EWbPqouk6bCBLQ
-    wHepPg4uK99Wsdh65qEryNnXQ5ZmO6aGb6T3TFENCaNKmi8Nh+/5dr7J7YfhIwpo
-    FqHvk0hrZ8r3EQlr8/td0Yb1/IKzeQ34638uXf9UxK7C6o+ilsmJDR4PHJUfZL23
-    Yb9qWJ0GEzd5AMsI7x6KuUxSuH9nKniv5Tzyty3Xmb4FwXUyADWE19cVuaT+HrFz
-    GraKnA3UXbEgWAU48/l4K2HcAHyHDD2Kbp8k+o1zUkH0fWUdfE6OUGtx19Fv44Jh
-    B7xDngK8K48C6nrj06/DSYfXlb2X7WQiapeG4jt6U57tLH2XAjHCkvu0IBZ+//+P
-    yIWduEHQ3w8FBRcIsTNJo5CjkGk580TVQB/OBLWfX48Ay3oF9zgnomDIlVjl9D0n
-    lKxw/KMCLkvB78rUeGbr1Kwj36FhGpTBw3FgcYGa5oWFZTlcOgMTXLqlbb9JnDlA
-    Zs7Tu0WTyOTV/Dne9nEm39Dzu6wRojiIpmygTD4FI7rmOy3CYNvL3XPv7XQj0hny
-    Ee/fLxugYlQnwPZSqOVEQY2HsG7AmEHRsvy4bIWIGt+yzAPZixt9MUdJh91ttRt7
-    QA/8J1pAsGqEuQpF6UUINZop3J7twfhO4zWYN/NNQ52eWNX2KLfjfGRhrvatzmZ0
-    BuCsCI9hwEeE6PTlhbX1Rs177MrDc3vlqz2V3Po0OrFjXAyg9DR/OC4iK5wOG2ZD
-    7StVSP8bzwQXsz3fJ0ardKXgnU2YDAP6Vykjgt+nFI09HV/S2faOc2g/UK4Y2khl
-    J93u/GHMz/Kr3bKWGY1/6nPdIdFheQjsiNhd5gI4tWik2B3QwU9mETToZ2LSvDHU
-    jYCys576xJLkdMM6nJdq72z4tCoES9IxyHVs4uLjHKIo/ZtKr+8xDo8IL4ax3U8+
-    NMhs/lwReHmPGahm1fu9zLRbNCVL7e0zrOqbjvKcSEftObpV/LLcPYXtEm+lZcck
-    /PMw49HSE364anKEXCH1cyVWJwdZRpFUHvRpLIrpHru7/cthhiEMdLgK1/x8sLob
-    DiyieLxH1DPeXT4X+z94ER4IuPVOcV5AXc/omghispEX6DNUnn5jC4e3WyabjUbw
-    MuO9lVH9Wi2/ynExCqVmQkdbTXuLwjni1fJ27Q5zb0aCmhO8eq6P869NCjhJuiUj
-    NI9XtGLP50YVWE0kL8KEJqnyFudky8Khzk4/dyixQFqin5GfT4vetrLunGHy7lRB
-    3LpnFrpMOr+0xr1RW1k9vlmjRsJSiojJfReYO7gH3B5swiww2azogoL+4jhF1Jxh
-    OYLWdkKhP2jSVGqtIDtny0O4lBm2+hLpWjiI0mJQ7wdA
-    -----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ+Sg92Hgy8EgVPf7t
+Hen1qwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEB5UX2xdDO8/AKA8
++Y5CZyUEggTQkArh4mMPpnAe3xOcDKMz8KCn5lrLb/6Dla7Rp9LHKGkUfyI11EZt
+m+OIriwy9oDQquKyVuLQVGAxXKk+3pyxMqLB0i3hLYamT3vzoPctyVwjuRuKoU3E
+CbF0YhCoxvWMvjHsolwYzx00DbLXouE4BGKvPjnhw5hwtdoZ9Px0ZnCXCxVXi8z/
+mlw7a2ptKEiHQVjuPPbttq+dA+ez7pbWonWVod5TMaPtyEZu5XfPD+0pMboceHZg
+H8ehgUhV3mzEJiisFGg1q9hj+4BaFl5m4tvqp43inCCdShE78CNnOPzJ7WCjKJqi
+jGvHjeMoVx3rZXHcZDAzfIZvDigp9uAfzjRJjpRG8sg5sDQVC7vdUhQDe5TorKT2
+Vb0tdVYxoEpMJ3dhU6Ds5JxMR6GTLjsjTqOkAl6db3HxulwfEpr7YjOpfODR+ttA
+BeIcUcMLsDHayIaQaMLIftHxOkfX7UxoFW9CMG5UMQf/m3eEgVUwgK/E5sUJRUTo
+yhRzJ4NAP4fgc4YH9tbzvUrhfdCXCBEOn6IlDQL66SZr8Mm+Ggu4Ij4TnKWXLrXL
+nSTDDa42kPOvtedKqxC/uXE7rrfh+uyw6J6OjSl6u86TIebndLuDo5DTdWKh8rsg
+fvZZ6332dfMp8JC9/4YnYIJdI7acInSoyHp52OB+2+dgYCr5OrZFjjKS7nELVfo7
+OxGy6uH3NHF9qyUEf3MN17TRHI7jP3zKbXcDTPSyxLQkWe/CU5B251CTmoTSidSW
+EhKnPlGZYbpVQJ4KGEL5UeY8W9PXQo4Dl7TmXBGvuPqNF8kMB3XrPIph7GmihmX0
+nlJqLk9eiRFmUETS0IdAyKJrm4R9Hf6rjYCbXlaApylyVUdSZ2BxgeoTY9BA6Kgf
+3xlgMv01MoUkXMx2+OLIc9MzhButQiDxh3mfS012CjKqUFrJhRSa8DOpUfVgmXpq
+/HP4drWamLWYJR8FsmJS11ZYc1EK/ctJTSpqfewvoUGOSHomhh7zXn1Acb6+9/3p
+bcrJjoR5K8Jg6NlG4dSNkpY/x92I7bFLXFqELIH5tteDrlQen5eASjaiyPPAoOw8
+IGfOmFS4VUPh1VP6g8Jtn5Hr2qXB3DoQoI6EvUZhJ6GJfi67mx5VKux6G9MzJkix
+GU1cL4WzWK2DU0l39UxXjS+4TmOYbrqLVnVMjusX0fwb8LkDC/fVohbhLwhHNwu6
+nSTSEpS9zSDrv1JXFtAtPv6XCSFs6ssPWJMwGSdThn7EfV0GEhG2mCzTyVhwxxQo
+6U/Suqq4oMZoracPUCZx0E4u/bb4KBoFA/eBNPJENTR18IiV+D7wAxlxauO3N1t4
+iJxwrrvSgQPmOGuxrh5LVD41UXYUWLtndzabnpByppFn2MbmvrqJgon0MSs84cTA
+7scnbPu1V3PpKy/t67gtVw9Ue8hLjrskWB1JPFYr7vRWvJzYjfbflyroF+QEJ3TA
+6rTfUC9+ePci6T+i9jF4xcmzqYzRtnGtp5nRUitJGw0uwBTDwzfI2WD6ltvvu7lc
+pHuzvY5zEapuu1JhjHLUd+OE8rVVM999DUXo/IDLsWyRCphCiYfVXJNogd9rB0Ta
+5AhVgpRhxkarBURZyLTYj7NRxCsbHq7XExJNrIdRG/KlBQfyEyIzZ7E=
+-----END ENCRYPTED PRIVATE KEY-----
     EOF
-    assert_same_rsa rsa, OpenSSL::PKey.read(pem, "abcdef")
+    assert_same_rsa rsa, OpenSSL::PKey.read(pem, "abcdefgh")
   end
 
   def test_params

