Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timestamp support #204

Merged
merged 18 commits into from
Sep 30, 2019
Merged

Timestamp support #204

merged 18 commits into from
Sep 30, 2019

Conversation

btoews
Copy link
Contributor

@btoews btoews commented Jun 21, 2018

This PR attempts to revive Feature #4183 adding timestamp protocol support. That issue has three revisions of patches. I applied each as a commit on top of master. Unsurprisingly, the code didn't compile and tests didn't pass. I did the bare minimum to get tests passing in dce927c.

I'm not much of a C hacker and know very little about Ruby internals. I'll do my best to respond to any feedback on this PR, but I might need some help.

cc @tenderlove since he's been helping me on this already
cc @emboss since this is his patch
fixes #203

@tenderlove
Copy link
Member

@mastahyeti it looks like OpenSSL 1.1.0 is getting errors because it changed a lot of these structures to be opaque. This means we have to use getters and setters to access the struct members. I'm not sure how to build against OpenSSL 1.1.0 yet, but we need to change the direct member access to getters / setters like this:

diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c
index 9b9a6db..b8bb3b7 100755
--- a/ext/openssl/ossl_ts.c
+++ b/ext/openssl/ossl_ts.c
@@ -148,8 +148,8 @@ ossl_tsreq_alloc(VALUE klass)
         ossl_raise(eTimestampError, NULL);
         return Qnil;
     }
-    req->version = ASN1_INTEGER_new();
-    ASN1_INTEGER_set(req->version, 1);
+    TS_REQ_set_version(req, ASN1_INTEGER_new());
+    ASN1_INTEGER_set(TS_REQ_get_version(req), 1);
     req->extensions = NULL;
     if (!(req->msg_imprint = TS_MSG_IMPRINT_new())) {
         ossl_raise(eTimestampError, NULL);

@tenderlove
Copy link
Member

OpenSSL change is openssl/openssl@ca4a494

@rhenium
Copy link
Member

rhenium commented Jun 22, 2018

In 2016, I also tried to port the patch to the OpenSSL 1.1 API, but I didn't finish that. It does compile with OpenSSL 1.1.0, but the test does not pass.

https://github.com/rhenium/ruby-openssl/tree/ky/timestamp-support

@tenderlove
Copy link
Member

@rhenium interesting. Do the tests pass on OpenSSL 1.0 using your patch?

@btoews
Copy link
Contributor Author

btoews commented Jul 6, 2018

All the OpenSSL builds are passing now. Almost there!

@btoews
Copy link
Contributor Author

btoews commented Jul 9, 2018

All the CI builds are passing now. I think this is ready for some 👀. Also, let me know what, if any, rebasing/squashing is needed.

@btoews
Copy link
Contributor Author

btoews commented Jul 16, 2018

Bump. I think this PR is ready for review.

@btoews
Copy link
Contributor Author

btoews commented Jul 23, 2018

@rhenium @zzak 👋 This PR revives Feature #4183 to add timestamp protocol support to this gem. Quite a few changes to that patch series were necessary because of changes to coding styles in this codebase as well as changes to OpenSSL. All the CI builds are passing, so I think this branch is ready for some review. Thanks.

Copy link
Member

@rhenium rhenium left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry for the long delay in responding. I've added minor notes as inline comments.

As for the interface, the accessors for the fields of TSTInfo are defined on the Timestamp::Response, but they should be moved to their own class (say, Timestamp::TokenInfo) since they aren't direct properties of a response but of a token that the response merely carries.

VALUE eTimestampError, eCertValidationError;
VALUE cTimestampRequest;
VALUE cTimestampResponse;
VALUE cTimestampFactory;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These variables can be static.

}

TS_REQ *
GetTsReqPtr(VALUE obj)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we now use TypedData, the callers can use GetTSRequest() directly.

ext/openssl/ossl_ts.c Outdated Show resolved Hide resolved
obj = obj_to_asn1obj(algo);
mi = TS_REQ_get_msg_imprint(req);
algor = TS_MSG_IMPRINT_get_algo(mi);
if (!X509_ALGOR_set0(algor, obj, V_ASN1_NULL, NULL))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

obj must be free'd in the error path.


GetTSRequest(self, req);
mi = TS_REQ_get_msg_imprint(req);
if (!TS_MSG_IMPRINT_set_msg(mi, (unsigned char *)RSTRING_PTR(hash), RSTRING_LEN(hash)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RSTRING_LEN -> RSTRING_LENINT

TS_RESP_CTX_add_md(ctx, EVP_get_digestbyname(OBJ_nid2sn(NID_sha224)));
TS_RESP_CTX_add_md(ctx, EVP_get_digestbyname(OBJ_nid2sn(NID_sha256)));
TS_RESP_CTX_add_md(ctx, EVP_get_digestbyname(OBJ_nid2sn(NID_sha384)));
TS_RESP_CTX_add_md(ctx, EVP_get_digestbyname(OBJ_nid2sn(NID_sha512)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EVP_get_digestbyname(OBJ_nid2sn(NID_md5)) -> EVP_md5()

ts = fac.create_timestamp(ee_key, intermediate_cert, req)

ts.verify(req, [ca_cert])
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TimestampError is actually raised by #create_timestamp, not by #verify.

if (ERR_GET_LIB(e) == ERR_LIB_TS) {
if (ERR_GET_REASON(e) == TS_R_CERTIFICATE_VERIFY_ERROR)
is_validation_err = 1;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of ERR_GET_{LIB,REASON}() makes me nervous because the error codes are not explicitly stated as part of the public API.

Is the distinction in the raised exception actually important? I don't know. But otherwise, I'd suggest doing simply ossl_raise(eTimestampError, "TS_RESP_verify_response").

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the caller might care whether there was an issue building the certificate chain versus the signature itself being invalid. If an exception is raised though, the caller can use other APIs to figure out if the issue was with the certificate chain. I'm fine with always raising TimestampError.

* OpenSSL::PKCS7.
*
* call-seq:
* response.pkcs7 -> nil or OpenSSL::PKCS7
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just name the method token? It sounds better to me.

si = TS_RESP_get_status_info(resp);
text = TS_STATUS_INFO_get0_text(si);
if (!text)
return Qnil;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be better to return an empty array in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't seen any timestamp servers return more than one string. It might be a nicer API if we just returned the strings joined together or nil. What do you think?

TS_RESP_CTX_set_time_cb(ctx, ossl_tsfac_time_cb, &gen_time);

TS_RESP_CTX_add_md(ctx, EVP_get_digestbyname(OBJ_nid2sn(NID_md5)));
TS_RESP_CTX_add_md(ctx, EVP_get_digestbyname(OBJ_nid2sn(NID_sha1)));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be okay to take an opinionated stance and not support SHA1 and MD5?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started wondering where the list comes in the first place. I suspect the best thing would be to not have a hard-coded list and to let the user set their own one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I'll refactor this to allow the user to specific a list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a Factory#allowed_digests accessor in 5e45f13. Factory#create_timestamp now requires the list of allowed digest algorithms to be provided.

@btoews
Copy link
Contributor Author

btoews commented Aug 9, 2018

@rhenium Thanks for the thorough review! I think I have addressed all your feedback in fb90bea...703e821.

}

void
ossl_ts_verify_ctx_free(TS_VERIFY_CTX *ctx)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is never used.

SetTSResponse(ret, response);

end:
if(asn1_serial) ASN1_INTEGER_free(asn1_serial);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*_free(NULL) are no-op, so the if conditions can be removed.

}
if (status)
rb_jump_tag(status);
return ret;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though it's a false-positive, GCC says ‘ret’ may be used uninitialized in this function.

ASN1_OBJECT *def_policy_id_obj = NULL;
long lgen_time;
const char * err_msg = NULL;
int i, status = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i and cert are never used.

ext/openssl/ossl_ts.c Outdated Show resolved Hide resolved
TS_RESP_CTX_add_md(ctx, EVP_sha512());

str = rb_funcall(request, rb_intern("to_der"), 0);
req_bio = ossl_obj2bio(&str);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rb_funcall() and ossl_obj2bio() can raise an exception. By the way, the rb_funcall() can be replaced by ossl_to_der(request) (this still raises an exception).

goto end;
}

ret = NewTSResponse(cTimestampResponse);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NewTSResponse() also raises an exception.

for (i=0; i < sk_X509_num(p7->d.sign->cert); i++) {
cert = sk_X509_value(p7->d.sign->cert, i);
X509_up_ref(cert);
sk_X509_push(x509inter, cert);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check the return value.

rb_jump_tag(status);
}
} else {
x509inter = sk_X509_new_null();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function can fail.

lgen_time = NUM2LONG(rb_funcall(gen_time, rb_intern("to_i"), 0));

serial_number = ossl_tsfac_get_serial_number(self);
if (serial_number == Qnil) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparison with nil should be done by NIL_P(val). This is mostly a style thing.

@rhenium
Copy link
Member

rhenium commented Aug 16, 2018

For some reason, the newly added files have an executable bit. Please remove them.

And there seem some unused variables in the test code.

/home/k/work/ruby-openssl/test/utils.rb:328: warning: assigned but unused variable - now
/home/k/work/ruby-openssl/test/utils.rb:341: warning: assigned but unused variable - now
/home/k/work/ruby-openssl/test/utils.rb:356: warning: assigned but unused variable - now
/home/k/work/ruby-openssl/test/utils.rb:370: warning: assigned but unused variable - now
/home/k/work/ruby-openssl/test/test_ts.rb:334: warning: assigned but unused variable - req

@btoews
Copy link
Contributor Author

btoews commented Aug 21, 2018

I think this is ready for another round of review 🙇

ASN1_INTEGER **snptr = (ASN1_INTEGER **)data;
ASN1_INTEGER *sn = *snptr;
*snptr = NULL;
return sn;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some context here: This callback is given the serial number by ossl_tsfac_create_ts. OpenSSL calls the callback to get the serial number and then eventually frees it for us. If OpenSSL hits an error before it calls the callback though, ossl_tsfac_create_ts has no way of knowing that the serial number didn't get freed. So, I'm passing the serial number in as a ASN1_INTEGER ** so we can avoid leaking or double-freeing in ossl_tsfac_create_ts.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can add this as a comment to the code.

@btoews
Copy link
Contributor Author

btoews commented Sep 5, 2018

Just a gentle reminder: this branch is ready for another round of review.

@btoews
Copy link
Contributor Author

btoews commented Oct 3, 2018

Bump 😬

@vcsjones
Copy link

Hi! This functionality would be very useful to me. Friendly +1 from an interested 3rd party 😊.

@clairernovotny
Copy link

Another interested 3rd party. Support for this would be helpful.

@ioquatix
Copy link
Member

Can you rebase on master and I will review.

@btoews
Copy link
Contributor Author

btoews commented Jun 19, 2019

Can you rebase on master and I will review.

Done 😄

@btoews
Copy link
Contributor Author

btoews commented Aug 8, 2019

@ioquatix Just checking in. This should be ready to merge.

emboss and others added 7 commits September 27, 2019 09:08
This commit applies the initial patches (ts.tar.gz) from https://bugs.ruby-lang.org/issues/4183

This compiles with several warnings. Tests don't run yet.
This commit applies the second patches (ts2.tar.gz) from https://bugs.ruby-lang.org/issues/4183
This commit applies the third patches (tsr3.tar.gz) from https://bugs.ruby-lang.org/issues/4183
A number of conventions seem to have changed, causing a fair bit of breakage:
  - `Data_*` was deprecated in favor of `TypedData_*`
  - `ossl_obj2bio` takes a `VALUE*` instead of `VALUE` now
  - `time_to_time_t()` was removed
- clean up whitespace
- be consistent with not returning after ossl_raise
- use accessor functions when working with openssl TS_* structs
- backport accessors for TS_STATUS_INFO, TS_VERIFY_CTX, and TS_RESP_CTX as macros
- define missing TS_RESP_CTX_set_time_cb
- handle alternate case for nil oid
- make some global variables static instead of extern
- get rid of GetTsReqPtr/GetTsRespPtr functions
- don't use c99 comments
- fix some leaks
- clarify what numeric type is returned (Integer or BN, never Fixnum)
- typos
- add missing checks, remove unecessary checks
- use OPENSSL_NO_TS instead of our own macros checking for ts support
- use EVP_{digest-name} instead of looking up algos by NID
- don't differentiate between failure reasons when verifying
- rename Response#pkcs7 to #token
This method allowed roots and intermediates to be specified in a number of ways.
This complexity wasn't super valuable though and its better to only allow an
X509::Store with an optional Array of intermediates. This greatly simplifies
the code and fixes a few leaks.
@btoews
Copy link
Contributor Author

btoews commented Sep 27, 2019

@ioquatix Any more thoughts on this one? I just rebased onto master again.

@ioquatix
Copy link
Member

Because this is new code can you please expand all tabs? Mixing tabs and spaces in C code in MRI is now old school.

ext/openssl/ossl_ts.c Outdated Show resolved Hide resolved
@ioquatix
Copy link
Member

If the formatting issues can be resolved I will merge this, it looks great.

@btoews
Copy link
Contributor Author

btoews commented Sep 30, 2019

Awesome. I've always hated that C style 😄 It would be nice to do a global search/replace across this repo for consistency.

@ioquatix ioquatix merged commit 775a8c0 into ruby:master Sep 30, 2019
@ioquatix
Copy link
Member

Thanks for your persistence with this PR.

@btoews
Copy link
Contributor Author

btoews commented Oct 1, 2019

Thanks for merging it! I'm happy to see this finally land

@btoews btoews deleted the ts branch October 1, 2019 12:54
@israelb
Copy link

israelb commented Feb 14, 2020

@mastahyeti How can I use this new feature? I need the timestamps but it was impossible, I got the next error:

NameError: uninitialized constant OpenSSL::Timestamp

and the gem is pointing to master

gem 'openssl', git: 'https://github.com/ruby/openssl'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Timestamp protocol support
8 participants