Skip to content
This repository has been archived by the owner on Oct 6, 2024. It is now read-only.

Commit

Permalink
btfuse/fuse#27 enable HTTPS support
Browse files Browse the repository at this point in the history
  • Loading branch information
breautek committed Nov 18, 2023
1 parent b3874b3 commit f271576
Show file tree
Hide file tree
Showing 12 changed files with 654 additions and 45 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ limitations under the License.

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.2.0-rc02' apply false
id 'com.android.library' version '8.2.0-rc02' apply false
id 'com.android.application' version '8.2.0-rc03' apply false
id 'com.android.library' version '8.2.0-rc03' apply false
}

allprojects {
Expand Down
6 changes: 6 additions & 0 deletions fuse/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ android {
}

buildTypes {
debug {
minifyEnabled false
}

release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
Expand Down Expand Up @@ -125,6 +129,8 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.10.0'
implementation 'androidx.webkit:webkit:1.8.0'
implementation 'org.bouncycastle:bcprov-jdk18on:1.76'
implementation 'org.bouncycastle:bcpkix-jdk18on:1.76'

testImplementation 'junit:junit:4.13.2'

Expand Down
35 changes: 26 additions & 9 deletions fuse/src/androidTest/java/com/breautek/fuse/test/FuseAPITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@

import static org.junit.Assert.*;

import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;

@RunWith(AndroidJUnit4.class)
public class FuseAPITest {

Expand Down Expand Up @@ -62,14 +65,21 @@ public void canDoSimpleEchoRequest() {
int port = activity.getFuseContext().getAPIPort();
String secret = activity.getFuseContext().getAPISecret();

FuseTestAPIClient client = new FuseTestAPIClient.Builder()
FuseTestAPIClient client;
try {
client = new FuseTestAPIClient.Builder()
.setFuseContext(activity.getFuseContext())
.setAPIPort(port)
.setAPISecret(secret)
.setPluginID("echo")
.setType("text/plain")
.setEndpoint("/echo")
.setContent("Hello Test!")
.build();
}
catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new RuntimeException(e);
}

FuseTestAPIClient.FuseAPITestResponse response = client.execute();
assertEquals(200, response.getStatus());
Expand All @@ -83,14 +93,21 @@ public void canUseAnAPIThatSwitchesToMainThread() {
int port = activity.getFuseContext().getAPIPort();
String secret = activity.getFuseContext().getAPISecret();

FuseTestAPIClient client = new FuseTestAPIClient.Builder()
.setAPIPort(port)
.setAPISecret(secret)
.setPluginID("echo")
.setType("text/plain")
.setEndpoint("/threadtest")
.setContent("Hello Test!")
.build();
FuseTestAPIClient client;
try {
client = new FuseTestAPIClient.Builder()
.setFuseContext(activity.getFuseContext())
.setAPIPort(port)
.setAPISecret(secret)
.setPluginID("echo")
.setType("text/plain")
.setEndpoint("/threadtest")
.setContent("Hello Test!")
.build();
}
catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new RuntimeException(e);
}

FuseTestAPIClient.FuseAPITestResponse response = client.execute();
assertEquals(200, response.getStatus());
Expand Down
33 changes: 33 additions & 0 deletions fuse/src/main/java/com/breautek/fuse/FuseAPIConnection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@


/*
Copyright 2023 Breautek
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/


package com.breautek.fuse;

import java.net.Socket;

import javax.net.ssl.SSLEngine;

public class FuseAPIConnection {
SSLEngine $engine;
Socket $socket;

public FuseAPIConnection(SSLEngine engine, Socket socket) {

}
}
126 changes: 104 additions & 22 deletions fuse/src/main/java/com/breautek/fuse/FuseAPIServer.java
Original file line number Diff line number Diff line change
@@ -1,73 +1,142 @@

/*
Copyright 2023 Breautek
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package com.breautek.fuse;

import android.net.http.SslCertificate;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.operator.OperatorCreationException;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.security.SecureRandom;

public class FuseAPIServer {

public static final String TAG = "FuseAPIServer";

private final ServerSocket $httpServer;
private final SSLServerSocket $httpServer;
private final SSLContext $sslContext;
private final FuseContext $context;

private final FuseCertificateProvider $certProvider;
private final FuseCertificateProvider.FuseCertificate $certificate;

private final String UID_OID = "0.9.2342.19200300.100.1.1";

String $secret;

public FuseAPIServer(FuseContext context) throws IOException {
public FuseAPIServer(FuseContext context) throws IOException, NoSuchAlgorithmException, CertificateException, OperatorCreationException, KeyStoreException, UnrecoverableKeyException, KeyManagementException {
$sslContext = SSLContext.getInstance("TLS");

$certProvider = new FuseCertificateProvider();
$certProvider.install();
$certificate = $certProvider.generate();

KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(null, null);
keystore.setKeyEntry("fuse-api-certificate", $certificate.keypair.getPrivate(), null, new X509Certificate[]{$certificate.certificate});

KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keystore, null);

TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmFactory.init(keystore);

$sslContext.init(keyManagerFactory.getKeyManagers(), tmFactory.getTrustManagers(), null);

$context = context;
$secret = $generateSecret();
$httpServer = new ServerSocket(0);

SSLServerSocketFactory sslServerSocketFactory = $sslContext.getServerSocketFactory();

$httpServer = (SSLServerSocket) sslServerSocketFactory.createServerSocket(0);
$httpServer.setEnabledCipherSuites($httpServer.getSupportedCipherSuites());

FuseLogger logger = $context.getLogger();

Thread serverThread = new Thread(() -> {
try {
while (true) {
Socket client = $httpServer.accept();

new Thread(() -> {
try {
$handleConnection(client);
} catch (IOException e) {
}
catch (SSLException e) {
logger.error(TAG, "Client SSL Error: ", e);
try {
client.close();
}
catch (IOException ex) {
logger.error(TAG, "Client SSL Error: ", e);
}
}
catch (IOException e) {
logger.error(TAG, "Client Socket Error: ", e);
try {
client.close();
} catch (IOException ex) {
}
catch (IOException ex) {
logger.error(TAG, "Client Socket Error: ", e);
}
}
}).start();
}
} catch (IOException e) {
}
catch (IOException e) {
logger.error(TAG, "Socket Error:", e);
}
});

serverThread.start();
}

public SSLContext getSSLContext() {
return $sslContext;
}

public String getSecretKey() {
return $secret;
}

private String $generateSecret() {
SecureRandom secureRandom = new SecureRandom();

// Generate a random byte array of the desired length
int secretLength = 32; // Length in bytes
byte[] secretBytes = new byte[secretLength];
secureRandom.nextBytes(secretBytes);

// Convert the byte array to a hexadecimal string
StringBuilder stringBuilder = new StringBuilder();
for (byte b : secretBytes) {
stringBuilder.append(String.format("%02x", b));
}

return stringBuilder.toString();
return FuseSecretGenerator.generate();
}

private static class Header {
Expand Down Expand Up @@ -215,4 +284,17 @@ public Map<String, String> getHeaders() {
public int getPort() {
return $httpServer.getLocalPort();
}

public boolean verifyCertificate(SslCertificate certificate) {
X500Name dname = new X500Name(certificate.getIssuedTo().getDName());
RDN[] rdns = dname.getRDNs(new ASN1ObjectIdentifier(UID_OID));
if (rdns.length == 0) {
return false;
}

RDN uidAttr = rdns[0];
String uid = uidAttr.getFirst().getValue().toString();

return uid.equals(this.$certificate.signature);
}
}
Loading

0 comments on commit f271576

Please sign in to comment.