forked from PoC-Consortium/burstcoin
-
-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: SSL support or Websockets (#835)
* feat: SSL support or Websockets * feat: used native java implementation to convert let encrypt PEM to PKCS2 * chore: updated docs, and minor cleanups
- Loading branch information
Showing
9 changed files
with
280 additions
and
72 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,4 +29,5 @@ target | |
/build/ | ||
/bin/ | ||
launch.json | ||
|
||
*.pem | ||
*.p12 |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
# SSL Configuration | ||
|
||
## Signum Node - Local SSL Configuration | ||
|
||
This guide explains how to generate SSL certificates to run a Signum Node locally with HTTPS enabled. | ||
|
||
### Prerequisites | ||
|
||
Ensure you have `openssl` installed on your system. You can verify this by running the following command: | ||
|
||
```bash | ||
openssl version | ||
``` | ||
|
||
If not installed, you can install it using your package manager (e.g., `brew install openssl` on macOS, | ||
`sudo apt install openssl` on Ubuntu). | ||
|
||
### Steps to Generate SSL Certificates | ||
|
||
1. **Generate a private key** | ||
|
||
Use the following command to generate a private RSA key: | ||
|
||
```bash | ||
openssl genpkey -algorithm RSA -out localhost.pem | ||
``` | ||
|
||
2. **Generate a self-signed certificate** | ||
|
||
With the private key, create a self-signed certificate valid for 365 days: | ||
|
||
```bash | ||
openssl req -x509 -new -key localhost.pem -out localhost_chain.pem -days 365 | ||
``` | ||
|
||
You will be prompted to fill in some details like Country, State, and Common Name. For local development, you can use | ||
`localhost` as the Common Name (CN). | ||
|
||
3. **Generate a keystore** | ||
|
||
Finally, create a PKCS#12 keystore that bundles the private key and certificate together: | ||
|
||
```bash | ||
openssl pkcs12 -export -inkey localhost.pem -in localhost_chain.pem -out localhost_keystore.p12 -name "localhost" -password pass:development | ||
``` | ||
|
||
This creates a keystore named `localhost_keystore.p12` protected with the password `development`. | ||
|
||
### Update `node.properties` | ||
|
||
In your `node.properties` file, enable SSL for the API and point to the newly created keystore. Add or update the | ||
following lines: | ||
|
||
```properties | ||
API.SSL=on | ||
API.SSL_keyStorePath=./localhost_keystore.p12 | ||
API.SSL_keyStorePassword=development | ||
``` | ||
|
||
### Final Steps | ||
|
||
1. Restart the Signum Node to apply the changes. | ||
2. Your Signum Node should now be running locally with SSL enabled. | ||
|
||
You can access it using `https://localhost:<your_port>` and/or `wss://localhost:<your_port>/events` with the port number | ||
configured for your node. | ||
|
||
## Using Certbot to Generate SSL Certificates for a Signum Node for your custom domain | ||
|
||
Certbot is a tool used to automate the process of obtaining and renewing SSL certificates from Let's Encrypt or other | ||
Certificate Authorities. This guide explains how to use Certbot to generate SSL certificates for running a Signum Node | ||
locally. | ||
### Prerequisites | ||
1. **Certbot installation**: Ensure Certbot is installed. You can check by running: | ||
```bash | ||
certbot --version | ||
``` | ||
If it's not installed, follow the [official installation guide](https://certbot.eff.org/instructions) for your | ||
system. | ||
|
||
2. **Domain name**: To use Certbot, you need a publicly accessible domain (Certbot won't work for pure localhost | ||
setups). If you are running a local node accessible from the internet (e.g., via a reverse proxy like Nginx), you'll | ||
need a registered domain name pointing to your local machine. | ||
|
||
3. **Port forwarding (optional)**: If your node is not publicly accessible, you may need to set up port forwarding to | ||
allow Certbot to perform HTTP-01 or DNS-01 validation. | ||
|
||
### Request a certificate | ||
|
||
Run Certbot to obtain a certificate for your domain. Replace `yourdomain.com` with your actual domain name. | ||
|
||
```bash | ||
sudo certbot certonly --standalone -d yourdomain.com | ||
``` | ||
|
||
Certbot will generate the necessary files, including the certificate (`.crt`) and private key (`.key`). | ||
|
||
By default, these will be stored in `/etc/letsencrypt/live/yourdomain.com/`. | ||
|
||
> The Signum Node looks into the "letsencryptpath" and converts it to the necesary keystore file. No further action necessary here. | ||
|
||
### Update `node.properties` | ||
|
||
In your `node.properties` file, enable SSL for the API and configure the path to the Certbot-generated keystore: | ||
|
||
```properties | ||
API.SSL=on | ||
# the file name of your keystore file. Let's Encrypt Cert will be automatically converted and stored under this path. | ||
API.SSL_keyStorePath=./keystore.p12 | ||
API.SSL_keyStorePassword=<your_password> | ||
# your path of letsencrypt certs. The Node looks for "privkey.pem" and "fullchain.pem" files | ||
API.SSL_letsencryptPath=/etc/letsencrypt/live/<yourdomain>.com | ||
``` | ||
|
||
### Automating Certificate Renewal | ||
|
||
Certbot certificates expire every 90 days. You can automate the renewal process using Certbot's cron job feature. | ||
> Signum Nodes reloads the certificate on startup and/or every 7 days while running | ||
1. Set up a cron job to automatically renew certificates: | ||
```bash | ||
sudo crontab -e | ||
``` | ||
2. Add the following line to renew certificates automatically: | ||
```bash | ||
0 0 * * * certbot renew --quiet | ||
``` | ||
### Final Steps | ||
1. Restart your Signum Node after the certificate is created and the `node.properties` file is updated. | ||
2. Access the Signum Node using `https://yourdomain.com:<your_port>`. |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
node.network = signum.net.MockNetwork | ||
DB.SqliteJournalMode = WAL | ||
|
||
# Database connection JDBC url | ||
DB.Url=jdbc:sqlite:file:./db/signum-test.sqlite.db | ||
|
||
SoloMiningPassphrases="some phrase" | ||
AllowOtherSoloMiners=true |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
DB.SqliteJournalMode = WAL | ||
|
||
# Database connection JDBC url | ||
DB.Url=jdbc:sqlite:file:./db/signum-mock.sqlite.db |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package brs.web.server; | ||
|
||
import brs.props.Props; | ||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; | ||
import org.bouncycastle.cert.X509CertificateHolder; | ||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; | ||
import org.bouncycastle.jce.provider.BouncyCastleProvider; | ||
import org.bouncycastle.openssl.PEMParser; | ||
import org.eclipse.jetty.server.*; | ||
import org.eclipse.jetty.util.ssl.SslContextFactory; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.io.*; | ||
import java.security.*; | ||
import java.security.cert.X509Certificate; | ||
import java.security.spec.PKCS8EncodedKeySpec; | ||
import java.util.concurrent.Executors; | ||
import java.util.concurrent.ScheduledExecutorService; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
public abstract class AbstractServerConnectorBuilder { | ||
|
||
private static final Logger logger = LoggerFactory.getLogger(AbstractServerConnectorBuilder.class); | ||
protected final WebServerContext context; | ||
|
||
public AbstractServerConnectorBuilder(WebServerContext context) { | ||
this.context = context; | ||
} | ||
|
||
abstract ServerConnector build(Server server); | ||
|
||
protected ServerConnector createSSLConnector(Server server) { | ||
logger.info("Creating SSL Connector"); | ||
HttpConfiguration httpsConfig = new HttpConfiguration(); | ||
httpsConfig.setSecureScheme("https"); | ||
httpsConfig.setSecurePort(context.getPropertyService().getInt(Props.API_PORT)); | ||
httpsConfig.addCustomizer(new SecureRequestCustomizer()); | ||
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); | ||
|
||
sslContextFactory.setKeyStorePath(context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PATH)); | ||
sslContextFactory.setKeyStorePassword(context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PASSWORD)); | ||
|
||
// Handle optional Let's Encrypt Certificates... | ||
String letsencryptPath = context.getPropertyService().getString(Props.API_SSL_LETSENCRYPT_PATH); | ||
if (letsencryptPath != null && !letsencryptPath.isEmpty()) { | ||
try { | ||
loadLetsEncryptCertsAsPkcs12(letsencryptPath, context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PATH), context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PASSWORD)); | ||
} catch (Exception e) { | ||
logger.error(e.getMessage()); | ||
} | ||
|
||
// Reload the certificate every week, in case it was renewed | ||
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); | ||
Runnable reloadCert = () -> { | ||
try { | ||
loadLetsEncryptCertsAsPkcs12(letsencryptPath, context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PATH), context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PASSWORD)); | ||
sslContextFactory.reload(consumer -> logger.info("SSL keystore from letsencrypt reloaded.")); | ||
} catch (Exception e) { | ||
logger.error(e.getMessage()); | ||
} | ||
}; | ||
scheduler.scheduleWithFixedDelay(reloadCert, 7, 7, TimeUnit.DAYS); | ||
} | ||
|
||
String[] strongCiphers = { | ||
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", | ||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", | ||
// Add more strong ciphers as needed | ||
}; | ||
sslContextFactory.setIncludeCipherSuites(strongCiphers); | ||
sslContextFactory.setIncludeProtocols("TLSv1.2", "TLSv1.3"); | ||
sslContextFactory.setExcludeProtocols("SSLv3"); | ||
return new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"), | ||
new HttpConnectionFactory(httpsConfig)); | ||
} | ||
|
||
public void loadLetsEncryptCertsAsPkcs12(String letsencryptPath, String p12Filename, String password) throws Exception { | ||
|
||
logger.info("Converting Let's Encrypt Certificate to PKCS12..."); | ||
Security.addProvider(new BouncyCastleProvider()); | ||
|
||
try (InputStream keyIn = new FileInputStream(letsencryptPath + "/privkey.pem"); | ||
InputStream certIn = new FileInputStream(letsencryptPath + "/fullchain.pem")) { | ||
|
||
// Load PEM files | ||
PEMParser keyParser = new PEMParser(new InputStreamReader(keyIn)); | ||
PEMParser certParser = new PEMParser(new InputStreamReader(certIn)); | ||
|
||
// make keys compatible with java.security.keystore | ||
PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) keyParser.readObject(); | ||
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); | ||
PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded())); | ||
|
||
X509CertificateHolder certObj = (X509CertificateHolder) certParser.readObject(); | ||
JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter(); | ||
certConverter.setProvider("BC"); | ||
X509Certificate certificate = certConverter.getCertificate(certObj); | ||
X509Certificate[] certificates = new X509Certificate[]{certificate}; | ||
|
||
// Add the private key and certificates to the keystore | ||
KeyStore keyStore = KeyStore.getInstance("PKCS12"); | ||
keyStore.load(null, null); | ||
keyStore.setKeyEntry("SIGNUM_NODE_CERT", privateKey, password.toCharArray(), certificates); | ||
|
||
// Finally, save as PKCS12 file... | ||
try (OutputStream out = new FileOutputStream(p12Filename)) { | ||
keyStore.store(out, password.toCharArray()); | ||
logger.info("Let's Encrypt Certificate successfully converted to PKCS12 and saved under: {}", p12Filename); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.