Add text format for signets and envelopes and support for import/export
This commit is contained in:
parent
74d41194cc
commit
d4d06574b8
15 changed files with 393 additions and 10 deletions
|
@ -30,7 +30,7 @@ func newEnvelope(name string) (*jess.Envelope, error) {
|
|||
"Encrypt with key",
|
||||
"Encrypt for someone and sign",
|
||||
"Encrypt for someone but don't sign",
|
||||
"Sign a file",
|
||||
"Sign a file (wrapped)",
|
||||
},
|
||||
}
|
||||
err := survey.AskOne(prompt, &preset, nil)
|
||||
|
@ -93,6 +93,7 @@ func editEnvelope(envelope *jess.Envelope) error {
|
|||
{"Recipients", formatSignetNames(envelope.Recipients)},
|
||||
{"Senders", formatSignetNames(envelope.Senders)},
|
||||
{""},
|
||||
{"Export", "export to text format"},
|
||||
{"Abort", "discard changes and return"},
|
||||
{"Delete", "delete and return"},
|
||||
}),
|
||||
|
@ -105,21 +106,28 @@ func editEnvelope(envelope *jess.Envelope) error {
|
|||
|
||||
switch {
|
||||
case strings.HasPrefix(submenu, "Done"):
|
||||
// Check if the envolope is valid.
|
||||
// Check if the envelope is valid.
|
||||
if envelope.SecurityLevel == 0 {
|
||||
fmt.Println("Envelope is invalid, please fix before saving.")
|
||||
continue
|
||||
}
|
||||
// Remove and keys and save.
|
||||
_ = envelope.LoopSecrets("", func(signet *jess.Signet) error {
|
||||
signet.Key = nil
|
||||
return nil
|
||||
})
|
||||
_ = envelope.LoopSenders("", func(signet *jess.Signet) error {
|
||||
signet.Key = nil
|
||||
return nil
|
||||
})
|
||||
envelope.CleanSignets()
|
||||
return trustStore.StoreEnvelope(envelope)
|
||||
case strings.HasPrefix(submenu, "Export"):
|
||||
// Check if the envelope is valid.
|
||||
if envelope.SecurityLevel == 0 {
|
||||
fmt.Println("Envelope is invalid, please fix before exporting.")
|
||||
continue
|
||||
}
|
||||
// Remove keys and export.
|
||||
envelope.CleanSignets()
|
||||
text, err := envelope.Export(false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to export: %w", err)
|
||||
}
|
||||
fmt.Println("Exported envelope:")
|
||||
fmt.Println(text)
|
||||
case strings.HasPrefix(submenu, "Abort"):
|
||||
return nil
|
||||
case strings.HasPrefix(submenu, "Delete"):
|
||||
|
|
14
cmd/testdata/.truststore/3911c84c-78f7-4354-a7f5-0e115aa2903c.recipient
vendored
Normal file
14
cmd/testdata/.truststore/3911c84c-78f7-4354-a7f5-0e115aa2903c.recipient
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
J{
|
||||
"Version": 1,
|
||||
"ID": "3911c84c-78f7-4354-a7f5-0e115aa2903c",
|
||||
"Scheme": "Ed25519",
|
||||
"Key": "ATYVZjmhR1Zwe0KAPV99pzbzI+6zWgKvELNhFwolRdnv",
|
||||
"Public": true,
|
||||
"Info": {
|
||||
"Name": "Safing Code Signing Cert 1",
|
||||
"Owner": "",
|
||||
"Created": "2022-07-11T10:23:31.705715613+02:00",
|
||||
"Expires": "0001-01-01T00:00:00Z",
|
||||
"Details": null
|
||||
}
|
||||
}
|
13
cmd/testdata/.truststore/3911c84c-78f7-4354-a7f5-0e115aa2903c.signet
vendored
Normal file
13
cmd/testdata/.truststore/3911c84c-78f7-4354-a7f5-0e115aa2903c.signet
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
J{
|
||||
"Version": 1,
|
||||
"ID": "3911c84c-78f7-4354-a7f5-0e115aa2903c",
|
||||
"Scheme": "Ed25519",
|
||||
"Key": "Aee5n/V1wJM8aNpaNEPBEPeN6S0Tl41OJP0rHwtsGcZcNhVmOaFHVnB7QoA9X32nNvMj7rNaAq8Qs2EXCiVF2e8=",
|
||||
"Info": {
|
||||
"Name": "Safing Code Signing Cert 1",
|
||||
"Owner": "",
|
||||
"Created": "2022-07-11T10:23:31.705715613+02:00",
|
||||
"Expires": "0001-01-01T00:00:00Z",
|
||||
"Details": null
|
||||
}
|
||||
}
|
23
cmd/testdata/.truststore/safing-codesign-1.envelope
vendored
Normal file
23
cmd/testdata/.truststore/safing-codesign-1.envelope
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
J{
|
||||
"Version": 1,
|
||||
"Name": "safing-codesign-1",
|
||||
"SuiteID": "signfile_v1",
|
||||
"Secrets": null,
|
||||
"Senders": [
|
||||
{
|
||||
"Version": 1,
|
||||
"ID": "3911c84c-78f7-4354-a7f5-0e115aa2903c",
|
||||
"Scheme": "Ed25519",
|
||||
"Key": null,
|
||||
"Info": {
|
||||
"Name": "Safing Code Signing Cert 1",
|
||||
"Owner": "",
|
||||
"Created": "2022-07-11T10:23:31.705715613+02:00",
|
||||
"Expires": "0001-01-01T00:00:00Z",
|
||||
"Details": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"Recipients": null,
|
||||
"SecurityLevel": 128
|
||||
}
|
1
cmd/testdata/test.txt
vendored
Normal file
1
cmd/testdata/test.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
hello world!
|
13
cmd/testdata/test.txt.letter
vendored
Normal file
13
cmd/testdata/test.txt.letter
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
‘J{
|
||||
"Version": 1,
|
||||
"SuiteID": "signfile_v1",
|
||||
"Nonce": "pKOQBQ==",
|
||||
"Signatures": [
|
||||
{
|
||||
"Scheme": "Ed25519",
|
||||
"ID": "3911c84c-78f7-4354-a7f5-0e115aa2903c",
|
||||
"Value": "ftsIINZ9oApKiXYQTcLIdAZDSflp6nRN/y8Gm0rdQC+3/wal6Q+7N3N8HEAxpoxWseSQNaRVCT9hSnRQStHYBA=="
|
||||
}
|
||||
]
|
||||
}
|
||||
hello world!
|
9
cmd/testdata/test.txt.sig
vendored
Normal file
9
cmd/testdata/test.txt.sig
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
-----BEGIN JESS SIGNATURE-----
|
||||
Q6VnVmVyc2lvbgFnU3VpdGVJRGtzaWduZmlsZV92MWVOb25jZUQb+MqAZERhdGFY
|
||||
d02Dq0xhYmVsZWRIYXNoxCIJIOz3Afcn2eLXfEqkmsb7vMmXJ4rKAQvd7rlhwQz1
|
||||
TUNaqFNpZ25lZEF01v9iy+uLqE1ldGFEYXRhgqd2ZXJzaW9upTAuMC4xqmlkZW50
|
||||
aWZpZXKyd2luZG93cy9jb2RlL3RoaW5nalNpZ25hdHVyZXOBo2ZTY2hlbWVnRWQy
|
||||
NTUxOWJJRHgkMzkxMWM4NGMtNzhmNy00MzU0LWE3ZjUtMGUxMTVhYTI5MDNjZVZh
|
||||
bHVlWECJZFbIifczUGAJkmATXCHy/MiQZkiktM99X7U/cPgw3IKpKAxQsJ5LobgZ
|
||||
4P2ecv0IlN4gQb+x+lycxl93E9sJ
|
||||
-----END JESS SIGNATURE-----
|
1
cmd/testdata/test3.txt
vendored
Normal file
1
cmd/testdata/test3.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
hello world!!
|
9
cmd/testdata/test3.txt.sig
vendored
Normal file
9
cmd/testdata/test3.txt.sig
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
-----BEGIN JESS SIGNATURE-----
|
||||
Q6VnVmVyc2lvbgFnU3VpdGVJRGtzaWduZmlsZV92MWVOb25jZUQJ9s/nZERhdGFY
|
||||
d02Dq0xhYmVsZWRIYXNoxCIJILtKnL1AHj7YubrWdLu1D+voud8Ky04vh756eTae
|
||||
rWQwqFNpZ25lZEF01v9izC6hqE1ldGFEYXRhgqd2ZXJzaW9upTAuMC4xqmlkZW50
|
||||
aWZpZXKyd2luZG93cy9jb2RlL3RoaW5nalNpZ25hdHVyZXOBo2ZTY2hlbWVnRWQy
|
||||
NTUxOWJJRHgkMzkxMWM4NGMtNzhmNy00MzU0LWE3ZjUtMGUxMTVhYTI5MDNjZVZh
|
||||
bHVlWEBLsd2QbM7VmEsnW60hHn/V6EP2mGFauWZgbEOlKTiqumVFbWU4K7Fi91KL
|
||||
Zgvwj+CNdZJ7Xv2qR7etviRDCmwC
|
||||
-----END JESS SIGNATURE-----
|
1
cmd/testdata/test4.txt
vendored
Normal file
1
cmd/testdata/test4.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
hello world!
|
1
cmd/testdata/testdir/test2.txt
vendored
Normal file
1
cmd/testdata/testdir/test2.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
hello world!
|
9
cmd/testdata/testdir/test2.txt.sig
vendored
Normal file
9
cmd/testdata/testdir/test2.txt.sig
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
-----BEGIN JESS SIGNATURE-----
|
||||
Q6VnVmVyc2lvbgFnU3VpdGVJRGtzaWduZmlsZV92MWVOb25jZUThzxO6ZERhdGFY
|
||||
d02Dq0xhYmVsZWRIYXNoxCIJIOz3Afcn2eLXfEqkmsb7vMmXJ4rKAQvd7rlhwQz1
|
||||
TUNaqFNpZ25lZEF01v9izC3SqE1ldGFEYXRhgqd2ZXJzaW9upTAuMC4xqmlkZW50
|
||||
aWZpZXKyd2luZG93cy9jb2RlL3RoaW5nalNpZ25hdHVyZXOBo2ZTY2hlbWVnRWQy
|
||||
NTUxOWJJRHgkMzkxMWM4NGMtNzhmNy00MzU0LWE3ZjUtMGUxMTVhYTI5MDNjZVZh
|
||||
bHVlWEAGLkIoej0+ilJrIyb+BzX8+Yw2LY0zkoL9vwI02/2KqKVT7/pH+LTDX1Hl
|
||||
h1epYkF8ICdwa1iVNDx6P7iNmWkL
|
||||
-----END JESS SIGNATURE-----
|
81
envelope.go
81
envelope.go
|
@ -3,6 +3,10 @@ package jess
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
|
||||
"github.com/safing/portbase/formats/dsd"
|
||||
)
|
||||
|
||||
// Envelope holds configuration for jess to put data into a letter.
|
||||
|
@ -268,3 +272,80 @@ func fillPassword(signet *Signet, createPassword bool, storage TrustStore, minSe
|
|||
}
|
||||
return getPasswordCallback(signet)
|
||||
}
|
||||
|
||||
// CleanSignets cleans all the signets from all the non-necessary data as well
|
||||
// as key material.
|
||||
// This is for preparing for serializing and saving the signet.
|
||||
func (e *Envelope) CleanSignets() {
|
||||
for i, signet := range e.Secrets {
|
||||
e.Secrets[i] = &Signet{
|
||||
Version: signet.Version,
|
||||
ID: signet.ID,
|
||||
Scheme: signet.Scheme,
|
||||
}
|
||||
}
|
||||
for i, signet := range e.Senders {
|
||||
e.Secrets[i] = &Signet{
|
||||
Version: signet.Version,
|
||||
ID: signet.ID,
|
||||
Scheme: signet.Scheme,
|
||||
}
|
||||
}
|
||||
for i, signet := range e.Recipients {
|
||||
e.Secrets[i] = &Signet{
|
||||
Version: signet.Version,
|
||||
ID: signet.ID,
|
||||
Scheme: signet.Scheme,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ToBytes serializes the envelope to a byte slice.
|
||||
func (e *Envelope) ToBytes() ([]byte, error) {
|
||||
// Minimize data and remove any key material.
|
||||
e.CleanSignets()
|
||||
|
||||
// Serialize envelope.
|
||||
data, err := dsd.Dump(e, dsd.CBOR)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to serialize the envelope: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// EnvelopeFromBytes parses and loads a serialized envelope.
|
||||
func EnvelopeFromBytes(data []byte) (*Envelope, error) {
|
||||
e := &Envelope{}
|
||||
|
||||
// Parse envelope from data.
|
||||
if _, err := dsd.Load(data, e); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse data format: %w", err)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// ToBase58 serializes the envelope and encodes it with base58.
|
||||
func (e *Envelope) ToBase58() (string, error) {
|
||||
// Serialize Signet.
|
||||
data, err := e.ToBytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Encode and return.
|
||||
return base58.Encode(data), nil
|
||||
}
|
||||
|
||||
// EnvelopeFromBase58 parses and loads a base58 encoded serialized envelope.
|
||||
func EnvelopeFromBase58(base58Encoded string) (*Envelope, error) {
|
||||
// Decode string.
|
||||
data, err := base58.Decode(base58Encoded)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode base58: %w", err)
|
||||
}
|
||||
|
||||
// Parse and return.
|
||||
return EnvelopeFromBytes(data)
|
||||
}
|
||||
|
|
192
import_export.go
Normal file
192
import_export.go
Normal file
|
@ -0,0 +1,192 @@
|
|||
package jess
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
ExportSenderKeyword = "sender"
|
||||
ExportSenderPrefix = "sender:"
|
||||
|
||||
ExportRecipientKeyword = "recipient"
|
||||
ExportRecipientPrefix = "recipient:"
|
||||
|
||||
ExportKeyKeyword = "secret"
|
||||
ExportKeyPrefix = "secret:"
|
||||
|
||||
ExportEnvelopeKeyword = "envelope"
|
||||
ExportEnvelopePrefix = "envelope:"
|
||||
)
|
||||
|
||||
// Export exports the public part of a signet in text format.
|
||||
func (signet *Signet) Export(short bool) (textFormat string, err error) {
|
||||
// Make public if needed.
|
||||
if !signet.Public {
|
||||
signet, err = signet.AsRecipient()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Transform to text format.
|
||||
return signet.toTextFormat(short)
|
||||
}
|
||||
|
||||
// Backup exports the private part of a signet in text format.
|
||||
func (signet *Signet) Backup(short bool) (textFormat string, err error) {
|
||||
// Abprt if public.
|
||||
if signet.Public {
|
||||
return "", errors.New("cannot backup (only export) a recipient")
|
||||
}
|
||||
|
||||
// Transform to text format.
|
||||
return signet.toTextFormat(short)
|
||||
}
|
||||
|
||||
func (signet *Signet) toTextFormat(short bool) (textFormat string, err error) {
|
||||
// Serialize to base58.
|
||||
base58data, err := signet.ToBase58()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Define keywords.
|
||||
var keyword, typeComment string
|
||||
switch {
|
||||
case signet.Scheme == SignetSchemePassword:
|
||||
return "", errors.New("cannot backup or export passwords")
|
||||
case signet.Scheme == SignetSchemeKey:
|
||||
// Check if the signet is marked as "public".
|
||||
if signet.Public {
|
||||
return "", errors.New("cannot export keys")
|
||||
}
|
||||
keyword = ExportKeyKeyword
|
||||
typeComment = "symmetric-key"
|
||||
case signet.Public:
|
||||
keyword = ExportRecipientKeyword
|
||||
typeComment = fmt.Sprintf(
|
||||
"public-%s-key", toTextFormatString(signet.Scheme),
|
||||
)
|
||||
default:
|
||||
keyword = ExportSenderKeyword
|
||||
typeComment = fmt.Sprintf(
|
||||
"private-%s-key", toTextFormatString(signet.Scheme),
|
||||
)
|
||||
}
|
||||
|
||||
// Transform to text format.
|
||||
if short {
|
||||
return fmt.Sprintf(
|
||||
"%s:%s",
|
||||
keyword,
|
||||
base58data,
|
||||
), nil
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"%s:%s:%s:%s",
|
||||
keyword,
|
||||
typeComment,
|
||||
toTextFormatString(signet.Info.Name),
|
||||
base58data,
|
||||
), nil
|
||||
}
|
||||
|
||||
// Export exports the envelope in text format.
|
||||
func (e *Envelope) Export(short bool) (textFormat string, err error) {
|
||||
// Remove and key data.
|
||||
e.CleanSignets()
|
||||
|
||||
// Serialize to base58.
|
||||
base58data, err := e.ToBase58()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Transform to text format.
|
||||
if short {
|
||||
return fmt.Sprintf(
|
||||
"%s:%s",
|
||||
ExportEnvelopeKeyword,
|
||||
base58data,
|
||||
), nil
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"%s:%s:%s:%s",
|
||||
ExportEnvelopeKeyword,
|
||||
e.SuiteID,
|
||||
e.Name,
|
||||
base58data,
|
||||
), nil
|
||||
}
|
||||
|
||||
// KeyFromTextFormat loads a secret key from the text format.
|
||||
func KeyFromTextFormat(textFormat string) (*Signet, error) {
|
||||
// Check the identifier.
|
||||
if !strings.HasPrefix(textFormat, ExportKeyPrefix) {
|
||||
return nil, errors.New("not a secret")
|
||||
}
|
||||
|
||||
// Parse the data section.
|
||||
splitted := strings.Split(textFormat, ":")
|
||||
if len(splitted) < 2 {
|
||||
return nil, errors.New("invalid format")
|
||||
}
|
||||
return SignetFromBase58(splitted[len(splitted)-1])
|
||||
}
|
||||
|
||||
// SenderFromTextFormat loads a sender (private key) from the text format.
|
||||
func SenderFromTextFormat(textFormat string) (*Signet, error) {
|
||||
// Check the identifier.
|
||||
if !strings.HasPrefix(textFormat, ExportSenderPrefix) {
|
||||
return nil, errors.New("not a sender")
|
||||
}
|
||||
|
||||
// Parse the data section.
|
||||
splitted := strings.Split(textFormat, ":")
|
||||
if len(splitted) < 2 {
|
||||
return nil, errors.New("invalid format")
|
||||
}
|
||||
return SignetFromBase58(splitted[len(splitted)-1])
|
||||
}
|
||||
|
||||
// RecipientFromTextFormat loads a recipient (public key) from the text format.
|
||||
func RecipientFromTextFormat(textFormat string) (*Signet, error) {
|
||||
// Check the identifier.
|
||||
if !strings.HasPrefix(textFormat, ExportRecipientPrefix) {
|
||||
return nil, errors.New("not a recipient")
|
||||
}
|
||||
|
||||
// Parse the data section.
|
||||
splitted := strings.Split(textFormat, ":")
|
||||
if len(splitted) < 2 {
|
||||
return nil, errors.New("invalid format")
|
||||
}
|
||||
return SignetFromBase58(splitted[len(splitted)-1])
|
||||
}
|
||||
|
||||
// EnvelopeFromTextFormat loads an envelope from the text format.
|
||||
func EnvelopeFromTextFormat(textFormat string) (*Envelope, error) {
|
||||
// Check the identifier.
|
||||
if !strings.HasPrefix(textFormat, ExportEnvelopePrefix) {
|
||||
return nil, errors.New("not an envelope")
|
||||
}
|
||||
|
||||
// Parse the data section.
|
||||
splitted := strings.Split(textFormat, ":")
|
||||
if len(splitted) < 2 {
|
||||
return nil, errors.New("invalid format")
|
||||
}
|
||||
return EnvelopeFromBase58(splitted[len(splitted)-1])
|
||||
}
|
||||
|
||||
var replaceForTextFormatMatcher = regexp.MustCompile(`[^A-Za-z\-]+`)
|
||||
|
||||
// toTextFormatString makes a string compatible with the text format.
|
||||
func toTextFormatString(s string) string {
|
||||
return strings.ToLower(
|
||||
replaceForTextFormatMatcher.ReplaceAllString(s, "_"),
|
||||
)
|
||||
}
|
|
@ -136,6 +136,14 @@ func (signet *Signet) SetLoadedKeys(pubKey crypto.PublicKey, privKey crypto.Priv
|
|||
|
||||
// AsRecipient returns a public version of the Signet.
|
||||
func (signet *Signet) AsRecipient() (*Signet, error) {
|
||||
// Check special signet schemes.
|
||||
switch signet.Scheme {
|
||||
case SignetSchemeKey:
|
||||
return nil, errors.New("keys cannot be a recipient")
|
||||
case SignetSchemePassword:
|
||||
return nil, errors.New("passwords cannot be a recipient")
|
||||
}
|
||||
|
||||
// load so we can split keys
|
||||
err := signet.LoadKey()
|
||||
if err != nil {
|
||||
|
|
Loading…
Add table
Reference in a new issue