ruby-jsonld-signatures 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/AUTHORS +2 -0
- data/LICENSE +21 -0
- data/README.md +199 -0
- data/VERSION +1 -0
- data/lib/json/ld/signature.rb +50 -0
- data/lib/json/ld/signature/ed25519Signer.rb +98 -0
- data/lib/json/ld/signature/ed25519Verifier.rb +68 -0
- data/lib/json/ld/signature/rsaSigner.rb +100 -0
- data/lib/json/ld/signature/rsaVerifier.rb +83 -0
- metadata +186 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4935f61cd06bb5e3d67e927527fb9f2b0965c28a61a94c32313dcfa7c39b7a9d
|
4
|
+
data.tar.gz: 603d4cd7d729f4f9d5a903f613cb0b4a75c32a0b726cd7d8dc4e0f4871a857f8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6c04d6231e6c1b0eb9257160f7fed3c26f64e2913a3aec70589d5fcceeae3538fd79ac2d2030025a31094f0442350f5c6afbfeecd6f1caf0ecbdd52db3404a05
|
7
|
+
data.tar.gz: 39c321c7477204ec0301f74a0f3e67625e25da1db90eaabcd056f7192da57660683cb69a5ead0a75933a4a08a22ca7010f1ebeb59f75439afed3b8d6ca1c3f65
|
data/AUTHORS
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Accreditrust Technologies, LLC.
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
This gem is an implementation of the JSON-LD Signatures specification
|
2
|
+
in Ruby that supports the following encryption options:
|
3
|
+
|
4
|
+
* RSA
|
5
|
+
* Ed25519
|
6
|
+
|
7
|
+
Demo
|
8
|
+
----
|
9
|
+
|
10
|
+
See [an example](https://ldsigdemo.herokuapp.com/) of the gem in action. The source code for the demo is [here](https://github.com/johncallahan/ldsigdemo).
|
11
|
+
|
12
|
+
Getting Started
|
13
|
+
---------------
|
14
|
+
|
15
|
+
Add the gem to your Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'ruby-jsonld-signatures'
|
19
|
+
```
|
20
|
+
|
21
|
+
then run `bundle install`
|
22
|
+
|
23
|
+
Development
|
24
|
+
-----------
|
25
|
+
|
26
|
+
Clone this repo, bundle and run the rspec tests:
|
27
|
+
|
28
|
+
```shell
|
29
|
+
git clone https://github.com/johncallahan/ruby-jsonld-signatures.git
|
30
|
+
bundle install
|
31
|
+
rspec
|
32
|
+
```
|
33
|
+
|
34
|
+
Description
|
35
|
+
-----------
|
36
|
+
|
37
|
+
Consider the following JSON-LD document:
|
38
|
+
|
39
|
+
```json
|
40
|
+
{
|
41
|
+
"@context": [ "https://w3id.org/credentials/v1","https://w3id.org/security/v1"],
|
42
|
+
"type" : [ "Credential" ],
|
43
|
+
"claim" : {
|
44
|
+
"id" : "did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk",
|
45
|
+
"publicKey" : "did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk#authn-key-1"
|
46
|
+
},
|
47
|
+
"issuer" : "did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk",
|
48
|
+
"issued" : "2018-03-15T00:00:00Z"
|
49
|
+
}
|
50
|
+
```
|
51
|
+
|
52
|
+
The goal of [Linked Data Signatures](https://w3c-dvcg.github.io/ld-signatures/) is to
|
53
|
+
cryptographically "sign" a JSON-LD document such that the the order of
|
54
|
+
key/pairs within the JSON-LD document does not matter. In other words,
|
55
|
+
the signature value of the JSON content above would be:
|
56
|
+
|
57
|
+
```
|
58
|
+
t/T2Wv335B2guVYW88I9uWKEdrE3HFddrXt14AVo9aD9yr5BAbGJT5eQbVGdG+O0Hn6RU9IYgi1o15/F3x37Ag==
|
59
|
+
```
|
60
|
+
|
61
|
+
The following document is equivalent to the JSON-LD document above even
|
62
|
+
though more whitespace is added and the key/value pairs (even in
|
63
|
+
embedded blocks) are in different order but their values are equal:
|
64
|
+
|
65
|
+
```json
|
66
|
+
{
|
67
|
+
"issued" : "2018-03-15T00:00:00Z",
|
68
|
+
|
69
|
+
"issuer" : "did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk",
|
70
|
+
"claim" : {
|
71
|
+
"publicKey" : "did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk#authn-key-1",
|
72
|
+
"id" : "did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk"
|
73
|
+
},
|
74
|
+
|
75
|
+
"type" : [ "Credential" ],
|
76
|
+
"@context": [ "https://w3id.org/credentials/v1","https://w3id.org/security/v1"]
|
77
|
+
}
|
78
|
+
|
79
|
+
```
|
80
|
+
|
81
|
+
After generating the signature value for the JSON-LD document, the
|
82
|
+
signature value is appended to the document with additional metadata:
|
83
|
+
|
84
|
+
```json
|
85
|
+
{
|
86
|
+
"@context":["https://w3id.org/credentials/v1","https://w3id.org/security/v1"],
|
87
|
+
"type":["Credential"],
|
88
|
+
"claim":{
|
89
|
+
"id":"did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk",
|
90
|
+
"publicKey":"did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk#authn-key-1"
|
91
|
+
},
|
92
|
+
"issuer":"did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk",
|
93
|
+
"issued":"2018-03-15T00:00:00Z",
|
94
|
+
"signature":{
|
95
|
+
"type":"Ed25519Signature2018",
|
96
|
+
"creator":"did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk#authn-key-1",
|
97
|
+
"created":"2018-03-15T00:00:00Z",
|
98
|
+
"signatureValue":"t/T2Wv335B2guVYW88I9uWKEdrE3HFddrXt14AVo9aD9yr5BAbGJT5eQbVGdG+O0Hn6RU9IYgi1o15/F3x37Ag=="
|
99
|
+
}
|
100
|
+
}
|
101
|
+
```
|
102
|
+
|
103
|
+
This signed content can be presented to other parties such that any
|
104
|
+
key/value pair change to the JSON content (not the order or
|
105
|
+
whitespace) can be detected. It is useful in [DID Auth](https://github.com/WebOfTrustInfo/rebooting-the-web-of-trust-spring2018/blob/master/final-documents/did-auth.md) where a
|
106
|
+
user (via their browser or mobile device) holds a private key and
|
107
|
+
needs to provide [verifiable credentials](https://github.com/WebOfTrustInfo/rwot7/blob/master/topics-and-advance-readings/verifiable-credentials-primer.md) to a replying party or
|
108
|
+
service provider. In the case of DID Auth, the relying party can
|
109
|
+
verify the signature by resolving the DID (via a [universal
|
110
|
+
resolver](https://github.com/decentralized-identity/universal-resolver)) to obtain the public key from a blockchain ([Veres
|
111
|
+
One](https://github.com/veres-one/veres-one) in this case).
|
112
|
+
|
113
|
+
The process of signing a JSON-LD document includes:
|
114
|
+
|
115
|
+
* resolving the context vocabularies (i.e., fetching them via their URLs in the @context]
|
116
|
+
* normalizing (or sometimes called 'canonicalizing') the document
|
117
|
+
* determining the signature value with a private key (using RSA or Ed25519)
|
118
|
+
* embedding the signature JSON with the metadata and signature value (not part of the JSON-LD document)
|
119
|
+
|
120
|
+
Verifying a signed JSON-LD document includes:
|
121
|
+
|
122
|
+
* extracting the signature block from the JSON-LD document (remove it as well)
|
123
|
+
* normalizing (or sometimes called 'canonicalizing') the remaining JSON-LD document
|
124
|
+
* verifying the signature value with the public key (using RSA or Ed25519)
|
125
|
+
|
126
|
+
The ruby-jsonld-signatures gem relies on other gems to perform signing
|
127
|
+
and verifying including:
|
128
|
+
|
129
|
+
* [json-ld](https://github.com/ruby-rdf/json-ld)
|
130
|
+
* [rdf-normalize](https://github.com/ruby-rdf/rdf-normalize)
|
131
|
+
* [ed25519](https://github.com/crypto-rb/ed25519)
|
132
|
+
|
133
|
+
NOTE: additional keys that are NOT in the context vocabularies will
|
134
|
+
NOT be part of the normalization process. Thus, the following JSON-LD
|
135
|
+
document is equalivalent to the blocks shown above:
|
136
|
+
|
137
|
+
```json
|
138
|
+
{
|
139
|
+
"@context": [ "https://w3id.org/credentials/v1","https://w3id.org/security/v1"],
|
140
|
+
"type" : [ "Credential" ],
|
141
|
+
"claim" : {
|
142
|
+
"id" : "did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk",
|
143
|
+
"publicKey" : "did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk#authn-key-1"
|
144
|
+
},
|
145
|
+
"foo" : "bar",
|
146
|
+
"issuer" : "did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk",
|
147
|
+
"issued" : "2018-03-15T00:00:00Z"
|
148
|
+
}
|
149
|
+
```
|
150
|
+
|
151
|
+
The key "foo" is not found in either the credentials or security
|
152
|
+
vocabularies (in the @context) and therefore *not* included in the
|
153
|
+
normalized content. But the following document is not equivalent (the
|
154
|
+
key "nonce" *is* part of both the credentials and security
|
155
|
+
vocabularies - but it just has to be in one of them):
|
156
|
+
|
157
|
+
```json
|
158
|
+
{
|
159
|
+
"@context": [ "https://w3id.org/credentials/v1","https://w3id.org/security/v1"],
|
160
|
+
"type" : [ "Credential" ],
|
161
|
+
"claim" : {
|
162
|
+
"id" : "did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk",
|
163
|
+
"publicKey" : "did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk#authn-key-1"
|
164
|
+
},
|
165
|
+
"nonce" : "thisisjustarandomstring",
|
166
|
+
"issuer" : "did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk",
|
167
|
+
"issued" : "2018-03-15T00:00:00Z"
|
168
|
+
}
|
169
|
+
```
|
170
|
+
|
171
|
+
By the way, here is the normalized (or "canonicalized") content for
|
172
|
+
all blocks above except the one with the "nonce" key-value pair added:
|
173
|
+
|
174
|
+
```json
|
175
|
+
<did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk> <https://w3id.org/security#publicKey> <did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk#authn-key-1> .
|
176
|
+
_:c14n0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/credentials#Credential> .
|
177
|
+
_:c14n0 <https://w3id.org/credentials#claim> <did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk> .
|
178
|
+
_:c14n0 <https://w3id.org/credentials#issued> "2018-03-15T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> .
|
179
|
+
_:c14n0 <https://w3id.org/credentials#issuer> <did:v1:test:nym:JApJf12r82Pe6PBJ3gJAAwo8F7uDnae6B4ab9EFQ7XXk> .
|
180
|
+
```
|
181
|
+
|
182
|
+
Tests
|
183
|
+
-----
|
184
|
+
|
185
|
+
* Sign a basic string
|
186
|
+
* Sign a basic normalized (i.e., "canonicalized") document
|
187
|
+
* Sign a basic JSON-LD document
|
188
|
+
* Signatures of a second document that contains a non-vocabulary element are equivalent
|
189
|
+
* Signatures of a second document that contains a vocabulary element are NOT equivalent
|
190
|
+
* Signatures of a second document in different order are equivalent
|
191
|
+
* Verify signature of a signed JSON-LD document
|
192
|
+
* Detect when signature of a signed JSON-LD document is invalid
|
193
|
+
|
194
|
+
Todo
|
195
|
+
----
|
196
|
+
|
197
|
+
* The gem currently uses the [ed25519](https://github.com/crypto-rb/ed25519) gem, but I have tested the
|
198
|
+
[rbnacl](https://github.com/crypto-rb/rbnacl) gem and it works. I just need to provide an option hook.
|
199
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.10
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module JSON
|
2
|
+
module LD
|
3
|
+
module SIGNATURE
|
4
|
+
|
5
|
+
require 'base64'
|
6
|
+
require 'json/ld'
|
7
|
+
require 'rdf/normalize'
|
8
|
+
require 'json/ld/signature'
|
9
|
+
require 'json/ld/signature/ed25519Signer'
|
10
|
+
require 'json/ld/signature/ed25519Verifier'
|
11
|
+
require 'json/ld/signature/rsaSigner'
|
12
|
+
require 'json/ld/signature/rsaVerifier'
|
13
|
+
|
14
|
+
autoload :Ed25519Singer, 'json/ld/signature/ed25519Singer'
|
15
|
+
autoload :Ed25519Verifier, 'json/ld/signature/ed25519Verifier'
|
16
|
+
autoload :RsaSinger, 'json/ld/signature/rsaSinger'
|
17
|
+
autoload :RsaVerifier, 'json/ld/signature/rsaVerifier'
|
18
|
+
|
19
|
+
def generateNormalizedGraph(jsonLDDoc, opts)
|
20
|
+
jsonLDDoc.delete 'signature'
|
21
|
+
|
22
|
+
graph = RDF::Graph.new << JSON::LD::API.toRdf(jsonLDDoc)
|
23
|
+
# TODO: Parameterize the normalization
|
24
|
+
normalized = graph.dump(:normalize)
|
25
|
+
|
26
|
+
# digestdoc = ''
|
27
|
+
# digestdoc << opts['nonce'] unless opts['nonce'].nil?
|
28
|
+
# digestdoc << opts['created']
|
29
|
+
# digestdoc << normalized
|
30
|
+
# digestdoc << '@' + opts['domain'] unless opts['domain'].nil?
|
31
|
+
# digestdoc
|
32
|
+
|
33
|
+
normalized
|
34
|
+
end
|
35
|
+
|
36
|
+
module_function :generateNormalizedGraph
|
37
|
+
|
38
|
+
SECURITY_CONTEXT_URL = 'https://w3id.org/security/v1'
|
39
|
+
|
40
|
+
class JsonLdSignatureError < JsonLdError
|
41
|
+
class InvalidJsonLdDocument < JsonLdSignatureError; @code = "invalid JSON-LD document"; end
|
42
|
+
class MissingCreator < JsonLdSignatureError; @code = "missing signature creator"; end
|
43
|
+
class MissingKey < JsonLdSignatureError; @code = "missing private PEM formatted string"; end
|
44
|
+
class InvalidKeyType < JsonLdSignatureError; @code = "invalid PEM key"; end
|
45
|
+
class WrongKeyType < JsonLdSignatureError; @code = "signing requires a private key"; end
|
46
|
+
class UnreachableKey < JsonLdSignatureError; @code = "unable to retrieve public key"; end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module JSON
|
2
|
+
module LD
|
3
|
+
module SIGNATURE
|
4
|
+
|
5
|
+
class Ed25519Signer
|
6
|
+
|
7
|
+
attr_writer :pub
|
8
|
+
attr_writer :priv
|
9
|
+
|
10
|
+
def pub
|
11
|
+
@pub
|
12
|
+
end
|
13
|
+
|
14
|
+
def priv
|
15
|
+
@priv
|
16
|
+
end
|
17
|
+
|
18
|
+
def sign(input, options = {} )
|
19
|
+
|
20
|
+
# We require a creator to identify the signing key
|
21
|
+
|
22
|
+
if options['creator'].nil?
|
23
|
+
raise JsonLdSignatureError::MissingCreator, "the creator of the signature must be identified"
|
24
|
+
end
|
25
|
+
|
26
|
+
creator = options['creator']
|
27
|
+
|
28
|
+
# TODO: Validate the resolvability of the URL?
|
29
|
+
|
30
|
+
# We require a privateKeyPem in the options hash
|
31
|
+
# if options['privateKey'].nil?
|
32
|
+
# raise JsonLdSignatureError::MissingKey, "options parameter must include privateKey"
|
33
|
+
# end
|
34
|
+
|
35
|
+
# The privateKeyPem can be either a String or a parsed RSA key
|
36
|
+
# privateKey = options['privateKey']
|
37
|
+
privateKey = priv
|
38
|
+
|
39
|
+
# unless privateKey.private?
|
40
|
+
# raise JsonLdSignatureError::WrongKeyType, "submitted key is a public key"
|
41
|
+
# end
|
42
|
+
|
43
|
+
# Check the input, it should either be a String or a parsed JSON object
|
44
|
+
|
45
|
+
jsonld = case input
|
46
|
+
when String then
|
47
|
+
begin
|
48
|
+
JSON.parse(input)
|
49
|
+
rescue JSON::ParserError => e
|
50
|
+
raise JsonLdSignatureError::InvalidJsonLdDocument, e.message
|
51
|
+
end
|
52
|
+
when Hash then input
|
53
|
+
else
|
54
|
+
raise JsonLdSignatureError::InvalidJsonLdDocument
|
55
|
+
end
|
56
|
+
|
57
|
+
jsonld.delete 'signature'
|
58
|
+
# created = Time.now.iso8601
|
59
|
+
created = "2018-03-15T00:00:00Z"
|
60
|
+
# nonce = options['nonce']
|
61
|
+
# nonce = "3699b48f-a194-4415-8da3-b76269f63746"
|
62
|
+
nonce = nil
|
63
|
+
# domain = options['domain']
|
64
|
+
domain = nil
|
65
|
+
|
66
|
+
normOpts = {
|
67
|
+
'nonce' => nonce,
|
68
|
+
'domain' => options['domain'],
|
69
|
+
'created' => created,
|
70
|
+
'creator' => creator
|
71
|
+
}
|
72
|
+
|
73
|
+
normalizedGraph = JSON::LD::SIGNATURE::generateNormalizedGraph jsonld, normOpts
|
74
|
+
# puts normalizedGraph
|
75
|
+
signature = privateKey.sign normalizedGraph
|
76
|
+
|
77
|
+
enc = Base64.strict_encode64(signature)
|
78
|
+
|
79
|
+
# "@context" : "https://w3id.org/security/v1",
|
80
|
+
|
81
|
+
sigobj = JSON.parse %({
|
82
|
+
"type" : "Ed25519Signature2018",
|
83
|
+
"creator" : "#{creator}",
|
84
|
+
"created" : "#{created}",
|
85
|
+
"signatureValue" : "#{enc}"
|
86
|
+
})
|
87
|
+
|
88
|
+
sigobj['domain'] = domain unless options['domain'].nil?
|
89
|
+
sigobj['nonce'] = nonce unless nonce.nil?
|
90
|
+
|
91
|
+
jsonld['signature'] = sigobj
|
92
|
+
jsonld.to_json
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module JSON
|
2
|
+
module LD
|
3
|
+
module SIGNATURE
|
4
|
+
|
5
|
+
class Ed25519Verifier
|
6
|
+
|
7
|
+
attr_writer :pub
|
8
|
+
attr_writer :priv
|
9
|
+
|
10
|
+
def pub
|
11
|
+
@pub
|
12
|
+
end
|
13
|
+
|
14
|
+
def priv
|
15
|
+
@priv
|
16
|
+
end
|
17
|
+
|
18
|
+
def verify(input, options = {})
|
19
|
+
|
20
|
+
# We require a publicKeyPem in the options hash
|
21
|
+
# if options['publicKey'].nil?
|
22
|
+
# raise JsonLdSignatureError::MissingKey, "options parameter must include publicKey"
|
23
|
+
# end
|
24
|
+
|
25
|
+
# The publicKeyPem can be either a String or a parsed RSA key
|
26
|
+
# publicKey = options['publicKey']
|
27
|
+
publicKey = pub
|
28
|
+
|
29
|
+
# Check the input, it should either be a String or a parsed JSON object
|
30
|
+
|
31
|
+
jsonld = case input
|
32
|
+
when String then
|
33
|
+
begin
|
34
|
+
JSON.parse(input)
|
35
|
+
rescue JSON::ParserError => e
|
36
|
+
raise JsonLdSignatureError::InvalidJsonLdDocument, e.message
|
37
|
+
end
|
38
|
+
when Hash then input
|
39
|
+
else
|
40
|
+
raise JsonLdSignatureError::InvalidJsonLdDocument
|
41
|
+
end
|
42
|
+
|
43
|
+
signature = jsonld['signature']
|
44
|
+
|
45
|
+
created = signature['created']
|
46
|
+
creator = signature['creator']
|
47
|
+
signatureValue = signature['signatureValue']
|
48
|
+
domain = signature['domain']
|
49
|
+
nonce = signature['nonce']
|
50
|
+
|
51
|
+
uri = URI(creator)
|
52
|
+
|
53
|
+
normOpts = {
|
54
|
+
'nonce' => nonce,
|
55
|
+
'domain' => domain,
|
56
|
+
'created' => created,
|
57
|
+
'creator' => creator
|
58
|
+
}
|
59
|
+
|
60
|
+
normalizedGraph = JSON::LD::SIGNATURE::generateNormalizedGraph jsonld, normOpts
|
61
|
+
|
62
|
+
publicKey.verify Base64.decode64(signatureValue), normalizedGraph
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module JSON
|
2
|
+
module LD
|
3
|
+
module SIGNATURE
|
4
|
+
|
5
|
+
class RsaSigner
|
6
|
+
|
7
|
+
attr_writer :pub
|
8
|
+
attr_writer :priv
|
9
|
+
|
10
|
+
def pub
|
11
|
+
@pub
|
12
|
+
end
|
13
|
+
|
14
|
+
def priv
|
15
|
+
@priv
|
16
|
+
end
|
17
|
+
|
18
|
+
def sign(input, options = {} )
|
19
|
+
|
20
|
+
# We require a creator to identify the signing key
|
21
|
+
|
22
|
+
if options['creator'].nil?
|
23
|
+
raise JsonLdSignatureError::MissingCreator, "the creator of the signature must be identified"
|
24
|
+
end
|
25
|
+
|
26
|
+
creator = options['creator']
|
27
|
+
|
28
|
+
# TODO: Validate the resolvability of the URL?
|
29
|
+
|
30
|
+
# We require a privateKeyPem in the options hash
|
31
|
+
# if options['privateKeyPem'].nil?
|
32
|
+
# raise JsonLdSignatureError::MissingKey, "options parameter must include privateKeyPem"
|
33
|
+
# end
|
34
|
+
|
35
|
+
# The privateKeyPem can be either a String or a parsed RSA key
|
36
|
+
# privateKey = case options['privateKeyPem']
|
37
|
+
# when String then OpenSSL::PKey::RSA.new options['privateKeyPem']
|
38
|
+
# when OpenSSL::PKey::RSA then options['privateKeyPem']
|
39
|
+
# else
|
40
|
+
# raise JsonLdSignatureError::InvalidKeyType, "key must be RSA Key or PEM String"
|
41
|
+
# end
|
42
|
+
privateKey = @priv
|
43
|
+
|
44
|
+
unless privateKey.private?
|
45
|
+
raise JsonLdSignatureError::WrongKeyType, "submitted key is a public key"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Check the input, it should either be a String or a parsed JSON object
|
49
|
+
|
50
|
+
jsonld = case input
|
51
|
+
when String then
|
52
|
+
begin
|
53
|
+
JSON.parse(input)
|
54
|
+
rescue JSON::ParserError => e
|
55
|
+
raise JsonLdSignatureError::InvalidJsonLdDocument, e.message
|
56
|
+
end
|
57
|
+
when Hash then input
|
58
|
+
else
|
59
|
+
raise JsonLdSignatureError::InvalidJsonLdDocument
|
60
|
+
end
|
61
|
+
|
62
|
+
jsonld.delete 'signature'
|
63
|
+
# created = Time.now.iso8601
|
64
|
+
created = "2018-03-15T00:00:00Z"
|
65
|
+
nonce = options['nonce']
|
66
|
+
domain = options['domain']
|
67
|
+
|
68
|
+
normOpts = {
|
69
|
+
'nonce' => nonce,
|
70
|
+
'domain' => options['domain'],
|
71
|
+
'created' => created,
|
72
|
+
'creator' => creator
|
73
|
+
}
|
74
|
+
|
75
|
+
normalizedGraph = JSON::LD::SIGNATURE::generateNormalizedGraph jsonld, normOpts
|
76
|
+
|
77
|
+
digest = OpenSSL::Digest::SHA256.new
|
78
|
+
signature = privateKey.sign digest, normalizedGraph
|
79
|
+
enc = Base64.strict_encode64(signature)
|
80
|
+
|
81
|
+
# "@context" : "https://w3id.org/security/v1",
|
82
|
+
|
83
|
+
sigobj = JSON.parse %({
|
84
|
+
"type" : "RsaSignature2017",
|
85
|
+
"creator" : "#{creator}",
|
86
|
+
"created" : "#{created}",
|
87
|
+
"signatureValue" : "#{enc}"
|
88
|
+
})
|
89
|
+
|
90
|
+
sigobj['domain'] = domain unless options['domain'].nil?
|
91
|
+
sigobj['nonce'] = nonce unless nonce.nil?
|
92
|
+
|
93
|
+
jsonld['signature'] = sigobj
|
94
|
+
jsonld.to_json
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module JSON
|
2
|
+
module LD
|
3
|
+
module SIGNATURE
|
4
|
+
|
5
|
+
class RsaVerifier
|
6
|
+
|
7
|
+
attr_writer :pub
|
8
|
+
attr_writer :priv
|
9
|
+
|
10
|
+
def pub
|
11
|
+
@pub
|
12
|
+
end
|
13
|
+
|
14
|
+
def priv
|
15
|
+
@priv
|
16
|
+
end
|
17
|
+
|
18
|
+
def verify(input, options = {})
|
19
|
+
|
20
|
+
# We require a publicKeyPem in the options hash
|
21
|
+
# if options['publicKeyPem'].nil?
|
22
|
+
# raise JsonLdSignatureError::MissingKey, "options parameter must include publicKeyPem"
|
23
|
+
# end
|
24
|
+
|
25
|
+
# The publicKeyPem can be either a String or a parsed RSA key
|
26
|
+
# publicKey = case options['publicKeyPem']
|
27
|
+
# when String then OpenSSL::PKey::RSA.new options['publicKeyPem']
|
28
|
+
# when OpenSSL::PKey::RSA then options['publicKeyPem']
|
29
|
+
# else
|
30
|
+
# raise JsonLdSignatureError::InvalidKeyType, "key must be RSA Key or PEM String"
|
31
|
+
# end
|
32
|
+
publicKey = @pub
|
33
|
+
|
34
|
+
# Check the input, it should either be a String or a parsed JSON object
|
35
|
+
|
36
|
+
jsonld = case input
|
37
|
+
when String then
|
38
|
+
begin
|
39
|
+
JSON.parse(input)
|
40
|
+
rescue JSON::ParserError => e
|
41
|
+
raise JsonLdSignatureError::InvalidJsonLdDocument, e.message
|
42
|
+
end
|
43
|
+
when Hash then input
|
44
|
+
else
|
45
|
+
raise JsonLdSignatureError::InvalidJsonLdDocument
|
46
|
+
end
|
47
|
+
|
48
|
+
signature = jsonld['signature']
|
49
|
+
|
50
|
+
created = signature['created']
|
51
|
+
creator = signature['creator']
|
52
|
+
signatureValue = signature['signatureValue']
|
53
|
+
domain = signature['domain']
|
54
|
+
nonce = signature['nonce']
|
55
|
+
|
56
|
+
uri = URI(creator)
|
57
|
+
# response = Net::HTTP.get_response(uri)
|
58
|
+
|
59
|
+
# case response.code
|
60
|
+
# when "200"
|
61
|
+
# publicKey = OpenSSL::PKey::RSA.new response.body
|
62
|
+
# else
|
63
|
+
# raise JsonLdSignatureError::UnreachableKey,
|
64
|
+
# "Key #{creator} could not be retrieved. Error: #{response.code}, #{response.message}"
|
65
|
+
# end
|
66
|
+
|
67
|
+
normOpts = {
|
68
|
+
'nonce' => nonce,
|
69
|
+
'domain' => domain,
|
70
|
+
'created' => created,
|
71
|
+
'creator' => creator
|
72
|
+
}
|
73
|
+
|
74
|
+
normalizedGraph = JSON::LD::SIGNATURE::generateNormalizedGraph jsonld, normOpts
|
75
|
+
|
76
|
+
digest = OpenSSL::Digest::SHA256.new
|
77
|
+
publicKey.verify digest, Base64.decode64(signatureValue), normalizedGraph
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
metadata
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-jsonld-signatures
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.10
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Sletten
|
8
|
+
- John Callahan
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2018-09-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rdf
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 3.0.4
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 3.0.4
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rdf-normalize
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rdf-turtle
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rdf-spec
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: open-uri-cached
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.0'
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 0.0.5
|
80
|
+
type: :development
|
81
|
+
prerelease: false
|
82
|
+
version_requirements: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - "~>"
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0.0'
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.0.5
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: rspec
|
92
|
+
requirement: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.2'
|
97
|
+
type: :development
|
98
|
+
prerelease: false
|
99
|
+
version_requirements: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.2'
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: webmock
|
106
|
+
requirement: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.3.2
|
111
|
+
type: :development
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 2.3.2
|
118
|
+
- !ruby/object:Gem::Dependency
|
119
|
+
name: json-ld
|
120
|
+
requirement: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 3.0.2
|
125
|
+
type: :development
|
126
|
+
prerelease: false
|
127
|
+
version_requirements: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 3.0.2
|
132
|
+
- !ruby/object:Gem::Dependency
|
133
|
+
name: yard
|
134
|
+
requirement: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.8'
|
139
|
+
type: :development
|
140
|
+
prerelease: false
|
141
|
+
version_requirements: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.8'
|
146
|
+
description: RDF::JSON::LD:Signature is an implementation of the JSON-LD Signature
|
147
|
+
specification for the RDF.rb library suite.
|
148
|
+
email: public-rdf-ruby@w3.org
|
149
|
+
executables: []
|
150
|
+
extensions: []
|
151
|
+
extra_rdoc_files: []
|
152
|
+
files:
|
153
|
+
- AUTHORS
|
154
|
+
- LICENSE
|
155
|
+
- README.md
|
156
|
+
- VERSION
|
157
|
+
- lib/json/ld/signature.rb
|
158
|
+
- lib/json/ld/signature/ed25519Signer.rb
|
159
|
+
- lib/json/ld/signature/ed25519Verifier.rb
|
160
|
+
- lib/json/ld/signature/rsaSigner.rb
|
161
|
+
- lib/json/ld/signature/rsaVerifier.rb
|
162
|
+
homepage: http://github.com/bsletten/rdf-jsonld-signature
|
163
|
+
licenses:
|
164
|
+
- MIT
|
165
|
+
metadata: {}
|
166
|
+
post_install_message:
|
167
|
+
rdoc_options: []
|
168
|
+
require_paths:
|
169
|
+
- lib
|
170
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - ">="
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: 1.9.2
|
175
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
requirements: []
|
181
|
+
rubyforge_project: rdf-normalize
|
182
|
+
rubygems_version: 2.7.7
|
183
|
+
signing_key:
|
184
|
+
specification_version: 4
|
185
|
+
summary: JSON-LD Signature implementation for Ruby.
|
186
|
+
test_files: []
|