Create a self-signed certificate
Preconditions:send messages)
Tasks-Change your existing http server to https server-Implement a basic user authenticationBackground reading:Classes needed in the implementation:
com.sun.net.httpserver.HttpsServe
com.sun.net.httpserver.HttpsConfigurator
com.sun.net.httpserver.BasicAuthenticator
com.sun.net.httpserver.HttpHandler
New tools to learn:
https://docs.oracle.com/en/java/javase/21/docs//specs/man/keytool.html
Instructions
Step 1 - Create a self-signed certificate
You can create the certificate with following command:keytool -genkey -alias alias -keyalg RSA -keystore keystore.jks -keysize 2048
Password should be strong but you need to remember as you need it later. The keytool will also ask yourname, organization, location information etc. As your first and last name, you must give “localhost” withoutthe quotation marks.The command creates keystore.jks in to the folder you are currently in. Copy the keystore.jks in to yourserver project folder.The keytool is part of the java jdk and if thecommand cannot be found, you need to either:add it to your PATH environment variable so you can use it easily just by writing “keytool”, or-write the whole path before the “keytool” so it is then found and executed.Previous assignment completed (instructions assume that you have a basic http server with a capability to receive andWeek 13.10-15.10Step 2 – Configure server to use TLS/HTTPS
Remember to read the oracle documentation about http server listed in background reading section in firstpage.
After reading the documentation, simply copy the code just right after the sentence: “A simple exampleSSLContext could be created as follows:” and paste it to a new function called (such as, you can rename it ifyou want):private static SSLContext myServerSSLContext() throws Exception
After that, you need to:-
Import the necessary classes to the .java file (shown in errors since they are unknown to IDE beforemporting them).
-“passphrase” - replace this with the password you gave in Step 1 when you created your self signedcertificate.-
“testkeys” - replace this with the key file name you used in the Step 1 — if you followed the
instructions, it would be “keystore.jks”.
-As the function should return a SSLContext, do that. You already have the SSLContext object ssl inthe code you copied. Return that to the callerNote that putting password directly to the code is not generally a good idea. There are better ways toprovide the passwords but for the coursework this is enough as we are testing our server locally.Step 3 – Changing HTTP server to HTTPS server
Next, change the HttpServer to HttpsServer (see the difference!) and import that class. You can nowremove the HttpServer import since it is no longer needed.Next thing you need to do is to add code to main() to actually use the function you implemented above.Right after you created the HttpsServer, add this line:SSLContext sslContext = myServerSSLContext();So you now call the method you just implemented to create a SSLContext for your HTTPS server, using theself signed certificate you created in the Step 1.Configure the HttpsServer to use the sslContext by adding this call to setHttpsConfigurator, which you givethe sslContext you just created. Add this code right after the line above.
rver.setHttpsConfigurator (new HttpsConfigurator(sslContext) {
public void configure (HttpsParameters params) {
InetSocketAddress remote = params.getClientAddress();
SSLCotext c = getSSLContext();
SSLParameters sslparams = c.getDefaultSSLParameters();
params.setSSLParameters(sslparams);
}
});
Step 4 – Error handling
You need to add a try/catch structure to the main function to catch errors that might happen during codeexecution. You can always just throw the error and let the operating system sort the ending of the problembut that is bad for the person who uses and tries to understand why your program failed. Therefore, we usethem to inform what went wrong and possibly log the information to a logger. For now, we just use consoleprint.If not yet there, add a try catch structure in the main, call all of the code you have there currently in the tryblock, and in the catch block, print out exception information, for example, just simply:
} catch (Exception e) {
e.printStackTrace();
}
You might want to catch different exceptions in different catch blocks to do some more fine grained error
handling, for example:
} catch (FileNotFoundException e) {
// Certificate file not found!
System.out.println(“Certificate not found!”);
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
More advanced logging and error handling will be discussed later during the course.
Step 5 – Test with curl
Note that by default, curl does not accept self signed certificates from a server. So you need to tell curl to
accept them. Use an option -k (or longer --insecure). For more information, see
https://curl.se/docs/sslcerts.html
Send a HTTPS POST request to the server, use:
UNIX:
curl -k -d 'Kauppatori' https://localhost:8001/info -H 'Content-Type: text/plain’Or if you are using windows, you might need to replace the ‘ -> “
curl -k -d “Kauppatori” https://localhost:8001/info -H “Content-Type: text/plain”And receive what you just sent to the server with command:
curl -k https://localhost:8001/infoYou may use the curl's -trace-ascii out.txt option to see how the client (curl) and the server communicate.
Check the generated out.txt -file for details.
Step 6 – basic authentication
The second goal of this exercise is to add Basic HTTP authentication to the server. After this, users cannotPOST or GET any messages unless authenticated.
Create new file called UserAuthenticator.java and add a new class to the server project, calledUserAuthenticator. It must extend com.sun.net.httpserver.BasicAuthenticator.We do not yet have a place to store user credentials. It will be implemented in a later exercise. For now,add a member variable Map<String,String> to hold the usernames and passwords to theUserAuthenticator. Set it to null when you declare it. You could name the member variable as “users”:private Map<String,String> users = null;
Create a constructor method for the UserAuthenticator class. In the beginning, call super(),
BasicAuthenticator’s constructor, giving “info” as the realm to apply the authentication to.
Also, in the constructor, instantiate the “users” map to be a Hashtable<String,String> (Map is just an
interface class, and Hashtable implements that interface). Then finally, add one dummy user in
UserAuthenticator’s constructor to the “users” map:
users = new Hashtable<String,String>();
users.put("dummy", "passwd");
Finally, implement the checkCredentials method inherited from the BasicAuthenticator class. There, you
should check if, in the Hashmap, there is a username/password that matches the ones in the parameters of
checkCredentials. If yes, return true, otherwise return false.
Now you have a simple basis for an authenticator for the server. Later, users can register as users to the
server, and the credentials they send are added to the Hashtable. Even later in the course, you will actually
store this user information into a database, instead of using a Hashmap.
But where do you call these methods to verify the user? You do not call them directly. Instead, you tell to
the context created earlier in the Exercise 1 to use this authenticator when serving client requests.
In the Server’s main method, just before creating the context, create an instance of the UserAuthenticator.
You already have the code to create the context by calling server.createContext. Change this so that you
take the return value of server.createContext function (we did not care about that earlier) to a HttpContext
variable. Then set the authenticator for that context by calling setAuthenticator, giving your
UserAuthenticator as a parameter. Step 7 – Test the authentication
In curl you need to authenticate using the same username and password you created in the
UserAuthenticator (assuming user “dummy” and password “passwd”, you may have something different
there):
curl -u dummy:passwd -k https://localhost:8001/info
Note the -u (as in user) option and the username and the password given. The same applies obviously to
POSTing messages, since the same basic authentication is applied to that realm too.
UNIX:
curl -u dummy:passwd -k -d 'Yliopisto' https://localhost:8001/info -H 'Content-Type: text/plain’
WINDOWS:
curl -u dummy:passwd -k -d “Nallikari” https://localhost:8001/info -H “Content-Type: text/plain”
Step 8 – Functionality for users to register
For registration, create a new HttpHandler with a new realm (or path) “/registration”. This is needed as info
-realm expects the user to be authenticated and therefore it cannot accept new users (as they do not have
the credentials yet).
Users can then register via this https: /localhost:8001/registration address, since the authentication
implemented in Step 6 only applies to the “info” realm. So we are not going to set an authenticator for this
new HttpHandler with “registration” realm. What you need to do is:
-
Create a new class RegistrationHandler, which (like Server.java if you followed the course example)
implements HttpHandler interface.
-
Create a constructor for RegistrationHandler, that takes as a parameter a UserAuthenticator object.
-
RegistrationHandler also needs to have a UserAuthenticator as a member variable so add it there.
-
In the constructor of RegistrationHandler, assign UserAuthenticator to the member variable,
because the RegistrationHandler must be able to use the UserAuthenticator later when handling
POST requests from clients to authenticate users.
-
Handle HTTP POST messages from clients, checking content type and content length etc. (see steps
below).
o But do not handle HTTP GET in RegistrationHandler! We do not want to provide any user
information to the clients through this non-authenticated path — the client could be
malicious, attempting to fish user information from registered users through this path that
is not using authorization! You can respond to HTTP GET requests with error 400 and
response body “Not supported”.
-
For now, we assume that the clients send the registration information in the HTTP POST request
body as a simple string “username:password”. (Later all content between client and the server will
be JSON structures).
-
Read the HTTP POST request body and parse the content. You can easily separate the two parts of
“username:password” using Java String class’ split() method.
-
Add the new user to UserAuthenticator from the two strings you got from calling String.split().
Obviously you want to check that the splitted string array has really got two strings, and if not, a response to client is needed that data was not OK. You need to add a new method to the
UserAuthenticator:
public boolean addUser(String userName, String password) {
// TODO implement this by adding user to the Hashtable
}
Note: do not allow registering same username twice! This would actually lead to a situation where theexisting user’s password is changed to the new one, since theata structure here is a Map. Map can onlyhold one key value, assigning a new element with a same key effectively just changes the value of the keyvalue pair -- thepassword! So check that the key does not exist and only then add the new user to the hash
able.Changing user credentials for an existing user should happen elsewhere (not in this handler) so that user isproperly authenticated before allowing changing user data! Remember, users are not authenticated usingHTTP Basic authentication when using the / registration URLpath!
So if the username is already in the Hashtable, return false to the RegistrationHandler, which should passthe error to the client in the response! You can select which HTTP error code to pass to client, but forexample 403, 405 might be good ones. Response body canfurther clarify to client what went wrong, forexample: "User already registered”.
Finally, in the Server’s main function, instantiate the user registration Handler, when creating a new
“/registration” Context
Step 9 – Test user registration with curl
You should be able to register new users with curl like this:
UNIX
curl -k -d 'dummyuser2:stillapassword' https://localhost:8001/registration -H 'ContentType: text/plain’
WINDOWS
curl -k -d “dummyuser2:stillapassword” https://localhost:8001/registration -H “ContentType: text/plain”
Note the path “/registration” in the address, and that content to the HTTP POST now has the username andpassword separated by “:”Test also sending new locations with the created user.
Step 10 – sending the code to gitlabAfter finishing your testing, make sure you replace the keystore location and password with args[0] andargs[1] to allow the gitlab pipeline to replace both keystore location and password with server generatedone. Your code will not compile if this has not been done (NOTE: in some cases it might actually work due keystore being part of your commit and the password hardcoded in your code (especially if the keystorelocation and path is in project root folder), this might work but is unintended and can cause problems infuture). 代 写Create a self-signed certificate Additional tasks (refactoring code):-Create a handler -class and move the handler implementation from Server class to it if you haven’tdone so. It makes the project and code more clean. (For example HandleLocation -class,HandleRegistrations -class, etc…) Markus edit: unnecessary instructions, the original reference wasto refactor the code IF the handle -class was implemented as part of the Server.java class where themain function was also present due examples.Troubleshootingkeytool is not found.
-As explained in the instructions, find the JDK bin directory where it should be. Add that path to thecomputers PATH environment variable, or launch the toolwitfullpath to the keytool binary.When server launches, it cannot find the certificate file.-If the file is without path, place it in the server root directory and launch the server from commandline in that directory using command java -jar target/your-jarfile.jar.
-You can put the certificate file name with full path information, so it is found.