mirror of
https://github.com/thegeeklab/drone-yaml.git
synced 2024-11-01 01:00:41 +00:00
152 lines
3.5 KiB
Go
152 lines
3.5 KiB
Go
package signer
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
|
|
"github.com/drone/drone-yaml/yaml"
|
|
|
|
goyaml "github.com/buildkite/yaml"
|
|
)
|
|
|
|
// ErrInvalidKey is returned when the key is missing or
|
|
// is less than 32-bytes.
|
|
var ErrInvalidKey = errors.New("signer: key must be 32-bytes")
|
|
|
|
// Key represents 32-byte signature.
|
|
type Key []byte
|
|
|
|
// KeyString is a helper function that returns a Key
|
|
// from a string.
|
|
func KeyString(s string) Key {
|
|
return []byte(s)
|
|
}
|
|
|
|
// Sign calculates and returns the hmac signature of the
|
|
// parsed yaml file.
|
|
func Sign(data []byte, key Key) (string, error) {
|
|
res, err := yaml.ParseRawBytes(data)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
hmac, err := sign(res, key)
|
|
return hex.EncodeToString(hmac), err
|
|
}
|
|
|
|
// SignUpdate calculates the hmac signature of the parsed
|
|
// yaml file and adds a signature resource. If a signature
|
|
// resource already exists, it is replaced.
|
|
func SignUpdate(data []byte, key Key) ([]byte, error) {
|
|
res, err := yaml.ParseRawBytes(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hmac, err := sign(res, key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
for _, r := range res {
|
|
if r.Kind != yaml.KindSignature {
|
|
buf.WriteString("---")
|
|
buf.WriteByte('\n')
|
|
buf.Write(r.Data)
|
|
}
|
|
}
|
|
|
|
buf.WriteString("---")
|
|
buf.WriteByte('\n')
|
|
buf.WriteString("kind: signature")
|
|
buf.WriteByte('\n')
|
|
buf.WriteString("hmac: " + hex.EncodeToString(hmac))
|
|
buf.WriteByte('\n')
|
|
buf.WriteByte('\n')
|
|
buf.WriteString("...")
|
|
buf.WriteByte('\n')
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// Verify returns true if the signature of the parsed
|
|
// yaml file can be verified.
|
|
func Verify(data []byte, key Key) (bool, error) {
|
|
res, err := yaml.ParseRawBytes(data)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
mac1, err := extract(res)
|
|
if err != nil {
|
|
return false, nil
|
|
}
|
|
mac2, err := sign(res, key)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return hmac.Equal(mac1, mac2), nil
|
|
}
|
|
|
|
// WriteTo writes the signature to the yaml file. If the
|
|
// signature already exists it is removed, and the new
|
|
// signature is appended to the end of the document.
|
|
func WriteTo(data []byte, hmac string) ([]byte, error) {
|
|
res, err := yaml.ParseRawBytes(data)
|
|
return upsert(res, hmac), err
|
|
}
|
|
|
|
// helper function extracts the hex-encoded signature
|
|
// resource from the parsed resource list.
|
|
func extract(res []*yaml.RawResource) ([]byte, error) {
|
|
for _, r := range res {
|
|
if r.Kind == yaml.KindSignature {
|
|
out := new(yaml.Signature)
|
|
err := goyaml.Unmarshal(r.Data, out)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return hex.DecodeString(out.Hmac)
|
|
}
|
|
}
|
|
return nil, errors.New("yaml: missing signature")
|
|
}
|
|
|
|
// helper function generates a hex-encoded signature
|
|
// based on the parsed resource list.
|
|
func sign(resources []*yaml.RawResource, key Key) ([]byte, error) {
|
|
if len(key) < 32 {
|
|
return nil, ErrInvalidKey
|
|
}
|
|
h := hmac.New(sha256.New, key)
|
|
for _, r := range resources {
|
|
if r.Kind != yaml.KindSignature {
|
|
h.Write(r.Data)
|
|
}
|
|
}
|
|
return h.Sum(nil), nil
|
|
}
|
|
|
|
// helper function inserts or updates the hmac signature
|
|
// into the yaml document, and returns an updated copy.
|
|
func upsert(res []*yaml.RawResource, hmac string) []byte {
|
|
var buf bytes.Buffer
|
|
for _, r := range res {
|
|
if r.Kind != yaml.KindSignature {
|
|
buf.WriteString("---")
|
|
buf.WriteByte('\n')
|
|
buf.Write(r.Data)
|
|
}
|
|
}
|
|
buf.WriteString("---")
|
|
buf.WriteByte('\n')
|
|
buf.WriteString("kind: signature")
|
|
buf.WriteByte('\n')
|
|
buf.WriteString("hmac: " + hmac)
|
|
buf.WriteByte('\n')
|
|
buf.WriteByte('\n')
|
|
buf.WriteString("...")
|
|
buf.WriteByte('\n')
|
|
return buf.Bytes()
|
|
}
|