mirror of
https://github.com/ncarlier/webhookd.git
synced 2025-04-05 20:23:44 +00:00
feat(signature): signature refactoring
- add ed5519 HTTP signature support - refactor truststore package - add P12 trust store support close #72
This commit is contained in:
parent
9020473d14
commit
f2054d2dc4
20
README.md
20
README.md
|
@ -311,9 +311,14 @@ $ curl -u api:test -XPOST "http://localhost:8080/echo?msg=hello"
|
|||
|
||||
### Signature
|
||||
|
||||
You can ensure message integrity (and authenticity) with [HTTP Signatures](https://www.ietf.org/archive/id/draft-cavage-http-signatures-12.txt).
|
||||
You can ensure message integrity (and authenticity) by signing HTTP requests.
|
||||
|
||||
To activate HTTP signature verification, you have to configure the trust store:
|
||||
Webhookd supports 2 signature methods:
|
||||
|
||||
- [HTTP Signatures](https://www.ietf.org/archive/id/draft-cavage-http-signatures-12.txt)
|
||||
- [Ed25519 Signature](https://ed25519.cr.yp.to/) (used by [Discord](https://discord.com/developers/docs/interactions/receiving-and-responding#security-and-authorization))
|
||||
|
||||
To activate request signature verification, you have to configure the trust store:
|
||||
|
||||
```bash
|
||||
$ export WHD_TRUST_STORE_FILE=/etc/webhookd/pubkey.pem
|
||||
|
@ -323,17 +328,24 @@ $ webhookd --trust-store-file /etc/webhookd/pubkey.pem
|
|||
|
||||
Public key is stored in PEM format.
|
||||
|
||||
Once configured, you must call webhooks using a valid HTTP signature:
|
||||
Once configured, you must call webhooks using a valid signature:
|
||||
|
||||
```bash
|
||||
# Using HTTP Signature:
|
||||
$ curl -X POST \
|
||||
-H 'Date: <req-date>' \
|
||||
-H 'Signature: keyId=<key-id>,algorithm="rsa-sha256",headers="(request-target) date",signature=<signature-string>' \
|
||||
-H 'Accept: application/json' \
|
||||
"http://localhost:8080/echo?msg=hello"
|
||||
# or using Ed25519 Signature:
|
||||
$ curl -X POST \
|
||||
-H 'X-Signature-Timestamp: <timestamp>' \
|
||||
-H 'X-Signature-Ed25519: <signature-string>' \
|
||||
-H 'Accept: application/json' \
|
||||
"http://localhost:8080/echo?msg=hello"
|
||||
```
|
||||
|
||||
You can find a small HTTP client in the ["tooling" directory](./tooling/httpsig/README.md) that is capable of forging HTTP signatures.
|
||||
You can find a small HTTP client in the ["tooling" directory](./tooling/httpsig/README.md) that is capable of forging `HTTP signatures`.
|
||||
|
||||
### TLS
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"github.com/ncarlier/webhookd/pkg/config"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/middleware"
|
||||
"github.com/ncarlier/webhookd/pkg/pubkey"
|
||||
"github.com/ncarlier/webhookd/pkg/truststore"
|
||||
)
|
||||
|
||||
var commonMiddlewares = middleware.Middlewares{
|
||||
|
@ -21,12 +21,12 @@ func buildMiddlewares(conf *config.Config) middleware.Middlewares {
|
|||
}
|
||||
|
||||
// Load trust store...
|
||||
trustStore, err := pubkey.NewTrustStore(conf.TrustStoreFile)
|
||||
ts, err := truststore.New(conf.TrustStoreFile)
|
||||
if err != nil {
|
||||
logger.Warning.Printf("unable to load trust store (\"%s\"): %s\n", conf.TrustStoreFile, err)
|
||||
}
|
||||
if trustStore != nil {
|
||||
middlewares = middlewares.UseAfter(middleware.HTTPSignature(trustStore))
|
||||
if ts != nil {
|
||||
middlewares = middlewares.UseAfter(middleware.Signature(ts))
|
||||
}
|
||||
|
||||
// Load authenticator...
|
||||
|
|
|
@ -3,31 +3,21 @@ package middleware
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-fed/httpsig"
|
||||
"github.com/ncarlier/webhookd/pkg/pubkey"
|
||||
"github.com/ncarlier/webhookd/pkg/middleware/signature"
|
||||
"github.com/ncarlier/webhookd/pkg/truststore"
|
||||
)
|
||||
|
||||
// HTTPSignature is a middleware to checks HTTP request signature
|
||||
func HTTPSignature(trustStore pubkey.TrustStore) Middleware {
|
||||
// Signature is a middleware to checks HTTP request signature
|
||||
func Signature(ts truststore.TrustStore) Middleware {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
verifier, err := httpsig.NewVerifier(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("invalid HTTP signature: " + err.Error()))
|
||||
return
|
||||
handler := signature.HTTPSignatureHandler
|
||||
if signature.IsEd25519SignatureRequest(r.Header) {
|
||||
handler = signature.Ed25519SignatureHandler
|
||||
}
|
||||
pubKeyID := verifier.KeyId()
|
||||
entry := trustStore.Get(pubKeyID)
|
||||
if entry == nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("invalid HTTP signature: public key not found: " + pubKeyID))
|
||||
return
|
||||
}
|
||||
err = verifier.Verify(entry.Pubkey, entry.Algorithm)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("invalid HTTP signature: " + err.Error()))
|
||||
if err := handler(r, ts); err != nil {
|
||||
w.WriteHeader(401)
|
||||
w.Write([]byte("401 Unauthorized: " + err.Error()))
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
|
|
70
pkg/middleware/signature/ed25519-signature.go
Normal file
70
pkg/middleware/signature/ed25519-signature.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package signature
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/truststore"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultKeyId = "default"
|
||||
xSignatureEd25519 = "X-Signature-Ed25519"
|
||||
xSignatureTimestamp = "X-Signature-Timestamp"
|
||||
)
|
||||
|
||||
// IsEd25519SignatureRequest test if HTTP headers contains Ed25519 Signature
|
||||
func IsEd25519SignatureRequest(headers http.Header) bool {
|
||||
return headers.Get(xSignatureEd25519) != ""
|
||||
}
|
||||
|
||||
// Ed25519SignatureHandler validate request HTTP signature
|
||||
func Ed25519SignatureHandler(r *http.Request, ts truststore.TrustStore) error {
|
||||
pubkey := ts.GetPublicKey(defaultKeyId)
|
||||
if pubkey == nil {
|
||||
return fmt.Errorf("public key not found: %s", defaultKeyId)
|
||||
}
|
||||
|
||||
key, ok := pubkey.(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return errors.New("invalid public key: verify the algorithm")
|
||||
}
|
||||
|
||||
value := r.Header.Get(xSignatureEd25519)
|
||||
timestamp := r.Header.Get(xSignatureTimestamp)
|
||||
if value == "" || timestamp == "" {
|
||||
return errors.New("missing signature header")
|
||||
}
|
||||
|
||||
sig, err := hex.DecodeString(value)
|
||||
if err != nil || len(sig) != ed25519.SignatureSize || sig[63]&224 != 0 {
|
||||
return fmt.Errorf("invalid signature format: %s", sig)
|
||||
}
|
||||
|
||||
var msg bytes.Buffer
|
||||
msg.WriteString(timestamp)
|
||||
|
||||
defer r.Body.Close()
|
||||
var body bytes.Buffer
|
||||
|
||||
// Copy the original body back into the request after finishing.
|
||||
defer func() {
|
||||
r.Body = io.NopCloser(&body)
|
||||
}()
|
||||
|
||||
// Copy body into buffers
|
||||
_, err = io.Copy(&msg, io.TeeReader(r.Body, &body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ed25519.Verify(key, msg.Bytes(), sig) {
|
||||
return errors.New("invalid signature")
|
||||
}
|
||||
return nil
|
||||
}
|
28
pkg/middleware/signature/http-signature.go
Normal file
28
pkg/middleware/signature/http-signature.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package signature
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-fed/httpsig"
|
||||
"github.com/ncarlier/webhookd/pkg/truststore"
|
||||
)
|
||||
|
||||
// HTTPSignatureHandler validate request HTTP signature
|
||||
func HTTPSignatureHandler(r *http.Request, ts truststore.TrustStore) error {
|
||||
verifier, err := httpsig.NewVerifier(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubkeyID := verifier.KeyId()
|
||||
pubkey := ts.GetPublicKey(pubkeyID)
|
||||
if pubkey == nil {
|
||||
return fmt.Errorf("public key not found: %s", pubkeyID)
|
||||
}
|
||||
// TODO dynamic algo
|
||||
err = verifier.Verify(pubkey, httpsig.RSA_SHA256)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
50
pkg/middleware/signature/test/ed5519-signature_test.go
Normal file
50
pkg/middleware/signature/test/ed5519-signature_test.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/assert"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/middleware/signature"
|
||||
"github.com/ncarlier/webhookd/pkg/truststore"
|
||||
)
|
||||
|
||||
func TestEd5519Signature(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
pubkey, privkey, err := ed25519.GenerateKey(rand.Reader)
|
||||
assert.Nil(t, err, "")
|
||||
|
||||
ts := &truststore.InMemoryTrustStore{
|
||||
Keys: map[string]crypto.PublicKey{
|
||||
"default": pubkey,
|
||||
},
|
||||
}
|
||||
|
||||
body := "this is a test"
|
||||
req, err := http.NewRequest("POST", "/", bytes.NewBufferString(body))
|
||||
assert.Nil(t, err, "")
|
||||
|
||||
now := time.Now()
|
||||
timestamp := strconv.FormatInt(now.Unix(), 10)
|
||||
|
||||
var msg bytes.Buffer
|
||||
msg.WriteString(timestamp)
|
||||
msg.WriteString(body)
|
||||
s := ed25519.Sign(privkey, msg.Bytes())
|
||||
req.Header.Set("X-Signature-Ed25519", hex.EncodeToString(s[:ed25519.SignatureSize]))
|
||||
req.Header.Set("X-Signature-Timestamp", timestamp)
|
||||
req.Header.Add("date", now.UTC().Format(http.TimeFormat))
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
|
||||
err = signature.Ed25519SignatureHandler(req, ts)
|
||||
assert.Nil(t, err, "")
|
||||
}
|
52
pkg/middleware/signature/test/http-signature_test.go
Normal file
52
pkg/middleware/signature/test/http-signature_test.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-fed/httpsig"
|
||||
"github.com/ncarlier/webhookd/pkg/assert"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/middleware/signature"
|
||||
"github.com/ncarlier/webhookd/pkg/truststore"
|
||||
)
|
||||
|
||||
func assertSigner(t *testing.T) httpsig.Signer {
|
||||
prefs := []httpsig.Algorithm{httpsig.RSA_SHA256}
|
||||
digestAlgorithm := httpsig.DigestSha256
|
||||
headers := []string{httpsig.RequestTarget, "date"}
|
||||
signer, _, err := httpsig.NewSigner(prefs, digestAlgorithm, headers, httpsig.Signature, 0)
|
||||
assert.Nil(t, err, "")
|
||||
return signer
|
||||
}
|
||||
|
||||
func TestHTTPSignature(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
privkey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
assert.Nil(t, err, "")
|
||||
pubkey := &privkey.PublicKey
|
||||
|
||||
ts := &truststore.InMemoryTrustStore{
|
||||
Keys: map[string]crypto.PublicKey{
|
||||
"default": pubkey,
|
||||
},
|
||||
}
|
||||
|
||||
//pk := assertPrivateKey(t)
|
||||
signer := assertSigner(t)
|
||||
var body []byte
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
assert.Nil(t, err, "")
|
||||
req.Header.Add("date", time.Now().UTC().Format(http.TimeFormat))
|
||||
err = signer.SignRequest(privkey, "default", req, body)
|
||||
assert.Nil(t, err, "")
|
||||
|
||||
// ts := assertTrustStore(t)
|
||||
err = signature.HTTPSignatureHandler(req, ts)
|
||||
assert.Nil(t, err, "")
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-fed/httpsig"
|
||||
"github.com/ncarlier/webhookd/pkg/assert"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/pubkey"
|
||||
)
|
||||
|
||||
func TestTrustStoreWithNoKeyID(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := pubkey.NewTrustStore("test-key-01.pem")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ts, "")
|
||||
entry := ts.Get("test")
|
||||
assert.True(t, entry == nil, "")
|
||||
entry = ts.Get("default")
|
||||
assert.NotNil(t, entry, "")
|
||||
assert.Equal(t, httpsig.RSA_SHA256, entry.Algorithm, "")
|
||||
}
|
||||
|
||||
func TestTrustStoreWithKeyID(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := pubkey.NewTrustStore("test-key-02.pem")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ts, "")
|
||||
entry := ts.Get("test")
|
||||
assert.NotNil(t, entry, "")
|
||||
assert.Equal(t, httpsig.RSA_SHA256, entry.Algorithm, "")
|
||||
}
|
||||
|
||||
func TestTrustStoreWithCertificate(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := pubkey.NewTrustStore("test-cert.pem")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ts, "")
|
||||
entry := ts.Get("test.localnet")
|
||||
assert.NotNil(t, entry, "")
|
||||
assert.Equal(t, httpsig.RSA_SHA256, entry.Algorithm, "")
|
||||
}
|
||||
|
||||
func TestTrustStoreWithMultipleEntries(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := pubkey.NewTrustStore("test-multi.pem")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ts, "")
|
||||
entry := ts.Get("test.localnet")
|
||||
assert.NotNil(t, entry, "")
|
||||
assert.Equal(t, httpsig.RSA_SHA256, entry.Algorithm, "")
|
||||
entry = ts.Get("foo")
|
||||
assert.NotNil(t, entry, "")
|
||||
assert.Equal(t, httpsig.RSA_SHA256, entry.Algorithm, "")
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----
|
|
@ -1,9 +0,0 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwdCB5DZD0cFeJYUu1W3I
|
||||
lNN9y+NZC/Jqktdkn8/WHlXec07nesyDyicveduuaaDBeUoR3imRUtTS+eFKvDR/
|
||||
HMke8HmoleZvADJ0ppqbvj0YZOhWp2SqZvAKJbce8D+OSKuLGpxqq6FLl0cq+Fv+
|
||||
mFpZyDcqPZtrwAz+AbJp6sxvVvNb0r0Z1LxEIVb0JHHLhsJkxJ06PcbT2tnvaPHT
|
||||
8S80cvFO57zFpiX/M8gQNaRCqwD1/sHEGJc6Av+WUBhrE7pz0XGMLjU4n7W6Ooyz
|
||||
g2QdBiLE3tW8HYL9iJ7EuLG5apYbFVVHJHl8HNWpmD0q8sVExvx5+AKQ3kEDp68m
|
||||
ywIDAQAB
|
||||
-----END PUBLIC KEY-----
|
|
@ -1,11 +0,0 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
key_id: test
|
||||
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwdCB5DZD0cFeJYUu1W3I
|
||||
lNN9y+NZC/Jqktdkn8/WHlXec07nesyDyicveduuaaDBeUoR3imRUtTS+eFKvDR/
|
||||
HMke8HmoleZvADJ0ppqbvj0YZOhWp2SqZvAKJbce8D+OSKuLGpxqq6FLl0cq+Fv+
|
||||
mFpZyDcqPZtrwAz+AbJp6sxvVvNb0r0Z1LxEIVb0JHHLhsJkxJ06PcbT2tnvaPHT
|
||||
8S80cvFO57zFpiX/M8gQNaRCqwD1/sHEGJc6Av+WUBhrE7pz0XGMLjU4n7W6Ooyz
|
||||
g2QdBiLE3tW8HYL9iJ7EuLG5apYbFVVHJHl8HNWpmD0q8sVExvx5+AKQ3kEDp68m
|
||||
ywIDAQAB
|
||||
-----END PUBLIC KEY-----
|
|
@ -1,54 +0,0 @@
|
|||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
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
|
||||
DUyBMrtuX98+CQnimJx/Rg==
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
|
@ -1,42 +0,0 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
key_id: foo
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwdCB5DZD0cFeJYUu1W3I
|
||||
lNN9y+NZC/Jqktdkn8/WHlXec07nesyDyicveduuaaDBeUoR3imRUtTS+eFKvDR/
|
||||
HMke8HmoleZvADJ0ppqbvj0YZOhWp2SqZvAKJbce8D+OSKuLGpxqq6FLl0cq+Fv+
|
||||
mFpZyDcqPZtrwAz+AbJp6sxvVvNb0r0Z1LxEIVb0JHHLhsJkxJ06PcbT2tnvaPHT
|
||||
8S80cvFO57zFpiX/M8gQNaRCqwD1/sHEGJc6Av+WUBhrE7pz0XGMLjU4n7W6Ooyz
|
||||
g2QdBiLE3tW8HYL9iJ7EuLG5apYbFVVHJHl8HNWpmD0q8sVExvx5+AKQ3kEDp68m
|
||||
ywIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFkDCCA3igAwIBAgIJAI76c8w4edCdMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQxFjAUBgNVBAMMDXRlc3QubG9jYWxuZXQwHhcNMjAwMzE1
|
||||
MjIxMTQzWhcNMjEwMzE1MjIxMTQzWjBdMQswCQYDVQQGEwJBVTETMBEGA1UECAwK
|
||||
U29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRYw
|
||||
FAYDVQQDDA10ZXN0LmxvY2FsbmV0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
|
||||
CgKCAgEA8WYogCpb9isAmX/5crbCvEj7yA3yHFNfNb+NQXnVCp/ONZVEXSjhGEp5
|
||||
Z2NQ1hHb9xeU1CjyDmAaIKSjBWHEfGwi95+qBa/5UzacAfMVaV1UHwVVwZU/yiTF
|
||||
AHFOVndtPKNbIK2XJIOcmeL1Ejp7LmV2NEKVE/HGAEE10J8X9CzgPZgcU0D8zePq
|
||||
Fg5NFGHEN0AyP07e+UooWtxyjOfPu1U88xr8Qxcg8Y9ffQNqA2XIQDf+zwR0K3tH
|
||||
LE1Wfbws088xF2cq3qp2nA1373t74wKOJu3cJKqMeyEsYIJ3aVwPbS5qP6jqakIB
|
||||
m9sqZqqWmdqm/S8V9O03AJv9w1/49maVxy6O0wABmiNpDaXU3iLqE4MLBOFJ56Ul
|
||||
lEJuUKHIgFZ6CxZIUMv1E4jKDSAYUgzFt2ON9WwVnweR0lbv+AiF26KOIx51XsZg
|
||||
vrTIMkKQU0lfnAWkgUxGyGAkJmVfXsrZUZvaBWF1H8Nt+nsZDwrp3ffJvSK/MIw7
|
||||
9VPYdCNhnyrBWQMI5ZTFQCrTEXokXLAugX7W1qjSptWu3+H+7klq1yvip/YgpZtf
|
||||
OgE95uJV9KTjcX4F3eAsnAfF7urEArV0t0qlRrb+rFWjNKJYnQejjXYuu83L+/ue
|
||||
J+5wiqJZKen2HnD+2T3OyhY7FkiP8PwVxKT8mJt4qvhJHeZHc6cCAwEAAaNTMFEw
|
||||
HQYDVR0OBBYEFJoUuP6T91NBZXCRcJU3w8mEny9iMB8GA1UdIwQYMBaAFJoUuP6T
|
||||
91NBZXCRcJU3w8mEny9iMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD
|
||||
ggIBABQ3zGtBz4KIgT5jz2CiyQVuwk+gfAlIj3UdcOS55764jG7Wj8kcWL+Lp3NJ
|
||||
h2lXBEmT5PaGqrhVUIPf5j0JQeC9A6+1msT3NPd0ZDnNXi5G2/KYDsLgh5OWB0gh
|
||||
qXMyKAME65k1MXQuOnM23JewfVn0+zNAAvzZl9ofWm9ZRHLOGMgDYMItxHE+Q+eQ
|
||||
AI7Mi/TewVZX8S39u4LfUKGrokFlhaaqecrNVAsGuFrgJyOK1/x3pUSn8PvsxRGT
|
||||
jtnkQ/nlQskCobof7e+CM55QWzcePS547X+kspyzqOoKPqvmnacipXuy2/Zxc4bS
|
||||
NV3J5cOdt+LxYrVru81qc7xM72ZirZepc4YvDMRJ3gMhCLciGsyiqoDkSBamGxcQ
|
||||
QVzbHdWXwlNWp6cJGgtPWSjP1baeLlplbLWiy+hK9fOsoBV8t2anE6WfQgZoZ4/t
|
||||
WSFKW5JS0g0eZq1KyGre9ynOSL4WCcHoDF5eY3pZwTCgPhu1SEqV1wnHo9YXiWZL
|
||||
LHuVpX+HI+zpChciV+XyD3OQ3p0eXbo/Czd2XWVkvUA24v5EZKkZWHzvHfqlC0pT
|
||||
yE9fmqizZlX9nZwyh4+dp/V3IUDIml6CmpZaPCC6zDbuFPpi0geYomgRFAGgePsf
|
||||
CJhfs0VHYlLZCvziqLfXMhC3Seg2NDNqoLR8icuUceXTc839
|
||||
-----END CERTIFICATE-----
|
|
@ -1,40 +0,0 @@
|
|||
package pubkey
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-fed/httpsig"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
)
|
||||
|
||||
const defaultAlgorithm = httpsig.RSA_SHA256
|
||||
|
||||
// TrustStoreEntry is a trust store entry
|
||||
type TrustStoreEntry struct {
|
||||
Pubkey crypto.PublicKey
|
||||
Algorithm httpsig.Algorithm
|
||||
}
|
||||
|
||||
// TrustStore is a generic interface to retrieve a public key
|
||||
type TrustStore interface {
|
||||
Get(keyID string) *TrustStoreEntry
|
||||
}
|
||||
|
||||
// NewTrustStore creates new Key Store from URI
|
||||
func NewTrustStore(filename string) (store TrustStore, err error) {
|
||||
if filename == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
logger.Debug.Printf("loading trust store: %s", filename)
|
||||
switch filepath.Ext(filename) {
|
||||
case ".pem":
|
||||
store, err = newPEMTrustStore(filename)
|
||||
default:
|
||||
err = fmt.Errorf("unsupported trust store file format: %s", filename)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
31
pkg/truststore/p12_truststore.go
Normal file
31
pkg/truststore/p12_truststore.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package truststore
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"golang.org/x/crypto/pkcs12"
|
||||
)
|
||||
|
||||
func newP12TrustStore(filename string) (TrustStore, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, cert, err := pkcs12.Decode(data, "test")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &InMemoryTrustStore{
|
||||
Keys: make(map[string]crypto.PublicKey),
|
||||
}
|
||||
|
||||
keyID := string(cert.Subject.CommonName)
|
||||
result.Keys[keyID] = cert.PublicKey
|
||||
logger.Debug.Printf("certificate \"%s\" loaded into the trustore", keyID)
|
||||
|
||||
return result, nil
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package pubkey
|
||||
package truststore
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
@ -10,26 +10,14 @@ import (
|
|||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
)
|
||||
|
||||
type pemTrustStore struct {
|
||||
keys map[string]TrustStoreEntry
|
||||
}
|
||||
|
||||
func (ts *pemTrustStore) Get(keyID string) *TrustStoreEntry {
|
||||
key, ok := ts.keys[keyID]
|
||||
if ok {
|
||||
return &key
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newPEMTrustStore(filename string) (TrustStore, error) {
|
||||
raw, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := pemTrustStore{
|
||||
keys: make(map[string]TrustStoreEntry),
|
||||
result := &InMemoryTrustStore{
|
||||
Keys: make(map[string]crypto.PublicKey),
|
||||
}
|
||||
for {
|
||||
block, rest := pem.Decode(raw)
|
||||
|
@ -38,38 +26,32 @@ func newPEMTrustStore(filename string) (TrustStore, error) {
|
|||
}
|
||||
switch block.Type {
|
||||
case "PUBLIC KEY":
|
||||
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rsaPublicKey, _ := pub.(*rsa.PublicKey)
|
||||
keyID, ok := block.Headers["key_id"]
|
||||
if !ok {
|
||||
keyID = "default"
|
||||
}
|
||||
result.keys[keyID] = TrustStoreEntry{
|
||||
Algorithm: defaultAlgorithm,
|
||||
Pubkey: rsaPublicKey,
|
||||
|
||||
key, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.Keys[keyID] = key
|
||||
logger.Debug.Printf("public key \"%s\" loaded into the trustore", keyID)
|
||||
case "CERTIFICATE":
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rsaPublicKey, _ := cert.PublicKey.(*rsa.PublicKey)
|
||||
keyID := string(cert.Subject.CommonName)
|
||||
result.keys[keyID] = TrustStoreEntry{
|
||||
Algorithm: defaultAlgorithm,
|
||||
Pubkey: rsaPublicKey,
|
||||
}
|
||||
result.Keys[keyID] = cert.PublicKey
|
||||
logger.Debug.Printf("certificate \"%s\" loaded into the trustore", keyID)
|
||||
}
|
||||
raw = rest
|
||||
}
|
||||
|
||||
if len(result.keys) == 0 {
|
||||
if len(result.Keys) == 0 {
|
||||
return nil, fmt.Errorf("no RSA public key found: %s", filename)
|
||||
}
|
||||
return &result, nil
|
||||
return result, nil
|
||||
}
|
23
pkg/truststore/test/p12_truststore_test.go
Normal file
23
pkg/truststore/test/p12_truststore_test.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"testing"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/assert"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/truststore"
|
||||
)
|
||||
|
||||
func TestTrustStoreWithP12(t *testing.T) {
|
||||
t.Skip()
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := truststore.New("test.p12")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ts, "")
|
||||
pubkey := ts.GetPublicKey("test.localnet")
|
||||
assert.NotNil(t, pubkey, "")
|
||||
_, ok := pubkey.(*rsa.PublicKey)
|
||||
assert.True(t, ok, "")
|
||||
}
|
64
pkg/truststore/test/pem_truststore_test.go
Normal file
64
pkg/truststore/test/pem_truststore_test.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"testing"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/assert"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/truststore"
|
||||
)
|
||||
|
||||
func TestTrustStoreWithNoKeyID(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := truststore.New("test-key-01.pem")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ts, "")
|
||||
pubkey := ts.GetPublicKey("test")
|
||||
assert.True(t, pubkey == nil, "")
|
||||
pubkey = ts.GetPublicKey("default")
|
||||
assert.NotNil(t, pubkey, "")
|
||||
_, ok := pubkey.(*rsa.PublicKey)
|
||||
assert.True(t, ok, "")
|
||||
}
|
||||
|
||||
func TestTrustStoreWithKeyID(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := truststore.New("test-key-02.pem")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ts, "")
|
||||
pubkey := ts.GetPublicKey("test")
|
||||
assert.NotNil(t, pubkey, "")
|
||||
_, ok := pubkey.(*rsa.PublicKey)
|
||||
assert.True(t, ok, "")
|
||||
}
|
||||
|
||||
func TestTrustStoreWithCertificate(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := truststore.New("test-cert.pem")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ts, "")
|
||||
pubkey := ts.GetPublicKey("test.localnet")
|
||||
assert.NotNil(t, pubkey, "")
|
||||
_, ok := pubkey.(*rsa.PublicKey)
|
||||
assert.True(t, ok, "")
|
||||
}
|
||||
|
||||
func TestTrustStoreWithMultipleEntries(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := truststore.New("test-multi.pem")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ts, "")
|
||||
pubkey := ts.GetPublicKey("test.localnet")
|
||||
assert.NotNil(t, pubkey, "")
|
||||
_, ok := pubkey.(*rsa.PublicKey)
|
||||
assert.True(t, ok, "")
|
||||
pubkey = ts.GetPublicKey("foo")
|
||||
assert.NotNil(t, pubkey, "")
|
||||
_, ok = pubkey.(*rsa.PublicKey)
|
||||
assert.True(t, ok, "")
|
||||
}
|
BIN
pkg/truststore/test/test.p12
Normal file
BIN
pkg/truststore/test/test.p12
Normal file
Binary file not shown.
45
pkg/truststore/truststore.go
Normal file
45
pkg/truststore/truststore.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package truststore
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
)
|
||||
|
||||
// TrustStore is a generic interface to retrieve a public key
|
||||
type TrustStore interface {
|
||||
GetPublicKey(keyID string) crypto.PublicKey
|
||||
}
|
||||
|
||||
// InMemoryTrustStore is a in memory storage for public keys
|
||||
type InMemoryTrustStore struct {
|
||||
Keys map[string]crypto.PublicKey
|
||||
}
|
||||
|
||||
func (ts *InMemoryTrustStore) GetPublicKey(keyID string) crypto.PublicKey {
|
||||
if key, ok := ts.Keys[keyID]; ok {
|
||||
return key
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// New creates new Trust Store from URI
|
||||
func New(filename string) (store TrustStore, err error) {
|
||||
if filename == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
logger.Debug.Printf("loading trust store: %s", filename)
|
||||
switch filepath.Ext(filename) {
|
||||
case ".pem":
|
||||
store, err = newPEMTrustStore(filename)
|
||||
case ".p12":
|
||||
store, err = newP12TrustStore(filename)
|
||||
default:
|
||||
err = fmt.Errorf("unsupported trust store file format: %s", filename)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
Loading…
Reference in New Issue
Block a user