ruby_scep 0.1.0
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/.circleci/config.yml +30 -0
- data/Gemfile.lock +40 -0
- data/License +21 -0
- data/README.md +54 -0
- data/example_server/Gemfile +4 -0
- data/example_server/Gemfile.lock +31 -0
- data/example_server/Procfile +1 -0
- data/example_server/application.rb +41 -0
- data/example_server/certs/ca.pem +30 -0
- data/example_server/certs/passwordless.key +51 -0
- data/example_server/config.ru +2 -0
- data/example_server/configuration.rb +22 -0
- data/lib/ruby_scep/certificate_builder.rb +28 -0
- data/lib/ruby_scep/configuration.rb +22 -0
- data/lib/ruby_scep/pki_message/degenerate.rb +34 -0
- data/lib/ruby_scep/pki_message/enveloped_data.rb +67 -0
- data/lib/ruby_scep/pki_message/signed_data.rb +153 -0
- data/lib/ruby_scep/pki_message.rb +108 -0
- data/lib/ruby_scep/pki_operation.rb +38 -0
- data/lib/ruby_scep/version.rb +14 -0
- data/lib/ruby_scep.rb +21 -0
- data/ruby_scep.gemspec +34 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 21d583fad0aab1365951300aaa682ada282a3e89
|
4
|
+
data.tar.gz: b67e773f8d760d5edeef761007f6eac24ceb2bec
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 40ab20bc9faf28782f58e726fb3cd9836afd431714aa1de335b95849506f168cb76e235ca1bd5391f95b8278fc7382d2cb57c869186bd516444a55330ff44728
|
7
|
+
data.tar.gz: 768b1540be23d55cc95bd2c7dbd44e896a44e1642254b9068343b3e4b2944ec26bc55a57e3ae2a264c2573b45310260b8759bc85a52c5d6610c52ab0c1a601cf
|
@@ -0,0 +1,30 @@
|
|
1
|
+
version: 2.0
|
2
|
+
jobs:
|
3
|
+
"ruby-2.3":
|
4
|
+
docker:
|
5
|
+
- image: circleci/ruby:2.3-node
|
6
|
+
working_directory: ~/ruby_scep
|
7
|
+
steps:
|
8
|
+
- checkout
|
9
|
+
- run: bundle install --path vendor/bundle
|
10
|
+
- run:
|
11
|
+
name: Run tests
|
12
|
+
command: bundle exec rspec spec --format progress
|
13
|
+
|
14
|
+
"ruby-2.4":
|
15
|
+
docker:
|
16
|
+
- image: circleci/ruby:2.4-node
|
17
|
+
working_directory: ~/ruby_scep
|
18
|
+
steps:
|
19
|
+
- checkout
|
20
|
+
- run: bundle install --path vendor/bundle
|
21
|
+
- run:
|
22
|
+
name: Run tests
|
23
|
+
command: bundle exec rspec spec --format progress
|
24
|
+
|
25
|
+
workflows:
|
26
|
+
version: 2
|
27
|
+
build:
|
28
|
+
jobs:
|
29
|
+
- "ruby-2.3"
|
30
|
+
- "ruby-2.4"
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
coderay (1.1.1)
|
5
|
+
diff-lcs (1.3)
|
6
|
+
method_source (0.8.2)
|
7
|
+
pry (0.10.4)
|
8
|
+
coderay (~> 1.1.0)
|
9
|
+
method_source (~> 0.8.1)
|
10
|
+
slop (~> 3.4)
|
11
|
+
rspec (3.6.0)
|
12
|
+
rspec-core (~> 3.6.0)
|
13
|
+
rspec-expectations (~> 3.6.0)
|
14
|
+
rspec-mocks (~> 3.6.0)
|
15
|
+
rspec-core (3.6.0)
|
16
|
+
rspec-support (~> 3.6.0)
|
17
|
+
rspec-expectations (3.6.0)
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
+
rspec-support (~> 3.6.0)
|
20
|
+
rspec-its (1.2.0)
|
21
|
+
rspec-core (>= 3.0.0)
|
22
|
+
rspec-expectations (>= 3.0.0)
|
23
|
+
rspec-mocks (3.6.0)
|
24
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
25
|
+
rspec-support (~> 3.6.0)
|
26
|
+
rspec-support (3.6.0)
|
27
|
+
slop (3.6.0)
|
28
|
+
timecop (0.9.1)
|
29
|
+
|
30
|
+
PLATFORMS
|
31
|
+
ruby
|
32
|
+
|
33
|
+
DEPENDENCIES
|
34
|
+
pry
|
35
|
+
rspec
|
36
|
+
rspec-its
|
37
|
+
timecop
|
38
|
+
|
39
|
+
BUNDLED WITH
|
40
|
+
1.15.3
|
data/License
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Appaloosa.io
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
Ruby SCEP [](https://circleci.com/gh/appaloosa-store/ruby_scep)
|
2
|
+
---
|
3
|
+
A Ruby gem to handle SCEP.
|
4
|
+
|
5
|
+
Installation
|
6
|
+
---
|
7
|
+
To install *Ruby Scep*:
|
8
|
+
|
9
|
+
```
|
10
|
+
$ gem install ruby_scep
|
11
|
+
```
|
12
|
+
|
13
|
+
Or you can include this in your project's `Gemfile`:
|
14
|
+
|
15
|
+
```
|
16
|
+
gem 'ruby_scep'
|
17
|
+
```
|
18
|
+
|
19
|
+
Then execute:
|
20
|
+
|
21
|
+
```
|
22
|
+
$ bundle
|
23
|
+
```
|
24
|
+
Usage
|
25
|
+
---
|
26
|
+
|
27
|
+
You must use a webserver (Webrick or related will do the trick) and declare two endpoints:
|
28
|
+
- `GET /scep`
|
29
|
+
- `POST /scep`
|
30
|
+
An example server is [included](https://github.com/appaloosa-store/ruby_scep/tree/master/example_server) in this gem.
|
31
|
+
|
32
|
+
Acknowledgements
|
33
|
+
---
|
34
|
+
This gem would not exist without the following repos:
|
35
|
+
- Nolan Browns's IosCertEnrollment. Non-working SCEP but the profiles generation part worked alright https://github.com/nolanbrown/ios-cert-enrollment
|
36
|
+
- MicroMDM's SCEP Go server. It worked perfectly, so we could use it as a reference https://github.com/micromdm/scep/
|
37
|
+
- OneLogin SCEP gem. The first instance of OpenSSL::ASN1 that led me to the AppBlade repo https://github.com/onelogin/scep-gem/blob/master/lib/scep/asn1.rb
|
38
|
+
- AppBlade's SCEP controller. The final pieces of the puzzle: the PKIMessage building using a ASN1 structure. I could'nt have done it without them. https://github.com/AppBlade/TestHub/blob/master/app/controllers/scep_controller.rb
|
39
|
+
|
40
|
+
We decided to open-source our solution to give back to the community that helped us greatly. Do the same with your projects!
|
41
|
+
|
42
|
+
Documentation
|
43
|
+
---
|
44
|
+
- CISCO's description of the SCEP protocol. Information about PKIMessage structure are also available here. https://www.cisco.com/c/en/us/support/docs/security-vpn/public-key-infrastructure-pki/116167-technote-scep-00.html
|
45
|
+
- SCEP RFC https://tools.ietf.org/html/draft-nourse-scep-23
|
46
|
+
- OID correspondance. A lifesaver to understand the ASN1 OIDs http://oid-info.com/
|
47
|
+
|
48
|
+
Contributing
|
49
|
+
---
|
50
|
+
1. Fork it ( https://github.com/appaloosa-store/ruby_scep )
|
51
|
+
2. Create your feature branch (git checkout -b my-new-feature)
|
52
|
+
3. Commit your changes (git commit -am 'Add some feature')
|
53
|
+
4. Push to the branch (git push origin my-new-feature)
|
54
|
+
5. Create a new Pull Request.
|
@@ -0,0 +1,31 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
ruby_scep (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
mustermann (1.0.1)
|
10
|
+
rack (2.0.3)
|
11
|
+
rack-protection (2.0.0)
|
12
|
+
rack
|
13
|
+
sinatra (2.0.0)
|
14
|
+
mustermann (~> 1.0)
|
15
|
+
rack (~> 2.0)
|
16
|
+
rack-protection (= 2.0.0)
|
17
|
+
tilt (~> 2.0)
|
18
|
+
tilt (2.0.8)
|
19
|
+
|
20
|
+
PLATFORMS
|
21
|
+
ruby
|
22
|
+
|
23
|
+
DEPENDENCIES
|
24
|
+
ruby_scep!
|
25
|
+
sinatra
|
26
|
+
|
27
|
+
RUBY VERSION
|
28
|
+
ruby 2.4.1p111
|
29
|
+
|
30
|
+
BUNDLED WITH
|
31
|
+
1.15.3
|
@@ -0,0 +1 @@
|
|
1
|
+
web: bundle exec rackup config.ru -p $PORT
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'sinatra'
|
3
|
+
require 'yaml'
|
4
|
+
require 'sinatra/base'
|
5
|
+
require 'webrick'
|
6
|
+
require 'webrick/https'
|
7
|
+
require 'openssl'
|
8
|
+
require 'ruby_scep'
|
9
|
+
|
10
|
+
RubyScep.configure do |config|
|
11
|
+
config.ca_cert_path = 'certs/ca.pem'
|
12
|
+
config.ca_key_path = 'certs/passwordless.key'
|
13
|
+
end
|
14
|
+
|
15
|
+
get '/scep' do
|
16
|
+
p 'get scep'
|
17
|
+
case params['operation']
|
18
|
+
when 'GetCACert'
|
19
|
+
p 'operation: GetCACert'
|
20
|
+
# todo, verify signer
|
21
|
+
content_type 'application/x-x509-ca-cert'
|
22
|
+
RubyScep.configuration.ca.to_der
|
23
|
+
when 'GetCACaps'
|
24
|
+
p 'operation: GetCACaps'
|
25
|
+
content_type 'text/plain'
|
26
|
+
"SHA-1\nSHA-256\nAES\nDES3\nSCEPStandard\nPOSTPKIOperation"
|
27
|
+
# see complete list of capabilities https://tools.ietf.org/html/draft-nourse-scep-23#appendix-C.2
|
28
|
+
else
|
29
|
+
'Invalid Action'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
post '/scep' do
|
34
|
+
p 'post scep'
|
35
|
+
if params['operation'] == 'PKIOperation'
|
36
|
+
content_type 'application/x-pki-message'
|
37
|
+
RubyScep::PkiOperation.build_response(request.body.read)
|
38
|
+
else
|
39
|
+
'Invalid Action'
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIFHzCCAwegAwIBAgIBATANBgkqhkiG9w0BAQsFADAxMQswCQYDVQQGEwJVUzEQ
|
3
|
+
MA4GA1UEChMHc2NlcC1jYTEQMA4GA1UECxMHU0NFUCBDQTAeFw0xNzA3MjUxMzIx
|
4
|
+
MzJaFw0yNzA3MjUxMzIxMzJaMDExCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdzY2Vw
|
5
|
+
LWNhMRAwDgYDVQQLEwdTQ0VQIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
|
6
|
+
CgKCAgEApMk9/OV2JFxvGHeG4KjqrJ2hua/rUHeXfsEvV2Kf7g7qtcY0+w24FSq1
|
7
|
+
wbTcnW7XG39eL5XNEtu2RF/0RdZp8jZ7VGuCUTX7X0tpKEzQqiy0+hSsarHoXEKA
|
8
|
+
k4lQMnJcM7tbiopz/vgtIjaREjAhmFcNv3OGkBAvNd7lk+4mVbiMpnh8IdZZBhf+
|
9
|
+
o0bVxGcTxxF39UIA6PqKp+SPvsVO1qZmEQJ1gpsu0nt0YTcl9PImIW+GYHD/r7UP
|
10
|
+
8AV+jfRJLZB5rzk46vAKMjV91jMhjASYJc+NUHcl4LDrha3O0pw5I9qLDmzcHel+
|
11
|
+
SFzpaTf9vBDo5ufQfKYlau6feNyq6O+L40V645jPmphU2lPOqr2UDqAaXELbNosP
|
12
|
+
xz6eFPY+EiU4XKMLumJrYgOK0ToE9KNoPwvRWUWjnEOnnJZrfKIMBClc0h6TG96T
|
13
|
+
oPavNbu+5nGS97dxs9pBYaRP1EuzCzXnmip5d/o0bTr2+l3nfZ1bNEAL/70YNZBx
|
14
|
+
2OIP1zGptBRqSV+2bLHJ1unRQSz8K/1xMnywJBAe9AS1HgaU0YTN59b8ptkQzDYE
|
15
|
+
UUuN85odKWiqp5QSaLDuZKA9GCnAFmg3FAGF7wMpkwXMdPXxo1SaQPzH0IC+rWIl
|
16
|
+
HD68kf3dmwMSrQKq63DxtuXx3kyOnA28Z6vZf4f/i4OZ66U3a0sCAwEAAaNCMEAw
|
17
|
+
DgYDVR0PAQH/BAQDAgIEMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFO2b2cg9
|
18
|
+
xw7Cx8MFKadgIsMJMl10MA0GCSqGSIb3DQEBCwUAA4ICAQAi8hKgoJYyl8mzBoJ1
|
19
|
+
G4UKWXdrsbWBkrmzy2OpA6BeLGk+EboJ6gsfFmxJ1T7MsqscsNx0VgXrxh/fz73+
|
20
|
+
uWI9FNZ29IQEjN8e24FO2uaYRVCCtC2T+DHtwOilCE0RbdZtbjH7pdceAJ+lnHBB
|
21
|
+
3zNnNm86lrjMG6dXF6NG68m7h8hV0sJnw5tGvccdg3b8nhxewXZSDfXgAew/nOQN
|
22
|
+
tdyZbIXOb6CKy+t4H3oKISC2DrSgreWqACLoIyAvFH6mC25QA/EIJBfoqbuHTinb
|
23
|
+
Mj8F13SpqgjeRsz4l0bhKzHDhmTgmcO0vXMmBbTFyzKtfa82eaL52rZ/YQrHOyef
|
24
|
+
ez1TmeHDtrfUZkbXxTEMEs01i6suiKfeb7rR2MzV6eIzX0yw/rSYgmbN+Ay7M6Vu
|
25
|
+
sVI3kTwEAFhlj2Gk2X1QjIXfB+rnmNe02TARLxNTAPZtuBrvhuLK7egLHofVRQcE
|
26
|
+
v6qWLZ2HmHn8rFs0wTsG73LFvvCo+zKboDDya+rHwbVGBFbk7cRw2cNTCoh58tSI
|
27
|
+
dzOqKwhKosE/uPNkFFu4wHoK7USjMSJL922B6DZKtZpzB02X5Q7M0pR507oFsBo/
|
28
|
+
6jp2/pUKk1XCiFe7aE/Xz71B3a4HwIDPvY/9re9LmTaHM2+V1jC3RzSbAHpElv7N
|
29
|
+
fEgf4g2JSNreyvurE0jM672I7Q==
|
30
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,51 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
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
|
51
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Server
|
3
|
+
class Configuration
|
4
|
+
attr_accessor :ca_cert_path, :ca_key_path,
|
5
|
+
:ca, :ca_key,
|
6
|
+
:certificates_store
|
7
|
+
|
8
|
+
def ca
|
9
|
+
@ca ||= OpenSSL::X509::Certificate.new(File.read(@ca_cert_path))
|
10
|
+
end
|
11
|
+
|
12
|
+
def ca_key
|
13
|
+
@ca_key ||= OpenSSL::PKey::RSA.new(File.read(@ca_key_path))
|
14
|
+
end
|
15
|
+
|
16
|
+
def certificates_store
|
17
|
+
return @certificates_store if defined?(@certificates_store)
|
18
|
+
@certificates_store = OpenSSL::X509::Store.new
|
19
|
+
@certificates_store.add_cert(ca)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module RubyScep
|
3
|
+
class CertificateBuilder
|
4
|
+
class << self
|
5
|
+
ONE_YEAR_IN_NUMBER_OF_SECONDS = 31536000
|
6
|
+
def build(csr)
|
7
|
+
certificate = OpenSSL::X509::Certificate.new
|
8
|
+
certificate.serial = Random.rand(730750818665451459101842416358141509827966271488) # will need to improve that
|
9
|
+
certificate.version = 1
|
10
|
+
certificate.public_key = csr.public_key
|
11
|
+
certificate.issuer = RubyScep.configuration.ca.subject
|
12
|
+
certificate.subject = csr.subject
|
13
|
+
certificate.not_before = Time.now
|
14
|
+
certificate.not_after = Time.now + ONE_YEAR_IN_NUMBER_OF_SECONDS
|
15
|
+
extension_factory = OpenSSL::X509::ExtensionFactory.new
|
16
|
+
extension_factory.subject_certificate = certificate
|
17
|
+
extension_factory.subject_request = csr
|
18
|
+
extension_factory.issuer_certificate = RubyScep.configuration.ca
|
19
|
+
certificate.add_extension(
|
20
|
+
extension_factory.create_extension(
|
21
|
+
'keyUsage', 'digitalSignature,keyEncipherment'
|
22
|
+
)
|
23
|
+
)
|
24
|
+
certificate
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module RubyScep
|
3
|
+
class Configuration
|
4
|
+
attr_accessor :ca_cert_path, :ca_key_path,
|
5
|
+
:ca, :ca_key,
|
6
|
+
:certificates_store
|
7
|
+
|
8
|
+
def ca
|
9
|
+
@ca ||= OpenSSL::X509::Certificate.new(File.read(@ca_cert_path))
|
10
|
+
end
|
11
|
+
|
12
|
+
def ca_key
|
13
|
+
@ca_key ||= OpenSSL::PKey::RSA.new(File.read(@ca_key_path))
|
14
|
+
end
|
15
|
+
|
16
|
+
def certificates_store
|
17
|
+
return @certificates_store if defined?(@certificates_store)
|
18
|
+
@certificates_store = OpenSSL::X509::Store.new
|
19
|
+
@certificates_store.add_cert(ca)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module RubyScep
|
3
|
+
class PkiMessage
|
4
|
+
class Degenerate
|
5
|
+
|
6
|
+
def initialize(certificate)
|
7
|
+
@certificate = certificate
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_der
|
11
|
+
OpenSSL::ASN1::Sequence.new(
|
12
|
+
[
|
13
|
+
OpenSSL::ASN1::ObjectId.new(OID_SIGNED_DATA),
|
14
|
+
OpenSSL::ASN1::ASN1Data.new(
|
15
|
+
[
|
16
|
+
OpenSSL::ASN1::Sequence.new(
|
17
|
+
[
|
18
|
+
OpenSSL::ASN1::Integer.new(1),
|
19
|
+
OpenSSL::ASN1::Set.new([]),
|
20
|
+
OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::ObjectId.new(OID_DATA)]),
|
21
|
+
OpenSSL::ASN1::ASN1Data.new([OpenSSL::ASN1::decode(@certificate.to_der)], 0, :CONTEXT_SPECIFIC),
|
22
|
+
OpenSSL::ASN1::ASN1Data.new([], 1, :CONTEXT_SPECIFIC),
|
23
|
+
OpenSSL::ASN1::Set.new([])
|
24
|
+
]
|
25
|
+
)
|
26
|
+
],
|
27
|
+
0,
|
28
|
+
:CONTEXT_SPECIFIC)
|
29
|
+
]
|
30
|
+
).to_der
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module RubyScep
|
3
|
+
class PkiMessage
|
4
|
+
class EnvelopedData
|
5
|
+
|
6
|
+
def initialize(p7, encryption_key, encryption_iv, encrypted_payload)
|
7
|
+
@p7 = p7
|
8
|
+
@encryption_key = encryption_key
|
9
|
+
@encryption_iv = encryption_iv
|
10
|
+
@encrypted_payload = encrypted_payload
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_der
|
14
|
+
OpenSSL::ASN1::Sequence.new(
|
15
|
+
[
|
16
|
+
OpenSSL::ASN1::ObjectId.new(OID_ENVELOPED_DATA),
|
17
|
+
OpenSSL::ASN1::ASN1Data.new(
|
18
|
+
[
|
19
|
+
OpenSSL::ASN1::Sequence.new(
|
20
|
+
[
|
21
|
+
OpenSSL::ASN1::Integer.new(0),
|
22
|
+
OpenSSL::ASN1::Set.new(
|
23
|
+
[
|
24
|
+
OpenSSL::ASN1::Sequence.new(
|
25
|
+
[
|
26
|
+
OpenSSL::ASN1::Integer.new(0),
|
27
|
+
OpenSSL::ASN1::Sequence.new(
|
28
|
+
[
|
29
|
+
OpenSSL::ASN1::decode(@p7.certificates.first.subject.to_der),
|
30
|
+
OpenSSL::ASN1::Integer.new(@p7.certificates.first.serial.to_i)
|
31
|
+
]
|
32
|
+
),
|
33
|
+
OpenSSL::ASN1::Sequence.new(
|
34
|
+
[
|
35
|
+
OpenSSL::ASN1::ObjectId.new(OID_RSA_ENCRYPTION),
|
36
|
+
OpenSSL::ASN1::Null.new(nil)
|
37
|
+
]
|
38
|
+
),
|
39
|
+
OpenSSL::ASN1::OctetString.new(@p7.certificates.first.public_key.public_encrypt(@encryption_key))
|
40
|
+
]
|
41
|
+
)
|
42
|
+
]
|
43
|
+
),
|
44
|
+
OpenSSL::ASN1::Sequence.new(
|
45
|
+
[
|
46
|
+
OpenSSL::ASN1::ObjectId.new(OID_DATA),
|
47
|
+
OpenSSL::ASN1::Sequence.new(
|
48
|
+
[
|
49
|
+
OpenSSL::ASN1::ObjectId.new(OID_DES_ALGO),
|
50
|
+
OpenSSL::ASN1::OctetString.new(@encryption_iv)
|
51
|
+
]
|
52
|
+
),
|
53
|
+
OpenSSL::ASN1::ASN1Data.new(@encrypted_payload, 0, :CONTEXT_SPECIFIC)
|
54
|
+
]
|
55
|
+
)
|
56
|
+
]
|
57
|
+
)
|
58
|
+
],
|
59
|
+
0,
|
60
|
+
:CONTEXT_SPECIFIC
|
61
|
+
)
|
62
|
+
]
|
63
|
+
).to_der
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module RubyScep
|
3
|
+
class PkiMessage
|
4
|
+
class SignedData
|
5
|
+
|
6
|
+
def initialize(envelop_sequence, ca, ca_key, sender_nonce, transaction_id)
|
7
|
+
@envelop_sequence = envelop_sequence
|
8
|
+
@ca = ca
|
9
|
+
@ca_key = ca_key
|
10
|
+
@sender_nonce = sender_nonce
|
11
|
+
@transaction_id = transaction_id
|
12
|
+
@sha1 = OpenSSL::Digest::SHA1.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_der
|
16
|
+
signed_attributes = signed_attributes_sequence
|
17
|
+
signed_attributes_digest = @ca_key.private_encrypt(
|
18
|
+
algo_identifier_sequence(signed_attributes, @sha1).to_der
|
19
|
+
)
|
20
|
+
OpenSSL::ASN1::Sequence.new(
|
21
|
+
[
|
22
|
+
OpenSSL::ASN1::ObjectId.new(OID_SIGNED_DATA),
|
23
|
+
OpenSSL::ASN1::ASN1Data.new(
|
24
|
+
[
|
25
|
+
OpenSSL::ASN1::Sequence.new(
|
26
|
+
[
|
27
|
+
OpenSSL::ASN1::Integer.new(1),
|
28
|
+
OpenSSL::ASN1::Set.new(
|
29
|
+
[
|
30
|
+
OpenSSL::ASN1::Sequence.new(
|
31
|
+
[
|
32
|
+
OpenSSL::ASN1::ObjectId.new(OID_HASH_ALGO_IDENTIFIER),
|
33
|
+
OpenSSL::ASN1::Null.new(nil)
|
34
|
+
]
|
35
|
+
)
|
36
|
+
]
|
37
|
+
),
|
38
|
+
OpenSSL::ASN1::Sequence.new(
|
39
|
+
[
|
40
|
+
OpenSSL::ASN1::ObjectId.new(OID_DATA),
|
41
|
+
OpenSSL::ASN1::ASN1Data.new([OpenSSL::ASN1::OctetString.new(@envelop_sequence)], 0, :CONTEXT_SPECIFIC)]
|
42
|
+
),
|
43
|
+
OpenSSL::ASN1::Set.new(
|
44
|
+
[
|
45
|
+
OpenSSL::ASN1::Sequence.new(
|
46
|
+
[
|
47
|
+
OpenSSL::ASN1::Integer.new(1),
|
48
|
+
OpenSSL::ASN1::Sequence.new(
|
49
|
+
[
|
50
|
+
OpenSSL::ASN1::decode(@ca.subject.to_der),
|
51
|
+
OpenSSL::ASN1::Integer.new(@ca.serial)
|
52
|
+
]
|
53
|
+
),
|
54
|
+
OpenSSL::ASN1::Sequence.new(
|
55
|
+
[
|
56
|
+
OpenSSL::ASN1::ObjectId.new(OID_HASH_ALGO_IDENTIFIER),
|
57
|
+
OpenSSL::ASN1::Null.new(nil)
|
58
|
+
]
|
59
|
+
),
|
60
|
+
signed_attributes,
|
61
|
+
OpenSSL::ASN1::Sequence.new(
|
62
|
+
[
|
63
|
+
OpenSSL::ASN1::ObjectId.new(OID_RSA_ENCRYPTION),
|
64
|
+
OpenSSL::ASN1::Null.new(nil)
|
65
|
+
]
|
66
|
+
),
|
67
|
+
OpenSSL::ASN1::OctetString.new(signed_attributes_digest)
|
68
|
+
]
|
69
|
+
),
|
70
|
+
]
|
71
|
+
)
|
72
|
+
]
|
73
|
+
)],
|
74
|
+
0,
|
75
|
+
:CONTEXT_SPECIFIC
|
76
|
+
)
|
77
|
+
]
|
78
|
+
).to_der
|
79
|
+
end
|
80
|
+
|
81
|
+
def signed_attributes_sequence
|
82
|
+
OpenSSL::ASN1::ASN1Data.new(
|
83
|
+
[
|
84
|
+
OpenSSL::ASN1::Sequence.new(
|
85
|
+
[
|
86
|
+
OpenSSL::ASN1::ObjectId.new(OID_CONTENT_TYPE),
|
87
|
+
OpenSSL::ASN1::Set.new([OpenSSL::ASN1::ObjectId.new(OID_DATA)])
|
88
|
+
]
|
89
|
+
),
|
90
|
+
OpenSSL::ASN1::Sequence.new(
|
91
|
+
[
|
92
|
+
OpenSSL::ASN1::ObjectId.new(OID_SIGNING_TIME),
|
93
|
+
OpenSSL::ASN1::Set.new([OpenSSL::ASN1::UTCTime.new(Time.now)])
|
94
|
+
]
|
95
|
+
),
|
96
|
+
OpenSSL::ASN1::Sequence.new(
|
97
|
+
[
|
98
|
+
OpenSSL::ASN1::ObjectId.new(OID_MESSAGE_DIGEST),
|
99
|
+
OpenSSL::ASN1::Set.new([OpenSSL::ASN1::OctetString.new(@sha1.digest(@envelop_sequence))])
|
100
|
+
]
|
101
|
+
),
|
102
|
+
OpenSSL::ASN1::Sequence.new(
|
103
|
+
[
|
104
|
+
OpenSSL::ASN1::ObjectId.new(OID_MESSAGE_TYPE),
|
105
|
+
OpenSSL::ASN1::Set.new([OpenSSL::ASN1::PrintableString.new(SCEP_MESSAGE_TYPES['CertRep'].to_s)])
|
106
|
+
]
|
107
|
+
),
|
108
|
+
OpenSSL::ASN1::Sequence.new(
|
109
|
+
[
|
110
|
+
OpenSSL::ASN1::ObjectId.new(OID_PKI_STATUS),
|
111
|
+
OpenSSL::ASN1::Set.new([OpenSSL::ASN1::PrintableString.new(SCEP_PKI_STATUSES['SUCCESS'].to_s)])
|
112
|
+
]
|
113
|
+
),
|
114
|
+
OpenSSL::ASN1::Sequence.new(
|
115
|
+
[
|
116
|
+
OpenSSL::ASN1::ObjectId.new(OID_RECIPIENT_NOUNCE),
|
117
|
+
OpenSSL::ASN1::Set.new([OpenSSL::ASN1::OctetString.new([@sender_nonce].pack('H*'))])
|
118
|
+
]
|
119
|
+
),
|
120
|
+
OpenSSL::ASN1::Sequence.new(
|
121
|
+
[
|
122
|
+
OpenSSL::ASN1::ObjectId.new(OID_SENDER_NONCE),
|
123
|
+
OpenSSL::ASN1::Set.new([OpenSSL::ASN1::OctetString.new([@sender_nonce].pack('H*'))])
|
124
|
+
]
|
125
|
+
),
|
126
|
+
OpenSSL::ASN1::Sequence.new(
|
127
|
+
[
|
128
|
+
OpenSSL::ASN1::ObjectId.new(OID_TRANSACTION_ID),
|
129
|
+
OpenSSL::ASN1::Set.new([OpenSSL::ASN1::PrintableString.new(@transaction_id)])
|
130
|
+
]
|
131
|
+
)
|
132
|
+
],
|
133
|
+
0,
|
134
|
+
:CONTEXT_SPECIFIC
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
def algo_identifier_sequence(signed_attributes, sha1)
|
139
|
+
OpenSSL::ASN1::Sequence.new(
|
140
|
+
[
|
141
|
+
OpenSSL::ASN1::Sequence.new(
|
142
|
+
[
|
143
|
+
OpenSSL::ASN1::ObjectId.new(OID_HASH_ALGO_IDENTIFIER),
|
144
|
+
OpenSSL::ASN1::Null.new(nil)
|
145
|
+
]
|
146
|
+
),
|
147
|
+
OpenSSL::ASN1::OctetString.new(sha1.digest(OpenSSL::ASN1::Set.new(signed_attributes.value[0..-1]).to_der))
|
148
|
+
]
|
149
|
+
)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module RubyScep
|
5
|
+
class PkiMessage
|
6
|
+
include OpenSSL::ASN1
|
7
|
+
|
8
|
+
# get OID corresponding name http://oid-info.com/get/<the oid>
|
9
|
+
# get possible balues for a given OID in the CMS RFC https://www.ietf.org/rfc/rfc3369.txt
|
10
|
+
OID_MESSAGE_TYPE = '2.16.840.1.113733.1.9.2'
|
11
|
+
OID_PKI_STATUS = '2.16.840.1.113733.1.9.3'
|
12
|
+
OID_FAIL_INFO = '2.16.840.1.113733.1.9.4'
|
13
|
+
OID_SENDER_NONCE = '2.16.840.1.113733.1.9.5'
|
14
|
+
OID_RECIPIENT_NOUNCE = '2.16.840.1.113733.1.9.6'
|
15
|
+
OID_TRANSACTION_ID = '2.16.840.1.113733.1.9.7'
|
16
|
+
OID_EXTENSION_REQUEST = '2.16.840.1.113733.1.9.8'
|
17
|
+
OID_SIGNED_DATA = '1.2.840.113549.1.7.2'
|
18
|
+
OID_DATA = '1.2.840.113549.1.7.1'
|
19
|
+
OID_ENVELOPED_DATA = '1.2.840.113549.1.7.3'
|
20
|
+
OID_RSA_ENCRYPTION = '1.2.840.113549.1.1.1'
|
21
|
+
OID_DES_ALGO = '1.2.840.113549.3.7'
|
22
|
+
OID_CONTENT_TYPE = '1.2.840.113549.1.9.3'
|
23
|
+
OID_SIGNING_TIME = '1.2.840.113549.1.9.5'
|
24
|
+
OID_MESSAGE_DIGEST = '1.2.840.113549.1.9.4'
|
25
|
+
OID_HASH_ALGO_IDENTIFIER = '1.3.14.3.2.26'
|
26
|
+
|
27
|
+
# complete list of possible SCEP values can be found in CISCO's documentation
|
28
|
+
# https://www.cisco.com/c/en/us/support/docs/security-vpn/public-key-infrastructure-pki/116167-technote-scep-00.html
|
29
|
+
SCEP_MESSAGE_TYPES = { 'PKCSReq' => 19, 'CertRep' => 3, 'GetCertInitial' => 20, 'GetCert' => 21, 'GetCRL' => 22 }
|
30
|
+
SCEP_PKI_STATUSES = { 'SUCCESS' => 0, 'FAILURE' => 2, 'PENDING' => 3 }
|
31
|
+
SCEP_FAIL_INFOS = { 'badAlg' => 0, 'badMessageCheck' => 1, 'badRequest' => 2, 'badTime' => 3, 'badCertId' => 4 }
|
32
|
+
|
33
|
+
attr_accessor :p7
|
34
|
+
|
35
|
+
def initialize(asn1, p7)
|
36
|
+
signed_attributes = retrieve_signed_attributes(asn1)
|
37
|
+
@message_type = SCEP_MESSAGE_TYPES.key(signed_attributes[OID_MESSAGE_TYPE].to_i)
|
38
|
+
@transaction_id = signed_attributes[OID_TRANSACTION_ID]
|
39
|
+
@sender_nonce = signed_attributes[OID_SENDER_NONCE]
|
40
|
+
@p7 = p7
|
41
|
+
end
|
42
|
+
|
43
|
+
# We are building a SCEP Secure Message Object with a valid PKCS7 structure, as referenced
|
44
|
+
# in https://tools.ietf.org/html/draft-nourse-scep-23#section-3
|
45
|
+
# To see a graphical representation of the final PKCS7 structure, go to
|
46
|
+
# https://www.cisco.com/c/dam/en/us/support/docs/security-vpn/public-key-infrastructure-pki/116167-technote-scep-00-01.jpeg
|
47
|
+
# Structure:
|
48
|
+
# 1. degenerate
|
49
|
+
# a. version
|
50
|
+
# b. x509
|
51
|
+
# 2. enveloped data
|
52
|
+
# a. version
|
53
|
+
# b. list of recepients
|
54
|
+
# c. encrypted data (aka 1. degenerate)
|
55
|
+
# 3. signed data
|
56
|
+
# a. version
|
57
|
+
# b. hashing algo
|
58
|
+
# c. signed (unencrypted) data (aka 2. enveloped data)
|
59
|
+
# d. ca certificate
|
60
|
+
# e. digital signature
|
61
|
+
def build_enrollment_response(csr)
|
62
|
+
degenerate_sequence = build_degenerate_sequence(csr)
|
63
|
+
enveloped_data_sequence = build_enveloped_data_sequence(degenerate_sequence)
|
64
|
+
build_signed_data_sequence(enveloped_data_sequence)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def retrieve_signed_attributes(asn1)
|
70
|
+
# cheers AppBlade! https://github.com/AppBlade/TestHub/blob/master/app/controllers/scep_controller.rb#L92-L112
|
71
|
+
raw_signed_attributes = asn1.value[1].value.first.value[4].first.value[3].value
|
72
|
+
raw_signed_attributes.inject({}) do |hash, raw_signed_attribute|
|
73
|
+
hash.merge(raw_signed_attribute.value.first.value => raw_signed_attribute.value.last.value.first.value)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_degenerate_sequence(csr)
|
78
|
+
certificate = CertificateBuilder.build(csr)
|
79
|
+
certificate.sign(RubyScep.configuration.ca_key, OpenSSL::Digest::SHA1.new)
|
80
|
+
PkiMessage::Degenerate.new(certificate).to_der
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_enveloped_data_sequence(degenerate_sequence)
|
84
|
+
encrypted_payload, encryption_key, encryption_iv = encrypt_payload(degenerate_sequence)
|
85
|
+
PkiMessage::EnvelopedData.new(@p7, encryption_key, encryption_iv, encrypted_payload).to_der
|
86
|
+
end
|
87
|
+
|
88
|
+
def encrypt_payload(der)
|
89
|
+
des = OpenSSL::Cipher::Cipher.new('des-ede3-cbc')
|
90
|
+
des.encrypt
|
91
|
+
encryption_key = des.random_key
|
92
|
+
encryption_iv = des.random_iv
|
93
|
+
des.key = encryption_key
|
94
|
+
des.iv = encryption_iv
|
95
|
+
[des.update(der) + des.final, encryption_key, encryption_iv]
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_signed_data_sequence(enveloped_data_sequence)
|
99
|
+
PkiMessage::SignedData.new(
|
100
|
+
enveloped_data_sequence,
|
101
|
+
RubyScep.configuration.ca,
|
102
|
+
RubyScep.configuration.ca_key,
|
103
|
+
@sender_nonce,
|
104
|
+
@transaction_id
|
105
|
+
).to_der
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module RubyScep
|
3
|
+
class PkiOperation
|
4
|
+
class << self
|
5
|
+
# @param raw_csr [String] The binary encoded CSR
|
6
|
+
# @return DER-encoded [String], PkiMessage represented in an OpenSSL::ASN1 structure containing the
|
7
|
+
# device's MDM certificate to be installed
|
8
|
+
def build_response(raw_csr)
|
9
|
+
pki_message = parse_pki_message(raw_csr)
|
10
|
+
csr = decrypt_pki_envelope(pki_message)
|
11
|
+
pki_message.build_enrollment_response(csr)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# @param raw_csr [String] The binary encoded CSR
|
17
|
+
# @return [RubyScep::PkiMessage], containing the CSR info
|
18
|
+
def parse_pki_message(raw_csr)
|
19
|
+
p7 = OpenSSL::PKCS7.new(raw_csr)
|
20
|
+
flags = OpenSSL::PKCS7::BINARY | OpenSSL::PKCS7::NOVERIFY
|
21
|
+
# OpenSSL::PKCS7::NOVERIFY is necessary otherwise the verify step fails
|
22
|
+
p7.verify(nil, RubyScep.configuration.certificates_store, nil, flags) # necessary to populate the p7 data field
|
23
|
+
asn1 = OpenSSL::ASN1.decode(p7.to_der)
|
24
|
+
PkiMessage.new(asn1, p7)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param pki_message [RubyScep::PkiMessage] The PkiMessage containing the CSR info sent by the iOS device
|
28
|
+
# @return [OpenSSL::X509::Request], the decrypted CSR
|
29
|
+
def decrypt_pki_envelope(pki_message)
|
30
|
+
encrypted_p7 = OpenSSL::PKCS7.new(pki_message.p7.data)
|
31
|
+
raw_csr = encrypted_p7.decrypt(RubyScep.configuration.ca_key, RubyScep.configuration.ca, OpenSSL::PKCS7::BINARY)
|
32
|
+
# this is the moment when we could extract the device info from the CSR (device id and challenge password)
|
33
|
+
# see https://github.com/AppBlade/TestHub/blob/master/app/controllers/scep_controller.rb#L57
|
34
|
+
OpenSSL::X509::Request.new(raw_csr)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/ruby_scep.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'ruby_scep/version'
|
3
|
+
require 'ruby_scep/configuration'
|
4
|
+
require 'ruby_scep/certificate_builder'
|
5
|
+
require 'ruby_scep/pki_message'
|
6
|
+
require 'ruby_scep/pki_message/degenerate'
|
7
|
+
require 'ruby_scep/pki_message/enveloped_data'
|
8
|
+
require 'ruby_scep/pki_message/signed_data'
|
9
|
+
require 'ruby_scep/pki_operation'
|
10
|
+
|
11
|
+
module RubyScep
|
12
|
+
attr_accessor :configuration
|
13
|
+
|
14
|
+
def self.configuration
|
15
|
+
@configuration ||= Configuration.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.configure
|
19
|
+
yield(configuration)
|
20
|
+
end
|
21
|
+
end
|
data/ruby_scep.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
4
|
+
require 'ruby_scep/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'ruby_scep'
|
8
|
+
s.version = RubyScep::Version::STRING
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.required_ruby_version = '>= 2.3.0'
|
11
|
+
s.authors = ['Christophe Valentin']
|
12
|
+
s.description = <<-EOF
|
13
|
+
Ruby implementation of SCEP
|
14
|
+
EOF
|
15
|
+
s.email = 'dev@appaloosa-store.com'
|
16
|
+
s.files = `git ls-files`.split($RS).reject do |file|
|
17
|
+
file =~ %r{^(?:
|
18
|
+
spec/.*
|
19
|
+
|Gemfile
|
20
|
+
|\.rspec
|
21
|
+
|\.gitignore
|
22
|
+
)$}x
|
23
|
+
end
|
24
|
+
s.extra_rdoc_files = %w(README.md)
|
25
|
+
s.homepage = 'https://github.com/appaloosa-store/ruby_scep'
|
26
|
+
s.licenses = ['MIT']
|
27
|
+
s.require_paths = ['lib']
|
28
|
+
|
29
|
+
s.summary = 'Ruby implementation of SCEP'
|
30
|
+
|
31
|
+
s.add_development_dependency('rspec', '~> 3.6')
|
32
|
+
s.add_development_dependency('rspec-its', '~> 1.2.0')
|
33
|
+
s.add_development_dependency('timecop', '~> 0.9.1')
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby_scep
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christophe Valentin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-its
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.2.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.2.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: timecop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.9.1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.9.1
|
55
|
+
description: " Ruby implementation of SCEP\n"
|
56
|
+
email: dev@appaloosa-store.com
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files:
|
60
|
+
- README.md
|
61
|
+
files:
|
62
|
+
- ".circleci/config.yml"
|
63
|
+
- Gemfile.lock
|
64
|
+
- License
|
65
|
+
- README.md
|
66
|
+
- example_server/Gemfile
|
67
|
+
- example_server/Gemfile.lock
|
68
|
+
- example_server/Procfile
|
69
|
+
- example_server/application.rb
|
70
|
+
- example_server/certs/ca.pem
|
71
|
+
- example_server/certs/passwordless.key
|
72
|
+
- example_server/config.ru
|
73
|
+
- example_server/configuration.rb
|
74
|
+
- lib/ruby_scep.rb
|
75
|
+
- lib/ruby_scep/certificate_builder.rb
|
76
|
+
- lib/ruby_scep/configuration.rb
|
77
|
+
- lib/ruby_scep/pki_message.rb
|
78
|
+
- lib/ruby_scep/pki_message/degenerate.rb
|
79
|
+
- lib/ruby_scep/pki_message/enveloped_data.rb
|
80
|
+
- lib/ruby_scep/pki_message/signed_data.rb
|
81
|
+
- lib/ruby_scep/pki_operation.rb
|
82
|
+
- lib/ruby_scep/version.rb
|
83
|
+
- ruby_scep.gemspec
|
84
|
+
homepage: https://github.com/appaloosa-store/ruby_scep
|
85
|
+
licenses:
|
86
|
+
- MIT
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.3.0
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 2.6.13
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: Ruby implementation of SCEP
|
108
|
+
test_files: []
|