/* * * Copyright (C) 2010 Martin Bosslet * All rights reserved. */ /* * This program is licenced under the same licence as Ruby. * (See the file 'LICENCE'.) */ #include "ossl.h" #ifndef OPENSSL_NO_TS #define NewTSRequest(klass) \ TypedData_Wrap_Struct((klass), &ossl_ts_req_type, 0) #define SetTSRequest(obj, req) do { \ if (!(req)) { \ ossl_raise(rb_eRuntimeError, "TS_REQ wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (req); \ } while (0) #define GetTSRequest(obj, req) do { \ TypedData_Get_Struct((obj), TS_REQ, &ossl_ts_req_type, (req)); \ if (!(req)) { \ ossl_raise(rb_eRuntimeError, "TS_REQ wasn't initialized."); \ } \ } while (0) #define NewTSResponse(klass) \ TypedData_Wrap_Struct((klass), &ossl_ts_resp_type, 0) #define SetTSResponse(obj, resp) do { \ if (!(resp)) { \ ossl_raise(rb_eRuntimeError, "TS_RESP wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (resp); \ } while (0) #define GetTSResponse(obj, resp) do { \ TypedData_Get_Struct((obj), TS_RESP, &ossl_ts_resp_type, (resp)); \ if (!(resp)) { \ ossl_raise(rb_eRuntimeError, "TS_RESP wasn't initialized."); \ } \ } while (0) #define NewTSTokenInfo(klass) \ TypedData_Wrap_Struct((klass), &ossl_ts_token_info_type, 0) #define SetTSTokenInfo(obj, info) do { \ if (!(info)) { \ ossl_raise(rb_eRuntimeError, "TS_TST_INFO wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (info); \ } while (0) #define GetTSTokenInfo(obj, info) do { \ TypedData_Get_Struct((obj), TS_TST_INFO, &ossl_ts_token_info_type, (info)); \ if (!(info)) { \ ossl_raise(rb_eRuntimeError, "TS_TST_INFO wasn't initialized."); \ } \ } while (0) #define ossl_tsfac_get_default_policy_id(o) rb_attr_get((o),rb_intern("@default_policy_id")) #define ossl_tsfac_get_serial_number(o) rb_attr_get((o),rb_intern("@serial_number")) #define ossl_tsfac_get_gen_time(o) rb_attr_get((o),rb_intern("@gen_time")) #define ossl_tsfac_get_additional_certs(o) rb_attr_get((o),rb_intern("@additional_certs")) #define ossl_tsfac_get_allowed_digests(o) rb_attr_get((o),rb_intern("@allowed_digests")) static VALUE mTimestamp; static VALUE eTimestampError; static VALUE cTimestampRequest; static VALUE cTimestampResponse; static VALUE cTimestampTokenInfo; static VALUE cTimestampFactory; static VALUE sBAD_ALG, sBAD_REQUEST, sBAD_DATA_FORMAT, sTIME_NOT_AVAILABLE; static VALUE sUNACCEPTED_POLICY, sUNACCEPTED_EXTENSION, sADD_INFO_NOT_AVAILABLE; static VALUE sSYSTEM_FAILURE; static void ossl_ts_req_free(void *ptr) { TS_REQ_free(ptr); } static const rb_data_type_t ossl_ts_req_type = { "OpenSSL/Timestamp/Request", { 0, ossl_ts_req_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, }; static void ossl_ts_resp_free(void *ptr) { TS_RESP_free(ptr); } static const rb_data_type_t ossl_ts_resp_type = { "OpenSSL/Timestamp/Response", { 0, ossl_ts_resp_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, }; static void ossl_ts_token_info_free(void *ptr) { TS_TST_INFO_free(ptr); } static const rb_data_type_t ossl_ts_token_info_type = { "OpenSSL/Timestamp/TokenInfo", { 0, ossl_ts_token_info_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, }; static VALUE asn1_to_der(void *template, int (*i2d)(void *template, unsigned char **pp)) { VALUE str; int len; unsigned char *p; if((len = i2d(template, NULL)) <= 0) ossl_raise(eTimestampError, "Error when encoding to DER"); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d(template, &p) <= 0) ossl_raise(eTimestampError, "Error when encoding to DER"); rb_str_set_len(str, p - (unsigned char*)RSTRING_PTR(str)); return str; } static ASN1_OBJECT* obj_to_asn1obj(VALUE obj) { ASN1_OBJECT *a1obj; StringValue(obj); a1obj = OBJ_txt2obj(RSTRING_PTR(obj), 0); if(!a1obj) a1obj = OBJ_txt2obj(RSTRING_PTR(obj), 1); if(!a1obj) ossl_raise(eASN1Error, "invalid OBJECT ID"); return a1obj; } static VALUE get_asn1obj(ASN1_OBJECT *obj) { BIO *out; VALUE ret; int nid; if ((nid = OBJ_obj2nid(obj)) != NID_undef) ret = rb_str_new2(OBJ_nid2sn(nid)); else{ if (!(out = BIO_new(BIO_s_mem()))) ossl_raise(eX509AttrError, NULL); i2a_ASN1_OBJECT(out, obj); ret = ossl_membio2str(out); } return ret; } static VALUE ossl_ts_req_alloc(VALUE klass) { TS_REQ *req; VALUE obj; obj = NewTSRequest(klass); if (!(req = TS_REQ_new())) ossl_raise(eTimestampError, NULL); SetTSRequest(obj, req); /* Defaults */ TS_REQ_set_version(req, 1); TS_REQ_set_cert_req(req, 1); return obj; } /* * When creating a Request with the +File+ or +string+ parameter, the * corresponding +File+ or +string+ must be DER-encoded. * * call-seq: * OpenSSL::Timestamp::Request.new(file) -> request * OpenSSL::Timestamp::Request.new(string) -> request * OpenSSL::Timestamp::Request.new -> empty request */ static VALUE ossl_ts_req_initialize(int argc, VALUE *argv, VALUE self) { TS_REQ *ts_req = DATA_PTR(self); BIO *in; VALUE arg; if(rb_scan_args(argc, argv, "01", &arg) == 0) { return self; } arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); ts_req = d2i_TS_REQ_bio(in, &ts_req); BIO_free(in); if (!ts_req) ossl_raise(eTimestampError, "Error when decoding the timestamp request"); DATA_PTR(self) = ts_req; return self; } /* * Returns the 'short name' of the object identifier that represents the * algorithm that was used to create the message imprint digest. * * call-seq: * request.algorithm -> string */ static VALUE ossl_ts_req_get_algorithm(VALUE self) { TS_REQ *req; TS_MSG_IMPRINT *mi; X509_ALGOR *algor; GetTSRequest(self, req); mi = TS_REQ_get_msg_imprint(req); algor = TS_MSG_IMPRINT_get_algo(mi); return get_asn1obj(algor->algorithm); } /* * Allows to set the object identifier or the 'short name' of the * algorithm that was used to create the message imprint digest. * * ===Example: * request.algorithm = "SHA1" * * call-seq: * request.algorithm = "string" -> string */ static VALUE ossl_ts_req_set_algorithm(VALUE self, VALUE algo) { TS_REQ *req; TS_MSG_IMPRINT *mi; ASN1_OBJECT *obj; X509_ALGOR *algor; GetTSRequest(self, req); 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)) { ASN1_OBJECT_free(obj); ossl_raise(eTimestampError, "X509_ALGOR_set0"); } return algo; } /* * Returns the message imprint (digest) of the data to be timestamped. * * call-seq: * request.message_imprint -> string or nil */ static VALUE ossl_ts_req_get_msg_imprint(VALUE self) { TS_REQ *req; TS_MSG_IMPRINT *mi; ASN1_OCTET_STRING *hashed_msg; VALUE ret; GetTSRequest(self, req); mi = TS_REQ_get_msg_imprint(req); hashed_msg = TS_MSG_IMPRINT_get_msg(mi); ret = rb_str_new((const char *)hashed_msg->data, hashed_msg->length); return ret; } /* * Set the message imprint digest. * * call-seq: * request.message_imprint = "string" -> string */ static VALUE ossl_ts_req_set_msg_imprint(VALUE self, VALUE hash) { TS_REQ *req; TS_MSG_IMPRINT *mi; StringValue(hash); GetTSRequest(self, req); mi = TS_REQ_get_msg_imprint(req); if (!TS_MSG_IMPRINT_set_msg(mi, (unsigned char *)RSTRING_PTR(hash), RSTRING_LENINT(hash))) ossl_raise(eTimestampError, "TS_MSG_IMPRINT_set_msg"); return hash; } /* * Returns the version of this request. +1+ is the default value. * * call-seq: * request.version -> Integer */ static VALUE ossl_ts_req_get_version(VALUE self) { TS_REQ *req; GetTSRequest(self, req); return LONG2NUM(TS_REQ_get_version(req)); } /* * Sets the version number for this Request. This should be +1+ for compliant * servers. * * call-seq: * request.version = number -> Integer */ static VALUE ossl_ts_req_set_version(VALUE self, VALUE version) { TS_REQ *req; long ver; if ((ver = NUM2LONG(version)) < 0) ossl_raise(eTimestampError, "version must be >= 0!"); GetTSRequest(self, req); if (!TS_REQ_set_version(req, ver)) ossl_raise(eTimestampError, "TS_REQ_set_version"); return version; } /* * Returns the 'short name' of the object identifier that represents the * timestamp policy under which the server shall create the timestamp. * * call-seq: * request.policy_id -> string or nil */ static VALUE ossl_ts_req_get_policy_id(VALUE self) { TS_REQ *req; GetTSRequest(self, req); if (!TS_REQ_get_policy_id(req)) return Qnil; return get_asn1obj(TS_REQ_get_policy_id(req)); } /* * Allows to set the object identifier that represents the * timestamp policy under which the server shall create the timestamp. This * may be left +nil+, implying that the timestamp server will issue the * timestamp using some default policy. * * ===Example: * request.policy_id = "1.2.3.4.5" * * call-seq: * request.policy_id = "string" -> string */ static VALUE ossl_ts_req_set_policy_id(VALUE self, VALUE oid) { TS_REQ *req; ASN1_OBJECT *obj; int ok; GetTSRequest(self, req); obj = obj_to_asn1obj(oid); ok = TS_REQ_set_policy_id(req, obj); ASN1_OBJECT_free(obj); if (!ok) ossl_raise(eTimestampError, "TS_REQ_set_policy_id"); return oid; } /* * Returns the nonce (number used once) that the server shall include in its * response. * * call-seq: * request.nonce -> BN or nil */ static VALUE ossl_ts_req_get_nonce(VALUE self) { TS_REQ *req; const ASN1_INTEGER * nonce; GetTSRequest(self, req); if (!(nonce = TS_REQ_get_nonce(req))) return Qnil; return asn1integer_to_num(nonce); } /* * Sets the nonce (number used once) that the server shall include in its * response. If the nonce is set, the server must return the same nonce value in * a valid Response. * * call-seq: * request.nonce = number -> BN */ static VALUE ossl_ts_req_set_nonce(VALUE self, VALUE num) { TS_REQ *req; ASN1_INTEGER *nonce; int ok; GetTSRequest(self, req); nonce = num_to_asn1integer(num, NULL); ok = TS_REQ_set_nonce(req, nonce); ASN1_INTEGER_free(nonce); if (!ok) ossl_raise(eTimestampError, NULL); return num; } /* * Indicates whether the response shall contain the timestamp authority's * certificate or not. * * call-seq: * request.cert_requested? -> true or false */ static VALUE ossl_ts_req_get_cert_requested(VALUE self) { TS_REQ *req; GetTSRequest(self, req); return TS_REQ_get_cert_req(req) ? Qtrue: Qfalse; } /* * Specify whether the response shall contain the timestamp authority's * certificate or not. The default value is +true+. * * call-seq: * request.cert_requested = boolean -> true or false */ static VALUE ossl_ts_req_set_cert_requested(VALUE self, VALUE requested) { TS_REQ *req; GetTSRequest(self, req); TS_REQ_set_cert_req(req, RTEST(requested)); return requested; } /* * DER-encodes this Request. * * call-seq: * request.to_der -> DER-encoded string */ static VALUE ossl_ts_req_to_der(VALUE self) { TS_REQ *req; TS_MSG_IMPRINT *mi; X509_ALGOR *algo; ASN1_OCTET_STRING *hashed_msg; GetTSRequest(self, req); mi = TS_REQ_get_msg_imprint(req); algo = TS_MSG_IMPRINT_get_algo(mi); if (OBJ_obj2nid(algo->algorithm) == NID_undef) ossl_raise(eTimestampError, "Message imprint missing algorithm"); hashed_msg = TS_MSG_IMPRINT_get_msg(mi); if (!hashed_msg->length) ossl_raise(eTimestampError, "Message imprint missing hashed message"); return asn1_to_der((void *)req, (int (*)(void *, unsigned char **))i2d_TS_REQ); } static VALUE ossl_ts_resp_alloc(VALUE klass) { TS_RESP *resp; VALUE obj; obj = NewTSResponse(klass); if (!(resp = TS_RESP_new())) ossl_raise(eTimestampError, NULL); SetTSResponse(obj, resp); return obj; } /* * Creates a Response from a +File+ or +string+ parameter, the * corresponding +File+ or +string+ must be DER-encoded. Please note * that Response is an immutable read-only class. If you'd like to create * timestamps please refer to Factory instead. * * call-seq: * OpenSSL::Timestamp::Response.new(file) -> response * OpenSSL::Timestamp::Response.new(string) -> response */ static VALUE ossl_ts_resp_initialize(VALUE self, VALUE der) { TS_RESP *ts_resp = DATA_PTR(self); BIO *in; der = ossl_to_der_if_possible(der); in = ossl_obj2bio(&der); ts_resp = d2i_TS_RESP_bio(in, &ts_resp); BIO_free(in); if (!ts_resp) ossl_raise(eTimestampError, "Error when decoding the timestamp response"); DATA_PTR(self) = ts_resp; return self; } /* * Returns one of GRANTED, GRANTED_WITH_MODS, REJECTION, WAITING, * REVOCATION_WARNING or REVOCATION_NOTIFICATION. A timestamp token has * been created only in case +status+ is equal to GRANTED or GRANTED_WITH_MODS. * * call-seq: * response.status -> BN (never nil) */ static VALUE ossl_ts_resp_get_status(VALUE self) { TS_RESP *resp; TS_STATUS_INFO *si; const ASN1_INTEGER *st; GetTSResponse(self, resp); si = TS_RESP_get_status_info(resp); st = TS_STATUS_INFO_get0_status(si); return asn1integer_to_num(st); } /* * In cases no timestamp token has been created, this field contains further * info about the reason why response creation failed. The method returns either * nil (the request was successful and a timestamp token was created) or one of * the following: * * :BAD_ALG - Indicates that the timestamp server rejects the message * imprint algorithm used in the Request * * :BAD_REQUEST - Indicates that the timestamp server was not able to process * the Request properly * * :BAD_DATA_FORMAT - Indicates that the timestamp server was not able to * parse certain data in the Request * * :TIME_NOT_AVAILABLE - Indicates that the server could not access its time * source * * :UNACCEPTED_POLICY - Indicates that the requested policy identifier is not * recognized or supported by the timestamp server * * :UNACCEPTED_EXTENSIION - Indicates that an extension in the Request is * not supported by the timestamp server * * :ADD_INFO_NOT_AVAILABLE -Indicates that additional information requested * is either not understood or currently not available * * :SYSTEM_FAILURE - Timestamp creation failed due to an internal error that * occurred on the timestamp server * * call-seq: * response.failure_info -> nil or symbol */ static VALUE ossl_ts_resp_get_failure_info(VALUE self) { TS_RESP *resp; TS_STATUS_INFO *si; /* The ASN1_BIT_STRING_get_bit changed from 1.0.0. to 1.1.0, making this * const. */ #if defined(HAVE_TS_STATUS_INFO_GET0_FAILURE_INFO) const ASN1_BIT_STRING *fi; #else ASN1_BIT_STRING *fi; #endif GetTSResponse(self, resp); si = TS_RESP_get_status_info(resp); fi = TS_STATUS_INFO_get0_failure_info(si); if (!fi) return Qnil; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_BAD_ALG)) return sBAD_ALG; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_BAD_REQUEST)) return sBAD_REQUEST; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_BAD_DATA_FORMAT)) return sBAD_DATA_FORMAT; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_TIME_NOT_AVAILABLE)) return sTIME_NOT_AVAILABLE; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_UNACCEPTED_POLICY)) return sUNACCEPTED_POLICY; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_UNACCEPTED_EXTENSION)) return sUNACCEPTED_EXTENSION; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_ADD_INFO_NOT_AVAILABLE)) return sADD_INFO_NOT_AVAILABLE; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_SYSTEM_FAILURE)) return sSYSTEM_FAILURE; ossl_raise(eTimestampError, "Unrecognized failure info."); } /* * In cases of failure this field may contain an array of strings further * describing the origin of the failure. * * call-seq: * response.status_text -> Array of strings or nil */ static VALUE ossl_ts_resp_get_status_text(VALUE self) { TS_RESP *resp; TS_STATUS_INFO *si; const STACK_OF(ASN1_UTF8STRING) *text; ASN1_UTF8STRING *current; int i; VALUE ret = rb_ary_new(); GetTSResponse(self, resp); si = TS_RESP_get_status_info(resp); if ((text = TS_STATUS_INFO_get0_text(si))) { for (i = 0; i < sk_ASN1_UTF8STRING_num(text); i++) { current = sk_ASN1_UTF8STRING_value(text, i); rb_ary_push(ret, asn1str_to_str(current)); } } return ret; } /* * If a timestamp token is present, this returns it in the form of a * OpenSSL::PKCS7. * * call-seq: * response.token -> nil or OpenSSL::PKCS7 */ static VALUE ossl_ts_resp_get_token(VALUE self) { TS_RESP *resp; PKCS7 *p7, *copy; VALUE obj; GetTSResponse(self, resp); if (!(p7 = TS_RESP_get_token(resp))) return Qnil; obj = NewPKCS7(cPKCS7); if (!(copy = PKCS7_dup(p7))) ossl_raise(eTimestampError, NULL); SetPKCS7(obj, copy); return obj; } /* * Get the response's token info if present. * * call-seq: * response.token_info -> nil or OpenSSL::Timestamp::TokenInfo */ static VALUE ossl_ts_resp_get_token_info(VALUE self) { TS_RESP *resp; TS_TST_INFO *info, *copy; VALUE obj; GetTSResponse(self, resp); if (!(info = TS_RESP_get_tst_info(resp))) return Qnil; obj = NewTSTokenInfo(cTimestampTokenInfo); if (!(copy = TS_TST_INFO_dup(info))) ossl_raise(eTimestampError, NULL); SetTSTokenInfo(obj, copy); return obj; } /* * If the Request specified to request the TSA certificate * (Request#cert_requested = true), then this field contains the * certificate of the timestamp authority. * * call-seq: * response.tsa_certificate -> OpenSSL::X509::Certificate or nil */ static VALUE ossl_ts_resp_get_tsa_certificate(VALUE self) { TS_RESP *resp; PKCS7 *p7; PKCS7_SIGNER_INFO *ts_info; X509 *cert; GetTSResponse(self, resp); if (!(p7 = TS_RESP_get_token(resp))) return Qnil; ts_info = sk_PKCS7_SIGNER_INFO_value(p7->d.sign->signer_info, 0); cert = PKCS7_cert_from_signer_info(p7, ts_info); if (!cert) return Qnil; return ossl_x509_new(cert); } /* * Returns the Response in DER-encoded form. * * call-seq: * response.to_der -> string */ static VALUE ossl_ts_resp_to_der(VALUE self) { TS_RESP *resp; GetTSResponse(self, resp); return asn1_to_der((void *)resp, (int (*)(void *, unsigned char **))i2d_TS_RESP); } /* * Verifies a timestamp token by checking the signature, validating the * certificate chain implied by tsa_certificate and by checking conformance to * a given Request. Mandatory parameters are the Request associated to this * Response, and an OpenSSL::X509::Store of trusted roots. * * Intermediate certificates can optionally be supplied for creating the * certificate chain. These intermediate certificates must all be * instances of OpenSSL::X509::Certificate. * * If validation fails, several kinds of exceptions can be raised: * * TypeError if types don't fit * * TimestampError if something is wrong with the timestamp token itself, if * it is not conformant to the Request, or if validation of the timestamp * certificate chain fails. * * call-seq: * response.verify(Request, root_store) -> Response * response.verify(Request, root_store, [intermediate_cert]) -> Response */ static VALUE ossl_ts_resp_verify(int argc, VALUE *argv, VALUE self) { VALUE ts_req, store, intermediates; TS_RESP *resp; TS_REQ *req; X509_STORE *x509st; TS_VERIFY_CTX *ctx; STACK_OF(X509) *x509inter = NULL; PKCS7* p7; X509 *cert; int status, i, ok; rb_scan_args(argc, argv, "21", &ts_req, &store, &intermediates); GetTSResponse(self, resp); GetTSRequest(ts_req, req); x509st = GetX509StorePtr(store); if (!(ctx = TS_REQ_to_TS_VERIFY_CTX(req, NULL))) { ossl_raise(eTimestampError, "Error when creating the verification context."); } if (!NIL_P(intermediates)) { x509inter = ossl_protect_x509_ary2sk(intermediates, &status); if (status) { TS_VERIFY_CTX_free(ctx); rb_jump_tag(status); } } else if (!(x509inter = sk_X509_new_null())) { TS_VERIFY_CTX_free(ctx); ossl_raise(eTimestampError, "sk_X509_new_null"); } if (!(p7 = TS_RESP_get_token(resp))) { TS_VERIFY_CTX_free(ctx); sk_X509_pop_free(x509inter, X509_free); ossl_raise(eTimestampError, "TS_RESP_get_token"); } for (i=0; i < sk_X509_num(p7->d.sign->cert); i++) { cert = sk_X509_value(p7->d.sign->cert, i); if (!sk_X509_push(x509inter, cert)) { sk_X509_pop_free(x509inter, X509_free); TS_VERIFY_CTX_free(ctx); ossl_raise(eTimestampError, "sk_X509_push"); } X509_up_ref(cert); } TS_VERIFY_CTS_set_certs(ctx, x509inter); TS_VERIFY_CTX_add_flags(ctx, TS_VFY_SIGNATURE); TS_VERIFY_CTX_set_store(ctx, x509st); ok = TS_RESP_verify_response(ctx, resp); /* WORKAROUND: * X509_STORE can count references, but X509_STORE_free() doesn't check * this. To prevent our X509_STORE from being freed with our * TS_VERIFY_CTX we set the store to NULL first. * Fixed in OpenSSL 1.0.2; bff9ce4db38b (master), 5b4b9ce976fc (1.0.2) */ TS_VERIFY_CTX_set_store(ctx, NULL); TS_VERIFY_CTX_free(ctx); if (!ok) ossl_raise(eTimestampError, "TS_RESP_verify_response"); return self; } static VALUE ossl_ts_token_info_alloc(VALUE klass) { TS_TST_INFO *info; VALUE obj; obj = NewTSTokenInfo(klass); if (!(info = TS_TST_INFO_new())) ossl_raise(eTimestampError, NULL); SetTSTokenInfo(obj, info); return obj; } /* * Creates a TokenInfo from a +File+ or +string+ parameter, the * corresponding +File+ or +string+ must be DER-encoded. Please note * that TokenInfo is an immutable read-only class. If you'd like to create * timestamps please refer to Factory instead. * * call-seq: * OpenSSL::Timestamp::TokenInfo.new(file) -> token-info * OpenSSL::Timestamp::TokenInfo.new(string) -> token-info */ static VALUE ossl_ts_token_info_initialize(VALUE self, VALUE der) { TS_TST_INFO *info = DATA_PTR(self); BIO *in; der = ossl_to_der_if_possible(der); in = ossl_obj2bio(&der); info = d2i_TS_TST_INFO_bio(in, &info); BIO_free(in); if (!info) ossl_raise(eTimestampError, "Error when decoding the timestamp token info"); DATA_PTR(self) = info; return self; } /* * Returns the version number of the token info. With compliant servers, * this value should be +1+ if present. If status is GRANTED or * GRANTED_WITH_MODS. * * call-seq: * token_info.version -> Integer or nil */ static VALUE ossl_ts_token_info_get_version(VALUE self) { TS_TST_INFO *info; GetTSTokenInfo(self, info); return LONG2NUM(TS_TST_INFO_get_version(info)); } /* * Returns the timestamp policy object identifier of the policy this timestamp * was created under. If status is GRANTED or GRANTED_WITH_MODS, this is never * +nil+. * * ===Example: * id = token_info.policy_id * puts id -> "1.2.3.4.5" * * call-seq: * token_info.policy_id -> string or nil */ static VALUE ossl_ts_token_info_get_policy_id(VALUE self) { TS_TST_INFO *info; GetTSTokenInfo(self, info); return get_asn1obj(TS_TST_INFO_get_policy_id(info)); } /* * Returns the 'short name' of the object identifier representing the algorithm * that was used to derive the message imprint digest. For valid timestamps, * this is the same value that was already given in the Request. If status is * GRANTED or GRANTED_WITH_MODS, this is never +nil+. * * ===Example: * algo = token_info.algorithm * puts algo -> "SHA1" * * call-seq: * token_info.algorithm -> string or nil */ static VALUE ossl_ts_token_info_get_algorithm(VALUE self) { TS_TST_INFO *info; TS_MSG_IMPRINT *mi; X509_ALGOR *algo; GetTSTokenInfo(self, info); mi = TS_TST_INFO_get_msg_imprint(info); algo = TS_MSG_IMPRINT_get_algo(mi); return get_asn1obj(algo->algorithm); } /* * Returns the message imprint digest. For valid timestamps, * this is the same value that was already given in the Request. * If status is GRANTED or GRANTED_WITH_MODS, this is never +nil+. * * ===Example: * mi = token_info.msg_imprint * puts mi -> "DEADBEEF" * * call-seq: * token_info.msg_imprint -> string. */ static VALUE ossl_ts_token_info_get_msg_imprint(VALUE self) { TS_TST_INFO *info; TS_MSG_IMPRINT *mi; ASN1_OCTET_STRING *hashed_msg; VALUE ret; GetTSTokenInfo(self, info); mi = TS_TST_INFO_get_msg_imprint(info); hashed_msg = TS_MSG_IMPRINT_get_msg(mi); ret = rb_str_new((const char *)hashed_msg->data, hashed_msg->length); return ret; } /* * Returns serial number of the timestamp token. This value shall never be the * same for two timestamp tokens issued by a dedicated timestamp authority. * If status is GRANTED or GRANTED_WITH_MODS, this is never +nil+. * * call-seq: * token_info.serial_number -> BN or nil */ static VALUE ossl_ts_token_info_get_serial_number(VALUE self) { TS_TST_INFO *info; GetTSTokenInfo(self, info); return asn1integer_to_num(TS_TST_INFO_get_serial(info)); } /* * Returns time when this timestamp token was created. If status is GRANTED or * GRANTED_WITH_MODS, this is never +nil+. * * call-seq: * token_info.gen_time -> Time */ static VALUE ossl_ts_token_info_get_gen_time(VALUE self) { TS_TST_INFO *info; GetTSTokenInfo(self, info); return asn1time_to_time(TS_TST_INFO_get_time(info)); } /* * If the ordering field is missing, or if the ordering field is present * and set to false, then the genTime field only indicates the time at * which the time-stamp token has been created by the TSA. In such a * case, the ordering of time-stamp tokens issued by the same TSA or * different TSAs is only possible when the difference between the * genTime of the first time-stamp token and the genTime of the second * time-stamp token is greater than the sum of the accuracies of the * genTime for each time-stamp token. * * If the ordering field is present and set to true, every time-stamp * token from the same TSA can always be ordered based on the genTime * field, regardless of the genTime accuracy. * * call-seq: * token_info.ordering -> true, falses or nil */ static VALUE ossl_ts_token_info_get_ordering(VALUE self) { TS_TST_INFO *info; GetTSTokenInfo(self, info); return TS_TST_INFO_get_ordering(info) ? Qtrue : Qfalse; } /* * If the timestamp token is valid then this field contains the same nonce that * was passed to the timestamp server in the initial Request. * * call-seq: * token_info.nonce -> BN or nil */ static VALUE ossl_ts_token_info_get_nonce(VALUE self) { TS_TST_INFO *info; const ASN1_INTEGER *nonce; GetTSTokenInfo(self, info); if (!(nonce = TS_TST_INFO_get_nonce(info))) return Qnil; return asn1integer_to_num(nonce); } /* * Returns the TokenInfo in DER-encoded form. * * call-seq: * token_info.to_der -> string */ static VALUE ossl_ts_token_info_to_der(VALUE self) { TS_TST_INFO *info; GetTSTokenInfo(self, info); return asn1_to_der((void *)info, (int (*)(void *, unsigned char **))i2d_TS_TST_INFO); } static ASN1_INTEGER * ossl_tsfac_serial_cb(struct TS_resp_ctx *ctx, void *data) { ASN1_INTEGER **snptr = (ASN1_INTEGER **)data; ASN1_INTEGER *sn = *snptr; *snptr = NULL; return sn; } static int ossl_tsfac_time_cb(struct TS_resp_ctx *ctx, void *data, long *sec, long *usec) { *sec = *((long *)data); *usec = 0; return 1; } /* * Creates a Response with the help of an OpenSSL::PKey, an * OpenSSL::X509::Certificate and a Request. * * Mandatory parameters for timestamp creation that need to be set in the * Request: * * * Request#algorithm * * Request#message_imprint * * Mandatory parameters that need to be set in the Factory: * * Factory#serial_number * * Factory#gen_time * * Factory#allowed_digests * * In addition one of either Request#policy_id or Factory#default_policy_id * must be set. * * Raises a TimestampError if creation fails, though successfully created error * responses may be returned. * * call-seq: * factory.create_timestamp(key, certificate, request) -> Response */ static VALUE ossl_tsfac_create_ts(VALUE self, VALUE key, VALUE certificate, VALUE request) { VALUE serial_number, def_policy_id, gen_time, additional_certs, allowed_digests; VALUE str; STACK_OF(X509) *inter_certs; VALUE tsresp, ret = Qnil; EVP_PKEY *sign_key; X509 *tsa_cert; TS_REQ *req; TS_RESP *response = NULL; TS_RESP_CTX *ctx = NULL; BIO *req_bio; ASN1_INTEGER *asn1_serial = NULL; ASN1_OBJECT *def_policy_id_obj = NULL; long lgen_time; const char * err_msg = NULL; int status = 0; tsresp = NewTSResponse(cTimestampResponse); tsa_cert = GetX509CertPtr(certificate); sign_key = GetPrivPKeyPtr(key); GetTSRequest(request, req); gen_time = ossl_tsfac_get_gen_time(self); if (!rb_obj_is_instance_of(gen_time, rb_cTime)) { err_msg = "@gen_time must be a Time."; goto end; } lgen_time = NUM2LONG(rb_funcall(gen_time, rb_intern("to_i"), 0)); serial_number = ossl_tsfac_get_serial_number(self); if (NIL_P(serial_number)) { err_msg = "@serial_number must be set."; goto end; } asn1_serial = num_to_asn1integer(serial_number, NULL); def_policy_id = ossl_tsfac_get_default_policy_id(self); if (NIL_P(def_policy_id) && !TS_REQ_get_policy_id(req)) { err_msg = "No policy id in the request and no default policy set"; goto end; } if (!NIL_P(def_policy_id) && !TS_REQ_get_policy_id(req)) { def_policy_id_obj = (ASN1_OBJECT*)rb_protect((VALUE (*)(VALUE))obj_to_asn1obj, (VALUE)def_policy_id, &status); if (status) goto end; } if (!(ctx = TS_RESP_CTX_new())) { err_msg = "Memory allocation failed."; goto end; } TS_RESP_CTX_set_serial_cb(ctx, ossl_tsfac_serial_cb, &asn1_serial); if (!TS_RESP_CTX_set_signer_cert(ctx, tsa_cert)) { err_msg = "Certificate does not contain the timestamping extension"; goto end; } additional_certs = ossl_tsfac_get_additional_certs(self); if (rb_obj_is_kind_of(additional_certs, rb_cArray)) { inter_certs = ossl_protect_x509_ary2sk(additional_certs, &status); if (status) goto end; /* this dups the sk_X509 and ups each cert's ref count */ TS_RESP_CTX_set_certs(ctx, inter_certs); sk_X509_pop_free(inter_certs, X509_free); } TS_RESP_CTX_set_signer_key(ctx, sign_key); if (!NIL_P(def_policy_id) && !TS_REQ_get_policy_id(req)) TS_RESP_CTX_set_def_policy(ctx, def_policy_id_obj); if (TS_REQ_get_policy_id(req)) TS_RESP_CTX_set_def_policy(ctx, TS_REQ_get_policy_id(req)); TS_RESP_CTX_set_time_cb(ctx, ossl_tsfac_time_cb, &lgen_time); allowed_digests = ossl_tsfac_get_allowed_digests(self); if (rb_obj_is_kind_of(allowed_digests, rb_cArray)) { int i; VALUE rbmd; const EVP_MD *md; for (i = 0; i < RARRAY_LEN(allowed_digests); i++) { rbmd = rb_ary_entry(allowed_digests, i); md = (const EVP_MD *)rb_protect((VALUE (*)(VALUE))ossl_evp_get_digestbyname, rbmd, &status); if (status) goto end; TS_RESP_CTX_add_md(ctx, md); } } str = rb_protect(ossl_to_der, request, &status); if (status) goto end; req_bio = (BIO*)rb_protect((VALUE (*)(VALUE))ossl_obj2bio, (VALUE)&str, &status); if (status) goto end; response = TS_RESP_create_response(ctx, req_bio); BIO_free(req_bio); if (!response) { err_msg = "Error during response generation"; goto end; } /* bad responses aren't exceptional, but openssl still sets error * information. */ ossl_clear_error(); SetTSResponse(tsresp, response); ret = tsresp; end: ASN1_INTEGER_free(asn1_serial); ASN1_OBJECT_free(def_policy_id_obj); TS_RESP_CTX_free(ctx); if (err_msg) ossl_raise(eTimestampError, err_msg); if (status) rb_jump_tag(status); return ret; } /* * INIT */ void Init_ossl_ts(void) { #if 0 mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */ #endif /* * Possible return value for +Response#failure_info+. Indicates that the * timestamp server rejects the message imprint algorithm used in the * +Request+ */ sBAD_ALG = ID2SYM(rb_intern_const("BAD_ALG")); /* * Possible return value for +Response#failure_info+. Indicates that the * timestamp server was not able to process the +Request+ properly. */ sBAD_REQUEST = ID2SYM(rb_intern_const("BAD_REQUEST")); /* * Possible return value for +Response#failure_info+. Indicates that the * timestamp server was not able to parse certain data in the +Request+. */ sBAD_DATA_FORMAT = ID2SYM(rb_intern_const("BAD_DATA_FORMAT")); sTIME_NOT_AVAILABLE = ID2SYM(rb_intern_const("TIME_NOT_AVAILABLE")); sUNACCEPTED_POLICY = ID2SYM(rb_intern_const("UNACCEPTED_POLICY")); sUNACCEPTED_EXTENSION = ID2SYM(rb_intern_const("UNACCEPTED_EXTENSION")); sADD_INFO_NOT_AVAILABLE = ID2SYM(rb_intern_const("ADD_INFO_NOT_AVAILABLE")); sSYSTEM_FAILURE = ID2SYM(rb_intern_const("SYSTEM_FAILURE")); /* Document-class: OpenSSL::Timestamp * Provides classes and methods to request, create and validate * {RFC3161-compliant}[http://www.ietf.org/rfc/rfc3161.txt] timestamps. * Request may be used to either create requests from scratch or to parse * existing requests that again can be used to request timestamps from a * timestamp server, e.g. via the net/http. The resulting timestamp * response may be parsed using Response. * * Please note that Response is read-only and immutable. To create a * Response, an instance of Factory as well as a valid Request are needed. * * ===Create a Response: * #Assumes ts.p12 is a PKCS#12-compatible file with a private key * #and a certificate that has an extended key usage of 'timeStamping' * p12 = OpenSSL::PKCS12.new(File.binread('ts.p12'), 'pwd') * md = OpenSSL::Digest.new('SHA1') * hash = md.digest(data) #some binary data to be timestamped * req = OpenSSL::Timestamp::Request.new * req.algorithm = 'SHA1' * req.message_imprint = hash * req.policy_id = "1.2.3.4.5" * req.nonce = 42 * fac = OpenSSL::Timestamp::Factory.new * fac.gen_time = Time.now * fac.serial_number = 1 * timestamp = fac.create_timestamp(p12.key, p12.certificate, req) * * ===Verify a timestamp response: * #Assume we have a timestamp token in a file called ts.der * ts = OpenSSL::Timestamp::Response.new(File.binread('ts.der')) * #Assume we have the Request for this token in a file called req.der * req = OpenSSL::Timestamp::Request.new(File.binread('req.der')) * # Assume the associated root CA certificate is contained in a * # DER-encoded file named root.cer * root = OpenSSL::X509::Certificate.new(File.binread('root.cer')) * # get the necessary intermediate certificates, available in * # DER-encoded form in inter1.cer and inter2.cer * inter1 = OpenSSL::X509::Certificate.new(File.binread('inter1.cer')) * inter2 = OpenSSL::X509::Certificate.new(File.binread('inter2.cer')) * ts.verify(req, root, inter1, inter2) -> ts or raises an exception if validation fails * */ mTimestamp = rb_define_module_under(mOSSL, "Timestamp"); /* Document-class: OpenSSL::Timestamp::TimestampError * Generic exception class of the Timestamp module. */ eTimestampError = rb_define_class_under(mTimestamp, "TimestampError", eOSSLError); /* Document-class: OpenSSL::Timestamp::Response * Immutable and read-only representation of a timestamp response returned * from a timestamp server after receiving an associated Request. Allows * access to specific information about the response but also allows to * verify the Response. */ cTimestampResponse = rb_define_class_under(mTimestamp, "Response", rb_cObject); rb_define_alloc_func(cTimestampResponse, ossl_ts_resp_alloc); rb_define_method(cTimestampResponse, "initialize", ossl_ts_resp_initialize, 1); rb_define_method(cTimestampResponse, "status", ossl_ts_resp_get_status, 0); rb_define_method(cTimestampResponse, "failure_info", ossl_ts_resp_get_failure_info, 0); rb_define_method(cTimestampResponse, "status_text", ossl_ts_resp_get_status_text, 0); rb_define_method(cTimestampResponse, "token", ossl_ts_resp_get_token, 0); rb_define_method(cTimestampResponse, "token_info", ossl_ts_resp_get_token_info, 0); rb_define_method(cTimestampResponse, "tsa_certificate", ossl_ts_resp_get_tsa_certificate, 0); rb_define_method(cTimestampResponse, "to_der", ossl_ts_resp_to_der, 0); rb_define_method(cTimestampResponse, "verify", ossl_ts_resp_verify, -1); /* Document-class: OpenSSL::Timestamp::TokenInfo * Immutable and read-only representation of a timestamp token info from a * Response. */ cTimestampTokenInfo = rb_define_class_under(mTimestamp, "TokenInfo", rb_cObject); rb_define_alloc_func(cTimestampTokenInfo, ossl_ts_token_info_alloc); rb_define_method(cTimestampTokenInfo, "initialize", ossl_ts_token_info_initialize, 1); rb_define_method(cTimestampTokenInfo, "version", ossl_ts_token_info_get_version, 0); rb_define_method(cTimestampTokenInfo, "policy_id", ossl_ts_token_info_get_policy_id, 0); rb_define_method(cTimestampTokenInfo, "algorithm", ossl_ts_token_info_get_algorithm, 0); rb_define_method(cTimestampTokenInfo, "message_imprint", ossl_ts_token_info_get_msg_imprint, 0); rb_define_method(cTimestampTokenInfo, "serial_number", ossl_ts_token_info_get_serial_number, 0); rb_define_method(cTimestampTokenInfo, "gen_time", ossl_ts_token_info_get_gen_time, 0); rb_define_method(cTimestampTokenInfo, "ordering", ossl_ts_token_info_get_ordering, 0); rb_define_method(cTimestampTokenInfo, "nonce", ossl_ts_token_info_get_nonce, 0); rb_define_method(cTimestampTokenInfo, "to_der", ossl_ts_token_info_to_der, 0); /* Document-class: OpenSSL::Timestamp::Request * Allows to create timestamp requests or parse existing ones. A Request is * also needed for creating timestamps from scratch with Factory. When * created from scratch, some default values are set: * * version is set to +1+ * * cert_requested is set to +true+ * * algorithm, message_imprint, policy_id, and nonce are set to +false+ */ cTimestampRequest = rb_define_class_under(mTimestamp, "Request", rb_cObject); rb_define_alloc_func(cTimestampRequest, ossl_ts_req_alloc); rb_define_method(cTimestampRequest, "initialize", ossl_ts_req_initialize, -1); rb_define_method(cTimestampRequest, "version=", ossl_ts_req_set_version, 1); rb_define_method(cTimestampRequest, "version", ossl_ts_req_get_version, 0); rb_define_method(cTimestampRequest, "algorithm=", ossl_ts_req_set_algorithm, 1); rb_define_method(cTimestampRequest, "algorithm", ossl_ts_req_get_algorithm, 0); rb_define_method(cTimestampRequest, "message_imprint=", ossl_ts_req_set_msg_imprint, 1); rb_define_method(cTimestampRequest, "message_imprint", ossl_ts_req_get_msg_imprint, 0); rb_define_method(cTimestampRequest, "policy_id=", ossl_ts_req_set_policy_id, 1); rb_define_method(cTimestampRequest, "policy_id", ossl_ts_req_get_policy_id, 0); rb_define_method(cTimestampRequest, "nonce=", ossl_ts_req_set_nonce, 1); rb_define_method(cTimestampRequest, "nonce", ossl_ts_req_get_nonce, 0); rb_define_method(cTimestampRequest, "cert_requested=", ossl_ts_req_set_cert_requested, 1); rb_define_method(cTimestampRequest, "cert_requested?", ossl_ts_req_get_cert_requested, 0); rb_define_method(cTimestampRequest, "to_der", ossl_ts_req_to_der, 0); /* * Indicates a successful response. Equal to +0+. */ rb_define_const(cTimestampResponse, "GRANTED", INT2NUM(TS_STATUS_GRANTED)); /* * Indicates a successful response that probably contains modifications * from the initial request. Equal to +1+. */ rb_define_const(cTimestampResponse, "GRANTED_WITH_MODS", INT2NUM(TS_STATUS_GRANTED_WITH_MODS)); /* * Indicates a failure. No timestamp token was created. Equal to +2+. */ rb_define_const(cTimestampResponse, "REJECTION", INT2NUM(TS_STATUS_REJECTION)); /* * Indicates a failure. No timestamp token was created. Equal to +3+. */ rb_define_const(cTimestampResponse, "WAITING", INT2NUM(TS_STATUS_WAITING)); /* * Indicates a failure. No timestamp token was created. Revocation of a * certificate is imminent. Equal to +4+. */ rb_define_const(cTimestampResponse, "REVOCATION_WARNING", INT2NUM(TS_STATUS_REVOCATION_WARNING)); /* * Indicates a failure. No timestamp token was created. A certificate * has been revoked. Equal to +5+. */ rb_define_const(cTimestampResponse, "REVOCATION_NOTIFICATION", INT2NUM(TS_STATUS_REVOCATION_NOTIFICATION)); /* Document-class: OpenSSL::Timestamp::Factory * * Used to generate a Response from scratch. * * Please bear in mind that the implementation will always apply and prefer * the policy object identifier given in the request over the default policy * id specified in the Factory. As a consequence, +default_policy_id+ will * only be applied if no Request#policy_id was given. But this also means * that one needs to check the policy identifier in the request manually * before creating the Response, e.g. to check whether it complies to a * specific set of acceptable policies. * * There exists also the possibility to add certificates (instances of * OpenSSL::X509::Certificate) besides the timestamping certificate * that will be included in the resulting timestamp token if * Request#cert_requested? is +true+. Ideally, one would also include any * intermediate certificates (the root certificate can be left out - in * order to trust it any verifying party will have to be in its possession * anyway). This simplifies validation of the timestamp since these * intermediate certificates are "already there" and need not be passed as * external parameters to Response#verify anymore, thus minimizing external * resources needed for verification. * * ===Example: Inclusion of (untrusted) intermediate certificates * * Assume we received a timestamp request that has set Request#policy_id to * +nil+ and Request#cert_requested? to true. The raw request bytes are * stored in a variable called +req_raw+. We'd still like to integrate * the necessary intermediate certificates (in +inter1.cer+ and * +inter2.cer+) to simplify validation of the resulting Response. +ts.p12+ * is a PKCS#12-compatible file including the private key and the * timestamping certificate. * * req = OpenSSL::Timestamp::Request.new(raw_bytes) * p12 = OpenSSL::PKCS12.new(File.binread('ts.p12'), 'pwd') * inter1 = OpenSSL::X509::Certificate.new(File.binread('inter1.cer')) * inter2 = OpenSSL::X509::Certificate.new(File.binread('inter2.cer')) * fac = OpenSSL::Timestamp::Factory.new * fac.gen_time = Time.now * fac.serial_number = 1 * fac.allowed_digests = ["sha256", "sha384", "sha512"] * #needed because the Request contained no policy identifier * fac.default_policy_id = '1.2.3.4.5' * fac.additional_certificates = [ inter1, inter2 ] * timestamp = fac.create_timestamp(p12.key, p12.certificate, req) * * ==Attributes * * ===default_policy_id * * Request#policy_id will always be preferred over this if present in the * Request, only if Request#policy_id is nil default_policy will be used. * If none of both is present, a TimestampError will be raised when trying * to create a Response. * * call-seq: * factory.default_policy_id = "string" -> string * factory.default_policy_id -> string or nil * * ===serial_number * * Sets or retrieves the serial number to be used for timestamp creation. * Must be present for timestamp creation. * * call-seq: * factory.serial_number = number -> number * factory.serial_number -> number or nil * * ===gen_time * * Sets or retrieves the Time value to be used in the Response. Must be * present for timestamp creation. * * call-seq: * factory.gen_time = Time -> Time * factory.gen_time -> Time or nil * * ===additional_certs * * Sets or retrieves additional certificates apart from the timestamp * certificate (e.g. intermediate certificates) to be added to the Response. * Must be an Array of OpenSSL::X509::Certificate. * * call-seq: * factory.additional_certs = [cert1, cert2] -> [ cert1, cert2 ] * factory.additional_certs -> array or nil * * ===allowed_digests * * Sets or retrieves the digest algorithms that the factory is allowed * create timestamps for. Known vulnerable or weak algorithms should not be * allowed where possible. * Must be an Array of String or OpenSSL::Digest subclass instances. * * call-seq: * factory.allowed_digests = ["sha1", OpenSSL::Digest.new('SHA256').new] -> [ "sha1", OpenSSL::Digest) ] * factory.allowed_digests -> array or nil * */ cTimestampFactory = rb_define_class_under(mTimestamp, "Factory", rb_cObject); rb_attr(cTimestampFactory, rb_intern_const("allowed_digests"), 1, 1, 0); rb_attr(cTimestampFactory, rb_intern_const("default_policy_id"), 1, 1, 0); rb_attr(cTimestampFactory, rb_intern_const("serial_number"), 1, 1, 0); rb_attr(cTimestampFactory, rb_intern_const("gen_time"), 1, 1, 0); rb_attr(cTimestampFactory, rb_intern_const("additional_certs"), 1, 1, 0); rb_define_method(cTimestampFactory, "create_timestamp", ossl_tsfac_create_ts, 3); } #endif