Skip to content

Commit d91e84d

Browse files
committedMar 15, 2020
feat(signature): refactore the trust store system
1 parent a5fe96d commit d91e84d

File tree

11 files changed

+104
-126
lines changed

11 files changed

+104
-126
lines changed
 

‎README.md

+4-5
Original file line numberDiff line numberDiff line change
@@ -293,16 +293,15 @@ $ curl -u api:test -XPOST "http://localhost:8080/echo?msg=hello"
293293

294294
You can ensure message integrity (and authenticity) with [HTTP Signatures](https://www.ietf.org/archive/id/draft-cavage-http-signatures-12.txt).
295295

296-
To activate HTTP signature verification, you have to configure the key store:
296+
To activate HTTP signature verification, you have to configure the trust store:
297297

298298
```bash
299-
$ export WHD_KEY_STORE_URI=file:///etc/webhookd/keys
299+
$ export WHD_TRUST_STORE_FILE=/etc/webhookd/pubkey.pem
300300
$ # or
301-
$ webhookd --key-store-uri file:///etc/webhookd/keys
301+
$ webhookd --trust-store-file /etc/webhookd/pubkey.pem
302302
```
303303

304-
Note that only `file://` URI s currently supported.
305-
All public keys stored in PEM format in the targeted directory will be loaded.
304+
Public key is stored in PEM format.
306305

307306
Once configured, you must call webhooks using a valid HTTP signature:
308307

‎etc/default/webhookd.env

+6-6
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@
55
# Output debug logs, default is false
66
#WHD_DEBUG=false
77

8-
# Key store URI, disabled by default
9-
# Enable HTTP signature verification if set.
10-
# Example: `file:///etc/webhookd/keys`
11-
#KEY_STORE_URI=
8+
# Maximum hook execution time in second, default is 10
9+
#WHD_HOOK_TIMEOUT=10
1210

1311
# HTTP listen address, default is ":8080"
1412
# Example: `localhost:8080` or `:8080` for all interfaces
@@ -30,8 +28,10 @@
3028
# Scripts location, default is "scripts"
3129
#WHD_SCRIPTS="scripts"
3230

33-
# Maximum hook execution time in second, default is 10
34-
#WHD_HOOK_TIMEOUT=10
31+
# Trust store URI, disabled by default
32+
# Enable HTTP signature verification if set.
33+
# Example: `/etc/webhookd/pubkey.pem`
34+
#WHD_TRUST_STORE_FILE=
3535

3636
# TLS listend address, disabled by default
3737
# Example: `localhost:8443` or `:8443` for all interfaces

‎pkg/api/router.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ func NewRouter(conf *config.Config) *http.ServeMux {
2626
}
2727

2828
// Load key store...
29-
keystore, err := pubkey.NewKeyStore(conf.KeyStoreURI)
29+
keystore, err := pubkey.NewTrustStore(conf.TrustStoreFile)
3030
if err != nil {
31-
logger.Warning.Printf("unable to load key store (\"%s\"): %s\n", conf.KeyStoreURI, err)
31+
logger.Warning.Printf("unable to load trust store (\"%s\"): %s\n", conf.TrustStoreFile, err)
3232
}
3333
if keystore != nil {
3434
middlewares = append(middlewares, middleware.HTTPSignature(keystore))

‎pkg/config/config.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ type Config struct {
1414
PasswdFile string `flag:"passwd-file" desc:"Password file for basic HTTP authentication" default:".htpasswd"`
1515
LogDir string `flag:"log-dir" desc:"Hook execution logs location" default:""`
1616
NotificationURI string `flag:"notification-uri" desc:"Notification URI"`
17-
KeyStoreURI string `flag:"key-store-uri" desc:"Key store URI used by HTTP signature verifier"`
17+
TrustStoreFile string `flag:"trust-store-file" desc:"Trust store used by HTTP signature verifier (.pem or .p12)"`
1818
}

‎pkg/middleware/signature.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
)
99

1010
// HTTPSignature is a middleware to checks HTTP request signature
11-
func HTTPSignature(keyStore pubkey.KeyStore) Middleware {
11+
func HTTPSignature(trustStore pubkey.TrustStore) Middleware {
1212
return func(next http.Handler) http.Handler {
1313
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1414
verifier, err := httpsig.NewVerifier(r)
@@ -18,7 +18,7 @@ func HTTPSignature(keyStore pubkey.KeyStore) Middleware {
1818
return
1919
}
2020
pubKeyID := verifier.KeyId()
21-
pubKey, algo, err := keyStore.Get(pubKeyID)
21+
pubKey, algo, err := trustStore.Get(pubKeyID)
2222
if err != nil {
2323
w.WriteHeader(400)
2424
w.Write([]byte("invalid HTTP signature: " + err.Error()))

‎pkg/pubkey/directory_keystore.go

-71
This file was deleted.

‎pkg/pubkey/keystore.go

-34
This file was deleted.

‎pkg/pubkey/pem_truststore.go

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package pubkey
2+
3+
import (
4+
"crypto"
5+
"crypto/rsa"
6+
"crypto/x509"
7+
"encoding/pem"
8+
"fmt"
9+
"io/ioutil"
10+
11+
"github.com/go-fed/httpsig"
12+
)
13+
14+
type pemTrustStore struct {
15+
key crypto.PublicKey
16+
}
17+
18+
func (ts *pemTrustStore) Get(keyID string) (crypto.PublicKey, httpsig.Algorithm, error) {
19+
return ts.key, defaultAlgorithm, nil
20+
}
21+
22+
func newPEMTrustStore(filename string) (*pemTrustStore, error) {
23+
data, err := ioutil.ReadFile(filename)
24+
if err != nil {
25+
return nil, err
26+
}
27+
28+
block, _ := pem.Decode(data)
29+
if block == nil {
30+
return nil, fmt.Errorf("invalid PEM file: %s", filename)
31+
}
32+
33+
var rsaPublicKey *rsa.PublicKey
34+
switch block.Type {
35+
case "PUBLIC KEY":
36+
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
37+
if err != nil {
38+
return nil, err
39+
}
40+
rsaPublicKey, _ = pub.(*rsa.PublicKey)
41+
case "CERTIFICATE":
42+
cert, err := x509.ParseCertificate(block.Bytes)
43+
if err != nil {
44+
return nil, err
45+
}
46+
rsaPublicKey, _ = cert.PublicKey.(*rsa.PublicKey)
47+
}
48+
49+
if rsaPublicKey == nil {
50+
return nil, fmt.Errorf("no RSA public key found: %s", filename)
51+
}
52+
return &pemTrustStore{
53+
key: rsaPublicKey,
54+
}, nil
55+
}

‎pkg/pubkey/test/keystore_test.go renamed to ‎pkg/pubkey/test/truststore_test.go

+1-4
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,12 @@ import (
1212
func TestKeyStore(t *testing.T) {
1313
logger.Init("warn")
1414

15-
ks, err := pubkey.NewKeyStore("file://.")
15+
ks, err := pubkey.NewTrustStore("test-key.pem")
1616
assert.Nil(t, err, "")
1717
assert.NotNil(t, ks, "")
1818

1919
pk, algo, err := ks.Get("test")
2020
assert.Nil(t, err, "")
2121
assert.NotNil(t, pk, "")
2222
assert.Equal(t, httpsig.RSA_SHA256, algo, "")
23-
24-
_, _, err = ks.Get("notfound")
25-
assert.NotNil(t, err, "")
2623
}

‎pkg/pubkey/truststore.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package pubkey
2+
3+
import (
4+
"crypto"
5+
"fmt"
6+
"path/filepath"
7+
8+
"github.com/go-fed/httpsig"
9+
)
10+
11+
const defaultAlgorithm = httpsig.RSA_SHA256
12+
13+
// TrustStore is a generic interface to retrieve a public key
14+
type TrustStore interface {
15+
Get(keyID string) (crypto.PublicKey, httpsig.Algorithm, error)
16+
}
17+
18+
// NewTrustStore creates new Key Store from URI
19+
func NewTrustStore(filename string) (store TrustStore, err error) {
20+
if filename == "" {
21+
return nil, nil
22+
}
23+
24+
switch filepath.Ext(filename) {
25+
case ".pem":
26+
store, err = newPEMTrustStore(filename)
27+
default:
28+
err = fmt.Errorf("unsupported TrustStore file format: %s", filename)
29+
}
30+
31+
return
32+
}

‎tooling/httpsig/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ MIIEowIBAAKCAQEAwdCB5DZD0cFeJYUu1W3IlNN9y+NZC/Jqktdkn8/WHlXec07n
2020
- Start Webhookd with HTTP signature support:
2121

2222
```bash
23-
$ webhookd -key-store-uri file://.
23+
$ webhookd --trust-store-file ./key-pub.pem
2424
```
2525

2626
- Make HTTP signed request:

0 commit comments

Comments
 (0)
Please sign in to comment.