54 lines
1.5 KiB
Go
54 lines
1.5 KiB
Go
package auditrelay
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"encoding/base64"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"io"
|
||
|
|
"os"
|
||
|
|
"regexp"
|
||
|
|
)
|
||
|
|
|
||
|
|
var keyIDPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$`)
|
||
|
|
|
||
|
|
type keyFile struct {
|
||
|
|
Version int `json:"version"`
|
||
|
|
Keys []struct {
|
||
|
|
KeyID string `json:"key_id"`
|
||
|
|
Secret string `json:"secret_base64url"`
|
||
|
|
} `json:"keys"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func LoadKey(path, keyID string) ([]byte, error) {
|
||
|
|
raw, err := os.ReadFile(path)
|
||
|
|
if err != nil {
|
||
|
|
return nil, errors.New("read audit relay key file")
|
||
|
|
}
|
||
|
|
var document keyFile
|
||
|
|
decoder := json.NewDecoder(bytes.NewReader(raw))
|
||
|
|
decoder.DisallowUnknownFields()
|
||
|
|
if err := decoder.Decode(&document); err != nil || document.Version != 1 || len(document.Keys) == 0 {
|
||
|
|
return nil, errors.New("invalid audit relay key file")
|
||
|
|
}
|
||
|
|
var trailing any
|
||
|
|
if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
|
||
|
|
return nil, errors.New("invalid audit relay key file")
|
||
|
|
}
|
||
|
|
values := make(map[string][]byte, len(document.Keys))
|
||
|
|
for _, candidate := range document.Keys {
|
||
|
|
secret, err := base64.RawURLEncoding.DecodeString(candidate.Secret)
|
||
|
|
if err != nil || !keyIDPattern.MatchString(candidate.KeyID) || len(secret) < 32 {
|
||
|
|
return nil, errors.New("invalid audit relay secret")
|
||
|
|
}
|
||
|
|
if _, duplicate := values[candidate.KeyID]; duplicate {
|
||
|
|
return nil, errors.New("duplicate audit relay key ID")
|
||
|
|
}
|
||
|
|
values[candidate.KeyID] = secret
|
||
|
|
}
|
||
|
|
if secret, exists := values[keyID]; exists {
|
||
|
|
return secret, nil
|
||
|
|
}
|
||
|
|
return nil, errors.New("audit relay key ID not found")
|
||
|
|
}
|