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,34 +84,61 @@ 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
101+ /*
102+ * This is inefficient as it will parse the same DER/PEM encoding
103+ * repeatedly, but OpenSSL 3.0 doesn't provide an API to return what
104+ * information an EVP_PKEY is holding.
105+ * OpenSSL issue: https://github.com/openssl/openssl/issues/9467
106+ */
107+ struct { int selection ; enum ossl_pkey_feature ps ; } selections [] = {
108+ { EVP_PKEY_KEYPAIR , OSSL_PKEY_HAS_PRIVATE },
109+ { EVP_PKEY_PUBLIC_KEY , OSSL_PKEY_HAS_PUBLIC },
110+ { EVP_PKEY_KEY_PARAMETERS , OSSL_PKEY_HAS_NONE },
111+ };
112+
99113 /* First check DER */
100- if (OSSL_DECODER_from_bio (dctx , bio ) == 1 )
101- goto out ;
114+ for (i = 0 ; i < sizeof (selections )/sizeof (selections [0 ]); i ++ ) {
115+ OSSL_DECODER_CTX_set_selection (dctx , selections [i ].selection );
116+ * ps = selections [i ].ps ;
117+ if (OSSL_DECODER_from_bio (dctx , bio ) == 1 )
118+ goto out ;
119+ OSSL_BIO_reset (bio );
120+ }
102121
103- /* Then check PEM; multiple OSSL_DECODER_from_bio() calls may be needed */
104- OSSL_BIO_reset (bio );
122+ /*
123+ * Then check PEM; multiple OSSL_DECODER_from_bio() calls may be needed for
124+ * each selection in case of stacked PEM structures
125+ */
105126 if (OSSL_DECODER_CTX_set_input_type (dctx , "PEM" ) != 1 )
106127 goto out ;
107- while (OSSL_DECODER_from_bio (dctx , bio ) != 1 ) {
108- if (BIO_eof (bio ))
109- goto out ;
110- pos2 = BIO_tell (bio );
111- if (pos2 < 0 || pos2 <= pos )
112- goto out ;
113- pos = pos2 ;
128+ for (i = 0 ; i < sizeof (selections )/sizeof (selections [0 ]); i ++ ) {
129+ OSSL_DECODER_CTX_set_selection (dctx , selections [i ].selection );
130+ * ps = selections [i ].ps ;
131+ while (true) {
132+ if (OSSL_DECODER_from_bio (dctx , bio ) == 1 )
133+ goto out ;
134+ if (BIO_eof (bio ))
135+ break ;
136+ pos2 = BIO_tell (bio );
137+ if (pos2 < 0 || pos2 <= pos )
138+ break ;
139+ pos = pos2 ;
140+ }
141+ OSSL_BIO_reset (bio );
114142 }
115143
116144 out :
@@ -119,26 +147,35 @@ ossl_pkey_read_generic(BIO *bio, VALUE pass, const char *input_type)
119147}
120148#else
121149EVP_PKEY *
122- ossl_pkey_read_generic (BIO * bio , VALUE pass , const char * input_type )
150+ ossl_pkey_read_generic (BIO * bio , VALUE pass , const char * input_type , enum ossl_pkey_feature * ps )
123151{
124152 void * ppass = (void * )pass ;
125153 EVP_PKEY * pkey ;
126154
155+ * ps = OSSL_PKEY_HAS_PRIVATE ;
127156 if ((pkey = d2i_PrivateKey_bio (bio , NULL )))
128157 goto out ;
129158 OSSL_BIO_reset (bio );
130159 if ((pkey = d2i_PKCS8PrivateKey_bio (bio , NULL , ossl_pem_passwd_cb , ppass )))
131160 goto out ;
161+
162+ * ps = OSSL_PKEY_HAS_PUBLIC ;
132163 OSSL_BIO_reset (bio );
133164 if ((pkey = d2i_PUBKEY_bio (bio , NULL )))
134165 goto out ;
135- OSSL_BIO_reset ( bio );
166+
136167 /* PEM_read_bio_PrivateKey() also parses PKCS #8 formats */
168+ * ps = OSSL_PKEY_HAS_PRIVATE ;
169+ OSSL_BIO_reset (bio );
137170 if ((pkey = PEM_read_bio_PrivateKey (bio , NULL , ossl_pem_passwd_cb , ppass )))
138171 goto out ;
172+
173+ * ps = OSSL_PKEY_HAS_PUBLIC ;
139174 OSSL_BIO_reset (bio );
140175 if ((pkey = PEM_read_bio_PUBKEY (bio , NULL , NULL , NULL )))
141176 goto out ;
177+
178+ * ps = OSSL_PKEY_HAS_NONE ;
142179 OSSL_BIO_reset (bio );
143180 if ((pkey = PEM_read_bio_Parameters (bio , NULL )))
144181 goto out ;
@@ -195,14 +232,15 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self)
195232 EVP_PKEY * pkey ;
196233 BIO * bio ;
197234 VALUE data , pass ;
235+ enum ossl_pkey_feature ps ;
198236
199237 rb_scan_args (argc , argv , "11" , & data , & pass );
200238 bio = ossl_obj2bio (& data );
201- pkey = ossl_pkey_read_generic (bio , ossl_pem_passwd_value (pass ), NULL );
239+ pkey = ossl_pkey_read_generic (bio , ossl_pem_passwd_value (pass ), NULL , & ps );
202240 BIO_free (bio );
203241 if (!pkey )
204242 ossl_raise (ePKeyError , "Could not parse PKey" );
205- return ossl_pkey_new (pkey );
243+ return ossl_pkey_new (pkey , ps );
206244}
207245
208246static VALUE
@@ -405,7 +443,7 @@ pkey_generate(int argc, VALUE *argv, VALUE self, int genparam)
405443 }
406444 }
407445
408- return ossl_pkey_new (gen_arg .pkey );
446+ return ossl_pkey_new (gen_arg .pkey , OSSL_PKEY_HAS_PRIVATE );
409447}
410448
411449/*
@@ -527,18 +565,9 @@ GetPrivPKeyPtr(VALUE obj)
527565 EVP_PKEY * pkey ;
528566
529567 GetPKey (obj , pkey );
530- if (OSSL_PKEY_IS_PRIVATE (obj ))
531- return pkey ;
532- /*
533- * The EVP API does not provide a way to check if the EVP_PKEY has private
534- * components. Assuming it does...
535- */
536- if (!rb_respond_to (obj , id_private_q ))
537- return pkey ;
538- if (RTEST (rb_funcallv (obj , id_private_q , 0 , NULL )))
539- return pkey ;
540-
541- rb_raise (rb_eArgError , "private key is needed" );
568+ if (!ossl_pkey_has (obj , OSSL_PKEY_HAS_PRIVATE ))
569+ rb_raise (rb_eArgError , "private key is needed" );
570+ return pkey ;
542571}
543572
544573EVP_PKEY *
@@ -614,6 +643,33 @@ ossl_pkey_oid(VALUE self)
614643 return rb_str_new_cstr (OBJ_nid2sn (nid ));
615644}
616645
646+ /*
647+ * call-seq:
648+ * pkey.public? -> true | false
649+ *
650+ * Indicates whether this PKey instance has a public key associated with it or
651+ * not.
652+ */
653+ static VALUE
654+ ossl_pkey_is_public (VALUE self )
655+ {
656+ return ossl_pkey_has (self , OSSL_PKEY_HAS_PUBLIC ) ? Qtrue : Qfalse ;
657+ }
658+
659+ /*
660+ * call-seq:
661+ * pkey.private? -> true | false
662+ *
663+ * Indicates whether this PKey instance has a private key associated with it or
664+ * not.
665+ */
666+ static VALUE
667+ ossl_pkey_is_private (VALUE self )
668+ {
669+ return ossl_pkey_has (self , OSSL_PKEY_HAS_PRIVATE ) ? Qtrue : Qfalse ;
670+ }
671+
672+
617673/*
618674 * call-seq:
619675 * pkey.inspect -> string
@@ -1580,6 +1636,8 @@ Init_ossl_pkey(void)
15801636 rb_undef_method (cPKey , "initialize_copy" );
15811637#endif
15821638 rb_define_method (cPKey , "oid" , ossl_pkey_oid , 0 );
1639+ rb_define_method (cPKey , "public?" , ossl_pkey_is_public , 0 );
1640+ rb_define_method (cPKey , "private?" , ossl_pkey_is_private , 0 );
15831641 rb_define_method (cPKey , "inspect" , ossl_pkey_inspect , 0 );
15841642 rb_define_method (cPKey , "to_text" , ossl_pkey_to_text , 0 );
15851643 rb_define_method (cPKey , "private_to_der" , ossl_pkey_private_to_der , -1 );
@@ -1597,7 +1655,7 @@ Init_ossl_pkey(void)
15971655 rb_define_method (cPKey , "encrypt" , ossl_pkey_encrypt , -1 );
15981656 rb_define_method (cPKey , "decrypt" , ossl_pkey_decrypt , -1 );
15991657
1600- id_private_q = rb_intern ( "private? " );
1658+ ossl_pkey_feature_id = rb_intern_const ( "state " );
16011659
16021660 /*
16031661 * INIT rsa, dsa, dh, ec
0 commit comments