DISTRIBUTED SYSTEMS API
Distributed Systems ACW
2023/24
You have been hired as a distributed systems programmer after Gribbald, their previous expert disappeared one foggy
night. You’ve been promised that your first job for them won’t be very hard and, thankfully, it’s really not. The task is
to develop a client/server, using ASP.NET Core Web API, which provides a number of extra secret security and
encryption services… sort of.
You’re lucky though. Gribbald was working on this project before you and has already created an outline solution with
some ‘TODO’ regions, based on the original specification. They have also broken down the specification into tasks for
you, which should make your life even easier!
On your first day you were handed this specification:
• The server must be able to handle multiple client requests simultaneously.
• The server must use Entity Framework, Code First, to create and manage a local database of Users (that will,
in the future be moved over to a production database).
• The client must be able to get a TalkBack/Hello message from the server. The server will respond with “Hello
World”.
• The client must be able to send a TalkBack/Sort message as a get request to the server with an array of
integers as parameters. The server will sort the integers into ascending order and return the sorted array to
the client, because – well, sorting data is tedious.
• The client must be able to send a User/New get request to the server which has a username string as a
parameter. The server must return a string identifying if the username already exists in the database.
• The client must be able to send a User/New post request to the server which has a username string in the
request body. The server must create a new user, generate a new GUID as an API Key, save the user to the
database and return the API Key to the user. If this is the first user they should be saved as Admin role,
otherwise just with User role.
• The client must be able to send a User/RemoveUser delete request to the server with an API Key in the header
and a string username in the URI. If the server receives this request, it must check to see if the API Key is in
the database and, if it is, it must check that the username and API Key are the same user and if they are, it
must delete this user from the database.
• The client must be able to send a User/ChangeRole Post request to the server with an API Key in the header
which links to an Admin role user, a string username in the body and a string role in the body. If the server
receives this request, it must check to see if the API Key is in the database and whether the role is Admin, if it
is, it must update the role for the given username to the role provided (User or Admin)
• The client must be able to send a Protected/Hello get request to the server with an API Key in the header. If
the server receives this request, it must check to see if the API Key is in the database and, if it is, it must get
the username associated with the API Key and send back “Hello <username>” (e.g. “Hello UserOne”).
• The client must be able to send a Protected/SHA1 get request to the server with an API Key in the header and
a string message as a parameter. If the server receives this request, it must check to see if the API Key is in
the database and, if it is, it must compute the SHA1 hash of the message and return it in hexadecimal form to
the client.
• Somebody told the boss that SHA256 is more secure than SHA1 so the client must be able to send a
Protected/SHA256 get request to the server with an API Key in the header and a string message as a
parameter. If the server receives this request, it must check to see if the API Key is in the database and, if it
is, it must compute the SHA256 hash of the message and return it in hexadecimal form to the client.
2
• The client must be able to send a Protected/GetPublicKey get request to the server with an API Key in the
header. If the server receives this request, it must check to see if the API Key is in the database and, if it is, it
must send back its RSA public key.
• The client must be able to send a Protected/Sign request with an API Key in the header and a string message
as a parameter. If the server receives this request, it must check to see if the API Key is in the database and,
if it is, it must digitally sign the message with its private RSA key and send the signed message back to the
client. The client must then be able to verify that the server signed the message by using the server’s public
RSA key.
• Finally, the client must be able to send a Protected/Mashify get request to the server with an API Key in the
header which links to an Admin role user, and three parameters comprising:
o A string of text, encrypted using the server’s public RSA key
o A symmetric key (using AES encryption), encrypted using the server’s public RSA key
o An IV (initialization vector) for the symmetric key, also encrypted using the server’s public RSA key
If the server receives this request, it must…
o check to see if the API Key is in the database and whether the role is Admin,
o if it is, it must decrypt all three parameters using its private RSA key.
o It must then ‘mashify’ the string it was given (see task 14), encrypt the mashified string using the
client’s symmetric (AES) key and IV and
o finally, it must send the newly encrypted string back to the client.
The client must then be able to…
o decrypt the string using its symmetric (AES) key and IV and
o finally, output the new mashified string to the console.
Your task is to follow the next instructions carefully and develop a console-based client and Web API-Based server with
a Code First Entity Framework Database.
➢ You must use the skeleton solution as your starting point, and
➢ You must ensure that you carefully follow instructions on request types and names, parameter naming,
responses and response types – if you do not conform to these instructions, some of the marking tools will
not be able to find your code and you will receive a mark of 0 in affected areas.
Please do not, under any circumstances, publish your work on a public source code repository – even
after you leave the University. We have had issues in the past with students using public repositories (e.g.
on GitHub) and their work being plagairised. In these circumstances it becomes very difficult to ascertain
who was cheating and both parties may be penalised for collusion under the academic misconduct policy,
which can have severe academic consequences. Furthermore, the skeleton code remains the intellectual
property of the University of Hull and you are not permitted to share this publicly. You may, however, use
any of the solution in your future work and share your solution (including the skeleton code) privately (e.g.
a repository set to private) with prospective employers, etc., as you wish.
3
Instructions
Download the skeleton solution from Canvas and unzip it. You may delete or modify any of the code that has already
been written if you want, but for the most part you will only need to add to it. Note how there are a number of regions
and comments that identify tasks by number – use these as a guide to identify where you might want to add your code.
The Server
The server that you have been given in the skeleton solution is an ASP.NET Web API. You may find it useful to refer to
the Microsoft documentation on Web API. You should also recognise it from labs and lecture material.
There are a few things you should look at before you start working on the server:
1. Open TalkBackController.cs – Gribbald started writing this controller before they mysteriously disappeared. It
contains two get request actions for /API/TalkBack/Hello and /API/Talkback/Sort. Task1 will be to complete
these methods, but they should also be a guide to help you get started – you may change any of the code you
feel needs to be changed.
2. Open Auth->CustomAuthenticationHandler.cs – this is custom authentication middleware that is added inside
the ConfigureServices method inside Startup.cs. This means that whenever an HTTP request is made, this
method will run before the request gets to your controller. You will need to modify this method in TASK5 to
verify that the API Key in the header is correct and authenticate the user. There is a similar
CustomAuthorizationHandler – middleware that runs immediately after authentication if the authentication
succeeds. You will need to make changes to this code in TASK6. The use of these handlers is directly linked
to the Authorize attribute.
3. Open BaseController.cs – you should note four things:
a. This is an abstract class. You cannot make an instance of this controller.
b. It inherits from ControllerBase and has the attribute ApiController – all Controllers need to descend
from ControllerBase and have the attribute ApiController.
c. It stores a protected instance of the Database Context that you’ll need to use for your data access.
d. The route mapping has been changed to api/{controller}/{action} which will allow you to call
actions inside your controller (e.g. /API/TalkBack/Hello, where TalkBack is the controller and Hello
is the action).
BaseController is an abstract base class you should inherit from to retain the above functionality in your
controllers. If you open TalkbackController.cs you can see that it inherits from BaseController. To make your
life easier use this as a template for any new controllers you create.
Before you start working on your server, you may find it useful to use a tool which allows you to craft requests (with
control over the header/body/URI/etc.) send them to your server for debugging purposes and receive RESTful
responses. This means you won’t need to have a working client to test your server out. A suitable (and free) tool is
PostMan: https://www.getpostman.com/apps
Finally, I strongly suggest you use the test server (prototype). This server responds as per this coursework
specification. If your server responds in the same way as the test then you are likely to get a good mark for the
server elements of this coursework and if your client works properly with the test server then you can be confident
it is working properly too. You may use Postman to analyse the responses from the test server before you start
producing your server. There’s more info at the end of TASK10 but the test server can be accessed by pointing
your client to:
http://150.237.94.9/<myUniqueCode>/
e.g. http://150.237.94.9/1234567/Api/TalkBack/Hello
Your unique 7 digit code should have been distributed to you via email already but if you have not received this
then email me: john.dixon@hull.ac.uk
4
Tasks
The following tasks are designed to help you identify exactly what to do to meet the
specification and they give a logical order for doing it too. You must ensure that your
server and client respond exactly as specified to get the marks for that section, so
read this specification very carefully. Most tasks also have a section dedicated to
testing so that you can test your solution against expected results. If your server and
client do not respond exactly as specified you will not receive marks for the
corresponding marking criteria.
Task1: [3 marks]
When the client makes a get request for api/TalkBack/Hello or api/TalkBack/Sort, the talkback controller must
handle the responses.
Complete these methods so that they offer the responses detailed in the specification.
Once you have created these methods, right-click the WebAPI project, select Debug and click Start New Instance.
This should fire up IIS, open your browser and take you to a web page with an address similar to localhost:24702
where 24702 is your portnumber. Identify what this port number is as it will be required in your client and for
testing.
For your personal testing you can identify if your server is working by sending a get request with the URI (replace <portnumber> with
your port number) of:
For Hello: localhost:<portnumber>/api/talkback/hello
Should return "Hello World" in the body of the result with a status code of OK (200)
For Sort: localhost:<portnumber>/api/talkback/sort?integers=8&integers=2&integers=5
Should return [2,5,8] in the body of the result with a status code of OK (200)
If there are no integers submitted, the result should be [] with a status code of OK (200)
If the submitted integers are invalid (e.g. a char is submitted) the server should return with a with a status code of BAD
REQUEST (400)
Task2:
In order to work with databases, you have been asked to use Entity Framework Core.
Gribbald has already put all of the references, etc. that you will need into the project. You just need to define a
User class.
Open User.cs, where you will find the Task2 region. Complete the User class with:
• An empty constructor
• A public string ApiKey property (which is the unique database key and is the API Key for the user)
• A public string UserName property (which is the username of the user)
• A public string or enum Role property (which saves the role of the user)
UserContext.cs has been created for you already, but to create the database correctly you’ll need to generate a
migration*.
*You may first need to set the project directory by typing cd DistSysAcw into the Package Manager Console
5
Task3:
You should keep your controller classes only loosely coupled to the database access code. Ensuring your code is
nicely abstracted is good coding practice as it will allow you to easily swap out the data access logic in the future
without editing your controllers. You may like to create these data access functions now.
For the next tasks, you may need database access such as:
1. Create a new user, using a username given as a parameter and creating a new GUID which is saved as a
string to the database as the ApiKey. This must return the ApiKey or the User object so that the server can
pass the Key back to the client.
2. Check if a user with a given ApiKey string exists in the database, returning true or false.
3. Check if a user with a given ApiKey and UserName exists in the database, returning true or false.
4. Check if a user with a given ApiKey string exists in the database, returning the User object.
5. Delete a user with a given ApiKey from the database
Hint: The BaseController already contains an instance of UserContext
Consider: What happens in your solution if two admin users simultaneously try to change a user’s role?
Task4:
When the client makes a api/User/New get request or api/User/New post request, the server must be able to
handle them and provide a response.
Create a new controller called UserController. The easiest way to do this is by right-clicking on Controllers and
adding a new, Empty API Controller. You may want to inspect the existing TalkBack Controller to help set up your
new controller – note it’s base type, constructor and routing.
Both of these actions have the name ‘New’ but one is a GET request that takes its parameter from the URI and
the other is a POST request that takes a JSON string parameter from the body. The POST request must use a
content-type of application/json. Note the difference between a JSON string and a JSON Object with a string.
For your personal testing you can identify if your server is working by sending a get request with the URI (replace <portnumber> with
your port number) of:
For GET: localhost:<portnumber>/api/user/new?username=UserOne
If a user with the username ‘UserOne’ exists in the database, the server should return "True - User Does Exist!
Did you mean to do a POST to create a new user?" in the body of the result with a status code of OK (200)
If a user with the username ‘UserOne’ does not exist in the database, the server should return "False - User Does
Not Exist! Did you mean to do a POST to create a new user?" in the body of the result with a status
code of OK (200).
If there is no string submitted, the server should return "False - User Does Not Exist! Did you mean to do
a POST to create a new user?" in the body of the result with a status code of OK (200).
For POST: localhost:<portnumber>/api/user/new with only “UserOne” in the body of the request
Should create a new User with the username ‘UserOne’, generate a new GUID as the user’s API Key, and then add the
new user to the database. Finally, the server should return the API Key as a string to the client with a status code of OK
(200). If this is the first user they should be saved as Admin role, otherwise just with User role.
If there is no string submitted in the body, the result should be "Oops. Make sure your body contains a string
with your username and your Content-Type is Content-Type:application/json" with a status code
of BAD REQUEST (400)
If the username is alrady taken, the result should be "Oops. This username is already in use. Please try
again with a new username." with a status code of FORBIDDEN (403)
6
Task5:
The client now has the ability to ask for an API Key, so our server will now need to be able to determine if a request
has a valid API Key in its header. A useful way to do this is to use an Authentication Scheme. Ours is a custom
Authentication Scheme so we can investige its functionality. Return to CustomAuthenticationHandler.cs. You must
add code to HandleAuthenticateAsync which tries to get the header ‘ApiKey’, and if it does exist, checks the
database to determine if the given API Key is valid. You should use your UserDatabaseAccess class that you
created in TASK3 to do the database access to loosen your coupling.
If the API Key is valid, you must get the relevant User from your database and set up a:
• Claim of type ClaimTypes.Name, using the user’s UserName as the string value
• Claim of type ClaimTypes.Role, using the user’s Role as the string value
• ClaimsIdentity with an authentication type of “ApiKey”, using an array containing both of your Claims
• ClaimsPrinciple, using the ClaimsIdentity above.
Finally, you must create an AuthenticationTicket using the ClaimsPrinciple and the scheme name (you can get this
using the property: this.Scheme.Name). You can now use the ticket to create a Success AuthenticateResult and
return as a Task.
If this is working, you should be able to use attributes such as [Authorize(Roles = "Admin")] or
[Authorize(Roles = "Admin, User")] to authorise requests which require a valid API key to be present.
If a user is not found, return a Fail AuthenticateResult and pass a 401 error with the JSON string "Unauthorized.
Check ApiKey in Header is correct." back to the user (see HandleChallengeAsync).
Task6:
So far we haven’t used any of our Roles. Before we can do this we’ll need to check that our users have the roles
they are supposed to. We are already looking at whether or not they have a valid API key (Task5), and we’re using
that easily by applying an attribute, so we’ll use a filter to modify the response.
Most of the filter has already been written. Open CustomAuthorizationHandler.cs and modify the code so that,
when the action requires a user to be in Admin role ONLY (e.g. [Authorize(Roles = "Admin")]) and the user
does not have the Admin role, you return a Forbidden status (403) with the message: "Forbidden. Admin access
only.". You will need the injected IHttpContextAccessor for this.
Task7:
Add to your UserController a method to handle an api/User/RemoveUser DELETE request.
• A user with role User or Admin should be able to use this request.
• The client must send its API Key in the header and a string username in the URI.
If the server receives this request, it must extract the ApiKey string from the header to see if the API Key is in the
database and, if it is, it must check that the username and API Key are the same user and if they are, it must
delete this user from the database. You should probably use your UserDatabaseAccess class that you created in
TASK3 to do the database access.
This method must return a Boolean value only. If a user has been deleted, the server must return true, otherwise,
the server must return false. In both cases, the server must return a status code of OK (200).
For your personal testing you can identify if your server is working by sending a delete request with the URI (replace <portnumber>
with your port number and <username> with a username) of:
RemoveUser: localhost:<portnumber>/api/user/removeuser?username=<username> with an ApiKey in the header of
the request
Should return true if the ApiKey and username match, are valid, and a user has been deleted or false if not.
7
Task8:
Inside UserController Add the api/User/ChangeRole method.
This method must only be accessible to users who are authorised by API key and have the role of Admin.
Update the role for the given username to the role provided (User or Admin).
The body should contain a JSON object in the form (where <username> is the given username string and <role>
is the given role string):
{ "username":<username>, "role":<role> }
For your personal testing you can identify if your server is working by sending a post request with the URI (replace <portnumber> with
your port number) of:
ChangeRole: localhost:<portnumber>/api/user/changerole with an ApiKey in the header of the request, a string username
in the body and a string role in the body
If success: Should return "DONE" in the body of the result, with a status code of OK (200)
If username does not exist: Should return "NOT DONE: Username does not exist" in the body of the result, with
a status code of BAD REQUEST (400)
If role is not User or Admin: Should return "NOT DONE: Role does not exist" in the body of the result, with a
status code of BAD REQUEST (400)
In all other error cases: Should return "NOT DONE: An error occured" in the body of the result, with a status code
of BAD REQUEST (400)
Task9:
Create a new ProtectedController. Add the api/Protected/Hello method, api/Protected/SHA1 method and
api/Protected/SHA256 method.
All of these requests must be authorised (User or Admin role) and all three must return strings to the client.
You may use the .NET SHA1 and SHA256 types for SHA1 and SHA256 hashing respectively.
Both SHA1 and SHA256 methods must take a string message from the URI and both must return the hexadecimal
hash as a string with no additional characters (e.g. no delimiters like -)
For your personal testing you can identify if your server is working by sending a get request with the URI (replace <portnumber> with
your port number) of:
For Hello: localhost:<portnumber>/api/protected/hello with an ApiKey in the header of the request
Should return "Hello <UserName>" in the body of the result, where UserName is the User’s UserName from the
database, with a status code of OK (200). E.g. “Hello UserOne”.
For SHA1: localhost:<portnumber>/api/protected/sha1?message=hello
Should return "AAF4C61DDCC5E8A2DABEDE0F3B482CD9AEA9434D" in the body of the result with a status code of OK
(200)
If there is no message string submitted, the result should be "Bad Request" with a status code of BAD REQUEST
(400)
For SHA256: localhost:<portnumber>/api/protected/sha256?message=hello
Should return "2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824" in the body of the
result with a status code of OK (200)
If there is no message string submitted, the result should be "Bad Request" with a status code of BAD REQUEST
(400)
8
Task10:
If you have not already started, begin to write the client (in project DistSysAcwClient) now as the next server tasks
are the most difficult and you should get the basic functionality of your client working before you attempt them.
The client must be a console application. It would make sense to use System.Net.Http.HttpClient. You don’t need
to use HttpClient but your client must be able to perform all of the same functions. Your client must request and
expect a JSON response type.
When your client is opened it should show: “Hello. What would you like to do?” and wait for user input.
Below is what the client must be able to do…
Input String
from Console
Window
Example Example Request Response Client
Function
Output to
Console
Window
TalkBack Hello TalkBack Hello localhost:<portnumber>/api/talkback/
hello
string None Response
string
TalkBack Sort
<integer array>
TalkBack Sort [6,1,8,4,3] localhost:<portnumber>/api/talkback/
sort?integers=6&integers=1&integers=
8&integers=4&integers=3
int[] None Response
as a string
User Get
<name>
User Get UserOne localhost:<portnumber>/api/user/new
?username=UserOne
string None Response
string
User Post
<name>
User Post UserOne localhost:<portnumber>/api/user/new
with “UserOne” in the request body
string Check status
of response.
Store API Key
and
username as
variables if
response: OK
“Got API
Key” if OK,
else print
response
string.
User Set
<name>
<apikey>
User Set UserOne
004b620d-a523-4b1b8bdc-857d8a5541b9
None – this function is only in the client n/a Store given
API Key and
username as
variables
“Stored”
User Delete User Delete Client must get the locally stored
username and ApiKey. If they don’t yet
exist the console must print “You need
to do a User Post or User Set first”.
localhost:<portnumber>/api/user/remo
veuser?username=<username>
(with <username> in the URI)
with ApiKey:<apikey> in the header
Boolean None “True” if
delete
succeeded,
otherwise
“False”
User Role
<username>
<role>
User Role UserOne
Admin
Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first
localhost:<portnumber>/api/user/chan
gerole
with ApiKey:<apikey> in the header
and with the JSON object:
{
“username” : “UserOne”,
“role” : “Admin”
}
in the request body
string None Response
as a string
Protected Hello Protected Hello Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first”.
string None Response
string
9
localhost:<portnumber>/api/protected/
hello
with ApiKey:<apikey> in the header
Protected SHA1
<message>
Protected SHA1 Hello Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first
localhost:<portnumber>/api/protected/
sha1?message=Hello
with ApiKey:<apikey> in the header
string None Response
String
Protected
SHA256
<message>
Protected SHA256 Hello Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first”.
localhost:<portnumber>/api/protected/
sha256?message=Hello
with ApiKey:<apikey> in the header
string None Response
String
If an error is returned, from your server, you must write out the error to the console or write out the given error
string if one is specified in the Output to Console Window column. The Console must output error messages and
never crash.
The client application should be asynchronous. The console must write “…please wait…” to the console window as
the request is sent and then write the output once the asynchronous Task has been completed. After the output
has been written, the console must write (on a new line) “What would you like to do next?”. When a new input is
entered, the Console Window must be cleared.
If the user enters “Exit” (without quotes), the console must close.
Whilst you do not have to, you may wish to have the console read/write to a file to save a valid UserName and
ApiKey, so that you don’t have to do a User Post or User Set every time you restart your application to test any of
the requests that require a username or ApiKey. If you do this, you should ensure that the software will run on
another computer.
Hint: you may find some of the information on this page useful: https://docs.microsoft.com/enus/dotnet/csharp/tutorials/console-webapiclient
10
**TEST SERVER**
As specified previously in this document, there is a test server from which to test your client and check that your
server responds in the same way. This test server acts as the ‘gold standard’. If your server responds in the same
way you can be certain that you will get the associated mark(s). You are very much encouraged to use the test
server as you will receive no marks for client functionality which does not work against the test server (even if it
works against your server).
I would very much advise you to create a single place in your client-side code (e.g. a const) which stores the first
part of your URL and then allows you to revert to using your server by changing only one line or config file.
The test domain is: http://150.237.94.9/<myUniqueCode>/
e.g. http://150.237.94.9/1234567/Api/TalkBack/Hello
Your unique 7 digit code should have been distributed via email but if you have not received this or you find a bug
in the test server prototype then please email me: john.dixon@hull.ac.uk. I would recommend you don’t share your
code as it provides data isolation - someone else editing your data may interfere with your testing.
You should ensure that when you hand in your solution it is configured to access and use your local server, not
the test server.
➢ There is an additional useful command on the test server. You can run this command from any browser or
from Postman so you don’t need to implement it into your Client:
http://150.237.94.9/<myUniqueCode>/Api/Other/Clear
This command clears all users and logs from your account on the test server, refreshing it back to empty. This may
help during testing. You do not need to replicate this command on your server/client unless you want to; there are
no marks for other/clear.
11
Task11:
First, set up an RSA Crypto Service Provider which is configured once and the same across all threads. You must
not make a new key pair for each client but you may generate a new key pair each time the server starts. The
private RSA key must be stored securely (it would be a good idea to use the machine key store as we are using
Windows).
Now, returning the public key should be as simple as returning the XmlString created by your RSA Crypto Service
Provider, containing the public RSA key for your server.
This request must be authorised (User or Admin role).
On receiving an api/Protected/GetPublicKey get request, the server must check to see if the API Key is in the
database and, if it is, it must send back its RSA public key.
Remember not to send over the private key!!
For your personal testing you can identify if your server is working by sending a get request with the URI (replace <portnumber> with
your port number) of:
GetPublicKey: localhost:<portnumber>/api/protected/getpublickey with an ApiKey in the header of the request
Should return the XmlString containing the public key, which should look something like this:
"<RSAKeyValue><Modulus>rSJEEeLC4H452XFL+taho/473M5OKdfSC/PKNFk55xOx/M5HbDxG9ihoENpazG7OHsGit
b0aXfn2qEVzhaaTtUJUKyO+sV2nQ6aaE0rwFUK0XxttFX1ann/d3qNTrlVxWdzygGq8ODn7qzvijcXjR/S2iyEguSNOuwhU
O/M98sk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>"
Now add the functionality to your client which will allow it to store the public key. You can store the key however
you like, but it must be usable when you need to encrypt something to send it to the server.
Input String
from Console
Window
Example Request Response Client
Function
Output to
Console
Window
Protected Get
PublicKey
Protected Get PublicKey Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first”.
localhost:<portnumber>/api/protected/
getpublickey
with ApiKey:<apikey> in the header
string Store the
public key
Xml (however
you like – can
be as a
variable or to
file)
“Got Public
Key” if a
key was
returned or
“Couldn’t
Get the
Public Key”
if an error
occurs
12
Task12:
If the server receives an api/Protected/Sign request with an API Key in the header and a string message as a
parameter it must check to see if the API Key is in the database and, if it is, it must digitally sign the message with
its private RSA key (using SHA1 hash in the signing), and send the signed message back to the client in a
hexadecimal format. The string to sign must be sent as it is given by the user, should be ASCII encoded and should
not be modified prior to signing/verification.
The hex string must include dashes as delimiters (-) between each hex value.
This request must be authorised (User or Admin role).
For your personal testing you can identify if your server is working by sending a get request with the URI (replace <portnumber> with
your port number) of:
Sign: localhost:<portnumber>/api/protected/sign?message=Hello with an ApiKey in the header of the request
Should return the signed message, e.g.
"98-4B-61-F0-77-3F-93-81-27-B0-E3-02-C2-38-56-FD-77-57-BC-E5-6E-43-D7-1E-59-EA-F6-E7-9C-8E-F5-F1-1C-06-C1-
8B-E4-C8-E0-E5-7D-DE-BE-99-A8-15-C3-8F-F6-2B-1D-43-2D-C2-33-90-17-FB-D4-D7-B9-15-44-77-5C-C0-01-D8-40-
76-15-DC-5B-E8-CF-CA-F6-33-1E-C1-DC-4B-CE-D4-38-71-46-6E-97-C0-C4-E8-0A-B0-90-32-55-18-7C-06-1C-AE-0A-06-
3F-3D-3B-B5-FE-B4-C7-FA-91-9A-23-1D-04-6A-35-0D-29-78-FA-4C-D1-C8-6A-D5"
Now add the functionality to your client to allow it to make a Sign request. On receipt of the response, the client
must verify that the server signed the message by using the server’s public RSA key.
Input String
from Console
Window
Example Request Response Client
Function
Output to
Console Window
Protected Sign
<message>
Protected
Sign Hello
Client must get the locally stored
ApiKey. If it doesn’t yet exist the
console must print “You need to do
a User Post or User Set first”.
localhost:<portnumber>/api/protec
ted/sign?message=Hello
with ApiKey:<apikey> in the header
string Use the
server’s
public key to
verify that the
message was
signed by the
server.
“Message was
successfully signed” if
signed response was
valid, “Message was not
successfully signed” if
signed response was
invalid, “Client doesn’t
yet have the public key”
if the client doesn’t know
the server public key yet.
TEST YOUR CLIENT AGAINST THE TEST SERVER – IF THIS ELEMENT DOES NOT FUNCTION WHEN TESTED AGAINTST
THE TEST SERVER YOU WILL NOT RECEIVE MARKS FOR YOUR CLIENT OR SERVER IMPLEMENTATION!
13
Task13:
The boss has been in, and is really impressed with your work so far, but is worried that the company won’t know
who is doing what on the server. They have asked you to add a new table to the database that stores logs.
Each User must have a collection of Log entries which must be accessible through User.Logs but all logs must
be saved in the same table on the database. A log must comprise LogID (unique primary key for the log), LogString
(the text describing what happened) and LogDateTime (the date and time of the log). Although you should note
that your database should be created using Code-First, the model for this is simply :
You will need to create a new Class (Log), create a virtual ICollection of Logs in your User class and add a Logs
DbSet to the context you already have.
You must then go back to all request handlers which require an API key to be passed in the header and add a log
to the relevant User identifying what request they made (e.g. “User requested /Protected/Hello”) and giving the
current DateTime.Now. You can leave the database to automatically generate the LogId.
You may find it useful to create another public constructor for Log that takes a string and creates a new Log object
with the passed-in string and DateTime.Now. Remember that you’ll have to add the log to a User’s collection of
logs and save the database. It would be good practice to do this work in the UserDatabaseAccess class you created
earlier.
Next, you’ll need to create a new database migration and update your database so that you can begin logging.
• Be aware that if a user is deleted, the logs should remain. The logs should still be linked to a user API key
(even if the user is deleted) for security reasons. You may decide how to do this but a good option would
be to create a new table: Log_Archives.
Finally, you should check everything you’ve coded so far still works as expected and make any changes if it does
not.
14
Task14:
If you haven’t already… you should start writing your report (see TASK15) before attempting this task.
Add a final method to your ProtectedController that handles an api/Protected/Mashify get request. The request
must be authenticated with an API Key in the header, may only be used by users with an Admin role and must
contain three parameters comprising:
• An string, encrypted using the server’s public RSA key, in hexadecimal format
• A symmetric key (using AES encryption), encrypted using the server’s public RSA key, in hexadecimal
format
• An IV (initialization vector) for the symmetric key, also encrypted using the server’s public RSA key, in
hexadecimal format
These three hex strings must include dashes as delimiters (-) between each hex value.
Note that you must use the padding mode RSAEncryptionPadding.OaepSHA1 for encryption and decryption
using RSA*.
You should add logging for this request (but you must not compromise any encrypted data in your log).
When the server receives this request, it must: check to see if the API Key is in the database and, if it is, it must:
• Decrypt all three parameters using its private RSA key.
• Then ‘mashify’ the string it was given (see below)
• Then encrypt the mashified string using the client’s symmetric (AES) key and IV.
Finally, it must send the newly encrypted string back to the client as a hexadecimal string.
This hex string must also include dashes as delimiters (-) between each hex value.
Mashify-ing a string:
To mashify a string, follow the below steps:
1. Convert all vowels into the upper-case character ‘X’
2. Reverse the string
Ensure that you keep all spaces, symbols and upper/lower case characters.
Example mashified strings:
Input string: Hello World
Mashified: dlrXW XllXH
Input string: I ate 12 cucumbers in one hour! Now I have a stomach ache!
Mashified: !XhcX hcXmXts X XvXh X wXN !rXXh XnX nX srXbmXcXc 21 XtX X
* Why OaepSHA1? Wouldn’t OaepSHA256 or something be more secure? Yes it would. However, there are a few ways to implement
the RSA algorithm in .NET Core. and using OaepSHA1 should work for all of them – so hopefully this minimises any clashes between
implementations.
15
For your personal testing, I very much recommend that you first write the client side in the server so that you can easily insert
breakpoints and test it before you need to start sending ‘stuff’ over the network. Otherwise, you will likely only be able to identify if
your server is working by using your client to send a get request.
Here is an example to show you what the request/response looks like, although it won’t work for you as it will use a different RSA key
pair from your solution.
Mashify: localhost:<portnumber>/api/protected/mashify?encryptedString=29-BA-14-64-0C-A4-33-7F-F7-1F-DA-7D-86-89-
2B-E7-E7-2F-49-4A-09-06-AF-11-27-2A-52-C8-82-68-A1-92-67-DC-E4-07-5F-CC-FB-AA-15-B7-63-ED-27-1A-66-DF-9A6B-39-AC-9B-62-32-50-F6-BB-33-ED-05-23-89-E0-E3-87-23-0B-F3-F6-72-D7-AA-25-AE-A7-BB-7B-12-F7-22-0C-B3-79-
12-FC-48-00-23-D1-BB-6D-6F-29-79-32-8F-5F-85-69-10-AF-93-C9-67-46-06-5C-1B-8D-1A-BB-89-C4-8D-13-76-AB-AF14-D6-BE-23-3C-D8-CB-15-28&encryptedSymKey=20-F7-EA-B5-FC-9E-74-05-98-B9-A8-9B-AF-AD-95-B2-D0-B6-81-66-
16-43-D8-AE-05-F1-FE-79-9D-35-A6-A8-B3-77-C0-0A-6C-99-51-2D-1A-61-90-42-E8-2B-F5-64-2D-C2-3A-8A-96-33-1C70-2B-99-31-CB-F0-7B-3D-4D-72-7F-9D-30-D2-E9-1F-7D-72-B0-F3-83-76-2D-77-61-32-4F-25-16-86-B1-CB-75-47-71-
E9-D6-C4-C9-C4-DD-86-44-28-DC-19-7E-88-15-C2-25-10-6A-9B-59-CB-65-15-8D-6F-11-DA-46-80-DA-DC-55-07-A0-EBD1-8B-27&encryptedIV=15-56-04-17-5E-A1-95-D0-C5-BD-32-FE-94-5B-C1-BA-C6-32-BC-E3-93-50-FC-39-8A-86-C4-FAC5-8E-A9-92-A7-8D-51-66-54-00-95-D7-05-9D-99-98-D4-BD-1B-A3-67-A8-95-82-E1-C7-02-F4-2E-3D-A4-96-0D-F1-22-
99-A1-66-8C-C8-79-D9-68-58-99-B5-04-0F-2E-80-E4-C0-07-F5-46-9E-AE-36-1A-43-6A-24-9E-63-CC-8A-10-26-21-53-
0E-85-0E-B9-F7-CC-F3-70-7A-5B-6C-27-00-E2-B4-92-44-BE-91-0F-5C-5D-A1-3F-D4-1D-39-52-07-69
with an ApiKey in the header of the request
Should return the given string mashified and then encrypted with the given AES key, as a hexadecimal string
e.g. “0E-94-10-D7-44-A1-1C-D6-86-21-A2-47-0A-4A-AA-73”
If an error occurs, the result should be "Bad Request" with a status code of BAD REQUEST (400)
The client must then be able to decrypt the string using its symmetric (AES) key and IV, and finally output the new
string to the console.
Input String
from Console
Window
Example Request Response Client
Function
Output to
Console
Window
Protected
Mashify
<string>
Protected Mashify Hello
World!
Client must get the locally stored
ApiKey. If it doesn’t yet exist the console
must print “You need to do a User Post
or User Set first”. Client must then
generate and store a new AES key and
IV. It must now encrypt <string>, the
AES key and the AES IV, using the
public key, which must have already
been requested from the server.
Remember to encrypt using the
OaepSHA1 padding mode.
“Client doesn’t yet have the public key”
must be written to the console if the
client doesn’t know the server public
key yet.
localhost:<portnumber>/api/protected/
mashify?encryptedString=<encryptedstr
ing>&encryptedsymkey=<encryptedAES
key>&encryptedIV=<encryptedAESiv>
with ApiKey:<apikey> in the header
string Decrypt the
returned hex
using the AES
key and IV
that was
previously
generated
Decrypted
string which
should be
“!dlrXW
XllXH” in
this
example.
“An error
occurred!”
if the
returned
hex is not a
valid string
TEST YOUR CLIENT AGAINST THE TEST SERVER – IF THIS ELEMENT DOES NOT FUNCTION WHEN TESTED AGAINTST
THE TEST SERVER YOU WILL NOT RECEIVE MARKS FOR YOUR CLIENT OR SERVER IMPLEMENTATION!
16
Task15 (Report) – 10%
You will need to write and submit up to 1,500 words in a report addressing these points:
1. Outline what your API is and does, how it manages requests and discuss why this API is stateless. Describe
the difference behaviours your server would exhibit if it had been stateful.
2. Briefly explain how you have implemented Route Mapping and specify how else you could have done it
using the ASP.NET WebAPI framework.
3. Briefly outline the different RESTful request methods you have used are and provide screenshots of where
you have used these requests in your server project to illustrate your written work.
4. Briefly describe how your Server and Client use the API key. Ensure you discuss the Authentication,
Authorization and middleware in your server. Identify if you think an API key is a good or bad option for
identifying users, giving your reasons. Is the API key safe for your solution? How would you ensure this API
key was kept safe if you were developing this Server/Client in the ‘real world’?
5. Briefly describe how you have used Entity Framework and how your code is loosely coupled.
6. Finally, write a short reflective statement about:
a. which tasks you completed and to what level,
b. any problems you had with any of the implementation/functionality, and
c. how you overcame these problems (if you managed to).
NOTE:
You must ensure that your report is written by you, is free of plagiarism and that any text you paraphrase or
quotes you use are appropriately referenced using the University of Hull Harvard referencing style. More
information on this and advice on writing reports is available here: https://libguides.hull.ac.uk/skillsguides
You may like to use bulletpoints to be concise in your report and save words.
Submission:
This coursework is to be submitted via Canvas. To submit your coursework:
1. Clean your solution by right-clicking on the solution in Solution Explorer (Visual Studio) and choosing Clean.
2. You must place your Visual Studio solution files in a single folder. Ensure you submit the entire solution.
3. Add an electronic copy (preferably PDF) of your report into the folder
4. ZIP the folder (not 7Zip, RAR, etc.). This ZIP file must then be submitted via Canvas.
请加QQ:99515681 邮箱:99515681@qq.com WX:codinghelp