From 8162aa3beaa2227b3a357118c73e845810296568 Mon Sep 17 00:00:00 2001 From: Kevin Gilpin Date: Mon, 1 Aug 2022 14:30:07 -0400 Subject: [PATCH 1/7] refactor: Move Ruby fixture code --- .../fixtures/ruby/circular_dependency/Gemfile | 4 - .../ruby/circular_dependency/appmap.yml | 5 - .../lib/command/command.rb | 12 - .../circular_dependency/test/command_test.rb | 9 - .../minitest/Command_command.appmap.json | 210 ------------------ .../.ruby-version | 0 .../test/fixtures/ruby/fixture/Gemfile | 6 + .../Gemfile.lock | 38 ++-- .../test/fixtures/ruby/fixture/appmap.yml | 3 + .../ruby/fixture/lib/circular/command.rb | 14 ++ .../lib => fixture/lib/circular}/pkg_a/a.rb | 0 .../lib => fixture/lib/circular}/pkg_b/b.rb | 0 .../ruby/fixture/test/circular_test.rb | 9 + .../test/scanner/circularDependency.spec.ts | 4 +- .../scanner/illegalPackageDependency.spec.ts | 12 +- 15 files changed, 63 insertions(+), 263 deletions(-) delete mode 100644 packages/scanner/test/fixtures/ruby/circular_dependency/Gemfile delete mode 100644 packages/scanner/test/fixtures/ruby/circular_dependency/appmap.yml delete mode 100644 packages/scanner/test/fixtures/ruby/circular_dependency/lib/command/command.rb delete mode 100644 packages/scanner/test/fixtures/ruby/circular_dependency/test/command_test.rb delete mode 100644 packages/scanner/test/fixtures/ruby/circular_dependency/tmp/appmap/minitest/Command_command.appmap.json rename packages/scanner/test/fixtures/ruby/{circular_dependency => fixture}/.ruby-version (100%) create mode 100644 packages/scanner/test/fixtures/ruby/fixture/Gemfile rename packages/scanner/test/fixtures/ruby/{circular_dependency => fixture}/Gemfile.lock (55%) create mode 100644 packages/scanner/test/fixtures/ruby/fixture/appmap.yml create mode 100644 packages/scanner/test/fixtures/ruby/fixture/lib/circular/command.rb rename packages/scanner/test/fixtures/ruby/{circular_dependency/lib => fixture/lib/circular}/pkg_a/a.rb (100%) rename packages/scanner/test/fixtures/ruby/{circular_dependency/lib => fixture/lib/circular}/pkg_b/b.rb (100%) create mode 100644 packages/scanner/test/fixtures/ruby/fixture/test/circular_test.rb diff --git a/packages/scanner/test/fixtures/ruby/circular_dependency/Gemfile b/packages/scanner/test/fixtures/ruby/circular_dependency/Gemfile deleted file mode 100644 index 4279f2f2ce..0000000000 --- a/packages/scanner/test/fixtures/ruby/circular_dependency/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source "https://rubygems.org" - -gem 'appmap' -gem 'minitest' \ No newline at end of file diff --git a/packages/scanner/test/fixtures/ruby/circular_dependency/appmap.yml b/packages/scanner/test/fixtures/ruby/circular_dependency/appmap.yml deleted file mode 100644 index 152c4557af..0000000000 --- a/packages/scanner/test/fixtures/ruby/circular_dependency/appmap.yml +++ /dev/null @@ -1,5 +0,0 @@ -name: circular_dependency -packages: -- path: lib/command -- path: lib/pkg_a -- path: lib/pkg_b diff --git a/packages/scanner/test/fixtures/ruby/circular_dependency/lib/command/command.rb b/packages/scanner/test/fixtures/ruby/circular_dependency/lib/command/command.rb deleted file mode 100644 index 1870ba374f..0000000000 --- a/packages/scanner/test/fixtures/ruby/circular_dependency/lib/command/command.rb +++ /dev/null @@ -1,12 +0,0 @@ -require_relative '../pkg_a/a' - -class Command - # @label command - def self.invoke - PkgA::A.new.invoke - end -end - -if $PROGRAM_NAME == __FILE__ - puts ::Command.invoke -end diff --git a/packages/scanner/test/fixtures/ruby/circular_dependency/test/command_test.rb b/packages/scanner/test/fixtures/ruby/circular_dependency/test/command_test.rb deleted file mode 100644 index e3b523f53c..0000000000 --- a/packages/scanner/test/fixtures/ruby/circular_dependency/test/command_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'minitest/autorun' -require 'appmap/minitest' -require_relative '../lib/command/command' - -class CommandTest < Minitest::Test - def test_command - assert_equal Command.invoke, 'cycle encountered' - end -end diff --git a/packages/scanner/test/fixtures/ruby/circular_dependency/tmp/appmap/minitest/Command_command.appmap.json b/packages/scanner/test/fixtures/ruby/circular_dependency/tmp/appmap/minitest/Command_command.appmap.json deleted file mode 100644 index d6232575c8..0000000000 --- a/packages/scanner/test/fixtures/ruby/circular_dependency/tmp/appmap/minitest/Command_command.appmap.json +++ /dev/null @@ -1,210 +0,0 @@ -{ - "version": "1.5.1", - "metadata": { - "app": "circular_dependency", - "language": { "name": "ruby", "engine": "ruby", "version": "3.0.1" }, - "client": { - "name": "appmap", - "url": "https://github.com/applandinc/appmap-ruby", - "version": "0.68.2" - }, - "git": { - "repository": "git@github.com:applandinc/scanner", - "branch": "feat/circular-dependency", - "commit": "fe01f3d94b2a26474dffb645d273dec12a12623a", - "status": [ - "M ../../../../.prettierrc.cjs", - "D ../../../../src/algorithms/dataStructures/linked-list/__test__/LinkedList.test.js", - "D ../../../../src/algorithms/dataStructures/linked-list/__test__/LinkedListNode.test.js", - "M ../../../../src/algorithms/graph/detect-cycle/index.ts", - "M ../../../../src/scanner/circularDependency.ts", - "M ../../../../src/scanner/tooManyJoins.ts", - "M ../../../../src/scanner/tooManyUpdates.ts", - "M ../../../algorithms/dataStructures/graph/GraphEdge.test.ts", - "M ../../../algorithms/dataStructures/graph/GraphVertex.test.ts", - "M ../../../algorithms/detectDirectedCycle.test.ts", - "D ../../../graph/Graph.test.js", - "M ../../../scanner.spec.ts", - "?? ../../../../scan.log", - "?? ../" - ], - "git_last_annotated_tag": null, - "git_last_tag": "v1.24.1", - "git_commits_since_last_annotated_tag": null, - "git_commits_since_last_tag": 0 - }, - "name": "Command command", - "source_location": "test/command_test.rb:6", - "frameworks": [{ "name": "minitest", "version": "5.14.4" }], - "recorder": { "name": "minitest" }, - "test_status": "succeeded" - }, - "classMap": [ - { - "name": "lib/command", - "type": "package", - "children": [ - { - "name": "Command", - "type": "class", - "children": [ - { - "name": "invoke", - "type": "function", - "location": "lib/command/command.rb:5", - "static": true - } - ] - } - ] - }, - { - "name": "lib/pkg_a", - "type": "package", - "children": [ - { - "name": "PkgA", - "type": "class", - "children": [ - { - "name": "A", - "type": "class", - "children": [ - { - "name": "invoke", - "type": "function", - "location": "lib/pkg_a/a.rb:5", - "static": false - }, - { - "name": "cycle", - "type": "function", - "location": "lib/pkg_a/a.rb:9", - "static": false - } - ] - } - ] - } - ] - }, - { - "name": "lib/pkg_b", - "type": "package", - "children": [ - { - "name": "PkgB", - "type": "class", - "children": [ - { - "name": "B", - "type": "class", - "children": [ - { - "name": "invoke", - "type": "function", - "location": "lib/pkg_b/b.rb:5", - "static": false - } - ] - } - ] - } - ] - } - ], - "events": [ - { - "id": 1, - "event": "call", - "thread_id": 1820, - "defined_class": "Command", - "method_id": "invoke", - "path": "lib/command/command.rb", - "lineno": 5, - "static": true, - "parameters": [], - "receiver": { "class": "Class", "object_id": 1800, "value": "Command" } - }, - { - "id": 2, - "event": "call", - "thread_id": 1820, - "defined_class": "PkgA::A", - "method_id": "invoke", - "path": "lib/pkg_a/a.rb", - "lineno": 5, - "static": false, - "parameters": [], - "receiver": { - "class": "PkgA::A", - "object_id": 1840, - "value": "#" - } - }, - { - "id": 3, - "event": "call", - "thread_id": 1820, - "defined_class": "PkgB::B", - "method_id": "invoke", - "path": "lib/pkg_b/b.rb", - "lineno": 5, - "static": false, - "parameters": [], - "receiver": { - "class": "PkgB::B", - "object_id": 1860, - "value": "#" - } - }, - { - "id": 4, - "event": "call", - "thread_id": 1820, - "defined_class": "PkgA::A", - "method_id": "cycle", - "path": "lib/pkg_a/a.rb", - "lineno": 9, - "static": false, - "parameters": [], - "receiver": { - "class": "PkgA::A", - "object_id": 1880, - "value": "#" - } - }, - { - "id": 5, - "event": "return", - "thread_id": 1820, - "parent_id": 4, - "elapsed": 4.0e-6, - "return_value": { "class": "String", "value": "cycle encountered", "object_id": 1900 } - }, - { - "id": 6, - "event": "return", - "thread_id": 1820, - "parent_id": 3, - "elapsed": 8.1e-5, - "return_value": { "class": "String", "value": "cycle encountered", "object_id": 1900 } - }, - { - "id": 7, - "event": "return", - "thread_id": 1820, - "parent_id": 2, - "elapsed": 0.000141, - "return_value": { "class": "String", "value": "cycle encountered", "object_id": 1900 } - }, - { - "id": 8, - "event": "return", - "thread_id": 1820, - "parent_id": 1, - "elapsed": 0.000229, - "return_value": { "class": "String", "value": "cycle encountered", "object_id": 1900 } - } - ] -} diff --git a/packages/scanner/test/fixtures/ruby/circular_dependency/.ruby-version b/packages/scanner/test/fixtures/ruby/fixture/.ruby-version similarity index 100% rename from packages/scanner/test/fixtures/ruby/circular_dependency/.ruby-version rename to packages/scanner/test/fixtures/ruby/fixture/.ruby-version diff --git a/packages/scanner/test/fixtures/ruby/fixture/Gemfile b/packages/scanner/test/fixtures/ruby/fixture/Gemfile new file mode 100644 index 0000000000..ad7f77b468 --- /dev/null +++ b/packages/scanner/test/fixtures/ruby/fixture/Gemfile @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem 'byebug' +gem 'rake' +gem 'appmap', github: 'applandinc/appmap-ruby', branch: 'master' +gem 'minitest' diff --git a/packages/scanner/test/fixtures/ruby/circular_dependency/Gemfile.lock b/packages/scanner/test/fixtures/ruby/fixture/Gemfile.lock similarity index 55% rename from packages/scanner/test/fixtures/ruby/circular_dependency/Gemfile.lock rename to packages/scanner/test/fixtures/ruby/fixture/Gemfile.lock index 022a007e1f..e32e7d45be 100644 --- a/packages/scanner/test/fixtures/ruby/circular_dependency/Gemfile.lock +++ b/packages/scanner/test/fixtures/ruby/fixture/Gemfile.lock @@ -1,39 +1,47 @@ +GIT + remote: https://github.com/applandinc/appmap-ruby.git + revision: cda70741cdd594f92863d2c32842c9627af38506 + branch: master + specs: + appmap (0.83.4) + activesupport + method_source + rack + reverse_markdown + GEM remote: https://rubygems.org/ specs: - activesupport (6.1.4.1) + activesupport (7.0.3.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) - appmap (0.68.2) - activesupport - method_source - rack - reverse_markdown - concurrent-ruby (1.1.9) - i18n (1.8.11) + byebug (11.1.3) + concurrent-ruby (1.1.10) + i18n (1.12.0) concurrent-ruby (~> 1.0) method_source (1.0.0) - minitest (5.14.4) - nokogiri (1.12.5-x86_64-darwin) + minitest (5.16.2) + nokogiri (1.13.8-x86_64-darwin) racc (~> 1.4) racc (1.6.0) - rack (2.2.3) + rack (2.2.4) + rake (13.0.6) reverse_markdown (2.1.1) nokogiri - tzinfo (2.0.4) + tzinfo (2.0.5) concurrent-ruby (~> 1.0) - zeitwerk (2.5.1) PLATFORMS x86_64-darwin-17 x86_64-darwin-20 DEPENDENCIES - appmap + appmap! + byebug minitest + rake BUNDLED WITH 2.2.30 diff --git a/packages/scanner/test/fixtures/ruby/fixture/appmap.yml b/packages/scanner/test/fixtures/ruby/fixture/appmap.yml new file mode 100644 index 0000000000..70fdd22af1 --- /dev/null +++ b/packages/scanner/test/fixtures/ruby/fixture/appmap.yml @@ -0,0 +1,3 @@ +name: scanner/ruby-fixture +packages: + - path: lib diff --git a/packages/scanner/test/fixtures/ruby/fixture/lib/circular/command.rb b/packages/scanner/test/fixtures/ruby/fixture/lib/circular/command.rb new file mode 100644 index 0000000000..7160224b0b --- /dev/null +++ b/packages/scanner/test/fixtures/ruby/fixture/lib/circular/command.rb @@ -0,0 +1,14 @@ +require_relative './pkg_a/a' + +module Circular + class Command + # @label command + def self.invoke + PkgA::A.new.invoke + end + end +end + +if $PROGRAM_NAME == __FILE__ + puts Circular::Command.invoke +end diff --git a/packages/scanner/test/fixtures/ruby/circular_dependency/lib/pkg_a/a.rb b/packages/scanner/test/fixtures/ruby/fixture/lib/circular/pkg_a/a.rb similarity index 100% rename from packages/scanner/test/fixtures/ruby/circular_dependency/lib/pkg_a/a.rb rename to packages/scanner/test/fixtures/ruby/fixture/lib/circular/pkg_a/a.rb diff --git a/packages/scanner/test/fixtures/ruby/circular_dependency/lib/pkg_b/b.rb b/packages/scanner/test/fixtures/ruby/fixture/lib/circular/pkg_b/b.rb similarity index 100% rename from packages/scanner/test/fixtures/ruby/circular_dependency/lib/pkg_b/b.rb rename to packages/scanner/test/fixtures/ruby/fixture/lib/circular/pkg_b/b.rb diff --git a/packages/scanner/test/fixtures/ruby/fixture/test/circular_test.rb b/packages/scanner/test/fixtures/ruby/fixture/test/circular_test.rb new file mode 100644 index 0000000000..91fd4c3045 --- /dev/null +++ b/packages/scanner/test/fixtures/ruby/fixture/test/circular_test.rb @@ -0,0 +1,9 @@ +require 'minitest/autorun' +require 'appmap/minitest' +require_relative '../lib/circular/command' + +class CircularTest < Minitest::Test + def test_cycle + assert_equal Circular::Command.invoke, 'cycle encountered' + end +end diff --git a/packages/scanner/test/scanner/circularDependency.spec.ts b/packages/scanner/test/scanner/circularDependency.spec.ts index a23ceb09ec..8b3c080021 100644 --- a/packages/scanner/test/scanner/circularDependency.spec.ts +++ b/packages/scanner/test/scanner/circularDependency.spec.ts @@ -2,7 +2,7 @@ import Check from '../../src/check'; import rule from '../../src/rules/circularDependency'; import { scan } from '../util'; -const appMapFileName = 'ruby/circular_dependency/tmp/appmap/minitest/Command_command.appmap.json'; +const appMapFileName = 'ruby/fixture/tmp/appmap/minitest/Circular_cycle.appmap.json'; const detectCycles = async (check: Check) => { return scan(check, appMapFileName); }; @@ -18,7 +18,7 @@ describe('circular dependency', () => { expect(finding.ruleId).toEqual('circular-dependency'); expect(finding.event.id).toEqual(2); expect(finding.message).toEqual( - `Cycle in package dependency graph: lib/pkg_a -> lib/pkg_b -> lib/pkg_a` + `Cycle in package dependency graph: lib/circular/pkg_a -> lib/circular/pkg_b -> lib/circular/pkg_a` ); expect(finding.relatedEvents!.map((e) => e.id)).toEqual([2, 3, 4]); }); diff --git a/packages/scanner/test/scanner/illegalPackageDependency.spec.ts b/packages/scanner/test/scanner/illegalPackageDependency.spec.ts index f3ac017d43..b572c3728c 100644 --- a/packages/scanner/test/scanner/illegalPackageDependency.spec.ts +++ b/packages/scanner/test/scanner/illegalPackageDependency.spec.ts @@ -7,13 +7,13 @@ import { scan } from '../util'; it('illegal package dependency', async () => { const options = new rule.Options(); options.callerPackages = [ - { equal: 'lib/pkg_a' } as MatchPatternConfig, - { equal: 'lib/command' } as MatchPatternConfig, + { equal: 'lib/circular/pkg_a' } as MatchPatternConfig, + { equal: 'lib/circular' } as MatchPatternConfig, ]; - options.calleePackages = [{ equal: 'lib/pkg_b' } as MatchPatternConfig]; + options.calleePackages = [{ equal: 'lib/circular/pkg_b' } as MatchPatternConfig]; const { appMap, findings } = await scan( new Check(rule, options), - 'ruby/circular_dependency/tmp/appmap/minitest/Command_command.appmap.json' + 'ruby/fixture/tmp/appmap/minitest/Circular_cycle.appmap.json' ); expect(findings).toHaveLength(1); const finding = findings[0]; @@ -35,11 +35,11 @@ stack[2].event_type=function stack[2].id=PkgA::A#invoke stack[2].raises_exception=false stack[3].event_type=function -stack[3].id=Command.invoke +stack[3].id=Circular::Command.invoke stack[3].raises_exception=false`); expect(finding.ruleId).toEqual('illegal-package-dependency'); expect(finding.event.id).toEqual(4); expect(finding.message).toEqual( - `Code object lib/pkg_a/PkgA::A#cycle was invoked from lib/pkg_b, not from lib/pkg_a or lib/command` + `Code object lib/circular/pkg_a/PkgA::A#cycle was invoked from lib/circular/pkg_b, not from lib/circular/pkg_a or lib/circular` ); }); From fe240a6ce18853f7905f696031dfa8675d7b4907 Mon Sep 17 00:00:00 2001 From: Kevin Gilpin Date: Mon, 1 Aug 2022 14:30:50 -0400 Subject: [PATCH 2/7] test: Add Ruby crypto test program --- .../ruby/fixture/lib/crypt/command.rb | 32 +++++++++++++++++++ .../fixtures/ruby/fixture/test/crypt_test.rb | 15 +++++++++ 2 files changed, 47 insertions(+) create mode 100644 packages/scanner/test/fixtures/ruby/fixture/lib/crypt/command.rb create mode 100644 packages/scanner/test/fixtures/ruby/fixture/test/crypt_test.rb diff --git a/packages/scanner/test/fixtures/ruby/fixture/lib/crypt/command.rb b/packages/scanner/test/fixtures/ruby/fixture/lib/crypt/command.rb new file mode 100644 index 0000000000..ec1093d94b --- /dev/null +++ b/packages/scanner/test/fixtures/ruby/fixture/lib/crypt/command.rb @@ -0,0 +1,32 @@ +module Crypt + class Command + # @label command + def self.encrypt_aes_128_cbc + data = "Very, very confidential data" + cipher = OpenSSL::Cipher.new('aes-128-cbc') + cipher.encrypt + key = cipher.random_key + iv = cipher.random_iv + + encrypted = cipher.update(data) + cipher.final + end + + # @label command + def self.encrypt_aes_256_gcm(record_id) + data = "Very, very confidential data" + cipher = OpenSSL::Cipher.new('aes-256-gcm') + cipher.encrypt + key = cipher.random_key + iv = cipher.random_iv + cipher.auth_data = record_id.to_s + + encrypted = cipher.update(data) + cipher.final + tag = cipher.auth_tag + encrypted + end + end +end + +if $PROGRAM_NAME == __FILE__ + puts Crypt::Command.encrypt_aes_128_cbc +end diff --git a/packages/scanner/test/fixtures/ruby/fixture/test/crypt_test.rb b/packages/scanner/test/fixtures/ruby/fixture/test/crypt_test.rb new file mode 100644 index 0000000000..e0ee561b35 --- /dev/null +++ b/packages/scanner/test/fixtures/ruby/fixture/test/crypt_test.rb @@ -0,0 +1,15 @@ +require 'minitest/autorun' +require 'appmap/minitest' +require_relative '../lib/crypt/command' + +class CryptTest < Minitest::Test + def test_crypt_aes_128_cbc + ciphertext = Crypt::Command.encrypt_aes_128_cbc + assert_equal 32, ciphertext.length + end + + def test_crypt_aes_256_gcm + ciphertext = Crypt::Command.encrypt_aes_256_gcm(:record_id) + assert_equal 28, ciphertext.length + end +end From 251586360a98eee7392c8c331cea6b7038606a32 Mon Sep 17 00:00:00 2001 From: Kevin Gilpin Date: Mon, 1 Aug 2022 14:31:23 -0400 Subject: [PATCH 3/7] feat: Check for unauthenticated encryption --- packages/scanner/doc/labels/crypto.encrypt.md | 7 + .../doc/labels/crypto.set_auth_data.md | 7 + .../doc/rules/unauthenticated-encryption.md | 14 + .../unauthenticated-encryption/metadata.ts | 12 + .../rules/unauthenticated-encryption/rule.ts | 26 ++ .../minitest/Circular_cycle.appmap.json | 1 + .../Crypt_crypt_aes_128_cbc.appmap.json | 361 ++++++++++++++++ .../Crypt_crypt_aes_256_gcm.appmap.json | 401 ++++++++++++++++++ .../scanner/unauthenticatedEncryption.spec.ts | 27 ++ 9 files changed, 856 insertions(+) create mode 100644 packages/scanner/doc/labels/crypto.encrypt.md create mode 100644 packages/scanner/doc/labels/crypto.set_auth_data.md create mode 100644 packages/scanner/doc/rules/unauthenticated-encryption.md create mode 100644 packages/scanner/src/rules/unauthenticated-encryption/metadata.ts create mode 100644 packages/scanner/src/rules/unauthenticated-encryption/rule.ts create mode 100644 packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Circular_cycle.appmap.json create mode 100644 packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_128_cbc.appmap.json create mode 100644 packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_256_gcm.appmap.json create mode 100644 packages/scanner/test/scanner/unauthenticatedEncryption.spec.ts diff --git a/packages/scanner/doc/labels/crypto.encrypt.md b/packages/scanner/doc/labels/crypto.encrypt.md new file mode 100644 index 0000000000..547151020d --- /dev/null +++ b/packages/scanner/doc/labels/crypto.encrypt.md @@ -0,0 +1,7 @@ +--- +name: crypto.encrypt +rules: + - unauthenticated-encryption +--- + +A function that performs encryption. diff --git a/packages/scanner/doc/labels/crypto.set_auth_data.md b/packages/scanner/doc/labels/crypto.set_auth_data.md new file mode 100644 index 0000000000..d59ff5b4a3 --- /dev/null +++ b/packages/scanner/doc/labels/crypto.set_auth_data.md @@ -0,0 +1,7 @@ +--- +name: crypto.set_auth_data +rules: + - unauthenticated-encryption +--- + +A function that sets authenticated data for an encryption operation. diff --git a/packages/scanner/doc/rules/unauthenticated-encryption.md b/packages/scanner/doc/rules/unauthenticated-encryption.md new file mode 100644 index 0000000000..c6233083e7 --- /dev/null +++ b/packages/scanner/doc/rules/unauthenticated-encryption.md @@ -0,0 +1,14 @@ +--- +rule: unauthenticated-encryption +name: Unauthenticated encryption +title: Unauthenticated encryption +references: + A02:2021: https://owasp.org/Top10/A02_2021-Cryptographic_Failures/ +impactDomain: Security +labels: + - crypto.encrypt + - crypto.set_auth_data +scope: root +--- + +Ensure that encryption operations use authenticated encryption. diff --git a/packages/scanner/src/rules/unauthenticated-encryption/metadata.ts b/packages/scanner/src/rules/unauthenticated-encryption/metadata.ts new file mode 100644 index 0000000000..160d620b2d --- /dev/null +++ b/packages/scanner/src/rules/unauthenticated-encryption/metadata.ts @@ -0,0 +1,12 @@ +import { Metadata } from '../lib/metadata'; + +export default { + title: 'Unauthenticated encryption', + scope: 'root', + enumerateScope: true, + impactDomain: 'Security', + references: { + 'A02:2021': 'https://owasp.org/Top10/A02_2021-Cryptographic_Failures/', + }, + labels: ['crypto.encrypt', 'crypto.set_auth_data'], +} as Metadata; diff --git a/packages/scanner/src/rules/unauthenticated-encryption/rule.ts b/packages/scanner/src/rules/unauthenticated-encryption/rule.ts new file mode 100644 index 0000000000..403d98c1d4 --- /dev/null +++ b/packages/scanner/src/rules/unauthenticated-encryption/rule.ts @@ -0,0 +1,26 @@ +import { Event } from '@appland/models'; +import { AppMapIndex, MatcherResult, RuleLogic } from '../../types'; + +function matcher(event: Event, appMapIndex: AppMapIndex): MatcherResult { + if (!event.receiver) return; + + const objectId = event.receiver.object_id; + const setAuthData = appMapIndex.appMap.events + .filter((evt) => evt.receiver?.object_id === objectId) + .find((evt) => evt.labels.has('crypto.set_auth_data')); + if (!setAuthData) { + return [ + { + event, + message: 'Encryption is not authenticated', + }, + ]; + } +} + +export default function rule(): RuleLogic { + return { + matcher, + where: (e: Event) => e.labels.has('crypto.encrypt'), + }; +} diff --git a/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Circular_cycle.appmap.json b/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Circular_cycle.appmap.json new file mode 100644 index 0000000000..84c3a19bad --- /dev/null +++ b/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Circular_cycle.appmap.json @@ -0,0 +1 @@ +{"version":"1.7.0","metadata":{"app":"scanner/ruby-fixture","language":{"name":"ruby","engine":"ruby","version":"3.0.1"},"client":{"name":"appmap","url":"https://github.com/applandinc/appmap-ruby","version":"0.83.4"},"git":{"repository":"git@github.com:applandinc/appmap-js.git","branch":"feat/hash-participating-events","commit":"7e82f8a0b13a1d0927aad73be4ee126d2d4695dc","status":["D ../circular_dependency/.ruby-version","D ../circular_dependency/Gemfile","D ../circular_dependency/Gemfile.lock","D ../circular_dependency/appmap.yml","D ../circular_dependency/lib/command/command.rb","D ../circular_dependency/lib/pkg_a/a.rb","D ../circular_dependency/lib/pkg_b/b.rb","D ../circular_dependency/test/command_test.rb","D ../circular_dependency/tmp/appmap/minitest/Command_command.appmap.json","M ../../../scanner/circularDependency.spec.ts","?? ../../../../../cli/src/cmds/sequenceDiagram/","?? ../../../../doc/rules/unauthenticated-encryption.md","?? ../../../../src/rules/unauthenticated-encryption/","?? ./","?? ../../../scanner/unauthenticatedEncryption.spec.ts"],"git_last_annotated_tag":"@appland/appmap-v2.3.4","git_last_tag":"@appland/appmap-v3.32.0","git_commits_since_last_annotated_tag":0,"git_commits_since_last_tag":0},"name":"Circular cycle","source_location":"test/circular_test.rb:6","frameworks":[{"name":"minitest","version":"5.16.2"}],"recorder":{"name":"minitest"},"test_status":"succeeded"},"classMap":[{"name":"lib/circular","type":"package","children":[{"name":"Circular","type":"class","children":[{"name":"Command","type":"class","children":[{"name":"invoke","type":"function","location":"lib/circular/command.rb:6","static":true,"labels":["command"],"comment":"# @label command\n"}]}]},{"name":"pkg_a","type":"package","children":[{"name":"PkgA","type":"class","children":[{"name":"A","type":"class","children":[{"name":"invoke","type":"function","location":"lib/circular/pkg_a/a.rb:5","static":false},{"name":"cycle","type":"function","location":"lib/circular/pkg_a/a.rb:9","static":false}]}]}]},{"name":"pkg_b","type":"package","children":[{"name":"PkgB","type":"class","children":[{"name":"B","type":"class","children":[{"name":"invoke","type":"function","location":"lib/circular/pkg_b/b.rb:5","static":false}]}]}]}]}],"events":[{"id":1,"event":"call","thread_id":2360,"defined_class":"Circular::Command","method_id":"invoke","path":"lib/circular/command.rb","lineno":6,"static":true,"parameters":[],"receiver":{"class":"Class","object_id":2340,"value":"Circular::Command"}},{"id":2,"event":"call","thread_id":2360,"defined_class":"PkgA::A","method_id":"invoke","path":"lib/circular/pkg_a/a.rb","lineno":5,"static":false,"parameters":[],"receiver":{"class":"PkgA::A","object_id":2380,"value":"#"}},{"id":3,"event":"call","thread_id":2360,"defined_class":"PkgB::B","method_id":"invoke","path":"lib/circular/pkg_b/b.rb","lineno":5,"static":false,"parameters":[],"receiver":{"class":"PkgB::B","object_id":2400,"value":"#"}},{"id":4,"event":"call","thread_id":2360,"defined_class":"PkgA::A","method_id":"cycle","path":"lib/circular/pkg_a/a.rb","lineno":9,"static":false,"parameters":[],"receiver":{"class":"PkgA::A","object_id":2420,"value":"#"}},{"id":5,"event":"return","thread_id":2360,"parent_id":4,"elapsed":1.00000761449337e-06,"return_value":{"class":"String","value":"cycle encountered","object_id":2440}},{"id":6,"event":"return","thread_id":2360,"parent_id":3,"elapsed":7.30000901967287e-05,"return_value":{"class":"String","value":"cycle encountered","object_id":2440}},{"id":7,"event":"return","thread_id":2360,"parent_id":2,"elapsed":0.00013400008901953697,"return_value":{"class":"String","value":"cycle encountered","object_id":2440}},{"id":8,"event":"return","thread_id":2360,"parent_id":1,"elapsed":0.00020899996161460876,"return_value":{"class":"String","value":"cycle encountered","object_id":2440}}]} \ No newline at end of file diff --git a/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_128_cbc.appmap.json b/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_128_cbc.appmap.json new file mode 100644 index 0000000000..3ecdd745eb --- /dev/null +++ b/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_128_cbc.appmap.json @@ -0,0 +1,361 @@ +{ + "version": "1.7.0", + "metadata": { + "app": "scanner/ruby-fixture", + "language": { "name": "ruby", "engine": "ruby", "version": "3.0.1" }, + "client": { + "name": "appmap", + "url": "https://github.com/applandinc/appmap-ruby", + "version": "0.83.4" + }, + "git": { + "repository": "git@github.com:applandinc/appmap-js.git", + "branch": "feat/rule-unauthenticated-encryption", + "commit": "9f81b2cf8ad2313ee5751a41719edc321c6e9227", + "status": ["?? ../../../../../cli/src/cmds/sequenceDiagram/"], + "git_last_annotated_tag": "@appland/appmap-v2.3.4", + "git_last_tag": "@appland/scanner-v1.63.0", + "git_commits_since_last_annotated_tag": 0, + "git_commits_since_last_tag": 0 + }, + "name": "Crypt crypt aes 128 cbc", + "source_location": "test/crypt_test.rb:6", + "frameworks": [{ "name": "minitest", "version": "5.16.2" }], + "recorder": { "name": "minitest" }, + "test_status": "succeeded" + }, + "classMap": [ + { + "name": "lib/crypt", + "type": "package", + "children": [ + { + "name": "Crypt", + "type": "class", + "children": [ + { + "name": "Command", + "type": "class", + "children": [ + { + "name": "encrypt_aes_128_cbc", + "type": "function", + "location": "lib/crypt/command.rb:4", + "static": true, + "labels": ["command"], + "comment": "# @label command\n" + } + ] + } + ] + } + ] + }, + { + "name": "openssl", + "type": "package", + "children": [ + { + "name": "OpenSSL", + "type": "class", + "children": [ + { + "name": "Cipher", + "type": "class", + "children": [ + { + "name": "encrypt", + "type": "function", + "location": "OpenSSL::Cipher#encrypt", + "static": false, + "labels": ["crypto.encrypt"] + }, + { + "name": "random_key", + "type": "function", + "location": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb:43", + "static": false, + "comment": "# call-seq:\n# cipher.random_key -> key\n#\n# Generate a random key with OpenSSL::Random.random_bytes and sets it to\n# the cipher, and returns it.\n#\n# You must call #encrypt or #decrypt before calling this method.\n" + }, + { + "name": "key=", + "type": "function", + "location": "OpenSSL::Cipher#key=", + "static": false, + "labels": ["crypto.set_key"] + }, + { + "name": "random_iv", + "type": "function", + "location": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb:55", + "static": false, + "comment": "# call-seq:\n# cipher.random_iv -> iv\n#\n# Generate a random IV with OpenSSL::Random.random_bytes and sets it to the\n# cipher, and returns it.\n#\n# You must call #encrypt or #decrypt before calling this method.\n" + }, + { + "name": "iv=", + "type": "function", + "location": "OpenSSL::Cipher#iv=", + "static": false, + "labels": ["crypto.set_iv"] + }, + { + "name": "update", + "type": "function", + "location": "OpenSSL::Cipher#update", + "static": false + }, + { + "name": "final", + "type": "function", + "location": "OpenSSL::Cipher#final", + "static": false, + "labels": ["crypto.final"] + } + ] + } + ] + } + ] + } + ], + "events": [ + { + "id": 19, + "event": "call", + "thread_id": 2320, + "defined_class": "Crypt::Command", + "method_id": "encrypt_aes_128_cbc", + "path": "lib/crypt/command.rb", + "lineno": 4, + "static": true, + "parameters": [], + "receiver": { "class": "Class", "object_id": 2300, "value": "Crypt::Command" } + }, + { + "id": 20, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "encrypt", + "path": "OpenSSL::Cipher#encrypt", + "static": false, + "parameters": [ + { + "name": "arg", + "class": "Array", + "object_id": 2540, + "value": "[]", + "kind": "rest", + "size": 0 + } + ], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2560, + "value": "#", + "labels": ["crypto.algorithm.AES-128-CBC"] + } + }, + { + "id": 21, + "event": "return", + "thread_id": 2320, + "parent_id": 20, + "elapsed": 8.00006091594696e-6, + "return_value": { + "class": "OpenSSL::Cipher", + "value": "#", + "object_id": 2560 + } + }, + { + "id": 22, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "random_key", + "path": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb", + "lineno": 43, + "static": false, + "parameters": [], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2560, + "value": "#" + } + }, + { + "id": 23, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "key=", + "path": "OpenSSL::Cipher#key=", + "static": false, + "parameters": [ + { + "name": "arg", + "class": "String", + "object_id": 2580, + "value": "___Vl\u0014@\u0001\u000fD__\t>[ ", + "kind": "req" + } + ], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2560, + "value": "#" + } + }, + { + "id": 24, + "event": "return", + "thread_id": 2320, + "parent_id": 23, + "elapsed": 3.00002284348011e-6, + "return_value": { + "class": "String", + "value": "___Vl\u0014@\u0001\u000fD__\t>[ ", + "object_id": 2580 + } + }, + { + "id": 25, + "event": "return", + "thread_id": 2320, + "parent_id": 22, + "elapsed": 9.700004011392593e-5, + "return_value": { + "class": "String", + "value": "___Vl\u0014@\u0001\u000fD__\t>[ ", + "object_id": 2580 + } + }, + { + "id": 26, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "random_iv", + "path": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb", + "lineno": 55, + "static": false, + "parameters": [], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2560, + "value": "#" + } + }, + { + "id": 27, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "iv=", + "path": "OpenSSL::Cipher#iv=", + "static": false, + "parameters": [ + { + "name": "arg", + "class": "String", + "object_id": 2600, + "value": "___F__mD____>_\u0004_", + "kind": "req" + } + ], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2560, + "value": "#" + } + }, + { + "id": 28, + "event": "return", + "thread_id": 2320, + "parent_id": 27, + "elapsed": 2.00001522898674e-6, + "return_value": { "class": "String", "value": "___F__mD____>_\u0004_", "object_id": 2600 } + }, + { + "id": 29, + "event": "return", + "thread_id": 2320, + "parent_id": 26, + "elapsed": 8.199992589652538e-5, + "return_value": { "class": "String", "value": "___F__mD____>_\u0004_", "object_id": 2600 } + }, + { + "id": 30, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "update", + "path": "OpenSSL::Cipher#update", + "static": false, + "parameters": [ + { + "name": "arg", + "class": "Array", + "object_id": 2620, + "value": "[Very, very confidential data]", + "kind": "rest", + "size": 1 + } + ], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2560, + "value": "#" + } + }, + { + "id": 31, + "event": "return", + "thread_id": 2320, + "parent_id": 30, + "elapsed": 1.200009137392044e-5, + "return_value": { "class": "String", "value": "_l_____\u0005L___I7_e", "object_id": 2640 } + }, + { + "id": 32, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "final", + "path": "OpenSSL::Cipher#final", + "static": false, + "parameters": [], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2560, + "value": "#" + } + }, + { + "id": 33, + "event": "return", + "thread_id": 2320, + "parent_id": 32, + "elapsed": 1.3999873772263527e-5, + "return_value": { + "class": "String", + "value": "\u0014__s_B\\3_ Y\u0014__fd", + "object_id": 2660 + } + }, + { + "id": 34, + "event": "return", + "thread_id": 2320, + "parent_id": 19, + "elapsed": 0.00044599990360438824, + "return_value": { + "class": "String", + "value": "_l_____\u0005L___I7_e\u0014__s_B\\3_ Y\u0014__fd", + "object_id": 2680 + } + } + ] +} diff --git a/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_256_gcm.appmap.json b/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_256_gcm.appmap.json new file mode 100644 index 0000000000..bfb9304711 --- /dev/null +++ b/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_256_gcm.appmap.json @@ -0,0 +1,401 @@ +{ + "version": "1.7.0", + "metadata": { + "app": "scanner/ruby-fixture", + "language": { "name": "ruby", "engine": "ruby", "version": "3.0.1" }, + "client": { + "name": "appmap", + "url": "https://github.com/applandinc/appmap-ruby", + "version": "0.83.4" + }, + "git": { + "repository": "git@github.com:applandinc/appmap-js.git", + "branch": "feat/rule-unauthenticated-encryption", + "commit": "9f81b2cf8ad2313ee5751a41719edc321c6e9227", + "status": ["?? ../../../../../cli/src/cmds/sequenceDiagram/"], + "git_last_annotated_tag": "@appland/appmap-v2.3.4", + "git_last_tag": "@appland/scanner-v1.63.0", + "git_commits_since_last_annotated_tag": 0, + "git_commits_since_last_tag": 0 + }, + "name": "Crypt crypt aes 256 gcm", + "source_location": "test/crypt_test.rb:11", + "frameworks": [{ "name": "minitest", "version": "5.16.2" }], + "recorder": { "name": "minitest" }, + "test_status": "succeeded" + }, + "classMap": [ + { + "name": "lib/crypt", + "type": "package", + "children": [ + { + "name": "Crypt", + "type": "class", + "children": [ + { + "name": "Command", + "type": "class", + "children": [ + { + "name": "encrypt_aes_256_gcm", + "type": "function", + "location": "lib/crypt/command.rb:15", + "static": true, + "labels": ["command"], + "comment": "# @label command\n" + } + ] + } + ] + } + ] + }, + { + "name": "openssl", + "type": "package", + "children": [ + { + "name": "OpenSSL", + "type": "class", + "children": [ + { + "name": "Cipher", + "type": "class", + "children": [ + { + "name": "encrypt", + "type": "function", + "location": "OpenSSL::Cipher#encrypt", + "static": false, + "labels": ["crypto.encrypt"] + }, + { + "name": "random_key", + "type": "function", + "location": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb:43", + "static": false, + "comment": "# call-seq:\n# cipher.random_key -> key\n#\n# Generate a random key with OpenSSL::Random.random_bytes and sets it to\n# the cipher, and returns it.\n#\n# You must call #encrypt or #decrypt before calling this method.\n" + }, + { + "name": "key=", + "type": "function", + "location": "OpenSSL::Cipher#key=", + "static": false, + "labels": ["crypto.set_key"] + }, + { + "name": "random_iv", + "type": "function", + "location": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb:55", + "static": false, + "comment": "# call-seq:\n# cipher.random_iv -> iv\n#\n# Generate a random IV with OpenSSL::Random.random_bytes and sets it to the\n# cipher, and returns it.\n#\n# You must call #encrypt or #decrypt before calling this method.\n" + }, + { + "name": "iv=", + "type": "function", + "location": "OpenSSL::Cipher#iv=", + "static": false, + "labels": ["crypto.set_iv"] + }, + { + "name": "auth_data=", + "type": "function", + "location": "OpenSSL::Cipher#auth_data=", + "static": false, + "labels": ["crypto.set_auth_data"] + }, + { + "name": "update", + "type": "function", + "location": "OpenSSL::Cipher#update", + "static": false + }, + { + "name": "final", + "type": "function", + "location": "OpenSSL::Cipher#final", + "static": false, + "labels": ["crypto.final"] + } + ] + } + ] + } + ] + } + ], + "events": [ + { + "id": 1, + "event": "call", + "thread_id": 2320, + "defined_class": "Crypt::Command", + "method_id": "encrypt_aes_256_gcm", + "path": "lib/crypt/command.rb", + "lineno": 15, + "static": true, + "parameters": [ + { + "name": "record_id", + "class": "Symbol", + "object_id": 1869148, + "value": ":record_id", + "kind": "req" + } + ], + "receiver": { "class": "Class", "object_id": 2300, "value": "Crypt::Command" } + }, + { + "id": 2, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "encrypt", + "path": "OpenSSL::Cipher#encrypt", + "static": false, + "parameters": [ + { + "name": "arg", + "class": "Array", + "object_id": 2340, + "value": "[]", + "kind": "rest", + "size": 0 + } + ], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2360, + "value": "#", + "labels": ["crypto.algorithm.id-aes256-GCM"] + } + }, + { + "id": 3, + "event": "return", + "thread_id": 2320, + "parent_id": 2, + "elapsed": 3.00002284348011e-6, + "return_value": { + "class": "OpenSSL::Cipher", + "value": "#", + "object_id": 2360 + } + }, + { + "id": 4, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "random_key", + "path": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb", + "lineno": 43, + "static": false, + "parameters": [], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2360, + "value": "#" + } + }, + { + "id": 5, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "key=", + "path": "OpenSSL::Cipher#key=", + "static": false, + "parameters": [ + { + "name": "arg", + "class": "String", + "object_id": 2380, + "value": "_E_H__IKu_b___n:_}___C_s_\u000b'_\"3 _", + "kind": "req" + } + ], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2360, + "value": "#" + } + }, + { + "id": 6, + "event": "return", + "thread_id": 2320, + "parent_id": 5, + "elapsed": 1.00000761449337e-5, + "return_value": { + "class": "String", + "value": "_E_H__IKu_b___n:_}___C_s_\u000b'_\"3 _", + "object_id": 2380 + } + }, + { + "id": 7, + "event": "return", + "thread_id": 2320, + "parent_id": 4, + "elapsed": 0.0012940000742673874, + "return_value": { + "class": "String", + "value": "_E_H__IKu_b___n:_}___C_s_\u000b'_\"3 _", + "object_id": 2380 + } + }, + { + "id": 8, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "random_iv", + "path": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb", + "lineno": 55, + "static": false, + "parameters": [], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2360, + "value": "#" + } + }, + { + "id": 9, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "iv=", + "path": "OpenSSL::Cipher#iv=", + "static": false, + "parameters": [ + { + "name": "arg", + "class": "String", + "object_id": 2400, + "value": "______\tW'_\u0017,", + "kind": "req" + } + ], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2360, + "value": "#" + } + }, + { + "id": 10, + "event": "return", + "thread_id": 2320, + "parent_id": 9, + "elapsed": 2.00001522898674e-6, + "return_value": { "class": "String", "value": "______\tW'_\u0017,", "object_id": 2400 } + }, + { + "id": 11, + "event": "return", + "thread_id": 2320, + "parent_id": 8, + "elapsed": 9.100022725760937e-5, + "return_value": { "class": "String", "value": "______\tW'_\u0017,", "object_id": 2400 } + }, + { + "id": 12, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "auth_data=", + "path": "OpenSSL::Cipher#auth_data=", + "static": false, + "parameters": [ + { "name": "arg", "class": "String", "object_id": 2420, "value": "record_id", "kind": "req" } + ], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2360, + "value": "#" + } + }, + { + "id": 13, + "event": "return", + "thread_id": 2320, + "parent_id": 12, + "elapsed": 3.00002284348011e-6, + "return_value": { "class": "String", "value": "record_id", "object_id": 2420 } + }, + { + "id": 14, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "update", + "path": "OpenSSL::Cipher#update", + "static": false, + "parameters": [ + { + "name": "arg", + "class": "Array", + "object_id": 2440, + "value": "[Very, very confidential data]", + "kind": "rest", + "size": 1 + } + ], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2360, + "value": "#" + } + }, + { + "id": 15, + "event": "return", + "thread_id": 2320, + "parent_id": 14, + "elapsed": 8.00006091594696e-6, + "return_value": { + "class": "String", + "value": "}N=__\u0002_V__M__\u0013\f\u000f_Q__y_0____I", + "object_id": 2460 + } + }, + { + "id": 16, + "event": "call", + "thread_id": 2320, + "defined_class": "OpenSSL::Cipher", + "method_id": "final", + "path": "OpenSSL::Cipher#final", + "static": false, + "parameters": [], + "receiver": { + "class": "OpenSSL::Cipher", + "object_id": 2360, + "value": "#" + } + }, + { + "id": 17, + "event": "return", + "thread_id": 2320, + "parent_id": 16, + "elapsed": 3.00002284348011e-6, + "return_value": { "class": "String", "value": "", "object_id": 2480 } + }, + { + "id": 18, + "event": "return", + "thread_id": 2320, + "parent_id": 1, + "elapsed": 0.0017880001105368137, + "return_value": { + "class": "String", + "value": "}N=__\u0002_V__M__\u0013\f\u000f_Q__y_0____I", + "object_id": 2500 + } + } + ] +} diff --git a/packages/scanner/test/scanner/unauthenticatedEncryption.spec.ts b/packages/scanner/test/scanner/unauthenticatedEncryption.spec.ts new file mode 100644 index 0000000000..9dbcca0416 --- /dev/null +++ b/packages/scanner/test/scanner/unauthenticatedEncryption.spec.ts @@ -0,0 +1,27 @@ +import Check from '../../src/check'; +import { loadRule } from '../../src/configuration/configurationProvider'; +import { scan } from '../util'; + +describe('unauthenticated encryption', () => { + it('found', async () => { + const rule = await loadRule('unauthenticated-encryption'); + const { findings } = await scan( + new Check(rule), + './ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_128_cbc.appmap.json' + ); + expect(findings).toHaveLength(1); + const finding1 = findings[0]; + expect(finding1.ruleId).toEqual('unauthenticated-encryption'); + expect(finding1.event.id).toEqual(2); + expect(finding1.message).toEqual(`Encryption is not authenticated`); + expect(finding1.relatedEvents).toHaveLength(1); + }); + it('not found', async () => { + const rule = await loadRule('unauthenticated-encryption'); + const { findings } = await scan( + new Check(rule), + './ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_256_gcm.appmap.json' + ); + expect(findings).toHaveLength(0); + }); +}); From e40beebb90696c5abe1bf833fd99299525ece986 Mon Sep 17 00:00:00 2001 From: Kevin Gilpin Date: Mon, 1 Aug 2022 14:36:42 -0400 Subject: [PATCH 4/7] feat: Add unauthenticated-encryption to default rule set --- packages/scanner/src/sampleConfig/default.yml | 51 ++++++++++--------- packages/scanner/test/cli/scan.spec.ts | 2 +- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/scanner/src/sampleConfig/default.yml b/packages/scanner/src/sampleConfig/default.yml index 76928dcc11..97cf07ee15 100644 --- a/packages/scanner/src/sampleConfig/default.yml +++ b/packages/scanner/src/sampleConfig/default.yml @@ -1,26 +1,27 @@ checks: - - rule: authzBeforeAuthn - # - rule: circularDependency - - rule: deserializationOfUntrustedData - - rule: execOfUntrustedCommand - - rule: http500 - # - rule: illegalPackageDependency - # - rule: incompatibleHttpClientRequest - # - rule: insecureCompare - # - rule: jobNotCancelled - - rule: logoutWithoutSessionReset - # - rule: missingAuthentication - - rule: missingContentType - - rule: nPlusOneQuery - # - rule: queryFromInvalidPackage - # - rule: queryFromView - # - rule: rpcWithoutCircuitBreaker - # - rule: saveWithoutValidation - - rule: secretInLog - # - rule: slowFunctionCall - # - rule: slowHttpServerRequest - # - rule: slowQuery - - rule: tooManyJoins - - rule: tooManyUpdates - # - rule: unbatchedMaterializedQuery - - rule: updateInGetRequest + - rule: authz-before-authn + # - rule: circular-dependency + - rule: deserialization-of-untrusted-data + - rule: exec-of-untrusted-command + - rule: http-500 + # - rule: illegal-package-dependency + # - rule: incompatible-http-client-request + # - rule: insecure-compare + # - rule: job-not-cancelled + - rule: logout-without-session-reset + # - rule: missing-authentication + - rule: missing-content-type + - rule: n-plus-one-query + # - rule: query-from-invalid-package + # - rule: query-from-view + # - rule: rpc-without-circuit-breaker + # - rule: save-without-validation + - rule: secret-in-log + # - rule: slow-function-call + # - rule: slow-httpServer-request + # - rule: slow-query + - rule: too-many-joins + - rule: too-many-updates + # - rule: unbatched-materialized-query + - rule: unauthenticated-encryption + - rule: update-in-get-request diff --git a/packages/scanner/test/cli/scan.spec.ts b/packages/scanner/test/cli/scan.spec.ts index 06df538442..7fe603ad35 100644 --- a/packages/scanner/test/cli/scan.spec.ts +++ b/packages/scanner/test/cli/scan.spec.ts @@ -60,7 +60,7 @@ describe('scan', () => { const appMapMetadata = scanResults.summary.appMapMetadata; expect(appMapMetadata.apps).toEqual(['spring-petclinic']); const checks = scanResults.configuration.checks; - ['http500', 'nPlusOneQuery'].forEach((rule) => + ['http-500', 'n-plus-one-query'].forEach((rule) => expect(checks.map((check) => check.rule)).toContain(rule) ); expect(Object.keys(scanResults).sort()).toEqual([ From b897e88ddeae65993dcbf53870bca2059c962bf0 Mon Sep 17 00:00:00 2001 From: Kevin Gilpin Date: Tue, 2 Aug 2022 16:45:51 -0400 Subject: [PATCH 5/7] test: Add more Ruby fixture tests for crypto --- .../test/fixtures/ruby/fixture/Gemfile | 2 +- .../test/fixtures/ruby/fixture/Gemfile.lock | 4 +- .../ruby/fixture/lib/crypt/command.rb | 8 +- .../fixtures/ruby/fixture/test/crypt_test.rb | 14 +- .../Crypt_crypt_aes_128_cbc.appmap.json | 362 +--------------- .../Crypt_crypt_aes_256_gcm.appmap.json | 402 +----------------- 6 files changed, 24 insertions(+), 768 deletions(-) diff --git a/packages/scanner/test/fixtures/ruby/fixture/Gemfile b/packages/scanner/test/fixtures/ruby/fixture/Gemfile index ad7f77b468..3910faeaa7 100644 --- a/packages/scanner/test/fixtures/ruby/fixture/Gemfile +++ b/packages/scanner/test/fixtures/ruby/fixture/Gemfile @@ -2,5 +2,5 @@ source "https://rubygems.org" gem 'byebug' gem 'rake' -gem 'appmap', github: 'applandinc/appmap-ruby', branch: 'master' +gem 'appmap', github: 'applandinc/appmap-ruby', branch: 'feat/crypto-algorithm' gem 'minitest' diff --git a/packages/scanner/test/fixtures/ruby/fixture/Gemfile.lock b/packages/scanner/test/fixtures/ruby/fixture/Gemfile.lock index e32e7d45be..8be15bd82e 100644 --- a/packages/scanner/test/fixtures/ruby/fixture/Gemfile.lock +++ b/packages/scanner/test/fixtures/ruby/fixture/Gemfile.lock @@ -1,7 +1,7 @@ GIT remote: https://github.com/applandinc/appmap-ruby.git - revision: cda70741cdd594f92863d2c32842c9627af38506 - branch: master + revision: 216c45468bdf50251fd8fcfd45849acaf997bd73 + branch: feat/crypto-algorithm specs: appmap (0.83.4) activesupport diff --git a/packages/scanner/test/fixtures/ruby/fixture/lib/crypt/command.rb b/packages/scanner/test/fixtures/ruby/fixture/lib/crypt/command.rb index ec1093d94b..e689e416f6 100644 --- a/packages/scanner/test/fixtures/ruby/fixture/lib/crypt/command.rb +++ b/packages/scanner/test/fixtures/ruby/fixture/lib/crypt/command.rb @@ -12,11 +12,15 @@ def self.encrypt_aes_128_cbc end # @label command - def self.encrypt_aes_256_gcm(record_id) + def self.encrypt_aes_256_gcm(record_id, key: nil) data = "Very, very confidential data" cipher = OpenSSL::Cipher.new('aes-256-gcm') cipher.encrypt - key = cipher.random_key + if key + cipher.key = key + else + key = cipher.random_key + end iv = cipher.random_iv cipher.auth_data = record_id.to_s diff --git a/packages/scanner/test/fixtures/ruby/fixture/test/crypt_test.rb b/packages/scanner/test/fixtures/ruby/fixture/test/crypt_test.rb index e0ee561b35..64f128a011 100644 --- a/packages/scanner/test/fixtures/ruby/fixture/test/crypt_test.rb +++ b/packages/scanner/test/fixtures/ruby/fixture/test/crypt_test.rb @@ -9,7 +9,19 @@ def test_crypt_aes_128_cbc end def test_crypt_aes_256_gcm - ciphertext = Crypt::Command.encrypt_aes_256_gcm(:record_id) + ciphertext = Crypt::Command.encrypt_aes_256_gcm('test_crypt_aes_256_gcm') + assert_equal 28, ciphertext.length + end + + def test_hard_coded_key + key = Base64.decode64 "f9hmz/Sh93xIdpfiU7ja385+1QtLf5GVX4MaJwjqh0E=" + ciphertext = Crypt::Command.encrypt_aes_256_gcm('test_hard_coded_key', key: key) + assert_equal 28, ciphertext.length + end + + def test_random_key + key = OpenSSL::Random.random_bytes(32) + ciphertext = Crypt::Command.encrypt_aes_256_gcm('test_hard_coded_key', key: key) assert_equal 28, ciphertext.length end end diff --git a/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_128_cbc.appmap.json b/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_128_cbc.appmap.json index 3ecdd745eb..17a58a86d4 100644 --- a/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_128_cbc.appmap.json +++ b/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_128_cbc.appmap.json @@ -1,361 +1 @@ -{ - "version": "1.7.0", - "metadata": { - "app": "scanner/ruby-fixture", - "language": { "name": "ruby", "engine": "ruby", "version": "3.0.1" }, - "client": { - "name": "appmap", - "url": "https://github.com/applandinc/appmap-ruby", - "version": "0.83.4" - }, - "git": { - "repository": "git@github.com:applandinc/appmap-js.git", - "branch": "feat/rule-unauthenticated-encryption", - "commit": "9f81b2cf8ad2313ee5751a41719edc321c6e9227", - "status": ["?? ../../../../../cli/src/cmds/sequenceDiagram/"], - "git_last_annotated_tag": "@appland/appmap-v2.3.4", - "git_last_tag": "@appland/scanner-v1.63.0", - "git_commits_since_last_annotated_tag": 0, - "git_commits_since_last_tag": 0 - }, - "name": "Crypt crypt aes 128 cbc", - "source_location": "test/crypt_test.rb:6", - "frameworks": [{ "name": "minitest", "version": "5.16.2" }], - "recorder": { "name": "minitest" }, - "test_status": "succeeded" - }, - "classMap": [ - { - "name": "lib/crypt", - "type": "package", - "children": [ - { - "name": "Crypt", - "type": "class", - "children": [ - { - "name": "Command", - "type": "class", - "children": [ - { - "name": "encrypt_aes_128_cbc", - "type": "function", - "location": "lib/crypt/command.rb:4", - "static": true, - "labels": ["command"], - "comment": "# @label command\n" - } - ] - } - ] - } - ] - }, - { - "name": "openssl", - "type": "package", - "children": [ - { - "name": "OpenSSL", - "type": "class", - "children": [ - { - "name": "Cipher", - "type": "class", - "children": [ - { - "name": "encrypt", - "type": "function", - "location": "OpenSSL::Cipher#encrypt", - "static": false, - "labels": ["crypto.encrypt"] - }, - { - "name": "random_key", - "type": "function", - "location": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb:43", - "static": false, - "comment": "# call-seq:\n# cipher.random_key -> key\n#\n# Generate a random key with OpenSSL::Random.random_bytes and sets it to\n# the cipher, and returns it.\n#\n# You must call #encrypt or #decrypt before calling this method.\n" - }, - { - "name": "key=", - "type": "function", - "location": "OpenSSL::Cipher#key=", - "static": false, - "labels": ["crypto.set_key"] - }, - { - "name": "random_iv", - "type": "function", - "location": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb:55", - "static": false, - "comment": "# call-seq:\n# cipher.random_iv -> iv\n#\n# Generate a random IV with OpenSSL::Random.random_bytes and sets it to the\n# cipher, and returns it.\n#\n# You must call #encrypt or #decrypt before calling this method.\n" - }, - { - "name": "iv=", - "type": "function", - "location": "OpenSSL::Cipher#iv=", - "static": false, - "labels": ["crypto.set_iv"] - }, - { - "name": "update", - "type": "function", - "location": "OpenSSL::Cipher#update", - "static": false - }, - { - "name": "final", - "type": "function", - "location": "OpenSSL::Cipher#final", - "static": false, - "labels": ["crypto.final"] - } - ] - } - ] - } - ] - } - ], - "events": [ - { - "id": 19, - "event": "call", - "thread_id": 2320, - "defined_class": "Crypt::Command", - "method_id": "encrypt_aes_128_cbc", - "path": "lib/crypt/command.rb", - "lineno": 4, - "static": true, - "parameters": [], - "receiver": { "class": "Class", "object_id": 2300, "value": "Crypt::Command" } - }, - { - "id": 20, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "encrypt", - "path": "OpenSSL::Cipher#encrypt", - "static": false, - "parameters": [ - { - "name": "arg", - "class": "Array", - "object_id": 2540, - "value": "[]", - "kind": "rest", - "size": 0 - } - ], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2560, - "value": "#", - "labels": ["crypto.algorithm.AES-128-CBC"] - } - }, - { - "id": 21, - "event": "return", - "thread_id": 2320, - "parent_id": 20, - "elapsed": 8.00006091594696e-6, - "return_value": { - "class": "OpenSSL::Cipher", - "value": "#", - "object_id": 2560 - } - }, - { - "id": 22, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "random_key", - "path": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb", - "lineno": 43, - "static": false, - "parameters": [], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2560, - "value": "#" - } - }, - { - "id": 23, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "key=", - "path": "OpenSSL::Cipher#key=", - "static": false, - "parameters": [ - { - "name": "arg", - "class": "String", - "object_id": 2580, - "value": "___Vl\u0014@\u0001\u000fD__\t>[ ", - "kind": "req" - } - ], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2560, - "value": "#" - } - }, - { - "id": 24, - "event": "return", - "thread_id": 2320, - "parent_id": 23, - "elapsed": 3.00002284348011e-6, - "return_value": { - "class": "String", - "value": "___Vl\u0014@\u0001\u000fD__\t>[ ", - "object_id": 2580 - } - }, - { - "id": 25, - "event": "return", - "thread_id": 2320, - "parent_id": 22, - "elapsed": 9.700004011392593e-5, - "return_value": { - "class": "String", - "value": "___Vl\u0014@\u0001\u000fD__\t>[ ", - "object_id": 2580 - } - }, - { - "id": 26, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "random_iv", - "path": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb", - "lineno": 55, - "static": false, - "parameters": [], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2560, - "value": "#" - } - }, - { - "id": 27, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "iv=", - "path": "OpenSSL::Cipher#iv=", - "static": false, - "parameters": [ - { - "name": "arg", - "class": "String", - "object_id": 2600, - "value": "___F__mD____>_\u0004_", - "kind": "req" - } - ], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2560, - "value": "#" - } - }, - { - "id": 28, - "event": "return", - "thread_id": 2320, - "parent_id": 27, - "elapsed": 2.00001522898674e-6, - "return_value": { "class": "String", "value": "___F__mD____>_\u0004_", "object_id": 2600 } - }, - { - "id": 29, - "event": "return", - "thread_id": 2320, - "parent_id": 26, - "elapsed": 8.199992589652538e-5, - "return_value": { "class": "String", "value": "___F__mD____>_\u0004_", "object_id": 2600 } - }, - { - "id": 30, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "update", - "path": "OpenSSL::Cipher#update", - "static": false, - "parameters": [ - { - "name": "arg", - "class": "Array", - "object_id": 2620, - "value": "[Very, very confidential data]", - "kind": "rest", - "size": 1 - } - ], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2560, - "value": "#" - } - }, - { - "id": 31, - "event": "return", - "thread_id": 2320, - "parent_id": 30, - "elapsed": 1.200009137392044e-5, - "return_value": { "class": "String", "value": "_l_____\u0005L___I7_e", "object_id": 2640 } - }, - { - "id": 32, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "final", - "path": "OpenSSL::Cipher#final", - "static": false, - "parameters": [], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2560, - "value": "#" - } - }, - { - "id": 33, - "event": "return", - "thread_id": 2320, - "parent_id": 32, - "elapsed": 1.3999873772263527e-5, - "return_value": { - "class": "String", - "value": "\u0014__s_B\\3_ Y\u0014__fd", - "object_id": 2660 - } - }, - { - "id": 34, - "event": "return", - "thread_id": 2320, - "parent_id": 19, - "elapsed": 0.00044599990360438824, - "return_value": { - "class": "String", - "value": "_l_____\u0005L___I7_e\u0014__s_B\\3_ Y\u0014__fd", - "object_id": 2680 - } - } - ] -} +{"version":"1.7.0","metadata":{"app":"scanner/ruby-fixture","language":{"name":"ruby","engine":"ruby","version":"3.0.1"},"client":{"name":"appmap","url":"https://github.com/applandinc/appmap-ruby","version":"0.83.4"},"git":{"repository":"git@github.com:applandinc/appmap-js.git","branch":"feat/hard-coded-password","commit":"f1111d3402ff3725ccc6545aa963fb7653ae624b","status":["M ../../../../doc/labels/crypto.encrypt.md","M ../../../../src/sampleConfig/default.yml","M Gemfile","M Gemfile.lock","M lib/crypt/command.rb","M test/crypt_test.rb","M tmp/appmap/minitest/Crypt_crypt_aes_128_cbc.appmap.json","M tmp/appmap/minitest/Crypt_crypt_aes_256_gcm.appmap.json","?? ../../../../../cli/src/cmds/sequenceDiagram/","?? ../../../../doc/labels/crypto.decrypt.md","?? ../../../../doc/labels/crypto.set_key.md","?? ../../../../doc/labels/string.unpack.md","?? ../../../../doc/rules/hard-coded-key.md","?? ../../../../src/rules/hard-coded-key/","?? ../../../scanner/hardCodedKey.spec.ts"],"git_last_annotated_tag":"@appland/appmap-v2.3.4","git_last_tag":"@appland/scanner-v1.63.0","git_commits_since_last_annotated_tag":0,"git_commits_since_last_tag":0},"name":"Crypt crypt aes 128 cbc","source_location":"test/crypt_test.rb:6","frameworks":[{"name":"minitest","version":"5.16.2"}],"recorder":{"name":"minitest"},"test_status":"succeeded"},"classMap":[{"name":"lib/crypt","type":"package","children":[{"name":"Crypt","type":"class","children":[{"name":"Command","type":"class","children":[{"name":"encrypt_aes_128_cbc","type":"function","location":"lib/crypt/command.rb:4","static":true,"labels":["command"],"comment":"# @label command\n"}]}]}]},{"name":"openssl","type":"package","children":[{"name":"OpenSSL","type":"class","children":[{"name":"Cipher","type":"class","children":[{"name":"encrypt","type":"function","location":"OpenSSL::Cipher#encrypt","static":false,"labels":["crypto.encrypt"]},{"name":"random_key","type":"function","location":"/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb:43","static":false,"comment":"# call-seq:\n# cipher.random_key -> key\n#\n# Generate a random key with OpenSSL::Random.random_bytes and sets it to\n# the cipher, and returns it.\n#\n# You must call #encrypt or #decrypt before calling this method.\n"},{"name":"key=","type":"function","location":"OpenSSL::Cipher#key=","static":false,"labels":["crypto.set_key"]},{"name":"random_iv","type":"function","location":"/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb:55","static":false,"comment":"# call-seq:\n# cipher.random_iv -> iv\n#\n# Generate a random IV with OpenSSL::Random.random_bytes and sets it to the\n# cipher, and returns it.\n#\n# You must call #encrypt or #decrypt before calling this method.\n"},{"name":"iv=","type":"function","location":"OpenSSL::Cipher#iv=","static":false,"labels":["crypto.set_iv"]},{"name":"update","type":"function","location":"OpenSSL::Cipher#update","static":false},{"name":"final","type":"function","location":"OpenSSL::Cipher#final","static":false,"labels":["crypto.final"]}]},{"name":"Random","type":"class","children":[{"name":"random_bytes","type":"function","location":"OpenSSL::Random.random_bytes","static":true,"labels":["random.secure"]}]}]}]}],"events":[{"id":21,"event":"call","thread_id":2400,"defined_class":"Crypt::Command","method_id":"encrypt_aes_128_cbc","path":"lib/crypt/command.rb","lineno":4,"static":true,"parameters":[],"receiver":{"class":"Class","object_id":2460,"value":"Crypt::Command"}},{"id":22,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Cipher","method_id":"encrypt","path":"OpenSSL::Cipher#encrypt","static":false,"parameters":[{"name":"arg","class":"Array","object_id":2660,"value":"[]","kind":"rest","size":0}],"receiver":{"class":"OpenSSL::Cipher","object_id":2680,"value":"#","labels":["crypto.algorithm.AES-128-CBC"]}},{"id":23,"event":"return","thread_id":2400,"parent_id":22,"elapsed":4.00003045797348e-06,"return_value":{"class":"OpenSSL::Cipher","value":"#","object_id":2680}},{"id":24,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Cipher","method_id":"random_key","path":"/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb","lineno":43,"static":false,"parameters":[],"receiver":{"class":"OpenSSL::Cipher","object_id":2680,"value":"#"}},{"id":25,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Random","method_id":"random_bytes","path":"OpenSSL::Random.random_bytes","static":true,"parameters":[{"name":"arg","class":"Integer","object_id":33,"value":"16","kind":"req"}],"receiver":{"class":"Module","object_id":2520,"value":"OpenSSL::Random"}},{"id":26,"event":"return","thread_id":2400,"parent_id":25,"elapsed":2.400018274784088e-05,"return_value":{"class":"String","value":"_(__h_?5__{____#","object_id":2700}},{"id":27,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Cipher","method_id":"key=","path":"OpenSSL::Cipher#key=","static":false,"parameters":[{"name":"arg","class":"String","object_id":2700,"value":"_(__h_?5__{____#","kind":"req"}],"receiver":{"class":"OpenSSL::Cipher","object_id":2680,"value":"#"}},{"id":28,"event":"return","thread_id":2400,"parent_id":27,"elapsed":2.00001522898674e-06,"return_value":{"class":"String","value":"_(__h_?5__{____#","object_id":2700}},{"id":29,"event":"return","thread_id":2400,"parent_id":24,"elapsed":0.00012199999764561653,"return_value":{"class":"String","value":"_(__h_?5__{____#","object_id":2700}},{"id":30,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Cipher","method_id":"random_iv","path":"/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb","lineno":55,"static":false,"parameters":[],"receiver":{"class":"OpenSSL::Cipher","object_id":2680,"value":"#"}},{"id":31,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Random","method_id":"random_bytes","path":"OpenSSL::Random.random_bytes","static":true,"parameters":[{"name":"arg","class":"Integer","object_id":33,"value":"16","kind":"req"}],"receiver":{"class":"Module","object_id":2520,"value":"OpenSSL::Random"}},{"id":32,"event":"return","thread_id":2400,"parent_id":31,"elapsed":2.9997900128364563e-06,"return_value":{"class":"String","value":"__i_~_#__{X_\u0007\"__","object_id":2720}},{"id":33,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Cipher","method_id":"iv=","path":"OpenSSL::Cipher#iv=","static":false,"parameters":[{"name":"arg","class":"String","object_id":2720,"value":"__i_~_#__{X_\u0007\"__","kind":"req"}],"receiver":{"class":"OpenSSL::Cipher","object_id":2680,"value":"#"}},{"id":34,"event":"return","thread_id":2400,"parent_id":33,"elapsed":2.00001522898674e-06,"return_value":{"class":"String","value":"__i_~_#__{X_\u0007\"__","object_id":2720}},{"id":35,"event":"return","thread_id":2400,"parent_id":30,"elapsed":8.299993351101875e-05,"return_value":{"class":"String","value":"__i_~_#__{X_\u0007\"__","object_id":2720}},{"id":36,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Cipher","method_id":"update","path":"OpenSSL::Cipher#update","static":false,"parameters":[{"name":"arg","class":"Array","object_id":2740,"value":"[Very, very confidential data]","kind":"rest","size":1}],"receiver":{"class":"OpenSSL::Cipher","object_id":2680,"value":"#"}},{"id":37,"event":"return","thread_id":2400,"parent_id":36,"elapsed":6.9998204708099365e-06,"return_value":{"class":"String","value":"_\u0013_\u00117____6\u0006#_(|_","object_id":2760}},{"id":38,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Cipher","method_id":"final","path":"OpenSSL::Cipher#final","static":false,"parameters":[],"receiver":{"class":"OpenSSL::Cipher","object_id":2680,"value":"#"}},{"id":39,"event":"return","thread_id":2400,"parent_id":38,"elapsed":9.999843314290047e-06,"return_value":{"class":"String","value":"_L___\u0002__r[__3_\u0019_","object_id":2780}},{"id":40,"event":"return","thread_id":2400,"parent_id":21,"elapsed":0.0005129999481141567,"return_value":{"class":"String","value":"_\u0013_\u00117____6\u0006#_(|__L___\u0002__r[__3_\u0019_","object_id":2800}}]} \ No newline at end of file diff --git a/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_256_gcm.appmap.json b/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_256_gcm.appmap.json index bfb9304711..38236ab851 100644 --- a/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_256_gcm.appmap.json +++ b/packages/scanner/test/fixtures/ruby/fixture/tmp/appmap/minitest/Crypt_crypt_aes_256_gcm.appmap.json @@ -1,401 +1 @@ -{ - "version": "1.7.0", - "metadata": { - "app": "scanner/ruby-fixture", - "language": { "name": "ruby", "engine": "ruby", "version": "3.0.1" }, - "client": { - "name": "appmap", - "url": "https://github.com/applandinc/appmap-ruby", - "version": "0.83.4" - }, - "git": { - "repository": "git@github.com:applandinc/appmap-js.git", - "branch": "feat/rule-unauthenticated-encryption", - "commit": "9f81b2cf8ad2313ee5751a41719edc321c6e9227", - "status": ["?? ../../../../../cli/src/cmds/sequenceDiagram/"], - "git_last_annotated_tag": "@appland/appmap-v2.3.4", - "git_last_tag": "@appland/scanner-v1.63.0", - "git_commits_since_last_annotated_tag": 0, - "git_commits_since_last_tag": 0 - }, - "name": "Crypt crypt aes 256 gcm", - "source_location": "test/crypt_test.rb:11", - "frameworks": [{ "name": "minitest", "version": "5.16.2" }], - "recorder": { "name": "minitest" }, - "test_status": "succeeded" - }, - "classMap": [ - { - "name": "lib/crypt", - "type": "package", - "children": [ - { - "name": "Crypt", - "type": "class", - "children": [ - { - "name": "Command", - "type": "class", - "children": [ - { - "name": "encrypt_aes_256_gcm", - "type": "function", - "location": "lib/crypt/command.rb:15", - "static": true, - "labels": ["command"], - "comment": "# @label command\n" - } - ] - } - ] - } - ] - }, - { - "name": "openssl", - "type": "package", - "children": [ - { - "name": "OpenSSL", - "type": "class", - "children": [ - { - "name": "Cipher", - "type": "class", - "children": [ - { - "name": "encrypt", - "type": "function", - "location": "OpenSSL::Cipher#encrypt", - "static": false, - "labels": ["crypto.encrypt"] - }, - { - "name": "random_key", - "type": "function", - "location": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb:43", - "static": false, - "comment": "# call-seq:\n# cipher.random_key -> key\n#\n# Generate a random key with OpenSSL::Random.random_bytes and sets it to\n# the cipher, and returns it.\n#\n# You must call #encrypt or #decrypt before calling this method.\n" - }, - { - "name": "key=", - "type": "function", - "location": "OpenSSL::Cipher#key=", - "static": false, - "labels": ["crypto.set_key"] - }, - { - "name": "random_iv", - "type": "function", - "location": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb:55", - "static": false, - "comment": "# call-seq:\n# cipher.random_iv -> iv\n#\n# Generate a random IV with OpenSSL::Random.random_bytes and sets it to the\n# cipher, and returns it.\n#\n# You must call #encrypt or #decrypt before calling this method.\n" - }, - { - "name": "iv=", - "type": "function", - "location": "OpenSSL::Cipher#iv=", - "static": false, - "labels": ["crypto.set_iv"] - }, - { - "name": "auth_data=", - "type": "function", - "location": "OpenSSL::Cipher#auth_data=", - "static": false, - "labels": ["crypto.set_auth_data"] - }, - { - "name": "update", - "type": "function", - "location": "OpenSSL::Cipher#update", - "static": false - }, - { - "name": "final", - "type": "function", - "location": "OpenSSL::Cipher#final", - "static": false, - "labels": ["crypto.final"] - } - ] - } - ] - } - ] - } - ], - "events": [ - { - "id": 1, - "event": "call", - "thread_id": 2320, - "defined_class": "Crypt::Command", - "method_id": "encrypt_aes_256_gcm", - "path": "lib/crypt/command.rb", - "lineno": 15, - "static": true, - "parameters": [ - { - "name": "record_id", - "class": "Symbol", - "object_id": 1869148, - "value": ":record_id", - "kind": "req" - } - ], - "receiver": { "class": "Class", "object_id": 2300, "value": "Crypt::Command" } - }, - { - "id": 2, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "encrypt", - "path": "OpenSSL::Cipher#encrypt", - "static": false, - "parameters": [ - { - "name": "arg", - "class": "Array", - "object_id": 2340, - "value": "[]", - "kind": "rest", - "size": 0 - } - ], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2360, - "value": "#", - "labels": ["crypto.algorithm.id-aes256-GCM"] - } - }, - { - "id": 3, - "event": "return", - "thread_id": 2320, - "parent_id": 2, - "elapsed": 3.00002284348011e-6, - "return_value": { - "class": "OpenSSL::Cipher", - "value": "#", - "object_id": 2360 - } - }, - { - "id": 4, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "random_key", - "path": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb", - "lineno": 43, - "static": false, - "parameters": [], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2360, - "value": "#" - } - }, - { - "id": 5, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "key=", - "path": "OpenSSL::Cipher#key=", - "static": false, - "parameters": [ - { - "name": "arg", - "class": "String", - "object_id": 2380, - "value": "_E_H__IKu_b___n:_}___C_s_\u000b'_\"3 _", - "kind": "req" - } - ], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2360, - "value": "#" - } - }, - { - "id": 6, - "event": "return", - "thread_id": 2320, - "parent_id": 5, - "elapsed": 1.00000761449337e-5, - "return_value": { - "class": "String", - "value": "_E_H__IKu_b___n:_}___C_s_\u000b'_\"3 _", - "object_id": 2380 - } - }, - { - "id": 7, - "event": "return", - "thread_id": 2320, - "parent_id": 4, - "elapsed": 0.0012940000742673874, - "return_value": { - "class": "String", - "value": "_E_H__IKu_b___n:_}___C_s_\u000b'_\"3 _", - "object_id": 2380 - } - }, - { - "id": 8, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "random_iv", - "path": "/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb", - "lineno": 55, - "static": false, - "parameters": [], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2360, - "value": "#" - } - }, - { - "id": 9, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "iv=", - "path": "OpenSSL::Cipher#iv=", - "static": false, - "parameters": [ - { - "name": "arg", - "class": "String", - "object_id": 2400, - "value": "______\tW'_\u0017,", - "kind": "req" - } - ], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2360, - "value": "#" - } - }, - { - "id": 10, - "event": "return", - "thread_id": 2320, - "parent_id": 9, - "elapsed": 2.00001522898674e-6, - "return_value": { "class": "String", "value": "______\tW'_\u0017,", "object_id": 2400 } - }, - { - "id": 11, - "event": "return", - "thread_id": 2320, - "parent_id": 8, - "elapsed": 9.100022725760937e-5, - "return_value": { "class": "String", "value": "______\tW'_\u0017,", "object_id": 2400 } - }, - { - "id": 12, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "auth_data=", - "path": "OpenSSL::Cipher#auth_data=", - "static": false, - "parameters": [ - { "name": "arg", "class": "String", "object_id": 2420, "value": "record_id", "kind": "req" } - ], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2360, - "value": "#" - } - }, - { - "id": 13, - "event": "return", - "thread_id": 2320, - "parent_id": 12, - "elapsed": 3.00002284348011e-6, - "return_value": { "class": "String", "value": "record_id", "object_id": 2420 } - }, - { - "id": 14, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "update", - "path": "OpenSSL::Cipher#update", - "static": false, - "parameters": [ - { - "name": "arg", - "class": "Array", - "object_id": 2440, - "value": "[Very, very confidential data]", - "kind": "rest", - "size": 1 - } - ], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2360, - "value": "#" - } - }, - { - "id": 15, - "event": "return", - "thread_id": 2320, - "parent_id": 14, - "elapsed": 8.00006091594696e-6, - "return_value": { - "class": "String", - "value": "}N=__\u0002_V__M__\u0013\f\u000f_Q__y_0____I", - "object_id": 2460 - } - }, - { - "id": 16, - "event": "call", - "thread_id": 2320, - "defined_class": "OpenSSL::Cipher", - "method_id": "final", - "path": "OpenSSL::Cipher#final", - "static": false, - "parameters": [], - "receiver": { - "class": "OpenSSL::Cipher", - "object_id": 2360, - "value": "#" - } - }, - { - "id": 17, - "event": "return", - "thread_id": 2320, - "parent_id": 16, - "elapsed": 3.00002284348011e-6, - "return_value": { "class": "String", "value": "", "object_id": 2480 } - }, - { - "id": 18, - "event": "return", - "thread_id": 2320, - "parent_id": 1, - "elapsed": 0.0017880001105368137, - "return_value": { - "class": "String", - "value": "}N=__\u0002_V__M__\u0013\f\u000f_Q__y_0____I", - "object_id": 2500 - } - } - ] -} +{"version":"1.7.0","metadata":{"app":"scanner/ruby-fixture","language":{"name":"ruby","engine":"ruby","version":"3.0.1"},"client":{"name":"appmap","url":"https://github.com/applandinc/appmap-ruby","version":"0.83.4"},"git":{"repository":"git@github.com:applandinc/appmap-js.git","branch":"feat/hard-coded-password","commit":"f1111d3402ff3725ccc6545aa963fb7653ae624b","status":["M ../../../../doc/labels/crypto.encrypt.md","M ../../../../src/sampleConfig/default.yml","M Gemfile","M Gemfile.lock","M lib/crypt/command.rb","M test/crypt_test.rb","M tmp/appmap/minitest/Crypt_crypt_aes_128_cbc.appmap.json","M tmp/appmap/minitest/Crypt_crypt_aes_256_gcm.appmap.json","?? ../../../../../cli/src/cmds/sequenceDiagram/","?? ../../../../doc/labels/crypto.decrypt.md","?? ../../../../doc/labels/crypto.set_key.md","?? ../../../../doc/labels/string.unpack.md","?? ../../../../doc/rules/hard-coded-key.md","?? ../../../../src/rules/hard-coded-key/","?? ../../../scanner/hardCodedKey.spec.ts"],"git_last_annotated_tag":"@appland/appmap-v2.3.4","git_last_tag":"@appland/scanner-v1.63.0","git_commits_since_last_annotated_tag":0,"git_commits_since_last_tag":0},"name":"Crypt crypt aes 256 gcm","source_location":"test/crypt_test.rb:11","frameworks":[{"name":"minitest","version":"5.16.2"}],"recorder":{"name":"minitest"},"test_status":"succeeded"},"classMap":[{"name":"lib/crypt","type":"package","children":[{"name":"Crypt","type":"class","children":[{"name":"Command","type":"class","children":[{"name":"encrypt_aes_256_gcm","type":"function","location":"lib/crypt/command.rb:15","static":true,"labels":["command"],"comment":"# @label command\n"}]}]}]},{"name":"openssl","type":"package","children":[{"name":"OpenSSL","type":"class","children":[{"name":"Cipher","type":"class","children":[{"name":"encrypt","type":"function","location":"OpenSSL::Cipher#encrypt","static":false,"labels":["crypto.encrypt"]},{"name":"random_key","type":"function","location":"/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb:43","static":false,"comment":"# call-seq:\n# cipher.random_key -> key\n#\n# Generate a random key with OpenSSL::Random.random_bytes and sets it to\n# the cipher, and returns it.\n#\n# You must call #encrypt or #decrypt before calling this method.\n"},{"name":"key=","type":"function","location":"OpenSSL::Cipher#key=","static":false,"labels":["crypto.set_key"]},{"name":"random_iv","type":"function","location":"/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb:55","static":false,"comment":"# call-seq:\n# cipher.random_iv -> iv\n#\n# Generate a random IV with OpenSSL::Random.random_bytes and sets it to the\n# cipher, and returns it.\n#\n# You must call #encrypt or #decrypt before calling this method.\n"},{"name":"iv=","type":"function","location":"OpenSSL::Cipher#iv=","static":false,"labels":["crypto.set_iv"]},{"name":"auth_data=","type":"function","location":"OpenSSL::Cipher#auth_data=","static":false,"labels":["crypto.set_auth_data"]},{"name":"update","type":"function","location":"OpenSSL::Cipher#update","static":false},{"name":"final","type":"function","location":"OpenSSL::Cipher#final","static":false,"labels":["crypto.final"]}]},{"name":"Random","type":"class","children":[{"name":"random_bytes","type":"function","location":"OpenSSL::Random.random_bytes","static":true,"labels":["random.secure"]}]}]}]}],"events":[{"id":41,"event":"call","thread_id":2400,"defined_class":"Crypt::Command","method_id":"encrypt_aes_256_gcm","path":"lib/crypt/command.rb","lineno":15,"static":true,"parameters":[{"name":"record_id","class":"String","object_id":2840,"value":"test_crypt_aes_256_gcm","kind":"req"},{"name":"key","class":"NilClass","object_id":8,"value":null,"kind":"key"}],"receiver":{"class":"Class","object_id":2460,"value":"Crypt::Command"}},{"id":42,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Cipher","method_id":"encrypt","path":"OpenSSL::Cipher#encrypt","static":false,"parameters":[{"name":"arg","class":"Array","object_id":2860,"value":"[]","kind":"rest","size":0}],"receiver":{"class":"OpenSSL::Cipher","object_id":2880,"value":"#","labels":["crypto.algorithm.id-aes256-GCM"]}},{"id":43,"event":"return","thread_id":2400,"parent_id":42,"elapsed":2.00001522898674e-06,"return_value":{"class":"OpenSSL::Cipher","value":"#","object_id":2880}},{"id":44,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Cipher","method_id":"random_key","path":"/Users/kgilpin/.rbenv/versions/3.0.1/lib/ruby/3.0.0/openssl/cipher.rb","lineno":43,"static":false,"parameters":[],"receiver":{"class":"OpenSSL::Cipher","object_id":2880,"value":"#"}},{"id":45,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Random","method_id":"random_bytes","path":"OpenSSL::Random.random_bytes","static":true,"parameters":[{"name":"arg","class":"Integer","object_id":65,"value":"32","kind":"req"}],"receiver":{"class":"Module","object_id":2520,"value":"OpenSSL::Random"}},{"id":46,"event":"return","thread_id":2400,"parent_id":45,"elapsed":6.00004568696022e-06,"return_value":{"class":"String","value":"c_la\f._.\u0005_\u001b___Qf\u0017b__\u000f__"}},{"id":48,"event":"return","thread_id":2400,"parent_id":47,"elapsed":2.00001522898674e-06,"return_value":{"class":"String","value":"c_la\f._.\u0005_\u001b___Qf\u0017b__\u000f__"}},{"id":51,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Random","method_id":"random_bytes","path":"OpenSSL::Random.random_bytes","static":true,"parameters":[{"name":"arg","class":"Integer","object_id":25,"value":"12","kind":"req"}],"receiver":{"class":"Module","object_id":2520,"value":"OpenSSL::Random"}},{"id":52,"event":"return","thread_id":2400,"parent_id":51,"elapsed":3.00002284348011e-06,"return_value":{"class":"String","value":"__\b__'W__7_\u000b","object_id":2920}},{"id":53,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Cipher","method_id":"iv=","path":"OpenSSL::Cipher#iv=","static":false,"parameters":[{"name":"arg","class":"String","object_id":2920,"value":"__\b__'W__7_\u000b","kind":"req"}],"receiver":{"class":"OpenSSL::Cipher","object_id":2880,"value":"#"}},{"id":54,"event":"return","thread_id":2400,"parent_id":53,"elapsed":1.00000761449337e-06,"return_value":{"class":"String","value":"__\b__'W__7_\u000b","object_id":2920}},{"id":55,"event":"return","thread_id":2400,"parent_id":50,"elapsed":6.3000014051795e-05,"return_value":{"class":"String","value":"__\b__'W__7_\u000b","object_id":2920}},{"id":56,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Cipher","method_id":"auth_data=","path":"OpenSSL::Cipher#auth_data=","static":false,"parameters":[{"name":"arg","class":"String","object_id":2840,"value":"test_crypt_aes_256_gcm","kind":"req"}],"receiver":{"class":"OpenSSL::Cipher","object_id":2880,"value":"#"}},{"id":57,"event":"return","thread_id":2400,"parent_id":56,"elapsed":2.00001522898674e-06,"return_value":{"class":"String","value":"test_crypt_aes_256_gcm","object_id":2840}},{"id":58,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Cipher","method_id":"update","path":"OpenSSL::Cipher#update","static":false,"parameters":[{"name":"arg","class":"Array","object_id":2940,"value":"[Very, very confidential data]","kind":"rest","size":1}],"receiver":{"class":"OpenSSL::Cipher","object_id":2880,"value":"#"}},{"id":59,"event":"return","thread_id":2400,"parent_id":58,"elapsed":2.00001522898674e-06,"return_value":{"class":"String","value":"\bnP\u001f__ SezK\u0012\f\u001e\r__jD_\u0015__UD\u001a+_","object_id":2960}},{"id":60,"event":"call","thread_id":2400,"defined_class":"OpenSSL::Cipher","method_id":"final","path":"OpenSSL::Cipher#final","static":false,"parameters":[],"receiver":{"class":"OpenSSL::Cipher","object_id":2880,"value":"#"}},{"id":61,"event":"return","thread_id":2400,"parent_id":60,"elapsed":2.00001522898674e-06,"return_value":{"class":"String","value":"","object_id":2980}},{"id":62,"event":"return","thread_id":2400,"parent_id":41,"elapsed":0.00036300020292401314,"return_value":{"class":"String","value":"\bnP\u001f__ SezK\u0012\f\u001e\r__jD_\u0015__UD\u001a+_","object_id":3000}}]} \ No newline at end of file From 13036b6111bcfed326098ebacdd489c67c299481 Mon Sep 17 00:00:00 2001 From: Kevin Gilpin Date: Tue, 2 Aug 2022 16:46:02 -0400 Subject: [PATCH 6/7] feat: Rule for hard-coded key --- packages/scanner/doc/labels/crypto.decrypt.md | 7 +++ packages/scanner/doc/labels/crypto.encrypt.md | 1 + packages/scanner/doc/labels/crypto.set_key.md | 7 +++ packages/scanner/doc/labels/string.unpack.md | 12 +++++ packages/scanner/doc/rules/hard-coded-key.md | 14 ++++++ .../src/rules/hard-coded-key/metadata.ts | 12 +++++ .../scanner/src/rules/hard-coded-key/rule.ts | 44 +++++++++++++++++++ packages/scanner/src/sampleConfig/default.yml | 1 + .../scanner/test/scanner/hardCodedKey.spec.ts | 29 ++++++++++++ 9 files changed, 127 insertions(+) create mode 100644 packages/scanner/doc/labels/crypto.decrypt.md create mode 100644 packages/scanner/doc/labels/crypto.set_key.md create mode 100644 packages/scanner/doc/labels/string.unpack.md create mode 100644 packages/scanner/doc/rules/hard-coded-key.md create mode 100644 packages/scanner/src/rules/hard-coded-key/metadata.ts create mode 100644 packages/scanner/src/rules/hard-coded-key/rule.ts create mode 100644 packages/scanner/test/scanner/hardCodedKey.spec.ts diff --git a/packages/scanner/doc/labels/crypto.decrypt.md b/packages/scanner/doc/labels/crypto.decrypt.md new file mode 100644 index 0000000000..456bd1af18 --- /dev/null +++ b/packages/scanner/doc/labels/crypto.decrypt.md @@ -0,0 +1,7 @@ +--- +name: crypto.decrypt +rules: + - hard-coded-key +--- + +A function that performs decryption. diff --git a/packages/scanner/doc/labels/crypto.encrypt.md b/packages/scanner/doc/labels/crypto.encrypt.md index 547151020d..3ba15eeb62 100644 --- a/packages/scanner/doc/labels/crypto.encrypt.md +++ b/packages/scanner/doc/labels/crypto.encrypt.md @@ -1,6 +1,7 @@ --- name: crypto.encrypt rules: + - hard-coded-key - unauthenticated-encryption --- diff --git a/packages/scanner/doc/labels/crypto.set_key.md b/packages/scanner/doc/labels/crypto.set_key.md new file mode 100644 index 0000000000..7b4b4cb6ed --- /dev/null +++ b/packages/scanner/doc/labels/crypto.set_key.md @@ -0,0 +1,7 @@ +--- +name: crypto.set_key +rules: + - hard-coded-key +--- + +A function that sets the crytographic key for encryption, decryption, or signature. diff --git a/packages/scanner/doc/labels/string.unpack.md b/packages/scanner/doc/labels/string.unpack.md new file mode 100644 index 0000000000..9a623158f3 --- /dev/null +++ b/packages/scanner/doc/labels/string.unpack.md @@ -0,0 +1,12 @@ +--- +name: string.unpack +rules: + - hard-coded-key +--- + +Unpacks a string into binary from a printable encoding such as hex or Base64. + +## Examples + +- Ruby [String#unpack](https://ruby-doc.org/core-3.1.2/String.html#method-i-unpack) +- Ruby [String#unpack1](https://ruby-doc.org/core-3.1.2/String.html#method-i-unpack1) diff --git a/packages/scanner/doc/rules/hard-coded-key.md b/packages/scanner/doc/rules/hard-coded-key.md new file mode 100644 index 0000000000..7b5b24ff1a --- /dev/null +++ b/packages/scanner/doc/rules/hard-coded-key.md @@ -0,0 +1,14 @@ +--- +rule: hard-coded-key +name: Hard coded key +title: Hard-coded key +references: + A02:2021: https://owasp.org/Top10/A02_2021-Cryptographic_Failures/ +impactDomain: Security +labels: + - crypto.encrypt + - crypto.decrypt + - crypto.set_key + - string.unpack +scope: root +--- \ No newline at end of file diff --git a/packages/scanner/src/rules/hard-coded-key/metadata.ts b/packages/scanner/src/rules/hard-coded-key/metadata.ts new file mode 100644 index 0000000000..9835346136 --- /dev/null +++ b/packages/scanner/src/rules/hard-coded-key/metadata.ts @@ -0,0 +1,12 @@ +import { Metadata } from '../lib/metadata'; + +export default { + title: 'Hard-coded key', + scope: 'root', + enumerateScope: true, + impactDomain: 'Security', + references: { + 'A02:2021': 'https://owasp.org/Top10/A02_2021-Cryptographic_Failures/', + }, + labels: ['crypto.encrypt', 'crypto.decrypt', 'crypto.set_key', 'string.unpack'], +} as Metadata; diff --git a/packages/scanner/src/rules/hard-coded-key/rule.ts b/packages/scanner/src/rules/hard-coded-key/rule.ts new file mode 100644 index 0000000000..6e2ec1bdcc --- /dev/null +++ b/packages/scanner/src/rules/hard-coded-key/rule.ts @@ -0,0 +1,44 @@ +import { Event } from '@appland/models'; +import { AppMapIndex, MatcherResult, RuleLogic } from '../../types'; + +function matcher(event: Event, appMapIndex: AppMapIndex): MatcherResult { + if (!event.receiver) return; + + const receiverObjectId = event.receiver.object_id; + const setKey = appMapIndex.appMap.events.find( + (evt) => + evt.isCall() && + evt.receiver?.object_id === receiverObjectId && + evt.labels.has('crypto.set_key') + ); + if (!setKey) return; + + const keyObject = setKey.parameters![0]; + if (!keyObject) return; + + const keyObjectId = keyObject.object_id; + const obtainKey = appMapIndex.appMap.events.find( + (evt) => + evt.isReturn() && + evt !== setKey.returnEvent && + !evt.codeObject.labels.has('string.unpack') && + evt.returnValue?.object_id === keyObjectId + ); + if (!obtainKey) { + return [ + { + level: 'warning', + event, + message: `Cryptographic key is not obtained from a function, and may be hard-coded`, + participatingEvents: { 'crypto.set_key': setKey }, + }, + ]; + } +} + +export default function rule(): RuleLogic { + return { + matcher, + where: (e: Event) => e.labels.has('crypto.encrypt'), + }; +} diff --git a/packages/scanner/src/sampleConfig/default.yml b/packages/scanner/src/sampleConfig/default.yml index 97cf07ee15..01badedb72 100644 --- a/packages/scanner/src/sampleConfig/default.yml +++ b/packages/scanner/src/sampleConfig/default.yml @@ -3,6 +3,7 @@ checks: # - rule: circular-dependency - rule: deserialization-of-untrusted-data - rule: exec-of-untrusted-command + - rule: hard-coded-key - rule: http-500 # - rule: illegal-package-dependency # - rule: incompatible-http-client-request diff --git a/packages/scanner/test/scanner/hardCodedKey.spec.ts b/packages/scanner/test/scanner/hardCodedKey.spec.ts new file mode 100644 index 0000000000..60e0cf13fa --- /dev/null +++ b/packages/scanner/test/scanner/hardCodedKey.spec.ts @@ -0,0 +1,29 @@ +import Check from '../../src/check'; +import { loadRule } from '../../src/configuration/configurationProvider'; +import { scan } from '../util'; + +describe('hard-coded key', () => { + it('found', async () => { + const rule = await loadRule('hard-coded-key'); + const { findings } = await scan( + new Check(rule), + './ruby/fixture/tmp/appmap/minitest/Crypt_hard_coded_key.appmap.json' + ); + expect(findings).toHaveLength(1); + const finding1 = findings[0]; + expect(finding1.ruleId).toEqual('hard-coded-key'); + expect(finding1.event.id).toEqual(4); + expect(finding1.message).toEqual( + `Cryptographic key is not obtained from a function, and may be hard-coded` + ); + expect(finding1.relatedEvents).toHaveLength(2); + }); + it('not found', async () => { + const rule = await loadRule('hard-coded-key'); + const { findings } = await scan( + new Check(rule), + './ruby/fixture/tmp/appmap/minitest/Crypt_random_key.appmap.json' + ); + expect(findings).toHaveLength(0); + }); +}); From fcb474d636f3b7fb0d01fad09ce8f30ed1e0e881 Mon Sep 17 00:00:00 2001 From: Kevin Gilpin Date: Tue, 2 Aug 2022 17:41:21 -0400 Subject: [PATCH 7/7] fixup! feat: Rule for hard-coded key --- packages/scanner/doc/rules/hard-coded-key.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/scanner/doc/rules/hard-coded-key.md b/packages/scanner/doc/rules/hard-coded-key.md index 7b5b24ff1a..6967de34c0 100644 --- a/packages/scanner/doc/rules/hard-coded-key.md +++ b/packages/scanner/doc/rules/hard-coded-key.md @@ -10,5 +10,8 @@ labels: - crypto.decrypt - crypto.set_key - string.unpack -scope: root ---- \ No newline at end of file +--- + +Finds occurrances in which a cryptographic key is used that is not provided by a function. + +`string.unpack` functions are an exception, because they are only modifying, not creating, the key.