blobfish-ejbca-client-ruby 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2a90d0f1923a7f24fa47088401156615a585df0d89734e0b15fa4e35fe41b378
4
+ data.tar.gz: 2137758317788acf47eea3e70309de7adbc4ffbbbefca9fab2a7f6a1737a0ce5
5
+ SHA512:
6
+ metadata.gz: 07bdeb0b43a002c890fd3e65f4797e75b3efbaef1fbcb621c9f37209713d1839fb1ba9d3cd29c7c6923d30f5e75b1552c9e0d54208b7f661710d5ae12f2def88
7
+ data.tar.gz: 98996e6d8dc19f8cdb21e1cf0433e4b4f07e171b6f3793a2b99c67faab8dda398285234db905d126b1a2e271ddfffd46b76c59e54e5bc10b26fe404a6f128eb6
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.idea/
11
+ /*.gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in blobfish-ejbca-client-ruby.gemspec
4
+ gemspec
@@ -0,0 +1,25 @@
1
+ # Blobfish::Ejbca
2
+
3
+ This gem allows integration with EJBCA services and currently supports:
4
+
5
+ - PFX generation on EJBCA side.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'blobfish-ejbca-client-ruby'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install blobfish-ejbca-client-ruby
22
+
23
+ ## Usage
24
+
25
+ For a demonstration project (in spanish) see https://github.com/hablutzel1/blobfish-ejbca-client-ruby-demo.
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "blobfish/ejbca/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "blobfish-ejbca-client-ruby"
8
+ spec.version = Blobfish::Ejbca::VERSION
9
+ spec.authors = ["Jaime Hablutzel"]
10
+ spec.email = ["hablutzel1@gmail.com"]
11
+
12
+ spec.summary = "Blobfish's Ruby client for EJBCA services."
13
+ spec.description = "Ruby client currently allowing to perform certain operations with EJBCA services, e.g. requesting PFX generation."
14
+ spec.homepage = "https://github.com/hablutzel1/blobfish-ejbca-client-ruby"
15
+
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.require_paths = ["lib"]
23
+ spec.add_runtime_dependency 'savon', '~> 2.12'
24
+ spec.add_development_dependency "bundler", "~> 1.16"
25
+ end
@@ -0,0 +1,9 @@
1
+
2
+ require 'blobfish/ejbca/version'
3
+ require 'blobfish/ejbca/client'
4
+
5
+ module Blobfish
6
+ module Ejbca
7
+ # Your code goes here...
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+
2
+ module Blobfish
3
+ module Ejbca
4
+ # TODO evaluate to extract this class to a new Blobfish's crypto utilities gem.
5
+ class Certificate < OpenSSL::X509::Certificate
6
+ def serial_hex
7
+ serial.to_s(16)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,162 @@
1
+
2
+ require 'savon'
3
+ require 'base64'
4
+ require_relative 'certificate'
5
+
6
+ module Blobfish
7
+ module Ejbca
8
+
9
+ # See too 'D:\d\ejbca\tests\ejbca_ws_client\src\p12onejbcaside' for a Java alternative for this client.
10
+ class Client
11
+ STATUS_NEW = 10
12
+ TOKEN_TYPE_P12 = 'P12'
13
+ TOKEN_TYPE_USERGENERATED = 'USERGENERATED'
14
+ RESPONSETYPE_CERTIFICATE = 'CERTIFICATE'
15
+ RESPONSETYPE_PKCS7WITHCHAIN = 'PKCS7WITHCHAIN'
16
+ REVOCATION_REASON_UNSPECIFIED = 0
17
+
18
+ # @param [String] ws_additional_trusted_anchors e.g. +ca-certificates.crt+. Required only if +wsdl_url+ uses a non-commercial SSL certificate, otherwise it should be +nil+.
19
+ def initialize(wsdl_url, ws_additional_trusted_anchors, ws_client_certificate, ws_client_key, ws_client_key_password, ca_name, cert_profile, ee_profile)
20
+ @savon_client = Savon.client(
21
+ wsdl: wsdl_url,
22
+ ssl_cert_file: ws_client_certificate,
23
+ ssl_cert_key_file: ws_client_key,
24
+ ssl_cert_key_password: ws_client_key_password,
25
+ ssl_ca_cert_file: ws_additional_trusted_anchors,
26
+ # log: true,
27
+ # log_level: :debug,
28
+ )
29
+ @ca_name = ca_name
30
+ @ca_dn = query_ca_dn(ca_name)
31
+ @cert_profile = cert_profile
32
+ @ee_profile = ee_profile
33
+ end
34
+
35
+ def self.escape_dn_attr_value(val)
36
+ # TODO study escaping rules in detail. Take a look at relevant standards and the EJBCA implementation. See too https://sourceforge.net/p/ejbca/discussion/123123/thread/d36bb985/.
37
+ val.gsub(",", "\\,")
38
+ end
39
+
40
+ # Note that it requires 'Allow validity override' set in the EJBCA certificate profile for the pair +validity_type,validity_value+ to be effective.
41
+ # 'subject_dn' should have its attributes values escaped using 'escape_dn_attr_value'.
42
+ # 'custom_friendly_name' is optional. It can be set to 'nil' to maintain the one set by EJBCA (TODO confirm if EJBCA actually sets a friendly name).
43
+ def request_pfx(ejbca_username, email_address, subject_dn, subject_alt_name, validity_type, validity_value, pfx_password, custom_friendly_name)
44
+ end_user = create_end_user(ejbca_username, pfx_password, TOKEN_TYPE_P12, email_address, subject_dn, subject_alt_name, validity_type, validity_value)
45
+ ws_call(:edit_user,
46
+ arg0: end_user)
47
+ ws_resp = ws_call(:pkcs12_req,
48
+ arg0: ejbca_username,
49
+ arg1: pfx_password,
50
+ arg3: '2048',
51
+ arg4: 'RSA'
52
+ )
53
+ pfx_bytes = Client.double_decode64(ws_resp[:keystore_data])
54
+ pkcs12 = OpenSSL::PKCS12.new(pfx_bytes, pfx_password)
55
+ unless custom_friendly_name.nil?
56
+ # NOTE that this is currently removing the friendlyName for all bundled CA certs, but this is not expected to produce problems.
57
+ updated_pkcs12 = OpenSSL::PKCS12.create(pfx_password, custom_friendly_name, pkcs12.key, pkcs12.certificate, pkcs12.ca_certs)
58
+ pfx_bytes = updated_pkcs12.to_der
59
+ end
60
+ {pfx: pfx_bytes, cert: Certificate.new(pkcs12.certificate)}
61
+
62
+ end
63
+
64
+ def request_from_csr(pem_csr, ejbca_username, email_address, subject_dn, subject_alt_name, validity_type, validity_value, response_type = RESPONSETYPE_CERTIFICATE)
65
+ end_user = create_end_user(ejbca_username, nil, TOKEN_TYPE_USERGENERATED, email_address, subject_dn, subject_alt_name, validity_type, validity_value)
66
+ ws_resp = ws_call(:certificate_request,
67
+ arg0: end_user,
68
+ arg1: pem_csr,
69
+ arg2: 0,
70
+ arg4: response_type)
71
+ resp_as_der = Client.double_decode64(ws_resp[:data])
72
+ if response_type == RESPONSETYPE_CERTIFICATE
73
+ Certificate.new(resp_as_der)
74
+ elsif response_type == RESPONSETYPE_PKCS7WITHCHAIN
75
+ OpenSSL::PKCS7.new(resp_as_der)
76
+ else
77
+ raise NotImplementedError
78
+ end
79
+ end
80
+
81
+ def revoke_cert(serial_number)
82
+ ws_call(:revoke_cert,
83
+ arg0: @ca_dn,
84
+ arg1: serial_number,
85
+ arg2: REVOCATION_REASON_UNSPECIFIED
86
+ )
87
+ end
88
+
89
+ def get_revocation_status(serial_number)
90
+ revocation_status = ws_call(:check_revokation_status,
91
+ arg0: @ca_dn,
92
+ arg1: serial_number,
93
+ )
94
+ raise "Certificate with serial number #{serial_number} doesn't exists for #{@ca_dn}" if revocation_status.nil?
95
+ revocation_status if revocation_status[:reason].to_i != -1
96
+ end
97
+
98
+ # NOTE that these entries aren't being ordered by issuance, but by the latest to expire, i.e. the latest cert to expire is returned first.
99
+ def get_all_certs(ejbca_username)
100
+ certs = ws_call(:find_certs,
101
+ arg0: ejbca_username,
102
+ arg1: false,
103
+ )
104
+ Enumerator.new do |yielder|
105
+ certs.each do |cert|
106
+ cert_as_der = Client.double_decode64(cert[:certificate_data])
107
+ yielder << Certificate.new(cert_as_der)
108
+ end
109
+ end
110
+ end
111
+
112
+ private
113
+
114
+ def create_end_user(ejbca_username, password, token_type, email_address, subject_dn, subject_alt_name, validity_type, validity_value)
115
+ end_user = {}
116
+ end_user[:username] = ejbca_username
117
+ # When password is nil, the element is excluded from the hash, otherwise it would produce <password xsi:nil="true"/> which is interpreted as "" in the EJBCA side. See https://github.com/savonrb/gyoku/#user-content-hash-values.
118
+ end_user[:password] = password unless password == nil
119
+ end_user[:status] = STATUS_NEW
120
+ end_user[:token_type] = token_type
121
+ end_user[:email] = email_address
122
+ end_user[:subjectDN] = subject_dn
123
+ end_user[:subject_alt_name] = subject_alt_name
124
+ end_user[:ca_name] = @ca_name
125
+ end_user[:certificate_profile_name] = @cert_profile
126
+ end_user[:end_entity_profile_name] = @ee_profile
127
+ if validity_type == :days_from_now
128
+ custom_endtime = "#{validity_value}:0:0"
129
+ elsif validity_type == :fixed_not_after
130
+ unless validity_value.is_a? Time
131
+ raise ArgumentError
132
+ end
133
+ not_after = validity_value.utc
134
+ custom_endtime = not_after.strftime('%Y-%m-%d %H:%M')
135
+ else
136
+ raise NotImplementedError
137
+ end
138
+ end_user[:extended_information] = [{name: 'customdata_ENDTIME', value: custom_endtime}]
139
+ end_user
140
+ end
141
+
142
+ def self.double_decode64(b64)
143
+ b64 = Base64.decode64(b64)
144
+ Base64.decode64(b64)
145
+ end
146
+
147
+ def query_ca_dn(ca_name)
148
+ ca_chain = ws_call(:get_last_ca_chain, arg0: ca_name)
149
+ ca_cert = ca_chain.kind_of?(Array) ? ca_chain[0] : ca_chain
150
+ ca_cert = Client.double_decode64(ca_cert[:certificate_data])
151
+ ca_cert = OpenSSL::X509::Certificate.new(ca_cert)
152
+ ca_cert.subject.to_s(OpenSSL::X509::Name::RFC2253)
153
+ end
154
+
155
+ def ws_call(operation_name, message)
156
+ response = @savon_client.call(operation_name, soap_action: false, message: message)
157
+ response.to_hash["#{operation_name}_response".to_sym][:return]
158
+ end
159
+
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,6 @@
1
+
2
+ module Blobfish
3
+ module Ejbca
4
+ VERSION = '0.1.1'
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blobfish-ejbca-client-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Jaime Hablutzel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-04-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: savon
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.12'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.16'
41
+ description: Ruby client currently allowing to perform certain operations with EJBCA
42
+ services, e.g. requesting PFX generation.
43
+ email:
44
+ - hablutzel1@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - Gemfile
51
+ - README.md
52
+ - blobfish-ejbca-client-ruby.gemspec
53
+ - lib/blobfish/ejbca.rb
54
+ - lib/blobfish/ejbca/certificate.rb
55
+ - lib/blobfish/ejbca/client.rb
56
+ - lib/blobfish/ejbca/version.rb
57
+ homepage: https://github.com/hablutzel1/blobfish-ejbca-client-ruby
58
+ licenses: []
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.1.2
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: Blobfish's Ruby client for EJBCA services.
79
+ test_files: []