/
Server.pod6
248 lines (165 loc) · 6.67 KB
/
Server.pod6
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
use v6.c;
=begin pod
=TITLE role Auth::SCRAM::Server
=SUBTITLE Server side authentication using SCRAM
unit package Auth;
class SCRAM::Server { ... }
=head1 Synopsis
To do authentication on a server is a more complex process. The server needs to
have some sort of user database to store the information in whiteout having the
risk that passwords are stolen. The server role helps to convert the username
and password into a credential tupple which can be stored without the risk that
the password can be retrieved from this data. Below is a simple implementation
of the necessary items. The methods C<get-the-users-database()>
and C<generate-a-proper-salt()> are not implemented here.
=begin code
class Credentials {
has Hash $!credentials-db;
has Auth::SCRAM $!scram handles <start-scram>;
=end code
That might be the initial setup. Now lets initialize ...
=begin code
# A user credentials database used to store added users to the system
# Credentials must be read from somewhere and saved to the same somewhere.
submethod BUILD ( ) {
$!scram .= new(:server-object(self));
$!credentials-db = self!get-the-users-database();
}
=end code
Then there is a need to add a user. This method will ask the server role to
generate the tupple and store it in the database.
=begin code
method add-user ( $username is copy, $password is copy ) {
my Buf $salt .= self!generate-a-proper-salt();
for $!scram.generate-user-credentials(
:$username, :$password, :$salt, :iter(4096), :server-object(self)
) -> $u, %h {
$!credentials-db{$u} = %h;
}
}
=end code
The rest of the class is needed in the authentication process
=begin code
method credentials ( Str $username, Str $authzid --> Hash ) {
return $!credentials-db{$username};
}
# return server first message to client, then receive and
# return client final response
method server-first ( Str:D $server-first-message --> Str ) {
is $server-first-message,
'r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096',
$server-first-message;
< c=biws
r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j
p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
>.join(',');
}
# return server final message
method server-final ( Str:D $server-final-message --> Str ) {
is $server-final-message,
'v=rmF9pqV8S7suAoZWja4dJRkFsKQ=',
$server-final-message;
'';
}
method error ( Str:D $message ) {
}
}
=end code
Then initialize the server. Suppose the server knows about only two things,
adding a user and authenticating a user. This will look someething like the code
below. Once the idea is understood, more useful commands can be added.
=begin code
# Server actions in advance ...
# - set up shop
my Credentials $crd .= new;
# - set up socket
# - listen to socket and wait
# - input from client
# - fork process, parent returns to listening on socket
# - child processes input for commands
# - command: add a user
# receive the password and password preferable over a secure connection
my Str $user = '...';
my Str $password = '...'
$crd.add-user( $user, $password);
# - command: autenticate
# receive client first message from client
my Str $client-first-message = "...";
my Str $emsg = $crd.start-scram(:$client-first-message);
=end code
=head1 Read and writable attributes
These attributes must be set before scram authentication is started.
=head2 client nonce
has Int $.c-nonce-size is rw = 24;
has Str $.c-nonce is rw;
Define a nonce. The result must be a hexadecimal string of the proper size
generated by a base64 encoding operation. When not set, the class will makeup
one of the default length of 24 octets and encode in base64.
$!c-nonce = encode-base64(
Buf.new((for ^$!c-nonce-size { (rand * 256).Int })),
:str
);
Normally it will not be needed to make one yourself. It was made available for
testing purposes.
=head2 server nonce
The server nonce can be modified too in the same way. Also for these attributes,
it is not needed to use them.
has Int $.s-nonce-size is rw = 18;
has Str $.s-nonce is rw;
The default is generated like so.
$!s-nonce = encode-base64(
Buf.new((for ^$!s-nonce-size { (rand * 256).Int })),
:str
);
=head2 extension data
has Str $.reserved-mext is rw;
has Hash $.extensions is rw = %();
These variables are not yet used in this module.
=head1 Methods
=head2 generate-user-credentials
method generate-user-credentials (
Str :$username, Str :$password,
Buf :$salt, Int :$iter,
Any :$server-object
--> List
)
When adding a user to the database this method helps creating the credential
data needed to authenticate a user later. The method returns the normalized
username(enforced profile) and a hash with the keys;
=item1 B<iter>. Number of iterations needed to generate the derived key
=item1 B<salt>. Salt used to generate that key. Both iter and salt are provided
by the user
=item B<stored-key>. See rfc5802.
=item B<server-key>. See rfc5802.
When the method C<mangle-password> is defined, that method will be called for
this process.
=head2 start-scram
method start-scram( Str:D :$client-first-message --> Str )
Start authentication. The authentication process is started by the user, so the
server must receive the first client message before starting the scram process.
The calls to the user provided client object are as follows;
=item1 B<server-first>. After processing the client first message, the server
first message must be send back to the client. As a result, the method must
return the client final message.
method server-first ( Str:D $server-first-message --> Str )
=item1 B<server-final>. Purpose is to send the server final message to the
client. If there is an error, return the error message. If successfull, return
the empty string.
method server-final ( Str:D $server-final-message --> Str )
=item1 B<credentials>. This method is called several times to get user
information. The method must return the same data returned from
C<generate-user-credentials>.
method credentials ( Str $username --> Hash )
=item1 B<mext>. Not yet implemented.
=item1 B<extension>. Not yet implemented.
=item1 B<authzid>. Not yet implemented.
=item1 B<error>. The error method is called with a message whenever there is
something wrong. The message is prefixed wit 'e=' and must be send back to the
client. The procedure is then terminated after returning from the error method
and the procedure will return the same message to the caller of C<start-scram>.
method error ( Str:D $message )
=item1 B<cleanup>. This optional method is called when all ends successfully.
After returning from this method it returns an empty string('') to the caller of
C<start-scram>
method cleanup()
=end pod