// Copyright 2018 by David A. Golden. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 package scram import ( "encoding/json" "fmt" "io/ioutil" "path/filepath" "strings" ) type TestCase struct { Label string Digest string User string Pass string AuthzID string SkipSASLprep bool Salt64 string Iters int ClientNonce string ServerNonce string Valid bool Steps []string } type testStep struct { Input string Expect string IsError bool } func getHGF(s string) (HashGeneratorFcn, error) { switch s { case "SHA-1": return SHA1, nil case "SHA-256": return SHA256, nil case "SHA-512": return SHA512, nil default: panic(fmt.Sprintf("Unknown hash function '%s'", s)) } } func decodeFile(s string) (TestCase, error) { var tc TestCase data, err := ioutil.ReadFile(s) if err != nil { return tc, err } err = json.Unmarshal(data, &tc) if err != nil { return tc, fmt.Errorf("error unmarshaling '%s': %v", s, err) } return tc, nil } func getTestFiles(dir string) ([]string, error) { subdir := filepath.Join("testdata", dir) files, err := ioutil.ReadDir(subdir) if err != nil { return nil, err } filenames := make([]string, len(files)) for i, v := range files { filenames[i] = filepath.Join(subdir, v.Name()) } return filenames, nil } func getTestData(dirs ...string) ([]TestCase, error) { var err error filenames := make([]string, 0) for _, v := range dirs { names, err := getTestFiles(v) if err != nil { return nil, err } filenames = append(filenames, names...) } cases := make([]TestCase, len(filenames)) for i, v := range filenames { cases[i], err = decodeFile(v) if err != nil { return nil, err } } return cases, nil } // Even steps are client messages; odd steps are server responses. func clientSteps(c TestCase) []testStep { n := len(c.Steps) // Test case requires at least two steps: the first client step // (which cannot fail) and the first server response -- after which // an error would prevent further client steps. if n < 2 { panic("Incomplete conversation for this test case") } // First step needs empty input. steps := []testStep{{Input: "", Expect: c.Steps[0]}} // From i==1 until end, construct conversations from pairs of steps. We // know that (n >= 2). If the last pair is incomplete (no client Expect) // that indicates error. last := n - 1 for i := 1; i <= last; i += 2 { steps = append(steps, assembleStep(c, i, last)) } return steps } // Even steps are client messages; odd steps are server responses. func serverSteps(c TestCase) []testStep { n := len(c.Steps) // Test case requires at least one step: the first client step // after which an error would prevent further server steps. if n == 0 { panic("Incomplete conversation for this test case") } steps := make([]testStep, 0, 1) // From i==0 until end, construct conversations from pairs of steps. We // know that (n >= 1). If the last pair is incomplete (no server Expect) // that indicates error. last := n - 1 for i := 0; i < last; i += 2 { ts := assembleStep(c, i, last) steps = append(steps, ts) } return steps } func assembleStep(c TestCase, i int, last int) testStep { ts := testStep{Input: c.Steps[i]} if i == last { ts.IsError = true } else { ts.Expect = c.Steps[i+1] if strings.HasPrefix(ts.Expect, "e=") { ts.IsError = true } } return ts }