├── .gitignore ├── LICENSE ├── README.md ├── shard.yml ├── spec ├── rsa_spec.cr ├── spec_helper.cr └── x509_spec.cr └── src ├── openssl_ext.cr └── openssl_ext ├── bio.cr ├── cipher.cr ├── lib_crypto.cr ├── pkey.cr ├── rsa.cr ├── version.cr └── x509 └── certificate.cr /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Connor Imrie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openssl_ext 2 | 3 | RSA C Bindings for Crystal (and other extensions) 4 | 5 | ## Installation 6 | 7 | Add this to your application's `shard.yml`: 8 | 9 | ```yaml 10 | dependencies: 11 | openssl_ext: 12 | github: randomstate/openssl_ext 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```crystal 18 | require "openssl_ext" 19 | ``` 20 | 21 | See `spec/rsa_spec.cr` and `spec/x509_spec.cr` for usage in depth. 22 | The bindings closely follows the API for https://ruby-doc.org/stdlib-2.4.0/libdoc/openssl/rdoc/OpenSSL/PKey/RSA.html 23 | 24 | **Encoding with a passphrase is not yet supported as OpenSSL fails silently without any errors (in order to fix).** 25 | 26 | 27 | ## Contributors 28 | 29 | - [cimrie](https://github.com/cimrie) Connor Imrie - creator, maintainer 30 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: openssl_ext 2 | version: 0.1.1 3 | 4 | authors: 5 | - Connor Imrie <3436576+CImrie@users.noreply.github.com> 6 | 7 | crystal: 0.24.1 8 | 9 | license: MIT 10 | -------------------------------------------------------------------------------- /spec/rsa_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | require "spec" 4 | require "../src/openssl_ext/rsa" 5 | 6 | describe OpenSSL::RSA do 7 | describe "instantiating and generate a key" do 8 | it "can instantiate and generate for a given key size" do 9 | pkey = OpenSSL::RSA.new(512) 10 | pkey.private?.should be_true 11 | pkey.public?.should be_false 12 | 13 | pkey.public_key.public?.should be_true 14 | end 15 | it "can export to PEM format" do 16 | pkey = OpenSSL::RSA.new(512) 17 | pkey.private?.should be_true 18 | 19 | pem = pkey.to_pem 20 | isEmpty = "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----\n" == pem 21 | 22 | pem.should contain("BEGIN PRIVATE KEY") 23 | isEmpty.should be_false 24 | end 25 | it "can export to DER format" do 26 | pkey = OpenSSL::RSA.new(512) 27 | pkey.private?.should be_true 28 | pem = pkey.to_pem 29 | der = pkey.to_der 30 | 31 | pkey = OpenSSL::RSA.new(der) 32 | pkey.to_pem.should eq pem 33 | pkey.to_der.should eq der 34 | end 35 | it "can instantiate with a PEM encoded key" do 36 | pem = OpenSSL::RSA.new(1024).to_pem 37 | pkey = OpenSSL::RSA.new(pem) 38 | 39 | pkey.to_pem.should eq pem 40 | end 41 | it "can instantiate with a DER encoded key" do 42 | der = OpenSSL::RSA.new(1024).to_der 43 | pkey = OpenSSL::RSA.new(der) 44 | 45 | pkey.to_der.should eq der 46 | end 47 | it "can instantiate with a PEM encoded key and a passphrase" do 48 | # At present, cannot export with passphrase - for some unknown openssl error means it writes an empty pem 49 | # The following encrypted_pem has the passphrase 'test' 50 | encrypted_pem = "-----BEGIN RSA PRIVATE KEY----- 51 | Proc-Type: 4,ENCRYPTED 52 | DEK-Info: DES-EDE3-CBC,ABD606CE6CAEC3F9 53 | 54 | KffBt0WVDVMnzcCG2lzeHmTao0N4pO+hnbGRFzpdvRtJeBRTJVY7fpVQS8dKinO4 55 | tQlnx/e8iMuxiiVAND8YOjKMCRFVmNyry64hWPpBq69KgvBNAgnPTppc2AwCY5ZQ 56 | juZjzQMllq1hxOuvh1zt0qQHFRVCoWtbTxziedqhgPHFCoUKoMOB+GD4Rj+t5jYh 57 | fg/qStG1/rZ4mrPKTwS3LCYbVxc9X/7XJQWQ7IHmkf2VRr8mk9XRAZ0GLJXyNeai 58 | O5PBYt+fw2hi+00kRCbvEU83yYYoELteSLcoYcd7scYl73myOkYruDAZWu1YJnnt 59 | areXUPidcE+qCRSiF7395Ri5aLxcmGSgRm/P6z/nm31+3t2ISlvJwm5G73uAJWii 60 | XrnZQq2uWsIagRQlm+SHW21dDfDw5v1QKzLGC6vhpKI1dPYJPqcU6CB3mym9l+xB 61 | FLMigVC+QdmtSoFvcBu711TXEhEiDmszZEDWQoiF1nVXk2h/t8V6CE4b8TcODlHi 62 | gZC/oMCdYsmt3lePIcnudNHjCcYJPxV7KciChuI4i6IfPzZGLr94Q7doNwYD8TLj 63 | 8MIcSNzsuUowMORKOfcV+kPGivm9hZ5hkvLqVHYKxkWJiTl0sHmx8QG/Mm4JW+cf 64 | kAK6rBib7m+r78eCRNPCh3nkW4mCE3R9z6QPBW//3FnTEqmTljK4Fa+uA59jpMOb 65 | 1iEcbf2vwv7jrRx/CEu1VmOgsptVm1dcTABl/cL17Qp4tR7JUaW8FJYbf6WDyHl2 66 | 7V/JgTownoEvqM38HpjZwF8kO1NckwSNdWCbDEpBbDR20cwMSWy79Q== 67 | -----END RSA PRIVATE KEY-----" 68 | # pkey = OpenSSL::RSA.new(1024) 69 | # cipher = OpenSSL::Cipher.new "des" 70 | # cipher.random_key 71 | # cipher.random_iv 72 | # cipher.update("test") 73 | 74 | # encrypted_pem = pkey.to_pem(cipher, "test") 75 | 76 | decoded = OpenSSL::RSA.new(encrypted_pem, "test") 77 | 78 | not_encrypted = "-----BEGIN RSA PRIVATE KEY----- 79 | MIICXQIBAAKBgQC5+5+xnWggxNnnmCSNbIwTQFjcyawcvmPupeXs10sfhUAHUxtm 80 | T5zH3AI46JrRZN7KV5Ac5bQWzF9ZMPeHqmq5FBdYooIF8W7lVtYx23OQX5vjFRN0 81 | LRY8hyOKL07Us+aUeMwDXX7M6o58XO4bqOh8pGOqFLscCAkdAP9lDgeDGwIDAQAB 82 | AoGAcRt/jnSNbEhrwXZ83GmkctzSbkxUWRLNEclhIP36WQwf2ZSIeFt4nO/Hhjao 83 | WSqAeAxyv7BPKwJWBpdKIv7Ycfbu2c1JxWgacuotewMk5IYPXUs89QY3AL5I4BJd 84 | Zqd3o9K4OWwakukkfjxHKFC/grifNa4yVQ6IZn+XuW/AspkCQQDlmzkUapzg0n0t 85 | 3gmK6KQD9f5YdXKYGYzYO3Scrtrz53fewqfDXdLC7TGL9qw9vGEFvSE727vwR3X+ 86 | +DZ6RWYvAkEAz1yqUNnrPwzGx3JuINIXgfzGTq4gSf+xRjb5qDJUPnMt4I3PrPyV 87 | pm34aUCgo26go2+itBGjzFDaJCOT4izi1QJAJq6E6kSf01yCzFRo5ScWYrhxtjNr 88 | L+a2DMPPfIoUxxyK3FOM8eP/mulc/Ih9MhVnfxEC5VO6kNtpLKBihSzl7wJBAJrR 89 | 4eu5uJV7kZJqEmV41spbkyg9g6gcOxxkgWQeJ5302wT0fGD4uTbolnbnJMjBGTjN 90 | adot7XDn0Ob4lTpiLv0CQQDkECppYQ4N0ecegg1xPVqf19fHo/WGHGuScjfUPTI/ 91 | k0LaJjYM2ycehinmuLHgY3qdDJgtEbt4WG5XNQzhyfaN 92 | -----END RSA PRIVATE KEY-----" 93 | 94 | not_encrypted_pkey = OpenSSL::RSA.new(not_encrypted) 95 | decoded.to_pem.should eq not_encrypted_pkey.to_pem 96 | end 97 | end 98 | describe "RSA-blinding" do 99 | it "can turn blinding on" do 100 | rsa = OpenSSL::RSA.new(512) 101 | rsa.blinding_on! 102 | 103 | rsa.blinding_on?.should be_true 104 | end 105 | it "can turn blinding off" do 106 | rsa = OpenSSL::RSA.new(512) 107 | rsa.blinding_on! 108 | rsa.blinding_off! 109 | 110 | rsa.blinding_on?.should be_false 111 | end 112 | end 113 | describe "encrypting / decrypting" do 114 | it "can encrypt a string using its private key and decrypt with public key" do 115 | rsa = OpenSSL::RSA.new(512) 116 | encrypted = rsa.private_encrypt "hello world" 117 | decrypted = rsa.public_decrypt encrypted 118 | 119 | String.new(decrypted).should eq "hello world" 120 | end 121 | it "can encrypt a string using its public key and decrypt with private key" do 122 | rsa = OpenSSL::RSA.new(512) 123 | encrypted = rsa.public_encrypt "hello world" 124 | decrypted = rsa.private_decrypt encrypted 125 | 126 | String.new(decrypted).should eq "hello world" 127 | end 128 | it "should be able to sign and verify data" do 129 | rsa = OpenSSL::RSA.new(1024) 130 | digest = OpenSSL::Digest.new("sha256") 131 | data = "my test data" 132 | 133 | signature = rsa.sign(digest, data) 134 | new_digest = OpenSSL::Digest.new("sha256") 135 | 136 | rsa.verify(new_digest, signature, data).should be_true 137 | rsa.verify(new_digest, signature[0, 10], data).should be_false 138 | end 139 | end 140 | describe "can set parameters for more efficient decryption" do 141 | it "can set dmp1, dmq1, iqmp" do 142 | end 143 | it "can set p and q" do 144 | end 145 | it "can set n, e and d for key" do 146 | end 147 | end 148 | 149 | it "can instantiate from public key PEM string" do 150 | x509 = OpenSSL::X509::Certificate.new "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIa9VSmwIf5zwwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTgw\nMzA3MjExOTQ4WhcNMTgwMzI0MDkzNDQ4WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAN1sDxP8oTHQ/5qz/S+QJB1iYLWgZYEFsUQnPyr+mtppI9yr\naS1p1WS5k5I6ygx4Mzoe5y+LUSUEgHyg6VEF+Iojm4vlBfWplovd+7Z39r5Meelt\nzN77EzexDQ1g5c/kZjNHgLaCXewMtqYi8oDb30GHl7aWT5eA680E2d0gJH8Rrtxw\nTegk/ZmRWAzLoBP5mMIr+tH9a83UBua90srBqHFRO7TIXf+B28ltC7UfPdyuQy+Q\n2hzi68y3wuTGEu4yJd7I98J0en4kFv/VhLVZo4cennR/ISP63XqtbdIBvG/ipdIR\nlOfO4MyPl0vRziKCx+KVjHxD989Mtcs3M/kXOdkCAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBAG03Jq/sWdGVG166NmQxcQTtYZCDh9KIQhVMR0v4K6sZ\n0UeUKcoICzyIk0gM0veyVw6lPRSsZthx3oylo7WeDzXVQ72FZVdg8MTi2H9gLFtJ\nJ81rwbGX/Sl5vKN25iPJrW+nzMJYB8wVQplOi94okmJWoBm573DRk2fXukJ93JS4\nb6xz1DQ6Axc6bC3azjSCCWzrx2iT2hyysnqqf2rngCjwSNVACU9xTS1XW40c590w\n9QTV6a885CiUWd4j4dRBwzs0tzvVM9Iwj+bCs6FkB4XrL2+d/sHiFHOF6VsMx7bI\nV1eRIDmhte6pPnDGMz9H/gHk4zGrs6qAxATDuwIIKMc=\n-----END CERTIFICATE-----\n" 151 | pem = x509.public_key.to_pem 152 | 153 | OpenSSL::RSA.new(pem, nil, false).to_pem.should eq pem 154 | end 155 | end 156 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/openssl_ext" 3 | -------------------------------------------------------------------------------- /spec/x509_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | require "spec" 4 | require "../src/openssl_ext/x509/certificate" 5 | 6 | include OpenSSL::X509 7 | 8 | describe OpenSSL::X509::Certificate do 9 | it "can be instantiated from a PEM string" do 10 | pem = "-----BEGIN CERTIFICATE----- 11 | MIIDHDCCAgSgAwIBAgIIcTFLZ2AHMiUwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE 12 | AxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTgw 13 | MzA3MDA0NTI2WhcNMTgwMzEwMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl 14 | bi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD 15 | ggEPADCCAQoCggEBAJr02/VJFlNZBZNvHqkuFHj8XrQ0QUQUVe/QvVF+atvIPJ+a 16 | FQ9Wd0CvYcW8kqPca6ro+m/QMS0Himi3UZpnVaXleWU1um7E7VboFlozS+TisCo4 17 | J5Reaj3oiY0NIi+mnSmJALbjbvzWBixqSaghqQDzddT8BtL8nG/jR0L4D4z21nPv 18 | 2PIE4kuBIP8kOhELY4exKlMQSUeebkHtdJJ9+ocE8y2YoetLfpKwvkXWzxmIF2wa 19 | UrN+svohzlnjkok+QOI+jhOJcOz88zkto0GrTAaGu03stZ37fajOpyTKfcpnHysU 20 | 7EEKmWrGQfM22PMQflGqAWZBZw6lyY1FI/I90bUCAwEAAaM4MDYwDAYDVR0TAQH/ 21 | BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ 22 | KoZIhvcNAQEFBQADggEBAJTBdFNRn47EK6wqGQvpHi3lBDk0OqIE09vwkvc81KAD 23 | En8ISqR5jJZFgiTi1NU6d/yRAzRYUCpa2YHoB2qqsZfV53kmcSjYhEuxDWZPNcLf 24 | XyZdGu2xtV5Z3SqVr9yGpasHx+ZsCTYI+jE9wi+nM5MWtWzr1hn6sFM10APkRd8l 25 | s6s7aLFlnJ+Xgt8EhNZxKxk87rr5Mi/Lk0QniTdI67tFAxHwyk80IHl4uzYntlHg 26 | DQ51uI5iyjWxS4QcFQZGVCZ45JOtzLsnRV1+NgnTasB1ah0gfnw4AXYOR4jV7kd4 27 | mfzRpDQdyLFZqfGFyYQ6WSw3EFqIunkL8WPWRpG++5g= 28 | -----END CERTIFICATE----- 29 | " 30 | from_pem = Certificate.new pem 31 | puts from_pem.subject.to_a[0][1].should eq "securetoken.system.gserviceaccount.com" 32 | end 33 | pem = "-----BEGIN CERTIFICATE----- 34 | MIIDHDCCAgSgAwIBAgIIcTFLZ2AHMiUwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE 35 | AxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTgw 36 | MzA3MDA0NTI2WhcNMTgwMzEwMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl 37 | bi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD 38 | ggEPADCCAQoCggEBAJr02/VJFlNZBZNvHqkuFHj8XrQ0QUQUVe/QvVF+atvIPJ+a 39 | FQ9Wd0CvYcW8kqPca6ro+m/QMS0Himi3UZpnVaXleWU1um7E7VboFlozS+TisCo4 40 | J5Reaj3oiY0NIi+mnSmJALbjbvzWBixqSaghqQDzddT8BtL8nG/jR0L4D4z21nPv 41 | 2PIE4kuBIP8kOhELY4exKlMQSUeebkHtdJJ9+ocE8y2YoetLfpKwvkXWzxmIF2wa 42 | UrN+svohzlnjkok+QOI+jhOJcOz88zkto0GrTAaGu03stZ37fajOpyTKfcpnHysU 43 | 7EEKmWrGQfM22PMQflGqAWZBZw6lyY1FI/I90bUCAwEAAaM4MDYwDAYDVR0TAQH/ 44 | BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ 45 | KoZIhvcNAQEFBQADggEBAJTBdFNRn47EK6wqGQvpHi3lBDk0OqIE09vwkvc81KAD 46 | En8ISqR5jJZFgiTi1NU6d/yRAzRYUCpa2YHoB2qqsZfV53kmcSjYhEuxDWZPNcLf 47 | XyZdGu2xtV5Z3SqVr9yGpasHx+ZsCTYI+jE9wi+nM5MWtWzr1hn6sFM10APkRd8l 48 | s6s7aLFlnJ+Xgt8EhNZxKxk87rr5Mi/Lk0QniTdI67tFAxHwyk80IHl4uzYntlHg 49 | DQ51uI5iyjWxS4QcFQZGVCZ45JOtzLsnRV1+NgnTasB1ah0gfnw4AXYOR4jV7kd4 50 | mfzRpDQdyLFZqfGFyYQ6WSw3EFqIunkL8WPWRpG++5g= 51 | -----END CERTIFICATE----- 52 | " 53 | 54 | it "can provide a public key PKey" do 55 | pem = "-----BEGIN CERTIFICATE----- 56 | MIIDHDCCAgSgAwIBAgIIcTFLZ2AHMiUwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE 57 | AxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTgw 58 | MzA3MDA0NTI2WhcNMTgwMzEwMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl 59 | bi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD 60 | ggEPADCCAQoCggEBAJr02/VJFlNZBZNvHqkuFHj8XrQ0QUQUVe/QvVF+atvIPJ+a 61 | FQ9Wd0CvYcW8kqPca6ro+m/QMS0Himi3UZpnVaXleWU1um7E7VboFlozS+TisCo4 62 | J5Reaj3oiY0NIi+mnSmJALbjbvzWBixqSaghqQDzddT8BtL8nG/jR0L4D4z21nPv 63 | 2PIE4kuBIP8kOhELY4exKlMQSUeebkHtdJJ9+ocE8y2YoetLfpKwvkXWzxmIF2wa 64 | UrN+svohzlnjkok+QOI+jhOJcOz88zkto0GrTAaGu03stZ37fajOpyTKfcpnHysU 65 | 7EEKmWrGQfM22PMQflGqAWZBZw6lyY1FI/I90bUCAwEAAaM4MDYwDAYDVR0TAQH/ 66 | BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ 67 | KoZIhvcNAQEFBQADggEBAJTBdFNRn47EK6wqGQvpHi3lBDk0OqIE09vwkvc81KAD 68 | En8ISqR5jJZFgiTi1NU6d/yRAzRYUCpa2YHoB2qqsZfV53kmcSjYhEuxDWZPNcLf 69 | XyZdGu2xtV5Z3SqVr9yGpasHx+ZsCTYI+jE9wi+nM5MWtWzr1hn6sFM10APkRd8l 70 | s6s7aLFlnJ+Xgt8EhNZxKxk87rr5Mi/Lk0QniTdI67tFAxHwyk80IHl4uzYntlHg 71 | DQ51uI5iyjWxS4QcFQZGVCZ45JOtzLsnRV1+NgnTasB1ah0gfnw4AXYOR4jV7kd4 72 | mfzRpDQdyLFZqfGFyYQ6WSw3EFqIunkL8WPWRpG++5g= 73 | -----END CERTIFICATE----- 74 | " 75 | 76 | cert = Certificate.new pem 77 | public_key = cert.public_key 78 | (public_key.is_a? OpenSSL::RSA).should be_true 79 | 80 | pkey_pem = "-----BEGIN PUBLIC KEY----- 81 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmvTb9UkWU1kFk28eqS4U 82 | ePxetDRBRBRV79C9UX5q28g8n5oVD1Z3QK9hxbySo9xrquj6b9AxLQeKaLdRmmdV 83 | peV5ZTW6bsTtVugWWjNL5OKwKjgnlF5qPeiJjQ0iL6adKYkAtuNu/NYGLGpJqCGp 84 | APN11PwG0vycb+NHQvgPjPbWc+/Y8gTiS4Eg/yQ6EQtjh7EqUxBJR55uQe10kn36 85 | hwTzLZih60t+krC+RdbPGYgXbBpSs36y+iHOWeOSiT5A4j6OE4lw7PzzOS2jQatM 86 | Boa7Tey1nft9qM6nJMp9ymcfKxTsQQqZasZB8zbY8xB+UaoBZkFnDqXJjUUj8j3R 87 | tQIDAQAB 88 | -----END PUBLIC KEY----- 89 | " 90 | 91 | public_key.to_pem.should eq pkey_pem 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /src/openssl_ext.cr: -------------------------------------------------------------------------------- 1 | require "./openssl_ext/*" 2 | require "./openssl_ext/x509/*" 3 | -------------------------------------------------------------------------------- /src/openssl_ext/bio.cr: -------------------------------------------------------------------------------- 1 | struct OpenSSL::GETS_BIO 2 | GETS_BIO = begin 3 | crystal_bio = OpenSSL::BIO::CRYSTAL_BIO 4 | bgets = LibCrypto::BioMethodGets.new do |bio, buffer, len| 5 | io = Box(IO).unbox(BIO.get_data(bio)) 6 | io.flush 7 | 8 | position = io.pos 9 | 10 | line = io.gets(len, false) 11 | 12 | if line.nil? 13 | return 0 14 | end 15 | 16 | io.seek(position) 17 | bytes = io.read(Slice.new(buffer, line.bytesize)).to_i 18 | 19 | bytes -= 1 unless bytes == 1 20 | bytes 21 | end 22 | {% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 %} 23 | LibCrypto.BIO_meth_set_gets(crystal_bio, bgets) 24 | {% else %} 25 | crystal_bio.value.bgets = bgets 26 | {% end %} 27 | crystal_bio 28 | end 29 | 30 | @boxed_io : Void* 31 | 32 | def initialize(@io : IO) 33 | @bio = LibCrypto.BIO_new(GETS_BIO) 34 | 35 | # We need to store a reference to the box because it's 36 | # stored in `@bio.value.ptr`, but that lives in C-land, 37 | # not in Crystal-land. 38 | @boxed_io = Box(IO).box(io) 39 | 40 | BIO.set_data(@bio, @boxed_io) 41 | end 42 | 43 | getter io 44 | 45 | def to_unsafe 46 | @bio 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /src/openssl_ext/cipher.cr: -------------------------------------------------------------------------------- 1 | require "random/secure" 2 | require "openssl/cipher" 3 | 4 | class OpenSSL::Cipher 5 | def to_unsafe 6 | cipher 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/openssl_ext/lib_crypto.cr: -------------------------------------------------------------------------------- 1 | lib LibCrypto 2 | struct EvpPKey 3 | type : LibC::Int 4 | save_type : LibC::Int 5 | references : LibC::Int 6 | ameth : EVP_PKEY_ASN1_method 7 | engine : Engine 8 | pkey : EVP_PKEY_KEY 9 | save_parameters : LibC::Int 10 | attributes : StackStX509Attribute* 11 | end 12 | 13 | struct StackStX509Attribute 14 | stack : Stack 15 | end 16 | 17 | struct Stack 18 | num : LibC::Int 19 | data : LibC::Char** 20 | sorted : LibC::Int 21 | num_alloc : LibC::Int 22 | comp : (Void*, Void* -> LibC::Int) 23 | end 24 | 25 | struct Dsa 26 | pad : LibC::Int 27 | version : LibC::Long 28 | write_params : LibC::Int 29 | p : Bignum* 30 | q : Bignum* 31 | g : Bignum* 32 | pub_key : Bignum* 33 | priv_key : Bignum* 34 | kinv : Bignum* 35 | r : Bignum* 36 | flags : LibC::Int 37 | method_mont_p : BnMontCtx* 38 | references : LibC::Int 39 | ex_data : CryptoExData 40 | meth : DsaMethod* 41 | engine : Engine 42 | end 43 | 44 | struct Bignum 45 | d : LibC::ULong* 46 | top : LibC::Int 47 | dmax : LibC::Int 48 | neg : LibC::Int 49 | flags : LibC::Int 50 | end 51 | 52 | struct BnMontCtx 53 | ri : LibC::Int 54 | rr : Bignum 55 | n : Bignum 56 | ni : Bignum 57 | n0 : LibC::ULong[2] 58 | flags : LibC::Int 59 | end 60 | 61 | struct CryptoExData 62 | sk : StackVoid* 63 | dummy : LibC::Int 64 | end 65 | 66 | struct DsaMethod 67 | name : LibC::Char* 68 | dsa_do_sign : (UInt8*, LibC::Int, Dsa* -> DsaSig*) 69 | dsa_sign_setup : (Dsa*, BnCtx, Bignum**, Bignum** -> LibC::Int) 70 | dsa_do_verify : (UInt8*, LibC::Int, DsaSig*, Dsa* -> LibC::Int) 71 | dsa_mod_exp : (Dsa*, Bignum*, Bignum*, Bignum*, Bignum*, Bignum*, Bignum*, BnCtx, BnMontCtx* -> LibC::Int) 72 | bn_mod_exp : (Dsa*, Bignum*, Bignum*, Bignum*, Bignum*, BnCtx, BnMontCtx* -> LibC::Int) 73 | init : (Dsa* -> LibC::Int) 74 | finish : (Dsa* -> LibC::Int) 75 | flags : LibC::Int 76 | app_data : LibC::Char* 77 | dsa_paramgen : (Dsa*, LibC::Int, UInt8*, LibC::Int, LibC::Int*, LibC::ULong*, BnGencb* -> LibC::Int) 78 | dsa_keygen : (Dsa* -> LibC::Int) 79 | end 80 | 81 | struct StackVoid 82 | stack : Stack 83 | end 84 | 85 | struct DsaSig 86 | r : Bignum* 87 | s : Bignum* 88 | end 89 | 90 | struct BnGencb 91 | ver : LibC::UInt 92 | arg : Void* 93 | cb : BnGencbCb 94 | end 95 | 96 | struct Dh 97 | pad : LibC::Int 98 | version : LibC::Int 99 | p : Bignum* 100 | g : Bignum* 101 | length : LibC::Long 102 | pub_key : Bignum* 103 | priv_key : Bignum* 104 | flags : LibC::Int 105 | method_mont_p : BnMontCtx* 106 | q : Bignum* 107 | j : Bignum* 108 | seed : UInt8* 109 | seedlen : LibC::Int 110 | counter : Bignum* 111 | references : LibC::Int 112 | ex_data : CryptoExData 113 | meth : DhMethod* 114 | engine : Engine 115 | end 116 | 117 | struct DhMethod 118 | name : LibC::Char* 119 | generate_key : (Dh* -> LibC::Int) 120 | compute_key : (UInt8*, Bignum*, Dh* -> LibC::Int) 121 | bn_mod_exp : (Dh*, Bignum*, Bignum*, Bignum*, Bignum*, BnCtx, BnMontCtx* -> LibC::Int) 122 | init : (Dh* -> LibC::Int) 123 | finish : (Dh* -> LibC::Int) 124 | flags : LibC::Int 125 | app_data : LibC::Char* 126 | generate_params : (Dh*, LibC::Int, LibC::Int, BnGencb* -> LibC::Int) 127 | end 128 | 129 | struct Rsa 130 | pad : LibC::Int 131 | version : LibC::Long 132 | meth : RsaMethod* 133 | engine : Engine 134 | n : Bignum* 135 | e : Bignum* 136 | d : Bignum* 137 | p : Bignum* 138 | q : Bignum* 139 | dmp1 : Bignum* 140 | dmq1 : Bignum* 141 | iqmp : Bignum* 142 | ex_data : CryptoExData 143 | references : LibC::Int 144 | flags : LibC::Int 145 | _method_mod_n : BnMontCtx* 146 | _method_mod_p : BnMontCtx* 147 | _method_mod_q : BnMontCtx* 148 | bignum_data : LibC::Char* 149 | blinding : BnBlinding 150 | mt_blinding : BnBlinding 151 | end 152 | 153 | struct RsaMethod 154 | name : LibC::Char* 155 | rsa_pub_enc : (LibC::Int, UInt8*, UInt8*, Rsa*, LibC::Int -> LibC::Int) 156 | rsa_pub_dec : (LibC::Int, UInt8*, UInt8*, Rsa*, LibC::Int -> LibC::Int) 157 | rsa_priv_enc : (LibC::Int, UInt8*, UInt8*, Rsa*, LibC::Int -> LibC::Int) 158 | rsa_priv_dec : (LibC::Int, UInt8*, UInt8*, Rsa*, LibC::Int -> LibC::Int) 159 | rsa_mod_exp : (Bignum*, Bignum*, Rsa*, BnCtx -> LibC::Int) 160 | bn_mod_exp : (Bignum*, Bignum*, Bignum*, Bignum*, BnCtx, BnMontCtx* -> LibC::Int) 161 | init : (Rsa* -> LibC::Int) 162 | finish : (Rsa* -> LibC::Int) 163 | flags : LibC::Int 164 | app_data : LibC::Char* 165 | rsa_sign : (LibC::Int, UInt8*, LibC::UInt, UInt8*, LibC::UInt*, Rsa* -> LibC::Int) 166 | rsa_verify : (LibC::Int, UInt8*, LibC::UInt, UInt8*, LibC::UInt, Rsa* -> LibC::Int) 167 | rsa_keygen : (Rsa*, LibC::Int, Bignum*, BnGencb* -> LibC::Int) 168 | end 169 | 170 | union EVP_PKEY_KEY 171 | ptr : LibC::Char* 172 | rsa : Rsa* 173 | dsa : Dsa* 174 | dh : Dh* 175 | ec : Void* 176 | end 177 | 178 | union BnGencbCb 179 | cb_1 : (LibC::Int, LibC::Int, Void* -> Void) 180 | cb_2 : (LibC::Int, LibC::Int, BnGencb* -> LibC::Int) 181 | end 182 | 183 | enum Padding 184 | PKCS1_PADDING = 1 185 | SSLV23_PADDING = 2 186 | NO_PADDING = 3 187 | PKCS1_OAEP_PADDING = 4 188 | X931_PADDING = 5 189 | PKCS1_PSS_PADDING = 6 190 | end 191 | 192 | type EVP_PKEY_ASN1_method = Void* 193 | type Engine = Void* 194 | type BnCtx = Void* 195 | type BnBlinding = Void* 196 | 197 | fun bignum_new = BN_new : Bignum* 198 | fun set_bignum_from_decimal = BN_dec2bn(a : Bignum**, str : LibC::Char*) : LibC::Int 199 | 200 | fun evp_pkey_new = EVP_PKEY_new : EvpPKey* 201 | fun evp_pkey_free = EVP_PKEY_free(pkey : EvpPKey*) 202 | fun evp_pkey_size = EVP_PKEY_size(pkey : EvpPKey*) : LibC::Int 203 | fun evp_pkey_get1_rsa = EVP_PKEY_get1_RSA(pkey : EvpPKey*) : Rsa* 204 | fun evp_pkey_set1_rsa = EVP_PKEY_set1_RSA(pkey : EvpPKey*, key : Rsa*) : LibC::Int 205 | 206 | fun rsa_new = RSA_new : Rsa* 207 | fun rsa_public_key_dup = RSAPublicKey_dup(rsa : Rsa*) : Rsa* 208 | fun rsa_blinding_on = RSA_blinding_on(rsa : Rsa*, ctx : BnCtx) : LibC::Int 209 | fun rsa_blinding_off = RSA_blinding_off(rsa : Rsa*) 210 | fun rsa_generate_key_ex = RSA_generate_key_ex(rsa : Rsa*, bits : LibC::Int, e : Bignum*, cb : BnGencb*) : LibC::Int 211 | fun rsa_private_encrypt = RSA_private_encrypt(flen : LibC::Int, from : UInt8*, to : UInt8*, rsa : Rsa*, padding : LibC::Int) : LibC::Int 212 | fun rsa_public_encrypt = RSA_public_encrypt(flen : LibC::Int, from : UInt8*, to : UInt8*, rsa : Rsa*, padding : LibC::Int) : LibC::Int 213 | fun rsa_private_decrypt = RSA_private_decrypt(flen : LibC::Int, from : UInt8*, to : UInt8*, rsa : Rsa*, padding : LibC::Int) : LibC::Int 214 | fun rsa_public_decrypt = RSA_public_decrypt(flen : LibC::Int, from : UInt8*, to : UInt8*, rsa : Rsa*, padding : LibC::Int) : LibC::Int 215 | 216 | fun pem_read_bio_private_key = PEM_read_bio_PrivateKey(bp : Bio*, x : EvpPKey**, cb : (LibC::Char*, LibC::Int, LibC::Int, Void* -> LibC::Int), u : Void*) : EvpPKey* 217 | fun pem_read_bio_public_key = PEM_read_bio_PUBKEY(bp : Bio*, x : EvpPKey**, cb : (LibC::Char*, LibC::Int, LibC::Int, Void* -> LibC::Int), u : Void*) : EvpPKey* 218 | fun pem_write_bio_rsa_private_key = PEM_write_bio_RSAPrivateKey(bp : Bio*, x : Rsa*, enc : EVP_MD*, kstr : UInt8*, klen : LibC::Int, cb : (LibC::Char*, LibC::Int, LibC::Int, Void* -> LibC::Int), u : Void*) : LibC::Int 219 | fun pem_write_bio_rsa_public_key = PEM_write_bio_RSAPublicKey(bp : Bio*, x : Rsa*) : LibC::Int 220 | fun pem_write_bio_pkcs8_private_key = PEM_write_bio_PKCS8PrivateKey(bp : Bio*, x : EvpPKey*, enc : EVP_CIPHER*, kstr : LibC::Char*, klen : LibC::Int, cb : (LibC::Char*, LibC::Int, LibC::Int, Void* -> LibC::Int), u : Void*) : LibC::Int 221 | fun pem_write_bio_public_key = PEM_write_bio_PUBKEY(bp : Bio*, x : EvpPKey*) : LibC::Int 222 | 223 | fun d2i_private_key_bio = d2i_PrivateKey_bio(bp : Bio*, a : EvpPKey**) : EvpPKey* 224 | fun i2d_private_key = i2d_PrivateKey(a : EvpPKey*, pp : UInt8**) : LibC::Int 225 | fun i2d_public_key = i2d_PublicKey(a : EvpPKey*, pp : UInt8**) : LibC::Int 226 | 227 | # Adding x509 Capabilities 228 | fun pem_read_bio_x509 = PEM_read_bio_X509(bp : Bio*, x : X509**, cb : (LibC::Char*, LibC::Int, LibC::Int, Void* -> LibC::Int), u : Void*) : X509 229 | fun pem_write_bio_x509 = PEM_write_bio_X509(bp : Bio*, x : X509*) : LibC::Int 230 | fun x509_get_public_key = X509_get_pubkey(x : X509) : EvpPKey* 231 | fun evp_sign_final = EVP_SignFinal(ctx : EVP_MD_CTX, md : UInt8*, s : LibC::UInt*, pkey : EvpPKey*) : LibC::Int 232 | fun evp_verify_final = EVP_VerifyFinal(ctx : EVP_MD_CTX, sigbuf : UInt8*, siglen : LibC::UInt, pkey : EvpPKey*) : LibC::Int 233 | end 234 | -------------------------------------------------------------------------------- /src/openssl_ext/pkey.cr: -------------------------------------------------------------------------------- 1 | require "./cipher" 2 | require "./bio" 3 | 4 | module OpenSSL 5 | abstract class PKey 6 | class PKeyError < OpenSSL::Error; end 7 | 8 | def initialize(@pkey : LibCrypto::EvpPKey*, @is_private : Bool) 9 | raise PKeyError.new "Invalid EVP_PKEY" unless @pkey 10 | end 11 | 12 | def initialize(is_private) 13 | initialize(LibCrypto.evp_pkey_new, is_private) 14 | end 15 | 16 | def self.new(encoded : String, passphrase = nil, is_private = true) 17 | self.new(IO::Memory.new(encoded), passphrase, is_private) 18 | end 19 | 20 | def self.new(io : IO, passphrase = nil, is_private = true) 21 | if is_private 22 | begin 23 | bio = GETS_BIO.new(io) 24 | new(LibCrypto.pem_read_bio_private_key(bio, nil, nil, passphrase), is_private) 25 | rescue 26 | bio = GETS_BIO.new(IO::Memory.new(Base64.decode(io.to_s))) 27 | new(LibCrypto.d2i_private_key_bio(bio, nil), is_private) 28 | end 29 | else 30 | bio = GETS_BIO.new(io) 31 | new(LibCrypto.pem_read_bio_public_key(bio, nil, nil, passphrase), is_private) 32 | end 33 | end 34 | 35 | def self.new(size : Int32) 36 | exponent = 65537.to_u32 37 | self.generate(size, exponent) 38 | end 39 | 40 | def to_unsafe 41 | @pkey 42 | end 43 | 44 | def finalize 45 | LibCrypto.evp_pkey_free(self) 46 | end 47 | 48 | def private? 49 | @is_private 50 | end 51 | 52 | def public? 53 | !private? 54 | end 55 | 56 | def to_pem(io : IO, cipher : (OpenSSL::Cipher | Nil) = nil, passphrase = nil) 57 | bio = BIO.new(io) 58 | 59 | if private? 60 | cipher_pointer = nil 61 | 62 | if !cipher.nil? 63 | unsafe = cipher.to_unsafe 64 | cipher_pointer = pointerof(unsafe) 65 | end 66 | 67 | raise PKeyError.new "Could not write to PEM" unless LibCrypto.pem_write_bio_pkcs8_private_key(bio, self, cipher_pointer, nil, 0, passphrase_callback, Box.box(passphrase)) == 1 68 | else 69 | raise PKeyError.new "Could not write to PEM" unless LibCrypto.pem_write_bio_public_key(bio, self) == 1 70 | end 71 | end 72 | 73 | def to_pem(cipher : OpenSSL::Cipher, passphrase) 74 | io = IO::Memory.new 75 | to_pem(io, cipher, passphrase) 76 | io.to_s 77 | end 78 | 79 | def to_pem 80 | io = IO::Memory.new 81 | to_pem(io) 82 | io.to_s 83 | end 84 | 85 | def to_der 86 | io = IO::Memory.new 87 | to_der(io) 88 | Base64.encode(io.to_s) 89 | end 90 | 91 | def to_der(io) 92 | fn = ->(buf : UInt8** | Nil) { 93 | if private? 94 | LibCrypto.i2d_private_key(self, buf) 95 | else 96 | LibCrypto.i2d_public_key(self, buf) 97 | end 98 | } 99 | 100 | len = fn.call(nil) 101 | if len <= 0 102 | raise PKeyError.new "Could not output in DER format" 103 | end 104 | slice = Slice(UInt8).new(len) 105 | p = slice.to_unsafe 106 | len = fn.call(pointerof(p)) 107 | 108 | output = slice[0, len] 109 | io.write(output) 110 | end 111 | 112 | private def passphrase_callback 113 | ->(buffer : UInt8*, key_size : Int32, is_read_write : Int32, u : Void*) { 114 | pwd = Box(String).unbox(u) 115 | 116 | if pwd.nil? 117 | return 0 118 | end 119 | 120 | len = pwd.bytesize 121 | 122 | if len <= 0 123 | return 0 124 | end 125 | 126 | if len > key_size 127 | len = key_size 128 | end 129 | 130 | buffer.copy_from(pwd.to_slice.pointer(len), len) 131 | 132 | return len 133 | } 134 | end 135 | 136 | def sign(digest, data) 137 | unless private? 138 | raise PKeyError.new "Private key is needed" 139 | end 140 | 141 | slice = Slice(UInt8).new(max_encrypt_size) 142 | digest.update(data) 143 | 144 | digest_pointer = digest.to_unsafe 145 | 146 | raise PKeyError.new "Unable to sign" unless LibCrypto.evp_sign_final(digest, slice, out len, self) 147 | 148 | slice[0, len.to_i32] 149 | end 150 | 151 | def verify(digest, signature, data) 152 | signature = signature.to_slice 153 | digest.update(data) 154 | 155 | case LibCrypto.evp_verify_final(digest, signature, signature.size.to_u32, self) 156 | when 0 157 | false 158 | when 1 159 | true 160 | else 161 | raise PKeyError.new "Unable to verify" 162 | end 163 | end 164 | 165 | private def max_encrypt_size 166 | LibCrypto.evp_pkey_size(self) 167 | end 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /src/openssl_ext/rsa.cr: -------------------------------------------------------------------------------- 1 | require "big" 2 | require "./pkey" 3 | 4 | module OpenSSL 5 | class RSA < PKey 6 | class RsaError < PKeyError; end 7 | 8 | @blinding_on : Bool = false 9 | 10 | def self.generate(size : Int32, exponent : UInt32) 11 | rsa_pointer = LibCrypto.rsa_new 12 | 13 | exponent_bn = LibCrypto.bignum_new 14 | LibCrypto.set_bignum_from_decimal(pointerof(exponent_bn), exponent.to_s) 15 | LibCrypto.rsa_generate_key_ex(rsa_pointer, size, exponent_bn, nil) 16 | 17 | new(true).tap do |pkey| 18 | LibCrypto.evp_pkey_set1_rsa(pkey, rsa_pointer) 19 | end 20 | end 21 | 22 | private def rsa 23 | LibCrypto.evp_pkey_get1_rsa(self) 24 | end 25 | 26 | def public_key 27 | pub_rsa = LibCrypto.rsa_public_key_dup(rsa) 28 | raise RsaError.new "Could not get public key from RSA" unless pub_rsa 29 | 30 | RSA.new(false).tap do |pkey| 31 | LibCrypto.evp_pkey_set1_rsa(pkey, pub_rsa) 32 | end 33 | end 34 | 35 | def public_encrypt(data, padding = LibCrypto::Padding::PKCS1_PADDING) 36 | from = data.to_slice 37 | if max_encrypt_size < from.size 38 | raise RsaError.new "value is too big to be encrypted" 39 | end 40 | to = Slice(UInt8).new max_encrypt_size 41 | len = LibCrypto.rsa_public_encrypt(from.size, from, to, rsa, padding) 42 | if len < 0 43 | raise RsaError.new "unable to encrypt" 44 | end 45 | to[0, len] 46 | end 47 | 48 | def public_decrypt(data, padding = LibCrypto::Padding::PKCS1_PADDING) 49 | from = data.to_slice 50 | to = Slice(UInt8).new max_encrypt_size 51 | len = LibCrypto.rsa_public_decrypt(from.size, from, to, rsa, padding) 52 | if len < 0 53 | raise RsaError.new "unable to decrypt" 54 | end 55 | to[0, len] 56 | end 57 | 58 | def private_encrypt(data, padding = LibCrypto::Padding::PKCS1_PADDING) 59 | unless private? 60 | raise RsaError.new "private key needed" 61 | end 62 | from = data.to_slice 63 | to = Slice(UInt8).new max_encrypt_size 64 | len = LibCrypto.rsa_private_encrypt(from.size, from, to, rsa, padding) 65 | if len < 0 66 | raise RsaError.new "unable to encrypt" 67 | end 68 | to[0, len] 69 | end 70 | 71 | def private_decrypt(data, padding = LibCrypto::Padding::PKCS1_PADDING) 72 | unless private? 73 | raise RsaError.new "private key needed" 74 | end 75 | 76 | from = data.to_slice 77 | to = Slice(UInt8).new max_encrypt_size 78 | len = LibCrypto.rsa_private_decrypt(from.size, from, to, rsa, padding) 79 | if len < 0 80 | raise RsaError.new "unable to decrypt" 81 | end 82 | to[0, len] 83 | end 84 | 85 | def blinding_on? 86 | @blinding_on 87 | end 88 | 89 | def blinding_on! 90 | @blinding_on = (LibCrypto.rsa_blinding_on(rsa, nil) == 1) 91 | end 92 | 93 | def blinding_off! 94 | LibCrypto.rsa_blinding_off(rsa) 95 | @blinding_on = false 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /src/openssl_ext/version.cr: -------------------------------------------------------------------------------- 1 | module OpensslRsa 2 | VERSION = "0.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /src/openssl_ext/x509/certificate.cr: -------------------------------------------------------------------------------- 1 | module OpenSSL::X509 2 | class Certificate 3 | class CertificateError < OpenSSL::Error; end 4 | 5 | def self.new(pem : String) 6 | io = IO::Memory.new(pem) 7 | bio = OpenSSL::GETS_BIO.new(io) 8 | x509 = LibCrypto.pem_read_bio_x509(bio, nil, nil, nil) 9 | 10 | raise CertificateError.new "Could not read PEM" unless x509 11 | new x509 12 | end 13 | 14 | def to_pem(io) 15 | bio = OpenSSL::GETS_BIO.new(io) 16 | cert_pointer = self.to_unsafe_pointer 17 | raise CertificateError.new "Could not convert to PEM" unless LibCrypto.pem_write_bio_x509(bio, cert_pointer) 18 | end 19 | 20 | def to_pem 21 | io = IO::Memory.new 22 | to_pem(io) 23 | io.to_s 24 | end 25 | 26 | def public_key 27 | RSA.new(LibCrypto.x509_get_public_key(self), false) 28 | end 29 | 30 | def to_unsafe_pointer 31 | pointerof(@cert) 32 | end 33 | end 34 | end 35 | --------------------------------------------------------------------------------