ruby_scep 0.2.1 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: be639a9b514a63be0075c577e716aab3076c4e29
4
- data.tar.gz: 63cc039f8f0cc54436b3688d23e946282b38f562
2
+ SHA256:
3
+ metadata.gz: 8641a708f5a69bdc558fb7b8ca8734e65dee33db559aebc26a1a3d1e5d4fba38
4
+ data.tar.gz: 7fa9f5520c8dae4d7d11ed09920a62206eb69ff90c7d97e075b5ae540715ada9
5
5
  SHA512:
6
- metadata.gz: 47b704260abacb4787a1db950a1f19995181c04e2bc0d8fe5b13a91956772248ef7382ff5aaed647db0eba7200973c48135ce61ff840dd554d84d387630db0f5
7
- data.tar.gz: 525f5dc3960a13fc3a4fe42fda7a36f91c36aab60ab430dc6c03698a8e7adf8e2893b67c50fbd646729fd1cc6f4584ecf284986a05ba9f5023401987d947dbe6
6
+ metadata.gz: 27f97adb6d2bf739f0f76430fbe9e1a44736fe7b3af88a40d7cfaba4d0821e50b9297353a8e16d91683da73a94a6bbf561ce9a3b5b0cbea53179633780ecb0e1
7
+ data.tar.gz: fc7f39457151f5c85ef71e20757d52bcfbdc17f7331a32856ac854bf26170f786fd4edecbaf5865674b4bf1c0ea9a548d41225f0df45a1d0694b0ba0cda0f268
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ # Pushes to the default branch run here; every other branch is covered by
5
+ # pull_request. Matching '**' as well would fire both events for one push and
6
+ # duplicate every check on the pull request.
7
+ push:
8
+ branches: [master]
9
+ pull_request:
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ ruby: ['3.2', '3.3', '3.4']
18
+
19
+ name: Ruby ${{ matrix.ruby }}
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Set up Ruby ${{ matrix.ruby }}
25
+ uses: ruby/setup-ruby@v1
26
+ with:
27
+ ruby-version: ${{ matrix.ruby }}
28
+ bundler-cache: true
29
+
30
+ - name: Run tests
31
+ run: bundle exec rspec spec --format progress
data/CHANGELOG.md CHANGED
@@ -1,6 +1,49 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ v0.3.0 (24/08/2026)
5
+ -------------------
6
+
7
+ Issued certificates change. Nothing already issued is invalidated, and no
8
+ method signature changes, but a device enrolling against this version gets a
9
+ materially different certificate.
10
+
11
+ ### Certificates
12
+
13
+ - Issue X.509 v3. `version` was set to 1, which encodes **v2**, while a
14
+ `keyUsage` extension was attached -- and extensions are only legal in v3, so
15
+ the certificate was structurally invalid. Relying parties that enforce the
16
+ version/extension rule reject such a certificate.
17
+ - Sign certificates with SHA-256 instead of SHA-1.
18
+ - Add `basicConstraints CA:FALSE`, `subjectKeyIdentifier` and
19
+ `authorityKeyIdentifier`, and mark `keyUsage` critical.
20
+ - Derive `subjectKeyIdentifier` per RFC 7093 method 1 -- the leftmost 160 bits of
21
+ SHA-256 over the subjectPublicKey BIT STRING -- rather than OpenSSL's SHA-1
22
+ `hash` keyword.
23
+ - Carry over a `subjectAltName` requested through the CSR's `extensionRequest`
24
+ attribute, under an allowlist. Requests for `basicConstraints`, `keyUsage` or
25
+ `extendedKeyUsage` are dropped: a CSR will ask to become a CA, and must not be
26
+ granted it. A requested extension cannot displace one this gem sets, repeated
27
+ OIDs collapse to one, and a carried `subjectAltName` is forced non-critical.
28
+ - `extendedKeyUsage` is deliberately still unset. Restricting it to `clientAuth`
29
+ makes OpenSSL's `PKCS7#verify` reject the certificate for S/MIME signing, which
30
+ is the purpose it checks by default and therefore how a device's signed
31
+ requests are authenticated. Setting it requires the verifying side to use
32
+ `store.purpose = OpenSSL::X509::PURPOSE_ANY` first.
33
+
34
+ ### Tooling
35
+
36
+ - Fix the test suite on Ruby 3: `Factories.build` now forwards keyword arguments,
37
+ which Ruby 3 no longer converts from a trailing Hash. The whole suite was
38
+ failing before this.
39
+ - Replace `OpenSSL::PKCS7::SignerInfo#name` with `#issuer` in the specs; the
40
+ former was removed from the openssl gem.
41
+ - Replace the deprecated `OpenSSL::Cipher::Cipher` alias with `OpenSSL::Cipher`.
42
+ Same class, no behaviour change.
43
+ - Stop committing the library `Gemfile.lock`, and declare development
44
+ dependencies once, in the gemspec.
45
+ - Require Ruby >= 3.2 and test against 3.2, 3.3 and 3.4 in CI (was 2.3-2.5).
46
+
4
47
  v0.2.1 (17/10/2017)
5
48
  -------------------
6
49
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- Ruby SCEP [![CircleCI](https://circleci.com/gh/appaloosa-store/ruby_scep.svg?style=svg)](https://circleci.com/gh/appaloosa-store/ruby_scep)
1
+ Ruby SCEP [![CI](https://github.com/appaloosa-store/ruby_scep/actions/workflows/ci.yml/badge.svg)](https://github.com/appaloosa-store/ruby_scep/actions/workflows/ci.yml)
2
2
  ---
3
3
  A Ruby gem to handle SCEP.
4
4
 
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -1,4 +1,4 @@
1
- source 'http://rubygems.org'
2
- ruby '2.4.1'
1
+ source 'https://rubygems.org'
2
+ ruby '2.5.3'
3
3
  gem 'sinatra'
4
- gem 'ruby_scep', path: '../'
4
+ gem 'ruby_scep', path: '../'
@@ -1,19 +1,19 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- ruby_scep (0.1.0)
4
+ ruby_scep (0.2.1)
5
5
 
6
6
  GEM
7
- remote: http://rubygems.org/
7
+ remote: https://rubygems.org/
8
8
  specs:
9
- mustermann (1.0.1)
10
- rack (2.0.3)
11
- rack-protection (2.0.0)
9
+ mustermann (1.0.3)
10
+ rack (2.0.6)
11
+ rack-protection (2.0.4)
12
12
  rack
13
- sinatra (2.0.0)
13
+ sinatra (2.0.4)
14
14
  mustermann (~> 1.0)
15
15
  rack (~> 2.0)
16
- rack-protection (= 2.0.0)
16
+ rack-protection (= 2.0.4)
17
17
  tilt (~> 2.0)
18
18
  tilt (2.0.8)
19
19
 
@@ -25,7 +25,7 @@ DEPENDENCIES
25
25
  sinatra
26
26
 
27
27
  RUBY VERSION
28
- ruby 2.4.1p111
28
+ ruby 2.5.3p105
29
29
 
30
30
  BUNDLED WITH
31
- 1.15.3
31
+ 1.17.1
@@ -1,27 +1,116 @@
1
1
  # frozen_string_literal: true
2
+ require 'securerandom'
3
+
2
4
  module RubyScep
3
5
  class CertificateBuilder
6
+ ONE_YEAR_IN_NUMBER_OF_SECONDS = 31_536_000
7
+
8
+ # Serial numbers must be positive and fit in 20 octets (RFC 5280 4.1.2.2).
9
+ SERIAL_BITS = 159
10
+
11
+ # RFC 7093 method 1 truncates the subjectKeyIdentifier to the leftmost
12
+ # 160 bits.
13
+ SKI_LENGTH = 20
14
+
15
+ # Extensions a CSR is allowed to influence. A device may legitimately ask
16
+ # for the names it will be known by; it may not ask to become a CA, nor to
17
+ # widen its own key usage. Everything outside this list is dropped rather
18
+ # than honoured -- a CSR requesting `basicConstraints CA:TRUE` must never
19
+ # produce a certificate that can sign others, and it will ask.
20
+ COPYABLE_EXTENSIONS = %w[subjectAltName].freeze
21
+
4
22
  class << self
5
- ONE_YEAR_IN_NUMBER_OF_SECONDS = 31536000
23
+ # @param csr [OpenSSL::X509::Request] the decrypted certificate request
24
+ # @return [OpenSSL::X509::Certificate] an unsigned certificate
6
25
  def build(csr)
26
+ ca = RubyScep.configuration.ca
27
+ now = Time.now
28
+
7
29
  certificate = OpenSSL::X509::Certificate.new
8
- certificate.serial = Random.rand(730750818665451459101842416358141509827966271488) # will need to improve that
9
- certificate.version = 1
30
+ # 2 means X.509 v3: the version field is zero-indexed, and the
31
+ # extensions added below are only legal in v3.
32
+ certificate.version = 2
33
+ certificate.serial = generate_serial
10
34
  certificate.public_key = csr.public_key
11
- certificate.issuer = RubyScep.configuration.ca.subject
35
+ certificate.issuer = ca.subject
12
36
  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
37
+ certificate.not_before = now
38
+ certificate.not_after = now + ONE_YEAR_IN_NUMBER_OF_SECONDS
39
+
40
+ add_extensions(certificate, csr, ca)
41
+
42
+ certificate
43
+ end
44
+
45
+ private
46
+
47
+ def add_extensions(certificate, csr, ca)
48
+ factory = OpenSSL::X509::ExtensionFactory.new
49
+ factory.subject_certificate = certificate
50
+ factory.subject_request = csr
51
+ factory.issuer_certificate = ca
52
+
53
+ certificate.add_extension(factory.create_extension('basicConstraints', 'CA:FALSE', true))
19
54
  certificate.add_extension(
20
- extension_factory.create_extension(
21
- 'keyUsage', 'digitalSignature,keyEncipherment'
22
- )
55
+ factory.create_extension('keyUsage', 'digitalSignature,keyEncipherment', true)
23
56
  )
24
- certificate
57
+ # No extendedKeyUsage here, deliberately. Restricting EKU to clientAuth
58
+ # makes OpenSSL's PKCS7#verify reject the certificate for S/MIME
59
+ # signing, which is the purpose it checks by default -- and that is how
60
+ # a device's signed requests are authenticated. Adding it requires the
61
+ # verifying side to set `store.purpose = OpenSSL::X509::PURPOSE_ANY`
62
+ # first, which keeps chain validation while dropping the purpose check.
63
+ certificate.add_extension(
64
+ factory.create_extension('subjectKeyIdentifier', subject_key_identifier(certificate.public_key))
65
+ )
66
+ # keyid here is a copy of the issuer's own subjectKeyIdentifier, so this
67
+ # follows whatever derivation the CA certificate used.
68
+ certificate.add_extension(factory.create_extension('authorityKeyIdentifier', 'keyid,issuer'))
69
+
70
+ server_set = certificate.extensions.map(&:oid)
71
+ copyable_extensions(csr, server_set).each { |extension| certificate.add_extension(extension) }
72
+ end
73
+
74
+ # @param already_set [Array<String>] OIDs this builder has already decided
75
+ def copyable_extensions(csr, already_set)
76
+ requested_extensions(csr)
77
+ .select { |extension| COPYABLE_EXTENSIONS.include?(extension.oid) }
78
+ .reject { |extension| already_set.include?(extension.oid) }
79
+ .uniq(&:oid)
80
+ .each { |extension| extension.critical = false }
81
+ end
82
+
83
+ # Extensions the CSR asked for through its extensionRequest attribute.
84
+ # A malformed attribute is ignored rather than raised on: this builder
85
+ # decides policy, it does not validate the request.
86
+ def requested_extensions(csr)
87
+ attribute = csr.attributes.find do |candidate|
88
+ %w[extReq extensionRequest msExtReq].include?(candidate.oid)
89
+ end
90
+ return [] if attribute.nil?
91
+
92
+ attribute.value.value.first.value.map { |asn1| OpenSSL::X509::Extension.new(asn1) }
93
+ rescue StandardError
94
+ []
95
+ end
96
+
97
+ def generate_serial
98
+ SecureRandom.random_number((1 << SERIAL_BITS) - 1) + 1
99
+ end
100
+
101
+ # RFC 7093 method 1: the leftmost 160 bits of SHA-256 over the
102
+ # subjectPublicKey BIT STRING contents, excluding the tag, length and
103
+ # unused-bits octet. OpenSSL's `hash` keyword would derive this with SHA-1
104
+ # instead (RFC 5280 4.2.1.2 method 1). This matches the derivation the CA
105
+ # uses, and micromdm's cryptoutil.GenerateSubjectKeyID.
106
+ #
107
+ # The BIT STRING contents are the second element of SubjectPublicKeyInfo,
108
+ # which for RSA is the RSAPublicKey DER -- already stripped of exactly what
109
+ # the RFC excludes.
110
+ def subject_key_identifier(public_key)
111
+ bit_string = OpenSSL::ASN1.decode(public_key.to_der).value[1].value
112
+ OpenSSL::Digest::SHA256.digest(bit_string)[0, SKI_LENGTH]
113
+ .unpack1('H*').upcase.scan(/../).join(':')
25
114
  end
26
115
  end
27
116
  end
@@ -89,7 +89,7 @@ module RubyScep
89
89
  # The certificate will be embedded in the PKIMessage response to complete the SCEP process.
90
90
  def generate_device_certificate!(csr)
91
91
  certificate = CertificateBuilder.build(csr)
92
- certificate.sign(RubyScep.configuration.ca_key, OpenSSL::Digest::SHA1.new)
92
+ certificate.sign(RubyScep.configuration.ca_key, OpenSSL::Digest.new('SHA256'))
93
93
  @device_certificate = certificate
94
94
  end
95
95
 
@@ -103,7 +103,7 @@ module RubyScep
103
103
  end
104
104
 
105
105
  def encrypt_payload(der)
106
- des = OpenSSL::Cipher::Cipher.new('des-ede3-cbc')
106
+ des = OpenSSL::Cipher.new('des-ede3-cbc')
107
107
  des.encrypt
108
108
  encryption_key = des.random_key
109
109
  encryption_iv = des.random_iv
@@ -3,7 +3,7 @@
3
3
 
4
4
  module RubyScep
5
5
  module Version
6
- STRING = '0.2.1'
6
+ STRING = '0.3.0'
7
7
 
8
8
  module_function
9
9
 
data/ruby_scep.gemspec CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.name = 'ruby_scep'
8
8
  s.version = RubyScep::Version::STRING
9
9
  s.platform = Gem::Platform::RUBY
10
- s.required_ruby_version = '>= 2.3.0'
10
+ s.required_ruby_version = '>= 3.2.0'
11
11
  s.authors = ['Christophe Valentin']
12
12
  s.description = <<-EOF
13
13
  Ruby implementation of SCEP
@@ -28,7 +28,8 @@ Gem::Specification.new do |s|
28
28
 
29
29
  s.summary = 'Ruby implementation of SCEP'
30
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
31
+ s.add_development_dependency('rake', '~> 13.0')
32
+ s.add_development_dependency('rspec', '~> 3.13')
33
+ s.add_development_dependency('rspec-its', '~> 2.0')
34
+ s.add_development_dependency('timecop', '~> 0.9')
35
+ end
metadata CHANGED
@@ -1,57 +1,70 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_scep
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christophe Valentin
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2017-10-17 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rake
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '13.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '13.0'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: rspec
15
28
  requirement: !ruby/object:Gem::Requirement
16
29
  requirements:
17
30
  - - "~>"
18
31
  - !ruby/object:Gem::Version
19
- version: '3.6'
32
+ version: '3.13'
20
33
  type: :development
21
34
  prerelease: false
22
35
  version_requirements: !ruby/object:Gem::Requirement
23
36
  requirements:
24
37
  - - "~>"
25
38
  - !ruby/object:Gem::Version
26
- version: '3.6'
39
+ version: '3.13'
27
40
  - !ruby/object:Gem::Dependency
28
41
  name: rspec-its
29
42
  requirement: !ruby/object:Gem::Requirement
30
43
  requirements:
31
44
  - - "~>"
32
45
  - !ruby/object:Gem::Version
33
- version: 1.2.0
46
+ version: '2.0'
34
47
  type: :development
35
48
  prerelease: false
36
49
  version_requirements: !ruby/object:Gem::Requirement
37
50
  requirements:
38
51
  - - "~>"
39
52
  - !ruby/object:Gem::Version
40
- version: 1.2.0
53
+ version: '2.0'
41
54
  - !ruby/object:Gem::Dependency
42
55
  name: timecop
43
56
  requirement: !ruby/object:Gem::Requirement
44
57
  requirements:
45
58
  - - "~>"
46
59
  - !ruby/object:Gem::Version
47
- version: 0.9.1
60
+ version: '0.9'
48
61
  type: :development
49
62
  prerelease: false
50
63
  version_requirements: !ruby/object:Gem::Requirement
51
64
  requirements:
52
65
  - - "~>"
53
66
  - !ruby/object:Gem::Version
54
- version: 0.9.1
67
+ version: '0.9'
55
68
  description: " Ruby implementation of SCEP\n"
56
69
  email: dev@appaloosa-store.com
57
70
  executables: []
@@ -59,11 +72,11 @@ extensions: []
59
72
  extra_rdoc_files:
60
73
  - README.md
61
74
  files:
62
- - ".circleci/config.yml"
75
+ - ".github/workflows/ci.yml"
63
76
  - CHANGELOG.md
64
- - Gemfile.lock
65
77
  - License
66
78
  - README.md
79
+ - Rakefile
67
80
  - example_server/Gemfile
68
81
  - example_server/Gemfile.lock
69
82
  - example_server/Procfile
@@ -86,7 +99,6 @@ homepage: https://github.com/appaloosa-store/ruby_scep
86
99
  licenses:
87
100
  - MIT
88
101
  metadata: {}
89
- post_install_message:
90
102
  rdoc_options: []
91
103
  require_paths:
92
104
  - lib
@@ -94,16 +106,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
106
  requirements:
95
107
  - - ">="
96
108
  - !ruby/object:Gem::Version
97
- version: 2.3.0
109
+ version: 3.2.0
98
110
  required_rubygems_version: !ruby/object:Gem::Requirement
99
111
  requirements:
100
112
  - - ">="
101
113
  - !ruby/object:Gem::Version
102
114
  version: '0'
103
115
  requirements: []
104
- rubyforge_project:
105
- rubygems_version: 2.6.14
106
- signing_key:
116
+ rubygems_version: 4.0.6
107
117
  specification_version: 4
108
118
  summary: Ruby implementation of SCEP
109
119
  test_files: []
data/.circleci/config.yml DELETED
@@ -1,30 +0,0 @@
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 DELETED
@@ -1,40 +0,0 @@
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