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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/Gemfile +4 -0
- data/README.md +25 -0
- data/blobfish-ejbca-client-ruby.gemspec +25 -0
- data/lib/blobfish/ejbca.rb +9 -0
- data/lib/blobfish/ejbca/certificate.rb +11 -0
- data/lib/blobfish/ejbca/client.rb +162 -0
- data/lib/blobfish/ejbca/version.rb +6 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -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,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
|
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: []
|