Skip to content

Commit

Permalink
add X509 client certificate support
Browse files Browse the repository at this point in the history
  • Loading branch information
ctron committed May 8, 2020
1 parent d320b7a commit 2a63d7e
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 26 deletions.
60 changes: 48 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,49 @@

This is a simple command line tool for testing Eclipse Hono™.

## Authentication

### Username and password

You can set the username and password for all operations using the `--username`
and `--password` parameters.

When publishing data the username is normally a combination of `<auth-id>@<tenant>`.
As you are already providing the tenant, you can use the `--auth-id` parameter
instead, which will internally generate the correct user name, by adding the
tenant suffix.

<dl>
<dt><code>--auth-id,-a</code></dt>
<dd>The username to use for authenticating with the backend.</dd>
<dt><code>--username,-u</code></dt>
<dd>The full username to use for authenticating with the backend.</dd>
<dt><code>--password,-p</code></dt>
<dd>The password to use for authenticating with the backend.</dd>
</dl>

Assuming you have a tenant `foo` and an authentication id of `auth1`, then
you can use either:

--username auth1@foo

Or:

--auth-id auth1

### X.509 client certificates

It is possible to use X.509 client certificates, instead of
username/password authentication. For this you can use the parameters:

<dl>
<dt><code>--client-key</code></dt>
<dd>The path to an X.509 PKCS#8 encoded private key</dd>
<dt><code>--client-cert</code></dt>
<dd>The path to an file containing a PEM encoded client certificate chain</dd>
</dl>


## Start a test consumer

Fill in your connection information, and then execute the following command:
Expand All @@ -25,12 +68,6 @@ You can use the following flags:
<dt><code>--cert</code></dt>
<dd>Path to the certificate bundle in PEM format (overrides system CA certs)</dd>

<dt><code>--username</code></dt>
<dd>Tenant username (if required)</dd>

<dt><code>--password</code></dt>
<dd>Tenant password (if required)</dd>

</dl>

### Telemetry & event
Expand Down Expand Up @@ -63,9 +100,9 @@ The following readers are available:

Fill in your connection information, and then execute the following command:

hot publish http telemety https://my.server tenant device auth password payload
hot publish http telemety https://my.server tenant device payload --username auth --password password

The following flags are supported:
The following additional flags are supported:

<dl>

Expand All @@ -82,9 +119,9 @@ wait for a command to the device</dd>

Fill in your connection information, and then execute the following command:

hot publish mqtt telemety ssl://my.server tenant device auth password payload
hot publish mqtt telemety ssl://my.server tenant device payload

The following flags are supported:
The following additional flags are supported:

<dl>

Expand All @@ -97,10 +134,9 @@ wait for a command to the device</dd>

</dl>


## Building

Building requires Go 1.12.x. You can build the binary by executing:
Building requires Go 1.13.x. You can build the binary by executing:

GO111MODULE=on go build -o hot ./cmd

33 changes: 24 additions & 9 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var insecure bool
var cert = ""
var clientKey = ""
var clientCert = ""
var authId = ""
var username = ""
var password = ""
var contentTypeFlag = "text/plain"
Expand Down Expand Up @@ -116,15 +117,26 @@ func loadClientCerts() ([]tls.Certificate, error) {
return nil, err
}

certBlock, _ := pem.Decode(certFile)
if certBlock == nil {
return nil, fmt.Errorf("failed to parse PEM cert file: %s", keyFile)
var certs = make([][]byte, 0)
var certBlock *pem.Block
rest := certFile
for {
certBlock, rest = pem.Decode(rest)
if certBlock != nil {
certs = append(certs, certBlock.Bytes)
} else {
break
}
}

if len(certs) <= 0 {
return nil, fmt.Errorf("unable to find any certificates in: %s", keyFile)
}

// certificate

return []tls.Certificate{
{Certificate: [][]byte{certBlock.Bytes}, PrivateKey: key},
{Certificate: certs, PrivateKey: key},
}, nil
}

Expand Down Expand Up @@ -168,7 +180,8 @@ func main() {
URI: args[1],
Tenant: args[2],
DeviceId: args[3],
AuthenticationId: username,
AuthenticationId: authId,
Username: username,
Password: password,
},
QoS: qos,
Expand All @@ -181,15 +194,16 @@ func main() {
cmdPublishMqtt := &cobra.Command{
Use: "mqtt <telemetry|event> <mqtt endpoint uri> <tenant> <deviceId> <payload>",
Short: "Publish via MQTT",
Args: cobra.ExactArgs(7),
Args: cobra.ExactArgs(5),
Run: func(cmd *cobra.Command, args []string) {
if err := publishMqtt(MqttPublishInformation{
CommonPublishInformation: CommonPublishInformation{
MessageType: args[0],
URI: args[1],
Tenant: args[2],
DeviceId: args[3],
AuthenticationId: username,
AuthenticationId: authId,
Username: username,
Password: password,
},
QoS: qos,
Expand Down Expand Up @@ -232,8 +246,9 @@ func main() {
cmdRoot.PersistentFlags().StringVar(&clientCert, "client-cert", "", "Path to a certificate in PEM format for client authentication")
cmdRoot.PersistentFlags().StringVar(&clientKey, "client-key", "", "Path to a key in PEM format for client authentication")

cmdRoot.PersistentFlags().StringVarP(&username, "username", "u", "", "Username")
cmdRoot.PersistentFlags().StringVarP(&password, "password", "p", "", "Password")
cmdRoot.PersistentFlags().StringVarP(&authId, "auth-id", "a", "", "Authentication ID for authenticating with the backend")
cmdRoot.PersistentFlags().StringVarP(&username, "username", "u", "", "Full username for authenticating with the backend")
cmdRoot.PersistentFlags().StringVarP(&password, "password", "p", "", "Password for authenticating with the backend")

if err := cmdRoot.Execute(); err != nil {
println(err.Error())
Expand Down
13 changes: 11 additions & 2 deletions cmd/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,18 @@ type CommonPublishInformation struct {
DeviceId string

AuthenticationId string
Username string
Password string
}

func (c *CommonPublishInformation) Username() string {
return c.AuthenticationId + "@" + c.Tenant
func (c CommonPublishInformation) HasUsernamePassword() bool {
return c.Password != "" || c.Username != "" || c.AuthenticationId != ""
}

func (c CommonPublishInformation) EffectiveUsername() string {
if c.Username != "" {
return c.Username
} else {
return c.AuthenticationId + "@" + c.Tenant
}
}
4 changes: 3 additions & 1 deletion cmd/publish_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ func publishHttp(info HttpPublishInformation, encoder encoding.PayloadEncoder, p
return err
}

request.SetBasicAuth(info.Username(), info.Password)
if info.HasUsernamePassword() {
request.SetBasicAuth(info.EffectiveUsername(), info.Password)
}

if qos > 0 {
request.Header.Set("QoS-Level", strconv.Itoa(int(qos)))
Expand Down
4 changes: 2 additions & 2 deletions cmd/publish_mqtt.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func publishMqtt(info MqttPublishInformation, encoder encoding.PayloadEncoder, p
opts := MQTT.NewClientOptions()
opts.AddBroker(info.URI)
opts.SetClientID(info.DeviceId)
if info.AuthenticationId != "" {
opts.SetUsername(info.Username())
if info.HasUsernamePassword() {
opts.SetUsername(info.EffectiveUsername())
opts.SetPassword(info.Password)
}
opts.SetCleanSession(true)
Expand Down

0 comments on commit 2a63d7e

Please sign in to comment.