Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ext/openssl/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ def find_openssl_library
have_func("EVP_MD_CTX_get_pkey_ctx(NULL)", evp_h)
have_func("EVP_PKEY_eq(NULL, NULL)", evp_h)
have_func("EVP_PKEY_dup(NULL)", evp_h)
have_func("EVP_PKEY_encapsulate_init(NULL, NULL)", evp_h)

# added in 3.2.0
have_func("SSL_get0_group_name(NULL)", ssl_h)
Expand Down
2 changes: 1 addition & 1 deletion ext/openssl/openssl.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
if Gem::Platform === spec.platform and spec.platform =~ 'java' or RUBY_ENGINE == 'jruby'
spec.platform = "java"
spec.files = []
spec.add_runtime_dependency('jruby-openssl', '~> 0.14')
spec.add_runtime_dependency('jruby-openssl')
else
spec.files = Dir.glob(["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md"], base: File.expand_path("..", __FILE__)) +
["BSDL", "COPYING"]
Expand Down
120 changes: 120 additions & 0 deletions ext/openssl/ossl_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,122 @@ ossl_pkey_derive(int argc, VALUE *argv, VALUE self)
return str;
}

#ifdef HAVE_EVP_PKEY_ENCAPSULATE_INIT
/*
* call-seq:
* pkey.encapsulate -> [ciphertext, shared_secret]
*
* Performs a key encapsulation operation using the public components of
* _pkey_.
*
* See also the man page EVP_PKEY_encapsulate(3).
*/
static VALUE
ossl_pkey_encapsulate(VALUE self)
{
EVP_PKEY *pkey;
EVP_PKEY_CTX *ctx;
VALUE ciphertext, shared_secret;
size_t ciphertextlen, shared_secretlen;
int state;

GetPKey(self, pkey);
ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
if (!ctx)
ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
if (EVP_PKEY_encapsulate_init(ctx, NULL) <= 0) {
EVP_PKEY_CTX_free(ctx);
ossl_raise(ePKeyError, "EVP_PKEY_encapsulate_init");
}
if (EVP_PKEY_encapsulate(ctx, NULL, &ciphertextlen, NULL, &shared_secretlen) <= 0) {
EVP_PKEY_CTX_free(ctx);
ossl_raise(ePKeyError, "EVP_PKEY_encapsulate");
}
if (ciphertextlen > LONG_MAX || shared_secretlen > LONG_MAX) {
EVP_PKEY_CTX_free(ctx);
rb_raise(ePKeyError, "encapsulated data would be too large");
}
ciphertext = ossl_str_new(NULL, (long)ciphertextlen, &state);
if (state) {
EVP_PKEY_CTX_free(ctx);
rb_jump_tag(state);
}
shared_secret = ossl_str_new(NULL, (long)shared_secretlen, &state);
if (state) {
EVP_PKEY_CTX_free(ctx);
rb_jump_tag(state);
}
if (EVP_PKEY_encapsulate(ctx,
(unsigned char *)RSTRING_PTR(ciphertext),
&ciphertextlen,
(unsigned char *)RSTRING_PTR(shared_secret),
&shared_secretlen) <= 0) {
EVP_PKEY_CTX_free(ctx);
ossl_raise(ePKeyError, "EVP_PKEY_encapsulate");
}
EVP_PKEY_CTX_free(ctx);
rb_str_set_len(ciphertext, ciphertextlen);
rb_str_set_len(shared_secret, shared_secretlen);
return rb_assoc_new(ciphertext, shared_secret);
}

/*
* call-seq:
* pkey.decapsulate(ciphertext) -> shared_secret
*
* Performs a key decapsulation operation using the private components of
* _pkey_.
*
* See also the man page EVP_PKEY_decapsulate(3).
*/
static VALUE
ossl_pkey_decapsulate(VALUE self, VALUE ciphertext)
{
EVP_PKEY *pkey;
EVP_PKEY_CTX *ctx;
VALUE shared_secret;
size_t shared_secretlen;
int state;

GetPKey(self, pkey);
StringValue(ciphertext);

ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
if (!ctx)
ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
if (EVP_PKEY_decapsulate_init(ctx, NULL) <= 0) {
EVP_PKEY_CTX_free(ctx);
ossl_raise(ePKeyError, "EVP_PKEY_decapsulate_init");
}
if (EVP_PKEY_decapsulate(ctx, NULL, &shared_secretlen,
(unsigned char *)RSTRING_PTR(ciphertext),
RSTRING_LEN(ciphertext)) <= 0) {
EVP_PKEY_CTX_free(ctx);
ossl_raise(ePKeyError, "EVP_PKEY_decapsulate");
}
if (shared_secretlen > LONG_MAX) {
EVP_PKEY_CTX_free(ctx);
rb_raise(ePKeyError, "decapsulated data would be too large");
}
shared_secret = ossl_str_new(NULL, (long)shared_secretlen, &state);
if (state) {
EVP_PKEY_CTX_free(ctx);
rb_jump_tag(state);
}
if (EVP_PKEY_decapsulate(ctx,
(unsigned char *)RSTRING_PTR(shared_secret),
&shared_secretlen,
(unsigned char *)RSTRING_PTR(ciphertext),
RSTRING_LEN(ciphertext)) <= 0) {
EVP_PKEY_CTX_free(ctx);
ossl_raise(ePKeyError, "EVP_PKEY_decapsulate");
}
EVP_PKEY_CTX_free(ctx);
rb_str_set_len(shared_secret, shared_secretlen);
return shared_secret;
}
#endif

/*
* call-seq:
* pkey.encrypt(data [, options]) -> string
Expand Down Expand Up @@ -1769,6 +1885,10 @@ Init_ossl_pkey(void)
rb_define_method(cPKey, "verify_raw", ossl_pkey_verify_raw, -1);
rb_define_method(cPKey, "verify_recover", ossl_pkey_verify_recover, -1);
rb_define_method(cPKey, "derive", ossl_pkey_derive, -1);
#ifdef HAVE_EVP_PKEY_ENCAPSULATE_INIT
rb_define_method(cPKey, "encapsulate", ossl_pkey_encapsulate, 0);
rb_define_method(cPKey, "decapsulate", ossl_pkey_decapsulate, 1);
#endif
rb_define_method(cPKey, "encrypt", ossl_pkey_encrypt, -1);
rb_define_method(cPKey, "decrypt", ossl_pkey_decrypt, -1);

Expand Down
39 changes: 39 additions & 0 deletions ext/openssl/ossl_x509crl.c
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,44 @@ ossl_x509crl_set_revoked(VALUE self, VALUE ary)
return ary;
}

/*
* call-seq:
* crl.by_serial(serial) -> OpenSSL::X509::Revoked or nil
*
* Looks up the certificate _serial_ (an Integer or OpenSSL::BN) in the CRL and
* returns the matching OpenSSL::X509::Revoked entry, or +nil+ if that serial is
* not listed.
*
* Unlike iterating over #revoked, this does not instantiate the entire
* revocation list: it performs a sorted lookup (wrapping the OpenSSL function
* +X509_CRL_get0_by_serial+), which is significantly faster and uses far less
* memory for large CRLs.
*
* crl.by_serial(cert.serial) #=> #<OpenSSL::X509::Revoked> or nil
* crl.by_serial(cert.serial)&.time #=> revocation time, if revoked
*/
static VALUE
ossl_x509crl_by_serial(VALUE self, VALUE serial)
{
X509_CRL *crl;
X509_REVOKED *rev = NULL;
ASN1_INTEGER *asn1_serial;
int found;

GetX509CRL(self, crl);
asn1_serial = num_to_asn1integer(serial, NULL);

/* 0 = not found, 1 = found, 2 = found with reason removeFromCRL */
found = X509_CRL_get0_by_serial(crl, &rev, asn1_serial);
ASN1_INTEGER_free(asn1_serial);

if (found == 0)
return Qnil;

/* ossl_x509revoked_new dups, so the result outlives the CRL safely */
return ossl_x509revoked_new(rev);
}

static VALUE
ossl_x509crl_add_revoked(VALUE self, VALUE revoked)
{
Expand Down Expand Up @@ -525,6 +563,7 @@ Init_ossl_x509crl(void)
rb_define_method(cX509CRL, "next_update=", ossl_x509crl_set_next_update, 1);
rb_define_method(cX509CRL, "revoked", ossl_x509crl_get_revoked, 0);
rb_define_method(cX509CRL, "revoked=", ossl_x509crl_set_revoked, 1);
rb_define_method(cX509CRL, "by_serial", ossl_x509crl_by_serial, 1);
rb_define_method(cX509CRL, "add_revoked", ossl_x509crl_add_revoked, 1);
rb_define_method(cX509CRL, "sign", ossl_x509crl_sign, 2);
rb_define_method(cX509CRL, "verify", ossl_x509crl_verify, 1);
Expand Down
81 changes: 69 additions & 12 deletions prism/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -13995,7 +13995,18 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for

// If we hit the terminator, then that means we have a trailing comma so
// we can accept that output as well.
if (match1(parser, terminator)) break;
if (match1(parser, terminator)) {
// A forwarding `...` argument must be the last argument and cannot
// be followed by a trailing comma, e.g. `foo(...,)`. A comma
// followed by another argument is already rejected at the top of
// this loop, so the only case left to reject here is the trailing
// one.
if (parsed_forwarding_arguments) {
pm_parser_err_previous(parser, PM_ERR_INVALID_COMMA);
}

break;
}
}
}

Expand Down Expand Up @@ -15133,9 +15144,15 @@ parse_block(pm_parser_t *parser, uint16_t depth) {
* Parse a list of arguments and their surrounding parentheses if they are
* present. It returns true if it found any pieces of arguments (parentheses,
* arguments, or blocks).
*
* When `full_arguments` is true the caller is a method or `super` call, which
* use the full `opt_call_args` grammar: a block argument, argument forwarding,
* a trailing block, and a trailing comma are all permitted. When it is false
* the caller is `yield`, whose restricted `call_args` grammar permits none of
* these.
*/
static bool
parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_block, uint8_t flags, uint16_t depth) {
parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool full_arguments, uint8_t flags, uint16_t depth) {
/* Fast path: if the current token can't begin an expression and isn't
* a parenthesis, block opener, or splat/block-pass operator, there are
* no arguments to parse. */
Expand All @@ -15157,7 +15174,17 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept
arguments->closing_loc = TOK2LOC(parser, &parser->previous);
} else {
pm_accepts_block_stack_push(parser, true);
parse_arguments(parser, arguments, accepts_block, PM_TOKEN_PARENTHESIS_RIGHT, (uint8_t) (flags & ~PM_PARSE_ACCEPTS_DO_BLOCK), (uint16_t) (depth + 1));
parse_arguments(parser, arguments, full_arguments, PM_TOKEN_PARENTHESIS_RIGHT, (uint8_t) (flags & ~PM_PARSE_ACCEPTS_DO_BLOCK), (uint16_t) (depth + 1));

// `yield` parses its arguments through the restricted `call_args`
// grammar, which (unlike the `opt_call_args` that method calls and
// `super` use) permits neither a block argument nor a trailing
// comma. `full_arguments` is false only for `yield`, so we use it
// to reject the trailing comma in `yield(a,)` that the arguments
// parser otherwise accepts before the closing parenthesis.
if (!full_arguments && parser->previous.type == PM_TOKEN_COMMA) {
PM_PARSER_ERR_TOKEN_FORMAT(parser, &parser->previous, PM_ERR_EXPECT_ARGUMENT, pm_token_str(parser->current.type));
}

if (!accept1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) {
PM_PARSER_ERR_TOKEN_FORMAT(parser, &parser->current, PM_ERR_ARGUMENT_TERM_PAREN, pm_token_str(parser->current.type));
Expand All @@ -15176,7 +15203,7 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept
// If we get here, then the subsequent token cannot be used as an infix
// operator. In this case we assume the subsequent token is part of an
// argument to this method call.
parse_arguments(parser, arguments, accepts_block, PM_TOKEN_EOF, flags, (uint16_t) (depth + 1));
parse_arguments(parser, arguments, full_arguments, PM_TOKEN_EOF, flags, (uint16_t) (depth + 1));

// If we have done with the arguments and still not consumed the comma,
// then we have a trailing comma where we need to check whether it is
Expand All @@ -15191,7 +15218,7 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept
// If we're at the end of the arguments, we can now check if there is a block
// node that starts with a {. If there is, then we can parse it and add it to
// the arguments.
if (accepts_block) {
if (full_arguments) {
pm_block_node_t *block = NULL;

if (accept1(parser, PM_TOKEN_BRACE_LEFT)) {
Expand Down Expand Up @@ -17380,8 +17407,23 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag

// Gather up all of the patterns into the list.
while (accept1(parser, PM_TOKEN_COMMA)) {
// Break early here in case we have a trailing comma.
if (match7(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_SEMICOLON, PM_TOKEN_KEYWORD_AND, PM_TOKEN_KEYWORD_OR)) {
// Break early here in case we have a trailing comma. The newline and
// EOF terminators cover a one-line match (`x => a,`) or a `case`/`in`
// clause (`in a,\n ...`); a newline is only lexed as a token here
// when `pattern_matching_newlines` is set, so this does not affect
// patterns nested in brackets or parentheses.
if (
match7(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_SEMICOLON, PM_TOKEN_KEYWORD_AND, PM_TOKEN_KEYWORD_OR) ||
match2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_EOF)
) {
// A trailing comma forms an implicit rest pattern (`[a,]` is
// `[a, *]`). If a rest pattern has already been parsed, then
// this is a second rest, which is not allowed (e.g. `[a, *b,]`
// or `x => a, *b,`).
if (trailing_rest) {
pm_parser_err_previous(parser, PM_ERR_PATTERN_REST);
}

node = UP(pm_implicit_rest_node_create(parser, &parser->previous));
pm_node_list_append(parser->arena, &nodes, node);
trailing_rest = true;
Expand Down Expand Up @@ -19727,6 +19769,16 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u
if (!(flags & PM_PARSE_ACCEPTS_COMMAND_CALL) && arguments.arguments != NULL) {
PM_PARSER_ERR_TOKEN_FORMAT(parser, &next, PM_ERR_EXPECT_EOL_AFTER_STATEMENT, pm_token_str(next.type));
}

// Reject a trailing comma, e.g. `return a,`. The arguments
// parser silently accepts a trailing comma only when it is
// immediately followed by the EOF terminator; in every other
// case (e.g. `return a,;`) it reports the dangling comma
// itself. We reject the accepted case here to stay in line
// with the command call argument parsing above.
if (parser->previous.type == PM_TOKEN_COMMA && match1(parser, PM_TOKEN_EOF)) {
PM_PARSER_ERR_TOKEN_FORMAT(parser, &parser->previous, PM_ERR_EXPECT_ARGUMENT, pm_token_str(parser->current.type));
}
}

// It's possible that we've parsed a block argument through our
Expand Down Expand Up @@ -22192,9 +22244,14 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t
// If the operator is nonassoc and we should not be able to parse the
// upcoming infix operator, break.
if (current_binding_powers.nonassoc) {
// If this is a non-assoc operator and we are about to parse the
// exact same operator, then we need to add an error.
if (match1(parser, current_token_type)) {
// If we are about to parse another non-associative operator at the
// same precedence as the one we just parsed, then we need to add an
// error. This covers chaining the same operator (`1 == 2 == 3`) as
// well as different operators that share a precedence, since they
// are equally non-associative with one another (`1 == 2 != 3`,
// `1...2..3`).
pm_binding_powers_t next_binding_powers = pm_binding_powers[parser->current.type];
if (next_binding_powers.nonassoc && next_binding_powers.left == current_binding_powers.left) {
PM_PARSER_ERR_TOKEN_FORMAT(parser, &parser->current, PM_ERR_NON_ASSOCIATIVE_OPERATOR, pm_token_str(parser->current.type), pm_token_str(current_token_type));
break;
}
Expand All @@ -22212,10 +22269,10 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, uint8_t
break;
}

if (PM_BINDING_POWER_TERM <= pm_binding_powers[parser->current.type].left) {
if (PM_BINDING_POWER_TERM <= next_binding_powers.left) {
break;
}
} else if (current_binding_powers.left <= pm_binding_powers[parser->current.type].left) {
} else if (current_binding_powers.left <= next_binding_powers.left) {
break;
}
}
Expand Down
22 changes: 22 additions & 0 deletions test/openssl/test_pkey.rb
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,28 @@ def test_ml_dsa
assert_equal(true, pub3.verify(nil, sig, "data"))
end

def test_ml_kem
# EVP_PKEY KEM APIs were added in OpenSSL 3.0.
omit "ML-KEM is not supported" unless openssl?(3, 5, 0)

pkey = OpenSSL::PKey.generate_key("ML-KEM-768")
raw_public_key = pkey.raw_public_key
raw_private_key = pkey.raw_private_key

assert_match(/type_name=ML-KEM-768/, pkey.inspect)
assert_equal(1184, raw_public_key.bytesize)
assert_equal(2400, raw_private_key.bytesize)

pubkey = OpenSSL::PKey.new_raw_public_key("ML-KEM-768", raw_public_key)
ciphertext, shared_secret = pubkey.encapsulate
assert_equal(1088, ciphertext.bytesize)
assert_equal(32, shared_secret.bytesize)
assert_equal(shared_secret, pkey.decapsulate(ciphertext))

privkey = OpenSSL::PKey.new_raw_private_key("ML-KEM-768", raw_private_key)
assert_equal(shared_secret, privkey.decapsulate(ciphertext))
end

def test_raw_initialize_errors
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("foo123", "xxx") }
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("ED25519", "xxx") }
Expand Down
Loading