Remediate SAF-01-005 Crypto: Unnecessary Configurability Considered Dangerous (Medium)

This is a rather large commit, as the change from a toolset to the new cipher suite system has a large impact.
This commit is contained in:
Daniel 2020-01-24 16:04:15 +01:00
parent 7990775cf3
commit 31216b0885
20 changed files with 1117 additions and 389 deletions

4
Gopkg.lock generated
View file

@ -104,7 +104,7 @@
[[projects]]
branch = "master"
digest = "1:23e3d5f66ff9fd60dd264fbf26e71cab6b4a3bd2ef993a1645432d5c4c3b50fc"
digest = "1:4df631634d7dc4496e9c51436cc2d3ccd0d0d4734640f63108950fe68b348b7e"
name = "golang.org/x/crypto"
packages = [
"blake2b",
@ -118,6 +118,7 @@
"poly1305",
"salsa20",
"salsa20/salsa",
"scrypt",
"sha3",
]
pruneopts = "UT"
@ -164,6 +165,7 @@
"golang.org/x/crypto/pbkdf2",
"golang.org/x/crypto/poly1305",
"golang.org/x/crypto/salsa20",
"golang.org/x/crypto/scrypt",
"golang.org/x/crypto/sha3",
]
solver-name = "gps-cdcl"

View file

@ -1,5 +1,7 @@
# Jess Specification
This document takes a closer look at the inner workings of Jess. It does not, however, define the formats and some other aspects. First, some basics will be covered. Then, the wire protocol is given a closer look.
## Basics
The basic building blocks of jess are:

View file

@ -11,20 +11,19 @@ import (
func TestWire(t *testing.T) {
wireReKeyAfterMsgs = 100
testWireCorrespondence(t, RecommendedNetwork, testData1)
testWireCorrespondence(t, RecommendedNetwork, testData2)
// current suites recommendation
testWireCorrespondence(t, getSuite(t, SuiteWire), testData1)
testWireCorrespondence(t, getSuite(t, SuiteWire), testData2)
testWireCorrespondence(t, []string{"ECDH-P224", "HKDF(SHA2-256)", "CHACHA20-POLY1305"}, testData1)
testWireCorrespondence(t, []string{"ECDH-P256", "HKDF(SHA2-256)", "CHACHA20-POLY1305"}, testData1)
testWireCorrespondence(t, []string{"ECDH-P384", "HKDF(SHA2-256)", "CHACHA20-POLY1305"}, testData1)
testWireCorrespondence(t, []string{"ECDH-P521", "HKDF(SHA2-256)", "CHACHA20-POLY1305"}, testData1)
testWireCorrespondence(t, []string{"RSA-OAEP(SHA2-256)", "HKDF(SHA2-256)", "CHACHA20-POLY1305"}, testData1)
// older suites
// testWireCorrespondence(t, getSuite(t, SuiteWireV1), testData1)
// testWireCorrespondence(t, getSuite(t, SuiteWireV1), testData2)
}
func testWireCorrespondence(t *testing.T, toolIDs []string, testData string) {
func testWireCorrespondence(t *testing.T, suite *Suite, testData string) {
wtr := &wireTestRange{t: t}
wtr.init(toolIDs, testData)
fmt.Printf("\n\nsimulating %v\n", toolIDs)
wtr.init(suite, testData)
fmt.Printf("\n\nsimulating %v\n", suite.ID)
fmt.Println("two dots are one packet send+recv:")
fmt.Println("\nclient ->")
@ -74,7 +73,7 @@ func testWireCorrespondence(t *testing.T, toolIDs []string, testData string) {
duration := wtr.endTime.Sub(wtr.startTime)
t.Logf(
"%v tested: msgsize=%d, rekey every %d msgs, %d msgs, %d bytes, +%f%% overhead, %s, %s per msg, %f Mbit/s",
wtr.toolIDs,
wtr.suite.ID,
len(testData),
wireReKeyAfterMsgs,
wtr.msgsTransferred,
@ -92,7 +91,7 @@ func testWireCorrespondence(t *testing.T, toolIDs []string, testData string) {
type wireTestRange struct {
t *testing.T
toolIDs []string
suite *Suite
testData string
client *Session
@ -108,12 +107,12 @@ type wireTestRange struct {
endTime time.Time
}
func (wtr *wireTestRange) init(toolIDs []string, testData string) (detectedInvalid bool) {
wtr.toolIDs = toolIDs
func (wtr *wireTestRange) init(suite *Suite, testData string) (detectedInvalid bool) {
wtr.suite = suite
e, err := setupEnvelopeAndTrustStore(wtr.t, wtr.toolIDs)
e, err := setupEnvelopeAndTrustStore(wtr.t, wtr.suite)
if err != nil {
wtr.t.Fatalf("%v failed to setup envelope: %s", wtr.toolIDs, err)
wtr.t.Fatalf("%s failed to setup envelope: %s", wtr.suite.ID, err)
return false
}
if e == nil {
@ -122,7 +121,7 @@ func (wtr *wireTestRange) init(toolIDs []string, testData string) (detectedInval
wtr.client, err = e.WireCorrespondence(testTrustStore)
if err != nil {
wtr.t.Fatalf("%v failed to init client session: %s", wtr.toolIDs, err)
wtr.t.Fatalf("%s failed to init client session: %s", wtr.suite.ID, err)
}
// setup and reset
@ -142,18 +141,18 @@ func (wtr *wireTestRange) init(toolIDs []string, testData string) (detectedInval
func (wtr *wireTestRange) clientSend() {
letter, err := wtr.client.Close([]byte(wtr.testData))
if err != nil {
wtr.t.Fatalf("%v failed to close: %s", wtr.toolIDs, err)
wtr.t.Fatalf("%s failed to close: %s", wtr.suite.ID, err)
}
wireData, err := letter.ToWire()
if err != nil {
wtr.t.Fatalf("%v failed to serialize to wire: %s", wtr.toolIDs, err)
wtr.t.Fatalf("%s failed to serialize to wire: %s", wtr.suite.ID, err)
}
select {
case wtr.clientToServer <- wireData:
default:
wtr.t.Fatalf("%v could not send to server", wtr.toolIDs)
wtr.t.Fatalf("%s could not send to server", wtr.suite.ID)
}
fmt.Print(".")
@ -167,27 +166,27 @@ func (wtr *wireTestRange) serverRecv() {
letter, err := LetterFromWire(wireData)
if err != nil {
wtr.t.Fatalf("%v failed to parse initial wired letter: %s", wtr.toolIDs, err)
wtr.t.Fatalf("%s failed to parse initial wired letter: %s", wtr.suite.ID, err)
}
if wtr.server == nil {
wtr.server, err = letter.WireCorrespondence(testTrustStore)
if err != nil {
wtr.t.Fatalf("%v failed to init server session: %s", wtr.toolIDs, err)
wtr.t.Fatalf("%s failed to init server session: %s", wtr.suite.ID, err)
}
}
origData, err := wtr.server.Open(letter)
if err != nil {
wtr.t.Fatalf("%v failed to open: %s", wtr.toolIDs, err)
wtr.t.Fatalf("%s failed to open: %s", wtr.suite.ID, err)
}
wtr.bytesTransferred += len(origData)
if string(origData) != wtr.testData {
wtr.t.Fatalf("%v testdata mismatch", wtr.toolIDs)
wtr.t.Fatalf("%s testdata mismatch", wtr.suite.ID)
}
default:
wtr.t.Fatalf("%v could not recv from client", wtr.toolIDs)
wtr.t.Fatalf("%s could not recv from client", wtr.suite.ID)
}
fmt.Print(".")
@ -196,18 +195,18 @@ func (wtr *wireTestRange) serverRecv() {
func (wtr *wireTestRange) serverSend() {
letter, err := wtr.server.Close([]byte(wtr.testData))
if err != nil {
wtr.t.Fatalf("%v failed to close: %s", wtr.toolIDs, err)
wtr.t.Fatalf("%s failed to close: %s", wtr.suite.ID, err)
}
wireData, err := letter.ToWire()
if err != nil {
wtr.t.Fatalf("%v failed to serialize to wire: %s", wtr.toolIDs, err)
wtr.t.Fatalf("%s failed to serialize to wire: %s", wtr.suite.ID, err)
}
select {
case wtr.serverToClient <- wireData:
default:
wtr.t.Fatalf("%v could not send to client", wtr.toolIDs)
wtr.t.Fatalf("%s could not send to client", wtr.suite.ID)
}
fmt.Print(".")
@ -221,20 +220,20 @@ func (wtr *wireTestRange) clientRecv() {
letter, err := LetterFromWire(wireData)
if err != nil {
wtr.t.Fatalf("%v failed to parse initial wired letter: %s", wtr.toolIDs, err)
wtr.t.Fatalf("%s failed to parse initial wired letter: %s", wtr.suite.ID, err)
}
origData, err := wtr.client.Open(letter)
if err != nil {
wtr.t.Fatalf("%v failed to open: %s", wtr.toolIDs, err)
wtr.t.Fatalf("%s failed to open: %s", wtr.suite.ID, err)
}
wtr.bytesTransferred += len(origData)
if string(origData) != wtr.testData {
wtr.t.Fatalf("%v testdata mismatch", wtr.toolIDs)
wtr.t.Fatalf("%s testdata mismatch", wtr.suite.ID)
}
default:
wtr.t.Fatalf("%v could not recv from server", wtr.toolIDs)
wtr.t.Fatalf("%s could not recv from server", wtr.suite.ID)
}
fmt.Print(".")

77
core.go
View file

@ -16,7 +16,7 @@ func (s *Session) Close(data []byte) (*Letter, error) { //nolint:gocognit
if s.wire == nil || s.wire.msgNo == 0 {
letter.Version = 1
letter.Tools = s.envelope.Tools
letter.SuiteID = s.envelope.SuiteID
}
/////////////////
@ -314,6 +314,15 @@ func (s *Session) Open(letter *Letter) ([]byte, error) { //nolint:gocognit,gocyc
// Verify verifies signatures of the given letter.
func (s *Session) Verify(letter *Letter) error {
// debugging:
/*
fmt.Printf("opening: %+v\n", letter)
for _, sig := range letter.Signatures {
fmt.Printf("sig: %+v\n", sig)
}
*/
var err error
if s.wire == nil && letter.Version != 1 {
return fmt.Errorf("unsupported letter version: %d", letter.Version)
@ -323,44 +332,54 @@ func (s *Session) Verify(letter *Letter) error {
// verify
/////////
if len(s.signers) == 0 {
return errors.New("letter is not signed")
}
// TODO: signature verification is run before tool setup. Currently, this is ok, but might change in the future. This might break additional signing algorithms that actually need setup.
data := letter.Data
associatedSigningData := letter.compileAssociatedSigningData(nil)
// run managed signing hashers
if s.managedSigningHashers != nil {
err = s.feedManagedHashers(s.managedSigningHashers, data, associatedSigningData)
if err != nil {
return err
}
defer s.resetManagedHashers(s.managedSigningHashers)
// build associated data
var associatedData []byte
if len(s.integratedCiphers) > 0 || len(s.macs) > 0 {
associatedData = letter.compileAssociatedData()
}
// run signers
if len(s.envelope.Senders) != len(letter.Signatures) {
return errors.New("mismatch regarding available signatures and senders")
}
sigIndex := 0
// Signature
if len(s.signers) > 0 {
associatedSigningData := letter.compileAssociatedSigningData(associatedData)
for _, tool := range s.signers {
//nolint:scopelint // function is executed immediately within loop
err = s.envelope.LoopSenders(tool.Info().Name, func(signet *Signet) error {
err := tool.Verify(data, associatedSigningData, letter.Signatures[sigIndex].Value, signet)
// run managed signing hashers
if s.managedSigningHashers != nil {
err = s.feedManagedHashers(s.managedSigningHashers, data, associatedSigningData)
if err != nil {
return fmt.Errorf("failed to verify signature (%s) with ID %s: %s", tool.Info().Name, letter.Signatures[sigIndex].ID, err)
return err
}
sigIndex++
return nil
})
if err != nil {
return err
defer s.resetManagedHashers(s.managedSigningHashers)
}
// run signers
if len(s.envelope.Senders) != len(letter.Signatures) {
return errors.New("mismatch regarding available signatures and senders")
}
sigIndex := 0
for _, tool := range s.signers {
//nolint:scopelint // function is executed immediately within loop
err = s.envelope.LoopSenders(tool.Info().Name, func(signet *Signet) error {
err := tool.Verify(data, associatedSigningData, letter.Signatures[sigIndex].Value, signet)
if err != nil {
return fmt.Errorf("failed to verify signature (%s) with ID %s: %s", tool.Info().Name, letter.Signatures[sigIndex].ID, err)
}
sigIndex++
return nil
})
if err != nil {
return err
}
}
} else {
return errors.New("no signatures to verify")
}
return nil

View file

@ -120,16 +120,8 @@ func init() {
}
func TestCoreBasic(t *testing.T) {
// toolsets to test
toolsets := [][]string{
RecommendedStorageKey,
RecommendedStoragePassword,
{"HKDF(SHA2-256)", "CHACHA20-POLY1305"},
{"PBKDF2-SHA2-256", "HKDF(SHA2-256)", "CHACHA20-POLY1305"},
}
for _, toolIDs := range toolsets {
testStorage(t, toolIDs)
for _, suite := range Suites() {
testStorage(t, suite)
}
}
@ -137,10 +129,12 @@ func TestCoreBasic(t *testing.T) {
func TestCoreAllCombinations(t *testing.T) {
// This shall test all tools in all combinations and every tool should be tested when placed before and after every other tool.
// skip in short tests
if testing.Short() {
// skip in short tests and when not running comprehensive
if testing.Short() || !runComprehensiveTestsActive {
return
}
// run this test with
// go test -timeout 10m github.com/safing/jess -v -count=1 -ldflags "-X github.com/safing/jess.RunComprehensiveTests=true" -run ^TestCoreAllCombinations$
// add all tools
var all []string
@ -193,7 +187,7 @@ func TestCoreAllCombinations(t *testing.T) {
// rotate to test before/after differences
for i := 0; i < len(testTools); i++ {
detectedInvalid := testStorage(t, testTools)
detectedInvalid := testStorage(t, &Suite{Tools: testTools})
combinationsTested++
if detectedInvalid {
combinationsDetectedInvalid++
@ -207,7 +201,7 @@ func TestCoreAllCombinations(t *testing.T) {
}
} else {
// test this order only
detectedInvalid := testStorage(t, testTools)
detectedInvalid := testStorage(t, &Suite{Tools: testTools})
combinationsTested++
if detectedInvalid {
combinationsDetectedInvalid++
@ -226,12 +220,12 @@ func TestCoreAllCombinations(t *testing.T) {
t.Logf("of these, %d were successfully detected as invalid", combinationsDetectedInvalid)
}
func testStorage(t *testing.T, toolIDs []string) (detectedInvalid bool) {
// t.Logf("testing storage with %v", toolIDs)
func testStorage(t *testing.T, suite *Suite) (detectedInvalid bool) {
// t.Logf("testing storage with %s", suite.ID)
e, err := setupEnvelopeAndTrustStore(t, toolIDs)
e, err := setupEnvelopeAndTrustStore(t, suite)
if err != nil {
tErrorf(t, "%v failed: %s", toolIDs, err)
tErrorf(t, "%s failed: %s", suite.ID, err)
return false
}
if e == nil {
@ -242,122 +236,72 @@ func testStorage(t *testing.T, toolIDs []string) (detectedInvalid bool) {
s, err := e.Correspondence(testTrustStore)
if err != nil {
tErrorf(t, "%v failed to init session (1): %s", toolIDs, err)
tErrorf(t, "%s failed to init session (1): %s", suite.ID, err)
return false
}
letter, err := s.Close([]byte(testData1))
if err != nil {
tErrorf(t, "%v failed to close (1): %s", toolIDs, err)
tErrorf(t, "%s failed to close (1): %s", suite.ID, err)
return false
}
msg, err := letter.ToJSON()
if err != nil {
tErrorf(t, "%v failed to json encode (1): %s", toolIDs, err)
tErrorf(t, "%s failed to json encode (1): %s", suite.ID, err)
return false
}
// test 2: open from session
// test 2: open
letter2, err := LetterFromJSON(msg)
if err != nil {
tErrorf(t, "%v failed to json decode (2): %s", toolIDs, err)
tErrorf(t, "%s failed to json decode (2): %s", suite.ID, err)
return false
}
origData2, err := s.Open(letter2)
origData2, err := letter2.Open(e.suite.Provides, testTrustStore)
if err != nil {
tErrorf(t, "%v failed to open (2): %s", toolIDs, err)
tErrorf(t, "%s failed to open (2): %s", suite.ID, err)
return false
}
if string(origData2) != testData1 {
tErrorf(t, "%v original data mismatch (2): %s", toolIDs, string(origData2))
tErrorf(t, "%s original data mismatch (2): %s", suite.ID, string(origData2))
return false
}
if len(letter2.Signatures) > 0 {
err = s.Verify(letter2)
if err != nil {
tErrorf(t, "%v failed to verify (2): %s", toolIDs, err)
return false
}
}
// extended tests
// only run for toolsets greater than 3 if we comprehensive testing is on
// for these tests, it is enough if every tool is tested once
if len(toolIDs) > 3 && RunComprehensiveTests != "true" {
return false
}
// test 2.1: open again to check if reset after opening works
// test 2.1: verify
letter21, err := LetterFromJSON(msg)
if err != nil {
tErrorf(t, "%v failed to json decode (2.1): %s", toolIDs, err)
tErrorf(t, "%s failed to json decode (2): %s", suite.ID, err)
return false
}
origData21, err := s.Open(letter21)
if err != nil {
tErrorf(t, "%v failed to open (2.1): %s", toolIDs, err)
return false
}
if string(origData21) != testData1 {
tErrorf(t, "%v original data mismatch (2.1): %s", toolIDs, string(origData21))
return false
}
// test 2.2: close and open again to check if reset after closing works
letter22, err := s.Close([]byte(testData1))
if err != nil {
tErrorf(t, "%v failed to close (2.2): %s", toolIDs, err)
return false
}
origData22, err := s.Open(letter22)
if err != nil {
tErrorf(t, "%v failed to open (2.2): %s", toolIDs, err)
return false
}
if string(origData22) != testData1 {
tErrorf(t, "%v original data mismatch (2.2): %s", toolIDs, string(origData22))
return false
}
// test 3: open from letter
// FIXME - other improvements broke these tests, pausing them
/*
letter3, err := LetterFromJSON(msg)
if len(letter21.Signatures) > 0 {
err = letter21.Verify(e.suite.Provides, testTrustStore)
if err != nil {
tErrorf(t, "%v failed to json decode (3): %s", toolIDs, err)
tErrorf(t, "%s failed to verify (2): %s", suite.ID, err)
return false
}
origData3, err := letter3.Open(nil, testTrustStore)
if err != nil {
tErrorf(t, "%v failed to open (3): %s", toolIDs, err)
return false
}
if string(origData3) != testData1 {
tErrorf(t, "%v original data mismatch (3): %s", toolIDs, string(origData3))
return false
}
*/
}
return false
}
//nolint:gocognit,gocyclo
func setupEnvelopeAndTrustStore(t *testing.T, toolIDs []string) (*Envelope, error) {
func setupEnvelopeAndTrustStore(t *testing.T, suite *Suite) (*Envelope, error) {
// check if suite is registered
if suite.ID == "" {
// register as test suite
suite.ID = fmt.Sprintf("__unit_test_suite__" + strings.Join(suite.Tools, "__"))
registerSuite(suite)
}
// create envelope baseline
e := &Envelope{
Tools: toolIDs,
requirements: newEmptyRequirements(),
SuiteID: suite.ID,
suite: suite,
}
// check vars
@ -366,7 +310,7 @@ func setupEnvelopeAndTrustStore(t *testing.T, toolIDs []string) (*Envelope, erro
asyncKeyEstablishmentPresent := false
// process tools and setup envelope
for _, toolID := range e.Tools {
for _, toolID := range e.suite.Tools {
// remove hasher argument for now
if strings.Contains(toolID, "(") {
@ -389,7 +333,7 @@ func setupEnvelopeAndTrustStore(t *testing.T, toolIDs []string) (*Envelope, erro
e.Secrets = append(e.Secrets, pw)
// add a second one!
if len(toolIDs) <= 2 {
if len(suite.Tools) <= 2 {
pw1, err := getOrMakeSignet(t, nil, false, "test-pw-2")
if err != nil {
return nil, err
@ -422,66 +366,69 @@ func setupEnvelopeAndTrustStore(t *testing.T, toolIDs []string) (*Envelope, erro
passDerPresent = true
// add passderivation requirements later, as it is a bit special
case tools.PurposeKeyExchange:
e.requirements.Add(RecipientAuthentication)
e.suite.Provides.Add(RecipientAuthentication)
case tools.PurposeKeyEncapsulation:
e.requirements.Add(RecipientAuthentication)
e.suite.Provides.Add(RecipientAuthentication)
case tools.PurposeSigning:
e.requirements.Add(SenderAuthentication)
e.suite.Provides.Add(SenderAuthentication)
case tools.PurposeIntegratedCipher:
e.requirements.Add(Confidentiality)
e.requirements.Add(Integrity)
e.suite.Provides.Add(Confidentiality)
e.suite.Provides.Add(Integrity)
case tools.PurposeCipher:
e.requirements.Add(Confidentiality)
e.suite.Provides.Add(Confidentiality)
case tools.PurposeMAC:
e.requirements.Add(Integrity)
e.suite.Provides.Add(Integrity)
}
}
// if invalid: test if toolset is recognized as invalid
// no requirements -> only "meta" tools (kdf, pass derivation)
if e.requirements.Empty() {
if e.suite.Provides.Empty() {
return nil, testInvalidToolset(e, "there are only meta tools in toolset")
}
// recipient auth, but no confidentiality? nope.
if e.requirements.Has(RecipientAuthentication) &&
!e.requirements.Has(Confidentiality) {
if e.suite.Provides.Has(RecipientAuthentication) &&
!e.suite.Provides.Has(Confidentiality) {
return nil, testInvalidToolset(e, "authenticating the recipient without using confidentiality does not make sense")
}
// check if we are missing key derivation - this is only ok if we are merely signing
if !keyDerPresent &&
(len(e.requirements.all) != 1 ||
!e.requirements.Has(SenderAuthentication)) {
(len(e.suite.Provides.all) != 1 ||
!e.suite.Provides.Has(SenderAuthentication)) {
return nil, testInvalidToolset(e, "omitting a key derivation tool is only allowed when merely signing")
}
// check if we have key derivation, but not need it
if keyDerPresent &&
(!e.requirements.Has(Confidentiality) &&
!e.requirements.Has(Integrity)) {
(!e.suite.Provides.Has(Confidentiality) &&
!e.suite.Provides.Has(Integrity)) {
return nil, testInvalidToolset(e, "a key derivation tool was specified, albeit none is needed")
}
// add passderivation here, as to easier handle the other cases
if passDerPresent {
e.requirements.Add(SenderAuthentication)
e.requirements.Add(RecipientAuthentication)
e.suite.Provides.Add(SenderAuthentication)
e.suite.Provides.Add(RecipientAuthentication)
// need Confidentiality for this to make sense
if !e.requirements.Has(Confidentiality) {
if !e.suite.Provides.Has(Confidentiality) {
return nil, testInvalidToolset(e, "using a password without confidentiality does not make sense")
}
}
if e.requirements.Has(Confidentiality) &&
!e.requirements.Has(Integrity) {
if e.suite.Provides.Has(Confidentiality) &&
!e.suite.Provides.Has(Integrity) {
return nil, testInvalidToolset(e, "having confidentiality without integrity does not make sense")
}
// add static key if needed
if !asyncKeyEstablishmentPresent && !passDerPresent && keyDerPresent {
e.suite.Provides.Add(SenderAuthentication)
e.suite.Provides.Add(RecipientAuthentication)
key, err := getOrMakeSignet(t, nil, false, "test-key-1")
if err != nil {
return nil, err
@ -489,7 +436,7 @@ func setupEnvelopeAndTrustStore(t *testing.T, toolIDs []string) (*Envelope, erro
e.Secrets = append(e.Secrets, key)
// add a second one!
if len(toolIDs) <= 2 {
if len(suite.Tools) <= 2 {
key2, err := getOrMakeSignet(t, nil, false, "test-key-2")
if err != nil {
return nil, err

View file

@ -3,10 +3,10 @@ package jess
var (
// must be var in order decrease for testing for better speed
defaultSecurityLevel = 256
defaultSecurityLevel = 128
minimumSecurityLevel = 0
defaultSymmetricKeySize = 32
defaultSymmetricKeySize = 16
minimumSymmetricKeySize = 0
)

View file

@ -9,7 +9,8 @@ import (
type Envelope struct { //nolint:maligned // TODO
Version uint8
Name string
Tools []string
SuiteID string
suite *Suite
// Secret keys and passwords
Secrets []*Signet
@ -26,11 +27,10 @@ type Envelope struct { //nolint:maligned // TODO
// For users, envelopes describe how a letter is closed.
// Therefore Secrets and Senders always refer to private keys and Recipients to public keys in that context.
// These distictions are important in order for the user to easily and confidently distinguish what is going to happen. Think of it as "human security".
// These distinctions are important in order for the user to easily and confidently distinguish what is going to happen. Think of it as "human security".
MinimumSecurityLevel int
No string
requirements *Requirements
// SecurityLevel is the security level of the envelope when it was created
SecurityLevel int
// flag to signify if envelope is used for opening
opening bool
@ -39,10 +39,8 @@ type Envelope struct { //nolint:maligned // TODO
// NewUnconfiguredEnvelope returns an unconfigured, but slightly initialized envelope.
func NewUnconfiguredEnvelope() *Envelope {
e := &Envelope{
Version: 1,
requirements: NewRequirements(),
Version: 1,
}
e.SerializeRequirements()
return e
}
@ -52,14 +50,17 @@ func (e *Envelope) Correspondence(trustStore TrustStore) (*Session, error) {
}
func (e *Envelope) initCorrespondence(trustStore TrustStore, verifying bool) (*Session, error) {
err := e.LoadRequirements()
err := e.LoadSuite()
if err != nil {
return nil, err
}
//nolint:gocritic // TODO: see below
if verifying {
// prep sender signets only
err = e.prepSignets(e.Senders, e.opening, trustStore)
// TODO: prep sender signets only
// TODO: for this to work, newSession needs to only check verification related things
// err = e.prepSignets(e.Senders, e.opening, trustStore)
err = e.PrepareSignets(trustStore)
} else {
// prep all signets
err = e.PrepareSignets(trustStore)
@ -92,82 +93,27 @@ func (e *Envelope) Check(trustStore TrustStore) error {
return err
}
// NoRecipientAuth removes the requirement to authenticate the recipient.
func (e *Envelope) NoRecipientAuth() *Envelope {
if e.requirements == nil {
e.requirements = NewRequirements()
}
e.requirements.Remove(RecipientAuthentication)
return e
// Suite returns the loaded suite.
func (e *Envelope) Suite() *Suite {
return e.suite
}
// NoSenderAuth removes the requirement to authenticate the sender.
func (e *Envelope) NoSenderAuth() *Envelope {
if e.requirements == nil {
e.requirements = NewRequirements()
}
e.requirements.Remove(SenderAuthentication)
e.SerializeRequirements()
return e
}
// NoConfidentiality removes the requirement to provide confidentiality.
func (e *Envelope) NoConfidentiality() *Envelope {
if e.requirements == nil {
e.requirements = NewRequirements()
}
e.requirements.Remove(Confidentiality)
e.SerializeRequirements()
return e
}
// NoIntegrity removes the requirement to provide integrity.
func (e *Envelope) NoIntegrity() *Envelope {
if e.requirements == nil {
e.requirements = NewRequirements()
}
e.requirements.Remove(Integrity)
e.SerializeRequirements()
return e
}
// Unsafe removes all requirements.
func (e *Envelope) Unsafe() *Envelope {
e.requirements = &Requirements{}
e.SerializeRequirements()
return e
}
// Requirements returns the required requirements.
func (e *Envelope) Requirements() *Requirements {
return e.requirements
}
// SetRequirements sets new requirements.
func (e *Envelope) SetRequirements(requirements *Requirements) {
e.requirements = requirements
}
// LoadRequirements loads the required requirements from the struct's exposed negated "No" specification.
func (e *Envelope) LoadRequirements() error {
if e.requirements == nil {
attrs, err := ParseRequirementsFromNoSpec(e.No)
if err != nil {
return nil
// LoadSuite loads the suite specified in the envelope.
func (e *Envelope) LoadSuite() error {
if e.suite == nil {
suite, ok := GetSuite(e.SuiteID)
if !ok {
return fmt.Errorf("suite %s does not exist", e.SuiteID)
}
e.requirements = attrs
e.suite = suite
}
return nil
}
// SerializeRequirements saves the requirement requirements in the struct's exposed negated "No" specification.
func (e *Envelope) SerializeRequirements() {
e.No = e.requirements.SerializeToNoSpec()
// ReloadSuite forces reloading the suite specified in the envelope.
func (e *Envelope) ReloadSuite() error {
e.suite = nil
return e.LoadSuite()
}
// LoopSecrets loops over all secrets of the given scheme.
@ -233,12 +179,16 @@ func (e *Envelope) prepSignets(signets []*Signet, recipients bool, storage Trust
// load from storage
if len(signet.Key) == 0 {
if signet.Scheme == SignetSchemePassword {
err := fillPassword(signet, !recipients, storage, e.MinimumSecurityLevel)
err := fillPassword(signet, !recipients, storage, e.suite.SecurityLevel)
if err != nil {
return fmt.Errorf(`failed to get password for "%s": %s`, signet.ID, err)
}
continue
}
// keys are _always_ signets
if signet.Scheme == SignetSchemeKey {
recipients = false
}
// signet is referrer
if len(signet.ID) == 0 {

View file

@ -15,9 +15,7 @@ import (
- 2: Sending Keys
- 4: Apply Keys
- Version: varint (if Setup Msg)
- Tools: (if Setup Msg)
- Amount: varint
- Names: byte blocks
- SuiteID: byte block (if Setup Msg)
- Keys:
- Amount: varint
- IDs/Values: byte blocks
@ -58,13 +56,8 @@ func (letter *Letter) ToWire() (*container.Container, error) {
// Version: varint (if Setup Msg)
c.AppendNumber(uint64(letter.Version))
// Tools: (if Setup Msg)
// - Amount: varint
// - Names: byte blocks
c.AppendInt(len(letter.Tools))
for _, toolName := range letter.Tools {
c.AppendAsBlock([]byte(toolName))
}
// SuiteID: byte block (if Setup Msg)
c.AppendAsBlock([]byte(letter.SuiteID))
}
if len(letter.Keys) > 0 {
@ -136,21 +129,12 @@ func LetterFromWire(c *container.Container) (*Letter, error) {
}
letter.Version = n
// Tools: (if Setup Msg)
// - Amount: varint
// - Names: byte blocks
n, err = c.GetNextN8()
// SuiteID: byte block (if Setup Msg)
suiteID, err := c.GetNextBlock()
if err != nil {
return nil, err
}
letter.Tools = make([]string, n)
for i := 0; i < len(letter.Tools); i++ {
toolName, err := c.GetNextBlock()
if err != nil {
return nil, err
}
letter.Tools[i] = string(toolName)
}
letter.SuiteID = string(suiteID)
}
if sendingKeys {

View file

@ -17,8 +17,8 @@ import (
// Letter is the data format for encrypted data at rest or in transit.
type Letter struct { //nolint:maligned // TODO
Version uint8 // signed, MAC'd (may not exist when wired)
Tools []string // signed, MAC'd (may not exist when wired)
Version uint8 // signed, MAC'd (may not exist when wired)
SuiteID string // signed, MAC'd (may not exist when wired)
Nonce []byte // signed, MAC'd
Keys []*Seal `json:",omitempty"` // signed, MAC'd
@ -45,18 +45,34 @@ type Seal struct {
}
// Envelope returns an envelope built from the letter, configured for opening it.
func (letter *Letter) Envelope() (*Envelope, error) {
func (letter *Letter) Envelope(requirements *Requirements) (*Envelope, error) {
// basic checks
if letter.Version == 0 {
return nil, fmt.Errorf("letter does not specify version")
}
if len(letter.Tools) == 0 {
return nil, fmt.Errorf("letter does not specify any tools")
if len(letter.SuiteID) == 0 {
return nil, fmt.Errorf("letter does not specify a suite")
}
// create envelope
e := &Envelope{
Version: letter.Version,
Tools: letter.Tools,
requirements: newEmptyRequirements(),
Version: letter.Version,
SuiteID: letter.SuiteID,
}
// get and check suite
err := e.LoadSuite()
if err != nil {
return nil, err
}
// default to full requirements
if requirements == nil {
requirements = NewRequirements()
}
// check suite against requirements
err = e.suite.Provides.CheckComplianceTo(requirements)
if err != nil {
return nil, err
}
for _, seal := range letter.Keys {
@ -91,13 +107,10 @@ func (letter *Letter) Envelope() (*Envelope, error) {
// Open creates a session and opens the letter in one step.
func (letter *Letter) Open(requirements *Requirements, trustStore TrustStore) ([]byte, error) {
e, err := letter.Envelope()
e, err := letter.Envelope(requirements)
if err != nil {
return nil, err
}
if requirements != nil {
e.requirements = requirements
}
s, err := e.Correspondence(trustStore)
if err != nil {
@ -109,13 +122,10 @@ func (letter *Letter) Open(requirements *Requirements, trustStore TrustStore) ([
// Verify creates a session and verifies the letter in one step.
func (letter *Letter) Verify(requirements *Requirements, trustStore TrustStore) error {
e, err := letter.Envelope()
e, err := letter.Envelope(requirements)
if err != nil {
return err
}
if requirements != nil {
e.requirements = requirements
}
s, err := e.initCorrespondence(trustStore, true)
if err != nil {
@ -127,7 +137,7 @@ func (letter *Letter) Verify(requirements *Requirements, trustStore TrustStore)
// WireCorrespondence creates a wire session (communication over a network connection) from a letter.
func (letter *Letter) WireCorrespondence(trustStore TrustStore) (*Session, error) {
e, err := letter.Envelope()
e, err := letter.Envelope(NewRequirements().Remove(SenderAuthentication))
if err != nil {
return nil, err
}
@ -179,7 +189,7 @@ const (
// These IDs MUST NOT CHANGE
fieldIDLetterVersion uint64 = 1 // signed, MAC'd (may not exist when wired)
fieldIDLetterTools uint64 = 2 // signed, MAC'd (may not exist when wired)
fieldIDLetterSuiteID uint64 = 2 // signed, MAC'd (may not exist when wired)
fieldIDLetterNonce uint64 = 3 // signed, MAC'd
fieldIDLetterKeys uint64 = 4 // signed, MAC'd
fieldIDLetterMac uint64 = 5 // signed
@ -199,12 +209,9 @@ func (letter *Letter) compileAssociatedData() []byte {
c.AppendNumber(fieldIDLetterVersion) // append field ID
c.AppendNumber(uint64(letter.Version))
}
if len(letter.Tools) > 0 {
c.AppendNumber(fieldIDLetterTools) // append field ID
c.AppendInt(len(letter.Tools)) // append number of tools
for _, toolID := range letter.Tools {
c.AppendAsBlock([]byte(toolID)) // append field content with length
}
if len(letter.SuiteID) > 0 {
c.AppendNumber(fieldIDLetterSuiteID) // append field ID
c.AppendAsBlock([]byte(letter.SuiteID)) // append field content with length
}
if len(letter.Nonce) > 0 {
c.AppendNumber(fieldIDLetterNonce) // append field ID

View file

@ -11,7 +11,7 @@ import (
func TestSerialization(t *testing.T) {
subject := &Letter{
Version: 1,
Tools: RecommendedNetwork,
SuiteID: SuiteComplete,
Keys: []*Seal{
{ID: "a"},
{ID: "b"},
@ -25,7 +25,7 @@ func TestSerialization(t *testing.T) {
testSerialize(t, subject, true)
subject.Version = 0
subject.Tools = nil
subject.SuiteID = ""
testSerialize(t, subject, true)
subject.ApplyKeys = false

View file

@ -2,6 +2,24 @@ package jess
import "testing"
func init() {
SetPasswordCallbacks(
func(signet *Signet, minSecurityLevel int) error {
return getTestPassword(signet)
},
getTestPassword,
)
}
func getTestPassword(signet *Signet) error {
pwSignet, err := testTrustStore.GetSignet(signet.ID, false)
if err != nil {
return err
}
signet.Key = pwSignet.Key
return nil
}
func TestCalculatePasswordSecurityLevel(t *testing.T) {
// basic weak
testPWSL(t, "asdf", -1)
@ -13,62 +31,62 @@ func TestCalculatePasswordSecurityLevel(t *testing.T) {
testPWSL(t, "aaaaaaaaAAAAAAAA00000000********", -1)
// chars only
testPWSL(t, "AVWHBwmF", 58)
testPWSL(t, "AVWHBwmFGt", 70)
testPWSL(t, "AVWHBwmFGtLM", 81)
testPWSL(t, "AVWHBwmFGtLMGh", 93)
testPWSL(t, "AVWHBwmFGtLMGhYf", 104)
testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQdxs", 195)
testPWSL(t, "AVWHBwmF", 64)
testPWSL(t, "AVWHBwmFGt", 76)
testPWSL(t, "AVWHBwmFGtLM", 87)
testPWSL(t, "AVWHBwmFGtLMGh", 98)
testPWSL(t, "AVWHBwmFGtLMGhYf", 110)
testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQdxs", 201)
// with number
testPWSL(t, "AVWHBwm1", 60)
testPWSL(t, "AVWHBwmFG1", 72)
testPWSL(t, "AVWHBwmFGtL1", 84)
testPWSL(t, "AVWHBwmFGtLMG1", 96)
testPWSL(t, "AVWHBwmFGtLMGhY1", 108)
testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQdx1", 203)
testPWSL(t, "AVWHBwm1", 66)
testPWSL(t, "AVWHBwmFG1", 78)
testPWSL(t, "AVWHBwmFGtL1", 90)
testPWSL(t, "AVWHBwmFGtLMG1", 102)
testPWSL(t, "AVWHBwmFGtLMGhY1", 114)
testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQdx1", 209)
// with number and special
testPWSL(t, "AVWHBw1_", 61)
testPWSL(t, "AVWHBwmF1_", 73)
testPWSL(t, "AVWHBwmFGt1_", 86)
testPWSL(t, "AVWHBwmFGtLM1_", 98)
testPWSL(t, "AVWHBwmFGtLMGh1_", 110)
testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQd1_", 207)
testPWSL(t, "AVWHBw1_", 67)
testPWSL(t, "AVWHBwmF1_", 79)
testPWSL(t, "AVWHBwmFGt1_", 91)
testPWSL(t, "AVWHBwmFGtLM1_", 103)
testPWSL(t, "AVWHBwmFGtLMGh1_", 116)
testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQd1_", 213)
// with number and more special
testPWSL(t, "AVWHBw1*", 65)
testPWSL(t, "AVWHBwmF1*", 78)
testPWSL(t, "AVWHBwmFGt1*", 91)
testPWSL(t, "AVWHBwmFGtLM1*", 104)
testPWSL(t, "AVWHBwmFGtLMGh1*", 117)
testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQd1*", 221)
testPWSL(t, "AVWHBw1*", 70)
testPWSL(t, "AVWHBwmF1*", 83)
testPWSL(t, "AVWHBwmFGt1*", 96)
testPWSL(t, "AVWHBwmFGtLM1*", 109)
testPWSL(t, "AVWHBwmFGtLMGh1*", 122)
testPWSL(t, "AVWHBwmFGtLMGhYfPkcyawfmZXRTQd1*", 226)
// created, strong
// "Schneier scheme"
// source: https://www.schneier.com/blog/archives/2014/03/choosing_secure_1.html
testPWSL(t, "WIw7,mstmsritt...", 116)
testPWSL(t, "Wow...doestcst", 94)
testPWSL(t, "Ltime@go-inag~faaa!", 135)
testPWSL(t, "uTVM,TPw55:utvm,tpwstillsecure", 210)
testPWSL(t, "WIw7,mstmsritt...", 122)
testPWSL(t, "Wow...doestcst", 100)
testPWSL(t, "Ltime@go-inag~faaa!", 140)
testPWSL(t, "uTVM,TPw55:utvm,tpwstillsecure", 216)
// generated, strong
testPWSL(t, "YebGPQuuoxQwyeJMvEWACTLexUUxVBFdHYqqUybBUNfBttCvWQxDdDCdYfgMPCQp", 378)
testPWSL(t, "dpPyXmXpbECn6LWuQDJaitTTJguGfRTqNUxWfoHnBKDHvRhjR2WiQ7iDcuRJNnEd", 394)
testPWSL(t, "WgEKCp8c8{bPrG{Zo(Ms97pKt3EsR9ycz4R=kMjPp^Uafqxsd2ZTFtkfvnoueKJz", 428)
testPWSL(t, "galena-fighter-festival", 127)
testPWSL(t, "impotent-drug-dropout-damage", 152)
testPWSL(t, "artless-newswire-rill-belgium-marplot", 196)
testPWSL(t, "forbade-momenta-spook-sure-devilish-wobbly", 221)
testPWSL(t, "YebGPQuuoxQwyeJMvEWACTLexUUxVBFdHYqqUybBUNfBttCvWQxDdDCdYfgMPCQp", 383)
testPWSL(t, "dpPyXmXpbECn6LWuQDJaitTTJguGfRTqNUxWfoHnBKDHvRhjR2WiQ7iDcuRJNnEd", 400)
testPWSL(t, "WgEKCp8c8{bPrG{Zo(Ms97pKt3EsR9ycz4R=kMjPp^Uafqxsd2ZTFtkfvnoueKJz", 434)
testPWSL(t, "galena-fighter-festival", 132)
testPWSL(t, "impotent-drug-dropout-damage", 157)
testPWSL(t, "artless-newswire-rill-belgium-marplot", 202)
testPWSL(t, "forbade-momenta-spook-sure-devilish-wobbly", 227)
}
func testPWSL(t *testing.T, password string, expectedSecurityLevel int) {
securityLevel := CalculatePasswordSecurityLevel(password, 20000)
securityLevel := CalculatePasswordSecurityLevel(password, 1<<20)
if securityLevel < expectedSecurityLevel {
t.Errorf("password %s (%di): %d - expected at least %d", password, 20000, securityLevel, expectedSecurityLevel)
t.Errorf("password %s (%di): %d - expected at least %d", password, 1<<20, securityLevel, expectedSecurityLevel)
} else {
t.Logf("password %s (%di): %d", password, 20000, securityLevel)
t.Logf("password %s (%di): %d", password, 1<<20, securityLevel)
}
}

View file

@ -81,7 +81,7 @@ func (requirements *Requirements) CheckComplianceTo(requirement *Requirements) e
}
}
if missing != nil {
return fmt.Errorf("missing tools with security requirements: %s", missing.String())
return fmt.Errorf("missing security requirements: %s", missing.String())
}
return nil
}

View file

@ -57,15 +57,34 @@ func (sh *managedHasher) Sum() ([]byte, error) {
}
func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
if len(e.Tools) == 0 {
return nil, errors.New("envelope is missing tools")
if e.suite == nil {
return nil, errors.New("suite not loaded")
}
// create session
s := &Session{
envelope: e,
toolRequirements: newEmptyRequirements(),
}
// check envelope security level
if e.SecurityLevel > 0 {
err := s.checkSecurityLevel(e.SecurityLevel, func() string {
return fmt.Sprintf(`envelope "%s"`, e.Name)
})
if err != nil {
return nil, err
}
}
// check suite security level
err := s.checkSecurityLevel(e.suite.SecurityLevel, func() string {
return fmt.Sprintf(`suite "%s"`, e.suite.ID)
})
if err != nil {
return nil, err
}
// prepare variables
var (
keySourceAvailable bool = false
totalSignetsSeen int
@ -74,13 +93,13 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
)
// tool init loop: start
for i, toolID := range s.envelope.Tools {
for i, toolID := range s.envelope.suite.Tools {
///////////////////////////////////////
// tool init loop: check for duplicates
///////////////////////////////////////
for j, dupeToolID := range s.envelope.Tools {
for j, dupeToolID := range s.envelope.suite.Tools {
if i != j && toolID == dupeToolID {
return nil, fmt.Errorf("cannot use tool %s twice, each tool may be only specified once", toolID)
}
@ -311,7 +330,10 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
}
// key signets
err := e.LoopSecrets(SignetSchemeKey, func(signet *Signet) error {
err = e.LoopSecrets(SignetSchemeKey, func(signet *Signet) error {
s.toolRequirements.Add(SenderAuthentication)
s.toolRequirements.Add(RecipientAuthentication)
totalSignetsSeen++
keySourceAvailable = true
return s.calcAndCheckSecurityLevel(nil, signet)
@ -350,7 +372,7 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
if s.toolRequirements.Empty() {
return nil, errors.New("envelope excludes all security requirements, no meaningful operation possible")
}
err = s.toolRequirements.CheckComplianceTo(s.envelope.requirements)
err = s.toolRequirements.CheckComplianceTo(s.envelope.suite.Provides)
if err != nil {
return nil, err
}
@ -368,7 +390,7 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
}
// check if we are missing a kdf, but need one
if s.kdf == nil && len(s.signers) != len(s.envelope.Tools) {
if s.kdf == nil && len(s.signers) != len(s.envelope.suite.Tools) {
return nil, errors.New("missing a key derivation tool")
}
@ -393,6 +415,15 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
return nil, fmt.Errorf("detected signet or recipient in envelope that is not used by any tool")
}
// check session security level
// while this should never result in an error (because every part was already checked separately) this is used as a precaution to catch errors in future code changes
err = s.checkSecurityLevel(s.SecurityLevel, func() string {
return "current session"
})
if err != nil {
return nil, err
}
return s, nil
}
@ -415,6 +446,9 @@ func (s *Session) calcAndCheckSecurityLevel(logic tools.ToolLogic, signet *Signe
// existence check is done when opening/closing
if len(signet.Key) > 0 {
switch logic.Info().Name {
case "SCRYPT-20":
// TODO: integrate this into the tool interface
calculatedSecurityLevel = CalculatePasswordSecurityLevel(string(signet.Key), 1<<20)
case "PBKDF2-SHA2-256":
// TODO: integrate this into the tool interface
calculatedSecurityLevel = CalculatePasswordSecurityLevel(string(signet.Key), 20000)
@ -451,38 +485,18 @@ func (s *Session) calcAndCheckSecurityLevel(logic tools.ToolLogic, signet *Signe
}
if signet != nil {
// signet based security level checks
switch {
case minimumSecurityLevel > 0:
// check against minimumSecurityLevel
// minimumSecurityLevel overrides other checks
if calculatedSecurityLevel < minimumSecurityLevel {
return fmt.Errorf(`supplied %s signet "%s" with a security level of %d is weaker than the desired security level of %d`, signet.Scheme, signet.ID, calculatedSecurityLevel, minimumSecurityLevel)
}
case s.envelope.MinimumSecurityLevel > 0 && calculatedSecurityLevel < s.envelope.MinimumSecurityLevel:
// check against envelope's minimum security level
return fmt.Errorf(`supplied %s signet "%s" with a security level of %d is weaker than the envelope's minimum security level of %d`, signet.Scheme, signet.ID, calculatedSecurityLevel, s.envelope.MinimumSecurityLevel)
case s.SecurityLevel > 0 && calculatedSecurityLevel < s.SecurityLevel:
// check against toolset's security level
return fmt.Errorf(`supplied %s signet "%s" with a security level of %d is weaker than the toolset's security level of %d`, signet.Scheme, signet.ID, calculatedSecurityLevel, s.SecurityLevel)
}
err = s.checkSecurityLevel(calculatedSecurityLevel, func() string {
return fmt.Sprintf(`supplied %s signet "%s"`, signet.Scheme, signet.ID)
})
} else {
// tool based security level checks
switch {
case minimumSecurityLevel > 0:
// check against minimumSecurityLevel
// minimumSecurityLevel overrides other checks
if calculatedSecurityLevel < minimumSecurityLevel {
return fmt.Errorf(`tool %s with a security level of %d is weaker than the desired security level of %d`, logic.Info().Name, calculatedSecurityLevel, minimumSecurityLevel)
}
case s.envelope.MinimumSecurityLevel > 0 && calculatedSecurityLevel < s.envelope.MinimumSecurityLevel:
// check against envelope's minimum security level
return fmt.Errorf(`tool %s with a security level of %d is weaker than the envelope's minimum security level of %d`, logic.Info().Name, calculatedSecurityLevel, s.envelope.MinimumSecurityLevel)
}
// tool based securty level checks
err = s.checkSecurityLevel(calculatedSecurityLevel, func() string {
return "tool %s" + logic.Info().Name
})
}
if err != nil {
return err
}
// adapt security level of session
@ -499,6 +513,42 @@ func (s *Session) calcAndCheckSecurityLevel(logic tools.ToolLogic, signet *Signe
return nil
}
func (s *Session) checkSecurityLevel(levelToCheck int, subject func() string) error {
switch {
case minimumSecurityLevel > 0:
// check against minimumSecurityLevel
// minimumSecurityLevel overrides other checks
if levelToCheck < minimumSecurityLevel {
return fmt.Errorf(
`%s with a security level of %d is weaker than the desired security level of %d`,
subject(),
levelToCheck,
minimumSecurityLevel,
)
}
case s.envelope.SecurityLevel > 0:
// check against envelope's minimum security level
if levelToCheck < s.envelope.SecurityLevel {
return fmt.Errorf(
`%s with a security level of %d is weaker than the envelope's minimum security level of %d`,
subject(),
levelToCheck,
s.envelope.SecurityLevel,
)
}
case levelToCheck < defaultSecurityLevel:
// check against default security level as fallback
return fmt.Errorf(
`%s with a security level of %d is weaker than the default minimum security level of %d`,
subject(),
levelToCheck,
defaultSecurityLevel,
)
}
return nil
}
// NonceSize returns the nonce size to use for new letters.
func (s *Session) NonceSize() int {
size := s.maxSecurityLevel / 32

View file

@ -220,8 +220,8 @@ func (signet *Signet) StoreKey() error {
// Verify verifies the signature of the signet.
func (signet *Signet) Verify() error {
// FIXME
return errors.New("NIY")
// TODO
return errors.New("signet verification not yet implemented")
}
// Burn destroys all the key material and renders the Signet unusable. This is currently ineffective, see known issues in the project's README.

17
suite.go Normal file
View file

@ -0,0 +1,17 @@
package jess
// Suite status options
const (
SuiteStatusDeprecated uint8 = 0
SuiteStatusPermitted uint8 = 1
SuiteStatusRecommended uint8 = 2
)
// Suite describes a cipher suite - a set of algorithms and the attributes they provide.
type Suite struct {
ID string
Tools []string
Provides *Requirements
SecurityLevel int
Status uint8
}

100
suites.go Normal file
View file

@ -0,0 +1,100 @@
package jess
var (
// lists
suitesMap = make(map[string]*Suite)
suitesList []*Suite
// suite definitions
// SuiteKeyV1 is a cipher suite for encryption with a key.
SuiteKeyV1 = registerSuite(&Suite{
ID: "key_v1",
Tools: []string{"HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements(),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuitePasswordV1 is a cipher suite for encryption with a password.
SuitePasswordV1 = registerSuite(&Suite{
ID: "pw_v1",
Tools: []string{"SCRYPT-20", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements(),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuiteRcptOnlyV1 is a cipher suite for encrypting for someone, but without verifying the sender/source.
SuiteRcptOnlyV1 = registerSuite(&Suite{
ID: "rcpt_v1",
Tools: []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements().Remove(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuiteSignV1 is a cipher suite for signing (no encryption).
SuiteSignV1 = registerSuite(&Suite{
ID: "sign_v1",
Tools: []string{"Ed25519(BLAKE2b-256)"},
Provides: newEmptyRequirements().Add(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuiteCompleteV1 is a cipher suite for both encrypting for someone and signing.
SuiteCompleteV1 = registerSuite(&Suite{
ID: "v1",
Tools: []string{"ECDH-X25519", "Ed25519(BLAKE2b-256)", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements(),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuiteWireV1 is a cipher suite for network communication, including authentication of the server, but not the client.
SuiteWireV1 = registerSuite(&Suite{
ID: "w1",
Tools: []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements().Remove(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// currently recommended suites
// SuiteKey is a a cipher suite for encryption with a key.
SuiteKey = SuiteKeyV1
// SuitePassword is a a cipher suite for encryption with a password.
SuitePassword = SuitePasswordV1
// SuiteRcptOnly is a a cipher suite for encrypting for someone, but without verifying the sender/source.
SuiteRcptOnly = SuiteRcptOnlyV1
// SuiteSign is a a cipher suite for signing (no encryption).
SuiteSign = SuiteSignV1
// SuiteComplete is a a cipher suite for both encrypting for someone and signing.
SuiteComplete = SuiteCompleteV1
// SuiteWire is a a cipher suite for network communication, including authentication of the server, but not the client.
SuiteWire = SuiteWireV1
)
func registerSuite(suite *Suite) (suiteID string) {
// add if not exists
_, ok := suitesMap[suite.ID]
if !ok {
suitesMap[suite.ID] = suite
suitesList = append(suitesList, suite)
}
return suite.ID
}
// GetSuite returns the suite with the given ID.
func GetSuite(suiteID string) (suite *Suite, ok bool) {
suite, ok = suitesMap[suiteID]
return
}
// Suites returns all registered suites as a slice.
func Suites() []*Suite {
return suitesList
}
// SuitesMap returns all registered suites as a map.
func SuitesMap() map[string]*Suite {
return suitesMap
}

474
suites_test.go Normal file
View file

@ -0,0 +1,474 @@
package jess
import (
"errors"
"fmt"
"strings"
"testing"
"github.com/safing/jess/hashtools"
"github.com/safing/jess/tools"
)
func getSuite(t *testing.T, suiteID string) (suite *Suite) {
suite, ok := GetSuite(suiteID)
if !ok {
t.Fatalf("suite %s does not exist", suiteID)
return nil
}
return suite
}
func TestSuites(t *testing.T) {
for _, suite := range Suites() {
err := suiteBullshitCheck(suite)
if err != nil {
t.Errorf("suite %s has incorrect property: %s", suite.ID, err)
continue
}
envelope, err := setupEnvelopeAndTrustStore(t, suite)
if err != nil {
t.Errorf("failed to setup test envelope for suite %s: %s", suite.ID, err)
continue
}
if envelope == nil {
t.Errorf("suite %s has an invalid toolset", suite.ID)
continue
}
session, err := envelope.Correspondence(testTrustStore)
if err != nil {
t.Errorf("failed to init session for suite %s: %s", suite.ID, err)
continue
}
letter, err := session.Close([]byte(testData1))
if err != nil {
tErrorf(t, "suite %s failed to close (1): %s", suite.ID, err)
continue
}
msg, err := letter.ToJSON()
if err != nil {
tErrorf(t, "suite %s failed to json encode (1): %s", suite.ID, err)
continue
}
// test 2: open
letter2, err := LetterFromJSON(msg)
if err != nil {
tErrorf(t, "suite %s failed to json decode (2): %s", suite.ID, err)
continue
}
origData2, err := letter2.Open(envelope.suite.Provides, testTrustStore)
if err != nil {
tErrorf(t, "suite %s failed to open (2): %s", suite.ID, err)
continue
}
if string(origData2) != testData1 {
tErrorf(t, "%v original data mismatch (2): %s", suite.ID, string(origData2))
continue
}
// test 2.1: verify
letter21, err := LetterFromJSON(msg)
if err != nil {
tErrorf(t, "suite %s failed to json decode (2): %s", suite.ID, err)
continue
}
if len(letter21.Signatures) > 0 {
err = letter21.Verify(envelope.suite.Provides, testTrustStore)
if err != nil {
tErrorf(t, "suite %s failed to verify (2): %s", suite.ID, err)
continue
}
}
}
}
func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
// pre checks
if suite.Provides == nil {
return errors.New("provides no requirement attributes")
}
if suite.SecurityLevel == 0 {
return errors.New("does not specify security level")
}
// create session struct for holding information
s := &Session{
envelope: &Envelope{
suite: suite,
},
toolRequirements: newEmptyRequirements(),
}
// check if we are assuming we have a key
assumeKey := strings.Contains(suite.ID, "key")
if assumeKey {
s.toolRequirements.Add(SenderAuthentication)
s.toolRequirements.Add(RecipientAuthentication)
}
// tool check loop: start
for i, toolID := range suite.Tools {
////////////////////////////////////////
// tool check loop: check for duplicates
////////////////////////////////////////
for j, dupeToolID := range suite.Tools {
if i != j && toolID == dupeToolID {
return fmt.Errorf("cannot use tool %s twice, each tool may be only specified once", toolID)
}
}
///////////////////////////////////////
// tool check loop: parse, prep and get
///////////////////////////////////////
var (
hashTool *hashtools.HashTool
hashSumFn func() ([]byte, error)
)
// parse ID for args
var arg string
if strings.Contains(toolID, "(") {
splitted := strings.Split(toolID, "(")
toolID = splitted[0]
arg = strings.Trim(splitted[1], "()")
}
// get tool
tool, err := tools.Get(toolID)
if err != nil {
return fmt.Errorf("the specified tool %s could not be found", toolID)
}
// create logic instance and add to logic and state lists
logic := tool.Factory()
s.all = append(s.all, logic)
if tool.Info.HasOption(tools.OptionHasState) {
s.toolsWithState = append(s.toolsWithState, logic)
}
///////////////////////////////////////////////////////////////
// tool check loop: assign tools to queues and add requirements
///////////////////////////////////////////////////////////////
switch tool.Info.Purpose {
case tools.PurposeKeyDerivation:
if s.kdf != nil {
return fmt.Errorf("cannot use %s, you may only specify one key derivation tool and %s was already specified", tool.Info.Name, s.kdf.Info().Name)
}
s.kdf = logic
case tools.PurposePassDerivation:
if s.passDerivator != nil {
return fmt.Errorf("cannot use %s, you may only specify one password derivation tool and %s was already specified", tool.Info.Name, s.passDerivator.Info().Name)
}
s.passDerivator = logic
s.toolRequirements.Add(SenderAuthentication)
s.toolRequirements.Add(RecipientAuthentication)
case tools.PurposeKeyExchange:
s.keyExchangers = append(s.keyExchangers, logic)
s.toolRequirements.Add(RecipientAuthentication)
case tools.PurposeKeyEncapsulation:
s.keyEncapsulators = append(s.keyEncapsulators, logic)
s.toolRequirements.Add(RecipientAuthentication)
case tools.PurposeSigning:
s.signers = append(s.signers, logic)
s.toolRequirements.Add(SenderAuthentication)
case tools.PurposeIntegratedCipher:
s.integratedCiphers = append(s.integratedCiphers, logic)
s.toolRequirements.Add(Confidentiality)
s.toolRequirements.Add(Integrity)
case tools.PurposeCipher:
s.ciphers = append(s.ciphers, logic)
s.toolRequirements.Add(Confidentiality)
case tools.PurposeMAC:
s.macs = append(s.macs, logic)
s.toolRequirements.Add(Integrity)
}
////////////////////////////////////////////////
// tool check loop: process options, get hashers
////////////////////////////////////////////////
for _, option := range tool.Info.Options {
switch option {
case tools.OptionNeedsManagedHasher:
// get managed hasher list
var managedHashers map[string]*managedHasher
switch tool.Info.Purpose {
case tools.PurposeMAC:
if s.managedMACHashers == nil {
s.managedMACHashers = make(map[string]*managedHasher)
}
managedHashers = s.managedMACHashers
case tools.PurposeSigning:
if s.managedSigningHashers == nil {
s.managedSigningHashers = make(map[string]*managedHasher)
}
managedHashers = s.managedSigningHashers
default:
return fmt.Errorf("only MAC and Signing tools may use managed hashers")
}
// get or assign a new managed hasher
mngdHasher, ok := managedHashers[arg]
if !ok {
// get hashtool
ht, err := hashtools.Get(arg)
if err != nil {
return fmt.Errorf("the specified hashtool for %s(%s) could not be found", toolID, arg)
}
// save to managed hashers
mngdHasher = &managedHasher{
tool: ht,
hash: ht.New(),
}
managedHashers[arg] = mngdHasher
}
hashTool = mngdHasher.tool
hashSumFn = mngdHasher.Sum
case tools.OptionNeedsDedicatedHasher:
hashTool, err = hashtools.Get(arg)
if err != nil {
return fmt.Errorf("the specified hashtool for %s(%s) could not be found", toolID, arg)
}
}
}
///////////////////////////////////
// tool check loop: initialize tool
///////////////////////////////////
// init tool
logic.Init(
tool,
&Helper{
session: s,
info: tool.Info,
},
hashTool,
hashSumFn,
)
//////////////////////////////////////////////////
// tool check loop: calc and check security levels
//////////////////////////////////////////////////
err = s.calcAndCheckSecurityLevel(logic, nil)
if err != nil {
return err
}
} // tool check loop: end
///////////////
// final checks
///////////////
// check requirements requirements
if s.toolRequirements.Empty() {
return errors.New("suite does not provide any security attributes")
}
// check if we have recipient auth without confidentiality
if s.toolRequirements.Has(RecipientAuthentication) &&
!s.toolRequirements.Has(Confidentiality) {
return errors.New("having recipient authentication without confidentiality does not make sense")
}
// check if we have confidentiality without integrity
if s.toolRequirements.Has(Confidentiality) &&
!s.toolRequirements.Has(Integrity) {
return errors.New("having confidentiality without integrity does not make sense")
}
// check if we are missing a kdf, but need one
if s.kdf == nil && len(s.signers) != len(s.envelope.suite.Tools) {
return errors.New("missing a key derivation tool")
}
// check if have a kdf, even if we don't need one
if len(s.integratedCiphers) == 0 &&
len(s.ciphers) == 0 &&
len(s.macs) == 0 &&
s.kdf != nil {
return errors.New("key derivation tool specified, but not needed")
}
/////////////////////////////////////////
// check if values match suite definition
/////////////////////////////////////////
// check if security level matches
if s.SecurityLevel != suite.SecurityLevel {
return fmt.Errorf("suite has incorrect security level: %d (expected %d)", suite.SecurityLevel, s.SecurityLevel)
}
// check if requirements match
if s.toolRequirements.SerializeToNoSpec() != suite.Provides.SerializeToNoSpec() {
return fmt.Errorf(
"suite has incorrect attributes: no %s (expected no %s)",
suite.Provides.SerializeToNoSpec(),
s.toolRequirements.SerializeToNoSpec(),
)
}
///////////////////////////////////////////////////////////
// check if computeSuiteAttributes returns the same results
///////////////////////////////////////////////////////////
computedSuite := computeSuiteAttributes(suite.Tools, assumeKey)
if computedSuite == nil {
return errors.New("internal error: could not compute suite attributes")
}
if suite.SecurityLevel != computedSuite.SecurityLevel {
return fmt.Errorf("internal error: computeSuiteAttributes error: security level: suite=%d computed=%d", suite.SecurityLevel, computedSuite.SecurityLevel)
}
if suite.Provides.SerializeToNoSpec() != computedSuite.Provides.SerializeToNoSpec() {
return fmt.Errorf(
"internal error: computeSuiteAttributes error: attributes: suite=no %s compute=no %s)",
suite.Provides.SerializeToNoSpec(),
computedSuite.Provides.SerializeToNoSpec(),
)
}
return nil
}
func computeSuiteAttributes(toolIDs []string, assumeKey bool) *Suite {
new := &Suite{
Provides: newEmptyRequirements(),
SecurityLevel: 0,
}
// if we have a key
if assumeKey {
new.Provides.Add(SenderAuthentication)
new.Provides.Add(RecipientAuthentication)
}
// check all security levels and collect attributes
for _, toolID := range toolIDs {
///////////////////////////////////////
// tool check loop: parse, prep and get
///////////////////////////////////////
var hashTool *hashtools.HashTool
// parse ID for args
var arg string
if strings.Contains(toolID, "(") {
splitted := strings.Split(toolID, "(")
toolID = splitted[0]
arg = strings.Trim(splitted[1], "()")
}
// get tool
tool, err := tools.Get(toolID)
if err != nil {
return nil
}
// create logic instance and add to logic and state lists
logic := tool.Factory()
//////////////////////////////////////
// tool check loop: collect attributes
//////////////////////////////////////
switch tool.Info.Purpose {
case tools.PurposePassDerivation:
new.Provides.Add(SenderAuthentication)
new.Provides.Add(RecipientAuthentication)
case tools.PurposeKeyExchange:
new.Provides.Add(RecipientAuthentication)
case tools.PurposeKeyEncapsulation:
new.Provides.Add(RecipientAuthentication)
case tools.PurposeSigning:
new.Provides.Add(SenderAuthentication)
case tools.PurposeIntegratedCipher:
new.Provides.Add(Confidentiality)
new.Provides.Add(Integrity)
case tools.PurposeCipher:
new.Provides.Add(Confidentiality)
case tools.PurposeMAC:
new.Provides.Add(Integrity)
}
////////////////////////////////////////////////
// tool check loop: process options, get hashers
////////////////////////////////////////////////
for _, option := range tool.Info.Options {
switch option {
case tools.OptionNeedsManagedHasher,
tools.OptionNeedsDedicatedHasher:
hashTool, err = hashtools.Get(arg)
if err != nil {
return nil
}
}
}
///////////////////////////////////
// tool check loop: initialize tool
///////////////////////////////////
// init tool
logic.Init(
tool,
&Helper{
info: tool.Info,
},
hashTool,
nil,
)
//////////////////////////////////////////
// tool check loop: compute security level
//////////////////////////////////////////
toolSecurityLevel, err := logic.SecurityLevel(nil)
if err != nil {
return nil
}
if new.SecurityLevel == 0 || toolSecurityLevel < new.SecurityLevel {
new.SecurityLevel = toolSecurityLevel
}
}
return new
}

View file

@ -35,6 +35,9 @@ func (oaep *RsaOAEP) EncapsulateKey(key []byte, signet tools.SignetInt) ([]byte,
if !ok {
return nil, tools.ErrInvalidKey
}
if rsaPubKey == nil {
return nil, tools.ErrInvalidKey
}
// check key length: The message must be no longer than the length of the public modulus minus twice the hash length, minus a further 2.
maxMsgSize := rsaPubKey.Size() - (2 * oaep.HashTool().DigestSize) - 2

View file

@ -1,7 +1,12 @@
package jess
import (
"bytes"
"encoding/json"
"testing"
"time"
"github.com/safing/jess/hashtools"
"github.com/safing/jess/tools"
@ -32,7 +37,39 @@ func TestConformity(t *testing.T) {
}
func TestPasswordHashingSpeed(t *testing.T) {
// skip in short tests and when not running comprehensive
if testing.Short() || !runComprehensiveTestsActive {
return
}
// run this test with
// go test -timeout 10m github.com/safing/jess -v -count=1 -ldflags "-X github.com/safing/jess.RunComprehensiveTests=true" -run ^TestPasswordHashingSpeed$
for _, tool := range tools.AsList() {
if tool.Info.Purpose == tools.PurposePassDerivation {
password := []byte(testPassword1)
salt, err := RandomBytes(4)
if err != nil {
t.Fatal(err)
}
start := time.Now()
_, err = tool.StaticLogic.DeriveKeyFromPassword(password, salt)
if err != nil {
t.Fatal(err)
}
t.Logf("%s took %s to derive key from password", tool.Info.Name, time.Since(start))
}
}
}
//nolint:gocognit,gocyclo
func TestSignetHandling(t *testing.T) {
hashTool, err := hashtools.Get("SHA2-256")
if err != nil {
t.Fatal(err)
}
for _, tool := range tools.AsList() {
switch tool.Info.Purpose {
case tools.PurposeKeyExchange,
@ -88,6 +125,128 @@ func TestSignetHandling(t *testing.T) {
t.Fatalf("failed to load %s recipient: %s", tool.Info.Name, err)
}
// store signet
signetJSON, err := json.Marshal(signet)
if err != nil {
t.Fatalf("failed to serialize %s signet: %s", tool.Info.Name, err)
}
// load signet
loadedSignet := &Signet{}
err = json.Unmarshal(signetJSON, loadedSignet)
if err != nil {
t.Fatalf("failed to parse serialized %s signet: %s", tool.Info.Name, err)
}
err = loadedSignet.LoadKey()
if err != nil {
t.Fatalf("failed to load key of %s signet: %s", tool.Info.Name, err)
}
// store rcpt
rcptJSON, err := json.Marshal(rcpt)
if err != nil {
t.Fatalf("failed to serialize %s rcpt: %s", tool.Info.Name, err)
}
// load rcpt
loadedRcpt := &Signet{}
err = json.Unmarshal(rcptJSON, loadedRcpt)
if err != nil {
t.Fatalf("failed to parse serialized %s rcpt: %s", tool.Info.Name, err)
}
err = loadedRcpt.LoadKey()
if err != nil {
t.Fatalf("failed to load key of %s rcpt: %s", tool.Info.Name, err)
}
// load tool
err = loadedSignet.loadTool()
if err != nil {
t.Fatalf("failed to load tool of %s: %s", tool.Info.Name, err)
}
// init tool with hashtool
toolLogic := tool.Factory()
hasher := managedHasher{
tool: hashTool,
hash: hashTool.New(),
}
toolLogic.Init(
tool,
&Helper{info: tool.Info},
hashTool,
hasher.Sum,
)
// do an operation
switch loadedSignet.tool.Info.Purpose {
case tools.PurposeKeyExchange:
// create and generate signet
peerSignet := NewSignetBase(tool)
err := peerSignet.GenerateKey()
if err != nil {
t.Fatalf("failed to generate key with %s peer: %s", tool.Info.Name, err)
}
// transform to recipient
peerRcpt, err := peerSignet.AsRecipient()
if err != nil {
t.Fatalf("failed to get %s peer as recipient: %s", tool.Info.Name, err)
}
sharedSecret1, err := toolLogic.MakeSharedKey(loadedSignet, peerRcpt)
if err != nil {
t.Fatalf("failed to make shared secret 1 with %s: %s", tool.Info.Name, err)
}
sharedSecret2, err := toolLogic.MakeSharedKey(peerSignet, loadedRcpt)
if err != nil {
t.Fatalf("failed to make shared secret 2 with %s: %s", tool.Info.Name, err)
}
if !bytes.Equal(sharedSecret1, sharedSecret2) {
t.Fatalf("shared secrets made with %s do not match, got:\ns1: %v\ns2: %v", tool.Info.Name, sharedSecret1, sharedSecret2)
}
case tools.PurposeKeyEncapsulation:
origKey, err := RandomBytes(16)
if err != nil {
t.Fatalf("failed to generate test key: %s", err)
}
wrappedKey, err := toolLogic.EncapsulateKey(origKey, loadedRcpt)
if err != nil {
t.Fatalf("failed to encapsulate key with %s: %s", tool.Info.Name, err)
}
unwrappedKey, err := toolLogic.UnwrapKey(wrappedKey, loadedSignet)
if err != nil {
t.Fatalf("failed to unwrap key with %s: %s", tool.Info.Name, err)
}
if !bytes.Equal(origKey, unwrappedKey) {
t.Fatalf("original and unwrapped key with %s do not match, got:\norig: %v\nunwrapped: %v", tool.Info.Name, origKey, unwrappedKey)
}
case tools.PurposeSigning:
testData, err := RandomBytes(16)
if err != nil {
t.Fatalf("failed to generate test data: %s", err)
}
_, err = hasher.hash.Write(testData)
if err != nil {
t.Fatalf("failed to write to hash: %s", err)
}
signature, err := toolLogic.Sign(testData, nil, loadedSignet)
if err != nil {
t.Fatalf("failed to sign with %s: %s", tool.Info.Name, err)
}
err = toolLogic.Verify(testData, nil, signature, loadedRcpt)
if err != nil {
t.Fatalf("failed to verify with %s: %s", tool.Info.Name, err)
}
}
}
}

View file

@ -65,9 +65,6 @@ func WriteEnvelopeToFile(envelope *jess.Envelope, filename string) error {
return errInvalidEnvelopeNameChars
}
// serialize requirements
envelope.SerializeRequirements()
// serialize
data, err := dsd.DumpIndent(envelope, dsd.JSON, "\t")
if err != nil {
@ -100,8 +97,8 @@ func LoadEnvelopeFromFile(filename string) (*jess.Envelope, error) {
return nil, err
}
// parse requirements from "No"
err = envelope.LoadRequirements()
// load suite using SuiteID
err = envelope.LoadSuite()
if err != nil {
return nil, err
}