nemid 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/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +70 -0
- data/LICENSE.txt +21 -0
- data/README.md +174 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/nemid.rb +12 -0
- data/lib/nemid/authentication.rb +7 -0
- data/lib/nemid/authentication/params.rb +52 -0
- data/lib/nemid/authentication/response.rb +93 -0
- data/lib/nemid/crypto.rb +53 -0
- data/lib/nemid/errors.rb +17 -0
- data/lib/nemid/errors/app.rb +49 -0
- data/lib/nemid/errors/auth.rb +180 -0
- data/lib/nemid/errors/can.rb +97 -0
- data/lib/nemid/errors/capp.rb +105 -0
- data/lib/nemid/errors/lib.rb +19 -0
- data/lib/nemid/errors/lock.rb +48 -0
- data/lib/nemid/errors/oces.rb +85 -0
- data/lib/nemid/errors/srv.rb +72 -0
- data/lib/nemid/errors/validation.rb +31 -0
- data/lib/nemid/ocsp.rb +81 -0
- data/lib/nemid/ocsp/errors.rb +29 -0
- data/lib/nemid/pid_cpr.rb +68 -0
- data/lib/nemid/version.rb +3 -0
- data/lib/nemid/xmldsig.rb +35 -0
- data/lib/nemid/xmldsig/document.rb +75 -0
- data/nemid.gemspec +32 -0
- metadata +139 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
module NemID
|
2
|
+
module OCSP
|
3
|
+
class Error < StandardError ; end
|
4
|
+
|
5
|
+
class InvalidSignatureError < Error
|
6
|
+
def initialize(msg='Response is not signed by a trusted certificate')
|
7
|
+
super(msg)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class NoStatusError < Error
|
12
|
+
def initialize(msg='basic_response does not have the status for the certificate')
|
13
|
+
super(msg)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class InvalidUpdateError < Error
|
18
|
+
def initialize(msg='this_update is in the future or next_update time has passed')
|
19
|
+
super(msg)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class NonceError < Error
|
24
|
+
def initialize(msg='Nonces both present and not equal')
|
25
|
+
super(msg)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'savon'
|
2
|
+
|
3
|
+
module NemID
|
4
|
+
class PIDCPR
|
5
|
+
PID_SERVICE_URL = 'https://pidws.pp.certifikat.dk/pid_serviceprovider_server/pidws'
|
6
|
+
|
7
|
+
def initialize(spid, cert, pass)
|
8
|
+
@crypto = NemID::Crypto.new(cert, pass)
|
9
|
+
@spid = spid
|
10
|
+
end
|
11
|
+
|
12
|
+
=begin
|
13
|
+
STATUS CODE RESPONSES:
|
14
|
+
O = OK (”OK", "OK")
|
15
|
+
1 = NO_MATCH ("CPR svarer ikke til PID", "CPR does not match PID")
|
16
|
+
2 = NO_PID ("PID eksisterer ikke", "PID doesn't exist")
|
17
|
+
4 = NO_PID_IN_CERTIFICATE ("PID kunne ikke findes i certifikatet", "No PID in certificate")
|
18
|
+
8 = NOT_AUTHORIZED_FOR_CPR_LOOKUP ("Der er ikke rettighed til at foretage CPR opslag", "Caller not authorized for CPR lookup")
|
19
|
+
16 = BRUTE_FORCE_ATTEMPT_DETECTED ("Forsøg på systematisk søgning på CPR", "Brute force attempt detected")
|
20
|
+
17 = NOT_AUTHORIZED_FOR_SERVICE_LOOKUP ( "Der er ikke rettighed til at foretage opslag på service", "Caller not authorized for service lookup")
|
21
|
+
4096 = NOT_PID_SERVICE_REQUEST ("Modtaget message ikke pidCprRequest eller pidCprServerStatus", "Non request XML received")
|
22
|
+
8192 = XML_PARSE_ERROR ("XML pakke kan ikke parses", "Non parsable XML received")
|
23
|
+
8193 = XML_VERSION_NOT_SUPPORTED ("Version er ikke understøttet", "Version not supported")
|
24
|
+
8194 = PID_DOES_NOT_MATCH_BASE64_CERTIFICATE ("PID stemmer med ikke med certifikat", "PID does not match certifikat")
|
25
|
+
8195 = MISSING_CLIENT_CERT ("Klient certifikat ikke præsenteret", "No client certificate presented")
|
26
|
+
16384 = INTERNAL_ERROR ("Intern DanID fejl", "Internal DanID error")
|
27
|
+
=end
|
28
|
+
def match pid:, cpr:
|
29
|
+
response = soap_client.call(:pid,
|
30
|
+
message: build_soap_message(pid: pid, cpr: cpr)
|
31
|
+
)
|
32
|
+
|
33
|
+
result = response.to_hash[:pid_response][:result][:pid_reply]
|
34
|
+
|
35
|
+
if result[:status_code] == "0"
|
36
|
+
true
|
37
|
+
else
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def build_soap_message pid:, cpr:
|
44
|
+
{
|
45
|
+
:pIDRequests => {
|
46
|
+
:PIDRequest => {
|
47
|
+
PID: pid,
|
48
|
+
CPR: cpr,
|
49
|
+
serviceId: @spid,
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def soap_client
|
56
|
+
options = {
|
57
|
+
:wsdl => "#{PID_SERVICE_URL}?WSDL",
|
58
|
+
:soap_version => 1,
|
59
|
+
:endpoint => PID_SERVICE_URL,
|
60
|
+
:convert_request_keys_to => :none,
|
61
|
+
:ssl_cert => @crypto.get_certificate,
|
62
|
+
:ssl_cert_key => @crypto.get_key,
|
63
|
+
:headers => { 'SOAPAction' => ''}
|
64
|
+
}
|
65
|
+
return Savon.client(options)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'xmldsig'
|
2
|
+
|
3
|
+
module NemID
|
4
|
+
module XMLDSig
|
5
|
+
NAMESPACES = {
|
6
|
+
'ds' => 'http://www.w3.org/2000/09/xmldsig#',
|
7
|
+
'openoces' => 'http://www.openoces.org/2006/07/signature#',
|
8
|
+
"ec" => "http://www.w3.org/2001/10/xml-exc-c14n#",
|
9
|
+
"wsu" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
|
10
|
+
}
|
11
|
+
|
12
|
+
# The "referenced_node" method needs to be overridden since NemID uses
|
13
|
+
# "Id" (as opposed to ID or wsu:Id) as the attribute name in the "Signature"
|
14
|
+
# element of the XML document.
|
15
|
+
Xmldsig::Reference.class_eval do
|
16
|
+
def referenced_node
|
17
|
+
if reference_uri && reference_uri != ""
|
18
|
+
id = reference_uri[1..-1]
|
19
|
+
if ref = document.dup.at_xpath("//*[@ID='#{id}' or @Id='#{id}' or @wsu:Id='#{id}']", NAMESPACES)
|
20
|
+
ref
|
21
|
+
else
|
22
|
+
raise(
|
23
|
+
ReferencedNodeNotFound,
|
24
|
+
"Could not find the referenced node #{id}'"
|
25
|
+
)
|
26
|
+
end
|
27
|
+
else
|
28
|
+
document.dup.root
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
require_relative "xmldsig/document"
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module NemID
|
4
|
+
module XMLDSig
|
5
|
+
class Document < Xmldsig::SignedDocument
|
6
|
+
def initialize(document, options = {})
|
7
|
+
super
|
8
|
+
@store = OpenSSL::X509::Store.new
|
9
|
+
@user_certificate = nil
|
10
|
+
extract_and_store_certificates
|
11
|
+
end
|
12
|
+
|
13
|
+
def extract_pid_or_rid
|
14
|
+
return @user_certificate.subject.to_a.assoc("serialNumber")[1]
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_user_certificate
|
18
|
+
return @user_certificate
|
19
|
+
end
|
20
|
+
|
21
|
+
def user_certificate_expired?
|
22
|
+
@user_certificate.not_after < Time.now.utc
|
23
|
+
end
|
24
|
+
|
25
|
+
def user_certificate_revoked?
|
26
|
+
ocsp.request(
|
27
|
+
subject: @user_certificate,
|
28
|
+
issuer: @intermediate_cert,
|
29
|
+
ca: @root_cert
|
30
|
+
)
|
31
|
+
rescue NemID::OCSP::Error
|
32
|
+
return true
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate_certificate_chain
|
36
|
+
@store.verify(@user_certificate)
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate_signature
|
40
|
+
validate(@user_certificate)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def certificates
|
45
|
+
xpath = '//ds:KeyInfo/ds:X509Data/ds:X509Certificate'
|
46
|
+
document.xpath(xpath, NAMESPACES).each
|
47
|
+
end
|
48
|
+
|
49
|
+
def extract_and_store_certificates
|
50
|
+
certificates.each do |element|
|
51
|
+
cert = x509_certificate(Base64.decode64(element.text))
|
52
|
+
cert_key_usage = cert.find_extension('keyUsage').value
|
53
|
+
|
54
|
+
if (cert_key_usage =~ /Digital Signature/)
|
55
|
+
@user_certificate = cert
|
56
|
+
elsif cert.issuer.cmp(cert.subject) == 0
|
57
|
+
@root_cert = cert
|
58
|
+
@store.add_cert(cert)
|
59
|
+
else
|
60
|
+
@intermediate_cert = cert
|
61
|
+
@store.add_cert(cert)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def ocsp
|
67
|
+
@ocsp ||= NemID::OCSP
|
68
|
+
end
|
69
|
+
|
70
|
+
def x509_certificate(raw)
|
71
|
+
OpenSSL::X509::Certificate.new(raw)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/nemid.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative 'lib/nemid/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "nemid"
|
5
|
+
spec.version = NemID::VERSION
|
6
|
+
spec.authors = ["David Cabeza"]
|
7
|
+
spec.email = ["david@meew.dk"]
|
8
|
+
|
9
|
+
spec.summary = %q{Ruby gem that makes it easy to integrate the NemID JavaScript client}
|
10
|
+
#spec.description = %q{TODO: Write a longer description or delete this line.}
|
11
|
+
spec.homepage = "https://github.com/davideluque/nemid"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
14
|
+
|
15
|
+
spec.add_dependency 'openssl', '~> 2.2', '>= 2.2.0'
|
16
|
+
spec.add_dependency 'savon', '~> 2.12.0', '>=2.12.0'
|
17
|
+
spec.add_dependency 'xmldsig', '~> 0.6.6', '>= 0.6.6'
|
18
|
+
|
19
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
20
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
21
|
+
spec.metadata["source_code_uri"] = "https://github.com/davideluque/nemid"
|
22
|
+
#spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
23
|
+
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
25
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
26
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
27
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nemid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Cabeza
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-09-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: openssl
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.2'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.2.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.2'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.2.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: savon
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 2.12.0
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 2.12.0
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 2.12.0
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 2.12.0
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: xmldsig
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 0.6.6
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.6.6
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.6.6
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 0.6.6
|
73
|
+
description:
|
74
|
+
email:
|
75
|
+
- david@meew.dk
|
76
|
+
executables: []
|
77
|
+
extensions: []
|
78
|
+
extra_rdoc_files: []
|
79
|
+
files:
|
80
|
+
- ".gitignore"
|
81
|
+
- ".rspec"
|
82
|
+
- ".travis.yml"
|
83
|
+
- CODE_OF_CONDUCT.md
|
84
|
+
- Gemfile
|
85
|
+
- Gemfile.lock
|
86
|
+
- LICENSE.txt
|
87
|
+
- README.md
|
88
|
+
- Rakefile
|
89
|
+
- bin/console
|
90
|
+
- bin/setup
|
91
|
+
- lib/nemid.rb
|
92
|
+
- lib/nemid/authentication.rb
|
93
|
+
- lib/nemid/authentication/params.rb
|
94
|
+
- lib/nemid/authentication/response.rb
|
95
|
+
- lib/nemid/crypto.rb
|
96
|
+
- lib/nemid/errors.rb
|
97
|
+
- lib/nemid/errors/app.rb
|
98
|
+
- lib/nemid/errors/auth.rb
|
99
|
+
- lib/nemid/errors/can.rb
|
100
|
+
- lib/nemid/errors/capp.rb
|
101
|
+
- lib/nemid/errors/lib.rb
|
102
|
+
- lib/nemid/errors/lock.rb
|
103
|
+
- lib/nemid/errors/oces.rb
|
104
|
+
- lib/nemid/errors/srv.rb
|
105
|
+
- lib/nemid/errors/validation.rb
|
106
|
+
- lib/nemid/ocsp.rb
|
107
|
+
- lib/nemid/ocsp/errors.rb
|
108
|
+
- lib/nemid/pid_cpr.rb
|
109
|
+
- lib/nemid/version.rb
|
110
|
+
- lib/nemid/xmldsig.rb
|
111
|
+
- lib/nemid/xmldsig/document.rb
|
112
|
+
- nemid.gemspec
|
113
|
+
homepage: https://github.com/davideluque/nemid
|
114
|
+
licenses:
|
115
|
+
- MIT
|
116
|
+
metadata:
|
117
|
+
allowed_push_host: https://rubygems.org
|
118
|
+
homepage_uri: https://github.com/davideluque/nemid
|
119
|
+
source_code_uri: https://github.com/davideluque/nemid
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: 2.5.0
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
requirements: []
|
135
|
+
rubygems_version: 3.1.2
|
136
|
+
signing_key:
|
137
|
+
specification_version: 4
|
138
|
+
summary: Ruby gem that makes it easy to integrate the NemID JavaScript client
|
139
|
+
test_files: []
|