1919VALUE mPKey ;
2020VALUE cPKey ;
2121VALUE ePKeyError ;
22- static ID id_private_q ;
22+ ID ossl_pkey_feature_id ;
2323
2424static void
2525ossl_evp_pkey_free (void * ptr )
@@ -65,7 +65,7 @@ pkey_new0(VALUE arg)
6565}
6666
6767VALUE
68- ossl_pkey_new (EVP_PKEY * pkey )
68+ ossl_pkey_new (EVP_PKEY * pkey , enum ossl_pkey_feature ps )
6969{
7070 VALUE obj ;
7171 int status ;
@@ -75,6 +75,7 @@ ossl_pkey_new(EVP_PKEY *pkey)
7575 EVP_PKEY_free (pkey );
7676 rb_jump_tag (status );
7777 }
78+ ossl_pkey_set (obj , ps );
7879
7980 return obj ;
8081}
@@ -83,29 +84,28 @@ ossl_pkey_new(EVP_PKEY *pkey)
8384# include <openssl/decoder.h>
8485
8586EVP_PKEY *
86- ossl_pkey_read_generic (BIO * bio , VALUE pass , const char * input_type )
87+ ossl_pkey_read_generic (BIO * bio , VALUE pass , const char * input_type , enum ossl_pkey_feature * ps )
8788{
8889 void * ppass = (void * )pass ;
8990 OSSL_DECODER_CTX * dctx ;
9091 EVP_PKEY * pkey = NULL ;
9192 int pos = 0 , pos2 ;
93+ size_t i ;
9294
9395 dctx = OSSL_DECODER_CTX_new_for_pkey (& pkey , "DER" , NULL , input_type , 0 , NULL , NULL );
9496 if (!dctx )
9597 goto out ;
9698 if (OSSL_DECODER_CTX_set_pem_password_cb (dctx , ossl_pem_passwd_cb , ppass ) != 1 )
9799 goto out ;
98100
99- /* First check DER */
100- if (OSSL_DECODER_from_bio (dctx , bio ) == 1 )
101- goto out ;
102- OSSL_BIO_reset (bio );
103-
104- /* Then check PEM; multiple OSSL_DECODER_from_bio() calls may be needed */
105- if (OSSL_DECODER_CTX_set_input_type (dctx , "PEM" ) != 1 )
106- goto out ;
107101 /*
108- * First check for private key formats. This is to keep compatibility with
102+ * This is very inefficient as it will try to decode the same DER/PEM
103+ * encoding repeatedly, but OpenSSL 3.0 doesn't provide an API to return
104+ * what kind of information an EVP_PKEY is holding.
105+ * OpenSSL issue: https://github.com/openssl/openssl/issues/9467
106+ *
107+ * The attempt order of selections is important: private key formats are
108+ * checked first. This is to keep compatibility with
109109 * ruby/openssl < 3.0 which decoded the following as a private key.
110110 *
111111 * $ openssl ecparam -name prime256v1 -genkey -outform PEM
@@ -125,59 +125,94 @@ ossl_pkey_read_generic(BIO *bio, VALUE pass, const char *input_type)
125125 * Note that normally, the input is supposed to contain a single decodable
126126 * PEM block only, so this special handling should not create a new problem.
127127 */
128- OSSL_DECODER_CTX_set_selection (dctx , EVP_PKEY_KEYPAIR );
129- while (1 ) {
128+ struct { int selection ; enum ossl_pkey_feature ps ; } selections [] = {
129+ #if OSSL_OPENSSL_PREREQ (3 , 0 , 0 ) && !OSSL_OPENSSL_PREREQ (3 , 0 , 3 )
130+ /*
131+ * Public key formats are checked last, with 0 (any) selection to avoid
132+ * segfault in OpenSSL <= 3.0.3.
133+ * Fixed by https://github.com/openssl/openssl/pull/18462
134+ *
135+ * This workaround is mainly for Ubuntu 22.04, which currently has yet
136+ * to backport it (as of 2022-07-26).
137+ */
138+ { EVP_PKEY_KEYPAIR , OSSL_PKEY_HAS_PRIVATE },
139+ { EVP_PKEY_KEY_PARAMETERS , OSSL_PKEY_HAS_NONE },
140+ { 0 , OSSL_PKEY_HAS_PUBLIC },
141+ #else
142+ { EVP_PKEY_KEYPAIR , OSSL_PKEY_HAS_PRIVATE },
143+ { EVP_PKEY_PUBLIC_KEY , OSSL_PKEY_HAS_PUBLIC },
144+ { EVP_PKEY_KEY_PARAMETERS , OSSL_PKEY_HAS_NONE },
145+ #endif
146+ };
147+
148+ /* First check DER */
149+ for (i = 0 ; i < sizeof (selections )/sizeof (selections [0 ]); i ++ ) {
150+ OSSL_DECODER_CTX_set_selection (dctx , selections [i ].selection );
151+ * ps = selections [i ].ps ;
130152 if (OSSL_DECODER_from_bio (dctx , bio ) == 1 )
131153 goto out ;
132- if (BIO_eof (bio ))
133- break ;
134- pos2 = BIO_tell (bio );
135- if (pos2 < 0 || pos2 <= pos )
136- break ;
137- ossl_clear_error ();
138- pos = pos2 ;
154+ OSSL_BIO_reset (bio );
139155 }
140156
141- OSSL_BIO_reset (bio );
142- OSSL_DECODER_CTX_set_selection (dctx , 0 );
143- while (1 ) {
144- if (OSSL_DECODER_from_bio (dctx , bio ) == 1 )
145- goto out ;
146- if (BIO_eof (bio ))
147- break ;
148- pos2 = BIO_tell (bio );
149- if (pos2 < 0 || pos2 <= pos )
150- break ;
151- ossl_clear_error ();
152- pos = pos2 ;
157+ /* Then check PEM; multiple OSSL_DECODER_from_bio() calls may be needed */
158+ if (OSSL_DECODER_CTX_set_input_type (dctx , "PEM" ) != 1 )
159+ goto out ;
160+ for (i = 0 ; i < sizeof (selections )/sizeof (selections [0 ]); i ++ ) {
161+ OSSL_DECODER_CTX_set_selection (dctx , selections [i ].selection );
162+ * ps = selections [i ].ps ;
163+ while (1 ) {
164+ if (OSSL_DECODER_from_bio (dctx , bio ) == 1 )
165+ goto out ;
166+ if (BIO_eof (bio ))
167+ break ;
168+ pos2 = BIO_tell (bio );
169+ if (pos2 < 0 || pos2 <= pos )
170+ break ;
171+ ossl_clear_error ();
172+ pos = pos2 ;
173+ }
174+ OSSL_BIO_reset (bio );
153175 }
154176
155177 out :
156178 OSSL_DECODER_CTX_free (dctx );
179+ #if OSSL_OPENSSL_PREREQ (3 , 0 , 0 ) && !OSSL_OPENSSL_PREREQ (3 , 0 , 3 )
180+ /* It also leaks an error queue entry in the success path */
181+ if (pkey ) ossl_clear_error ();
182+ #endif
157183 return pkey ;
158184}
159185#else
160186EVP_PKEY *
161- ossl_pkey_read_generic (BIO * bio , VALUE pass , const char * input_type )
187+ ossl_pkey_read_generic (BIO * bio , VALUE pass , const char * input_type , enum ossl_pkey_feature * ps )
162188{
163189 void * ppass = (void * )pass ;
164190 EVP_PKEY * pkey ;
165191
192+ * ps = OSSL_PKEY_HAS_PRIVATE ;
166193 if ((pkey = d2i_PrivateKey_bio (bio , NULL )))
167194 goto out ;
168195 OSSL_BIO_reset (bio );
169196 if ((pkey = d2i_PKCS8PrivateKey_bio (bio , NULL , ossl_pem_passwd_cb , ppass )))
170197 goto out ;
198+
199+ * ps = OSSL_PKEY_HAS_PUBLIC ;
171200 OSSL_BIO_reset (bio );
172201 if ((pkey = d2i_PUBKEY_bio (bio , NULL )))
173202 goto out ;
174- OSSL_BIO_reset ( bio );
203+
175204 /* PEM_read_bio_PrivateKey() also parses PKCS #8 formats */
205+ * ps = OSSL_PKEY_HAS_PRIVATE ;
206+ OSSL_BIO_reset (bio );
176207 if ((pkey = PEM_read_bio_PrivateKey (bio , NULL , ossl_pem_passwd_cb , ppass )))
177208 goto out ;
209+
210+ * ps = OSSL_PKEY_HAS_PUBLIC ;
178211 OSSL_BIO_reset (bio );
179212 if ((pkey = PEM_read_bio_PUBKEY (bio , NULL , NULL , NULL )))
180213 goto out ;
214+
215+ * ps = OSSL_PKEY_HAS_NONE ;
181216 OSSL_BIO_reset (bio );
182217 if ((pkey = PEM_read_bio_Parameters (bio , NULL )))
183218 goto out ;
@@ -234,14 +269,15 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self)
234269 EVP_PKEY * pkey ;
235270 BIO * bio ;
236271 VALUE data , pass ;
272+ enum ossl_pkey_feature ps ;
237273
238274 rb_scan_args (argc , argv , "11" , & data , & pass );
239275 bio = ossl_obj2bio (& data );
240- pkey = ossl_pkey_read_generic (bio , ossl_pem_passwd_value (pass ), NULL );
276+ pkey = ossl_pkey_read_generic (bio , ossl_pem_passwd_value (pass ), NULL , & ps );
241277 BIO_free (bio );
242278 if (!pkey )
243279 ossl_raise (ePKeyError , "Could not parse PKey" );
244- return ossl_pkey_new (pkey );
280+ return ossl_pkey_new (pkey , ps );
245281}
246282
247283static VALUE
@@ -445,7 +481,7 @@ pkey_generate(int argc, VALUE *argv, VALUE self, int genparam)
445481 }
446482 }
447483
448- return ossl_pkey_new (gen_arg .pkey );
484+ return ossl_pkey_new (gen_arg .pkey , OSSL_PKEY_HAS_PRIVATE );
449485}
450486
451487/*
@@ -567,18 +603,9 @@ GetPrivPKeyPtr(VALUE obj)
567603 EVP_PKEY * pkey ;
568604
569605 GetPKey (obj , pkey );
570- if (OSSL_PKEY_IS_PRIVATE (obj ))
571- return pkey ;
572- /*
573- * The EVP API does not provide a way to check if the EVP_PKEY has private
574- * components. Assuming it does...
575- */
576- if (!rb_respond_to (obj , id_private_q ))
577- return pkey ;
578- if (RTEST (rb_funcallv (obj , id_private_q , 0 , NULL )))
579- return pkey ;
580-
581- rb_raise (rb_eArgError , "private key is needed" );
606+ if (!ossl_pkey_has (obj , OSSL_PKEY_HAS_PRIVATE ))
607+ rb_raise (rb_eArgError , "private key is needed" );
608+ return pkey ;
582609}
583610
584611EVP_PKEY *
@@ -654,6 +681,33 @@ ossl_pkey_oid(VALUE self)
654681 return rb_str_new_cstr (OBJ_nid2sn (nid ));
655682}
656683
684+ /*
685+ * call-seq:
686+ * pkey.public? -> true | false
687+ *
688+ * Indicates whether this PKey instance has a public key associated with it or
689+ * not.
690+ */
691+ static VALUE
692+ ossl_pkey_is_public (VALUE self )
693+ {
694+ return ossl_pkey_has (self , OSSL_PKEY_HAS_PUBLIC ) ? Qtrue : Qfalse ;
695+ }
696+
697+ /*
698+ * call-seq:
699+ * pkey.private? -> true | false
700+ *
701+ * Indicates whether this PKey instance has a private key associated with it or
702+ * not.
703+ */
704+ static VALUE
705+ ossl_pkey_is_private (VALUE self )
706+ {
707+ return ossl_pkey_has (self , OSSL_PKEY_HAS_PRIVATE ) ? Qtrue : Qfalse ;
708+ }
709+
710+
657711/*
658712 * call-seq:
659713 * pkey.inspect -> string
@@ -1620,6 +1674,8 @@ Init_ossl_pkey(void)
16201674 rb_undef_method (cPKey , "initialize_copy" );
16211675#endif
16221676 rb_define_method (cPKey , "oid" , ossl_pkey_oid , 0 );
1677+ rb_define_method (cPKey , "public?" , ossl_pkey_is_public , 0 );
1678+ rb_define_method (cPKey , "private?" , ossl_pkey_is_private , 0 );
16231679 rb_define_method (cPKey , "inspect" , ossl_pkey_inspect , 0 );
16241680 rb_define_method (cPKey , "to_text" , ossl_pkey_to_text , 0 );
16251681 rb_define_method (cPKey , "private_to_der" , ossl_pkey_private_to_der , -1 );
@@ -1637,7 +1693,7 @@ Init_ossl_pkey(void)
16371693 rb_define_method (cPKey , "encrypt" , ossl_pkey_encrypt , -1 );
16381694 rb_define_method (cPKey , "decrypt" , ossl_pkey_decrypt , -1 );
16391695
1640- id_private_q = rb_intern ( "private? " );
1696+ ossl_pkey_feature_id = rb_intern_const ( "state " );
16411697
16421698 /*
16431699 * INIT rsa, dsa, dh, ec
0 commit comments