-
Notifications
You must be signed in to change notification settings - Fork 172
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
Timestamp support #204
Conversation
@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); |
OpenSSL change is openssl/openssl@ca4a494 |
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 |
@rhenium interesting. Do the tests pass on OpenSSL 1.0 using your patch? |
All the OpenSSL builds are passing now. Almost there! |
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. |
Bump. I think this PR is ready for review. |
@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. |
There was a problem hiding this 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.
ext/openssl/ossl_ts.c
Outdated
VALUE eTimestampError, eCertValidationError; | ||
VALUE cTimestampRequest; | ||
VALUE cTimestampResponse; | ||
VALUE cTimestampFactory; |
There was a problem hiding this comment.
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
.
ext/openssl/ossl_ts.c
Outdated
} | ||
|
||
TS_REQ * | ||
GetTsReqPtr(VALUE obj) |
There was a problem hiding this comment.
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
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)) |
There was a problem hiding this comment.
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.
ext/openssl/ossl_ts.c
Outdated
|
||
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))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RSTRING_LEN
-> RSTRING_LENINT
ext/openssl/ossl_ts.c
Outdated
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))); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
ext/openssl/ossl_ts.c
Outdated
if (ERR_GET_LIB(e) == ERR_LIB_TS) { | ||
if (ERR_GET_REASON(e) == TS_R_CERTIFICATE_VERIFY_ERROR) | ||
is_validation_err = 1; | ||
} |
There was a problem hiding this comment.
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")
.
There was a problem hiding this comment.
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
.
ext/openssl/ossl_ts.c
Outdated
* OpenSSL::PKCS7. | ||
* | ||
* call-seq: | ||
* response.pkcs7 -> nil or OpenSSL::PKCS7 |
There was a problem hiding this comment.
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.
ext/openssl/ossl_ts.c
Outdated
si = TS_RESP_get_status_info(resp); | ||
text = TS_STATUS_INFO_get0_text(si); | ||
if (!text) | ||
return Qnil; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
ext/openssl/ossl_ts.c
Outdated
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))); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@rhenium Thanks for the thorough review! I think I have addressed all your feedback in fb90bea...703e821. |
ext/openssl/ossl_ts.c
Outdated
} | ||
|
||
void | ||
ossl_ts_verify_ctx_free(TS_VERIFY_CTX *ctx) |
There was a problem hiding this comment.
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.
ext/openssl/ossl_ts.c
Outdated
SetTSResponse(ret, response); | ||
|
||
end: | ||
if(asn1_serial) ASN1_INTEGER_free(asn1_serial); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
.
ext/openssl/ossl_ts.c
Outdated
ASN1_OBJECT *def_policy_id_obj = NULL; | ||
long lgen_time; | ||
const char * err_msg = NULL; | ||
int i, status = 0; |
There was a problem hiding this comment.
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
TS_RESP_CTX_add_md(ctx, EVP_sha512()); | ||
|
||
str = rb_funcall(request, rb_intern("to_der"), 0); | ||
req_bio = ossl_obj2bio(&str); |
There was a problem hiding this comment.
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).
ext/openssl/ossl_ts.c
Outdated
goto end; | ||
} | ||
|
||
ret = NewTSResponse(cTimestampResponse); |
There was a problem hiding this comment.
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.
ext/openssl/ossl_ts.c
Outdated
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check the return value.
ext/openssl/ossl_ts.c
Outdated
rb_jump_tag(status); | ||
} | ||
} else { | ||
x509inter = sk_X509_new_null(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function can fail.
ext/openssl/ossl_ts.c
Outdated
lgen_time = NUM2LONG(rb_funcall(gen_time, rb_intern("to_i"), 0)); | ||
|
||
serial_number = ossl_tsfac_get_serial_number(self); | ||
if (serial_number == Qnil) { |
There was a problem hiding this comment.
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.
For some reason, the newly added files have an executable bit. Please remove them. And there seem some unused variables in the test code.
|
I think this is ready for another round of review 🙇 |
ASN1_INTEGER **snptr = (ASN1_INTEGER **)data; | ||
ASN1_INTEGER *sn = *snptr; | ||
*snptr = NULL; | ||
return sn; |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
Just a gentle reminder: this branch is ready for another round of review. |
Bump 😬 |
Hi! This functionality would be very useful to me. Friendly +1 from an interested 3rd party 😊. |
Another interested 3rd party. Support for this would be helpful. |
Can you rebase on master and I will review. |
Done 😄 |
@ioquatix Just checking in. This should be ready to merge. |
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.
@ioquatix Any more thoughts on this one? I just rebased onto master again. |
Because this is new code can you please expand all tabs? Mixing tabs and spaces in C code in MRI is now old school. |
If the formatting issues can be resolved I will merge this, it looks great. |
Awesome. I've always hated that C style 😄 It would be nice to do a global search/replace across this repo for consistency. |
Thanks for your persistence with this PR. |
Thanks for merging it! I'm happy to see this finally land |
@mastahyeti How can I use this new feature? I need the timestamps but it was impossible, I got the next error:
and the gem is pointing to master gem 'openssl', git: 'https://github.com/ruby/openssl' |
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