summaryrefslogtreecommitdiff
path: root/test/openssl
diff options
context:
space:
mode:
Diffstat (limited to 'test/openssl')
-rw-r--r--test/openssl/fixtures/pkey/dh-1.pem13
-rw-r--r--test/openssl/fixtures/pkey/dh2048_ffdhe2048.pem8
-rw-r--r--test/openssl/fixtures/pkey/dsa2048.pem15
-rw-r--r--test/openssl/fixtures/pkey/mldsa65-1.pem88
-rw-r--r--test/openssl/fixtures/pkey/mldsa65-2.pem88
-rw-r--r--test/openssl/fixtures/pkey/p256.pem5
-rw-r--r--test/openssl/fixtures/pkey/rsa-1.pem51
-rw-r--r--test/openssl/fixtures/pkey/rsa-2.pem51
-rw-r--r--test/openssl/fixtures/pkey/rsa-3.pem51
-rw-r--r--test/openssl/fixtures/pkey/rsa2048.pem27
-rw-r--r--test/openssl/ssl_server.rb81
-rw-r--r--test/openssl/test_asn1.rb617
-rw-r--r--test/openssl/test_bn.rb386
-rw-r--r--test/openssl/test_buffering.rb97
-rw-r--r--test/openssl/test_cipher.rb481
-rw-r--r--test/openssl/test_config.rb306
-rw-r--r--test/openssl/test_digest.rb184
-rw-r--r--test/openssl/test_ec.rb113
-rw-r--r--test/openssl/test_engine.rb91
-rw-r--r--test/openssl/test_fips.rb59
-rw-r--r--test/openssl/test_hmac.rb89
-rw-r--r--test/openssl/test_kdf.rb161
-rw-r--r--test/openssl/test_ns_spki.rb30
-rw-r--r--test/openssl/test_ocsp.rb335
-rw-r--r--test/openssl/test_ossl.rb103
-rw-r--r--test/openssl/test_pair.rb543
-rw-r--r--test/openssl/test_pkcs12.rb417
-rw-r--r--test/openssl/test_pkcs7.rb463
-rw-r--r--test/openssl/test_pkey.rb324
-rw-r--r--test/openssl/test_pkey_dh.rb235
-rw-r--r--test/openssl/test_pkey_dsa.rb261
-rw-r--r--test/openssl/test_pkey_ec.rb494
-rw-r--r--test/openssl/test_pkey_rsa.rb551
-rw-r--r--test/openssl/test_provider.rb84
-rw-r--r--test/openssl/test_random.rb19
-rw-r--r--test/openssl/test_ssl.rb2653
-rw-r--r--test/openssl/test_ssl_session.rb443
-rw-r--r--test/openssl/test_ts.rb650
-rw-r--r--test/openssl/test_x509attr.rb98
-rw-r--r--test/openssl/test_x509cert.rb420
-rw-r--r--test/openssl/test_x509crl.rb197
-rw-r--r--test/openssl/test_x509ext.rb101
-rw-r--r--test/openssl/test_x509name.rb255
-rw-r--r--test/openssl/test_x509req.rb135
-rw-r--r--test/openssl/test_x509store.rb452
-rw-r--r--test/openssl/ut_eof.rb141
-rw-r--r--test/openssl/utils.rb411
47 files changed, 11447 insertions, 1430 deletions
diff --git a/test/openssl/fixtures/pkey/dh-1.pem b/test/openssl/fixtures/pkey/dh-1.pem
new file mode 100644
index 0000000000..3340a6a188
--- /dev/null
+++ b/test/openssl/fixtures/pkey/dh-1.pem
@@ -0,0 +1,13 @@
+-----BEGIN DH PARAMETERS-----
+MIICCAKCAgEAvRzXYxY6L2DjeYmm1eowtMDu1it3j+VwFr6s6PRWzc1apMtztr9G
+xZ2mYndUAJLgNLO3n2fUDCYVMB6ZkcekW8Siocof3xWiMA6wqZ6uw0dsE3q7ZX+6
+TLjgSjaXeGvjutvuEwVrFeaUi83bMgfXN8ToxIQVprIF35sYFt6fpbFATKfW7qqi
+P1pQkjmCskU4tztaWvlLh0qg85wuQGnpJaQT3gS30378i0IGbA0EBvJcSpTHYbLa
+nsdI9bfN/ZVgeolVMNMU9/n8R8vRhNPcHuciFwaqS656q+HavCIyxw/LfjSwwFvR
+TngCn0wytRErkzFIXnRKckh8/BpI4S+0+l1NkOwG4WJ55KJ/9OOdZW5o/QCp2bDi
+E0JN1EP/gkSom/prq8JR/yEqtsy99uc5nUxPmzv0IgdcFHZEfiQU7iRggEbx7qfQ
+Ve55XksmmJInmpCy1bSabAEgIKp8Ckt5KLYZ0RgTXUhcEpsxEo6cuAwoSJT5o4Rp
+yG3xow2ozPcqZkvb+d2CHj1sc54w9BVFAjVANEKmRil/9WKz14bu3wxEhOPqC54n
+QojjLcoXSoT66ZUOQnYxTSiLtzoKGPy8cAVPbkBrXz2u2sj5gcvr1JjoGjdHm9/3
+qnqC8fsTz8UndKNIQC337o4K0833bQMzRGl1/qjbAPit2B7E3b6xTZMCAQI=
+-----END DH PARAMETERS-----
diff --git a/test/openssl/fixtures/pkey/dh2048_ffdhe2048.pem b/test/openssl/fixtures/pkey/dh2048_ffdhe2048.pem
new file mode 100644
index 0000000000..9b182b7201
--- /dev/null
+++ b/test/openssl/fixtures/pkey/dh2048_ffdhe2048.pem
@@ -0,0 +1,8 @@
+-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
+87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
+YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
+7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
+ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
+-----END DH PARAMETERS-----
diff --git a/test/openssl/fixtures/pkey/dsa2048.pem b/test/openssl/fixtures/pkey/dsa2048.pem
new file mode 100644
index 0000000000..3f22b22b58
--- /dev/null
+++ b/test/openssl/fixtures/pkey/dsa2048.pem
@@ -0,0 +1,15 @@
+-----BEGIN PRIVATE KEY-----
+MIICXgIBADCCAjYGByqGSM44BAEwggIpAoIBAQDXZhJ/dQoWkQELzjzlx8FtIp96
+voCYe5NY0H8j0jz7GyHpXt41+MteqkZK3/Ah+cNR9uG8iEYArAZ71LcWotfee2Gz
+xdxozr9bRt0POYhO2YIsfMpBrEskPsDH2g/2nFV8l4OJgxU2qZUrF4PN5ha+Mu6u
+sVtN8hjvAvnbf4Pxn0b8NN9f4PJncroUa8acv5WsV85E1RW7NYCefggU4LytYIHg
+euRF9eY9gVCX5MkUgW2xODHIYJhwk/+5lJxG7qUsSahD/nPHO/yoWgdVHq2DkdTq
+KYXkAxx2PJcTBOHTglhE6mgCbEKp8vcfElnBWyCT6QykclZiPXXD2JV829J/Ah0A
+vYa+/G/gUZiomyejVje6UsGoCc+vInxmovOL8QKCAQEAhnKEigYPw6u8JY7v5iGo
+Ylz8qiMFYmaJCwevf3KCjWeEXuNO4OrKdfzkQl1tPuGLioYFfP1A2yGosjdUdLEB
+0JqnzlKxUp+G6RfBj+WYzbgc5hr7t0M+reAJh09/hDzqfxjcgiHstq7mpRXBP8Y7
+iu27s7TRYJNSAYRvWcXNSBEUym3mHBBbZn7VszYooSrn60/iZ8I+VY1UF/fgqhbj
+JfaaZNQCDO9K3Vb3rsXoYd8+bOZIen9uHB+pNjMqhpl4waysqrlpGFeeqdxivH6S
+vkrHLs6/eWVMnS08RdcryoCrI3Bm8mMBKQglDwKLnWLfzG565qEhslzyCd/l9k9a
+cwQfAh0Ao8/g72fSFmo04FizM7DZJSIPqDLjfZu9hLvUFA==
+-----END PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/mldsa65-1.pem b/test/openssl/fixtures/pkey/mldsa65-1.pem
new file mode 100644
index 0000000000..21f08e3ac6
--- /dev/null
+++ b/test/openssl/fixtures/pkey/mldsa65-1.pem
@@ -0,0 +1,88 @@
+-----BEGIN PRIVATE KEY-----
+MIIP/gIBADALBglghkgBZQMEAxIEgg/qMIIP5gQg6Xunp08Ia0w6d93rvBnXnlYf
+ih3Z+9IDZSRIyAGfjbQEgg/A9DPSakjm2xFsVzCHpfwcUwP5dYpJGRYwG7/eSp8b
+/lJOHPmIHjOAC8jN3xS66UXcouWozGXbmieGjLzNs1HjBaJ0CEw51wQOuPLDg8nj
+Pdesnqu5Ct1sNzqz0K57ixyEPrdPI+Vd7XDNaXfOytZ1d4+yFBC6cGpznQ9CiRYm
+PpFEgUZSg3QzFmB0hREkB4FHhTIUZlckclcxNRRTg4UFUIVTdTcThxVyJSFFInZl
+GEUnKAIEcXBUdmgwMQMhRngCFEIFBIB2BVRjEiEwI3FwAQJEEScySFh0UVdQExeB
+ZDYgIUhlFUYxh1g2V2YRdohodCVgBXYIJRJCQHFGRiI0GGQENkgBFwUGNUeAMlh0
+ZgMhIWRmhyhGVEeAUUOHVVKEZHU4BQdwMjEBIIcQeBYFZhKBdwFjBlQECCJEdoYT
+E3FXYlcECCNIZAF4cTaAQwYxBkd2YRE3FTIYRCYgF2SBUiBCJ1ImFjZSFDCIaAY1
+J1ExVDRRVzFGgiUIV1UBCDcVFmcIM4OGeDIXEyZXaCFzBBeBFQQxcFeIJWBmJCdw
+hWMUdYFiNFAmMIEyKIRYIIeIgHA3AmNTElA3gwVmBUUHERE2AAJHUhQTJQAFAXhY
+QmYDdSZHWFhHQXUkRFBWViQ4VERRF1eEN0I2VzR1dUIxg1Uid2NmcDIWdBAzITEA
+AmJgh3JlgwIREgVoIiEoMCADEGSHFIGHUnJVU2I0CGRxaAR3JUVgAnJQOAQiZ2Vh
+OFM3MIEHUmFzQhcEdzSBhBcYVCgEKCIiWHUBhCVxhkBjRzUzJAhDQ2F3eGdUNTEj
+QQcjY3E3BnR2Q4cWIjJohyd2hCUzOIgSUodmIxY1AmaDFVBQEghncQQYQ2QIcjiB
+ZWNQZGZFhHiBIQcXURQQAVg2RBQQd0aDgGQXE3Q4eHY1iERUVoUTcQIjcmh0UVci
+Rhd4MRI0ZUVHNXEAIDQ2cVIGZYMiB1ZQQSYVMlZBQiAwQHJYCEUBExAjF4QwQBMT
+cjdGVCckJTBFJAcXREMzAlYmhRJXgEVSESFYAAJwFXRyKBZCQwcDIFQkJWUFB4gV
+F2GBiEcVEUAmcWEFREA4dEFDQwhFUQUIcHQRMRGHFTA2g0YmZxIiMjMjAQFgcHYF
+IgJlFyA4RhdRgWY1FVEhM4VoUhiIQkcIeCCFZIESZjVWFjNWQEZTNGUoBiR0cgFn
+hDNIAhIRdXQgaGBxNUdyBUIWAniAZIOHYTIyUYd0YXExBjEShGA1GBQ3FBZVEmEw
+EhhEJQNwRjUyQnSIAVNlQjKDdIEBQQhgdSdxc2RjEmRiFyY2RRYicjQCh3AhNSZo
+QzRlGDaGMzCIhRcjIocxN4cwM2gRA2WIN0NyaHR1NRVRAXIkFTR0J2NCCCEHKHYn
+FSYGF3QXBwhDYUZEJmdHEWAlBCIyVRFBR2NSNgJwZgBoAgMjcYQRNAFjI2ZVVgEE
+V3Y1eIREhFc1ABVEGGcUNGRnWAJYOAdlZmgWQlZncWJVNFNkBwOGRiAmJGQGNlIx
+NwNwZ3WBaGIBdiNlMEVXFwFkZRRCE3ZwFxgQWFKHVxOBdxJHJBhSF0d4IlAgIRUY
+UXdCWIQIKDGAhAIoVBAhdXU3AHKCiCBkF4KEhhRyU2JTIIdwdGYSCDcXIWM4QYIY
+NoCFNSESeBMnciUkMxV1RYNzYUSGggMIgWVCM3aAMRcCITMgKERIEjgiUkIlNoUT
+JkMjMzc3dxV1WGOCZRJSMzIhGIAgBEgicDM2CFclACBlBoYyhyNgJoOIExIoY3Qh
+EFRQhFYBZoJERhNoQCIUaAIARgiCQSUXYkUgR4RnYAczh0ECOCeGdBJXExNohzZH
+GFMAE2MAUUZ3NzF2FldoGCKEIzJzGDFHd3KAU4ZHUichAYUmEYVAdShYFlh3FIIl
+UzQYdScUdlAAglVjBiNCJYGBKHVTNEJBMBAQEFQhJSI0diJ0dFA3KBIAg2RxcXIA
+cWMBcoIEQBgAUBUyEDUwcQYTVwSCMjBCVEQiQWMThDhVE2YWZBNQhVZzY4cBhIZh
+IIBBQihmh4VgJjZkJ2J1VlAB5kLlGDaGXIOc++2QqMCGeB9FnTYpHFoSXQrOjQhS
+tfTln0rEelihhKhi3Bu8mdhyTSFZTShsQidqlN1/U50KnMTqII7r9QltUZqPH9sW
+CswVssxnVe1GAXY/LqJPN5DEN2ZEMoAgmxLbGYB5YdKID1lj5zquaCqpDUGDI/Wi
+zJ5xpFzn7nGJwedU2MBqcqlIVJg8VeIInkLL/v3y2uqD4+pewW8OewqosJOfBgjI
+RH1FXcdGbnqKJk1YZ8iwVMTNoU9U8gGDI5kk/dWWqqAdxaVrsmevmNRp6wtibFG6
+FxrSRb7hOP8IVv7TkMA+Cv4MRs0UhYJ2W8x0G0LxP4M+m3cAJkaHyHDda1NHjfTV
+zG9hWK8Ad7t+F9hw5++KBPlkW+/sX4eYpOlC/XjpMp1W6WIr9oIbRp6RXKNUuBXQ
+58uNAmq6peDenbwsmiBKG+RWntbMxtjOM9bo/JXMV9dIT/KIbljl2C/4TRbWy0D3
+KfZlvAHpiw2oH/vaLUFbIg7sK823keZA/uSFJ2KSPBVC6+AYX5tM/P/KKLJmFoVY
+U7h4F/SDCbOt5PJu9yg+fN6ftBT3a2723TAx7M8+WqPrvvOB5UFJRNCcpwnjqriz
+8ENLgoze5wm2sIk+QvB15tFG0n3+9eTOjD+q0dJDSxq5xAuAalBoFp7vSt2x1UO/
+4Nf/jXvJT2nXjR7QgtabQRzKqbP5lHVtL0BCJeGFlbGeuAGIfNuVY0809E66sWDo
+S18hNAfp9jKe0aU7MxGU6RvCB8vLK+cld/RzujyK8C307PJdzwCLEYIBMC3SvBcQ
+9CpJFuPIcEVoM1RiThw/l1MAaKJ3y73ekU5p+Dd2CN4P4pCDSiVj/PAOW1c7iA2A
+QBVuCfPMYJyW93toHaqpaZuD9VN3OKbtJvuMWCOIN59ERFvttv5CNQ01rhgCv3dZ
+kkkFrJsmFcwsgMW1JIGozMKywFzi9yDWUL6j/ZCc8xqkfP9fYPBBTcSsUvWV9Zq6
+AU22B9j6/EUP8crw0VViacbEJy2sJgIumEQiVlVNavorpPwjtWpVQFvsBrDm6X80
+jk9H/yTKrrR6LaTH7999s/88jOLszmbX7Yt8VmMkkliml2rd6UqG9D6zq2xEj1IV
+6ZT2zhVe+wHNmpkr1kYTIVsLXrHNpCWEQeHscSCzz/lg+aOv8kSfFqGq2VFjxnts
+7Z88TjxzIOQk14Lzkgl0PCyHXau8i2bteCOimqRYEd3ihNcC8U9MXLYrOiv24oXM
+RpkzoHGOtZoAie6k1Xj6aDwIl2mTBHg5BF0A4U+d/z7wS8Gr9nEc574s9OyKAZn6
+5L/1GgpWa0e2buxn8fkPAMptY0773prqKqwvV/SWdvUJ4B4HLNLsU70+N4XAZlRS
+7saNkghBkrD/WobJQwa/9OWWa5Gw6Frurr0AmnBU+EN7u6niFwARsa9f1yjuW8IJ
+tLD7H+Yu2bGouHWpeoXQHwqFxl+me7rQ/ePvOYQk/SzlzvroaqAGECrDoHU3kzhn
+rhJLueA9b0j3u0/+CQaNOFPWb6GAjmafVWpBcXtOSkHVUXitclURlITEwe47tn+g
+XffSw3k1q3XBKkFkJQrgPa2IbpAWvFKA7rOInY/b8N/lCI0bZAei2OOR2/MLifkx
+F3L8daWXslp7QSlIjUXwtgdD6CwQsEui99dZvTYlSxzUKC9nsF0oPYxWpHAcuoCE
+pQCR1CuyuGkDCaod2VNWqWOcZ5QXjEtbVHFO8qJdePJPKWV+0YcltaR4X5q2Pts9
+4a0SJMSM/tXrUi9g9RjjnB+F++rc4a5FrQ2r7FDXudk7NUEoJPyBvBDeowiSmXvv
+SHrL6WsQgf8n5sZxfA0uqs+8OMSLLNj72CSoBQMJNVJgYQkSyBuHl6Zk59+k/WeJ
+wX1qevXwaC6JrdF+naRcp16tNv+7230GPO1d3+X3zZOtAEuAzk6kw3da8Y15qZ5j
+FqzXPO8TsURyOf4Fp+kxpETSQ+mf8Do0hWzUYE8Cj2EFcwuE2Q7+c1ZHAFpQNk1j
+T4vR//yCYjO8/lY0yDV7iDzkT36twyvKZ/cMxC001RSNmtr3QNWWkRRDBWCSwnjW
++cn408gCVFPwVUOBwUr6aOeUY+fCcvWnYPCDj7ggdS5wEoUk+xrk4v2kU2gAH/mp
+DqhFNouIcExoNW5j7j0w0YKnZtZJ9pviiM0EXS6vhk4ayxI2pi3VOqL2RhoNleAa
+bTcCQ71wOxqpp4khssLcOsUR8trpadlvZJ9sc1ksUfoOz/pMI9Yj0IWctbuiriJp
+l193X2sPzVMn3MaEt+XPrsX5wOogbQAfSJyY8pfCnZuhVLoZDpJADJxou0EhP+wH
+p9yZc5GZosFgDJvTEhZfUmituLW4+op1FLJqA/LQSxBVz51OnmtzpgJLyR0ctTLG
+9CcYbFTrzltPlOTHVjVW4rD9jyoLjLdfUf9qG65qVpGBisV+wD+SI6P0x5rhN7Dt
+nC0YNZZ0cYyN24xw8Bxzcc9RkY8/MFfbTXOG43Uuh7fkPIdY2NQSUK2tkfiMdPgu
+zlR1HoZHBrCcsQXJH0OhbuJ6Uwzm340Upj4b/eykq+uUcVY8PAUHSg6mwKy+E4yp
+Za5Z5U50Kv9rFcE9Hwh09fGfdUrKTCFxoKrqfeW+ogTXJHQR5A41r9PP1l7/9Bp7
+P+UtdjJtAHzTO1r7/dckvghBslqhNBzA55wtWEmjMFh4Mm3lBMvBGrCelKPtaOrb
+CYlv4eqGZMEeE3VoEKO3QnXU/dqJvhwQhjCcgxPtOzm9eSrofTvXa4xIMKyuNF2z
+F6K0S5o3I+pBUInshXHWwN1pAT1R4FRYAUTv9mZbhLP+MWgPIMrdWWHAMDL5DeBH
+G4AT5RQbzIHjQ12fJq30m1LajjLlL+mF5og+plMgEGOCJMHZyT2NcNb7gFHWk2mh
+JmO/qxdXQ1FQ/oEf+gNmfgdlw/N6TY7PvmkVfdkhgp/zQLcGgJ33gj0gy4Jr284G
+EhmeOGQflVsMFDqrAgjCEEJSLl/+FXuDfJjTixyly/yTTJCAeiEXsSW4xDisYZyR
+dmEXPtx7eyelJjbsM2yMTNacvCA8TCywTqxYMlYF45kHhTrnQoMvx83U0vqB+ALA
+JsGGrYQZ3tx9j8ae27b0rkSrccFYhKCXI/mwEZcZ6SG3q6/PhHWQOaie2EkuVLDq
+YAK0ZjlTv0znE1OVN3ovKAqq8ga/y5tOKXREo/i/SRPj4aHel4Lky26+Nmm+t+E2
+CL3SBcqhBC45qIB27kdsqBsnCfSzm1fQsy6jivCEDneLTLNoltDyXunSwyLP/7HI
+qclQDtLzvC0mHUNlhcds4I20
+-----END PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/mldsa65-2.pem b/test/openssl/fixtures/pkey/mldsa65-2.pem
new file mode 100644
index 0000000000..0ae64c2c5d
--- /dev/null
+++ b/test/openssl/fixtures/pkey/mldsa65-2.pem
@@ -0,0 +1,88 @@
+-----BEGIN PRIVATE KEY-----
+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
+qX4cmZjkE9fKw15cRjvQt1K1
+-----END PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/p256.pem b/test/openssl/fixtures/pkey/p256.pem
new file mode 100644
index 0000000000..97c97d9f9d
--- /dev/null
+++ b/test/openssl/fixtures/pkey/p256.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIID49FDqcf1O1eO8saTgG70UbXQw9Fqwseliit2aWhH1oAoGCCqGSM49
+AwEHoUQDQgAEFglk2c+oVUIKQ64eZG9bhLNPWB7lSZ/ArK41eGy5wAzU/0G51Xtt
+CeBUl+MahZtn9fO1JKdF4qJmS39dXnpENg==
+-----END EC PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/rsa-1.pem b/test/openssl/fixtures/pkey/rsa-1.pem
new file mode 100644
index 0000000000..bd5a624f6b
--- /dev/null
+++ b/test/openssl/fixtures/pkey/rsa-1.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/rsa-2.pem b/test/openssl/fixtures/pkey/rsa-2.pem
new file mode 100644
index 0000000000..e4fd4f4370
--- /dev/null
+++ b/test/openssl/fixtures/pkey/rsa-2.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/rsa-3.pem b/test/openssl/fixtures/pkey/rsa-3.pem
new file mode 100644
index 0000000000..6c9c9cedd2
--- /dev/null
+++ b/test/openssl/fixtures/pkey/rsa-3.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/rsa2048.pem b/test/openssl/fixtures/pkey/rsa2048.pem
new file mode 100644
index 0000000000..ac89cd88ed
--- /dev/null
+++ b/test/openssl/fixtures/pkey/rsa2048.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAuV9ht9J7k4NBs38jOXvvTKY9gW8nLICSno5EETR1cuF7i4pN
+s9I1QJGAFAX0BEO4KbzXmuOvfCpD3CU+Slp1enenfzq/t/e/1IRW0wkJUJUFQign
+4CtrkJL+P07yx18UjyPlBXb81ApEmAB5mrJVSrWmqbjs07JbuS4QQGGXLc+Su96D
+kYKmSNVjBiLxVVSpyZfAY3hD37d60uG+X8xdW5v68JkRFIhdGlb6JL8fllf/A/bl
+NwdJOhVr9mESHhwGjwfSeTDPfd8ZLE027E5lyAVX9KZYcU00mOX+fdxOSnGqS/8J
+DRh0EPHDL15RcJjV2J6vZjPb0rOYGDoMcH+94wIDAQABAoIBAAzsamqfYQAqwXTb
+I0CJtGg6msUgU7HVkOM+9d3hM2L791oGHV6xBAdpXW2H8LgvZHJ8eOeSghR8+dgq
+PIqAffo4x1Oma+FOg3A0fb0evyiACyrOk+EcBdbBeLo/LcvahBtqnDfiUMQTpy6V
+seSoFCwuN91TSCeGIsDpRjbG1vxZgtx+uI+oH5+ytqJOmfCksRDCkMglGkzyfcl0
+Xc5CUhIJ0my53xijEUQl19rtWdMnNnnkdbG8PT3LZlOta5Do86BElzUYka0C6dUc
+VsBDQ0Nup0P6rEQgy7tephHoRlUGTYamsajGJaAo1F3IQVIrRSuagi7+YpSpCqsW
+wORqorkCgYEA7RdX6MDVrbw7LePnhyuaqTiMK+055/R1TqhB1JvvxJ1CXk2rDL6G
+0TLHQ7oGofd5LYiemg4ZVtWdJe43BPZlVgT6lvL/iGo8JnrncB9Da6L7nrq/+Rvj
+XGjf1qODCK+LmreZWEsaLPURIoR/Ewwxb9J2zd0CaMjeTwafJo1CZvcCgYEAyCgb
+aqoWvUecX8VvARfuA593Lsi50t4MEArnOXXcd1RnXoZWhbx5rgO8/ATKfXr0BK/n
+h2GF9PfKzHFm/4V6e82OL7gu/kLy2u9bXN74vOvWFL5NOrOKPM7Kg+9I131kNYOw
+Ivnr/VtHE5s0dY7JChYWE1F3vArrOw3T00a4CXUCgYEA0SqY+dS2LvIzW4cHCe9k
+IQqsT0yYm5TFsUEr4sA3xcPfe4cV8sZb9k/QEGYb1+SWWZ+AHPV3UW5fl8kTbSNb
+v4ng8i8rVVQ0ANbJO9e5CUrepein2MPL0AkOATR8M7t7dGGpvYV0cFk8ZrFx0oId
+U0PgYDotF/iueBWlbsOM430CgYEAqYI95dFyPI5/AiSkY5queeb8+mQH62sdcCCr
+vd/w/CZA/K5sbAo4SoTj8dLk4evU6HtIa0DOP63y071eaxvRpTNqLUOgmLh+D6gS
+Cc7TfLuFrD+WDBatBd5jZ+SoHccVrLR/4L8jeodo5FPW05A+9gnKXEXsTxY4LOUC
+9bS4e1kCgYAqVXZh63JsMwoaxCYmQ66eJojKa47VNrOeIZDZvd2BPVf30glBOT41
+gBoDG3WMPZoQj9pb7uMcrnvs4APj2FIhMU8U15LcPAj59cD6S6rWnAxO8NFK7HQG
+4Jxg3JNNf8ErQoCHb1B3oVdXJkmbJkARoDpBKmTCgKtP8ADYLmVPQw==
+-----END RSA PRIVATE KEY-----
diff --git a/test/openssl/ssl_server.rb b/test/openssl/ssl_server.rb
deleted file mode 100644
index 6e620629c5..0000000000
--- a/test/openssl/ssl_server.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-require "socket"
-require "thread"
-require "openssl"
-require File.join(File.dirname(__FILE__), "utils.rb")
-
-def get_pem(io=$stdin)
- buf = ""
- while line = io.gets
- if /^-----BEGIN / =~ line
- buf << line
- break
- end
- end
- while line = io.gets
- buf << line
- if /^-----END / =~ line
- break
- end
- end
- return buf
-end
-
-def make_key(pem)
- begin
- return OpenSSL::PKey::RSA.new(pem)
- rescue
- return OpenSSL::PKey::DSA.new(pem)
- end
-end
-
-ca_cert = OpenSSL::X509::Certificate.new(get_pem)
-ssl_cert = OpenSSL::X509::Certificate.new(get_pem)
-ssl_key = make_key(get_pem)
-port = Integer(ARGV.shift)
-verify_mode = Integer(ARGV.shift)
-start_immediately = (/yes/ =~ ARGV.shift)
-
-store = OpenSSL::X509::Store.new
-store.add_cert(ca_cert)
-store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
-ctx = OpenSSL::SSL::SSLContext.new
-ctx.cert_store = store
-#ctx.extra_chain_cert = [ ca_cert ]
-ctx.cert = ssl_cert
-ctx.key = ssl_key
-ctx.verify_mode = verify_mode
-
-Socket.do_not_reverse_lookup = true
-tcps = nil
-100.times{|i|
- begin
- tcps = TCPServer.new("0.0.0.0", port+i)
- port = port + i
- break
- rescue Errno::EADDRINUSE
- next
- end
-}
-ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx)
-ssls.start_immediately = start_immediately
-
-$stdout.sync = true
-$stdout.puts Process.pid
-$stdout.puts port
-
-loop do
- ssl = ssls.accept rescue next
- Thread.start{
- q = Queue.new
- th = Thread.start{ ssl.write(q.shift) while true }
- while line = ssl.gets
- if line =~ /^STARTTLS$/
- ssl.accept
- next
- end
- q.push(line)
- end
- th.kill if q.empty?
- ssl.close
- }
-end
diff --git a/test/openssl/test_asn1.rb b/test/openssl/test_asn1.rb
index f196bc48fb..0293813a8d 100644
--- a/test/openssl/test_asn1.rb
+++ b/test/openssl/test_asn1.rb
@@ -1,14 +1,12 @@
-begin
- require "openssl"
- require File.join(File.dirname(__FILE__), "utils.rb")
-rescue LoadError
-end
-require 'test/unit'
+# frozen_string_literal: true
+require_relative 'utils'
+
+if defined?(OpenSSL)
-class OpenSSL::TestASN1 < Test::Unit::TestCase
- def test_decode
+class OpenSSL::TestASN1 < OpenSSL::TestCase
+ def test_decode_x509_certificate
subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA")
- key = OpenSSL::TestUtils::TEST_KEY_RSA1024
+ key = Fixtures.pkey("rsa-1")
now = Time.at(Time.now.to_i) # suppress usec
s = 0xdeadbeafdeadbeafdeadbeafdeadbeaf
exts = [
@@ -16,9 +14,9 @@ class OpenSSL::TestASN1 < Test::Unit::TestCase
["keyUsage","keyCertSign, cRLSign",true],
["subjectKeyIdentifier","hash",false],
]
- dgst = OpenSSL::Digest::SHA1.new
+ dgst = OpenSSL::Digest.new('SHA256')
cert = OpenSSL::TestUtils.issue_cert(
- subj, key, s, now, now+3600, exts, nil, nil, dgst)
+ subj, key, s, exts, nil, nil, digest: dgst, not_before: now, not_after: now+3600)
asn1 = OpenSSL::ASN1.decode(cert)
@@ -44,7 +42,7 @@ class OpenSSL::TestASN1 < Test::Unit::TestCase
assert_equal(OpenSSL::ASN1::Sequence, sig.class)
assert_equal(2, sig.value.size)
assert_equal(OpenSSL::ASN1::ObjectId, sig.value[0].class)
- assert_equal("1.2.840.113549.1.1.5", sig.value[0].oid)
+ assert_equal("1.2.840.113549.1.1.11", sig.value[0].oid)
assert_equal(OpenSSL::ASN1::Null, sig.value[1].class)
dn = tbs_cert.value[3] # issuer
@@ -132,9 +130,9 @@ class OpenSSL::TestASN1 < Test::Unit::TestCase
assert_equal(OpenSSL::ASN1::Sequence, spkey.class)
assert_equal(2, spkey.value.size)
assert_equal(OpenSSL::ASN1::Integer, spkey.value[0].class)
- assert_equal(143085709396403084580358323862163416700436550432664688288860593156058579474547937626086626045206357324274536445865308750491138538454154232826011964045825759324933943290377903384882276841880081931690695505836279972214003660451338124170055999155993192881685495391496854691199517389593073052473319331505702779271, spkey.value[0].value)
+ assert_equal(cert.public_key.n, spkey.value[0].value)
assert_equal(OpenSSL::ASN1::Integer, spkey.value[1].class)
- assert_equal(65537, spkey.value[1].value)
+ assert_equal(cert.public_key.e, spkey.value[1].value)
extensions = tbs_cert.value[7]
assert_equal(:CONTEXT_SPECIFIC, extensions.tag_class)
@@ -169,10 +167,10 @@ class OpenSSL::TestASN1 < Test::Unit::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
+ str = +"\000"; str[0] = 0b00000110.chr
assert_equal(str, extv.value)
- ext = extensions.value[0].value[2] # subjetKeyIdentifier
+ ext = extensions.value[0].value[2] # subjectKeyIdentifier
assert_equal(OpenSSL::ASN1::Sequence, ext.class)
assert_equal(2, ext.value.size)
assert_equal(OpenSSL::ASN1::ObjectId, ext.value[0].class)
@@ -180,7 +178,7 @@ class OpenSSL::TestASN1 < Test::Unit::TestCase
assert_equal(OpenSSL::ASN1::OctetString, ext.value[1].class)
extv = OpenSSL::ASN1.decode(ext.value[1].value)
assert_equal(OpenSSL::ASN1::OctetString, extv.class)
- sha1 = OpenSSL::Digest::SHA1.new
+ sha1 = OpenSSL::Digest.new('SHA1')
sha1.update(pkey.value[1].value)
assert_equal(sha1.digest, extv.value)
@@ -191,7 +189,588 @@ class OpenSSL::TestASN1 < Test::Unit::TestCase
assert_equal(OpenSSL::ASN1::Null, pkey.value[0].value[1].class)
assert_equal(OpenSSL::ASN1::BitString, sig_val.class)
- cululated_sig = key.sign(OpenSSL::Digest::SHA1.new, tbs_cert.to_der)
+ cululated_sig = key.sign(OpenSSL::Digest.new('SHA256'), tbs_cert.to_der)
assert_equal(cululated_sig, sig_val.value)
end
-end if defined?(OpenSSL)
+
+ def test_decode_all
+ raw = B(%w{ 02 01 01 02 01 02 02 01 03 })
+ ary = OpenSSL::ASN1.decode_all(raw)
+ assert_equal(3, ary.size)
+ ary.each_with_index do |asn1, i|
+ assert_universal(OpenSSL::ASN1::INTEGER, asn1)
+ assert_equal(i + 1, asn1.value)
+ end
+ end
+
+ def test_object_id_register
+ oid = "1.2.34.56789"
+ pend "OID 1.2.34.56789 is already registered" if OpenSSL::ASN1::ObjectId(oid).sn
+ assert_equal true, OpenSSL::ASN1::ObjectId.register(oid, "ossl-test-sn", "ossl-test-ln")
+ obj = OpenSSL::ASN1::ObjectId(oid)
+ assert_equal oid, obj.oid
+ assert_equal "ossl-test-sn", obj.sn
+ assert_equal "ossl-test-ln", obj.ln
+ obj = encode_decode_test B(%w{ 06 05 2A 22 83 BB 55 }), OpenSSL::ASN1::ObjectId("ossl-test-ln")
+ assert_equal "ossl-test-sn", obj.value
+ end
+
+ def test_end_of_content
+ encode_decode_test B(%w{ 00 00 }), OpenSSL::ASN1::EndOfContent.new
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 00 01 00 }))
+ }
+ end
+
+ def test_boolean
+ encode_decode_test B(%w{ 01 01 00 }), OpenSSL::ASN1::Boolean.new(false)
+ encode_decode_test B(%w{ 01 01 FF }), OpenSSL::ASN1::Boolean.new(true)
+ decode_test B(%w{ 01 01 01 }), OpenSSL::ASN1::Boolean.new(true)
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 01 02 00 00 }))
+ }
+ end
+
+ def test_integer
+ encode_decode_test B(%w{ 02 01 00 }), OpenSSL::ASN1::Integer.new(0)
+ encode_decode_test B(%w{ 02 01 48 }), OpenSSL::ASN1::Integer.new(72)
+ encode_decode_test B(%w{ 02 02 00 80 }), OpenSSL::ASN1::Integer.new(128)
+ encode_decode_test B(%w{ 02 01 81 }), OpenSSL::ASN1::Integer.new(-127)
+ encode_decode_test B(%w{ 02 01 80 }), OpenSSL::ASN1::Integer.new(-128)
+ encode_decode_test B(%w{ 02 01 FF }), OpenSSL::ASN1::Integer.new(-1)
+ encode_decode_test B(%w{ 02 09 01 00 00 00 00 00 00 00 00 }), OpenSSL::ASN1::Integer.new(2 ** 64)
+ encode_decode_test B(%w{ 02 09 FF 00 00 00 00 00 00 00 00 }), OpenSSL::ASN1::Integer.new(-(2 ** 64))
+ # FIXME: OpenSSL < 1.1.0 does not fail
+ # assert_raise(OpenSSL::ASN1::ASN1Error) {
+ # OpenSSL::ASN1.decode(B(%w{ 02 02 00 7F }))
+ # }
+ # assert_raise(OpenSSL::ASN1::ASN1Error) {
+ # OpenSSL::ASN1.decode(B(%w{ 02 02 FF 80 }))
+ # }
+ end
+
+ def test_enumerated
+ encode_decode_test B(%w{ 0A 01 00 }), OpenSSL::ASN1::Enumerated.new(0)
+ encode_decode_test B(%w{ 0A 01 48 }), OpenSSL::ASN1::Enumerated.new(72)
+ encode_decode_test B(%w{ 0A 02 00 80 }), OpenSSL::ASN1::Enumerated.new(128)
+ encode_decode_test B(%w{ 0A 09 01 00 00 00 00 00 00 00 00 }), OpenSSL::ASN1::Enumerated.new(2 ** 64)
+ end
+
+ def test_bitstring
+ encode_decode_test B(%w{ 03 01 00 }), OpenSSL::ASN1::BitString.new(B(%w{}))
+ encode_decode_test B(%w{ 03 02 00 01 }), OpenSSL::ASN1::BitString.new(B(%w{ 01 }))
+ obj = OpenSSL::ASN1::BitString.new(B(%w{ F0 }))
+ obj.unused_bits = 4
+ encode_decode_test B(%w{ 03 02 04 F0 }), obj
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 03 00 }))
+ }
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 03 03 08 FF 00 }))
+ }
+ # OpenSSL does not seem to prohibit this, though X.690 8.6.2.3 (15/08) does
+ # assert_raise(OpenSSL::ASN1::ASN1Error) {
+ # OpenSSL::ASN1.decode(B(%w{ 03 01 04 }))
+ # }
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ obj = OpenSSL::ASN1::BitString.new(B(%w{ FF FF }))
+ obj.unused_bits = 8
+ obj.to_der
+ }
+ end
+
+ def test_string_basic
+ test = -> (tag, klass) {
+ encode_decode_test tag.chr + B(%w{ 00 }), klass.new(B(%w{}))
+ encode_decode_test tag.chr + B(%w{ 02 00 01 }), klass.new(B(%w{ 00 01 }))
+ }
+ test.(4, OpenSSL::ASN1::OctetString)
+ test.(12, OpenSSL::ASN1::UTF8String)
+ test.(18, OpenSSL::ASN1::NumericString)
+ test.(19, OpenSSL::ASN1::PrintableString)
+ test.(20, OpenSSL::ASN1::T61String)
+ test.(21, OpenSSL::ASN1::VideotexString)
+ test.(22, OpenSSL::ASN1::IA5String)
+ test.(25, OpenSSL::ASN1::GraphicString)
+ test.(26, OpenSSL::ASN1::ISO64String)
+ test.(27, OpenSSL::ASN1::GeneralString)
+ test.(28, OpenSSL::ASN1::UniversalString)
+ test.(30, OpenSSL::ASN1::BMPString)
+ end
+
+ def test_null
+ encode_decode_test B(%w{ 05 00 }), OpenSSL::ASN1::Null.new(nil)
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 05 01 00 }))
+ }
+ end
+
+ def test_object_identifier
+ obj = encode_decode_test B(%w{ 06 01 00 }), OpenSSL::ASN1::ObjectId.new("0.0".b)
+ assert_equal "0.0", obj.oid
+ assert_nil obj.sn
+ assert_nil obj.ln
+ assert_equal obj.oid, obj.value
+ encode_decode_test B(%w{ 06 01 28 }), OpenSSL::ASN1::ObjectId.new("1.0".b)
+ encode_decode_test B(%w{ 06 03 88 37 03 }), OpenSSL::ASN1::ObjectId.new("2.999.3".b)
+ encode_decode_test B(%w{ 06 05 2A 22 83 BB 55 }), OpenSSL::ASN1::ObjectId.new("1.2.34.56789".b)
+ obj = encode_decode_test B(%w{ 06 09 60 86 48 01 65 03 04 02 01 }), OpenSSL::ASN1::ObjectId.new("sha256")
+ assert_equal "2.16.840.1.101.3.4.2.1", obj.oid
+ assert_equal "SHA256", obj.sn
+ assert_equal "sha256", obj.ln
+ assert_equal obj.sn, obj.value
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 06 00 }))
+ }
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 06 01 80 }))
+ }
+ assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1::ObjectId.new("3.0".b).to_der }
+ assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1::ObjectId.new("0.40".b).to_der }
+
+ oid = (0...100).to_a.join(".").b
+ obj = OpenSSL::ASN1::ObjectId.new(oid)
+ assert_equal oid, obj.oid
+ end
+
+ def test_object_identifier_equality
+ 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_equal true, a == b
+ end
+
+ ski.each do |b|
+ assert_equal false, a == b
+ end
+ end
+
+ obj1 = OpenSSL::ASN1::ObjectId.new("1.2.34.56789.10")
+ obj2 = OpenSSL::ASN1::ObjectId.new("1.2.34.56789.10")
+ obj3 = OpenSSL::ASN1::ObjectId.new("1.2.34.56789.11")
+ omit "OID 1.2.34.56789.10 is registered" if obj1.sn
+ assert_equal true, obj1 == obj2
+ assert_equal false, obj1 == obj3
+
+ assert_equal false, OpenSSL::ASN1::ObjectId.new("authorityKeyIdentifier") == nil
+ end
+
+ def test_sequence
+ encode_decode_test B(%w{ 30 00 }), OpenSSL::ASN1::Sequence.new([])
+ encode_decode_test B(%w{ 30 07 05 00 30 00 04 01 00 }), OpenSSL::ASN1::Sequence.new([
+ OpenSSL::ASN1::Null.new(nil),
+ OpenSSL::ASN1::Sequence.new([]),
+ OpenSSL::ASN1::OctetString.new(B(%w{ 00 }))
+ ])
+
+ expected = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::OctetString.new(B(%w{ 00 }))])
+ expected.indefinite_length = true
+ encode_decode_test B(%w{ 30 80 04 01 00 00 00 }), expected
+
+ # OpenSSL::ASN1::EndOfContent can only be at the end
+ obj = OpenSSL::ASN1::Sequence.new([
+ OpenSSL::ASN1::EndOfContent.new,
+ OpenSSL::ASN1::OctetString.new(B(%w{ 00 })),
+ OpenSSL::ASN1::EndOfContent.new,
+ ])
+ obj.indefinite_length = true
+ assert_raise(OpenSSL::ASN1::ASN1Error) { obj.to_der }
+
+ # The last EOC in value is ignored if indefinite length form is used
+ expected = OpenSSL::ASN1::Sequence.new([
+ OpenSSL::ASN1::OctetString.new(B(%w{ 00 })),
+ OpenSSL::ASN1::EndOfContent.new
+ ])
+ expected.indefinite_length = true
+ encode_test B(%w{ 30 80 04 01 00 00 00 }), expected
+
+ # Missing EOC at the end of contents octets
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 30 80 01 01 FF }))
+ }
+ end
+
+ def test_set
+ encode_decode_test B(%w{ 31 00 }), OpenSSL::ASN1::Set.new([])
+ encode_decode_test B(%w{ 31 07 05 00 30 00 04 01 00 }), OpenSSL::ASN1::Set.new([
+ OpenSSL::ASN1::Null.new(nil),
+ OpenSSL::ASN1::Sequence.new([]),
+ OpenSSL::ASN1::OctetString.new(B(%w{ 00 }))
+ ])
+ expected = OpenSSL::ASN1::Set.new([OpenSSL::ASN1::OctetString.new(B(%w{ 00 }))])
+ expected.indefinite_length = true
+ encode_decode_test B(%w{ 31 80 04 01 00 00 00 }), expected
+ end
+
+ def test_utctime
+ encode_decode_test B(%w{ 17 0D }) + "160908234339Z".b,
+ OpenSSL::ASN1::UTCTime.new(Time.utc(2016, 9, 8, 23, 43, 39))
+
+ # 1950-2049 range is assumed to match RFC 5280's expectation
+ encode_decode_test B(%w{ 17 0D }) + "490908234339Z".b,
+ OpenSSL::ASN1::UTCTime.new(Time.utc(2049, 9, 8, 23, 43, 39))
+ encode_decode_test B(%w{ 17 0D }) + "500908234339Z".b,
+ OpenSSL::ASN1::UTCTime.new(Time.utc(1950, 9, 8, 23, 43, 39))
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1::UTCTime.new(Time.new(2049, 12, 31, 23, 0, 0, "-04:00")).to_der
+ }
+
+ # UTC offset (BER): ASN1_TIME_to_tm() may or may not support it
+ # decode_test B(%w{ 17 11 }) + "500908234339+0930".b,
+ # OpenSSL::ASN1::UTCTime.new(Time.new(1950, 9, 8, 23, 43, 39, "+09:30"))
+ # decode_test B(%w{ 17 0F }) + "5009082343-0930".b,
+ # OpenSSL::ASN1::UTCTime.new(Time.new(1950, 9, 8, 23, 43, 0, "-09:30"))
+
+ # Seconds is omitted (BER)
+ # decode_test B(%w{ 18 0D }) + "201612081934Z".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 0))
+
+ # Fractional seconds is not allowed in UTCTime
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 17 0F }) + "160908234339.5Z".b)
+ }
+
+ # Missing "Z"
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 17 0C }) + "500908234339".b)
+ }
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 17 0D }) + "500908234339Y".b)
+ }
+ end
+
+ def test_generalizedtime
+ encode_decode_test B(%w{ 18 0F }) + "20161208193429Z".b,
+ OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 29))
+ encode_decode_test B(%w{ 18 0F }) + "99990908234339Z".b,
+ OpenSSL::ASN1::GeneralizedTime.new(Time.utc(9999, 9, 8, 23, 43, 39))
+
+ # Fractional seconds (DER). Not supported by ASN1_TIME_to_tm()
+ # because struct tm cannot store it.
+ # encode_decode_test B(%w{ 18 11 }) + "20161208193439.5Z".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 39.5))
+
+ # UTC offset (BER): ASN1_TIME_to_tm() may or may not support it
+ # decode_test B(%w{ 18 13 }) + "20161208193439+0930".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.new(2016, 12, 8, 19, 34, 39, "+09:30"))
+ # decode_test B(%w{ 18 11 }) + "201612081934-0930".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.new(2016, 12, 8, 19, 34, 0, "-09:30"))
+ # decode_test B(%w{ 18 11 }) + "201612081934-09".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.new(2016, 12, 8, 19, 34, 0, "-09:00"))
+
+ # Minutes and seconds are omitted (BER)
+ # decode_test B(%w{ 18 0B }) + "2016120819Z".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 0, 0))
+ # Fractional hours (BER)
+ # decode_test B(%w{ 18 0D }) + "2016120819.5Z".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 30, 0))
+ # Fractional hours with "," as the decimal separator (BER)
+ # decode_test B(%w{ 18 0D }) + "2016120819,5Z".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 30, 0))
+
+ # Seconds is omitted (BER)
+ # decode_test B(%w{ 18 0D }) + "201612081934Z".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 0))
+ # Fractional minutes (BER)
+ # decode_test B(%w{ 18 0F }) + "201612081934.5Z".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 30))
+
+ # Missing "Z"
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 18 0F }) + "20161208193429Y".b)
+ }
+
+ # Encoding year out of range
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1::GeneralizedTime.new(Time.utc(10000, 9, 8, 23, 43, 39)).to_der
+ }
+ end
+
+ def test_basic_asn1data
+ encode_test B(%w{ 00 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 0, :UNIVERSAL)
+ encode_test B(%w{ 01 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 1, :UNIVERSAL)
+ encode_decode_test B(%w{ 41 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 1, :APPLICATION)
+ encode_decode_test B(%w{ 81 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 1, :CONTEXT_SPECIFIC)
+ encode_decode_test B(%w{ C1 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 1, :PRIVATE)
+ encode_decode_test B(%w{ 1F 20 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 32, :UNIVERSAL)
+ encode_decode_test B(%w{ 9F C0 20 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 8224, :CONTEXT_SPECIFIC)
+ encode_decode_test B(%w{ 41 02 AB CD }), OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD }), 1, :APPLICATION)
+ encode_decode_test B(%w{ 41 81 80 } + %w{ AB CD } * 64), OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD } * 64), 1, :APPLICATION)
+ encode_decode_test B(%w{ 41 82 01 00 } + %w{ AB CD } * 128), OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD } * 128), 1, :APPLICATION)
+ encode_decode_test B(%w{ 61 00 }), OpenSSL::ASN1::ASN1Data.new([], 1, :APPLICATION)
+ obj = OpenSSL::ASN1::ASN1Data.new([OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD }), 2, :PRIVATE)], 1, :APPLICATION)
+ obj.indefinite_length = true
+ encode_decode_test B(%w{ 61 80 C2 02 AB CD 00 00 }), obj
+ obj = OpenSSL::ASN1::ASN1Data.new([
+ OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD }), 2, :PRIVATE),
+ OpenSSL::ASN1::EndOfContent.new
+ ], 1, :APPLICATION)
+ obj.indefinite_length = true
+ encode_test B(%w{ 61 80 C2 02 AB CD 00 00 }), obj
+ obj = OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD }), 1, :UNIVERSAL)
+ obj.indefinite_length = true
+ assert_raise(OpenSSL::ASN1::ASN1Error) { obj.to_der }
+ end
+
+ def test_basic_primitive
+ encode_test B(%w{ 00 00 }), OpenSSL::ASN1::Primitive.new(B(%w{}), 0)
+ encode_test B(%w{ 01 00 }), OpenSSL::ASN1::Primitive.new(B(%w{}), 1, nil, :UNIVERSAL)
+ encode_test B(%w{ 81 00 }), OpenSSL::ASN1::Primitive.new(B(%w{}), 1, nil, :CONTEXT_SPECIFIC)
+ encode_test B(%w{ 01 02 AB CD }), OpenSSL::ASN1::Primitive.new(B(%w{ AB CD }), 1)
+ assert_raise(TypeError) { OpenSSL::ASN1::Primitive.new([], 1).to_der }
+
+ prim = OpenSSL::ASN1::Integer.new(50)
+ assert_equal false, prim.indefinite_length
+ assert_not_respond_to prim, :indefinite_length=
+ end
+
+ def test_basic_constructed
+ octet_string = OpenSSL::ASN1::OctetString.new(B(%w{ AB CD }))
+ encode_test B(%w{ 20 00 }), OpenSSL::ASN1::Constructive.new([], 0)
+ encode_test B(%w{ 21 00 }), OpenSSL::ASN1::Constructive.new([], 1, nil, :UNIVERSAL)
+ encode_test B(%w{ A1 00 }), OpenSSL::ASN1::Constructive.new([], 1, nil, :CONTEXT_SPECIFIC)
+ encode_test B(%w{ 21 04 04 02 AB CD }), OpenSSL::ASN1::Constructive.new([octet_string], 1)
+ obj = OpenSSL::ASN1::Constructive.new([octet_string], 1)
+ obj.indefinite_length = true
+ encode_decode_test B(%w{ 21 80 04 02 AB CD 00 00 }), obj
+ obj = OpenSSL::ASN1::Constructive.new([octet_string, OpenSSL::ASN1::EndOfContent.new], 1)
+ obj.indefinite_length = true
+ encode_test B(%w{ 21 80 04 02 AB CD 00 00 }), obj
+ end
+
+ def test_prim_explicit_tagging
+ oct_str = OpenSSL::ASN1::OctetString.new("a", 0, :EXPLICIT)
+ encode_test B(%w{ A0 03 04 01 61 }), oct_str
+ oct_str2 = OpenSSL::ASN1::OctetString.new("a", 1, :EXPLICIT, :APPLICATION)
+ encode_test B(%w{ 61 03 04 01 61 }), oct_str2
+
+ decoded = OpenSSL::ASN1.decode(oct_str2.to_der)
+ assert_equal :APPLICATION, decoded.tag_class
+ assert_equal 1, decoded.tag
+ assert_equal 1, decoded.value.size
+ inner = decoded.value[0]
+ assert_equal OpenSSL::ASN1::OctetString, inner.class
+ assert_equal B(%w{ 61 }), inner.value
+ end
+
+ def test_prim_implicit_tagging
+ int = OpenSSL::ASN1::Integer.new(1, 0, :IMPLICIT)
+ encode_test B(%w{ 80 01 01 }), int
+ int2 = OpenSSL::ASN1::Integer.new(1, 1, :IMPLICIT, :APPLICATION)
+ encode_test B(%w{ 41 01 01 }), int2
+ decoded = OpenSSL::ASN1.decode(int2.to_der)
+ assert_equal :APPLICATION, decoded.tag_class
+ assert_equal 1, decoded.tag
+ assert_equal B(%w{ 01 }), decoded.value
+
+ # Special behavior: Encoding universal types with non-default 'tag'
+ # attribute and nil tagging method.
+ int3 = OpenSSL::ASN1::Integer.new(1, 1)
+ encode_test B(%w{ 01 01 01 }), int3
+ end
+
+ def test_cons_explicit_tagging
+ content = [ OpenSSL::ASN1::PrintableString.new('abc') ]
+ seq = OpenSSL::ASN1::Sequence.new(content, 2, :EXPLICIT)
+ encode_test B(%w{ A2 07 30 05 13 03 61 62 63 }), seq
+ seq2 = OpenSSL::ASN1::Sequence.new(content, 3, :EXPLICIT, :APPLICATION)
+ encode_test B(%w{ 63 07 30 05 13 03 61 62 63 }), seq2
+
+ content3 = [ OpenSSL::ASN1::PrintableString.new('abc'),
+ OpenSSL::ASN1::EndOfContent.new() ]
+ seq3 = OpenSSL::ASN1::Sequence.new(content3, 2, :EXPLICIT)
+ seq3.indefinite_length = true
+ encode_test B(%w{ A2 80 30 80 13 03 61 62 63 00 00 00 00 }), seq3
+ end
+
+ def test_cons_implicit_tagging
+ content = [ OpenSSL::ASN1::Null.new(nil) ]
+ seq = OpenSSL::ASN1::Sequence.new(content, 1, :IMPLICIT)
+ encode_test B(%w{ A1 02 05 00 }), seq
+ seq2 = OpenSSL::ASN1::Sequence.new(content, 1, :IMPLICIT, :APPLICATION)
+ encode_test B(%w{ 61 02 05 00 }), seq2
+
+ content3 = [ OpenSSL::ASN1::Null.new(nil),
+ OpenSSL::ASN1::EndOfContent.new() ]
+ seq3 = OpenSSL::ASN1::Sequence.new(content3, 1, :IMPLICIT)
+ seq3.indefinite_length = true
+ encode_test B(%w{ A1 80 05 00 00 00 }), seq3
+
+ # Special behavior: Encoding universal types with non-default 'tag'
+ # attribute and nil tagging method.
+ seq4 = OpenSSL::ASN1::Sequence.new([], 1)
+ encode_test B(%w{ 21 00 }), seq4
+ end
+
+ def test_octet_string_constructed_tagging
+ octets = [ OpenSSL::ASN1::OctetString.new('aaa') ]
+ cons = OpenSSL::ASN1::Constructive.new(octets, 0, :IMPLICIT)
+ encode_test B(%w{ A0 05 04 03 61 61 61 }), cons
+
+ octets = [ OpenSSL::ASN1::OctetString.new('aaa'),
+ OpenSSL::ASN1::EndOfContent.new() ]
+ cons = OpenSSL::ASN1::Constructive.new(octets, 0, :IMPLICIT)
+ cons.indefinite_length = true
+ encode_test B(%w{ A0 80 04 03 61 61 61 00 00 }), cons
+ end
+
+ def test_recursive_octet_string_indefinite_length
+ octets_sub1 = [ OpenSSL::ASN1::OctetString.new("\x01"),
+ OpenSSL::ASN1::EndOfContent.new() ]
+ octets_sub2 = [ OpenSSL::ASN1::OctetString.new("\x02"),
+ OpenSSL::ASN1::EndOfContent.new() ]
+ container1 = OpenSSL::ASN1::Constructive.new(octets_sub1, OpenSSL::ASN1::OCTET_STRING, nil, :UNIVERSAL)
+ container1.indefinite_length = true
+ container2 = OpenSSL::ASN1::Constructive.new(octets_sub2, OpenSSL::ASN1::OCTET_STRING, nil, :UNIVERSAL)
+ container2.indefinite_length = true
+ octets3 = OpenSSL::ASN1::OctetString.new("\x03")
+
+ octets = [ container1, container2, octets3,
+ OpenSSL::ASN1::EndOfContent.new() ]
+ cons = OpenSSL::ASN1::Constructive.new(octets, OpenSSL::ASN1::OCTET_STRING, nil, :UNIVERSAL)
+ cons.indefinite_length = true
+ raw = B(%w{ 24 80 24 80 04 01 01 00 00 24 80 04 01 02 00 00 04 01 03 00 00 })
+ assert_equal(raw, cons.to_der)
+ assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+ end
+
+ def test_recursive_octet_string_parse
+ raw = B(%w{ 24 80 24 80 04 01 01 00 00 24 80 04 01 02 00 00 04 01 03 00 00 })
+ asn1 = OpenSSL::ASN1.decode(raw)
+ assert_equal(OpenSSL::ASN1::Constructive, asn1.class)
+ assert_universal(OpenSSL::ASN1::OCTET_STRING, asn1)
+ assert_equal(true, asn1.indefinite_length)
+ assert_equal(3, asn1.value.size)
+ nested1 = asn1.value[0]
+ assert_equal(OpenSSL::ASN1::Constructive, nested1.class)
+ assert_universal(OpenSSL::ASN1::OCTET_STRING, nested1)
+ assert_equal(true, nested1.indefinite_length)
+ assert_equal(1, nested1.value.size)
+ oct1 = nested1.value[0]
+ assert_universal(OpenSSL::ASN1::OCTET_STRING, oct1)
+ assert_equal(false, oct1.indefinite_length)
+ nested2 = asn1.value[1]
+ assert_equal(OpenSSL::ASN1::Constructive, nested2.class)
+ assert_universal(OpenSSL::ASN1::OCTET_STRING, nested2)
+ assert_equal(true, nested2.indefinite_length)
+ assert_equal(1, nested2.value.size)
+ oct2 = nested2.value[0]
+ assert_universal(OpenSSL::ASN1::OCTET_STRING, oct2)
+ assert_equal(false, oct2.indefinite_length)
+ oct3 = asn1.value[2]
+ assert_universal(OpenSSL::ASN1::OCTET_STRING, oct3)
+ assert_equal(false, oct3.indefinite_length)
+ end
+
+ def test_decode_constructed_overread
+ test = %w{ 31 06 31 02 30 02 05 00 }
+ # ^ <- invalid
+ raw = [test.join].pack("H*")
+ ret = []
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.traverse(raw) { |x| ret << x }
+ }
+ assert_equal 2, ret.size
+ assert_equal 17, ret[0][6]
+ assert_equal 17, ret[1][6]
+
+ test = %w{ 31 80 30 03 00 00 }
+ # ^ <- invalid
+ raw = [test.join].pack("H*")
+ ret = []
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.traverse(raw) { |x| ret << x }
+ }
+ assert_equal 1, ret.size
+ assert_equal 17, ret[0][6]
+ end
+
+ def test_decode_constructed_deeply_nested
+ bool = OpenSSL::ASN1::Boolean.new(true)
+ nested_100 = B(%w{ 30 80 }) * 100 + bool.to_der + B(%w{ 00 00 }) * 100
+ decoded = OpenSSL::ASN1.decode(nested_100)
+ assert_equal(nested_100, decoded.to_der)
+ content = 100.times.inject(decoded) { |a,| a.value[0] }
+ assert_kind_of(OpenSSL::ASN1::Boolean, content)
+
+ nested_500 = B(%w{ 30 80 }) * 500 + bool.to_der + B(%w{ 00 00 }) * 500
+ assert_raise_with_message(OpenSSL::ASN1::ASN1Error, /nesting depth/) {
+ OpenSSL::ASN1.decode(nested_500)
+ }
+ end
+
+ def test_constructive_each
+ data = [OpenSSL::ASN1::Integer.new(0), OpenSSL::ASN1::Integer.new(1)]
+ seq = OpenSSL::ASN1::Sequence.new data
+
+ assert_equal data, seq.entries
+ end
+
+ # Very time consuming test.
+ # def test_gc_stress
+ # assert_ruby_status(['--disable-gems', '-eGC.stress=true', '-erequire "openssl.so"'])
+ # end
+
+ private
+
+ def B(ary)
+ [ary.join].pack("H*")
+ end
+
+ def assert_asn1_equal(a, b)
+ assert_equal a.class, b.class
+ assert_equal a.tag, b.tag
+ assert_equal a.tag_class, b.tag_class
+ assert_equal a.indefinite_length, b.indefinite_length
+ assert_equal a.unused_bits, b.unused_bits if a.respond_to?(:unused_bits)
+ case a.value
+ when Array
+ a.value.each_with_index { |ai, i|
+ assert_asn1_equal ai, b.value[i]
+ }
+ else
+ if OpenSSL::ASN1::ObjectId === a
+ assert_equal a.oid, b.oid
+ else
+ assert_equal a.value, b.value
+ end
+ end
+ assert_equal a.to_der, b.to_der
+ end
+
+ def encode_test(der, obj)
+ assert_equal der, obj.to_der
+ end
+
+ def decode_test(der, obj)
+ decoded = OpenSSL::ASN1.decode(der)
+ assert_asn1_equal obj, decoded
+ decoded
+ end
+
+ def encode_decode_test(der, obj)
+ encode_test(der, obj)
+ decode_test(der, obj)
+ end
+
+ def assert_universal(tag, asn1)
+ assert_equal(tag, asn1.tag)
+ if asn1.respond_to?(:tagging)
+ assert_nil(asn1.tagging)
+ end
+ assert_equal(:UNIVERSAL, asn1.tag_class)
+ end
+end
+
+end
diff --git a/test/openssl/test_bn.rb b/test/openssl/test_bn.rb
new file mode 100644
index 0000000000..f663102d45
--- /dev/null
+++ b/test/openssl/test_bn.rb
@@ -0,0 +1,386 @@
+# coding: us-ascii
+# frozen_string_literal: true
+require_relative 'utils'
+
+if defined?(OpenSSL)
+
+class OpenSSL::TestBN < OpenSSL::TestCase
+ def setup
+ super
+ @e1 = OpenSSL::BN.new(999.to_s(16), 16) # OpenSSL::BN.new(str, 16) must be most stable
+ @e2 = OpenSSL::BN.new("-" + 999.to_s(16), 16)
+ @e3 = OpenSSL::BN.new((2**107-1).to_s(16), 16)
+ @e4 = OpenSSL::BN.new("-" + (2**107-1).to_s(16), 16)
+ 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))
+ assert_equal(@e1, OpenSSL::BN.new("\x00\x00\x00\x02\x03\xE7", 0))
+ assert_equal(@e2, OpenSSL::BN.new("-999"))
+ assert_equal(@e2, OpenSSL::BN.new("-999", 10))
+ assert_equal(@e2, OpenSSL::BN.new("\x00\x00\x00\x02\x83\xE7", 0))
+ assert_equal(@e3, OpenSSL::BN.new((2**107-1).to_s))
+ assert_equal(@e3, OpenSSL::BN.new((2**107-1).to_s, 10))
+ assert_equal(@e3, OpenSSL::BN.new("\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 2))
+ assert_equal(@e3, OpenSSL::BN.new("\x00\x00\x00\x0E\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 0))
+ assert_equal(@e4, OpenSSL::BN.new("-" + (2**107-1).to_s))
+ assert_equal(@e4, OpenSSL::BN.new("-" + (2**107-1).to_s, 10))
+ assert_equal(@e4, OpenSSL::BN.new("\x00\x00\x00\x0E\x87\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 0))
+
+ e1copy = OpenSSL::BN.new(@e1)
+ assert_equal(@e1, e1copy)
+ e1copy.clear_bit!(0) #=> 998
+ assert_not_equal(@e1, e1copy)
+
+ assert_equal(@e1, OpenSSL::BN.new(999))
+ assert_equal(@e2, OpenSSL::BN.new(-999))
+ assert_equal(@e3, OpenSSL::BN.new(2**107-1))
+ assert_equal(@e4, OpenSSL::BN.new(-(2**107-1)))
+
+ assert_equal(@e1, 999.to_bn)
+ assert_equal(@e2, -999.to_bn)
+ assert_equal(@e3, (2**107-1).to_bn)
+ assert_equal(@e4, (-(2**107-1)).to_bn)
+ end
+
+ def test_to_str
+ assert_equal("999", @e1.to_s(10))
+ assert_equal("-999", @e2.to_s(10))
+ assert_equal((2**107-1).to_s, @e3.to_s(10))
+ assert_equal((-(2**107-1)).to_s, @e4.to_s(10))
+ assert_equal("999", @e1.to_s)
+
+ assert_equal("03E7", @e1.to_s(16))
+ assert_equal("-03E7", @e2.to_s(16))
+ assert_equal("07FFFFFFFFFFFFFFFFFFFFFFFFFF", @e3.to_s(16))
+ assert_equal("-07FFFFFFFFFFFFFFFFFFFFFFFFFF", @e4.to_s(16))
+
+ assert_equal("\x03\xe7", @e1.to_s(2))
+ assert_equal("\x03\xe7", @e2.to_s(2))
+ assert_equal("\x07\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", @e3.to_s(2))
+ assert_equal("\x07\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", @e4.to_s(2))
+
+ assert_equal("\x00\x00\x00\x02\x03\xe7", @e1.to_s(0))
+ assert_equal("\x00\x00\x00\x02\x83\xe7", @e2.to_s(0))
+ assert_equal("\x00\x00\x00\x0e\x07\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", @e3.to_s(0))
+ assert_equal("\x00\x00\x00\x0e\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", @e4.to_s(0))
+ end
+
+ def test_to_int
+ assert_equal(999, @e1.to_i)
+ assert_equal(-999, @e2.to_i)
+ assert_equal(2**107-1, @e3.to_i)
+ assert_equal(-(2**107-1), @e4.to_i)
+
+ assert_equal(999, @e1.to_int)
+ end
+
+ def test_coerce
+ assert_equal(["", "-999"], @e2.coerce(""))
+ assert_equal([1000, -999], @e2.coerce(1000))
+ assert_raise(TypeError) { @e2.coerce(Class.new.new) }
+ end
+
+ def test_zero_p
+ assert_equal(true, 0.to_bn.zero?)
+ assert_equal(false, 1.to_bn.zero?)
+ end
+
+ def test_one_p
+ assert_equal(true, 1.to_bn.one?)
+ assert_equal(false, 2.to_bn.one?)
+ end
+
+ def test_odd_p
+ assert_equal(true, 1.to_bn.odd?)
+ assert_equal(false, 2.to_bn.odd?)
+ end
+
+ def test_negative_p
+ assert_equal(false, 0.to_bn.negative?)
+ assert_equal(false, @e1.negative?)
+ assert_equal(true, @e2.negative?)
+ end
+
+ def test_sqr
+ assert_equal(1, 1.to_bn.sqr)
+ assert_equal(100, 10.to_bn.sqr)
+ end
+
+ def test_four_ops
+ assert_equal(3, 1.to_bn + 2)
+ assert_equal(-1, 1.to_bn + -2)
+ assert_equal(-1, 1.to_bn - 2)
+ assert_equal(3, 1.to_bn - -2)
+ assert_equal(2, 1.to_bn * 2)
+ assert_equal(-2, 1.to_bn * -2)
+ assert_equal([0, 1], 1.to_bn / 2)
+ assert_equal([2, 0], 2.to_bn / 1)
+ assert_raise(OpenSSL::BNError) { 1.to_bn / 0 }
+ end
+
+ def test_unary_plus_minus
+ assert_equal(999, +@e1)
+ assert_equal(-999, +@e2)
+ assert_equal(-999, -@e1)
+ assert_equal(+999, -@e2)
+
+ # These methods create new BN instances due to BN mutability
+ # Ensure that the instance isn't the same
+ e1_plus = +@e1
+ e1_minus = -@e1
+ assert_equal(false, @e1.equal?(e1_plus))
+ assert_equal(true, @e1 == e1_plus)
+ assert_equal(false, @e1.equal?(e1_minus))
+ end
+
+ def test_abs
+ assert_equal(@e1, @e2.abs)
+ assert_equal(@e3, @e4.abs)
+ assert_not_equal(@e2, @e2.abs)
+ assert_not_equal(@e4, @e4.abs)
+ assert_equal(false, @e2.abs.negative?)
+ assert_equal(false, @e4.abs.negative?)
+ assert_equal(true, (-@e1.abs).negative?)
+ assert_equal(true, (-@e2.abs).negative?)
+ assert_equal(true, (-@e3.abs).negative?)
+ assert_equal(true, (-@e4.abs).negative?)
+ end
+
+ def test_mod
+ assert_equal(1, 1.to_bn % 2)
+ assert_equal(0, 2.to_bn % 1)
+ assert_equal(-2, -2.to_bn % 7)
+ end
+
+ def test_exp
+ assert_equal(1, 1.to_bn ** 5)
+ assert_equal(32, 2.to_bn ** 5)
+ end
+
+ def test_gcd
+ assert_equal(1, 7.to_bn.gcd(5))
+ assert_equal(8, 24.to_bn.gcd(16))
+ end
+
+ def test_mod_sqr
+ assert_equal(4, 3.to_bn.mod_sqr(5))
+ assert_equal(0, 59.to_bn.mod_sqr(59))
+ end
+
+ def test_mod_sqrt
+ assert_equal(4, 4.to_bn.mod_sqrt(5).mod_sqr(5))
+ # One of 189484 or 326277 is returned as a square root of 2 (mod 515761).
+ assert_equal(2, 2.to_bn.mod_sqrt(515761).mod_sqr(515761))
+ assert_equal(0, 5.to_bn.mod_sqrt(5))
+ assert_raise(OpenSSL::BNError) { 3.to_bn.mod_sqrt(5) }
+ end
+
+ def test_mod_inverse
+ assert_equal(2, 3.to_bn.mod_inverse(5))
+ assert_raise(OpenSSL::BNError) { 3.to_bn.mod_inverse(6) }
+ end
+
+ def test_mod_add
+ assert_equal(1, 3.to_bn.mod_add(5, 7))
+ assert_equal(2, 3.to_bn.mod_add(5, 3))
+ assert_equal(5, 3.to_bn.mod_add(-5, 7))
+ end
+
+ def test_mod_sub
+ assert_equal(1, 11.to_bn.mod_sub(3, 7))
+ assert_equal(2, 11.to_bn.mod_sub(3, 3))
+ assert_equal(5, 3.to_bn.mod_sub(5, 7))
+ end
+
+ def test_mod_mul
+ assert_equal(1, 2.to_bn.mod_mul(4, 7))
+ assert_equal(5, 2.to_bn.mod_mul(-1, 7))
+ end
+
+ def test_mod_exp
+ assert_equal(1, 3.to_bn.mod_exp(2, 8))
+ assert_equal(4, 2.to_bn.mod_exp(5, 7))
+ end
+
+ def test_bit_operations
+ e = 0b10010010.to_bn
+ assert_equal(0b10010011, e.set_bit!(0))
+ assert_equal(0b10010011, e.set_bit!(1))
+ assert_equal(0b1010010011, e.set_bit!(9))
+
+ e = 0b10010010.to_bn
+ assert_equal(0b10010010, e.clear_bit!(0))
+ assert_equal(0b10010000, e.clear_bit!(1))
+
+ e = 0b10010010.to_bn
+ assert_equal(0b10010010, e.mask_bits!(8))
+ assert_equal(0b10, e.mask_bits!(3))
+
+ e = 0b10010010.to_bn
+ assert_equal(false, e.bit_set?(0))
+ assert_equal(true, e.bit_set?(1))
+ assert_equal(false, e.bit_set?(1000))
+
+ e = 0b10010010.to_bn
+ assert_equal(0b1001001000, e << 2)
+ assert_equal(0b10010010, e)
+ assert_equal(0b1001001000, e.lshift!(2))
+ assert_equal(0b1001001000, e)
+
+ e = 0b10010010.to_bn
+ assert_equal(0b100100, e >> 2)
+ assert_equal(0b10010010, e)
+ assert_equal(0b100100, e.rshift!(2))
+ assert_equal(0b100100, e)
+ end
+
+ def test_random
+ 10.times {
+ r1 = OpenSSL::BN.rand(8)
+ assert_include(128..255, r1)
+ r2 = OpenSSL::BN.rand(8, -1)
+ assert_include(0..255, r2)
+ r3 = OpenSSL::BN.rand(8, 1)
+ assert_include(192..255, r3)
+ r4 = OpenSSL::BN.rand(8, 1, true)
+ assert_include(192..255, r4)
+ assert_equal(true, r4.odd?)
+
+ r5 = OpenSSL::BN.rand_range(256)
+ assert_include(0..255, r5)
+ }
+
+ # Aliases
+ assert_include(128..255, OpenSSL::BN.pseudo_rand(8))
+ assert_include(0..255, OpenSSL::BN.pseudo_rand_range(256))
+ end
+
+ begin
+ require "prime"
+
+ def test_prime
+ p1 = OpenSSL::BN.generate_prime(32)
+ assert_include(0...2**32, p1)
+ assert_equal(true, Prime.prime?(p1.to_i))
+ p2 = OpenSSL::BN.generate_prime(32, true)
+ assert_equal(true, Prime.prime?((p2.to_i - 1) / 2))
+ p3 = OpenSSL::BN.generate_prime(32, false, 4)
+ assert_equal(1, p3 % 4)
+ p4 = OpenSSL::BN.generate_prime(32, false, 4, 3)
+ assert_equal(3, p4 % 4)
+
+ assert_equal(true, p1.prime?)
+ assert_equal(true, p2.prime?)
+ assert_equal(true, p3.prime?)
+ assert_equal(true, p4.prime?)
+ assert_equal(true, @e3.prime?)
+ assert_equal(true, @e3.prime_fasttest?)
+ end
+ rescue LoadError
+ # prime is the bundled gems at Ruby 3.1
+ end
+
+ def test_num_bits_bytes
+ assert_equal(10, @e1.num_bits)
+ assert_equal(2, @e1.num_bytes)
+ assert_equal(107, @e3.num_bits)
+ assert_equal(14, @e3.num_bytes)
+ assert_equal(0, 0.to_bn.num_bits)
+ assert_equal(0, 0.to_bn.num_bytes)
+ assert_equal(9, -256.to_bn.num_bits)
+ assert_equal(2, -256.to_bn.num_bytes)
+ end
+
+ def test_comparison
+ assert_equal(false, @e1 == nil)
+ assert_equal(false, @e1 == -999)
+ assert_equal(true, @e1 == 999)
+ assert_equal(true, @e1 == 999.to_bn)
+ assert_equal(false, @e1.eql?(nil))
+ assert_equal(false, @e1.eql?(999))
+ assert_equal(true, @e1.eql?(999.to_bn))
+ assert_equal(@e1.hash, 999.to_bn.hash)
+ assert_not_equal(@e1.hash, @e3.hash)
+ assert_equal(0, @e1.cmp(999))
+ assert_equal(1, @e1.cmp(-999))
+ assert_equal(0, @e1.ucmp(999))
+ assert_equal(0, @e1.ucmp(-999))
+ assert_instance_of(String, @e1.hash.to_s)
+ end
+
+ def test_argument_error
+ bug15760 = '[ruby-core:92231] [Bug #15760]'
+ assert_raise(ArgumentError, bug15760) { OpenSSL::BN.new(nil, 2) }
+ end
+
+ def test_get_flags_and_set_flags
+ return if aws_lc? # AWS-LC does not support BN::CONSTTIME.
+
+ e = OpenSSL::BN.new(999)
+
+ assert_equal(0, e.get_flags(OpenSSL::BN::CONSTTIME))
+
+ e.set_flags(OpenSSL::BN::CONSTTIME)
+ assert_equal(OpenSSL::BN::CONSTTIME, e.get_flags(OpenSSL::BN::CONSTTIME))
+
+ b = OpenSSL::BN.new(2)
+ m = OpenSSL::BN.new(99)
+ assert_equal("17", b.mod_exp(e, m).to_s)
+
+ # mod_exp fails when m is even and any argument has CONSTTIME flag
+ m = OpenSSL::BN.new(98)
+ assert_raise(OpenSSL::BNError) do
+ b.mod_exp(e, m)
+ end
+
+ # It looks like flags cannot be removed once enabled
+ e.set_flags(0)
+ assert_equal(4, e.get_flags(OpenSSL::BN::CONSTTIME))
+ end
+
+ if defined?(Ractor) && respond_to?(:ractor)
+ unless Ractor.method_defined?(:value) # Ruby 3.4 or earlier
+ using Module.new {
+ refine Ractor do
+ alias value take
+ end
+ }
+ end
+
+ ractor
+ def test_ractor
+ assert_equal(@e1, Ractor.new { OpenSSL::BN.new("999") }.value)
+ assert_equal(@e3, Ractor.new { OpenSSL::BN.new("\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 2) }.value)
+ assert_equal("999", Ractor.new(@e1) { |e1| e1.to_s }.value)
+ assert_equal("07FFFFFFFFFFFFFFFFFFFFFFFFFF", Ractor.new(@e3) { |e3| e3.to_s(16) }.value)
+ assert_equal(2**107-1, Ractor.new(@e3) { _1.to_i }.value)
+ assert_equal([1000, -999], Ractor.new(@e2) { _1.coerce(1000) }.value)
+ assert_equal(false, Ractor.new { 1.to_bn.zero? }.value)
+ assert_equal(true, Ractor.new { 1.to_bn.one? }.value)
+ assert_equal(true, Ractor.new(@e2) { _1.negative? }.value)
+ assert_equal("-03E7", Ractor.new(@e2) { _1.to_s(16) }.value)
+ assert_equal(2**107-1, Ractor.new(@e3) { _1.to_i }.value)
+ assert_equal([1000, -999], Ractor.new(@e2) { _1.coerce(1000) }.value)
+ assert_equal(true, Ractor.new { 0.to_bn.zero? }.value)
+ assert_equal(true, Ractor.new { 1.to_bn.one? }.value )
+ assert_equal(false,Ractor.new { 2.to_bn.odd? }.value)
+ assert_equal(true, Ractor.new(@e2) { _1.negative? }.value)
+ assert_include(128..255, Ractor.new { OpenSSL::BN.rand(8)}.value)
+ assert_include(0...2**32, Ractor.new { OpenSSL::BN.generate_prime(32) }.value)
+ if !aws_lc? # AWS-LC does not support BN::CONSTTIME.
+ assert_equal(0, Ractor.new { OpenSSL::BN.new(999).get_flags(OpenSSL::BN::CONSTTIME) }.value)
+ end
+ # test if shareable when frozen
+ assert Ractor.shareable?(@e1.freeze)
+ end
+ end
+end
+
+end
diff --git a/test/openssl/test_buffering.rb b/test/openssl/test_buffering.rb
new file mode 100644
index 0000000000..466bbcfa23
--- /dev/null
+++ b/test/openssl/test_buffering.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+require_relative 'utils'
+
+if defined?(OpenSSL)
+
+class OpenSSL::TestBuffering < OpenSSL::TestCase
+ class IO
+ include OpenSSL::Buffering
+
+ attr_accessor :sync
+
+ def initialize
+ @io = Buffer.new
+ def @io.sync
+ true
+ end
+
+ super
+
+ @sync = false
+ end
+
+ def string
+ @io
+ end
+
+ def sysread(size)
+ str = @io.slice!(0, size)
+ raise EOFError if str.empty?
+ str
+ end
+
+ def syswrite(str)
+ @io.append_as_bytes(str)
+ str.size
+ end
+ end
+
+ def setup
+ super
+ @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'
+
+ assert_not_predicate @io, :sync
+ assert_empty @io.string
+
+ assert_equal @io, @io.flush
+
+ assert_not_predicate @io, :sync
+ assert_equal 'a', @io.string
+ end
+
+ def test_flush_error
+ @io.write 'a'
+
+ assert_not_predicate @io, :sync
+ assert_empty @io.string
+
+ def @io.syswrite *a
+ raise SystemCallError, 'fail'
+ end
+
+ assert_raise SystemCallError do
+ @io.flush
+ end
+
+ assert_not_predicate @io, :sync, 'sync must not change'
+ end
+
+ def test_getc
+ @io.syswrite('abc')
+ assert_equal(?a, @io.getc)
+ assert_equal(?b, @io.getc)
+ assert_equal(?c, @io.getc)
+ end
+
+ def test_each_byte
+ @io.syswrite('abc')
+ res = []
+ @io.each_byte do |c|
+ res << c
+ end
+ assert_equal([97, 98, 99], res)
+ end
+end
+
+end
diff --git a/test/openssl/test_cipher.rb b/test/openssl/test_cipher.rb
index d671908165..6a405da0a9 100644
--- a/test/openssl/test_cipher.rb
+++ b/test/openssl/test_cipher.rb
@@ -1,93 +1,444 @@
-begin
- require "openssl"
-rescue LoadError
-end
-require "test/unit"
+# frozen_string_literal: true
+require_relative 'utils'
if defined?(OpenSSL)
-class OpenSSL::TestCipher < Test::Unit::TestCase
- def setup
- @c1 = OpenSSL::Cipher::Cipher.new("DES-EDE3-CBC")
- @c2 = OpenSSL::Cipher::DES.new(:EDE3, "CBC")
- @key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
- @iv = "\0\0\0\0\0\0\0\0"
- @hexkey = "0000000000000000000000000000000000000000000000"
- @hexiv = "0000000000000000"
- @data = "DATA"
+class OpenSSL::TestCipher < OpenSSL::TestCase
+ module Helper
+ def has_cipher?(name)
+ @ciphers ||= OpenSSL::Cipher.ciphers
+ @ciphers.include?(name)
+ end
end
+ include Helper
+ extend Helper
- def teardown
- @c1 = @c2 = nil
+ def test_encrypt_decrypt
+ # NIST SP 800-38A F.2.1
+ key = ["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*")
+ iv = ["000102030405060708090a0b0c0d0e0f"].pack("H*")
+ pt = ["6bc1bee22e409f96e93d7e117393172a" \
+ "ae2d8a571e03ac9c9eb76fac45af8e51"].pack("H*")
+ ct = ["7649abac8119b246cee98e9b12e9197d" \
+ "5086cb9b507219ee95db113a917678b2"].pack("H*")
+ cipher = new_encryptor("aes-128-cbc", key: key, iv: iv, padding: 0)
+ assert_equal ct, cipher.update(pt) << cipher.final
+ cipher = new_decryptor("aes-128-cbc", key: key, iv: iv, padding: 0)
+ assert_equal pt, cipher.update(ct) << cipher.final
end
- def test_crypt
- @c1.encrypt.pkcs5_keyivgen(@key, @iv)
- @c2.encrypt.pkcs5_keyivgen(@key, @iv)
- s1 = @c1.update(@data) + @c1.final
- s2 = @c2.update(@data) + @c2.final
- assert_equal(s1, s2, "encrypt")
+ def test_pkcs5_keyivgen
+ pass = "\x00" * 8
+ salt = "\x01" * 8
+ num = 2048
+ pt = "data to be encrypted"
+ cipher = OpenSSL::Cipher.new("AES-256-CBC").encrypt
+ cipher.pkcs5_keyivgen(pass, salt, num, "SHA256")
+ s1 = cipher.update(pt) << cipher.final
+
+ d1 = num.times.inject(pass + salt) {|out, _| OpenSSL::Digest.digest('SHA256', out) }
+ d2 = num.times.inject(d1 + pass + salt) {|out, _| OpenSSL::Digest.digest('SHA256', out) }
+ key = (d1 + d2)[0, 32]
+ iv = (d1 + d2)[32, 16]
+ cipher = new_encryptor("AES-256-CBC", key: key, iv: iv)
+ s2 = cipher.update(pt) << cipher.final
- @c1.decrypt.pkcs5_keyivgen(@key, @iv)
- @c2.decrypt.pkcs5_keyivgen(@key, @iv)
- assert_equal(@data, @c1.update(s1)+@c1.final, "decrypt")
- assert_equal(@data, @c2.update(s2)+@c2.final, "decrypt")
+ assert_equal s1, s2
+
+ cipher2 = OpenSSL::Cipher.new("AES-256-CBC").encrypt
+ assert_raise(ArgumentError) { cipher2.pkcs5_keyivgen(pass, salt, -1, "SHA256") }
end
def test_info
- assert_equal("DES-EDE3-CBC", @c1.name, "name")
- assert_equal("DES-EDE3-CBC", @c2.name, "name")
- assert_kind_of(Fixnum, @c1.key_len, "key_len")
- assert_kind_of(Fixnum, @c1.iv_len, "iv_len")
+ cipher = OpenSSL::Cipher.new("AES-256-CBC").encrypt
+ assert_equal "AES-256-CBC", cipher.name
+ assert_equal 32, cipher.key_len
+ assert_equal 16, cipher.iv_len
end
def test_dup
- assert_equal(@c1.name, @c1.dup.name, "dup")
- assert_equal(@c1.name, @c1.clone.name, "clone")
- @c1.encrypt
- @c1.key = @key
- @c1.iv = @iv
- tmpc = @c1.dup
- s1 = @c1.update(@data) + @c1.final
- s2 = tmpc.update(@data) + tmpc.final
+ cipher = OpenSSL::Cipher.new("aes-128-cbc").encrypt
+ assert_equal cipher.name, cipher.dup.name
+ cipher.encrypt
+ cipher.random_key
+ cipher.random_iv
+ tmpc = cipher.dup
+ s1 = cipher.update("data") + cipher.final
+ s2 = tmpc.update("data") + tmpc.final
assert_equal(s1, s2, "encrypt dup")
end
def test_reset
- @c1.encrypt
- @c1.key = @key
- @c1.iv = @iv
- s1 = @c1.update(@data) + @c1.final
- @c1.reset
- s2 = @c1.update(@data) + @c1.final
+ cipher = OpenSSL::Cipher.new("aes-128-cbc").encrypt
+ cipher.encrypt
+ cipher.random_key
+ cipher.random_iv
+ s1 = cipher.update("data") + cipher.final
+ cipher.reset
+ s2 = cipher.update("data") + cipher.final
assert_equal(s1, s2, "encrypt reset")
end
- def test_empty_data
- @c1.encrypt
- assert_raises(ArgumentError){ @c1.update("") }
+ def test_key_iv_set
+ cipher = OpenSSL::Cipher.new("AES-256-CBC").encrypt
+ assert_raise(ArgumentError) { cipher.key = "\x01" * 31 }
+ assert_nothing_raised { cipher.key = "\x01" * 32 }
+ assert_raise(ArgumentError) { cipher.key = "\x01" * 33 }
+ assert_raise(ArgumentError) { cipher.iv = "\x01" * 15 }
+ assert_nothing_raised { cipher.iv = "\x01" * 16 }
+ assert_raise(ArgumentError) { cipher.iv = "\x01" * 17 }
+ end
+
+ def test_random_key_iv
+ data = "data"
+ s1, s2 = 2.times.map do
+ cipher = OpenSSL::Cipher.new("aes-128-cbc").encrypt
+ cipher.random_key
+ cipher.iv = "\x01" * 16
+ cipher.update(data) << cipher.final
+ end
+ assert_not_equal s1, s2
+
+ s1, s2 = 2.times.map do
+ cipher = OpenSSL::Cipher.new("aes-128-cbc").encrypt
+ cipher.key = "\x01" * 16
+ cipher.random_iv
+ cipher.update(data) << cipher.final
+ end
+ assert_not_equal s1, s2
+ end
+
+ def test_initialize
+ cipher = OpenSSL::Cipher.new("AES-256-CBC")
+ assert_raise(RuntimeError) { cipher.__send__(:initialize, "AES-256-CBC") }
+ assert_raise(RuntimeError) { OpenSSL::Cipher.allocate.final }
+ assert_raise(OpenSSL::Cipher::CipherError) {
+ OpenSSL::Cipher.new("no such algorithm")
+ }
+ end
+
+ def test_ctr_if_exists
+ # NIST SP 800-38A F.5.1
+ key = ["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*")
+ iv = ["f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"].pack("H*")
+ pt = ["6bc1bee22e409f96e93d7e117393172a" \
+ "ae2d8a571e03ac9c9eb76fac45af8e51"].pack("H*")
+ ct = ["874d6191b620e3261bef6864990db6ce" \
+ "9806f66b7970fdff8617187bb9fffdff"].pack("H*")
+ cipher = new_encryptor("aes-128-ctr", key: key, iv: iv, padding: 0)
+ assert_equal ct, cipher.update(pt) << cipher.final
+ cipher = new_decryptor("aes-128-ctr", key: key, iv: iv, padding: 0)
+ assert_equal pt, cipher.update(ct) << cipher.final
+ end
+
+ def test_update_with_buffer
+ cipher = OpenSSL::Cipher.new("aes-128-ecb").encrypt
+ cipher.random_key
+ expected = cipher.update("data" * 10) << cipher.final
+ assert_equal 48, expected.bytesize
+
+ # Buffer is supplied
+ cipher.reset
+ buf = String.new
+ assert_same buf, cipher.update("data" * 10, buf)
+ assert_equal 32, buf.bytesize
+ assert_equal expected, buf + cipher.final
+
+ # Buffer is frozen
+ cipher.reset
+ assert_raise(FrozenError) { cipher.update("data", String.new.freeze) }
+
+ # Buffer is a shared string [ruby-core:120141] [Bug #20937]
+ cipher.reset
+ buf = "x".b * 1024
+ shared = buf[-("data".bytesize * 10 + 32)..-1]
+ assert_same shared, cipher.update("data" * 10, shared)
+ assert_equal expected, shared + cipher.final
+ end
+
+ def test_ciphers
+ ciphers = OpenSSL::Cipher.ciphers
+ assert_kind_of Array, ciphers
+ assert_include ciphers, "aes-128-cbc"
+ assert_include ciphers, "aes128" # alias of aes-128-cbc
+ assert_include ciphers, "aes-128-gcm"
+ end
+
+ def test_AES
+ pt = File.read(__FILE__)
+ %w(ecb cbc cfb ofb).each{|mode|
+ c1 = OpenSSL::Cipher.new("aes-256-#{mode}")
+ c1.encrypt
+ c1.pkcs5_keyivgen("passwd", "12345678", 10000, "SHA256")
+ ct = c1.update(pt) + c1.final
+
+ c2 = OpenSSL::Cipher.new("aes-256-#{mode}")
+ c2.decrypt
+ c2.pkcs5_keyivgen("passwd", "12345678", 10000, "SHA256")
+ assert_equal(pt, c2.update(ct) + c2.final)
+ }
end
- if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00907000
- def test_ciphers
- OpenSSL::Cipher.ciphers.each{|name|
- assert(OpenSSL::Cipher::Cipher.new(name).is_a?(OpenSSL::Cipher::Cipher))
- }
+ def test_update_raise_if_key_not_set
+ assert_raise(OpenSSL::Cipher::CipherError) do
+ # it caused OpenSSL SEGV by uninitialized key [Bug #2768]
+ OpenSSL::Cipher.new("aes-128-ecb").update "." * 17
end
+ end
+
+ def test_auth_tag_error_inheritance
+ assert_equal OpenSSL::Cipher::CipherError, OpenSSL::Cipher::AuthTagError.superclass
+ end
+
+ def test_authenticated
+ cipher = OpenSSL::Cipher.new('aes-128-gcm')
+ assert_predicate(cipher, :authenticated?)
+ cipher = OpenSSL::Cipher.new('aes-128-cbc')
+ assert_not_predicate(cipher, :authenticated?)
+ end
+
+ def test_aes_ccm
+ # RFC 3610 Section 8, Test Case 1
+ key = ["c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"].pack("H*")
+ iv = ["00000003020100a0a1a2a3a4a5"].pack("H*")
+ aad = ["0001020304050607"].pack("H*")
+ pt = ["08090a0b0c0d0e0f101112131415161718191a1b1c1d1e"].pack("H*")
+ ct = ["588c979a61c663d2f066d0c2c0f989806d5f6b61dac384"].pack("H*")
+ tag = ["17e8d12cfdf926e0"].pack("H*")
+
+ kwargs = {auth_tag_len: 8, iv_len: 13, key: key, iv: iv}
+ cipher = new_encryptor("aes-128-ccm", **kwargs, ccm_data_len: pt.length, auth_data: aad)
+ assert_equal ct, cipher.update(pt) << cipher.final
+ assert_equal tag, cipher.auth_tag
+ cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag, auth_data: aad)
+ assert_equal pt, cipher.update(ct) << cipher.final
+
+ # truncated tag is accepted
+ cipher = new_encryptor("aes-128-ccm", **kwargs, ccm_data_len: pt.length, auth_data: aad)
+ assert_equal ct, cipher.update(pt) << cipher.final
+ assert_equal tag[0, 8], cipher.auth_tag(8)
+ cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag[0, 8], auth_data: aad)
+ assert_equal pt, cipher.update(ct) << cipher.final
+
+ # wrong tag is rejected - in CCM, authentication happens during update, but
+ # we consider this a general CipherError since update failures can have various causes
+ tag2 = tag.dup
+ tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff)
+ cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag2, auth_data: aad)
+ assert_raise(OpenSSL::Cipher::CipherError) { cipher.update(ct) }
+
+ # wrong aad is rejected
+ aad2 = aad[0..-2] << aad[-1].succ
+ cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag, auth_data: aad2)
+ assert_raise(OpenSSL::Cipher::CipherError) { cipher.update(ct) }
+
+ # wrong ciphertext is rejected
+ ct2 = ct[0..-2] << ct[-1].succ
+ cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct2.length, auth_tag: tag, auth_data: aad)
+ assert_raise(OpenSSL::Cipher::CipherError) { cipher.update(ct2) }
+ end if has_cipher?("aes-128-ccm") &&
+ OpenSSL::Cipher.new("aes-128-ccm").authenticated? &&
+ openssl?(1, 1, 1, 0x03, 0xf) # version >= 1.1.1c
+
+ def test_aes_gcm
+ # GCM spec Appendix B Test Case 4
+ key = ["feffe9928665731c6d6a8f9467308308"].pack("H*")
+ iv = ["cafebabefacedbaddecaf888"].pack("H*")
+ aad = ["feedfacedeadbeeffeedfacedeadbeef" \
+ "abaddad2"].pack("H*")
+ pt = ["d9313225f88406e5a55909c5aff5269a" \
+ "86a7a9531534f7da2e4c303d8a318a72" \
+ "1c3c0c95956809532fcf0e2449a6b525" \
+ "b16aedf5aa0de657ba637b39"].pack("H*")
+ ct = ["42831ec2217774244b7221b784d0d49c" \
+ "e3aa212f2c02a4e035c17e2329aca12e" \
+ "21d514b25466931c7d8f6a5aac84aa05" \
+ "1ba30b396a0aac973d58e091"].pack("H*")
+ tag = ["5bc94fbc3221a5db94fae95ae7121a47"].pack("H*")
+
+ cipher = new_encryptor("aes-128-gcm", key: key, iv: iv, auth_data: aad)
+ assert_equal ct, cipher.update(pt) << cipher.final
+ assert_equal tag, cipher.auth_tag
+ cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad)
+ assert_equal pt, cipher.update(ct) << cipher.final
+
+ # truncated tag is accepted
+ cipher = new_encryptor("aes-128-gcm", key: key, iv: iv, auth_data: aad)
+ assert_equal ct, cipher.update(pt) << cipher.final
+ assert_equal tag[0, 8], cipher.auth_tag(8)
+ cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag[0, 8], auth_data: aad)
+ assert_equal pt, cipher.update(ct) << cipher.final
+
+ # wrong tag is rejected
+ tag2 = tag.dup
+ tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff)
+ cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag2, auth_data: aad)
+ cipher.update(ct)
+ assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final }
+
+ # wrong aad is rejected
+ aad2 = aad[0..-2] << aad[-1].succ
+ cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad2)
+ cipher.update(ct)
+ assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final }
+
+ # wrong ciphertext is rejected
+ ct2 = ct[0..-2] << ct[-1].succ
+ cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad)
+ cipher.update(ct2)
+ assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final }
+ end
+
+ def test_aes_gcm_variable_iv_len
+ # GCM spec Appendix B Test Case 5
+ key = ["feffe9928665731c6d6a8f9467308308"].pack("H*")
+ iv = ["cafebabefacedbad"].pack("H*")
+ aad = ["feedfacedeadbeeffeedfacedeadbeef" \
+ "abaddad2"].pack("H*")
+ pt = ["d9313225f88406e5a55909c5aff5269a" \
+ "86a7a9531534f7da2e4c303d8a318a72" \
+ "1c3c0c95956809532fcf0e2449a6b525" \
+ "b16aedf5aa0de657ba637b39"].pack("H*")
+ ct = ["61353b4c2806934a777ff51fa22a4755" \
+ "699b2a714fcdc6f83766e5f97b6c7423" \
+ "73806900e49f24b22b097544d4896b42" \
+ "4989b5e1ebac0f07c23f4598"].pack("H*")
+ tag = ["3612d2e79e3b0785561be14aaca2fccb"].pack("H*")
+
+ cipher = new_encryptor("aes-128-gcm", key: key, iv_len: 8, iv: iv, auth_data: aad)
+ assert_equal ct, cipher.update(pt) << cipher.final
+ assert_equal tag, cipher.auth_tag
+ cipher = new_decryptor("aes-128-gcm", key: key, iv_len: 8, iv: iv, auth_tag: tag, auth_data: aad)
+ assert_equal pt, cipher.update(ct) << cipher.final
+ end
+
+ def test_aes_ocb_tag_len
+ # AES-128-OCB is not FIPS-approved.
+ omit_on_fips
+
+ # RFC 7253 Appendix A; the second sample
+ key = ["000102030405060708090A0B0C0D0E0F"].pack("H*")
+ iv = ["BBAA99887766554433221101"].pack("H*")
+ aad = ["0001020304050607"].pack("H*")
+ pt = ["0001020304050607"].pack("H*")
+ ct = ["6820B3657B6F615A"].pack("H*")
+ tag = ["5725BDA0D3B4EB3A257C9AF1F8F03009"].pack("H*")
+
+ cipher = new_encryptor("aes-128-ocb", key: key, iv: iv, auth_data: aad)
+ assert_equal ct, cipher.update(pt) << cipher.final
+ assert_equal tag, cipher.auth_tag
+ cipher = new_decryptor("aes-128-ocb", key: key, iv: iv, auth_tag: tag, auth_data: aad)
+ assert_equal pt, cipher.update(ct) << cipher.final
+
+ # RFC 7253 Appendix A; with 96 bits tag length
+ key = ["0F0E0D0C0B0A09080706050403020100"].pack("H*")
+ iv = ["BBAA9988776655443322110D"].pack("H*")
+ aad = ["000102030405060708090A0B0C0D0E0F1011121314151617" \
+ "18191A1B1C1D1E1F2021222324252627"].pack("H*")
+ pt = ["000102030405060708090A0B0C0D0E0F1011121314151617" \
+ "18191A1B1C1D1E1F2021222324252627"].pack("H*")
+ ct = ["1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1" \
+ "A0124B0A55BAE884ED93481529C76B6A"].pack("H*")
+ tag = ["D0C515F4D1CDD4FDAC4F02AA"].pack("H*")
+
+ cipher = new_encryptor("aes-128-ocb", auth_tag_len: 12, key: key, iv: iv, auth_data: aad)
+ assert_equal ct, cipher.update(pt) << cipher.final
+ assert_equal tag, cipher.auth_tag
+ cipher = new_decryptor("aes-128-ocb", auth_tag_len: 12, key: key, iv: iv, auth_tag: tag, auth_data: aad)
+ assert_equal pt, cipher.update(ct) << cipher.final
+
+ end if has_cipher?("aes-128-ocb")
+
+ def test_aes_gcm_siv
+ # AES-128-GCM-SIV is not FIPS-approved.
+ omit_on_fips
+
+ # RFC 8452 Appendix C.1., 8th example
+ key = ["01000000000000000000000000000000"].pack("H*")
+ iv = ["030000000000000000000000"].pack("H*")
+ aad = ["01"].pack("H*")
+ pt = ["0200000000000000"].pack("H*")
+ ct = ["1e6daba35669f4273b0a1a2560969cdf790d99759abd1508"].pack("H*")
+ tag = ["3b0a1a2560969cdf790d99759abd1508"].pack("H*")
+ ct_without_tag = ct.byteslice(0, ct.bytesize - tag.bytesize)
+
+ cipher = new_encryptor("aes-128-gcm-siv", key: key, iv: iv, auth_data: aad)
+ assert_equal ct_without_tag, cipher.update(pt) << cipher.final
+ assert_equal tag, cipher.auth_tag
+ cipher = new_decryptor("aes-128-gcm-siv", key: key, iv: iv, auth_tag: tag,
+ auth_data: aad)
+ assert_equal pt, cipher.update(ct_without_tag) << cipher.final
+ end if openssl?(3, 2, 0)
+
+ def test_aes_gcm_key_iv_order_issue
+ pt = "[ruby/openssl#49]"
+ cipher = OpenSSL::Cipher.new("aes-128-gcm").encrypt
+ cipher.key = "x" * 16
+ cipher.iv = "a" * 12
+ ct1 = cipher.update(pt) << cipher.final
+ tag1 = cipher.auth_tag
+
+ cipher = OpenSSL::Cipher.new("aes-128-gcm").encrypt
+ cipher.iv = "a" * 12
+ cipher.key = "x" * 16
+ ct2 = cipher.update(pt) << cipher.final
+ tag2 = cipher.auth_tag
+
+ assert_equal ct1, ct2
+ assert_equal tag1, tag2
+ end
+
+ def test_aes_keywrap_pad
+ # RFC 5649 Section 6; The second example
+ kek = ["5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8"].pack("H*")
+ key = ["466f7250617369"].pack("H*")
+ wrap = ["afbeb0f07dfbf5419200f2ccb50bb24f"].pack("H*")
+
+ begin
+ cipher = OpenSSL::Cipher.new("id-aes192-wrap-pad").encrypt
+ rescue OpenSSL::Cipher::CipherError
+ omit "id-aes192-wrap-pad is not supported: #$!"
+ end
+ cipher.key = kek
+ ct = cipher.update(key) << cipher.final
+ assert_equal wrap, ct
+ end
+
+ def test_non_aead_cipher_set_auth_data
+ assert_raise(OpenSSL::Cipher::CipherError) {
+ cipher = OpenSSL::Cipher.new("aes-128-cfb").encrypt
+ cipher.auth_data = "123"
+ }
+ 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)
+ OpenSSL::Cipher.new(algo).tap do |cipher|
+ cipher.encrypt
+ kwargs.each {|k, v| cipher.send(:"#{k}=", v) }
+ end
+ end
- def test_AES
- pt = File.read(__FILE__)
- %w(ECB CBC CFB OFB).each{|mode|
- c1 = OpenSSL::Cipher::AES256.new(mode)
- c1.encrypt
- c1.pkcs5_keyivgen("passwd")
- ct = c1.update(pt) + c1.final
-
- c2 = OpenSSL::Cipher::AES256.new(mode)
- c2.decrypt
- c2.pkcs5_keyivgen("passwd")
- assert_equal(pt, c2.update(ct) + c2.final)
- }
+ def new_decryptor(algo, **kwargs)
+ OpenSSL::Cipher.new(algo).tap do |cipher|
+ cipher.decrypt
+ kwargs.each {|k, v| cipher.send(:"#{k}=", v) }
end
end
end
diff --git a/test/openssl/test_config.rb b/test/openssl/test_config.rb
new file mode 100644
index 0000000000..c10a855a4b
--- /dev/null
+++ b/test/openssl/test_config.rb
@@ -0,0 +1,306 @@
+# frozen_string_literal: true
+require_relative 'utils'
+
+if defined?(OpenSSL)
+
+class OpenSSL::TestConfig < OpenSSL::TestCase
+ def setup
+ super
+ file = Tempfile.open("openssl.cnf")
+ file << <<__EOD__
+HOME = .
+[ ca ]
+default_ca = CA_default
+[ CA_default ]
+dir = ./demoCA
+certs = ./certs
+__EOD__
+ file.close
+ @tmpfile = file
+ @it = OpenSSL::Config.new(file.path)
+ end
+
+ def teardown
+ super
+ @tmpfile.close!
+ end
+
+ def test_constants
+ assert(defined?(OpenSSL::Config::DEFAULT_CONFIG_FILE))
+ config_file = OpenSSL::Config::DEFAULT_CONFIG_FILE
+ pend "DEFAULT_CONFIG_FILE may return a wrong path on your platforms. [Bug #6830]" unless File.readable?(config_file)
+ assert_nothing_raised do
+ OpenSSL::Config.load(config_file)
+ end
+ end
+
+ def test_s_parse
+ c = OpenSSL::Config.parse('')
+ assert_equal("[ default ]\n\n", c.to_s)
+ c = OpenSSL::Config.parse(@it.to_s)
+ assert_equal(['CA_default', 'ca', 'default'], c.sections.sort)
+ assert_predicate(c, :frozen?)
+ end
+
+ def test_s_parse_format
+ # AWS-LC removed support for parsing $foo variables.
+ return if aws_lc?
+
+ c = OpenSSL::Config.parse(<<__EOC__)
+ baz =qx\t # "baz = qx"
+
+foo::bar = baz # shortcut section::key format
+ default::bar = baz # ditto
+a=\t \t # "a = ": trailing spaces are ignored
+ =b # " = b": empty key
+ =c # " = c": empty key (override the above line)
+ d= # "c = ": trailing comment is ignored
+
+sq = 'foo''b\\'ar'
+ dq ="foo""''\\""
+ dq2 = foo""bar
+esc=a\\r\\n\\b\\tb
+foo\\bar = foo\\b\\\\ar
+foo\\bar::foo\\bar = baz
+[default1 default2]\t\t # space is allowed in section name
+ fo =b ar # space allowed in value
+[emptysection]
+ [dollar ]
+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(['', 'a', 'bar', 'baz', 'd', 'dq', 'dq2', 'esc', 'foo\\bar', 'sq'], c['default'].keys.sort)
+ assert_equal('c', c['default'][''])
+ assert_equal('', c['default']['a'])
+ assert_equal('qx', c['default']['baz'])
+ assert_equal('', c['default']['d'])
+ assert_equal('baz', c['default']['bar'])
+ assert_equal("foob'ar", c['default']['sq'])
+ assert_equal("foo''\"", c['default']['dq'])
+ assert_equal("foobar", c['default']['dq2'])
+ assert_equal("a\r\n\b\tb", c['default']['esc'])
+ assert_equal("foo\b\\ar", c['default']['foo\\bar'])
+ assert_equal('baz', c['foo']['bar'])
+ 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'])
+
+ assert_raise_with_message(OpenSSL::ConfigError, /error in line 1: variable has no value/) do
+ OpenSSL::Config.parse("foo = $bar")
+ end
+
+ assert_raise_with_message(OpenSSL::ConfigError, /error in line 1: no close brace/) do
+ OpenSSL::Config.parse("foo = $(bar")
+ end
+
+ assert_raise_with_message(OpenSSL::ConfigError, /error in line 1: missing equal sign/) do
+ OpenSSL::Config.parse("f o =b ar # no space in key")
+ end
+
+ assert_raise_with_message(OpenSSL::ConfigError, /error in line 7: missing close square bracket/) do
+ OpenSSL::Config.parse(<<__EOC__)
+# comment 1 # comments
+
+#
+ # comment 2
+\t#comment 3
+ [second ]\t
+[third # section not terminated
+__EOC__
+ end
+ end
+
+ def test_s_parse_include
+ if !openssl?(1, 1, 1, 2)
+ # OpenSSL < 1.1.1 parses .include directive as a normal assignment
+ pend ".include directive is not supported"
+ end
+
+ in_tmpdir("ossl-config-include-test") do |dir|
+ Dir.mkdir("child")
+ File.write("child/a.conf", <<~__EOC__)
+ [default]
+ file-a = a.conf
+ [sec-a]
+ a = 123
+ __EOC__
+ File.write("child/b.cnf", <<~__EOC__)
+ [default]
+ file-b = b.cnf
+ [sec-b]
+ b = 123
+ __EOC__
+ File.write("include-child.conf", <<~__EOC__)
+ key_outside_section = value_a
+ .include child
+ __EOC__
+
+ include_file = <<~__EOC__
+ [default]
+ file-main = unnamed
+ [sec-main]
+ main = 123
+ .include = include-child.conf
+ __EOC__
+
+ # Include a file by relative path
+ c1 = OpenSSL::Config.parse(include_file)
+ assert_equal(["default", "sec-a", "sec-b", "sec-main"], c1.sections.sort)
+ assert_equal(["file-a", "file-b", "file-main"], c1["default"].keys.sort)
+ assert_equal({"a" => "123"}, c1["sec-a"])
+ assert_equal({"b" => "123"}, c1["sec-b"])
+ assert_equal({"main" => "123", "key_outside_section" => "value_a"}, c1["sec-main"])
+
+ # Relative paths are from the working directory
+ # Inclusion fails, but the error is ignored silently
+ c2 = Dir.chdir("child") { OpenSSL::Config.parse(include_file) }
+ assert_equal(["default", "sec-main"], c2.sections.sort)
+ end
+ end
+
+ def test_s_load
+ # alias of new
+ c = OpenSSL::Config.load
+ assert_equal("", c.to_s)
+ assert_equal([], c.sections)
+ #
+ Tempfile.create("openssl.cnf") {|file|
+ file.close
+ c = OpenSSL::Config.load(file.path)
+ assert_equal("[ default ]\n\n", c.to_s)
+ assert_equal(['default'], c.sections)
+ }
+ end
+
+ def test_s_parse_config
+ ret = OpenSSL::Config.parse_config(@it.to_s)
+ assert_equal(@it.sections.sort, ret.keys.sort)
+ assert_equal(@it["default"], ret["default"])
+ end
+
+ def test_initialize
+ c = OpenSSL::Config.new
+ assert_equal("", c.to_s)
+ assert_equal([], c.sections)
+ assert_predicate(c, :frozen?)
+ end
+
+ def test_initialize_with_empty_file
+ Tempfile.create("openssl.cnf") {|file|
+ file.close
+ c = OpenSSL::Config.new(file.path)
+ assert_equal("[ default ]\n\n", c.to_s)
+ assert_equal(['default'], c.sections)
+ }
+ end
+
+ def test_initialize_with_example_file
+ assert_equal(['CA_default', 'ca', 'default'], @it.sections.sort)
+ end
+
+ def test_get_value
+ assert_equal('CA_default', @it.get_value('ca', 'default_ca'))
+ assert_equal(nil, @it.get_value('ca', 'no such key'))
+ assert_equal(nil, @it.get_value('no such section', 'no such key'))
+ assert_equal('.', @it.get_value('', 'HOME'))
+ assert_raise(TypeError) do
+ @it.get_value(nil, 'HOME') # not allowed unlike Config#value
+ end
+ unless aws_lc? # AWS-LC does not support the fallback
+ # fallback to 'default' ugly...
+ assert_equal('.', @it.get_value('unknown', 'HOME'))
+ end
+ end
+
+ def test_get_value_ENV
+ # LibreSSL and AWS-LC removed support for NCONF_get_string(conf, "ENV", str)
+ return if libressl? || aws_lc?
+
+ key = ENV.keys.first
+ assert_not_nil(key) # make sure we have at least one ENV var.
+ assert_equal(ENV[key], @it.get_value('ENV', key))
+ end
+
+ def test_aref
+ assert_equal({'HOME' => '.'}, @it['default'])
+ assert_equal({'dir' => './demoCA', 'certs' => './certs'}, @it['CA_default'])
+ assert_equal({}, @it['no_such_section'])
+ assert_equal({}, @it[''])
+ end
+
+ def test_sections
+ assert_equal(['CA_default', 'ca', 'default'], @it.sections.sort)
+ Tempfile.create("openssl.cnf") { |f|
+ f.write File.read(@tmpfile.path)
+ f.puts "[ new_section ]"
+ f.puts "foo = bar"
+ f.puts "[ empty_section ]"
+ f.close
+
+ c = OpenSSL::Config.new(f.path)
+ assert_equal(['CA_default', 'ca', 'default', 'empty_section', 'new_section'],
+ c.sections.sort)
+ }
+ end
+
+ def test_each
+ # each returns [section, key, value] array.
+ ary = @it.map { |e| e }.sort { |a, b| a[0] <=> b[0] }
+ assert_equal(4, ary.size)
+ assert_equal('CA_default', ary[0][0])
+ assert_equal('CA_default', ary[1][0])
+ assert_equal(["ca", "default_ca", "CA_default"], ary[2])
+ assert_equal(["default", "HOME", "."], ary[3])
+ end
+
+ def test_to_s
+ c = OpenSSL::Config.parse("[empty]\n")
+ assert_equal("[ default ]\n\n[ empty ]\n\n", c.to_s)
+ end
+
+ def test_inspect
+ assert_match(/#<OpenSSL::Config sections=\[.*\]>/, @it.inspect)
+ end
+
+ def test_dup
+ assert_equal(['CA_default', 'ca', 'default'], @it.sections.sort)
+ c1 = @it.dup
+ assert_predicate(c1, :frozen?)
+ assert_equal(@it.sections.sort, c1.sections.sort)
+ c2 = @it.clone
+ assert_predicate(c2, :frozen?)
+ assert_equal(@it.sections.sort, c2.sections.sort)
+ end
+
+ if respond_to?(:ractor)
+ ractor
+ def test_ractor
+ assert(Ractor.shareable?(@it))
+ assert(Ractor.shareable?(OpenSSL::Config.parse("[empty]\n")))
+ assert(Ractor.shareable?(OpenSSL::Config::DEFAULT_CONFIG_FILE))
+ end
+ end
+
+ private
+
+ def in_tmpdir(*args)
+ Dir.mktmpdir(*args) do |dir|
+ dir = File.realpath(dir)
+ Dir.chdir(dir) do
+ yield dir
+ end
+ end
+ end
+end
+
+end
diff --git a/test/openssl/test_digest.rb b/test/openssl/test_digest.rb
index 8941588b97..bc1f680df5 100644
--- a/test/openssl/test_digest.rb
+++ b/test/openssl/test_digest.rb
@@ -1,36 +1,36 @@
-begin
- require "openssl"
-rescue LoadError
-end
-require "digest/md5"
-require "test/unit"
+# frozen_string_literal: true
+require_relative 'utils'
if defined?(OpenSSL)
-class OpenSSL::TestDigest < Test::Unit::TestCase
+class OpenSSL::TestDigest < OpenSSL::TestCase
def setup
- @d1 = OpenSSL::Digest::Digest::new("MD5")
- @d2 = OpenSSL::Digest::MD5.new
- @md = Digest::MD5.new
- @data = "DATA"
+ super
+ @d1 = OpenSSL::Digest.new("SHA256")
+ @d2 = OpenSSL::Digest::SHA256.new
end
- def teardown
- @d1 = @d2 = @md = nil
+ def test_initialize
+ assert_raise(OpenSSL::Digest::DigestError) {
+ OpenSSL::Digest.new("no such algorithm")
+ }
end
def test_digest
- assert_equal(@md.digest, @d1.digest)
- assert_equal(@md.hexdigest, @d1.hexdigest)
- @d1 << @data
- @d2 << @data
- @md << @data
- assert_equal(@md.digest, @d1.digest)
- assert_equal(@md.hexdigest, @d1.hexdigest)
- assert_equal(@d1.digest, @d2.digest)
- assert_equal(@d1.hexdigest, @d2.hexdigest)
- assert_equal(@md.digest, OpenSSL::Digest::MD5.digest(@data))
- assert_equal(@md.hexdigest, OpenSSL::Digest::MD5.hexdigest(@data))
+ # SHA256 null value calculated by `echo -n "" | sha256sum`
+ null_hex = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ null_bin = [null_hex].pack("H*")
+ data = "DATA"
+ # SHA256 DATA value calculated by `echo -n "DATA" | sha256sum`
+ hex = "c97c29c7a71b392b437ee03fd17f09bb10b75e879466fc0eb757b2c4a78ac938"
+ bin = [hex].pack("H*")
+ assert_equal(null_bin, @d1.digest)
+ assert_equal(null_hex, @d1.hexdigest)
+ @d1 << data
+ assert_equal(bin, @d1.digest)
+ assert_equal(hex, @d1.hexdigest)
+ assert_equal(bin, OpenSSL::Digest.digest('SHA256', data))
+ assert_equal(hex, OpenSSL::Digest.hexdigest('SHA256', data))
end
def test_eql
@@ -40,47 +40,135 @@ class OpenSSL::TestDigest < Test::Unit::TestCase
end
def test_info
- assert_equal("MD5", @d1.name, "name")
- assert_equal("MD5", @d2.name, "name")
- assert_equal(16, @d1.size, "size")
+ assert_equal("SHA256", @d1.name, "name")
+ assert_equal("SHA256", @d2.name, "name")
+ assert_equal(32, @d1.size, "size")
end
def test_dup
- @d1.update(@data)
+ @d1.update("DATA")
assert_equal(@d1.name, @d1.dup.name, "dup")
assert_equal(@d1.name, @d1.clone.name, "clone")
assert_equal(@d1.digest, @d1.clone.digest, "clone .digest")
end
def test_reset
- @d1.update(@data)
+ @d1.update("DATA")
dig1 = @d1.digest
@d1.reset
- @d1.update(@data)
+ @d1.update("DATA")
dig2 = @d1.digest
assert_equal(dig1, dig2, "reset")
end
- if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
- def encode16(str)
- str.unpack("H*").first
+ def test_digest_constants
+ non_fips_names = %w{MD5}
+ names = %w{SHA1 SHA224 SHA256 SHA384 SHA512}
+ names = non_fips_names + names unless OpenSSL.fips_mode
+ names.each do |name|
+ assert_not_nil(OpenSSL::Digest.new(name))
+ klass = OpenSSL::Digest.const_get(name.tr('-', '_'))
+ assert_not_nil(klass.new)
+ end
+ end
+
+ def test_digest_by_oid_and_name
+ # SHA256
+ o1 = OpenSSL::Digest.digest("SHA256", "")
+ o2 = OpenSSL::Digest.digest("sha256", "")
+ assert_equal(o1, o2)
+ o3 = OpenSSL::Digest.digest("2.16.840.1.101.3.4.2.1", "")
+ assert_equal(o1, o3)
+
+ # An alias for SHA256 recognized by EVP_get_digestbyname(), but not by
+ # EVP_MD_fetch()
+ o4 = OpenSSL::Digest.digest("RSA-SHA256", "")
+ assert_equal(o1, o4)
+ end
+
+ def encode16(str)
+ str.unpack1("H*")
+ end
+
+ def test_sha2
+ sha224_a = "abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5"
+ sha256_a = "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"
+ sha384_a = "54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31"
+ sha512_a = "1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75"
+
+ assert_equal(sha224_a, OpenSSL::Digest.hexdigest('SHA224', "a"))
+ assert_equal(sha256_a, OpenSSL::Digest.hexdigest('SHA256', "a"))
+ assert_equal(sha384_a, OpenSSL::Digest.hexdigest('SHA384', "a"))
+ assert_equal(sha512_a, OpenSSL::Digest.hexdigest('SHA512', "a"))
+
+ assert_equal(sha224_a, encode16(OpenSSL::Digest.digest('SHA224', "a")))
+ assert_equal(sha256_a, encode16(OpenSSL::Digest.digest('SHA256', "a")))
+ assert_equal(sha384_a, encode16(OpenSSL::Digest.digest('SHA384', "a")))
+ assert_equal(sha512_a, encode16(OpenSSL::Digest.digest('SHA512', "a")))
+ end
+
+ def test_sha512_truncate
+ sha512_224_a = "d5cdb9ccc769a5121d4175f2bfdd13d6310e0d3d361ea75d82108327"
+ sha512_256_a = "455e518824bc0601f9fb858ff5c37d417d67c2f8e0df2babe4808858aea830f8"
+
+ assert_equal(sha512_224_a, OpenSSL::Digest.hexdigest('SHA512-224', "a"))
+ assert_equal(sha512_256_a, OpenSSL::Digest.hexdigest('SHA512-256', "a"))
+
+ assert_equal(sha512_224_a, encode16(OpenSSL::Digest.digest('SHA512-224', "a")))
+ assert_equal(sha512_256_a, encode16(OpenSSL::Digest.digest('SHA512-256', "a")))
+ end
+
+ def test_sha3
+ s224 = '6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7'
+ s256 = 'a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a'
+ s384 = '0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004'
+ s512 = 'a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26'
+ assert_equal(s224, OpenSSL::Digest.hexdigest('SHA3-224', ""))
+ assert_equal(s256, OpenSSL::Digest.hexdigest('SHA3-256', ""))
+ assert_equal(s384, OpenSSL::Digest.hexdigest('SHA3-384', ""))
+ assert_equal(s512, OpenSSL::Digest.hexdigest('SHA3-512', ""))
+ end
+
+ def test_fetched_evp_md
+ # KECCAK-256 is not FIPS-approved.
+ omit_on_fips
+
+ # Pre-NIST Keccak is an example of a digest algorithm that doesn't have an
+ # NID and requires dynamic allocation of EVP_MD
+ hex = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
+ assert_equal(hex, OpenSSL::Digest.hexdigest("KECCAK-256", ""))
+ end if openssl?(3, 2, 0)
+
+ def test_openssl_digest
+ assert_equal OpenSSL::Digest::MD5, OpenSSL::Digest("MD5")
+
+ assert_raise NameError do
+ OpenSSL::Digest("no such digest")
end
+ end
+
+ def test_digests
+ digests = OpenSSL::Digest.digests
+ assert_kind_of Array, digests
+ assert_include digests, "md5"
+ assert_include digests, "sha1"
+ assert_include digests, "sha256"
+ assert_include digests, "sha512"
+ end
+
+ if respond_to?(:ractor) && defined?(Ractor.shareable_proc)
+ ractor
- def test_098_features
- sha224_a = "abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5"
- sha256_a = "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"
- sha384_a = "54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31"
- sha512_a = "1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75"
-
- assert_equal(sha224_a, OpenSSL::Digest::SHA224.hexdigest("a"))
- assert_equal(sha256_a, OpenSSL::Digest::SHA256.hexdigest("a"))
- assert_equal(sha384_a, OpenSSL::Digest::SHA384.hexdigest("a"))
- assert_equal(sha512_a, OpenSSL::Digest::SHA512.hexdigest("a"))
-
- assert_equal(sha224_a, encode16(OpenSSL::Digest::SHA224.digest("a")))
- assert_equal(sha256_a, encode16(OpenSSL::Digest::SHA256.digest("a")))
- assert_equal(sha384_a, encode16(OpenSSL::Digest::SHA384.digest("a")))
- assert_equal(sha512_a, encode16(OpenSSL::Digest::SHA512.digest("a")))
+ def test_ractor
+ assert_nothing_raised do
+ Ractor.new {
+ [
+ OpenSSL::Digest::SHA256.new(""),
+ OpenSSL::Digest::SHA256.hexdigest(""),
+ OpenSSL::Digest::SHA256.digest(""),
+ ]
+ }.value
+ end
end
end
end
diff --git a/test/openssl/test_ec.rb b/test/openssl/test_ec.rb
deleted file mode 100644
index 671901ca36..0000000000
--- a/test/openssl/test_ec.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-begin
- require "openssl"
- require File.join(File.dirname(__FILE__), "utils.rb")
-rescue LoadError
-end
-require "test/unit"
-
-if defined?(OpenSSL::PKey::EC)
-
-class OpenSSL::TestEC < Test::Unit::TestCase
- def setup
- @data1 = 'foo'
- @data2 = 'bar' * 1000 # data too long for DSA sig
-
- @group1 = OpenSSL::PKey::EC::Group.new('secp112r1')
- @group2 = OpenSSL::PKey::EC::Group.new('sect163k1')
-
- @key1 = OpenSSL::PKey::EC.new
- @key1.group = @group1
- @key1.generate_key
-
- @key2 = OpenSSL::PKey::EC.new(@group2.curve_name)
- @key2.generate_key
-
- @groups = [@group1, @group2]
- @keys = [@key1, @key2]
- end
-
- def compare_keys(k1, k2)
- assert_equal(k1.to_pem, k2.to_pem)
- end
-
- def test_curve_names
- @groups.each_with_index do |group, idx|
- key = @keys[idx]
- assert_equal(group.curve_name, key.group.curve_name)
- end
- end
-
- def test_check_key
- for key in @keys
- assert_equal(key.check_key, true)
- assert_equal(key.private_key?, true)
- assert_equal(key.public_key?, true)
- end
- end
-
- def test_encoding
- for group in @groups
- for meth in [:to_der, :to_pem]
- txt = group.send(meth)
- gr = OpenSSL::PKey::EC::Group.new(txt)
- assert_equal(txt, gr.send(meth))
-
- assert_equal(group.generator.to_bn, gr.generator.to_bn)
- assert_equal(group.cofactor, gr.cofactor)
- assert_equal(group.order, gr.order)
- assert_equal(group.seed, gr.seed)
- assert_equal(group.degree, gr.degree)
- end
- end
-
- for key in @keys
- group = key.group
-
- for meth in [:to_der, :to_pem]
- txt = key.send(meth)
- assert_equal(txt, OpenSSL::PKey::EC.new(txt).send(meth))
- end
-
- bn = key.public_key.to_bn
- assert_equal(bn, OpenSSL::PKey::EC::Point.new(group, bn).to_bn)
- end
- end
-
- def test_set_keys
- for key in @keys
- k = OpenSSL::PKey::EC.new
- k.group = key.group
- k.private_key = key.private_key
- k.public_key = key.public_key
-
- compare_keys(key, k)
- end
- end
-
- def test_dsa_sign_verify
- for key in @keys
- sig = key.dsa_sign_asn1(@data1)
- assert_equal(key.dsa_verify_asn1(@data1, sig), true)
-
- assert_raises(OpenSSL::PKey::ECError) { key.dsa_sign_asn1(@data2) }
- end
- end
-
- def test_dh_compute_key
- for key in @keys
- k = OpenSSL::PKey::EC.new(key.group)
- k.generate_key
-
- puba = key.public_key
- pubb = k.public_key
- a = key.dh_compute_key(pubb)
- b = k.dh_compute_key(puba)
- assert_equal(a, b)
- end
- end
-
-# test Group: asn1_flag, point_conversion
-
-end
-
-end
diff --git a/test/openssl/test_engine.rb b/test/openssl/test_engine.rb
new file mode 100644
index 0000000000..b6025f915b
--- /dev/null
+++ b/test/openssl/test_engine.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+require_relative 'utils'
+
+if defined?(OpenSSL) && defined?(OpenSSL::Engine)
+
+class OpenSSL::TestEngine < OpenSSL::TestCase
+ def test_engines_free # [ruby-dev:44173]
+ with_openssl <<-'end;'
+ OpenSSL::Engine.load("openssl")
+ OpenSSL::Engine.engines
+ OpenSSL::Engine.engines
+ end;
+ end
+
+ def test_openssl_engine_builtin
+ with_openssl <<-'end;'
+ orig = OpenSSL::Engine.engines
+ pend "'openssl' is already loaded" if orig.any? { |e| e.id == "openssl" }
+ engine = OpenSSL::Engine.load("openssl")
+ assert_equal(true, engine)
+ assert_equal(1, OpenSSL::Engine.engines.size - orig.size)
+ end;
+ end
+
+ def test_openssl_engine_by_id_string
+ with_openssl <<-'end;'
+ orig = OpenSSL::Engine.engines
+ pend "'openssl' is already loaded" if orig.any? { |e| e.id == "openssl" }
+ engine = OpenSSL::Engine.by_id("openssl")
+ assert_not_nil(engine)
+ assert_equal(1, OpenSSL::Engine.engines.size - orig.size)
+ end;
+ end
+
+ def test_openssl_engine_id_name_inspect
+ with_openssl <<-'end;'
+ engine = OpenSSL::Engine.by_id("openssl")
+ assert_equal("openssl", engine.id)
+ assert_not_nil(engine.name)
+ assert_not_nil(engine.inspect)
+ end;
+ end
+
+ def test_openssl_engine_digest_sha1
+ with_openssl <<-'end;'
+ engine = OpenSSL::Engine.by_id("openssl")
+ digest = engine.digest("SHA1")
+ assert_not_nil(digest)
+ data = "test"
+ assert_equal(OpenSSL::Digest.digest('SHA1', data), digest.digest(data))
+ end;
+ end
+
+ def test_openssl_engine_cipher_rc4
+ begin
+ OpenSSL::Cipher.new("rc4")
+ rescue OpenSSL::Cipher::CipherError
+ pend "RC4 is not supported"
+ end
+
+ with_openssl(<<-'end;', ignore_stderr: true)
+ engine = OpenSSL::Engine.by_id("openssl")
+ algo = "RC4"
+ data = "a" * 1000
+ key = OpenSSL::Random.random_bytes(16)
+
+ cipher = engine.cipher(algo)
+ cipher.encrypt
+ cipher.key = key
+ encrypted = cipher.update(data) + cipher.final
+
+ cipher = OpenSSL::Cipher.new(algo)
+ cipher.decrypt
+ cipher.key = key
+ decrypted = cipher.update(encrypted) + cipher.final
+
+ assert_equal(data, decrypted)
+ end;
+ end
+
+ private
+
+ # this is required because OpenSSL::Engine methods change global state
+ def with_openssl(code, **opts)
+ assert_separately(["-ropenssl"], <<~"end;", **opts)
+ #{code}
+ end;
+ end
+end
+
+end
diff --git a/test/openssl/test_fips.rb b/test/openssl/test_fips.rb
new file mode 100644
index 0000000000..683e0011e8
--- /dev/null
+++ b/test/openssl/test_fips.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+require_relative 'utils'
+
+if defined?(OpenSSL)
+
+class OpenSSL::TestFIPS < OpenSSL::TestCase
+ def test_fips_mode_get_is_true_on_fips_mode_enabled
+ unless ENV["TEST_RUBY_OPENSSL_FIPS_ENABLED"]
+ omit "Only for FIPS mode environment"
+ end
+
+ assert_separately(["-ropenssl"], <<~"end;")
+ assert OpenSSL.fips_mode == true, ".fips_mode should return true on FIPS mode enabled"
+ end;
+ end
+
+ def test_fips_mode_get_is_false_on_fips_mode_disabled
+ if ENV["TEST_RUBY_OPENSSL_FIPS_ENABLED"]
+ omit "Only for non-FIPS mode environment"
+ end
+
+ assert_separately(["-ropenssl"], <<~"end;")
+ message = ".fips_mode should return false on FIPS mode disabled. " \
+ "If you run the test on FIPS mode, please set " \
+ "TEST_RUBY_OPENSSL_FIPS_ENABLED=true"
+ assert OpenSSL.fips_mode == false, message
+ end;
+ end
+
+ def test_fips_mode_is_reentrant
+ return if aws_lc? # AWS-LC's FIPS mode is decided at compile time.
+
+ assert_ruby_status(["-ropenssl"], <<~"end;")
+ OpenSSL.fips_mode = false
+ OpenSSL.fips_mode = false
+ end;
+ end
+
+ def test_fips_mode_get_with_fips_mode_set
+ return if aws_lc? # AWS-LC's FIPS mode is decided at compile time.
+ unless ENV["TEST_RUBY_OPENSSL_FIPS_ENABLED"]
+ omit "Only for FIPS mode environment"
+ end
+
+ assert_separately(["-ropenssl"], <<~"end;")
+ begin
+ OpenSSL.fips_mode = true
+ assert OpenSSL.fips_mode == true, ".fips_mode should return true when .fips_mode=true"
+
+ OpenSSL.fips_mode = false
+ assert OpenSSL.fips_mode == false, ".fips_mode should return false when .fips_mode=false"
+ rescue OpenSSL::OpenSSLError
+ pend "Could not set FIPS mode (OpenSSL::OpenSSLError: \#$!); skipping"
+ end
+ end;
+ end
+end
+
+end
diff --git a/test/openssl/test_hmac.rb b/test/openssl/test_hmac.rb
index 2f8d6bba20..7cf820628e 100644
--- a/test/openssl/test_hmac.rb
+++ b/test/openssl/test_hmac.rb
@@ -1,33 +1,80 @@
-begin
- require "openssl"
-rescue LoadError
-end
-require "test/unit"
+# frozen_string_literal: true
+require_relative 'utils'
if defined?(OpenSSL)
-class OpenSSL::TestHMAC < Test::Unit::TestCase
- def setup
- @digest = OpenSSL::Digest::MD5.new
- @key = "KEY"
- @data = "DATA"
- @h1 = OpenSSL::HMAC.new(@key, @digest)
- @h2 = OpenSSL::HMAC.new(@key, @digest)
- end
+class OpenSSL::TestHMAC < OpenSSL::TestCase
+ def test_hmac_md5
+ omit_on_fips # MD5
- def teardown
+ # RFC 2202 2. Test Cases for HMAC-MD5
+ hmac = OpenSSL::HMAC.new(["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*"), "MD5")
+ hmac.update("Hi There")
+ assert_equal ["9294727a3638bb1c13f48ef8158bfc9d"].pack("H*"), hmac.digest
+ assert_equal "9294727a3638bb1c13f48ef8158bfc9d", hmac.hexdigest
+ assert_equal "kpRyejY4uxwT9I74FYv8nQ==", hmac.base64digest
end
- def test_hmac
- @h1.update(@data)
- assert_equal(OpenSSL::HMAC.digest(@digest, @key, @data), @h1.digest, "digest")
- assert_equal(OpenSSL::HMAC.hexdigest(@digest, @key, @data), @h1.hexdigest, "hexdigest")
+ def test_hmac_sha224
+ # RFC 4231 4.2. Test Case 1
+ hmac = OpenSSL::HMAC.new(["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*"), "SHA224")
+ hmac.update("Hi There")
+ assert_equal ["896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22"].pack("H*"), hmac.digest
+ assert_equal "896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22", hmac.hexdigest
+ assert_equal "iW+xEoq73xloMhB81J3zP0e0sRaZErpPU2hLIg==", hmac.base64digest
end
def test_dup
- @h1.update(@data)
- h = @h1.dup
- assert_equal(@h1.digest, h.digest, "dup digest")
+ h1 = OpenSSL::HMAC.new("KEY"*32, "SHA256")
+ h1.update("DATA")
+ h = h1.dup
+ assert_equal(h1.digest, h.digest, "dup digest")
+ end
+
+ def test_binary_update
+ data = "Lücíllé: Bût... yøü sáîd hé wås âlrîght.\nDr. Físhmån: Yés. Hé's løst hîs léft hånd, sø hé's gøîng tø bé åll rîght"
+ hmac = OpenSSL::HMAC.new("qShkcwN92rsM9nHfdnP4ugcVU2iI7iM/trovs01ZWok", "SHA256")
+ result = hmac.update(data).hexdigest
+ assert_equal "a13984b929a07912e4e21c5720876a8e150d6f67f854437206e7f86547248396", result
+ end
+
+ def test_reset_keep_key
+ h1 = OpenSSL::HMAC.new("KEY"*32, "SHA256")
+ first = h1.update("test").hexdigest
+ h1.reset
+ second = h1.update("test").hexdigest
+ assert_equal first, second
+ end
+
+ def test_eq
+ h1 = OpenSSL::HMAC.new("KEY"*32, "SHA256")
+ h2 = OpenSSL::HMAC.new("KEY"*32, OpenSSL::Digest.new("SHA256"))
+ h3 = OpenSSL::HMAC.new("FOO"*32, "SHA256")
+
+ assert_equal h1, h2
+ refute_equal h1, h2.digest
+ refute_equal h1, h3
+ end
+
+ def test_singleton_methods
+ # RFC 4231 4.2. Test Case 1
+ key = ["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*")
+ digest = OpenSSL::HMAC.digest("SHA256", key, "Hi There")
+ assert_equal ["b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"].pack("H*"), digest
+ hexdigest = OpenSSL::HMAC.hexdigest("SHA256", key, "Hi There")
+ assert_equal "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", hexdigest
+ b64digest = OpenSSL::HMAC.base64digest("SHA256", key, "Hi There")
+ assert_equal "sDRMYdjbOFNcqK/OrwvxK4gdwgDJgz2nJuk3bC4yz/c=", b64digest
+ end
+
+ def test_zero_length_key
+ omit_on_fips # Key length
+
+ # Empty string as the key
+ hexdigest = OpenSSL::HMAC.hexdigest("SHA256", "\0"*32, "test")
+ assert_equal "43b0cef99265f9e34c10ea9d3501926d27b39f57c6d674561d8ba236e7a819fb", hexdigest
+ hexdigest = OpenSSL::HMAC.hexdigest("SHA256", "", "test")
+ assert_equal "43b0cef99265f9e34c10ea9d3501926d27b39f57c6d674561d8ba236e7a819fb", hexdigest
end
end
diff --git a/test/openssl/test_kdf.rb b/test/openssl/test_kdf.rb
new file mode 100644
index 0000000000..708d1883af
--- /dev/null
+++ b/test/openssl/test_kdf.rb
@@ -0,0 +1,161 @@
+# frozen_string_literal: true
+require_relative 'utils'
+
+if defined?(OpenSSL)
+
+class OpenSSL::TestKDF < OpenSSL::TestCase
+ def test_pkcs5_pbkdf2_hmac_compatibility
+ # PBKDF2 salt >= 16 bytes (128 bits) and iterations >= 1000 are required in
+ # FIPS.
+ # SP 800-132.
+ # https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf
+ # * 5.1 The Salt (S)
+ # * 5.2 The Iteration Count (C)
+ # https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/providers/implementations/kdfs/pbkdf2.c#L235-L240
+ # https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/providers/implementations/kdfs/pbkdf2.c#L247-L252
+ # Use the same parameters with test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25.
+ expected = OpenSSL::KDF.pbkdf2_hmac("passwordPASSWORDpassword",
+ salt: "saltSALTsaltSALTsaltSALTsaltSALTsalt",
+ iterations: 4096,
+ length: 25,
+ hash: "sha1")
+ assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac("passwordPASSWORDpassword",
+ "saltSALTsaltSALTsaltSALTsaltSALTsalt",
+ 4096,
+ 25,
+ "sha1"))
+ assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac_sha1("passwordPASSWORDpassword",
+ "saltSALTsaltSALTsaltSALTsaltSALTsalt",
+ 4096,
+ 25))
+ end
+
+ def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25
+ p ="passwordPASSWORDpassword"
+ s = "saltSALTsaltSALTsaltSALTsaltSALTsalt"
+ c = 4096
+ dk_len = 25
+
+ raw = %w{ 3d 2e ec 4f e4 1c 84 9b
+ 80 c8 d8 36 62 c0 e4 4a
+ 8b 29 1a 96 4c f2 f0 70
+ 38 }
+ expected = [raw.join('')].pack('H*')
+ value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1")
+ assert_equal(expected, value)
+ end
+
+ def test_pbkdf2_hmac_sha256_c_20000_len_32
+ #unfortunately no official test vectors available yet for SHA-2
+ p ="password"
+ s = OpenSSL::Random.random_bytes(16)
+ c = 20000
+ dk_len = 32
+ value1 = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha256")
+ value2 = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha256")
+ assert_equal(value1, value2)
+ end
+
+ def test_scrypt_rfc7914_first
+ pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0
+ # scrypt is not available in FIPS.
+ # EVP_KDF_fetch(ctx, OSSL_KDF_NAME_SCRYPT, propq) returns NULL in FIPS.
+ # https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/crypto/evp/pbe_scrypt.c#L67-L71
+ omit_on_fips
+
+ pass = ""
+ salt = ""
+ n = 16
+ r = 1
+ p = 1
+ dklen = 64
+ expected = B(%w{ 77 d6 57 62 38 65 7b 20 3b 19 ca 42 c1 8a 04 97
+ f1 6b 48 44 e3 07 4a e8 df df fa 3f ed e2 14 42
+ fc d0 06 9d ed 09 48 f8 32 6a 75 3a 0f c8 1f 17
+ e8 d3 e0 fb 2e 0d 36 28 cf 35 e2 0c 38 d1 89 06 })
+ assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen))
+ end
+
+ def test_scrypt_rfc7914_second
+ pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0
+ # scrypt is not available in FIPS.
+ omit_on_fips
+
+ pass = "password"
+ salt = "NaCl"
+ n = 1024
+ r = 8
+ p = 16
+ dklen = 64
+ expected = B(%w{ fd ba be 1c 9d 34 72 00 78 56 e7 19 0d 01 e9 fe
+ 7c 6a d7 cb c8 23 78 30 e7 73 76 63 4b 37 31 62
+ 2e af 30 d9 2e 22 a3 88 6f f1 09 27 9d 98 30 da
+ c7 27 af b9 4a 83 ee 6d 83 60 cb df a2 cc 06 40 })
+ assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen))
+ end
+
+ # https://www.rfc-editor.org/rfc/rfc5869#appendix-A.1
+ def test_hkdf_rfc5869_test_case_1
+ hash = "sha256"
+ ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
+ salt = B("000102030405060708090a0b0c")
+ info = B("f0f1f2f3f4f5f6f7f8f9")
+ l = 42
+
+ okm = B("3cb25f25faacd57a90434f64d0362f2a" \
+ "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" \
+ "34007208d5b887185865")
+ assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash))
+ end
+
+ # https://www.rfc-editor.org/rfc/rfc5869#appendix-A.3
+ def test_hkdf_rfc5869_test_case_3
+ hash = "sha256"
+ ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
+ salt = B("")
+ info = B("")
+ l = 42
+
+ okm = B("8da4e775a563c18f715f802a063c5a31" \
+ "b8a11f5c5ee1879ec3454e5f3c738d2d" \
+ "9d201395faa4b61a96c8")
+ assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash))
+ end
+
+ # https://www.rfc-editor.org/rfc/rfc5869#appendix-A.5
+ def test_hkdf_rfc5869_test_case_5
+ hash = "sha1"
+ ikm = B("000102030405060708090a0b0c0d0e0f" \
+ "101112131415161718191a1b1c1d1e1f" \
+ "202122232425262728292a2b2c2d2e2f" \
+ "303132333435363738393a3b3c3d3e3f" \
+ "404142434445464748494a4b4c4d4e4f")
+ salt = B("606162636465666768696a6b6c6d6e6f" \
+ "707172737475767778797a7b7c7d7e7f" \
+ "808182838485868788898a8b8c8d8e8f" \
+ "909192939495969798999a9b9c9d9e9f" \
+ "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf")
+ info = B("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" \
+ "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" \
+ "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" \
+ "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" \
+ "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
+ l = 82
+
+ okm = B("0bd770a74d1160f7c9f12cd5912a06eb" \
+ "ff6adcae899d92191fe4305673ba2ffe" \
+ "8fa3f1a4e5ad79f3f334b3b202b2173c" \
+ "486ea37ce3d397ed034c7f9dfeb15c5e" \
+ "927336d0441f4c4300e2cff0d0900b52" \
+ "d3b4")
+ assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash))
+ end
+
+ private
+
+ def B(ary)
+ [Array(ary).join].pack("H*")
+ end
+end
+
+end
diff --git a/test/openssl/test_ns_spki.rb b/test/openssl/test_ns_spki.rb
index 3937132aa0..0484429289 100644
--- a/test/openssl/test_ns_spki.rb
+++ b/test/openssl/test_ns_spki.rb
@@ -1,35 +1,28 @@
-begin
- require "openssl"
- require File.join(File.dirname(__FILE__), "utils.rb")
-rescue LoadError
-end
-require "test/unit"
+# frozen_string_literal: true
+require_relative 'utils'
if defined?(OpenSSL)
-
-class OpenSSL::TestNSSPI < Test::Unit::TestCase
+class OpenSSL::TestNSSPI < OpenSSL::TestCase
def setup
+ super
# 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"
@b64 << "i0//rgBvmco="
end
- def teardown
- end
-
def test_build_data
- key1 = OpenSSL::TestUtils::TEST_KEY_RSA1024
- key2 = OpenSSL::TestUtils::TEST_KEY_RSA2048
+ key1 = Fixtures.pkey("rsa-1")
+ key2 = Fixtures.pkey("rsa-2")
spki = OpenSSL::Netscape::SPKI.new
spki.challenge = "RandomString"
spki.public_key = key1.public_key
- spki.sign(key1, OpenSSL::Digest::SHA1.new)
+ spki.sign(key1, OpenSSL::Digest.new('SHA256'))
assert(spki.verify(spki.public_key))
assert(spki.verify(key1.public_key))
assert(!spki.verify(key2.public_key))
@@ -39,18 +32,19 @@ class OpenSSL::TestNSSPI < Test::Unit::TestCase
assert_equal("RandomString", spki.challenge)
assert_equal(key1.public_key.to_der, spki.public_key.to_der)
assert(spki.verify(spki.public_key))
+ assert_not_nil(spki.to_text)
end
def test_decode_data
spki = OpenSSL::Netscape::SPKI.new(@b64)
assert_equal(@b64, spki.to_pem)
- assert_equal(@b64.unpack("m").first, spki.to_der)
+ assert_equal(@b64.unpack1("m"), spki.to_der)
assert_equal("MozillaIsMyFriend", spki.challenge)
assert_equal(OpenSSL::PKey::RSA, spki.public_key.class)
- spki = OpenSSL::Netscape::SPKI.new(@b64.unpack("m").first)
+ spki = OpenSSL::Netscape::SPKI.new(@b64.unpack1("m"))
assert_equal(@b64, spki.to_pem)
- assert_equal(@b64.unpack("m").first, spki.to_der)
+ assert_equal(@b64.unpack1("m"), spki.to_der)
assert_equal("MozillaIsMyFriend", spki.challenge)
assert_equal(OpenSSL::PKey::RSA, spki.public_key.class)
end
diff --git a/test/openssl/test_ocsp.rb b/test/openssl/test_ocsp.rb
new file mode 100644
index 0000000000..c43ff5cb55
--- /dev/null
+++ b/test/openssl/test_ocsp.rb
@@ -0,0 +1,335 @@
+# frozen_string_literal: true
+require_relative "utils"
+
+if defined?(OpenSSL)
+
+class OpenSSL::TestOCSP < OpenSSL::TestCase
+ def setup
+ super
+ # @ca_cert
+ # |
+ # @cert
+ # |----------|
+ # @cert2 @ocsp_cert
+
+ ca_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA")
+ @ca_key = Fixtures.pkey("rsa-1")
+ ca_exts = [
+ ["basicConstraints", "CA:TRUE", true],
+ ["keyUsage", "cRLSign,keyCertSign", true],
+ ]
+ @ca_cert = OpenSSL::TestUtils.issue_cert(
+ ca_subj, @ca_key, 1, ca_exts, nil, nil)
+
+ cert_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA2")
+ @cert_key = Fixtures.pkey("rsa-2")
+ cert_exts = [
+ ["basicConstraints", "CA:TRUE", true],
+ ["keyUsage", "cRLSign,keyCertSign", true],
+ ]
+ @cert = OpenSSL::TestUtils.issue_cert(
+ cert_subj, @cert_key, 5, cert_exts, @ca_cert, @ca_key)
+
+ cert2_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCert")
+ @cert2_key = Fixtures.pkey("rsa-3")
+ cert2_exts = [
+ ]
+ @cert2 = OpenSSL::TestUtils.issue_cert(
+ cert2_subj, @cert2_key, 10, cert2_exts, @cert, @cert_key)
+
+ ocsp_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCAOCSP")
+ @ocsp_key = Fixtures.pkey("p256")
+ ocsp_exts = [
+ ["extendedKeyUsage", "OCSPSigning", true],
+ ]
+ @ocsp_cert = OpenSSL::TestUtils.issue_cert(
+ ocsp_subj, @ocsp_key, 100, ocsp_exts, @cert, @cert_key)
+ end
+
+ def test_new_certificate_id
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+ assert_kind_of OpenSSL::OCSP::CertificateId, cid
+ assert_equal @cert.serial, cid.serial
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA256'))
+ assert_kind_of OpenSSL::OCSP::CertificateId, cid
+ assert_equal @cert.serial, cid.serial
+ end
+
+ def test_certificate_id_issuer_name_hash
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+ assert_equal OpenSSL::Digest.hexdigest('SHA1', @cert.issuer.to_der), cid.issuer_name_hash
+ assert_equal "d91f736ac4dc3242f0fb9b77a3149bd83c5c43d0", cid.issuer_name_hash
+ end
+
+ def test_certificate_id_issuer_key_hash
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+ # content of subjectPublicKey (bit string) in SubjectPublicKeyInfo
+ spki = OpenSSL::ASN1.decode(@ca_key.public_to_der)
+ assert_equal OpenSSL::Digest.hexdigest("SHA1", spki.value[1].value),
+ cid.issuer_key_hash
+ end
+
+ def test_certificate_id_hash_algorithm
+ cid_sha1 = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ cid_sha256 = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA256'))
+ assert_equal "sha1", cid_sha1.hash_algorithm
+ assert_equal "sha256", cid_sha256.hash_algorithm
+ end
+
+ def test_certificate_id_der
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+ der = cid.to_der
+ asn1 = OpenSSL::ASN1.decode(der)
+ # hash algorithm defaults to SHA-1
+ assert_equal OpenSSL::ASN1.ObjectId("SHA1").to_der, asn1.value[0].value[0].to_der
+ assert_equal [cid.issuer_name_hash].pack("H*"), asn1.value[1].value
+ 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
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+ assert_equal cid.to_der, cid.dup.to_der
+ end
+
+ def test_request_der
+ request = OpenSSL::OCSP::Request.new
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ request.add_certid(cid)
+ request.sign(@cert, @cert_key, [@ca_cert], 0)
+ asn1 = OpenSSL::ASN1.decode(request.to_der)
+ assert_equal cid.to_der, asn1.value[0].value.find { |a| a.tag_class == :UNIVERSAL }.value[0].value[0].to_der
+ assert_equal OpenSSL::ASN1.ObjectId("sha256WithRSAEncryption").to_der, asn1.value[1].value[0].value[0].value[0].to_der
+ assert_equal @cert.to_der, asn1.value[1].value[0].value[2].value[0].value[0].to_der
+ assert_equal @ca_cert.to_der, asn1.value[1].value[0].value[2].value[0].value[1].to_der
+ assert_equal asn1.to_der, OpenSSL::OCSP::Request.new(asn1.to_der).to_der
+ end
+
+ def test_request_sign_verify
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+ store = OpenSSL::X509::Store.new.add_cert(@ca_cert)
+
+ # with signer cert
+ req = OpenSSL::OCSP::Request.new.add_certid(cid)
+ req.sign(@cert, @cert_key, [])
+ assert_equal true, req.verify([], store)
+
+ # without signer cert
+ req = OpenSSL::OCSP::Request.new.add_certid(cid)
+ req.sign(@cert, @cert_key, nil)
+ assert_equal false, req.verify([@cert2], store)
+ assert_equal false, req.verify([], store) # no signer
+ assert_equal false, req.verify([], store, OpenSSL::OCSP::NOVERIFY)
+
+ assert_equal true, req.verify([@cert], store, OpenSSL::OCSP::NOINTERN)
+ ret = req.verify([@cert], store)
+ assert_equal true, ret
+
+ # not signed
+ req = OpenSSL::OCSP::Request.new.add_certid(cid)
+ assert_equal false, req.verify([], store)
+ end
+
+ def test_request_is_signed
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+ req = OpenSSL::OCSP::Request.new
+ req.add_certid(cid)
+ assert_equal false, req.signed?
+ assert_equal false, OpenSSL::OCSP::Request.new(req.to_der).signed?
+ req.sign(@cert, @cert_key, [])
+ assert_equal true, req.signed?
+ assert_equal true, OpenSSL::OCSP::Request.new(req.to_der).signed?
+ end
+
+ def test_request_nonce
+ req0 = OpenSSL::OCSP::Request.new
+ req1 = OpenSSL::OCSP::Request.new.add_nonce("NONCE")
+ req2 = OpenSSL::OCSP::Request.new.add_nonce("ABCDE")
+ bres = OpenSSL::OCSP::BasicResponse.new
+ assert_equal 2, req0.check_nonce(bres)
+ bres.copy_nonce(req1)
+ assert_equal 3, req0.check_nonce(bres)
+ assert_equal 1, req1.check_nonce(bres)
+ bres.add_nonce("NONCE")
+ assert_equal 1, req1.check_nonce(bres)
+ assert_equal 0, req2.check_nonce(bres)
+ end
+
+ def test_request_dup
+ request = OpenSSL::OCSP::Request.new
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ request.add_certid(cid)
+ assert_equal request.to_der, request.dup.to_der
+ end
+
+ def test_basic_response_der
+ bres = OpenSSL::OCSP::BasicResponse.new
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0, nil, -300, 500, [])
+ bres.add_nonce("NONCE")
+ bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert], 0)
+ der = bres.to_der
+ asn1 = OpenSSL::ASN1.decode(der)
+ assert_equal OpenSSL::ASN1.Sequence([@ocsp_cert, @ca_cert]).to_der, asn1.value[3].value[0].to_der
+ assert_equal der, OpenSSL::OCSP::BasicResponse.new(der).to_der
+ rescue TypeError
+ if /GENERALIZEDTIME/ =~ $!.message
+ pend "OCSP_basic_sign() is broken"
+ else
+ raise
+ end
+ end
+
+ def test_basic_response_sign_verify
+ store = OpenSSL::X509::Store.new.add_cert(@ca_cert)
+
+ # signed by CA
+ bres = OpenSSL::OCSP::BasicResponse.new
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, "SHA256")
+ bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, nil, -400, -300, 500, [])
+ bres.sign(@ca_cert, @ca_key, nil, 0, "SHA256")
+ assert_equal false, bres.verify([], store) # signer not found
+ assert_equal true, bres.verify([@ca_cert], store)
+ bres.sign(@ca_cert, @ca_key, [], 0, "SHA256")
+ assert_equal true, bres.verify([], store)
+
+ # signed by OCSP signer
+ bres = OpenSSL::OCSP::BasicResponse.new
+ cid = OpenSSL::OCSP::CertificateId.new(@cert2, @cert)
+ bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, nil, -400, -300, 500, [])
+ bres.sign(@ocsp_cert, @ocsp_key, [@cert])
+ assert_equal true, bres.verify([], store)
+ assert_equal false, bres.verify([], store, OpenSSL::OCSP::NOCHAIN)
+ # OpenSSL had a bug on this; test that our workaround works
+ bres.sign(@ocsp_cert, @ocsp_key, [])
+ assert_equal true, bres.verify([@cert], store)
+ end
+
+ def test_basic_response_dup
+ bres = OpenSSL::OCSP::BasicResponse.new
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0, nil, -300, 500, [])
+ bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert], 0)
+ assert_equal bres.to_der, bres.dup.to_der
+ end
+
+ def test_basic_response_status_good
+ bres = OpenSSL::OCSP::BasicResponse.new
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0, nil, -300, 500, nil)
+ bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert])
+
+ statuses = bres.status
+ assert_equal 1, statuses.size
+ status = statuses[0]
+ assert_equal cid.to_der, status[0].to_der
+ assert_equal OpenSSL::OCSP::V_CERTSTATUS_GOOD, status[1]
+ assert_nil status[3] # revtime should be nil for GOOD status
+ end
+
+ def test_basic_response_status_revoked
+ bres = OpenSSL::OCSP::BasicResponse.new
+ now = Time.at(Time.now.to_i)
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_REVOKED,
+ OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED, now - 400, -300, nil, nil)
+ bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert])
+
+ statuses = bres.status
+ assert_equal 1, statuses.size
+ status = statuses[0]
+ assert_equal OpenSSL::OCSP::V_CERTSTATUS_REVOKED, status[1]
+ assert_equal now - 400, status[3] # revtime should be the revocation time
+ end
+
+ def test_basic_response_response_operations
+ bres = OpenSSL::OCSP::BasicResponse.new
+ now = Time.at(Time.now.to_i)
+ cid1 = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ cid2 = OpenSSL::OCSP::CertificateId.new(@ocsp_cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ cid3 = OpenSSL::OCSP::CertificateId.new(@ca_cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ bres.add_status(cid1, OpenSSL::OCSP::V_CERTSTATUS_REVOKED, OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED, now - 400, -300, nil, nil)
+ bres.add_status(cid2, OpenSSL::OCSP::V_CERTSTATUS_GOOD, nil, nil, -300, 500, [])
+
+ assert_equal 2, bres.responses.size
+ single = bres.responses.first
+ assert_equal cid1.to_der, single.certid.to_der
+ assert_equal OpenSSL::OCSP::V_CERTSTATUS_REVOKED, single.cert_status
+ assert_equal OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED, single.revocation_reason
+ assert_equal now - 400, single.revocation_time
+ assert_in_delta (now - 300), single.this_update, 1
+ assert_equal nil, single.next_update
+ assert_equal [], single.extensions
+
+ assert_equal cid2.to_der, bres.find_response(cid2).certid.to_der
+ assert_equal nil, bres.find_response(cid3)
+ end
+
+ def test_single_response_der
+ bres = OpenSSL::OCSP::BasicResponse.new
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+ bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, nil, nil, -300, 500, nil)
+ single = bres.responses[0]
+ der = single.to_der
+ asn1 = OpenSSL::ASN1.decode(der)
+ assert_equal :CONTEXT_SPECIFIC, asn1.value[1].tag_class
+ assert_equal 0, asn1.value[1].tag # good
+ assert_equal der, OpenSSL::OCSP::SingleResponse.new(der).to_der
+ end
+
+ def test_single_response_check_validity
+ bres = OpenSSL::OCSP::BasicResponse.new
+ cid1 = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ cid2 = OpenSSL::OCSP::CertificateId.new(@ocsp_cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ bres.add_status(cid1, OpenSSL::OCSP::V_CERTSTATUS_REVOKED, OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED, -400, -300, -50, [])
+ bres.add_status(cid2, OpenSSL::OCSP::V_CERTSTATUS_REVOKED, OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED, -400, -300, nil, [])
+ bres.add_status(cid2, OpenSSL::OCSP::V_CERTSTATUS_GOOD, nil, nil, Time.now + 100, nil, nil)
+
+ single1 = bres.responses[0]
+ assert_equal false, single1.check_validity
+ assert_equal false, single1.check_validity(30)
+ assert_equal true, single1.check_validity(60)
+ single2 = bres.responses[1]
+ assert_equal true, single2.check_validity
+ assert_equal true, single2.check_validity(0, 500)
+ assert_equal false, single2.check_validity(0, 200)
+ single3 = bres.responses[2]
+ assert_equal false, single3.check_validity
+ end
+
+ def test_response
+ bres = OpenSSL::OCSP::BasicResponse.new
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0, nil, -300, 500, [])
+ bres.sign(@ocsp_cert, @ocsp_key, [])
+ res = OpenSSL::OCSP::Response.create(OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, bres)
+
+ assert_equal bres.to_der, res.basic.to_der
+ assert_equal OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, res.status
+ end
+
+ def test_response_der
+ bres = OpenSSL::OCSP::BasicResponse.new
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0, nil, -300, 500, [])
+ bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert], 0)
+ res = OpenSSL::OCSP::Response.create(OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, bres)
+ der = res.to_der
+ asn1 = OpenSSL::ASN1.decode(der)
+ assert_equal OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, asn1.value[0].value
+ assert_equal OpenSSL::ASN1.ObjectId("basicOCSPResponse").to_der, asn1.value[1].value[0].value[0].to_der
+ assert_equal bres.to_der, asn1.value[1].value[0].value[1].value
+ assert_equal der, OpenSSL::OCSP::Response.new(der).to_der
+ end
+
+ def test_response_dup
+ bres = OpenSSL::OCSP::BasicResponse.new
+ bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert], 0)
+ res = OpenSSL::OCSP::Response.create(OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, bres)
+ assert_equal res.to_der, res.dup.to_der
+ end
+end
+
+end
diff --git a/test/openssl/test_ossl.rb b/test/openssl/test_ossl.rb
new file mode 100644
index 0000000000..1b9bde53ef
--- /dev/null
+++ b/test/openssl/test_ossl.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+require_relative "utils"
+
+if defined?(OpenSSL)
+
+class OpenSSL::TestOSSL < OpenSSL::TestCase
+ 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_true(OpenSSL.fixed_length_secure_compare("aaa", "aaa"))
+ assert_true(OpenSSL.fixed_length_secure_compare(
+ OpenSSL::Digest.digest('SHA256', "aaa"), OpenSSL::Digest::SHA256.digest("aaa")
+ ))
+
+ assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "aaaa") }
+ assert_false(OpenSSL.fixed_length_secure_compare("aaa", "baa"))
+ assert_false(OpenSSL.fixed_length_secure_compare("aaa", "aba"))
+ assert_false(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") }
+ assert_false(OpenSSL.fixed_length_secure_compare("aaa", "bbb"))
+ assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "bbbb") }
+ end
+
+ def test_fixed_length_secure_compare_uaf
+ str1 = "A" * 1000000
+ evil_obj = Object.new
+ evil_obj.define_singleton_method(:to_str) do
+ str1.replace("C" * 1000000)
+ "B" * 1000000
+ end
+ assert_false(OpenSSL.fixed_length_secure_compare(str1, evil_obj))
+ end
+
+ def test_secure_compare
+ assert_false(OpenSSL.secure_compare("aaa", "a"))
+ assert_false(OpenSSL.secure_compare("aaa", "aa"))
+
+ assert_true(OpenSSL.secure_compare("aaa", "aaa"))
+
+ assert_false(OpenSSL.secure_compare("aaa", "aaaa"))
+ assert_false(OpenSSL.secure_compare("aaa", "baa"))
+ assert_false(OpenSSL.secure_compare("aaa", "aba"))
+ assert_false(OpenSSL.secure_compare("aaa", "aab"))
+ assert_false(OpenSSL.secure_compare("aaa", "aaab"))
+ assert_false(OpenSSL.secure_compare("aaa", "b"))
+ assert_false(OpenSSL.secure_compare("aaa", "bb"))
+ assert_false(OpenSSL.secure_compare("aaa", "bbb"))
+ assert_false(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"
+
+ a_b_time = a_c_time = 0
+ 100.times do
+ t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ 100.times { OpenSSL.fixed_length_secure_compare(a, b) }
+ t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ 100.times { OpenSSL.fixed_length_secure_compare(a, c) }
+ t3 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+
+ a_b_time += t2 - t1
+ a_c_time += t3 - t2
+ end
+ assert_operator(a_b_time, :<, a_c_time * 10, "fixed_length_secure_compare timing test failed")
+ assert_operator(a_c_time, :<, a_b_time * 10, "fixed_length_secure_compare timing test failed")
+ end if ENV["OSSL_TEST_ALL"] == "1"
+
+ def test_error_data
+ # X509V3_EXT_nconf_nid() called from
+ # OpenSSL::X509::ExtensionFactory#create_ext is a function that uses
+ # ERR_raise_data() to append additional information about the error.
+ #
+ # The generated message should look like:
+ # "subjectAltName = IP:not.a.valid.ip.address: bad ip address (value=not.a.valid.ip.address)"
+ # "subjectAltName = IP:not.a.valid.ip.address: error in extension (name=subjectAltName, value=IP:not.a.valid.ip.address)"
+ #
+ # The string inside parentheses is the ERR_TXT_STRING data, and is appended
+ # by ossl_make_error(), so we check it here.
+ ef = OpenSSL::X509::ExtensionFactory.new
+ e = assert_raise(OpenSSL::X509::ExtensionError) {
+ ef.create_ext("subjectAltName", "IP:not.a.valid.ip.address")
+ }
+ assert_match(/not.a.valid.ip.address\)\z/, e.message)
+
+ # We currently craft the strings based on ERR_error_string()'s style:
+ # error:<error code in hex>:<library>:<function>:<reason> (data)
+ assert_instance_of(Array, e.errors)
+ assert_match(/\Aerror:.*not.a.valid.ip.address\)\z/, e.errors.last)
+ assert_include(e.detailed_message, "not.a.valid.ip.address")
+ end
+end
+
+end
diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb
index 7273554362..10942191dd 100644
--- a/test/openssl/test_pair.rb
+++ b/test/openssl/test_pair.rb
@@ -1,87 +1,99 @@
-begin
- require "openssl"
-rescue LoadError
-end
-require 'test/unit'
-
-if defined?(OpenSSL)
+# frozen_string_literal: true
+require_relative 'utils'
+require_relative 'ut_eof'
-require 'socket'
-dir = File.expand_path(__FILE__)
-2.times {dir = File.dirname(dir)}
-$:.replace([File.join(dir, "ruby")] | $:)
-require 'ut_eof'
+if defined?(OpenSSL::SSL)
-module SSLPair
- def server
- host = "127.0.0.1"
- port = 0
- ctx = OpenSSL::SSL::SSLContext.new()
- ctx.ciphers = "ADH"
- tcps = TCPServer.new(host, port)
- ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx)
- return ssls
+module OpenSSL::SSLPairM
+ def setup
+ svr_dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost")
+ ee_exts = [
+ ["keyUsage", "keyEncipherment,digitalSignature", true],
+ ]
+ @svr_key = OpenSSL::TestUtils::Fixtures.pkey("rsa-1")
+ @svr_cert = issue_cert(svr_dn, @svr_key, 1, ee_exts, nil, nil)
end
- def client(port)
+ def ssl_pair
host = "127.0.0.1"
- ctx = OpenSSL::SSL::SSLContext.new()
- ctx.ciphers = "ADH"
- s = TCPSocket.new(host, port)
- ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
- ssl.connect
- ssl.sync_close = true
- ssl
- end
+ tcps = create_tcp_server(host, 0)
+ port = tcps.connect_address.ip_port
- def ssl_pair
- ssls = server
th = Thread.new {
+ sctx = OpenSSL::SSL::SSLContext.new
+ sctx.cert = @svr_cert
+ sctx.key = @svr_key
+ sctx.options |= OpenSSL::SSL::OP_NO_COMPRESSION
+ ssls = OpenSSL::SSL::SSLServer.new(tcps, sctx)
ns = ssls.accept
ssls.close
ns
}
- port = ssls.to_io.addr[1]
- c = client(port)
+
+ tcpc = create_tcp_client(host, port)
+ c = OpenSSL::SSL::SSLSocket.new(tcpc)
+ c.connect
s = th.value
- if block_given?
- begin
- yield c, s
- ensure
- c.close unless c.closed?
- s.close unless s.closed?
- end
- else
- return c, s
- end
+
+ yield c, s
+ ensure
+ tcpc&.close
+ tcps&.close
+ s&.close
end
end
-class OpenSSL::TestEOF1 < Test::Unit::TestCase
- include TestEOF
- include SSLPair
+module OpenSSL::SSLPair
+ include OpenSSL::SSLPairM
- def open_file(content)
- s1, s2 = ssl_pair
- Thread.new { s2 << content; s2.close }
- yield s1
+ def create_tcp_server(host, port)
+ TCPServer.new(host, port)
+ end
+
+ def create_tcp_client(host, port)
+ TCPSocket.new(host, port)
end
end
-class OpenSSL::TestEOF2 < Test::Unit::TestCase
- include TestEOF
- include SSLPair
+module OpenSSL::SSLPairLowlevelSocket
+ include OpenSSL::SSLPairM
+
+ def create_tcp_server(host, port)
+ Addrinfo.tcp(host, port).listen
+ end
+ def create_tcp_client(host, port)
+ Addrinfo.tcp(host, port).connect
+ end
+end
+
+module OpenSSL::TestEOF1M
def open_file(content)
- s1, s2 = ssl_pair
- Thread.new { s1 << content; s1.close }
- yield s2
+ ssl_pair { |s1, s2|
+ begin
+ th = Thread.new { s2 << content; s2.close }
+ yield s1
+ ensure
+ th&.join
+ end
+ }
end
end
-class OpenSSL::TestPair < Test::Unit::TestCase
- include SSLPair
+module OpenSSL::TestEOF2M
+ def open_file(content)
+ ssl_pair { |s1, s2|
+ begin
+ th = Thread.new { s1 << content; s1.close }
+ yield s2
+ ensure
+ th&.join
+ end
+ }
+ end
+end
+module OpenSSL::TestPairM
def test_getc
ssl_pair {|s1, s2|
s1 << "a"
@@ -89,13 +101,71 @@ class OpenSSL::TestPair < Test::Unit::TestCase
}
end
+ def test_getbyte
+ ssl_pair {|s1, s2|
+ s1 << "a"
+ assert_equal(97, s2.getbyte)
+ }
+ end
+
+ def test_readbyte
+ ssl_pair {|s1, s2|
+ s1 << "b"
+ assert_equal(98, s2.readbyte)
+ }
+ end
+
+ def test_readbyte_eof
+ ssl_pair {|s1, s2|
+ s2.close
+ assert_raise(EOFError) { s1.readbyte }
+ }
+ end
+
+ def test_gets
+ ssl_pair {|s1, s2|
+ s1 << "abc\n\n$def123ghi"
+ s1.close
+ ret = s2.gets
+ assert_equal Encoding::BINARY, ret.encoding
+ assert_equal "abc\n", ret
+ assert_equal "\n$", s2.gets("$")
+ assert_equal "def123", s2.gets(/\d+/)
+ assert_equal "ghi", s2.gets
+ assert_equal nil, s2.gets
+ }
+ end
+
+ def test_gets_chomp
+ ssl_pair {|s1, s2|
+ s1 << "line1\r\nline2\r\nline3\r\n"
+ s1.close
+
+ assert_equal("line1", s2.gets("\r\n", chomp: true))
+ assert_equal("line2\r\n", s2.gets("\r\n", chomp: false))
+ assert_equal("line3", s2.gets(chomp: true))
+ }
+ end
+
+ def test_gets_eof_limit
+ ssl_pair {|s1, s2|
+ s1.write("hello")
+ s1.close # trigger EOF
+ assert_match "hello", s2.gets("\n", 6), "[ruby-core:70149] [Bug #11400]"
+ }
+ end
+
def test_readpartial
ssl_pair {|s1, s2|
s2.write "a\nbcd"
assert_equal("a\n", s1.gets)
- assert_equal("bcd", s1.readpartial(10))
+ result = String.new
+ result << s1.readpartial(10) until result.length == 3
+ assert_equal("bcd", result)
s2.write "efg"
- assert_equal("efg", s1.readpartial(10))
+ result = String.new
+ result << s1.readpartial(10) until result.length == 3
+ assert_equal("efg", result)
s2.close
assert_raise(EOFError) { s1.readpartial(10) }
assert_raise(EOFError) { s1.readpartial(10) }
@@ -117,28 +187,357 @@ class OpenSSL::TestPair < Test::Unit::TestCase
}
end
- def test_puts_meta
+ def test_puts_empty
+ ssl_pair {|s1, s2|
+ s1.puts
+ s1.close
+ assert_equal("\n", s2.read)
+ }
+ end
+
+ def test_multibyte_read_write
+ # German a umlaut
+ auml = [%w{ C3 A4 }.join('')].pack('H*')
+ auml.force_encoding(Encoding::UTF_8)
+ bsize = auml.bytesize
+
+ ssl_pair { |s1, s2|
+ assert_equal bsize, s1.write(auml)
+ read = s2.read(bsize)
+ assert_equal Encoding::ASCII_8BIT, read.encoding
+ assert_equal bsize, read.bytesize
+ assert_equal auml, read.force_encoding(Encoding::UTF_8)
+
+ s1.puts(auml)
+ read = s2.gets
+ assert_equal Encoding::ASCII_8BIT, read.encoding
+ assert_equal bsize + 1, read.bytesize
+ assert_equal auml + "\n", read.force_encoding(Encoding::UTF_8)
+ }
+ end
+
+ def test_read_nonblock
ssl_pair {|s1, s2|
- begin
- old = $/
- $/ = '*'
- s1.puts 'a'
- ensure
- $/ = old
+ err = nil
+ assert_raise(OpenSSL::SSL::SSLErrorWaitReadable) {
+ begin
+ s2.read_nonblock(10)
+ ensure
+ err = $!
+ end
+ }
+ assert_kind_of(IO::WaitReadable, err)
+ s1.write "abc\ndef\n"
+ IO.select([s2])
+ assert_equal("ab", s2.read_nonblock(2))
+ assert_equal("c\n", s2.gets)
+ ret = nil
+ assert_nothing_raised("[ruby-core:20298]") { ret = s2.read_nonblock(10) }
+ assert_equal("def\n", ret)
+ s1.close
+ IO.select([s2])
+ assert_raise(EOFError) { s2.read_nonblock(10) }
+ }
+ end
+
+ def test_read_nonblock_no_exception
+ ssl_pair {|s1, s2|
+ assert_equal :wait_readable, s2.read_nonblock(10, exception: false)
+ s1.write "abc\ndef\n"
+ IO.select([s2])
+ assert_equal("ab", s2.read_nonblock(2, exception: false))
+ assert_equal("c\n", s2.gets)
+ ret = nil
+ assert_nothing_raised("[ruby-core:20298]") { ret = s2.read_nonblock(10, exception: false) }
+ assert_equal("def\n", ret)
+ s1.close
+ IO.select([s2])
+ assert_equal(nil, s2.read_nonblock(10, exception: false))
+ }
+ end
+
+ def test_read_with_outbuf
+ ssl_pair { |s1, s2|
+ s1.write("abc\n")
+ buf = String.new
+ ret = s2.read(2, buf)
+ assert_same ret, buf
+ assert_equal "ab", ret
+
+ buf = +"garbage"
+ ret = s2.read(2, buf)
+ assert_same ret, buf
+ assert_equal "c\n", ret
+
+ buf = +"garbage"
+ assert_equal :wait_readable, s2.read_nonblock(100, buf, exception: false)
+ assert_equal "garbage", buf
+
+ s1.close
+ buf = +"garbage"
+ assert_nil s2.read(100, buf)
+ assert_equal "", buf
+
+ buf = +"garbage"
+ ret = s2.read(0, buf)
+ assert_same buf, ret
+ assert_equal "", ret
+ }
+ end
+
+ def test_write_nonblock
+ ssl_pair {|s1, s2|
+ assert_equal 3, s1.write_nonblock("foo")
+ assert_equal "foo", s2.read(3)
+
+ data = "x" * 16384
+ written = 0
+ while true
+ begin
+ written += s1.write_nonblock(data)
+ rescue IO::WaitWritable, IO::WaitReadable
+ break
+ end
+ end
+ assert written > 0
+ assert_equal written, s2.read(written).bytesize
+ }
+ end
+
+ def test_write_nonblock_no_exceptions
+ ssl_pair {|s1, s2|
+ assert_equal 3, s1.write_nonblock("foo", exception: false)
+ assert_equal "foo", s2.read(3)
+
+ data = "x" * 16384
+ written = 0
+ while true
+ case ret = s1.write_nonblock(data, exception: false)
+ when :wait_readable, :wait_writable
+ break
+ else
+ written += ret
+ end
end
+ assert written > 0
+ assert_equal written, s2.read(written).bytesize
+ }
+ end
+
+ def test_write_nonblock_with_buffered_data
+ ssl_pair {|s1, s2|
+ s1.write "foo"
+ s1.write_nonblock("bar")
+ s1.write "baz"
s1.close
- assert_equal("a\n", s2.read)
+ assert_equal("foobarbaz", s2.read)
}
end
- def test_puts_empty
+ def test_write_nonblock_with_buffered_data_no_exceptions
ssl_pair {|s1, s2|
- s1.puts
+ s1.write "foo"
+ s1.write_nonblock("bar", exception: false)
+ s1.write "baz"
s1.close
- assert_equal("\n", s2.read)
+ assert_equal("foobarbaz", s2.read)
+ }
+ end
+
+ def test_write_nonblock_retry
+ ssl_pair {|s1, s2|
+ # fill up a socket so we hit EAGAIN
+ written = String.new
+ n = 0
+ buf = 'a' * 4099
+ case ret = s1.write_nonblock(buf, exception: false)
+ when :wait_readable then break
+ when :wait_writable then break
+ when Integer
+ written << buf
+ n += ret
+ exp = buf.bytesize
+ if ret != exp
+ buf = buf.byteslice(ret, exp - ret)
+ end
+ end while true
+ assert_kind_of Symbol, ret
+
+ # make more space for subsequent write:
+ readed = s2.read(n)
+ assert_equal written, readed
+
+ # this fails if SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER is missing:
+ buf2 = Marshal.load(Marshal.dump(buf))
+ assert_kind_of Integer, s1.write_nonblock(buf2, exception: false)
+ }
+ end
+
+ def test_write_zero
+ ssl_pair {|s1, s2|
+ assert_equal 0, s2.write_nonblock('', exception: false)
+ assert_kind_of Symbol, s1.read_nonblock(1, exception: false)
+ assert_equal 0, s2.syswrite('')
+ assert_kind_of Symbol, s1.read_nonblock(1, exception: false)
+ assert_equal 0, s2.write('')
+ assert_kind_of Symbol, s1.read_nonblock(1, exception: false)
+ }
+ end
+
+ def test_write_multiple_arguments
+ ssl_pair {|s1, s2|
+ str1 = "foo"; str2 = "bar"
+ assert_equal 6, s1.write(str1, str2)
+ s1.close
+ assert_equal "foobar", s2.read
}
end
+ def test_partial_tls_record_read_nonblock
+ ssl_pair { |s1, s2|
+ # the beginning of a TLS record
+ s1.io.write("\x17")
+ # should raise a IO::WaitReadable since a full TLS record is not available
+ # for reading
+ assert_raise(IO::WaitReadable) { s2.read_nonblock(1) }
+ }
+ end
+
+ def tcp_pair
+ host = "127.0.0.1"
+ serv = TCPServer.new(host, 0)
+ port = serv.connect_address.ip_port
+ sock1 = TCPSocket.new(host, port)
+ sock2 = serv.accept
+ serv.close
+ [sock1, sock2]
+ ensure
+ serv.close if serv && !serv.closed?
+ end
+
+ def test_connect_accept_nonblock_no_exception
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.cert = @svr_cert
+ ctx2.key = @svr_key
+
+ sock1, sock2 = tcp_pair
+
+ s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+ accepted = s2.accept_nonblock(exception: false)
+ assert_equal :wait_readable, accepted
+
+ ctx1 = OpenSSL::SSL::SSLContext.new
+ s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1)
+ th = Thread.new do
+ rets = []
+ begin
+ rv = s1.connect_nonblock(exception: false)
+ rets << rv
+ case rv
+ when :wait_writable
+ IO.select(nil, [s1], nil, 5)
+ when :wait_readable
+ IO.select([s1], nil, nil, 5)
+ end
+ end until rv == s1
+ rets
+ end
+
+ until th.join(0.01)
+ accepted = s2.accept_nonblock(exception: false)
+ assert_include([s2, :wait_readable, :wait_writable ], accepted)
+ end
+
+ rets = th.value
+ assert_instance_of Array, rets
+ rets.each do |rv|
+ assert_include([s1, :wait_readable, :wait_writable ], rv)
+ end
+ ensure
+ th.join if th
+ s1.close if s1
+ s2.close if s2
+ sock1.close if sock1
+ sock2.close if sock2
+ accepted.close if accepted.respond_to?(:close)
+ end
+
+ def test_connect_accept_nonblock
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.cert = @svr_cert
+ ctx.key = @svr_key
+
+ sock1, sock2 = tcp_pair
+
+ th = Thread.new {
+ s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx)
+ 5.times {
+ begin
+ break s2.accept_nonblock
+ rescue IO::WaitReadable
+ IO.select([s2], nil, nil, 1)
+ rescue IO::WaitWritable
+ IO.select(nil, [s2], nil, 1)
+ end
+ sleep 0.2
+ }
+ }
+
+ s1 = OpenSSL::SSL::SSLSocket.new(sock1)
+ 5.times {
+ begin
+ break s1.connect_nonblock
+ rescue IO::WaitReadable
+ IO.select([s1], nil, nil, 1)
+ rescue IO::WaitWritable
+ IO.select(nil, [s1], nil, 1)
+ end
+ sleep 0.2
+ }
+
+ s2 = th.value
+
+ s1.print "a\ndef"
+ assert_equal("a\n", s2.gets)
+ ensure
+ sock1&.close
+ sock2&.close
+ th&.join
+ end
+end
+
+class OpenSSL::TestEOF1 < OpenSSL::TestCase
+ include OpenSSL::TestEOF
+ include OpenSSL::SSLPair
+ include OpenSSL::TestEOF1M
+end
+
+class OpenSSL::TestEOF1LowlevelSocket < OpenSSL::TestCase
+ include OpenSSL::TestEOF
+ include OpenSSL::SSLPairLowlevelSocket
+ include OpenSSL::TestEOF1M
+end
+
+class OpenSSL::TestEOF2 < OpenSSL::TestCase
+ include OpenSSL::TestEOF
+ include OpenSSL::SSLPair
+ include OpenSSL::TestEOF2M
+end
+
+class OpenSSL::TestEOF2LowlevelSocket < OpenSSL::TestCase
+ include OpenSSL::TestEOF
+ include OpenSSL::SSLPairLowlevelSocket
+ include OpenSSL::TestEOF2M
+end
+
+class OpenSSL::TestPair < OpenSSL::TestCase
+ include OpenSSL::SSLPair
+ include OpenSSL::TestPairM
+end
+
+class OpenSSL::TestPairLowlevelSocket < OpenSSL::TestCase
+ include OpenSSL::SSLPairLowlevelSocket
+ include OpenSSL::TestPairM
end
end
diff --git a/test/openssl/test_pkcs12.rb b/test/openssl/test_pkcs12.rb
new file mode 100644
index 0000000000..617c156cbd
--- /dev/null
+++ b/test/openssl/test_pkcs12.rb
@@ -0,0 +1,417 @@
+# frozen_string_literal: true
+require_relative "utils"
+
+if defined?(OpenSSL)
+
+# OpenSSL::PKCS12.create calling the PKCS12_create() has the argument mac_iter
+# which uses a MAC key using PKCS12KDF which is not FIPS-approved.
+# OpenSSL::PKCS12.new with base64-encoded example calling PKCS12_parse()
+# verifies the MAC key using PKCS12KDF which is not FIPS-approved.
+#
+# PBE-SHA1-3DES uses PKCS12KDF which is not FIPS-approved according to the RFC
+# 7292 PKCS#12.
+# https://datatracker.ietf.org/doc/html/rfc7292#appendix-C
+# > The PBES1 encryption scheme defined in PKCS #5 provides a number of
+# > algorithm identifiers for deriving keys and IVs; here, we specify a
+# > few more, all of which use the procedure detailed in Appendices B.2
+# > and B.3 to construct keys (and IVs, where needed). As is implied by
+# > their names, all of the object identifiers below use the hash
+# > function SHA-1.
+# > ...
+# > pbeWithSHAAnd3-KeyTripleDES-CBC OBJECT IDENTIFIER ::= {pkcs-12PbeIds 3}
+#
+# Note that the pbeWithSHAAnd3-KeyTripleDES-CBC (pkcs12-pbeids 3) in the RFC
+# 7292 PKCS#12 means PBE-SHA1-3DES in OpenSSL. PKCS12KDF is used in PKCS#12.
+# https://oidref.com/1.2.840.113549.1.12.1.3
+# https://github.com/openssl/openssl/blob/ed57d1e06dca28689190e00d9893e0fd7ecc67c1/crypto/objects/objects.txt#L385
+return if OpenSSL.fips_mode
+
+module OpenSSL
+ class TestPKCS12 < OpenSSL::TestCase
+ DEFAULT_PBE_PKEYS = "PBE-SHA1-3DES"
+ DEFAULT_PBE_CERTS = "PBE-SHA1-3DES"
+
+ def setup
+ super
+ ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
+ ca_exts = [
+ ["basicConstraints","CA:TRUE",true],
+ ["keyUsage","keyCertSign, cRLSign",true],
+ ["subjectKeyIdentifier","hash",false],
+ ["authorityKeyIdentifier","keyid:always",false],
+ ]
+ ca_key = Fixtures.pkey("rsa-1")
+ @cacert = issue_cert(ca, ca_key, 1, ca_exts, nil, nil)
+
+ inter_ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=Intermediate CA")
+ inter_ca_key = Fixtures.pkey("rsa-2")
+ @inter_cacert = issue_cert(inter_ca, inter_ca_key, 2, ca_exts, @cacert, ca_key)
+
+ exts = [
+ ["keyUsage","digitalSignature",true],
+ ["subjectKeyIdentifier","hash",false],
+ ]
+ ee = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=Ruby PKCS12 Test Certificate")
+ @mykey = Fixtures.pkey("rsa-3")
+ @mycert = issue_cert(ee, @mykey, 3, exts, @inter_cacert, inter_ca_key)
+ end
+
+ def test_create_single_key_single_cert
+ pkcs12 = OpenSSL::PKCS12.create(
+ "omg",
+ "hello",
+ @mykey,
+ @mycert,
+ nil,
+ DEFAULT_PBE_PKEYS,
+ DEFAULT_PBE_CERTS,
+ )
+ assert_equal @mycert, pkcs12.certificate
+ assert_equal @mykey.to_der, pkcs12.key.to_der
+ assert_nil pkcs12.ca_certs
+
+ der = pkcs12.to_der
+ decoded = OpenSSL::PKCS12.new(der, "omg")
+ assert_equal @mykey.to_der, decoded.key.to_der
+ assert_equal @mycert, decoded.certificate
+ assert_equal [], Array(decoded.ca_certs)
+ end
+
+ def test_create_no_pass
+ pkcs12 = OpenSSL::PKCS12.create(
+ nil,
+ "hello",
+ @mykey,
+ @mycert,
+ nil,
+ DEFAULT_PBE_PKEYS,
+ DEFAULT_PBE_CERTS,
+ )
+ assert_equal @mycert, pkcs12.certificate
+ assert_equal @mykey.to_der, pkcs12.key.to_der
+ assert_nil pkcs12.ca_certs
+
+ decoded = OpenSSL::PKCS12.new(pkcs12.to_der)
+ assert_equal @mycert, decoded.certificate
+ end
+
+ def test_create_with_chain
+ chain = [@inter_cacert, @cacert]
+
+ pkcs12 = OpenSSL::PKCS12.create(
+ "omg",
+ "hello",
+ @mykey,
+ @mycert,
+ chain,
+ DEFAULT_PBE_PKEYS,
+ DEFAULT_PBE_CERTS,
+ )
+ assert_equal chain, pkcs12.ca_certs
+ end
+
+ def test_create_with_chain_decode
+ chain = [@cacert, @inter_cacert]
+
+ passwd = "omg"
+
+ pkcs12 = OpenSSL::PKCS12.create(
+ passwd,
+ "hello",
+ @mykey,
+ @mycert,
+ chain,
+ DEFAULT_PBE_PKEYS,
+ DEFAULT_PBE_CERTS,
+ )
+
+ decoded = OpenSSL::PKCS12.new(pkcs12.to_der, passwd)
+ assert_equal chain.size, decoded.ca_certs.size
+ assert_include decoded.ca_certs, @cacert
+ assert_include decoded.ca_certs, @inter_cacert
+ assert_equal @mycert, decoded.certificate
+ assert_equal @mykey.to_der, decoded.key.to_der
+ end
+
+ def test_create_with_bad_nid
+ assert_raise(ArgumentError) do
+ OpenSSL::PKCS12.create(
+ "omg",
+ "hello",
+ @mykey,
+ @mycert,
+ [],
+ "foo"
+ )
+ end
+ end
+
+ def test_create_with_itr
+ OpenSSL::PKCS12.create(
+ "omg",
+ "hello",
+ @mykey,
+ @mycert,
+ [],
+ DEFAULT_PBE_PKEYS,
+ DEFAULT_PBE_CERTS,
+ 2048
+ )
+
+ assert_raise(TypeError) do
+ OpenSSL::PKCS12.create(
+ "omg",
+ "hello",
+ @mykey,
+ @mycert,
+ [],
+ DEFAULT_PBE_PKEYS,
+ DEFAULT_PBE_CERTS,
+ "omg"
+ )
+ end
+ end
+
+ def test_create_with_mac_itr
+ OpenSSL::PKCS12.create(
+ "omg",
+ "hello",
+ @mykey,
+ @mycert,
+ [],
+ DEFAULT_PBE_PKEYS,
+ DEFAULT_PBE_CERTS,
+ nil,
+ 2048
+ )
+
+ assert_raise(TypeError) do
+ OpenSSL::PKCS12.create(
+ "omg",
+ "hello",
+ @mykey,
+ @mycert,
+ [],
+ DEFAULT_PBE_PKEYS,
+ DEFAULT_PBE_CERTS,
+ nil,
+ "omg"
+ )
+ end
+ end
+
+ def test_create_with_keytype
+ omit "AWS-LC does not support KEY_SIG and KEY_EX" if aws_lc?
+
+ OpenSSL::PKCS12.create(
+ "omg",
+ "hello",
+ @mykey,
+ @mycert,
+ [],
+ DEFAULT_PBE_PKEYS,
+ DEFAULT_PBE_CERTS,
+ nil,
+ nil,
+ OpenSSL::PKCS12::KEY_SIG
+ )
+
+ assert_raise(ArgumentError) do
+ OpenSSL::PKCS12.create(
+ "omg",
+ "hello",
+ @mykey,
+ @mycert,
+ [],
+ DEFAULT_PBE_PKEYS,
+ DEFAULT_PBE_CERTS,
+ nil,
+ nil,
+ 2048
+ )
+ end
+ end
+
+ def test_new_with_no_keys
+ # Generated with the following steps:
+ # Print the value of the @mycert such as by `puts @mycert.to_s` and
+ # save the value as the file `mycert.pem`.
+ # Run the following commands:
+ # openssl pkcs12 -certpbe PBE-SHA1-3DES -in <(cat mycert.pem) \
+ # -nokeys -export -passout pass:abc123 -out /tmp/p12.out
+ # base64 -w 60 /tmp/p12.out
+ str = <<~EOF.unpack1("m")
+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
+AA==
+ EOF
+ p12 = OpenSSL::PKCS12.new(str, "abc123")
+
+ assert_equal nil, p12.key
+ assert_equal nil, p12.certificate
+ assert_equal 1, p12.ca_certs.size
+ assert_equal @mycert.subject, p12.ca_certs[0].subject
+ end
+
+ def test_new_with_no_certs
+ # Generated with the folowing steps:
+ # openssl pkcs12 -inkey test/openssl/fixtures/pkey/rsa-1.pem \
+ # -nocerts -export -passout pass:abc123 -out /tmp/p12.out
+ # base64 -w 60 /tmp/p12.out
+ str = <<~EOF.unpack1("m")
+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
+BC8fv38mue8LZVcbHQQIUNrWKEnskCoCAggA
+ EOF
+ p12 = OpenSSL::PKCS12.new(str, "abc123")
+
+ assert_equal Fixtures.pkey("rsa-1").to_der, p12.key.to_der
+ assert_equal nil, p12.certificate
+ assert_equal [], Array(p12.ca_certs)
+ end
+
+ def test_dup
+ p12 = OpenSSL::PKCS12.create(
+ "pass",
+ "name",
+ @mykey,
+ @mycert,
+ nil,
+ DEFAULT_PBE_PKEYS,
+ DEFAULT_PBE_CERTS,
+ )
+ assert_equal p12.to_der, p12.dup.to_der
+ end
+
+ def test_set_mac_pkcs12kdf
+ p12 = OpenSSL::PKCS12.create(
+ "pass",
+ "name",
+ @mykey,
+ @mycert,
+ nil,
+ nil,
+ nil,
+ nil,
+ 1234, # mac_iter
+ nil,
+ )
+ macdata = macdata(p12)
+ # Depends on the OpenSSL version: SHA256 in OpenSSL >= 3.0
+ assert_include ["SHA1", "SHA256"], macdata[:mac_algo]
+ assert_equal 1234, macdata[:iter]
+
+ p12.set_mac("pass", "macsalt", 2345, "SHA384")
+ macdata = macdata(p12)
+ assert_equal "SHA384", macdata[:mac_algo]
+ assert_equal "macsalt", macdata[:salt]
+ assert_equal 2345, macdata[:iter]
+ assert_equal @mykey.to_der, OpenSSL::PKCS12.new(p12.to_der, "pass").key.to_der
+ end
+
+ private
+
+ def macdata(p12)
+ # See RFC 7292
+ asn1 = OpenSSL::ASN1.decode(p12.to_der)
+ macdata = asn1.value[2]
+ mac = macdata.value[0]
+ mac_algo = mac.value[0].value[0].value
+ _mac_params = mac.value[0].value[1]
+ {
+ mac_algo: mac_algo,
+ salt: macdata.value[1].value,
+ iter: macdata.value[2]&.value,
+ }
+ end
+ end
+end
+
+end
diff --git a/test/openssl/test_pkcs7.rb b/test/openssl/test_pkcs7.rb
index 9b472c1795..b3129c0cdf 100644
--- a/test/openssl/test_pkcs7.rb
+++ b/test/openssl/test_pkcs7.rb
@@ -1,153 +1,448 @@
-begin
- require "openssl"
- require File.join(File.dirname(__FILE__), "utils.rb")
-rescue LoadError
-end
-require "test/unit"
+# frozen_string_literal: true
+require_relative 'utils'
if defined?(OpenSSL)
-class OpenSSL::TestPKCS7 < Test::Unit::TestCase
+class OpenSSL::TestPKCS7 < OpenSSL::TestCase
def setup
- @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024
- @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048
- ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
- ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1")
- ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2")
+ super
+ @ca_key = Fixtures.pkey("rsa-1")
+ @ee1_key = Fixtures.pkey("rsa-2")
+ @ee2_key = Fixtures.pkey("rsa-3")
+ ca = OpenSSL::X509::Name.new([["CN", "CA"]])
+ ee1 = OpenSSL::X509::Name.new([["CN", "EE1"]])
+ ee2 = OpenSSL::X509::Name.new([["CN", "EE2"]])
- now = Time.now
ca_exts = [
- ["basicConstraints","CA:TRUE",true],
- ["keyUsage","keyCertSign, cRLSign",true],
- ["subjectKeyIdentifier","hash",false],
- ["authorityKeyIdentifier","keyid:always",false],
+ ["basicConstraints", "CA:TRUE", true],
+ ["keyUsage", "keyCertSign, cRLSign", true],
+ ["subjectKeyIdentifier", "hash", false],
+ ["authorityKeyIdentifier", "keyid:always", false],
]
- @ca_cert = issue_cert(ca, @rsa2048, 1, Time.now, Time.now+3600, ca_exts,
- nil, nil, OpenSSL::Digest::SHA1.new)
+ @ca_cert = issue_cert(ca, @ca_key, 1, ca_exts, nil, nil)
ee_exts = [
- ["keyUsage","Non Repudiation, Digital Signature, Key Encipherment",true],
- ["authorityKeyIdentifier","keyid:always",false],
- ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false],
+ ["keyUsage", "nonRepudiation, digitalSignature, keyEncipherment", true],
+ ["authorityKeyIdentifier", "keyid:always", false],
+ ["extendedKeyUsage", "clientAuth, emailProtection, codeSigning", false],
]
- @ee1_cert = issue_cert(ee1, @rsa1024, 2, Time.now, Time.now+1800, ee_exts,
- @ca_cert, @rsa2048, OpenSSL::Digest::SHA1.new)
- @ee2_cert = issue_cert(ee2, @rsa1024, 3, Time.now, Time.now+1800, ee_exts,
- @ca_cert, @rsa2048, OpenSSL::Digest::SHA1.new)
- end
-
- def issue_cert(*args)
- OpenSSL::TestUtils.issue_cert(*args)
+ @ee1_cert = issue_cert(ee1, @ee1_key, 2, ee_exts, @ca_cert, @ca_key)
+ @ee2_cert = issue_cert(ee2, @ee2_key, 3, ee_exts, @ca_cert, @ca_key)
end
def test_signed
store = OpenSSL::X509::Store.new
store.add_cert(@ca_cert)
+
+ data = "aaaaa\nbbbbb\nccccc\n"
ca_certs = [@ca_cert]
+ tmp = OpenSSL::PKCS7.sign(@ee1_cert, @ee1_key, data, ca_certs)
+ # TODO: #data contains untranslated content
+ assert_equal("aaaaa\nbbbbb\nccccc\n", tmp.data)
+ assert_nil(tmp.error_string)
+
+ p7 = OpenSSL::PKCS7.new(tmp.to_der)
+ assert_nil(p7.data)
+ assert_nil(p7.error_string)
+
+ assert_true(p7.verify([], store))
+ # AWS-LC does not appear to convert to CRLF automatically
+ assert_equal("aaaaa\r\nbbbbb\r\nccccc\r\n", p7.data) unless aws_lc?
+ assert_nil(p7.error_string)
- data = "aaaaa\r\nbbbbb\r\nccccc\r\n"
- tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs)
- p7 = OpenSSL::PKCS7::PKCS7.new(tmp.to_der)
certs = p7.certificates
- signers = p7.signers
- assert(p7.verify([], store))
- assert_equal(data, p7.data)
assert_equal(2, certs.size)
- assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s)
- assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s)
+ assert_equal(@ee1_cert.subject, certs[0].subject)
+ assert_equal(@ca_cert.subject, certs[1].subject)
+
+ signers = p7.signers
assert_equal(1, signers.size)
assert_equal(@ee1_cert.serial, signers[0].serial)
- assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s)
+ assert_equal(@ee1_cert.issuer, signers[0].issuer)
+ # AWS-LC does not generate authenticatedAttributes
+ assert_in_delta(Time.now, signers[0].signed_time, 10) unless aws_lc?
+
+ assert_false(p7.verify([@ca_cert], OpenSSL::X509::Store.new))
+ end
- # Normaly OpenSSL tries to translate the supplied content into canonical
+ def test_signed_flags
+ store = OpenSSL::X509::Store.new
+ store.add_cert(@ca_cert)
+
+ # Normally OpenSSL tries to translate the supplied content into canonical
# MIME format (e.g. a newline character is converted into CR+LF).
# If the content is a binary, PKCS7::BINARY flag should be used.
-
+ #
+ # PKCS7::NOATTR flag suppresses authenticatedAttributes.
data = "aaaaa\nbbbbb\nccccc\n"
- flag = OpenSSL::PKCS7::BINARY
- tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag)
- p7 = OpenSSL::PKCS7::PKCS7.new(tmp.to_der)
- certs = p7.certificates
- signers = p7.signers
- assert(p7.verify([], store))
+ flag = OpenSSL::PKCS7::BINARY | OpenSSL::PKCS7::NOATTR
+ tmp = OpenSSL::PKCS7.sign(@ee1_cert, @ee1_key, data, [@ca_cert], flag)
+ p7 = OpenSSL::PKCS7.new(tmp.to_der)
+
+ assert_true(p7.verify([], store))
assert_equal(data, p7.data)
+
+ certs = p7.certificates
assert_equal(2, certs.size)
- assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s)
- assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s)
+ assert_equal(@ee1_cert.subject, certs[0].subject)
+ assert_equal(@ca_cert.subject, certs[1].subject)
+
+ signers = p7.signers
assert_equal(1, signers.size)
assert_equal(@ee1_cert.serial, signers[0].serial)
- assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s)
+ assert_equal(@ee1_cert.issuer, signers[0].issuer)
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { signers[0].signed_time }
+ end
+
+ def test_signed_multiple_signers
+ store = OpenSSL::X509::Store.new
+ store.add_cert(@ca_cert)
- # A signed-data which have multiple signatures can be created
+ # A signed-data which have multiple signatures can be created
# through the following steps.
# 1. create two signed-data
# 2. copy signerInfo and certificate from one to another
-
- tmp1 = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, [], flag)
- tmp2 = OpenSSL::PKCS7.sign(@ee2_cert, @rsa1024, data, [], flag)
+ data = "aaaaa\r\nbbbbb\r\nccccc\r\n"
+ tmp1 = OpenSSL::PKCS7.sign(@ee1_cert, @ee1_key, data)
+ tmp2 = OpenSSL::PKCS7.sign(@ee2_cert, @ee2_key, data)
tmp1.add_signer(tmp2.signers[0])
- tmp1.add_certificate(@ee2_cert)
+ tmp1.add_certificate(@ee2_cert)
- p7 = OpenSSL::PKCS7::PKCS7.new(tmp1.to_der)
- certs = p7.certificates
- signers = p7.signers
- assert(p7.verify([], store))
+ p7 = OpenSSL::PKCS7.new(tmp1.to_der)
+ assert_true(p7.verify([], store))
assert_equal(data, p7.data)
+
+ certs = p7.certificates
assert_equal(2, certs.size)
+
+ signers = p7.signers
assert_equal(2, signers.size)
assert_equal(@ee1_cert.serial, signers[0].serial)
- assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s)
+ assert_equal(@ee1_cert.issuer, signers[0].issuer)
assert_equal(@ee2_cert.serial, signers[1].serial)
- assert_equal(@ee2_cert.issuer.to_s, signers[1].issuer.to_s)
+ assert_equal(@ee2_cert.issuer, signers[1].issuer)
+ end
+
+ def test_signed_add_signer
+ data = "aaaaa\nbbbbb\nccccc\n"
+ psi = OpenSSL::PKCS7::SignerInfo.new(@ee1_cert, @ee1_key, "sha256")
+ p7 = OpenSSL::PKCS7.new
+ p7.type = :signed
+ p7.add_signer(psi)
+ p7.add_certificate(@ee1_cert)
+ p7.add_certificate(@ca_cert)
+ p7.add_data(data)
+
+ store = OpenSSL::X509::Store.new
+ store.add_cert(@ca_cert)
+
+ assert_equal(true, p7.verify([], store))
+ assert_equal(true, OpenSSL::PKCS7.new(p7.to_der).verify([], store))
+ assert_equal(1, p7.signers.size)
end
def test_detached_sign
store = OpenSSL::X509::Store.new
store.add_cert(@ca_cert)
- ca_certs = [@ca_cert]
data = "aaaaa\nbbbbb\nccccc\n"
+ ca_certs = [@ca_cert]
flag = OpenSSL::PKCS7::BINARY|OpenSSL::PKCS7::DETACHED
- tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag)
- p7 = OpenSSL::PKCS7::PKCS7.new(tmp.to_der)
- a1 = OpenSSL::ASN1.decode(p7)
+ tmp = OpenSSL::PKCS7.sign(@ee1_cert, @ee1_key, data, ca_certs, flag)
+ p7 = OpenSSL::PKCS7.new(tmp.to_der)
+ assert_predicate(p7, :detached?)
+ assert_true(p7.detached)
- certs = p7.certificates
- signers = p7.signers
- assert(!p7.verify([], store))
- assert(p7.verify([], store, data))
+ assert_false(p7.verify([], store))
+ # FIXME: Should it be nil?
+ assert_equal("", p7.data)
+ assert_match(/no content|NO_CONTENT/, p7.error_string)
+
+ assert_true(p7.verify([], store, data))
assert_equal(data, p7.data)
+ assert_nil(p7.error_string)
+
+ certs = p7.certificates
assert_equal(2, certs.size)
- assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s)
- assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s)
+ assert_equal(@ee1_cert.subject, certs[0].subject)
+ assert_equal(@ca_cert.subject, certs[1].subject)
+
+ signers = p7.signers
assert_equal(1, signers.size)
assert_equal(@ee1_cert.serial, signers[0].serial)
- assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s)
+ assert_equal(@ee1_cert.issuer, signers[0].issuer)
+ end
+
+ def test_signed_authenticated_attributes
+ # Using static PEM data because AWS-LC does not support generating one
+ # with authenticatedAttributes.
+ #
+ # p7 was generated with OpenSSL 3.4.1 with this program with commandline
+ # "faketime 2025-04-03Z ruby prog.rb":
+ #
+ # require_relative "test/openssl/utils"
+ # include OpenSSL::TestUtils
+ # key = Fixtures.pkey("p256")
+ # cert = issue_cert(OpenSSL::X509::Name.new([["CN", "cert"]]), key, 1, [], nil, nil)
+ # p7 = OpenSSL::PKCS7.sign(cert, key, "content", [])
+ # puts p7.to_pem
+ p7 = OpenSSL::PKCS7.new(<<~EOF)
+-----BEGIN PKCS7-----
+MIICvgYJKoZIhvcNAQcCoIICrzCCAqsCAQExDzANBglghkgBZQMEAgEFADAWBgkq
+hkiG9w0BBwGgCQQHY29udGVudKCCAQ4wggEKMIGxoAMCAQICAQEwCgYIKoZIzj0E
+AwIwDzENMAsGA1UEAwwEY2VydDAeFw0yNTA0MDIyMzAwMDFaFw0yNTA0MDMwMTAw
+MDFaMA8xDTALBgNVBAMMBGNlcnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQW
+CWTZz6hVQgpDrh5kb1uEs09YHuVJn8CsrjV4bLnADNT/QbnVe20J4FSX4xqFm2f1
+87Ukp0XiomZLf11eekQ2MAoGCCqGSM49BAMCA0gAMEUCIEg1fDI8b3hZAArgniVk
+HeM6puwgcMh5NXwvJ9x0unVmAiEAppecVTSQ+yEPyBG415Og6sK+RC78pcByEC81
+C/QSwRYxggFpMIIBZQIBATAUMA8xDTALBgNVBAMMBGNlcnQCAQEwDQYJYIZIAWUD
+BAIBBQCggeQwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx
+DxcNMjUwNDAzMDAwMDAxWjAvBgkqhkiG9w0BCQQxIgQg7XACtDnprIRfIjV9gius
+FERzD722AW0+yUMil7nsn3MweQYJKoZIhvcNAQkPMWwwajALBglghkgBZQMEASow
+CwYJYIZIAWUDBAEWMAsGCWCGSAFlAwQBAjAKBggqhkiG9w0DBzAOBggqhkiG9w0D
+AgICAIAwDQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwCgYI
+KoZIzj0EAwIESDBGAiEAssymc28HySAhg+XeWIpSbtzkwycr2JG6dzHRZ+vn0ocC
+IQCJVpo1FTLZOHSc9UpjS+VKR4cg50Iz0HiPyo6hwjCrwA==
+-----END PKCS7-----
+ EOF
+
+ cert = p7.certificates[0]
+ store = OpenSSL::X509::Store.new.tap { |store|
+ store.time = Time.utc(2025, 4, 3)
+ store.add_cert(cert)
+ }
+ assert_equal(true, p7.verify([], store))
+ assert_equal(1, p7.signers.size)
+ signer = p7.signers[0]
+ assert_in_delta(Time.utc(2025, 4, 3), signer.signed_time, 10)
end
def test_enveloped
- if OpenSSL::OPENSSL_VERSION_NUMBER <= 0x0090704f
- # PKCS7_encrypt() of OpenSSL-0.9.7d goes to SEGV.
- # http://www.mail-archive.com/openssl-dev@openssl.org/msg17376.html
- return
- end
+ omit_on_fips # PKCS #1 v1.5 padding
certs = [@ee1_cert, @ee2_cert]
cipher = OpenSSL::Cipher::AES.new("128-CBC")
data = "aaaaa\nbbbbb\nccccc\n"
tmp = OpenSSL::PKCS7.encrypt(certs, data, cipher, OpenSSL::PKCS7::BINARY)
- p7 = OpenSSL::PKCS7::PKCS7.new(tmp.to_der)
+ p7 = OpenSSL::PKCS7.new(tmp.to_der)
recip = p7.recipients
assert_equal(:enveloped, p7.type)
assert_equal(2, recip.size)
- assert_equal(@ca_cert.subject.to_s, recip[0].issuer.to_s)
- assert_equal(2, recip[0].serial)
- assert_equal(data, p7.decrypt(@rsa1024, @ee1_cert))
+ assert_equal(@ca_cert.subject, recip[0].issuer)
+ assert_equal(@ee1_cert.serial, recip[0].serial)
+ assert_equal(16, @ee1_key.decrypt(recip[0].enc_key).size)
+ assert_equal(data, p7.decrypt(@ee1_key, @ee1_cert))
+
+ assert_equal(@ca_cert.subject, recip[1].issuer)
+ assert_equal(@ee2_cert.serial, recip[1].serial)
+ assert_equal(data, p7.decrypt(@ee2_key, @ee2_cert))
+
+ assert_equal(data, p7.decrypt(@ee1_key))
+
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) {
+ p7.decrypt(@ca_key, @ca_cert)
+ }
+
+ # Default cipher has been removed in v3.3
+ assert_raise_with_message(ArgumentError, /RC2-40-CBC/) {
+ OpenSSL::PKCS7.encrypt(certs, data)
+ }
+ end
+
+ def test_enveloped_add_recipient
+ omit_on_fips # PKCS #1 v1.5 padding
+
+ data = "aaaaa\nbbbbb\nccccc\n"
+ ktri_ee1 = OpenSSL::PKCS7::RecipientInfo.new(@ee1_cert)
+ ktri_ee2 = OpenSSL::PKCS7::RecipientInfo.new(@ee2_cert)
+
+ tmp = OpenSSL::PKCS7.new
+ tmp.type = :enveloped
+ tmp.cipher = "AES-128-CBC"
+ tmp.add_recipient(ktri_ee1)
+ tmp.add_recipient(ktri_ee2)
+ tmp.add_data(data)
+
+ p7 = OpenSSL::PKCS7.new(tmp.to_der)
+ assert_equal(:enveloped, p7.type)
+ assert_equal(data, p7.decrypt(@ee1_key, @ee1_cert))
+ assert_equal(data, p7.decrypt(@ee2_key, @ee2_cert))
+ assert_equal([@ee1_cert.serial, @ee2_cert.serial].sort,
+ p7.recipients.map(&:serial).sort)
+ end
+
+ def test_data
+ asn1 = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::ObjectId("pkcs7-data"),
+ OpenSSL::ASN1::OctetString("content", 0, :EXPLICIT),
+ ])
+ p7 = OpenSSL::PKCS7.new
+ p7.type = :data
+ p7.data = "content"
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.add_certificate(@ee1_cert) }
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.certificates = [@ee1_cert] }
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.cipher = "aes-128-cbc" }
+ assert_equal(asn1.to_der, p7.to_der)
+
+ p7 = OpenSSL::PKCS7.new(asn1)
+ assert_equal(:data, p7.type)
+ assert_equal(false, p7.detached)
+ assert_equal(false, p7.detached?)
+ # Not applicable
+ assert_nil(p7.certificates)
+ assert_nil(p7.crls)
+ # Not applicable. Should they return nil or raise an exception instead?
+ assert_equal([], p7.signers)
+ assert_equal([], p7.recipients)
+ # PKCS7#verify can't distinguish verification failure and other errors
+ store = OpenSSL::X509::Store.new
+ assert_equal(false, p7.verify([@ee1_cert], store))
+ assert_match(/wrong content type|WRONG_CONTENT_TYPE/, p7.error_string)
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.decrypt(@ee1_key) }
+ end
+
+ def test_empty_signed_data_ruby_bug_19974
+ data = "-----BEGIN PKCS7-----\nMAsGCSqGSIb3DQEHAg==\n-----END PKCS7-----\n"
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { OpenSSL::PKCS7.new(data) }
+
+ data = <<END
+MIME-Version: 1.0
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Type: application/x-pkcs7-mime; smime-type=signed-data; name="smime.p7m"
+Content-Transfer-Encoding: base64
+
+#{data}
+END
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { OpenSSL::PKCS7.read_smime(data) }
+ end
+
+ def test_graceful_parsing_failure #[ruby-core:43250]
+ contents = "not a valid PKCS #7 PEM block"
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { OpenSSL::PKCS7.new(contents) }
+ end
+
+ def test_set_type_signed
+ p7 = OpenSSL::PKCS7.new
+ p7.type = "signed"
+ assert_equal(:signed, p7.type)
+ end
+
+ def test_set_type_data
+ p7 = OpenSSL::PKCS7.new
+ p7.type = "data"
+ assert_equal(:data, p7.type)
+ end
+
+ def test_set_type_signed_and_enveloped
+ p7 = OpenSSL::PKCS7.new
+ p7.type = "signedAndEnveloped"
+ assert_equal(:signedAndEnveloped, p7.type)
+ end
+
+ def test_set_type_encrypted
+ p7 = OpenSSL::PKCS7.new
+ p7.type = "encrypted"
+ assert_equal(:encrypted, p7.type)
+ end
+
+ def test_smime
+ pend "AWS-LC has no current support for SMIME with PKCS7" if aws_lc?
+
+ 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, @ee1_key, data, ca_certs)
+ p7 = OpenSSL::PKCS7.new(tmp.to_der)
+ smime = OpenSSL::PKCS7.write_smime(p7)
+ assert_equal(true, smime.start_with?(<<END))
+MIME-Version: 1.0
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Type: application/x-pkcs7-mime; smime-type=signed-data; name="smime.p7m"
+Content-Transfer-Encoding: base64
+
+END
+ assert_equal(p7.to_der, OpenSSL::PKCS7.read_smime(smime).to_der)
+
+ smime = OpenSSL::PKCS7.write_smime(p7, nil, 0)
+ assert_equal(p7.to_der, OpenSSL::PKCS7.read_smime(smime).to_der)
+ end
+
+ def test_to_text
+ omit "AWS-LC does not support PKCS7.to_text" if aws_lc?
+
+ p7 = OpenSSL::PKCS7.new
+ p7.type = "signed"
+ assert_match(/signed/, p7.to_text)
+ end
+
+ def test_degenerate_pkcs7
+ ca_cert_pem = <<END
+-----BEGIN CERTIFICATE-----
+MIID4DCCAsigAwIBAgIJAL1oVI72wmQwMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV
+BAYTAkFVMQ4wDAYDVQQIEwVTdGF0ZTENMAsGA1UEBxMEQ2l0eTEQMA4GA1UEChMH
+RXhhbXBsZTETMBEGA1UEAxMKRXhhbXBsZSBDQTAeFw0xMjEwMTgwOTE2NTBaFw0y
+MjEwMTYwOTE2NTBaMFMxCzAJBgNVBAYTAkFVMQ4wDAYDVQQIEwVTdGF0ZTENMAsG
+A1UEBxMEQ2l0eTEQMA4GA1UEChMHRXhhbXBsZTETMBEGA1UEAxMKRXhhbXBsZSBD
+QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMTSPNxOkd5NN19XO0fJ
+tGVlWN4DWuvVL9WbWnXJXX9rU6X8sSOL9RrRA64eEZf2UBFjz9fMHZj/OGcxZpus
+4YtzfSrMU6xfvsIHeqX+mT60ms2RfX4UXab50MQArBin3JVKHGnOi25uyAOylVFU
+TuzzQJvKyB67vjuRPMlVAgVAZAP07ru9gW0ajt/ODxvUfvXxp5SFF68mVP2ipMBr
+4fujUwQC6cVHmnuL6p87VFoo9uk87TSQVDOQGL8MK4moMFtEW9oUTU22CgnxnCsS
+sCCELYhy9BdaTWQH26LzMfhnwSuIRHZyprW4WZtU0akrYXNiCj8o92rZmQWXJDbl
+qNECAwEAAaOBtjCBszAdBgNVHQ4EFgQUNtVw4jvkZZbkdQbkYi2/F4QN79owgYMG
+A1UdIwR8MHqAFDbVcOI75GWW5HUG5GItvxeEDe/aoVekVTBTMQswCQYDVQQGEwJB
+VTEOMAwGA1UECBMFU3RhdGUxDTALBgNVBAcTBENpdHkxEDAOBgNVBAoTB0V4YW1w
+bGUxEzARBgNVBAMTCkV4YW1wbGUgQ0GCCQC9aFSO9sJkMDAMBgNVHRMEBTADAQH/
+MA0GCSqGSIb3DQEBBQUAA4IBAQBvJIsY9bIqliZ3WD1KoN4cvAQeRAPsoLXQkkHg
+P6Nrcw9rJ5JvoHfYbo5aNlwbnkbt/B2xlVEXUYpJoBZFXafgxG2gJleioIgnaDS4
+FPPwZf1C5ZrOgUBfxTGjHex4ghSAoNGOd35jQzin5NGKOvZclPjZ2vQ++LP3aA2l
+9Fn2qASS46IzMGJlC75mlTOTQwDM16UunMAK26lNG9J6q02o4d/oU2a7x0fD80yF
+64kNA1wDAwaVCYiUH541qKp+b4iDqer8nf8HqzYDFlpje18xYZMEd1hj8dVOharM
+pISJ+D52hV/BGEYF8r5k3hpC5d76gSP2oCcaY0XvLBf97qik
+-----END CERTIFICATE-----
+END
+ p7 = OpenSSL::PKCS7.new
+ p7.type = "signed"
+ ca_cert = OpenSSL::X509::Certificate.new(ca_cert_pem)
+ p7.add_certificate ca_cert
+ p7.add_data ""
+
+ assert_nothing_raised do
+ p7.to_pem
+ end
+ end
+
+ def test_decode_ber_constructed_string
+ omit_on_fips # PKCS #1 v1.5 padding
+
+ p7 = OpenSSL::PKCS7.encrypt([@ee1_cert], "content", "aes-128-cbc")
+
+ # Make an equivalent BER to p7.to_der. Here we convert the encryptedContent
+ # field of EncryptedContentInfo into a constructed encoding using the
+ # indefinite length form.
+ # See https://www.rfc-editor.org/rfc/rfc2315#section-10.1
+ asn1 = OpenSSL::ASN1.decode(p7.to_der)
+ asn1.indefinite_length = true
+ enveloped_data_explicit_tag = asn1.value[1]
+ enveloped_data_explicit_tag.indefinite_length = true
+ enveloped_data = enveloped_data_explicit_tag.value[0]
+ enveloped_data.indefinite_length = true
+ encrypted_content_info = enveloped_data.value[2]
+ encrypted_content_info.indefinite_length = true
+ orig = encrypted_content_info.value[2]
+ encrypted_content_info.value[2] = OpenSSL::ASN1::ASN1Data.new([
+ OpenSSL::ASN1::OctetString(orig.value[...5]),
+ OpenSSL::ASN1::OctetString(orig.value[5...]),
+ ], 0, :CONTEXT_SPECIFIC).tap { |x| x.indefinite_length = true }
+
+ assert_not_equal(p7.to_der, asn1.to_der)
+ assert_equal(p7.to_der, OpenSSL::PKCS7.new(asn1.to_der).to_der)
- assert_equal(@ca_cert.subject.to_s, recip[1].issuer.to_s)
- assert_equal(3, recip[1].serial)
- assert_equal(data, p7.decrypt(@rsa1024, @ee2_cert))
+ assert_equal("content", OpenSSL::PKCS7.new(p7.to_der).decrypt(@ee1_key))
+ assert_equal("content", OpenSSL::PKCS7.new(asn1.to_der).decrypt(@ee1_key))
end
end
diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb
new file mode 100644
index 0000000000..93d9e1d42f
--- /dev/null
+++ b/test/openssl/test_pkey.rb
@@ -0,0 +1,324 @@
+# frozen_string_literal: true
+require_relative "utils"
+
+class OpenSSL::TestPKey < OpenSSL::PKeyTestCase
+ def test_generic_oid_inspect_rsa
+ # RSA private key
+ rsa = Fixtures.pkey("rsa-1")
+ assert_instance_of OpenSSL::PKey::RSA, rsa
+ assert_equal "rsaEncryption", rsa.oid
+ assert_match %r{oid=rsaEncryption}, rsa.inspect
+ assert_match %r{type_name=RSA}, rsa.inspect if openssl?(3, 0, 0)
+ end
+
+ def test_s_generate_parameters
+ pkey = OpenSSL::PKey.generate_parameters("EC", {
+ "ec_paramgen_curve" => "secp384r1",
+ })
+ assert_instance_of OpenSSL::PKey::EC, pkey
+ assert_equal "secp384r1", pkey.group.curve_name
+ assert_equal nil, pkey.private_key
+
+ # Invalid options are checked
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey.generate_parameters("EC", "invalid" => "option")
+ }
+ end
+
+ def test_s_generate_parameters_with_block
+ # DSA kengen is not FIPS-approved.
+ # https://github.com/openssl/openssl/commit/49a35f0#diff-605396c063194975af8ce31399d42690ab18186b422fb5012101cc9132660fe1R611-R614
+ omit_on_fips
+
+ # Parameter generation callback is called
+ if openssl?(3, 0, 0, 0) && !openssl?(3, 0, 0, 6)
+ # Errors in BN_GENCB were not properly handled. This special pend is to
+ # suppress failures on Ubuntu 22.04, which uses OpenSSL 3.0.2.
+ pend "unstable test on OpenSSL 3.0.[0-5]"
+ end
+ cb_called = []
+ assert_raise(RuntimeError) {
+ OpenSSL::PKey.generate_parameters("DSA") { |*args|
+ cb_called << args
+ raise "exit!" if cb_called.size == 3
+ }
+ }
+ assert_not_empty cb_called
+ end
+
+ def test_s_generate_key
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ # DSA key pair cannot be generated without parameters
+ OpenSSL::PKey.generate_key("DSA")
+ }
+ pkey_params = OpenSSL::PKey.generate_parameters("EC", {
+ "ec_paramgen_curve" => "secp384r1",
+ })
+ pkey = OpenSSL::PKey.generate_key(pkey_params)
+ assert_instance_of OpenSSL::PKey::EC, pkey
+ assert_equal "secp384r1", pkey.group.curve_name
+ assert_not_equal nil, pkey.private_key
+ end
+
+ def test_s_read_pem_unknown_block
+ # A PEM-encoded certificate and a PEM-encoded private key are combined.
+ # Check that OSSL_STORE doesn't stop after the first PEM block.
+ orig = Fixtures.pkey("rsa-1")
+ subject = OpenSSL::X509::Name.new([["CN", "test"]])
+ cert = issue_cert(subject, orig, 1, [], nil, nil)
+
+ input = cert.to_text + cert.to_pem + orig.to_text + orig.private_to_pem
+ pkey = OpenSSL::PKey.read(input)
+ assert_equal(orig.private_to_der, pkey.private_to_der)
+ end
+
+ def test_s_read_der_then_pem
+ # If the input is valid as both DER and PEM (which allows garbage data
+ # before and after the block), it is read as DER
+ #
+ # TODO: Garbage data after DER should not be allowed, but it is currently
+ # ignored
+ orig1 = Fixtures.pkey("rsa-1")
+ orig2 = Fixtures.pkey("rsa-2")
+ pkey = OpenSSL::PKey.read(orig1.public_to_der + orig2.private_to_pem)
+ assert_equal(orig1.public_to_der, pkey.public_to_der)
+ assert_not_predicate(pkey, :private?)
+ end
+
+ def test_s_read_passphrase
+ orig = Fixtures.pkey("rsa-1")
+ encrypted_pem = orig.private_to_pem("AES-256-CBC", "correct_passphrase")
+ assert_match(/\A-----BEGIN ENCRYPTED PRIVATE KEY-----/, encrypted_pem)
+
+ # Correct passphrase passed as the second argument
+ pkey1 = OpenSSL::PKey.read(encrypted_pem, "correct_passphrase")
+ assert_equal(orig.private_to_der, pkey1.private_to_der)
+
+ # Correct passphrase returned by the block. The block gets false
+ called = 0
+ flag = nil
+ pkey2 = OpenSSL::PKey.read(encrypted_pem) { |f|
+ called += 1
+ flag = f
+ "correct_passphrase"
+ }
+ assert_equal(orig.private_to_der, pkey2.private_to_der)
+ assert_equal(1, called)
+ assert_false(flag)
+
+ # Incorrect passphrase passed. The block is not called
+ called = 0
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey.read(encrypted_pem, "incorrect_passphrase") {
+ called += 1
+ }
+ }
+ assert_equal(0, called)
+
+ # Incorrect passphrase returned by the block. The block is called only once
+ called = 0
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey.read(encrypted_pem) {
+ called += 1
+ "incorrect_passphrase"
+ }
+ }
+ assert_equal(1, called)
+ end
+
+ def test_s_read_passphrase_tty
+ omit "https://github.com/aws/aws-lc/pull/2555" if aws_lc?
+
+ orig = Fixtures.pkey("rsa-1")
+ encrypted_pem = orig.private_to_pem("AES-256-CBC", "correct_passphrase")
+
+ # Correct passphrase passed to OpenSSL's prompt
+ script = <<~"end;"
+ require "openssl"
+ Process.setsid
+ OpenSSL::PKey.read(#{encrypted_pem.dump})
+ puts "ok"
+ end;
+ assert_in_out_err([*$:.map { |l| "-I#{l}" }, "-e#{script}"],
+ "correct_passphrase\n") { |stdout, stderr|
+ assert_equal(["Enter PEM pass phrase:"], stderr)
+ assert_equal(["ok"], stdout)
+ }
+
+ # Incorrect passphrase passed to OpenSSL's prompt
+ script = <<~"end;"
+ require "openssl"
+ Process.setsid
+ begin
+ OpenSSL::PKey.read(#{encrypted_pem.dump})
+ rescue OpenSSL::PKey::PKeyError
+ puts "ok"
+ else
+ puts "expected OpenSSL::PKey::PKeyError"
+ end
+ end;
+ stdin = "incorrect_passphrase\n" * 5
+ assert_in_out_err([*$:.map { |l| "-I#{l}" }, "-e#{script}"],
+ stdin) { |stdout, stderr|
+ assert_equal(1, stderr.count("Enter PEM pass phrase:"))
+ assert_equal(["ok"], stdout)
+ }
+ end if ENV["OSSL_TEST_ALL"] == "1" && Process.respond_to?(:setsid)
+
+ def test_hmac_sign_verify
+ pkey = OpenSSL::PKey.generate_key("HMAC", { "key" => "a"*32 })
+
+ hmac = OpenSSL::HMAC.new("a"*32, "SHA256").update("data").digest
+ assert_equal hmac, pkey.sign("SHA256", "data")
+
+ # EVP_PKEY_HMAC does not support verify
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ pkey.verify("SHA256", "data", hmac)
+ }
+ end
+
+ def test_ed25519
+ # Ed25519 is not FIPS-approved.
+ omit_on_fips
+
+ # Test vector from RFC 8032 Section 7.1 TEST 2
+ priv_pem = <<~EOF
+ -----BEGIN PRIVATE KEY-----
+ MC4CAQAwBQYDK2VwBCIEIEzNCJso/5banbbDRuwRTg9bijGfNaumJNqM9u1PuKb7
+ -----END PRIVATE KEY-----
+ EOF
+ pub_pem = <<~EOF
+ -----BEGIN PUBLIC KEY-----
+ MCowBQYDK2VwAyEAPUAXw+hDiVqStwqnTRt+vJyYLM8uxJaMwM1V8Sr0Zgw=
+ -----END PUBLIC KEY-----
+ EOF
+ priv = OpenSSL::PKey.read(priv_pem)
+ pub = OpenSSL::PKey.read(pub_pem)
+ assert_instance_of OpenSSL::PKey::PKey, priv
+ assert_instance_of OpenSSL::PKey::PKey, pub
+ assert_equal priv_pem, priv.private_to_pem
+ assert_equal pub_pem, priv.public_to_pem
+ assert_equal pub_pem, pub.public_to_pem
+
+ assert_equal "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
+ priv.raw_private_key.unpack1("H*")
+ assert_equal OpenSSL::PKey.new_raw_private_key("ED25519", priv.raw_private_key).private_to_pem,
+ priv.private_to_pem
+ assert_equal "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
+ priv.raw_public_key.unpack1("H*")
+ assert_equal OpenSSL::PKey.new_raw_public_key("ED25519", priv.raw_public_key).public_to_pem,
+ pub.public_to_pem
+
+ sig = [<<~EOF.gsub(/[^0-9a-f]/, "")].pack("H*")
+ 92a009a9f0d4cab8720e820b5f642540
+ a2b27b5416503f8fb3762223ebdb69da
+ 085ac1e43e15996e458f3613d0f11d8c
+ 387b2eaeb4302aeeb00d291612bb0c00
+ EOF
+ data = ["72"].pack("H*")
+ assert_equal sig, priv.sign(nil, data)
+ assert_equal true, priv.verify(nil, sig, data)
+ assert_equal true, pub.verify(nil, sig, data)
+ assert_equal false, pub.verify(nil, sig, data.succ)
+
+ # PureEdDSA wants nil as the message digest
+ assert_raise(OpenSSL::PKey::PKeyError) { priv.sign("SHA512", data) }
+ assert_raise(OpenSSL::PKey::PKeyError) { pub.verify("SHA512", sig, data) }
+
+ # Ed25519 pkey type does not support key derivation
+ assert_raise(OpenSSL::PKey::PKeyError) { priv.derive(pub) }
+ end
+
+ def test_x25519
+ omit_on_fips
+
+ # Test vector from RFC 7748 Section 6.1
+ alice_pem = <<~EOF
+ -----BEGIN PRIVATE KEY-----
+ MC4CAQAwBQYDK2VuBCIEIHcHbQpzGKV9PBbBclGyZkXfTC+H68CZKrF3+6UduSwq
+ -----END PRIVATE KEY-----
+ EOF
+ bob_pem = <<~EOF
+ -----BEGIN PUBLIC KEY-----
+ MCowBQYDK2VuAyEA3p7bfXt9wbTTW2HC7OQ1Nz+DQ8hbeGdNrfx+FG+IK08=
+ -----END PUBLIC KEY-----
+ EOF
+ shared_secret = "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"
+
+ alice = OpenSSL::PKey.read(alice_pem)
+ bob = OpenSSL::PKey.read(bob_pem)
+ assert_instance_of OpenSSL::PKey::PKey, alice
+ assert_equal "X25519", alice.oid
+ assert_match %r{oid=X25519}, alice.inspect
+ assert_equal alice_pem, alice.private_to_pem
+ assert_equal bob_pem, bob.public_to_pem
+ assert_equal [shared_secret].pack("H*"), alice.derive(bob)
+
+ alice_private = OpenSSL::PKey.new_raw_private_key("X25519", alice.raw_private_key)
+ bob_public = OpenSSL::PKey.new_raw_public_key("X25519", bob.raw_public_key)
+ assert_equal alice_private.private_to_pem,
+ alice.private_to_pem
+ assert_equal bob_public.public_to_pem,
+ bob.public_to_pem
+ assert_equal "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
+ alice.raw_private_key.unpack1("H*")
+ assert_equal "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f",
+ bob.raw_public_key.unpack1("H*")
+ end
+
+ def test_ml_dsa
+ # AWS-LC also supports ML-DSA, but it's implemented in a different way
+ return unless openssl?(3, 5, 0)
+
+ pkey = OpenSSL::PKey.generate_key("ML-DSA-44")
+ assert_match(/type_name=ML-DSA-44/, pkey.inspect)
+ sig = pkey.sign(nil, "data")
+ assert_equal(2420, sig.bytesize)
+ assert_equal(true, pkey.verify(nil, sig, "data"))
+
+ pub2 = OpenSSL::PKey.read(pkey.public_to_der)
+ assert_equal(true, pub2.verify(nil, sig, "data"))
+
+ raw_public_key = pkey.raw_public_key
+ assert_equal(1312, raw_public_key.bytesize)
+ pub3 = OpenSSL::PKey.new_raw_public_key("ML-DSA-44", raw_public_key)
+ assert_equal(true, pub3.verify(nil, sig, "data"))
+ end
+
+ def test_raw_initialize_errors
+ assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("foo123", "xxx") }
+ assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("ED25519", "xxx") }
+ assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_public_key("foo123", "xxx") }
+ assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_public_key("ED25519", "xxx") }
+ end
+
+ def test_compare?
+ key1 = Fixtures.pkey("rsa-1")
+ key2 = Fixtures.pkey("rsa-1")
+ key3 = Fixtures.pkey("rsa-2")
+ key4 = Fixtures.pkey("p256")
+
+ assert_equal(true, key1.compare?(key2))
+ assert_equal(true, key1.public_key.compare?(key2))
+ assert_equal(true, key2.compare?(key1))
+ assert_equal(true, key2.public_key.compare?(key1))
+
+ assert_equal(false, key1.compare?(key3))
+
+ assert_raise(TypeError) do
+ key1.compare?(key4)
+ end
+ end
+
+ def test_to_text
+ rsa = Fixtures.pkey("rsa-1")
+ assert_include rsa.to_text, "publicExponent"
+ end
+
+ def test_legacy_error_classes
+ assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::DSAError)
+ assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::DHError)
+ assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::ECError)
+ assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::RSAError)
+ end
+end
diff --git a/test/openssl/test_pkey_dh.rb b/test/openssl/test_pkey_dh.rb
new file mode 100644
index 0000000000..cd13283a2a
--- /dev/null
+++ b/test/openssl/test_pkey_dh.rb
@@ -0,0 +1,235 @@
+# frozen_string_literal: true
+require_relative 'utils'
+
+if defined?(OpenSSL) && defined?(OpenSSL::PKey::DH)
+
+class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase
+ def test_new_empty
+ # pkeys are immutable with OpenSSL >= 3.0
+ if openssl?(3, 0, 0)
+ assert_raise(ArgumentError) { OpenSSL::PKey::DH.new }
+ else
+ dh = OpenSSL::PKey::DH.new
+ assert_nil(dh.p)
+ assert_nil(dh.priv_key)
+ end
+ end
+
+ def test_new_generate
+ begin
+ dh1 = OpenSSL::PKey::DH.new(512)
+ rescue OpenSSL::PKey::PKeyError
+ omit "generating 512-bit DH parameters failed; " \
+ "likely not supported by this OpenSSL build"
+ end
+ assert_equal(512, dh1.p.num_bits)
+ assert_key(dh1)
+
+ dh2 = OpenSSL::PKey::DH.generate(512)
+ assert_equal(512, dh2.p.num_bits)
+ assert_key(dh2)
+ assert_not_equal(dh1.p, dh2.p)
+ end if ENV["OSSL_TEST_ALL"] == "1"
+
+ def test_new_break
+ unless openssl? && OpenSSL.fips_mode
+ assert_raise(RuntimeError) do
+ OpenSSL::PKey::DH.new(2048) { raise }
+ end
+ else
+ # The block argument is not executed in FIPS case.
+ # See https://github.com/ruby/openssl/issues/692 for details.
+ assert_kind_of(OpenSSL::PKey::DH, OpenSSL::PKey::DH.new(2048) { raise })
+ end
+ end
+
+ def test_derive_key
+ params = Fixtures.pkey("dh2048_ffdhe2048")
+ dh1 = OpenSSL::PKey.generate_key(params)
+ dh2 = OpenSSL::PKey.generate_key(params)
+ dh1_pub = OpenSSL::PKey.read(dh1.public_to_der)
+ dh2_pub = OpenSSL::PKey.read(dh2.public_to_der)
+
+ z = dh1.g.mod_exp(dh1.priv_key, dh1.p).mod_exp(dh2.priv_key, dh1.p).to_s(2)
+ assert_equal z, dh1.derive(dh2_pub)
+ assert_equal z, dh2.derive(dh1_pub)
+
+ assert_raise(OpenSSL::PKey::PKeyError) { params.derive(dh1_pub) }
+ assert_raise(OpenSSL::PKey::PKeyError) { dh1_pub.derive(params) }
+
+ assert_equal z, dh1.compute_key(dh2.pub_key)
+ assert_equal z, dh2.compute_key(dh1.pub_key)
+ end
+
+ def test_DHparams
+ dh_params = Fixtures.pkey("dh2048_ffdhe2048")
+
+ asn1 = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(dh_params.p),
+ OpenSSL::ASN1::Integer(dh_params.g)
+ ])
+ assert_equal(asn1.to_der, dh_params.to_der)
+ key = OpenSSL::PKey::DH.new(asn1.to_der)
+ assert_same_dh_params(dh_params, key)
+
+ pem = <<~EOF
+ -----BEGIN DH PARAMETERS-----
+ MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
+ +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
+ 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
+ YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
+ 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
+ ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
+ -----END DH PARAMETERS-----
+ EOF
+ assert_equal(pem, dh_params.export)
+
+ key = OpenSSL::PKey::DH.new(pem)
+ assert_same_dh_params(dh_params, key)
+ assert_no_key(key)
+ key = OpenSSL::PKey.read(pem)
+ assert_same_dh_params(dh_params, key)
+ assert_no_key(key)
+
+ key = OpenSSL::PKey.generate_key(dh_params)
+ assert_same_dh_params(dh_params, key)
+ assert_key(key)
+ assert_equal(dh_params.to_der, key.to_der)
+ assert_equal(dh_params.to_pem, key.to_pem)
+ end
+
+ def test_public_key
+ dh = Fixtures.pkey("dh2048_ffdhe2048")
+ public_key = dh.public_key
+ assert_no_key(public_key) #implies public_key.public? is false!
+ assert_equal(dh.to_der, public_key.to_der)
+ assert_equal(dh.to_pem, public_key.to_pem)
+ end
+
+ def test_generate_key
+ # Deprecated in v3.0.0; incompatible with OpenSSL 3.0
+ dh = Fixtures.pkey("dh2048_ffdhe2048")
+ assert_no_key(dh)
+ dh.generate_key!
+ assert_key(dh)
+
+ dh2 = OpenSSL::PKey::DH.new(dh.to_der)
+ dh2.generate_key!
+ assert_not_equal(dh.pub_key, dh2.pub_key)
+ assert_equal(dh.compute_key(dh2.pub_key), dh2.compute_key(dh.pub_key))
+ end if !openssl?(3, 0, 0)
+
+ def test_params_ok?
+ omit_on_fips
+
+ # Skip the tests in old OpenSSL version 1.1.1c or early versions before
+ # applying the following commits in OpenSSL 1.1.1d to make `DH_check`
+ # function pass the RFC 7919 FFDHE group texts.
+ # https://github.com/openssl/openssl/pull/9435
+ if openssl? && !openssl?(1, 1, 1, 4)
+ pend 'DH check for RFC 7919 FFDHE group texts is not implemented'
+ end
+
+ dh0 = Fixtures.pkey("dh2048_ffdhe2048")
+
+ dh1 = OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(dh0.p),
+ OpenSSL::ASN1::Integer(dh0.g)
+ ]))
+ assert_equal(true, dh1.params_ok?)
+
+ # AWS-LC automatically does parameter checks on the parsed params.
+ if aws_lc?
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(dh0.p + 1),
+ OpenSSL::ASN1::Integer(dh0.g)
+ ]))
+ }
+ else
+ dh2 = OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(dh0.p + 1),
+ OpenSSL::ASN1::Integer(dh0.g)
+ ]))
+ assert_equal(false, dh2.params_ok?)
+ end
+
+ end
+
+ def test_params
+ dh = Fixtures.pkey("dh2048_ffdhe2048")
+ assert_kind_of(OpenSSL::BN, dh.p)
+ assert_equal(dh.p, dh.params["p"])
+ assert_kind_of(OpenSSL::BN, dh.g)
+ assert_equal(dh.g, dh.params["g"])
+ assert_nil(dh.pub_key)
+ assert_nil(dh.params["pub_key"])
+ assert_nil(dh.priv_key)
+ assert_nil(dh.params["priv_key"])
+
+ dhkey = OpenSSL::PKey.generate_key(dh)
+ assert_equal(dh.params["p"], dhkey.params["p"])
+ assert_kind_of(OpenSSL::BN, dhkey.pub_key)
+ assert_equal(dhkey.pub_key, dhkey.params["pub_key"])
+ assert_kind_of(OpenSSL::BN, dhkey.priv_key)
+ assert_equal(dhkey.priv_key, dhkey.params["priv_key"])
+ end
+
+ def test_dup
+ # Parameters only
+ dh1 = Fixtures.pkey("dh2048_ffdhe2048")
+ dh2 = dh1.dup
+ assert_equal dh1.to_der, dh2.to_der
+ assert_not_equal nil, dh1.p
+ assert_not_equal nil, dh1.g
+ assert_equal [dh1.p, dh1.g], [dh2.p, dh2.g]
+ assert_equal nil, dh1.pub_key
+ assert_equal nil, dh1.priv_key
+ assert_equal [dh1.pub_key, dh1.priv_key], [dh2.pub_key, dh2.priv_key]
+
+ # PKey is immutable in OpenSSL >= 3.0
+ if !openssl?(3, 0, 0)
+ dh2.set_pqg(dh2.p + 1, nil, dh2.g)
+ assert_not_equal dh2.p, dh1.p
+ end
+
+ # With a key pair
+ dh3 = OpenSSL::PKey.generate_key(Fixtures.pkey("dh2048_ffdhe2048"))
+ dh4 = dh3.dup
+ assert_equal dh3.to_der, dh4.to_der
+ assert_equal dh1.to_der, dh4.to_der # encodes parameters only
+ assert_equal [dh1.p, dh1.g], [dh4.p, dh4.g]
+ assert_not_equal nil, dh3.pub_key
+ assert_not_equal nil, dh3.priv_key
+ assert_equal [dh3.pub_key, dh3.priv_key], [dh4.pub_key, dh4.priv_key]
+ end
+
+ def test_marshal
+ dh = Fixtures.pkey("dh2048_ffdhe2048")
+ deserialized = Marshal.load(Marshal.dump(dh))
+
+ assert_equal dh.to_der, deserialized.to_der
+ end
+
+ private
+
+ def assert_no_key(dh)
+ assert_equal(false, dh.public?)
+ assert_equal(false, dh.private?)
+ assert_equal(nil, dh.pub_key)
+ assert_equal(nil, dh.priv_key)
+ end
+
+ def assert_key(dh)
+ assert_true(dh.public?)
+ assert_true(dh.private?)
+ assert_kind_of(OpenSSL::BN, dh.pub_key)
+ assert_kind_of(OpenSSL::BN, dh.priv_key)
+ end
+
+ def assert_same_dh_params(expected, key)
+ check_component(expected, key, [:p, :q, :g])
+ end
+end
+
+end
diff --git a/test/openssl/test_pkey_dsa.rb b/test/openssl/test_pkey_dsa.rb
new file mode 100644
index 0000000000..1ec0bf0b4d
--- /dev/null
+++ b/test/openssl/test_pkey_dsa.rb
@@ -0,0 +1,261 @@
+# frozen_string_literal: true
+require_relative 'utils'
+
+if defined?(OpenSSL) && defined?(OpenSSL::PKey::DSA)
+
+class OpenSSL::TestPKeyDSA < OpenSSL::PKeyTestCase
+ def setup
+ # May not be available in FIPS mode as DSA has been deprecated in FIPS 186-5
+ omit_on_fips
+ end
+
+ def test_private
+ key = Fixtures.pkey("dsa2048")
+ assert_equal true, key.private?
+ key2 = OpenSSL::PKey::DSA.new(key.to_der)
+ assert_equal true, key2.private?
+ key3 = key.public_key
+ assert_equal false, key3.private?
+ key4 = OpenSSL::PKey::DSA.new(key3.to_der)
+ assert_equal false, key4.private?
+ end
+
+ def test_new
+ key = OpenSSL::PKey::DSA.new(2048)
+ pem = key.public_key.to_pem
+ OpenSSL::PKey::DSA.new pem
+ end
+
+ def test_new_break
+ assert_nil(OpenSSL::PKey::DSA.new(2048) { break })
+ assert_raise(RuntimeError) do
+ OpenSSL::PKey::DSA.new(2048) { raise }
+ end
+ end
+
+ def test_new_empty
+ # pkeys are immutable with OpenSSL >= 3.0
+ if openssl?(3, 0, 0)
+ assert_raise(ArgumentError) { OpenSSL::PKey::DSA.new }
+ else
+ key = OpenSSL::PKey::DSA.new
+ assert_nil(key.p)
+ assert_raise(OpenSSL::PKey::PKeyError) { key.to_der }
+ end
+ end
+
+ def test_generate
+ # DSA.generate used to call DSA_generate_parameters_ex(), which adjusts the
+ # size of q according to the size of p
+ key1024 = OpenSSL::PKey::DSA.generate(1024)
+ assert_predicate key1024, :private?
+ assert_equal 1024, key1024.p.num_bits
+ assert_equal 160, key1024.q.num_bits
+
+ if ENV["OSSL_TEST_ALL"] == "1" # slow
+ key2048 = OpenSSL::PKey::DSA.generate(2048)
+ assert_equal 2048, key2048.p.num_bits
+ assert_equal 256, key2048.q.num_bits
+
+ key3072 = OpenSSL::PKey::DSA.generate(3072)
+ assert_equal 3072, key3072.p.num_bits
+ assert_equal 256, key3072.q.num_bits
+ end
+ end
+
+ def test_sign_verify
+ # The DSA valid size is 2048 or 3072 on FIPS.
+ # https://github.com/openssl/openssl/blob/7649b5548e5c0352b91d9d3ed695e42a2ac1e99c/providers/common/securitycheck.c#L185-L188
+ dsa = Fixtures.pkey("dsa2048")
+ data = "Sign me!"
+ if defined?(OpenSSL::Digest::DSS1)
+ signature = dsa.sign(OpenSSL::Digest.new('DSS1'), data)
+ assert_equal true, dsa.verify(OpenSSL::Digest.new('DSS1'), signature, data)
+ end
+
+ signature = dsa.sign("SHA256", data)
+ assert_equal true, dsa.verify("SHA256", signature, data)
+
+ signature0 = (<<~'end;').unpack1("m")
+ MD4CHQC0zmRkVOAHJTm28fS5PVUv+4LtBeNaKqr/yfmVAh0AsTcLqofWHoW8X5oWu8AOvngOcFVZ
+ cLTvhY3XNw==
+ end;
+ assert_equal true, dsa.verify("SHA256", signature0, data)
+ signature1 = signature0.succ
+ assert_equal false, dsa.verify("SHA256", signature1, data)
+ end
+
+ def test_sign_verify_raw
+ key = Fixtures.pkey("dsa2048")
+ data = 'Sign me!'
+ digest = OpenSSL::Digest.digest('SHA1', data)
+
+ invalid_sig = key.sign_raw(nil, digest.succ)
+ malformed_sig = "*" * invalid_sig.bytesize
+
+ # Sign by #syssign
+ sig = key.syssign(digest)
+ assert_equal true, key.sysverify(digest, sig)
+ assert_equal false, key.sysverify(digest, invalid_sig)
+ assert_sign_verify_false_or_error { key.sysverify(digest, malformed_sig) }
+ assert_equal true, key.verify_raw(nil, sig, digest)
+ assert_equal false, key.verify_raw(nil, invalid_sig, digest)
+ assert_sign_verify_false_or_error { key.verify_raw(nil, malformed_sig, digest) }
+
+ # Sign by #sign_raw
+ sig = key.sign_raw(nil, digest)
+ assert_equal true, key.sysverify(digest, sig)
+ assert_equal false, key.sysverify(digest, invalid_sig)
+ assert_sign_verify_false_or_error { key.sysverify(digest, malformed_sig) }
+ assert_equal true, key.verify_raw(nil, sig, digest)
+ assert_equal false, key.verify_raw(nil, invalid_sig, digest)
+ assert_sign_verify_false_or_error { key.verify_raw(nil, malformed_sig, digest) }
+ end
+
+ def test_DSAPrivateKey
+ # OpenSSL DSAPrivateKey format; similar to RSAPrivateKey
+ orig = Fixtures.pkey("dsa2048")
+ asn1 = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(0),
+ OpenSSL::ASN1::Integer(orig.p),
+ OpenSSL::ASN1::Integer(orig.q),
+ OpenSSL::ASN1::Integer(orig.g),
+ OpenSSL::ASN1::Integer(orig.pub_key),
+ OpenSSL::ASN1::Integer(orig.priv_key)
+ ])
+ key = OpenSSL::PKey::DSA.new(asn1.to_der)
+ assert_predicate key, :private?
+ assert_same_dsa orig, key
+
+ pem = der_to_pem(asn1.to_der, "DSA PRIVATE KEY")
+ key = OpenSSL::PKey::DSA.new(pem)
+ assert_same_dsa orig, key
+
+ assert_equal asn1.to_der, orig.to_der
+ assert_equal pem, orig.export
+ end
+
+ def test_DSAPrivateKey_encrypted
+ # OpenSSL DSAPrivateKey with OpenSSL encryption
+ orig = Fixtures.pkey("dsa2048")
+
+ pem = der_to_encrypted_pem(orig.to_der, "DSA PRIVATE KEY", "abcdef")
+ key = OpenSSL::PKey::DSA.new(pem, "abcdef")
+ assert_same_dsa orig, key
+ key = OpenSSL::PKey::DSA.new(pem) { "abcdef" }
+ assert_same_dsa orig, key
+
+ cipher = OpenSSL::Cipher.new("aes-128-cbc")
+ exported = orig.to_pem(cipher, "abcdef\0\1")
+ assert_same_dsa orig, OpenSSL::PKey::DSA.new(exported, "abcdef\0\1")
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey::DSA.new(exported, "abcdef")
+ }
+ end
+
+ def test_PUBKEY
+ orig = Fixtures.pkey("dsa2048")
+ pub = OpenSSL::PKey::DSA.new(orig.public_to_der)
+
+ asn1 = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::ObjectId("DSA"),
+ OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(orig.p),
+ OpenSSL::ASN1::Integer(orig.q),
+ OpenSSL::ASN1::Integer(orig.g)
+ ])
+ ]),
+ OpenSSL::ASN1::BitString(
+ OpenSSL::ASN1::Integer(orig.pub_key).to_der
+ )
+ ])
+ key = OpenSSL::PKey::DSA.new(asn1.to_der)
+ assert_not_predicate key, :private?
+ assert_same_dsa pub, key
+
+ pem = der_to_pem(asn1.to_der, "PUBLIC KEY")
+ key = OpenSSL::PKey::DSA.new(pem)
+ assert_same_dsa pub, key
+
+ assert_equal asn1.to_der, key.to_der
+ assert_equal pem, key.export
+
+ assert_equal asn1.to_der, orig.public_to_der
+ assert_equal asn1.to_der, key.public_to_der
+ assert_equal pem, orig.public_to_pem
+ assert_equal pem, key.public_to_pem
+ end
+
+ def test_read_DSAPublicKey_pem
+ # TODO: where is the standard? PKey::DSA.new can read only PEM
+ p = 12260055936871293565827712385212529106400444521449663325576634579961635627321079536132296996623400607469624537382977152381984332395192110731059176842635699
+ q = 979494906553787301107832405790107343409973851677
+ g = 3731695366899846297271147240305742456317979984190506040697507048095553842519347835107669437969086119948785140453492839427038591924536131566350847469993845
+ y = 10505239074982761504240823422422813362721498896040719759460296306305851824586095328615844661273887569281276387605297130014564808567159023649684010036304695
+ pem = <<-EOF
+-----BEGIN DSA PUBLIC KEY-----
+MIHfAkEAyJSJ+g+P/knVcgDwwTzC7Pwg/pWs2EMd/r+lYlXhNfzg0biuXRul8VR4
+VUC/phySExY0PdcqItkR/xYAYNMbNwJBAOoV57X0FxKO/PrNa/MkoWzkCKV/hzhE
+p0zbFdsicw+hIjJ7S6Sd/FlDlo89HQZ2FuvWJ6wGLM1j00r39+F2qbMCFQCrkhIX
+SG+is37hz1IaBeEudjB2HQJAR0AloavBvtsng8obsjLb7EKnB+pSeHr/BdIQ3VH7
+fWLOqqkzFeRrYMDzUpl36XktY6Yq8EJYlW9pCMmBVNy/dQ==
+-----END DSA PUBLIC KEY-----
+ EOF
+ key = OpenSSL::PKey::DSA.new(pem)
+ assert(key.public?)
+ assert(!key.private?)
+ assert_equal(p, key.p)
+ assert_equal(q, key.q)
+ assert_equal(g, key.g)
+ assert_equal(y, key.pub_key)
+ assert_equal(nil, key.priv_key)
+ end
+
+ def test_params
+ key = Fixtures.pkey("dsa2048")
+ assert_kind_of(OpenSSL::BN, key.p)
+ assert_equal(key.p, key.params["p"])
+ assert_kind_of(OpenSSL::BN, key.q)
+ assert_equal(key.q, key.params["q"])
+ assert_kind_of(OpenSSL::BN, key.g)
+ assert_equal(key.g, key.params["g"])
+ assert_kind_of(OpenSSL::BN, key.pub_key)
+ assert_equal(key.pub_key, key.params["pub_key"])
+ assert_kind_of(OpenSSL::BN, key.priv_key)
+ assert_equal(key.priv_key, key.params["priv_key"])
+
+ pubkey = OpenSSL::PKey.read(key.public_to_der)
+ assert_equal(key.params["p"], pubkey.params["p"])
+ assert_equal(key.pub_key, pubkey.pub_key)
+ assert_equal(key.pub_key, pubkey.params["pub_key"])
+ assert_nil(pubkey.priv_key)
+ assert_nil(pubkey.params["priv_key"])
+ end
+
+ def test_dup
+ key = Fixtures.pkey("dsa2048")
+ key2 = key.dup
+ assert_equal key.params, key2.params
+
+ # PKey is immutable in OpenSSL >= 3.0
+ if !openssl?(3, 0, 0)
+ key2.set_pqg(key2.p + 1, key2.q, key2.g)
+ assert_not_equal key.params, key2.params
+ end
+ end
+
+ def test_marshal
+ key = Fixtures.pkey("dsa2048")
+ deserialized = Marshal.load(Marshal.dump(key))
+
+ assert_equal key.to_der, deserialized.to_der
+ end
+
+ private
+ def assert_same_dsa(expected, key)
+ check_component(expected, key, [:p, :q, :g, :pub_key, :priv_key])
+ end
+end
+
+end
diff --git a/test/openssl/test_pkey_ec.rb b/test/openssl/test_pkey_ec.rb
new file mode 100644
index 0000000000..ec97a747a3
--- /dev/null
+++ b/test/openssl/test_pkey_ec.rb
@@ -0,0 +1,494 @@
+# frozen_string_literal: true
+require_relative 'utils'
+
+if defined?(OpenSSL)
+
+class OpenSSL::TestEC < OpenSSL::PKeyTestCase
+ def test_ec_key_new
+ key1 = OpenSSL::PKey::EC.generate("prime256v1")
+
+ key3 = OpenSSL::PKey::EC.new(key1)
+ assert_equal key1.to_der, key3.to_der
+
+ key4 = OpenSSL::PKey::EC.new(key1.to_der)
+ assert_equal key1.to_der, key4.to_der
+
+ key5 = key1.dup
+ assert_equal key1.to_der, key5.to_der
+
+ # PKey is immutable in OpenSSL >= 3.0; EC object should not be modified
+ if !openssl?(3, 0, 0)
+ key_tmp = OpenSSL::PKey::EC.generate("prime256v1")
+ key5.private_key = key_tmp.private_key
+ key5.public_key = key_tmp.public_key
+ assert_not_equal key1.to_der, key5.to_der
+ end
+ end
+
+ def test_ec_key_new_empty
+ # pkeys are immutable with OpenSSL >= 3.0; constructing an empty EC object is
+ # disallowed
+ if openssl?(3, 0, 0)
+ assert_raise(ArgumentError) { OpenSSL::PKey::EC.new }
+ else
+ key = OpenSSL::PKey::EC.new
+ assert_nil(key.group)
+
+ p256 = Fixtures.pkey("p256")
+ key.group = p256.group
+ key.private_key = p256.private_key
+ key.public_key = p256.public_key
+ assert_equal(p256.to_der, key.to_der)
+ end
+ end
+
+ def test_builtin_curves
+ builtin_curves = OpenSSL::PKey::EC.builtin_curves
+ assert_not_empty builtin_curves
+ assert_equal 2, builtin_curves[0].size
+ assert_kind_of String, builtin_curves[0][0]
+ assert_kind_of String, builtin_curves[0][1]
+
+ builtin_curve_names = builtin_curves.map { |name, comment| name }
+ assert_include builtin_curve_names, "prime256v1"
+ end
+
+ def test_generate
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey::EC.generate("non-existent")
+ }
+ g = OpenSSL::PKey::EC::Group.new("prime256v1")
+ ec = OpenSSL::PKey::EC.generate(g)
+ assert_equal(true, ec.private?)
+ ec = OpenSSL::PKey::EC.generate("prime256v1")
+ assert_equal(true, ec.private?)
+ end
+
+ def test_generate_key
+ ec = OpenSSL::PKey::EC.new("prime256v1")
+ assert_equal false, ec.private?
+ assert_raise(OpenSSL::PKey::PKeyError) { ec.to_der }
+ ec.generate_key!
+ assert_equal true, ec.private?
+ assert_nothing_raised { ec.to_der }
+ end if !openssl?(3, 0, 0)
+
+ def test_marshal
+ key = Fixtures.pkey("p256")
+ deserialized = Marshal.load(Marshal.dump(key))
+
+ assert_equal key.to_der, deserialized.to_der
+ end
+
+ def test_check_key
+ omit_on_fips
+
+ key0 = Fixtures.pkey("p256")
+ assert_equal(true, key0.check_key)
+ assert_equal(true, key0.private?)
+ assert_equal(true, key0.public?)
+
+ key1 = OpenSSL::PKey.read(key0.public_to_der)
+ assert_equal(true, key1.check_key)
+ assert_equal(false, key1.private?)
+ assert_equal(true, key1.public?)
+
+ key2 = OpenSSL::PKey.read(key0.private_to_der)
+ assert_equal(true, key2.private?)
+ assert_equal(true, key2.public?)
+ assert_equal(true, key2.check_key)
+
+ # Behavior of EVP_PKEY_public_check changes between OpenSSL 1.1.1 and 3.0
+ # The public key does not match the private key
+ ec_key_data = <<~EOF
+ -----BEGIN EC PRIVATE KEY-----
+ MHcCAQEEIP+TT0V8Fndsnacji9tyf6hmhHywcOWTee9XkiBeJoVloAoGCCqGSM49
+ AwEHoUQDQgAEBkhhJIU/2/YdPSlY2I1k25xjK4trr5OXSgXvBC21PtY0HQ7lor7A
+ jzT0giJITqmcd81fwGw5+96zLcdxTF1hVQ==
+ -----END EC PRIVATE KEY-----
+ EOF
+ if aws_lc? # AWS-LC automatically does key checks on the parsed key.
+ assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(ec_key_data) }
+ else
+ key4 = OpenSSL::PKey.read(ec_key_data)
+ assert_raise(OpenSSL::PKey::PKeyError) { key4.check_key }
+ end
+
+ # EC#private_key= is deprecated in 3.0 and won't work on OpenSSL 3.0
+ if !openssl?(3, 0, 0)
+ key2.private_key += 1
+ assert_raise(OpenSSL::PKey::PKeyError) { key2.check_key }
+ end
+ end
+
+ def test_sign_verify
+ p256 = Fixtures.pkey("p256")
+ data = "Sign me!"
+ signature = p256.sign("SHA256", data)
+ assert_equal true, p256.verify("SHA256", signature, data)
+
+ signature0 = (<<~'end;').unpack1("m")
+ MEQCIEOTY/hD7eI8a0qlzxkIt8LLZ8uwiaSfVbjX2dPAvN11AiAQdCYx56Fq
+ QdBp1B4sxJoA8jvODMMklMyBKVmudboA6A==
+ end;
+ assert_equal true, p256.verify("SHA256", signature0, data)
+ signature1 = signature0.succ
+ assert_equal false, p256.verify("SHA256", signature1, data)
+ end
+
+ def test_derive_key
+ # NIST CAVP, KAS_ECC_CDH_PrimitiveTest.txt, P-256 COUNT = 0
+ qCAVSx = "700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287"
+ qCAVSy = "db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac"
+ dIUT = "7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534"
+ zIUT = "46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b"
+ a = OpenSSL::PKey::EC.new("prime256v1")
+ a.private_key = OpenSSL::BN.new(dIUT, 16)
+ b = OpenSSL::PKey::EC.new("prime256v1")
+ uncompressed = OpenSSL::BN.new("04" + qCAVSx + qCAVSy, 16)
+ b.public_key = OpenSSL::PKey::EC::Point.new(b.group, uncompressed)
+ assert_equal [zIUT].pack("H*"), a.derive(b)
+
+ assert_equal a.derive(b), a.dh_compute_key(b.public_key)
+ end if !openssl?(3, 0, 0) # TODO: Test it without using #private_key=
+
+ def test_sign_verify_raw
+ key = Fixtures.pkey("p256")
+ data1 = "foo"
+ data2 = "bar"
+
+ malformed_sig = "*" * 30
+
+ # Sign by #dsa_sign_asn1
+ sig = key.dsa_sign_asn1(data1)
+ assert_equal true, key.dsa_verify_asn1(data1, sig)
+ assert_equal false, key.dsa_verify_asn1(data2, sig)
+ assert_sign_verify_false_or_error { key.dsa_verify_asn1(data1, malformed_sig) }
+ assert_equal true, key.verify_raw(nil, sig, data1)
+ assert_equal false, key.verify_raw(nil, sig, data2)
+ assert_sign_verify_false_or_error { key.verify_raw(nil, malformed_sig, data1) }
+
+ # Sign by #sign_raw
+ sig = key.sign_raw(nil, data1)
+ assert_equal true, key.dsa_verify_asn1(data1, sig)
+ assert_equal false, key.dsa_verify_asn1(data2, sig)
+ assert_sign_verify_false_or_error { key.dsa_verify_asn1(data1, malformed_sig) }
+ assert_equal true, key.verify_raw(nil, sig, data1)
+ assert_equal false, key.verify_raw(nil, sig, data2)
+ assert_sign_verify_false_or_error{ key.verify_raw(nil, malformed_sig, data1) }
+ end
+
+ def test_dsa_sign_asn1_FIPS186_3
+ key = OpenSSL::PKey::EC.generate("prime256v1")
+ size = key.group.order.num_bits / 8 + 1
+ dgst = (1..size).to_a.pack('C*')
+ sig = key.dsa_sign_asn1(dgst)
+ # dgst is auto-truncated according to FIPS186-3 after openssl-0.9.8m
+ assert(key.dsa_verify_asn1(dgst + "garbage", sig))
+ end
+
+ def test_dh_compute_key
+ key_a = OpenSSL::PKey::EC.generate("prime256v1")
+ key_b = OpenSSL::PKey::EC.generate(key_a.group)
+
+ pub_a = key_a.public_key
+ pub_b = key_b.public_key
+ a = key_a.dh_compute_key(pub_b)
+ b = key_b.dh_compute_key(pub_a)
+ assert_equal a, b
+ end
+
+ def test_ECPrivateKey
+ p256 = Fixtures.pkey("p256")
+ asn1 = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(1),
+ OpenSSL::ASN1::OctetString(p256.private_key.to_s(2)),
+ OpenSSL::ASN1::ObjectId("prime256v1", 0, :EXPLICIT),
+ OpenSSL::ASN1::BitString(p256.public_key.to_octet_string(:uncompressed),
+ 1, :EXPLICIT)
+ ])
+ key = OpenSSL::PKey::EC.new(asn1.to_der)
+ assert_predicate key, :private?
+ assert_same_ec p256, key
+
+ pem = <<~EOF
+ -----BEGIN EC PRIVATE KEY-----
+ MHcCAQEEIID49FDqcf1O1eO8saTgG70UbXQw9Fqwseliit2aWhH1oAoGCCqGSM49
+ AwEHoUQDQgAEFglk2c+oVUIKQ64eZG9bhLNPWB7lSZ/ArK41eGy5wAzU/0G51Xtt
+ CeBUl+MahZtn9fO1JKdF4qJmS39dXnpENg==
+ -----END EC PRIVATE KEY-----
+ EOF
+ key = OpenSSL::PKey::EC.new(pem)
+ assert_same_ec p256, key
+
+ assert_equal asn1.to_der, p256.to_der
+ assert_equal pem, p256.export
+ end
+
+ def test_ECPrivateKey_with_parameters
+ p256 = Fixtures.pkey("p256")
+
+ # The format used by "openssl ecparam -name prime256v1 -genkey -outform PEM"
+ #
+ # "EC PARAMETERS" block should be ignored if it is followed by an
+ # "EC PRIVATE KEY" block
+ in_pem = <<~EOF
+ -----BEGIN EC PARAMETERS-----
+ BggqhkjOPQMBBw==
+ -----END EC PARAMETERS-----
+ -----BEGIN EC PRIVATE KEY-----
+ MHcCAQEEIID49FDqcf1O1eO8saTgG70UbXQw9Fqwseliit2aWhH1oAoGCCqGSM49
+ AwEHoUQDQgAEFglk2c+oVUIKQ64eZG9bhLNPWB7lSZ/ArK41eGy5wAzU/0G51Xtt
+ CeBUl+MahZtn9fO1JKdF4qJmS39dXnpENg==
+ -----END EC PRIVATE KEY-----
+ EOF
+
+ key = OpenSSL::PKey::EC.new(in_pem)
+ assert_same_ec p256, key
+ assert_equal p256.to_der, key.to_der
+ end
+
+ def test_ECPrivateKey_encrypted
+ omit_on_fips
+
+ p256 = Fixtures.pkey("p256")
+ # key = abcdef
+ pem = <<~EOF
+ -----BEGIN EC PRIVATE KEY-----
+ Proc-Type: 4,ENCRYPTED
+ DEK-Info: AES-128-CBC,85743EB6FAC9EA76BF99D9328AFD1A66
+
+ nhsP1NHxb53aeZdzUe9umKKyr+OIwQq67eP0ONM6E1vFTIcjkDcFLR6PhPFufF4m
+ y7E2HF+9uT1KPQhlE+D63i1m1Mvez6PWfNM34iOQp2vEhaoHHKlR3c43lLyzaZDI
+ 0/dGSU5SzFG+iT9iFXCwCvv+bxyegkBOyALFje1NAsM=
+ -----END EC PRIVATE KEY-----
+ EOF
+ key = OpenSSL::PKey::EC.new(pem, "abcdef")
+ assert_same_ec p256, key
+ key = OpenSSL::PKey::EC.new(pem) { "abcdef" }
+ assert_same_ec p256, key
+
+ cipher = OpenSSL::Cipher.new("aes-128-cbc")
+ exported = p256.to_pem(cipher, "abcdef\0\1")
+ assert_same_ec p256, OpenSSL::PKey::EC.new(exported, "abcdef\0\1")
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey::EC.new(exported, "abcdef")
+ }
+ end
+
+ def test_PUBKEY
+ p256 = Fixtures.pkey("p256")
+ p256pub = OpenSSL::PKey::EC.new(p256.public_to_der)
+
+ asn1 = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::ObjectId("id-ecPublicKey"),
+ OpenSSL::ASN1::ObjectId("prime256v1")
+ ]),
+ OpenSSL::ASN1::BitString(
+ p256.public_key.to_octet_string(:uncompressed)
+ )
+ ])
+ key = OpenSSL::PKey::EC.new(asn1.to_der)
+ assert_not_predicate key, :private?
+ assert_same_ec p256pub, key
+
+ pem = <<~EOF
+ -----BEGIN PUBLIC KEY-----
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFglk2c+oVUIKQ64eZG9bhLNPWB7l
+ SZ/ArK41eGy5wAzU/0G51XttCeBUl+MahZtn9fO1JKdF4qJmS39dXnpENg==
+ -----END PUBLIC KEY-----
+ EOF
+ key = OpenSSL::PKey::EC.new(pem)
+ assert_same_ec p256pub, key
+
+ assert_equal asn1.to_der, key.to_der
+ assert_equal pem, key.export
+
+ assert_equal asn1.to_der, p256.public_to_der
+ assert_equal asn1.to_der, key.public_to_der
+ assert_equal pem, p256.public_to_pem
+ assert_equal pem, key.public_to_pem
+ end
+
+ def test_ec_group
+ group1 = OpenSSL::PKey::EC::Group.new("prime256v1")
+ key1 = OpenSSL::PKey::EC.new(group1)
+ assert_equal group1, key1.group
+
+ group2 = OpenSSL::PKey::EC::Group.new(group1)
+ assert_equal group1.to_der, group2.to_der
+ assert_equal group1, group2
+ group2.asn1_flag ^=OpenSSL::PKey::EC::NAMED_CURVE
+ # AWS-LC does not support serializing explicit curves.
+ unless aws_lc?
+ assert_not_equal group1.to_der, group2.to_der
+ end
+ assert_equal group1, group2
+
+ group3 = group1.dup
+ assert_equal group1.to_der, group3.to_der
+
+ assert group1.asn1_flag & OpenSSL::PKey::EC::NAMED_CURVE # our default
+ der = group1.to_der
+ group4 = OpenSSL::PKey::EC::Group.new(der)
+ group1.point_conversion_form = group4.point_conversion_form = :uncompressed
+ assert_equal :uncompressed, group1.point_conversion_form
+ assert_equal :uncompressed, group4.point_conversion_form
+ assert_equal group1, group4
+ assert_equal group1.curve_name, group4.curve_name
+ assert_equal group1.generator.to_octet_string(:uncompressed),
+ group4.generator.to_octet_string(:uncompressed)
+ assert_equal group1.order, group4.order
+ assert_equal group1.cofactor, group4.cofactor
+ assert_equal group1.seed, group4.seed
+ assert_equal group1.degree, group4.degree
+ end
+
+ def test_ec_group_initialize_error_message
+ # Test that passing 2 arguments raises the helpful error
+ e = assert_raise(ArgumentError) do
+ OpenSSL::PKey::EC::Group.new(:GFp, 123)
+ end
+
+ assert_equal("wrong number of arguments (given 2, expected 1 or 4)", e.message)
+ end
+
+ def test_ec_point
+ group = OpenSSL::PKey::EC::Group.new("prime256v1")
+ key = OpenSSL::PKey::EC.generate(group)
+ point = key.public_key
+
+ point2 = OpenSSL::PKey::EC::Point.new(group, point.to_bn)
+ assert_equal point, point2
+ assert_equal point.to_bn, point2.to_bn
+ assert_equal point.to_octet_string(:uncompressed),
+ point2.to_octet_string(:uncompressed)
+
+ point3 = OpenSSL::PKey::EC::Point.new(group,
+ point.to_octet_string(:uncompressed))
+ assert_equal point, point3
+ assert_equal point.to_bn, point3.to_bn
+ assert_equal point.to_octet_string(:uncompressed),
+ point3.to_octet_string(:uncompressed)
+
+ point2.invert!
+ point3.invert!
+ assert_not_equal point.to_octet_string(:uncompressed),
+ point2.to_octet_string(:uncompressed)
+ assert_equal point2.to_octet_string(:uncompressed),
+ point3.to_octet_string(:uncompressed)
+ end
+
+ def test_small_curve
+ begin
+ group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2)
+ group.point_conversion_form = :uncompressed
+ generator = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 05 01 }))
+ group.set_generator(generator, 19, 1)
+ rescue OpenSSL::PKey::EC::Group::Error
+ pend "Patched OpenSSL rejected curve" if /unsupported field/ =~ $!.message
+ raise
+ end
+ assert_equal 17.to_bn.num_bits, group.degree
+ assert_equal B(%w{ 04 05 01 }),
+ group.generator.to_octet_string(:uncompressed)
+ assert_equal 19.to_bn, group.order
+ assert_equal 1.to_bn, group.cofactor
+ assert_nil group.curve_name
+
+ point = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 }))
+ assert_equal 0x040603.to_bn, point.to_bn
+ assert_equal 0x040603.to_bn, point.to_bn(:uncompressed)
+ assert_equal 0x0306.to_bn, point.to_bn(:compressed)
+ assert_equal 0x070603.to_bn, point.to_bn(:hybrid)
+
+ group2 = group.dup; group2.point_conversion_form = :compressed
+ point2 = OpenSSL::PKey::EC::Point.new(group2, B(%w{ 04 06 03 }))
+ assert_equal 0x0306.to_bn, point2.to_bn
+
+ assert_equal B(%w{ 04 06 03 }), point.to_octet_string(:uncompressed)
+ assert_equal B(%w{ 03 06 }), point.to_octet_string(:compressed)
+ assert_equal B(%w{ 07 06 03 }), point.to_octet_string(:hybrid)
+
+ assert_equal true, point.on_curve?
+ point.invert! # 8.5
+ assert_equal B(%w{ 04 06 0E }), point.to_octet_string(:uncompressed)
+ assert_equal true, point.on_curve?
+
+ assert_equal false, point.infinity?
+ point.set_to_infinity!
+ assert_equal true, point.infinity?
+ assert_equal 0.to_bn, point.to_bn
+ assert_equal B(%w{ 00 }), point.to_octet_string(:uncompressed)
+ assert_equal true, point.on_curve?
+ end
+
+ def test_ec_point_add
+ begin
+ group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2)
+ group.point_conversion_form = :uncompressed
+ gen = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 05 01 }))
+ group.set_generator(gen, 19, 1)
+
+ point_a = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 }))
+ point_b = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 10 0D }))
+ rescue OpenSSL::PKey::EC::Group::Error
+ pend "Patched OpenSSL rejected curve" if /unsupported field/ =~ $!.message
+ raise
+ end
+
+ result = point_a.add(point_b)
+ assert_equal B(%w{ 04 0D 07 }), result.to_octet_string(:uncompressed)
+
+ assert_raise(TypeError) { point_a.add(nil) }
+ assert_raise(ArgumentError) { point_a.add }
+ end
+
+ def test_ec_point_mul
+ begin
+ # y^2 = x^3 + 2x + 2 over F_17
+ # generator is (5, 1)
+ group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2)
+ group.point_conversion_form = :uncompressed
+ gen = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 05 01 }))
+ group.set_generator(gen, 19, 1)
+
+ # 3 * (6, 3) = (16, 13)
+ point_a = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 }))
+ result_a1 = point_a.mul(3)
+ assert_equal B(%w{ 04 10 0D }), result_a1.to_octet_string(:uncompressed)
+ # 3 * (6, 3) + 3 * (5, 1) = (7, 6)
+ result_a2 = point_a.mul(3, 3)
+ assert_equal B(%w{ 04 07 06 }), result_a2.to_octet_string(:uncompressed)
+ rescue OpenSSL::PKey::EC::Group::Error
+ # CentOS patches OpenSSL to reject curves defined over Fp where p < 256 bits
+ raise if $!.message !~ /unsupported field/
+ end
+
+ p256_key = Fixtures.pkey("p256")
+ p256_g = p256_key.group
+ assert_equal(p256_key.public_key, p256_g.generator.mul(p256_key.private_key))
+
+ # invalid argument
+ point = p256_key.public_key
+ assert_raise(TypeError) { point.mul(nil) }
+
+ # mul with arrays was removed in version 4.0.0
+ assert_raise(NotImplementedError) { point.mul([1], []) }
+ end
+
+# test Group: asn1_flag, point_conversion
+
+ private
+
+ def B(ary)
+ [Array(ary).join].pack("H*")
+ end
+
+ def assert_same_ec(expected, key)
+ check_component(expected, key, [:group, :public_key, :private_key])
+ end
+end
+
+end
diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb
index 401cb6c3e0..1716aef380 100644
--- a/test/openssl/test_pkey_rsa.rb
+++ b/test/openssl/test_pkey_rsa.rb
@@ -1,24 +1,173 @@
-begin
- require "openssl"
- require File.join(File.dirname(__FILE__), "utils.rb")
-rescue LoadError
-end
-require 'test/unit'
+# frozen_string_literal: true
+require_relative "utils"
if defined?(OpenSSL)
-class OpenSSL::TestPKeyRSA < Test::Unit::TestCase
- def test_padding
+class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
+ def test_no_private_exp
+ key = OpenSSL::PKey::RSA.new
+ rsa = Fixtures.pkey("rsa-1")
+ key.set_key(rsa.n, rsa.e, nil)
+ key.set_factors(rsa.p, rsa.q)
+ assert_raise(OpenSSL::PKey::PKeyError){ key.private_encrypt("foo") }
+ assert_raise(OpenSSL::PKey::PKeyError){ key.private_decrypt("foo") }
+ end if !openssl?(3, 0, 0) # Impossible state in OpenSSL 3.0
+
+ def test_private
+ key = Fixtures.pkey("rsa-1")
+
+ # Generated by DER
+ key2 = OpenSSL::PKey::RSA.new(key.to_der)
+ assert_true(key2.private?)
+
+ # public key
+ key3 = key.public_key
+ assert_false(key3.private?)
+
+ # Generated by public key DER
+ key4 = OpenSSL::PKey::RSA.new(key3.to_der)
+ assert_false(key4.private?)
+
+ if !openssl?(3, 0, 0)
+ # Generated by RSA#set_key
+ key5 = OpenSSL::PKey::RSA.new
+ key5.set_key(key.n, key.e, key.d)
+ assert_true(key5.private?)
+
+ # Generated by RSA#set_key, without d
+ key6 = OpenSSL::PKey::RSA.new
+ key6.set_key(key.n, key.e, nil)
+ assert_false(key6.private?)
+ end
+ end
+
+ def test_new
+ key = OpenSSL::PKey::RSA.new(2048)
+ assert_equal 2048, key.n.num_bits
+ assert_equal 65537, key.e
+ assert_not_nil key.d
+ assert(key.private?)
+ end
+
+ def test_new_public_exponent
+ # At least 2024-bits RSA key are required in FIPS.
+ omit_on_fips
+
+ # Specify public exponent
key = OpenSSL::PKey::RSA.new(512, 3)
+ assert_equal 512, key.n.num_bits
+ assert_equal 3, key.e
+ end
+
+ def test_new_empty
+ # pkeys are immutable with OpenSSL >= 3.0
+ if openssl?(3, 0, 0)
+ assert_raise(ArgumentError) { OpenSSL::PKey::RSA.new }
+ else
+ key = OpenSSL::PKey::RSA.new
+ assert_nil(key.n)
+ end
+ end
+
+ def test_s_generate
+ key1 = OpenSSL::PKey::RSA.generate(2048)
+ assert_equal 2048, key1.n.num_bits
+ assert_equal 65537, key1.e
+ end
+
+ def test_s_generate_public_exponent
+ # At least 2024-bits RSA key are required in FIPS.
+ omit_on_fips
+
+ # Specify public exponent
+ key = OpenSSL::PKey::RSA.generate(512, 3)
+ assert_equal 512, key.n.num_bits
+ assert_equal 3, key.e
+ end
+
+ def test_new_break
+ assert_nil(OpenSSL::PKey::RSA.new(2048) { break })
+ assert_raise(RuntimeError) do
+ OpenSSL::PKey::RSA.new(2048) { raise }
+ end
+ end
+
+ def test_sign_verify
+ rsa = Fixtures.pkey("rsa2048")
+ data = "Sign me!"
+ signature = rsa.sign("SHA256", data)
+ assert_equal true, rsa.verify("SHA256", signature, data)
+
+ signature0 = (<<~'end;').unpack1("m")
+ ooy49i8aeFtkDYUU0RPDsEugGiNw4lZxpbQPnIwtdftEkka945IqKZ/MY3YSw7wKsvBZeaTy8GqL
+ lSWLThsRFDV+UUS9zUBbQ9ygNIT8OjdV+tNL63ZpKGprczSnw4F05MQIpajNRud/8jiI9rf+Wysi
+ WwXecjMl2FlXlLJHY4PFQZU5TiametB4VCQRMcjLo1uf26u/yRpiGaYyqn5vxs0SqNtUDM1UL6x4
+ NHCAdqLjuFRQPjYp1vGLD3eSl4061pS8x1NVap3YGbYfGUyzZO4VfwFwf1jPdhp/OX/uZw4dGB2H
+ gSK+q1JiDFwEE6yym5tdKovL1g1NhFYHF6gkZg==
+ end;
+ assert_equal true, rsa.verify("SHA256", signature0, data)
+ signature1 = signature0.succ
+ assert_equal false, rsa.verify("SHA256", signature1, data)
+ end
+
+ def test_sign_verify_options
+ key = Fixtures.pkey("rsa2048")
+ data = "Sign me!"
+ pssopts = {
+ "rsa_padding_mode" => "pss",
+ "rsa_pss_saltlen" => 20,
+ "rsa_mgf1_md" => "SHA256"
+ }
+ sig_pss = key.sign("SHA256", data, pssopts)
+ assert_equal 256, sig_pss.bytesize
+ assert_equal true, key.verify("SHA256", sig_pss, data, pssopts)
+ assert_equal true, key.verify_pss("SHA256", sig_pss, data,
+ salt_length: 20, mgf1_hash: "SHA256")
+ # Defaults to PKCS #1 v1.5 padding => verification failure
+ assert_equal false, key.verify("SHA256", sig_pss, data)
+
+ # option type check
+ assert_raise_with_message(TypeError, /expected Hash/) {
+ key.sign("SHA256", data, ["x"])
+ }
+ end
+
+ def test_sign_verify_raw
+ key = Fixtures.pkey("rsa-1")
+ data = "Sign me!"
+ hash = OpenSSL::Digest.digest("SHA256", data)
+ signature = key.sign_raw("SHA256", hash)
+ assert_equal true, key.verify_raw("SHA256", signature, hash)
+ assert_equal true, key.verify("SHA256", signature, data)
+
+ # Too long data
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ key.sign_raw("SHA1", "x" * (key.n.num_bytes + 1))
+ }
+
+ # With options
+ pssopts = {
+ "rsa_padding_mode" => "pss",
+ "rsa_pss_saltlen" => 20,
+ "rsa_mgf1_md" => "SHA256"
+ }
+ sig_pss = key.sign_raw("SHA256", hash, pssopts)
+ assert_equal true, key.verify("SHA256", sig_pss, data, pssopts)
+ assert_equal true, key.verify_raw("SHA256", sig_pss, hash, pssopts)
+ end
+
+ def test_sign_verify_raw_legacy
+ key = Fixtures.pkey("rsa-1")
+ bits = key.n.num_bits
# Need right size for raw mode
- plain0 = "x" * (512/8)
+ plain0 = "x" * (bits/8)
cipher = key.private_encrypt(plain0, OpenSSL::PKey::RSA::NO_PADDING)
plain1 = key.public_decrypt(cipher, OpenSSL::PKey::RSA::NO_PADDING)
assert_equal(plain0, plain1)
# Need smaller size for pkcs1 mode
- plain0 = "x" * (512/8 - 11)
+ plain0 = "x" * (bits/8 - 11)
cipher1 = key.private_encrypt(plain0, OpenSSL::PKey::RSA::PKCS1_PADDING)
plain1 = key.public_decrypt(cipher1, OpenSSL::PKey::RSA::PKCS1_PADDING)
assert_equal(plain0, plain1)
@@ -31,18 +180,380 @@ class OpenSSL::TestPKeyRSA < Test::Unit::TestCase
# Failure cases
assert_raise(ArgumentError){ key.private_encrypt() }
assert_raise(ArgumentError){ key.private_encrypt("hi", 1, nil) }
- assert_raise(OpenSSL::PKey::RSAError){ key.private_encrypt(plain0, 666) }
+ assert_raise(OpenSSL::PKey::PKeyError){ key.private_encrypt(plain0, 666) }
end
- def test_private
- key = OpenSSL::PKey::RSA.new(512, 3)
- assert(key.private?)
- key2 = OpenSSL::PKey::RSA.new(key.to_der)
- assert(key2.private?)
- key3 = key.public_key
- assert(!key3.private?)
- key4 = OpenSSL::PKey::RSA.new(key3.to_der)
- assert(!key4.private?)
+
+ def test_verify_empty_rsa
+ rsa = OpenSSL::PKey::RSA.new
+ assert_raise(OpenSSL::PKey::PKeyError, "[Bug #12783]") {
+ rsa.verify("SHA1", "a", "b")
+ }
+ end unless openssl?(3, 0, 0) # Empty RSA is not possible with OpenSSL >= 3.0
+
+ def test_sign_verify_pss
+ key = Fixtures.pkey("rsa2048")
+ data = "Sign me!"
+ invalid_data = "Sign me?"
+
+ signature = key.sign_pss("SHA256", data, salt_length: 20, mgf1_hash: "SHA256")
+ assert_equal 256, signature.bytesize
+ assert_equal true,
+ key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA256")
+ assert_equal true,
+ key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA256")
+ assert_equal false,
+ key.verify_pss("SHA256", signature, invalid_data, salt_length: 20, mgf1_hash: "SHA256")
+
+ signature = key.sign_pss("SHA256", data, salt_length: :digest, mgf1_hash: "SHA256")
+ assert_equal true,
+ key.verify_pss("SHA256", signature, data, salt_length: 32, mgf1_hash: "SHA256")
+ assert_equal true,
+ key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA256")
+ assert_equal false,
+ key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA256")
+
+ # The sign_pss with `salt_length: :max` raises the "invalid salt length"
+ # error in FIPS. We need to skip the tests in FIPS.
+ # According to FIPS 186-5 section 5.4, the salt length shall be between zero
+ # and the output block length of the digest function (inclusive).
+ #
+ # FIPS 186-5 section 5.4 PKCS #1
+ # https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf
+ unless OpenSSL.fips_mode
+ signature = key.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA256")
+ # Should verify on the following salt_length (sLen).
+ # sLen <= emLen (octat) - 2 - hLen (octet) = 2048 / 8 - 2 - 256 / 8 = 222
+ # https://datatracker.ietf.org/doc/html/rfc8017#section-9.1.1
+ assert_equal true,
+ key.verify_pss("SHA256", signature, data, salt_length: 222, mgf1_hash: "SHA256")
+ assert_equal true,
+ key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA256")
+ end
+
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ key.sign_pss("SHA256", data, salt_length: 223, mgf1_hash: "SHA256")
+ }
+ end
+
+ def test_encrypt_decrypt
+ rsapriv = Fixtures.pkey("rsa-1")
+ rsapub = OpenSSL::PKey.read(rsapriv.public_to_der)
+
+ # Defaults to PKCS #1 v1.5
+ raw = "data"
+ # According to the NIST SP 800-131A Rev. 2 section 6, PKCS#1 v1.5 padding is
+ # not permitted for key agreement and key transport using RSA in FIPS.
+ # https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar2.pdf
+ unless OpenSSL.fips_mode
+ enc = rsapub.encrypt(raw)
+ assert_equal raw, rsapriv.decrypt(enc)
+ end
+
+ # Invalid options
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ rsapub.encrypt(raw, { "nonexistent" => "option" })
+ }
+ end
+
+ def test_encrypt_decrypt_legacy
+ rsapriv = Fixtures.pkey("rsa-1")
+ rsapub = OpenSSL::PKey.read(rsapriv.public_to_der)
+
+ # Defaults to PKCS #1 v1.5
+ unless OpenSSL.fips_mode
+ raw = "data"
+ enc_legacy = rsapub.public_encrypt(raw)
+ assert_equal raw, rsapriv.decrypt(enc_legacy)
+ enc_new = rsapub.encrypt(raw)
+ assert_equal raw, rsapriv.private_decrypt(enc_new)
+ end
+
+ # OAEP with default parameters
+ raw = "data"
+ enc_legacy = rsapub.public_encrypt(raw, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
+ assert_equal raw, rsapriv.decrypt(enc_legacy, { "rsa_padding_mode" => "oaep" })
+ enc_new = rsapub.encrypt(raw, { "rsa_padding_mode" => "oaep" })
+ assert_equal raw, rsapriv.private_decrypt(enc_legacy, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
+ end
+
+ def test_export
+ orig = Fixtures.pkey("rsa-1")
+
+ pub = OpenSSL::PKey.read(orig.public_to_der)
+ assert_not_equal orig.export, pub.export
+ assert_equal orig.public_to_pem, pub.export
+
+ # PKey is immutable in OpenSSL >= 3.0
+ if !openssl?(3, 0, 0)
+ key = OpenSSL::PKey::RSA.new
+
+ # key has only n, e and d
+ key.set_key(orig.n, orig.e, orig.d)
+ assert_equal orig.public_key.export, key.export
+
+ # key has only n, e, d, p and q
+ key.set_factors(orig.p, orig.q)
+ assert_equal orig.public_key.export, key.export
+
+ # key has n, e, d, p, q, dmp1, dmq1 and iqmp
+ key.set_crt_params(orig.dmp1, orig.dmq1, orig.iqmp)
+ assert_equal orig.export, key.export
+ end
+ end
+
+ def test_to_der
+ orig = Fixtures.pkey("rsa-1")
+
+ pub = OpenSSL::PKey.read(orig.public_to_der)
+ assert_not_equal orig.to_der, pub.to_der
+ assert_equal orig.public_to_der, pub.to_der
+
+ # PKey is immutable in OpenSSL >= 3.0
+ if !openssl?(3, 0, 0)
+ key = OpenSSL::PKey::RSA.new
+
+ # key has only n, e and d
+ key.set_key(orig.n, orig.e, orig.d)
+ assert_equal orig.public_key.to_der, key.to_der
+
+ # key has only n, e, d, p and q
+ key.set_factors(orig.p, orig.q)
+ assert_equal orig.public_key.to_der, key.to_der
+
+ # key has n, e, d, p, q, dmp1, dmq1 and iqmp
+ key.set_crt_params(orig.dmp1, orig.dmq1, orig.iqmp)
+ assert_equal orig.to_der, key.to_der
+ end
+ end
+
+ def test_RSAPrivateKey
+ rsa = Fixtures.pkey("rsa-1")
+ asn1 = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(0),
+ OpenSSL::ASN1::Integer(rsa.n),
+ OpenSSL::ASN1::Integer(rsa.e),
+ OpenSSL::ASN1::Integer(rsa.d),
+ OpenSSL::ASN1::Integer(rsa.p),
+ OpenSSL::ASN1::Integer(rsa.q),
+ OpenSSL::ASN1::Integer(rsa.dmp1),
+ OpenSSL::ASN1::Integer(rsa.dmq1),
+ OpenSSL::ASN1::Integer(rsa.iqmp)
+ ])
+ key = OpenSSL::PKey::RSA.new(asn1.to_der)
+ assert_predicate key, :private?
+ assert_same_rsa rsa, key
+
+ pem = der_to_pem(asn1.to_der, "RSA PRIVATE KEY")
+ key = OpenSSL::PKey::RSA.new(pem)
+ assert_same_rsa rsa, key
+
+ assert_equal asn1.to_der, rsa.to_der
+ assert_equal pem, rsa.export
+
+ # Unknown PEM prepended
+ cert = issue_cert(OpenSSL::X509::Name.new([["CN", "nobody"]]), rsa, 1, [], nil, nil)
+ str = cert.to_text + cert.to_pem + rsa.to_pem
+ key = OpenSSL::PKey::RSA.new(str)
+ assert_same_rsa rsa, key
+ end
+
+ def test_RSAPrivateKey_encrypted
+ # PKCS #1 RSAPrivateKey with OpenSSL encryption
+ omit_on_fips
+
+ rsa = Fixtures.pkey("rsa2048")
+
+ pem = der_to_encrypted_pem(rsa.to_der, "RSA PRIVATE KEY", "abcdef")
+ key = OpenSSL::PKey::RSA.new(pem, "abcdef")
+ assert_same_rsa rsa, key
+ key = OpenSSL::PKey::RSA.new(pem) { "abcdef" }
+ assert_same_rsa rsa, key
+
+ cipher = OpenSSL::Cipher.new("aes-128-cbc")
+ exported = rsa.to_pem(cipher, "abcdef\0\1")
+ assert_same_rsa rsa, OpenSSL::PKey::RSA.new(exported, "abcdef\0\1")
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey::RSA.new(exported, "abcdef")
+ }
+ end
+
+ def test_RSAPublicKey
+ # PKCS #1 RSAPublicKey. Only decoding is supported
+ orig = Fixtures.pkey("rsa-1")
+ pub = OpenSSL::PKey::RSA.new(orig.public_to_der)
+
+ asn1 = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(orig.n),
+ OpenSSL::ASN1::Integer(orig.e)
+ ])
+ key = OpenSSL::PKey::RSA.new(asn1.to_der)
+ assert_not_predicate key, :private?
+ assert_same_rsa pub, key
+
+ pem = der_to_pem(asn1.to_der, "RSA PUBLIC KEY")
+ key = OpenSSL::PKey::RSA.new(pem)
+ assert_same_rsa pub, key
+ end
+
+ def test_PUBKEY
+ orig = Fixtures.pkey("rsa-1")
+ pub = OpenSSL::PKey::RSA.new(orig.public_to_der)
+
+ asn1 = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::ObjectId("rsaEncryption"),
+ OpenSSL::ASN1::Null(nil)
+ ]),
+ OpenSSL::ASN1::BitString(
+ OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(orig.n),
+ OpenSSL::ASN1::Integer(orig.e)
+ ]).to_der
+ )
+ ])
+ key = OpenSSL::PKey::RSA.new(asn1.to_der)
+ assert_not_predicate key, :private?
+ assert_same_rsa pub, key
+
+ pem = der_to_pem(asn1.to_der, "PUBLIC KEY")
+ key = OpenSSL::PKey::RSA.new(pem)
+ assert_same_rsa pub, key
+
+ assert_equal asn1.to_der, key.to_der
+ assert_equal pem, key.export
+
+ assert_equal asn1.to_der, orig.public_to_der
+ assert_equal asn1.to_der, key.public_to_der
+ assert_equal pem, orig.public_to_pem
+ assert_equal pem, key.public_to_pem
+ end
+
+ def test_pem_passwd
+ omit_on_fips
+
+ key = Fixtures.pkey("rsa-1")
+ pem3c = key.to_pem("aes-128-cbc", "key")
+ assert_match (/ENCRYPTED/), pem3c
+ assert_equal key.to_der, OpenSSL::PKey.read(pem3c, "key").to_der
+ assert_equal key.to_der, OpenSSL::PKey.read(pem3c) { "key" }.to_der
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey.read(pem3c) { nil }
+ }
+ end
+
+ def test_private_encoding
+ pkey = Fixtures.pkey("rsa-1")
+ asn1 = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(0),
+ OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::ObjectId("rsaEncryption"),
+ OpenSSL::ASN1::Null(nil)
+ ]),
+ OpenSSL::ASN1::OctetString(pkey.to_der)
+ ])
+ assert_equal asn1.to_der, pkey.private_to_der
+ assert_same_rsa pkey, OpenSSL::PKey.read(asn1.to_der)
+
+ pem = der_to_pem(asn1.to_der, "PRIVATE KEY")
+ assert_equal pem, pkey.private_to_pem
+ assert_same_rsa pkey, OpenSSL::PKey.read(pem)
+ end
+
+ def test_private_encoding_encrypted
+ rsa = Fixtures.pkey("rsa2048")
+ encoded = rsa.private_to_der("aes-128-cbc", "abcdefgh")
+ asn1 = OpenSSL::ASN1.decode(encoded) # PKCS #8 EncryptedPrivateKeyInfo
+ assert_kind_of OpenSSL::ASN1::Sequence, asn1
+ assert_equal 2, asn1.value.size
+ assert_not_equal rsa.private_to_der, encoded
+ assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdefgh")
+ assert_same_rsa rsa, OpenSSL::PKey.read(encoded) { "abcdefgh" }
+ assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(encoded, "abcxyz") }
+
+ encoded = rsa.private_to_pem("aes-128-cbc", "abcdefgh")
+ assert_match (/BEGIN ENCRYPTED PRIVATE KEY/), encoded.lines[0]
+ assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdefgh")
+
+ # Use openssl instead of certtool due to https://gitlab.com/gnutls/gnutls/-/issues/1632
+ # openssl pkcs8 -in test/openssl/fixtures/pkey/rsa2048.pem -topk8 -v2 aes-128-cbc -passout pass:abcdefgh
+ pem = <<~EOF
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ+Sg92Hgy8EgVPf7t
+Hen1qwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEB5UX2xdDO8/AKA8
++Y5CZyUEggTQkArh4mMPpnAe3xOcDKMz8KCn5lrLb/6Dla7Rp9LHKGkUfyI11EZt
+m+OIriwy9oDQquKyVuLQVGAxXKk+3pyxMqLB0i3hLYamT3vzoPctyVwjuRuKoU3E
+CbF0YhCoxvWMvjHsolwYzx00DbLXouE4BGKvPjnhw5hwtdoZ9Px0ZnCXCxVXi8z/
+mlw7a2ptKEiHQVjuPPbttq+dA+ez7pbWonWVod5TMaPtyEZu5XfPD+0pMboceHZg
+H8ehgUhV3mzEJiisFGg1q9hj+4BaFl5m4tvqp43inCCdShE78CNnOPzJ7WCjKJqi
+jGvHjeMoVx3rZXHcZDAzfIZvDigp9uAfzjRJjpRG8sg5sDQVC7vdUhQDe5TorKT2
+Vb0tdVYxoEpMJ3dhU6Ds5JxMR6GTLjsjTqOkAl6db3HxulwfEpr7YjOpfODR+ttA
+BeIcUcMLsDHayIaQaMLIftHxOkfX7UxoFW9CMG5UMQf/m3eEgVUwgK/E5sUJRUTo
+yhRzJ4NAP4fgc4YH9tbzvUrhfdCXCBEOn6IlDQL66SZr8Mm+Ggu4Ij4TnKWXLrXL
+nSTDDa42kPOvtedKqxC/uXE7rrfh+uyw6J6OjSl6u86TIebndLuDo5DTdWKh8rsg
+fvZZ6332dfMp8JC9/4YnYIJdI7acInSoyHp52OB+2+dgYCr5OrZFjjKS7nELVfo7
+OxGy6uH3NHF9qyUEf3MN17TRHI7jP3zKbXcDTPSyxLQkWe/CU5B251CTmoTSidSW
+EhKnPlGZYbpVQJ4KGEL5UeY8W9PXQo4Dl7TmXBGvuPqNF8kMB3XrPIph7GmihmX0
+nlJqLk9eiRFmUETS0IdAyKJrm4R9Hf6rjYCbXlaApylyVUdSZ2BxgeoTY9BA6Kgf
+3xlgMv01MoUkXMx2+OLIc9MzhButQiDxh3mfS012CjKqUFrJhRSa8DOpUfVgmXpq
+/HP4drWamLWYJR8FsmJS11ZYc1EK/ctJTSpqfewvoUGOSHomhh7zXn1Acb6+9/3p
+bcrJjoR5K8Jg6NlG4dSNkpY/x92I7bFLXFqELIH5tteDrlQen5eASjaiyPPAoOw8
+IGfOmFS4VUPh1VP6g8Jtn5Hr2qXB3DoQoI6EvUZhJ6GJfi67mx5VKux6G9MzJkix
+GU1cL4WzWK2DU0l39UxXjS+4TmOYbrqLVnVMjusX0fwb8LkDC/fVohbhLwhHNwu6
+nSTSEpS9zSDrv1JXFtAtPv6XCSFs6ssPWJMwGSdThn7EfV0GEhG2mCzTyVhwxxQo
+6U/Suqq4oMZoracPUCZx0E4u/bb4KBoFA/eBNPJENTR18IiV+D7wAxlxauO3N1t4
+iJxwrrvSgQPmOGuxrh5LVD41UXYUWLtndzabnpByppFn2MbmvrqJgon0MSs84cTA
+7scnbPu1V3PpKy/t67gtVw9Ue8hLjrskWB1JPFYr7vRWvJzYjfbflyroF+QEJ3TA
+6rTfUC9+ePci6T+i9jF4xcmzqYzRtnGtp5nRUitJGw0uwBTDwzfI2WD6ltvvu7lc
+pHuzvY5zEapuu1JhjHLUd+OE8rVVM999DUXo/IDLsWyRCphCiYfVXJNogd9rB0Ta
+5AhVgpRhxkarBURZyLTYj7NRxCsbHq7XExJNrIdRG/KlBQfyEyIzZ7E=
+-----END ENCRYPTED PRIVATE KEY-----
+ EOF
+ assert_same_rsa rsa, OpenSSL::PKey.read(pem, "abcdefgh")
+ end
+
+ def test_params
+ key = Fixtures.pkey("rsa2048")
+ assert_equal(2048, key.n.num_bits)
+ assert_equal(key.n, key.params["n"])
+ assert_equal(65537, key.e)
+ assert_equal(key.e, key.params["e"])
+ [:d, :p, :q, :dmp1, :dmq1, :iqmp].each do |name|
+ assert_kind_of(OpenSSL::BN, key.send(name))
+ assert_equal(key.send(name), key.params[name.to_s])
+ end
+
+ pubkey = OpenSSL::PKey.read(key.public_to_der)
+ assert_equal(key.n, pubkey.n)
+ assert_equal(key.e, pubkey.e)
+ [:d, :p, :q, :dmp1, :dmq1, :iqmp].each do |name|
+ assert_nil(pubkey.send(name))
+ assert_nil(pubkey.params[name.to_s])
+ end
+ end
+
+ def test_dup
+ key = Fixtures.pkey("rsa-1")
+ key2 = key.dup
+ assert_equal key.params, key2.params
+
+ # PKey is immutable in OpenSSL >= 3.0
+ if !openssl?(3, 0, 0)
+ key2.set_key(key2.n, 3, key2.d)
+ assert_not_equal key.params, key2.params
+ end
+ end
+
+ def test_marshal
+ key = Fixtures.pkey("rsa-1")
+ deserialized = Marshal.load(Marshal.dump(key))
+
+ assert_equal key.to_der, deserialized.to_der
+ end
+
+ private
+ def assert_same_rsa(expected, key)
+ check_component(expected, key, [:n, :e, :d, :p, :q, :dmp1, :dmq1, :iqmp])
end
end
diff --git a/test/openssl/test_provider.rb b/test/openssl/test_provider.rb
new file mode 100644
index 0000000000..10081e208c
--- /dev/null
+++ b/test/openssl/test_provider.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+require_relative 'utils'
+if defined?(OpenSSL) && defined?(OpenSSL::Provider)
+
+class OpenSSL::TestProvider < OpenSSL::TestCase
+ def test_openssl_provider_name_inspect
+ with_openssl <<-'end;'
+ provider = OpenSSL::Provider.load("default")
+ assert_equal("default", provider.name)
+ assert_not_nil(provider.inspect)
+ end;
+ end
+
+ def test_openssl_provider_names
+ # We expect the following providers are loaded in the cases:
+ # * Non-FIPS: default
+ # * FIPS: fips, base
+ # Use the null provider to test the added provider.
+ # See provider(7) - OPENSSL PROVIDERS to see the list of providers, and
+ # OSSL_PROVIDER-null(7) to check the details of the null provider.
+ with_openssl <<-'end;'
+ num = OpenSSL::Provider.provider_names.size
+
+ added_provider = OpenSSL::Provider.load("null")
+ assert_equal(num + 1, OpenSSL::Provider.provider_names.size)
+ assert_includes(OpenSSL::Provider.provider_names, "null")
+
+ assert_equal(true, added_provider.unload)
+ assert_equal(num, OpenSSL::Provider.provider_names.size)
+ assert_not_includes(OpenSSL::Provider.provider_names, "null")
+ end;
+ end
+
+ def test_unloaded_openssl_provider
+ with_openssl <<-'end;'
+ default_provider = OpenSSL::Provider.load("default")
+ assert_equal(true, default_provider.unload)
+ assert_raise(OpenSSL::Provider::ProviderError) { default_provider.name }
+ assert_raise(OpenSSL::Provider::ProviderError) { default_provider.unload }
+ end;
+ end
+
+ def test_openssl_legacy_provider
+ # The legacy provider is not supported on FIPS.
+ omit_on_fips
+
+ with_openssl(<<-'end;')
+ begin
+ OpenSSL::Provider.load("default")
+ OpenSSL::Provider.load("legacy")
+ rescue OpenSSL::Provider::ProviderError
+ omit "Only for OpenSSL with legacy provider"
+ end
+
+ algo = "RC4"
+ data = "a" * 1000
+ key = OpenSSL::Random.random_bytes(16)
+
+ # default provider does not support RC4
+ cipher = OpenSSL::Cipher.new(algo)
+ cipher.encrypt
+ cipher.key = key
+ encrypted = cipher.update(data) + cipher.final
+
+ other_cipher = OpenSSL::Cipher.new(algo)
+ other_cipher.decrypt
+ other_cipher.key = key
+ decrypted = other_cipher.update(encrypted) + other_cipher.final
+
+ assert_equal(data, decrypted)
+ end;
+ end
+
+ private
+
+ # this is required because OpenSSL::Provider methods change global state
+ def with_openssl(code, **opts)
+ assert_separately(["-ropenssl"], <<~"end;", **opts)
+ #{code}
+ end;
+ end
+end
+
+end
diff --git a/test/openssl/test_random.rb b/test/openssl/test_random.rb
new file mode 100644
index 0000000000..33af375720
--- /dev/null
+++ b/test/openssl/test_random.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+require_relative "utils"
+
+if defined?(OpenSSL)
+
+class OpenSSL::TestRandom < OpenSSL::TestCase
+ def test_random_bytes
+ assert_equal("", OpenSSL::Random.random_bytes(0))
+ assert_equal(12, OpenSSL::Random.random_bytes(12).bytesize)
+ end
+
+ def test_pseudo_bytes
+ # deprecated as of OpenSSL 1.1.0
+ assert_equal("", OpenSSL::Random.pseudo_bytes(0))
+ assert_equal(12, OpenSSL::Random.pseudo_bytes(12).bytesize)
+ end if OpenSSL::Random.methods.include?(:pseudo_bytes)
+end
+
+end
diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb
index 2caadd1c67..e4fd581079 100644
--- a/test/openssl/test_ssl.rb
+++ b/test/openssl/test_ssl.rb
@@ -1,234 +1,467 @@
-begin
- require "openssl"
- require File.join(File.dirname(__FILE__), "utils.rb")
-rescue LoadError
-end
-require "rbconfig"
-require "socket"
-require "test/unit"
-begin
- loadpath = $:.dup
- $:.replace($: | [File.expand_path("../ruby", File.dirname(__FILE__))])
- require 'envutil'
-ensure
- $:.replace(loadpath)
-end
+# frozen_string_literal: true
+require_relative "utils"
-if defined?(OpenSSL)
-
-class OpenSSL::TestSSL < Test::Unit::TestCase
- RUBY = EnvUtil.rubybin
- SSL_SERVER = File.join(File.dirname(__FILE__), "ssl_server.rb")
- PORT = 20443
- ITERATIONS = ($0 == __FILE__) ? 100 : 10
-
- def setup
- @ca_key = OpenSSL::TestUtils::TEST_KEY_RSA2048
- @svr_key = OpenSSL::TestUtils::TEST_KEY_RSA1024
- @cli_key = OpenSSL::TestUtils::TEST_KEY_DSA256
- @ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
- @svr = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost")
- @cli = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost")
-
- now = Time.at(Time.now.to_i)
- ca_exts = [
- ["basicConstraints","CA:TRUE",true],
- ["keyUsage","cRLSign,keyCertSign",true],
- ]
- ee_exts = [
- ["keyUsage","keyEncipherment,digitalSignature",true],
- ]
- @ca_cert = issue_cert(@ca, @ca_key, 1, now, now+3600, ca_exts,
- nil, nil, OpenSSL::Digest::SHA1.new)
- @svr_cert = issue_cert(@svr, @svr_key, 2, now, now+1800, ee_exts,
- @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new)
- @cli_cert = issue_cert(@cli, @cli_key, 3, now, now+1800, ee_exts,
- @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new)
- @server = nil
+if defined?(OpenSSL::SSL)
+
+class OpenSSL::TestSSL < OpenSSL::SSLTestCase
+ def test_bad_socket
+ bad_socket = Struct.new(:sync).new
+ assert_raise TypeError do
+ socket = OpenSSL::SSL::SSLSocket.new bad_socket
+ # if the socket is not a T_FILE, `connect` will segv because it tries
+ # to get the underlying file descriptor but the API it calls assumes
+ # the object type is T_FILE
+ socket.connect
+ end
end
- def teardown
+ def test_ctx_setup
+ ctx = OpenSSL::SSL::SSLContext.new
+ assert_equal true, ctx.setup
+ assert_predicate ctx, :frozen?
+ assert_equal nil, ctx.setup
end
- def issue_cert(*arg)
- OpenSSL::TestUtils.issue_cert(*arg)
+ def test_ctx_options
+ ctx = OpenSSL::SSL::SSLContext.new
+
+ ctx.options = 4
+ assert_equal 4, ctx.options & 4
+ if ctx.options != 4
+ pend "SSL_CTX_set_options() seems to be modified by distributor"
+ end
+ ctx.options = nil
+ assert_equal OpenSSL::SSL::OP_ALL, ctx.options
+
+ assert_equal true, ctx.setup
+ assert_predicate ctx, :frozen?
+ assert_equal nil, ctx.setup
end
- def issue_crl(*arg)
- OpenSSL::TestUtils.issue_crl(*arg)
+ def test_ctx_options_config
+ omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc?
+
+ Tempfile.create("openssl.cnf") { |f|
+ f.puts(<<~EOF)
+ openssl_conf = default_conf
+ [default_conf]
+ ssl_conf = ssl_sect
+ [ssl_sect]
+ system_default = ssl_default_sect
+ [ssl_default_sect]
+ Options = -SessionTicket
+ EOF
+ f.close
+
+ assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl"], <<~"end;")
+ ctx = OpenSSL::SSL::SSLContext.new
+ assert_equal OpenSSL::SSL::OP_NO_TICKET, ctx.options & OpenSSL::SSL::OP_NO_TICKET
+ ctx.set_params
+ assert_equal OpenSSL::SSL::OP_NO_TICKET, ctx.options & OpenSSL::SSL::OP_NO_TICKET
+ end;
+ }
end
- def readwrite_loop(ctx, ssl)
- while line = ssl.gets
- if line =~ /^STARTTLS$/
- ssl.accept
- next
+ def test_ssl_with_server_cert
+ ctx_proc = -> ctx {
+ ctx.cert = @svr_cert
+ ctx.key = @svr_key
+ ctx.extra_chain_cert = [@ca_cert]
+ }
+ server_proc = -> (ctx, ssl) {
+ assert_equal @svr_cert.to_der, ssl.cert.to_der
+ assert_equal nil, ssl.peer_cert
+
+ readwrite_loop(ctx, ssl)
+ }
+ start_server(ctx_proc: ctx_proc, server_proc: server_proc) { |port|
+ begin
+ sock = TCPSocket.new("127.0.0.1", port)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.connect
+
+ assert_equal sock, ssl.io
+ assert_equal nil, ssl.cert
+ assert_equal @svr_cert.to_der, ssl.peer_cert.to_der
+ assert_equal 2, ssl.peer_cert_chain.size
+ assert_equal @svr_cert.to_der, ssl.peer_cert_chain[0].to_der
+ assert_equal @ca_cert.to_der, ssl.peer_cert_chain[1].to_der
+
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ ensure
+ ssl&.close
+ sock&.close
end
- ssl.write(line)
- end
- rescue OpenSSL::SSL::SSLError
- rescue IOError
- ensure
- ssl.close rescue nil
+ }
end
- def server_loop(ctx, ssls, server_proc)
- loop do
- ssl = nil
+ def test_socket_open
+ start_server { |port|
begin
- ssl = ssls.accept
- rescue OpenSSL::SSL::SSLError
- retry
- end
+ ssl = OpenSSL::SSL::SSLSocket.open("127.0.0.1", port)
+ ssl.sync_close = true
+ ssl.connect
- Thread.start do
- Thread.current.abort_on_exception = true
- server_proc.call(ctx, ssl)
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ ensure
+ ssl&.close
end
- end
- rescue Errno::EBADF, IOError
+ }
end
- def start_server(port0, verify_mode, start_immediately, args = {}, &block)
- ctx_proc = args[:ctx_proc]
- server_proc = args[:server_proc]
- server_proc ||= method(:readwrite_loop)
-
- store = OpenSSL::X509::Store.new
- store.add_cert(@ca_cert)
- store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
- ctx = OpenSSL::SSL::SSLContext.new
- ctx.cert_store = store
- #ctx.extra_chain_cert = [ ca_cert ]
- ctx.cert = @svr_cert
- ctx.key = @svr_key
- ctx.verify_mode = verify_mode
- ctx_proc.call(ctx) if ctx_proc
-
- Socket.do_not_reverse_lookup = true
- tcps = nil
- port = port0
- begin
- tcps = TCPServer.new("127.0.0.1", port)
- rescue Errno::EADDRINUSE
- port += 1
- retry
- end
-
- ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx)
- ssls.start_immediately = start_immediately
+ def test_socket_open_with_context
+ start_server { |port|
+ begin
+ ctx = OpenSSL::SSL::SSLContext.new
+ ssl = OpenSSL::SSL::SSLSocket.open("127.0.0.1", port, context: ctx)
+ ssl.sync_close = true
+ ssl.connect
- begin
- server = Thread.new do
- Thread.current.abort_on_exception = true
- server_loop(ctx, ssls, server_proc)
+ assert_equal ssl.context, ctx
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ ensure
+ ssl&.close
end
+ }
+ end
- $stderr.printf("%s started: pid=%d port=%d\n", SSL_SERVER, pid, port) if $DEBUG
+ def test_socket_open_with_local_address_port_context
+ start_server { |port|
+ begin
+ # Guess a free port number
+ random_port = rand(49152..65535)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ssl = OpenSSL::SSL::SSLSocket.open("127.0.0.1", port, "127.0.0.1", random_port, context: ctx)
+ ssl.sync_close = true
+ ssl.connect
- block.call(server, port.to_i)
- ensure
- tcps.close if (tcps)
- if (server)
- server.join(5)
- if server.alive?
- server.kill
- server.join
- flunk("TCPServer was closed and SSLServer is still alive") unless $!
- end
+ assert_equal ctx, ssl.context
+ assert_equal random_port, ssl.io.local_address.ip_port
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ rescue Errno::EADDRINUSE, Errno::EACCES
+ ensure
+ ssl&.close
end
- end
+ }
end
- def starttls(ssl)
- ssl.puts("STARTTLS")
+ def test_socket_close_write
+ server_proc = proc do |ctx, ssl|
+ message = ssl.read
+ ssl.write(message)
+ ssl.close_write
+ ensure
+ ssl.close
+ end
- sleep 1 # When this line is eliminated, process on Cygwin blocks
- # forever at ssl.connect. But I don't know why it does.
+ start_server(server_proc: server_proc) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ssl = OpenSSL::SSL::SSLSocket.open("127.0.0.1", port, context: ctx)
+ ssl.sync_close = true
+ ssl.connect
- ssl.connect
+ message = "abc"*1024
+ ssl.write message
+ ssl.close_write
+ assert_equal message, ssl.read
+ ensure
+ ssl&.close
+ end
end
- def test_ctx_setup
- ctx = OpenSSL::SSL::SSLContext.new
- assert_equal(ctx.setup, true)
- assert_equal(ctx.setup, nil)
+ def test_add_certificate
+ ctx_proc = -> ctx {
+ # Unset values set by start_server
+ ctx.cert = ctx.key = ctx.extra_chain_cert = nil
+ ctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA
+ }
+ start_server(ctx_proc: ctx_proc) do |port|
+ server_connect(port) { |ssl|
+ assert_equal @svr_cert.subject, ssl.peer_cert.subject
+ assert_equal [@svr_cert.subject, @ca_cert.subject],
+ ssl.peer_cert_chain.map(&:subject)
+
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ end
end
- def test_connect_and_close
- start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
- sock = TCPSocket.new("127.0.0.1", port)
- ssl = OpenSSL::SSL::SSLSocket.new(sock)
- assert(ssl.connect)
- ssl.close
- assert(!sock.closed?)
- sock.close
+ def test_add_certificate_multiple_certs
+ ca2_key = Fixtures.pkey("rsa-3")
+ ca2_exts = [
+ ["basicConstraints", "CA:TRUE", true],
+ ["keyUsage", "cRLSign, keyCertSign", true],
+ ]
+ ca2_dn = OpenSSL::X509::Name.parse_rfc2253("CN=CA2")
+ ca2_cert = issue_cert(ca2_dn, ca2_key, 123, ca2_exts, nil, nil)
- sock = TCPSocket.new("127.0.0.1", port)
- ssl = OpenSSL::SSL::SSLSocket.new(sock)
- ssl.sync_close = true # !!
- assert(ssl.connect)
- ssl.close
- assert(sock.closed?)
+ ecdsa_key = Fixtures.pkey("p256")
+ exts = [
+ ["keyUsage", "digitalSignature", false],
+ ]
+ ecdsa_dn = OpenSSL::X509::Name.parse_rfc2253("CN=localhost2")
+ ecdsa_cert = issue_cert(ecdsa_dn, ecdsa_key, 456, exts, ca2_cert, ca2_key)
+
+ ctx_proc = -> ctx {
+ # Unset values set by start_server
+ ctx.cert = ctx.key = ctx.extra_chain_cert = nil
+ ctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA
+ ctx.add_certificate(ecdsa_cert, ecdsa_key, [ca2_cert])
}
+ start_server(ctx_proc: ctx_proc) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.max_version = :TLS1_2 # TODO: We need this to force certificate type
+ ctx.ciphers = "aECDSA"
+ server_connect(port, ctx) { |ssl|
+ assert_equal ecdsa_cert.subject, ssl.peer_cert.subject
+ assert_equal [ecdsa_cert.subject, ca2_cert.subject],
+ ssl.peer_cert_chain.map(&:subject)
+ }
+
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.max_version = :TLS1_2
+ ctx.ciphers = "aRSA"
+ server_connect(port, ctx) { |ssl|
+ assert_equal @svr_cert.subject, ssl.peer_cert.subject
+ assert_equal [@svr_cert.subject, @ca_cert.subject],
+ ssl.peer_cert_chain.map(&:subject)
+ }
+ end
end
- def test_read_and_write
- start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
- sock = TCPSocket.new("127.0.0.1", port)
- ssl = OpenSSL::SSL::SSLSocket.new(sock)
- ssl.sync_close = true
- ssl.connect
+ def test_extra_chain_cert_auto_chain
+ start_server { |port|
+ server_connect(port) { |ssl|
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ assert_equal @svr_cert.to_der, ssl.peer_cert.to_der
+ assert_equal [@svr_cert], ssl.peer_cert_chain
+ }
+ }
+
+ # AWS-LC enables SSL_MODE_NO_AUTO_CHAIN by default
+ unless aws_lc?
+ ctx_proc = -> ctx {
+ # Sanity check: start_server won't set extra_chain_cert
+ assert_nil ctx.extra_chain_cert
+ ctx.cert_store = OpenSSL::X509::Store.new.tap { |store|
+ store.add_cert(@ca_cert)
+ }
+ }
+ start_server(ctx_proc: ctx_proc) { |port|
+ server_connect(port) { |ssl|
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ assert_equal @svr_cert.to_der, ssl.peer_cert.to_der
+ assert_equal [@svr_cert, @ca_cert], ssl.peer_cert_chain
+ }
+ }
+ end
+ end
- # syswrite and sysread
- ITERATIONS.times{|i|
- str = "x" * 100 + "\n"
+ def test_sysread_and_syswrite
+ start_server { |port|
+ server_connect(port) { |ssl|
+ str = +("x" * 100 + "\n")
ssl.syswrite(str)
- assert_equal(str, ssl.sysread(str.size))
+ newstr = ssl.sysread(str.bytesize)
+ assert_equal(str, newstr)
- str = "x" * i * 100 + "\n"
- buf = ""
+ buf = String.new
ssl.syswrite(str)
- assert_equal(buf.object_id, ssl.sysread(str.size, buf).object_id)
+ assert_same buf, ssl.sysread(str.size, buf)
assert_equal(str, buf)
+
+ obj = Object.new
+ obj.define_singleton_method(:to_str) { str }
+ ssl.syswrite(obj)
+ assert_equal(str, ssl.sysread(str.bytesize))
}
+ }
+ end
- # read and write
- ITERATIONS.times{|i|
- str = "x" * 100 + "\n"
- ssl.write(str)
- assert_equal(str, ssl.read(str.size))
+ def test_read_with_timeout
+ omit "does not support timeout" unless IO.method_defined?(:timeout)
- str = "x" * i * 100 + "\n"
- buf = ""
- ssl.write(str)
- assert_equal(buf.object_id, ssl.read(str.size, buf).object_id)
- assert_equal(str, buf)
+ start_server do |port|
+ server_connect(port) do |ssl|
+ str = +("x" * 100 + "\n")
+ ssl.syswrite(str)
+ assert_equal(str, ssl.sysread(str.bytesize))
+
+ ssl.timeout = 0.1
+ assert_raise(IO::TimeoutError) { ssl.sysread(1) }
+
+ ssl.syswrite(str)
+ assert_equal(str, ssl.sysread(str.bytesize))
+
+ buf = "orig".b
+ assert_raise(IO::TimeoutError) { ssl.sysread(1, buf) }
+ assert_equal("orig", buf)
+ assert_nothing_raised { buf.clear }
+ end
+ end
+ end
+
+ def test_getbyte
+ start_server { |port|
+ server_connect(port) { |ssl|
+ str = +("x" * 100 + "\n")
+ ssl.syswrite(str)
+ newstr = str.bytesize.times.map { |i|
+ ssl.getbyte
+ }.pack("C*")
+ assert_equal(str, newstr)
}
+ }
+ end
- ssl.close
+ def test_readbyte
+ start_server { |port|
+ server_connect(port) { |ssl|
+ str = +("x" * 100 + "\n")
+ ssl.syswrite(str)
+ newstr = str.bytesize.times.map { |i|
+ ssl.readbyte
+ }.pack("C*")
+ assert_equal(str, newstr)
+ }
}
end
- def test_client_auth
- vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
- start_server(PORT, vflag, true){|server, port|
- assert_raises(OpenSSL::SSL::SSLError){
+ def test_sync_close
+ start_server do |port|
+ begin
+ sock = TCPSocket.new("127.0.0.1", port)
+ ssl = OpenSSL::SSL::SSLSocket.new(sock)
+ ssl.connect
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ ssl.close
+ assert_not_predicate sock, :closed?
+ ensure
+ sock&.close
+ end
+
+ begin
sock = TCPSocket.new("127.0.0.1", port)
ssl = OpenSSL::SSL::SSLSocket.new(sock)
+ ssl.sync_close = true # !!
ssl.connect
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ ssl.close
+ assert_predicate sock, :closed?
+ ensure
+ sock&.close
+ end
+ end
+ end
+
+ def test_sync_close_initialize_opt
+ start_server do |port|
+ begin
+ sock = TCPSocket.new("127.0.0.1", port)
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, sync_close: true)
+ assert_equal true, ssl.sync_close
+ ssl.connect
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ ssl.close
+ assert_predicate sock, :closed?
+ ensure
+ sock&.close
+ end
+ end
+ end
+
+ def test_copy_stream
+ start_server do |port|
+ server_connect(port) do |ssl|
+ IO.pipe do |r, w|
+ str = "hello world\n"
+ w.write(str)
+ IO.copy_stream(r, ssl, str.bytesize)
+ IO.copy_stream(ssl, w, str.bytesize)
+ assert_equal str, r.read(str.bytesize)
+ end
+ end
+ end
+ end
+
+ def test_verify_mode_default
+ ctx = OpenSSL::SSL::SSLContext.new
+ assert_equal OpenSSL::SSL::VERIFY_NONE, ctx.verify_mode
+ end
+
+ def test_verify_mode_server_cert
+ start_server(ignore_listener_error: true) { |port|
+ populated_store = OpenSSL::X509::Store.new
+ populated_store.add_cert(@ca_cert)
+ empty_store = OpenSSL::X509::Store.new
+
+ # Valid certificate, SSL_VERIFY_PEER
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ ctx.cert_store = populated_store
+ assert_nothing_raised {
+ server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets }
+ }
+
+ # Invalid certificate, SSL_VERIFY_NONE
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ ctx.cert_store = empty_store
+ assert_nothing_raised {
+ server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets }
}
+ # Invalid certificate, SSL_VERIFY_PEER
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ ctx.cert_store = empty_store
+ assert_raise(OpenSSL::SSL::SSLError) {
+ server_connect(port, ctx)
+ }
+ }
+ end
+
+ def test_verify_mode_client_cert_required
+ # Optional, client certificate not supplied
+ vflag = OpenSSL::SSL::VERIFY_PEER
+ accept_proc = -> ssl {
+ assert_equal nil, ssl.peer_cert
+ }
+ start_server(verify_mode: vflag, accept_proc: accept_proc) { |port|
+ assert_nothing_raised {
+ server_connect(port) { |ssl| ssl.puts("abc"); ssl.gets }
+ }
+ }
+
+ # Required, client certificate not supplied
+ vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
+ start_server(verify_mode: vflag, ignore_listener_error: true) { |port|
+ assert_handshake_error {
+ server_connect(port) { |ssl| ssl.puts("abc"); ssl.gets }
+ }
+ }
+ end
+
+ def test_client_auth_success
+ vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
+ ctx_proc = proc { |ctx|
+ store = OpenSSL::X509::Store.new
+ store.add_cert(@ca_cert)
+ store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
+ ctx.cert_store = store
+ # LibreSSL doesn't support client_cert_cb in TLS 1.3
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl?
+ }
+ start_server(verify_mode: vflag, ctx_proc: ctx_proc) { |port|
ctx = OpenSSL::SSL::SSLContext.new
ctx.key = @cli_key
ctx.cert = @cli_cert
- sock = TCPSocket.new("127.0.0.1", port)
- ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
- ssl.sync_close = true
- ssl.connect
- ssl.puts("foo")
- assert_equal("foo\n", ssl.gets)
- ssl.close
+
+ server_connect(port, ctx) { |ssl|
+ ssl.puts("foo")
+ assert_equal("foo\n", ssl.gets)
+ }
called = nil
ctx = OpenSSL::SSL::SSLContext.new
@@ -236,43 +469,76 @@ class OpenSSL::TestSSL < Test::Unit::TestCase
called = true
[@cli_cert, @cli_key]
}
- sock = TCPSocket.new("127.0.0.1", port)
- ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
- ssl.sync_close = true
- ssl.connect
- assert(called)
- ssl.puts("foo")
- assert_equal("foo\n", ssl.gets)
- ssl.close
+
+ server_connect(port, ctx) { |ssl|
+ assert(called)
+ ssl.puts("foo")
+ assert_equal("foo\n", ssl.gets)
+ }
}
end
- def test_starttls
- start_server(PORT, OpenSSL::SSL::VERIFY_NONE, false){|server, port|
- sock = TCPSocket.new("127.0.0.1", port)
- ssl = OpenSSL::SSL::SSLSocket.new(sock)
- ssl.sync_close = true
- str = "x" * 1000 + "\n"
-
- ITERATIONS.times{
- ssl.puts(str)
- assert_equal(str, ssl.gets)
+ def test_client_cert_cb_ignore_error
+ vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
+ start_server(verify_mode: vflag, ignore_listener_error: true) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.client_cert_cb = -> ssl {
+ raise "exception in client_cert_cb must be suppressed"
+ }
+ # 1. Exception in client_cert_cb is suppressed
+ # 2. No client certificate will be sent to the server
+ # 3. SSL_VERIFY_FAIL_IF_NO_PEER_CERT causes the handshake to fail
+ assert_handshake_error {
+ server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets }
}
+ end
+ end
- starttls(ssl)
+ def test_client_ca
+ pend "LibreSSL doesn't support certificate_authorities" if libressl?
- ITERATIONS.times{
- ssl.puts(str)
- assert_equal(str, ssl.gets)
- }
+ ctx_proc = Proc.new do |ctx|
+ store = OpenSSL::X509::Store.new
+ store.add_cert(@ca_cert)
+ store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
+ ctx.cert_store = store
+ ctx.client_ca = [@ca_cert]
+ end
- ssl.close
+ vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
+ start_server(verify_mode: vflag, ctx_proc: ctx_proc) { |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ client_ca_from_server = nil
+ ctx.client_cert_cb = Proc.new do |sslconn|
+ client_ca_from_server = sslconn.client_ca
+ [@cli_cert, @cli_key]
+ end
+ server_connect(port, ctx) { |ssl|
+ assert_equal([@ca], client_ca_from_server)
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
}
end
+ def test_unstarted_session
+ start_server do |port|
+ sock = TCPSocket.new("127.0.0.1", port)
+ ssl = OpenSSL::SSL::SSLSocket.new(sock)
+
+ assert_raise(OpenSSL::SSL::SSLError) { ssl.syswrite("data") }
+ assert_raise(OpenSSL::SSL::SSLError) { ssl.sysread(1) }
+
+ ssl.connect
+ ssl.puts "abc"
+ assert_equal "abc\n", ssl.gets
+ ensure
+ ssl&.close
+ sock&.close
+ end
+ end
+
def test_parallel
- GC.start
- start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
+ start_server { |port|
ssls = []
10.times{
sock = TCPSocket.new("127.0.0.1", port)
@@ -293,245 +559,1870 @@ class OpenSSL::TestSSL < Test::Unit::TestCase
end
def test_verify_result
- start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
+ start_server(ignore_listener_error: true) { |port|
sock = TCPSocket.new("127.0.0.1", port)
ctx = OpenSSL::SSL::SSLContext.new
- ctx.set_params
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
- assert_raise(OpenSSL::SSL::SSLError){ ssl.connect }
- assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, ssl.verify_result)
+ ssl.sync_close = true
+ begin
+ assert_raise(OpenSSL::SSL::SSLError){ ssl.connect }
+ assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, ssl.verify_result)
+ ensure
+ ssl.close
+ end
+ }
+
+ start_server { |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ ctx.verify_callback = Proc.new do |preverify_ok, store_ctx|
+ store_ctx.error = OpenSSL::X509::V_OK
+ true
+ end
+ server_connect(port, ctx) { |ssl|
+ assert_equal(OpenSSL::X509::V_OK, ssl.verify_result)
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ }
+ start_server(ignore_listener_error: true) { |port|
sock = TCPSocket.new("127.0.0.1", port)
ctx = OpenSSL::SSL::SSLContext.new
- ctx.set_params(
- :verify_callback => Proc.new do |preverify_ok, store_ctx|
- store_ctx.error = OpenSSL::X509::V_OK
- true
- end
- )
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ ctx.verify_callback = Proc.new do |preverify_ok, store_ctx|
+ store_ctx.error = OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION
+ false
+ end
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
- ssl.connect
- assert_equal(OpenSSL::X509::V_OK, ssl.verify_result)
+ ssl.sync_close = true
+ begin
+ assert_raise(OpenSSL::SSL::SSLError){ ssl.connect }
+ assert_equal(OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION, ssl.verify_result)
+ ensure
+ ssl.close
+ end
+ }
+ end
+ def test_exception_in_verify_callback_is_ignored
+ start_server(ignore_listener_error: true) { |port|
sock = TCPSocket.new("127.0.0.1", port)
ctx = OpenSSL::SSL::SSLContext.new
- ctx.set_params(
- :verify_callback => Proc.new do |preverify_ok, store_ctx|
- store_ctx.error = OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION
- false
- end
- )
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ ctx.verify_callback = Proc.new do |preverify_ok, store_ctx|
+ store_ctx.error = OpenSSL::X509::V_OK
+ raise RuntimeError
+ end
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
- assert_raise(OpenSSL::SSL::SSLError){ ssl.connect }
- assert_equal(OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION, ssl.verify_result)
+ ssl.sync_close = true
+ begin
+ EnvUtil.suppress_warning do
+ # SSLError, not RuntimeError
+ assert_raise(OpenSSL::SSL::SSLError) { ssl.connect }
+ end
+ assert_equal(OpenSSL::X509::V_ERR_CERT_REJECTED, ssl.verify_result)
+ ensure
+ ssl.close
+ end
+ }
+ end
+
+ def test_ca_file
+ start_server(ignore_listener_error: true) { |port|
+ # X509_STORE is shared; setting ca_file to SSLContext affects store
+ store = OpenSSL::X509::Store.new
+ assert_equal false, store.verify(@svr_cert)
+
+ ctx = Tempfile.create("ca_cert.pem") { |f|
+ f.puts(@ca_cert.to_pem)
+ f.close
+
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ ctx.cert_store = store
+ ctx.ca_file = f.path
+ ctx.setup
+ ctx
+ }
+ assert_nothing_raised {
+ server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets }
+ }
+ assert_equal true, store.verify(@svr_cert)
+ }
+ end
+
+ def test_ca_file_not_found
+ path = Tempfile.create("ca_cert.pem") { |f| f.path }
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.ca_file = path
+ # OpenSSL >= 1.1.0: /no certificate or crl found/
+ assert_raise(OpenSSL::SSL::SSLError) {
+ ctx.setup
+ }
+ 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|
+ ssl.puts "abc"; ssl.gets
+
+ client_finished = ssl.finished_message
+ client_peer_finished = ssl.peer_finished_message
+ }
}
+ assert_not_nil(server_finished)
+ assert_not_nil(client_finished)
+ assert_equal(server_finished, client_peer_finished)
+ assert_equal(server_peer_finished, client_finished)
end
def test_sslctx_set_params
- start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
- sock = TCPSocket.new("127.0.0.1", port)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.set_params
+
+ assert_equal OpenSSL::SSL::VERIFY_PEER, ctx.verify_mode
+ ciphers_names = ctx.ciphers.collect{|v, _, _, _| v }
+ assert ciphers_names.all?{|v| /A(EC)?DH/ !~ v }, "anon ciphers are disabled"
+ assert ciphers_names.all?{|v| /(RC4|MD5|EXP|DES(?!-EDE|-CBC3))/ !~ v }, "weak ciphers are disabled"
+ assert_equal 0, ctx.options & OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS
+ assert_equal OpenSSL::SSL::OP_NO_COMPRESSION,
+ ctx.options & OpenSSL::SSL::OP_NO_COMPRESSION
+ end
+
+ def test_post_connect_check_with_anon_ciphers
+ # DH missing the q value on unknown named parameters is not FIPS-approved.
+ omit_on_fips
+ omit "AWS-LC does not support DHE ciphersuites" if aws_lc?
+
+ ctx_proc = -> ctx {
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ ctx.ciphers = "aNULL"
+ ctx.tmp_dh = Fixtures.pkey("dh-1")
+ ctx.security_level = 0
+ }
+
+ start_server(ctx_proc: ctx_proc) { |port|
ctx = OpenSSL::SSL::SSLContext.new
- ctx.set_params
- assert_equal(OpenSSL::SSL::VERIFY_PEER, ctx.verify_mode)
- assert_equal(OpenSSL::SSL::OP_ALL, ctx.options)
- ciphers = ctx.ciphers
- ciphers_versions = ciphers.collect{|_, v, _, _| v }
- ciphers_names = ciphers.collect{|v, _, _, _| v }
- assert(ciphers_names.all?{|v| /ADH/ !~ v })
- assert(ciphers_versions.all?{|v| /SSLv2/ !~ v })
- ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
- assert_raise(OpenSSL::SSL::SSLError){ ssl.connect }
- assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, ssl.verify_result)
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ ctx.ciphers = "aNULL"
+ ctx.security_level = 0
+ server_connect(port, ctx) { |ssl|
+ assert_raise_with_message(OpenSSL::SSL::SSLError, /anonymous cipher suite/i) {
+ ssl.post_connection_check("localhost.localdomain")
+ }
+ }
}
end
def test_post_connection_check
sslerr = OpenSSL::SSL::SSLError
- start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
- sock = TCPSocket.new("127.0.0.1", port)
- ssl = OpenSSL::SSL::SSLSocket.new(sock)
- ssl.connect
- assert_raises(sslerr){ssl.post_connection_check("localhost.localdomain")}
- assert_raises(sslerr){ssl.post_connection_check("127.0.0.1")}
- assert(ssl.post_connection_check("localhost"))
- assert_raises(sslerr){ssl.post_connection_check("foo.example.com")}
+ start_server { |port|
+ server_connect(port) { |ssl|
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
- cert = ssl.peer_cert
- assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain"))
- assert(!OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1"))
- assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost"))
- assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com"))
+ assert_raise(sslerr){ssl.post_connection_check("localhost.localdomain")}
+ assert_raise(sslerr){ssl.post_connection_check("127.0.0.1")}
+ assert(ssl.post_connection_check("localhost"))
+ assert_raise(sslerr){ssl.post_connection_check("foo.example.com")}
+
+ cert = ssl.peer_cert
+ assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain"))
+ assert(!OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1"))
+ assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost"))
+ assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com"))
+ }
}
- now = Time.now
exts = [
["keyUsage","keyEncipherment,digitalSignature",true],
- ["subjectAltName","DNS:localhost.localdomain",false],
- ["subjectAltName","IP:127.0.0.1",false],
+ ["subjectAltName","DNS:localhost.localdomain,IP:127.0.0.1",false],
]
- @svr_cert = issue_cert(@svr, @svr_key, 4, now, now+1800, exts,
- @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new)
- start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
- sock = TCPSocket.new("127.0.0.1", port)
- ssl = OpenSSL::SSL::SSLSocket.new(sock)
- ssl.connect
- assert(ssl.post_connection_check("localhost.localdomain"))
- assert(ssl.post_connection_check("127.0.0.1"))
- assert_raises(sslerr){ssl.post_connection_check("localhost")}
- assert_raises(sslerr){ssl.post_connection_check("foo.example.com")}
+ @svr_cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key)
+ start_server { |port|
+ server_connect(port) { |ssl|
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+
+ assert(ssl.post_connection_check("localhost.localdomain"))
+ assert(ssl.post_connection_check("127.0.0.1"))
+ assert_raise(sslerr){ssl.post_connection_check("localhost")}
+ assert_raise(sslerr){ssl.post_connection_check("foo.example.com")}
- cert = ssl.peer_cert
- assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain"))
- assert(OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1"))
- assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost"))
- assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com"))
+ cert = ssl.peer_cert
+ assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain"))
+ assert(OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1"))
+ assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost"))
+ assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com"))
+ }
}
- now = Time.now
exts = [
["keyUsage","keyEncipherment,digitalSignature",true],
["subjectAltName","DNS:*.localdomain",false],
]
- @svr_cert = issue_cert(@svr, @svr_key, 5, now, now+1800, exts,
- @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new)
- start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true){|server, port|
+ @svr_cert = issue_cert(@svr, @svr_key, 5, exts, @ca_cert, @ca_key)
+ start_server { |port|
+ server_connect(port) { |ssl|
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+
+ assert(ssl.post_connection_check("localhost.localdomain"))
+ assert_raise(sslerr){ssl.post_connection_check("127.0.0.1")}
+ assert_raise(sslerr){ssl.post_connection_check("localhost")}
+ assert_raise(sslerr){ssl.post_connection_check("foo.example.com")}
+ cert = ssl.peer_cert
+ assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain"))
+ assert(!OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1"))
+ assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost"))
+ assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com"))
+ }
+ }
+ end
+
+ def test_verify_certificate_identity
+ [true, false].each do |criticality|
+ cert = create_null_byte_SAN_certificate(criticality)
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, 'www.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, "www.example.com\0.evil.com"))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.255'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.1'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '13::17'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '13::18'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '13:0:0:0:0:0:0:17'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '44:0:0:0:0:0:0:17'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '0013:0000:0000:0000:0000:0000:0000:0017'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '1313:0000:0000:0000:0000:0000:0000:0017'))
+ end
+ end
+
+ def test_verify_hostname
+ assert_equal(true, OpenSSL::SSL.verify_hostname("www.example.com", "*.example.com"))
+ assert_equal(false, OpenSSL::SSL.verify_hostname("www.subdomain.example.com", "*.example.com"))
+ end
+
+ def test_verify_wildcard
+ assert_equal(false, OpenSSL::SSL.verify_wildcard("foo", "x*"))
+ assert_equal(true, OpenSSL::SSL.verify_wildcard("foo", "foo"))
+ assert_equal(true, OpenSSL::SSL.verify_wildcard("foo", "f*"))
+ assert_equal(true, OpenSSL::SSL.verify_wildcard("foo", "*"))
+ assert_equal(false, OpenSSL::SSL.verify_wildcard("abc*bcd", "abcd"))
+ assert_equal(false, OpenSSL::SSL.verify_wildcard("xn--qdk4b9b", "x*"))
+ assert_equal(false, OpenSSL::SSL.verify_wildcard("xn--qdk4b9b", "*--qdk4b9b"))
+ assert_equal(true, OpenSSL::SSL.verify_wildcard("xn--qdk4b9b", "xn--qdk4b9b"))
+ end
+
+ # Comments in this test is excerpted from https://www.rfc-editor.org/rfc/rfc6125#page-27
+ def test_post_connection_check_wildcard_san
+ # case-insensitive ASCII comparison
+ # RFC 6125, section 6.4.1
+ #
+ # "..matching of the reference identifier against the presented identifier
+ # is performed by comparing the set of domain name labels using a
+ # case-insensitive ASCII comparison, as clarified by [DNS-CASE] (e.g.,
+ # "WWW.Example.Com" would be lower-cased to "www.example.com" for
+ # comparison purposes)
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:*.example.com'), 'www.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:*.Example.COM'), 'www.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:*.example.com'), 'WWW.Example.COM'))
+ # 1. The client SHOULD NOT attempt to match a presented identifier in
+ # which the wildcard character comprises a label other than the
+ # left-most label (e.g., do not match bar.*.example.net).
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:www.*.com'), 'www.example.com'))
+ # 2. If the wildcard character is the only character of the left-most
+ # label in the presented identifier, the client SHOULD NOT compare
+ # against anything but the left-most label of the reference
+ # identifier (e.g., *.example.com would match foo.example.com but
+ # not bar.foo.example.com or example.com).
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:*.example.com'), 'foo.example.com'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:*.example.com'), 'bar.foo.example.com'))
+ # 3. The client MAY match a presented identifier in which the wildcard
+ # character is not the only character of the label (e.g.,
+ # baz*.example.net and *baz.example.net and b*z.example.net would
+ # be taken to match baz1.example.net and foobaz.example.net and
+ # buzz.example.net, respectively). ...
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:baz*.example.com'), 'baz1.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:*baz.example.com'), 'foobaz.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:b*z.example.com'), 'buzz.example.com'))
+
+ # Section 6.4.3 of RFC6125 states that client should NOT match identifier
+ # where wildcard is other than left-most label.
+ #
+ # Also implicitly mentions the wildcard character only in singular form,
+ # and discourages matching against more than one wildcard.
+ #
+ # See RFC 6125, section 7.2, subitem 2.
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:*b*.example.com'), 'abc.example.com'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:*b*.example.com'), 'ab.example.com'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:*b*.example.com'), 'bc.example.com'))
+ # ... However, the client SHOULD NOT
+ # attempt to match a presented identifier where the wildcard
+ # character is embedded within an A-label or U-label [IDNA-DEFS] of
+ # an internationalized domain name [IDNA-PROTO].
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:xn*.example.com'), 'xn1ca.example.com'))
+ # part of A-label
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:xn--*.example.com'), 'xn--1ca.example.com'))
+ # part of U-label
+ # dNSName in RFC5280 is an IA5String so U-label should NOT be allowed
+ # regardless of wildcard.
+ #
+ # See Section 7.2 of RFC 5280:
+ # IA5String is limited to the set of ASCII characters.
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_san('DNS:á*.example.com'), 'á1.example.com'))
+ end
+
+ def test_post_connection_check_wildcard_cn
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('*.example.com'), 'www.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('*.Example.COM'), 'www.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('*.example.com'), 'WWW.Example.COM'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('www.*.com'), 'www.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('*.example.com'), 'foo.example.com'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('*.example.com'), 'bar.foo.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('baz*.example.com'), 'baz1.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('*baz.example.com'), 'foobaz.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('b*z.example.com'), 'buzz.example.com'))
+ # Section 6.4.3 of RFC6125 states that client should NOT match identifier
+ # where wildcard is other than left-most label.
+ #
+ # Also implicitly mentions the wildcard character only in singular form,
+ # and discourages matching against more than one wildcard.
+ #
+ # See RFC 6125, section 7.2, subitem 2.
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('*b*.example.com'), 'abc.example.com'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('*b*.example.com'), 'ab.example.com'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('*b*.example.com'), 'bc.example.com'))
+ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('xn*.example.com'), 'xn1ca.example.com'))
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('xn--*.example.com'), 'xn--1ca.example.com'))
+ # part of U-label
+ # Subject in RFC5280 states case-insensitive ASCII comparison.
+ #
+ # See Section 7.2 of RFC 5280:
+ # IA5String is limited to the set of ASCII characters.
+ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+ create_cert_with_name('á*.example.com'), 'á1.example.com'))
+ end
+
+ def create_cert_with_san(san)
+ cert = OpenSSL::X509::Certificate.new
+ cert.subject = OpenSSL::X509::Name.parse("/DC=some/DC=site/CN=Some Site")
+ v = OpenSSL::ASN1::Sequence(san.split(",").map { |item|
+ type, value = item.split(":", 2)
+ case type
+ when "DNS" then OpenSSL::ASN1::IA5String(value, 2, :IMPLICIT)
+ when "IP" then OpenSSL::ASN1::OctetString(IPAddr.new(value).hton, 7, :IMPLICIT)
+ else raise "unsupported"
+ end
+ })
+ cert.add_extension(OpenSSL::X509::Extension.new("subjectAltName", v))
+ cert
+ end
+
+ def create_cert_with_name(name)
+ cert = OpenSSL::X509::Certificate.new
+ cert.subject = OpenSSL::X509::Name.new([['DC', 'some'], ['DC', 'site'], ['CN', name]])
+ cert
+ end
+
+
+ # Create NULL byte SAN certificate
+ def create_null_byte_SAN_certificate(critical = false)
+ ef = OpenSSL::X509::ExtensionFactory.new
+ cert = OpenSSL::X509::Certificate.new
+ cert.subject = OpenSSL::X509::Name.parse "/DC=some/DC=site/CN=Some Site"
+ ext = ef.create_ext('subjectAltName', 'DNS:placeholder,IP:192.168.7.1,IP:13::17', critical)
+ ext_asn1 = OpenSSL::ASN1.decode(ext.to_der)
+ san_list_der = ext_asn1.value.reduce(nil) { |memo,val| val.tag == 4 ? val.value : memo }
+ san_list_asn1 = OpenSSL::ASN1.decode(san_list_der)
+ san_list_asn1.value[0].value = "www.example.com\0.evil.com"
+ pos = critical ? 2 : 1
+ ext_asn1.value[pos].value = san_list_asn1.to_der
+ real_ext = OpenSSL::X509::Extension.new ext_asn1
+ cert.add_extension(real_ext)
+ cert
+ end
+
+ def socketpair
+ if defined? UNIXSocket
+ UNIXSocket.pair
+ else
+ Socket.pair(Socket::AF_INET, Socket::SOCK_STREAM, 0)
+ end
+ end
+
+ def test_keylog_cb
+ omit "Keylog callback is not supported" if libressl?
+
+ prefix = 'CLIENT_RANDOM'
+ context = OpenSSL::SSL::SSLContext.new
+ context.min_version = context.max_version = OpenSSL::SSL::TLS1_2_VERSION
+
+ cb_called = false
+ context.keylog_cb = proc do |_sock, line|
+ cb_called = true
+ assert_equal(prefix, line.split.first)
+ end
+
+ start_server do |port|
+ server_connect(port, context) do |ssl|
+ ssl.puts "abc"
+ assert_equal("abc\n", ssl.gets)
+ assert_equal(true, cb_called)
+ end
+ end
+
+ prefixes = [
+ 'SERVER_HANDSHAKE_TRAFFIC_SECRET',
+ 'EXPORTER_SECRET',
+ 'SERVER_TRAFFIC_SECRET_0',
+ 'CLIENT_HANDSHAKE_TRAFFIC_SECRET',
+ 'CLIENT_TRAFFIC_SECRET_0',
+ ]
+ context = OpenSSL::SSL::SSLContext.new
+ context.min_version = context.max_version = OpenSSL::SSL::TLS1_3_VERSION
+ cb_called = false
+ context.keylog_cb = proc do |_sock, line|
+ cb_called = true
+ assert_not_nil(prefixes.delete(line.split.first))
+ end
+
+ start_server do |port|
+ server_connect(port, context) do |ssl|
+ ssl.puts "abc"
+ assert_equal("abc\n", ssl.gets)
+ assert_equal(true, cb_called)
+ end
+ assert_equal(0, prefixes.size)
+ end
+ end
+
+ def test_tlsext_hostname
+ fooctx = OpenSSL::SSL::SSLContext.new
+ fooctx.cert = @cli_cert
+ fooctx.key = @cli_key
+
+ ctx_proc = proc { |ctx|
+ ctx.servername_cb = proc { |ssl, servername|
+ case servername
+ when "foo.example.com"
+ fooctx
+ when "bar.example.com"
+ nil
+ else
+ raise "unreachable"
+ end
+ }
+ }
+ start_server(ctx_proc: ctx_proc) do |port|
sock = TCPSocket.new("127.0.0.1", port)
- ssl = OpenSSL::SSL::SSLSocket.new(sock)
- ssl.connect
- assert(ssl.post_connection_check("localhost.localdomain"))
- assert_raises(sslerr){ssl.post_connection_check("127.0.0.1")}
- assert_raises(sslerr){ssl.post_connection_check("localhost")}
- assert_raises(sslerr){ssl.post_connection_check("foo.example.com")}
- cert = ssl.peer_cert
- assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain"))
- assert(!OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1"))
- assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost"))
- assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com"))
- }
- end
-
- def test_client_session
- last_session = nil
- start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port|
- 2.times do
+ begin
+ ssl = OpenSSL::SSL::SSLSocket.new(sock)
+ ssl.hostname = "foo.example.com"
+ ssl.connect
+ assert_equal @cli_cert.serial, ssl.peer_cert.serial
+ assert_predicate fooctx, :frozen?
+
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ ensure
+ ssl&.close
+ sock.close
+ end
+
+ sock = TCPSocket.new("127.0.0.1", port)
+ begin
+ ssl = OpenSSL::SSL::SSLSocket.new(sock)
+ ssl.hostname = "bar.example.com"
+ ssl.connect
+ assert_equal @svr_cert.serial, ssl.peer_cert.serial
+
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ ensure
+ ssl&.close
+ sock.close
+ end
+ end
+ end
+
+ def test_servername_cb_exception
+ sock1, sock2 = socketpair
+
+ t = Thread.new {
+ s1 = OpenSSL::SSL::SSLSocket.new(sock1)
+ s1.hostname = "localhost"
+ assert_raise_with_message(OpenSSL::SSL::SSLError, /unrecognized.name/i) {
+ s1.connect
+ }
+ }
+
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.servername_cb = lambda { |args| raise RuntimeError, "foo" }
+ s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+ assert_raise_with_message(RuntimeError, "foo") { s2.accept }
+ assert t.join
+ ensure
+ sock1.close
+ sock2.close
+ t.kill.join
+ end
+
+ def test_servername_cb_raises_an_exception_on_unknown_objects
+ sock1, sock2 = socketpair
+
+ t = Thread.new {
+ s1 = OpenSSL::SSL::SSLSocket.new(sock1)
+ s1.hostname = "localhost"
+ assert_raise(OpenSSL::SSL::SSLError) { s1.connect }
+ }
+
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.servername_cb = lambda { |args| Object.new }
+ s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+ assert_raise(ArgumentError) { s2.accept }
+ assert t.join
+ ensure
+ sock1.close
+ sock2.close
+ t.kill.join
+ end
+
+ def test_accept_errors_include_peeraddr
+ context = OpenSSL::SSL::SSLContext.new
+ context.cert = @svr_cert
+ context.key = @svr_key
+
+ server = TCPServer.new("127.0.0.1", 0)
+ port = server.connect_address.ip_port
+
+ ssl_server = OpenSSL::SSL::SSLServer.new(server, context)
+
+ t = Thread.new do
+ assert_raise_with_message(OpenSSL::SSL::SSLError, /peeraddr=127\.0\.0\.1/) do
+ ssl_server.accept
+ end
+ end
+
+ sock = TCPSocket.new("127.0.0.1", port)
+ sock << "\x00" * 1024
+
+ assert t.join
+ ensure
+ sock&.close
+ server.close
+ end
+
+ def test_verify_hostname_on_connect
+ ctx_proc = proc { |ctx|
+ exts = [
+ ["keyUsage", "keyEncipherment,digitalSignature", true],
+ ["subjectAltName", "DNS:a.example.com,DNS:*.b.example.com," \
+ "DNS:c*.example.com,DNS:d.*.example.com"],
+ ]
+ ctx.cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key)
+ ctx.key = @svr_key
+ }
+
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ assert_equal false, ctx.verify_hostname
+ ctx.verify_hostname = true
+ ctx.cert_store = OpenSSL::X509::Store.new
+ ctx.cert_store.add_cert(@ca_cert)
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+
+ [
+ ["a.example.com", true],
+ ["A.Example.Com", true],
+ ["x.example.com", false],
+ ["b.example.com", false],
+ ["x.b.example.com", true],
+ ["cx.example.com", true],
+ ["d.x.example.com", false],
+ ].each do |name, expected_ok|
+ begin
+ sock = TCPSocket.new("127.0.0.1", port)
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.hostname = name
+ if expected_ok
+ ssl.connect
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ else
+ assert_raise(OpenSSL::SSL::SSLError) { ssl.connect }
+ end
+ ensure
+ ssl.close if ssl
+ sock.close if sock
+ end
+ end
+ end
+ end
+
+ def test_verify_hostname_failure_error_code
+ ctx_proc = proc { |ctx|
+ exts = [
+ ["keyUsage", "keyEncipherment,digitalSignature", true],
+ ["subjectAltName", "DNS:a.example.com"],
+ ]
+ ctx.cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key)
+ ctx.key = @svr_key
+ }
+
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
+ verify_callback_ok = verify_callback_err = nil
+
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.verify_hostname = true
+ ctx.cert_store = OpenSSL::X509::Store.new
+ ctx.cert_store.add_cert(@ca_cert)
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ ctx.verify_callback = -> (preverify_ok, store_ctx) {
+ verify_callback_ok = preverify_ok
+ verify_callback_err = store_ctx.error
+ preverify_ok
+ }
+
+ begin
sock = TCPSocket.new("127.0.0.1", port)
- # Debian's openssl 0.9.8g-13 failed at assert(ssl.session_reused?),
- # when use default SSLContext. [ruby-dev:36167]
- ctx = OpenSSL::SSL::SSLContext.new("TLSv1")
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
- ssl.sync_close = true
- ssl.session = last_session if last_session
- ssl.connect
+ ssl.hostname = "b.example.com"
+ assert_raise(OpenSSL::SSL::SSLError) { ssl.connect }
+ assert_equal false, verify_callback_ok
+ assert_equal OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH, verify_callback_err
+ ensure
+ sock&.close
+ end
+ end
+ end
+
+ def test_connect_certificate_verify_failed_exception_message
+ start_server(ignore_listener_error: true) { |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.set_params
+ assert_raise_with_message(OpenSSL::SSL::SSLError, /unable to get local issuer certificate/) {
+ server_connect(port, ctx)
+ }
+ }
+
+ ctx_proc = proc { |ctx|
+ now = Time.now
+ ctx.cert = issue_cert(@svr, @svr_key, 30, [], @ca_cert, @ca_key,
+ not_before: now - 7200, not_after: now - 3600)
+ }
+ start_server(ignore_listener_error: true, ctx_proc: ctx_proc) { |port|
+ store = OpenSSL::X509::Store.new
+ store.add_cert(@ca_cert)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.set_params(cert_store: store)
+ assert_raise_with_message(OpenSSL::SSL::SSLError, /expired/) {
+ server_connect(port, ctx)
+ }
+ }
+ end
+
+ def test_unset_OP_ALL
+ ctx_proc = Proc.new { |ctx|
+ # If OP_DONT_INSERT_EMPTY_FRAGMENTS is not defined, this test is
+ # redundant because the default options already are equal to OP_ALL.
+ # But it also degrades gracefully, so keep it
+ ctx.options = OpenSSL::SSL::OP_ALL
+ }
+ start_server(ctx_proc: ctx_proc) { |port|
+ server_connect(port) { |ssl|
+ ssl.puts('hello')
+ assert_equal("hello\n", ssl.gets)
+ }
+ }
+ end
+
+ def check_supported_protocol_versions
+ possible_versions = [
+ OpenSSL::SSL::SSL3_VERSION,
+ OpenSSL::SSL::TLS1_VERSION,
+ OpenSSL::SSL::TLS1_1_VERSION,
+ OpenSSL::SSL::TLS1_2_VERSION,
+ OpenSSL::SSL::TLS1_3_VERSION,
+ ]
+
+ supported = []
+ ctx_proc = proc { |ctx|
+ # The default security level is 1 in OpenSSL <= 3.1, 2 in OpenSSL >= 3.2
+ # In OpenSSL >= 3.0, TLS 1.1 or older is disabled at level 1
+ ctx.security_level = 0
+ # Explicitly reset them to avoid influenced by OPENSSL_CONF
+ ctx.min_version = ctx.max_version = nil
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
+ possible_versions.each do |ver|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.security_level = 0
+ ctx.min_version = ctx.max_version = ver
+ server_connect(port, ctx) { |ssl|
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ supported << ver
+ rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET
+ end
+ end
+
+ # Sanity check: in our test suite we assume these are always supported
+ assert_include(supported, OpenSSL::SSL::TLS1_2_VERSION)
+ assert_include(supported, OpenSSL::SSL::TLS1_3_VERSION)
+
+ supported
+ end
+
+ def test_set_params_min_version
+ supported = check_supported_protocol_versions
+ store = OpenSSL::X509::Store.new
+ store.add_cert(@ca_cert)
+
+ if supported.include?(OpenSSL::SSL::SSL3_VERSION)
+ # SSLContext#set_params properly disables SSL 3.0 by default
+ ctx_proc = proc { |ctx|
+ ctx.min_version = ctx.max_version = OpenSSL::SSL::SSL3_VERSION
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.set_params(cert_store: store, verify_hostname: false)
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) }
+ }
+ end
+ end
+
+ def test_minmax_version
+ supported = check_supported_protocol_versions
+
+ # name: The string that would be returned by SSL_get_version()
+ # method: The version-specific method name (if any)
+ vmap = {
+ OpenSSL::SSL::SSL3_VERSION => { name: "SSLv3", method: "SSLv3" },
+ OpenSSL::SSL::SSL3_VERSION => { name: "SSLv3", method: "SSLv3" },
+ OpenSSL::SSL::TLS1_VERSION => { name: "TLSv1", method: "TLSv1" },
+ OpenSSL::SSL::TLS1_1_VERSION => { name: "TLSv1.1", method: "TLSv1_1" },
+ OpenSSL::SSL::TLS1_2_VERSION => { name: "TLSv1.2", method: "TLSv1_2" },
+ OpenSSL::SSL::TLS1_3_VERSION => { name: "TLSv1.3", method: nil },
+ }
- session = ssl.session
- if last_session
- assert(ssl.session_reused?)
+ # Server enables a single version
+ supported.each do |ver|
+ ctx_proc = proc { |ctx|
+ ctx.security_level = 0
+ ctx.min_version = ctx.max_version = ver
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
+ supported.each do |cver|
+ # Client enables a single version
+ ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.security_level = 0
+ ctx1.min_version = ctx1.max_version = cver
+ if ver == cver
+ server_connect(port, ctx1) { |ssl|
+ assert_equal vmap[cver][:name], ssl.ssl_version
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ else
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) }
+ end
- if session.respond_to?(:id)
- assert_equal(session.id, last_session.id)
+ # There is no version-specific SSL methods for TLS 1.3
+ if cver <= OpenSSL::SSL::TLS1_2_VERSION
+ # Client enables a single version using #ssl_version=
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.security_level = 0
+ ctx2.ssl_version = vmap[cver][:method]
+ if ver == cver
+ server_connect(port, ctx2) { |ssl|
+ assert_equal vmap[cver][:name], ssl.ssl_version
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ else
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx2) }
+ end
end
- assert_equal(session.to_pem, last_session.to_pem)
- assert_equal(session.to_der, last_session.to_der)
- # Older version of OpenSSL may not be consistent. Look up which versions later.
- assert_equal(session.to_text, last_session.to_text)
+ end
+
+ # Client enables all supported versions
+ ctx3 = OpenSSL::SSL::SSLContext.new
+ ctx3.security_level = 0
+ ctx3.min_version = ctx3.max_version = nil
+ server_connect(port, ctx3) { |ssl|
+ assert_equal vmap[ver][:name], ssl.ssl_version
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ }
+ end
+
+ if supported.size == 1
+ pend "More than one protocol version must be supported"
+ end
+
+ # Server sets min_version (earliest is disabled)
+ sver = supported[1]
+ ctx_proc = proc { |ctx|
+ ctx.security_level = 0
+ ctx.min_version = sver
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
+ supported.each do |cver|
+ # Client sets min_version
+ ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.security_level = 0
+ ctx1.min_version = cver
+ ctx1.max_version = 0
+ server_connect(port, ctx1) { |ssl|
+ assert_equal vmap[supported.last][:name], ssl.ssl_version
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+
+ # Client sets max_version
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.security_level = 0
+ ctx2.min_version = 0
+ ctx2.max_version = cver
+ if cver >= sver
+ server_connect(port, ctx2) { |ssl|
+ assert_equal vmap[cver][:name], ssl.ssl_version
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
else
- assert(!ssl.session_reused?)
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx2) }
end
- last_session = session
+ end
+ }
- str = "x" * 100 + "\n"
- ssl.puts(str)
- assert_equal(str, ssl.gets)
+ # Server sets max_version (latest is disabled)
+ sver = supported[-2]
+ ctx_proc = proc { |ctx|
+ ctx.security_level = 0
+ ctx.min_version = 0
+ ctx.max_version = sver
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
+ supported.each do |cver|
+ # Client sets min_version
+ ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.min_version = cver
+ if cver <= sver
+ server_connect(port, ctx1) { |ssl|
+ assert_equal vmap[sver][:name], ssl.ssl_version
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ else
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) }
+ end
- ssl.close
+ # Client sets max_version
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.security_level = 0
+ ctx2.min_version = 0
+ ctx2.max_version = cver
+ server_connect(port, ctx2) { |ssl|
+ if cver >= sver
+ assert_equal vmap[sver][:name], ssl.ssl_version
+ else
+ assert_equal vmap[cver][:name], ssl.ssl_version
+ end
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ end
+ }
+ end
+
+ def test_minmax_version_system_default
+ omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc?
+
+ Tempfile.create("openssl.cnf") { |f|
+ f.puts(<<~EOF)
+ openssl_conf = default_conf
+ [default_conf]
+ ssl_conf = ssl_sect
+ [ssl_sect]
+ system_default = ssl_default_sect
+ [ssl_default_sect]
+ MaxProtocol = TLSv1.2
+ EOF
+ f.close
+
+ start_server(ignore_listener_error: true) do |port|
+ assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;")
+ sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.sync_close = true
+ ssl.connect
+ assert_equal("TLSv1.2", ssl.ssl_version)
+ ssl.puts("abc"); assert_equal("abc\n", ssl.gets)
+ ssl.close
+ end;
+
+ assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;")
+ sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
+ ctx.max_version = nil
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.sync_close = true
+ ssl.connect
+ assert_equal("TLSv1.3", ssl.ssl_version)
+ ssl.puts("abc"); assert_equal("abc\n", ssl.gets)
+ ssl.close
+ end;
+ end
+ }
+ end
+
+ def test_respect_system_default_min
+ omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc?
+
+ Tempfile.create("openssl.cnf") { |f|
+ f.puts(<<~EOF)
+ openssl_conf = default_conf
+ [default_conf]
+ ssl_conf = ssl_sect
+ [ssl_sect]
+ system_default = ssl_default_sect
+ [ssl_default_sect]
+ MinProtocol = TLSv1.3
+ EOF
+ f.close
+
+ ctx_proc = proc { |ctx|
+ ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
+ assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;")
+ sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.sync_close = true
+ assert_raise(OpenSSL::SSL::SSLError) do
+ ssl.connect
+ end
+ ssl.close
+ end;
+ end
+
+ ctx_proc = proc { |ctx|
+ ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
+ assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;")
+ sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.sync_close = true
+ ssl.connect
+ assert_equal("TLSv1.3", ssl.ssl_version)
+ ssl.puts("abc"); assert_equal("abc\n", ssl.gets)
+ ssl.close
+ end;
end
+ }
+ end
+
+ def test_options_disable_versions
+ # It's recommended to use SSLContext#{min,max}_version= instead in real
+ # applications. The purpose of this test case is to check that SSL options
+ # are properly propagated to OpenSSL library.
+ supported = check_supported_protocol_versions
+ if !supported.include?(OpenSSL::SSL::TLS1_2_VERSION) ||
+ !supported.include?(OpenSSL::SSL::TLS1_3_VERSION)
+ pend "this test case requires both TLS 1.2 and TLS 1.3 to be supported " \
+ "and enabled by default"
end
+
+ # Server disables TLS 1.2 and earlier
+ ctx_proc = proc { |ctx|
+ ctx.options |= OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 |
+ OpenSSL::SSL::OP_NO_TLSv1 | OpenSSL::SSL::OP_NO_TLSv1_1 |
+ OpenSSL::SSL::OP_NO_TLSv1_2
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
+ # Client only supports TLS 1.2
+ ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.min_version = ctx1.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) }
+
+ # Client only supports TLS 1.3
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.min_version = ctx2.max_version = OpenSSL::SSL::TLS1_3_VERSION
+ assert_nothing_raised { server_connect(port, ctx2) { } }
+ }
+
+ # Server only supports TLS 1.2
+ ctx_proc = proc { |ctx|
+ ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
+ # Client doesn't support TLS 1.2
+ ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.options |= OpenSSL::SSL::OP_NO_TLSv1_2
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) }
+
+ # Client supports TLS 1.2 by default
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.options |= OpenSSL::SSL::OP_NO_TLSv1_3
+ assert_nothing_raised { server_connect(port, ctx2) { } }
+ }
end
- def test_server_session
- connections = 0
- saved_session = nil
-
- ctx_proc = Proc.new do |ctx, ssl|
-# add test for session callbacks here
- end
-
- server_proc = Proc.new do |ctx, ssl|
- session = ssl.session
- stats = ctx.session_cache_stats
-
- case connections
- when 0
- assert_equal(stats[:cache_num], 1)
- assert_equal(stats[:cache_hits], 0)
- assert_equal(stats[:cache_misses], 0)
- assert(!ssl.session_reused?)
- when 1
- assert_equal(stats[:cache_num], 1)
- assert_equal(stats[:cache_hits], 1)
- assert_equal(stats[:cache_misses], 0)
- assert(ssl.session_reused?)
- ctx.session_remove(session)
- saved_session = session
- when 2
- assert_equal(stats[:cache_num], 1)
- assert_equal(stats[:cache_hits], 1)
- assert_equal(stats[:cache_misses], 1)
- assert(!ssl.session_reused?)
- ctx.session_add(saved_session)
- when 3
- assert_equal(stats[:cache_num], 2)
- assert_equal(stats[:cache_hits], 2)
- assert_equal(stats[:cache_misses], 1)
- assert(ssl.session_reused?)
- ctx.flush_sessions(Time.now + 5000)
- when 4
- assert_equal(stats[:cache_num], 1)
- assert_equal(stats[:cache_hits], 2)
- assert_equal(stats[:cache_misses], 2)
- assert(!ssl.session_reused?)
- ctx.session_add(saved_session)
- end
- connections += 1
-
- readwrite_loop(ctx, ssl)
+ def test_ssl_methods_constant
+ EnvUtil.suppress_warning { # Deprecated in v2.1.0
+ base = [:TLSv1_2, :TLSv1_1, :TLSv1, :SSLv3, :SSLv2, :SSLv23]
+ base.each do |name|
+ assert_include OpenSSL::SSL::SSLContext::METHODS, name
+ assert_include OpenSSL::SSL::SSLContext::METHODS, :"#{name}_client"
+ assert_include OpenSSL::SSL::SSLContext::METHODS, :"#{name}_server"
+ end
+ }
+ end
+
+ def test_renegotiation_cb
+ num_handshakes = 0
+ renegotiation_cb = Proc.new { |ssl| num_handshakes += 1 }
+ ctx_proc = Proc.new { |ctx| ctx.renegotiation_cb = renegotiation_cb }
+ start_server(ctx_proc: ctx_proc) { |port|
+ server_connect(port) { |ssl|
+ assert_equal(1, num_handshakes)
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ }
+ end
+
+ def test_alpn_protocol_selection_ary
+ advertised = ["http/1.1", "spdy/2"]
+ ctx_proc = Proc.new { |ctx|
+ ctx.alpn_select_cb = -> (protocols) {
+ protocols.first
+ }
+ ctx.alpn_protocols = advertised
+ }
+ start_server(ctx_proc: ctx_proc) { |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.alpn_protocols = advertised
+ server_connect(port, ctx) { |ssl|
+ assert_equal(advertised.first, ssl.alpn_protocol)
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ }
+ end
+
+ def test_alpn_protocol_selection_cancel
+ sock1, sock2 = socketpair
+
+ ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.cert = @svr_cert
+ ctx1.key = @svr_key
+ ctx1.alpn_select_cb = -> (protocols) { nil }
+ ssl1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1)
+
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.alpn_protocols = ["http/1.1"]
+ ssl2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+
+ t = Thread.new {
+ ssl2.connect_nonblock(exception: false)
+ }
+ assert_raise_with_message(TypeError, /nil/) { ssl1.accept }
+ t.join
+ ensure
+ sock1&.close
+ sock2&.close
+ ssl1&.close
+ ssl2&.close
+ t&.kill
+ t&.join
+ end
+
+ def test_npn_protocol_selection_ary
+ return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb)
+
+ advertised = ["http/1.1", "spdy/2"]
+ ctx_proc = proc { |ctx| ctx.npn_protocols = advertised }
+ start_server(ctx_proc: ctx_proc) { |port|
+ selector = lambda { |which|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.max_version = :TLS1_2
+ ctx.npn_select_cb = -> (protocols) { protocols.send(which) }
+ server_connect(port, ctx) { |ssl|
+ assert_equal(advertised.send(which), ssl.npn_protocol)
+ }
+ }
+ selector.call(:first)
+ selector.call(:last)
+ }
+ end
+
+ def test_npn_protocol_selection_enum
+ return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb)
+
+ advertised = Object.new
+ def advertised.each
+ yield "http/1.1"
+ yield "spdy/2"
end
+ ctx_proc = Proc.new { |ctx| ctx.npn_protocols = advertised }
+ start_server(ctx_proc: ctx_proc) { |port|
+ selector = lambda { |selected, which|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.max_version = :TLS1_2
+ ctx.npn_select_cb = -> (protocols) { protocols.to_a.send(which) }
+ server_connect(port, ctx) { |ssl|
+ assert_equal(selected, ssl.npn_protocol)
+ }
+ }
+ selector.call("http/1.1", :first)
+ selector.call("spdy/2", :last)
+ }
+ end
- first_session = nil
- start_server(PORT, OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_proc, :server_proc => server_proc) do |server, port|
- 10.times do |i|
- sock = TCPSocket.new("127.0.0.1", port)
+ def test_npn_protocol_selection_cancel
+ return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb)
+
+ ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.max_version = :TLS1_2
+ ctx.npn_select_cb = -> (protocols) { raise RuntimeError.new }
+ assert_raise(RuntimeError) { server_connect(port, ctx) }
+ }
+ end
+
+ def test_npn_advertised_protocol_too_long
+ return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb)
+
+ ctx = OpenSSL::SSL::SSLContext.new
+ assert_raise(OpenSSL::SSL::SSLError) do
+ ctx.npn_protocols = ["a" * 256]
+ ctx.setup
+ end
+ end
+
+ def test_npn_selected_protocol_too_long
+ return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb)
+
+ ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.max_version = :TLS1_2
+ ctx.npn_select_cb = -> (protocols) { "a" * 256 }
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) }
+ }
+ end
+
+ def readwrite_loop_safe(ctx, ssl)
+ readwrite_loop(ctx, ssl)
+ rescue OpenSSL::SSL::SSLError
+ end
+
+ def test_close_after_socket_close
+ start_server(server_proc: method(:readwrite_loop_safe)) { |port|
+ sock = TCPSocket.new("127.0.0.1", port)
+ ssl = OpenSSL::SSL::SSLSocket.new(sock)
+ ssl.connect
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ sock.close
+ assert_nothing_raised do
+ ssl.close
+ end
+ }
+ end
+
+ def test_sync_close_without_connect
+ Socket.open(:INET, :STREAM) {|s|
+ ssl = OpenSSL::SSL::SSLSocket.new(s)
+ ssl.sync_close = true
+ ssl.close
+ assert(s.closed?)
+ }
+ end
+
+ def test_get_ephemeral_key
+ # kRSA is not FIPS-approved.
+ omit_on_fips
+
+ # kRSA
+ ctx_proc1 = proc { |ctx|
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ ctx.ciphers = "kRSA"
+ }
+ start_server(ctx_proc: ctx_proc1, ignore_listener_error: true) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ ctx.ciphers = "kRSA"
+ begin
+ server_connect(port, ctx) { |ssl| assert_nil ssl.tmp_key }
+ rescue OpenSSL::SSL::SSLError
+ # kRSA seems disabled
+ raise unless $!.message =~ /no cipher/
+ end
+ end
+
+ # DHE
+ # OpenSSL 3.0 added support for named FFDHE groups in TLS 1.3
+ # LibreSSL does not support named FFDHE groups currently
+ # AWS-LC does not support DHE ciphersuites
+ if openssl?(3, 0, 0)
+ start_server do |port|
ctx = OpenSSL::SSL::SSLContext.new
- if defined?(OpenSSL::SSL::OP_NO_TICKET)
- # disable RFC4507 support
- ctx.options = OpenSSL::SSL::OP_NO_TICKET
+ ctx.groups = "ffdhe3072"
+ server_connect(port, ctx) { |ssl|
+ assert_instance_of OpenSSL::PKey::DH, ssl.tmp_key
+ assert_equal 3072, ssl.tmp_key.p.num_bits
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ end
+ end
+
+ # ECDHE
+ ctx_proc3 = proc { |ctx|
+ ctx.groups = "P-256"
+ }
+ start_server(ctx_proc: ctx_proc3) do |port|
+ server_connect(port) { |ssl|
+ assert_instance_of OpenSSL::PKey::EC, ssl.tmp_key
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ end
+ end
+
+ def test_fallback_scsv
+ supported = check_supported_protocol_versions
+ unless supported.include?(OpenSSL::SSL::TLS1_1_VERSION)
+ omit "TLS 1.1 support is required to run this test case"
+ end
+
+ omit "Fallback SCSV is not supported" if libressl?
+
+ start_server do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ # Here is OK
+ # TLS1.2 supported and this is what we ask the first time
+ server_connect(port, ctx)
+ end
+
+ ctx_proc = proc { |ctx|
+ ctx.security_level = 0
+ ctx.min_version = 0
+ ctx.max_version = OpenSSL::SSL::TLS1_1_VERSION
+ }
+ start_server(ctx_proc: ctx_proc) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.enable_fallback_scsv
+ ctx.security_level = 0
+ ctx.min_version = 0
+ ctx.max_version = OpenSSL::SSL::TLS1_1_VERSION
+ # Here is OK too
+ # TLS1.2 not supported, fallback to TLS1.1 and signaling the fallback
+ # Server doesn't support better, so connection OK
+ server_connect(port, ctx)
+ end
+
+ # Here is not OK
+ # TLS1.2 is supported, fallback to TLS1.1 (downgrade attack) and signaling the fallback
+ # Server support better, so refuse the connection
+ sock1, sock2 = socketpair
+ begin
+ # This test is for the downgrade protection mechanism of TLS1.2.
+ # This is why ctx1 bounds max_version == TLS1.2.
+ # Otherwise, this test fails when using openssl 1.1.1 (or later) that supports TLS1.3.
+ # TODO: We may need another test for TLS1.3 because it seems to have a different mechanism.
+ ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.security_level = 0
+ ctx1.min_version = 0
+ ctx1.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1)
+
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.enable_fallback_scsv
+ ctx2.security_level = 0
+ ctx2.min_version = 0
+ ctx2.max_version = OpenSSL::SSL::TLS1_1_VERSION
+ s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+ # AWS-LC has slightly different error messages in all-caps.
+ t = Thread.new {
+ assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback|INAPPROPRIATE_FALLBACK/) {
+ s2.connect
+ }
+ }
+ assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback|INAPPROPRIATE_FALLBACK/) {
+ s1.accept
+ }
+ t.join
+ ensure
+ sock1.close
+ sock2.close
+ end
+ end
+
+ def test_tmp_dh_callback
+ # DH missing the q value on unknown named parameters is not FIPS-approved.
+ omit_on_fips
+ omit "AWS-LC does not support DHE ciphersuites" if aws_lc?
+
+ dh = Fixtures.pkey("dh-1")
+ called = false
+ ctx_proc = -> ctx {
+ ctx.max_version = :TLS1_2
+ ctx.ciphers = "DH:!NULL"
+ ctx.tmp_dh_callback = ->(*args) {
+ called = true
+ dh
+ }
+ }
+ start_server(ctx_proc: ctx_proc) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.groups = "P-256" # Exclude RFC 7919 groups
+ server_connect(port, ctx) { |ssl|
+ assert called, "dh callback should be called"
+ assert_equal dh.to_der, ssl.tmp_key.to_der
+ }
+ end
+ end
+
+ def test_ciphersuites_method_tls_connection
+ csuite = ['TLS_AES_128_GCM_SHA256', 'TLSv1.3', 128, 128]
+ inputs = [csuite[0], [csuite[0]], [csuite]]
+
+ start_server do |port|
+ inputs.each do |input|
+ cli_ctx = OpenSSL::SSL::SSLContext.new
+ cli_ctx.min_version = cli_ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
+ cli_ctx.ciphersuites = input
+
+ server_connect(port, cli_ctx) do |ssl|
+ assert_equal('TLSv1.3', ssl.ssl_version)
+ assert_equal(csuite[0], ssl.cipher[0])
+ ssl.puts('abc'); assert_equal("abc\n", ssl.gets)
end
- ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
- ssl.sync_close = true
- ssl.session = first_session if first_session
- ssl.connect
+ end
+ end
+ end
- session = ssl.session
- if first_session
- case i
- when 1; assert(ssl.session_reused?)
- when 2; assert(!ssl.session_reused?)
- when 3; assert(ssl.session_reused?)
- when 4; assert(!ssl.session_reused?)
- when 5..10; assert(ssl.session_reused?)
- end
+ def test_ciphersuites_method_nil_argument
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+ assert_nothing_raised { ssl_ctx.ciphersuites = nil }
+ end
+
+ def test_ciphersuites_method_frozen_object
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+ ssl_ctx.freeze
+ assert_raise(FrozenError) { ssl_ctx.ciphersuites = 'TLS_AES_256_GCM_SHA384' }
+ end
+
+ def test_ciphersuites_method_bogus_csuite
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+ # AWS-LC has slightly different error messages in all-caps.
+ assert_raise_with_message(
+ OpenSSL::SSL::SSLError,
+ /SSL_CTX_set_ciphersuites: (no cipher match|NO_CIPHER_MATCH)/i
+ ) { ssl_ctx.ciphersuites = 'BOGUS' }
+ end
+
+ def test_ciphers_method_tls_connection
+ csuite = ['ECDHE-RSA-AES256-GCM-SHA384', 'TLSv1.2', 256, 256]
+ inputs = [csuite[0], [csuite[0]], [csuite]]
+
+ start_server do |port|
+ inputs.each do |input|
+ cli_ctx = OpenSSL::SSL::SSLContext.new
+ cli_ctx.min_version = cli_ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ cli_ctx.ciphers = input
+
+ server_connect(port, cli_ctx) do |ssl|
+ assert_equal('TLSv1.2', ssl.ssl_version)
+ assert_equal(csuite[0], ssl.cipher[0])
+ ssl.puts('abc'); assert_equal("abc\n", ssl.gets)
end
- first_session ||= session
+ end
+ end
+ end
- str = "x" * 100 + "\n"
- ssl.puts(str)
- assert_equal(str, ssl.gets)
+ def test_ciphers_method_nil_argument
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+ assert_nothing_raised { ssl_ctx.ciphers = nil }
+ end
- ssl.close
+ def test_ciphers_method_frozen_object
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+
+ ssl_ctx.freeze
+ assert_raise(FrozenError) { ssl_ctx.ciphers = 'ECDHE-RSA-AES128-SHA' }
+ end
+
+ def test_ciphers_method_bogus_csuite
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+
+ # AWS-LC has slightly different error messages in all-caps.
+ assert_raise_with_message(
+ OpenSSL::SSL::SSLError,
+ /SSL_CTX_set_cipher_list: (no cipher match|NO_CIPHER_MATCH)/i
+ ) { ssl_ctx.ciphers = 'BOGUS' }
+ end
+
+ def test_sigalgs
+ omit "SSL_CTX_set1_sigalgs_list() not supported" if libressl?
+
+ svr_exts = [
+ ["keyUsage", "keyEncipherment,digitalSignature", true],
+ ["subjectAltName", "DNS:localhost", false],
+ ]
+ ecdsa_key = Fixtures.pkey("p256")
+ ecdsa_cert = issue_cert(@svr, ecdsa_key, 10, svr_exts, @ca_cert, @ca_key)
+
+ ctx_proc = -> ctx {
+ # Unset values set by start_server
+ ctx.cert = ctx.key = ctx.extra_chain_cert = nil
+ ctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA
+ ctx.add_certificate(ecdsa_cert, ecdsa_key, [@ca_cert]) # ECDSA
+ }
+ start_server(ctx_proc: ctx_proc) do |port|
+ ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.sigalgs = "rsa_pss_rsae_sha256"
+ server_connect(port, ctx1) { |ssl|
+ assert_kind_of(OpenSSL::PKey::RSA, ssl.peer_cert.public_key)
+ ssl.puts("abc"); ssl.gets
+ }
+
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.sigalgs = "ed25519:ecdsa_secp256r1_sha256"
+ server_connect(port, ctx2) { |ssl|
+ assert_kind_of(OpenSSL::PKey::EC, ssl.peer_cert.public_key)
+ ssl.puts("abc"); ssl.gets
+ }
+ end
+
+ # Frozen
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+ ssl_ctx.freeze
+ assert_raise(FrozenError) { ssl_ctx.sigalgs = "ECDSA+SHA256:RSA+SHA256" }
+
+ # Bogus
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+ assert_raise(TypeError) { ssl_ctx.sigalgs = nil }
+ assert_raise(OpenSSL::SSL::SSLError) { ssl_ctx.sigalgs = "BOGUS" }
+ end
+
+ def test_client_sigalgs
+ omit "SSL_CTX_set1_client_sigalgs_list() not supported" if libressl? || aws_lc?
+
+ cli_exts = [
+ ["keyUsage", "keyEncipherment,digitalSignature", true],
+ ["subjectAltName", "DNS:localhost", false],
+ ]
+ ecdsa_key = Fixtures.pkey("p256")
+ ecdsa_cert = issue_cert(@cli, ecdsa_key, 10, cli_exts, @ca_cert, @ca_key)
+
+ ctx_proc = -> ctx {
+ store = OpenSSL::X509::Store.new
+ store.add_cert(@ca_cert)
+ store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
+ ctx.cert_store = store
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
+ ctx.client_sigalgs = "ECDSA+SHA256"
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
+ ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.add_certificate(@cli_cert, @cli_key) # RSA
+ assert_handshake_error {
+ server_connect(port, ctx1) { |ssl|
+ ssl.puts("abc"); ssl.gets
+ }
+ }
+
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.add_certificate(ecdsa_cert, ecdsa_key) # ECDSA
+ server_connect(port, ctx2) { |ssl|
+ ssl.puts("abc"); ssl.gets
+ }
+ end
+ end
+
+ def test_get_sigalg
+ # SSL_get0_signature_name() not supported
+ # SSL_get0_peer_signature_name() not supported
+ return unless openssl?(3, 5, 0)
+
+ server_proc = -> (ctx, ssl) {
+ assert_equal('rsa_pss_rsae_sha256', ssl.sigalg)
+ assert_nil(ssl.peer_sigalg)
+
+ readwrite_loop(ctx, ssl)
+ }
+ start_server(server_proc: server_proc) do |port|
+ cli_ctx = OpenSSL::SSL::SSLContext.new
+ server_connect(port, cli_ctx) do |ssl|
+ assert_nil(ssl.sigalg)
+ assert_equal('rsa_pss_rsae_sha256', ssl.peer_sigalg)
+ ssl.puts "abc"; ssl.gets
+ end
+ end
+ end
+
+ def test_pqc_sigalg
+ # PQC algorithm ML-DSA (FIPS 204) is supported on OpenSSL 3.5 or later.
+ return unless openssl?(3, 5, 0)
+
+ mldsa = Fixtures.pkey("mldsa65-1")
+ mldsa_ca_key = Fixtures.pkey("mldsa65-2")
+ mldsa_ca_cert = issue_cert(@ca, mldsa_ca_key, 1, @ca_exts, nil, nil,
+ digest: nil)
+ mldsa_cert = issue_cert(@svr, mldsa, 60, [], mldsa_ca_cert, mldsa_ca_key,
+ digest: nil)
+ rsa = Fixtures.pkey("rsa-1")
+ rsa_cert = issue_cert(@svr, rsa, 61, [], @ca_cert, @ca_key)
+ ctx_proc = -> ctx {
+ # Unset values set by start_server
+ ctx.cert = ctx.key = ctx.extra_chain_cert = nil
+ ctx.sigalgs = "rsa_pss_rsae_sha256:mldsa65"
+ ctx.add_certificate(mldsa_cert, mldsa)
+ ctx.add_certificate(rsa_cert, rsa)
+ }
+
+ server_proc = -> (ctx, ssl) {
+ assert_equal('mldsa65', ssl.sigalg)
+
+ readwrite_loop(ctx, ssl)
+ }
+ start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ # Set signature algorithm because while OpenSSL may use ML-DSA by
+ # default, the system OpenSSL configuration affects the used signature
+ # algorithm.
+ ctx.sigalgs = 'mldsa65'
+ server_connect(port, ctx) { |ssl|
+ assert_equal('mldsa65', ssl.peer_sigalg)
+ ssl.puts "abc"; ssl.gets
+ }
+ end
+
+ server_proc = -> (ctx, ssl) {
+ assert_equal('rsa_pss_rsae_sha256', ssl.sigalg)
+
+ readwrite_loop(ctx, ssl)
+ }
+ start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.sigalgs = 'rsa_pss_rsae_sha256'
+ server_connect(port, ctx) { |ssl|
+ assert_equal('rsa_pss_rsae_sha256', ssl.peer_sigalg)
+ ssl.puts "abc"; ssl.gets
+ }
+ end
+ end
+
+ def test_connect_works_when_setting_dh_callback_to_nil
+ omit "AWS-LC does not support DHE ciphersuites" if aws_lc?
+
+ ctx_proc = -> ctx {
+ ctx.max_version = :TLS1_2
+ ctx.ciphers = "DH:!NULL" # use DH
+ ctx.tmp_dh_callback = nil
+ }
+ start_server(ctx_proc: ctx_proc) do |port|
+ assert_nothing_raised { server_connect(port) { } }
+ end
+ end
+
+ def test_tmp_dh
+ # DH missing the q value on unknown named parameters is not FIPS-approved.
+ omit_on_fips
+ omit "AWS-LC does not support DHE ciphersuites" if aws_lc?
+
+ dh = Fixtures.pkey("dh-1")
+ ctx_proc = -> ctx {
+ ctx.max_version = :TLS1_2
+ ctx.ciphers = "DH:!NULL" # use DH
+ ctx.tmp_dh = dh
+ }
+ start_server(ctx_proc: ctx_proc) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.groups = "P-256" # Exclude RFC 7919 groups
+ server_connect(port, ctx) { |ssl|
+ assert_equal dh.to_der, ssl.tmp_key.to_der
+ }
+ end
+ end
+
+ def test_set_groups_tls12
+ ctx_proc = -> ctx {
+ # Enable both ECDHE (~ TLS 1.2) cipher suites and TLS 1.3
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ ctx.ciphers = "kEECDH"
+ ctx.groups = "P-384:P-521"
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
+ # Test 1: Client=P-256:P-384, Server=P-384:P-521 --> P-384
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.groups = "P-256:P-384"
+ server_connect(port, ctx) { |ssl|
+ cs = ssl.cipher[0]
+ assert_match (/\AECDH/), cs
+ # SSL_get0_group_name() is supported on OpenSSL 3.2 or later.
+ assert_equal "secp384r1", ssl.group if openssl?(3, 2, 0)
+ assert_equal "secp384r1", ssl.tmp_key.group.curve_name
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+
+ # Test 2: Client=P-256, Server=P-521:P-384 --> Fail
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.groups = "P-256"
+ assert_raise(OpenSSL::SSL::SSLError) {
+ server_connect(port, ctx) { }
+ }
+
+ # Test 3: Client=P-521:P-384, Server=P-521:P-384 --> P-521
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.groups = "P-521:P-384"
+ server_connect(port, ctx) { |ssl|
+ assert_equal "secp521r1", ssl.tmp_key.group.curve_name
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+
+ # Test 4: #ecdh_curves= alias
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.ecdh_curves = "P-256:P-384"
+ server_connect(port, ctx) { |ssl|
+ assert_equal "secp384r1", ssl.tmp_key.group.curve_name
+ }
+ end
+ end
+
+ def test_set_groups_tls13
+ ctx_proc = -> ctx {
+ # Assume TLS 1.3 is enabled and chosen by default
+ ctx.groups = "P-384:P-521"
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.groups = "P-256:P-384" # disable P-521
+
+ server_connect(port, ctx) { |ssl|
+ assert_equal "TLSv1.3", ssl.ssl_version
+ # SSL_get0_group_name() is supported on OpenSSL 3.2 or later.
+ assert_equal "secp384r1", ssl.group if openssl?(3, 2, 0)
+ assert_equal "secp384r1", ssl.tmp_key.group.curve_name
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ end
+ end
+
+ def test_pqc_group
+ # PQC algorithm ML-KEM (FIPS 203) is supported on OpenSSL 3.5 or later.
+ return unless openssl?(3, 5, 0)
+
+ [
+ 'X25519MLKEM768',
+ 'SecP256r1MLKEM768',
+ 'SecP384r1MLKEM1024'
+ ].each do |group|
+ ctx_proc = -> ctx {
+ ctx.groups = group
+ }
+ start_server(ctx_proc: ctx_proc) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.groups = group
+ server_connect(port, ctx) { |ssl|
+ assert_equal(group, ssl.group)
+ ssl.puts "abc"; ssl.gets
+ }
end
end
end
+
+ def test_security_level
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.security_level = 1
+ if aws_lc? # AWS-LC does not support security levels.
+ assert_equal(0, ctx.security_level)
+ return
+ end
+ assert_equal(1, ctx.security_level)
+
+ # See SSL_CTX_set_security_level(3). Definitions of security levels may
+ # change in future OpenSSL versions. As of OpenSSL 1.1.0:
+ # - Level 1 requires 160-bit ECC keys or 1024-bit RSA keys.
+ # - Level 2 requires 224-bit ECC keys or 2048-bit RSA keys.
+ begin
+ ec112 = OpenSSL::PKey::EC.generate("secp112r1")
+ ec112_cert = issue_cert(@svr, ec112, 50, [], @ca_cert, @ca_key)
+ ec192 = OpenSSL::PKey::EC.generate("prime192v1")
+ ec192_cert = issue_cert(@svr, ec192, 51, [], @ca_cert, @ca_key)
+ rescue OpenSSL::PKey::PKeyError
+ # Distro-provided OpenSSL may refuse to generate small keys
+ return
+ end
+
+ assert_raise(OpenSSL::SSL::SSLError) {
+ ctx.add_certificate(ec112_cert, ec112)
+ }
+ assert_nothing_raised {
+ ctx.add_certificate(ec192_cert, ec192)
+ }
+ ctx.security_level = 2
+ assert_raise(OpenSSL::SSL::SSLError) {
+ # < 112 bits of security
+ ctx.add_certificate(ec192_cert, ec192)
+ }
+ end
+
+ def test_dup
+ ctx = OpenSSL::SSL::SSLContext.new
+ sock1, sock2 = socketpair
+ ssl = OpenSSL::SSL::SSLSocket.new(sock1, ctx)
+
+ assert_raise(NoMethodError) { ctx.dup }
+ assert_raise(NoMethodError) { ssl.dup }
+ ensure
+ ssl.close if ssl
+ sock1.close
+ sock2.close
+ end
+
+ def test_freeze_calls_setup
+ bug = "[ruby/openssl#85]"
+ start_server(ignore_listener_error: true) { |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ ctx.freeze
+ assert_raise(OpenSSL::SSL::SSLError, bug) {
+ server_connect(port, ctx)
+ }
+ }
+ 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
+
+ def test_export_keying_material
+ start_server do |port|
+ cli_ctx = OpenSSL::SSL::SSLContext.new
+ server_connect(port, cli_ctx) do |ssl|
+ assert_instance_of(String, ssl.export_keying_material('ttls keying material', 64))
+ assert_operator(64, :==, ssl.export_keying_material('ttls keying material', 64).b.length)
+ assert_operator(8, :==, ssl.export_keying_material('ttls keying material', 8).b.length)
+ assert_operator(5, :==, ssl.export_keying_material('test', 5, 'context').b.length)
+ ssl.puts "abc"; ssl.gets # workaround to make tests work on windows
+ end
+ end
+ end
+
+ # OpenSSL::Buffering requires $/ accessible from non-main Ractors (Ruby 4.0)
+ # https://bugs.ruby-lang.org/issues/21109
+ #
+ # Hangs on Windows
+ # https://bugs.ruby-lang.org/issues/21537
+ if respond_to?(:ractor) && RUBY_VERSION >= "4.0" && RUBY_PLATFORM !~ /mswin|mingw/
+ ractor
+ def test_ractor_client
+ start_server { |port|
+ s = Ractor.new(port, @ca_cert) { |port, ca_cert|
+ sock = TCPSocket.new("127.0.0.1", port)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ ctx.cert_store = OpenSSL::X509::Store.new.tap { |store|
+ store.add_cert(ca_cert)
+ }
+ begin
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.connect
+ ssl.puts("abc")
+ ssl.gets
+ ensure
+ ssl.close
+ sock.close
+ end
+ }.value
+ assert_equal("abc\n", s)
+ }
+ end
+
+ ractor
+ def test_ractor_set_params
+ # We cannot actually test default stores in the test suite as it depends
+ # on the environment, but at least check that it does not raise an
+ # exception
+ ok = Ractor.new {
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.set_params
+ ctx.cert_store.kind_of?(OpenSSL::X509::Store)
+ }.value
+ assert(ok, "ctx.cert_store is an instance of OpenSSL::X509::Store")
+ end
+ end
+
+ private
+
+ def server_connect(port, ctx = nil)
+ sock = TCPSocket.new("127.0.0.1", port)
+ ssl = ctx ? OpenSSL::SSL::SSLSocket.new(sock, ctx) : OpenSSL::SSL::SSLSocket.new(sock)
+ ssl.sync_close = true
+ ssl.connect
+ yield ssl if block_given?
+ ensure
+ if ssl
+ ssl.close
+ elsif sock
+ sock.close
+ end
+ end
+
+ def assert_handshake_error
+ # different OpenSSL versions react differently when facing a SSL/TLS version
+ # that has been marked as forbidden, therefore any of these may be raised
+ assert_raise(OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::EPIPE) {
+ yield
+ }
+ end
end
end
diff --git a/test/openssl/test_ssl_session.rb b/test/openssl/test_ssl_session.rb
new file mode 100644
index 0000000000..37874ca273
--- /dev/null
+++ b/test/openssl/test_ssl_session.rb
@@ -0,0 +1,443 @@
+# frozen_string_literal: true
+require_relative "utils"
+
+if defined?(OpenSSL::SSL)
+
+class OpenSSL::TestSSLSession < OpenSSL::SSLTestCase
+ def test_session
+ ctx_proc = proc { |ctx|
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ }
+ start_server(ctx_proc: ctx_proc) do |port|
+ server_connect_with_session(port, nil, nil) { |ssl|
+ session = ssl.session
+ assert(session == OpenSSL::SSL::Session.new(session.to_pem))
+ assert(session == OpenSSL::SSL::Session.new(ssl))
+ session.timeout = 5
+ assert_equal(5, session.timeout)
+ assert_not_nil(session.time)
+ # SSL_SESSION_time keeps long value so we can't keep nsec fragment.
+ session.time = t1 = Time.now.to_i
+ assert_equal(Time.at(t1), session.time)
+ assert_not_nil(session.id)
+ pem = session.to_pem
+ assert_match(/\A-----BEGIN SSL SESSION PARAMETERS-----/, pem)
+ assert_match(/-----END SSL SESSION PARAMETERS-----\Z/, pem)
+ pem.gsub!(/-----(BEGIN|END) SSL SESSION PARAMETERS-----/, '').gsub!(/[\r\n]+/m, '')
+ assert_equal(session.to_der, pem.unpack1('m'))
+ assert_not_nil(session.to_text)
+ }
+ end
+ end
+
+ # PEM file updated to use TLS 1.2 with ECDHE-RSA-AES256-SHA.
+ DUMMY_SESSION = <<__EOS__
+-----BEGIN SSL SESSION PARAMETERS-----
+MIIDzQIBAQICAwMEAsAUBCAF219w9ZEV8dNA60cpEGOI34hJtIFbf3bkfzSgMyad
+MQQwyGLbkCxE4OiMLdKKem+pyh8V7ifoP7tCxhdmwoDlJxI1v6nVCjai+FGYuncy
+NNSWoQYCBE4DDWuiAwIBCqOCAo4wggKKMIIBcqADAgECAgECMA0GCSqGSIb3DQEB
+BQUAMD0xEzARBgoJkiaJk/IsZAEZFgNvcmcxGTAXBgoJkiaJk/IsZAEZFglydWJ5
+LWxhbmcxCzAJBgNVBAMMAkNBMB4XDTExMDYyMzA5NTQ1MVoXDTExMDYyMzEwMjQ1
+MVowRDETMBEGCgmSJomT8ixkARkWA29yZzEZMBcGCgmSJomT8ixkARkWCXJ1Ynkt
+bGFuZzESMBAGA1UEAwwJbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
+iQKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7CxaKPERYHs
+k4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbCz0layNqHyywQEVLFmp1cpIt/Q3geLv8Z
+D9pihowKJDyMDiN6ArYUmZczvW4976MU3+l54E6lF/JfFEU5hwIDAQABoxIwEDAO
+BgNVHQ8BAf8EBAMCBaAwDQYJKoZIhvcNAQEFBQADggEBACj5WhoZ/ODVeHpwgq1d
+8fW/13ICRYHYpv6dzlWihyqclGxbKMlMnaVCPz+4JaVtMz3QB748KJQgL3Llg3R1
+ek+f+n1MBCMfFFsQXJ2gtLB84zD6UCz8aaCWN5/czJCd7xMz7fRLy3TOIW5boXAU
+zIa8EODk+477K1uznHm286ab0Clv+9d304hwmBZgkzLg6+31Of6d6s0E0rwLGiS2
+sOWYg34Y3r4j8BS9Ak4jzpoLY6cJ0QAKCOJCgmjGr4XHpyXMLbicp3ga1uSbwtVO
+gF/gTfpLhJC+y0EQ5x3Ftl88Cq7ZJuLBDMo/TLIfReJMQu/HlrTT7+LwtneSWGmr
+KkSkAgQApQMCAROqgcMEgcAuDkAVfj6QAJMz9yqTzW5wPFyty7CxUEcwKjUqj5UP
+/Yvky1EkRuM/eQfN7ucY+MUvMqv+R8ZSkHPsnjkBN5ChvZXjrUSZKFVjR4eFVz2V
+jismLEJvIFhQh6pqTroRrOjMfTaM5Lwoytr2FTGobN9rnjIRsXeFQW1HLFbXn7Dh
+8uaQkMwIVVSGRB8T7t6z6WIdWruOjCZ6G5ASI5XoqAHwGezhLodZuvJEfsVyCF9y
+j+RBGfCFrrQbBdnkFI/ztgM=
+-----END SSL SESSION PARAMETERS-----
+__EOS__
+
+ # PEM file updated to use TLS 1.1 with ECDHE-RSA-AES256-SHA.
+ DUMMY_SESSION_NO_EXT = <<-__EOS__
+-----BEGIN SSL SESSION PARAMETERS-----
+MIIDCAIBAQICAwIEAsAUBCDyAW7rcpzMjDSosH+Tv6sukymeqgq3xQVVMez628A+
+lAQw9TrKzrIqlHEh6ltuQaqv/Aq83AmaAlogYktZgXAjOGnhX7ifJDNLMuCfQq53
+hPAaoQYCBE4iDeeiBAICASyjggKOMIICijCCAXKgAwIBAgIBAjANBgkqhkiG9w0B
+AQUFADA9MRMwEQYKCZImiZPyLGQBGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVi
+eS1sYW5nMQswCQYDVQQDDAJDQTAeFw0xMTA3MTYyMjE3MTFaFw0xMTA3MTYyMjQ3
+MTFaMEQxEzARBgoJkiaJk/IsZAEZFgNvcmcxGTAXBgoJkiaJk/IsZAEZFglydWJ5
+LWxhbmcxEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEAy8LEsNRApz7U/j5DoB4XBgO9Z8Atv5y/OVQRp0ag8Tqo1YewsWijxEWB
+7JOATwpBN267U4T1nPZIxxEEO7n/WNa2ws9JWsjah8ssEBFSxZqdXKSLf0N4Hi7/
+GQ/aYoaMCiQ8jA4jegK2FJmXM71uPe+jFN/peeBOpRfyXxRFOYcCAwEAAaMSMBAw
+DgYDVR0PAQH/BAQDAgWgMA0GCSqGSIb3DQEBBQUAA4IBAQA3TRzABRG3kz8jEEYr
+tDQqXgsxwTsLhTT5d1yF0D8uFw+y15hJAJnh6GJHjqhWBrF4zNoTApFo+4iIL6g3
+q9C3mUsxIVAHx41DwZBh/FI7J4FqlAoGOguu7892CNVY3ZZjc3AXMTdKjcNoWPzz
+FCdj5fNT24JMMe+ZdGZK97ChahJsdn/6B3j6ze9NK9mfYEbiJhejGTPLOFVHJCGR
+KYYZ3ZcKhLDr9ql4d7cCo1gBtemrmFQGPui7GttNEqmXqUKvV8mYoa8farf5i7T4
+L6a/gp2cVZTaDIS1HjbJsA/Ag7AajZqiN6LfqShNUVsrMZ+5CoV8EkBDTZPJ9MSr
+a3EqpAIEAKUDAgET
+-----END SSL SESSION PARAMETERS-----
+__EOS__
+
+
+ def test_session_time
+ sess = OpenSSL::SSL::Session.new(DUMMY_SESSION_NO_EXT)
+ sess.time = (now = Time.now)
+ assert_equal(now.to_i, sess.time.to_i)
+ sess.time = 1
+ assert_equal(1, sess.time.to_i)
+ sess.time = 1.2345
+ assert_equal(1, sess.time.to_i)
+ # Can OpenSSL handle t>2038y correctly? Version?
+ sess.time = 2**31 - 1
+ assert_equal(2**31 - 1, sess.time.to_i)
+ end
+
+ def test_session_timeout
+ sess = OpenSSL::SSL::Session.new(DUMMY_SESSION_NO_EXT)
+ assert_raise(TypeError) do
+ sess.timeout = Time.now
+ end
+ sess.timeout = 1
+ assert_equal(1, sess.timeout.to_i)
+ sess.timeout = 1.2345
+ assert_equal(1, sess.timeout.to_i)
+ sess.timeout = 2**31 - 1
+ assert_equal(2**31 - 1, sess.timeout.to_i)
+ end
+
+ def test_session_exts_read
+ assert(OpenSSL::SSL::Session.new(DUMMY_SESSION))
+ end
+
+ def test_resumption
+ non_resumable = nil
+ start_server { |port|
+ server_connect_with_session(port, nil, nil) { |ssl|
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ non_resumable = ssl.session
+ }
+ }
+
+ ctx_proc = proc { |ctx|
+ ctx.options &= ~OpenSSL::SSL::OP_NO_TICKET
+ # Disable server-side session cache which is enabled by default
+ ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_OFF
+ # Session tickets must be retrieved via ctx.session_new_cb in TLS 1.3 in AWS-LC.
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl? || aws_lc?
+ }
+ start_server(ctx_proc: ctx_proc) do |port|
+ sess1 = server_connect_with_session(port, nil, nil) { |ssl|
+ ssl.puts("abc"); assert_equal "abc\n", ssl.gets
+ assert_equal false, ssl.session_reused?
+ ssl.session
+ }
+
+ server_connect_with_session(port, nil, non_resumable) { |ssl|
+ ssl.puts("abc"); assert_equal "abc\n", ssl.gets
+ assert_equal false, ssl.session_reused?
+ }
+
+ server_connect_with_session(port, nil, sess1) { |ssl|
+ ssl.puts("abc"); assert_equal "abc\n", ssl.gets
+ assert_equal true, ssl.session_reused?
+ }
+ end
+ end
+
+ def test_server_session_cache
+ ctx_proc = Proc.new do |ctx|
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ ctx.options |= OpenSSL::SSL::OP_NO_TICKET
+ end
+
+ connections = nil
+ saved_session = nil
+ server_proc = Proc.new do |ctx, ssl|
+ stats = ctx.session_cache_stats
+
+ case connections
+ when 0
+ assert_equal false, ssl.session_reused?
+ assert_equal 1, stats[:cache_num]
+ assert_equal 0, stats[:cache_hits]
+ assert_equal 0, stats[:cache_misses]
+ when 1
+ assert_equal true, ssl.session_reused?
+ assert_equal 1, stats[:cache_num]
+ assert_equal 1, stats[:cache_hits]
+ assert_equal 0, stats[:cache_misses]
+
+ saved_session = ssl.session
+ assert_equal true, ctx.session_remove(ssl.session)
+ when 2
+ assert_equal false, ssl.session_reused?
+ assert_equal 1, stats[:cache_num]
+ assert_equal 1, stats[:cache_hits]
+ assert_equal 1, stats[:cache_misses]
+
+ assert_equal true, ctx.session_add(saved_session.dup)
+ when 3
+ assert_equal true, ssl.session_reused?
+ assert_equal 2, stats[:cache_num]
+ assert_equal 2, stats[:cache_hits]
+ assert_equal 1, stats[:cache_misses]
+
+ ctx.flush_sessions(Time.now + 10000)
+ when 4
+ assert_equal false, ssl.session_reused?
+ assert_equal 1, stats[:cache_num]
+ assert_equal 2, stats[:cache_hits]
+ assert_equal 2, stats[:cache_misses]
+
+ assert_equal true, ctx.session_add(saved_session.dup)
+ end
+
+ readwrite_loop(ctx, ssl)
+ end
+
+ start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port|
+ first_session = nil
+ 10.times do |i|
+ connections = i
+ cctx = OpenSSL::SSL::SSLContext.new
+ cctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ server_connect_with_session(port, cctx, first_session) { |ssl|
+ ssl.puts("abc"); assert_equal "abc\n", ssl.gets
+ first_session ||= ssl.session
+
+ case connections
+ when 0;
+ when 1; assert_equal true, ssl.session_reused?
+ when 2; assert_equal false, ssl.session_reused?
+ when 3; assert_equal true, ssl.session_reused?
+ when 4; assert_equal false, ssl.session_reused?
+ when 5..9; assert_equal true, ssl.session_reused?
+ end
+ }
+ end
+ end
+ end
+
+ # Skipping tests that use session_remove_cb by default because it may cause
+ # deadlock.
+ TEST_SESSION_REMOVE_CB = ENV["OSSL_TEST_UNSAFE"] == "1"
+
+ def test_ctx_client_session_cb_tls12
+ start_server do |port|
+ called = {}
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.min_version = ctx.max_version = :TLS1_2
+ ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT
+ ctx.session_new_cb = lambda { |ary|
+ sock, sess = ary
+ called[:new] = [sock, sess]
+ }
+ if TEST_SESSION_REMOVE_CB
+ ctx.session_remove_cb = lambda { |ary|
+ ctx, sess = ary
+ called[:remove] = [ctx, sess]
+ }
+ end
+
+ server_connect_with_session(port, ctx, nil) { |ssl|
+ assert_equal(1, ctx.session_cache_stats[:connect_good])
+ assert_equal([ssl, ssl.session], called[:new])
+ # AWS-LC doesn't support internal session caching on the client, but
+ # the callback is still enabled as expected.
+ unless aws_lc?
+ assert_equal(1, ctx.session_cache_stats[:cache_num])
+ assert_equal(true, ctx.session_remove(ssl.session))
+ if TEST_SESSION_REMOVE_CB
+ assert_equal([ctx, ssl.session], called[:remove])
+ end
+ end
+ assert_equal(false, ctx.session_remove(ssl.session))
+ }
+ end
+ end
+
+ def test_ctx_client_session_cb_tls13
+ omit "LibreSSL does not call session_new_cb in TLS 1.3" if libressl?
+ omit "AWS-LC does not support internal session caching on the client" if aws_lc?
+
+ start_server do |port|
+ called = {}
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.min_version = :TLS1_3
+ ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT
+ ctx.session_new_cb = lambda { |ary|
+ sock, sess = ary
+ called[:new] = [sock, sess]
+ }
+
+ server_connect_with_session(port, ctx, nil) { |ssl|
+ ssl.puts("abc"); assert_equal("abc\n", ssl.gets)
+
+ assert_operator(1, :<=, ctx.session_cache_stats[:cache_num])
+ assert_operator(1, :<=, ctx.session_cache_stats[:connect_good])
+ assert_equal([ssl, ssl.session], called[:new])
+ }
+ end
+ end
+
+ def test_ctx_client_session_cb_tls13_exception
+ omit "LibreSSL does not call session_new_cb in TLS 1.3" if libressl?
+
+ server_proc = lambda do |ctx, ssl|
+ readwrite_loop(ctx, ssl)
+ rescue SystemCallError, OpenSSL::SSL::SSLError
+ end
+ start_server(server_proc: server_proc) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.min_version = :TLS1_3
+ ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT
+ ctx.session_new_cb = lambda { |ary|
+ raise "in session_new_cb"
+ }
+
+ server_connect_with_session(port, ctx, nil) { |ssl|
+ assert_raise_with_message(RuntimeError, /in session_new_cb/) {
+ ssl.puts("abc"); assert_equal("abc\n", ssl.gets)
+ }
+ }
+ end
+ end
+
+ def test_ctx_server_session_cb
+ connections = nil
+ called = {}
+ cctx = OpenSSL::SSL::SSLContext.new
+ cctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ sctx = nil
+ ctx_proc = Proc.new { |ctx|
+ sctx = ctx
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ ctx.options |= OpenSSL::SSL::OP_NO_TICKET
+
+ # get_cb is called whenever a client proposed to resume a session but
+ # the session could not be found in the internal session cache.
+ last_server_session = nil
+ ctx.session_get_cb = lambda { |ary|
+ _sess, data = ary
+ called[:get] = data
+
+ if connections == 2
+ last_server_session.dup
+ else
+ nil
+ end
+ }
+
+ ctx.session_new_cb = lambda { |ary|
+ _sock, sess = ary
+ called[:new] = sess
+ last_server_session = sess
+ }
+
+ if TEST_SESSION_REMOVE_CB
+ ctx.session_remove_cb = lambda { |ary|
+ _ctx, sess = ary
+ called[:remove] = sess
+ }
+ end
+ }
+ start_server(ctx_proc: ctx_proc) do |port|
+ connections = 0
+ sess0 = server_connect_with_session(port, cctx, nil) { |ssl|
+ ssl.puts("abc"); assert_equal "abc\n", ssl.gets
+ assert_equal false, ssl.session_reused?
+ ssl.session
+ }
+ assert_nil called[:get]
+ assert_not_nil called[:new]
+ assert_equal sess0.id, called[:new].id
+ if TEST_SESSION_REMOVE_CB
+ assert_nil called[:remove]
+ end
+ called.clear
+
+ # Internal cache hit
+ connections = 1
+ server_connect_with_session(port, cctx, sess0.dup) { |ssl|
+ ssl.puts("abc"); assert_equal "abc\n", ssl.gets
+ assert_equal true, ssl.session_reused?
+ ssl.session
+ }
+ assert_nil called[:get]
+ assert_nil called[:new]
+ if TEST_SESSION_REMOVE_CB
+ assert_nil called[:remove]
+ end
+ called.clear
+
+ sctx.flush_sessions(Time.now + 10000)
+ if TEST_SESSION_REMOVE_CB
+ assert_not_nil called[:remove]
+ assert_equal sess0.id, called[:remove].id
+ end
+ called.clear
+
+ # External cache hit
+ connections = 2
+ sess2 = server_connect_with_session(port, cctx, sess0.dup) { |ssl|
+ ssl.puts("abc"); assert_equal "abc\n", ssl.gets
+ assert_equal true, ssl.session_reused?
+ ssl.session
+ }
+ assert_equal sess0.id, sess2.id
+ assert_equal sess0.id, called[:get]
+ assert_nil called[:new]
+ if TEST_SESSION_REMOVE_CB
+ assert_nil called[:remove]
+ end
+ called.clear
+
+ sctx.flush_sessions(Time.now + 10000)
+ if TEST_SESSION_REMOVE_CB
+ assert_not_nil called[:remove]
+ assert_equal sess0.id, called[:remove].id
+ end
+ called.clear
+
+ # Cache miss
+ connections = 3
+ sess3 = server_connect_with_session(port, cctx, sess0.dup) { |ssl|
+ ssl.puts("abc"); assert_equal "abc\n", ssl.gets
+ assert_equal false, ssl.session_reused?
+ ssl.session
+ }
+ assert_not_equal sess0.id, sess3.id
+ assert_equal sess0.id, called[:get]
+ assert_not_nil called[:new]
+ assert_equal sess3.id, called[:new].id
+ if TEST_SESSION_REMOVE_CB
+ assert_nil called[:remove]
+ end
+ end
+ end
+
+ def test_dup
+ sess_orig = OpenSSL::SSL::Session.new(DUMMY_SESSION)
+ sess_dup = sess_orig.dup
+ assert_equal(sess_orig.to_der, sess_dup.to_der)
+ end
+
+ private
+
+ def server_connect_with_session(port, ctx = nil, sess = nil)
+ sock = TCPSocket.new("127.0.0.1", port)
+ ctx ||= OpenSSL::SSL::SSLContext.new
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.session = sess if sess
+ ssl.sync_close = true
+ ssl.connect
+ yield ssl if block_given?
+ ensure
+ ssl&.close
+ sock&.close
+ end
+end
+
+end
diff --git a/test/openssl/test_ts.rb b/test/openssl/test_ts.rb
new file mode 100644
index 0000000000..69780a6579
--- /dev/null
+++ b/test/openssl/test_ts.rb
@@ -0,0 +1,650 @@
+require_relative "utils"
+
+if defined?(OpenSSL) && defined?(OpenSSL::Timestamp)
+
+class OpenSSL::TestTimestamp < OpenSSL::TestCase
+ def intermediate_key
+ @intermediate_key ||= Fixtures.pkey("rsa-1")
+ end
+
+ def ee_key
+ @ee_key ||= Fixtures.pkey("rsa-2")
+ 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_request_mandatory_fields
+ req = OpenSSL::Timestamp::Request.new
+ assert_raise(OpenSSL::Timestamp::TimestampError) do
+ req.to_der
+ end
+ req.algorithm = "sha1"
+ assert_raise(OpenSSL::Timestamp::TimestampError) do
+ req.to_der
+ end
+ req.message_imprint = OpenSSL::Digest.digest('SHA1', "data")
+ assert_nothing_raised { 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_equal("SHA1", OpenSSL::ASN1.ObjectId("SHA1").sn)
+ 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.digest('SHA1', "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_request_invalid_asn1
+ assert_raise(OpenSSL::Timestamp::TimestampError) do
+ OpenSSL::Timestamp::Request.new("*" * 44)
+ end
+ 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.digest('SHA1', "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_failure_info
+ resp = OpenSSL::Timestamp::Response.new("0\"0 \x02\x01\x020\x17\f\x15Invalid TimeStampReq.\x03\x02\x06\x80")
+ assert_equal(:BAD_ALG, resp.failure_info)
+ 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.digest('SHA1', "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.digest('SHA1', "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.new('SHA1')]
+ 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.digest('SHA256', "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.new('SHA1'), 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.digest('SHA1', "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)
+
+ assert_match(/1\.2\.3\.4\.6/, resp.to_text)
+ end
+
+ def test_response_bad_purpose
+ req = OpenSSL::Timestamp::Request.new
+ req.algorithm = "SHA1"
+ digest = OpenSSL::Digest.digest('SHA1', "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_response_invalid_asn1
+ assert_raise(OpenSSL::Timestamp::TimestampError) do
+ OpenSSL::Timestamp::Response.new("*" * 44)
+ end
+ end
+
+ def test_no_cert_requested
+ req = OpenSSL::Timestamp::Request.new
+ req.algorithm = "SHA1"
+ digest = OpenSSL::Digest.digest('SHA1', "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
+ req = OpenSSL::Timestamp::Request.new
+ req.algorithm = "SHA1"
+ digest = OpenSSL::Digest.digest('SHA1', "test")
+ req.message_imprint = digest
+
+ 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, ts_cert_ee, req)
+ end
+ end
+
+ def test_verify_ee_no_req
+ ts, _ = timestamp_ee
+ assert_raise(TypeError) do
+ ts.verify(nil, ca_cert)
+ end
+ end
+
+ def test_verify_ee_no_store
+ ts, req = timestamp_ee
+ assert_raise(TypeError) do
+ ts.verify(req, nil)
+ end
+ end
+
+ def test_verify_ee_wrong_root_no_intermediate
+ ts, req = timestamp_ee
+ assert_raise(OpenSSL::Timestamp::TimestampError) do
+ ts.verify(req, intermediate_store)
+ end
+ end
+
+ def test_verify_ee_wrong_root_wrong_intermediate
+ ts, req = timestamp_ee
+ assert_raise(OpenSSL::Timestamp::TimestampError) do
+ ts.verify(req, intermediate_store, [ca_cert])
+ end
+ end
+
+ def test_verify_ee_nonce_mismatch
+ ts, req = timestamp_ee
+ req.nonce = 1
+ assert_raise(OpenSSL::Timestamp::TimestampError) do
+ ts.verify(req, ca_store, [intermediate_cert])
+ end
+ end
+
+ def test_verify_ee_intermediate_missing
+ ts, req = timestamp_ee
+ assert_raise(OpenSSL::Timestamp::TimestampError) do
+ 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.digest('SHA1', "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
+ ts, req = timestamp_direct
+ assert_raise(OpenSSL::Timestamp::TimestampError) do
+ ts.verify(req, intermediate_store)
+ end
+ end
+
+ def test_verify_direct_no_cert_no_intermediate
+ ts, req = timestamp_direct_no_cert
+ assert_raise(OpenSSL::Timestamp::TimestampError) do
+ ts.verify(req, ca_store)
+ end
+ end
+
+ def test_verify_ee_no_cert
+ ts, req = timestamp_ee_no_cert
+ assert_same(ts, ts.verify(req, ca_store, [ts_cert_ee, intermediate_cert]))
+ end
+
+ def test_verify_ee_no_cert_no_intermediate
+ ts, req = timestamp_ee_no_cert
+ assert_raise(OpenSSL::Timestamp::TimestampError) do
+ 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.digest('SHA1', "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.digest('SHA1', "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.digest('SHA1', "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.digest('SHA1', "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.digest('SHA1', "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
+
+ def test_token_info_invalid_asn1
+ assert_raise(OpenSSL::Timestamp::TimestampError) do
+ OpenSSL::Timestamp::TokenInfo.new("*" * 44)
+ end
+ 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.digest('SHA1', "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.digest('SHA1', "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.digest('SHA1', "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.digest('SHA1', "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
new file mode 100644
index 0000000000..bec03a1871
--- /dev/null
+++ b/test/openssl/test_x509attr.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+require_relative "utils"
+
+if defined?(OpenSSL)
+
+class OpenSSL::TestX509Attribute < OpenSSL::TestCase
+ def test_new
+ ef = OpenSSL::X509::ExtensionFactory.new
+ val = OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([
+ ef.create_extension("keyUsage", "keyCertSign", true)
+ ])])
+ attr = OpenSSL::X509::Attribute.new("extReq", val)
+ assert_equal("extReq", attr.oid)
+ assert_equal(val.to_der, attr.value.to_der)
+
+ attr = OpenSSL::X509::Attribute.new("1.2.840.113549.1.9.14", val)
+ assert_equal("extReq", attr.oid)
+ end
+
+ def test_from_der
+ # oid: challengePassword, values: Set[UTF8String<"abc123">]
+ test_der = "\x30\x15\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x07\x31\x08" \
+ "\x0c\x06\x61\x62\x63\x31\x32\x33".b
+ attr = OpenSSL::X509::Attribute.new(test_der)
+ assert_equal(test_der, attr.to_der)
+ assert_equal("challengePassword", attr.oid)
+ assert_equal("abc123", attr.value.value[0].value)
+ end
+
+ def test_to_der
+ ef = OpenSSL::X509::ExtensionFactory.new
+ val = OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([
+ ef.create_extension("keyUsage", "keyCertSign", true)
+ ])])
+ attr = OpenSSL::X509::Attribute.new("extReq", val)
+ expected = OpenSSL::ASN1::Sequence.new([
+ OpenSSL::ASN1::ObjectId.new("extReq"),
+ val
+ ])
+ assert_equal(expected.to_der, attr.to_der)
+ end
+
+ def test_invalid_value
+ # should not change the original value
+ test_der = "\x30\x15\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x07\x31\x08" \
+ "\x0c\x06\x61\x62\x63\x31\x32\x33".b
+ attr = OpenSSL::X509::Attribute.new(test_der)
+ assert_raise(TypeError) {
+ attr.value = "1234"
+ }
+ assert_raise(OpenSSL::X509::AttributeError) {
+ v = OpenSSL::ASN1::Set([OpenSSL::ASN1::UTF8String("1234")], 17, :EXPLICIT)
+ attr.value = v
+ }
+ assert_equal(test_der, attr.to_der)
+ assert_raise(OpenSSL::X509::AttributeError) {
+ attr.oid = "abc123"
+ }
+ assert_equal(test_der, attr.to_der)
+ end
+
+ def test_dup
+ val = OpenSSL::ASN1::Set([
+ OpenSSL::ASN1::UTF8String("abc123")
+ ])
+ attr = OpenSSL::X509::Attribute.new("challengePassword", val)
+ assert_equal(attr.to_der, attr.dup.to_der)
+ end
+
+ def test_eq
+ val1 = OpenSSL::ASN1::Set([
+ OpenSSL::ASN1::UTF8String("abc123")
+ ])
+ attr1 = OpenSSL::X509::Attribute.new("challengePassword", val1)
+ attr2 = OpenSSL::X509::Attribute.new("challengePassword", val1)
+ ef = OpenSSL::X509::ExtensionFactory.new
+ val2 = OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([
+ ef.create_extension("keyUsage", "keyCertSign", true)
+ ])])
+ attr3 = OpenSSL::X509::Attribute.new("extReq", val2)
+
+ assert_equal false, attr1 == 12345
+ 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 a5a75ff1b6..9e0aa4edf6 100644
--- a/test/openssl/test_x509cert.rb
+++ b/test/openssl/test_x509cert.rb
@@ -1,34 +1,21 @@
-begin
- require "openssl"
- require File.join(File.dirname(__FILE__), "utils.rb")
-rescue LoadError
-end
-require "test/unit"
+# frozen_string_literal: true
+require_relative "utils"
if defined?(OpenSSL)
-class OpenSSL::TestX509Certificate < Test::Unit::TestCase
+class OpenSSL::TestX509Certificate < OpenSSL::TestCase
def setup
- @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024
- @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048
- @dsa256 = OpenSSL::TestUtils::TEST_KEY_DSA256
- @dsa512 = OpenSSL::TestUtils::TEST_KEY_DSA512
+ super
+ @rsa1 = Fixtures.pkey("rsa-1")
+ @rsa2 = Fixtures.pkey("rsa-2")
+ @ec1 = Fixtures.pkey("p256")
@ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
@ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1")
- @ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2")
- end
-
- def teardown
- end
-
- def issue_cert(*args)
- OpenSSL::TestUtils.issue_cert(*args)
end
def test_serial
[1, 2**32, 2**100].each{|s|
- cert = issue_cert(@ca, @rsa2048, s, Time.now, Time.now+3600, [],
- nil, nil, OpenSSL::Digest::SHA1.new)
+ cert = issue_cert(@ca, @rsa1, s, [], nil, nil)
assert_equal(s, cert.serial)
cert = OpenSSL::X509::Certificate.new(cert.to_der)
assert_equal(s, cert.serial)
@@ -41,57 +28,47 @@ class OpenSSL::TestX509Certificate < Test::Unit::TestCase
["subjectKeyIdentifier","hash",false],
["authorityKeyIdentifier","keyid:always",false],
]
-
- sha1 = OpenSSL::Digest::SHA1.new
- dss1 = OpenSSL::Digest::DSS1.new
- [
- [@rsa1024, sha1], [@rsa2048, sha1], [@dsa256, dss1], [@dsa512, dss1],
- ].each{|pk, digest|
- cert = issue_cert(@ca, pk, 1, Time.now, Time.now+3600, exts,
- nil, nil, digest)
- assert_equal(cert.extensions[1].value,
- OpenSSL::TestUtils.get_subject_key_id(cert))
- cert = OpenSSL::X509::Certificate.new(cert.to_der)
- assert_equal(cert.extensions[1].value,
- OpenSSL::TestUtils.get_subject_key_id(cert))
- }
+ cert = issue_cert(@ca, @rsa1, 1, exts, nil, nil)
+ assert_kind_of(OpenSSL::PKey::RSA, cert.public_key)
+ assert_equal(@rsa1.public_to_der, cert.public_key.public_to_der)
+ cert = OpenSSL::X509::Certificate.new(cert.to_der)
+ assert_equal(@rsa1.public_to_der, cert.public_key.public_to_der)
end
def test_validity
- now = Time.now until now && now.usec != 0
- cert = issue_cert(@ca, @rsa2048, 1, now, now+3600, [],
- nil, nil, OpenSSL::Digest::SHA1.new)
- assert_not_equal(now, cert.not_before)
- assert_not_equal(now+3600, cert.not_after)
+ now = Time.at(Time.now.to_i + 0.9)
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil,
+ not_before: now, not_after: now+3600)
+ assert_equal(Time.at(now.to_i), cert.not_before)
+ assert_equal(Time.at(now.to_i+3600), cert.not_after)
now = Time.at(now.to_i)
- cert = issue_cert(@ca, @rsa2048, 1, now, now+3600, [],
- nil, nil, OpenSSL::Digest::SHA1.new)
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil,
+ not_before: now, not_after: now+3600)
assert_equal(now.getutc, cert.not_before)
assert_equal((now+3600).getutc, cert.not_after)
now = Time.at(0)
- cert = issue_cert(@ca, @rsa2048, 1, now, now, [],
- nil, nil, OpenSSL::Digest::SHA1.new)
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil,
+ not_before: now, not_after: now)
assert_equal(now.getutc, cert.not_before)
assert_equal(now.getutc, cert.not_after)
now = Time.at(0x7fffffff)
- cert = issue_cert(@ca, @rsa2048, 1, now, now, [],
- nil, nil, OpenSSL::Digest::SHA1.new)
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil,
+ not_before: now, not_after: now)
assert_equal(now.getutc, cert.not_before)
assert_equal(now.getutc, cert.not_after)
end
- def test_extension
+ def test_extension_factory
ca_exts = [
["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, Time.now, Time.now+3600, ca_exts,
- nil, nil, OpenSSL::Digest::SHA1.new)
+ ca_cert = issue_cert(@ca, @rsa1, 1, ca_exts, nil, nil)
ca_cert.extensions.each_with_index{|ext, i|
assert_equal(ca_exts[i].first, ext.oid)
assert_equal(ca_exts[i].last, ext.critical?)
@@ -100,76 +77,311 @@ class OpenSSL::TestX509Certificate < Test::Unit::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],
]
- ee1_cert = issue_cert(@ee1, @rsa1024, 2, Time.now, Time.now+1800, ee1_exts,
- ca_cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+ ee1_cert = issue_cert(@ee1, @rsa2, 2, ee1_exts, ca_cert, @rsa1)
assert_equal(ca_cert.subject.to_der, ee1_cert.issuer.to_der)
ee1_cert.extensions.each_with_index{|ext, i|
assert_equal(ee1_exts[i].first, ext.oid)
assert_equal(ee1_exts[i].last, ext.critical?)
}
+ end
- ee2_exts = [
- ["keyUsage","Non Repudiation, Digital Signature, Key Encipherment",true],
- ["subjectKeyIdentifier","hash",false],
- ["authorityKeyIdentifier","issuer:always",false],
- ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false],
- ["subjectAltName","email:ee2@ruby-lang.org",false],
- ]
- ee2_cert = issue_cert(@ee2, @rsa1024, 3, Time.now, Time.now+1800, ee2_exts,
- ca_cert, @rsa2048, OpenSSL::Digest::MD5.new)
- assert_equal(ca_cert.subject.to_der, ee2_cert.issuer.to_der)
- ee2_cert.extensions.each_with_index{|ext, i|
- assert_equal(ee2_exts[i].first, ext.oid)
- assert_equal(ee2_exts[i].last, ext.critical?)
- }
+ def test_akiski
+ ca_cert = generate_cert(@ca, @rsa1, 4, nil)
+ ef = OpenSSL::X509::ExtensionFactory.new(ca_cert, ca_cert)
+ ca_cert.add_extension(
+ ef.create_extension("subjectKeyIdentifier", "hash", false))
+ ca_cert.add_extension(
+ ef.create_extension("authorityKeyIdentifier", "issuer:always,keyid:always", false))
+ ca_cert.sign(@rsa1, "sha256")
+ ca_keyid = get_subject_key_id(ca_cert.to_der, hex: false)
+ assert_equal ca_keyid, ca_cert.authority_key_identifier
+ assert_equal ca_keyid, ca_cert.subject_key_identifier
+
+ ee_cert = generate_cert(@ee1, @rsa2, 5, ca_cert)
+ ef = OpenSSL::X509::ExtensionFactory.new(ca_cert, ee_cert)
+ ee_cert.add_extension(
+ ef.create_extension("subjectKeyIdentifier", "hash", false))
+ ee_cert.add_extension(
+ ef.create_extension("authorityKeyIdentifier", "issuer:always,keyid:always", false))
+ ee_cert.sign(@rsa1, "sha256")
+
+ ee_keyid = get_subject_key_id(ee_cert.to_der, hex: false)
+ assert_equal ca_keyid, ee_cert.authority_key_identifier
+ assert_equal ee_keyid, ee_cert.subject_key_identifier
end
- def test_sign_and_verify
- cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [],
- nil, nil, OpenSSL::Digest::SHA1.new)
- assert_equal(false, cert.verify(@rsa1024))
- assert_equal(true, cert.verify(@rsa2048))
- assert_equal(false, cert.verify(@dsa256))
- assert_equal(false, cert.verify(@dsa512))
- cert.serial = 2
- assert_equal(false, cert.verify(@rsa2048))
-
- cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [],
- nil, nil, OpenSSL::Digest::MD5.new)
- assert_equal(false, cert.verify(@rsa1024))
- assert_equal(true, cert.verify(@rsa2048))
- assert_equal(false, cert.verify(@dsa256))
- assert_equal(false, cert.verify(@dsa512))
- cert.subject = @ee1
- assert_equal(false, cert.verify(@rsa2048))
-
- cert = issue_cert(@ca, @dsa512, 1, Time.now, Time.now+3600, [],
- nil, nil, OpenSSL::Digest::DSS1.new)
- assert_equal(false, cert.verify(@rsa1024))
- assert_equal(false, cert.verify(@rsa2048))
- assert_equal(false, cert.verify(@dsa256))
- assert_equal(true, cert.verify(@dsa512))
- cert.not_after = Time.now
- assert_equal(false, cert.verify(@dsa512))
-
- assert_raises(OpenSSL::X509::CertificateError){
- cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [],
- nil, nil, OpenSSL::Digest::DSS1.new)
+ def test_akiski_missing
+ cert = issue_cert(@ee1, @rsa1, 1, [], nil, nil)
+ assert_nil(cert.authority_key_identifier)
+ assert_nil(cert.subject_key_identifier)
+ end
+
+ def test_crl_uris_no_crl_distribution_points
+ cert = issue_cert(@ee1, @rsa1, 1, [], nil, nil)
+ assert_nil(cert.crl_uris)
+ end
+
+ def test_crl_uris
+ # Multiple DistributionPoint contains a single general name each
+ 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, @rsa1, 3, nil)
+ ef.subject_certificate = cdp_cert
+ cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "@crlDistPts"))
+ cdp_cert.sign(@rsa1, "sha256")
+ assert_equal(
+ ["http://www.example.com/crl", "ldap://ldap.example.com/cn=ca?certificateRevocationList;binary"],
+ cdp_cert.crl_uris
+ )
+ end
+
+ def test_crl_uris_multiple_general_names
+ # Single DistributionPoint contains multiple general names of type URI
+ ef = OpenSSL::X509::ExtensionFactory.new
+ ef.config = OpenSSL::Config.parse(<<~_cnf_)
+ [crlDistPts_section]
+ fullname = URI:http://www.example.com/crl, URI:ldap://ldap.example.com/cn=ca?certificateRevocationList;binary
+ _cnf_
+ cdp_cert = generate_cert(@ee1, @rsa1, 3, nil)
+ ef.subject_certificate = cdp_cert
+ cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "crlDistPts_section"))
+ cdp_cert.sign(@rsa1, "sha256")
+ assert_equal(
+ ["http://www.example.com/crl", "ldap://ldap.example.com/cn=ca?certificateRevocationList;binary"],
+ cdp_cert.crl_uris
+ )
+ end
+
+ def test_crl_uris_no_uris
+ # The only DistributionPointName is a directoryName
+ ef = OpenSSL::X509::ExtensionFactory.new
+ ef.config = OpenSSL::Config.parse(<<~_cnf_)
+ [crlDistPts_section]
+ fullname = dirName:dirname_section
+ [dirname_section]
+ CN = dirname
+ _cnf_
+ cdp_cert = generate_cert(@ee1, @rsa1, 3, nil)
+ ef.subject_certificate = cdp_cert
+ cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "crlDistPts_section"))
+ cdp_cert.sign(@rsa1, "sha256")
+ assert_nil(cdp_cert.crl_uris)
+ end
+
+ def test_aia_missing
+ cert = issue_cert(@ee1, @rsa1, 1, [], nil, nil)
+ assert_nil(cert.ca_issuer_uris)
+ assert_nil(cert.ocsp_uris)
+ end
+
+ def test_aia
+ ef = OpenSSL::X509::ExtensionFactory.new
+ aia_cert = generate_cert(@ee1, @rsa1, 4, nil)
+ 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(@rsa1, "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
+ )
+ end
+
+ def test_invalid_extension
+ integer = OpenSSL::ASN1::Integer.new(0)
+ invalid_exts_cert = generate_cert(@ee1, @rsa1, 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_raises(OpenSSL::X509::CertificateError){
- cert = issue_cert(@ca, @dsa512, 1, Time.now, Time.now+3600, [],
- nil, nil, OpenSSL::Digest::MD5.new)
+ assert_raise(OpenSSL::ASN1::ASN1Error, "invalid extension") {
+ invalid_exts_cert.crl_uris
}
- assert_raises(OpenSSL::X509::CertificateError){
- cert = issue_cert(@ca, @dsa512, 1, Time.now, Time.now+3600, [],
- nil, nil, OpenSSL::Digest::SHA1.new)
+ 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
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil, digest: "SHA256")
+ assert_equal("sha256WithRSAEncryption", cert.signature_algorithm) # ln
+ assert_equal(true, cert.verify(@rsa1))
+ assert_equal(false, cert.verify(@rsa2))
+ assert_equal(false, certificate_error_returns_false { cert.verify(@ec1) })
+ cert.serial = 2
+ assert_equal(false, cert.verify(@rsa1))
+ end
+
+ def test_sign_and_verify_nil_digest
+ # Ed25519 is not FIPS-approved.
+ omit_on_fips
+ ed25519 = OpenSSL::PKey::generate_key("ED25519")
+ cert = issue_cert(@ca, ed25519, 1, [], nil, nil, digest: nil)
+ assert_equal(true, cert.verify(ed25519))
+ end
+
+ def test_check_private_key
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ assert_equal(true, cert.check_private_key(@rsa1))
+ end
+
+ def test_read_from_file
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ Tempfile.create("cert") { |f|
+ f << cert.to_pem
+ f.rewind
+ assert_equal cert.to_der, OpenSSL::X509::Certificate.new(f).to_der
}
end
+
+ def test_read_der_then_pem
+ cert1 = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ exts = [
+ # A new line before PEM block
+ ["nsComment", "Another certificate:\n" + cert1.to_pem],
+ ]
+ cert2 = issue_cert(@ca, @rsa1, 2, exts, nil, nil)
+
+ assert_equal cert2, OpenSSL::X509::Certificate.new(cert2.to_der)
+ assert_equal cert2, OpenSSL::X509::Certificate.new(cert2.to_pem)
+ end
+
+ def test_eq
+ now = Time.now
+ cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil,
+ not_before: now, not_after: now + 3600)
+ cert1 = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1,
+ not_before: now, not_after: now + 3600)
+ cert2 = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1,
+ not_before: now, not_after: now + 3600)
+ cert3 = issue_cert(@ee1, @rsa2, 3, [], cacert, @rsa1,
+ not_before: now, not_after: now + 3600)
+ cert4 = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1,
+ digest: "sha512", not_before: now, not_after: now + 3600)
+
+ assert_equal false, cert1 == 12345
+ assert_equal true, cert1 == cert2
+ assert_equal false, cert1 == cert3
+ assert_equal false, cert1 == cert4
+ assert_equal false, cert3 == cert4
+ end
+
+ def test_inspect
+ cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ assert_include(cacert.inspect, "subject=#{@ca.inspect}")
+
+ # Do not raise an exception for an invalid certificate
+ assert_instance_of(String, OpenSSL::X509::Certificate.new.inspect)
+ end
+
+ def test_marshal
+ now = Time.now
+ cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil,
+ not_before: now, not_after: now + 3600)
+ cert = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1,
+ not_before: now, not_after: now + 3600)
+ deserialized = Marshal.load(Marshal.dump(cert))
+
+ assert_equal cert.to_der, deserialized.to_der
+ end
+
+ def test_load_file_empty_pem
+ Tempfile.create("empty.pem") do |f|
+ f.close
+
+ assert_raise(OpenSSL::X509::CertificateError) do
+ OpenSSL::X509::Certificate.load_file(f.path)
+ end
+ end
+ end
+
+ def test_load_file_fullchain_pem
+ cert1 = issue_cert(@ee1, @rsa1, 1, [], nil, nil)
+ cert2 = issue_cert(@ca, @rsa2, 1, [], nil, nil)
+
+ Tempfile.create("fullchain.pem") do |f|
+ f.puts cert1.to_pem
+ f.puts cert2.to_pem
+ f.close
+
+ certificates = OpenSSL::X509::Certificate.load_file(f.path)
+ assert_equal 2, certificates.size
+ assert_equal @ee1, certificates[0].subject
+ assert_equal @ca, certificates[1].subject
+ end
+ end
+
+ def test_load_file_certificate_der
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ Tempfile.create("certificate.der", binmode: true) do |f|
+ f.write cert.to_der
+ f.close
+
+ certificates = OpenSSL::X509::Certificate.load_file(f.path)
+
+ # DER encoding can only contain one certificate:
+ assert_equal 1, certificates.size
+ assert_equal cert.to_der, certificates[0].to_der
+ end
+ end
+
+ def test_load_file_fullchain_garbage
+ Tempfile.create("garbage.txt") do |f|
+ f.puts "not a certificate"
+ f.close
+
+ assert_raise(OpenSSL::X509::CertificateError) do
+ OpenSSL::X509::Certificate.load_file(f.path)
+ end
+ end
+ end
+
+ def test_tbs_precert_bytes
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ seq = OpenSSL::ASN1.decode(cert.tbs_bytes)
+
+ assert_equal 7, seq.value.size
+ end
+
+ private
+
+ def certificate_error_returns_false
+ yield
+ rescue OpenSSL::X509::CertificateError
+ false
+ end
end
end
diff --git a/test/openssl/test_x509crl.rb b/test/openssl/test_x509crl.rb
index 444a00a586..81c9247df2 100644
--- a/test/openssl/test_x509crl.rb
+++ b/test/openssl/test_x509crl.rb
@@ -1,45 +1,26 @@
-begin
- require "openssl"
- require File.join(File.dirname(__FILE__), "utils.rb")
-rescue LoadError
-end
-require "test/unit"
+# frozen_string_literal: true
+require_relative "utils"
if defined?(OpenSSL)
-class OpenSSL::TestX509CRL < Test::Unit::TestCase
+class OpenSSL::TestX509CRL < OpenSSL::TestCase
def setup
- @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024
- @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048
- @dsa256 = OpenSSL::TestUtils::TEST_KEY_DSA256
- @dsa512 = OpenSSL::TestUtils::TEST_KEY_DSA512
+ super
+ @rsa1 = Fixtures.pkey("rsa-1")
+ @rsa2 = Fixtures.pkey("rsa-2")
@ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
- @ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1")
- @ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2")
- end
-
- def teardown
- end
-
- def issue_crl(*args)
- OpenSSL::TestUtils.issue_crl(*args)
- end
-
- def issue_cert(*args)
- OpenSSL::TestUtils.issue_cert(*args)
end
def test_basic
now = Time.at(Time.now.to_i)
- cert = issue_cert(@ca, @rsa2048, 1, now, now+3600, [],
- nil, nil, OpenSSL::Digest::SHA1.new)
- crl = issue_crl([], 1, now, now+1600, [],
- cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ crl = issue_crl([], 1, now, now+1600, [], cert, @rsa1, "SHA256")
assert_equal(1, crl.version)
assert_equal(cert.issuer.to_der, crl.issuer.to_der)
assert_equal(now, crl.last_update)
assert_equal(now+1600, crl.next_update)
+ assert_equal("sha256WithRSAEncryption", crl.signature_algorithm) # ln
crl = OpenSSL::X509::CRL.new(crl.to_der)
assert_equal(1, crl.version)
@@ -70,10 +51,9 @@ class OpenSSL::TestX509CRL < Test::Unit::TestCase
[4, now, 4],
[5, now, 5],
]
- cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [],
- nil, nil, OpenSSL::Digest::SHA1.new)
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
crl = issue_crl(revoke_info, 1, Time.now, Time.now+1600, [],
- cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+ cert, @rsa1, "SHA256")
revoked = crl.revoked
assert_equal(5, revoked.size)
assert_equal(1, revoked[0].serial)
@@ -106,7 +86,6 @@ class OpenSSL::TestX509CRL < Test::Unit::TestCase
assert_equal(false, revoked[3].extensions[0].critical?)
assert_equal(false, revoked[4].extensions[0].critical?)
- crl = OpenSSL::X509::CRL.new(crl.to_der)
assert_equal("Key Compromise", revoked[0].extensions[0].value)
assert_equal("CA Compromise", revoked[1].extensions[0].value)
assert_equal("Affiliation Changed", revoked[2].extensions[0].value)
@@ -115,36 +94,42 @@ class OpenSSL::TestX509CRL < Test::Unit::TestCase
revoke_info = (1..1000).collect{|i| [i, now, 0] }
crl = issue_crl(revoke_info, 1, Time.now, Time.now+1600, [],
- cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+ cert, @rsa1, "SHA256")
revoked = crl.revoked
assert_equal(1000, revoked.size)
assert_equal(1, revoked[0].serial)
assert_equal(1000, revoked[999].serial)
+
+ crl.revoked = revoked
+ revoked2 = crl.revoked
+ assert_equal(revoked.map(&:serial), revoked2.map(&:serial))
end
def test_extension
cert_exts = [
["basicConstraints", "CA:TRUE", true],
- ["subjectKeyIdentifier", "hash", false],
- ["authorityKeyIdentifier", "keyid:always", false],
+ ["subjectKeyIdentifier", "hash", false],
+ ["authorityKeyIdentifier", "keyid:always", false],
["subjectAltName", "email:xyzzy@ruby-lang.org", false],
["keyUsage", "cRLSign, keyCertSign", true],
]
crl_exts = [
- ["authorityKeyIdentifier", "keyid:always", false],
+ ["authorityKeyIdentifier", "issuer:always,keyid:always", false],
["issuerAltName", "issuer:copy", false],
]
- cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, cert_exts,
- nil, nil, OpenSSL::Digest::SHA1.new)
+ cert = issue_cert(@ca, @rsa1, 1, cert_exts, nil, nil)
crl = issue_crl([], 1, Time.now, Time.now+1600, crl_exts,
- cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+ cert, @rsa1, "SHA256")
exts = crl.extensions
assert_equal(3, exts.size)
assert_equal("1", exts[0].value)
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)
@@ -169,49 +154,137 @@ class OpenSSL::TestX509CRL < Test::Unit::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, @rsa1, "SHA256")
+ assert_equal nil, no_ext_crl.authority_key_identifier
end
def test_crlnumber
- cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [],
- nil, nil, OpenSSL::Digest::SHA1.new)
- crl = issue_crl([], 1, Time.now, Time.now+1600, [],
- cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, @rsa1, "SHA256")
assert_match(1.to_s, crl.extensions[0].value)
assert_match(/X509v3 CRL Number:\s+#{1}/m, crl.to_text)
crl = issue_crl([], 2**32, Time.now, Time.now+1600, [],
- cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+ cert, @rsa1, "SHA256")
assert_match((2**32).to_s, crl.extensions[0].value)
assert_match(/X509v3 CRL Number:\s+#{2**32}/m, crl.to_text)
crl = issue_crl([], 2**100, Time.now, Time.now+1600, [],
- cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+ cert, @rsa1, "SHA256")
assert_match(/X509v3 CRL Number:\s+#{2**100}/m, crl.to_text)
assert_match((2**100).to_s, crl.extensions[0].value)
end
def test_sign_and_verify
- cert = issue_cert(@ca, @rsa2048, 1, Time.now, Time.now+3600, [],
- nil, nil, OpenSSL::Digest::SHA1.new)
- crl = issue_crl([], 1, Time.now, Time.now+1600, [],
- cert, @rsa2048, OpenSSL::Digest::SHA1.new)
- assert_equal(false, crl.verify(@rsa1024))
- assert_equal(true, crl.verify(@rsa2048))
- assert_equal(false, crl.verify(@dsa256))
- assert_equal(false, crl.verify(@dsa512))
+ p256 = Fixtures.pkey("p256")
+
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, @rsa1, "SHA256")
+ assert_equal(true, crl.verify(@rsa1))
+ assert_equal(false, crl.verify(@rsa2))
+ assert_equal(false, crl_error_returns_false { crl.verify(p256) })
+ crl.version = 0
+ assert_equal(false, crl.verify(@rsa1))
+
+ cert = issue_cert(@ca, p256, 1, [], nil, nil)
+ crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, p256, "SHA256")
+ assert_equal(false, crl_error_returns_false { crl.verify(@rsa1) })
+ assert_equal(false, crl_error_returns_false { crl.verify(@rsa2) })
+ assert_equal(true, crl.verify(p256))
crl.version = 0
- assert_equal(false, crl.verify(@rsa2048))
+ assert_equal(false, crl.verify(p256))
+ end
- cert = issue_cert(@ca, @dsa512, 1, Time.now, Time.now+3600, [],
- nil, nil, OpenSSL::Digest::DSS1.new)
+ def test_sign_and_verify_nil_digest
+ # Ed25519 is not FIPS-approved.
+ omit_on_fips
+ ed25519 = OpenSSL::PKey::generate_key("ED25519")
+ cert = issue_cert(@ca, ed25519, 1, [], nil, nil, digest: nil)
crl = issue_crl([], 1, Time.now, Time.now+1600, [],
- cert, @dsa512, OpenSSL::Digest::DSS1.new)
- assert_equal(false, crl.verify(@rsa1024))
- assert_equal(false, crl.verify(@rsa2048))
- assert_equal(false, crl.verify(@dsa256))
- assert_equal(true, crl.verify(@dsa512))
+ cert, ed25519, nil)
+ assert_equal(false, crl_error_returns_false { crl.verify(@rsa1) })
+ assert_equal(false, crl.verify(OpenSSL::PKey::generate_key("ED25519")))
+ assert_equal(true, crl.verify(ed25519))
crl.version = 0
- assert_equal(false, crl.verify(@dsa512))
+ assert_equal(false, crl.verify(ed25519))
+ end
+
+ def test_revoked_to_der
+ # revokedCertificates SEQUENCE OF SEQUENCE {
+ # userCertificate CertificateSerialNumber,
+ # revocationDate Time,
+ # crlEntryExtensions Extensions OPTIONAL
+ # -- if present, version MUST be v2
+ # } OPTIONAL,
+
+ now = Time.utc(2000, 1, 1)
+ rev1 = OpenSSL::X509::Revoked.new
+ rev1.serial = 123
+ rev1.time = now
+ ext = OpenSSL::X509::Extension.new("CRLReason", OpenSSL::ASN1::Enumerated(1))
+ rev1.extensions = [ext]
+ asn1 = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(123),
+ OpenSSL::ASN1::UTCTime(now),
+ OpenSSL::ASN1::Sequence([ext.to_der])
+ ])
+
+ assert_equal asn1.to_der, rev1.to_der
+ end
+
+ def test_eq
+ now = Time.now
+
+ cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ crl1 = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1, "SHA256")
+ rev1 = OpenSSL::X509::Revoked.new.tap { |rev|
+ rev.serial = 1
+ rev.time = now
+ }
+ crl1.add_revoked(rev1)
+ crl2 = OpenSSL::X509::CRL.new(crl1.to_der)
+
+ # CRL
+ assert_equal false, crl1 == 12345
+ assert_equal true, crl1 == crl2
+ rev2 = OpenSSL::X509::Revoked.new.tap { |rev|
+ rev.serial = 2
+ rev.time = now
+ }
+ crl2.add_revoked(rev2)
+ assert_equal false, crl1 == crl2
+
+ # Revoked
+ assert_equal false, rev1 == 12345
+ assert_equal true, rev1 == crl2.revoked[0]
+ assert_equal false, rev1 == crl2.revoked[1]
+ assert_equal true, rev2 == crl2.revoked[1]
+ end
+
+ def test_marshal
+ now = Time.now
+
+ cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ crl = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1, "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
+ yield
+ rescue OpenSSL::X509::CRLError
+ false
end
end
diff --git a/test/openssl/test_x509ext.rb b/test/openssl/test_x509ext.rb
index d43bbd6bd3..59a41ed736 100644
--- a/test/openssl/test_x509ext.rb
+++ b/test/openssl/test_x509ext.rb
@@ -1,14 +1,11 @@
-begin
- require "openssl"
- require File.join(File.dirname(__FILE__), "utils.rb")
-rescue LoadError
-end
-require "test/unit"
+# frozen_string_literal: true
+require_relative 'utils'
if defined?(OpenSSL)
-class OpenSSL::TestX509Extension < Test::Unit::TestCase
+class OpenSSL::TestX509Extension < OpenSSL::TestCase
def setup
+ super
@basic_constraints_value = OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Boolean(true), # CA
OpenSSL::ASN1::Integer(2) # pathlen
@@ -20,9 +17,6 @@ class OpenSSL::TestX509Extension < Test::Unit::TestCase
])
end
- def teardown
- end
-
def test_new
ext = OpenSSL::X509::Extension.new(@basic_constraints.to_der)
assert_equal("basicConstraints", ext.oid)
@@ -43,31 +37,84 @@ class OpenSSL::TestX509Extension < Test::Unit::TestCase
bc = ef.create_extension("basicConstraints", "CA:TRUE, pathlen:2", true)
assert_equal(@basic_constraints.to_der, bc.to_der)
- begin
- ef.config = OpenSSL::Config.parse(<<-_end_of_cnf_)
- [crlDistPts]
- URI.1 = http://www.example.com/crl
- URI.2 = ldap://ldap.example.com/cn=ca?certificateRevocationList;binary
- _end_of_cnf_
- rescue NotImplementedError
- return
- end
+ ef.config = OpenSSL::Config.parse(<<-_end_of_cnf_)
+ [crlDistPts]
+ URI.1 = http://www.example.com/crl
+ URI.2 = ldap://ldap.example.com/cn=ca?certificateRevocationList;binary
+
+ [certPolicies]
+ policyIdentifier = 2.23.140.1.2.1
+ CPS.1 = http://cps.example.com
+ _end_of_cnf_
cdp = ef.create_extension("crlDistributionPoints", "@crlDistPts")
assert_equal(false, cdp.critical?)
assert_equal("crlDistributionPoints", cdp.oid)
- assert_match(%{URI:http://www\.example\.com/crl}, cdp.value)
- assert_match(
- %r{URI:ldap://ldap\.example\.com/cn=ca\?certificateRevocationList;binary},
- cdp.value)
+ assert_include(cdp.value, "URI:http://www.example.com/crl")
+ assert_include(cdp.value,
+ "URI:ldap://ldap.example.com/cn=ca?certificateRevocationList;binary")
cdp = ef.create_extension("crlDistributionPoints", "critical, @crlDistPts")
assert_equal(true, cdp.critical?)
assert_equal("crlDistributionPoints", cdp.oid)
- assert_match(%{URI:http://www.example.com/crl}, cdp.value)
- assert_match(
- %r{URI:ldap://ldap.example.com/cn=ca\?certificateRevocationList;binary},
- cdp.value)
+ assert_include(cdp.value, "URI:http://www.example.com/crl")
+ assert_include(cdp.value,
+ "URI:ldap://ldap.example.com/cn=ca?certificateRevocationList;binary")
+
+ cp = ef.create_extension("certificatePolicies", "@certPolicies")
+ assert_equal(false, cp.critical?)
+ assert_equal("certificatePolicies", cp.oid)
+ assert_include(cp.value, "2.23.140.1.2.1")
+ assert_include(cp.value, "http://cps.example.com")
+ end
+
+ def test_factory_create_extension_sn_ln
+ ef = OpenSSL::X509::ExtensionFactory.new
+ bc_sn = ef.create_extension("basicConstraints", "critical, CA:TRUE, pathlen:2")
+ bc_ln = ef.create_extension("X509v3 Basic Constraints", "critical, CA:TRUE, pathlen:2")
+ assert_equal(@basic_constraints.to_der, bc_sn.to_der)
+ assert_equal(@basic_constraints.to_der, bc_ln.to_der)
+ end
+
+ def test_factory_create_extension_oid
+ ef = OpenSSL::X509::ExtensionFactory.new
+ ef.config = OpenSSL::Config.parse(<<~_end_of_cnf_)
+ [basic_constraints]
+ cA = BOOLEAN:TRUE
+ pathLenConstraint = INTEGER:2
+ _end_of_cnf_
+ bc_oid = ef.create_extension("2.5.29.19", "ASN1:SEQUENCE:basic_constraints", true)
+ assert_equal(@basic_constraints.to_der, bc_oid.to_der)
+ end
+
+ def test_dup
+ ext = OpenSSL::X509::Extension.new(@basic_constraints.to_der)
+ assert_equal(@basic_constraints.to_der, ext.to_der)
+ assert_equal(ext.to_der, ext.dup.to_der)
+ end
+
+ def test_eq
+ ext1 = OpenSSL::X509::Extension.new(@basic_constraints.to_der)
+ ef = OpenSSL::X509::ExtensionFactory.new
+ ext2 = ef.create_extension("basicConstraints", "critical, CA:TRUE, pathlen:2")
+ ext3 = ef.create_extension("basicConstraints", "critical, CA:TRUE")
+
+ assert_equal false, ext1 == 12345
+ 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
diff --git a/test/openssl/test_x509name.rb b/test/openssl/test_x509name.rb
index fb5a1ae4ff..223c575e4e 100644
--- a/test/openssl/test_x509name.rb
+++ b/test/openssl/test_x509name.rb
@@ -1,25 +1,16 @@
-begin
- require "openssl"
-rescue LoadError
-end
-require "test/unit"
+# coding: ASCII-8BIT
+# frozen_string_literal: true
+require_relative 'utils'
if defined?(OpenSSL)
-class OpenSSL::TestX509Name < Test::Unit::TestCase
- OpenSSL::ASN1::ObjectId.register(
- "1.2.840.113549.1.9.1", "emailAddress", "emailAddress")
- OpenSSL::ASN1::ObjectId.register(
- "2.5.4.5", "serialNumber", "serialNumber")
-
+class OpenSSL::TestX509Name < OpenSSL::TestCase
def setup
+ super
@obj_type_tmpl = Hash.new(OpenSSL::ASN1::PRINTABLESTRING)
@obj_type_tmpl.update(OpenSSL::X509::Name::OBJECT_TYPE_TEMPLATE)
end
- def teardown
- end
-
def test_s_new
dn = [ ["C", "JP"], ["O", "example"], ["CN", "www.example.jp"] ]
name = OpenSSL::X509::Name.new(dn)
@@ -104,34 +95,81 @@ class OpenSSL::TestX509Name < Test::Unit::TestCase
assert_equal(name_from_der.to_der, name.to_der)
end
+ def test_unrecognized_oid
+ dn = [ ["1.2.3.4.5.6.7.8.9.7.5.3.1", "Unknown OID 1"],
+ ["1.1.2.3.5.8.13.21.34", "Unknown OID 2"],
+ ["C", "US"],
+ ["postalCode", "60602"],
+ ["ST", "Illinois"],
+ ["L", "Chicago"],
+ #["street", "123 Fake St"],
+ ["O", "Some Company LLC"],
+ ["CN", "mydomain.com"] ]
+
+ name = OpenSSL::X509::Name.new(dn)
+ ary = name.to_a
+ #assert_equal("/1.2.3.4.5.6.7.8.9.7.5.3.1=Unknown OID 1/1.1.2.3.5.8.13.21.34=Unknown OID 2/C=US/postalCode=60602/ST=Illinois/L=Chicago/street=123 Fake St/O=Some Company LLC/CN=mydomain.com", name.to_s)
+ assert_equal("/1.2.3.4.5.6.7.8.9.7.5.3.1=Unknown OID 1/1.1.2.3.5.8.13.21.34=Unknown OID 2/C=US/postalCode=60602/ST=Illinois/L=Chicago/O=Some Company LLC/CN=mydomain.com", name.to_s)
+ assert_equal("1.2.3.4.5.6.7.8.9.7.5.3.1", ary[0][0])
+ assert_equal("1.1.2.3.5.8.13.21.34", ary[1][0])
+ assert_equal("C", ary[2][0])
+ assert_equal("postalCode", ary[3][0])
+ assert_equal("ST", ary[4][0])
+ assert_equal("L", ary[5][0])
+ #assert_equal("street", ary[6][0])
+ assert_equal("O", ary[6][0])
+ assert_equal("CN", ary[7][0])
+ assert_equal("Unknown OID 1", ary[0][1])
+ assert_equal("Unknown OID 2", ary[1][1])
+ assert_equal("US", ary[2][1])
+ assert_equal("60602", ary[3][1])
+ assert_equal("Illinois", ary[4][1])
+ assert_equal("Chicago", ary[5][1])
+ #assert_equal("123 Fake St", ary[6][1])
+ assert_equal("Some Company LLC", ary[6][1])
+ assert_equal("mydomain.com", ary[7][1])
+ end
+
+ def test_unrecognized_oid_parse_encode_equality
+ dn = [ ["1.2.3.4.5.6.7.8.9.7.5.3.2", "Unknown OID1"],
+ ["1.1.2.3.5.8.13.21.35", "Unknown OID2"],
+ ["C", "US"],
+ ["postalCode", "60602"],
+ ["ST", "Illinois"],
+ ["L", "Chicago"],
+ #["street", "123 Fake St"],
+ ["O", "Some Company LLC"],
+ ["CN", "mydomain.com"] ]
+
+ name1 = OpenSSL::X509::Name.new(dn)
+ name2 = OpenSSL::X509::Name.parse(name1.to_s)
+ assert_equal(name1.to_s, name2.to_s)
+ assert_equal(name1.to_a, name2.to_a)
+ end
+
def test_s_parse
- dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org"
+ dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org/1.2.3.4.5.6=A=BCD"
name = OpenSSL::X509::Name.parse(dn)
assert_equal(dn, name.to_s)
ary = name.to_a
- assert_equal("DC", ary[0][0])
- assert_equal("DC", ary[1][0])
- assert_equal("CN", ary[2][0])
- assert_equal("org", ary[0][1])
- assert_equal("ruby-lang", ary[1][1])
- assert_equal("www.ruby-lang.org", ary[2][1])
- assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2])
- assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2])
- assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2])
+ assert_equal [
+ ["DC", "org", OpenSSL::ASN1::IA5STRING],
+ ["DC", "ruby-lang", OpenSSL::ASN1::IA5STRING],
+ ["CN", "www.ruby-lang.org", OpenSSL::ASN1::UTF8STRING],
+ ["1.2.3.4.5.6", "A=BCD", OpenSSL::ASN1::UTF8STRING],
+ ], ary
- dn2 = "DC=org, DC=ruby-lang, CN=www.ruby-lang.org"
- name = OpenSSL::X509::Name.parse(dn)
- ary = name.to_a
+ dn2 = "DC=org, DC=ruby-lang, CN=www.ruby-lang.org, 1.2.3.4.5.6=A=BCD"
+ name = OpenSSL::X509::Name.parse(dn2)
assert_equal(dn, name.to_s)
- assert_equal("org", ary[0][1])
- assert_equal("ruby-lang", ary[1][1])
- assert_equal("www.ruby-lang.org", ary[2][1])
+ assert_equal ary, name.to_a
- name = OpenSSL::X509::Name.parse(dn, @obj_type_tmpl)
+ name = OpenSSL::X509::Name.parse(dn2, @obj_type_tmpl)
ary = name.to_a
assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2])
assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2])
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[2][2])
+ assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[3][2])
end
def test_s_parse_rfc2253
@@ -261,6 +299,161 @@ class OpenSSL::TestX509Name < Test::Unit::TestCase
assert_equal(OpenSSL::ASN1::IA5STRING, ary[3][2])
assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[4][2])
end
+
+ def test_add_entry_street
+ # openssl/crypto/objects/obj_mac.h 1.83
+ dn = [
+ ["DC", "org"],
+ ["DC", "ruby-lang"],
+ ["CN", "GOTOU Yuuzou"],
+ ["emailAddress", "gotoyuzo@ruby-lang.org"],
+ ["serialNumber", "123"],
+ ["street", "Namiki"],
+ ]
+ name = OpenSSL::X509::Name.new
+ dn.each{|attr| name.add_entry(*attr) }
+ ary = name.to_a
+ assert_equal("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou/emailAddress=gotoyuzo@ruby-lang.org/serialNumber=123/street=Namiki", name.to_s)
+ assert_equal("Namiki", ary[5][1])
+ end
+
+ def test_add_entry_placing
+ der = %w{ 30 2A
+ 31 12
+ 30 10 06 03 55 04 0A 0C 09 72 75 62 79 2D 6C 61 6E 67
+ 31 14
+ 30 08 06 03 55 04 0B 0C 01 61
+ 30 08 06 03 55 04 0B 0C 01 62 }
+ orig = OpenSSL::X509::Name.new([der.join].pack("H*"))
+ assert_equal("OU=b+OU=a,O=ruby-lang", orig.to_s(OpenSSL::X509::Name::RFC2253))
+ # Skip for now; they do not work
+ #
+ # dn = orig.dup
+ # dn.add_entry("CN", "unya", loc: 0, set: 0)
+ # assert_equal("OU=b+OU=a,O=ruby-lang,CN=unya", dn.dup.to_s(OpenSSL::X509::Name::RFC2253))
+ # dn = orig.dup
+ # dn.add_entry("CN", "unya", loc: 0, set: 1)
+ # assert_equal("OU=b+OU=a,O=ruby-lang+CN=unya", dn.dup.to_s(OpenSSL::X509::Name::RFC2253))
+ dn = orig.dup
+ dn.add_entry("CN", "unya", loc: 1, set: -1)
+ assert_equal("OU=b+OU=a,O=ruby-lang+CN=unya", dn.dup.to_s(OpenSSL::X509::Name::RFC2253))
+ # dn = orig.dup
+ # dn.add_entry("CN", "unya", loc: 1, set: 0)
+ # assert_equal("OU=b+OU=a,CN=unya,O=ruby-lang", dn.dup.to_s(OpenSSL::X509::Name::RFC2253))
+ dn = orig.dup
+ dn.add_entry("CN", "unya", loc: 1, set: 1)
+ assert_equal("CN=unya+OU=b+OU=a,O=ruby-lang", dn.dup.to_s(OpenSSL::X509::Name::RFC2253))
+ dn = orig.dup
+ dn.add_entry("CN", "unya", loc: -1, set: -1)
+ assert_equal("CN=unya+OU=b+OU=a,O=ruby-lang", dn.dup.to_s(OpenSSL::X509::Name::RFC2253))
+ dn = orig.dup
+ dn.add_entry("CN", "unya", loc: -1, set: 0)
+ assert_equal("CN=unya,OU=b+OU=a,O=ruby-lang", dn.dup.to_s(OpenSSL::X509::Name::RFC2253))
+ end
+
+ def test_to_s
+ dn = [
+ ["DC", "org"],
+ ["DC", "ruby-lang"],
+ ["CN", "フー, バー"],
+ ]
+ name = OpenSSL::X509::Name.new
+ dn.each { |x| name.add_entry(*x) }
+
+ assert_equal "/DC=org/DC=ruby-lang/" \
+ "CN=\\xE3\\x83\\x95\\xE3\\x83\\xBC, \\xE3\\x83\\x90\\xE3\\x83\\xBC",
+ name.to_s
+ # OpenSSL escapes characters with MSB by default
+ assert_equal \
+ "CN=\\E3\\83\\95\\E3\\83\\BC\\, \\E3\\83\\90\\E3\\83\\BC," \
+ "DC=ruby-lang,DC=org",
+ name.to_s(OpenSSL::X509::Name::RFC2253)
+ assert_equal "DC = org, DC = ruby-lang, " \
+ "CN = \"\\E3\\83\\95\\E3\\83\\BC, \\E3\\83\\90\\E3\\83\\BC\"",
+ name.to_s(OpenSSL::X509::Name::ONELINE)
+
+ empty = OpenSSL::X509::Name.new
+ assert_equal "", empty.to_s
+ assert_equal "", empty.to_s(OpenSSL::X509::Name::COMPAT)
+ assert_equal "", empty.to_s(OpenSSL::X509::Name::RFC2253)
+ assert_equal "", empty.to_s(OpenSSL::X509::Name::ONELINE)
+ end
+
+ def test_to_utf8
+ dn = [
+ ["DC", "org"],
+ ["DC", "ruby-lang"],
+ ["CN", "フー, バー"],
+ ]
+ name = OpenSSL::X509::Name.new
+ dn.each { |x| name.add_entry(*x) }
+
+ str = name.to_utf8
+ 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
+
+ empty = OpenSSL::X509::Name.new
+ assert_equal "", empty.to_utf8
+ end
+
+ def test_equals2
+ n1 = OpenSSL::X509::Name.parse_rfc2253 'CN=a'
+ 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
+ n1 = OpenSSL::X509::Name.new([["CN", "a"]])
+ n2 = OpenSSL::X509::Name.new([["CN", "a"]])
+ n3 = OpenSSL::X509::Name.new([["CN", "ab"]])
+
+ assert_equal(0, n1 <=> n2)
+ assert_equal(-1, n1 <=> n3)
+ assert_equal(0, n2 <=> n1)
+ 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 test_hash_old
+ omit_on_fips # MD5
+
+ dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org"
+ name = OpenSSL::X509::Name.parse(dn)
+ d = OpenSSL::Digest.digest('MD5', name.to_der)
+ expected = (d[0].ord & 0xff) | (d[1].ord & 0xff) << 8 | (d[2].ord & 0xff) << 16 | (d[3].ord & 0xff) << 24
+ assert_equal(expected, name.hash_old)
+ end
+
+ def test_equality
+ name0 = OpenSSL::X509::Name.new([["DC", "org"], ["DC", "ruby-lang"], ["CN", "bar.ruby-lang.org"]])
+ name1 = OpenSSL::X509::Name.new([["DC", "org"], ["DC", "ruby-lang"], ["CN", "bar.ruby-lang.org"]])
+ name2 = OpenSSL::X509::Name.new([["DC", "org"], ["DC", "ruby-lang"], ["CN", "baz.ruby-lang.org"]])
+ assert_equal true, name0 == name1
+ assert_equal true, name0.eql?(name1)
+ assert_equal false, name0 == name2
+ 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)
+ end
end
end
diff --git a/test/openssl/test_x509req.rb b/test/openssl/test_x509req.rb
index a37ed5c5ef..b198a1185a 100644
--- a/test/openssl/test_x509req.rb
+++ b/test/openssl/test_x509req.rb
@@ -1,18 +1,13 @@
-begin
- require "openssl"
- require File.join(File.dirname(__FILE__), "utils.rb")
-rescue LoadError
-end
-require "test/unit"
+# frozen_string_literal: true
+require_relative "utils"
if defined?(OpenSSL)
-class OpenSSL::TestX509Request < Test::Unit::TestCase
+class OpenSSL::TestX509Request < OpenSSL::TestCase
def setup
- @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024
- @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048
- @dsa256 = OpenSSL::TestUtils::TEST_KEY_DSA256
- @dsa512 = OpenSSL::TestUtils::TEST_KEY_DSA512
+ super
+ @rsa1 = Fixtures.pkey("rsa-1")
+ @rsa2 = Fixtures.pkey("rsa-2")
@dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou")
end
@@ -20,42 +15,38 @@ class OpenSSL::TestX509Request < Test::Unit::TestCase
req = OpenSSL::X509::Request.new
req.version = ver
req.subject = dn
- req.public_key = key.public_key
+ req.public_key = key
req.sign(key, digest)
req
end
def test_public_key
- req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
- assert_equal(@rsa1024.public_key.to_der, req.public_key.to_der)
- req = OpenSSL::X509::Request.new(req.to_der)
- assert_equal(@rsa1024.public_key.to_der, req.public_key.to_der)
-
- req = issue_csr(0, @dn, @dsa512, OpenSSL::Digest::DSS1.new)
- assert_equal(@dsa512.public_key.to_der, req.public_key.to_der)
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
+ assert_kind_of(OpenSSL::PKey::RSA, req.public_key)
+ assert_equal(@rsa1.public_to_der, req.public_key.public_to_der)
req = OpenSSL::X509::Request.new(req.to_der)
- assert_equal(@dsa512.public_key.to_der, req.public_key.to_der)
+ assert_equal(@rsa1.public_to_der, req.public_key.public_to_der)
end
def test_version
- req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
assert_equal(0, req.version)
req = OpenSSL::X509::Request.new(req.to_der)
assert_equal(0, req.version)
-
- req = issue_csr(1, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
- assert_equal(1, req.version)
- req = OpenSSL::X509::Request.new(req.to_der)
- assert_equal(1, req.version)
end
def test_subject
- req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
assert_equal(@dn.to_der, req.subject.to_der)
req = OpenSSL::X509::Request.new(req.to_der)
assert_equal(@dn.to_der, req.subject.to_der)
end
+ def test_signature_algorithm
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
+ assert_equal("sha256WithRSAEncryption", req.signature_algorithm) # ln
+ end
+
def create_ext_req(exts)
ef = OpenSSL::X509::ExtensionFactory.new
exts = exts.collect{|e| ef.create_extension(*e) }
@@ -81,9 +72,9 @@ class OpenSSL::TestX509Request < Test::Unit::TestCase
OpenSSL::X509::Attribute.new("msExtReq", attrval),
]
- req0 = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
+ req0 = issue_csr(0, @dn, @rsa1, "SHA256")
attrs.each{|attr| req0.add_attribute(attr) }
- req1 = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
+ req1 = issue_csr(0, @dn, @rsa1, "SHA256")
req1.attributes = attrs
assert_equal(req0.to_der, req1.to_der)
@@ -103,37 +94,63 @@ class OpenSSL::TestX509Request < Test::Unit::TestCase
assert_equal(exts, get_ext_req(attrs[1].value))
end
+ def test_sign_digest_instance
+ req1 = issue_csr(0, @dn, @rsa1, "SHA256")
+ req2 = issue_csr(0, @dn, @rsa1, OpenSSL::Digest.new("SHA256"))
+ assert_equal(req1.to_der, req2.to_der)
+ end
+
def test_sign_and_verify
- req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
- assert_equal(true, req.verify(@rsa1024))
- assert_equal(false, req.verify(@rsa2048))
- assert_equal(false, req.verify(@dsa256))
- assert_equal(false, req.verify(@dsa512))
- req.version = 1
- assert_equal(false, req.verify(@rsa1024))
-
- req = issue_csr(0, @dn, @rsa2048, OpenSSL::Digest::MD5.new)
- assert_equal(false, req.verify(@rsa1024))
- assert_equal(true, req.verify(@rsa2048))
- assert_equal(false, req.verify(@dsa256))
- assert_equal(false, req.verify(@dsa512))
- req.subject = OpenSSL::X509::Name.parse("/C=JP/CN=FooBar")
- assert_equal(false, req.verify(@rsa2048))
-
- req = issue_csr(0, @dn, @dsa512, OpenSSL::Digest::DSS1.new)
- assert_equal(false, req.verify(@rsa1024))
- assert_equal(false, req.verify(@rsa2048))
- assert_equal(false, req.verify(@dsa256))
- assert_equal(true, req.verify(@dsa512))
- req.public_key = @rsa1024.public_key
- assert_equal(false, req.verify(@dsa512))
-
- assert_raise(OpenSSL::X509::RequestError){
- issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::DSS1.new) }
- assert_raise(OpenSSL::X509::RequestError){
- issue_csr(0, @dn, @dsa512, OpenSSL::Digest::SHA1.new) }
- assert_raise(OpenSSL::X509::RequestError){
- issue_csr(0, @dn, @dsa512, OpenSSL::Digest::MD5.new) }
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
+ assert_equal(true, req.verify(@rsa1))
+ assert_equal(false, req.verify(@rsa2))
+ ec = OpenSSL::PKey::EC.generate("prime256v1")
+ assert_equal(false, request_error_returns_false { req.verify(ec) })
+ req.subject = OpenSSL::X509::Name.parse_rfc2253("CN=FooBarFooBar,C=JP")
+ assert_equal(false, req.verify(@rsa1))
+ end
+
+ def test_sign_and_verify_nil_digest
+ # Ed25519 is not FIPS-approved.
+ omit_on_fips
+ ed25519 = OpenSSL::PKey::generate_key("ED25519")
+ req = issue_csr(0, @dn, ed25519, nil)
+ assert_equal(false, request_error_returns_false { req.verify(@rsa1) })
+ assert_equal(false, request_error_returns_false { req.verify(@rsa2) })
+ assert_equal(false, req.verify(OpenSSL::PKey::generate_key("ED25519")))
+ assert_equal(true, req.verify(ed25519))
+ req.public_key = @rsa1
+ assert_equal(false, req.verify(ed25519))
+ end
+
+ def test_dup
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
+ assert_equal(req.to_der, req.dup.to_der)
+ end
+
+ def test_eq
+ req1 = issue_csr(0, @dn, @rsa1, "SHA256")
+ req2 = issue_csr(0, @dn, @rsa1, "SHA256")
+ req3 = issue_csr(0, @dn, @rsa1, "SHA512")
+
+ assert_equal false, req1 == 12345
+ assert_equal true, req1 == req2
+ assert_equal false, req1 == req3
+ end
+
+ def test_marshal
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
+ deserialized = Marshal.load(Marshal.dump(req))
+
+ assert_equal req.to_der, deserialized.to_der
+ end
+
+ private
+
+ def request_error_returns_false
+ yield
+ rescue OpenSSL::X509::RequestError
+ false
end
end
diff --git a/test/openssl/test_x509store.rb b/test/openssl/test_x509store.rb
index b0fe597262..c13beae364 100644
--- a/test/openssl/test_x509store.rb
+++ b/test/openssl/test_x509store.rb
@@ -1,218 +1,382 @@
-begin
- require "openssl"
- require File.join(File.dirname(__FILE__), "utils.rb")
-rescue LoadError
-end
-require "test/unit"
+# frozen_string_literal: true
+require_relative "utils"
if defined?(OpenSSL)
-class OpenSSL::TestX509Store < Test::Unit::TestCase
- def setup
- @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024
- @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048
- @dsa256 = OpenSSL::TestUtils::TEST_KEY_DSA256
- @dsa512 = OpenSSL::TestUtils::TEST_KEY_DSA512
- @ca1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA1")
- @ca2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA2")
- @ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1")
- @ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2")
+class OpenSSL::TestX509Store < OpenSSL::TestCase
+ def test_store_new
+ # v2.3.0 emits explicit warning
+ assert_warning(/new does not take any arguments/) {
+ OpenSSL::X509::Store.new(123)
+ }
end
- def teardown
- end
+ def test_add_file_path
+ ca_exts = [
+ ["basicConstraints", "CA:TRUE", true],
+ ["keyUsage", "cRLSign,keyCertSign", true],
+ ]
+ cert1_subj = OpenSSL::X509::Name.parse_rfc2253("CN=Cert 1")
+ cert1_key = Fixtures.pkey("rsa-1")
+ cert1 = issue_cert(cert1_subj, cert1_key, 1, ca_exts, nil, nil)
+ cert2_subj = OpenSSL::X509::Name.parse_rfc2253("CN=Cert 2")
+ cert2_key = Fixtures.pkey("rsa-2")
+ cert2 = issue_cert(cert2_subj, cert2_key, 1, ca_exts, nil, nil)
- def issue_cert(*args)
- OpenSSL::TestUtils.issue_cert(*args)
- end
+ # X509::Store#add_file reads concatenated PEM file
+ tmpfile = Tempfile.open { |f| f << cert1.to_pem << cert2.to_pem; f }
+ store = OpenSSL::X509::Store.new
+ assert_equal false, store.verify(cert1)
+ assert_equal false, store.verify(cert2)
+ store.add_file(tmpfile.path)
+ assert_equal true, store.verify(cert1)
+ assert_equal true, store.verify(cert2)
+
+ # X509::Store#add_path
+ Dir.mktmpdir do |dir|
+ hash1 = "%08x.%d" % [cert1_subj.hash, 0]
+ File.write(File.join(dir, hash1), cert1.to_pem)
+ store = OpenSSL::X509::Store.new
+ store.add_path(dir)
- def issue_crl(*args)
- OpenSSL::TestUtils.issue_crl(*args)
+ assert_equal true, store.verify(cert1)
+ assert_equal false, store.verify(cert2)
+ end
+
+ # OpenSSL < 1.1.1 leaks an error on a duplicate certificate
+ assert_nothing_raised { store.add_file(tmpfile.path) }
+ assert_equal [], OpenSSL.errors
+
+ # Non-String is given
+ assert_raise(TypeError) { store.add_file(nil) }
+ ensure
+ tmpfile and tmpfile.close!
end
- def test_verify
- now = Time.at(Time.now.to_i)
+ def test_verify_simple
ca_exts = [
- ["basicConstraints","CA:TRUE",true],
- ["keyUsage","cRLSign,keyCertSign",true],
+ ["basicConstraints", "CA:TRUE", true],
+ ["keyUsage", "cRLSign,keyCertSign", true],
]
+ ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
+ ca1_key = Fixtures.pkey("rsa-1")
+ ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil)
+ ca2 = OpenSSL::X509::Name.parse_rfc2253("CN=Intermediate CA")
+ ca2_key = Fixtures.pkey("rsa-2")
+ ca2_cert = issue_cert(ca2, ca2_key, 2, ca_exts, ca1_cert, ca1_key)
+
ee_exts = [
- ["keyUsage","keyEncipherment,digitalSignature",true],
+ ["keyUsage", "keyEncipherment,digitalSignature", true],
]
- ca1_cert = issue_cert(@ca1, @rsa2048, 1, now, now+3600, ca_exts,
- nil, nil, OpenSSL::Digest::SHA1.new)
- ca2_cert = issue_cert(@ca2, @rsa1024, 2, now, now+1800, ca_exts,
- ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new)
- ee1_cert = issue_cert(@ee1, @dsa256, 10, now, now+1800, ee_exts,
- ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new)
- ee2_cert = issue_cert(@ee2, @dsa512, 20, now, now+1800, ee_exts,
- ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new)
- ee3_cert = issue_cert(@ee2, @dsa512, 30, now-100, now-1, ee_exts,
- ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new)
- ee4_cert = issue_cert(@ee2, @dsa512, 40, now+1000, now+2000, ee_exts,
- ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new)
+ ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
+ ee1_key = Fixtures.pkey("rsa-3")
+ ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca2_cert, ca2_key)
- revoke_info = []
- crl1 = issue_crl(revoke_info, 1, now, now+1800, [],
- ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new)
- revoke_info = [ [2, now, 1], ]
- crl1_2 = issue_crl(revoke_info, 2, now, now+1800, [],
- ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new)
- revoke_info = [ [20, now, 1], ]
- crl2 = issue_crl(revoke_info, 1, now, now+1800, [],
- ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new)
- revoke_info = []
- crl2_2 = issue_crl(revoke_info, 2, now-100, now-1, [],
- ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new)
+ # Nothing trusted
+ store = OpenSSL::X509::Store.new
+ assert_equal(false, store.verify(ee1_cert, [ca2_cert, ca1_cert]))
+ assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, store.error)
+ assert_match(/self.signed/i, store.error_string)
- assert(true, ca1_cert.verify(ca1_cert.public_key)) # self signed
- assert(true, ca2_cert.verify(ca1_cert.public_key)) # issued by ca1
- assert(true, ee1_cert.verify(ca2_cert.public_key)) # issued by ca2
- assert(true, ee2_cert.verify(ca2_cert.public_key)) # issued by ca2
- assert(true, ee3_cert.verify(ca2_cert.public_key)) # issued by ca2
- assert(true, crl1.verify(ca1_cert.public_key)) # issued by ca1
- assert(true, crl1_2.verify(ca1_cert.public_key)) # issued by ca1
- assert(true, crl2.verify(ca2_cert.public_key)) # issued by ca2
- assert(true, crl2_2.verify(ca2_cert.public_key)) # issued by ca2
+ # CA1 trusted, CA2 missing
+ store = OpenSSL::X509::Store.new
+ store.add_cert(ca1_cert)
+ assert_equal(false, store.verify(ee1_cert))
+ assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, store.error)
+ # CA1 trusted, CA2 supplied
store = OpenSSL::X509::Store.new
- assert_equal(false, store.verify(ca1_cert))
- assert_not_equal(OpenSSL::X509::V_OK, store.error)
+ store.add_cert(ca1_cert)
+ assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
+ assert_match(/ok/i, store.error_string)
+ assert_equal(OpenSSL::X509::V_OK, store.error)
+ assert_equal([ee1_cert, ca2_cert, ca1_cert], store.chain)
- assert_equal(false, store.verify(ca2_cert))
- assert_not_equal(OpenSSL::X509::V_OK, store.error)
+ # Manually instantiated StoreContext
+ # Nothing trusted
+ store = OpenSSL::X509::Store.new
+ ctx = OpenSSL::X509::StoreContext.new(store, ee1_cert)
+ assert_nil(ctx.current_cert)
+ assert_nil(ctx.current_crl)
+ assert_equal(false, ctx.verify)
+ assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, ctx.error)
+ assert_equal(0, ctx.error_depth)
+ assert_equal([ee1_cert], ctx.chain)
+ assert_equal(ee1_cert, ctx.current_cert)
+ end
+
+ def test_verify_callback
+ ca_exts = [
+ ["basicConstraints", "CA:TRUE", true],
+ ["keyUsage", "cRLSign,keyCertSign", true],
+ ]
+ ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
+ ca1_key = Fixtures.pkey("rsa-1")
+ ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil)
+ ca2 = OpenSSL::X509::Name.parse_rfc2253("CN=Intermediate CA")
+ ca2_key = Fixtures.pkey("rsa-2")
+ ca2_cert = issue_cert(ca2, ca2_key, 2, ca_exts, ca1_cert, ca1_key)
+
+ ee_exts = [
+ ["keyUsage", "keyEncipherment,digitalSignature", true],
+ ]
+ ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
+ ee1_key = Fixtures.pkey("rsa-3")
+ ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca2_cert, ca2_key)
+
+ # verify_callback on X509::Store is called with proper arguments
+ cb_calls = []
+ store = OpenSSL::X509::Store.new
+ store.verify_callback = -> (preverify_ok, sctx) {
+ cb_calls << [preverify_ok, sctx.current_cert]
+ preverify_ok
+ }
+ store.add_cert(ca1_cert)
+ assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
+ assert_include([2, 3, 4, 5], cb_calls.size)
+ cb_calls.each do |pre_ok, cert|
+ assert_equal(true, pre_ok)
+ assert_include([ca1_cert, ca2_cert, ee1_cert], cert)
+ end
+ # verify_callback can change verification result
+ store = OpenSSL::X509::Store.new
+ store.verify_callback = -> (preverify_ok, sctx) {
+ next preverify_ok if sctx.current_cert != ee1_cert
+ sctx.error = OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION
+ false
+ }
store.add_cert(ca1_cert)
- assert_equal(true, store.verify(ca2_cert))
- assert_equal(OpenSSL::X509::V_OK, store.error)
- assert_equal("ok", store.error_string)
- chain = store.chain
- assert_equal(2, chain.size)
- assert_equal(@ca2.to_der, chain[0].subject.to_der)
- assert_equal(@ca1.to_der, chain[1].subject.to_der)
+ assert_equal(false, store.verify(ee1_cert, [ca2_cert]))
+ assert_equal(OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION, store.error)
- store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
- assert_equal(false, store.verify(ca2_cert))
- assert_not_equal(OpenSSL::X509::V_OK, store.error)
+ # Exception raised by verify_callback is currently suppressed, and is
+ # treated as a non-truthy return (with warning)
+ store = OpenSSL::X509::Store.new
+ store.verify_callback = -> (preverify_ok, sctx) {
+ raise "suppressed"
+ }
+ store.add_cert(ca1_cert)
+ assert_warning(/exception in verify_callback/) {
+ assert_equal(false, store.verify(ee1_cert, [ca2_cert]))
+ }
- store.purpose = OpenSSL::X509::PURPOSE_CRL_SIGN
- assert_equal(true, store.verify(ca2_cert))
- assert_equal(OpenSSL::X509::V_OK, store.error)
+ # The block given to X509::Store#verify replaces it
+ called = nil
+ store = OpenSSL::X509::Store.new
+ store.verify_callback = -> (preverify_ok, sctx) { called = :store; preverify_ok }
+ store.add_cert(ca1_cert)
+ blk = proc { |preverify_ok, sctx| called = :block; preverify_ok }
+ assert_equal(true, store.verify(ee1_cert, [ca2_cert], &blk))
+ assert_equal(:block, called)
+ end
- store.add_cert(ca2_cert)
- store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
- assert_equal(true, store.verify(ee1_cert))
- assert_equal(true, store.verify(ee2_cert))
- assert_equal(OpenSSL::X509::V_OK, store.error)
- assert_equal("ok", store.error_string)
- chain = store.chain
- assert_equal(3, chain.size)
- assert_equal(@ee2.to_der, chain[0].subject.to_der)
- assert_equal(@ca2.to_der, chain[1].subject.to_der)
- assert_equal(@ca1.to_der, chain[2].subject.to_der)
- assert_equal(false, store.verify(ee3_cert))
- assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
- assert_match(/expire/i, store.error_string)
- assert_equal(false, store.verify(ee4_cert))
- assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error)
- assert_match(/not yet valid/i, store.error_string)
+ def test_verify_purpose
+ ca_exts = [
+ ["basicConstraints", "CA:TRUE", true],
+ ["keyUsage", "cRLSign,keyCertSign", true],
+ ]
+ ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
+ ca1_key = Fixtures.pkey("rsa-1")
+ ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil)
+
+ ee_exts = [
+ ["keyUsage", "keyEncipherment,digitalSignature", true],
+ ]
+ ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
+ ee1_key = Fixtures.pkey("rsa-3")
+ ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca1_cert, ca1_key)
+ # Purpose not set
store = OpenSSL::X509::Store.new
store.add_cert(ca1_cert)
- store.add_cert(ca2_cert)
- store.time = now + 1500
assert_equal(true, store.verify(ca1_cert))
- assert_equal(true, store.verify(ca2_cert))
- assert_equal(true, store.verify(ee4_cert))
- store.time = now + 1900
+ assert_equal(true, store.verify(ee1_cert))
+
+ # Purpose set to X509::PURPOSE_SSL_CLIENT; keyUsage is checked
+ store = OpenSSL::X509::Store.new
+ store.purpose = OpenSSL::X509::PURPOSE_CRL_SIGN
+ store.add_cert(ca1_cert)
assert_equal(true, store.verify(ca1_cert))
- assert_equal(false, store.verify(ca2_cert))
- assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
- assert_equal(false, store.verify(ee4_cert))
- assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
- store.time = now + 4000
assert_equal(false, store.verify(ee1_cert))
- assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
- assert_equal(false, store.verify(ee4_cert))
- assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
+ end
- # the underlying X509 struct caches the result of the last
- # verification for signature and not-before. so the following code
- # rebuilds new objects to avoid site effect.
- store.time = Time.now - 4000
- assert_equal(false, store.verify(OpenSSL::X509::Certificate.new(ca2_cert)))
+ def test_verify_validity_period
+ # Creating test certificates with validity periods:
+ #
+ # now-5000 now-1000 now+1000 now+5000
+ # CA1:|---------------------------------------------------------------|
+ # EE1:|---------------------------------------------------------------|
+ # EE2:|-------------------------|
+ # EE3: |-------------------------|
+ now = Time.now
+ ca_exts = [
+ ["basicConstraints", "CA:TRUE", true],
+ ["keyUsage", "cRLSign,keyCertSign", true],
+ ]
+ ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
+ ca1_key = Fixtures.pkey("rsa-1")
+ ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil,
+ not_before: now - 5000, not_after: now + 5000)
+
+ ee_exts = [
+ ["keyUsage", "keyEncipherment,digitalSignature", true],
+ ]
+ ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
+ ee1_key = Fixtures.pkey("rsa-1")
+ ee1_cert = issue_cert(ee1, ee1_key, 11, ee_exts, ca1_cert, ca1_key,
+ not_before: now - 5000, not_after: now + 5000)
+ ee2 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 2")
+ ee2_key = Fixtures.pkey("rsa-2")
+ ee2_cert = issue_cert(ee2, ee2_key, 12, ee_exts, ca1_cert, ca1_key,
+ not_before: now - 5000, not_after: now - 1000)
+ ee3 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 3")
+ ee3_key = Fixtures.pkey("rsa-3")
+ ee3_cert = issue_cert(ee3, ee3_key, 13, ee_exts, ca1_cert, ca1_key,
+ not_before: now + 1000, not_after: now + 5000)
+
+ # Using system time
+ store = OpenSSL::X509::Store.new
+ store.add_cert(ca1_cert)
+ assert_equal(true, store.verify(ee1_cert))
+ assert_equal(false, store.verify(ee2_cert))
+ assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
+ assert_equal(false, store.verify(ee3_cert))
assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error)
- assert_equal(false, store.verify(OpenSSL::X509::Certificate.new(ee1_cert)))
+
+ # Time set to now-2000; EE2 is still valid
+ store = OpenSSL::X509::Store.new
+ store.time = now - 2000
+ store.add_cert(ca1_cert)
+ assert_equal(true, store.verify(ee1_cert))
+ assert_equal(true, store.verify(ee2_cert))
+ assert_equal(false, store.verify(ee3_cert))
assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error)
+ end
+
+ def test_verify_with_crl
+ ca_exts = [
+ ["basicConstraints", "CA:TRUE", true],
+ ["keyUsage", "cRLSign,keyCertSign", true],
+ ]
+ ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
+ ca1_key = Fixtures.pkey("rsa-1")
+ ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil)
+ ca2 = OpenSSL::X509::Name.parse_rfc2253("CN=Intermediate CA")
+ ca2_key = Fixtures.pkey("rsa-2")
+ ca2_cert = issue_cert(ca2, ca2_key, 2, ca_exts, ca1_cert, ca1_key)
+
+ ee_exts = [
+ ["keyUsage", "keyEncipherment,digitalSignature", true],
+ ]
+ ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
+ ee1_key = Fixtures.pkey("rsa-3")
+ ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca2_cert, ca2_key)
+ ee2 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 2")
+ ee2_key = Fixtures.pkey("rsa-3")
+ ee2_cert = issue_cert(ee2, ee2_key, 20, ee_exts, ca2_cert, ca2_key)
- return unless defined?(OpenSSL::X509::V_FLAG_CRL_CHECK)
+ # OpenSSL uses time(2) while Time.now uses clock_gettime(CLOCK_REALTIME),
+ # and there may be difference, so giving 50 seconds margin.
+ now = Time.now - 50
+ revoke_info = []
+ ca1_crl1 = issue_crl(revoke_info, 1, now, now+1800, [], ca1_cert, ca1_key, "sha256")
+ revoke_info = [ [2, now, 1], ]
+ ca1_crl2 = issue_crl(revoke_info, 2, now, now+1800, [], ca1_cert, ca1_key, "sha256")
+
+ revoke_info = [ [20, now, 1], ]
+ ca2_crl1 = issue_crl(revoke_info, 1, now, now+1800, [], ca2_cert, ca2_key, "sha256")
+ revoke_info = []
+ ca2_crl2 = issue_crl(revoke_info, 2, now-100, now-1, [], ca2_cert, ca2_key, "sha256")
+ # CRL check required, but no CRL supplied
store = OpenSSL::X509::Store.new
- store.purpose = OpenSSL::X509::PURPOSE_ANY
store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK
store.add_cert(ca1_cert)
- store.add_crl(crl1) # revoke no cert
- store.add_crl(crl2) # revoke ee2_cert
- assert_equal(true, store.verify(ca1_cert))
- assert_equal(true, store.verify(ca2_cert))
- assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
+ assert_equal(false, store.verify(ca2_cert))
+ assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_CRL, store.error)
+
+ # Intermediate CA revoked EE2
+ store = OpenSSL::X509::Store.new
+ store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK
+ store.add_cert(ca1_cert)
+ store.add_crl(ca1_crl1) # revoke no cert
+ store.add_crl(ca2_crl1) # revoke ee2_cert
+ assert_equal(true, store.verify(ca2_cert))
+ assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
assert_equal(false, store.verify(ee2_cert, [ca2_cert]))
+ # Root CA revoked Intermediate CA; Intermediate CA revoked EE2
store = OpenSSL::X509::Store.new
- store.purpose = OpenSSL::X509::PURPOSE_ANY
store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK
store.add_cert(ca1_cert)
- store.add_crl(crl1_2) # revoke ca2_cert
- store.add_crl(crl2) # revoke ee2_cert
- assert_equal(true, store.verify(ca1_cert))
+ store.add_crl(ca1_crl2) # revoke ca2_cert
+ store.add_crl(ca2_crl1) # revoke ee2_cert
assert_equal(false, store.verify(ca2_cert))
- assert_equal(true, store.verify(ee1_cert, [ca2_cert]),
- "This test is expected to be success with OpenSSL 0.9.7c or later.")
+ # Validity of intermediate CAs is not checked by default
+ assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
assert_equal(false, store.verify(ee2_cert, [ca2_cert]))
- store.flags =
- OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
- assert_equal(true, store.verify(ca1_cert))
+ # Same as above, but with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
+ store = OpenSSL::X509::Store.new
+ store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
+ store.add_cert(ca1_cert)
+ store.add_crl(ca1_crl2) # revoke ca2_cert
+ store.add_crl(ca2_crl1) # revoke ee2_cert
assert_equal(false, store.verify(ca2_cert))
assert_equal(false, store.verify(ee1_cert, [ca2_cert]))
assert_equal(false, store.verify(ee2_cert, [ca2_cert]))
+ # Expired CRL supplied
store = OpenSSL::X509::Store.new
- store.purpose = OpenSSL::X509::PURPOSE_ANY
- store.flags =
- OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
+ store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
store.add_cert(ca1_cert)
store.add_cert(ca2_cert)
- store.add_crl(crl1)
- store.add_crl(crl2_2) # issued by ca2 but expired.
- assert_equal(true, store.verify(ca1_cert))
+ store.add_crl(ca1_crl1)
+ store.add_crl(ca2_crl2) # issued by ca2 but expired
assert_equal(true, store.verify(ca2_cert))
assert_equal(false, store.verify(ee1_cert))
assert_equal(OpenSSL::X509::V_ERR_CRL_HAS_EXPIRED, store.error)
assert_equal(false, store.verify(ee2_cert))
end
- def test_set_errors
- now = Time.now
- ca1_cert = issue_cert(@ca1, @rsa2048, 1, now, now+3600, [],
- nil, nil, OpenSSL::Digest::SHA1.new)
+ def test_add_cert_duplicate
+ ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
+ ca1_key = Fixtures.pkey("rsa-1")
+ ca1_cert = issue_cert(ca1, ca1_key, 1, [], nil, nil)
store = OpenSSL::X509::Store.new
store.add_cert(ca1_cert)
- assert_raises(OpenSSL::X509::StoreError){
+ assert_nothing_raised {
store.add_cert(ca1_cert) # add same certificate twice
}
+ now = Time.now
revoke_info = []
crl1 = issue_crl(revoke_info, 1, now, now+1800, [],
- ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+ ca1_cert, ca1_key, "sha256")
revoke_info = [ [2, now, 1], ]
crl2 = issue_crl(revoke_info, 2, now+1800, now+3600, [],
- ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+ ca1_cert, ca1_key, "sha256")
store.add_crl(crl1)
- assert_raises(OpenSSL::X509::StoreError){
+ assert_nothing_raised {
store.add_crl(crl2) # add CRL issued by same CA twice.
}
end
+
+ def test_dup
+ store = OpenSSL::X509::Store.new
+ assert_raise(NoMethodError) { store.dup }
+ ctx = OpenSSL::X509::StoreContext.new(store)
+ assert_raise(NoMethodError) { ctx.dup }
+ end
+
+ def test_ctx_cleanup
+ # Deprecated in Ruby 1.9.3
+ cert = OpenSSL::X509::Certificate.new
+ store = OpenSSL::X509::Store.new
+ ctx = OpenSSL::X509::StoreContext.new(store, cert, [])
+ assert_warning(/cleanup/) { ctx.cleanup }
+ end
end
end
diff --git a/test/openssl/ut_eof.rb b/test/openssl/ut_eof.rb
new file mode 100644
index 0000000000..06aa632a65
--- /dev/null
+++ b/test/openssl/ut_eof.rb
@@ -0,0 +1,141 @@
+# frozen_string_literal: true
+require 'test/unit'
+
+if defined?(OpenSSL)
+
+module OpenSSL::TestEOF
+ def test_getbyte_eof
+ open_file("") {|f| assert_nil f.getbyte }
+ end
+
+ def test_readbyte_eof
+ open_file("") {|f| assert_raise(EOFError) { f.readbyte } }
+ end
+
+ def test_eof_0
+ open_file("") {|f|
+ assert_equal("", f.read(0))
+ assert_equal("", f.read(0))
+ assert_equal("", f.read)
+ assert_equal("", f.read(0))
+ assert_equal("", f.read(0))
+ }
+ open_file("") {|f|
+ assert_nil(f.read(1))
+ assert_equal("", f.read)
+ assert_nil(f.read(1))
+ }
+ open_file("") {|f|
+ s = +"x"
+ assert_equal("", f.read(nil, s))
+ assert_equal("", s)
+ }
+ open_file("") {|f|
+ s = +"x"
+ assert_nil(f.read(10, s))
+ assert_equal("", s)
+ }
+ end
+
+ def test_eof_0_rw
+ return unless respond_to? :open_file_rw
+ open_file_rw("") {|f|
+ assert_equal("", f.read)
+ assert_equal("", f.read)
+ assert_equal(0, f.syswrite(""))
+ assert_equal("", f.read)
+ }
+ end
+
+ def test_eof_1
+ open_file("a") {|f|
+ assert_equal("", f.read(0))
+ assert_equal("a", f.read(1))
+ assert_equal("" , f.read(0))
+ assert_equal("" , f.read(0))
+ assert_equal("", f.read)
+ assert_equal("", f.read(0))
+ assert_equal("", f.read(0))
+ }
+ open_file("a") {|f|
+ assert_equal("a", f.read(1))
+ assert_nil(f.read(1))
+ }
+ open_file("a") {|f|
+ assert_equal("a", f.read(2))
+ assert_nil(f.read(1))
+ assert_equal("", f.read)
+ assert_nil(f.read(1))
+ }
+ open_file("a") {|f|
+ assert_equal("a", f.read)
+ assert_nil(f.read(1))
+ assert_equal("", f.read)
+ assert_nil(f.read(1))
+ }
+ open_file("a") {|f|
+ assert_equal("a", f.read(2))
+ assert_equal("", f.read)
+ assert_equal("", f.read)
+ }
+ open_file("a") {|f|
+ assert_equal("a", f.read)
+ assert_equal("", f.read(0))
+ }
+ open_file("a") {|f|
+ s = +"x"
+ assert_equal("a", f.read(nil, s))
+ assert_equal("a", s)
+ }
+ open_file("a") {|f|
+ s = +"x"
+ assert_equal("a", f.read(10, s))
+ assert_equal("a", s)
+ }
+ end
+
+ def test_eof_2
+ open_file("") {|f|
+ assert_equal("", f.read)
+ assert_predicate(f, :eof?)
+ }
+ end
+
+ def test_eof_3
+ open_file("") {|f|
+ assert_predicate(f, :eof?)
+ }
+ end
+
+ module Seek
+ def open_file_seek(content, pos)
+ open_file(content) do |f|
+ f.seek(pos)
+ yield f
+ end
+ end
+
+ def test_eof_0_seek
+ open_file_seek("", 10) {|f|
+ assert_equal(10, f.pos)
+ assert_equal("", f.read(0))
+ assert_equal("", f.read)
+ assert_equal("", f.read(0))
+ assert_equal("", f.read)
+ }
+ end
+
+ def test_eof_1_seek
+ open_file_seek("a", 10) {|f|
+ assert_equal("", f.read)
+ assert_equal("", f.read)
+ }
+ open_file_seek("a", 1) {|f|
+ assert_equal("", f.read)
+ assert_equal("", f.read)
+ }
+ end
+ end
+end
+
+end
diff --git a/test/openssl/utils.rb b/test/openssl/utils.rb
index c923705b86..7e6fe8b163 100644
--- a/test/openssl/utils.rb
+++ b/test/openssl/utils.rb
@@ -1,91 +1,55 @@
-require "openssl"
+# frozen_string_literal: true
+begin
+ require "openssl"
+rescue LoadError
+end
+
require "test/unit"
+require "core_assertions"
+require "tempfile"
+require "socket"
+
+if defined?(OpenSSL)
module OpenSSL::TestUtils
- TEST_KEY_RSA1024 = OpenSSL::PKey::RSA.new <<-_end_of_pem_
------BEGIN RSA PRIVATE KEY-----
-MIICXgIBAAKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7Cx
-aKPERYHsk4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbCz0layNqHyywQEVLFmp1cpIt/
-Q3geLv8ZD9pihowKJDyMDiN6ArYUmZczvW4976MU3+l54E6lF/JfFEU5hwIDAQAB
-AoGBAKSl/MQarye1yOysqX6P8fDFQt68VvtXkNmlSiKOGuzyho0M+UVSFcs6k1L0
-maDE25AMZUiGzuWHyaU55d7RXDgeskDMakD1v6ZejYtxJkSXbETOTLDwUWTn618T
-gnb17tU1jktUtU67xK/08i/XodlgnQhs6VoHTuCh3Hu77O6RAkEA7+gxqBuZR572
-74/akiW/SuXm0SXPEviyO1MuSRwtI87B02D0qgV8D1UHRm4AhMnJ8MCs1809kMQE
-JiQUCrp9mQJBANlt2ngBO14us6NnhuAseFDTBzCHXwUUu1YKHpMMmxpnGqaldGgX
-sOZB3lgJsT9VlGf3YGYdkLTNVbogQKlKpB8CQQDiSwkb4vyQfDe8/NpU5Not0fII
-8jsDUCb+opWUTMmfbxWRR3FBNu8wnym/m19N4fFj8LqYzHX4KY0oVPu6qvJxAkEA
-wa5snNekFcqONLIE4G5cosrIrb74sqL8GbGb+KuTAprzj5z1K8Bm0UW9lTjVDjDi
-qRYgZfZSL+x1P/54+xTFSwJAY1FxA/N3QPCXCjPh5YqFxAMQs2VVYTfg+t0MEcJD
-dPMQD5JX6g5HKnHFg2mZtoXQrWmJSn7p8GJK8yNTopEErA==
------END RSA PRIVATE KEY-----
- _end_of_pem_
-
- TEST_KEY_RSA2048 = OpenSSL::PKey::RSA.new <<-_end_of_pem_
------BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEAuV9ht9J7k4NBs38jOXvvTKY9gW8nLICSno5EETR1cuF7i4pN
-s9I1QJGAFAX0BEO4KbzXmuOvfCpD3CU+Slp1enenfzq/t/e/1IRW0wkJUJUFQign
-4CtrkJL+P07yx18UjyPlBXb81ApEmAB5mrJVSrWmqbjs07JbuS4QQGGXLc+Su96D
-kYKmSNVjBiLxVVSpyZfAY3hD37d60uG+X8xdW5v68JkRFIhdGlb6JL8fllf/A/bl
-NwdJOhVr9mESHhwGjwfSeTDPfd8ZLE027E5lyAVX9KZYcU00mOX+fdxOSnGqS/8J
-DRh0EPHDL15RcJjV2J6vZjPb0rOYGDoMcH+94wIDAQABAoIBAAzsamqfYQAqwXTb
-I0CJtGg6msUgU7HVkOM+9d3hM2L791oGHV6xBAdpXW2H8LgvZHJ8eOeSghR8+dgq
-PIqAffo4x1Oma+FOg3A0fb0evyiACyrOk+EcBdbBeLo/LcvahBtqnDfiUMQTpy6V
-seSoFCwuN91TSCeGIsDpRjbG1vxZgtx+uI+oH5+ytqJOmfCksRDCkMglGkzyfcl0
-Xc5CUhIJ0my53xijEUQl19rtWdMnNnnkdbG8PT3LZlOta5Do86BElzUYka0C6dUc
-VsBDQ0Nup0P6rEQgy7tephHoRlUGTYamsajGJaAo1F3IQVIrRSuagi7+YpSpCqsW
-wORqorkCgYEA7RdX6MDVrbw7LePnhyuaqTiMK+055/R1TqhB1JvvxJ1CXk2rDL6G
-0TLHQ7oGofd5LYiemg4ZVtWdJe43BPZlVgT6lvL/iGo8JnrncB9Da6L7nrq/+Rvj
-XGjf1qODCK+LmreZWEsaLPURIoR/Ewwxb9J2zd0CaMjeTwafJo1CZvcCgYEAyCgb
-aqoWvUecX8VvARfuA593Lsi50t4MEArnOXXcd1RnXoZWhbx5rgO8/ATKfXr0BK/n
-h2GF9PfKzHFm/4V6e82OL7gu/kLy2u9bXN74vOvWFL5NOrOKPM7Kg+9I131kNYOw
-Ivnr/VtHE5s0dY7JChYWE1F3vArrOw3T00a4CXUCgYEA0SqY+dS2LvIzW4cHCe9k
-IQqsT0yYm5TFsUEr4sA3xcPfe4cV8sZb9k/QEGYb1+SWWZ+AHPV3UW5fl8kTbSNb
-v4ng8i8rVVQ0ANbJO9e5CUrepein2MPL0AkOATR8M7t7dGGpvYV0cFk8ZrFx0oId
-U0PgYDotF/iueBWlbsOM430CgYEAqYI95dFyPI5/AiSkY5queeb8+mQH62sdcCCr
-vd/w/CZA/K5sbAo4SoTj8dLk4evU6HtIa0DOP63y071eaxvRpTNqLUOgmLh+D6gS
-Cc7TfLuFrD+WDBatBd5jZ+SoHccVrLR/4L8jeodo5FPW05A+9gnKXEXsTxY4LOUC
-9bS4e1kCgYAqVXZh63JsMwoaxCYmQ66eJojKa47VNrOeIZDZvd2BPVf30glBOT41
-gBoDG3WMPZoQj9pb7uMcrnvs4APj2FIhMU8U15LcPAj59cD6S6rWnAxO8NFK7HQG
-4Jxg3JNNf8ErQoCHb1B3oVdXJkmbJkARoDpBKmTCgKtP8ADYLmVPQw==
------END RSA PRIVATE KEY-----
- _end_of_pem_
-
- TEST_KEY_DSA256 = OpenSSL::PKey::DSA.new <<-_end_of_pem_
------BEGIN DSA PRIVATE KEY-----
-MIH3AgEAAkEAhk2libbY2a8y2Pt21+YPYGZeW6wzaW2yfj5oiClXro9XMR7XWLkE
-9B7XxLNFCS2gmCCdMsMW1HulaHtLFQmB2wIVAM43JZrcgpu6ajZ01VkLc93gu/Ed
-AkAOhujZrrKV5CzBKutKLb0GVyVWmdC7InoNSMZEeGU72rT96IjM59YzoqmD0pGM
-3I1o4cGqg1D1DfM1rQlnN1eSAkBq6xXfEDwJ1mLNxF6q8Zm/ugFYWR5xcX/3wFiT
-b4+EjHP/DbNh9Vm5wcfnDBJ1zKvrMEf2xqngYdrV/3CiGJeKAhRvL57QvJZcQGvn
-ISNX5cMzFHRW3Q==
------END DSA PRIVATE KEY-----
- _end_of_pem_
-
- TEST_KEY_DSA512 = OpenSSL::PKey::DSA.new <<-_end_of_pem_
------BEGIN DSA PRIVATE KEY-----
-MIH4AgEAAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgTYiEEHaOYhkIxv0Ok
-RZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB4DZGH7UyarcaGy6D
-AkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqoji3/lHdKoVdTQNuR
-S/m6DlCwhjRjiQ/lBRgCLCcaAkEAjN891JBjzpMj4bWgsACmMggFf57DS0Ti+5++
-Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S
-55jreJD3Se3slps=
------END DSA PRIVATE KEY-----
- _end_of_pem_
+ module Fixtures
+ module_function
+
+ def pkey(name)
+ OpenSSL::PKey.read(read_file("pkey", name))
+ end
+
+ def read_file(category, name)
+ @file_cache ||= {}
+ @file_cache[[category, name]] ||=
+ File.read(File.join(__dir__, "fixtures", category, name + ".pem"))
+ end
+ end
module_function
- def issue_cert(dn, key, serial, not_before, not_after, extensions,
- issuer, issuer_key, digest)
+ 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
cert.issuer = issuer.subject
- cert.public_key = key.public_key
- cert.not_before = not_before
- cert.not_after = not_after
+ cert.public_key = key
+ 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
@@ -96,16 +60,16 @@ Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S
cert
end
- def issue_crl(revoke_info, serial, lastup, nextup, extensions,
+ def issue_crl(revoke_info, serial, lastup, nextup, extensions,
issuer, issuer_key, digest)
crl = OpenSSL::X509::CRL.new
crl.issuer = issuer.subject
crl.version = 1
crl.last_update = lastup
crl.next_update = nextup
- revoke_info.each{|serial, time, reason_code|
+ revoke_info.each{|rserial, time, reason_code|
revoked = OpenSSL::X509::Revoked.new
- revoked.serial = serial
+ revoked.serial = rserial
revoked.time = time
enum = OpenSSL::ASN1::Enumerated(reason_code)
ext = OpenSSL::X509::Extension.new("CRLReason", enum)
@@ -124,12 +88,297 @@ Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S
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.digest('SHA1', pkvalue)
+ if hex
+ digest.unpack("H2"*20).join(":").upcase
+ else
+ digest
+ end
+ end
+
+ def openssl?(major = nil, minor = nil, fix = nil, patch = 0, status = 0)
+ return false if OpenSSL::OPENSSL_VERSION.include?("LibreSSL") || OpenSSL::OPENSSL_VERSION.include?("AWS-LC")
+ return true unless major
+ OpenSSL::OPENSSL_VERSION_NUMBER >=
+ major * 0x10000000 + minor * 0x100000 + fix * 0x1000 + patch * 0x10 +
+ status * 0x1
+ end
+
+ def libressl?(major = nil, minor = nil, fix = nil)
+ version = OpenSSL::OPENSSL_VERSION.scan(/LibreSSL (\d+)\.(\d+)\.(\d+).*/)[0]
+ return false unless version
+ !major || (version.map(&:to_i) <=> [major, minor, fix]) >= 0
+ end
+
+ def aws_lc?
+ OpenSSL::OPENSSL_VERSION.include?("AWS-LC")
end
end
+
+class OpenSSL::TestCase < Test::Unit::TestCase
+ include OpenSSL::TestUtils
+ extend OpenSSL::TestUtils
+ include Test::Unit::CoreAssertions
+
+ def setup
+ if ENV["OSSL_GC_STRESS"] == "1"
+ GC.stress = true
+ end
+ end
+
+ def teardown
+ if ENV["OSSL_GC_STRESS"] == "1"
+ GC.stress = false
+ end
+ # OpenSSL error stack must be empty
+ assert_equal([], OpenSSL.errors)
+ end
+
+ # Omit the tests in FIPS.
+ #
+ # For example, the password based encryption used in the PEM format uses MD5
+ # for deriving the encryption key from the password, and MD5 is not
+ # FIPS-approved.
+ #
+ # See https://github.com/openssl/openssl/discussions/21830#discussioncomment-6865636
+ # for details.
+ def omit_on_fips
+ return unless OpenSSL.fips_mode
+
+ omit <<~MESSAGE
+ Only for OpenSSL non-FIPS with the following possible reasons:
+ * A testing logic is non-FIPS specific.
+ * An encryption used in the test is not FIPS-approved.
+ MESSAGE
+ end
+
+ def omit_on_non_fips
+ return if OpenSSL.fips_mode
+
+ omit "Only for OpenSSL FIPS"
+ end
+end
+
+class OpenSSL::SSLTestCase < OpenSSL::TestCase
+ RUBY = EnvUtil.rubybin
+ ITERATIONS = ($0 == __FILE__) ? 100 : 10
+
+ def setup
+ super
+ @ca_key = Fixtures.pkey("rsa-1")
+ @svr_key = Fixtures.pkey("rsa-2")
+ @cli_key = Fixtures.pkey("rsa-3")
+ @ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
+ @svr = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost")
+ @cli = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost")
+ @ca_exts = [
+ ["basicConstraints","CA:TRUE",true],
+ ["keyUsage","cRLSign,keyCertSign",true],
+ ]
+ @ee_exts = [
+ ["keyUsage","keyEncipherment,digitalSignature",true],
+ ]
+ @ca_cert = issue_cert(@ca, @ca_key, 1, @ca_exts, nil, nil)
+ @svr_cert = issue_cert(@svr, @svr_key, 2, @ee_exts, @ca_cert, @ca_key)
+ @cli_cert = issue_cert(@cli, @cli_key, 3, @ee_exts, @ca_cert, @ca_key)
+ @server = nil
+ end
+
+ def readwrite_loop(ctx, ssl)
+ while line = ssl.gets
+ ssl.write(line)
+ end
+ end
+
+ def start_server(verify_mode: OpenSSL::SSL::VERIFY_NONE,
+ ctx_proc: nil, server_proc: method(:readwrite_loop),
+ accept_proc: proc{},
+ ignore_listener_error: false, &block)
+ IO.pipe {|stop_pipe_r, stop_pipe_w|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.cert = @svr_cert
+ ctx.key = @svr_key
+ ctx.verify_mode = verify_mode
+ ctx_proc.call(ctx) if ctx_proc
+
+ Socket.do_not_reverse_lookup = true
+ tcps = TCPServer.new("127.0.0.1", 0)
+ port = tcps.connect_address.ip_port
+
+ ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx)
+
+ threads = []
+ begin
+ server_thread = Thread.new do
+ Thread.current.report_on_exception = false
+
+ begin
+ loop do
+ begin
+ 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
+ raise
+ end
+
+ th = Thread.new do
+ Thread.current.report_on_exception = false
+
+ begin
+ server_proc.call(ctx, ssl)
+ ensure
+ ssl.close
+ end
+ true
+ end
+ threads << th
+ end
+ ensure
+ tcps.close
+ end
+ end
+
+ client_thread = Thread.new do
+ Thread.current.report_on_exception = false
+
+ begin
+ block.call(port)
+ ensure
+ # Stop accepting new connection
+ stop_pipe_w.close
+ server_thread.join
+ end
+ end
+ threads.unshift client_thread
+ ensure
+ # Terminate existing connections. If a thread did 'pend', re-raise it.
+ pend = nil
+ threads.each { |th|
+ begin
+ timeout = EnvUtil.apply_timeout_scale(30)
+ th.join(timeout) or
+ th.raise(RuntimeError, "[start_server] thread did not exit in #{timeout} secs")
+ rescue Test::Unit::PendedError
+ pend = $!
+ rescue Exception
+ end
+ }
+ raise pend if pend
+ assert_join_threads(threads)
+ end
+ }
+ end
+end
+
+class OpenSSL::PKeyTestCase < OpenSSL::TestCase
+ def check_component(base, test, keys)
+ keys.each { |comp|
+ assert_equal base.send(comp), test.send(comp)
+ }
+ end
+
+ def assert_sign_verify_false_or_error
+ ret = yield
+ rescue => e
+ assert_kind_of(OpenSSL::PKey::PKeyError, e)
+ else
+ assert_equal(false, ret)
+ end
+
+ def der_to_pem(der, pem_header)
+ # RFC 7468
+ <<~EOS
+ -----BEGIN #{pem_header}-----
+ #{[der].pack("m0").scan(/.{1,64}/).join("\n")}
+ -----END #{pem_header}-----
+ EOS
+ end
+
+ def der_to_encrypted_pem(der, pem_header, password)
+ # OpenSSL encryption, non-standard
+ iv = 16.times.to_a.pack("C*")
+ encrypted = OpenSSL::Cipher.new("aes-128-cbc").encrypt.then { |cipher|
+ cipher.key = OpenSSL::Digest.digest("MD5", password + iv[0, 8])
+ cipher.iv = iv
+ cipher.update(der) << cipher.final
+ }
+ <<~EOS
+ -----BEGIN #{pem_header}-----
+ Proc-Type: 4,ENCRYPTED
+ DEK-Info: AES-128-CBC,#{iv.unpack1("H*").upcase}
+
+ #{[encrypted].pack("m0").scan(/.{1,64}/).join("\n")}
+ -----END #{pem_header}-----
+ EOS
+ 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