// Copyright (c) 2020, Google Inc. // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. package subprocess // NOTES: // - subprocess_test does not include testing for all subcomponents. It does // include unit tests for the following: // - hashPrimitive (for sha2-256 only) // - blockCipher (for AES) // - drbg (for ctrDRBG) // - All sample data (the valid & invalid strings) comes from calls to acvp as // of 2020-04-02. import ( "encoding/hex" "encoding/json" "fmt" "reflect" "testing" ) var validSHA2_256 = []byte(`{ "vsId" : 182183, "algorithm" : "SHA2-256", "revision" : "1.0", "isSample" : true, "testGroups" : [ { "tgId" : 1, "testType" : "AFT", "tests" : [ { "tcId" : 1, "msg" : "", "len" : 0 }, { "tcId" : 2, "msg" : "", "len" : 0 }, { "tcId" : 3, "msg" : "8E", "len" : 8 }, { "tcId" : 4, "msg" : "7F10", "len" : 16 }, { "tcId" : 5, "msg" : "F4422F", "len" : 24 }, { "tcId" : 6, "msg" : "B3EF9698", "len" : 32 }] }] }`) var callsSHA2_256 = []fakeTransactCall{ fakeTransactCall{cmd: "SHA2-256", expectedNumResults: 1, args: [][]byte{[]byte{}}}, fakeTransactCall{cmd: "SHA2-256", expectedNumResults: 1, args: [][]byte{[]byte{}}}, fakeTransactCall{cmd: "SHA2-256", expectedNumResults: 1, args: [][]byte{fromHex("8E")}}, fakeTransactCall{cmd: "SHA2-256", expectedNumResults: 1, args: [][]byte{fromHex("7F10")}}, fakeTransactCall{cmd: "SHA2-256", expectedNumResults: 1, args: [][]byte{fromHex("F4422F")}}, fakeTransactCall{cmd: "SHA2-256", expectedNumResults: 1, args: [][]byte{fromHex("B3EF9698")}}, } var invalidSHA2_256 = []byte(`{ "vsId" : 180207, "algorithm" : "SHA2-256", "revision" : "1.0", "isSample" : true, "testGroups" : [ { "tgId" : abc, "testType" : "AFT", "tests" : [ { "tcId" : 1, "msg" : "", "len" : 0 }, { "tcId" : 2, "msg" : "", "len" : 0 }] }] }`) var validKDFJSON = []byte(`{ "vsId": 1564, "algorithm": "counterMode", "revision": "1.0", "testGroups": [{ "tgId": 1, "kdfMode": "counter", "macMode": "CMAC-AES128", "counterLocation": "after fixed data", "keyOutLength": 1024, "counterLength": 8, "tests": [{ "tcId": 1, "keyIn": "5DA38931E8D9174BC3279C8942D2DB82", "deferred": false }, { "tcId": 2, "keyIn": "58F5426A40E3D5D2C94F0F97EB30C739", "deferred": false } ] }] }`) var callsKDF = []fakeTransactCall{ fakeTransactCall{cmd: "KDF-counter", expectedNumResults: 3, args: [][]byte{ uint32le(128), // outputBytes []byte("CMAC-AES128"), // macMode []byte("after fixed data"), // counterLocation fromHex("5DA38931E8D9174BC3279C8942D2DB82"), // keyIn uint32le(8), // counterLength }}, fakeTransactCall{cmd: "KDF-counter", expectedNumResults: 3, args: [][]byte{ uint32le(128), // outputBytes []byte("CMAC-AES128"), // macMode []byte("after fixed data"), // counterLocation fromHex("58F5426A40E3D5D2C94F0F97EB30C739"), // keyIn uint32le(8), // counterLength }}, } var invalidKDFJSON = []byte(`{ "vsId": 1564, "algorithm": "counterMode", "revision": "1.0", "testGroups": [{ "tgId": 1, "kdfMode": "counter", "macMode": "CMAC-AES128", "counterLocation": "after fixed data", "keyOutLength": 1024, "counterLength": 8, "tests": [{ "tcId": 1, "keyIn": "5DA38931E8D9174BC3279C8942D2DB82", "deferred": false }, { "tcId": abc, "keyIn": "58F5426A40E3D5D2C94F0F97EB30C739", "deferred": false } ] }] }`) var validACVPAESECB = []byte(`{ "vsId" : 181726, "algorithm" : "ACVP-AES-ECB", "revision" : "1.0", "isSample" : true, "testGroups" : [ { "tgId" : 1, "testType" : "AFT", "direction" : "encrypt", "keyLen" : 128, "tests" : [ { "tcId" : 1, "pt" : "F34481EC3CC627BACD5DC3FB08F273E6", "key" : "00000000000000000000000000000000" }, { "tcId" : 2, "pt" : "9798C4640BAD75C7C3227DB910174E72", "key" : "00000000000000000000000000000000" }] }] }`) var invalidACVPAESECB = []byte(`{ "vsId" : 181726, "algorithm" : "ACVP-AES-ECB", "revision" : "1.0", "isSample" : true, "testGroups" : [ { "tgId" : 1, "testType" : "AFT", "direction" : "encrypt", "keyLen" : 128, "tests" : [ { "tcId" : abc, "pt" : "F34481EC3CC627BACD5DC3FB08F273E6", "key" : "00000000000000000000000000000000" }, { "tcId" : 2, "pt" : "9798C4640BAD75C7C3227DB910174E72", "key" : "00000000000000000000000000000000" }] }] }`) var callsACVPAESECB = []fakeTransactCall{ fakeTransactCall{cmd: "AES/encrypt", expectedNumResults: 1, args: [][]byte{ fromHex("00000000000000000000000000000000"), fromHex("F34481EC3CC627BACD5DC3FB08F273E6"), }}, fakeTransactCall{cmd: "AES/encrypt", expectedNumResults: 1, args: [][]byte{ fromHex("00000000000000000000000000000000"), fromHex("9798C4640BAD75C7C3227DB910174E72"), }}, } var validCTRDRBG = []byte(`{ "vsId" : 181791, "algorithm" : "ctrDRBG", "revision" : "1.0", "isSample" : true, "testGroups" : [ { "tgId" : 1, "testType" : "AFT", "derFunc" : false, "reSeed" : false, "predResistance" : false, "entropyInputLen" : 384, "nonceLen" : 0, "persoStringLen" : 0, "additionalInputLen" : 0, "returnedBitsLen" : 2048, "mode" : "AES-256", "tests" : [ { "tcId" : 1, "entropyInput" : "0D9E8EB273307D95C616C7ACC65669C246265E8A850EDCF36990D8A6F7EC3AEA0A7DDB888EE8D7ECC19EA7830310782C", "nonce" : "", "persoString" : "", "otherInput" : [ { "intendedUse" : "generate", "additionalInput" : "", "entropyInput" : "" }, { "intendedUse" : "generate", "additionalInput" : "", "entropyInput" : "" } ] }] }] }`) var callsCTRDRBG = []fakeTransactCall{ fakeTransactCall{cmd: "ctrDRBG/AES-256", expectedNumResults: 1, args: [][]byte{ fromHex("00010000"), // uint32(256) fromHex("0D9E8EB273307D95C616C7ACC65669C246265E8A850EDCF36990D8A6F7EC3AEA0A7DDB888EE8D7ECC19EA7830310782C"), []byte{}, []byte{}, []byte{}, []byte{}, }}, } var invalidCTRDRBG = []byte(`{ "vsId" : 181791, "algorithm" : "ctrDRBG", "revision" : "1.0", "isSample" : true, "testGroups" : [ { "tgId" : 1, "testType" : "AFT", "derFunc" : false, "reSeed" : false, "predResistance" : false, "entropyInputLen" : 384, "nonceLen" : 0, "persoStringLen" : 0, "additionalInputLen" : 0, "returnedBitsLen" : 2048, "mode" : "AES-256", "tests" : [ { "tcId" : abc, "entropyInput" : "0D9E8EB273307D95C616C7ACC65669C246265E8A850EDCF36990D8A6F7EC3AEA0A7DDB888EE8D7ECC19EA7830310782C", "nonce" : "", "persoString" : "", "otherInput" : [ { "intendedUse" : "generate", "additionalInput" : "", "entropyInput" : "" }, { "intendedUse" : "generate", "additionalInput" : "", "entropyInput" : "" } ] }] }] }`) var validCMACAESJSON = []byte(`{ "vsId": 1, "algorithm": "CMAC-AES", "revision": "1.0", "testGroups": [{ "tgId": 4, "testType": "AFT", "direction": "gen", "keyLen": 128, "msgLen": 2752, "macLen": 64, "tests": [{ "tcId": 25, "key": "E2547E38B28B2C24892C133FF4770688", "message": "89DE09D747FB4B2669B59759A15BAAF068CAF31FD938DFCFFB38ECED53BA91DD659FD91E6CCCFEC5F972B1AD66BF78FE7FE319E58F514362FC75A346C144981B63FD18195A2AD482AF83711C9ADC449F7EAD32EBD5F4DB7EB93348404EAD496B8F4C89AB5FF7ACB2CFEFD96BD0FC9645B6F1F30AB02767ECA8771106DCA47188EE42183121FB9172B8E2133DE084F6CA3924E4BF3638ADA77DAAA6F06A6494E32CBAEFC6C6D0699BB12A425DCFE5974F687B6A71879D42DE08DF018A96429CFA40E32378D35E46A4956C5D7916B6877F353D33075FD4C64F32C3250D74FF070EA358135664CD8C9B82C9454EED75A12EEC758A9E514053533A884560FAC96DDBBA4AEEB8E473F4BFDB8447B22800D7782320D6E2DAC2599111F8CA598D6720CA7C6E4FC5EDC54FC3576460AAD1644B04E1D2C81B93EA49090FDB7E33374C243B2F19177405B94BEC3C69CC24CC686D8F2B01A6B2A350E394" }] }] }`) var callsCMACAES = []fakeTransactCall{ fakeTransactCall{cmd: "CMAC-AES", expectedNumResults: 1, args: [][]byte{ uint32le(64 / 8), // outputBytes fromHex("E2547E38B28B2C24892C133FF4770688"), // key fromHex("89DE09D747FB4B2669B59759A15BAAF068CAF31FD938DFCFFB38ECED53BA91DD659FD91E6CCCFEC5F972B1AD66BF78FE7FE319E58F514362FC75A346C144981B63FD18195A2AD482AF83711C9ADC449F7EAD32EBD5F4DB7EB93348404EAD496B8F4C89AB5FF7ACB2CFEFD96BD0FC9645B6F1F30AB02767ECA8771106DCA47188EE42183121FB9172B8E2133DE084F6CA3924E4BF3638ADA77DAAA6F06A6494E32CBAEFC6C6D0699BB12A425DCFE5974F687B6A71879D42DE08DF018A96429CFA40E32378D35E46A4956C5D7916B6877F353D33075FD4C64F32C3250D74FF070EA358135664CD8C9B82C9454EED75A12EEC758A9E514053533A884560FAC96DDBBA4AEEB8E473F4BFDB8447B22800D7782320D6E2DAC2599111F8CA598D6720CA7C6E4FC5EDC54FC3576460AAD1644B04E1D2C81B93EA49090FDB7E33374C243B2F19177405B94BEC3C69CC24CC686D8F2B01A6B2A350E394"), // msg }}, } var invalidCMACAESJSON = []byte(`{ "vsId": 1, "algorithm": "CMAC-AES", "revision": "1.0", "testGroups": [{ "tgId": 4, "testType": "AFT", "direction": "gen", "keyLen": 128, "msgLen": 2752, "macLen": 64, "tests": [{ "tcId": abc, "key": "E2547E38B28B2C24892C133FF4770688", "message": "89DE09D747FB4B2669B59759A15BAAF068CAF31FD938DFCFFB38ECED53BA91DD659FD91E6CCCFEC5F972B1AD66BF78FE7FE319E58F514362FC75A346C144981B63FD18195A2AD482AF83711C9ADC449F7EAD32EBD5F4DB7EB93348404EAD496B8F4C89AB5FF7ACB2CFEFD96BD0FC9645B6F1F30AB02767ECA8771106DCA47188EE42183121FB9172B8E2133DE084F6CA3924E4BF3638ADA77DAAA6F06A6494E32CBAEFC6C6D0699BB12A425DCFE5974F687B6A71879D42DE08DF018A96429CFA40E32378D35E46A4956C5D7916B6877F353D33075FD4C64F32C3250D74FF070EA358135664CD8C9B82C9454EED75A12EEC758A9E514053533A884560FAC96DDBBA4AEEB8E473F4BFDB8447B22800D7782320D6E2DAC2599111F8CA598D6720CA7C6E4FC5EDC54FC3576460AAD1644B04E1D2C81B93EA49090FDB7E33374C243B2F19177405B94BEC3C69CC24CC686D8F2B01A6B2A350E394" }] }] }`) // fakeTransactable provides a fake to return results that don't go to the ACVP // server. type fakeTransactable struct { calls []fakeTransactCall results []fakeTransactResult } type fakeTransactCall struct { cmd string expectedNumResults int args [][]byte } type fakeTransactResult struct { bytes [][]byte err error } func (f *fakeTransactable) Transact(cmd string, expectedNumResults int, args ...[]byte) ([][]byte, error) { f.calls = append(f.calls, fakeTransactCall{cmd, expectedNumResults, args}) if len(f.results) == 0 { return nil, fmt.Errorf("Transact called but no TransactResults remain") } ret := f.results[0] f.results = f.results[1:] return ret.bytes, ret.err } func newFakeTransactable(numResponses int) *fakeTransactable { ret := new(fakeTransactable) // Add results requested by caller. dummyResult := [][]byte{[]byte("dummy result")} for i := 0; i < numResponses; i++ { ret.results = append(ret.results, fakeTransactResult{bytes: dummyResult, err: nil}) } return ret } // TestPrimitiveParsesJSON verifies that basic JSON parsing with a // small passing case & a single failing case. func TestPrimitives(t *testing.T) { var tests = []struct { algo string p primitive validJSON []byte invalidJSON []byte expectedCalls []fakeTransactCall results []fakeTransactResult }{ { algo: "SHA2-256", p: &hashPrimitive{"SHA2-256", 32}, validJSON: validSHA2_256, invalidJSON: invalidSHA2_256, expectedCalls: callsSHA2_256, }, { algo: "kdf", p: &kdfPrimitive{}, validJSON: validKDFJSON, invalidJSON: invalidKDFJSON, expectedCalls: callsKDF, results: []fakeTransactResult{ {bytes: [][]byte{ fromHex("5DA38931E8D9174BC3279C8942D2DB82"), []byte("data1"), []byte("keyOut1"), }}, {bytes: [][]byte{ fromHex("58F5426A40E3D5D2C94F0F97EB30C739"), []byte("data2"), []byte("keyOut2"), }}, }, }, { algo: "CMAC-AES", p: &keyedMACPrimitive{"CMAC-AES"}, validJSON: validCMACAESJSON, invalidJSON: invalidCMACAESJSON, expectedCalls: callsCMACAES, results: []fakeTransactResult{ {bytes: [][]byte{ fromHex("0102030405060708"), }}, }, }, { algo: "ACVP-AES-ECB", p: &blockCipher{"AES", 16, false}, validJSON: validACVPAESECB, invalidJSON: invalidACVPAESECB, expectedCalls: callsACVPAESECB, }, { algo: "ctrDRBG", p: &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}}, validJSON: validCTRDRBG, invalidJSON: invalidCTRDRBG, expectedCalls: callsCTRDRBG, results: []fakeTransactResult{ fakeTransactResult{bytes: [][]byte{make([]byte, 256)}}, }, }, } for _, test := range tests { transactable := newFakeTransactable(len(test.expectedCalls)) if len(test.results) > 0 { transactable.results = test.results } if _, err := test.p.Process(test.validJSON, transactable); err != nil { t.Errorf("%s: valid input failed unexpectedly: %v", test.algo, err) continue } if len(transactable.calls) != len(test.expectedCalls) { t.Errorf("%s: got %d results, but want %d", test.algo, len(transactable.calls), len(test.expectedCalls)) continue } if !reflect.DeepEqual(transactable.calls, test.expectedCalls) { t.Errorf("%s: got:\n%#v\n\nwant:\n%#v", test.algo, transactable.calls, test.expectedCalls) } if _, err := test.p.Process(test.invalidJSON, transactable); !isJSONSyntaxError(err) { t.Errorf("Test %v with invalid input either passed or failed with the wrong error (%v)", test.algo, err) } } } // isJSONSyntaxError returns true if the error is a json syntax error. func isJSONSyntaxError(err error) bool { _, ok := err.(*json.SyntaxError) return ok } // fromHex wraps hex.DecodeString so it can be used in initializers. Panics on error. func fromHex(s string) []byte { key, err := hex.DecodeString(s) if err != nil { panic(fmt.Sprintf("Failed on hex.DecodeString(%q) with %v", s, err)) } return key }