Skip to content

Commit

Permalink
Add filesig package for signing files in a common way
Browse files Browse the repository at this point in the history
  • Loading branch information
dhaavi committed Jul 8, 2022
1 parent 8ad0eab commit 0bb5f33
Show file tree
Hide file tree
Showing 5 changed files with 681 additions and 0 deletions.
123 changes: 123 additions & 0 deletions filesig/format_armor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package jess

import (
"bytes"
"encoding/base64"
"fmt"
"regexp"

"github.com/safing/jess"
"github.com/safing/portbase/formats/dsd"
)

const (
sigFileArmorStart = "-----BEGIN JESS SIGNATURE-----"
sigFileArmorEnd = "-----END JESS SIGNATURE-----"
sigFileLineLength = 64
)

var (
sigFileArmorFindMatcher = regexp.MustCompile(`(?ms)` + sigFileArmorStart + `(.+?)` + sigFileArmorEnd)
sigFileArmorRemoveMatcher = regexp.MustCompile(`(?ms)` + sigFileArmorStart + `.+?` + sigFileArmorEnd + `\r?\n?`)
whitespaceMatcher = regexp.MustCompile(`(?ms)\s`)
)

// ParseSigFile parses a signature file and extracts any jess signatures from it.
// If signatures are returned along with an error, the error should be treated
// as a warning, but the result should also not be treated as a full success,
// as there might be missing signatures.
func ParseSigFile(fileData []byte) (signatures []*jess.Letter, err error) {
var warning error
captured := make([][]byte, 0, 1)

// Find any signature blocks.
matches := sigFileArmorFindMatcher.FindAllSubmatch(fileData, -1)
for _, subMatches := range matches {
if len(subMatches) >= 2 {
// First entry is the whole match, second the submatch.
captured = append(
captured,
bytes.TrimPrefix(
bytes.TrimSuffix(
whitespaceMatcher.ReplaceAll(subMatches[1], nil),
[]byte(sigFileArmorEnd),
),
[]byte(sigFileArmorStart),
),
)
}
}

// Parse any found signatures.
signatures = make([]*jess.Letter, 0, len(captured))
for _, sigBase64Data := range captured {
// Decode from base64
sigData := make([]byte, base64.RawStdEncoding.DecodedLen(len(sigBase64Data)))
_, err = base64.RawStdEncoding.Decode(sigData, sigBase64Data)
if err != nil {
warning = err
continue
}

// Parse signature.
var letter *jess.Letter
letter, err = jess.LetterFromDSD(sigData)
if err != nil {
warning = err
} else {
signatures = append(signatures, letter)
}
}

return signatures, warning
}

// MakeSigFileSection creates a new section for a signature file.
func MakeSigFileSection(signature *jess.Letter) ([]byte, error) {
// Serialize.
data, err := signature.ToDSD(dsd.CBOR)
if err != nil {
return nil, fmt.Errorf("failed to serialize signature: %w", err)
}

// Encode to base64
encodedData := make([]byte, base64.RawStdEncoding.EncodedLen(len(data)))
base64.RawStdEncoding.Encode(encodedData, data)

// Split into lines and add armor.
splittedData := make([][]byte, 0, (len(encodedData)/sigFileLineLength)+3)
splittedData = append(splittedData, []byte(sigFileArmorStart))
for len(encodedData) > 0 {
if len(encodedData) > sigFileLineLength {
splittedData = append(splittedData, encodedData[:sigFileLineLength])
encodedData = encodedData[sigFileLineLength:]
} else {
splittedData = append(splittedData, encodedData)
encodedData = nil
}
}
splittedData = append(splittedData, []byte(sigFileArmorEnd))
linedData := bytes.Join(splittedData, []byte("\n"))

return linedData, nil
}

// AddToSigFile adds the given signature to the signature file.
func AddToSigFile(signature *jess.Letter, sigFileData []byte, removeExistingJessSignatures bool) (newFileData []byte, err error) {
// Create new section for new sig.
newSigSection, err := MakeSigFileSection(signature)
if err != nil {
return nil, err
}

// Remove any existing jess signature sections.
if removeExistingJessSignatures {
sigFileData = sigFileArmorRemoveMatcher.ReplaceAll(sigFileData, nil)
}

// Append new signature section to end of file with a newline.
sigFileData = append(sigFileData, []byte("\n")...)
sigFileData = append(sigFileData, newSigSection...)

return sigFileData, nil
}
197 changes: 197 additions & 0 deletions filesig/format_armor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package jess

import (
"bytes"
"testing"

"github.com/safing/jess"
"github.com/safing/jess/lhash"
)

var (
testFileSigOneKey = "7KoUBdrRfF6drrPvKianoGfEXTQFCS5wDbfQyc87VQnYApPckRS8SfrrmAXZhV1JgKfnh44ib9nydQVEDRJiZArV22RqMfPrJmQdoAsE7zuzPRSrku8yF7zfnEv46X5GsmgfdSDrFMdG7XJd3fdaxStYCXTYDS5R"

testFileSigOneData = []byte("The quick brown fox jumps over the lazy dog")

testFileSigOneMetaData = map[string]string{
"id": "resource/path",
"version": "0.0.1",
}

testFileSigOneSignature = []byte(`
-----BEGIN JESS SIGNATURE-----
Q6VnVmVyc2lvbgFnU3VpdGVJRGdzaWduX3YxZU5vbmNlRA40a/BkRGF0YVhqTYOr
TGFiZWxlZEhhc2jEIhkgAXGM7DXNPXlt0AAg4L/stHOtI0V9Bjt17/KcD/ouWKmo
U2lnbmVkQXTW/2LH/ueoTWV0YURhdGGComlkrXJlc291cmNlL3BhdGindmVyc2lv
bqUwLjAuMWpTaWduYXR1cmVzgaNmU2NoZW1lZ0VkMjU1MTliSURwZmlsZXNpZy10
ZXN0LWtleWVWYWx1ZVhA4b1kfIJF7do6OcJnemQ5mtj/ZyMFJWWTmD1W5KvkpZac
2AP5f+dDJhzWBHsoSXTCl6uA3DA3+RbABMYAZn6eDg
-----END JESS SIGNATURE-----
`)
)

func TestFileSigFormat(t *testing.T) {
t.Parallel()

// Load test key.
signet, err := jess.SignetFromBase58(testFileSigOneKey)
if err != nil {
t.Fatal(err)
}

// Store signet.
if err := testTrustStore.StoreSignet(signet); err != nil {
t.Fatal(err)
}
// Store public key for verification.
recipient, err := signet.AsRecipient()
if err != nil {
t.Fatal(err)
}
if err := testTrustStore.StoreSignet(recipient); err != nil {
t.Fatal(err)
}

// Create envelope.
envelope := jess.NewUnconfiguredEnvelope()
envelope.SuiteID = jess.SuiteSignV1
envelope.Senders = []*jess.Signet{signet}

// Hash and sign file.
hash := lhash.Digest(lhash.BLAKE2b_256, testFileSigOneData)
letter, _, err := SignFileData(hash, testFileSigOneMetaData, envelope, testTrustStore)
if err != nil {
t.Fatal(err)
}

// Serialize signature.
sigFile, err := MakeSigFileSection(letter)
if err != nil {
t.Fatal(err)
}
// fmt.Println("Signature:")
// fmt.Println(string(sigFile))

// Parse signature again.
sigs, err := ParseSigFile(sigFile)
if err != nil {
t.Fatal(err)
}
if len(sigs) != 1 {
t.Fatalf("one sig expected, got %d", len(sigs))
}

// Verify Signature.
fileData, err := VerifyFileData(sigs[0], testFileSigOneMetaData, testTrustStore)
if err != nil {
t.Fatal(err)
}

// Verify File.
if !fileData.FileHash().MatchesData(testFileSigOneData) {
t.Fatal("file hash does not match")
}

// Verify the saved version of the signature.

// Parse the saved signature.
sigs, err = ParseSigFile(testFileSigOneSignature)
if err != nil {
t.Fatal(err)
}
if len(sigs) != 1 {
t.Fatalf("only one sig expected, got %d", len(sigs))
}

// Verify Signature.
fileData, err = VerifyFileData(sigs[0], testFileSigOneMetaData, testTrustStore)
if err != nil {
t.Fatal(err)
}

// Verify File.
if !fileData.FileHash().MatchesData(testFileSigOneData) {
t.Fatal("file hash does not match")
}
}

var (
testFileSigFormat1 = []byte(`TGFiZWxlZEhhc2jEIhkgAXGM7DXNPXlt0AAg4L
-----BEGIN JESS SIGNATURE-----
Q6VnVmVyc2lvbgFnU3VpdGVJRGdzaWduX3YxZU5vbmNlRA40a/BkRGF0YVhqTYOr
TGFiZWxlZEhhc2jEIhkgAXGM7DXNPXlt0AAg4L/stHOtI0V9Bjt17/KcD/ouWKmo
U2lnbmVkQXTW/2LH/ueoTWV0YURhdGGComlkrXJlc291cmNlL3BhdGindmVyc2lv
bqUwLjAuMWpTaWduYXR1cmVzgaNmU2NoZW1lZ0VkMjU1MTliSURwZmlsZXNpZy10
ZXN0LWtleWVWYWx1ZVhA4b1kfIJF7do6OcJnemQ5mtj/ZyMFJWWTmD1W5KvkpZac
2AP5f+dDJhzWBHsoSXTCl6uA3DA3+RbABMYAZn6eDg
-----END JESS SIGNATURE-----
-----END JESS SIGNATURE-----
-----BEGIN JESS SIGNATURE-----
Q6VnVmVyc2lvbgFnU3VpdGVJRGdzaWduX3YxZU5vbmNlRA40a/BkRGF0YVhqTYOr
TGFiZWxlZEhhc2jEIhkgAXGM7DXNPXlt0AAg4L/stHOtI0V9Bjt17/KcD/ouWKmo
U2lnbmVkQXTW/2LH/ueoTWV0YURhdGGComlk
rXJlc291cmNlL3BhdGindmVyc2lvbqUwLjAuMWpTaWduYXR1cmVzgaNmU2NoZW1lZ0VkMjU1MTliSURwZmlsZXNpZy10
ZXN0LWtleWVWYWx1ZVhA4b1kfIJF7do6OcJnemQ5mtj/ZyMFJWWTmD1W5KvkpZac
2AP5f+dDJhzWBHsoSXTCl6uA3DA3+RbABMYAZn6eDg
-----END JESS SIGNATURE-----
end`)

testFileSigFormat2 = []byte(`test data 1
-----BEGIN JESS SIGNATURE-----
invalid sig
-----END JESS SIGNATURE-----
test data 2`)

testFileSigFormat3 = []byte(`test data 1
-----BEGIN JESS SIGNATURE-----
invalid sig
-----END JESS SIGNATURE-----
test data 2
-----BEGIN JESS SIGNATURE-----
Q6VnVmVyc2lvbgFnU3VpdGVJRGdzaWduX3YxZU5vbmNlRA40a/BkRGF0YVhqTYOr
TGFiZWxlZEhhc2jEIhkgAXGM7DXNPXlt0AAg4L/stHOtI0V9Bjt17/KcD/ouWKmo
U2lnbmVkQXTW/2LH/ueoTWV0YURhdGGComlkrXJlc291cmNlL3BhdGindmVyc2lv
bqUwLjAuMWpTaWduYXR1cmVzgaNmU2NoZW1lZ0VkMjU1MTliSURwZmlsZXNpZy10
ZXN0LWtleWVWYWx1ZVhA4b1kfIJF7do6OcJnemQ5mtj/ZyMFJWWTmD1W5KvkpZac
2AP5f+dDJhzWBHsoSXTCl6uA3DA3+RbABMYAZn6eDg
-----END JESS SIGNATURE-----`)

testFileSigFormat4 = []byte(`test data 1
test data 2
-----BEGIN JESS SIGNATURE-----
Q6VnVmVyc2lvbgFnU3VpdGVJRGdzaWduX3YxZU5vbmNlRA40a/BkRGF0YVhqTYOr
TGFiZWxlZEhhc2jEIhkgAXGM7DXNPXlt0AAg4L/stHOtI0V9Bjt17/KcD/ouWKmo
U2lnbmVkQXTW/2LH/ueoTWV0YURhdGGComlkrXJlc291cmNlL3BhdGindmVyc2lv
bqUwLjAuMWpTaWduYXR1cmVzgaNmU2NoZW1lZ0VkMjU1MTliSURwZmlsZXNpZy10
ZXN0LWtleWVWYWx1ZVhA4b1kfIJF7do6OcJnemQ5mtj/ZyMFJWWTmD1W5KvkpZac
2AP5f+dDJhzWBHsoSXTCl6uA3DA3+RbABMYAZn6eDg
-----END JESS SIGNATURE-----`)
)

func TestFileSigFormatParsing(t *testing.T) {
t.Parallel()

sigs, err := ParseSigFile(testFileSigFormat1)
if err != nil {
t.Fatal(err)
}
if len(sigs) != 2 {
t.Fatalf("expected two signatures, got %d", 1)
}

newFile, err := AddToSigFile(sigs[0], testFileSigFormat2, false)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(newFile, testFileSigFormat3) {
t.Fatalf("unexpected output:\n%s", string(newFile))
}
newFile, err = AddToSigFile(sigs[0], testFileSigFormat2, true)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(newFile, testFileSigFormat4) {
t.Fatalf("unexpected output:\n%s", string(newFile))
}
}
Loading

0 comments on commit 0bb5f33

Please sign in to comment.