The SupermarketPlanner is running a Raspberry Pi Rest Server. This allows me to create a list on the Windows Client and Sync over the Internet to either another Windows Client or an Android Phone App.
As I can potentially connect these over the Internet, I wanted to setup a secure connection between my clients and server. The obvious solution to this was to setup SSL.
With SSL, a private and public key is created. The public key is distributed in the form of a certificate. This can be used to encrypt a message that can only be decrypted with the private key.
When a client connects to a server this information can be used to setup a secure connection via a hand-shake mechanism, A good explanation can be found here.
For my purposes, since I am running both client and server, I decided on setting up a self-signed certificate. I used OpenSSL on my Raspberry Pi to create this, following the steps below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Create a Private key for the Certificate Authority | |
openssl genrsa -des3 -out myCA.key 2048 | |
Create the Certificate Authority cert | |
openssl req -x509 -new -nodes -key myCA.key -sha256 -days 3650 -out myCA.pem | |
Create Private key for Certificate | |
openssl req -new -key jsco-cert.key -out jsco.csr | |
Create certificate signing request | |
openssl req -new -key jsco-cert.key -out jsco.csr | |
Create new certificate, sign against Certificate Authority | |
openssl x509 -req -in jsco.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out jsco-cert.crt -days 365 -sha256 -extfile config.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
authorityKeyIdentifier=keyid,issuer | |
basicConstraints=CA:FALSE | |
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment | |
subjectAltName = @alt_names | |
[alt_names] | |
DNS.1 = jsco.hopto.org |
- Chrome
- Settings -> Advanced -> Privacy and Security -> Manage Certificates -> Authorities tab -> Import CA certificate
- Edge / IE11
- Open IE11
- Internet Options -> Content -> Certificates
- Select Trusted Root Certification Authorities
- Click Import and choose the CA certificate to upload
Enabling SSL on the web server is simple, using web.py I just needed to add the following (This for version 0.40):
As for my client applications, after adding my CA to IE11, SupermarketPlanner, being a .NET Core app running on Windows worked with no changes other than to use the https url. This worked on both Windows 10 and Windows 8.1 boxes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from cheroot.server import HTTPServer | |
from cheroot.ssl.builtin import BuiltinSSLAdapter | |
HTTPServer.ssl_adapter = BuiltinSSLAdapter( | |
certificate = './Certificates/jsco-cert.crt', | |
private_key = './Certificates/jsco-cert.key') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using (var client = new HttpClient()) | |
{ | |
var postData = new KeyValuePair<string, string>[] | |
{ | |
new KeyValuePair<string, string>("A Key", "Some Payload") | |
}; | |
var content = new FormUrlEncodedContent(postData); | |
// _serverUrl can be https://x.y.z:port | |
var response = await client.PostAsync(_serverUrl, content); | |
// Do something with response | |
} |
For my Android App this was a bit more complicated. When simply changing the URL I got this Exception:
CertPathValidatorException : Trust Anchor for Certification Path not found
The documentation here: https://developer.android.com/training/articles/security-ssl was what I needed for this problem. This allows me to add my own CA to the trust chain.
I added my CA certificate to the assets/ folder in the Android project. An assets folder is a location for files that are copied as-is to the .apk file. This can then be referenced and navigated as a normal directory using the AssetManager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected String getData(String... params) | |
{ | |
// Removed params checks | |
try | |
{ | |
if (!params[0].isEmpty()) | |
{ | |
m_restUrl += "?date=" + params[0]; | |
} | |
URL url = new URL(m_restUrl); | |
SSLContext sslContext = getSSLContext(); | |
HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection(); | |
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory()); | |
InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream()); | |
try (ByteArrayOutputStream result = new ByteArrayOutputStream()) | |
{ | |
byte[] buffer = new byte[1024]; | |
int length; | |
while ((length = inputStream.read(buffer)) != -1) | |
{ | |
result.write(buffer, 0, length); | |
} | |
output = result.toString("UTF-8"); | |
} finally | |
{ | |
urlConnection.disconnect(); | |
} | |
} catch (Exception ex) | |
{ | |
output = "ERROR: " + ex.getMessage(); | |
} | |
return output; | |
} | |
/** | |
* If we aren't using a public CA for the SSL connection we can trust the self-signed CA | |
* @return SSLContext that includes self-signed CA | |
*/ | |
private SSLContext getSSLContext() | |
{ | |
try { | |
CertificateFactory cf = CertificateFactory.getInstance("X.509"); | |
// Load the CA. I've included in the Assets folder | |
AssetManager assetManager = m_context.getAssets(); | |
InputStream caInput = assetManager.open("myCA.pem"); | |
Certificate ca; | |
try | |
{ | |
ca = cf.generateCertificate(caInput); | |
} | |
finally | |
{ | |
caInput.close(); | |
} | |
// Create a KeyStore containing our trusted CA | |
String keyStoreType = KeyStore.getDefaultType(); | |
KeyStore keyStore = KeyStore.getInstance(keyStoreType); | |
keyStore.load(null, null); | |
keyStore.setCertificateEntry("ca", ca); | |
// Create a TrustManager that trusts the CAs in our KeyStore | |
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); | |
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); | |
tmf.init(keyStore); | |
// Create an SSLContext that uses our TrustManager | |
SSLContext context = SSLContext.getInstance("TLS"); | |
context.init(null, tmf.getTrustManagers(), null); | |
return context; | |
} | |
catch(Exception ex) | |
{ | |
return null; | |
} | |
} |
No comments:
Post a Comment