diff --git a/.env b/.env new file mode 100644 index 0000000..6995156 --- /dev/null +++ b/.env @@ -0,0 +1,57 @@ +MPROXY_MQTT_WITHOUT_TLS_ADDRESS=:1884 +MPROXY_MQTT_WITHOUT_TLS_TARGET=localhost:1883 + +MPROXY_MQTT_WITH_TLS_ADDRESS=:8883 +MPROXY_MQTT_WITH_TLS_TARGET=localhost:1883 +MPROXY_MQTT_WITH_TLS_CERT_FILE=ssl/certs/server.crt +MPROXY_MQTT_WITH_TLS_KEY_FILE=ssl/certs/server.key +MPROXY_MQTT_WITH_TLS_SERVER_CA_FILE=ssl/certs/ca.crt + +MPROXY_MQTT_WITH_MTLS_ADDRESS=:8884 +MPROXY_MQTT_WITH_MTLS_TARGET=localhost:1883 +MPROXY_MQTT_WITH_MTLS_CERT_FILE=ssl/certs/server.crt +MPROXY_MQTT_WITH_MTLS_KEY_FILE=ssl/certs/server.key +MPROXY_MQTT_WITH_MTLS_SERVER_CA_FILE=ssl/certs/ca.crt +MPROXY_MQTT_WITH_MTLS_CLIENT_CA_FILE=ssl/certs/ca.crt +MPROXY_MQTT_WITH_MTLS_CERT_VERIFICATION_METHODS=ocsp +MPROXY_MQTT_WITH_MTLS_OCSP_RESPONDER_URL=http://localhost:8080/ocsp + +MPROXY_MQTT_WS_WITHOUT_TLS_ADDRESS=:8083 +MPROXY_MQTT_WS_WITHOUT_TLS_TARGET=ws://localhost:8000/ + +MPROXY_MQTT_WS_WITH_TLS_ADDRESS=:8084 +MPROXY_MQTT_WS_WITH_TLS_TARGET=ws://localhost:8000/ +MPROXY_MQTT_WS_WITH_TLS_CERT_FILE=ssl/certs/server.crt +MPROXY_MQTT_WS_WITH_TLS_KEY_FILE=ssl/certs/server.key +MPROXY_MQTT_WS_WITH_TLS_SERVER_CA_FILE=ssl/certs/ca.crt + +MPROXY_MQTT_WS_WITH_MTLS_ADDRESS=:8085 +MPROXY_MQTT_WS_WITH_MTLS_PREFIX_PATH=/mqtt +MPROXY_MQTT_WS_WITH_MTLS_TARGET=ws://localhost:8000/ +MPROXY_MQTT_WS_WITH_MTLS_CERT_FILE=ssl/certs/server.crt +MPROXY_MQTT_WS_WITH_MTLS_KEY_FILE=ssl/certs/server.key +MPROXY_MQTT_WS_WITH_MTLS_SERVER_CA_FILE=ssl/certs/ca.crt +MPROXY_MQTT_WS_WITH_MTLS_CLIENT_CA_FILE=ssl/certs/ca.crt +MPROXY_MQTT_WS_WITH_MTLS_CERT_VERIFICATION_METHODS=ocsp +MPROXY_MQTT_WS_WITH_MTLS_OCSP_RESPONDER_URL=http://localhost:8080/ocsp + +MPROXY_HTTP_WITHOUT_TLS_ADDRESS=:8086 +MPROXY_HTTP_WITHOUT_TLS_PREFIX_PATH=/messages +MPROXY_HTTP_WITHOUT_TLS_TARGET=http://localhost:8888/ + +MPROXY_HTTP_WITH_TLS_ADDRESS=:8087 +MPROXY_HTTP_WITH_TLS_PREFIX_PATH=/messages +MPROXY_HTTP_WITH_TLS_TARGET=http://localhost:8888/ +MPROXY_HTTP_WITH_TLS_CERT_FILE=ssl/certs/server.crt +MPROXY_HTTP_WITH_TLS_KEY_FILE=ssl/certs/server.key +MPROXY_HTTP_WITH_TLS_SERVER_CA_FILE=ssl/certs/ca.crt + +MPROXY_HTTP_WITH_MTLS_ADDRESS=:8088 +MPROXY_HTTP_WITH_MTLS_PREFIX_PATH=/messages +MPROXY_HTTP_WITH_MTLS_TARGET=http://localhost:8888/ +MPROXY_HTTP_WITH_MTLS_CERT_FILE=ssl/certs/server.crt +MPROXY_HTTP_WITH_MTLS_KEY_FILE=ssl/certs/server.key +MPROXY_HTTP_WITH_MTLS_SERVER_CA_FILE=ssl/certs/ca.crt +MPROXY_HTTP_WITH_MTLS_CLIENT_CA_FILE=ssl/certs/ca.crt +MPROXY_HTTP_WITH_MTLS_CERT_VERIFICATION_METHODS=ocsp +MPROXY_HTTP_WITH_MTLS_OCSP_RESPONDER_URL=http://localhost:8080/ocsp diff --git a/.golangci.yml b/.golangci.yml index dd4ea8d..f0589c8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -12,7 +12,6 @@ linters-settings: importas: no-unaliased: true no-extra-aliases: false - gocritic: enabled-checks: - captLocal diff --git a/README.md b/README.md index ff36f12..0b1ba49 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ It is deployed in front of MQTT broker and can be used for authorization, packet logging and debugging and various other purposes. ## Usage + ```bash go get github.com/absmach/mproxy cd $(GOPATH)/github.com/absmach/mproxy @@ -17,18 +18,20 @@ make ``` ## Architecture + mProxy starts TCP and WS servers, offering connections to devices. Upon the connection, it establishes a session with a remote MQTT broker. It then pipes packets from devices to MQTT broker, inspecting or modifying them as they flow through proxy. Here is the flow in more details: + - Device connects to mProxy's TCP server - mProxy accepts the inbound (IN) connection and estabishes a new session with remote MQTT broker -(i.e. it dials out to MQTT broker only once it accepted new connection from a device. -This way one device-mProxy connection corresponds to one mProxy-MQTT broker connection.) + (i.e. it dials out to MQTT broker only once it accepted new connection from a device. + This way one device-mProxy connection corresponds to one mProxy-MQTT broker connection.) - mProxy then spawn 2 goroutines: one that will read incoming packets from device-mProxy socket (INBOUND or UPLINK), -inspect them (calling event handlers) and write them to mProxy-broker socket (forwarding them towards the broker) -and other that will be reading MQTT broker responses from mProxy-broker socket and writing them towards device, -in device-mProxy socket (OUTBOUND or DOWNLINK). + inspect them (calling event handlers) and write them to mProxy-broker socket (forwarding them towards the broker) + and other that will be reading MQTT broker responses from mProxy-broker socket and writing them towards device, + in device-mProxy socket (OUTBOUND or DOWNLINK).

@@ -38,74 +41,293 @@ Event handlers should implement the following interface defined in [pkg/mqtt/eve ```go // Handler is an interface for mProxy hooks type Handler interface { - // Authorization on client `CONNECT` - // Each of the params are passed by reference, so that it can be changed - AuthConnect(ctx context.Context) error + // Authorization on client `CONNECT` + // Each of the params are passed by reference, so that it can be changed + AuthConnect(ctx context.Context) error - // Authorization on client `PUBLISH` - // Topic is passed by reference, so that it can be modified - AuthPublish(ctx context.Context, topic *string, payload *[]byte) error + // Authorization on client `PUBLISH` + // Topic is passed by reference, so that it can be modified + AuthPublish(ctx context.Context, topic *string, payload *[]byte) error - // Authorization on client `SUBSCRIBE` - // Topics are passed by reference, so that they can be modified - AuthSubscribe(ctx context.Context, topics *[]string) error + // Authorization on client `SUBSCRIBE` + // Topics are passed by reference, so that they can be modified + AuthSubscribe(ctx context.Context, topics *[]string) error - // After client successfully connected - Connect(ctx context.Context) + // After client successfully connected + Connect(ctx context.Context) - // After client successfully published - Publish(ctx context.Context, topic *string, payload *[]byte) + // After client successfully published + Publish(ctx context.Context, topic *string, payload *[]byte) - // After client successfully subscribed - Subscribe(ctx context.Context, topics *[]string) + // After client successfully subscribed + Subscribe(ctx context.Context, topics *[]string) - // After client unsubscribed - Unsubscribe(ctx context.Context, topics *[]string) + // After client unsubscribed + Unsubscribe(ctx context.Context, topics *[]string) - // Disconnect on connection with client lost - Disconnect(ctx context.Context) + // Disconnect on connection with client lost + Disconnect(ctx context.Context) } ``` An example of implementation is given [here](examples/simple/simple.go), alongside with it's [`main()` function](cmd/main.go). ## Deployment -mProxy does not do load balancing - just pure and simple proxying. This is why it should be deployed + +mProxy does not do load balancing - just pure and simple proxying with TLS termination. This is why it should be deployed right in front of it's corresponding MQTT broker instance: one mProxy for each MQTT broker instance in the MQTT cluster. Usually this is done by deploying mProxy as a side-car in the same Kubernetes pod alongside with MQTT broker instance (MQTT cluster node).

-TLS termination and LB tasks can be offloaded to a standard ingress proxy - for example NginX. +LB tasks can be offloaded to a standard ingress proxy - for example NginX. + +## Example Setup & Testing of mProxy + +### Requirements + +- Golang +- Mosquitto MQTT Server +- Mosquitto Publisher and Subscriber Client + +### Example Setup of mProxy + +mProxy is used to proxy requests to a backend server. For the example setup, we will use Mosquitto server as the backend for MQTT, and MQTT over Websocket and an HTTP echo server for HTTP. + +1. Start the Mosquitto MQTT Server with the following command. This bash script will initiate the Mosquitto MQTT server with Websocket support. The Mosquitto Server will listen for MQTT connections on port 1883 and MQTT over Websocket connections on port 8000. + + ```bash + examples/server/mosquitto/server.sh + ``` + +2. Start the HTTP Echo Server: + + ```bash + go run examples/server/http-echo/main.go + ``` + +3. Start the OCSP/CRL Mock responder: + + ```bash + go run examples/ocsp-crl-responder/main.go + ``` + +4. Start the example mProxy servers for various protocols: + + ```bash + go run cmd/main.go + ``` + + The cmd/main.go Go program initializes mProxy servers for the following protocols: + + - mProxy server for `MQTT` protocol `without TLS` on port `1884` + - mProxy server for `MQTT` protocol `with TLS` on port `8883` + - mProxy server for `MQTT` protocol `with mTLS` on port `8884` + - mProxy server for `MQTT over Websocket without TLS` on port `8083` + - mProxy server for `MQTT over Websocket with TLS` on port `8084` + - mProxy server for `MQTT over Websocket with mTLS` on port `8085` with prefix path `/mqtt` + - mProxy server for `HTTP protocol without TLS` on port `8086` with prefix path `/messages` + - mProxy server for `HTTP protocol with TLS` on port `8087` with prefix path `/messages` + - mProxy server for `HTTP protocol with mTLS` on port `8088` with prefix path `/messages` + +### Example testing of mProxy + +#### Test mProxy server for MQTT protocols + +Bash scripts available in `examples/client/mqtt` directory helps to test the mProxy servers running for MQTT protocols + +- Script to test mProxy server running at port 1884 for MQTT without TLS + + ```bash + examples/client/mqtt/without_tls.sh + ``` + +- Script to test mProxy server running at port 8883 for MQTT with TLS + + ```bash + examples/client/mqtt/with_tls.sh + ``` + +- Script to test mProxy server running at port 8884 for MQTT with mTLS + + ```bash + examples/client/mqtt/with_mtls.sh + ``` + +#### Test mProxy server for MQTT over Websocket protocols + +Go programs available in `examples/client/websocket/*/main.go` directory helps to test the mProxy servers running for MQTT over Websocket protocols + +- Go program to test mProxy server running at port 8083 for MQTT over Websocket without TLS + + ```bash + go run examples/client/websocket/without_tls/main.go + ``` + +- Go program to test mProxy server running at port 8084 for MQTT over Websocket with TLS + + ```bash + go run examples/client/websocket/with_tls/main.go + ``` + +- Go program to test mProxy server running at port 8085 for MQTT over Websocket with mTLS + + ```bash + go run examples/client/websocket/with_mtls/main.go + ``` + +#### Test mProxy server for HTTP protocols + +Bash scripts available in `examples/client/http` directory helps to test the mProxy servers running for HTTP protocols + +- Script to test mProxy server running at port 8086 for HTTP without TLS + + ```bash + examples/client/http/without_tls.sh + ``` + +- Script to test mProxy server running at port 8087 for HTTP with TLS + + ```bash + examples/client/http/with_tls.sh + ``` + +- Script to test mProxy server running at port 8088 for HTTP with mTLS + + ```bash + examples/client/http/with_mtls.sh + ``` ## Configuration The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -|-------------------------|------------------------------------------------|-----------| -| MPROXY_WS_HOST | WebSocket inbound (IN) connection host | 0.0.0.0 | -| MPROXY_WS_PORT | WebSocket inbound (IN) connection port | 8080 | -| MPROXY_WS_PATH | WebSocket inbound (IN) connection path | /mqtt | -| MPROXY_WSS_PORT | WebSocket Secure inbound (IN) connection port | 8080 | -| MPROXY_WSS_PATH | WebSocket Secure inbound (IN) connection path | /mqtt | -| MPROXY_WS_TARGET_SCHEME | WebSocket Target schema | ws | -| MPROXY_WS_TARGET_HOST | WebSocket Target host | localhost | -| MPROXY_WS_TARGET_PORT | WebSocket Target port | 8888 | -| MPROXY_WS_TARGET_PATH | WebSocket Target path | /mqtt | -| MPROXY_MQTT_HOST | MQTT inbound connection host | 0.0.0.0 | -| MPROXY_MQTT_PORT | MQTT inbound connection port | 1883 | -| MPROXY_MQTTS_PORT | MQTTS inbound connection port | 8883 | -| MPROXY_MQTT_TARGET_HOST | MQTT broker host | 0.0.0.0 | -| MPROXY_MQTT_TARGET_PORT | MQTT broker port | 1884 | -| MPROXY_CLIENT_TLS | Flag that indicates if TLS should be turned on | false | -| MPROXY_CA_CERTS | Path to trusted CAs in PEM format | | -| MPROXY_SERVER_CERT | Path to server certificate in pem format | | -| MPROXY_SERVER_KEY | Path to server key in pem format | | -| MPROXY_LOG_LEVEL | Log level | debug | +| Variable | Description | Default | +| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| MPROXY_MQTT_WITHOUT_TLS_ADDRESS | MQTT without TLS inbound (IN) connection listening address | :1884 | +| MPROXY_MQTT_WITHOUT_TLS_TARGET | MQTT without TLS outbound (OUT) connection address | localhost:1883 | +| MPROXY_MQTT_WITH_TLS_ADDRESS | MQTT with TLS inbound (IN) connection listening address | :8883 | +| MPROXY_MQTT_WITH_TLS_TARGET | MQTT with TLS outbound (OUT) connection address | localhost:1883 | +| MPROXY_MQTT_WITH_TLS_CERT_FILE | MQTT with TLS certificate file path | ssl/certs/server.crt | +| MPROXY_MQTT_WITH_TLS_KEY_FILE | MQTT with TLS key file path | ssl/certs/server.key | +| MPROXY_MQTT_WITH_TLS_SERVER_CA_FILE | MQTT with TLS server CA file path | ssl/certs/ca.crt | +| MPROXY_MQTT_WITH_MTLS_ADDRESS | MQTT with mTLS inbound (IN) connection listening address | :8884 | +| MPROXY_MQTT_WITH_MTLS_TARGET | MQTT with mTLS outbound (OUT) connection address | localhost:1883 | +| MPROXY_MQTT_WITH_MTLS_CERT_FILE | MQTT with mTLS certificate file path | ssl/certs/server.crt | +| MPROXY_MQTT_WITH_MTLS_KEY_FILE | MQTT with mTLS key file path | ssl/certs/server.key | +| MPROXY_MQTT_WITH_MTLS_SERVER_CA_FILE | MQTT with mTLS server CA file path | ssl/certs/ca.crt | +| MPROXY_MQTT_WITH_MTLS_CLIENT_CA_FILE | MQTT with mTLS client CA file path | ssl/certs/ca.crt | +| MPROXY_MQTT_WITH_MTLS_CERT_VERIFICATION_METHODS | MQTT with mTLS certificate verification methods, if no value or unset then mProxy server will not do client validation | ocsp | +| MPROXY_MQTT_WITH_MTLS_OCSP_RESPONDER_URL | MQTT with mTLS OCSP responder URL, it is used if OCSP responder URL is not available in client certificate AIA | http://localhost:8080/ocsp | +| MPROXY_MQTT_WS_WITHOUT_TLS_ADDRESS | MQTT over Websocket without TLS inbound (IN) connection listening address | :8083 | +| MPROXY_MQTT_WS_WITHOUT_TLS_TARGET | MQTT over Websocket without TLS outbound (OUT) connection address | ws://localhost:8000/ | +| MPROXY_MQTT_WS_WITH_TLS_ADDRESS | MQTT over Websocket with TLS inbound (IN) connection listening address | :8084 | +| MPROXY_MQTT_WS_WITH_TLS_TARGET | MQTT over Websocket with TLS outbound (OUT) connection address | ws://localhost:8000/ | +| MPROXY_MQTT_WS_WITH_TLS_CERT_FILE | MQTT over Websocket with TLS certificate file path | ssl/certs/server.crt | +| MPROXY_MQTT_WS_WITH_TLS_KEY_FILE | MQTT over Websocket with TLS key file path | ssl/certs/server.key | +| MPROXY_MQTT_WS_WITH_TLS_SERVER_CA_FILE | MQTT over Websocket with TLS server CA file path | ssl/certs/ca.crt | +| MPROXY_MQTT_WS_WITH_MTLS_ADDRESS | MQTT over Websocket with mTLS inbound (IN) connection listening address | :8085 | +| MPROXY_MQTT_WS_WITH_MTLS_PREFIX_PATH | MQTT over Websocket with mTLS inbound (IN) connection path | /mqtt | +| MPROXY_MQTT_WS_WITH_MTLS_TARGET | MQTT over Websocket with mTLS outbound (OUT) connection address | ws://localhost:8000/ | +| MPROXY_MQTT_WS_WITH_MTLS_CERT_FILE | MQTT over Websocket with mTLS certificate file path | ssl/certs/server.crt | +| MPROXY_MQTT_WS_WITH_MTLS_KEY_FILE | MQTT over Websocket with mTLS key file path | ssl/certs/server.key | +| MPROXY_MQTT_WS_WITH_MTLS_SERVER_CA_FILE | MQTT over Websocket with mTLS server CA file path | ssl/certs/ca.crt | +| MPROXY_MQTT_WS_WITH_MTLS_CLIENT_CA_FILE | MQTT over Websocket with mTLS client CA file path | ssl/certs/ca.crt | +| MPROXY_MQTT_WS_WITH_MTLS_CERT_VERIFICATION_METHODS | MQTT over Websocket with mTLS certificate verification methods, if no value or unset then mProxy server will not do client validation | ocsp | +| MPROXY_MQTT_WS_WITH_MTLS_OCSP_RESPONDER_URL | MQTT over Websocket with mTLS OCSP responder URL, it is used if OCSP responder URL is not available in client certificate AIA | http://localhost:8080/ocsp | +| MPROXY_HTTP_WITHOUT_TLS_ADDRESS | HTTP without TLS inbound (IN) connection listening address | :8086 | +| MPROXY_HTTP_WITHOUT_TLS_PREFIX_PATH | HTTP without TLS inbound (IN) connection path | /messages | +| MPROXY_HTTP_WITHOUT_TLS_TARGET | HTTP without TLS outbound (OUT) connection address | http://localhost:8888/ | +| MPROXY_HTTP_WITH_TLS_ADDRESS | HTTP with TLS inbound (IN) connection listening address | :8087 | +| MPROXY_HTTP_WITH_TLS_PREFIX_PATH | HTTP with TLS inbound (IN) connection path | /messages | +| MPROXY_HTTP_WITH_TLS_TARGET | HTTP with TLS outbound (OUT) connection address | http://localhost:8888/ | +| MPROXY_HTTP_WITH_TLS_CERT_FILE | HTTP with TLS certificate file path | ssl/certs/server.crt | +| MPROXY_HTTP_WITH_TLS_KEY_FILE | HTTP with TLS key file path | ssl/certs/server.key | +| MPROXY_HTTP_WITH_TLS_SERVER_CA_FILE | HTTP with TLS server CA file path | ssl/certs/ca.crt | +| MPROXY_HTTP_WITH_MTLS_ADDRESS | HTTP with mTLS inbound (IN) connection listening address | :8088 | +| MPROXY_HTTP_WITH_MTLS_PREFIX_PATH | HTTP with mTLS inbound (IN) connection path | /messages | +| MPROXY_HTTP_WITH_MTLS_TARGET | HTTP with mTLS outbound (OUT) connection address | http://localhost:8888/ | +| MPROXY_HTTP_WITH_MTLS_CERT_FILE | HTTP with mTLS certificate file path | ssl/certs/server.crt | +| MPROXY_HTTP_WITH_MTLS_KEY_FILE | HTTP with mTLS key file path | ssl/certs/server.key | +| MPROXY_HTTP_WITH_MTLS_SERVER_CA_FILE | HTTP with mTLS server CA file path | ssl/certs/ca.crt | +| MPROXY_HTTP_WITH_MTLS_CLIENT_CA_FILE | HTTP with mTLS client CA file path | ssl/certs/ca.crt | +| MPROXY_HTTP_WITH_MTLS_CERT_VERIFICATION_METHODS | HTTP with mTLS certificate verification methods, if no value or unset then mProxy server will not do client validation | ocsp | +| MPROXY_HTTP_WITH_MTLS_OCSP_RESPONDER_URL | HTTP with mTLS OCSP responder URL, it is used if OCSP responder URL is not available in client certificate AIA | http://localhost:8080/ocsp | + +## mProxy Configuration Environment Variables + +### Server Configuration Environment Variables + +- `ADDRESS` : Specifies the address at which mProxy will listen. Supports MQTT, MQTT over WebSocket, and HTTP proxy connections. +- `PREFIX_PATH` : Defines the path prefix when listening for MQTT over WebSocket or HTTP connections. +- `TARGET` : Specifies the address of the target server, including any prefix path if available. The target server can be an MQTT server, MQTT over WebSocket, or an HTTP server. + +### TLS Configuration Environment Variables + +- `CERT_FILE` : Path to the TLS certificate file. +- `KEY_FILE` : Path to the TLS certificate key file. +- `SERVER_CA_FILE` : Path to the Server CA certificate file. +- `CLIENT_CA_FILE` : Path to the Client CA certificate file. +- `CERT_VERIFICATION_METHODS` : Methods for validating certificates. Accepted values are `ocsp` or `crl`. + For the `ocsp` value, the `tls.Config` attempts to retrieve the OCSP responder/server URL from the Authority Information Access (AIA) section of the client certificate. If the client certificate lacks an OCSP responder URL or if an alternative URL is preferred, you can override it using the environmental variable `OCSP_RESPONDER_URL`. + For the `crl` value, the `tls.Config` attempts to obtain the Certificate Revocation List (CRL) file from the CRL Distribution Point section in the client certificate. If the client certificate lacks a CRL distribution point section, or if you prefer to override it, you can use the environmental variables `CRL_DISTRIBUTION_POINTS` and `CRL_DISTRIBUTION_POINTS_ISSUER_CERT_FILE`. If no CRL distribution point server is available, you can specify an offline CRL file using the environmental variables `OFFLINE_CRL_FILE` and `OFFLINE_CRL_ISSUER_CERT_FILE`. + +#### OCSP Configuration Environment Variables + +- `OCSP_DEPTH` : Depth of client certificate verification in the OCSP method. The default value is 0, meaning there is no limit, and all certificates are verified. +- `OCSP_RESPONDER_URL` : Override value for the OCSP responder URL present in the Authority Information Access (AIA) section of the client certificate. If left empty, it expects the OCSP responder URL from the AIA section of the client certificate. + +#### CRL Configuration Environment Variables + +- `CRL_DEPTH`: Depth of client certificate verification in the CRL method. The default value is 1, meaning only the leaf certificate is verified. +- `CRL_DISTRIBUTION_POINTS` : Override for the CRL Distribution Point value present in the certificate's CRL Distribution Point section. +- `CRL_DISTRIBUTION_POINTS_ISSUER_CERT_FILE` : Path to the issuer certificate file for verifying the CRL retrieved from `CRL_DISTRIBUTION_POINTS`. +- `OFFLINE_CRL_FILE` : Path to th offline CRL file, which can be used if the CRL Distribution point is not available in either the environmental variable or the certificate's CRL Distribution Point section. +- `OFFLINE_CRL_ISSUER_CERT_FILE` : Location of the issuer certificate file for verifying the offline CRL file specified in `OFFLINE_CRL_FILE`. + +## Adding Prefix to Environmental Variables + +mProxy relies on the [caarlos0/env](https://github.com/caarlos0/env) package to load environmental variables into its [configuration](https://github.com/arvindh123/mproxy/blob/main/config.go#L15). +You can control how these variables are loaded by passing `env.Options` to the `config.EnvParse` function. + +To add a prefix to environmental variables, use `env.Options{Prefix: "MPROXY_"}` from the [caarlos0/env](https://github.com/caarlos0/env) package. For example: + +```go +package main +import ( + "github.com/caarlos0/env/v10" + "github.com/absmach/mproxy" +) + +mqttConfig := mproxy.Config{} +if err := mqttConfig.EnvParse(env.Options{Prefix: "MPROXY_" }); err != nil { + panic(err) +} +fmt.Printf("%+v\n") +``` + +In the above snippet, `mqttConfig.EnvParse` expects all environmental variables with the prefix `MPROXY_`. +For instance: + +- MPROXY_ADDRESS +- MPROXY_PREFIX_PATH +- MPROXY_TARGET +- MPROXY_CERT_FILE +- MPROXY_KEY_FILE +- MPROXY_SERVER_CA_FILE +- MPROXY_CLIENT_CA_FILE +- MPROXY_CERT_VERIFICATION_METHODS +- MPROXY_OCSP_DEPTH +- MPROXY_OCSP_RESPONDER_URL +- MPROXY_CRL_DEPTH +- MPROXY_CRL_DISTRIBUTION_POINTS +- MPROXY_CRL_DISTRIBUTION_POINTS_ISSUER_CERT_FILE +- MPROXY_OFFLINE_CRL_FILE +- MPROXY_OFFLINE_CRL_ISSUER_CERT_FILE ## License + [Apache-2.0](LICENSE) [grc]: https://goreportcard.com/badge/github.com/absmach/mproxy diff --git a/cmd/main.go b/cmd/main.go index 3ee07c1..f66176a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -5,353 +5,192 @@ package main import ( "context" - "crypto/tls" "fmt" - "log" "log/slog" - "net/http" "os" "os/signal" - "strconv" "syscall" + "github.com/absmach/mproxy" "github.com/absmach/mproxy/examples/simple" - hproxy "github.com/absmach/mproxy/pkg/http" + "github.com/absmach/mproxy/pkg/http" "github.com/absmach/mproxy/pkg/mqtt" "github.com/absmach/mproxy/pkg/mqtt/websocket" "github.com/absmach/mproxy/pkg/session" - mptls "github.com/absmach/mproxy/pkg/tls" - "github.com/absmach/mproxy/pkg/websockets" + "github.com/caarlos0/env/v10" + "github.com/joho/godotenv" + "golang.org/x/sync/errgroup" ) const ( - defMQTTWSHost = "0.0.0.0" - defMQTTWSPath = "/mqtt" - defMQTTWSPort = "8080" - defMQTTWSSPath = "/mqtt" - defMQTTWSSPort = "8081" - defMQTTWSTargetScheme = "ws" - defMQTTWSTargetHost = "localhost" - defMQTTWSTargetPort = "8888" - defMQTTWSTargetPath = "/mqtt" - - envMQTTWSHost = "MPROXY_MQTT_WS_HOST" - envMQTTWSPort = "MPROXY_MQTT_WS_PORT" - envMQTTWSPath = "MPROXY_MQTT_WS_PATH" - envMQTTWSSPort = "MPROXY_MQTT_WSS_PORT" - envMQTTWSSPath = "MPROXY_MQTT_WSS_PATH" - envMQTTWSTargetScheme = "MPROXY_MQTT_WS_TARGET_SCHEME" - envMQTTWSTargetHost = "MPROXY_MQTT_WS_TARGET_HOST" - envMQTTWSTargetPort = "MPROXY_MQTT_WS_TARGET_PORT" - envMQTTWSTargetPath = "MPROXY_MQTT_WS_TARGET_PATH" - - defMQTTHost = "0.0.0.0" - defMQTTPort = "1883" - defMQTTSPort = "8883" - defMQTTTargetHost = "0.0.0.0" - defMQTTTargetPort = "1884" - defCACerts = "" - defServerCert = "" - defServerKey = "" - - envMQTTHost = "MPROXY_MQTT_HOST" - envMQTTPort = "MPROXY_MQTT_PORT" - envMQTTSPort = "MPROXY_MQTTS_PORT" - envMQTTTargetHost = "MPROXY_MQTT_TARGET_HOST" - envMQTTTargetPort = "MPROXY_MQTT_TARGET_PORT" - envCACerts = "MPROXY_CA_CERTS" - envServerCert = "MPROXY_SERVER_CERT" - envServerKey = "MPROXY_SERVER_KEY" - - defWSHost = "0.0.0.0" - defWSPort = "8081" - defWSTargetHost = "ws://localhost" - defWSTargetPort = "8889" - - envWSHost = "MPROXY_WS_HOST" - envWSPort = "MPROXY_WS_PORT" - envWSTargetHost = "MPROXY_MQTT_WS_TARGET_HOST" - envWSTargetPort = "MPROXY_MQTT_WS_TARGET_PORT" - - defClientTLS = "false" - envClientTLS = "MPROXY_CLIENT_TLS" - defLogLevel = "debug" - envLogLevel = "MPROXY_LOG_LEVEL" - - envHTTPHost = "MPROXY_HTTP_HOST" - envHTTPPort = "MPROXY_HTTP_PORT" - envHTTPargetHost = "MPROXY_HTTP_TARGET_HOST" - envHTTPTargetPort = "MPROXY_HTTP_TARGET_PORT" - envHTTPServerCert = "MPROXY_HTTP_SERVER_CERT" - envHTTPServerKey = "MPROXY_HTTP_SERVER_KEY" - - defHTTPHost = "0.0.0.0" - defHTTPPort = "8888" - defHTTPTargetHost = "http://localhost" - defHTTPTargetPort = "8081" - defHTTPServerCert = "" - defHTTPServerKey = "" -) - -type config struct { - clientTLS bool - caCerts string - serverCert string - serverKey string - - httpConfig HTTPConfig - mqttConfig MQTTConfig - wsMQTTConfig WSMQTTConfig - wsConfig WSConfig - - logLevel slog.Level -} - -type WSConfig struct { - host string - port string - targetHost string - targetPort string -} + mqttWithoutTLS = "MPROXY_MQTT_WITHOUT_TLS_" + mqttWithTLS = "MPROXY_MQTT_WITH_TLS_" + mqttWithmTLS = "MPROXY_MQTT_WITH_MTLS_" -type WSMQTTConfig struct { - host string - port string - path string - wssPort string - wssPath string - targetScheme string - targetHost string - targetPort string - targetPath string -} - -type MQTTConfig struct { - host string - port string - mqttsPort string - targetHost string - targetPort string -} + mqttWSWithoutTLS = "MPROXY_MQTT_WS_WITHOUT_TLS_" + mqttWSWithTLS = "MPROXY_MQTT_WS_WITH_TLS_" + mqttWSWithmTLS = "MPROXY_MQTT_WS_WITH_MTLS_" -type HTTPConfig struct { - host string - port string - targetHost string - targetPort string - serverCert string - serverKey string -} + httpWithoutTLS = "MPROXY_HTTP_WITHOUT_TLS_" + httpWithTLS = "MPROXY_HTTP_WITH_TLS_" + httpWithmTLS = "MPROXY_HTTP_WITH_MTLS_" +) func main() { - cfg := loadConfig() + ctx, cancel := context.WithCancel(context.Background()) + g, ctx := errgroup.WithContext(ctx) logHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ - Level: cfg.logLevel, + Level: slog.LevelDebug, }) logger := slog.New(logHandler) - h := simple.New(logger) - - errs := make(chan error, 3) - - ctx := context.Background() - - if cfg.clientTLS { - tlsCfg, err := mptls.LoadTLSCfg(cfg.caCerts, cfg.serverCert, cfg.serverKey) - if err != nil { - errs <- err - } - - // WSS - MQTT - logServerStart(logger, "encrypted WebSocket", cfg.wsMQTTConfig.wssPort, cfg.wsMQTTConfig.targetPort) - go proxyMQTTWSS(cfg, tlsCfg, logger, h, errs) - // MQTTS - logServerStart(logger, "MQTTS", cfg.mqttConfig.mqttsPort, cfg.mqttConfig.targetPort) - go proxyMQTTS(ctx, cfg.mqttConfig, tlsCfg, logger, h, errs) - // WSS - logServerStart(logger, "WSS", cfg.wsConfig.port, cfg.wsConfig.targetPort) - go proxyWSS(ctx, cfg, logger, h, errs) - // HTTPS - logServerStart(logger, "HTTPS", cfg.httpConfig.port, cfg.httpConfig.targetPort) - go proxyHTTPS(ctx, cfg.httpConfig, logger, h, errs) - } else { - // WS - MQTT - logServerStart(logger, "WebSocket", cfg.wsMQTTConfig.port, cfg.wsMQTTConfig.targetPort) - go proxyMQTTWS(cfg.wsMQTTConfig, logger, h, errs) - - // MQTT - logServerStart(logger, "MQTT", cfg.mqttConfig.port, cfg.mqttConfig.targetPort) - go proxyMQTT(ctx, cfg.mqttConfig, logger, h, errs) - // WS - logServerStart(logger, "WS", cfg.wsConfig.port, cfg.wsConfig.targetPort) - go proxyWS(ctx, cfg.wsConfig, logger, h, errs) - // HTTP - logServerStart(logger, "HTTP", cfg.httpConfig.port, cfg.httpConfig.targetPort) - go proxyHTTP(ctx, cfg.httpConfig, logger, h, errs) - } + handler := simple.New(logger) - go func() { - c := make(chan os.Signal, 2) - signal.Notify(c, syscall.SIGINT) - errs <- fmt.Errorf("%s", <-c) - }() + var interceptor session.Interceptor - err := <-errs - logger.Error(fmt.Sprintf("mProxy terminated: %s", err)) -} + // Loading .env file to environment + err := godotenv.Load() + if err != nil { + panic(err) + } -func env(key, fallback string) string { - if v := os.Getenv(key); v != "" { - return v + // mProxy server Configuration for MQTT without TLS + mqttConfig, err := mproxy.NewConfig(env.Options{Prefix: mqttWithoutTLS}) + if err != nil { + panic(err) } - return fallback -} + // mProxy server for MQTT without TLS + mqttProxy := mqtt.New(mqttConfig, handler, interceptor, logger) + g.Go(func() error { + return mqttProxy.Listen(ctx) + }) -func loadConfig() config { - clientTLS, err := strconv.ParseBool(env(envClientTLS, defClientTLS)) + // mProxy server Configuration for MQTT with TLS + mqttTLSConfig, err := mproxy.NewConfig(env.Options{Prefix: mqttWithTLS}) if err != nil { - log.Fatalf("Invalid value passed for %s\n", envClientTLS) + panic(err) } - var level slog.Level - if err := level.UnmarshalText([]byte(env(envLogLevel, defLogLevel))); err != nil { - log.Fatalf("Invalid value passed for %s with error: %s\n", envLogLevel, err) + // mProxy server for MQTT with TLS + mqttTLSProxy := mqtt.New(mqttTLSConfig, handler, interceptor, logger) + g.Go(func() error { + return mqttTLSProxy.Listen(ctx) + }) + + // mProxy server Configuration for MQTT with mTLS + mqttMTLSConfig, err := mproxy.NewConfig(env.Options{Prefix: mqttWithmTLS}) + if err != nil { + panic(err) } - return config{ - // WS - wsMQTTConfig: WSMQTTConfig{ - host: env(envMQTTWSHost, defMQTTWSHost), - port: env(envMQTTWSPort, defMQTTWSPort), - path: env(envMQTTWSPath, defMQTTWSPath), - wssPort: env(envMQTTWSSPort, defMQTTWSSPort), - wssPath: env(envMQTTWSSPath, defMQTTWSSPath), - targetScheme: env(envMQTTWSTargetScheme, defMQTTWSTargetScheme), - targetHost: env(envMQTTWSTargetHost, defMQTTWSTargetHost), - targetPort: env(envMQTTWSTargetPort, defMQTTWSTargetPort), - targetPath: env(envMQTTWSTargetPath, defMQTTWSTargetPath), - }, - - // MQTT - mqttConfig: MQTTConfig{ - host: env(envMQTTHost, defMQTTHost), - port: env(envMQTTPort, defMQTTPort), - mqttsPort: env(envMQTTSPort, defMQTTSPort), - targetHost: env(envMQTTTargetHost, defMQTTTargetHost), - targetPort: env(envMQTTTargetPort, defMQTTTargetPort), - }, - clientTLS: clientTLS, - caCerts: env(envCACerts, defCACerts), - serverCert: env(envServerCert, defServerCert), - serverKey: env(envServerKey, defServerKey), - - // HTTP - httpConfig: HTTPConfig{ - port: env(envHTTPPort, defHTTPPort), - host: env(envHTTPHost, defHTTPHost), - targetHost: env(envHTTPargetHost, defHTTPTargetHost), - targetPort: env(envHTTPTargetPort, defHTTPTargetPort), - serverCert: env(envHTTPServerCert, defHTTPServerCert), - serverKey: env(envHTTPServerKey, defHTTPServerKey), - }, - - // WS - wsConfig: WSConfig{ - host: env(envWSHost, defWSHost), - port: env(envWSPort, defWSPort), - targetHost: env(envWSTargetHost, defWSTargetHost), - targetPort: env(envWSTargetPort, defWSTargetPort), - }, - - // Log - logLevel: level, + // mProxy server for MQTT with mTLS + mqttMTlsProxy := mqtt.New(mqttMTLSConfig, handler, interceptor, logger) + g.Go(func() error { + return mqttMTlsProxy.Listen(ctx) + }) + + // mProxy server Configuration for MQTT over Websocket without TLS + wsConfig, err := mproxy.NewConfig(env.Options{Prefix: mqttWSWithoutTLS}) + if err != nil { + panic(err) } -} -func proxyMQTTWS(cfg WSMQTTConfig, logger *slog.Logger, handler session.Handler, errs chan error) { - target := fmt.Sprintf("%s:%s", cfg.targetHost, cfg.targetPort) - wp := websocket.New(target, cfg.targetPath, cfg.targetScheme, handler, nil, logger) - http.Handle(cfg.path, wp.Handler()) + // mProxy server for MQTT over Websocket without TLS + wsProxy := websocket.New(wsConfig, handler, interceptor, logger) + g.Go(func() error { + return wsProxy.Listen(ctx) + }) - errs <- wp.Listen(cfg.port) -} + // mProxy server Configuration for MQTT over Websocket with TLS + wsTLSConfig, err := mproxy.NewConfig(env.Options{Prefix: mqttWSWithTLS}) + if err != nil { + panic(err) + } -func proxyMQTTWSS(cfg config, tlsCfg *tls.Config, logger *slog.Logger, handler session.Handler, errs chan error) { - target := fmt.Sprintf("%s:%s", cfg.wsMQTTConfig.targetHost, cfg.wsMQTTConfig.targetPort) - wp := websocket.New(target, cfg.wsMQTTConfig.targetPath, cfg.wsMQTTConfig.targetScheme, handler, nil, logger) - http.Handle(cfg.wsMQTTConfig.wssPath, wp.Handler()) - errs <- wp.ListenTLS(tlsCfg, cfg.serverCert, cfg.serverKey, cfg.wsMQTTConfig.wssPort) -} + // mProxy server for MQTT over Websocket with TLS + wsTLSProxy := websocket.New(wsTLSConfig, handler, interceptor, logger) + g.Go(func() error { + return wsTLSProxy.Listen(ctx) + }) -func proxyMQTT(ctx context.Context, cfg MQTTConfig, logger *slog.Logger, handler session.Handler, errs chan error) { - address := fmt.Sprintf("%s:%s", cfg.host, cfg.port) - target := fmt.Sprintf("%s:%s", cfg.targetHost, cfg.targetPort) - mp := mqtt.New(address, target, handler, nil, logger) + // mProxy server Configuration for MQTT over Websocket with mTLS + wsMTLSConfig, err := mproxy.NewConfig(env.Options{Prefix: mqttWSWithmTLS}) + if err != nil { + panic(err) + } - errs <- mp.Listen(ctx) -} + // mProxy server for MQTT over Websocket with mTLS + wsMTLSProxy := websocket.New(wsMTLSConfig, handler, interceptor, logger) + g.Go(func() error { + return wsMTLSProxy.Listen(ctx) + }) -func proxyMQTTS(ctx context.Context, cfg MQTTConfig, tlsCfg *tls.Config, logger *slog.Logger, handler session.Handler, errs chan error) { - address := fmt.Sprintf("%s:%s", cfg.host, cfg.mqttsPort) - target := fmt.Sprintf("%s:%s", cfg.targetHost, cfg.targetPort) - mp := mqtt.New(address, target, handler, nil, logger) + // mProxy server Configuration for HTTP without TLS + httpConfig, err := mproxy.NewConfig(env.Options{Prefix: httpWithoutTLS}) + if err != nil { + panic(err) + } - errs <- mp.ListenTLS(ctx, tlsCfg) -} + // mProxy server for HTTP without TLS + httpProxy, err := http.NewProxy(httpConfig, handler, logger) + if err != nil { + panic(err) + } + g.Go(func() error { + return httpProxy.Listen(ctx) + }) -func proxyHTTP(_ context.Context, cfg HTTPConfig, logger *slog.Logger, handler session.Handler, errs chan error) { - address := fmt.Sprintf("%s:%s", cfg.host, cfg.port) - target := fmt.Sprintf("%s:%s", cfg.targetHost, cfg.targetPort) - hp, err := hproxy.NewProxy(address, target, handler, logger) + // mProxy server Configuration for HTTP with TLS + httpTLSConfig, err := mproxy.NewConfig(env.Options{Prefix: httpWithTLS}) if err != nil { - errs <- err - return + panic(err) } - http.HandleFunc("/", hp.Handler) - errs <- hp.Listen() -} -func proxyHTTPS(_ context.Context, cfg HTTPConfig, logger *slog.Logger, handler session.Handler, errs chan error) { - address := fmt.Sprintf("%s:%s", cfg.host, cfg.port) - target := fmt.Sprintf("%s:%s", cfg.targetHost, cfg.targetPort) - hp, err := hproxy.NewProxy(address, target, handler, logger) + // mProxy server for HTTP with TLS + httpTLSProxy, err := http.NewProxy(httpTLSConfig, handler, logger) if err != nil { - errs <- err - return + panic(err) } - http.HandleFunc("/", hp.Handler) - errs <- hp.ListenTLS(cfg.serverCert, cfg.serverKey) -} + g.Go(func() error { + return httpTLSProxy.Listen(ctx) + }) -func proxyWS(_ context.Context, cfg WSConfig, logger *slog.Logger, handler session.Handler, errs chan error) { - address := fmt.Sprintf("%s:%s", cfg.host, cfg.port) - target := fmt.Sprintf("%s:%s", cfg.targetHost, cfg.targetPort) - wp, err := websockets.NewProxy(address, target, logger, handler) + // mProxy server Configuration for HTTP with mTLS + httpMTLSConfig, err := mproxy.NewConfig(env.Options{Prefix: httpWithmTLS}) if err != nil { - errs <- err + panic(err) } - errs <- wp.Listen() -} -func proxyWSS(_ context.Context, cfg config, logger *slog.Logger, handler session.Handler, errs chan error) { - address := fmt.Sprintf("%s:%s", cfg.wsConfig.host, cfg.wsConfig.port) - target := fmt.Sprintf("%s:%s", cfg.wsConfig.targetHost, cfg.wsConfig.targetPort) - wp, err := websockets.NewProxy(address, target, logger, handler) + // mProxy server for HTTP with mTLS + httpMTLSProxy, err := http.NewProxy(httpMTLSConfig, handler, logger) if err != nil { - errs <- err + panic(err) + } + g.Go(func() error { + return httpMTLSProxy.Listen(ctx) + }) + + g.Go(func() error { + return StopSignalHandler(ctx, cancel, logger) + }) + + if err := g.Wait(); err != nil { + logger.Error(fmt.Sprintf("mProxy service terminated with error: %s", err)) + } else { + logger.Info("mProxy service stopped") } - errs <- wp.ListenTLS(cfg.serverCert, cfg.serverKey) } -func logServerStart(logger *slog.Logger, name, port, targetPort string) { - logger.Info("Starting "+name+" proxy", - slog.Group("server", - slog.String("port", port), - ), - slog.Group("target", - slog.String("port", targetPort), - ), - ) +func StopSignalHandler(ctx context.Context, cancel context.CancelFunc, logger *slog.Logger) error { + c := make(chan os.Signal, 2) + signal.Notify(c, syscall.SIGINT, syscall.SIGABRT) + select { + case <-c: + cancel() + return nil + case <-ctx.Done(): + return nil + } } diff --git a/config.go b/config.go new file mode 100644 index 0000000..6f5c01b --- /dev/null +++ b/config.go @@ -0,0 +1,36 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package mproxy + +import ( + "crypto/tls" + + mptls "github.com/absmach/mproxy/pkg/tls" + "github.com/caarlos0/env/v10" +) + +type Config struct { + Address string `env:"ADDRESS" envDefault:""` + PrefixPath string `env:"PREFIX_PATH" envDefault:""` + Target string `env:"TARGET" envDefault:""` + TLSConfig *tls.Config +} + +func NewConfig(opts env.Options) (Config, error) { + c := Config{} + if err := env.ParseWithOptions(&c, opts); err != nil { + return Config{}, err + } + + cfg, err := mptls.NewConfig(opts) + if err != nil { + return Config{}, err + } + + c.TLSConfig, err = mptls.Load(&cfg) + if err != nil { + return Config{}, err + } + return c, nil +} diff --git a/examples/client/http/with_mtls.sh b/examples/client/http/with_mtls.sh new file mode 100755 index 0000000..734f98e --- /dev/null +++ b/examples/client/http/with_mtls.sh @@ -0,0 +1,39 @@ +#!/bin/bash +protocol=https +host=localhost +port=8088 +path="messages" +content="application/json" +message="{\"message\": \"Hello mProxy\"}" +invalidPath="invalid_path" +cafile=ssl/certs/ca.crt +certfile=ssl/certs/client.crt +keyfile=ssl/certs/client.key +reovokedcertfile=ssl/certs/client_revoked.crt +reovokedkeyfile=ssl/certs/client_revoked.key +unknowncertfile=ssl/certs/client_unknown.crt +unknownkeyfile=ssl/certs/client_unknown.key + +echo "Posting message to ${protocol}://${host}:${port}/${path} with tls, Authorization header, ca & client certificates ${cafile} ${certfile} ${keyfile}..." +curl -sSiX POST "${protocol}://${host}:${port}/${path}" -H "content-type:${content}" -H "Authorization:TOKEN" -d "${message}" --cacert $cafile --cert $certfile --key $keyfile + +echo -e "\nPosting message to ${protocol}://${host}:${port}/${path} with tls, basic authentication, ca & client certificates ${cafile} ${certfile} ${keyfile}..." +curl -sSi -u username:password -X POST "${protocol}://${host}:${port}/${path}" -H "content-type:${content}" -d "${message}" --cacert $cafile --cert $certfile --key $keyfile + +echo -e "\nPosting message to invalid path ${protocol}://${host}:${port}/${path}/${invalidPath} with tls, Authorization header, ca & client certificates ${cafile} ${certfile} ${keyfile}..." +curl -sSiX POST "${protocol}://${host}:${port}/${path}/${invalidPath}" -H "content-type:${content}" -H "Authorization:TOKEN" -d "${message}" --cacert $cafile --cert $certfile --key $keyfile + +echo -e "\nPosting message to invalid path ${protocol}://${host}:${port}/${invalidPath} with tls, Authorization header, ca & client certificates ${cafile} ${certfile} ${keyfile}..." +curl -sSiX POST "${protocol}://${host}:${port}/${invalidPath}" -H "content-type:${content}" -H "Authorization:TOKEN" -d "${message}" --cacert $cafile --cert $certfile --key $keyfile + +echo -e "\nPosting message to ${protocol}://${host}:${port}/${path} with tls, Authorization header, ca certificates ${cafile} & reovked client certificate ${reovokedcertfile} ${reovokedkeyfile}..." +curl -sSiX POST "${protocol}://${host}:${port}/${path}" -H "content-type:${content}" -H "Authorization:TOKEN" -d "${message}" --cacert $cafile --cert $reovokedcertfile --key $reovokedkeyfile + +echo -e "\nPosting message to ${protocol}://${host}:${port}/${path} with tls, Authorization header, ca certificates ${cafile} & unknown client certificate ${unknowncertfile} ${unknownkeyfile}..." +curl -sSiX POST "${protocol}://${host}:${port}/${path}" -H "content-type:${content}" -H "Authorization:TOKEN" -d "${message}" --cacert $cafile --cert $unknowncertfile --key $unknownkeyfile + +echo -e "\nPosting message to ${protocol}://${host}:${port}/${path} with tls, Authorization header, ca certificate ${cafile} & without client certificates.." +curl -sSiX POST "${protocol}://${host}:${port}/${path}" -H "content-type:${content}" -H "Authorization:TOKEN" -d "${message}" --cacert $cafile 2>&1 + +echo -e "\nPosting message to ${protocol}://${host}:${port}/${path} with tls, Authorization header, & without ca , client certificates.." +curl -sSiX POST "${protocol}://${host}:${port}/${path}" -H "content-type:${content}" -H "Authorization:TOKEN" -d "${message}" 2>&1 diff --git a/examples/client/http/with_tls.sh b/examples/client/http/with_tls.sh new file mode 100755 index 0000000..f88bc1e --- /dev/null +++ b/examples/client/http/with_tls.sh @@ -0,0 +1,29 @@ +#!/bin/bash +protocol=https +host=localhost +port=8087 +path="messages" +content="application/json" +message="{\"message\": \"Hello mProxy\"}" +invalidPath="invalid_path" +cafile=ssl/certs/ca.crt +certfile=ssl/certs/client.crt +keyfile=ssl/certs/client.key +reovokedcertfile=ssl/certs/client_revoked.crt +reovokedkeyfile=ssl/certs/client_revoked.key +unknowncertfile=ssl/certs/client_unknown.crt +unknownkeyfile=ssl/certs/client_unknown.key + +echo "Posting message to ${protocol}://${host}:${port}/${path} with tls, Authorization header, ca certificate ${cafile}..." +curl -sSiX POST "${protocol}://${host}:${port}/${path}" -H "content-type:${content}" -H "Authorization:TOKEN" -d "${message}" --cacert $cafile + + +echo -e "\nPosting message to ${protocol}://${host}:${port}/${path} with tls, basic authentication ca certificate ${cafile}...." +curl -sSi -u username:password -X POST "${protocol}://${host}:${port}/${path}" -H "content-type:${content}" -d "${message}" --cacert $cafile + +echo -e "\nPosting message to invalid path ${protocol}://${host}:${port}/${invalidPath} with tls, Authorization header, ca certificate ${cafile}..." +curl -sSiX POST "${protocol}://${host}:${port}/${invalidPath}" -H "content-type:${content}" -H "Authorization:TOKEN" -d "${message}" --cacert $cafile + +echo -e "\nPosting message to ${protocol}://${host}:${port}/${path} with tls, Authorization header, and without ca certificate.." +curl -sSiX POST "${protocol}://${host}:${port}/${invalidPath}" -H "content-type:${content}" -H "Authorization:TOKEN" -d "${message}" 2>&1 + diff --git a/examples/client/http/without_tls.sh b/examples/client/http/without_tls.sh new file mode 100755 index 0000000..2d9f3f9 --- /dev/null +++ b/examples/client/http/without_tls.sh @@ -0,0 +1,19 @@ +#!/bin/bash +protocol=http +host=localhost +port=8086 +path="messages" +content="application/json" +message="{\"message\": \"Hello mProxy\"}" +invalidPath="invalid_path" + +echo "Posting message to ${protocol}://${host}:${port}/${path} without tls ..." +curl -sSiX POST "${protocol}://${host}:${port}/${path}" -H "content-type:${content}" -H "Authorization:TOKEN" -d "${message}" + + +echo -e "\nPosting message to ${protocol}://${host}:${port}/${path} without tls and with basic authentication..." +curl -sSi -u username:password -X POST "${protocol}://${host}:${port}/${path}" -H "content-type:${content}" -d "${message}" + + +echo -e "\nPosting message to invalid path ${protocol}://${host}:${port}/${invalidPath} without tls..." +curl -sSiX POST "${protocol}://${host}:${port}/${invalidPath}" -H "content-type:${content}" -H "Authorization:TOKEN" -d "${message}" diff --git a/examples/client/mqtt/with_mtls.sh b/examples/client/mqtt/with_mtls.sh new file mode 100755 index 0000000..bea26cf --- /dev/null +++ b/examples/client/mqtt/with_mtls.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +topic="test/topic" +message="Hello mProxy" +port=8884 +host=localhost +cafile=ssl/certs/ca.crt +certfile=ssl/certs/client.crt +keyfile=ssl/certs/client.key +reovokedcertfile=ssl/certs/client_revoked.crt +reovokedkeyfile=ssl/certs/client_revoked.key +unknowncertfile=ssl/certs/client_unknown.crt +unknownkeyfile=ssl/certs/client_unknown.key + +echo "Subscribing to topic ${topic} with mTLS certificate ${cafile} ${certfile} ${keyfile}..." +mosquitto_sub -h $host -p $port -t $topic --cafile $cafile --cert $certfile --key $keyfile & +sub_pid=$! +sleep 1 + +cleanup() { + echo "Cleaning up..." + kill $sub_pid +} + +trap cleanup EXIT + +echo "Publishing to topic ${topic} with mTLS, with ca certificate ${cafile} and with client certificate ${certfile} ${keyfile}..." +mosquitto_pub -h $host -p $port -t $topic -m "${message}" --cafile $cafile --cert $certfile --key $keyfile +sleep 1 + +echo "Publishing to topic ${topic} with mTLS, with ca certificate ${cafile} and with client revoked certificate ${reovokedcertfile} ${reovokedkeyfile}..." +mosquitto_pub -h $host -p $port -t $topic -m "${message}" --cafile $cafile --cert $reovokedcertfile --key $reovokedkeyfile 2>&1 +sleep 1 + +echo "Publishing to topic ${topic} with mTLS, with ca certificate ${cafile} and with client unknown certificate ${unknowncertfile} ${unknownkeyfile}..." +mosquitto_pub -h $host -p $port -t $topic -m "${message}" --cafile $cafile --cert $unknowncertfile --key $unknownkeyfile 2>&1 +sleep 1 + +echo "Publishing to topic ${topic} with mTLS, with ca certificate ${cafile} and without any clinet certificate ...." +mosquitto_pub -h $host -p $port -t $topic -m "${message}" --cafile $cafile 2>&1 +sleep 1 + +echo "Publishing to topic ${topic} without mTLS, without any certificate ...." +mosquitto_pub -h $host -p $port -t $topic -m "${message}" 2>&1 +sleep 1 diff --git a/examples/client/mqtt/with_tls.sh b/examples/client/mqtt/with_tls.sh new file mode 100755 index 0000000..22d2f02 --- /dev/null +++ b/examples/client/mqtt/with_tls.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +topic="test/topic" +message="Hello mProxy" +host=localhost +port=8883 +cafile=ssl/certs/ca.crt + +echo "Subscribing to topic ${topic} with TLS certifcate ${cafile}..." +mosquitto_sub -h $host -p $port -t $topic --cafile $cafile & +sub_pid=$! +sleep 1 + +cleanup() { + echo "Cleaning up..." + kill $sub_pid +} + +trap cleanup EXIT + +echo "Publishing to topic ${topic} with TLS, with ca certificate ${cafile}..." +mosquitto_pub -h $host -p $port -t $topic -m "${message}" --cafile $cafile +sleep 1 + + +echo "Publishing to topic ${topic} with TLS, without ca certificate ...." +mosquitto_pub -h $host -p $port -t $topic -m "${message}" 2>&1 +sleep 1 diff --git a/examples/client/mqtt/without_tls.sh b/examples/client/mqtt/without_tls.sh new file mode 100755 index 0000000..e8ffa0f --- /dev/null +++ b/examples/client/mqtt/without_tls.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +topic="test/topic" +message="Hello mProxy" +host=localhost +port=1884 + +echo "Subscribing to topic ${topic} without TLS..." +mosquitto_sub -h $host -p $port -t $topic & +sub_pid=$! +sleep 1 + +cleanup() { + echo "Cleaning up..." + kill $sub_pid +} + +# Trap the EXIT and ERR signals and call the cleanup function +trap cleanup EXIT + +echo "Publishing to topic ${topic} without TLS..." +mosquitto_pub -h $host -p $port -t $topic -m "${message}" +sleep 1 diff --git a/examples/client/websocket/connect.go b/examples/client/websocket/connect.go new file mode 100644 index 0000000..d93a28f --- /dev/null +++ b/examples/client/websocket/connect.go @@ -0,0 +1,85 @@ +package websocket + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "os" + + mqtt "github.com/eclipse/paho.mqtt.golang" +) + +var ( + errLoadCerts = errors.New("failed to load certificates") + errLoadServerCA = errors.New("failed to load Server CA") + errLoadClientCA = errors.New("failed to load Client CA") + errAppendCA = errors.New("failed to append root ca tls.Config") +) + +func Connect(brokerAddress string, tlsCfg *tls.Config) (mqtt.Client, error) { + opts := mqtt.NewClientOptions().AddBroker(brokerAddress) + + if tlsCfg != nil { + opts.SetTLSConfig(tlsCfg) + } + + client := mqtt.NewClient(opts) + + if token := client.Connect(); token.Wait() && token.Error() != nil { + return client, token.Error() + } + return client, nil +} + +// Load return a TLS configuration that can be used in TLS servers +func LoadTLS(certFile, keyFile, serverCAFile, clientCAFile string) (*tls.Config, error) { + tlsConfig := &tls.Config{} + + // Load Certs and Key if available + if certFile != "" || keyFile != "" { + certificate, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil, errors.Join(errLoadCerts, err) + } + tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{certificate}, + } + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + } + + // Load Server CA if available + rootCA, err := loadCertFile(serverCAFile) + if err != nil { + return nil, errors.Join(errLoadServerCA, err) + } + if len(rootCA) > 0 { + if tlsConfig.RootCAs == nil { + tlsConfig.RootCAs = x509.NewCertPool() + } + if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCA) { + return nil, errAppendCA + } + } + + // Load Client CA if available + clientCA, err := loadCertFile(clientCAFile) + if err != nil { + return nil, errors.Join(errLoadClientCA, err) + } + if len(clientCA) > 0 { + if tlsConfig.ClientCAs == nil { + tlsConfig.ClientCAs = x509.NewCertPool() + } + if !tlsConfig.ClientCAs.AppendCertsFromPEM(clientCA) { + return nil, errAppendCA + } + } + return tlsConfig, nil +} + +func loadCertFile(certFile string) ([]byte, error) { + if certFile != "" { + return os.ReadFile(certFile) + } + return []byte{}, nil +} diff --git a/examples/client/websocket/with_mtls/main.go b/examples/client/websocket/with_mtls/main.go new file mode 100644 index 0000000..896b192 --- /dev/null +++ b/examples/client/websocket/with_mtls/main.go @@ -0,0 +1,119 @@ +package main + +import ( + "fmt" + + "github.com/absmach/mproxy/examples/client/websocket" + mqtt "github.com/eclipse/paho.mqtt.golang" +) + +var ( + brokerAddress = "wss://localhost:8085/mqtt" + topic = "test/topic" + payload = "Hello mProxy" + certFile = "ssl/certs/client.crt" + keyFile = "ssl/certs/client.key" + serverCAFile = "ssl/certs/ca.crt" + clientCAFile = "" +) + +func main() { + fmt.Printf("Subscribing to topic %s with mTLS, with ca certificate %s and with client certificate %s %s \n", topic, serverCAFile, certFile, keyFile) + + tlsCfg, err := websocket.LoadTLS(certFile, keyFile, serverCAFile, clientCAFile) + if err != nil { + panic(err) + } + + subClient, err := websocket.Connect(brokerAddress, tlsCfg) + if err != nil { + panic(err) + } + defer subClient.Disconnect(250) + + done := make(chan struct{}, 1) + if token := subClient.Subscribe(topic, 0, func(c mqtt.Client, m mqtt.Message) { onMessage(c, m, done) }); token.Wait() && token.Error() != nil { + panic(token.Error()) + } + + fmt.Printf("Publishing to topic %s with mTLS, with ca certificate %s and with client certificate %s %s \n", topic, serverCAFile, certFile, keyFile) + pubClient, err := websocket.Connect(brokerAddress, tlsCfg) + if err != nil { + panic(err) + } + defer pubClient.Disconnect(250) + + pubClient.Publish(topic, 0, false, payload) + <-done + + // Publisher with revoked certs + certFile = "ssl/certs/client_revoked.crt" + keyFile = "ssl/certs/client_revoked.key" + fmt.Printf("Publishing to topic %s with mTLS, with ca certificate %s and with revoked client certificate %s %s \n", topic, serverCAFile, certFile, keyFile) + tlsCfg, err = websocket.LoadTLS(certFile, keyFile, serverCAFile, clientCAFile) + if err != nil { + panic(err) + } + + pubClient, err = websocket.Connect(brokerAddress, tlsCfg) + if err == nil { + pubClient.Disconnect(250) + panic("some thing went wrong") + } + fmt.Printf("Failed to connect Publisher with revoked client certs,error : %s\n", err.Error()) + + // Publisher with unknown certs + certFile = "ssl/certs/client_unknown.crt" + keyFile = "ssl/certs/client_unknown.key" + fmt.Printf("Publishing to topic %s with mTLS, with ca certificate %s and with unknown client certificate %s %s \n", topic, serverCAFile, certFile, keyFile) + tlsCfg, err = websocket.LoadTLS(certFile, keyFile, serverCAFile, clientCAFile) + if err != nil { + panic(err) + } + + pubClient, err = websocket.Connect(brokerAddress, tlsCfg) + if err == nil { + pubClient.Disconnect(250) + panic("some thing went wrong") + } + fmt.Printf("Failed to connect with unknown client certs,error : %s\n", err.Error()) + + // Publisher with no client certs + certFile = "" + keyFile = "" + fmt.Printf("Publishing to topic %s with mTLS, with ca certificate %s and without client certificate\n", topic, serverCAFile) + tlsCfg1, err := websocket.LoadTLS(certFile, keyFile, serverCAFile, clientCAFile) + if err != nil { + panic(err) + } + + pubClient, err = websocket.Connect(brokerAddress, tlsCfg1) + if err == nil { + pubClient.Disconnect(250) + panic("some thing went wrong") + } + fmt.Printf("Failed to connect without client certs,error : %s\n", err.Error()) + + // Publisher with no client certs + serverCAFile = "" + certFile = "" + keyFile = "" + fmt.Printf("Publishing to topic %s with mTLS, without ca certificate and without client certificate\n", topic) + tlsCfg, err = websocket.LoadTLS(certFile, keyFile, serverCAFile, clientCAFile) + if err != nil { + panic(err) + } + + pubClient, err = websocket.Connect(brokerAddress, tlsCfg) + if err == nil { + pubClient.Disconnect(250) + panic("some thing went wrong") + } + fmt.Printf("Failed to connect without client certs,error : %s\n", err.Error()) + +} + +func onMessage(_ mqtt.Client, m mqtt.Message, done chan struct{}) { + fmt.Printf("Subscription Message Received, Topic : %s, Payload %s\n", m.Topic(), string(m.Payload())) + done <- struct{}{} +} diff --git a/examples/client/websocket/with_tls/main.go b/examples/client/websocket/with_tls/main.go new file mode 100644 index 0000000..25f1914 --- /dev/null +++ b/examples/client/websocket/with_tls/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "fmt" + + "github.com/absmach/mproxy/examples/client/websocket" + mqtt "github.com/eclipse/paho.mqtt.golang" +) + +var ( + brokerAddress = "wss://localhost:8084" + topic = "test/topic" + payload = "Hello mProxy" + certFile = "" + keyFile = "" + serverCAFile = "ssl/certs/ca.crt" + clientCAFile = "" +) + +func main() { + // Replace these with your MQTT broker details + fmt.Printf("Subscribing to topic %s with TLS, with ca certificate %s \n", topic, serverCAFile) + + tlsCfg, err := websocket.LoadTLS(certFile, keyFile, serverCAFile, clientCAFile) + if err != nil { + panic(err) + } + subClient, err := websocket.Connect(brokerAddress, tlsCfg) + if err != nil { + panic(err) + } + defer subClient.Disconnect(250) + + done := make(chan struct{}, 1) + if token := subClient.Subscribe(topic, 0, func(c mqtt.Client, m mqtt.Message) { onMessage(c, m, done) }); token.Wait() && token.Error() != nil { + panic(token.Error()) + } + + fmt.Printf("Publishing to topic %s with TLS, with ca certificate %s \n", topic, serverCAFile) + pubClient, err := websocket.Connect(brokerAddress, tlsCfg) + if err != nil { + panic(err) + } + + defer pubClient.Disconnect(250) + + pubClient.Publish(topic, 0, false, payload) + <-done + + invalidPathBrokerAddress := brokerAddress + "/invalid_path" + fmt.Printf("Publishing to topic %s with TLS, with ca certificate %s to invalid path %s \n", topic, serverCAFile, invalidPathBrokerAddress) + pubClientInvalidPath, err := websocket.Connect(invalidPathBrokerAddress, tlsCfg) + if err == nil { + pubClientInvalidPath.Disconnect(250) + panic("some thing went wrong") + } + fmt.Printf("Failed to connect with invalid path %s,error : %s\n", invalidPathBrokerAddress, err.Error()) + + serverCAFile = "" + fmt.Printf("Publishing to topic %s with TLS, without ca certificate %s \n", topic, serverCAFile) + tlsCfg, err = websocket.LoadTLS(certFile, keyFile, serverCAFile, clientCAFile) + if err != nil { + panic(err) + } + + pubClientNoCerts, err := websocket.Connect(brokerAddress, tlsCfg) + if err == nil { + pubClientNoCerts.Disconnect(250) + panic("some thing went wrong") + } + fmt.Printf("Failed to connect without Server certs,error : %s\n", err.Error()) + +} + +func onMessage(_ mqtt.Client, m mqtt.Message, done chan struct{}) { + fmt.Printf("Subscription Message Received, Topic : %s, Payload %s\n", m.Topic(), string(m.Payload())) + done <- struct{}{} +} diff --git a/examples/client/websocket/without_tls/main.go b/examples/client/websocket/without_tls/main.go new file mode 100644 index 0000000..2ad3dd1 --- /dev/null +++ b/examples/client/websocket/without_tls/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + + "github.com/absmach/mproxy/examples/client/websocket" + mqtt "github.com/eclipse/paho.mqtt.golang" +) + +var ( + brokerAddress = "ws://localhost:8083" + topic = "test/topic" + payload = "Hello mProxy" +) + +func main() { + // Replace these with your MQTT broker details + fmt.Printf("Subscribing to topic %s without TLS\n", topic) + subClient, err := websocket.Connect(brokerAddress, nil) + if err != nil { + panic(err) + } + defer subClient.Disconnect(250) + + done := make(chan struct{}, 1) + if token := subClient.Subscribe(topic, 0, func(c mqtt.Client, m mqtt.Message) { onMessage(c, m, done) }); token.Wait() && token.Error() != nil { + panic(token.Error()) + } + + fmt.Printf("Publishing to topic %s without TLS\n", topic) + pubClient, err := websocket.Connect(brokerAddress, nil) + if err != nil { + panic(err) + } + + defer pubClient.Disconnect(250) + + pubClient.Publish(topic, 0, false, payload) + <-done + + invalidPathBrokerAddress := brokerAddress + "/invalid_path" + fmt.Printf("Publishing to topic %s without TLS to invalid path %s \n", topic, invalidPathBrokerAddress) + pubClientInvalidPath, err := websocket.Connect(invalidPathBrokerAddress, nil) + if err == nil { + pubClientInvalidPath.Disconnect(250) + panic("some thing went wrong") + } + fmt.Printf("Failed to connect with invalid path %s,error : %s\n", invalidPathBrokerAddress, err.Error()) +} + +func onMessage(_ mqtt.Client, m mqtt.Message, done chan struct{}) { + fmt.Printf("Subscription Message Received, Topic : %s, Payload %s\n", m.Topic(), string(m.Payload())) + done <- struct{}{} +} diff --git a/examples/ocsp-crl-responder/main.go b/examples/ocsp-crl-responder/main.go new file mode 100644 index 0000000..5112f1f --- /dev/null +++ b/examples/ocsp-crl-responder/main.go @@ -0,0 +1,240 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "crypto" + "crypto/x509" + "encoding/pem" + "fmt" + "io" + "log/slog" + "math/big" + "math/rand" + "net/http" + "os" + "strings" + "time" + + "golang.org/x/crypto/ocsp" +) + +var ( + certFile = "ssl/certs/ca.crt" + issuerCertFile = "ssl/certs/ca.crt" + keyFile = "ssl/certs/ca.key" + crlFile = "ssl/certs/revoked_certs.crl" +) + +var ( + goodCertsPath = []string{"ssl/certs/client.crt", "ssl/certs/ca.crt"} + revokedCertsPath = []string{"ssl/certs/client_revoked.crt"} +) + +func main() { + logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelDebug, + })) + + goodCerts, err := serialNumbersFromCertsPath(goodCertsPath) + if err != nil { + panic(err) + } + + revokedCerts, err := serialNumbersFromCertsPath(revokedCertsPath) + if err != nil { + panic(err) + } + + // Load the certificate, issuer certificate, and private key + cert, err := loadCertificate(certFile) + if err != nil { + fmt.Println("Error loading certificate:", err) + return + } + + issuerCert, err := loadCertificate(issuerCertFile) + if err != nil { + fmt.Println("Error loading issuer certificate:", err) + return + } + + privateKey, err := loadPrivateKey(keyFile) + if err != nil { + fmt.Println("Error loading private key:", err) + return + } + + // Register OCSP handler + http.HandleFunc("/ocsp", func(w http.ResponseWriter, r *http.Request) { + ocspHandler(w, r, cert, issuerCert, privateKey, goodCerts, revokedCerts, *logger) + }) + + http.HandleFunc("/crl.pem", func(w http.ResponseWriter, r *http.Request) { + fileHandler(w, r, crlFile, "crl.pem", *logger) + }) + + http.HandleFunc("/ca.pem", func(w http.ResponseWriter, r *http.Request) { + fileHandler(w, r, issuerCertFile, "ca.pem", *logger) + }) + + // Start the HTTP server + fmt.Println("OCSP/CRL responder listening on :8080") + if err := http.ListenAndServe(":8080", nil); err != nil { + panic(err) + } +} + +func loadCertificate(file string) (*x509.Certificate, error) { + pemData, err := os.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("failed to read cert file %s, error : %w", file, err) + } + + block, _ := pem.Decode(pemData) + if block == nil { + return nil, fmt.Errorf("failed to decode PEM block of cert file %s", file) + } + + return x509.ParseCertificate(block.Bytes) +} + +func loadPrivateKey(file string) (interface{}, error) { + keyData, err := os.ReadFile(file) + if err != nil { + return nil, err + } + + block, _ := pem.Decode(keyData) + if block == nil { + return nil, fmt.Errorf("failed to decode PEM block") + } + return x509.ParsePKCS8PrivateKey(block.Bytes) +} + +func fileHandler(w http.ResponseWriter, r *http.Request, crlFile, fileName string, logger slog.Logger) { + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName)) + files, err := os.ReadFile(crlFile) + args := []any{ + slog.String("request", r.URL.String()), + } + if err != nil { + http.Error(w, "Server Error", http.StatusInternalServerError) + args = append(args, slog.String("error", err.Error())) + logger.Info("Request failed ", args...) + return + } + if _, err := w.Write(files); err != nil { + http.Error(w, "Server Error", http.StatusInternalServerError) + args = append(args, slog.String("error", err.Error())) + logger.Info("Request failed ", args...) + return + } + + logger.Info("Request complete successfully ", args...) + +} +func ocspHandler(w http.ResponseWriter, r *http.Request, cert, issuerCert *x509.Certificate, privateKey interface{}, goodCerts, revokedCerts []*big.Int, logger slog.Logger) { + ocspStatus := ocsp.Unknown + + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "Failed to read OCSP request", http.StatusBadRequest) + } + req, err := ocsp.ParseRequest(body) + if err != nil { + http.Error(w, "Failed to parse OCSP request", http.StatusBadRequest) + return + } + signer, ok := privateKey.(crypto.Signer) + if !ok { + http.Error(w, "Server Error", http.StatusInternalServerError) + return + } + + for _, sn := range goodCerts { + if req.SerialNumber.Cmp(sn) == 0 { + ocspStatus = ocsp.Good + } + } + + for _, sn := range revokedCerts { + if req.SerialNumber.Cmp(sn) == 0 { + ocspStatus = ocsp.Revoked + } + } + + statusParam := strings.ToUpper(strings.TrimSpace(r.URL.Query().Get("force_status"))) + switch statusParam { + case "REVOKE": + ocspStatus = ocsp.Revoked + case "GOOD": + ocspStatus = ocsp.Good + case "SERVERFAILED": + ocspStatus = ocsp.ServerFailed + case "RANDOM": + r := rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) + ocspStatus = r.Intn(ocsp.ServerFailed) + } + + template := ocsp.Response{ + Status: ocspStatus, + SerialNumber: req.SerialNumber, + ThisUpdate: time.Now(), + NextUpdate: time.Now(), + Certificate: cert, + IssuerHash: req.HashAlgorithm, + } + + if ocspStatus == ocsp.Revoked { + template.RevokedAt = time.Now() + } + + response, err := ocsp.CreateResponse(issuerCert, cert, template, signer) + if err != nil { + http.Error(w, "Failed to create OCSP response", http.StatusInternalServerError) + return + } + + args := []any{ + slog.String("request", r.URL.String()), + slog.String("ocsp_status", getOCSPStatus(ocspStatus)), + slog.String("request_certificate_serial_number", fmt.Sprintf("%x", req.SerialNumber)), + } + + w.Header().Set("Content-Type", "application/ocsp-response") + if _, err := w.Write(response); err != nil { + args = append(args, slog.String("error", err.Error())) + logger.Info("Request complete with errors ", args...) + return + } + logger.Info("Request complete successfully ", args...) +} + +func serialNumbersFromCertsPath(certsPath []string) ([]*big.Int, error) { + sns := make([]*big.Int, 0) + for _, certPath := range certsPath { + cert, err := loadCertificate(certPath) + if err != nil { + return nil, fmt.Errorf("failed to parse certificate in path %s", certPath) + } + sns = append(sns, cert.SerialNumber) + } + return sns, nil +} + +func getOCSPStatus(status int) string { + switch status { + case ocsp.Revoked: + return "REVOKE" + case ocsp.Good: + return "GOOD" + case ocsp.ServerFailed: + return "SERVERFAILED" + case ocsp.Unknown: + fallthrough + default: + return "UNKNOWN" + } +} diff --git a/examples/server/http-echo/main.go b/examples/server/http-echo/main.go new file mode 100644 index 0000000..943dfe1 --- /dev/null +++ b/examples/server/http-echo/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "log" + "net/http" +) + +const defaultPort = "8888" + +func echoHandler(writer http.ResponseWriter, request *http.Request) { + log.Println("Echoing back request made to " + request.URL.Path + " to client (" + request.RemoteAddr + ")") + writer.Header().Set("Access-Control-Allow-Origin", "*") + writer.Header().Set("Content-Type", request.Header.Get("Content-Type")) + request.Write(writer) +} + +func main() { + log.Println("starting echo server, listening on port " + defaultPort) + mux := http.NewServeMux() + mux.HandleFunc("/", echoHandler) + if err := http.ListenAndServe(":"+defaultPort, mux); err != nil { + panic(err) + } +} diff --git a/examples/server/mosquitto/server.conf b/examples/server/mosquitto/server.conf new file mode 100644 index 0000000..5b890ff --- /dev/null +++ b/examples/server/mosquitto/server.conf @@ -0,0 +1,4 @@ +listener 1883 +listener 8000 +protocol websockets +allow_anonymous true diff --git a/examples/server/mosquitto/server.sh b/examples/server/mosquitto/server.sh new file mode 100755 index 0000000..a95b062 --- /dev/null +++ b/examples/server/mosquitto/server.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -euo pipefail + +scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" + + +mosquitto -c $scriptdir/server.conf diff --git a/go.mod b/go.mod index 0deca6e..6fd9a90 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,13 @@ go 1.21 toolchain go1.21.4 require ( + github.com/caarlos0/env/v10 v10.0.0 github.com/eclipse/paho.mqtt.golang v1.4.3 github.com/google/uuid v1.5.0 github.com/gorilla/websocket v1.5.1 + github.com/joho/godotenv v1.5.1 + golang.org/x/crypto v0.20.0 golang.org/x/sync v0.6.0 ) -require golang.org/x/net v0.20.0 // indirect +require golang.org/x/net v0.21.0 // indirect diff --git a/go.sum b/go.sum index 3372973..ec95eb4 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,16 @@ +github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= +github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik= github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= diff --git a/pkg/http/http.go b/pkg/http/http.go index 774b9ce..61b7251 100644 --- a/pkg/http/http.go +++ b/pkg/http/http.go @@ -5,15 +5,23 @@ package http import ( "bytes" + "context" + "crypto/tls" "encoding/json" "errors" + "fmt" "io" "log/slog" + "net" "net/http" "net/http/httputil" "net/url" + "strings" + "github.com/absmach/mproxy" "github.com/absmach/mproxy/pkg/session" + mptls "github.com/absmach/mproxy/pkg/tls" + "golang.org/x/sync/errgroup" ) const contentType = "application/json" @@ -21,9 +29,12 @@ const contentType = "application/json" // ErrMissingAuthentication returned when no basic or Authorization header is set. var ErrMissingAuthentication = errors.New("missing authorization") -// Handler default handler reads authorization header and -// performs authorization before proxying the request. -func (p *Proxy) Handler(w http.ResponseWriter, r *http.Request) { +func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != p.config.PrefixPath { + http.NotFound(w, r) + return + } + username, password, ok := r.BasicAuth() switch { case ok: @@ -78,40 +89,66 @@ func encodeError(w http.ResponseWriter, statusCode int, err error) { // Proxy represents HTTP Proxy. type Proxy struct { - address string + config mproxy.Config target *httputil.ReverseProxy session session.Handler logger *slog.Logger } -func NewProxy(address, targetUrl string, handler session.Handler, logger *slog.Logger) (Proxy, error) { - target, err := url.Parse(targetUrl) +func NewProxy(config mproxy.Config, handler session.Handler, logger *slog.Logger) (Proxy, error) { + config.PrefixPath = strings.TrimSpace(config.PrefixPath) + switch { + case config.PrefixPath != "" && config.PrefixPath[0] != '/': + config.PrefixPath = "/" + config.PrefixPath + case config.PrefixPath == "": + config.PrefixPath = "/" + } + + target, err := url.Parse(config.Target) if err != nil { return Proxy{}, err } return Proxy{ - address: address, + config: config, target: httputil.NewSingleHostReverseProxy(target), session: handler, logger: logger, }, nil } -func (p *Proxy) Listen() error { - if err := http.ListenAndServe(p.address, nil); err != nil { +func (p Proxy) Listen(ctx context.Context) error { + l, err := net.Listen("tcp", p.config.Address) + if err != nil { return err } - p.logger.Info("Server Exiting...") - return nil -} - -func (p *Proxy) ListenTLS(cert, key string) error { - if err := http.ListenAndServeTLS(p.address, cert, key, nil); err != nil { - return err + if p.config.TLSConfig != nil { + l = tls.NewListener(l, p.config.TLSConfig) } + status := mptls.SecurityStatus(p.config.TLSConfig) + + p.logger.Info(fmt.Sprintf("HTTP proxy server started at %s%s with %s", p.config.Address, p.config.PrefixPath, status)) - p.logger.Info("Server Exiting...") + var server http.Server + g, ctx := errgroup.WithContext(ctx) + + mux := http.NewServeMux() + mux.Handle(p.config.PrefixPath, p) + server.Handler = mux + + g.Go(func() error { + return server.Serve(l) + }) + + g.Go(func() error { + <-ctx.Done() + return server.Close() + }) + if err := g.Wait(); err != nil { + p.logger.Info(fmt.Sprintf("HTTP proxy server at %s%s with %s exiting with errors", p.config.Address, p.config.PrefixPath, status), slog.String("error", err.Error())) + } else { + p.logger.Info(fmt.Sprintf("HTTP proxy server at %s%s with %s exiting...", p.config.Address, p.config.PrefixPath, status)) + } return nil } diff --git a/pkg/mqtt/mqtt.go b/pkg/mqtt/mqtt.go index 46cda36..1ad2c34 100644 --- a/pkg/mqtt/mqtt.go +++ b/pkg/mqtt/mqtt.go @@ -11,14 +11,15 @@ import ( "log/slog" "net" + "github.com/absmach/mproxy" "github.com/absmach/mproxy/pkg/session" mptls "github.com/absmach/mproxy/pkg/tls" + "golang.org/x/sync/errgroup" ) // Proxy is main MQTT proxy struct. type Proxy struct { - address string - target string + config mproxy.Config handler session.Handler interceptor session.Interceptor logger *slog.Logger @@ -26,10 +27,9 @@ type Proxy struct { } // New returns a new MQTT Proxy instance. -func New(address, target string, handler session.Handler, interceptor session.Interceptor, logger *slog.Logger) *Proxy { +func New(config mproxy.Config, handler session.Handler, interceptor session.Interceptor, logger *slog.Logger) *Proxy { return &Proxy{ - address: address, - target: target, + config: config, handler: handler, logger: logger, interceptor: interceptor, @@ -38,22 +38,26 @@ func New(address, target string, handler session.Handler, interceptor session.In func (p Proxy) accept(ctx context.Context, l net.Listener) { for { - conn, err := l.Accept() - if err != nil { - p.logger.Warn("Accept error " + err.Error()) - continue + select { + case <-ctx.Done(): + return + default: + conn, err := l.Accept() + if err != nil { + p.logger.Warn("Accept error " + err.Error()) + continue + } + p.logger.Info("Accepted new client") + go p.handle(ctx, conn) } - - p.logger.Info("Accepted new client") - go p.handle(ctx, conn) } } func (p Proxy) handle(ctx context.Context, inbound net.Conn) { defer p.close(inbound) - outbound, err := p.dialer.Dial("tcp", p.target) + outbound, err := p.dialer.Dial("tcp", p.config.Target) if err != nil { - p.logger.Error("Cannot connect to remote broker " + p.target + " due to: " + err.Error()) + p.logger.Error("Cannot connect to remote broker " + p.config.Target + " due to: " + err.Error()) return } defer p.close(outbound) @@ -71,31 +75,33 @@ func (p Proxy) handle(ctx context.Context, inbound net.Conn) { // Listen of the server, this will block. func (p Proxy) Listen(ctx context.Context) error { - l, err := net.Listen("tcp", p.address) + l, err := net.Listen("tcp", p.config.Address) if err != nil { return err } - defer l.Close() - - // Acceptor loop - p.accept(ctx, l) - - p.logger.Info("Server Exiting...") - return nil -} -// ListenTLS - version of Listen with TLS encryption. -func (p Proxy) ListenTLS(ctx context.Context, tlsCfg *tls.Config) error { - l, err := tls.Listen("tcp", p.address, tlsCfg) - if err != nil { - return err + if p.config.TLSConfig != nil { + l = tls.NewListener(l, p.config.TLSConfig) } - defer l.Close() + status := mptls.SecurityStatus(p.config.TLSConfig) + p.logger.Info(fmt.Sprintf("MQTT proxy server started at %s with %s", p.config.Address, status)) + g, ctx := errgroup.WithContext(ctx) // Acceptor loop - p.accept(ctx, l) - - p.logger.Info("Server Exiting...") + g.Go(func() error { + p.accept(ctx, l) + return nil + }) + + g.Go(func() error { + <-ctx.Done() + return l.Close() + }) + if err := g.Wait(); err != nil { + p.logger.Info(fmt.Sprintf("MQTT proxy server at %s with %s exiting with errors", p.config.Address, status), slog.String("error", err.Error())) + } else { + p.logger.Info(fmt.Sprintf("MQTT proxy server at %s with %s exiting...", p.config.Address, status)) + } return nil } diff --git a/pkg/mqtt/websocket/websocket.go b/pkg/mqtt/websocket/websocket.go index 8350106..9456d18 100644 --- a/pkg/mqtt/websocket/websocket.go +++ b/pkg/mqtt/websocket/websocket.go @@ -8,31 +8,37 @@ import ( "crypto/tls" "fmt" "log/slog" + "net" "net/http" - "net/url" + "strings" "time" + "github.com/absmach/mproxy" "github.com/absmach/mproxy/pkg/session" mptls "github.com/absmach/mproxy/pkg/tls" "github.com/gorilla/websocket" + "golang.org/x/sync/errgroup" ) // Proxy represents WS Proxy. type Proxy struct { - target string - path string - scheme string + config mproxy.Config handler session.Handler interceptor session.Interceptor logger *slog.Logger } // New - creates new WS proxy. -func New(target, path, scheme string, handler session.Handler, interceptor session.Interceptor, logger *slog.Logger) *Proxy { +func New(config mproxy.Config, handler session.Handler, interceptor session.Interceptor, logger *slog.Logger) *Proxy { + config.PrefixPath = strings.TrimSpace(config.PrefixPath) + switch { + case config.PrefixPath != "" && config.PrefixPath[0] != '/': + config.PrefixPath = "/" + config.PrefixPath + case config.PrefixPath == "": + config.PrefixPath = "/" + } return &Proxy{ - target: target, - path: path, - scheme: scheme, + config: config, handler: handler, interceptor: interceptor, logger: logger, @@ -50,37 +56,28 @@ var upgrader = websocket.Upgrader{ }, } -// Handler - proxies WS traffic. -func (p Proxy) Handler() http.Handler { - return p.handle() -} - -func (p Proxy) handle() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - cconn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - p.logger.Error("Error upgrading connection", slog.Any("error", err)) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } +func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != p.config.PrefixPath { + http.NotFound(w, r) + return + } + cconn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + p.logger.Error("Error upgrading connection", slog.Any("error", err)) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } - go p.pass(r.Context(), cconn) - }) + go p.pass(r.Context(), cconn) } func (p Proxy) pass(ctx context.Context, in *websocket.Conn) { defer in.Close() - websocketURL := url.URL{ - Scheme: p.scheme, - Host: p.target, - Path: p.path, - } - dialer := &websocket.Dialer{ Subprotocols: []string{"mqtt"}, } - srv, _, err := dialer.Dial(websocketURL.String(), nil) + srv, _, err := dialer.Dial(p.config.Target, nil) if err != nil { p.logger.Error("Unable to connect to broker", slog.Any("error", err)) return @@ -104,18 +101,38 @@ func (p Proxy) pass(ctx context.Context, in *websocket.Conn) { p.logger.Warn("Broken connection for client", slog.Any("error", err)) } -// Listen of the server. -func (p Proxy) Listen(wsPort string) error { - port := fmt.Sprintf(":%s", wsPort) - return http.ListenAndServe(port, nil) -} +func (p Proxy) Listen(ctx context.Context) error { + l, err := net.Listen("tcp", p.config.Address) + if err != nil { + return err + } + + if p.config.TLSConfig != nil { + l = tls.NewListener(l, p.config.TLSConfig) + } + + var server http.Server + g, ctx := errgroup.WithContext(ctx) -// ListenTLS - version of Listen with TLS encryption. -func (p Proxy) ListenTLS(tlsCfg *tls.Config, crt, key, wssPort string) error { - port := fmt.Sprintf(":%s", wssPort) - server := &http.Server{ - Addr: port, - TLSConfig: tlsCfg, + mux := http.NewServeMux() + mux.Handle(p.config.PrefixPath, p) + server.Handler = mux + + g.Go(func() error { + return server.Serve(l) + }) + status := mptls.SecurityStatus(p.config.TLSConfig) + + p.logger.Info(fmt.Sprintf("MQTT websocket proxy server started at %s%s with %s", p.config.Address, p.config.PrefixPath, status)) + + g.Go(func() error { + <-ctx.Done() + return server.Close() + }) + if err := g.Wait(); err != nil { + p.logger.Info(fmt.Sprintf("MQTT websocket proxy server at %s%s with %s exiting with errors", p.config.Address, p.config.PrefixPath, status), slog.String("error", err.Error())) + } else { + p.logger.Info(fmt.Sprintf("MQTT websocket proxy server at %s%s with %s exiting...", p.config.Address, p.config.PrefixPath, status)) } - return server.ListenAndServeTLS(crt, key) + return nil } diff --git a/pkg/tls/config.go b/pkg/tls/config.go new file mode 100644 index 0000000..e40e736 --- /dev/null +++ b/pkg/tls/config.go @@ -0,0 +1,32 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package tls + +import ( + "github.com/absmach/mproxy/pkg/tls/verifier" + "github.com/caarlos0/env/v10" +) + +type Config struct { + CertFile string `env:"CERT_FILE" envDefault:""` + KeyFile string `env:"KEY_FILE" envDefault:""` + ServerCAFile string `env:"SERVER_CA_FILE" envDefault:""` + ClientCAFile string `env:"CLIENT_CA_FILE" envDefault:""` + Validator verifier.Validator +} + +func NewConfig(opts env.Options) (Config, error) { + c := Config{} + var err error + if err = env.ParseWithOptions(&c, opts); err != nil { + return Config{}, err + } + verifiers, err := newVerifiers(opts) + if err != nil { + return Config{}, err + } + c.Validator = verifier.NewValidator(verifiers) + + return c, nil +} diff --git a/pkg/tls/tls.go b/pkg/tls/tls.go index a7167de..be66aad 100644 --- a/pkg/tls/tls.go +++ b/pkg/tls/tls.go @@ -12,31 +12,61 @@ import ( ) var ( - errTLSdetails = errors.New("failed to get TLS details of connection") - errParseRoot = errors.New("failed to parse root certificate") + errTLSdetails = errors.New("failed to get TLS details of connection") + errLoadCerts = errors.New("failed to load certificates") + errLoadServerCA = errors.New("failed to load Server CA") + errLoadClientCA = errors.New("failed to load Client CA") + errAppendCA = errors.New("failed to append root ca tls.Config") ) -// LoadTLSCfg return a TLS configuration that can be used in TLS servers. -func LoadTLSCfg(ca, crt, key string) (*tls.Config, error) { - caCertPEM, err := os.ReadFile(ca) +// Load return a TLS configuration that can be used in TLS servers. +func Load(c *Config) (*tls.Config, error) { + if c.CertFile == "" || c.KeyFile == "" { + return nil, nil + } + + tlsConfig := &tls.Config{} + + certificate, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile) if err != nil { - return nil, err + return nil, errors.Join(errLoadCerts, err) + } + tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{certificate}, } - roots := x509.NewCertPool() - if ok := roots.AppendCertsFromPEM(caCertPEM); !ok { - return nil, errParseRoot + // Loading Server CA file + rootCA, err := loadCertFile(c.ServerCAFile) + if err != nil { + return nil, errors.Join(errLoadServerCA, err) + } + if len(rootCA) > 0 { + if tlsConfig.RootCAs == nil { + tlsConfig.RootCAs = x509.NewCertPool() + } + if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCA) { + return nil, errAppendCA + } } - cert, err := tls.LoadX509KeyPair(crt, key) + // Loading Client CA File + clientCA, err := loadCertFile(c.ClientCAFile) if err != nil { - return nil, err + return nil, errors.Join(errLoadClientCA, err) + } + if len(clientCA) > 0 { + if tlsConfig.ClientCAs == nil { + tlsConfig.ClientCAs = x509.NewCertPool() + } + if !tlsConfig.ClientCAs.AppendCertsFromPEM(clientCA) { + return nil, errAppendCA + } + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + if c.Validator != nil { + tlsConfig.VerifyPeerCertificate = c.Validator + } } - return &tls.Config{ - Certificates: []tls.Certificate{cert}, - ClientAuth: tls.RequireAndVerifyClientCert, - ClientCAs: roots, - }, nil + return tlsConfig, nil } // ClientCert returns client certificate. @@ -59,3 +89,26 @@ func ClientCert(conn net.Conn) (x509.Certificate, error) { return x509.Certificate{}, nil } } + +// SecurityStatus returns log message from TLS config. +func SecurityStatus(c *tls.Config) string { + if c == nil { + return "no TLS" + } + ret := "TLS" + // It is possible to establish TLS with client certificates only. + if c.Certificates == nil || len(c.Certificates) == 0 { + ret = "no server certificates" + } + if c.ClientCAs != nil { + ret += " and " + c.ClientAuth.String() + } + return ret +} + +func loadCertFile(certFile string) ([]byte, error) { + if certFile != "" { + return os.ReadFile(certFile) + } + return []byte{}, nil +} diff --git a/pkg/tls/verifications.go b/pkg/tls/verifications.go new file mode 100644 index 0000000..42f1cad --- /dev/null +++ b/pkg/tls/verifications.go @@ -0,0 +1,96 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package tls + +import ( + "errors" + "reflect" + "strings" + + "github.com/absmach/mproxy/pkg/tls/verifier" + "github.com/absmach/mproxy/pkg/tls/verifier/crl" + "github.com/absmach/mproxy/pkg/tls/verifier/ocsp" + "github.com/caarlos0/env/v10" +) + +// ErrInvalidCertVerification represents an error during the cert verification +// method loading. Supported are OCSP and CRL verification methods. +var ErrInvalidCertVerification = errors.New("invalid certificate verification method") + +type verification int + +const ( + OCSP verification = iota + 1 + CRL +) + +func newVerifiers(opts env.Options) ([]verifier.Verifier, error) { + if opts.FuncMap == nil { + opts.FuncMap = make(map[reflect.Type]env.ParserFunc) + } + opts.FuncMap[reflect.TypeOf(make([]verification, 0))] = envParseSliceValidate + opts.FuncMap[reflect.TypeOf(new(verification))] = envParseValidation + + var c struct { + Verifications []verification `env:"CERT_VERIFICATION_METHODS" envDefault:""` + } + if err := env.ParseWithOptions(&c, opts); err != nil { + return nil, err + } + if len(c.Verifications) == 0 { + return nil, nil + } + + var vms []verifier.Verifier + for _, v := range c.Verifications { + switch v { + case OCSP: + vm, err := ocsp.New(opts) + if err != nil { + return nil, err + } + vms = append(vms, vm) + case CRL: + vm, err := crl.New(opts) + if err != nil { + return nil, err + } + vms = append(vms, vm) + default: + return nil, ErrInvalidCertVerification + } + } + + return vms, nil +} + +func parseValidation(v string) (verification, error) { + v = strings.ToUpper(strings.TrimSpace(v)) + switch v { + case "OCSP": + return OCSP, nil + case "CRL": + return CRL, nil + default: + return 0, ErrInvalidCertVerification + } +} + +func envParseSliceValidate(v string) (interface{}, error) { + var vms []verification + v = strings.TrimSpace(v) + vmss := strings.Split(v, ",") + for _, vm := range vmss { + v, err := parseValidation(vm) + if err != nil { + return nil, err + } + vms = append(vms, v) + } + return vms, nil +} + +func envParseValidation(v string) (interface{}, error) { + return parseValidation(v) +} diff --git a/pkg/tls/verifier/crl/crl.go b/pkg/tls/verifier/crl/crl.go new file mode 100644 index 0000000..0b46a89 --- /dev/null +++ b/pkg/tls/verifier/crl/crl.go @@ -0,0 +1,285 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package crl + +import ( + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "time" + + "github.com/absmach/mproxy/pkg/tls/verifier" + "github.com/caarlos0/env/v10" +) + +var ( + errRetrieveCRL = errors.New("failed to retrieve CRL") + errReadCRL = errors.New("failed to read CRL") + errParseCRL = errors.New("failed to parse CRL") + errExpiredCRL = errors.New("crl expired") + errCRLSign = errors.New("failed to verify CRL signature") + errOfflineCRLLoad = errors.New("failed to load offline CRL file") + errOfflineCRLIssuer = errors.New("failed to load offline CRL issuer cert file") + errOfflineCRLIssuerPEM = errors.New("failed to decode PEM block in offline CRL issuer cert file") + errCRLDistIssuer = errors.New("failed to load CRL distribution points issuer cert file") + errCRLDistIssuerPEM = errors.New("failed to decode PEM block in CRL distribution points issuer cert file") + errNoCRL = errors.New("neither offline crl file nor crl distribution points in certificate / environmental variable CRL_DISTRIBUTION_POINTS & CRL_DISTRIBUTION_POINTS_ISSUER_CERT_FILE have values") + errCertRevoked = errors.New("certificate revoked") +) + +var ( + errParseCert = errors.New("failed to parse Certificate") + errClientCrt = errors.New("client certificate not received") +) + +type config struct { + CRLDepth uint `env:"CRL_DEPTH" envDefault:"1"` + OfflineCRLFile string `env:"OFFLINE_CRL_FILE" envDefault:""` + OfflineCRLIssuerCertFile string `env:"OFFLINE_CRL_ISSUER_CERT_FILE" envDefault:""` + CRLDistributionPoints url.URL `env:"CRL_DISTRIBUTION_POINTS" envDefault:""` + CRLDistributionPointsIssuerCertFile string `env:"CRL_DISTRIBUTION_POINTS_ISSUER_CERT_FILE " envDefault:""` +} + +var _ verifier.Verifier = (*config)(nil) + +func New(opts env.Options) (verifier.Verifier, error) { + var c config + if err := env.ParseWithOptions(&c, opts); err != nil { + return nil, err + } + return &c, nil +} + +func (c *config) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + switch { + case len(verifiedChains) > 0: + return c.VerifyVerifiedPeerCertificates(verifiedChains) + case len(rawCerts) > 0: + var peerCertificates []*x509.Certificate + peerCertificates, err := parseCertificates(rawCerts) + if err != nil { + return err + } + return c.VerifyRawPeerCertificates(peerCertificates) + default: + return errClientCrt + } +} + +func (c *config) VerifyVerifiedPeerCertificates(verifiedPeerCertificateChains [][]*x509.Certificate) error { + offlineCRL, err := c.loadOfflineCRL() + if err != nil { + return err + } + for _, verifiedChain := range verifiedPeerCertificateChains { + for i := range verifiedChain { + cert := verifiedChain[i] + issuer := cert + if i+1 < len(verifiedChain) { + issuer = verifiedChain[i+1] + } + + crl, err := c.getCRLFromDistributionPoint(cert, issuer) + if err != nil { + return err + } + switch { + case crl == nil && offlineCRL != nil: + crl = offlineCRL + case crl == nil && offlineCRL == nil: + return errNoCRL + } + + if err := c.crlVerify(cert, crl); err != nil { + return err + } + } + } + return nil +} + +func (c *config) VerifyRawPeerCertificates(peerCertificates []*x509.Certificate) error { + offlineCRL, err := c.loadOfflineCRL() + if err != nil { + return err + } + for i, peerCertificate := range peerCertificates { + issuerCert := retrieveIssuerCert(peerCertificate.Issuer, peerCertificates) + crl, err := c.getCRLFromDistributionPoint(peerCertificate, issuerCert) + if err != nil { + return err + } + switch { + case crl == nil && offlineCRL != nil: + crl = offlineCRL + case crl == nil && offlineCRL == nil: + return errNoCRL + } + + if err := c.crlVerify(peerCertificate, crl); err != nil { + return err + } + if i+1 == int(c.CRLDepth) { + return nil + } + } + return nil +} + +func (c *config) crlVerify(peerCertificate *x509.Certificate, crl *x509.RevocationList) error { + for _, revokedCertificate := range crl.RevokedCertificateEntries { + if revokedCertificate.SerialNumber.Cmp(peerCertificate.SerialNumber) == 0 { + return errCertRevoked + } + } + return nil +} + +func (c *config) loadOfflineCRL() (*x509.RevocationList, error) { + offlineCRLBytes, err := loadCertFile(c.OfflineCRLFile) + if err != nil { + return nil, errors.Join(errOfflineCRLLoad, err) + } + if len(offlineCRLBytes) == 0 { + return nil, nil + } + fmt.Println(c.OfflineCRLIssuerCertFile) + issuer, err := c.loadOfflineCRLIssuerCert() + if err != nil { + return nil, err + } + _ = issuer + offlineCRL, err := parseVerifyCRL(offlineCRLBytes, nil, false) + if err != nil { + return nil, err + } + return offlineCRL, nil +} + +func (c *config) getCRLFromDistributionPoint(cert, issuer *x509.Certificate) (*x509.RevocationList, error) { + switch { + case len(cert.CRLDistributionPoints) > 0: + return retrieveCRL(cert.CRLDistributionPoints[0], issuer, true) + case c.CRLDistributionPoints.String() != "" && c.CRLDistributionPointsIssuerCertFile != "": + var crlIssuerCrt *x509.Certificate + var err error + if crlIssuerCrt, err = c.loadDistPointCRLIssuerCert(); err != nil { + return nil, err + } + return retrieveCRL(c.CRLDistributionPoints.String(), crlIssuerCrt, true) + default: + return nil, nil + } +} + +func (c *config) loadDistPointCRLIssuerCert() (*x509.Certificate, error) { + crlIssuerCertBytes, err := loadCertFile(c.CRLDistributionPointsIssuerCertFile) + if err != nil { + return nil, errors.Join(errCRLDistIssuer, err) + } + if len(crlIssuerCertBytes) == 0 { + return nil, nil + } + crlIssuerCertPEM, _ := pem.Decode(crlIssuerCertBytes) + if crlIssuerCertPEM == nil { + return nil, errCRLDistIssuerPEM + } + crlIssuerCert, err := x509.ParseCertificate(crlIssuerCertPEM.Bytes) + if err != nil { + return nil, errors.Join(errCRLDistIssuer, err) + } + return crlIssuerCert, nil +} + +func (c *config) loadOfflineCRLIssuerCert() (*x509.Certificate, error) { + offlineCrlIssuerCertBytes, err := loadCertFile(c.OfflineCRLIssuerCertFile) + if err != nil { + return nil, errors.Join(errOfflineCRLIssuer, err) + } + if len(offlineCrlIssuerCertBytes) == 0 { + return nil, nil + } + offlineCrlIssuerCertPEM, _ := pem.Decode(offlineCrlIssuerCertBytes) + if offlineCrlIssuerCertPEM == nil { + return nil, errOfflineCRLIssuerPEM + } + crlIssuerCert, err := x509.ParseCertificate(offlineCrlIssuerCertPEM.Bytes) + if err != nil { + return nil, errors.Join(errOfflineCRLIssuer, err) + } + return crlIssuerCert, nil +} + +func retrieveCRL(crlDistributionPoints string, issuerCert *x509.Certificate, checkSign bool) (*x509.RevocationList, error) { + resp, err := http.Get(crlDistributionPoints) + if err != nil { + return nil, errors.Join(errRetrieveCRL, err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errors.Join(errReadCRL, err) + } + return parseVerifyCRL(body, issuerCert, checkSign) +} + +func parseVerifyCRL(clrB []byte, issuerCert *x509.Certificate, checkSign bool) (*x509.RevocationList, error) { + block, _ := pem.Decode(clrB) + if block == nil { + return nil, errParseCRL + } + + crl, err := x509.ParseRevocationList(block.Bytes) + if err != nil { + return nil, errors.Join(errParseCRL, err) + } + + if checkSign { + if err := crl.CheckSignatureFrom(issuerCert); err != nil { + return nil, errors.Join(errCRLSign, err) + } + } + + if crl.NextUpdate.Before(time.Now()) { + return nil, errExpiredCRL + } + return crl, nil +} + +func loadCertFile(certFile string) ([]byte, error) { + if certFile != "" { + return os.ReadFile(certFile) + } + return []byte{}, nil +} + +func retrieveIssuerCert(issuerSubject pkix.Name, certs []*x509.Certificate) *x509.Certificate { + for _, cert := range certs { + if cert.Subject.SerialNumber != "" && issuerSubject.SerialNumber != "" && cert.Subject.SerialNumber == issuerSubject.SerialNumber { + return cert + } + if (cert.Subject.SerialNumber == "" || issuerSubject.SerialNumber == "") && cert.Subject.String() == issuerSubject.String() { + return cert + } + } + return nil +} + +func parseCertificates(rawCerts [][]byte) ([]*x509.Certificate, error) { + var certs []*x509.Certificate + for _, rawCert := range rawCerts { + cert, err := x509.ParseCertificate(rawCert) + if err != nil { + return nil, errors.Join(errParseCert, err) + } + certs = append(certs, cert) + } + return certs, nil +} diff --git a/pkg/tls/verifier/ocsp/ocsp.go b/pkg/tls/verifier/ocsp/ocsp.go new file mode 100644 index 0000000..67e92c0 --- /dev/null +++ b/pkg/tls/verifier/ocsp/ocsp.go @@ -0,0 +1,239 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package ocsp + +import ( + "bytes" + "crypto" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "errors" + "fmt" + "io" + "net/http" + "net/url" + + "github.com/absmach/mproxy/pkg/tls/verifier" + "github.com/caarlos0/env/v10" + "golang.org/x/crypto/ocsp" +) + +var ( + errParseIssuerCrt = errors.New("failed to parse issuer certificate") + errCreateOCSPReq = errors.New("failed to create OCSP Request") + errCreateOCSPHTTPReq = errors.New("failed to create OCSP HTTP Request") + errParseOCSPUrl = errors.New("failed to parse OCSP server URL") + errOCSPReq = errors.New("OCSP request failed") + errOCSPReadResp = errors.New("failed to read OCSP response") + errParseOCSPRespForCert = errors.New("failed to parse OCSP Response for Certificate") + errIssuerCert = errors.New("neither the issuer certificate is present in the chain nor is the issuer certificate URL present in AIA") + errNoOCSPURL = errors.New("neither OCSP Server/Responder URL is not present AIA of certificate nor environmental variable OCSP_RESPONDER_URL have value") + errOCSPServerFailed = errors.New("OCSP Server Failed") + errOCSPUnknown = errors.New("OCSP status unknown") + errCertRevoked = errors.New("certificate revoked") + errRetrieveIssuerCrt = errors.New("failed to retrieve issuer certificate") + errReadIssuerCrt = errors.New("failed to read issuer certificate") + errIssuerCrtPEM = errors.New("failed to decode issuer certificate PEM") + + errParseCert = errors.New("failed to parse Certificate") + errClientCrt = errors.New("client certificate not received") +) + +type config struct { + OCSPDepth uint `env:"OCSP_DEPTH" envDefault:"0"` + OCSPResponderURL url.URL `env:"OCSP_RESPONDER_URL" envDefault:""` +} + +var _ verifier.Verifier = (*config)(nil) + +func New(opts env.Options) (verifier.Verifier, error) { + var c config + if err := env.ParseWithOptions(&c, opts); err != nil { + return nil, err + } + return &c, nil +} + +func (c *config) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + switch { + case len(verifiedChains) > 0: + return c.VerifyVerifiedPeerCertificates(verifiedChains) + case len(rawCerts) > 0: + var peerCertificates []*x509.Certificate + peerCertificates, err := parseCertificates(rawCerts) + if err != nil { + return err + } + return c.VerifyRawPeerCertificates(peerCertificates) + default: + return errClientCrt + } +} + +func (c *config) VerifyRawPeerCertificates(peerCertificates []*x509.Certificate) error { + for i, peerCertificate := range peerCertificates { + issuer := retrieveIssuerCert(peerCertificate.Issuer, peerCertificates) + if err := c.ocspVerify(peerCertificate, issuer); err != nil { + return err + } + if i+1 == int(c.OCSPDepth) { + return nil + } + } + return nil +} + +func (c *config) VerifyVerifiedPeerCertificates(verifiedPeerCertificateChains [][]*x509.Certificate) error { + for _, verifiedChain := range verifiedPeerCertificateChains { + for i := range verifiedChain { + cert := verifiedChain[i] + issuer := cert + if i+1 < len(verifiedChain) { + issuer = verifiedChain[i+1] + } + if err := c.ocspVerify(cert, issuer); err != nil { + return err + } + } + } + return nil +} + +func (c *config) ocspVerify(peerCertificate, issuerCert *x509.Certificate) error { + opts := &ocsp.RequestOptions{Hash: crypto.SHA256} + var err error + + if !isRootCA(peerCertificate) { + if issuerCert == nil { + if len(peerCertificate.IssuingCertificateURL) < 1 { + return fmt.Errorf("%w common name %s and serial number %x", errIssuerCert, peerCertificate.Subject.CommonName, peerCertificate.SerialNumber) + } + issuerCert, err = retrieveIssuingCertificate(peerCertificate.IssuingCertificateURL[0]) + if err != nil { + return err + } + } + } else { + issuerCert = peerCertificate + } + + buffer, err := ocsp.CreateRequest(peerCertificate, issuerCert, opts) + if err != nil { + return errors.Join(errCreateOCSPReq, err) + } + + ocspURL := "" + ocspURLHost := "" + if c.OCSPResponderURL.String() == "" { + if len(peerCertificate.OCSPServer) < 1 { + return fmt.Errorf("%w common name %s and serial number %x", errNoOCSPURL, peerCertificate.Subject.CommonName, peerCertificate.SerialNumber) + } + ocspURL = peerCertificate.OCSPServer[0] + ocspParsedURL, err := url.Parse(peerCertificate.OCSPServer[0]) + if err != nil { + return errors.Join(errParseOCSPUrl, err) + } + ocspURLHost = ocspParsedURL.Host + } else { + ocspURLHost = c.OCSPResponderURL.Host + ocspURL = c.OCSPResponderURL.String() + } + + httpRequest, err := http.NewRequest(http.MethodPost, ocspURL, bytes.NewBuffer(buffer)) + if err != nil { + return errors.Join(errCreateOCSPHTTPReq, err) + } + httpRequest.Header.Add("Content-Type", "application/ocsp-request") + httpRequest.Header.Add("Accept", "application/ocsp-response") + httpRequest.Header.Add("host", ocspURLHost) + + httpClient := &http.Client{} + httpResponse, err := httpClient.Do(httpRequest) + if err != nil { + return errors.Join(errOCSPReq, err) + } + defer httpResponse.Body.Close() + output, err := io.ReadAll(httpResponse.Body) + if err != nil { + return errors.Join(errOCSPReadResp, err) + } + ocspResponse, err := ocsp.ParseResponseForCert(output, peerCertificate, issuerCert) + if err != nil { + return errors.Join(errParseOCSPRespForCert, err) + } + switch ocspResponse.Status { + case ocsp.Good: + return nil + case ocsp.Revoked: + return fmt.Errorf("%w command name %s and serial number %x revoked at %v", errCertRevoked, peerCertificate.Subject.CommonName, peerCertificate.SerialNumber, ocspResponse.RevokedAt) + case ocsp.ServerFailed: + return errOCSPServerFailed + case ocsp.Unknown: + fallthrough + default: + return errOCSPUnknown + } +} + +func retrieveIssuerCert(issuerSubject pkix.Name, certs []*x509.Certificate) *x509.Certificate { + for _, cert := range certs { + if cert.Subject.SerialNumber != "" && issuerSubject.SerialNumber != "" && cert.Subject.SerialNumber == issuerSubject.SerialNumber { + return cert + } + if (cert.Subject.SerialNumber == "" || issuerSubject.SerialNumber == "") && cert.Subject.String() == issuerSubject.String() { + return cert + } + } + return nil +} + +func retrieveIssuingCertificate(issuingCertificateURL string) (*x509.Certificate, error) { + resp, err := http.Get(issuingCertificateURL) + if err != nil { + return nil, errors.Join(errRetrieveIssuerCrt, err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errors.Join(errReadIssuerCrt, err) + } + + block, _ := pem.Decode(body) + if block == nil { + return nil, errIssuerCrtPEM + } + + issCert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, errors.Join(errParseIssuerCrt, err) + } + return issCert, nil +} + +func isRootCA(cert *x509.Certificate) bool { + if cert.IsCA { + // Check AuthorityKeyId and SubjectKeyId are same. + if len(cert.AuthorityKeyId) > 0 && len(cert.SubjectKeyId) > 0 && bytes.Equal(cert.AuthorityKeyId, cert.SubjectKeyId) { + return true + } + // Alternatively, check Issuer and Subject are same. + if cert.Issuer.String() == cert.Subject.String() { + return true + } + } + return false +} + +func parseCertificates(rawCerts [][]byte) ([]*x509.Certificate, error) { + var certs []*x509.Certificate + for _, rawCert := range rawCerts { + cert, err := x509.ParseCertificate(rawCert) + if err != nil { + return nil, errors.Join(errParseCert, err) + } + certs = append(certs, cert) + } + return certs, nil +} diff --git a/pkg/tls/verifier/verifier.go b/pkg/tls/verifier/verifier.go new file mode 100644 index 0000000..ebd9bcc --- /dev/null +++ b/pkg/tls/verifier/verifier.go @@ -0,0 +1,24 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package verifier + +import "crypto/x509" + +type Verifier interface { + // VerifyPeerCertificate is used to verify certificates in TLS config. + VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error +} + +type Validator func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error + +func NewValidator(verifiers []Verifier) Validator { + return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + for _, vm := range verifiers { + if err := vm.VerifyPeerCertificate(rawCerts, verifiedChains); err != nil { + return err + } + } + return nil + } +} diff --git a/ssl/.gitignore b/ssl/.gitignore new file mode 100644 index 0000000..841e0f8 --- /dev/null +++ b/ssl/.gitignore @@ -0,0 +1,5 @@ +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +*srl +*conf diff --git a/ssl/Makefile b/ssl/Makefile new file mode 100644 index 0000000..8ce1aed --- /dev/null +++ b/ssl/Makefile @@ -0,0 +1,165 @@ +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +CRT_LOCATION = certs +O = Magistrala +OU_CA = magistrala_ca +OU_CRT = magistrala_crt +EA = info@magistrala.com +CN_CA = Magistrala_Self_Signed_CA +CN_SRV = localhost +CLINET_CN = mProxy_client +CRT_FILE_NAME = client +OCSP_URL=http://localhost:8080/ocsp +OCSP_CA_URL=http://localhost:8080/ca.pem +CRL_URL=http://localhost:8080/crl.pem + +define CERT_CONFIG +[ca] +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer:always + +[req] +req_extensions = v3_req +distinguished_name = dn +prompt = no + +[dn] +CN = $(COMMON_NAME) +C = RS +ST = RS +L = BELGRADE +O = MAGISTRALA +OU = MAGISTRALA + +[v3_req] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names +authorityInfoAccess = @issuer_info +crlDistributionPoints = @crl_info + +[alt_names] +DNS.1 = $(COMMON_NAME) + +[issuer_info] +OCSP;URI.0 = $(OCSP_URL) +caIssuers;URI.0 = $(OCSP_CA_URL) + +[crl_info] +URI.0 = $(CRL_URL) +endef + +define REVOKE_CONFIG +[ca] +default_ca = CA_default + +[CA_default] +database = $(CRT_LOCATION)/index.txt +crlnumber = $(CRT_LOCATION)/crlnumber + +default_crl_days = 30 +default_md = sha256 +policy = policy_match + +endef + +define ANNOUNCE_BODY +Version $(VERSION) of $(PACKAGE_NAME) has been released. + +It can be downloaded from $(DOWNLOAD_URL). + +etc, etc. +endef +all: clean_certs ca server_cert client_cert client_cert_revoked client_cert_unknown + +.PHONY: all clean_certs ca server_cert client_cert client_cert_revoked client_cert_unknown target + +# CA name and key is "ca". +ca: + openssl req -newkey rsa:2048 -x509 -nodes -sha512 -days 1095 \ + -keyout $(CRT_LOCATION)/ca.key -out $(CRT_LOCATION)/ca.crt -subj "/CN=$(CN_CA)/O=$(O)/OU=$(OU_CA)/emailAddress=$(EA)" + +# Server cert and key name is "server". +server_cert: + $(eval COMMON_NAME=localhost) + $(file > $(CRT_LOCATION)/server.cnf,$(CERT_CONFIG) ) + + # Create magistrala server key and CSR. + openssl req -new -sha256 -newkey rsa:4096 -nodes \ + -keyout $(CRT_LOCATION)/server.key \ + -out $(CRT_LOCATION)/server.csr \ + -config $(CRT_LOCATION)/server.cnf \ + -extensions v3_req + + # Sign server CSR. + openssl x509 -req -days 1000 -in $(CRT_LOCATION)/server.csr \ + -CA $(CRT_LOCATION)/ca.crt \ + -CAkey $(CRT_LOCATION)/ca.key \ + -CAcreateserial \ + -out $(CRT_LOCATION)/server.crt \ + -extfile $(CRT_LOCATION)/server.cnf \ + -extensions v3_req + + # Remove CSR. + rm $(CRT_LOCATION)/server.csr $(CRT_LOCATION)/server.cnf + + +client_cert: + # Create magistrala server key and CSR. + openssl req -new -sha256 -newkey rsa:4096 -nodes -keyout $(CRT_LOCATION)/$(CRT_FILE_NAME).key \ + -out $(CRT_LOCATION)/$(CRT_FILE_NAME).csr -subj "/CN=$(CLINET_CN)/O=$(O)/OU=$(OU_CRT)/emailAddress=$(EA)" + + # Sign client CSR. + openssl x509 -req -days 730 -in $(CRT_LOCATION)/$(CRT_FILE_NAME).csr -CA $(CRT_LOCATION)/ca.crt -CAkey $(CRT_LOCATION)/ca.key -CAcreateserial -out $(CRT_LOCATION)/$(CRT_FILE_NAME).crt + + # Remove CSR. + rm $(CRT_LOCATION)/$(CRT_FILE_NAME).csr + +client_cert_revoked: + # Create magistrala server key and CSR. + $(eval COMMON_NAME=$(CLINET_CN)) + $(file >> $(CRT_LOCATION)/client.cnf,$(CERT_CONFIG)) + + openssl req -new -sha256 -newkey rsa:4096 -nodes \ + -keyout $(CRT_LOCATION)/$(CRT_FILE_NAME)_revoked.key \ + -out $(CRT_LOCATION)/$(CRT_FILE_NAME)_revoked.csr \ + -config $(CRT_LOCATION)/client.cnf \ + -extensions v3_req + + + # Sign client CSR. + openssl x509 -req -days 730 -in $(CRT_LOCATION)/$(CRT_FILE_NAME)_revoked.csr \ + -CA $(CRT_LOCATION)/ca.crt \ + -CAkey $(CRT_LOCATION)/ca.key \ + -CAcreateserial \ + -out $(CRT_LOCATION)/$(CRT_FILE_NAME)_revoked.crt \ + -extfile $(CRT_LOCATION)/client.cnf \ + -extensions v3_req + + $(file > $(CRT_LOCATION)/ca.cnf,$(REVOKE_CONFIG) ) + @touch $(CRT_LOCATION)/index.txt + @echo "01" > $(CRT_LOCATION)/crlnumber + openssl ca -config $(CRT_LOCATION)/ca.cnf --cert $(CRT_LOCATION)/ca.crt --keyfile $(CRT_LOCATION)/ca.key -revoke $(CRT_LOCATION)/$(CRT_FILE_NAME)_revoked.crt + openssl ca -config $(CRT_LOCATION)/ca.cnf --cert $(CRT_LOCATION)/ca.crt --keyfile $(CRT_LOCATION)/ca.key -gencrl -out $(CRT_LOCATION)/revoked_certs.crl + + # Remove CSR. + rm $(CRT_LOCATION)/$(CRT_FILE_NAME)_revoked.csr $(CRT_LOCATION)/ca.cnf $(CRT_LOCATION)/client.cnf $(CRT_LOCATION)/index.txt* $(CRT_LOCATION)/crlnumber $(CRT_LOCATION)/crlnumber.old + +client_cert_unknown: + # Create magistrala server key and CSR. + openssl req -new -sha256 -newkey rsa:4096 -nodes -keyout $(CRT_LOCATION)/$(CRT_FILE_NAME)_unknown.key \ + -out $(CRT_LOCATION)/$(CRT_FILE_NAME)_unknown.csr -subj "/CN=$(CLINET_CN)/O=$(O)/OU=$(OU_CRT)/emailAddress=$(EA)" + + # Sign client CSR. + openssl x509 -req -days 730 -in $(CRT_LOCATION)/$(CRT_FILE_NAME)_unknown.csr -CA $(CRT_LOCATION)/ca.crt -CAkey $(CRT_LOCATION)/ca.key -CAcreateserial -out $(CRT_LOCATION)/$(CRT_FILE_NAME)_unknown.crt + + # Remove CSR. + rm $(CRT_LOCATION)/$(CRT_FILE_NAME)_unknown.csr + +clean_certs: + rm -r $(CRT_LOCATION)/*.crt + rm -r $(CRT_LOCATION)/*.key + rm -r $(CRT_LOCATION)/*.crl + rm -r $(CRT_LOCATION)/*.srl + diff --git a/ssl/certs/ca.crt b/ssl/certs/ca.crt new file mode 100644 index 0000000..205ff45 --- /dev/null +++ b/ssl/certs/ca.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDyzCCArOgAwIBAgIUCDyo3c8gsReIS/VFaUrDd+U8H3gwDQYJKoZIhvcNAQEN +BQAwdTEiMCAGA1UEAwwZTWFnaXN0cmFsYV9TZWxmX1NpZ25lZF9DQTETMBEGA1UE +CgwKTWFnaXN0cmFsYTEWMBQGA1UECwwNbWFnaXN0cmFsYV9jYTEiMCAGCSqGSIb3 +DQEJARYTaW5mb0BtYWdpc3RyYWxhLmNvbTAeFw0yNDAzMTkxMTEwMjJaFw0yNzAz +MTkxMTEwMjJaMHUxIjAgBgNVBAMMGU1hZ2lzdHJhbGFfU2VsZl9TaWduZWRfQ0Ex +EzARBgNVBAoMCk1hZ2lzdHJhbGExFjAUBgNVBAsMDW1hZ2lzdHJhbGFfY2ExIjAg +BgkqhkiG9w0BCQEWE2luZm9AbWFnaXN0cmFsYS5jb20wggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDxQKhLeTOTClxiGaxAW6QZRfVP2j/dUisU4O10MsVt +6FifkhPW/I+m9e19FLA2MaqZdqzF/iqjV549b03n3XtNyPoc8aoEKTDLWaMdVXll +S+2g31fQwNSyml7euw5n6OT3sNn7CMIr/AnjZ0Y4D4t7RV3re10IGnJfDkTFI+70 +I2SzWVuSe+tCfWLmAMi4vCAgY0+tagiLnHmo8dcnrCOcs27mn3w+kIqPnOhyE7Tn +rd+f15Az82cIwvLGfwOtAJvglKUQ8XBzSS+BBaYeo80ggCU/ALjZ1ojkzmmlSitq +JqiujZK0BNucbB3PCqW0U+u7oGabSx4uJJflNtVRFk0vAgMBAAGjUzBRMB0GA1Ud +DgQWBBSChJpaiCK4nRJ3BmIKR91M54xl1TAfBgNVHSMEGDAWgBSChJpaiCK4nRJ3 +BmIKR91M54xl1TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQBn +JYXjHgjD5FUD9G9NNk9Dt8UXP1SGf6fnbzVx0KZrxXBC5jKWfYZs0MvUJkVg5iho +uxJMKNc9IZ9zmbUMbn63Hfyx483/mOsnH+hOkdMdR3300HpUyjbKXviRO0Ftwinl +1LAxluBAAM8VOxSyeM6id9NgLpUszT8rnIWChIC00LSWMq5VAsANwIrJEGzWIJb9 +tTpjNG4eAWKUegZseutaNqTgAwJpQqhVUyO7O0T82qAsoo0N1yAU1rB/uP+vXu1T +FaCe4s+jf4XwChDoge190GreQfkzauy1NZCTJhXScCgyOeh4A8ng3Yv24SdSXqtF +dt8K8LaF4BJyGjKQ+GV2 +-----END CERTIFICATE----- diff --git a/ssl/certs/ca.key b/ssl/certs/ca.key new file mode 100644 index 0000000..ef12c59 --- /dev/null +++ b/ssl/certs/ca.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDxQKhLeTOTClxi +GaxAW6QZRfVP2j/dUisU4O10MsVt6FifkhPW/I+m9e19FLA2MaqZdqzF/iqjV549 +b03n3XtNyPoc8aoEKTDLWaMdVXllS+2g31fQwNSyml7euw5n6OT3sNn7CMIr/Anj +Z0Y4D4t7RV3re10IGnJfDkTFI+70I2SzWVuSe+tCfWLmAMi4vCAgY0+tagiLnHmo +8dcnrCOcs27mn3w+kIqPnOhyE7Tnrd+f15Az82cIwvLGfwOtAJvglKUQ8XBzSS+B +BaYeo80ggCU/ALjZ1ojkzmmlSitqJqiujZK0BNucbB3PCqW0U+u7oGabSx4uJJfl +NtVRFk0vAgMBAAECggEANnwkxg7UYAP6A0nLBinsMK0FDwi6XbCd/DQkTJbe0cik +oz0pb+dQRiPKg/I1Lbgf0FLmCD0zwfoaycMmdBQoMdQO8JR/kjr/Qx22YfyY0xDH +1Qo72fCC2OdWP7U9Nqh5dZsiT7iVqbtdPFdpm2WfWbK72F/fDeJzLBab324THF2l +skuM7W4KXWw6qDZdnJ5I5T3byaOAIDtrinu4akMW4Ifxl8LNtsRs4ZvxpCYi8Kk+ +Jit5JwT/ahsEJI/+C//r1WUsrCSfPVmOGGxa2H3UOemPbcnqB1yWc8zsklE9W+Ri +XiwqbQSkbCzynMrU0uW9GVJmnEzI2yPHQ08uT+yKCQKBgQD5+kT6C9R4if5LPo60 +dPF3uMjShjUHw24RI7+bMSj9auUfhj/CU7hLDe4ms2EU3ZbqhqT7bYguBDKYVCb7 +7TMGr5TnwHqC8gGVQ5ToWHuVAylda//PxR/ndjMN0LpTgJbPCTULHUn0vLiytwci +w0bZ+7Ea5ljKAXtvfjg4y++5hQKBgQD3EJOSpbfvVM7/xx1L7I0/aHj37gVkEP0Q +/Hus48Ers4erqArPs1s4bo5fxy08YfNqypxKHdBGyKSELBWr+eJOcdv8u9V2Ns5d +wX0RnG1bURNNbkhCmdS/EicNmINTnKry3w3D8vga8DZWHjuOvgSK2y9PU68Cc5k4 +m3aeVZswIwKBgFlTuHBN5+gHD6rduOr9Wwg4LPMl6C9VA7D2QFL5tzcPir7AH2qW +tiWD3dVQhmGJaZN299+s3VvsPhQ3BhNCSNibx68lFeJTDoPDOaPpcofmMFLk8+3P ++D2i/E10iBCsw7dKB/1nwvTEt4+NXexrMZdVnI0LQTE2ZGCMDxUksw5tAoGACVWK +/cNdC6G8oFU5eiDZxUeBKnzHn1w082tWkNWZ+rixq36Ryf8QtzUWE/BGeT2KIMK+ +3N37fHEgJg40U2cBcc1Dbmz6R5kSeuBQhVXPm9+YRxifikSdkQpyh0T30TTeEIb0 +0i5fyUTCCMbjoST0rvpQ80JKHmGXrFfTR3AciNkCgYBlFOr7iPgjVtVMtysScvmt +DPAKb+pUljThfKeFOdm9kKiBboUMnxuiVCscTA11yqXg7g74t++cyj7+AkfocWwh +5Y5uohgdarKEzQaCBLDtKJH4IxchHZ2euszEPK7/M0QX5PA3o3w1S8XIspS8lpwG +akehbEFGgB2ybLoiqXGF9g== +-----END PRIVATE KEY----- diff --git a/ssl/certs/client.crt b/ssl/certs/client.crt new file mode 100644 index 0000000..95600fd --- /dev/null +++ b/ssl/certs/client.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEZjCCA04CFCB+3u+r5Ex/K05tb/guMlmD9sfDMA0GCSqGSIb3DQEBCwUAMHUx +IjAgBgNVBAMMGU1hZ2lzdHJhbGFfU2VsZl9TaWduZWRfQ0ExEzARBgNVBAoMCk1h +Z2lzdHJhbGExFjAUBgNVBAsMDW1hZ2lzdHJhbGFfY2ExIjAgBgkqhkiG9w0BCQEW +E2luZm9AbWFnaXN0cmFsYS5jb20wHhcNMjQwMzE5MTExMDIzWhcNMjYwMzE5MTEx +MDIzWjBqMRYwFAYDVQQDDA1tUHJveHlfY2xpZW50MRMwEQYDVQQKDApNYWdpc3Ry +YWxhMRcwFQYDVQQLDA5tYWdpc3RyYWxhX2NydDEiMCAGCSqGSIb3DQEJARYTaW5m +b0BtYWdpc3RyYWxhLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AN20U2fu03nE3ZX7dizKwZ+EOCfqjjAkASDMHU1u+yqDHD41U/B5SfG7kHsvreZq +tCpmQsXNqKp+l4BccOJ4hCLTCNu4HFoztDexJm+0C/k+zKHB28q3D6Ko1sRvhXuY +s7khGrSuOs400+7Vq/nPrsB5hA85DXfC+LdOnXaEPHZTQgDG89ZwgxqJlz0oYVR3 +5T/W5H7z4psJe0xeA4sd3T36XP4ZBwNR8RdSURM/lSFaIc47kpZn0Iih55Q78UGA +YoX/lCP8QV0uP02N5Zgajq+/k72g6oLEDEOqNb+Nh3IcR2V65znGsjkTEuwTgjTr +sCLCS7cLaZR2lJ5se322FBQqj3pPD5/t7wGpcKwZfhwob/ZeFhaCvbJ85fjoGz/c +n7vVoTR6bFZZFBavdvpjd6XbSyK7CjP5TqcMq2C7hALxv8FI/XjBl4fCr8k9Byj+ +NzgxvWMGFAi9ioGPvAsxthMD7oRm7SNYoj4S3nIYYwc83irl3/XOBK16i0wnldrF +C1tp8RhXET4Iu2nf5w5rgbpNfU7/OrFnzzkds/ff69ywheSXZ1bimMBq5pcqqJXJ +SjsoWOAXWsTpCJSBqw7BEnqBGNSesVRF7e0ISJ0rroAJGX707pXr4ydrTRD2VFqa +lnqpe1zj9mcbfGaYWDaiVufxkL8iGsmiriBsPIL1K9ShAgMBAAEwDQYJKoZIhvcN +AQELBQADggEBAKeQ1ljVo1jgFYCSgDKgjWQpe6Ipgj8hq3axbciSRIOKF55CJEEp +L6H27LAE0o+m4+vNARYfnUz87YeLolNpQkO62q1CjbxQ7oQ0qOPPPiLsPP0UwqcO +y2d50EA5+XD6r16u7tEcdSV/y+1zxKtRcSWIjUgHXK95WyG5JNILC9wNrYj3WaWT +xXIHF6O4R9oxgdQE3wvV2r2jnsKKIiFJngjNcgYOf2XHFLnhokgw5wR0cPGwxohk +z0LrdL7igZcNgDsEDbXNqxldONt0klzyYKDkE585f/clMi8tLdRzKdQFsPdnxOxC +tAjPKsy1JbzDFZyNVG0k8ttZfedUwLmH/EM= +-----END CERTIFICATE----- diff --git a/ssl/certs/client.key b/ssl/certs/client.key new file mode 100644 index 0000000..03f12f3 --- /dev/null +++ b/ssl/certs/client.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +r4JF9DelBl8jVK5E9wnEzXyTjSxLcA== +-----END PRIVATE KEY----- diff --git a/ssl/certs/client_revoked.crt b/ssl/certs/client_revoked.crt new file mode 100644 index 0000000..7385713 --- /dev/null +++ b/ssl/certs/client_revoked.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFOjCCBCKgAwIBAgIUIH7e76vkTH8rTm1v+C4yWYP2x8QwDQYJKoZIhvcNAQEL +BQAwdTEiMCAGA1UEAwwZTWFnaXN0cmFsYV9TZWxmX1NpZ25lZF9DQTETMBEGA1UE +CgwKTWFnaXN0cmFsYTEWMBQGA1UECwwNbWFnaXN0cmFsYV9jYTEiMCAGCSqGSIb3 +DQEJARYTaW5mb0BtYWdpc3RyYWxhLmNvbTAeFw0yNDAzMTkxMTEwMjRaFw0yNjAz +MTkxMTEwMjRaMG8xFjAUBgNVBAMMDW1Qcm94eV9jbGllbnQxCzAJBgNVBAYTAlJT +MQswCQYDVQQIDAJSUzERMA8GA1UEBwwIQkVMR1JBREUxEzARBgNVBAoMCk1BR0lT +VFJBTEExEzARBgNVBAsMCk1BR0lTVFJBTEEwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDOZQmavjyqw0lS0rufhosh0TWlbS3a2abzlan99Y0i6EsHaEo5 +ZLtcy5rb+a4s9rhyBJJshtaUScA4Hq3FamB79zd8EOBsZEoltt0QMibOHWc4yTlA +jf2DbmFIpg26u9rdaXQprcikdSO+15ATB05A11ngeHrHGTLxyllnvDyqPyKulqXM +FjL60qFWxlBMPRy2P28zPBJoxM5VyXFcyWMSC4GcNZlK3nrhTJ7Gws4d9V6hyIJt +TihkF8lPvID5l8nGxVrVpjYPyqKt3OqHvAa4GHMoT/uUweQ+/DqIst3NdJ+y+tmI +a3SkkRemWnhUyG0Y23kjhBCOpogzpnqVduV6ghHDzvYstOnTQlh5qhEBzPiI0jVC +9DAYpUCYUeiysSw8PR+L6lJXkYSxP3Ila8cwk/50bkr/wGYU7rwfsaJbh2+Kr7Pw +BPdUqsHFC7/kdiz5S813F3/XTWc76i1pN0+wn81WUu14A4S6xv80+DvWG6hnMpe7 +TBOu2RoefMGhB2I9NihfoR0J18D2Y6sQqdsncdq18VHWtXlFeT5stCgXnUYCV5G9 +we32tgra3iaJXpiRV+Z/B0G9gr3XSSj3yFk4OXWreb6hdiWL3hlh/v0VcO4MFDGF +Z2361uvamuJV5mEPHC7efuIKZ+Lx/CXeQe5X8xoHEiCwbVVRjI+Rs5b3lQIDAQAB +o4HHMIHEMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMBgGA1UdEQQRMA+CDW1Qcm94 +eV9jbGllbnQwYAYIKwYBBQUHAQEEVDBSMCYGCCsGAQUFBzABhhpodHRwOi8vbG9j +YWxob3N0OjgwODAvb2NzcDAoBggrBgEFBQcwAoYcaHR0cDovL2xvY2FsaG9zdDo4 +MDgwL2NhLnBlbTAuBgNVHR8EJzAlMCOgIaAfhh1odHRwOi8vbG9jYWxob3N0Ojgw +ODAvY3JsLnBlbTANBgkqhkiG9w0BAQsFAAOCAQEAO65jlGzEEsCGTPwgj5UX2oc0 +Va2wzWlcj4Iv/LEvtuOy84WuFzA9EKuQSFHjB2sSuGknUvxUxzuLLfWBBe1uNSk+ +E8in+wkWrWKqiZkV5l2W+R4ECQi1DBIYeshTVvFY8dZ8GGiGIEjhidwa5vW1u2q8 +xQ4We9bAYDoH50tuw7xgojC0IqgKVbVLKKhBRrHB6DwL3gXK7WX/klkrxOj2ezcO +0rWqDogmHiV8Lu5Cx7X3qS634XkoM2HSoaz9mKrRqK626hzoGuy9zlZYqrMMJmbs ++2JZHjNkCjcPxQR0YZnNz9+pwy4D0UQpDEf1JU4OH5pOlrgZDVcLzwL+jQa2Vw== +-----END CERTIFICATE----- diff --git a/ssl/certs/client_revoked.key b/ssl/certs/client_revoked.key new file mode 100644 index 0000000..91f7c3a --- /dev/null +++ b/ssl/certs/client_revoked.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +RivuWLTgQYqF3Ria6Qm5rK+APM0wGA== +-----END PRIVATE KEY----- diff --git a/ssl/certs/client_unknown.crt b/ssl/certs/client_unknown.crt new file mode 100644 index 0000000..4aa1b73 --- /dev/null +++ b/ssl/certs/client_unknown.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEZjCCA04CFCB+3u+r5Ex/K05tb/guMlmD9sfFMA0GCSqGSIb3DQEBCwUAMHUx +IjAgBgNVBAMMGU1hZ2lzdHJhbGFfU2VsZl9TaWduZWRfQ0ExEzARBgNVBAoMCk1h +Z2lzdHJhbGExFjAUBgNVBAsMDW1hZ2lzdHJhbGFfY2ExIjAgBgkqhkiG9w0BCQEW +E2luZm9AbWFnaXN0cmFsYS5jb20wHhcNMjQwMzE5MTExMDI0WhcNMjYwMzE5MTEx +MDI0WjBqMRYwFAYDVQQDDA1tUHJveHlfY2xpZW50MRMwEQYDVQQKDApNYWdpc3Ry +YWxhMRcwFQYDVQQLDA5tYWdpc3RyYWxhX2NydDEiMCAGCSqGSIb3DQEJARYTaW5m +b0BtYWdpc3RyYWxhLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AOU2gLKM6tDROVu2lPzTupneY+c4DZ9g5t6zuGEGVY8h498NB1OFTZ1jRoYAkX92 +syADG/c4tyGLuashKyVm3Z+h/r9IkWBJOUkGo1WvW59OxIyo+1JYBgLk+GpdKyXJ +qkP2uCUa1gyMz6oqAemie6j9aqOtd9N2JmU2EA66qNFxeZ7YaKF8ccju5NcwhIr1 +Wd8MAmOfKVb2zVAzauTbF30g2qQNw0w8nNrOisPV4VUiM/NT5T+qoZojbKd+DAUe +gciVJPP76C10mkGJuff1al+l8V2fYHPhX6bSaVaoMoGIoLs4WHJUMb949aFrlnvy +36d4Z01XGrVbyINg2YGpiTTlHTVyLjgBkNDlLjWlu9hcIcfNVVgb/Zj95oli122O +mEabFsIHCeKodi8aSoDBnnmMeZmMm4H6/ZBz68C4xEA2GwabKUbPIKwWmbT6625F +/EXsUdNBWGlfgGEeDPjR1ZRUC2eZ9Wg4bsNadTa5YoLeSqPtY7bMI54bkaXYPCaj +i5XFxmUdB8aEGEKaT4kTxg9IATXH4kWU/LOwxXPzqttETLOTCa2KB8uzXPPjUvQM +nnobr60QBLfAavxTHq5I2P6Xz4ftW+UdkzGuQS03MdIeoxWcKCEi+pall5g+0g0U +CNRcdsEOm3fc1EmfBCKrfT3Un0O34LPwLokp79JdALOVAgMBAAEwDQYJKoZIhvcN +AQELBQADggEBAOh9qhVw4o2X5kFwK8dSrh0N/poz2x1VW7pnKzwiCiCnSXjCWcxp +GGH5+aUclzFD/PC2isj9YCuetgk0crKOr2ox8PVXH/Xf7/7XMg3NxeYEKdcB7dLi +bYGAog4FIwHGkVPFWjSrlbPnXVj8NN5B3Av/6HtfPAB58+wbHA6ZKmd6ShKhxf25 +ekBIlD1mQwzucy29OfWxJ/4SEo617nK3fIeTQEo2OXPhPxs0bT5tJdpJJfs2vF7j +XzmxKVvKwbNEbIL1emc9mB8RpmuXTowMfEAiPwXLVU3tLdA74lGDse1njJ5+UUn7 +Lt45WYnzpkBhoje0K0cLDgqWTGn+BmIIguI= +-----END CERTIFICATE----- diff --git a/ssl/certs/client_unknown.key b/ssl/certs/client_unknown.key new file mode 100644 index 0000000..228e91a --- /dev/null +++ b/ssl/certs/client_unknown.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +CFbbItXr1M8s1lN38O0lG4wBmPM/zg== +-----END PRIVATE KEY----- diff --git a/ssl/certs/revoked_certs.crl b/ssl/certs/revoked_certs.crl new file mode 100644 index 0000000..7f8f6dd --- /dev/null +++ b/ssl/certs/revoked_certs.crl @@ -0,0 +1,13 @@ +-----BEGIN X509 CRL----- +MIIB9zCB4AIBATANBgkqhkiG9w0BAQsFADB1MSIwIAYDVQQDDBlNYWdpc3RyYWxh +X1NlbGZfU2lnbmVkX0NBMRMwEQYDVQQKDApNYWdpc3RyYWxhMRYwFAYDVQQLDA1t +YWdpc3RyYWxhX2NhMSIwIAYJKoZIhvcNAQkBFhNpbmZvQG1hZ2lzdHJhbGEuY29t +Fw0yNDAzMTkxMTEwMjRaFw0yNDA0MTgxMTEwMjRaMCcwJQIUIH7e76vkTH8rTm1v ++C4yWYP2x8QXDTI0MDMxOTExMTAyNFqgDjAMMAoGA1UdFAQDAgEBMA0GCSqGSIb3 +DQEBCwUAA4IBAQCvRBSVGtSCmt4j/05g7R+/36ajhPUi/7vD97GcGq1XbPLjSseE +Z/i8QHDeShSlv1mKhx+MmKyl4sxwysBxxenxp1+a/Rmq2BtbPrv7bOtTllEo9tj0 +DuM7QtfZAuxYNDZF0CXGWlR8P9nHbWa8IEWW2+9XJiye0yn1DNE+DQwGnRasVsFM +mqPSQxMpHIckIdtJYVozqeqWRIBVCRmtXYNWEh/n+/3vgI1QV6jMlkt1lzdNETrS +1BDh3hiBktIxapO+IysVpYpyD44aNN5OV1KwQGGpnEFuAbP2TC61L5vqckRUBHyD +yaoLKCoz1MEmyXqUJJywvUDQHOZcuou0zz3v +-----END X509 CRL----- diff --git a/ssl/certs/server.crt b/ssl/certs/server.crt new file mode 100644 index 0000000..93fe0b7 --- /dev/null +++ b/ssl/certs/server.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFMjCCBBqgAwIBAgIUIH7e76vkTH8rTm1v+C4yWYP2x8IwDQYJKoZIhvcNAQEL +BQAwdTEiMCAGA1UEAwwZTWFnaXN0cmFsYV9TZWxmX1NpZ25lZF9DQTETMBEGA1UE +CgwKTWFnaXN0cmFsYTEWMBQGA1UECwwNbWFnaXN0cmFsYV9jYTEiMCAGCSqGSIb3 +DQEJARYTaW5mb0BtYWdpc3RyYWxhLmNvbTAeFw0yNDAzMTkxMTEwMjJaFw0yNjEy +MTQxMTEwMjJaMGsxEjAQBgNVBAMMCWxvY2FsaG9zdDELMAkGA1UEBhMCUlMxCzAJ +BgNVBAgMAlJTMREwDwYDVQQHDAhCRUxHUkFERTETMBEGA1UECgwKTUFHSVNUUkFM +QTETMBEGA1UECwwKTUFHSVNUUkFMQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBANQkDr7IcabLmfquVaLSBEehJSdifblKj3wpgpLDnzBm7M49NJhnxfN2 +yUe+26fSoF7r/Pw/VICBbzCkJI45ffhSg7bew73esj5Vt5+7yeAl4P4JWxQ+2o5b +48kVe4ljPk2p/Hi8au2mw1wc8Etayw90Flh1Urqv4OTiwSOhkYSqFsEd/xodmOEf +IiCBQqEcYDEVy+RRfLpE023JRz1Ed4BFe6h9PBkKHCThZhuYzKW3xgZW2aoIvl/0 +d/dwlnR6OmLwOWeYr/8P9jh6xJp/Gm6kOcdT2alfXXzBZtC//mtvfHIht2lqWQbk +qhBtJkbaX/pHIEwbV+qeA8HcvkyUsddteJtwGDPbjH2DfgMDnWo9NtN1yBNH0Lcn +GAZUJRpEkcw47uK/j/TQuSiTcUAZNFrKpK7CVJ0I4q3m6iROsVIT5wA60Lz+S+RZ +mdHqEhZkrhvnkNiNIObmA5Mai5XQKAJCRhbetq7Afcd1iC5uvw08URWSYdPV/HT4 +L0HJnB+KRGh0G60BDz70QpDp/45GfFLMDa2VYt6I5sY+3GRM9int1Zux69k+FFvY +fp1GWRzNQmTnCDaHcRrk4XaaizVjp96io0Qh1i85mct7OwTwxgR9+qHtC3CK6XOx +eWAUK7SAk5LMoQyQ5ZeT538d8jT5cN5h5UqelUyQuP6eNiGx/CmFAgMBAAGjgcMw +gcAwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwFAYDVR0RBA0wC4IJbG9jYWxob3N0 +MGAGCCsGAQUFBwEBBFQwUjAmBggrBgEFBQcwAYYaaHR0cDovL2xvY2FsaG9zdDo4 +MDgwL29jc3AwKAYIKwYBBQUHMAKGHGh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9jYS5w +ZW0wLgYDVR0fBCcwJTAjoCGgH4YdaHR0cDovL2xvY2FsaG9zdDo4MDgwL2NybC5w +ZW0wDQYJKoZIhvcNAQELBQADggEBAKHJix8BMBBKU0+GVmLwXEPdfbYlxTPUPumS +nmLj4I5qyhBP8c85NjdIQfdnHeu4MOLOmnJ0TCMrBpezwYa5Azs43strMHNXBZhE +9sQbasN16aJDRYvX9dJl4bnkj4IWNGLrj2QDHn3CJwugeVnpjIUgVpfCvISM84o8 +Fk49CJ+5HrGxmXSvBk5KE9hzogV6+GuxUxNLwmf/ZT2j5DiUc2cf+Nf49J7TN8TB +ouaJ2k7T5AzO+FbCjG+h42DguGaYtoX5UWKrX+ArUf2hwczznN3NZhg4Ylr5olU+ +lGc3Iub97bK8B1p03aOgmqTfr/TCMPGEJvbZJiVQcGXF9X3hmNc= +-----END CERTIFICATE----- diff --git a/ssl/certs/server.key b/ssl/certs/server.key new file mode 100644 index 0000000..8cd269e --- /dev/null +++ b/ssl/certs/server.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +DGJ8ittbS+/BR5eNobICUJJrmjtpA4g= +-----END PRIVATE KEY-----