blob: ee9b38cae7d7142abc4f43f772867ff05d09c766 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
|
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ssh::Signature, feature_category: :source_code_management do
# ssh-keygen -t ed25519
let_it_be(:committer_email) { 'ssh-commit-test@example.com' }
let_it_be(:public_key_text) { 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHZ8NHEnCIpC4mnot+BRxv6L+fq+TnN1CgsRrHWLmfwb' }
let_it_be_with_reload(:user) { create(:user, email: committer_email) }
let_it_be_with_reload(:key) { create(:key, usage_type: :signing, key: public_key_text, user: user) }
let(:signed_text) { 'This message was signed by an ssh key' }
let(:signature_text) do
# ssh-keygen -Y sign -n git -f id_test message.txt
<<~SIG
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgdnw0cScIikLiaei34FHG/ov5+r
5Oc3UKCxGsdYuZ/BsAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
AAAAQDWOEauf0jXyA9caa5bOgK5QZD6c69pm+EbG3GMw5QBL3N/Gt+r413McCSJFohWWBk
Lxemg8NzZ0nB7lTFbaxQc=
-----END SSH SIGNATURE-----
SIG
end
subject(:signature) do
described_class.new(
signature_text,
signed_text,
committer_email
)
end
shared_examples 'verified signature' do
it 'reports verified status' do
expect(signature.verification_status).to eq(:verified)
end
end
shared_examples 'unverified signature' do
it 'reports unverified status' do
expect(signature.verification_status).to eq(:unverified)
end
end
describe 'signature verification' do
context 'when signature is valid and user email is verified' do
it_behaves_like 'verified signature'
end
context 'when using an RSA key' do
let(:public_key_text) do
<<~KEY.delete("\n")
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDkq6ko8LMxf2NwyJKh+77KSDc7/ynPgUJD
IopkhqftuHFYe2Y+V3MBJnpzfSRwR2xGfXQUUzLU9AGyfZIO/ZLK2yvfhlO3k//5PbAaZb3y
urlnF9T1d2nhtfi8wuzsEn7Boh6qdoWPFIsloAL/X0PXH1HWKmzyNer92HKGrnWFfaaEMo0n
T3ureAhRG4IONyUcOK+DyoH+YbxXSlHnLO2oHHlWaP9RrJCHbfAQbfDhaZCI0cNkXXOwUwA4
yWGzDibfXZTvaYxpjbz1xoHmCAq8IrobCgkQaEg3PH3vPGnbP0TpViXjMnZyBZyT7tg9WHBV
kAsl0CizyUgZHPAPYuqKy5JNlnjVjeqYeIgdN4Tj7hpJ1n0hVpRk4zQNYRmAAj3GNqgPAsd0
3i4rW8cqmhO0fmhP5DgQ7Mt5S9AgcTcCr6niPacK34XrwKiRjxXmCLjr36q8wuRU3QdMt+MK
Zxk/qJdAUIltz+nuGiwct0w+sWefYzmiRXu6hljBBrRAvnU=
KEY
end
let(:signature_text) do
<<~SIG
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAAZcAAAAHc3NoLXJzYQAAAAMBAAEAAAGBAOSrqSjwszF/Y3DIkqH7vs
pINzv/Kc+BQkMiimSGp+24cVh7Zj5XcwEmenN9JHBHbEZ9dBRTMtT0AbJ9kg79ksrbK9+G
U7eT//k9sBplvfK6uWcX1PV3aeG1+LzC7OwSfsGiHqp2hY8UiyWgAv9fQ9cfUdYqbPI16v
3YcoaudYV9poQyjSdPe6t4CFEbgg43JRw4r4PKgf5hvFdKUecs7agceVZo/1GskIdt8BBt
8OFpkIjRw2Rdc7BTADjJYbMOJt9dlO9pjGmNvPXGgeYICrwiuhsKCRBoSDc8fe88ads/RO
lWJeMydnIFnJPu2D1YcFWQCyXQKLPJSBkc8A9i6orLkk2WeNWN6ph4iB03hOPuGknWfSFW
lGTjNA1hGYACPcY2qA8Cx3TeLitbxyqaE7R+aE/kOBDsy3lL0CBxNwKvqeI9pwrfhevAqJ
GPFeYIuOvfqrzC5FTdB0y34wpnGT+ol0BQiW3P6e4aLBy3TD6xZ59jOaJFe7qGWMEGtEC+
dQAAAANnaXQAAAAAAAAABnNoYTUxMgAAAZQAAAAMcnNhLXNoYTItNTEyAAABgEnuYyYOlM
CSR+wvmBY7eKHzFor5ByM7N4F7VZAGKK/vbS3C38xDdiJZwsZUscpe5WspJVCWUTkFxXjn
GW7vseIfJBVkyqnu2uN8X1j/VDLFESEajcchPhPxtfAMK1/NL99O7rCrYX2pmpkm9tWsFk
NX5B93sRyDUnHAOkB+zdqU8P0xdzc8kmBl5OOqu1rSjZIgnQjcauEIRIUN+rFuiRRmIvJp
UvMhkKSsRCH93btGW7A6x5e4iPzP+Em0UFYJdOx2lvu9aVAktQzysGwDN+9c4IC+07UHKT
UIE5jSbR1QKfavcywNQnCltQ2bTxpnm4A6QHKcdr9Q57dV014FgtmtT/Pw03iyl5MwbEqW
7YEHSkMyAcd1rjEpOCN2pJjjbrOKLePG0R2ffgvVJnTWGFklCxsJ1/7IASHst1wg1/gu1g
Kx/TEv+gOKpehAgs2Sz/4kZtFuHO2dbHYC3UrPR5HT8JnQWeCfiT0qwsVQ6xribw0jEYyd
ZBNWKkPdNocAbA==
-----END SSH SIGNATURE-----
SIG
end
before do
key.update!(key: public_key_text)
end
it_behaves_like 'verified signature'
end
context 'when signed text is an empty string' do
let(:signed_text) { '' }
let(:signature_text) do
<<~SIG
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgdnw0cScIikLiaei34FHG/ov5+r
5Oc3UKCxGsdYuZ/BsAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
AAAAQP2liwaQ44PC9oXf5Xzjq20WLdWEK9nyonvDGtduGUXMOL4yP5A6WvKz7kSt7Vba/U
MNK0nmnNc7Aokfh/2eRQE=
-----END SSH SIGNATURE-----
SIG
end
it_behaves_like 'verified signature'
end
context 'when signed text is nil' do
let(:signed_text) { nil }
let(:signature_text) do
<<~SIG
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgko5+o4fR8N175Rr/VI5uRcHUIQ
MXkzpR8BEylbcXzu4AAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx
OQAAAEC1y2I7o3KqKFlnM+MLkhIo+uRX3YQOYCqycfibyfvmkZTcwqMxgNBInBM9pY3VvS
sbW2iEdgz34agHbi+1BHIM
-----END SSH SIGNATURE-----
SIG
end
it_behaves_like 'unverified signature'
end
context 'when committer_email is empty' do
let(:committer_email) { '' }
it_behaves_like 'unverified signature'
end
context 'when committer_email is nil' do
let(:committer_email) { nil }
it_behaves_like 'unverified signature'
end
context 'when signature_text is empty' do
let(:signature_text) { '' }
it_behaves_like 'unverified signature'
end
context 'when signature_text is nil' do
let(:signature_text) { nil }
it_behaves_like 'unverified signature'
end
context 'when user email is not verified' do
before do
email = user.emails.find_by(email: committer_email)
email.update!(confirmed_at: nil)
user.update!(confirmed_at: nil)
end
it 'reports unverified status' do
expect(signature.verification_status).to eq(:unverified)
end
end
context 'when no user exist with the committer email' do
before do
user.delete
end
it 'reports other_user status' do
expect(signature.verification_status).to eq(:other_user)
end
end
context 'when no user exists with the committer email' do
let(:committer_email) { 'different-email+ssh-commit-test@example.com' }
it 'reports other_user status' do
expect(signature.verification_status).to eq(:other_user)
end
end
context 'when signature is invalid' do
let(:signature_text) do
# truncated base64
<<~SIG
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgko5+o4fR8N175Rr/VI5uRcHUIQ
MXkzpR8BEylbcXzu4AAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx
OQAAAECQa95KgBkgbMwIPNwHRjHu0WYrKvAc5O/FaBXlTDcPWQHi8WRDhbPNN6MqSYLg/S
-----END SSH SIGNATURE-----
SIG
end
it_behaves_like 'unverified signature'
end
context 'when signature is for a different namespace' do
let(:signature_text) do
<<~SIG
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgdnw0cScIikLiaei34FHG/ov5+r
5Oc3UKCxGsdYuZ/BsAAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx
OQAAAEAd6Psg4D/5IdSVTy35D4t2iNX4udJnX8JrUCjQl0GoPl1vzPjgyvxdzdoQl6bh1w
4rror3RuzUYBGzIioIc1MP
-----END SSH SIGNATURE-----
SIG
end
it_behaves_like 'unverified signature'
end
context 'when signature is for a different message' do
let(:signature_text) do
<<~SIG
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgQtog20+l2pMcPnuoaWXuNpw9u7
OzPnJzdLUon0+ELNQAAAAEZmlsZQAAAAAAAAAGc2hhNTEyAAAAUwAAAAtzc2gtZWQyNTUx
OQAAAEB3/B+6c3+XqEuqjiqlVQwQmUdj8WquROtkhdtScEOP8GXcGQx+aaQs5nq4ZJCuu5
ywcU+4xQaLVpCf7tfGWa4K
-----END SSH SIGNATURE-----
SIG
end
it_behaves_like 'unverified signature'
end
context 'when message has been tampered' do
let(:signed_text) do
<<~MSG
This message was signed by an ssh key
The pubkey fingerprint is SHA256:RjzeOilYHkiHqz5fefdnrWr8qn5nbroAisuuTMoH9PU
MSG
end
it_behaves_like 'unverified signature'
end
context 'when the signing key does not exist in GitLab' do
context 'when the key is not a signing one' do
before do
key.auth!
end
it 'reports unknown_key status' do
expect(signature.verification_status).to eq(:unknown_key)
end
end
context 'when the key is removed' do
before do
key.delete
end
it 'reports unknown_key status' do
expect(signature.verification_status).to eq(:unknown_key)
end
end
end
context 'when key belongs to someone other than the committer' do
let_it_be(:other_user) { create(:user, email: 'other-user@example.com') }
let(:committer_email) { other_user.email }
it 'reports other_user status' do
expect(signature.verification_status).to eq(:other_user)
end
end
end
describe '#key_fingerprint' do
it 'returns the pubkey sha256 fingerprint' do
expect(signature.key_fingerprint).to eq('dw7gPSvYtkCBU+BbTolbbckUEX3sL6NsGIJTQ4PYEnM')
end
end
end
|