mirror of
https://github.com/ncarlier/webhookd.git
synced 2025-04-06 16:50:36 +00:00
feat(signature): multi entries for a PEM file
This commit is contained in:
parent
8a393cc0c3
commit
0e2f58012d
|
@ -56,3 +56,13 @@ func ContainsStr(t *testing.T, expected string, array []string, message string)
|
|||
}
|
||||
t.Fatalf("%s - array: %v, expected value: %s", message, array, expected)
|
||||
}
|
||||
|
||||
// True assert that an expression is true
|
||||
func True(t *testing.T, expression bool, message string) {
|
||||
if message == "" {
|
||||
message = "Expression is not true"
|
||||
}
|
||||
if !expression {
|
||||
t.Fatalf("%s : %v", message, expression)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,13 +18,13 @@ func HTTPSignature(trustStore pubkey.TrustStore) Middleware {
|
|||
return
|
||||
}
|
||||
pubKeyID := verifier.KeyId()
|
||||
pubKey, algo, err := trustStore.Get(pubKeyID)
|
||||
if err != nil {
|
||||
entry := trustStore.Get(pubKeyID)
|
||||
if entry == nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("invalid HTTP signature: " + err.Error()))
|
||||
return
|
||||
}
|
||||
err = verifier.Verify(pubKey, algo)
|
||||
err = verifier.Verify(entry.Pubkey, entry.Algorythm)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("invalid HTTP signature: " + err.Error()))
|
||||
|
|
|
@ -1,55 +1,75 @@
|
|||
package pubkey
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/go-fed/httpsig"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
)
|
||||
|
||||
type pemTrustStore struct {
|
||||
key crypto.PublicKey
|
||||
keys map[string]TrustStoreEntry
|
||||
}
|
||||
|
||||
func (ts *pemTrustStore) Get(keyID string) (crypto.PublicKey, httpsig.Algorithm, error) {
|
||||
return ts.key, defaultAlgorithm, nil
|
||||
func (ts *pemTrustStore) Get(keyID string) *TrustStoreEntry {
|
||||
key, ok := ts.keys[keyID]
|
||||
if ok {
|
||||
return &key
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newPEMTrustStore(filename string) (*pemTrustStore, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
raw, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("invalid PEM file: %s", filename)
|
||||
result := pemTrustStore{
|
||||
keys: make(map[string]TrustStoreEntry),
|
||||
}
|
||||
for {
|
||||
block, rest := pem.Decode(raw)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
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{
|
||||
Algorythm: defaultAlgorithm,
|
||||
Pubkey: rsaPublicKey,
|
||||
}
|
||||
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{
|
||||
Algorythm: defaultAlgorithm,
|
||||
Pubkey: rsaPublicKey,
|
||||
}
|
||||
logger.Debug.Printf("certificate \"%s\" loaded into the trustore", keyID)
|
||||
}
|
||||
raw = rest
|
||||
}
|
||||
|
||||
var rsaPublicKey *rsa.PublicKey
|
||||
switch block.Type {
|
||||
case "PUBLIC KEY":
|
||||
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rsaPublicKey, _ = pub.(*rsa.PublicKey)
|
||||
case "CERTIFICATE":
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rsaPublicKey, _ = cert.PublicKey.(*rsa.PublicKey)
|
||||
}
|
||||
|
||||
if rsaPublicKey == nil {
|
||||
if len(result.keys) == 0 {
|
||||
return nil, fmt.Errorf("no RSA public key found: %s", filename)
|
||||
}
|
||||
return &pemTrustStore{
|
||||
key: rsaPublicKey,
|
||||
}, nil
|
||||
return &result, nil
|
||||
}
|
||||
|
|
59
pkg/pubkey/test/pem_truststore_test.go
Normal file
59
pkg/pubkey/test/pem_truststore_test.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
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.Algorythm, "")
|
||||
}
|
||||
|
||||
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.Algorythm, "")
|
||||
}
|
||||
|
||||
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.Algorythm, "")
|
||||
}
|
||||
|
||||
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.Algorythm, "")
|
||||
entry = ts.Get("foo")
|
||||
assert.NotNil(t, entry, "")
|
||||
assert.Equal(t, httpsig.RSA_SHA256, entry.Algorythm, "")
|
||||
}
|
|
@ -1,11 +1,54 @@
|
|||
-----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-----
|
||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
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
|
||||
DUyBMrtuX98+CQnimJx/Rg==
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
|
|
|
@ -1,23 +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 TestKeyStore(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
ks, err := pubkey.NewTrustStore("test-key.pem")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ks, "")
|
||||
|
||||
pk, algo, err := ks.Get("test")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, pk, "")
|
||||
assert.Equal(t, httpsig.RSA_SHA256, algo, "")
|
||||
}
|
|
@ -6,13 +6,20 @@ import (
|
|||
"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
|
||||
Algorythm httpsig.Algorithm
|
||||
}
|
||||
|
||||
// TrustStore is a generic interface to retrieve a public key
|
||||
type TrustStore interface {
|
||||
Get(keyID string) (crypto.PublicKey, httpsig.Algorithm, error)
|
||||
Get(keyID string) *TrustStoreEntry
|
||||
}
|
||||
|
||||
// NewTrustStore creates new Key Store from URI
|
||||
|
@ -21,11 +28,12 @@ func NewTrustStore(filename string) (store TrustStore, err error) {
|
|||
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 TrustStore file format: %s", filename)
|
||||
err = fmt.Errorf("unsupported trust store file format: %s", filename)
|
||||
}
|
||||
|
||||
return
|
||||
|
|
Loading…
Reference in New Issue
Block a user