From b99775b163ce44079c1f8727ce9b4ed8bb03489d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sun, 16 Feb 2020 15:21:29 +0900 Subject: Import openssl-2.2.0 (#2693) Import the master branch of ruby/openssl for preparing to release openssl-2.2.0 --- ext/openssl/History.md | 53 ++ ext/openssl/depend | 100 ++- ext/openssl/deprecation.rb | 8 +- ext/openssl/extconf.rb | 23 +- ext/openssl/lib/openssl.rb | 33 +- ext/openssl/lib/openssl/bn.rb | 2 +- ext/openssl/lib/openssl/buffering.rb | 33 +- ext/openssl/lib/openssl/cipher.rb | 2 +- ext/openssl/lib/openssl/config.rb | 7 +- ext/openssl/lib/openssl/digest.rb | 27 +- ext/openssl/lib/openssl/hmac.rb | 13 + ext/openssl/lib/openssl/pkcs5.rb | 2 +- ext/openssl/lib/openssl/pkey.rb | 2 +- ext/openssl/lib/openssl/ssl.rb | 42 +- ext/openssl/lib/openssl/version.rb | 5 + ext/openssl/lib/openssl/x509.rb | 170 +++- ext/openssl/openssl.gemspec | 47 +- ext/openssl/openssl_missing.h | 37 +- ext/openssl/ossl.c | 54 +- ext/openssl/ossl.h | 7 +- ext/openssl/ossl_asn1.c | 25 + ext/openssl/ossl_bn.c | 5 +- ext/openssl/ossl_cipher.c | 7 +- ext/openssl/ossl_digest.c | 2 - ext/openssl/ossl_engine.c | 3 + ext/openssl/ossl_hmac.c | 14 +- ext/openssl/ossl_kdf.c | 20 +- ext/openssl/ossl_ocsp.c | 15 +- ext/openssl/ossl_ocsp.h | 6 +- ext/openssl/ossl_pkcs7.c | 18 +- ext/openssl/ossl_pkcs7.h | 16 + ext/openssl/ossl_pkey.c | 152 +++- ext/openssl/ossl_pkey_ec.c | 29 + ext/openssl/ossl_pkey_rsa.c | 26 +- ext/openssl/ossl_ssl.c | 113 ++- ext/openssl/ossl_ts.c | 1519 ++++++++++++++++++++++++++++++++ ext/openssl/ossl_ts.h | 16 + ext/openssl/ossl_version.h | 15 - ext/openssl/ossl_x509ext.c | 14 + ext/openssl/ossl_x509name.c | 10 +- test/openssl/fixtures/chain/dh512.pem | 4 + test/openssl/fixtures/chain/server.crt | 13 + test/openssl/fixtures/chain/server.csr | 11 + test/openssl/fixtures/chain/server.key | 15 + test/openssl/test_asn1.rb | 38 +- test/openssl/test_bn.rb | 10 +- test/openssl/test_buffering.rb | 11 +- test/openssl/test_cipher.rb | 17 +- test/openssl/test_config.rb | 18 +- test/openssl/test_digest.rb | 35 +- test/openssl/test_engine.rb | 2 +- test/openssl/test_fips.rb | 2 +- test/openssl/test_hmac.rb | 12 +- test/openssl/test_kdf.rb | 2 +- test/openssl/test_ns_spki.rb | 4 +- test/openssl/test_ocsp.rb | 3 +- test/openssl/test_ossl.rb | 62 ++ test/openssl/test_pair.rb | 14 +- test/openssl/test_pkcs12.rb | 2 +- test/openssl/test_pkcs7.rb | 24 +- test/openssl/test_pkey_dh.rb | 2 +- test/openssl/test_pkey_dsa.rb | 2 +- test/openssl/test_pkey_ec.rb | 18 +- test/openssl/test_pkey_rsa.rb | 142 ++- test/openssl/test_random.rb | 2 +- test/openssl/test_ssl.rb | 128 ++- test/openssl/test_ssl_session.rb | 2 +- test/openssl/test_ts.rb | 667 ++++++++++++++ test/openssl/test_x509attr.rb | 12 +- test/openssl/test_x509cert.rb | 93 +- test/openssl/test_x509crl.rb | 27 +- test/openssl/test_x509ext.rb | 15 +- test/openssl/test_x509name.rb | 32 +- test/openssl/test_x509req.rb | 9 +- test/openssl/test_x509store.rb | 2 +- test/openssl/ut_eof.rb | 10 +- test/openssl/utils.rb | 92 +- 77 files changed, 3905 insertions(+), 341 deletions(-) create mode 100644 ext/openssl/lib/openssl/hmac.rb create mode 100644 ext/openssl/lib/openssl/version.rb create mode 100644 ext/openssl/ossl_ts.c create mode 100644 ext/openssl/ossl_ts.h delete mode 100644 ext/openssl/ossl_version.h create mode 100644 test/openssl/fixtures/chain/dh512.pem create mode 100644 test/openssl/fixtures/chain/server.crt create mode 100644 test/openssl/fixtures/chain/server.csr create mode 100644 test/openssl/fixtures/chain/server.key create mode 100644 test/openssl/test_ossl.rb create mode 100644 test/openssl/test_ts.rb diff --git a/ext/openssl/History.md b/ext/openssl/History.md index db5050014e..cdb44b1293 100644 --- a/ext/openssl/History.md +++ b/ext/openssl/History.md @@ -1,3 +1,56 @@ +Version 2.2.0 (not yet released) +============= + +* Change default `OpenSSL::SSL::SSLServer#listen` backlog argument from + 5 to `Socket::SOMAXCONN`. +* Make `OpenSSL::HMAC#==` use a timing safe string comparison. +* Remove unsupported MDC2, DSS, DSS1, and SHA algorithms. +* Add support for SHA3 and BLAKE digests. +* Add `OpenSSL::SSL::SSLSocket.open` for opening a `TCPSocket` and + returning an `OpenSSL::SSL::SSLSocket` for it. +* Support marshalling of `OpenSSL::X509` objects. +* Add `OpenSSL.secure_compare` for timing safe string comparison for + strings of possibly unequal length. +* Add `OpenSSL.fixed_length_secure_compare` for timing safe string + comparison for strings of equal length. +* Add `OpenSSL::SSL::SSLSocket#{finished_message,peer_finished_message}` + for last finished message sent and received. +* Add `OpenSSL::Timestamp` module for handing timestamp requests and + responses. +* Add helper methods for `OpenSSL::X509::Certificate`: + `find_extension`, `subject_key_identifier`, + `authority_key_identifier`, `crl_uris`, `ca_issuer_uris` and + `ocsp_uris`. +* Add helper methods for `OpenSSL::X509::CRL`: + `find_extension` and `subject_key_identifier`. +* Remove `OpenSSL::PKCS7::SignerInfo#name` alias for `#issuer`. +* Add `OpenSSL::ECPoint#add` for adding points to an elliptic curve + group. + [[GitHub #261]](https://github.com/ruby/openssl/pull/261) +* Make `OpenSSL::PKey::RSA#{export,to_der}` correctly check `key`, + `factors`, and `crt_params`. + [[GitHub #258]](https://github.com/ruby/openssl/pull/258) +* Add `OpenSSL::SSL::{SSLSocket,SSLServer}#fileno`, returning the + underlying socket file descriptor number. + [[GitHub #247]](https://github.com/ruby/openssl/pull/247) +* Support client certificates with TLS 1.3, and support post-handshake + authentication with OpenSSL 1.1.1+. + [[GitHub #239]](https://github.com/ruby/openssl/pull/239) +* Add `OpenSSL::ASN1::ObjectId#==` for equality testing. +* Add `OpenSSL::X509::Extension#value_der` for the raw value of + the extension. + [[GitHub #234]](https://github.com/ruby/openssl/pull/234) +* Signficantly reduce allocated memory in `OpenSSL::Buffering#do_write`. + [[GitHub #212]](https://github.com/ruby/openssl/pull/212) +* Ensure all valid IPv6 addresses are considered valid as elements + of subjectAlternativeName in certificates. + [[GitHub #185]](https://github.com/ruby/openssl/pull/185) +* Allow recipient's certificate to be omitted in PCKS7#decrypt. + [[GitHub #183]](https://github.com/ruby/openssl/pull/183) +* Add support for reading keys in PKCS8 format and export via instance methods + added to `OpenSSL::PKey` classes: `private_to_der`, `private_to_pem`, + `public_to_der` and `public_to_pem`. + Version 2.1.2 ============= diff --git a/ext/openssl/depend b/ext/openssl/depend index 68cf357294..943f4f4e47 100644 --- a/ext/openssl/depend +++ b/ext/openssl/depend @@ -39,7 +39,7 @@ ossl.o: ossl_pkcs7.h ossl.o: ossl_pkey.h ossl.o: ossl_rand.h ossl.o: ossl_ssl.h -ossl.o: ossl_version.h +ossl.o: ossl_ts.h ossl.o: ossl_x509.h ossl.o: ruby_missing.h ossl_asn1.o: $(RUBY_EXTCONF_H) @@ -77,7 +77,7 @@ ossl_asn1.o: ossl_pkcs7.h ossl_asn1.o: ossl_pkey.h ossl_asn1.o: ossl_rand.h ossl_asn1.o: ossl_ssl.h -ossl_asn1.o: ossl_version.h +ossl_asn1.o: ossl_ts.h ossl_asn1.o: ossl_x509.h ossl_asn1.o: ruby_missing.h ossl_bio.o: $(RUBY_EXTCONF_H) @@ -115,7 +115,7 @@ ossl_bio.o: ossl_pkcs7.h ossl_bio.o: ossl_pkey.h ossl_bio.o: ossl_rand.h ossl_bio.o: ossl_ssl.h -ossl_bio.o: ossl_version.h +ossl_bio.o: ossl_ts.h ossl_bio.o: ossl_x509.h ossl_bio.o: ruby_missing.h ossl_bn.o: $(RUBY_EXTCONF_H) @@ -153,7 +153,7 @@ ossl_bn.o: ossl_pkcs7.h ossl_bn.o: ossl_pkey.h ossl_bn.o: ossl_rand.h ossl_bn.o: ossl_ssl.h -ossl_bn.o: ossl_version.h +ossl_bn.o: ossl_ts.h ossl_bn.o: ossl_x509.h ossl_bn.o: ruby_missing.h ossl_cipher.o: $(RUBY_EXTCONF_H) @@ -191,7 +191,7 @@ ossl_cipher.o: ossl_pkcs7.h ossl_cipher.o: ossl_pkey.h ossl_cipher.o: ossl_rand.h ossl_cipher.o: ossl_ssl.h -ossl_cipher.o: ossl_version.h +ossl_cipher.o: ossl_ts.h ossl_cipher.o: ossl_x509.h ossl_cipher.o: ruby_missing.h ossl_config.o: $(RUBY_EXTCONF_H) @@ -229,7 +229,7 @@ ossl_config.o: ossl_pkcs7.h ossl_config.o: ossl_pkey.h ossl_config.o: ossl_rand.h ossl_config.o: ossl_ssl.h -ossl_config.o: ossl_version.h +ossl_config.o: ossl_ts.h ossl_config.o: ossl_x509.h ossl_config.o: ruby_missing.h ossl_digest.o: $(RUBY_EXTCONF_H) @@ -267,7 +267,7 @@ ossl_digest.o: ossl_pkcs7.h ossl_digest.o: ossl_pkey.h ossl_digest.o: ossl_rand.h ossl_digest.o: ossl_ssl.h -ossl_digest.o: ossl_version.h +ossl_digest.o: ossl_ts.h ossl_digest.o: ossl_x509.h ossl_digest.o: ruby_missing.h ossl_engine.o: $(RUBY_EXTCONF_H) @@ -305,7 +305,7 @@ ossl_engine.o: ossl_pkcs7.h ossl_engine.o: ossl_pkey.h ossl_engine.o: ossl_rand.h ossl_engine.o: ossl_ssl.h -ossl_engine.o: ossl_version.h +ossl_engine.o: ossl_ts.h ossl_engine.o: ossl_x509.h ossl_engine.o: ruby_missing.h ossl_hmac.o: $(RUBY_EXTCONF_H) @@ -343,7 +343,7 @@ ossl_hmac.o: ossl_pkcs7.h ossl_hmac.o: ossl_pkey.h ossl_hmac.o: ossl_rand.h ossl_hmac.o: ossl_ssl.h -ossl_hmac.o: ossl_version.h +ossl_hmac.o: ossl_ts.h ossl_hmac.o: ossl_x509.h ossl_hmac.o: ruby_missing.h ossl_kdf.o: $(RUBY_EXTCONF_H) @@ -381,7 +381,7 @@ ossl_kdf.o: ossl_pkcs7.h ossl_kdf.o: ossl_pkey.h ossl_kdf.o: ossl_rand.h ossl_kdf.o: ossl_ssl.h -ossl_kdf.o: ossl_version.h +ossl_kdf.o: ossl_ts.h ossl_kdf.o: ossl_x509.h ossl_kdf.o: ruby_missing.h ossl_ns_spki.o: $(RUBY_EXTCONF_H) @@ -419,7 +419,7 @@ ossl_ns_spki.o: ossl_pkcs7.h ossl_ns_spki.o: ossl_pkey.h ossl_ns_spki.o: ossl_rand.h ossl_ns_spki.o: ossl_ssl.h -ossl_ns_spki.o: ossl_version.h +ossl_ns_spki.o: ossl_ts.h ossl_ns_spki.o: ossl_x509.h ossl_ns_spki.o: ruby_missing.h ossl_ocsp.o: $(RUBY_EXTCONF_H) @@ -457,7 +457,7 @@ ossl_ocsp.o: ossl_pkcs7.h ossl_ocsp.o: ossl_pkey.h ossl_ocsp.o: ossl_rand.h ossl_ocsp.o: ossl_ssl.h -ossl_ocsp.o: ossl_version.h +ossl_ocsp.o: ossl_ts.h ossl_ocsp.o: ossl_x509.h ossl_ocsp.o: ruby_missing.h ossl_pkcs12.o: $(RUBY_EXTCONF_H) @@ -495,7 +495,7 @@ ossl_pkcs12.o: ossl_pkcs7.h ossl_pkcs12.o: ossl_pkey.h ossl_pkcs12.o: ossl_rand.h ossl_pkcs12.o: ossl_ssl.h -ossl_pkcs12.o: ossl_version.h +ossl_pkcs12.o: ossl_ts.h ossl_pkcs12.o: ossl_x509.h ossl_pkcs12.o: ruby_missing.h ossl_pkcs7.o: $(RUBY_EXTCONF_H) @@ -533,7 +533,7 @@ ossl_pkcs7.o: ossl_pkcs7.h ossl_pkcs7.o: ossl_pkey.h ossl_pkcs7.o: ossl_rand.h ossl_pkcs7.o: ossl_ssl.h -ossl_pkcs7.o: ossl_version.h +ossl_pkcs7.o: ossl_ts.h ossl_pkcs7.o: ossl_x509.h ossl_pkcs7.o: ruby_missing.h ossl_pkey.o: $(RUBY_EXTCONF_H) @@ -571,7 +571,7 @@ ossl_pkey.o: ossl_pkey.c ossl_pkey.o: ossl_pkey.h ossl_pkey.o: ossl_rand.h ossl_pkey.o: ossl_ssl.h -ossl_pkey.o: ossl_version.h +ossl_pkey.o: ossl_ts.h ossl_pkey.o: ossl_x509.h ossl_pkey.o: ruby_missing.h ossl_pkey_dh.o: $(RUBY_EXTCONF_H) @@ -609,7 +609,7 @@ ossl_pkey_dh.o: ossl_pkey.h ossl_pkey_dh.o: ossl_pkey_dh.c ossl_pkey_dh.o: ossl_rand.h ossl_pkey_dh.o: ossl_ssl.h -ossl_pkey_dh.o: ossl_version.h +ossl_pkey_dh.o: ossl_ts.h ossl_pkey_dh.o: ossl_x509.h ossl_pkey_dh.o: ruby_missing.h ossl_pkey_dsa.o: $(RUBY_EXTCONF_H) @@ -647,7 +647,7 @@ ossl_pkey_dsa.o: ossl_pkey.h ossl_pkey_dsa.o: ossl_pkey_dsa.c ossl_pkey_dsa.o: ossl_rand.h ossl_pkey_dsa.o: ossl_ssl.h -ossl_pkey_dsa.o: ossl_version.h +ossl_pkey_dsa.o: ossl_ts.h ossl_pkey_dsa.o: ossl_x509.h ossl_pkey_dsa.o: ruby_missing.h ossl_pkey_ec.o: $(RUBY_EXTCONF_H) @@ -685,7 +685,7 @@ ossl_pkey_ec.o: ossl_pkey.h ossl_pkey_ec.o: ossl_pkey_ec.c ossl_pkey_ec.o: ossl_rand.h ossl_pkey_ec.o: ossl_ssl.h -ossl_pkey_ec.o: ossl_version.h +ossl_pkey_ec.o: ossl_ts.h ossl_pkey_ec.o: ossl_x509.h ossl_pkey_ec.o: ruby_missing.h ossl_pkey_rsa.o: $(RUBY_EXTCONF_H) @@ -723,7 +723,7 @@ ossl_pkey_rsa.o: ossl_pkey.h ossl_pkey_rsa.o: ossl_pkey_rsa.c ossl_pkey_rsa.o: ossl_rand.h ossl_pkey_rsa.o: ossl_ssl.h -ossl_pkey_rsa.o: ossl_version.h +ossl_pkey_rsa.o: ossl_ts.h ossl_pkey_rsa.o: ossl_x509.h ossl_pkey_rsa.o: ruby_missing.h ossl_rand.o: $(RUBY_EXTCONF_H) @@ -761,7 +761,7 @@ ossl_rand.o: ossl_pkey.h ossl_rand.o: ossl_rand.c ossl_rand.o: ossl_rand.h ossl_rand.o: ossl_ssl.h -ossl_rand.o: ossl_version.h +ossl_rand.o: ossl_ts.h ossl_rand.o: ossl_x509.h ossl_rand.o: ruby_missing.h ossl_ssl.o: $(RUBY_EXTCONF_H) @@ -799,7 +799,7 @@ ossl_ssl.o: ossl_pkey.h ossl_ssl.o: ossl_rand.h ossl_ssl.o: ossl_ssl.c ossl_ssl.o: ossl_ssl.h -ossl_ssl.o: ossl_version.h +ossl_ssl.o: ossl_ts.h ossl_ssl.o: ossl_x509.h ossl_ssl.o: ruby_missing.h ossl_ssl_session.o: $(RUBY_EXTCONF_H) @@ -837,9 +837,47 @@ ossl_ssl_session.o: ossl_pkey.h ossl_ssl_session.o: ossl_rand.h ossl_ssl_session.o: ossl_ssl.h ossl_ssl_session.o: ossl_ssl_session.c -ossl_ssl_session.o: ossl_version.h +ossl_ssl_session.o: ossl_ts.h ossl_ssl_session.o: ossl_x509.h ossl_ssl_session.o: ruby_missing.h +ossl_ts.o: $(RUBY_EXTCONF_H) +ossl_ts.o: $(arch_hdrdir)/ruby/config.h +ossl_ts.o: $(hdrdir)/ruby.h +ossl_ts.o: $(hdrdir)/ruby/assert.h +ossl_ts.o: $(hdrdir)/ruby/backward.h +ossl_ts.o: $(hdrdir)/ruby/defines.h +ossl_ts.o: $(hdrdir)/ruby/encoding.h +ossl_ts.o: $(hdrdir)/ruby/intern.h +ossl_ts.o: $(hdrdir)/ruby/io.h +ossl_ts.o: $(hdrdir)/ruby/missing.h +ossl_ts.o: $(hdrdir)/ruby/onigmo.h +ossl_ts.o: $(hdrdir)/ruby/oniguruma.h +ossl_ts.o: $(hdrdir)/ruby/ruby.h +ossl_ts.o: $(hdrdir)/ruby/st.h +ossl_ts.o: $(hdrdir)/ruby/subst.h +ossl_ts.o: $(hdrdir)/ruby/thread.h +ossl_ts.o: openssl_missing.h +ossl_ts.o: ossl.h +ossl_ts.o: ossl_asn1.h +ossl_ts.o: ossl_bio.h +ossl_ts.o: ossl_bn.h +ossl_ts.o: ossl_cipher.h +ossl_ts.o: ossl_config.h +ossl_ts.o: ossl_digest.h +ossl_ts.o: ossl_engine.h +ossl_ts.o: ossl_hmac.h +ossl_ts.o: ossl_kdf.h +ossl_ts.o: ossl_ns_spki.h +ossl_ts.o: ossl_ocsp.h +ossl_ts.o: ossl_pkcs12.h +ossl_ts.o: ossl_pkcs7.h +ossl_ts.o: ossl_pkey.h +ossl_ts.o: ossl_rand.h +ossl_ts.o: ossl_ssl.h +ossl_ts.o: ossl_ts.c +ossl_ts.o: ossl_ts.h +ossl_ts.o: ossl_x509.h +ossl_ts.o: ruby_missing.h ossl_x509.o: $(RUBY_EXTCONF_H) ossl_x509.o: $(arch_hdrdir)/ruby/config.h ossl_x509.o: $(hdrdir)/ruby.h @@ -874,7 +912,7 @@ ossl_x509.o: ossl_pkcs7.h ossl_x509.o: ossl_pkey.h ossl_x509.o: ossl_rand.h ossl_x509.o: ossl_ssl.h -ossl_x509.o: ossl_version.h +ossl_x509.o: ossl_ts.h ossl_x509.o: ossl_x509.c ossl_x509.o: ossl_x509.h ossl_x509.o: ruby_missing.h @@ -912,7 +950,7 @@ ossl_x509attr.o: ossl_pkcs7.h ossl_x509attr.o: ossl_pkey.h ossl_x509attr.o: ossl_rand.h ossl_x509attr.o: ossl_ssl.h -ossl_x509attr.o: ossl_version.h +ossl_x509attr.o: ossl_ts.h ossl_x509attr.o: ossl_x509.h ossl_x509attr.o: ossl_x509attr.c ossl_x509attr.o: ruby_missing.h @@ -950,7 +988,7 @@ ossl_x509cert.o: ossl_pkcs7.h ossl_x509cert.o: ossl_pkey.h ossl_x509cert.o: ossl_rand.h ossl_x509cert.o: ossl_ssl.h -ossl_x509cert.o: ossl_version.h +ossl_x509cert.o: ossl_ts.h ossl_x509cert.o: ossl_x509.h ossl_x509cert.o: ossl_x509cert.c ossl_x509cert.o: ruby_missing.h @@ -988,7 +1026,7 @@ ossl_x509crl.o: ossl_pkcs7.h ossl_x509crl.o: ossl_pkey.h ossl_x509crl.o: ossl_rand.h ossl_x509crl.o: ossl_ssl.h -ossl_x509crl.o: ossl_version.h +ossl_x509crl.o: ossl_ts.h ossl_x509crl.o: ossl_x509.h ossl_x509crl.o: ossl_x509crl.c ossl_x509crl.o: ruby_missing.h @@ -1026,7 +1064,7 @@ ossl_x509ext.o: ossl_pkcs7.h ossl_x509ext.o: ossl_pkey.h ossl_x509ext.o: ossl_rand.h ossl_x509ext.o: ossl_ssl.h -ossl_x509ext.o: ossl_version.h +ossl_x509ext.o: ossl_ts.h ossl_x509ext.o: ossl_x509.h ossl_x509ext.o: ossl_x509ext.c ossl_x509ext.o: ruby_missing.h @@ -1064,7 +1102,7 @@ ossl_x509name.o: ossl_pkcs7.h ossl_x509name.o: ossl_pkey.h ossl_x509name.o: ossl_rand.h ossl_x509name.o: ossl_ssl.h -ossl_x509name.o: ossl_version.h +ossl_x509name.o: ossl_ts.h ossl_x509name.o: ossl_x509.h ossl_x509name.o: ossl_x509name.c ossl_x509name.o: ruby_missing.h @@ -1102,7 +1140,7 @@ ossl_x509req.o: ossl_pkcs7.h ossl_x509req.o: ossl_pkey.h ossl_x509req.o: ossl_rand.h ossl_x509req.o: ossl_ssl.h -ossl_x509req.o: ossl_version.h +ossl_x509req.o: ossl_ts.h ossl_x509req.o: ossl_x509.h ossl_x509req.o: ossl_x509req.c ossl_x509req.o: ruby_missing.h @@ -1140,7 +1178,7 @@ ossl_x509revoked.o: ossl_pkcs7.h ossl_x509revoked.o: ossl_pkey.h ossl_x509revoked.o: ossl_rand.h ossl_x509revoked.o: ossl_ssl.h -ossl_x509revoked.o: ossl_version.h +ossl_x509revoked.o: ossl_ts.h ossl_x509revoked.o: ossl_x509.h ossl_x509revoked.o: ossl_x509revoked.c ossl_x509revoked.o: ruby_missing.h @@ -1178,7 +1216,7 @@ ossl_x509store.o: ossl_pkcs7.h ossl_x509store.o: ossl_pkey.h ossl_x509store.o: ossl_rand.h ossl_x509store.o: ossl_ssl.h -ossl_x509store.o: ossl_version.h +ossl_x509store.o: ossl_ts.h ossl_x509store.o: ossl_x509.h ossl_x509store.o: ossl_x509store.c ossl_x509store.o: ruby_missing.h diff --git a/ext/openssl/deprecation.rb b/ext/openssl/deprecation.rb index afe989ead1..6af7d562fb 100644 --- a/ext/openssl/deprecation.rb +++ b/ext/openssl/deprecation.rb @@ -1,9 +1,9 @@ -# frozen_string_literal: false +# frozen_string_literal: true module OpenSSL def self.deprecated_warning_flag unless flag = (@deprecated_warning_flag ||= nil) if try_compile("", flag = "-Werror=deprecated-declarations") - $warnflags = "#{@warnflags = $warnflags}" #{flag}" + $warnflags << " #{flag}" else flag = "" end @@ -12,10 +12,6 @@ module OpenSSL flag end - def self.restore_warning_flag - $warnflags = @warnflags - end - def self.check_func(func, header) have_func(func, header, deprecated_warning_flag) end diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 264130bb51..344d759652 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -1,5 +1,5 @@ # -*- coding: us-ascii -*- -# frozen_string_literal: false +# frozen_string_literal: true =begin = Info 'OpenSSL for Ruby 2' project @@ -19,7 +19,7 @@ dir_config("kerberos") Logging::message "=== OpenSSL for Ruby configurator ===\n" -# Check with -Werror=deprecated-declarations if available +# Add -Werror=deprecated-declarations to $warnflags if available OpenSSL.deprecated_warning_flag ## @@ -40,6 +40,12 @@ end Logging::message "=== Checking for required stuff... ===\n" result = pkg_config("openssl") && have_header("openssl/ssl.h") +if $mingw + append_cflags '-D_FORTIFY_SOURCE=2' + append_ldflags '-fstack-protector' + have_library 'ssp' +end + def find_openssl_library if $mswin || $mingw # required for static OpenSSL libraries @@ -109,7 +115,8 @@ Logging::message "=== Checking for OpenSSL features... ===\n" # compile options have_func("RAND_egd") engines = %w{builtin_engines openbsd_dev_crypto dynamic 4758cca aep atalla chil - cswift nuron sureware ubsec padlock capi gmp gost cryptodev aesni} + cswift nuron sureware ubsec padlock capi gmp gost cryptodev aesni + cloudhsm} engines.each { |name| OpenSSL.check_func_or_macro("ENGINE_load_#{name}", "openssl/engine.h") } @@ -144,6 +151,7 @@ have_func("HMAC_CTX_free") OpenSSL.check_func("RAND_pseudo_bytes", "openssl/rand.h") # deprecated have_func("X509_STORE_get_ex_data") have_func("X509_STORE_set_ex_data") +have_func("X509_STORE_get_ex_new_index") have_func("X509_CRL_get0_signature") have_func("X509_REQ_get0_signature") have_func("X509_REVOKED_get0_serialNumber") @@ -164,11 +172,18 @@ OpenSSL.check_func_or_macro("SSL_CTX_set_min_proto_version", "openssl/ssl.h") have_func("SSL_CTX_get_security_level") have_func("X509_get0_notBefore") have_func("SSL_SESSION_get_protocol_version") +have_func("TS_STATUS_INFO_get0_status") +have_func("TS_STATUS_INFO_get0_text") +have_func("TS_STATUS_INFO_get0_failure_info") +have_func("TS_VERIFY_CTS_set_certs") +have_func("TS_VERIFY_CTX_set_store") +have_func("TS_VERIFY_CTX_add_flags") +have_func("TS_RESP_CTX_set_time_cb") have_func("EVP_PBE_scrypt") +have_func("SSL_CTX_set_post_handshake_auth") Logging::message "=== Checking done. ===\n" create_header -OpenSSL.restore_warning_flag create_makefile("openssl") Logging::message "Done.\n" diff --git a/ext/openssl/lib/openssl.rb b/ext/openssl/lib/openssl.rb index 0914282920..7ba2229a1b 100644 --- a/ext/openssl/lib/openssl.rb +++ b/ext/openssl/lib/openssl.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true =begin = Info 'OpenSSL for Ruby 2' project @@ -12,11 +12,26 @@ require 'openssl.so' -require 'openssl/bn' -require 'openssl/pkey' -require 'openssl/cipher' -require 'openssl/config' -require 'openssl/digest' -require 'openssl/x509' -require 'openssl/ssl' -require 'openssl/pkcs5' +require_relative 'openssl/bn' +require_relative 'openssl/pkey' +require_relative 'openssl/cipher' +require_relative 'openssl/config' +require_relative 'openssl/digest' +require_relative 'openssl/hmac' +require_relative 'openssl/x509' +require_relative 'openssl/ssl' +require_relative 'openssl/pkcs5' + +module OpenSSL + # call-seq: + # OpenSSL.secure_compare(string, string) -> boolean + # + # Constant time memory comparison. Inputs are hashed using SHA-256 to mask + # the length of the secret. Returns +true+ if the strings are identical, + # +false+ otherwise. + def self.secure_compare(a, b) + hashed_a = OpenSSL::Digest::SHA256.digest(a) + hashed_b = OpenSSL::Digest::SHA256.digest(b) + OpenSSL.fixed_length_secure_compare(hashed_a, hashed_b) && a == b + end +end diff --git a/ext/openssl/lib/openssl/bn.rb b/ext/openssl/lib/openssl/bn.rb index 8d1ebefb6e..0a5e11b4c2 100644 --- a/ext/openssl/lib/openssl/bn.rb +++ b/ext/openssl/lib/openssl/bn.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # # = Ruby-space definitions that completes C-space funcs for BN diff --git a/ext/openssl/lib/openssl/buffering.rb b/ext/openssl/lib/openssl/buffering.rb index 5d1586e594..a5f4241bf4 100644 --- a/ext/openssl/lib/openssl/buffering.rb +++ b/ext/openssl/lib/openssl/buffering.rb @@ -1,5 +1,5 @@ # coding: binary -# frozen_string_literal: false +# frozen_string_literal: true #-- #= Info # 'OpenSSL for Ruby 2' project @@ -22,6 +22,29 @@ module OpenSSL::Buffering include Enumerable + # A buffer which will retain binary encoding. + class Buffer < String + BINARY = Encoding::BINARY + + def initialize + super + + force_encoding(BINARY) + end + + def << string + if string.encoding == BINARY + super(string) + else + super(string.b) + end + + return self + end + + alias concat << + end + ## # The "sync mode" of the SSLSocket. # @@ -40,7 +63,7 @@ module OpenSSL::Buffering def initialize(*) super @eof = false - @rbuffer = "" + @rbuffer = Buffer.new @sync = @io.sync end @@ -312,7 +335,7 @@ module OpenSSL::Buffering # buffer is flushed to the underlying socket. def do_write(s) - @wbuffer = "" unless defined? @wbuffer + @wbuffer = Buffer.new unless defined? @wbuffer @wbuffer << s @wbuffer.force_encoding(Encoding::BINARY) @sync ||= false @@ -398,7 +421,7 @@ module OpenSSL::Buffering # See IO#puts for full details. def puts(*args) - s = "" + s = Buffer.new if args.empty? s << "\n" end @@ -416,7 +439,7 @@ module OpenSSL::Buffering # See IO#print for full details. def print(*args) - s = "" + s = Buffer.new args.each{ |arg| s << arg.to_s } do_write(s) nil diff --git a/ext/openssl/lib/openssl/cipher.rb b/ext/openssl/lib/openssl/cipher.rb index af721b3a80..8ad8c35dd3 100644 --- a/ext/openssl/lib/openssl/cipher.rb +++ b/ext/openssl/lib/openssl/cipher.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # = Ruby-space predefined Cipher subclasses # diff --git a/ext/openssl/lib/openssl/config.rb b/ext/openssl/lib/openssl/config.rb index 48d8be0069..ef83c57b20 100644 --- a/ext/openssl/lib/openssl/config.rb +++ b/ext/openssl/lib/openssl/config.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true =begin = Ruby-space definitions that completes C-space funcs for Config @@ -53,9 +53,8 @@ module OpenSSL def parse_config(io) begin parse_config_lines(io) - rescue ConfigError => e - e.message.replace("error in line #{io.lineno}: " + e.message) - raise + rescue => error + raise ConfigError, "error in line #{io.lineno}: " + error.message end end diff --git a/ext/openssl/lib/openssl/digest.rb b/ext/openssl/lib/openssl/digest.rb index b6744de6bd..c953911954 100644 --- a/ext/openssl/lib/openssl/digest.rb +++ b/ext/openssl/lib/openssl/digest.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # = Ruby-space predefined Digest subclasses # @@ -15,11 +15,17 @@ module OpenSSL class Digest - alg = %w(MD2 MD4 MD5 MDC2 RIPEMD160 SHA1 SHA224 SHA256 SHA384 SHA512) - if OPENSSL_VERSION_NUMBER < 0x10100000 - alg += %w(DSS DSS1 SHA) + # You can get a list of all algorithms: + # openssl list -digest-algorithms + + ALGORITHMS = %w(MD4 MD5 RIPEMD160 SHA1 SHA224 SHA256 SHA384 SHA512) + + if !OPENSSL_VERSION.include?("LibreSSL") && OPENSSL_VERSION_NUMBER > 0x10101000 + ALGORITHMS.concat %w(BLAKE2b512 BLAKE2s256 SHA3-224 SHA3-256 SHA3-384 SHA3-512) end + ALGORITHMS.freeze + # Return the hash value computed with _name_ Digest. _name_ is either the # long name or short name of a supported digest algorithm. # @@ -35,17 +41,20 @@ module OpenSSL super(data, name) end - alg.each{|name| + ALGORITHMS.each do |name| klass = Class.new(self) { define_method(:initialize, ->(data = nil) {super(name, data)}) } + singleton = (class << klass; self; end) + singleton.class_eval{ - define_method(:digest){|data| new.digest(data) } - define_method(:hexdigest){|data| new.hexdigest(data) } + define_method(:digest) {|data| new.digest(data)} + define_method(:hexdigest) {|data| new.hexdigest(data)} } - const_set(name, klass) - } + + const_set(name.tr('-', '_'), klass) + end # Deprecated. # diff --git a/ext/openssl/lib/openssl/hmac.rb b/ext/openssl/lib/openssl/hmac.rb new file mode 100644 index 0000000000..3d4427611d --- /dev/null +++ b/ext/openssl/lib/openssl/hmac.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module OpenSSL + class HMAC + # Securely compare with another HMAC instance in constant time. + def ==(other) + return false unless HMAC === other + return false unless self.digest.bytesize == other.digest.bytesize + + OpenSSL.fixed_length_secure_compare(self.digest, other.digest) + end + end +end diff --git a/ext/openssl/lib/openssl/pkcs5.rb b/ext/openssl/lib/openssl/pkcs5.rb index 959447df5e..8dedc4beef 100644 --- a/ext/openssl/lib/openssl/pkcs5.rb +++ b/ext/openssl/lib/openssl/pkcs5.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # Ruby/OpenSSL Project # Copyright (C) 2017 Ruby/OpenSSL Project Authors diff --git a/ext/openssl/lib/openssl/pkey.rb b/ext/openssl/lib/openssl/pkey.rb index 8a547c340d..ecb112f792 100644 --- a/ext/openssl/lib/openssl/pkey.rb +++ b/ext/openssl/lib/openssl/pkey.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # Ruby/OpenSSL Project # Copyright (C) 2017 Ruby/OpenSSL Project Authors diff --git a/ext/openssl/lib/openssl/ssl.rb b/ext/openssl/lib/openssl/ssl.rb index 355eb2ebbb..8554ada0bb 100644 --- a/ext/openssl/lib/openssl/ssl.rb +++ b/ext/openssl/lib/openssl/ssl.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true =begin = Info 'OpenSSL for Ruby 2' project @@ -13,6 +13,7 @@ require "openssl/buffering" require "io/nonblock" require "ipaddr" +require "socket" module OpenSSL module SSL @@ -231,6 +232,11 @@ YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 end module SocketForwarder + # The file descriptor for the socket. + def fileno + to_io.fileno + end + def addr to_io.addr end @@ -435,6 +441,38 @@ YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 def session_get_cb @context.session_get_cb end + + class << self + + # call-seq: + # open(remote_host, remote_port, local_host=nil, local_port=nil, context: nil) + # + # Creates a new instance of SSLSocket. + # _remote\_host_ and _remote\_port_ are used to open TCPSocket. + # If _local\_host_ and _local\_port_ are specified, + # then those parameters are used on the local end to establish the connection. + # If _context_ is provided, + # the SSL Sockets initial params will be taken from the context. + # + # === Examples + # + # sock = OpenSSL::SSL::SSLSocket.open('localhost', 443) + # sock.connect # Initiates a connection to localhost:443 + # + # with SSLContext: + # + # ctx = OpenSSL::SSL::SSLContext.new + # sock = OpenSSL::SSL::SSLSocket.open('localhost', 443, context: ctx) + # sock.connect # Initiates a connection to localhost:443 with SSLContext + def open(remote_host, remote_port, local_host=nil, local_port=nil, context: nil) + sock = ::TCPSocket.open(remote_host, remote_port, local_host, local_port) + if context.nil? + return OpenSSL::SSL::SSLSocket.new(sock) + else + return OpenSSL::SSL::SSLSocket.new(sock, context) + end + end + end end ## @@ -465,7 +503,7 @@ YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 end # See TCPServer#listen for details. - def listen(backlog=5) + def listen(backlog=Socket::SOMAXCONN) @svr.listen(backlog) end diff --git a/ext/openssl/lib/openssl/version.rb b/ext/openssl/lib/openssl/version.rb new file mode 100644 index 0000000000..b07bb33009 --- /dev/null +++ b/ext/openssl/lib/openssl/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module OpenSSL + VERSION = "2.2.0" unless defined?(VERSION) +end diff --git a/ext/openssl/lib/openssl/x509.rb b/ext/openssl/lib/openssl/x509.rb index 98358f90da..1d2a5aaca8 100644 --- a/ext/openssl/lib/openssl/x509.rb +++ b/ext/openssl/lib/openssl/x509.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true #-- # = Ruby-space definitions that completes C-space funcs for X509 and subclasses # @@ -14,6 +14,22 @@ module OpenSSL module X509 + module Marshal + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + def _load(string) + new(string) + end + end + + def _dump(_level) + to_der + end + end + class ExtensionFactory def create_extension(*arg) if arg.size > 1 @@ -41,6 +57,8 @@ module OpenSSL end class Extension + include Marshal + def ==(other) return false unless Extension === other to_der == other.to_der @@ -60,9 +78,146 @@ module OpenSSL def to_a [ self.oid, self.value, self.critical? ] end + + module Helpers + def find_extension(oid) + extensions.find { |e| e.oid == oid } + end + end + + module SubjectKeyIdentifier + include Helpers + + # Get the subject's key identifier from the subjectKeyIdentifier + # exteension, as described in RFC5280 Section 4.2.1.2. + # + # Returns the binary String key identifier or nil or raises + # ASN1::ASN1Error. + def subject_key_identifier + ext = find_extension("subjectKeyIdentifier") + return nil if ext.nil? + + ski_asn1 = ASN1.decode(ext.value_der) + if ext.critical? || ski_asn1.tag_class != :UNIVERSAL || ski_asn1.tag != ASN1::OCTET_STRING + raise ASN1::ASN1Error, "invalid extension" + end + + ski_asn1.value + end + end + + module AuthorityKeyIdentifier + include Helpers + + # Get the issuing certificate's key identifier from the + # authorityKeyIdentifier extension, as described in RFC5280 + # Section 4.2.1.1 + # + # Returns the binary String keyIdentifier or nil or raises + # ASN1::ASN1Error. + def authority_key_identifier + ext = find_extension("authorityKeyIdentifier") + return nil if ext.nil? + + aki_asn1 = ASN1.decode(ext.value_der) + if ext.critical? || aki_asn1.tag_class != :UNIVERSAL || aki_asn1.tag != ASN1::SEQUENCE + raise ASN1::ASN1Error, "invalid extension" + end + + key_id = aki_asn1.value.find do |v| + v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0 + end + + key_id.nil? ? nil : key_id.value + end + end + + module CRLDistributionPoints + include Helpers + + # Get the distributionPoint fullName URI from the certificate's CRL + # distribution points extension, as described in RFC5280 Section + # 4.2.1.13 + # + # Returns an array of strings or nil or raises ASN1::ASN1Error. + def crl_uris + ext = find_extension("crlDistributionPoints") + return nil if ext.nil? + + cdp_asn1 = ASN1.decode(ext.value_der) + if cdp_asn1.tag_class != :UNIVERSAL || cdp_asn1.tag != ASN1::SEQUENCE + raise ASN1::ASN1Error, "invalid extension" + end + + crl_uris = cdp_asn1.map do |crl_distribution_point| + distribution_point = crl_distribution_point.value.find do |v| + v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0 + end + full_name = distribution_point&.value&.find do |v| + v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0 + end + full_name&.value&.find do |v| + v.tag_class == :CONTEXT_SPECIFIC && v.tag == 6 # uniformResourceIdentifier + end + end + + crl_uris&.map(&:value) + end + end + + module AuthorityInfoAccess + include Helpers + + # Get the information and services for the issuer from the certificate's + # authority information access extension exteension, as described in RFC5280 + # Section 4.2.2.1. + # + # Returns an array of strings or nil or raises ASN1::ASN1Error. + def ca_issuer_uris + aia_asn1 = parse_aia_asn1 + return nil if aia_asn1.nil? + + ca_issuer = aia_asn1.value.select do |authority_info_access| + authority_info_access.value.first.value == "caIssuers" + end + + ca_issuer&.map(&:value)&.map(&:last)&.map(&:value) + end + + # Get the URIs for OCSP from the certificate's authority information access + # extension exteension, as described in RFC5280 Section 4.2.2.1. + # + # Returns an array of strings or nil or raises ASN1::ASN1Error. + def ocsp_uris + aia_asn1 = parse_aia_asn1 + return nil if aia_asn1.nil? + + ocsp = aia_asn1.value.select do |authority_info_access| + authority_info_access.value.first.value == "OCSP" + end + + ocsp&.map(&:value)&.map(&:last)&.map(&:value) + end + + private + + def parse_aia_asn1 + ext = find_extension("authorityInfoAccess") + return nil if ext.nil? + + aia_asn1 = ASN1.decode(ext.value_der) + if ext.critical? || aia_asn1.tag_class != :UNIVERSAL || aia_asn1.tag != ASN1::SEQUENCE + raise ASN1::ASN1Error, "invalid extension" + end + + aia_asn1 + end + end end class Name + include Marshal + module RFC2253DN Special = ',=+<>#;' HexChar = /[0-9a-fA-F]/ @@ -166,6 +321,8 @@ module OpenSSL end class Attribute + include Marshal + def ==(other) return false unless Attribute === other to_der == other.to_der @@ -179,6 +336,12 @@ module OpenSSL end class Certificate + include Marshal + include Extension::SubjectKeyIdentifier + include Extension::AuthorityKeyIdentifier + include Extension::CRLDistributionPoints + include Extension::AuthorityInfoAccess + def pretty_print(q) q.object_group(self) { q.breakable @@ -192,6 +355,9 @@ module OpenSSL end class CRL + include Marshal + include Extension::AuthorityKeyIdentifier + def ==(other) return false unless CRL === other to_der == other.to_der @@ -206,6 +372,8 @@ module OpenSSL end class Request + include Marshal + def ==(other) return false unless Request === other to_der == other.to_der diff --git a/ext/openssl/openssl.gemspec b/ext/openssl/openssl.gemspec index 295379fb6c..471a3c4265 100644 --- a/ext/openssl/openssl.gemspec +++ b/ext/openssl/openssl.gemspec @@ -1,29 +1,26 @@ -# -*- encoding: utf-8 -*- +Gem::Specification.new do |spec| + spec.name = "openssl" + spec.version = "2.2.0" + spec.authors = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] + spec.email = ["ruby-core@ruby-lang.org"] + spec.summary = %q{OpenSSL provides SSL, TLS and general purpose cryptography.} + spec.description = %q{It wraps the OpenSSL library.} + spec.homepage = "https://github.com/ruby/openssl" + spec.license = "Ruby" -Gem::Specification.new do |s| - s.name = "openssl" - s.version = "2.1.2" + spec.files = Dir["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md", "BSDL", "LICENSE.txt"] + spec.require_paths = ["lib"] + spec.extensions = ["ext/openssl/extconf.rb"] - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.metadata = { "msys2_mingw_dependencies" => "openssl" } if s.respond_to? :metadata= - s.require_paths = ["lib"] - s.authors = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] - s.date = "2018-10-17" - s.description = "It wraps the OpenSSL library." - s.email = ["ruby-core@ruby-lang.org"] - s.extensions = ["ext/openssl/extconf.rb"] - s.extra_rdoc_files = ["README.md", "CONTRIBUTING.md", "History.md"] - s.files = ["BSDL", "CONTRIBUTING.md", "History.md", "LICENSE.txt", "README.md", "ext/openssl/deprecation.rb", "ext/openssl/extconf.rb", "ext/openssl/openssl_missing.c", "ext/openssl/openssl_missing.h", "ext/openssl/ossl.c", "ext/openssl/ossl.h", "ext/openssl/ossl_asn1.c", "ext/openssl/ossl_asn1.h", "ext/openssl/ossl_bio.c", "ext/openssl/ossl_bio.h", "ext/openssl/ossl_bn.c", "ext/openssl/ossl_bn.h", "ext/openssl/ossl_cipher.c", "ext/openssl/ossl_cipher.h", "ext/openssl/ossl_config.c", "ext/openssl/ossl_config.h", "ext/openssl/ossl_digest.c", "ext/openssl/ossl_digest.h", "ext/openssl/ossl_engine.c", "ext/openssl/ossl_engine.h", "ext/openssl/ossl_hmac.c", "ext/openssl/ossl_hmac.h", "ext/openssl/ossl_kdf.c", "ext/openssl/ossl_kdf.h", "ext/openssl/ossl_ns_spki.c", "ext/openssl/ossl_ns_spki.h", "ext/openssl/ossl_ocsp.c", "ext/openssl/ossl_ocsp.h", "ext/openssl/ossl_pkcs12.c", "ext/openssl/ossl_pkcs12.h", "ext/openssl/ossl_pkcs7.c", "ext/openssl/ossl_pkcs7.h", "ext/openssl/ossl_pkey.c", "ext/openssl/ossl_pkey.h", "ext/openssl/ossl_pkey_dh.c", "ext/openssl/ossl_pkey_dsa.c", "ext/openssl/ossl_pkey_ec.c", "ext/openssl/ossl_pkey_rsa.c", "ext/openssl/ossl_rand.c", "ext/openssl/ossl_rand.h", "ext/openssl/ossl_ssl.c", "ext/openssl/ossl_ssl.h", "ext/openssl/ossl_ssl_session.c", "ext/openssl/ossl_version.h", "ext/openssl/ossl_x509.c", "ext/openssl/ossl_x509.h", "ext/openssl/ossl_x509attr.c", "ext/openssl/ossl_x509cert.c", "ext/openssl/ossl_x509crl.c", "ext/openssl/ossl_x509ext.c", "ext/openssl/ossl_x509name.c", "ext/openssl/ossl_x509req.c", "ext/openssl/ossl_x509revoked.c", "ext/openssl/ossl_x509store.c", "ext/openssl/ruby_missing.h", "lib/openssl.rb", "lib/openssl/bn.rb", "lib/openssl/buffering.rb", "lib/openssl/cipher.rb", "lib/openssl/config.rb", "lib/openssl/digest.rb", "lib/openssl/pkcs5.rb", "lib/openssl/pkey.rb", "lib/openssl/ssl.rb", "lib/openssl/x509.rb"] - s.homepage = "https://github.com/ruby/openssl" - s.licenses = ["Ruby"] - s.rdoc_options = ["--main", "README.md"] - s.required_ruby_version = Gem::Requirement.new(">= 2.3.0") - s.rubygems_version = "3.0.0.beta1" - s.summary = "OpenSSL provides SSL, TLS and general purpose cryptography." + spec.extra_rdoc_files = Dir["*.md"] + spec.rdoc_options = ["--main", "README.md"] - s.add_runtime_dependency("ipaddr", [">= 0"]) - s.add_development_dependency("rake", [">= 0"]) - s.add_development_dependency("rake-compiler", [">= 0"]) - s.add_development_dependency("test-unit", ["~> 3.0"]) - s.add_development_dependency("rdoc", [">= 0"]) + spec.required_ruby_version = ">= 2.3.0" + + spec.add_development_dependency "rake" + spec.add_development_dependency "rake-compiler" + spec.add_development_dependency "test-unit", "~> 3.0" + spec.add_development_dependency "rdoc" + + spec.metadata["msys2_mingw_dependencies"] = "openssl" end diff --git a/ext/openssl/openssl_missing.h b/ext/openssl/openssl_missing.h index 09998214e1..7d218f86f5 100644 --- a/ext/openssl/openssl_missing.h +++ b/ext/openssl/openssl_missing.h @@ -72,6 +72,9 @@ void ossl_HMAC_CTX_free(HMAC_CTX *); #if !defined(HAVE_X509_STORE_SET_EX_DATA) # define X509_STORE_set_ex_data(x, idx, data) \ CRYPTO_set_ex_data(&(x)->ex_data, (idx), (data)) +#endif + +#if !defined(HAVE_X509_STORE_GET_EX_NEW_INDEX) && !defined(X509_STORE_get_ex_new_index) # define X509_STORE_get_ex_new_index(l, p, newf, dupf, freef) \ CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_X509_STORE, (l), (p), \ (newf), (dupf), (freef)) @@ -144,7 +147,8 @@ void ossl_X509_REQ_get0_signature(const X509_REQ *, const ASN1_BIT_STRING **, co CRYPTO_add(&(x)->references, 1, CRYPTO_LOCK_EVP_PKEY); #endif -#if !defined(HAVE_OPAQUE_OPENSSL) +#if !defined(HAVE_OPAQUE_OPENSSL) && \ + (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER < 0x2070000fL) #define IMPL_PKEY_GETTER(_type, _name) \ static inline _type *EVP_PKEY_get0_##_type(EVP_PKEY *pkey) { \ return pkey->pkey._name; } @@ -219,4 +223,35 @@ IMPL_PKEY_GETTER(EC_KEY, ec) # define SSL_SESSION_get_protocol_version(s) ((s)->ssl_version) #endif +#if !defined(HAVE_TS_STATUS_INFO_GET0_STATUS) +# define TS_STATUS_INFO_get0_status(a) ((a)->status) +#endif + +#if !defined(HAVE_TS_STATUS_INFO_GET0_TEXT) +# define TS_STATUS_INFO_get0_text(a) ((a)->text) +#endif + +#if !defined(HAVE_TS_STATUS_INFO_GET0_FAILURE_INFO) +# define TS_STATUS_INFO_get0_failure_info(a) ((a)->failure_info) +#endif + +#if !defined(HAVE_TS_VERIFY_CTS_SET_CERTS) +# define TS_VERIFY_CTS_set_certs(ctx, crts) ((ctx)->certs=(crts)) +#endif + +#if !defined(HAVE_TS_VERIFY_CTX_SET_STORE) +# define TS_VERIFY_CTX_set_store(ctx, str) ((ctx)->store=(str)) +#endif + +#if !defined(HAVE_TS_VERIFY_CTX_ADD_FLAGS) +# define TS_VERIFY_CTX_add_flags(ctx, f) ((ctx)->flags |= (f)) +#endif + +#if !defined(HAVE_TS_RESP_CTX_SET_TIME_CB) +# define TS_RESP_CTX_set_time_cb(ctx, callback, dta) do { \ + (ctx)->time_cb = (callback); \ + (ctx)->time_cb_data = (dta); \ + } while (0) +#endif + #endif /* _OSSL_OPENSSL_MISSING_H_ */ diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index e4196f0754..14a7919a93 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -604,6 +604,35 @@ static void Init_ossl_locks(void) } #endif /* !HAVE_OPENSSL_110_THREADING_API */ +/* + * call-seq: + * OpenSSL.fixed_length_secure_compare(string, string) -> boolean + * + * Constant time memory comparison for fixed length strings, such as results + * of HMAC calculations. + * + * Returns +true+ if the strings are identical, +false+ if they are of the same + * length but not identical. If the length is different, +ArgumentError+ is + * raised. + */ +static VALUE +ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) +{ + const unsigned char *p1 = (const unsigned char *)StringValuePtr(str1); + const unsigned char *p2 = (const unsigned char *)StringValuePtr(str2); + long len1 = RSTRING_LEN(str1); + long len2 = RSTRING_LEN(str2); + + if (len1 != len2) { + ossl_raise(rb_eArgError, "inputs must be of equal length"); + } + + switch (CRYPTO_memcmp(p1, p2, len1)) { + case 0: return Qtrue; + default: return Qfalse; + } +} + /* * OpenSSL provides SSL, TLS and general purpose cryptography. It wraps the * OpenSSL[https://www.openssl.org/] library. @@ -635,7 +664,7 @@ static void Init_ossl_locks(void) * ahold of the key may use it unless it is encrypted. In order to securely * export a key you may export it with a pass phrase. * - * cipher = OpenSSL::Cipher.new 'AES-128-CBC' + * cipher = OpenSSL::Cipher.new 'AES-256-CBC' * pass_phrase = 'my secure pass phrase goes here' * * key_secure = key.export cipher, pass_phrase @@ -745,7 +774,7 @@ static void Init_ossl_locks(void) * using PBKDF2. PKCS #5 v2.0 recommends at least 8 bytes for the salt, * the number of iterations largely depends on the hardware being used. * - * cipher = OpenSSL::Cipher.new 'AES-128-CBC' + * cipher = OpenSSL::Cipher.new 'AES-256-CBC' * cipher.encrypt * iv = cipher.random_iv * @@ -768,7 +797,7 @@ static void Init_ossl_locks(void) * Use the same steps as before to derive the symmetric AES key, this time * setting the Cipher up for decryption. * - * cipher = OpenSSL::Cipher.new 'AES-128-CBC' + * cipher = OpenSSL::Cipher.new 'AES-256-CBC' * cipher.decrypt * cipher.iv = iv # the one generated with #random_iv * @@ -803,7 +832,7 @@ static void Init_ossl_locks(void) * * First set up the cipher for encryption * - * encryptor = OpenSSL::Cipher.new 'AES-128-CBC' + * encryptor = OpenSSL::Cipher.new 'AES-256-CBC' * encryptor.encrypt * encryptor.pkcs5_keyivgen pass_phrase, salt * @@ -816,7 +845,7 @@ static void Init_ossl_locks(void) * * Use a new Cipher instance set up for decryption * - * decryptor = OpenSSL::Cipher.new 'AES-128-CBC' + * decryptor = OpenSSL::Cipher.new 'AES-256-CBC' * decryptor.decrypt * decryptor.pkcs5_keyivgen pass_phrase, salt * @@ -833,7 +862,7 @@ static void Init_ossl_locks(void) * signature. * * key = OpenSSL::PKey::RSA.new 2048 - * name = OpenSSL::X509::Name.parse 'CN=nobody/DC=example' + * name = OpenSSL::X509::Name.parse '/CN=nobody/DC=example' * * cert = OpenSSL::X509::Certificate.new * cert.version = 2 @@ -904,7 +933,7 @@ static void Init_ossl_locks(void) * ca_key = OpenSSL::PKey::RSA.new 2048 * pass_phrase = 'my secure pass phrase goes here' * - * cipher = OpenSSL::Cipher.new 'AES-128-CBC' + * cipher = OpenSSL::Cipher.new 'AES-256-CBC' * * open 'ca_key.pem', 'w', 0400 do |io| * io.write ca_key.export(cipher, pass_phrase) @@ -915,7 +944,7 @@ static void Init_ossl_locks(void) * A CA certificate is created the same way we created a certificate above, but * with different extensions. * - * ca_name = OpenSSL::X509::Name.parse 'CN=ca/DC=example' + * ca_name = OpenSSL::X509::Name.parse '/CN=ca/DC=example' * * ca_cert = OpenSSL::X509::Certificate.new * ca_cert.serial = 0 @@ -1125,11 +1154,7 @@ Init_openssl(void) */ mOSSL = rb_define_module("OpenSSL"); rb_global_variable(&mOSSL); - - /* - * OpenSSL ruby extension version - */ - rb_define_const(mOSSL, "VERSION", rb_str_new2(OSSL_VERSION)); + rb_define_singleton_method(mOSSL, "fixed_length_secure_compare", ossl_crypto_fixed_length_secure_compare, 2); /* * Version of OpenSSL the ruby OpenSSL extension was built with @@ -1205,6 +1230,9 @@ Init_openssl(void) Init_ossl_pkey(); Init_ossl_rand(); Init_ossl_ssl(); +#ifndef OPENSSL_NO_TS + Init_ossl_ts(); +#endif Init_ossl_x509(); Init_ossl_ocsp(); Init_ossl_engine(); diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 6af7ddd7d0..8074afcd79 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -27,6 +27,9 @@ #include #include #include +#ifndef OPENSSL_NO_TS + #include +#endif #include #if !defined(OPENSSL_NO_ENGINE) # include @@ -167,7 +170,9 @@ void ossl_debug(const char *, ...); #include "ossl_pkey.h" #include "ossl_rand.h" #include "ossl_ssl.h" -#include "ossl_version.h" +#ifndef OPENSSL_NO_TS + #include "ossl_ts.h" +#endif #include "ossl_x509.h" #include "ossl_engine.h" #include "ossl_kdf.h" diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 0085d4beab..9eb1826f02 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -1285,6 +1285,30 @@ ossl_asn1obj_get_ln(VALUE self) return ret; } +/* + * call-seq: + * oid == other_oid => true or false + * + * Returns +true+ if _other_oid_ is the same as _oid_ + */ +static VALUE +ossl_asn1obj_eq(VALUE self, VALUE other) +{ + VALUE valSelf, valOther; + int nidSelf, nidOther; + + valSelf = ossl_asn1_get_value(self); + valOther = ossl_asn1_get_value(other); + + if ((nidSelf = OBJ_txt2nid(StringValueCStr(valSelf))) == NID_undef) + ossl_raise(eASN1Error, "OBJ_txt2nid"); + + if ((nidOther = OBJ_txt2nid(StringValueCStr(valOther))) == NID_undef) + ossl_raise(eASN1Error, "OBJ_txt2nid"); + + return nidSelf == nidOther ? Qtrue : Qfalse; +} + static VALUE asn1obj_get_oid_i(VALUE vobj) { @@ -1818,6 +1842,7 @@ do{\ rb_define_method(cASN1ObjectId, "oid", ossl_asn1obj_get_oid, 0); rb_define_alias(cASN1ObjectId, "short_name", "sn"); rb_define_alias(cASN1ObjectId, "long_name", "ln"); + rb_define_method(cASN1ObjectId, "==", ossl_asn1obj_eq, 1); rb_attr(cASN1BitString, rb_intern("unused_bits"), 1, 1, 0); rb_define_method(cASN1EndOfContent, "initialize", ossl_asn1eoc_initialize, 0); diff --git a/ext/openssl/ossl_bn.c b/ext/openssl/ossl_bn.c index 6f0064e966..6493e051e9 100644 --- a/ext/openssl/ossl_bn.c +++ b/ext/openssl/ossl_bn.c @@ -173,7 +173,6 @@ ossl_bn_alloc(VALUE klass) /* * call-seq: - * OpenSSL::BN.new => aBN * OpenSSL::BN.new(bn) => aBN * OpenSSL::BN.new(integer) => aBN * OpenSSL::BN.new(string) => aBN @@ -193,6 +192,10 @@ ossl_bn_initialize(int argc, VALUE *argv, VALUE self) base = NUM2INT(bs); } + if (NIL_P(str)) { + ossl_raise(rb_eArgError, "invalid argument"); + } + if (RB_INTEGER_TYPE_P(str)) { GetBN(self, bn); integer_to_bnptr(str, bn); diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index 0840c84a71..66bf0beb11 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -104,7 +104,7 @@ ossl_cipher_alloc(VALUE klass) * call-seq: * Cipher.new(string) -> cipher * - * The string must be a valid cipher name like "AES-128-CBC" or "3DES". + * The string must contain a valid cipher name like "AES-256-CBC". * * A list of cipher names is available by calling OpenSSL::Cipher.ciphers. */ @@ -237,8 +237,7 @@ ossl_cipher_init(int argc, VALUE *argv, VALUE self, int mode) ossl_raise(eCipherError, NULL); } - if (p_key) - rb_ivar_set(self, id_key_set, Qtrue); + rb_ivar_set(self, id_key_set, p_key ? Qtrue : Qfalse); return self; } @@ -896,7 +895,7 @@ Init_ossl_cipher(void) * without processing the password further. A simple and secure way to * create a key for a particular Cipher is * - * cipher = OpenSSL::AES256.new(:CFB) + * cipher = OpenSSL::Cipher::AES256.new(:CFB) * cipher.encrypt * key = cipher.random_key # also sets the generated key on the Cipher * diff --git a/ext/openssl/ossl_digest.c b/ext/openssl/ossl_digest.c index 112ce33647..661b230fb7 100644 --- a/ext/openssl/ossl_digest.c +++ b/ext/openssl/ossl_digest.c @@ -352,8 +352,6 @@ Init_ossl_digest(void) * * SHA, SHA1, SHA224, SHA256, SHA384 and SHA512 * * MD2, MD4, MDC2 and MD5 * * RIPEMD160 - * * DSS, DSS1 (Pseudo algorithms to be used for DSA signatures. DSS is - * equal to SHA and DSS1 is equal to SHA1) * * For each of these algorithms, there is a sub-class of Digest that * can be instantiated as simply as e.g. diff --git a/ext/openssl/ossl_engine.c b/ext/openssl/ossl_engine.c index 5ca0d4ca3f..f90bf061df 100644 --- a/ext/openssl/ossl_engine.c +++ b/ext/openssl/ossl_engine.c @@ -150,6 +150,9 @@ ossl_engine_s_load(int argc, VALUE *argv, VALUE klass) #if HAVE_ENGINE_LOAD_AESNI OSSL_ENGINE_LOAD_IF_MATCH(aesni, AESNI); #endif +#if HAVE_ENGINE_LOAD_CLOUDHSM + OSSL_ENGINE_LOAD_IF_MATCH(cloudhsm, CLOUDHSM); +#endif #endif #ifdef HAVE_ENGINE_LOAD_OPENBSD_DEV_CRYPTO OSSL_ENGINE_LOAD_IF_MATCH(openbsd_dev_crypto, OPENBSD_DEV_CRYPTO); diff --git a/ext/openssl/ossl_hmac.c b/ext/openssl/ossl_hmac.c index 564dcab522..2ac2e5c6ce 100644 --- a/ext/openssl/ossl_hmac.c +++ b/ext/openssl/ossl_hmac.c @@ -84,18 +84,12 @@ ossl_hmac_alloc(VALUE klass) * * === A note about comparisons * - * Two instances won't be equal when they're compared, even if they have the - * same value. Use #to_s or #hexdigest to return the authentication code that - * the instance represents. For example: + * Two instances can be securely compared with #== in constant time: * * other_instance = OpenSSL::HMAC.new('key', OpenSSL::Digest.new('sha1')) - * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f - * instance - * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f - * instance == other_instance - * #=> false - * instance.to_s == other_instance.to_s - * #=> true + * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f + * instance == other_instance + * #=> true * */ static VALUE diff --git a/ext/openssl/ossl_kdf.c b/ext/openssl/ossl_kdf.c index ee124718b5..3d0e66b5f5 100644 --- a/ext/openssl/ossl_kdf.c +++ b/ext/openssl/ossl_kdf.c @@ -284,24 +284,8 @@ Init_ossl_kdf(void) * Typically, "==" short-circuits on evaluation, and is therefore * vulnerable to timing attacks. The proper way is to use a method that * always takes the same amount of time when comparing two values, thus - * not leaking any information to potential attackers. To compare two - * values, the following could be used: - * - * def eql_time_cmp(a, b) - * unless a.length == b.length - * return false - * end - * cmp = b.bytes - * result = 0 - * a.bytes.each_with_index {|c,i| - * result |= c ^ cmp[i] - * } - * result == 0 - * end - * - * Please note that the premature return in case of differing lengths - * typically does not leak valuable information - when using PBKDF2, the - * length of the values to be compared is of fixed size. + * not leaking any information to potential attackers. To do this, use + * +OpenSSL.fixed_length_secure_compare+. */ mKDF = rb_define_module_under(mOSSL, "KDF"); /* diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c index c0237791da..2ca4f62f70 100644 --- a/ext/openssl/ossl_ocsp.c +++ b/ext/openssl/ossl_ocsp.c @@ -1489,13 +1489,15 @@ ossl_ocspcid_initialize_copy(VALUE self, VALUE other) * call-seq: * OpenSSL::OCSP::CertificateId.new(subject, issuer, digest = nil) -> certificate_id * OpenSSL::OCSP::CertificateId.new(der_string) -> certificate_id + * OpenSSL::OCSP::CertificateId.new(obj) -> certificate_id * * Creates a new OpenSSL::OCSP::CertificateId for the given _subject_ and * _issuer_ X509 certificates. The _digest_ is a digest algorithm that is used * to compute the hash values. This defaults to SHA-1. * * If only one argument is given, decodes it as DER representation of a - * certificate ID. + * certificate ID or generates certificate ID from the object that responds to + * the to_der method. */ static VALUE ossl_ocspcid_initialize(int argc, VALUE *argv, VALUE self) @@ -1734,18 +1736,11 @@ Init_ossl_ocsp(void) * To submit the request to the CA for verification we need to extract the * OCSP URI from the subject certificate: * - * authority_info_access = subject.extensions.find do |extension| - * extension.oid == 'authorityInfoAccess' - * end - * - * descriptions = authority_info_access.value.split "\n" - * ocsp = descriptions.find do |description| - * description.start_with? 'OCSP' - * end + * ocsp_uris = subject.ocsp_uris * * require 'uri' * - * ocsp_uri = URI ocsp[/URI:(.*)/, 1] + * ocsp_uri = URI ocsp_uris[0] * * To submit the request we'll POST the request to the OCSP URI (per RFC * 2560). Note that we only handle HTTP requests and don't handle any diff --git a/ext/openssl/ossl_ocsp.h b/ext/openssl/ossl_ocsp.h index 21e2c99a2e..6d2aac8657 100644 --- a/ext/openssl/ossl_ocsp.h +++ b/ext/openssl/ossl_ocsp.h @@ -13,9 +13,9 @@ #if !defined(OPENSSL_NO_OCSP) extern VALUE mOCSP; -extern VALUE cOPCSReq; -extern VALUE cOPCSRes; -extern VALUE cOPCSBasicRes; +extern VALUE cOCSPReq; +extern VALUE cOCSPRes; +extern VALUE cOCSPBasicRes; #endif void Init_ossl_ocsp(void); diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index 28010c81fb..ea8e92d1bc 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -9,21 +9,6 @@ */ #include "ossl.h" -#define NewPKCS7(klass) \ - TypedData_Wrap_Struct((klass), &ossl_pkcs7_type, 0) -#define SetPKCS7(obj, pkcs7) do { \ - if (!(pkcs7)) { \ - ossl_raise(rb_eRuntimeError, "PKCS7 wasn't initialized."); \ - } \ - RTYPEDDATA_DATA(obj) = (pkcs7); \ -} while (0) -#define GetPKCS7(obj, pkcs7) do { \ - TypedData_Get_Struct((obj), PKCS7, &ossl_pkcs7_type, (pkcs7)); \ - if (!(pkcs7)) { \ - ossl_raise(rb_eRuntimeError, "PKCS7 wasn't initialized."); \ - } \ -} while (0) - #define NewPKCS7si(klass) \ TypedData_Wrap_Struct((klass), &ossl_pkcs7_signer_info_type, 0) #define SetPKCS7si(obj, p7si) do { \ @@ -75,7 +60,7 @@ ossl_pkcs7_free(void *ptr) PKCS7_free(ptr); } -static const rb_data_type_t ossl_pkcs7_type = { +const rb_data_type_t ossl_pkcs7_type = { "OpenSSL/PKCS7", { 0, ossl_pkcs7_free, @@ -1088,7 +1073,6 @@ Init_ossl_pkcs7(void) rb_define_alloc_func(cPKCS7Signer, ossl_pkcs7si_alloc); rb_define_method(cPKCS7Signer, "initialize", ossl_pkcs7si_initialize,3); rb_define_method(cPKCS7Signer, "issuer", ossl_pkcs7si_get_issuer, 0); - rb_define_alias(cPKCS7Signer, "name", "issuer"); rb_define_method(cPKCS7Signer, "serial", ossl_pkcs7si_get_serial,0); rb_define_method(cPKCS7Signer,"signed_time",ossl_pkcs7si_get_signed_time,0); diff --git a/ext/openssl/ossl_pkcs7.h b/ext/openssl/ossl_pkcs7.h index 139e00d640..3e1b094670 100644 --- a/ext/openssl/ossl_pkcs7.h +++ b/ext/openssl/ossl_pkcs7.h @@ -10,6 +10,22 @@ #if !defined(_OSSL_PKCS7_H_) #define _OSSL_PKCS7_H_ +#define NewPKCS7(klass) \ + TypedData_Wrap_Struct((klass), &ossl_pkcs7_type, 0) +#define SetPKCS7(obj, pkcs7) do { \ + if (!(pkcs7)) { \ + ossl_raise(rb_eRuntimeError, "PKCS7 wasn't initialized."); \ + } \ + RTYPEDDATA_DATA(obj) = (pkcs7); \ +} while (0) +#define GetPKCS7(obj, pkcs7) do { \ + TypedData_Get_Struct((obj), PKCS7, &ossl_pkcs7_type, (pkcs7)); \ + if (!(pkcs7)) { \ + ossl_raise(rb_eRuntimeError, "PKCS7 wasn't initialized."); \ + } \ +} while (0) + +extern const rb_data_type_t ossl_pkcs7_type; extern VALUE cPKCS7; extern VALUE cPKCS7Signer; extern VALUE cPKCS7Recipient; diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index e1fffb2446..fc4cac3bc4 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -167,21 +167,27 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self) pass = ossl_pem_passwd_value(pass); bio = ossl_obj2bio(&data); - if (!(pkey = d2i_PrivateKey_bio(bio, NULL))) { - OSSL_BIO_reset(bio); - if (!(pkey = PEM_read_bio_PrivateKey(bio, NULL, ossl_pem_passwd_cb, (void *)pass))) { - OSSL_BIO_reset(bio); - if (!(pkey = d2i_PUBKEY_bio(bio, NULL))) { - OSSL_BIO_reset(bio); - pkey = PEM_read_bio_PUBKEY(bio, NULL, ossl_pem_passwd_cb, (void *)pass); - } - } - } + if ((pkey = d2i_PrivateKey_bio(bio, NULL))) + goto ok; + OSSL_BIO_reset(bio); + if ((pkey = d2i_PKCS8PrivateKey_bio(bio, NULL, ossl_pem_passwd_cb, (void *)pass))) + goto ok; + OSSL_BIO_reset(bio); + if ((pkey = d2i_PUBKEY_bio(bio, NULL))) + goto ok; + OSSL_BIO_reset(bio); + /* PEM_read_bio_PrivateKey() also parses PKCS #8 formats */ + if ((pkey = PEM_read_bio_PrivateKey(bio, NULL, ossl_pem_passwd_cb, (void *)pass))) + goto ok; + OSSL_BIO_reset(bio); + if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL))) + goto ok; BIO_free(bio); - if (!pkey) - ossl_raise(ePKeyError, "Could not parse PKey"); + ossl_raise(ePKeyError, "Could not parse PKey"); +ok: + BIO_free(bio); return ossl_pkey_new(pkey); } @@ -293,6 +299,124 @@ ossl_pkey_initialize(VALUE self) return self; } +static VALUE +do_pkcs8_export(int argc, VALUE *argv, VALUE self, int to_der) +{ + EVP_PKEY *pkey; + VALUE cipher, pass; + const EVP_CIPHER *enc = NULL; + BIO *bio; + + GetPKey(self, pkey); + rb_scan_args(argc, argv, "02", &cipher, &pass); + if (argc > 0) { + /* + * TODO: EncryptedPrivateKeyInfo actually has more options. + * Should they be exposed? + */ + enc = ossl_evp_get_cipherbyname(cipher); + pass = ossl_pem_passwd_value(pass); + } + + bio = BIO_new(BIO_s_mem()); + if (!bio) + ossl_raise(ePKeyError, "BIO_new"); + if (to_der) { + if (!i2d_PKCS8PrivateKey_bio(bio, pkey, enc, NULL, 0, + ossl_pem_passwd_cb, (void *)pass)) { + BIO_free(bio); + ossl_raise(ePKeyError, "i2d_PKCS8PrivateKey_bio"); + } + } + else { + if (!PEM_write_bio_PKCS8PrivateKey(bio, pkey, enc, NULL, 0, + ossl_pem_passwd_cb, (void *)pass)) { + BIO_free(bio); + ossl_raise(ePKeyError, "PEM_write_bio_PKCS8PrivateKey"); + } + } + return ossl_membio2str(bio); +} + +/* + * call-seq: + * pkey.private_to_der -> string + * pkey.private_to_der(cipher, password) -> string + * + * Serializes the private key to DER-encoded PKCS #8 format. If called without + * arguments, unencrypted PKCS #8 PrivateKeyInfo format is used. If called with + * a cipher name and a password, PKCS #8 EncryptedPrivateKeyInfo format with + * PBES2 encryption scheme is used. + */ +static VALUE +ossl_pkey_private_to_der(int argc, VALUE *argv, VALUE self) +{ + return do_pkcs8_export(argc, argv, self, 1); +} + +/* + * call-seq: + * pkey.private_to_pem -> string + * pkey.private_to_pem(cipher, password) -> string + * + * Serializes the private key to PEM-encoded PKCS #8 format. See #private_to_der + * for more details. + */ +static VALUE +ossl_pkey_private_to_pem(int argc, VALUE *argv, VALUE self) +{ + return do_pkcs8_export(argc, argv, self, 0); +} + +static VALUE +do_spki_export(VALUE self, int to_der) +{ + EVP_PKEY *pkey; + BIO *bio; + + GetPKey(self, pkey); + bio = BIO_new(BIO_s_mem()); + if (!bio) + ossl_raise(ePKeyError, "BIO_new"); + if (to_der) { + if (!i2d_PUBKEY_bio(bio, pkey)) { + BIO_free(bio); + ossl_raise(ePKeyError, "i2d_PUBKEY_bio"); + } + } + else { + if (!PEM_write_bio_PUBKEY(bio, pkey)) { + BIO_free(bio); + ossl_raise(ePKeyError, "PEM_write_bio_PUBKEY"); + } + } + return ossl_membio2str(bio); +} + +/* + * call-seq: + * pkey.public_to_der -> string + * + * Serializes the public key to DER-encoded X.509 SubjectPublicKeyInfo format. + */ +static VALUE +ossl_pkey_public_to_der(VALUE self) +{ + return do_spki_export(self, 1); +} + +/* + * call-seq: + * pkey.public_to_pem -> string + * + * Serializes the public key to PEM-encoded X.509 SubjectPublicKeyInfo format. + */ +static VALUE +ossl_pkey_public_to_pem(VALUE self) +{ + return do_spki_export(self, 0); +} + /* * call-seq: * pkey.sign(digest, data) -> String @@ -491,6 +615,10 @@ Init_ossl_pkey(void) rb_define_alloc_func(cPKey, ossl_pkey_alloc); rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0); + rb_define_method(cPKey, "private_to_der", ossl_pkey_private_to_der, -1); + rb_define_method(cPKey, "private_to_pem", ossl_pkey_private_to_pem, -1); + rb_define_method(cPKey, "public_to_der", ossl_pkey_public_to_der, 0); + rb_define_method(cPKey, "public_to_pem", ossl_pkey_public_to_pem, 0); rb_define_method(cPKey, "sign", ossl_pkey_sign, 2); rb_define_method(cPKey, "verify", ossl_pkey_verify, 3); diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c index 8bb611248b..fc2bc6c815 100644 --- a/ext/openssl/ossl_pkey_ec.c +++ b/ext/openssl/ossl_pkey_ec.c @@ -1562,6 +1562,34 @@ ossl_ec_point_to_octet_string(VALUE self, VALUE conversion_form) return str; } +/* + * call-seq: + * point.add(point) => point + * + * Performs elliptic curve point addition. + */ +static VALUE ossl_ec_point_add(VALUE self, VALUE other) +{ + EC_POINT *point_self, *point_other, *point_result; + const EC_GROUP *group; + VALUE group_v = rb_attr_get(self, id_i_group); + VALUE result; + + GetECPoint(self, point_self); + GetECPoint(other, point_other); + GetECGroup(group_v, group); + + result = rb_obj_alloc(cEC_POINT); + ossl_ec_point_initialize(1, &group_v, result); + GetECPoint(result, point_result); + + if (EC_POINT_add(group, point_result, point_self, point_other, ossl_bn_ctx) != 1) { + ossl_raise(eEC_POINT, "EC_POINT_add"); + } + + return result; +} + /* * call-seq: * point.mul(bn1 [, bn2]) => point @@ -1786,6 +1814,7 @@ void Init_ossl_ec(void) /* all the other methods */ rb_define_method(cEC_POINT, "to_octet_string", ossl_ec_point_to_octet_string, 1); + rb_define_method(cEC_POINT, "add", ossl_ec_point_add, 1); rb_define_method(cEC_POINT, "mul", ossl_ec_point_mul, -1); id_i_group = rb_intern("@group"); diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index 4800fb2710..761866c66a 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -26,10 +26,10 @@ static inline int RSA_HAS_PRIVATE(RSA *rsa) { - const BIGNUM *p, *q; + const BIGNUM *e, *d; - RSA_get0_factors(rsa, &p, &q); - return p && q; /* d? why? */ + RSA_get0_key(rsa, NULL, &e, &d); + return e && d; } static inline int @@ -341,6 +341,7 @@ static VALUE ossl_rsa_export(int argc, VALUE *argv, VALUE self) { RSA *rsa; + const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp; BIO *out; const EVP_CIPHER *ciph = NULL; VALUE cipher, pass, str; @@ -356,7 +357,10 @@ ossl_rsa_export(int argc, VALUE *argv, VALUE self) if (!(out = BIO_new(BIO_s_mem()))) { ossl_raise(eRSAError, NULL); } - if (RSA_HAS_PRIVATE(rsa)) { + RSA_get0_key(rsa, &n, &e, &d); + RSA_get0_factors(rsa, &p, &q); + RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp); + if (n && e && d && p && q && dmp1 && dmq1 && iqmp) { if (!PEM_write_bio_RSAPrivateKey(out, rsa, ciph, NULL, 0, ossl_pem_passwd_cb, (void *)pass)) { BIO_free(out); @@ -383,23 +387,27 @@ static VALUE ossl_rsa_to_der(VALUE self) { RSA *rsa; + const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp; int (*i2d_func)(const RSA *, unsigned char **); - unsigned char *p; + unsigned char *ptr; long len; VALUE str; GetRSA(self, rsa); - if (RSA_HAS_PRIVATE(rsa)) + RSA_get0_key(rsa, &n, &e, &d); + RSA_get0_factors(rsa, &p, &q); + RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp); + if (n && e && d && p && q && dmp1 && dmq1 && iqmp) i2d_func = i2d_RSAPrivateKey; else i2d_func = (int (*)(const RSA *, unsigned char **))i2d_RSA_PUBKEY; if((len = i2d_func(rsa, NULL)) <= 0) ossl_raise(eRSAError, NULL); str = rb_str_new(0, len); - p = (unsigned char *)RSTRING_PTR(str); - if(i2d_func(rsa, &p) < 0) + ptr = (unsigned char *)RSTRING_PTR(str); + if(i2d_func(rsa, &ptr) < 0) ossl_raise(eRSAError, NULL); - ossl_str_adjust(str, p); + ossl_str_adjust(str, ptr); return str; } diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 80a42383f7..dfbfbb22ee 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -810,6 +810,10 @@ ossl_sslctx_setup(VALUE self) } #endif /* OPENSSL_NO_EC */ +#ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH + SSL_CTX_set_post_handshake_auth(ctx, 1); +#endif + val = rb_attr_get(self, id_i_cert_store); if (!NIL_P(val)) { X509_STORE *store = GetX509StorePtr(val); /* NO NEED TO DUP */ @@ -1318,6 +1322,17 @@ ossl_sslctx_add_certificate(int argc, VALUE *argv, VALUE self) return self; } +static VALUE +ossl_sslctx_add_certificate_chain_file(VALUE self, VALUE path) +{ + StringValue(path); + SSL_CTX *ctx = NULL; + + GetSSLCTX(self, ctx); + + return SSL_CTX_use_certificate_chain_file(ctx, RSTRING_PTR(path)) == 1 ? Qtrue : Qfalse; +} + /* * call-seq: * ctx.session_add(session) -> true | false @@ -1877,18 +1892,24 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) } } else { - ID meth = nonblock ? rb_intern("read_nonblock") : rb_intern("sysread"); + ID meth = nonblock ? rb_intern("read_nonblock") : rb_intern("sysread"); - rb_warning("SSL session is not started yet."); - if (nonblock) { + rb_warning("SSL session is not started yet."); +#if defined(RB_PASS_KEYWORDS) + if (nonblock) { VALUE argv[3]; argv[0] = len; argv[1] = str; argv[2] = opts; return rb_funcallv_kw(io, meth, 3, argv, RB_PASS_KEYWORDS); } - else - return rb_funcall(io, meth, 2, len, str); +#else + if (nonblock) { + return rb_funcall(io, meth, 3, len, str, opts); + } +#endif + else + return rb_funcall(io, meth, 2, len, str); } end: @@ -1911,7 +1932,6 @@ ossl_ssl_read(int argc, VALUE *argv, VALUE self) } /* - * :nodoc: * call-seq: * ssl.sysread_nonblock(length) => string * ssl.sysread_nonblock(length, buffer) => buffer @@ -1976,15 +1996,21 @@ ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts) ID meth = nonblock ? rb_intern("write_nonblock") : rb_intern("syswrite"); - rb_warning("SSL session is not started yet."); - if (nonblock) { + rb_warning("SSL session is not started yet."); +#if defined(RB_PASS_KEYWORDS) + if (nonblock) { VALUE argv[2]; argv[0] = str; argv[1] = opts; return rb_funcallv_kw(io, meth, 2, argv, RB_PASS_KEYWORDS); } - else - return rb_funcall(io, meth, 1, str); +#else + if (nonblock) { + return rb_funcall(io, meth, 2, str, opts); + } +#endif + else + return rb_funcall(io, meth, 1, str); } end: @@ -2004,7 +2030,6 @@ ossl_ssl_write(VALUE self, VALUE str) } /* - * :nodoc: * call-seq: * ssl.syswrite_nonblock(string) => Integer * @@ -2022,7 +2047,6 @@ ossl_ssl_write_nonblock(int argc, VALUE *argv, VALUE self) } /* - * :nodoc: * call-seq: * ssl.stop => nil * @@ -2292,6 +2316,56 @@ ossl_ssl_get_verify_result(VALUE self) return INT2NUM(SSL_get_verify_result(ssl)); } +/* + * call-seq: + * ssl.finished_message => "finished message" + * + * Returns the last *Finished* message sent + * + */ +static VALUE +ossl_ssl_get_finished(VALUE self) +{ + SSL *ssl; + + GetSSL(self, ssl); + + char sizer[1]; + size_t len = SSL_get_finished(ssl, sizer, 0); + + if(len == 0) + return Qnil; + + char* buf = ALLOCA_N(char, len); + SSL_get_finished(ssl, buf, len); + return rb_str_new(buf, len); +} + +/* + * call-seq: + * ssl.peer_finished_message => "peer finished message" + * + * Returns the last *Finished* message received + * + */ +static VALUE +ossl_ssl_get_peer_finished(VALUE self) +{ + SSL *ssl; + + GetSSL(self, ssl); + + char sizer[1]; + size_t len = SSL_get_peer_finished(ssl, sizer, 0); + + if(len == 0) + return Qnil; + + char* buf = ALLOCA_N(char, len); + SSL_get_peer_finished(ssl, buf, len); + return rb_str_new(buf, len); +} + /* * call-seq: * ssl.client_ca => [x509name, ...] @@ -2614,13 +2688,13 @@ Init_ossl_ssl(void) rb_define_const(mSSLExtConfig, "HAVE_TLSEXT_HOST_NAME", Qtrue); /* - * A callback invoked whenever a new handshake is initiated. May be used - * to disable renegotiation entirely. + * A callback invoked whenever a new handshake is initiated on an + * established connection. May be used to disable renegotiation entirely. * * The callback is invoked with the active SSLSocket. The callback's - * return value is irrelevant, normal return indicates "approval" of the + * return value is ignored. A normal return indicates "approval" of the * renegotiation and will continue the process. To forbid renegotiation - * and to cancel the process, an Error may be raised within the callback. + * and to cancel the process, raise an exception within the callback. * * === Disable client renegotiation * @@ -2628,10 +2702,8 @@ Init_ossl_ssl(void) * renegotiation entirely. You may use a callback as follows to implement * this feature: * - * num_handshakes = 0 * ctx.renegotiation_cb = lambda do |ssl| - * num_handshakes += 1 - * raise RuntimeError.new("Client renegotiation disabled") if num_handshakes > 1 + * raise RuntimeError, "Client renegotiation disabled" * end */ rb_attr(cSSLContext, rb_intern("renegotiation_cb"), 1, 1, Qfalse); @@ -2712,6 +2784,7 @@ Init_ossl_ssl(void) rb_define_method(cSSLContext, "enable_fallback_scsv", ossl_sslctx_enable_fallback_scsv, 0); #endif rb_define_method(cSSLContext, "add_certificate", ossl_sslctx_add_certificate, -1); + rb_define_method(cSSLContext, "add_certificate_chain_file", ossl_sslctx_add_certificate_chain_file, 1); rb_define_method(cSSLContext, "setup", ossl_sslctx_setup, 0); rb_define_alias(cSSLContext, "freeze", "setup"); @@ -2809,6 +2882,8 @@ Init_ossl_ssl(void) rb_define_method(cSSLSocket, "client_ca", ossl_ssl_get_client_ca_list, 0); /* #hostname is defined in lib/openssl/ssl.rb */ rb_define_method(cSSLSocket, "hostname=", ossl_ssl_set_hostname, 1); + rb_define_method(cSSLSocket, "finished_message", ossl_ssl_get_finished, 0); + rb_define_method(cSSLSocket, "peer_finished_message", ossl_ssl_get_peer_finished, 0); # ifdef HAVE_SSL_GET_SERVER_TMP_KEY rb_define_method(cSSLSocket, "tmp_key", ossl_ssl_tmp_key, 0); # endif diff --git a/ext/openssl/ossl_ts.c b/ext/openssl/ossl_ts.c new file mode 100644 index 0000000000..24f5289cdb --- /dev/null +++ b/ext/openssl/ossl_ts.c @@ -0,0 +1,1519 @@ +/* + * + * 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 ID sBAD_ALG, sBAD_REQUEST, sBAD_DATA_FORMAT, sTIME_NOT_AVAILABLE; +static ID sUNACCEPTED_POLICY, sUNACCEPTED_EXTENSION, sADD_INFO_NOT_AVAILABLE; +static ID 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.get_algorithm -> string or nil + */ +static VALUE +ossl_ts_req_get_algorithm(VALUE self) +{ + TS_REQ *req; + TS_MSG_IMPRINT *mi; + X509_ALGOR *algor; + int algi; + + GetTSRequest(self, req); + mi = TS_REQ_get_msg_imprint(req); + algor = TS_MSG_IMPRINT_get_algo(mi); + algi = OBJ_obj2nid(algor->algorithm); + if (algi == NID_undef || algi == NID_ccitt) + return Qnil; + + 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 = rb_intern("BAD_ALG"); + + /* + * Possible return value for +Response#failure_info+. Indicates that the + * timestamp server was not able to process the +Request+ properly. + */ + sBAD_REQUEST = rb_intern("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 = rb_intern("BAD_DATA_FORMAT"); + + sTIME_NOT_AVAILABLE = rb_intern("TIME_NOT_AVAILABLE"); + sUNACCEPTED_POLICY = rb_intern("UNACCEPTED_POLICY"); + sUNACCEPTED_EXTENSION = rb_intern("UNACCEPTED_EXTENSION"); + sADD_INFO_NOT_AVAILABLE = rb_intern("ADD_INFO_NOT_AVAILABLE"); + sSYSTEM_FAILURE = rb_intern("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.open('ts.p12', 'rb'), 'pwd') + * md = OpenSSL::Digest::SHA1.new + * 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.open('ts.der', 'rb') + * #Assume we have the Request for this token in a file called req.der + * req = OpenSSL::Timestamp::Request.new(File.open('req.der', 'rb') + * # Assume the associated root CA certificate is contained in a + * # DER-encoded file named root.cer + * root = OpenSSL::X509::Certificate.new(File.open('root.cer', 'rb') + * # get the necessary intermediate certificates, available in + * # DER-encoded form in inter1.cer and inter2.cer + * inter1 = OpenSSL::X509::Certificate.new(File.open('inter1.cer', 'rb') + * inter2 = OpenSSL::X509::Certificate.new(File.open('inter2.cer', 'rb') + * 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.open('ts.p12', 'rb'), 'pwd') + * inter1 = OpenSSL::X509::Certificate.new(File.open('inter1.cer', 'rb') + * inter2 = OpenSSL::X509::Certificate.new(File.open('inter2.cer', 'rb') + * 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::SHA256.new] -> [ "sha1", OpenSSL::Digest::SHA256.new ] + * factory.allowed_digests -> array or nil + * + */ + cTimestampFactory = rb_define_class_under(mTimestamp, "Factory", rb_cObject); + rb_attr(cTimestampFactory, rb_intern("allowed_digests"), 1, 1, 0); + rb_attr(cTimestampFactory, rb_intern("default_policy_id"), 1, 1, 0); + rb_attr(cTimestampFactory, rb_intern("serial_number"), 1, 1, 0); + rb_attr(cTimestampFactory, rb_intern("gen_time"), 1, 1, 0); + rb_attr(cTimestampFactory, rb_intern("additional_certs"), 1, 1, 0); + rb_define_method(cTimestampFactory, "create_timestamp", ossl_tsfac_create_ts, 3); +} + +#endif diff --git a/ext/openssl/ossl_ts.h b/ext/openssl/ossl_ts.h new file mode 100644 index 0000000000..25fb0e1d64 --- /dev/null +++ b/ext/openssl/ossl_ts.h @@ -0,0 +1,16 @@ +/* + * + * Copyright (C) 2010 Martin Bosslet + * All rights reserved. + */ +/* + * This program is licenced under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ + +#if !defined(_OSSL_TS_H_) +#define _OSSL_TS_H_ + +void Init_ossl_ts(void); + +#endif diff --git a/ext/openssl/ossl_version.h b/ext/openssl/ossl_version.h deleted file mode 100644 index c162f8c2a8..0000000000 --- a/ext/openssl/ossl_version.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 'OpenSSL for Ruby' project - * Copyright (C) 2001-2002 Michal Rokos - * All rights reserved. - */ -/* - * This program is licensed under the same licence as Ruby. - * (See the file 'LICENCE'.) - */ -#if !defined(_OSSL_VERSION_H_) -#define _OSSL_VERSION_H_ - -#define OSSL_VERSION "2.1.2" - -#endif /* _OSSL_VERSION_H_ */ diff --git a/ext/openssl/ossl_x509ext.c b/ext/openssl/ossl_x509ext.c index 30ec09d7a3..5eb9bd759f 100644 --- a/ext/openssl/ossl_x509ext.c +++ b/ext/openssl/ossl_x509ext.c @@ -402,6 +402,19 @@ ossl_x509ext_get_value(VALUE obj) return ret; } +static VALUE +ossl_x509ext_get_value_der(VALUE obj) +{ + X509_EXTENSION *ext; + ASN1_OCTET_STRING *value; + + GetX509Ext(obj, ext); + if ((value = X509_EXTENSION_get_data(ext)) == NULL) + ossl_raise(eX509ExtError, NULL); + + return rb_str_new((const char *)value->data, value->length); +} + static VALUE ossl_x509ext_get_critical(VALUE obj) { @@ -472,6 +485,7 @@ Init_ossl_x509ext(void) rb_define_method(cX509Ext, "critical=", ossl_x509ext_set_critical, 1); rb_define_method(cX509Ext, "oid", ossl_x509ext_get_oid, 0); rb_define_method(cX509Ext, "value", ossl_x509ext_get_value, 0); + rb_define_method(cX509Ext, "value_der", ossl_x509ext_get_value_der, 0); rb_define_method(cX509Ext, "critical?", ossl_x509ext_get_critical, 0); rb_define_method(cX509Ext, "to_der", ossl_x509ext_to_der, 0); } diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c index 1ea8400dbb..1522c3d897 100644 --- a/ext/openssl/ossl_x509name.c +++ b/ext/openssl/ossl_x509name.c @@ -387,17 +387,21 @@ ossl_x509name_cmp0(VALUE self, VALUE other) /* * call-seq: - * name.cmp(other) -> -1 | 0 | 1 - * name <=> other -> -1 | 0 | 1 + * name.cmp(other) -> -1 | 0 | 1 | nil + * name <=> other -> -1 | 0 | 1 | nil * * Compares this Name with _other_ and returns +0+ if they are the same and +-1+ * or ++1+ if they are greater or less than each other respectively. + * Returns +nil+ if they are not comparable (i.e. different types). */ static VALUE ossl_x509name_cmp(VALUE self, VALUE other) { int result; + if (!rb_obj_is_kind_of(other, cX509Name)) + return Qnil; + result = ossl_x509name_cmp0(self, other); if (result < 0) return INT2FIX(-1); if (result > 0) return INT2FIX(1); @@ -494,7 +498,7 @@ ossl_x509name_to_der(VALUE self) * You can create a Name by parsing a distinguished name String or by * supplying the distinguished name as an Array. * - * name = OpenSSL::X509::Name.parse 'CN=nobody/DC=example' + * name = OpenSSL::X509::Name.parse '/CN=nobody/DC=example' * * name = OpenSSL::X509::Name.new [['CN', 'nobody'], ['DC', 'example']] */ diff --git a/test/openssl/fixtures/chain/dh512.pem b/test/openssl/fixtures/chain/dh512.pem new file mode 100644 index 0000000000..fec138c7a9 --- /dev/null +++ b/test/openssl/fixtures/chain/dh512.pem @@ -0,0 +1,4 @@ +-----BEGIN DH PARAMETERS----- +MEYCQQCjDVzTg9C4u43MV0TKDGsBuYdChrPMczr4IYjy+jHQvXm2DDadNNWBIDau +4zNtwfLCg2gMwOc7t18m4Ten/NOLAgEC +-----END DH PARAMETERS----- diff --git a/test/openssl/fixtures/chain/server.crt b/test/openssl/fixtures/chain/server.crt new file mode 100644 index 0000000000..d6b814f450 --- /dev/null +++ b/test/openssl/fixtures/chain/server.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICATCCAWoCCQDbxIRGgXeWaDANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJO +WjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMB4XDTE5MDYxMzA1MDU0MloXDTI5MDYxMDA1MDU0MlowRTELMAkG +A1UEBhMCTloxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA29Vu +Y6m8pRrsXxUhlK2BX48CDChr8D53SqZozcQI26BCm+05TBnQxKAHOknR3y/ige2U +2zftSwbSoK/zKUC8o5pKVL+l36anDEnZ6RWc9Z9CvmaCFjlcP4nXZO+yD1Is/jCy +KqGGC8lQ920VXOCFflJj6AWg88+4C3GLjxJe6bMCAwEAATANBgkqhkiG9w0BAQsF +AAOBgQCDaqKGBkYxNxnv37vEKp7zi/cov8LvEsZaAD1pcSU+ysBiBes/B7a/Qjcj +PTZsH/hedn9mVynLkjc7LrztUWngTeW9gk5EB9YSwJdPhwLntV1TdaBlf/tu0n/c +s7QxaZhFMUyo1Eof28zXVHhs1OEhlSjwJ8lxuC3vBE4F1BjSNQ== +-----END CERTIFICATE----- diff --git a/test/openssl/fixtures/chain/server.csr b/test/openssl/fixtures/chain/server.csr new file mode 100644 index 0000000000..51b38e33f2 --- /dev/null +++ b/test/openssl/fixtures/chain/server.csr @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBhDCB7gIBADBFMQswCQYDVQQGEwJOWjETMBEGA1UECAwKU29tZS1TdGF0ZTEh +MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDb1W5jqbylGuxfFSGUrYFfjwIMKGvwPndKpmjNxAjboEKb +7TlMGdDEoAc6SdHfL+KB7ZTbN+1LBtKgr/MpQLyjmkpUv6XfpqcMSdnpFZz1n0K+ +ZoIWOVw/iddk77IPUiz+MLIqoYYLyVD3bRVc4IV+UmPoBaDzz7gLcYuPEl7pswID +AQABoAAwDQYJKoZIhvcNAQELBQADgYEAONaTWYVfyMmd8irCtognRoM4tFF4xvDg +PTcnHjVb/6oPPMU+mtQVD9qNf8SOdhNuYVTZ61mDLQGeq45CLM5qWjZkqFPHnngf +ajfZRE7Y3vA8ZaWFvsTJYcU+R3/FRS0XnFYj99+q9Yi3JExSY+arElyAW3tFYlcs +RWOCk1pT2Yc= +-----END CERTIFICATE REQUEST----- diff --git a/test/openssl/fixtures/chain/server.key b/test/openssl/fixtures/chain/server.key new file mode 100644 index 0000000000..9590235db8 --- /dev/null +++ b/test/openssl/fixtures/chain/server.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDb1W5jqbylGuxfFSGUrYFfjwIMKGvwPndKpmjNxAjboEKb7TlM +GdDEoAc6SdHfL+KB7ZTbN+1LBtKgr/MpQLyjmkpUv6XfpqcMSdnpFZz1n0K+ZoIW +OVw/iddk77IPUiz+MLIqoYYLyVD3bRVc4IV+UmPoBaDzz7gLcYuPEl7pswIDAQAB +AoGAGO+q5+83ENtu+JIjDwRnanmEV/C13biYO4WI2d5kytTw+VL9bt52yfcFGt2I +yvJZlTdn7T340svhVIzg3ksTmp1xQk3zh6zR00zQy45kYwY8uyd8Xfh2IsnpByoc +h2jWVX6LSqi1Iy3RxanHmMYPSMy15otsjwlwnnTAHLnnvzECQQDvw3TL90DucQSD +S0h6DWAGakaiOMhY/PpFbTsjzw+uG+Up65tpz4QqPbsXfoReeK0CQIuyE/LlYoJl +VOlIsL6HAkEA6rh4zsWi6KVTGa7qd5x70TEgxeMMAW1qUbak1THxeZTFYnyvucBz +i+VQvHEVnCadhVpHIwbBNUeOyS5DXjj6dQJAA0Caf/3Noq5jykgmJomx6MReSusM +RLDB0FlH+Rdg9hKozCXHCOtoto350LrFnuZyKlqnynWc0OHCNQ+uzm6fVwJAbtyW +YsNCQLPlXhoZsEj+yj10B0NH5lyxfMrRa8jdDtnPqMbPkOJvMMIssfSPimNKvzN2 +qfqEww97R1ZMh3JOCQJBAIIwGHBN5rDGIb4CgR+PLsh8bve1X+gO8UnOYJXa/Uzx +gAXE0uzHNH6rNSG0V/IQnFYlSHpNJGgcdSl+MZNLldQ= +-----END RSA PRIVATE KEY----- diff --git a/test/openssl/test_asn1.rb b/test/openssl/test_asn1.rb index cc11301804..5f4575516a 100644 --- a/test/openssl/test_asn1.rb +++ b/test/openssl/test_asn1.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) @@ -167,7 +167,7 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase assert_equal(OpenSSL::ASN1::OctetString, ext.value[2].class) extv = OpenSSL::ASN1.decode(ext.value[2].value) assert_equal(OpenSSL::ASN1::BitString, extv.class) - str = "\000"; str[0] = 0b00000110.chr + str = +"\000"; str[0] = 0b00000110.chr assert_equal(str, extv.value) ext = extensions.value[0].value[2] # subjetKeyIdentifier @@ -332,6 +332,32 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase pend "OBJ_obj2txt() not working (LibreSSL?)" if $!.message =~ /OBJ_obj2txt/ raise end + + aki = [ + OpenSSL::ASN1::ObjectId.new("authorityKeyIdentifier"), + OpenSSL::ASN1::ObjectId.new("X509v3 Authority Key Identifier"), + OpenSSL::ASN1::ObjectId.new("2.5.29.35") + ] + + ski = [ + OpenSSL::ASN1::ObjectId.new("subjectKeyIdentifier"), + OpenSSL::ASN1::ObjectId.new("X509v3 Subject Key Identifier"), + OpenSSL::ASN1::ObjectId.new("2.5.29.14") + ] + + aki.each do |a| + aki.each do |b| + assert a == b + end + + ski.each do |b| + refute a == b + end + end + + assert_raise(TypeError) { + OpenSSL::ASN1::ObjectId.new("authorityKeyIdentifier") == nil + } end def test_sequence @@ -635,10 +661,10 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase assert_equal data, seq.entries end - def test_gc_stress - skip "very time consuming test" - assert_ruby_status(['--disable-gems', '-eGC.stress=true', '-erequire "openssl.so"']) - end + # Very time consuming test. + # def test_gc_stress + # assert_ruby_status(['--disable-gems', '-eGC.stress=true', '-erequire "openssl.so"']) + # end private diff --git a/test/openssl/test_bn.rb b/test/openssl/test_bn.rb index 0b5cd84241..547d334c64 100644 --- a/test/openssl/test_bn.rb +++ b/test/openssl/test_bn.rb @@ -1,5 +1,5 @@ # coding: us-ascii -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' require "prime" @@ -15,6 +15,10 @@ class OpenSSL::TestBN < OpenSSL::TestCase end def test_new + assert_raise(ArgumentError) { OpenSSL::BN.new } + assert_raise(ArgumentError) { OpenSSL::BN.new(nil) } + assert_raise(ArgumentError) { OpenSSL::BN.new(nil, 2) } + assert_equal(@e1, OpenSSL::BN.new("999")) assert_equal(@e1, OpenSSL::BN.new("999", 10)) assert_equal(@e1, OpenSSL::BN.new("\x03\xE7", 2)) @@ -273,9 +277,9 @@ class OpenSSL::TestBN < OpenSSL::TestCase assert_instance_of(String, @e1.hash.to_s) end - def test_type_error + def test_argument_error bug15760 = '[ruby-core:92231] [Bug #15760]' - assert_raise(TypeError, bug15760) { OpenSSL::BN.new(nil, 2) } + assert_raise(ArgumentError, bug15760) { OpenSSL::BN.new(nil, 2) } end end diff --git a/test/openssl/test_buffering.rb b/test/openssl/test_buffering.rb index c85a6f020b..7575c5b4fe 100644 --- a/test/openssl/test_buffering.rb +++ b/test/openssl/test_buffering.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) @@ -10,7 +10,7 @@ class OpenSSL::TestBuffering < OpenSSL::TestCase attr_accessor :sync def initialize - @io = "" + @io = Buffer.new def @io.sync true end @@ -41,6 +41,13 @@ class OpenSSL::TestBuffering < OpenSSL::TestCase @io = IO.new end + def test_encoding + @io.write '😊' + @io.flush + + assert_equal @io.string.encoding, Encoding::BINARY + end + def test_flush @io.write 'a' diff --git a/test/openssl/test_cipher.rb b/test/openssl/test_cipher.rb index d83fa4ec3d..f6ec498087 100644 --- a/test/openssl/test_cipher.rb +++ b/test/openssl/test_cipher.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) @@ -305,6 +305,21 @@ class OpenSSL::TestCipher < OpenSSL::TestCase } end + def test_crypt_after_key + key = ["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*") + %w'ecb cbc cfb ctr gcm'.each do |c| + cipher = OpenSSL::Cipher.new("aes-128-#{c}") + cipher.key = key + cipher.encrypt + assert_raise(OpenSSL::Cipher::CipherError) { cipher.update("") } + + cipher = OpenSSL::Cipher.new("aes-128-#{c}") + cipher.key = key + cipher.decrypt + assert_raise(OpenSSL::Cipher::CipherError) { cipher.update("") } + end + end + private def new_encryptor(algo, **kwargs) diff --git a/test/openssl/test_config.rb b/test/openssl/test_config.rb index 3606c67d65..dba66b0802 100644 --- a/test/openssl/test_config.rb +++ b/test/openssl/test_config.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) @@ -61,14 +61,14 @@ foo\\bar::foo\\bar = baz [default1 default2]\t\t # space is allowed in section name fo =b ar # space allowed in value [emptysection] - [dollar ] + [doller ] foo=bar bar = $(foo) baz = 123$(default::bar)456${foo}798 qux = ${baz} quxx = $qux.$qux __EOC__ - assert_equal(['default', 'default1 default2', 'dollar', 'emptysection', 'foo', 'foo\\bar'], c.sections.sort) + assert_equal(['default', 'default1 default2', 'doller', 'emptysection', 'foo', 'foo\\bar'], c.sections.sort) assert_equal(['', 'a', 'bar', 'baz', 'd', 'dq', 'dq2', 'esc', 'foo\\bar', 'sq'], c['default'].keys.sort) assert_equal('c', c['default']['']) assert_equal('', c['default']['a']) @@ -84,12 +84,12 @@ __EOC__ assert_equal('baz', c['foo\\bar']['foo\\bar']) assert_equal('b ar', c['default1 default2']['fo']) - # dollar - assert_equal('bar', c['dollar']['foo']) - assert_equal('bar', c['dollar']['bar']) - assert_equal('123baz456bar798', c['dollar']['baz']) - assert_equal('123baz456bar798', c['dollar']['qux']) - assert_equal('123baz456bar798.123baz456bar798', c['dollar']['quxx']) + # dolloer + assert_equal('bar', c['doller']['foo']) + assert_equal('bar', c['doller']['bar']) + assert_equal('123baz456bar798', c['doller']['baz']) + assert_equal('123baz456bar798', c['doller']['qux']) + assert_equal('123baz456bar798.123baz456bar798', c['doller']['quxx']) excn = assert_raise(OpenSSL::ConfigError) do OpenSSL::Config.parse("foo = $bar") diff --git a/test/openssl/test_digest.rb b/test/openssl/test_digest.rb index 2cb878b6fa..e47fc0a356 100644 --- a/test/openssl/test_digest.rb +++ b/test/openssl/test_digest.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) @@ -53,14 +53,21 @@ class OpenSSL::TestDigest < OpenSSL::TestCase assert_equal(dig1, dig2, "reset") end - def test_digest_constants - algs = %w(MD4 MD5 RIPEMD160 SHA1 SHA224 SHA256 SHA384 SHA512) - if !libressl? && !openssl?(1, 1, 0) - algs += %w(DSS1 SHA) + def test_required_digests + algorithms = OpenSSL::Digest::ALGORITHMS + required = %w{MD4 MD5 RIPEMD160 SHA1 SHA224 SHA256 SHA384 SHA512} + + required.each do |name| + assert_include(algorithms, name) end - algs.each do |alg| - assert_not_nil(OpenSSL::Digest.new(alg)) - klass = OpenSSL::Digest.const_get(alg) + end + + def test_digest_constants + algorithms = OpenSSL::Digest::ALGORITHMS + + algorithms.each do |name| + assert_not_nil(OpenSSL::Digest.new(name)) + klass = OpenSSL::Digest.const_get(name.tr('-', '_')) assert_not_nil(klass.new) end end @@ -91,6 +98,18 @@ class OpenSSL::TestDigest < OpenSSL::TestCase assert_equal(sha512_a, encode16(OpenSSL::Digest::SHA512.digest("a"))) end + def test_sha3 + pend "SHA3 is not implemented" unless OpenSSL::Digest.const_defined?(:SHA3_224) + s224 = '6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7' + s256 = 'a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a' + s384 = '0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004' + s512 = 'a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26' + assert_equal(OpenSSL::Digest::SHA3_224.hexdigest(""), s224) + assert_equal(OpenSSL::Digest::SHA3_256.hexdigest(""), s256) + assert_equal(OpenSSL::Digest::SHA3_384.hexdigest(""), s384) + assert_equal(OpenSSL::Digest::SHA3_512.hexdigest(""), s512) + end + def test_digest_by_oid_and_name_sha2 check_digest(OpenSSL::ASN1::ObjectId.new("SHA224")) check_digest(OpenSSL::ASN1::ObjectId.new("SHA256")) diff --git a/test/openssl/test_engine.rb b/test/openssl/test_engine.rb index bb1123d516..232ba866a5 100644 --- a/test/openssl/test_engine.rb +++ b/test/openssl/test_engine.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) && defined?(OpenSSL::Engine) diff --git a/test/openssl/test_fips.rb b/test/openssl/test_fips.rb index a577d7891e..8cd474f9a3 100644 --- a/test/openssl/test_fips.rb +++ b/test/openssl/test_fips.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) diff --git a/test/openssl/test_hmac.rb b/test/openssl/test_hmac.rb index 831a5b6b37..9cb3c5a864 100644 --- a/test/openssl/test_hmac.rb +++ b/test/openssl/test_hmac.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) @@ -39,6 +39,16 @@ class OpenSSL::TestHMAC < OpenSSL::TestCase second = h1.update("test").hexdigest assert_equal first, second end + + def test_eq + h1 = OpenSSL::HMAC.new("KEY", "MD5") + h2 = OpenSSL::HMAC.new("KEY", OpenSSL::Digest.new("MD5")) + h3 = OpenSSL::HMAC.new("FOO", "MD5") + + assert_equal h1, h2 + refute_equal h1, h2.digest + refute_equal h1, h3 + end end end diff --git a/test/openssl/test_kdf.rb b/test/openssl/test_kdf.rb index 5e1db80c5f..f4790c96af 100644 --- a/test/openssl/test_kdf.rb +++ b/test/openssl/test_kdf.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) diff --git a/test/openssl/test_ns_spki.rb b/test/openssl/test_ns_spki.rb index aa1e61824f..052507de5b 100644 --- a/test/openssl/test_ns_spki.rb +++ b/test/openssl/test_ns_spki.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) @@ -9,7 +9,7 @@ class OpenSSL::TestNSSPI < OpenSSL::TestCase # This request data is adopt from the specification of # "Netscape Extensions for User Key Generation". # -- http://wp.netscape.com/eng/security/comm4-keygen.html - @b64 = "MIHFMHEwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAnX0TILJrOMUue+PtwBRE6XfV" + @b64 = +"MIHFMHEwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAnX0TILJrOMUue+PtwBRE6XfV" @b64 << "WtKQbsshxk5ZhcUwcwyvcnIq9b82QhJdoACdD34rqfCAIND46fXKQUnb0mvKzQID" @b64 << "AQABFhFNb3ppbGxhSXNNeUZyaWVuZDANBgkqhkiG9w0BAQQFAANBAAKv2Eex2n/S" @b64 << "r/7iJNroWlSzSMtTiQTEB+ADWHGj9u1xrUrOilq/o2cuQxIfZcNZkYAkWP4DubqW" diff --git a/test/openssl/test_ocsp.rb b/test/openssl/test_ocsp.rb index 50ad6c31f5..a490a1539e 100644 --- a/test/openssl/test_ocsp.rb +++ b/test/openssl/test_ocsp.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) @@ -84,6 +84,7 @@ class OpenSSL::TestOCSP < OpenSSL::TestCase assert_equal [cid.issuer_key_hash].pack("H*"), asn1.value[2].value assert_equal @cert.serial, asn1.value[3].value assert_equal der, OpenSSL::OCSP::CertificateId.new(der).to_der + assert_equal der, OpenSSL::OCSP::CertificateId.new(asn1).to_der end def test_certificate_id_dup diff --git a/test/openssl/test_ossl.rb b/test/openssl/test_ossl.rb new file mode 100644 index 0000000000..f517b1d83d --- /dev/null +++ b/test/openssl/test_ossl.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true +require_relative "utils" + +require 'benchmark' + +if defined?(OpenSSL) + +class OpenSSL::OSSL < OpenSSL::SSLTestCase + def test_fixed_length_secure_compare + assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "a") } + assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "aa") } + + assert OpenSSL.fixed_length_secure_compare("aaa", "aaa") + assert OpenSSL.fixed_length_secure_compare( + OpenSSL::Digest::SHA256.digest("aaa"), OpenSSL::Digest::SHA256.digest("aaa") + ) + + assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "aaaa") } + refute OpenSSL.fixed_length_secure_compare("aaa", "baa") + refute OpenSSL.fixed_length_secure_compare("aaa", "aba") + refute OpenSSL.fixed_length_secure_compare("aaa", "aab") + assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "aaab") } + assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "b") } + assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "bb") } + refute OpenSSL.fixed_length_secure_compare("aaa", "bbb") + assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "bbbb") } + end + + def test_secure_compare + refute OpenSSL.secure_compare("aaa", "a") + refute OpenSSL.secure_compare("aaa", "aa") + + assert OpenSSL.secure_compare("aaa", "aaa") + + refute OpenSSL.secure_compare("aaa", "aaaa") + refute OpenSSL.secure_compare("aaa", "baa") + refute OpenSSL.secure_compare("aaa", "aba") + refute OpenSSL.secure_compare("aaa", "aab") + refute OpenSSL.secure_compare("aaa", "aaab") + refute OpenSSL.secure_compare("aaa", "b") + refute OpenSSL.secure_compare("aaa", "bb") + refute OpenSSL.secure_compare("aaa", "bbb") + refute OpenSSL.secure_compare("aaa", "bbbb") + end + + def test_memcmp_timing + # Ensure using fixed_length_secure_compare takes almost exactly the same amount of time to compare two different strings. + # Regular string comparison will short-circuit on the first non-matching character, failing this test. + # NOTE: this test may be susceptible to noise if the system running the tests is otherwise under load. + a = "x" * 512_000 + b = "#{a}y" + c = "y#{a}" + a = "#{a}x" + + n = 10_000 + a_b_time = Benchmark.measure { n.times { OpenSSL.fixed_length_secure_compare(a, b) } }.real + a_c_time = Benchmark.measure { n.times { OpenSSL.fixed_length_secure_compare(a, c) } }.real + assert_in_delta(a_b_time, a_c_time, 1, "fixed_length_secure_compare timing test failed") + end +end + +end diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb index 08c15a0f75..e9cf98df15 100644 --- a/test/openssl/test_pair.rb +++ b/test/openssl/test_pair.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' require_relative 'ut_eof' @@ -128,11 +128,11 @@ module OpenSSL::TestPairM ssl_pair {|s1, s2| s2.write "a\nbcd" assert_equal("a\n", s1.gets) - result = "" + result = String.new result << s1.readpartial(10) until result.length == 3 assert_equal("bcd", result) s2.write "efg" - result = "" + result = String.new result << s1.readpartial(10) until result.length == 3 assert_equal("efg", result) s2.close @@ -242,22 +242,22 @@ module OpenSSL::TestPairM def test_read_with_outbuf ssl_pair { |s1, s2| s1.write("abc\n") - buf = "" + buf = String.new ret = s2.read(2, buf) assert_same ret, buf assert_equal "ab", ret - buf = "garbage" + buf = +"garbage" ret = s2.read(2, buf) assert_same ret, buf assert_equal "c\n", ret - buf = "garbage" + buf = +"garbage" assert_equal :wait_readable, s2.read_nonblock(100, buf, exception: false) assert_equal "", buf s1.close - buf = "garbage" + buf = +"garbage" assert_equal nil, s2.read(100, buf) assert_equal "", buf } diff --git a/test/openssl/test_pkcs12.rb b/test/openssl/test_pkcs12.rb index de8e35ed79..fdbe753b17 100644 --- a/test/openssl/test_pkcs12.rb +++ b/test/openssl/test_pkcs12.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) diff --git a/test/openssl/test_pkcs7.rb b/test/openssl/test_pkcs7.rb index 6437112b74..d0d9dcaf81 100644 --- a/test/openssl/test_pkcs7.rb +++ b/test/openssl/test_pkcs7.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) @@ -172,6 +172,28 @@ class OpenSSL::TestPKCS7 < OpenSSL::TestCase assert_equal(:encrypted, p7.type) end + def test_smime + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + ca_certs = [@ca_cert] + + data = "aaaaa\r\nbbbbb\r\nccccc\r\n" + tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs) + p7 = OpenSSL::PKCS7.new(tmp.to_der) + smime = OpenSSL::PKCS7.write_smime(p7) + assert_equal(true, smime.start_with?(< ctx { # Unset values set by start_server @@ -139,15 +185,20 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end end + def test_add_certificate_chain_file + ctx = OpenSSL::SSL::SSLContext.new + assert ctx.add_certificate_chain_file(Fixtures.file_path("chain", "server.crt")) + end + def test_sysread_and_syswrite start_server { |port| server_connect(port) { |ssl| - str = "x" * 100 + "\n" + str = +("x" * 100 + "\n") ssl.syswrite(str) newstr = ssl.sysread(str.bytesize) assert_equal(str, newstr) - buf = "" + buf = String.new ssl.syswrite(str) assert_same buf, ssl.sysread(str.size, buf) assert_equal(str, buf) @@ -155,23 +206,21 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase } end - def test_sysread_nonblock_and_syswrite_nonblock_keywords - start_server(ignore_listener_error: true) do |port| - sock = TCPSocket.new("127.0.0.1", port) - ssl = OpenSSL::SSL::SSLSocket.new(sock) - - assert_warn ("") do - ssl.send(:syswrite_nonblock, "1", exception: false) - ssl.send(:sysread_nonblock, 1, exception: false) rescue nil - ssl.send(:sysread_nonblock, 1, String.new, exception: false) rescue nil - end - ensure - sock&.close - end - end + # TODO fix this test + # def test_sysread_nonblock_and_syswrite_nonblock_keywords + # start_server do |port| + # server_connect(port) do |ssl| + # assert_warning("") do + # ssl.send(:syswrite_nonblock, "12", exception: false) + # ssl.send(:sysread_nonblock, 1, exception: false) rescue nil + # ssl.send(:sysread_nonblock, 1, String.new, exception: false) rescue nil + # end + # end + # end + # end def test_sync_close - start_server { |port| + start_server do |port| begin sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) @@ -194,7 +243,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ensure sock&.close end - } + end end def test_copy_stream @@ -434,6 +483,29 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase } end + def test_finished_messages + server_finished = nil + server_peer_finished = nil + client_finished = nil + client_peer_finished = nil + + start_server(accept_proc: proc { |server| + server_finished = server.finished_message + server_peer_finished = server.peer_finished_message + }) { |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE + server_connect(port, ctx) { |ssl| + client_finished = ssl.finished_message + client_peer_finished = ssl.peer_finished_message + sleep 0.05 + ssl.send :stop + } + } + assert_equal(server_finished, client_peer_finished) + assert_equal(server_peer_finished, client_finished) + end + def test_sslctx_set_params ctx = OpenSSL::SSL::SSLContext.new ctx.set_params @@ -1565,6 +1637,20 @@ end } end + def test_fileno + ctx = OpenSSL::SSL::SSLContext.new + sock1, sock2 = socketpair + + socket = OpenSSL::SSL::SSLSocket.new(sock1) + server = OpenSSL::SSL::SSLServer.new(sock2, ctx) + + assert_equal socket.fileno, socket.to_io.fileno + assert_equal server.fileno, server.to_io.fileno + ensure + sock1.close + sock2.close + end + private def start_server_version(version, ctx_proc = nil, @@ -1597,8 +1683,8 @@ end def assert_handshake_error # different OpenSSL versions react differently when facing a SSL/TLS version - # that has been marked as forbidden, therefore either of these may be raised - assert_raise(OpenSSL::SSL::SSLError, Errno::ECONNRESET) { + # that has been marked as forbidden, therefore any of these may be raised + assert_raise(OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::EPIPE) { yield } end diff --git a/test/openssl/test_ssl_session.rb b/test/openssl/test_ssl_session.rb index e199f86d2b..89726d4463 100644 --- a/test/openssl/test_ssl_session.rb +++ b/test/openssl/test_ssl_session.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) diff --git a/test/openssl/test_ts.rb b/test/openssl/test_ts.rb new file mode 100644 index 0000000000..c57f08cf7a --- /dev/null +++ b/test/openssl/test_ts.rb @@ -0,0 +1,667 @@ +require_relative "utils" + +if defined?(OpenSSL) && defined?(OpenSSL::Timestamp) + +class OpenSSL::TestTimestamp < OpenSSL::TestCase + def intermediate_key + @intermediate_key ||= OpenSSL::PKey::RSA.new <<-_end_of_pem_ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQCcyODxH+oTrr7l7MITWcGaYnnBma6vidCCJjuSzZpaRmXZHAyH +0YcY4ttC0BdJ4uV+cE05IySVC7tyvVfFb8gFQ6XJV+AEktP+XkLbcxZgj9d2NVu1 +ziXdI+ldXkPnMhyWpMS5E7SD6gflv9NhUYEsmAGsUgdK6LDmm2W2/4TlewIDAQAB +AoGAYgx6KDFWONLqjW3f/Sv/mGYHUNykUyDzpcD1Npyf797gqMMSzwlo3FZa2tC6 +D7n23XirwpTItvEsW9gvgMikJDPlThAeGLZ+L0UbVNNBHVxGP998Nda1kxqKvhRE +pfZCKc7PLM9ZXc6jBTmgxdcAYfVCCVUoa2mEf9Ktr3BlI4kCQQDQAM09+wHDXGKP +o2UnCwCazGtyGU2r0QCzHlh9BVY+KD2KjjhuWh86rEbdWN7hEW23Je1vXIhuM6Pa +/Ccd+XYnAkEAwPZ91PK6idEONeGQ4I3dyMKV2SbaUjfq3MDL4iIQPQPuj7QsBO/5 +3Nf9ReSUUTRFCUVwoC8k4Z1KAJhR/K/ejQJANE7PTnPuGJQGETs09+GTcFpR9uqY +FspDk8fg1ufdrVnvSAXF+TJewiGK3KU5v33jinhWQngRsyz3Wt2odKhEZwJACbjh +oicQqvzzgFd7GzVKpWDYd/ZzLY1PsgusuhoJQ2m9TVRAm4cTycLAKhNYPbcqe0sa +X5fAffWU0u7ZwqeByQJAOUAbYET4RU3iymAvAIDFj8LiQnizG9t5Ty3HXlijKQYv +y8gsvWd4CdxwOPatWpBUX9L7IXcMJmD44xXTUvpbfQ== +-----END RSA PRIVATE KEY----- +_end_of_pem_ + end + + def ee_key + @ee_key ||= OpenSSL::PKey::RSA.new <<-_end_of_pem_ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDA6eB5r2O5KOKNbKMBhzadl43lgpwqq28m+G0gH38kKCL1f3o9 +P8xUZm7sZqcWEervZMSSXMGBV9DgeoSR+U6FMJywgQGx/JNRx7wZTMNym3PvgLkl +xCXh6ZA0/xbtJtcNI+UUv0ENBkTIuUWBhkAf3jQclAr9aQ0ktYBuHAcRcQIDAQAB +AoGAKNhcAuezwZx6e18pFEXAtpVEIfgJgK9TlXi8AjUpAkrNPBWFmDpN1QDrM3p4 +nh+lEpLPW/3vqqchPqYyM4YJraMLpS3KUG+s7+m9QIia0ri2WV5Cig7WL+Tl9p7K +b3oi2Aj/wti8GfOLFQXOQQ4Ea4GoCv2Sxe0GZR39UBxzTsECQQD1zuVIwBvqU2YR +8innsoa+j4u2hulRmQO6Zgpzj5vyRYfA9uZxQ9nKbfJvzuWwUv+UzyS9RqxarqrP +5nQw5EmVAkEAyOmJg6+AfGrgvSWfSpXEds/WA/sHziCO3rE4/sd6cnDc6XcTgeMs +mT8Z3kAYGpqFDew5orUylPfJJa+PUueJbQJAY+gkvw3+Cp69FLw1lgu0wo07fwOU +n2qu3jsNMm0DOFRUWfTAMvcd9S385L7WEnWZldUfnKK1+OGXYYrMXPbchQJAChU2 +UoaHQzc16iguM1cK0g+iJPb/MEgQA3sPajHmokGpxIm2T+lvvo0dJjs/Om6QyN8X +EWRYkoNQ8/Q4lCeMjQJAfvDIGtyqF4PieFHYgluQAv5pGgYpakdc8SYyeRH9NKey +GaL27FRs4fRWf9OmxPhUVgIyGzLGXrueemvQUDHObA== +-----END RSA PRIVATE KEY----- +_end_of_pem_ + end + + def ca_cert + @ca_cert ||= OpenSSL::Certs.ca_cert + end + + def ca_store + @ca_store ||= OpenSSL::X509::Store.new.tap { |s| s.add_cert(ca_cert) } + end + + def ts_cert_direct + @ts_cert_direct ||= OpenSSL::Certs.ts_cert_direct(ee_key, ca_cert) + end + + def intermediate_cert + @intermediate_cert ||= OpenSSL::Certs.intermediate_cert(intermediate_key, ca_cert) + end + + def intermediate_store + @intermediate_store ||= OpenSSL::X509::Store.new.tap { |s| s.add_cert(intermediate_cert) } + end + + def ts_cert_ee + @ts_cert_ee ||= OpenSSL::Certs.ts_cert_ee(ee_key, intermediate_cert, intermediate_key) + end + + def test_create_request + req = OpenSSL::Timestamp::Request.new + assert_equal(true, req.cert_requested?) + assert_equal(1, req.version) + assert_nil(req.algorithm) + assert_equal("", req.message_imprint) + assert_nil(req.policy_id) + assert_nil(req.nonce) + end + + def test_request_mandatory_fields + req = OpenSSL::Timestamp::Request.new + assert_raise(OpenSSL::Timestamp::TimestampError) do + tmp = req.to_der + pp OpenSSL::ASN1.decode(tmp) + end + req.algorithm = "sha1" + assert_raise(OpenSSL::Timestamp::TimestampError) do + req.to_der + end + req.message_imprint = OpenSSL::Digest::SHA1.new.digest("data") + req.to_der + end + + def test_request_assignment + req = OpenSSL::Timestamp::Request.new + + req.version = 2 + assert_equal(2, req.version) + assert_raise(TypeError) { req.version = nil } + assert_raise(TypeError) { req.version = "foo" } + + req.algorithm = "SHA1" + assert_equal("SHA1", req.algorithm) + assert_raise(TypeError) { req.algorithm = nil } + assert_raise(OpenSSL::ASN1::ASN1Error) { req.algorithm = "xxx" } + + req.message_imprint = "test" + assert_equal("test", req.message_imprint) + assert_raise(TypeError) { req.message_imprint = nil } + + req.policy_id = "1.2.3.4.5" + assert_equal("1.2.3.4.5", req.policy_id) + assert_raise(TypeError) { req.policy_id = 123 } + assert_raise(TypeError) { req.policy_id = nil } + + req.nonce = 42 + assert_equal(42, req.nonce) + assert_raise(TypeError) { req.nonce = "foo" } + assert_raise(TypeError) { req.nonce = nil } + + req.cert_requested = false + assert_equal(false, req.cert_requested?) + req.cert_requested = nil + assert_equal(false, req.cert_requested?) + req.cert_requested = 123 + assert_equal(true, req.cert_requested?) + req.cert_requested = "asdf" + assert_equal(true, req.cert_requested?) + end + + def test_request_serialization + req = OpenSSL::Timestamp::Request.new + + req.version = 2 + req.algorithm = "SHA1" + req.message_imprint = "test" + req.policy_id = "1.2.3.4.5" + req.nonce = 42 + req.cert_requested = true + + req = OpenSSL::Timestamp::Request.new(req.to_der) + + assert_equal(2, req.version) + assert_equal("SHA1", req.algorithm) + assert_equal("test", req.message_imprint) + assert_equal("1.2.3.4.5", req.policy_id) + assert_equal(42, req.nonce) + assert_equal(true, req.cert_requested?) + + end + + def test_request_re_assignment + #tests whether the potential 'freeing' of previous values in C works properly + req = OpenSSL::Timestamp::Request.new + req.version = 2 + req.version = 3 + req.algorithm = "SHA1" + req.algorithm = "SHA256" + req.message_imprint = "test" + req.message_imprint = "test2" + req.policy_id = "1.2.3.4.5" + req.policy_id = "1.2.3.4.6" + req.nonce = 42 + req.nonce = 24 + req.cert_requested = false + req.cert_requested = true + req.to_der + end + + def test_request_encode_decode + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + req.policy_id = "1.2.3.4.5" + req.nonce = 42 + + qer = OpenSSL::Timestamp::Request.new(req.to_der) + assert_equal(1, qer.version) + assert_equal("SHA1", qer.algorithm) + assert_equal(digest, qer.message_imprint) + assert_equal("1.2.3.4.5", qer.policy_id) + assert_equal(42, qer.nonce) + + #put OpenSSL::ASN1.decode inbetween + qer2 = OpenSSL::Timestamp::Request.new(OpenSSL::ASN1.decode(req.to_der)) + assert_equal(1, qer2.version) + assert_equal("SHA1", qer2.algorithm) + assert_equal(digest, qer2.message_imprint) + assert_equal("1.2.3.4.5", qer2.policy_id) + assert_equal(42, qer2.nonce) + end + + def test_response_constants + assert_equal(0, OpenSSL::Timestamp::Response::GRANTED) + assert_equal(1, OpenSSL::Timestamp::Response::GRANTED_WITH_MODS) + assert_equal(2, OpenSSL::Timestamp::Response::REJECTION) + assert_equal(3, OpenSSL::Timestamp::Response::WAITING) + assert_equal(4, OpenSSL::Timestamp::Response::REVOCATION_WARNING) + assert_equal(5, OpenSSL::Timestamp::Response::REVOCATION_NOTIFICATION) + end + + def test_response_creation + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + req.policy_id = "1.2.3.4.5" + + fac = OpenSSL::Timestamp::Factory.new + time = Time.now + fac.gen_time = time + fac.serial_number = 1 + fac.allowed_digests = ["sha1"] + + resp = fac.create_timestamp(ee_key, ts_cert_ee, req) + resp = OpenSSL::Timestamp::Response.new(resp) + assert_equal(OpenSSL::Timestamp::Response::GRANTED, resp.status) + assert_nil(resp.failure_info) + assert_equal([], resp.status_text) + assert_equal(1, resp.token_info.version) + assert_equal("1.2.3.4.5", resp.token_info.policy_id) + assert_equal("SHA1", resp.token_info.algorithm) + assert_equal(digest, resp.token_info.message_imprint) + assert_equal(1, resp.token_info.serial_number) + assert_equal(time.to_i, resp.token_info.gen_time.to_i) + assert_equal(false, resp.token_info.ordering) + assert_nil(resp.token_info.nonce) + assert_cert(ts_cert_ee, resp.tsa_certificate) + #compare PKCS7 + token = OpenSSL::ASN1.decode(resp.to_der).value[1] + assert_equal(token.to_der, resp.token.to_der) + end + + def test_response_mandatory_fields + fac = OpenSSL::Timestamp::Factory.new + req = OpenSSL::Timestamp::Request.new + assert_raise(OpenSSL::Timestamp::TimestampError) do + fac.create_timestamp(ee_key, ts_cert_ee, req) + end + req.algorithm = "sha1" + assert_raise(OpenSSL::Timestamp::TimestampError) do + fac.create_timestamp(ee_key, ts_cert_ee, req) + end + req.message_imprint = OpenSSL::Digest::SHA1.new.digest("data") + assert_raise(OpenSSL::Timestamp::TimestampError) do + fac.create_timestamp(ee_key, ts_cert_ee, req) + end + fac.gen_time = Time.now + assert_raise(OpenSSL::Timestamp::TimestampError) do + fac.create_timestamp(ee_key, ts_cert_ee, req) + end + fac.serial_number = 1 + fac.allowed_digests = ["sha1"] + assert_raise(OpenSSL::Timestamp::TimestampError) do + fac.create_timestamp(ee_key, ts_cert_ee, req) + end + fac.default_policy_id = "1.2.3.4.5" + assert_equal OpenSSL::Timestamp::Response::GRANTED, fac.create_timestamp(ee_key, ts_cert_ee, req).status + fac.default_policy_id = nil + assert_raise(OpenSSL::Timestamp::TimestampError) do + fac.create_timestamp(ee_key, ts_cert_ee, req) + end + req.policy_id = "1.2.3.4.5" + assert_equal OpenSSL::Timestamp::Response::GRANTED, fac.create_timestamp(ee_key, ts_cert_ee, req).status + end + + def test_response_allowed_digests + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + req.message_imprint = OpenSSL::Digest::SHA1.digest("test") + + fac = OpenSSL::Timestamp::Factory.new + fac.gen_time = Time.now + fac.serial_number = 1 + fac.default_policy_id = "1.2.3.4.6" + + # None allowed by default + resp = fac.create_timestamp(ee_key, ts_cert_ee, req) + assert_equal OpenSSL::Timestamp::Response::REJECTION, resp.status + + # Explicitly allow SHA1 (string) + fac.allowed_digests = ["sha1"] + resp = fac.create_timestamp(ee_key, ts_cert_ee, req) + assert_equal OpenSSL::Timestamp::Response::GRANTED, resp.status + + # Explicitly allow SHA1 (object) + fac.allowed_digests = [OpenSSL::Digest::SHA1.new] + resp = fac.create_timestamp(ee_key, ts_cert_ee, req) + assert_equal OpenSSL::Timestamp::Response::GRANTED, resp.status + + # Others not allowed + req.algorithm = "SHA256" + req.message_imprint = OpenSSL::Digest::SHA256.digest("test") + resp = fac.create_timestamp(ee_key, ts_cert_ee, req) + assert_equal OpenSSL::Timestamp::Response::REJECTION, resp.status + + # Non-Array + fac.allowed_digests = 123 + resp = fac.create_timestamp(ee_key, ts_cert_ee, req) + assert_equal OpenSSL::Timestamp::Response::REJECTION, resp.status + + # Non-String, non-Digest Array element + fac.allowed_digests = ["sha1", OpenSSL::Digest::SHA1.new, 123] + assert_raise(TypeError) do + fac.create_timestamp(ee_key, ts_cert_ee, req) + end + end + + def test_response_default_policy + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + + fac = OpenSSL::Timestamp::Factory.new + fac.gen_time = Time.now + fac.serial_number = 1 + fac.allowed_digests = ["sha1"] + fac.default_policy_id = "1.2.3.4.6" + + resp = fac.create_timestamp(ee_key, ts_cert_ee, req) + assert_equal(OpenSSL::Timestamp::Response::GRANTED, resp.status) + assert_equal("1.2.3.4.6", resp.token_info.policy_id) + end + + def test_response_bad_purpose + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + 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 + fac.allowed_digests = ["sha1"] + + + assert_raise(OpenSSL::Timestamp::TimestampError) do + fac.create_timestamp(ee_key, intermediate_cert, req) + end + end + + def test_no_cert_requested + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + req.cert_requested = false + + fac = OpenSSL::Timestamp::Factory.new + fac.gen_time = Time.now + fac.serial_number = 1 + fac.allowed_digests = ["sha1"] + fac.default_policy_id = "1.2.3.4.5" + + resp = fac.create_timestamp(ee_key, ts_cert_ee, req) + assert_equal(OpenSSL::Timestamp::Response::GRANTED, resp.status) + assert_nil(resp.tsa_certificate) + end + + def test_response_no_policy_defined + assert_raise(OpenSSL::Timestamp::TimestampError) do + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + + fac = OpenSSL::Timestamp::Factory.new + fac.gen_time = Time.now + fac.serial_number = 1 + fac.allowed_digests = ["sha1"] + + fac.create_timestamp(ee_key, ts_cert_ee, req) + end + end + + def test_verify_ee_no_req + assert_raise(TypeError) do + ts, _ = timestamp_ee + ts.verify(nil, ca_cert) + end + end + + def test_verify_ee_no_store + assert_raise(TypeError) do + ts, req = timestamp_ee + ts.verify(req, nil) + end + end + + def test_verify_ee_wrong_root_no_intermediate + assert_raise(OpenSSL::Timestamp::TimestampError) do + ts, req = timestamp_ee + ts.verify(req, intermediate_store) + end + end + + def test_verify_ee_wrong_root_wrong_intermediate + assert_raise(OpenSSL::Timestamp::TimestampError) do + ts, req = timestamp_ee + ts.verify(req, intermediate_store, [ca_cert]) + end + end + + def test_verify_ee_nonce_mismatch + assert_raise(OpenSSL::Timestamp::TimestampError) do + ts, req = timestamp_ee + req.nonce = 1 + ts.verify(req, ca_store, [intermediate_cert]) + end + end + + def test_verify_ee_intermediate_missing + assert_raise(OpenSSL::Timestamp::TimestampError) do + ts, req = timestamp_ee + ts.verify(req, ca_store) + end + end + + def test_verify_ee_intermediate + ts, req = timestamp_ee + ts.verify(req, ca_store, [intermediate_cert]) + end + + def test_verify_ee_intermediate_type_error + ts, req = timestamp_ee + assert_raise(TypeError) { ts.verify(req, [ca_cert], 123) } + end + + def test_verify_ee_def_policy + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + req.nonce = 42 + + fac = OpenSSL::Timestamp::Factory.new + fac.gen_time = Time.now + fac.serial_number = 1 + fac.allowed_digests = ["sha1"] + fac.default_policy_id = "1.2.3.4.5" + + ts = fac.create_timestamp(ee_key, ts_cert_ee, req) + ts.verify(req, ca_store, [intermediate_cert]) + end + + def test_verify_direct + ts, req = timestamp_direct + ts.verify(req, ca_store) + end + + def test_verify_direct_redundant_untrusted + ts, req = timestamp_direct + ts.verify(req, ca_store, [ts.tsa_certificate, ts.tsa_certificate]) + end + + def test_verify_direct_unrelated_untrusted + ts, req = timestamp_direct + ts.verify(req, ca_store, [intermediate_cert]) + end + + def test_verify_direct_wrong_root + assert_raise(OpenSSL::Timestamp::TimestampError) do + ts, req = timestamp_direct + ts.verify(req, intermediate_store) + end + end + + def test_verify_direct_no_cert_no_intermediate + assert_raise(OpenSSL::Timestamp::TimestampError) do + ts, req = timestamp_direct_no_cert + ts.verify(req, ca_store) + end + end + + def test_verify_ee_no_cert + ts, req = timestamp_ee_no_cert + ts.verify(req, ca_store, [ts_cert_ee, intermediate_cert]) + end + + def test_verify_ee_no_cert_no_intermediate + assert_raise(OpenSSL::Timestamp::TimestampError) do + ts, req = timestamp_ee_no_cert + ts.verify(req, ca_store, [ts_cert_ee]) + end + end + + def test_verify_ee_additional_certs_array + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + 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 + fac.allowed_digests = ["sha1"] + fac.additional_certs = [intermediate_cert] + ts = fac.create_timestamp(ee_key, ts_cert_ee, req) + assert_equal(2, ts.token.certificates.size) + fac.additional_certs = nil + ts.verify(req, ca_store) + ts = fac.create_timestamp(ee_key, ts_cert_ee, req) + assert_equal(1, ts.token.certificates.size) + end + + def test_verify_ee_additional_certs_with_root + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + 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 + fac.allowed_digests = ["sha1"] + fac.additional_certs = [intermediate_cert, ca_cert] + ts = fac.create_timestamp(ee_key, ts_cert_ee, req) + assert_equal(3, ts.token.certificates.size) + ts.verify(req, ca_store) + end + + def test_verify_ee_cert_inclusion_not_requested + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + req.nonce = 42 + req.cert_requested = false + fac = OpenSSL::Timestamp::Factory.new + fac.gen_time = Time.now + fac.serial_number = 1 + fac.allowed_digests = ["sha1"] + #needed because the Request contained no policy identifier + fac.default_policy_id = '1.2.3.4.5' + fac.additional_certs = [ ts_cert_ee, intermediate_cert ] + ts = fac.create_timestamp(ee_key, ts_cert_ee, req) + assert_nil(ts.token.certificates) #since cert_requested? == false + ts.verify(req, ca_store, [ts_cert_ee, intermediate_cert]) + end + + def test_reusable + #test if req and faq are reusable, i.e. the internal + #CTX_free methods don't mess up e.g. the certificates + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + 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 + fac.allowed_digests = ["sha1"] + fac.additional_certs = [ intermediate_cert ] + ts1 = fac.create_timestamp(ee_key, ts_cert_ee, req) + ts1.verify(req, ca_store) + ts2 = fac.create_timestamp(ee_key, ts_cert_ee, req) + ts2.verify(req, ca_store) + refute_nil(ts1.tsa_certificate) + refute_nil(ts2.tsa_certificate) + end + + def test_token_info_creation + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + req.policy_id = "1.2.3.4.5" + req.nonce = OpenSSL::BN.new(123) + + fac = OpenSSL::Timestamp::Factory.new + time = Time.now + fac.gen_time = time + fac.serial_number = 1 + fac.allowed_digests = ["sha1"] + + resp = fac.create_timestamp(ee_key, ts_cert_ee, req) + info = resp.token_info + info = OpenSSL::Timestamp::TokenInfo.new(info.to_der) + + assert_equal(1, info.version) + assert_equal("1.2.3.4.5", info.policy_id) + assert_equal("SHA1", info.algorithm) + assert_equal(digest, info.message_imprint) + assert_equal(1, info.serial_number) + assert_equal(time.to_i, info.gen_time.to_i) + assert_equal(false, info.ordering) + assert_equal(123, info.nonce) + end + + private + + def assert_cert expected, actual + assert_equal expected.to_der, actual.to_der + end + + def timestamp_ee + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + 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 + fac.allowed_digests = ["sha1"] + return fac.create_timestamp(ee_key, ts_cert_ee, req), req + end + + def timestamp_ee_no_cert + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + req.policy_id = "1.2.3.4.5" + req.nonce = 42 + req.cert_requested = false + + fac = OpenSSL::Timestamp::Factory.new + fac.gen_time = Time.now + fac.serial_number = 1 + fac.allowed_digests = ["sha1"] + return fac.create_timestamp(ee_key, ts_cert_ee, req), req + end + + def timestamp_direct + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + 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 + fac.allowed_digests = ["sha1"] + return fac.create_timestamp(ee_key, ts_cert_direct, req), req + end + + def timestamp_direct_no_cert + req = OpenSSL::Timestamp::Request.new + req.algorithm = "SHA1" + digest = OpenSSL::Digest::SHA1.new.digest("test") + req.message_imprint = digest + req.policy_id = "1.2.3.4.5" + req.nonce = 42 + req.cert_requested = false + + fac = OpenSSL::Timestamp::Factory.new + fac.gen_time = Time.now + fac.serial_number = 1 + fac.allowed_digests = ["sha1"] + return fac.create_timestamp(ee_key, ts_cert_direct, req), req + end +end + +end diff --git a/test/openssl/test_x509attr.rb b/test/openssl/test_x509attr.rb index c6c48e86ab..2919d23d2d 100644 --- a/test/openssl/test_x509attr.rb +++ b/test/openssl/test_x509attr.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) @@ -79,6 +79,16 @@ class OpenSSL::TestX509Attribute < OpenSSL::TestCase assert_equal true, attr1 == attr2 assert_equal false, attr1 == attr3 end + + def test_marshal + val = OpenSSL::ASN1::Set([ + OpenSSL::ASN1::UTF8String("abc123") + ]) + attr = OpenSSL::X509::Attribute.new("challengePassword", val) + deserialized = Marshal.load(Marshal.dump(attr)) + + assert_equal attr.to_der, deserialized.to_der + end end end diff --git a/test/openssl/test_x509cert.rb b/test/openssl/test_x509cert.rb index 40a5b0ad74..ed0758a6b3 100644 --- a/test/openssl/test_x509cert.rb +++ b/test/openssl/test_x509cert.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) @@ -73,9 +73,12 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase ["basicConstraints","CA:TRUE",true], ["keyUsage","keyCertSign, cRLSign",true], ["subjectKeyIdentifier","hash",false], - ["authorityKeyIdentifier","keyid:always",false], + ["authorityKeyIdentifier","issuer:always,keyid:always",false], ] ca_cert = issue_cert(@ca, @rsa2048, 1, ca_exts, nil, nil) + keyid = get_subject_key_id(ca_cert.to_der, hex: false) + assert_equal keyid, ca_cert.authority_key_identifier + assert_equal keyid, ca_cert.subject_key_identifier ca_cert.extensions.each_with_index{|ext, i| assert_equal(ca_exts[i].first, ext.oid) assert_equal(ca_exts[i].last, ext.critical?) @@ -84,9 +87,10 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase ee1_exts = [ ["keyUsage","Non Repudiation, Digital Signature, Key Encipherment",true], ["subjectKeyIdentifier","hash",false], - ["authorityKeyIdentifier","keyid:always",false], + ["authorityKeyIdentifier","issuer:always,keyid:always",false], ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false], ["subjectAltName","email:ee1@ruby-lang.org",false], + ["authorityInfoAccess","caIssuers;URI:http://www.example.com/caIssuers,OCSP;URI:http://www.example.com/ocsp",false], ] ee1_cert = issue_cert(@ee1, @rsa1024, 2, ee1_exts, ca_cert, @rsa2048) assert_equal(ca_cert.subject.to_der, ee1_cert.issuer.to_der) @@ -94,6 +98,78 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase assert_equal(ee1_exts[i].first, ext.oid) assert_equal(ee1_exts[i].last, ext.critical?) } + assert_nil(ee1_cert.crl_uris) + + ef = OpenSSL::X509::ExtensionFactory.new + ef.config = OpenSSL::Config.parse(<<~_cnf_) + [crlDistPts] + URI.1 = http://www.example.com/crl + URI.2 = ldap://ldap.example.com/cn=ca?certificateRevocationList;binary + _cnf_ + cdp_cert = generate_cert(@ee1, @rsa1024, 3, ca_cert) + ef.subject_certificate = cdp_cert + cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "@crlDistPts")) + cdp_cert.sign(@rsa2048, "sha256") + assert_equal( + ["http://www.example.com/crl", "ldap://ldap.example.com/cn=ca?certificateRevocationList;binary"], + cdp_cert.crl_uris + ) + + ef = OpenSSL::X509::ExtensionFactory.new + aia_cert = generate_cert(@ee1, @rsa1024, 4, ca_cert) + ef.subject_certificate = aia_cert + aia_cert.add_extension( + ef.create_extension( + "authorityInfoAccess", + "caIssuers;URI:http://www.example.com/caIssuers," \ + "caIssuers;URI:ldap://ldap.example.com/cn=ca?authorityInfoAccessCaIssuers;binary," \ + "OCSP;URI:http://www.example.com/ocsp," \ + "OCSP;URI:ldap://ldap.example.com/cn=ca?authorityInfoAccessOcsp;binary", + false + ) + ) + aia_cert.sign(@rsa2048, "sha256") + assert_equal( + ["http://www.example.com/caIssuers", "ldap://ldap.example.com/cn=ca?authorityInfoAccessCaIssuers;binary"], + aia_cert.ca_issuer_uris + ) + assert_equal( + ["http://www.example.com/ocsp", "ldap://ldap.example.com/cn=ca?authorityInfoAccessOcsp;binary"], + aia_cert.ocsp_uris + ) + + no_exts_cert = issue_cert(@ca, @rsa2048, 5, [], nil, nil) + assert_equal nil, no_exts_cert.authority_key_identifier + assert_equal nil, no_exts_cert.subject_key_identifier + assert_equal nil, no_exts_cert.crl_uris + assert_equal nil, no_exts_cert.ca_issuer_uris + assert_equal nil, no_exts_cert.ocsp_uris + end + + def test_invalid_extension + integer = OpenSSL::ASN1::Integer.new(0) + invalid_exts_cert = generate_cert(@ee1, @rsa1024, 1, nil) + ["subjectKeyIdentifier", "authorityKeyIdentifier", "crlDistributionPoints", "authorityInfoAccess"].each do |ext| + invalid_exts_cert.add_extension( + OpenSSL::X509::Extension.new(ext, integer.to_der) + ) + end + + assert_raise(OpenSSL::ASN1::ASN1Error, "invalid extension") { + invalid_exts_cert.authority_key_identifier + } + assert_raise(OpenSSL::ASN1::ASN1Error, "invalid extension") { + invalid_exts_cert.subject_key_identifier + } + assert_raise(OpenSSL::ASN1::ASN1Error, "invalid extension") { + invalid_exts_cert.crl_uris + } + assert_raise(OpenSSL::ASN1::ASN1Error, "invalid extension") { + invalid_exts_cert.ca_issuer_uris + } + assert_raise(OpenSSL::ASN1::ASN1Error, "invalid extension") { + invalid_exts_cert.ocsp_uris + } end def test_sign_and_verify_rsa_sha1 @@ -189,6 +265,17 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase assert_equal false, cert3 == cert4 end + def test_marshal + now = Time.now + cacert = issue_cert(@ca, @rsa1024, 1, [], nil, nil, + not_before: now, not_after: now + 3600) + cert = issue_cert(@ee1, @rsa2048, 2, [], cacert, @rsa1024, + not_before: now, not_after: now + 3600) + deserialized = Marshal.load(Marshal.dump(cert)) + + assert_equal cert.to_der, deserialized.to_der + end + private def certificate_error_returns_false diff --git a/test/openssl/test_x509crl.rb b/test/openssl/test_x509crl.rb index 03fdf64dd4..a6d0adc592 100644 --- a/test/openssl/test_x509crl.rb +++ b/test/openssl/test_x509crl.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) @@ -118,7 +118,7 @@ class OpenSSL::TestX509CRL < OpenSSL::TestCase ["keyUsage", "cRLSign, keyCertSign", true], ] crl_exts = [ - ["authorityKeyIdentifier", "keyid:always", false], + ["authorityKeyIdentifier", "issuer:always,keyid:always", false], ["issuerAltName", "issuer:copy", false], ] @@ -131,6 +131,9 @@ class OpenSSL::TestX509CRL < OpenSSL::TestCase assert_equal("crlNumber", exts[0].oid) assert_equal(false, exts[0].critical?) + expected_keyid = OpenSSL::TestUtils.get_subject_key_id(cert, hex: false) + assert_equal expected_keyid, crl.authority_key_identifier + assert_equal("authorityKeyIdentifier", exts[1].oid) keyid = OpenSSL::TestUtils.get_subject_key_id(cert) assert_match(/^keyid:#{keyid}/, exts[1].value) @@ -155,6 +158,10 @@ class OpenSSL::TestX509CRL < OpenSSL::TestCase assert_equal("issuerAltName", exts[2].oid) assert_equal("email:xyzzy@ruby-lang.org", exts[2].value) assert_equal(false, exts[2].critical?) + + no_ext_crl = issue_crl([], 1, Time.now, Time.now+1600, [], + cert, @rsa2048, OpenSSL::Digest::SHA1.new) + assert_equal nil, no_ext_crl.authority_key_identifier end def test_crlnumber @@ -249,6 +256,22 @@ class OpenSSL::TestX509CRL < OpenSSL::TestCase assert_equal true, rev2 == crl2.revoked[1] end + def test_marshal + now = Time.now + + cacert = issue_cert(@ca, @rsa1024, 1, [], nil, nil) + crl = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1024, "sha256") + rev = OpenSSL::X509::Revoked.new.tap { |rev| + rev.serial = 1 + rev.time = now + } + crl.add_revoked(rev) + deserialized = Marshal.load(Marshal.dump(crl)) + + assert_equal crl.to_der, deserialized.to_der + assert_equal crl.revoked[0].to_der, deserialized.revoked[0].to_der + end + private def crl_error_returns_false diff --git a/test/openssl/test_x509ext.rb b/test/openssl/test_x509ext.rb index 91ce202fec..7ad010d1ed 100644 --- a/test/openssl/test_x509ext.rb +++ b/test/openssl/test_x509ext.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) @@ -86,6 +86,19 @@ class OpenSSL::TestX509Extension < OpenSSL::TestCase assert_equal true, ext1 == ext2 assert_equal false, ext1 == ext3 end + + def test_marshal + ef = OpenSSL::X509::ExtensionFactory.new + ext = ef.create_extension("basicConstraints", "critical, CA:TRUE, pathlen:2") + deserialized = Marshal.load(Marshal.dump(ext)) + + assert_equal ext.to_der, deserialized.to_der + end + + def test_value_der + ext = OpenSSL::X509::Extension.new(@basic_constraints.to_der) + assert_equal @basic_constraints_value.to_der, ext.value_der + end end end diff --git a/test/openssl/test_x509name.rb b/test/openssl/test_x509name.rb index 8a4596ea6e..4ec5db2018 100644 --- a/test/openssl/test_x509name.rb +++ b/test/openssl/test_x509name.rb @@ -1,5 +1,5 @@ # coding: ASCII-8BIT -# frozen_string_literal: false +# frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) @@ -242,16 +242,15 @@ class OpenSSL::TestX509Name < OpenSSL::TestCase assert_match(/^multi-valued RDN is not supported: #{dn_r}/, ex.message) } - bad_dc = "exa#{"pm"}le" # <- typo of "example" [ - ["DC=org,DC=#{bad_dc},CN", "CN"], + ["DC=org,DC=exapmle,CN", "CN"], ["DC=org,DC=example,", ""], - ["DC=org,DC=#{bad_dc},CN=www.example.org;", "CN=www.example.org;"], - ["DC=org,DC=#{bad_dc},CN=#www.example.org", "CN=#www.example.org"], - ["DC=org,DC=#{bad_dc},CN=#777777.example.org", "CN=#777777.example.org"], - ["DC=org,DC=#{bad_dc},CN=\"www.example\".org", "CN=\"www.example\".org"], - ["DC=org,DC=#{bad_dc},CN=www.\"example.org\"", "CN=www.\"example.org\""], - ["DC=org,DC=#{bad_dc},CN=www.\"example\".org", "CN=www.\"example\".org"], + ["DC=org,DC=exapmle,CN=www.example.org;", "CN=www.example.org;"], + ["DC=org,DC=exapmle,CN=#www.example.org", "CN=#www.example.org"], + ["DC=org,DC=exapmle,CN=#777777.example.org", "CN=#777777.example.org"], + ["DC=org,DC=exapmle,CN=\"www.example\".org", "CN=\"www.example\".org"], + ["DC=org,DC=exapmle,CN=www.\"example.org\"", "CN=www.\"example.org\""], + ["DC=org,DC=exapmle,CN=www.\"example\".org", "CN=www.\"example\".org"], ].each{|dn, msg| ex = scanner.call(dn) rescue $! assert_match(/^malformed RDN: .*=>#{Regexp.escape(msg)}/, ex.message) @@ -390,7 +389,7 @@ class OpenSSL::TestX509Name < OpenSSL::TestCase dn.each { |x| name.add_entry(*x) } str = name.to_utf8 - expected = "CN=フー\\, バー,DC=ruby-lang,DC=org".force_encoding("UTF-8") + expected = String.new("CN=フー\\, バー,DC=ruby-lang,DC=org").force_encoding("UTF-8") assert_equal expected, str assert_equal Encoding.find("UTF-8"), str.encoding @@ -403,6 +402,9 @@ class OpenSSL::TestX509Name < OpenSSL::TestCase n2 = OpenSSL::X509::Name.parse_rfc2253 'CN=a' assert_equal n1, n2 + + assert_equal(false, n1 == 'abc') + assert_equal(false, n2 == nil) end def test_spaceship @@ -416,6 +418,9 @@ class OpenSSL::TestX509Name < OpenSSL::TestCase assert_equal(-1, n2 <=> n3) assert_equal(1, n3 <=> n1) assert_equal(1, n3 <=> n2) + assert_equal(nil, n1 <=> 'abc') + assert_equal(nil, n2 <=> 123) + assert_equal(nil, n3 <=> nil) end def name_hash(name) @@ -448,6 +453,13 @@ class OpenSSL::TestX509Name < OpenSSL::TestCase assert_equal false, name0.eql?(name2) end + def test_marshal + name = OpenSSL::X509::Name.new([["DC", "org"], ["DC", "ruby-lang"], ["CN", "bar.ruby-lang.org"]]) + deserialized = Marshal.load(Marshal.dump(name)) + + assert_equal name.to_der, deserialized.to_der + end + def test_dup name = OpenSSL::X509::Name.parse("/CN=ruby-lang.org") assert_equal(name.to_der, name.dup.to_der) diff --git a/test/openssl/test_x509req.rb b/test/openssl/test_x509req.rb index 2c447ccdd5..bace06b347 100644 --- a/test/openssl/test_x509req.rb +++ b/test/openssl/test_x509req.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) @@ -151,6 +151,13 @@ class OpenSSL::TestX509Request < OpenSSL::TestCase assert_equal false, req1 == req3 end + def test_marshal + req = issue_csr(0, @dn, @rsa1024, "sha256") + deserialized = Marshal.load(Marshal.dump(req)) + + assert_equal req.to_der, deserialized.to_der + end + private def request_error_returns_false diff --git a/test/openssl/test_x509store.rb b/test/openssl/test_x509store.rb index 6412249b93..8223c9b7ac 100644 --- a/test/openssl/test_x509store.rb +++ b/test/openssl/test_x509store.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) diff --git a/test/openssl/ut_eof.rb b/test/openssl/ut_eof.rb index bd62fd50f9..cf1f2d423e 100644 --- a/test/openssl/ut_eof.rb +++ b/test/openssl/ut_eof.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require 'test/unit' if defined?(OpenSSL) @@ -18,12 +18,12 @@ module OpenSSL::TestEOF assert_nil(f.read(1)) } open_file("") {|f| - s = "x" + s = +"x" assert_equal("", f.read(nil, s)) assert_equal("", s) } open_file("") {|f| - s = "x" + s = +"x" assert_nil(f.read(10, s)) assert_equal("", s) } @@ -75,12 +75,12 @@ module OpenSSL::TestEOF assert_equal("", f.read(0)) } open_file("a") {|f| - s = "x" + s = +"x" assert_equal("a", f.read(nil, s)) assert_equal("a", s) } open_file("a") {|f| - s = "x" + s = +"x" assert_equal("a", f.read(10, s)) assert_equal("a", s) } diff --git a/test/openssl/utils.rb b/test/openssl/utils.rb index bf19163052..acece97911 100644 --- a/test/openssl/utils.rb +++ b/test/openssl/utils.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true begin require "openssl" @@ -52,15 +52,18 @@ module OpenSSL::TestUtils @file_cache[[category, name]] ||= File.read(File.join(__dir__, "fixtures", category, name + ".pem")) end + + def file_path(category, name) + File.join(__dir__, "fixtures", category, name) + end end module_function - def issue_cert(dn, key, serial, extensions, issuer, issuer_key, - not_before: nil, not_after: nil, digest: "sha256") + def generate_cert(dn, key, serial, issuer, + not_before: nil, not_after: nil) cert = OpenSSL::X509::Certificate.new issuer = cert unless issuer - issuer_key = key unless issuer_key cert.version = 2 cert.serial = serial cert.subject = dn @@ -69,6 +72,16 @@ module OpenSSL::TestUtils now = Time.now cert.not_before = not_before || now - 3600 cert.not_after = not_after || now + 3600 + cert + end + + + def issue_cert(dn, key, serial, extensions, issuer, issuer_key, + not_before: nil, not_after: nil, digest: "sha256") + cert = generate_cert(dn, key, serial, issuer, + not_before: not_before, not_after: not_after) + issuer = cert unless issuer + issuer_key = key unless issuer_key ef = OpenSSL::X509::ExtensionFactory.new ef.subject_certificate = cert ef.issuer_certificate = issuer @@ -107,13 +120,18 @@ module OpenSSL::TestUtils crl end - def get_subject_key_id(cert) + def get_subject_key_id(cert, hex: true) asn1_cert = OpenSSL::ASN1.decode(cert) tbscert = asn1_cert.value[0] pkinfo = tbscert.value[6] publickey = pkinfo.value[1] pkvalue = publickey.value - OpenSSL::Digest::SHA1.hexdigest(pkvalue).scan(/../).join(":").upcase + digest = OpenSSL::Digest::SHA1.digest(pkvalue) + if hex + digest.unpack("H2"*20).join(":").upcase + else + digest + end end def openssl?(major = nil, minor = nil, fix = nil, patch = 0) @@ -189,6 +207,7 @@ class OpenSSL::SSLTestCase < OpenSSL::TestCase def start_server(verify_mode: OpenSSL::SSL::VERIFY_NONE, start_immediately: true, ctx_proc: nil, server_proc: method(:readwrite_loop), + accept_proc: proc{}, ignore_listener_error: false, &block) IO.pipe {|stop_pipe_r, stop_pipe_w| store = OpenSSL::X509::Store.new @@ -222,6 +241,7 @@ class OpenSSL::SSLTestCase < OpenSSL::TestCase readable, = IO.select([ssls, stop_pipe_r]) break if readable.include? stop_pipe_r ssl = ssls.accept + accept_proc.call(ssl) rescue OpenSSL::SSL::SSLError, IOError, Errno::EBADF, Errno::EINVAL, Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET retry if ignore_listener_error @@ -268,7 +288,7 @@ class OpenSSL::SSLTestCase < OpenSSL::TestCase begin timeout = EnvUtil.apply_timeout_scale(30) th.join(timeout) or - th.raise(RuntimeError, "[start_server] thread did not exit in #{ timeout } secs") + th.raise(RuntimeError, "[start_server] thread did not exit in #{timeout} secs") rescue (defined?(MiniTest::Skip) ? MiniTest::Skip : Test::Unit::PendedError) # MiniTest::Skip is for the Ruby tree pend = $! @@ -316,4 +336,62 @@ class OpenSSL::PKeyTestCase < OpenSSL::TestCase end end +module OpenSSL::Certs + include OpenSSL::TestUtils + + module_function + + def ca_cert + ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=Timestamp Root CA") + + ca_exts = [ + ["basicConstraints","CA:TRUE,pathlen:1",true], + ["keyUsage","keyCertSign, cRLSign",true], + ["subjectKeyIdentifier","hash",false], + ["authorityKeyIdentifier","keyid:always",false], + ] + OpenSSL::TestUtils.issue_cert(ca, Fixtures.pkey("rsa2048"), 1, ca_exts, nil, nil) + end + + def ts_cert_direct(key, ca_cert) + dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/OU=Timestamp/CN=Server Direct") + + exts = [ + ["basicConstraints","CA:FALSE",true], + ["keyUsage","digitalSignature, nonRepudiation", true], + ["subjectKeyIdentifier", "hash",false], + ["authorityKeyIdentifier","keyid,issuer", false], + ["extendedKeyUsage", "timeStamping", true] + ] + + OpenSSL::TestUtils.issue_cert(dn, key, 2, exts, ca_cert, Fixtures.pkey("rsa2048")) + end + + def intermediate_cert(key, ca_cert) + dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/OU=Timestamp/CN=Timestamp Intermediate CA") + + exts = [ + ["basicConstraints","CA:TRUE,pathlen:0",true], + ["keyUsage","keyCertSign, cRLSign",true], + ["subjectKeyIdentifier","hash",false], + ["authorityKeyIdentifier","keyid:always",false], + ] + + OpenSSL::TestUtils.issue_cert(dn, key, 3, exts, ca_cert, Fixtures.pkey("rsa2048")) + end + + def ts_cert_ee(key, intermediate, im_key) + dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/OU=Timestamp/CN=Server End Entity") + + exts = [ + ["keyUsage","digitalSignature, nonRepudiation", true], + ["subjectKeyIdentifier", "hash",false], + ["authorityKeyIdentifier","keyid,issuer", false], + ["extendedKeyUsage", "timeStamping", true] + ] + + OpenSSL::TestUtils.issue_cert(dn, key, 4, exts, intermediate, im_key) + end +end + end -- cgit v1.2.3