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) { t.Helper() suite, ok := GetSuite(suiteID) if !ok { t.Fatalf("suite %s does not exist", suiteID) return nil } return suite } func TestSuites(t *testing.T) { t.Parallel() 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:maintidx // 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(Integrity) 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 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 { newSuite := &Suite{ Provides: newEmptyRequirements(), SecurityLevel: 0, } // if we have a key if assumeKey { newSuite.Provides.Add(SenderAuthentication) newSuite.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: newSuite.Provides.Add(SenderAuthentication) newSuite.Provides.Add(RecipientAuthentication) case tools.PurposeKeyExchange: newSuite.Provides.Add(RecipientAuthentication) case tools.PurposeKeyEncapsulation: newSuite.Provides.Add(RecipientAuthentication) case tools.PurposeSigning: newSuite.Provides.Add(Integrity) newSuite.Provides.Add(SenderAuthentication) case tools.PurposeIntegratedCipher: newSuite.Provides.Add(Confidentiality) newSuite.Provides.Add(Integrity) case tools.PurposeCipher: newSuite.Provides.Add(Confidentiality) case tools.PurposeMAC: newSuite.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 newSuite.SecurityLevel == 0 || toolSecurityLevel < newSuite.SecurityLevel { newSuite.SecurityLevel = toolSecurityLevel } } return newSuite }