Skip to content

Commit c601f4c

Browse files
committed
cipher: use EVP_CIPHER_fetch() if available
Likewise, use EVP_MD_fetch() if it is available. This adds support for AES-GCM-SIV with OpenSSL 3.2 or later.
1 parent d3b229c commit c601f4c

File tree

5 files changed

+94
-29
lines changed

5 files changed

+94
-29
lines changed

ext/openssl/ossl_cipher.c

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
static VALUE cCipher;
3434
static VALUE eCipherError;
3535
static VALUE eAuthTagError;
36-
static ID id_auth_tag_len, id_key_set;
36+
static ID id_auth_tag_len, id_key_set, id_cipher_holder;
3737

3838
static VALUE ossl_cipher_alloc(VALUE klass);
3939
static void ossl_cipher_free(void *ptr);
@@ -46,30 +46,58 @@ static const rb_data_type_t ossl_cipher_type = {
4646
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
4747
};
4848

49+
#ifdef OSSL_USE_PROVIDER
50+
static void
51+
ossl_evp_cipher_free(void *ptr)
52+
{
53+
// This is safe to call against const EVP_CIPHER * returned by
54+
// EVP_get_cipherbyname()
55+
EVP_CIPHER_free(ptr);
56+
}
57+
58+
static const rb_data_type_t ossl_evp_cipher_holder_type = {
59+
"OpenSSL/EVP_CIPHER",
60+
{
61+
.dfree = ossl_evp_cipher_free,
62+
},
63+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
64+
};
65+
#endif
66+
4967
/*
5068
* PUBLIC
5169
*/
5270
const EVP_CIPHER *
53-
ossl_evp_get_cipherbyname(VALUE obj)
71+
ossl_evp_cipher_fetch(VALUE obj, volatile VALUE *holder)
5472
{
73+
*holder = Qnil;
5574
if (rb_obj_is_kind_of(obj, cCipher)) {
56-
EVP_CIPHER_CTX *ctx;
57-
58-
GetCipher(obj, ctx);
59-
60-
return EVP_CIPHER_CTX_cipher(ctx);
75+
EVP_CIPHER_CTX *ctx;
76+
GetCipher(obj, ctx);
77+
EVP_CIPHER *cipher = (EVP_CIPHER *)EVP_CIPHER_CTX_cipher(ctx);
78+
#ifdef OSSL_USE_PROVIDER
79+
*holder = TypedData_Wrap_Struct(0, &ossl_evp_cipher_holder_type, NULL);
80+
if (!EVP_CIPHER_up_ref(cipher))
81+
ossl_raise(eCipherError, "EVP_CIPHER_up_ref");
82+
RTYPEDDATA_DATA(*holder) = cipher;
83+
#endif
84+
return cipher;
6185
}
62-
else {
63-
const EVP_CIPHER *cipher;
6486

65-
StringValueCStr(obj);
66-
cipher = EVP_get_cipherbyname(RSTRING_PTR(obj));
67-
if (!cipher)
68-
ossl_raise(rb_eArgError,
69-
"unsupported cipher algorithm: %"PRIsVALUE, obj);
70-
71-
return cipher;
87+
const char *name = StringValueCStr(obj);
88+
EVP_CIPHER *cipher = (EVP_CIPHER *)EVP_get_cipherbyname(name);
89+
#ifdef OSSL_USE_PROVIDER
90+
if (!cipher) {
91+
ossl_clear_error();
92+
*holder = TypedData_Wrap_Struct(0, &ossl_evp_cipher_holder_type, NULL);
93+
cipher = EVP_CIPHER_fetch(NULL, name, NULL);
94+
RTYPEDDATA_DATA(*holder) = cipher;
7295
}
96+
#endif
97+
if (!cipher)
98+
ossl_raise(rb_eArgError, "unsupported cipher algorithm: %"PRIsVALUE,
99+
obj);
100+
return cipher;
73101
}
74102

75103
VALUE
@@ -78,6 +106,9 @@ ossl_cipher_new(const EVP_CIPHER *cipher)
78106
VALUE ret;
79107
EVP_CIPHER_CTX *ctx;
80108

109+
// NOTE: This does not set id_cipher_holder because this function should
110+
// only be called from ossl_engine.c, which will not use any
111+
// reference-counted ciphers.
81112
ret = ossl_cipher_alloc(cCipher);
82113
AllocCipher(ret, ctx);
83114
if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1)
@@ -114,15 +145,17 @@ ossl_cipher_initialize(VALUE self, VALUE str)
114145
{
115146
EVP_CIPHER_CTX *ctx;
116147
const EVP_CIPHER *cipher;
148+
VALUE cipher_holder;
117149

118150
GetCipherInit(self, ctx);
119151
if (ctx) {
120152
ossl_raise(rb_eRuntimeError, "Cipher already initialized!");
121153
}
122-
cipher = ossl_evp_get_cipherbyname(str);
154+
cipher = ossl_evp_cipher_fetch(str, &cipher_holder);
123155
AllocCipher(self, ctx);
124156
if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1)
125-
ossl_raise(eCipherError, NULL);
157+
ossl_raise(eCipherError, "EVP_CipherInit_ex");
158+
rb_ivar_set(self, id_cipher_holder, cipher_holder);
126159

127160
return self;
128161
}
@@ -1106,4 +1139,5 @@ Init_ossl_cipher(void)
11061139

11071140
id_auth_tag_len = rb_intern_const("auth_tag_len");
11081141
id_key_set = rb_intern_const("key_set");
1142+
id_cipher_holder = rb_intern_const("EVP_CIPHER_holder");
11091143
}

ext/openssl/ossl_cipher.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,16 @@
1010
#if !defined(_OSSL_CIPHER_H_)
1111
#define _OSSL_CIPHER_H_
1212

13-
const EVP_CIPHER *ossl_evp_get_cipherbyname(VALUE);
13+
/*
14+
* Gets EVP_CIPHER from a String or an OpenSSL::Digest instance (discouraged,
15+
* but still supported for compatibility). A holder object is created if the
16+
* EVP_CIPHER is a "fetched" algorithm.
17+
*/
18+
const EVP_CIPHER *ossl_evp_cipher_fetch(VALUE obj, volatile VALUE *holder);
19+
/*
20+
* This is meant for OpenSSL::Engine#cipher. EVP_CIPHER must not be a fetched
21+
* one.
22+
*/
1423
VALUE ossl_cipher_new(const EVP_CIPHER *);
1524
void Init_ossl_cipher(void);
1625

ext/openssl/ossl_pkcs7.c

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ static VALUE cPKCS7;
6868
static VALUE cPKCS7Signer;
6969
static VALUE cPKCS7Recipient;
7070
static VALUE ePKCS7Error;
71-
static ID id_md_holder;
71+
static ID id_md_holder, id_cipher_holder;
7272

7373
static void
7474
ossl_pkcs7_free(void *ptr)
@@ -313,7 +313,7 @@ ossl_pkcs7_s_sign(int argc, VALUE *argv, VALUE klass)
313313
static VALUE
314314
ossl_pkcs7_s_encrypt(int argc, VALUE *argv, VALUE klass)
315315
{
316-
VALUE certs, data, cipher, flags;
316+
VALUE certs, data, cipher, flags, cipher_holder;
317317
STACK_OF(X509) *x509s;
318318
BIO *in;
319319
const EVP_CIPHER *ciph;
@@ -327,7 +327,7 @@ ossl_pkcs7_s_encrypt(int argc, VALUE *argv, VALUE klass)
327327
"cipher must be specified. Before version 3.3, " \
328328
"the default cipher was RC2-40-CBC.");
329329
}
330-
ciph = ossl_evp_get_cipherbyname(cipher);
330+
ciph = ossl_evp_cipher_fetch(cipher, &cipher_holder);
331331
flg = NIL_P(flags) ? 0 : NUM2INT(flags);
332332
ret = NewPKCS7(cPKCS7);
333333
in = ossl_obj2bio(&data);
@@ -344,6 +344,7 @@ ossl_pkcs7_s_encrypt(int argc, VALUE *argv, VALUE klass)
344344
BIO_free(in);
345345
SetPKCS7(ret, p7);
346346
ossl_pkcs7_set_data(ret, data);
347+
rb_ivar_set(ret, id_cipher_holder, cipher_holder);
347348
sk_X509_pop_free(x509s, X509_free);
348349

349350
return ret;
@@ -536,11 +537,13 @@ static VALUE
536537
ossl_pkcs7_set_cipher(VALUE self, VALUE cipher)
537538
{
538539
PKCS7 *pkcs7;
540+
const EVP_CIPHER *ciph;
541+
VALUE cipher_holder;
539542

540543
GetPKCS7(self, pkcs7);
541-
if (!PKCS7_set_cipher(pkcs7, ossl_evp_get_cipherbyname(cipher))) {
542-
ossl_raise(ePKCS7Error, NULL);
543-
}
544+
ciph = ossl_evp_cipher_fetch(cipher, &cipher_holder);
545+
if (!PKCS7_set_cipher(pkcs7, ciph))
546+
ossl_raise(ePKCS7Error, "PKCS7_set_cipher");
544547

545548
return cipher;
546549
}
@@ -1165,4 +1168,5 @@ Init_ossl_pkcs7(void)
11651168
DefPKCS7Const(NOSMIMECAP);
11661169

11671170
id_md_holder = rb_intern_const("EVP_MD_holder");
1171+
id_cipher_holder = rb_intern_const("EVP_CIPHER_holder");
11681172
}

ext/openssl/ossl_pkey.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -814,14 +814,14 @@ VALUE
814814
ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, int to_der)
815815
{
816816
EVP_PKEY *pkey;
817-
VALUE cipher, pass;
817+
VALUE cipher, pass, cipher_holder;
818818
const EVP_CIPHER *enc = NULL;
819819
BIO *bio;
820820

821821
GetPKey(self, pkey);
822822
rb_scan_args(argc, argv, "02", &cipher, &pass);
823823
if (!NIL_P(cipher)) {
824-
enc = ossl_evp_get_cipherbyname(cipher);
824+
enc = ossl_evp_cipher_fetch(cipher, &cipher_holder);
825825
pass = ossl_pem_passwd_value(pass);
826826
}
827827

@@ -849,7 +849,7 @@ static VALUE
849849
do_pkcs8_export(int argc, VALUE *argv, VALUE self, int to_der)
850850
{
851851
EVP_PKEY *pkey;
852-
VALUE cipher, pass;
852+
VALUE cipher, pass, cipher_holder;
853853
const EVP_CIPHER *enc = NULL;
854854
BIO *bio;
855855

@@ -860,7 +860,7 @@ do_pkcs8_export(int argc, VALUE *argv, VALUE self, int to_der)
860860
* TODO: EncryptedPrivateKeyInfo actually has more options.
861861
* Should they be exposed?
862862
*/
863-
enc = ossl_evp_get_cipherbyname(cipher);
863+
enc = ossl_evp_cipher_fetch(cipher, &cipher_holder);
864864
pass = ossl_pem_passwd_value(pass);
865865
}
866866

test/openssl/test_cipher.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,24 @@ def test_aes_ocb_tag_len
343343

344344
end if has_cipher?("aes-128-ocb")
345345

346+
def test_aes_gcm_siv
347+
# RFC 8452 Appendix C.1., 8th example
348+
key = ["01000000000000000000000000000000"].pack("H*")
349+
iv = ["030000000000000000000000"].pack("H*")
350+
aad = ["01"].pack("H*")
351+
pt = ["0200000000000000"].pack("H*")
352+
ct = ["1e6daba35669f4273b0a1a2560969cdf790d99759abd1508"].pack("H*")
353+
tag = ["3b0a1a2560969cdf790d99759abd1508"].pack("H*")
354+
ct_without_tag = ct.byteslice(0, ct.bytesize - tag.bytesize)
355+
356+
cipher = new_encryptor("aes-128-gcm-siv", key: key, iv: iv, auth_data: aad)
357+
assert_equal ct_without_tag, cipher.update(pt) << cipher.final
358+
assert_equal tag, cipher.auth_tag
359+
cipher = new_decryptor("aes-128-gcm-siv", key: key, iv: iv, auth_tag: tag,
360+
auth_data: aad)
361+
assert_equal pt, cipher.update(ct_without_tag) << cipher.final
362+
end if openssl?(3, 2, 0)
363+
346364
def test_aes_gcm_key_iv_order_issue
347365
pt = "[ruby/openssl#49]"
348366
cipher = OpenSSL::Cipher.new("aes-128-gcm").encrypt

0 commit comments

Comments
 (0)