rest_pki 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +28 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +19 -0
- data/Rakefile +2 -0
- data/lib/rest_pki.rb +35 -0
- data/lib/rest_pki/authentication.rb +43 -0
- data/lib/rest_pki/cades_signature_finisher.rb +48 -0
- data/lib/rest_pki/cades_signature_starter.rb +148 -0
- data/lib/rest_pki/full_xml_signature_starter.rb +51 -0
- data/lib/rest_pki/namespace_manager.rb +16 -0
- data/lib/rest_pki/object.rb +126 -0
- data/lib/rest_pki/pades_signature_finisher.rb +50 -0
- data/lib/rest_pki/pades_signature_starter.rb +110 -0
- data/lib/rest_pki/pades_visual_positioning_presets.rb +32 -0
- data/lib/rest_pki/resources/authentication_model.rb +5 -0
- data/lib/rest_pki/resources/cades_model.rb +5 -0
- data/lib/rest_pki/resources/pades_model.rb +5 -0
- data/lib/rest_pki/resources/response_model.rb +5 -0
- data/lib/rest_pki/resources/xml_model.rb +5 -0
- data/lib/rest_pki/rest_base_error.rb +12 -0
- data/lib/rest_pki/rest_error.rb +16 -0
- data/lib/rest_pki/rest_unreachable_error.rb +13 -0
- data/lib/rest_pki/restpki_client.rb +95 -0
- data/lib/rest_pki/restpki_error.rb +16 -0
- data/lib/rest_pki/signature_finisher.rb +33 -0
- data/lib/rest_pki/signature_starter.rb +59 -0
- data/lib/rest_pki/standard_security_contexts.rb +10 -0
- data/lib/rest_pki/standard_signature_policies.rb +25 -0
- data/lib/rest_pki/validation_error.rb +11 -0
- data/lib/rest_pki/validation_item.rb +33 -0
- data/lib/rest_pki/validation_results.rb +107 -0
- data/lib/rest_pki/version.rb +3 -0
- data/lib/rest_pki/xml_element_signature_starter.rb +68 -0
- data/lib/rest_pki/xml_id_resolution_table.rb +40 -0
- data/lib/rest_pki/xml_insertion_options.rb +11 -0
- data/lib/rest_pki/xml_signature_finisher.rb +52 -0
- data/lib/rest_pki/xml_signature_starter.rb +84 -0
- data/rest_pki.gemspec +30 -0
- metadata +146 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
module RestPki
|
2
|
+
class RestPkiObject
|
3
|
+
attr_reader :attributes
|
4
|
+
|
5
|
+
RESOURCES = Dir[File.expand_path('../resources/*.rb', __FILE__)].map do |path|
|
6
|
+
File.basename(path, '.rb').to_sym
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(response = {}, object)
|
10
|
+
@attributes = Hash.new
|
11
|
+
@unsaved_attributes = Set.new
|
12
|
+
update(response, object)
|
13
|
+
end
|
14
|
+
|
15
|
+
def []=(key,value)
|
16
|
+
@attributes[key] = value
|
17
|
+
@unsaved_attributes.add key
|
18
|
+
end
|
19
|
+
|
20
|
+
def unsaved_attributes
|
21
|
+
Hash[@unsaved_attributes.map do |key|
|
22
|
+
[ key, to_hash_value(self[key], :unsaved_attributes) ]
|
23
|
+
end]
|
24
|
+
end
|
25
|
+
|
26
|
+
def empty?
|
27
|
+
@attributes.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
self.class == other.class && id == other.id
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_hash
|
35
|
+
Hash[@attributes.map do |key, value|
|
36
|
+
[ key, to_hash_value(value, :to_hash) ]
|
37
|
+
end]
|
38
|
+
end
|
39
|
+
|
40
|
+
def respond_to?(name, include_all = false)
|
41
|
+
return true if name.to_s.end_with? '='
|
42
|
+
|
43
|
+
@attributes.has_key?(name.to_s) || super
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
def update(attributes, object)
|
48
|
+
removed_attributes = @attributes.keys - attributes.keys
|
49
|
+
|
50
|
+
removed_attributes.each do |key|
|
51
|
+
@attributes.delete key
|
52
|
+
end
|
53
|
+
|
54
|
+
attributes.each do |key, value|
|
55
|
+
key = key.to_s
|
56
|
+
|
57
|
+
@attributes[key] = RestPkiObject.convert(value, object)
|
58
|
+
@unsaved_attributes.delete key
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_hash_value(value, type)
|
63
|
+
case value
|
64
|
+
when RestPkiObject
|
65
|
+
value.send type
|
66
|
+
when Array
|
67
|
+
value.map do |v|
|
68
|
+
to_hash_value v, type
|
69
|
+
end
|
70
|
+
else
|
71
|
+
value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def method_missing(name, *args, &block)
|
76
|
+
name = name.to_s
|
77
|
+
|
78
|
+
unless block_given?
|
79
|
+
if name.end_with?('=') && args.size == 1
|
80
|
+
attribute_name = name[0...-1]
|
81
|
+
return self[attribute_name] = args[0]
|
82
|
+
end
|
83
|
+
|
84
|
+
if args.size == 0
|
85
|
+
return self[name]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
if attributes.respond_to? name
|
90
|
+
return attributes.public_send name, *args, &block
|
91
|
+
end
|
92
|
+
|
93
|
+
super name, *args, &block
|
94
|
+
end
|
95
|
+
|
96
|
+
class << self
|
97
|
+
|
98
|
+
def convert(response, object)
|
99
|
+
case response
|
100
|
+
when Array
|
101
|
+
response.map{ |i| convert(i, object) }
|
102
|
+
when Hash
|
103
|
+
resource_class_for(object).new(response, object) #response['object']).new(response)
|
104
|
+
else
|
105
|
+
response
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
def resource_class_for(resource_name)
|
111
|
+
return RestPki::RestPkiObject if resource_name.nil?
|
112
|
+
|
113
|
+
if RESOURCES.include? resource_name.to_sym
|
114
|
+
Object.const_get "RestPki::#{capitalize_name resource_name}"
|
115
|
+
else
|
116
|
+
RestPki::RestPkiObject
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def capitalize_name(name)
|
121
|
+
name.gsub(/(\A\w|\_\w)/){ |str| str.gsub('_', '').upcase }
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
module RestPki
|
3
|
+
class PadesSignatureFinisher < SignatureFinisher
|
4
|
+
|
5
|
+
def initialize(restpki_client)
|
6
|
+
super(restpki_client)
|
7
|
+
@signed_pdf_content = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def finish
|
11
|
+
if @token.to_s.blank?
|
12
|
+
raise 'The token was not set'
|
13
|
+
end
|
14
|
+
|
15
|
+
if @signature.to_s.blank?
|
16
|
+
response = @restpki_client.post("Api/PadesSignatures/#{@token}/Finalize", nil, 'pades_model')
|
17
|
+
else
|
18
|
+
request = { signature: @signature }
|
19
|
+
response = @restpki_client.post("Api/PadesSignatures/#{@token}/SignedBytes", request, 'pades_model')
|
20
|
+
end
|
21
|
+
|
22
|
+
@signed_pdf_content = Base64.decode64(response['signedPdf'])
|
23
|
+
@callback_argument = response['callbackArgument']
|
24
|
+
@certificate_info = response['certificate']
|
25
|
+
@done = true
|
26
|
+
|
27
|
+
@signed_pdf_content
|
28
|
+
end
|
29
|
+
|
30
|
+
def signed_pdf_content
|
31
|
+
unless @done
|
32
|
+
raise 'The field "signed_pdf_content" can only be accessed after calling the finish method'
|
33
|
+
end
|
34
|
+
|
35
|
+
@signed_pdf_content
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_signed_pdf(pdf_path)
|
39
|
+
unless @done
|
40
|
+
raise 'The method write_signed_pdf can only be called after calling the finish method'
|
41
|
+
end
|
42
|
+
|
43
|
+
file = File.open(pdf_path, 'wb')
|
44
|
+
file.write(@signed_pdf_content)
|
45
|
+
file.close
|
46
|
+
|
47
|
+
nil # No value is returned.
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module RestPki
|
4
|
+
class PadesSignatureStarter < SignatureStarter
|
5
|
+
attr_accessor :visual_representation
|
6
|
+
|
7
|
+
def initialize(restpki_client)
|
8
|
+
super(restpki_client)
|
9
|
+
@pdf_content_base64 = nil
|
10
|
+
@visual_representation = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
#region set_pdf_tosign
|
14
|
+
|
15
|
+
def set_pdf_tosign_from_path(pdf_path)
|
16
|
+
file = File.open(pdf_path, 'rb')
|
17
|
+
@pdf_content_base64 = Base64.encode64(file.read)
|
18
|
+
file.close
|
19
|
+
|
20
|
+
@pdf_content_base64
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_pdf_tosign_from_raw(content_raw)
|
24
|
+
@pdf_content_base64 = Base64.encode64(content_raw)
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_pdf_tosign_from_base64(content_base64)
|
28
|
+
@pdf_content_base64 = content_base64
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_pdf_content_tosign(content_raw)
|
32
|
+
set_pdf_tosign_from_raw(content_raw)
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_pdf_file_tosign(pdf_path)
|
36
|
+
set_pdf_tosign_from_path(pdf_path)
|
37
|
+
end
|
38
|
+
|
39
|
+
#endregion
|
40
|
+
|
41
|
+
def start_with_webpki
|
42
|
+
if @pdf_content_base64.to_s.blank?
|
43
|
+
raise 'The PDF to sign was not set'
|
44
|
+
end
|
45
|
+
if @signature_policy_id.to_s.blank?
|
46
|
+
raise 'The signature policy was not set'
|
47
|
+
end
|
48
|
+
|
49
|
+
request = {
|
50
|
+
securityContextId: @security_context_id,
|
51
|
+
pdfToSign: @pdf_content_base64,
|
52
|
+
signaturePolicyId: @signature_policy_id,
|
53
|
+
callbackArgument: @callback_argument,
|
54
|
+
visualRepresentation: @visual_representation,
|
55
|
+
ignoreRevocationStatusUnknown: @ignore_revocation_status_unknown
|
56
|
+
}
|
57
|
+
unless @signer_certificate_base64.to_s.blank?
|
58
|
+
request['certificate'] = Base64.encode64(@signer_certificate_base64)
|
59
|
+
end
|
60
|
+
|
61
|
+
response = @restpki_client.post('Api/PadesSignatures', request, 'pades_model')
|
62
|
+
|
63
|
+
unless response['certificate'].nil?
|
64
|
+
@certificate = response['certificate']
|
65
|
+
end
|
66
|
+
@done = true
|
67
|
+
|
68
|
+
response['token']
|
69
|
+
end
|
70
|
+
|
71
|
+
def start
|
72
|
+
if @pdf_content_base64.to_s.blank?
|
73
|
+
raise 'The PDF to sign was not set'
|
74
|
+
end
|
75
|
+
if @signature_policy_id.to_s.blank?
|
76
|
+
raise 'The signature policy was not set'
|
77
|
+
end
|
78
|
+
if @signer_certificate_base64.to_s.blank?
|
79
|
+
raise 'The signer certificate was not set'
|
80
|
+
end
|
81
|
+
|
82
|
+
request = {
|
83
|
+
securityContextId: @security_context_id,
|
84
|
+
pdfToSign: @pdf_content_base64,
|
85
|
+
signaturePolicyId: @signature_policy_id,
|
86
|
+
callbackArgument: @callback_argument,
|
87
|
+
visualRepresentation: @visual_representation,
|
88
|
+
ignoreRevocationStatusUnknown: @ignore_revocation_status_unknown
|
89
|
+
}
|
90
|
+
unless @signer_certificate_base64.to_s.blank?
|
91
|
+
request['certificate'] = Base64.encode64(@signer_certificate_base64)
|
92
|
+
end
|
93
|
+
|
94
|
+
response = @restpki_client.post('Api/PadesSignatures', request, 'pades_model')
|
95
|
+
|
96
|
+
unless response['certificate'].nil?
|
97
|
+
@certificate = response['certificate']
|
98
|
+
end
|
99
|
+
@done = true
|
100
|
+
|
101
|
+
{
|
102
|
+
:token => response['token'],
|
103
|
+
:to_sign_data => response['toSignData'],
|
104
|
+
:to_sign_hash => response['toSignHash'],
|
105
|
+
:digest_algorithm_oid => response['digestAlgorithmOid'],
|
106
|
+
:signature_algorithm => get_signature_algorithm(response['digestAlgorithmOid'])
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
module RestPki
|
3
|
+
class PadesVisualPositioningPresets
|
4
|
+
@@cached_presets = Hash.new
|
5
|
+
|
6
|
+
def self.get_footnote(restpki_client, page_number=nil, rows=nil)
|
7
|
+
url_segment = 'Footnote'
|
8
|
+
unless page_number.to_s.blank?
|
9
|
+
url_segment += '?pageNumber=' + page_number
|
10
|
+
end
|
11
|
+
unless rows.to_s.blank?
|
12
|
+
url_segment += '?rows=' + rows
|
13
|
+
end
|
14
|
+
get_preset(restpki_client, url_segment)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.get_new_page(restpki_client)
|
18
|
+
get_preset(restpki_client, 'NewPage')
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
private
|
23
|
+
def self.get_preset(restpki_client, url_segment)
|
24
|
+
if @@cached_presets.to_hash.has_key? url_segment
|
25
|
+
return @@cached_presets[url_segment]
|
26
|
+
end
|
27
|
+
preset = restpki_client.get("Api/PadesVisualPositioningPresets/#{url_segment}", 'pades_model')
|
28
|
+
@@cached_presets[url_segment] = preset
|
29
|
+
preset
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
module RestPki
|
3
|
+
class RestError < RestBaseError
|
4
|
+
attr_reader :status_code, :error_message
|
5
|
+
|
6
|
+
def initialize(verb, url, status_code, error_message=nil)
|
7
|
+
message = "REST action #{verb} #{url} returned HTTP error #{status_code}"
|
8
|
+
unless error_message.to_s.blank?
|
9
|
+
message += ": #{error_message}"
|
10
|
+
end
|
11
|
+
super(message, verb, url)
|
12
|
+
@status_code = status_code
|
13
|
+
@error_message = error_message
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
module RestPki
|
3
|
+
class RestUnreachableError < RestBaseError
|
4
|
+
|
5
|
+
def initialize(verb, url, error_message=nil)
|
6
|
+
message = "REST action #{verb} #{url} unreachable"
|
7
|
+
unless error_message.to_s.blank?
|
8
|
+
message += ": #{error_message}"
|
9
|
+
end
|
10
|
+
super(message, verb, url)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'rest_client'
|
3
|
+
require 'multi_json'
|
4
|
+
|
5
|
+
module RestPki
|
6
|
+
class RestPkiClient
|
7
|
+
|
8
|
+
def initialize(endpoint_url, access_token)
|
9
|
+
@endpoint_url = endpoint_url
|
10
|
+
@access_token = access_token
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(url, object_model)
|
14
|
+
verb = 'GET'
|
15
|
+
params = get_rest_params(verb, url)
|
16
|
+
|
17
|
+
begin
|
18
|
+
response = RestClient::Request.execute params
|
19
|
+
rescue RestClient::Exception => ex
|
20
|
+
response = RestPkiObject.convert({
|
21
|
+
:code => ex.http_code,
|
22
|
+
:body => ex.response
|
23
|
+
}, 'response_model')
|
24
|
+
rescue Exception => ex
|
25
|
+
raise RestUnreachableError.new(verb, url, ex.message)
|
26
|
+
end
|
27
|
+
check_response(verb, url, response)
|
28
|
+
|
29
|
+
RestPkiObject.convert(MultiJson.decode(response.body), object_model)
|
30
|
+
end
|
31
|
+
|
32
|
+
def post(url, data, object_model)
|
33
|
+
verb = 'POST'
|
34
|
+
params = get_rest_params(verb, url, data)
|
35
|
+
|
36
|
+
begin
|
37
|
+
response = RestClient::Request.execute params
|
38
|
+
rescue RestClient::Exception => ex
|
39
|
+
response = RestPkiObject.convert({
|
40
|
+
:code => ex.http_code,
|
41
|
+
:body => ex.response
|
42
|
+
}, 'response_model')
|
43
|
+
rescue Exception => ex
|
44
|
+
raise RestUnreachableError.new(verb, url, ex.message)
|
45
|
+
end
|
46
|
+
check_response(verb, url, response)
|
47
|
+
|
48
|
+
RestPkiObject.convert(MultiJson.decode(response.body), object_model)
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_authentication
|
52
|
+
Authentication.new(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def get_rest_params(method, url, params=nil)
|
57
|
+
{
|
58
|
+
method: method,
|
59
|
+
url: @endpoint_url + url,
|
60
|
+
payload: params ? MultiJson.encode(params) : nil,
|
61
|
+
open_timeout: 30,
|
62
|
+
timeout: 90,
|
63
|
+
headers: {
|
64
|
+
'Content-Type': 'application/json; charset=utf8',
|
65
|
+
Accept: 'application/json',
|
66
|
+
Authorization: "Bearer #{@access_token}",
|
67
|
+
'X-RestPki-Client': "Ruby #{RestPki::VERSION}"
|
68
|
+
}
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def check_response(verb, url, http_response)
|
73
|
+
status_code = http_response.code
|
74
|
+
if status_code < 200 || status_code > 299
|
75
|
+
ex = nil
|
76
|
+
begin
|
77
|
+
response = MultiJson.decode http_response.body
|
78
|
+
if status_code == 422 && response['code'].to_s.blank?
|
79
|
+
if response['code'] == 'ValidationError'
|
80
|
+
vr = ValidationResults.new(response['validationResults'])
|
81
|
+
ex = ValidationError.new(verb, url, vr)
|
82
|
+
else
|
83
|
+
ex = RestPkiError.new(verb, url, response['code'], response['detail'])
|
84
|
+
end
|
85
|
+
else
|
86
|
+
ex = RestError.new(verb, url, status_code, response['message'])
|
87
|
+
end
|
88
|
+
rescue => e
|
89
|
+
ex = RestError.new(verb, url, status_code)
|
90
|
+
end
|
91
|
+
raise ex
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|