ssh_sig 0.1.1
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/.rspec +3 -0
- data/.rubocop.yml +31 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +115 -0
- data/LICENSE.txt +21 -0
- data/README.md +124 -0
- data/Rakefile +23 -0
- data/bin/console +21 -0
- data/bin/setup +8 -0
- data/bin/setup-integration +11 -0
- data/lib/ssh_sig/blob.rb +154 -0
- data/lib/ssh_sig/error.rb +7 -0
- data/lib/ssh_sig/key_loader/http.rb +29 -0
- data/lib/ssh_sig/key_loader/pub_key.rb +33 -0
- data/lib/ssh_sig/serializable.rb +19 -0
- data/lib/ssh_sig/signature.rb +38 -0
- data/lib/ssh_sig/verifier.rb +47 -0
- data/lib/ssh_sig/version.rb +5 -0
- data/lib/ssh_sig.rb +11 -0
- metadata +274 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b3210fd902e6e37ef1e0d89964f2562daff95a412dae0ddf79d6a50c14704514
|
|
4
|
+
data.tar.gz: 5f209bdfad63784811c1f8de2eda27ee42df6e6cdffe4bd61a0aad8dfa7c5fd9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c2ceddd49854f0640b1aa0b7f46b0ffeb160f2c7244460dc6eae84d5849f89b4d6455091d2c2e96145ca3c1321eae92a92c8503f9c1e19c2334aad1d6312e13d
|
|
7
|
+
data.tar.gz: d4b46151147347bc5e817b5f7ae671cf746ba47b16d307da9ad12a42421e29c9a3f56837efbec95f6f64236c2d62b537c3db657879d395d648455f53699f62d4
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
inherit_gem:
|
|
3
|
+
gitlab-styles:
|
|
4
|
+
- rubocop-default.yml
|
|
5
|
+
|
|
6
|
+
AllCops:
|
|
7
|
+
NewCops: enable
|
|
8
|
+
TargetRubyVersion: 2.7
|
|
9
|
+
Exclude:
|
|
10
|
+
- 'vendor/**/*'
|
|
11
|
+
- 'tmp/**/*'
|
|
12
|
+
- 'bin/**/*'
|
|
13
|
+
CacheRootDirectory: tmp
|
|
14
|
+
MaxFilesInCache: 25000
|
|
15
|
+
|
|
16
|
+
Rails:
|
|
17
|
+
Enabled: false
|
|
18
|
+
|
|
19
|
+
CodeReuse/ActiveRecord:
|
|
20
|
+
Enabled: false
|
|
21
|
+
|
|
22
|
+
RSpec/MultipleMemoizedHelpers:
|
|
23
|
+
Max: 10
|
|
24
|
+
|
|
25
|
+
RSpec/NamedSubject:
|
|
26
|
+
Enabled: true
|
|
27
|
+
|
|
28
|
+
# Our heredoc delimiters do in fact contain files,
|
|
29
|
+
# so EOF is appropriate.
|
|
30
|
+
Naming/HeredocDelimiterNaming:
|
|
31
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
ssh_sig (0.1.1)
|
|
5
|
+
bcrypt_pbkdf (~> 1.1)
|
|
6
|
+
ed25519 (~> 1.2)
|
|
7
|
+
net-ssh (>= 6.3.0.beta1)
|
|
8
|
+
zeitwerk (~> 2.4)
|
|
9
|
+
|
|
10
|
+
GEM
|
|
11
|
+
remote: https://rubygems.org/
|
|
12
|
+
specs:
|
|
13
|
+
activesupport (6.1.4.1)
|
|
14
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
15
|
+
i18n (>= 1.6, < 2)
|
|
16
|
+
minitest (>= 5.1)
|
|
17
|
+
tzinfo (~> 2.0)
|
|
18
|
+
zeitwerk (~> 2.3)
|
|
19
|
+
ast (2.4.2)
|
|
20
|
+
bcrypt_pbkdf (1.1.0)
|
|
21
|
+
binding_ninja (0.2.3)
|
|
22
|
+
coderay (1.1.3)
|
|
23
|
+
concurrent-ruby (1.1.9)
|
|
24
|
+
diff-lcs (1.4.4)
|
|
25
|
+
ed25519 (1.2.4)
|
|
26
|
+
gitlab-styles (6.2.1)
|
|
27
|
+
rubocop (~> 0.91, >= 0.91.1)
|
|
28
|
+
rubocop-gitlab-security (~> 0.1.1)
|
|
29
|
+
rubocop-performance (~> 1.9.2)
|
|
30
|
+
rubocop-rails (~> 2.9)
|
|
31
|
+
rubocop-rspec (~> 1.44)
|
|
32
|
+
i18n (1.8.11)
|
|
33
|
+
concurrent-ruby (~> 1.0)
|
|
34
|
+
method_source (1.0.0)
|
|
35
|
+
minitest (5.14.4)
|
|
36
|
+
net-ssh (6.3.0.beta1)
|
|
37
|
+
parallel (1.21.0)
|
|
38
|
+
parser (3.0.2.0)
|
|
39
|
+
ast (~> 2.4.1)
|
|
40
|
+
proc_to_ast (0.1.0)
|
|
41
|
+
coderay
|
|
42
|
+
parser
|
|
43
|
+
unparser
|
|
44
|
+
pry (0.14.1)
|
|
45
|
+
coderay (~> 1.1)
|
|
46
|
+
method_source (~> 1.0)
|
|
47
|
+
rack (2.2.3)
|
|
48
|
+
rainbow (3.0.0)
|
|
49
|
+
rake (13.0.6)
|
|
50
|
+
regexp_parser (2.1.1)
|
|
51
|
+
rexml (3.2.5)
|
|
52
|
+
rspec (3.10.0)
|
|
53
|
+
rspec-core (~> 3.10.0)
|
|
54
|
+
rspec-expectations (~> 3.10.0)
|
|
55
|
+
rspec-mocks (~> 3.10.0)
|
|
56
|
+
rspec-core (3.10.1)
|
|
57
|
+
rspec-support (~> 3.10.0)
|
|
58
|
+
rspec-expectations (3.10.1)
|
|
59
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
60
|
+
rspec-support (~> 3.10.0)
|
|
61
|
+
rspec-mocks (3.10.2)
|
|
62
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
63
|
+
rspec-support (~> 3.10.0)
|
|
64
|
+
rspec-parameterized (0.5.0)
|
|
65
|
+
binding_ninja (>= 0.2.3)
|
|
66
|
+
parser
|
|
67
|
+
proc_to_ast
|
|
68
|
+
rspec (>= 2.13, < 4)
|
|
69
|
+
unparser
|
|
70
|
+
rspec-support (3.10.3)
|
|
71
|
+
rubocop (0.93.1)
|
|
72
|
+
parallel (~> 1.10)
|
|
73
|
+
parser (>= 2.7.1.5)
|
|
74
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
75
|
+
regexp_parser (>= 1.8)
|
|
76
|
+
rexml
|
|
77
|
+
rubocop-ast (>= 0.6.0)
|
|
78
|
+
ruby-progressbar (~> 1.7)
|
|
79
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
|
80
|
+
rubocop-ast (1.13.0)
|
|
81
|
+
parser (>= 3.0.1.1)
|
|
82
|
+
rubocop-gitlab-security (0.1.1)
|
|
83
|
+
rubocop (>= 0.51)
|
|
84
|
+
rubocop-performance (1.9.2)
|
|
85
|
+
rubocop (>= 0.90.0, < 2.0)
|
|
86
|
+
rubocop-ast (>= 0.4.0)
|
|
87
|
+
rubocop-rails (2.9.1)
|
|
88
|
+
activesupport (>= 4.2.0)
|
|
89
|
+
rack (>= 1.1)
|
|
90
|
+
rubocop (>= 0.90.0, < 2.0)
|
|
91
|
+
rubocop-rspec (1.44.1)
|
|
92
|
+
rubocop (~> 0.87)
|
|
93
|
+
rubocop-ast (>= 0.7.1)
|
|
94
|
+
ruby-progressbar (1.11.0)
|
|
95
|
+
tzinfo (2.0.4)
|
|
96
|
+
concurrent-ruby (~> 1.0)
|
|
97
|
+
unicode-display_width (1.8.0)
|
|
98
|
+
unparser (0.6.2)
|
|
99
|
+
diff-lcs (~> 1.3)
|
|
100
|
+
parser (>= 3.0.0)
|
|
101
|
+
zeitwerk (2.5.1)
|
|
102
|
+
|
|
103
|
+
PLATFORMS
|
|
104
|
+
x86_64-darwin-20
|
|
105
|
+
|
|
106
|
+
DEPENDENCIES
|
|
107
|
+
gitlab-styles (~> 6.2.0)
|
|
108
|
+
pry (~> 0.14.1)
|
|
109
|
+
rake (~> 13.0)
|
|
110
|
+
rspec (~> 3.0)
|
|
111
|
+
rspec-parameterized (~> 0.5.0)
|
|
112
|
+
ssh_sig!
|
|
113
|
+
|
|
114
|
+
BUNDLED WITH
|
|
115
|
+
2.2.30
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Brian Williams
|
|
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,124 @@
|
|
|
1
|
+
# SshSig - SSH signature verification in pure ruby
|
|
2
|
+
|
|
3
|
+
SshSig is a Ruby gem which can be used to verify signatures signed created by `ssh-keygen`.
|
|
4
|
+
This capability was [first added](https://github.com/openssh/openssh-portable/commit/2a9c9f7272c1e8665155118fe6536bebdafb6166) in OpenSSH 8.0
|
|
5
|
+
allows SSH keys to be used for GPG-like signing capabilities, [including signing git commits](https://github.com/git/git/pull/1041).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'ssh_sig'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then execute:
|
|
16
|
+
|
|
17
|
+
$ bundle install
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
$ gem install ssh_sig
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
Version 1 of [the SSH signature format](https://github.com/openssh/openssh-portable/blob/b7ffbb17e37f59249c31f1ff59d6c5d80888f689/PROTOCOL.sshsig)
|
|
26
|
+
supports `ed25519` and `rsa` keys. It is recommended that you use `ed25519` over `rsa` where possible (`ssh-keygen -t ed25519`).
|
|
27
|
+
|
|
28
|
+
In order to verify a signature you need:
|
|
29
|
+
|
|
30
|
+
1. The public key of the sender
|
|
31
|
+
1. The signature file
|
|
32
|
+
1. The message to be verified.
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
require 'ssh_sig'
|
|
36
|
+
|
|
37
|
+
armored_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILXPkJPI4TMFWZP4xRBQjNeizUG99KuZCt9G23rX48kz"
|
|
38
|
+
|
|
39
|
+
blob = ::SshSig::Blob.from_armor(
|
|
40
|
+
<<~EOF
|
|
41
|
+
-----BEGIN SSH SIGNATURE-----
|
|
42
|
+
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgtc+Qk8jhMwVZk/jFEFCM16LNQb
|
|
43
|
+
30q5kK30bbetfjyTMAAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx
|
|
44
|
+
OQAAAECJITeYJIlEeydsCTh1DkfdhlDJFBa73ojfWe0MbrIzoJKd9THd9WeQrhygSRGsNG
|
|
45
|
+
cU/stk3/919nykg67yG2gN
|
|
46
|
+
-----END SSH SIGNATURE-----
|
|
47
|
+
EOF
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
message = "This message was definitely sent by Brian Williams"
|
|
51
|
+
|
|
52
|
+
valid = ::SshSig::Verifier
|
|
53
|
+
.from_armored_pubkey(armored_pubkey)
|
|
54
|
+
.verify(blob, message)
|
|
55
|
+
|
|
56
|
+
if valid
|
|
57
|
+
puts 'Signature is valid'
|
|
58
|
+
else
|
|
59
|
+
puts 'Signature is not valid'
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Signatures can be created using `ssh-keygen -Y sign -n file -f ~/.ssh/ed_25519 message.txt`
|
|
64
|
+
and will be outputted in `message.txt.sig`.
|
|
65
|
+
|
|
66
|
+
Public keys can be found in a variety of places, including:
|
|
67
|
+
|
|
68
|
+
- Your `~/.ssh/id_<alg>.pub` file
|
|
69
|
+
- `authorized_keys` files on servers
|
|
70
|
+
- `https://gitlab.com/<username>.keys`
|
|
71
|
+
- `https://github.com/<username>.keys`
|
|
72
|
+
|
|
73
|
+
The `SshSig::Verifier#from_gitlab` and `SshSig::Verifier#from_github` methods are provided
|
|
74
|
+
to automatically load public keys from the respective `<username>.keys` urls.
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
require 'ssh_sig'
|
|
78
|
+
|
|
79
|
+
blob = ::SshSig::Blob.from_armor(
|
|
80
|
+
<<~EOF
|
|
81
|
+
-----BEGIN SSH SIGNATURE-----
|
|
82
|
+
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgtc+Qk8jhMwVZk/jFEFCM16LNQb
|
|
83
|
+
30q5kK30bbetfjyTMAAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx
|
|
84
|
+
OQAAAECJITeYJIlEeydsCTh1DkfdhlDJFBa73ojfWe0MbrIzoJKd9THd9WeQrhygSRGsNG
|
|
85
|
+
cU/stk3/919nykg67yG2gN
|
|
86
|
+
-----END SSH SIGNATURE-----
|
|
87
|
+
EOF
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
message = 'This message was definitely sent by Brian Williams'
|
|
91
|
+
|
|
92
|
+
valid = ::SshSig::Verifier
|
|
93
|
+
.from_gitlab('bwill')
|
|
94
|
+
.verify(blob, message)
|
|
95
|
+
|
|
96
|
+
if valid
|
|
97
|
+
puts 'Signature is valid'
|
|
98
|
+
else
|
|
99
|
+
puts 'Signature is not valid'
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Is it safe to re-purpose SSH keys for signing?
|
|
104
|
+
|
|
105
|
+
Yes. The [SSH signature protocol](https://github.com/openssh/openssh-portable/blob/d575cf44895104e0fcb0629920fb645207218129/PROTOCOL.sshsig)
|
|
106
|
+
is designed to be resistant to cross-protocol attacks, where signatures created for one purpose (i.e. signing a git commit),
|
|
107
|
+
may be re-used for another purpose (i.e. authenticating to a server). It does this using the magic pre-amble (to differentiate
|
|
108
|
+
between messages signed by `ssh-keygen` and messages used for SSH authentication) and namespaces (to differentiate between
|
|
109
|
+
messages signed by `ssh-keygen` but used for different purposes). This causes identical messages to produce different signatures
|
|
110
|
+
for each different protocol.
|
|
111
|
+
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
115
|
+
|
|
116
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
117
|
+
|
|
118
|
+
## Contributing
|
|
119
|
+
|
|
120
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ssh_sig. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/ssh_sig/blob/main/CODE_OF_CONDUCT.md).
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
|
|
6
|
+
# Run both unit and integration tests
|
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
8
|
+
|
|
9
|
+
# Run tests excluding integration tests
|
|
10
|
+
RSpec::Core::RakeTask.new(:spec_unit) do |t|
|
|
11
|
+
t.rspec_opts = "--tag ~@integration"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Run only integration tests
|
|
15
|
+
RSpec::Core::RakeTask.new(:spec_integration) do |t|
|
|
16
|
+
t.rspec_opts = "--tag integration"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
require "rubocop/rake_task"
|
|
20
|
+
|
|
21
|
+
RuboCop::RakeTask.new
|
|
22
|
+
|
|
23
|
+
task default: %i[spec rubocop]
|
data/bin/console
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'ssh_sig'
|
|
6
|
+
require 'net/ssh'
|
|
7
|
+
|
|
8
|
+
# require 'pry'
|
|
9
|
+
# Pry.start
|
|
10
|
+
|
|
11
|
+
require_relative '../spec/support/helpers/byte_helpers'
|
|
12
|
+
# rubocop:disable Syle/MixinUsage
|
|
13
|
+
include ByteHelpers
|
|
14
|
+
# rubocop:enable Syle/MixinUsage
|
|
15
|
+
|
|
16
|
+
def hexdump(bytes)
|
|
17
|
+
puts bin_to_hex(bytes)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
require 'irb'
|
|
21
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
echo "Installing OpenSSH version ${OPENSSH_VERSION?:OPENSSH_VERSION must be set}"
|
|
6
|
+
|
|
7
|
+
curl -sSL -o /tmp/openssh.tar.gz "https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-${OPENSSH_VERSION}.tar.gz"
|
|
8
|
+
|
|
9
|
+
tar -xzvf /tmp/openssh.tar.gz -C /tmp/
|
|
10
|
+
|
|
11
|
+
"/tmp/openssh-${OPENSSH_VERSION}/configure"
|
data/lib/ssh_sig/blob.rb
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/ssh/buffer'
|
|
4
|
+
require 'digest'
|
|
5
|
+
|
|
6
|
+
module SshSig
|
|
7
|
+
class Blob
|
|
8
|
+
include Serializable
|
|
9
|
+
extend Serializable
|
|
10
|
+
|
|
11
|
+
attr_reader :namespace, :hash_algorithm, :signature
|
|
12
|
+
|
|
13
|
+
def initialize(
|
|
14
|
+
public_key:,
|
|
15
|
+
namespace:,
|
|
16
|
+
hash_algorithm:,
|
|
17
|
+
signature:
|
|
18
|
+
)
|
|
19
|
+
@public_key = public_key
|
|
20
|
+
@namespace = namespace
|
|
21
|
+
@hash_algorithm = hash_algorithm
|
|
22
|
+
@signature = signature
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# public_key is parsed from the signature data and is untrusted
|
|
26
|
+
# We make this clear using accessor naming
|
|
27
|
+
def public_key_untrusted
|
|
28
|
+
@public_key
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.from_armor(armor)
|
|
32
|
+
from_bytes(armor_to_blob(armor))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# decode_blob parses the binary signature data as described in
|
|
36
|
+
# https://github.com/openssh/openssh-portable/blob/e665ed2d0c24fe11d5470ce72fa1e187377d3fc4/PROTOCOL.sshsig
|
|
37
|
+
#
|
|
38
|
+
# byte[6] MAGIC_PREAMBLE
|
|
39
|
+
# uint32 SIG_VERSION
|
|
40
|
+
# string publickey
|
|
41
|
+
# string namespace
|
|
42
|
+
# string reserved
|
|
43
|
+
# string hash_algorithm
|
|
44
|
+
# string signature
|
|
45
|
+
def self.from_bytes(blob)
|
|
46
|
+
buf = ::Net::SSH::Buffer.new(blob)
|
|
47
|
+
|
|
48
|
+
preamble = buf.read!(6)
|
|
49
|
+
|
|
50
|
+
raise DecodeError, 'Invalid magic preamble' unless preamble == MAGIC_PREAMBLE
|
|
51
|
+
|
|
52
|
+
version = read_uint64(buf)
|
|
53
|
+
|
|
54
|
+
raise DecodeError, 'Unsupported signature version' unless version == SIG_VERSION
|
|
55
|
+
|
|
56
|
+
public_key = buf.read_key
|
|
57
|
+
|
|
58
|
+
raise DecodeError, 'Signature is missing public key' if public_key.nil?
|
|
59
|
+
|
|
60
|
+
namespace = buf.read_string
|
|
61
|
+
|
|
62
|
+
raise DecodeError, 'Signature is missing namespace' if namespace.nil?
|
|
63
|
+
|
|
64
|
+
# Read past the reserved value and ignore it.
|
|
65
|
+
buf.read_string
|
|
66
|
+
|
|
67
|
+
hash_algorithm = buf.read_string
|
|
68
|
+
|
|
69
|
+
raise DecodeError, 'Signature is missing hash algorithm' if hash_algorithm.nil?
|
|
70
|
+
raise DecodeError, 'Hash algorithm is not supported' unless hash_algorithm_allowed?(hash_algorithm)
|
|
71
|
+
|
|
72
|
+
signature_raw = buf.read_string
|
|
73
|
+
|
|
74
|
+
raise DecodeError, 'Signature is missing signed data' if signature_raw.nil?
|
|
75
|
+
|
|
76
|
+
signature = Signature.from_bytes(signature_raw)
|
|
77
|
+
|
|
78
|
+
raise DecodeError, 'Signature algorithm is not supported' \
|
|
79
|
+
unless signature_algorithm_allowed?(signature.algorithm)
|
|
80
|
+
|
|
81
|
+
Blob.new(
|
|
82
|
+
public_key: public_key,
|
|
83
|
+
namespace: namespace,
|
|
84
|
+
hash_algorithm: hash_algorithm,
|
|
85
|
+
signature: signature
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# signature_data creates the "message" passed to the
|
|
90
|
+
# signing function as described in section 3 of
|
|
91
|
+
# https://github.com/openssh/openssh-portable/blob/b7ffbb17e37f59249c31f1ff59d6c5d80888f689/PROTOCOL.sshsig
|
|
92
|
+
#
|
|
93
|
+
# Despite the documentation's use of the word "concatenated",
|
|
94
|
+
# this data must use the same DER-like encoding as the signature blob.
|
|
95
|
+
#
|
|
96
|
+
# byte[6] MAGIC_PREAMBLE
|
|
97
|
+
# string namespace
|
|
98
|
+
# string reserved
|
|
99
|
+
# string hash_algorithm
|
|
100
|
+
# string H(message)
|
|
101
|
+
def signature_data(message)
|
|
102
|
+
buf = ::Net::SSH::Buffer.new
|
|
103
|
+
|
|
104
|
+
buf.write(MAGIC_PREAMBLE)
|
|
105
|
+
buf.write_string(namespace)
|
|
106
|
+
buf.write_string('') # reserved
|
|
107
|
+
buf.write_string(hash_algorithm)
|
|
108
|
+
buf.write_string(hash(message))
|
|
109
|
+
|
|
110
|
+
buf.to_s
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def self.read_uint64(buf)
|
|
116
|
+
b = buf.read(8)
|
|
117
|
+
|
|
118
|
+
return nil unless b
|
|
119
|
+
|
|
120
|
+
b.unpack1("N")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def self.armor_to_blob(armor)
|
|
124
|
+
# Remove starting and ending whitespace for header checks.
|
|
125
|
+
armor = armor.strip
|
|
126
|
+
|
|
127
|
+
raise DecodeError, "Couldn't parse signature: missing header" unless armor.start_with?(BEGIN_SIGNATURE)
|
|
128
|
+
|
|
129
|
+
raise DecodeError, "Couldn't parse signature: missing footer" unless armor.end_with?(END_SIGNATURE)
|
|
130
|
+
|
|
131
|
+
b64 = armor
|
|
132
|
+
.delete_prefix(BEGIN_SIGNATURE)
|
|
133
|
+
.delete_suffix(END_SIGNATURE)
|
|
134
|
+
.gsub(/\s+/, '') # Remove all remaining whitespace to ensure valid Base64
|
|
135
|
+
|
|
136
|
+
begin
|
|
137
|
+
Base64.strict_decode64(b64)
|
|
138
|
+
rescue ArgumentError => e
|
|
139
|
+
raise DecodeError, "Couldn't decode armor body: #{e.message}"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def hash(data)
|
|
144
|
+
case hash_algorithm
|
|
145
|
+
when "sha512"
|
|
146
|
+
::Digest::SHA2.new(512).digest(data)
|
|
147
|
+
when "sha256"
|
|
148
|
+
::Digest::SHA2.new(256).digest(data)
|
|
149
|
+
else
|
|
150
|
+
raise VerifyError, "Hash algorithm #{hash_algorithm} is not supported"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open-uri'
|
|
4
|
+
|
|
5
|
+
module SshSig
|
|
6
|
+
module KeyLoader
|
|
7
|
+
class Http < PubKey
|
|
8
|
+
class << self
|
|
9
|
+
def load(url)
|
|
10
|
+
keys = get(url)
|
|
11
|
+
|
|
12
|
+
super(keys)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def load_dot_keys(username, base_addr = 'https://gitlab.com')
|
|
16
|
+
load("#{base_addr}/#{username}.keys")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def get(url)
|
|
22
|
+
URI(url).read
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
raise ::SshSig::LoadError, "Error received from remote: #{e.message}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'net/ssh'
|
|
3
|
+
|
|
4
|
+
module SshSig
|
|
5
|
+
module KeyLoader
|
|
6
|
+
class PubKey
|
|
7
|
+
SUPPORTED_KEY_ALGORITHMS = %w[ssh-ed25519 ssh-rsa].freeze
|
|
8
|
+
class << self
|
|
9
|
+
def load(armored)
|
|
10
|
+
keys = armored.split("\n")
|
|
11
|
+
|
|
12
|
+
keys
|
|
13
|
+
.filter { |key| supported_key_algorithm?(key) }
|
|
14
|
+
.map { |key| load_data_public_key(key) }
|
|
15
|
+
rescue ::Net::SSH::Exception, ::ArgumentError
|
|
16
|
+
raise ::SshSig::LoadError, 'Public key is not valid'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def load_data_public_key(key)
|
|
22
|
+
::Net::SSH::KeyFactory.load_data_public_key(key)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def supported_key_algorithm?(key)
|
|
26
|
+
alg = key.split(' ').first
|
|
27
|
+
|
|
28
|
+
SUPPORTED_KEY_ALGORITHMS.any?(alg)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module SshSig
|
|
3
|
+
module Serializable
|
|
4
|
+
SIG_VERSION = 1
|
|
5
|
+
MAGIC_PREAMBLE = "SSHSIG"
|
|
6
|
+
BEGIN_SIGNATURE = "-----BEGIN SSH SIGNATURE-----"
|
|
7
|
+
END_SIGNATURE = "-----END SSH SIGNATURE-----"
|
|
8
|
+
SIGALG_ALLOWED = %w[ssh-ed25519 rsa-sha2-512 rsa-sha2-256].freeze
|
|
9
|
+
HASHALG_ALLOWED = %w[sha512 sha256].freeze
|
|
10
|
+
|
|
11
|
+
def signature_algorithm_allowed?(alg)
|
|
12
|
+
SIGALG_ALLOWED.any?(alg)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def hash_algorithm_allowed?(alg)
|
|
16
|
+
HASHALG_ALLOWED.any?(alg)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module SshSig
|
|
3
|
+
class Signature
|
|
4
|
+
attr_reader :algorithm, :bytes
|
|
5
|
+
|
|
6
|
+
def initialize(
|
|
7
|
+
algorithm:,
|
|
8
|
+
bytes:
|
|
9
|
+
)
|
|
10
|
+
@algorithm = algorithm
|
|
11
|
+
@bytes = bytes
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# While not described well in the protocol documenation,
|
|
15
|
+
# the signature has two parts. There is a string describing the algorithm,
|
|
16
|
+
# which can be one of ssh-ed25519, rsa-sha2-512, or rsa-sha2-256,
|
|
17
|
+
# followed by the actual signature bytes.
|
|
18
|
+
#
|
|
19
|
+
# string algorithm
|
|
20
|
+
# string signature_bytes
|
|
21
|
+
def self.from_bytes(blob)
|
|
22
|
+
buf = ::Net::SSH::Buffer.new(blob)
|
|
23
|
+
|
|
24
|
+
algorithm = buf.read_string
|
|
25
|
+
|
|
26
|
+
raise DecodeError, 'Signature algorithm is missing' if algorithm.nil?
|
|
27
|
+
|
|
28
|
+
bytes = buf.read_string
|
|
29
|
+
|
|
30
|
+
raise DecodeError, 'Signature data is missing' if bytes.nil?
|
|
31
|
+
|
|
32
|
+
Signature.new(
|
|
33
|
+
algorithm: algorithm,
|
|
34
|
+
bytes: bytes
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ed25519'
|
|
4
|
+
|
|
5
|
+
module SshSig
|
|
6
|
+
class Verifier
|
|
7
|
+
def initialize(public_keys)
|
|
8
|
+
@public_keys = public_keys
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.from_armored_pubkey(armored_pubkey)
|
|
12
|
+
public_keys = ::SshSig::KeyLoader::PubKey.load(armored_pubkey)
|
|
13
|
+
|
|
14
|
+
new(public_keys)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.from_github(username, base_addr = 'https://github.com')
|
|
18
|
+
public_keys = ::SshSig::KeyLoader::Http.load_dot_keys(username, base_addr)
|
|
19
|
+
|
|
20
|
+
new(public_keys)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.from_gitlab(username, base_addr = 'https://gitlab.com')
|
|
24
|
+
public_keys = ::SshSig::KeyLoader::Http.load_dot_keys(username, base_addr)
|
|
25
|
+
|
|
26
|
+
new(public_keys)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def verify(blob, message)
|
|
30
|
+
return false unless blob&.signature
|
|
31
|
+
|
|
32
|
+
@public_keys.any? do |key|
|
|
33
|
+
key.ssh_do_verify(
|
|
34
|
+
blob.signature.bytes,
|
|
35
|
+
blob.signature_data(message),
|
|
36
|
+
# When using RSA, net-ssh uses this to determine the digest algorithm to use.
|
|
37
|
+
# Added in net-ssh 6.3.0.beta1
|
|
38
|
+
{ host_key: blob.signature.algorithm }
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
rescue ::Ed25519::VerifyError
|
|
42
|
+
# Ed25519 public keys raise exceptions when they fail to verify,
|
|
43
|
+
# but RSA public keys don't
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/ssh_sig.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ssh_sig
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Brian Williams
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2021-12-07 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bcrypt_pbkdf
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.1'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: ed25519
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.2'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.2'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: net-ssh
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: 6.3.0.beta1
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 6.3.0.beta1
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: zeitwerk
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '2.4'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '2.4'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: gitlab-styles
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 6.2.0
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 6.2.0
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rspec-parameterized
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 0.5.0
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 0.5.0
|
|
97
|
+
description: |
|
|
98
|
+
# SshSig - SSH signature verification in pure ruby
|
|
99
|
+
|
|
100
|
+
SshSig is a Ruby gem which can be used to verify signatures signed created by `ssh-keygen`.
|
|
101
|
+
This capability was [first added](https://github.com/openssh/openssh-portable/commit/2a9c9f7272c1e8665155118fe6536bebdafb6166) in OpenSSH 8.0
|
|
102
|
+
allows SSH keys to be used for GPG-like signing capabilities, [including signing git commits](https://github.com/git/git/pull/1041).
|
|
103
|
+
|
|
104
|
+
## Installation
|
|
105
|
+
|
|
106
|
+
Add this line to your application's Gemfile:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
gem 'ssh_sig'
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
And then execute:
|
|
113
|
+
|
|
114
|
+
$ bundle install
|
|
115
|
+
|
|
116
|
+
Or install it yourself as:
|
|
117
|
+
|
|
118
|
+
$ gem install ssh_sig
|
|
119
|
+
|
|
120
|
+
## Usage
|
|
121
|
+
|
|
122
|
+
Version 1 of [the SSH signature format](https://github.com/openssh/openssh-portable/blob/b7ffbb17e37f59249c31f1ff59d6c5d80888f689/PROTOCOL.sshsig)
|
|
123
|
+
supports `ed25519` and `rsa` keys. It is recommended that you use `ed25519` over `rsa` where possible (`ssh-keygen -t ed25519`).
|
|
124
|
+
|
|
125
|
+
In order to verify a signature you need:
|
|
126
|
+
|
|
127
|
+
1. The public key of the sender
|
|
128
|
+
1. The signature file
|
|
129
|
+
1. The message to be verified.
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
require 'ssh_sig'
|
|
133
|
+
|
|
134
|
+
armored_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILXPkJPI4TMFWZP4xRBQjNeizUG99KuZCt9G23rX48kz"
|
|
135
|
+
|
|
136
|
+
blob = ::SshSig::Blob.from_armor(
|
|
137
|
+
<<~EOF
|
|
138
|
+
-----BEGIN SSH SIGNATURE-----
|
|
139
|
+
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgtc+Qk8jhMwVZk/jFEFCM16LNQb
|
|
140
|
+
30q5kK30bbetfjyTMAAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx
|
|
141
|
+
OQAAAECJITeYJIlEeydsCTh1DkfdhlDJFBa73ojfWe0MbrIzoJKd9THd9WeQrhygSRGsNG
|
|
142
|
+
cU/stk3/919nykg67yG2gN
|
|
143
|
+
-----END SSH SIGNATURE-----
|
|
144
|
+
EOF
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
message = "This message was definitely sent by Brian Williams"
|
|
148
|
+
|
|
149
|
+
valid = ::SshSig::Verifier
|
|
150
|
+
.from_armored_pubkey(armored_pubkey)
|
|
151
|
+
.verify(blob, message)
|
|
152
|
+
|
|
153
|
+
if valid
|
|
154
|
+
puts 'Signature is valid'
|
|
155
|
+
else
|
|
156
|
+
puts 'Signature is not valid'
|
|
157
|
+
end
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Signatures can be created using `ssh-keygen -Y sign -n file -f ~/.ssh/ed_25519 message.txt`
|
|
161
|
+
and will be outputted in `message.txt.sig`.
|
|
162
|
+
|
|
163
|
+
Public keys can be found in a variety of places, including:
|
|
164
|
+
|
|
165
|
+
- Your `~/.ssh/id_<alg>.pub` file
|
|
166
|
+
- `authorized_keys` files on servers
|
|
167
|
+
- `https://gitlab.com/<username>.keys`
|
|
168
|
+
- `https://github.com/<username>.keys`
|
|
169
|
+
|
|
170
|
+
The `SshSig::Verifier#from_gitlab` and `SshSig::Verifier#from_github` methods are provided
|
|
171
|
+
to automatically load public keys from the respective `<username>.keys` urls.
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
require 'ssh_sig'
|
|
175
|
+
|
|
176
|
+
blob = ::SshSig::Blob.from_armor(
|
|
177
|
+
<<~EOF
|
|
178
|
+
-----BEGIN SSH SIGNATURE-----
|
|
179
|
+
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgtc+Qk8jhMwVZk/jFEFCM16LNQb
|
|
180
|
+
30q5kK30bbetfjyTMAAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx
|
|
181
|
+
OQAAAECJITeYJIlEeydsCTh1DkfdhlDJFBa73ojfWe0MbrIzoJKd9THd9WeQrhygSRGsNG
|
|
182
|
+
cU/stk3/919nykg67yG2gN
|
|
183
|
+
-----END SSH SIGNATURE-----
|
|
184
|
+
EOF
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
message = 'This message was definitely sent by Brian Williams'
|
|
188
|
+
|
|
189
|
+
valid = ::SshSig::Verifier
|
|
190
|
+
.from_gitlab('bwill')
|
|
191
|
+
.verify(blob, message)
|
|
192
|
+
|
|
193
|
+
if valid
|
|
194
|
+
puts 'Signature is valid'
|
|
195
|
+
else
|
|
196
|
+
puts 'Signature is not valid'
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Is it safe to re-purpose SSH keys for signing?
|
|
201
|
+
|
|
202
|
+
Yes. The [SSH signature protocol](https://github.com/openssh/openssh-portable/blob/d575cf44895104e0fcb0629920fb645207218129/PROTOCOL.sshsig)
|
|
203
|
+
is designed to be resistant to cross-protocol attacks, where signatures created for one purpose (i.e. signing a git commit),
|
|
204
|
+
may be re-used for another purpose (i.e. authenticating to a server). It does this using the magic pre-amble (to differentiate
|
|
205
|
+
between messages signed by `ssh-keygen` and messages used for SSH authentication) and namespaces (to differentiate between
|
|
206
|
+
messages signed by `ssh-keygen` but used for different purposes). This causes identical messages to produce different signatures
|
|
207
|
+
for each different protocol.
|
|
208
|
+
|
|
209
|
+
## Development
|
|
210
|
+
|
|
211
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
212
|
+
|
|
213
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
214
|
+
|
|
215
|
+
## Contributing
|
|
216
|
+
|
|
217
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ssh_sig. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/ssh_sig/blob/main/CODE_OF_CONDUCT.md).
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
222
|
+
email:
|
|
223
|
+
- bwilliams@gitlab.com
|
|
224
|
+
executables: []
|
|
225
|
+
extensions: []
|
|
226
|
+
extra_rdoc_files: []
|
|
227
|
+
files:
|
|
228
|
+
- ".rspec"
|
|
229
|
+
- ".rubocop.yml"
|
|
230
|
+
- CHANGELOG.md
|
|
231
|
+
- Gemfile
|
|
232
|
+
- Gemfile.lock
|
|
233
|
+
- LICENSE.txt
|
|
234
|
+
- README.md
|
|
235
|
+
- Rakefile
|
|
236
|
+
- bin/console
|
|
237
|
+
- bin/setup
|
|
238
|
+
- bin/setup-integration
|
|
239
|
+
- lib/ssh_sig.rb
|
|
240
|
+
- lib/ssh_sig/blob.rb
|
|
241
|
+
- lib/ssh_sig/error.rb
|
|
242
|
+
- lib/ssh_sig/key_loader/http.rb
|
|
243
|
+
- lib/ssh_sig/key_loader/pub_key.rb
|
|
244
|
+
- lib/ssh_sig/serializable.rb
|
|
245
|
+
- lib/ssh_sig/signature.rb
|
|
246
|
+
- lib/ssh_sig/verifier.rb
|
|
247
|
+
- lib/ssh_sig/version.rb
|
|
248
|
+
homepage: https://gitlab.com/bwill/ssh_sig
|
|
249
|
+
licenses:
|
|
250
|
+
- MIT
|
|
251
|
+
metadata:
|
|
252
|
+
homepage_uri: https://gitlab.com/bwill/ssh_sig
|
|
253
|
+
source_code_uri: https://gitlab.com/bwill/ssh_sig
|
|
254
|
+
changelog_uri: https://gitlab.com/bwill/ssh_sig/-/blob/main/CHANGELOG.md
|
|
255
|
+
post_install_message:
|
|
256
|
+
rdoc_options: []
|
|
257
|
+
require_paths:
|
|
258
|
+
- lib
|
|
259
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
260
|
+
requirements:
|
|
261
|
+
- - ">="
|
|
262
|
+
- !ruby/object:Gem::Version
|
|
263
|
+
version: 2.7.0
|
|
264
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
265
|
+
requirements:
|
|
266
|
+
- - ">="
|
|
267
|
+
- !ruby/object:Gem::Version
|
|
268
|
+
version: '0'
|
|
269
|
+
requirements: []
|
|
270
|
+
rubygems_version: 3.1.6
|
|
271
|
+
signing_key:
|
|
272
|
+
specification_version: 4
|
|
273
|
+
summary: SSH Signatures in Ruby
|
|
274
|
+
test_files: []
|