scanoss 1.23.0__py3-none-any.whl → 1.25.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scanoss/__init__.py +1 -1
- scanoss/cli.py +195 -34
- scanoss/constants.py +2 -0
- scanoss/cryptography.py +274 -0
- scanoss/data/build_date.txt +1 -1
- scanoss/inspection/policy_check.py +77 -47
- scanoss/scanossgrpc.py +87 -33
- scanoss/utils/file.py +2 -2
- scanoss/winnowing.py +64 -7
- {scanoss-1.23.0.dist-info → scanoss-1.25.0.dist-info}/METADATA +1 -1
- {scanoss-1.23.0.dist-info → scanoss-1.25.0.dist-info}/RECORD +15 -14
- {scanoss-1.23.0.dist-info → scanoss-1.25.0.dist-info}/WHEEL +1 -1
- {scanoss-1.23.0.dist-info → scanoss-1.25.0.dist-info}/entry_points.txt +0 -0
- {scanoss-1.23.0.dist-info → scanoss-1.25.0.dist-info}/licenses/LICENSE +0 -0
- {scanoss-1.23.0.dist-info → scanoss-1.25.0.dist-info}/top_level.txt +0 -0
scanoss/__init__.py
CHANGED
scanoss/cli.py
CHANGED
|
@@ -31,6 +31,7 @@ from typing import List
|
|
|
31
31
|
|
|
32
32
|
import pypac
|
|
33
33
|
|
|
34
|
+
from scanoss.cryptography import Cryptography, create_cryptography_config_from_args
|
|
34
35
|
from scanoss.scanners.container_scanner import (
|
|
35
36
|
DEFAULT_SYFT_COMMAND,
|
|
36
37
|
DEFAULT_SYFT_TIMEOUT,
|
|
@@ -50,6 +51,7 @@ from scanoss.scanossgrpc import (
|
|
|
50
51
|
from . import __version__
|
|
51
52
|
from .components import Components
|
|
52
53
|
from .constants import (
|
|
54
|
+
DEFAULT_API_TIMEOUT,
|
|
53
55
|
DEFAULT_POST_SIZE,
|
|
54
56
|
DEFAULT_RETRY,
|
|
55
57
|
DEFAULT_TIMEOUT,
|
|
@@ -292,15 +294,6 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
292
294
|
help='component sub-commands',
|
|
293
295
|
)
|
|
294
296
|
|
|
295
|
-
# Component Sub-command: component crypto
|
|
296
|
-
c_crypto = comp_sub.add_parser(
|
|
297
|
-
'crypto',
|
|
298
|
-
aliases=['cr'],
|
|
299
|
-
description=f'Show Cryptographic algorithms: {__version__}',
|
|
300
|
-
help='Retrieve cryptographic algorithms for the given components',
|
|
301
|
-
)
|
|
302
|
-
c_crypto.set_defaults(func=comp_crypto)
|
|
303
|
-
|
|
304
297
|
# Component Sub-command: component vulns
|
|
305
298
|
c_vulns = comp_sub.add_parser(
|
|
306
299
|
'vulns',
|
|
@@ -361,18 +354,76 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
361
354
|
c_versions.add_argument('--limit', '-l', type=int, help='Generic component search')
|
|
362
355
|
c_versions.set_defaults(func=comp_versions)
|
|
363
356
|
|
|
357
|
+
# Sub-command: crypto
|
|
358
|
+
p_crypto = subparsers.add_parser(
|
|
359
|
+
'crypto',
|
|
360
|
+
aliases=['cr'],
|
|
361
|
+
description=f'SCANOSS Crypto commands: {__version__}',
|
|
362
|
+
help='Crypto support commands',
|
|
363
|
+
)
|
|
364
|
+
crypto_sub = p_crypto.add_subparsers(
|
|
365
|
+
title='Crypto Commands',
|
|
366
|
+
dest='subparsercmd',
|
|
367
|
+
description='crypto sub-commands',
|
|
368
|
+
help='crypto sub-commands',
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
# GetAlgorithms and GetAlgorithmsInRange gRPC APIs
|
|
372
|
+
p_crypto_algorithms = crypto_sub.add_parser(
|
|
373
|
+
'algorithms',
|
|
374
|
+
aliases=['alg'],
|
|
375
|
+
description=f'Show Cryptographic algorithms: {__version__}',
|
|
376
|
+
help='Retrieve cryptographic algorithms for the given components',
|
|
377
|
+
)
|
|
378
|
+
p_crypto_algorithms.add_argument(
|
|
379
|
+
'--with-range',
|
|
380
|
+
action='store_true',
|
|
381
|
+
help='Returns the list of versions in the specified range that contains cryptographic algorithms',
|
|
382
|
+
)
|
|
383
|
+
p_crypto_algorithms.set_defaults(func=crypto_algorithms)
|
|
384
|
+
|
|
385
|
+
# GetEncryptionHints and GetHintsInRange gRPC APIs
|
|
386
|
+
p_crypto_hints = crypto_sub.add_parser(
|
|
387
|
+
'hints',
|
|
388
|
+
description=f'Show Encryption hints: {__version__}',
|
|
389
|
+
help='Retrieve encryption hints for the given components',
|
|
390
|
+
)
|
|
391
|
+
p_crypto_hints.add_argument(
|
|
392
|
+
'--with-range',
|
|
393
|
+
action='store_true',
|
|
394
|
+
help='Returns the list of versions in the specified range that contains encryption hints',
|
|
395
|
+
)
|
|
396
|
+
p_crypto_hints.set_defaults(func=crypto_hints)
|
|
397
|
+
|
|
398
|
+
p_crypto_versions_in_range = crypto_sub.add_parser(
|
|
399
|
+
'versions-in-range',
|
|
400
|
+
aliases=['vr'],
|
|
401
|
+
description=f'Show versions in range: {__version__}',
|
|
402
|
+
help="Given a list of PURLS and version ranges, get a list of versions that do/don't contain crypto algorithms",
|
|
403
|
+
)
|
|
404
|
+
p_crypto_versions_in_range.set_defaults(func=crypto_versions_in_range)
|
|
405
|
+
|
|
364
406
|
# Common purl Component sub-command options
|
|
365
|
-
for p in [
|
|
407
|
+
for p in [c_vulns, c_semgrep, c_provenance, p_crypto_algorithms, p_crypto_hints, p_crypto_versions_in_range]:
|
|
366
408
|
p.add_argument('--purl', '-p', type=str, nargs='*', help='Package URL - PURL to process.')
|
|
367
409
|
p.add_argument('--input', '-i', type=str, help='Input file name')
|
|
368
410
|
|
|
369
411
|
# Common Component sub-command options
|
|
370
|
-
for p in [
|
|
412
|
+
for p in [
|
|
413
|
+
c_vulns,
|
|
414
|
+
c_search,
|
|
415
|
+
c_versions,
|
|
416
|
+
c_semgrep,
|
|
417
|
+
c_provenance,
|
|
418
|
+
p_crypto_algorithms,
|
|
419
|
+
p_crypto_hints,
|
|
420
|
+
p_crypto_versions_in_range,
|
|
421
|
+
]:
|
|
371
422
|
p.add_argument(
|
|
372
423
|
'--timeout',
|
|
373
424
|
'-M',
|
|
374
425
|
type=int,
|
|
375
|
-
default=
|
|
426
|
+
default=DEFAULT_API_TIMEOUT,
|
|
376
427
|
help='Timeout (in seconds) for API communication (optional - default 600)',
|
|
377
428
|
)
|
|
378
429
|
|
|
@@ -588,7 +639,6 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
588
639
|
p_dep,
|
|
589
640
|
p_fc,
|
|
590
641
|
p_cnv,
|
|
591
|
-
c_crypto,
|
|
592
642
|
c_vulns,
|
|
593
643
|
c_search,
|
|
594
644
|
c_versions,
|
|
@@ -597,6 +647,9 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
597
647
|
p_c_dwnld,
|
|
598
648
|
p_folder_scan,
|
|
599
649
|
p_folder_hash,
|
|
650
|
+
p_crypto_algorithms,
|
|
651
|
+
p_crypto_hints,
|
|
652
|
+
p_crypto_versions_in_range,
|
|
600
653
|
]:
|
|
601
654
|
p.add_argument('--output', '-o', type=str, help='Output result file name (optional - default stdout).')
|
|
602
655
|
|
|
@@ -674,7 +727,6 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
674
727
|
# Global Scan/GRPC options
|
|
675
728
|
for p in [
|
|
676
729
|
p_scan,
|
|
677
|
-
c_crypto,
|
|
678
730
|
c_vulns,
|
|
679
731
|
c_search,
|
|
680
732
|
c_versions,
|
|
@@ -682,6 +734,9 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
682
734
|
c_provenance,
|
|
683
735
|
p_folder_scan,
|
|
684
736
|
p_cs,
|
|
737
|
+
p_crypto_algorithms,
|
|
738
|
+
p_crypto_hints,
|
|
739
|
+
p_crypto_versions_in_range,
|
|
685
740
|
]:
|
|
686
741
|
p.add_argument(
|
|
687
742
|
'--key', '-k', type=str, help='SCANOSS API Key token (optional - not required for default OSSKB URL)'
|
|
@@ -708,7 +763,19 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
708
763
|
)
|
|
709
764
|
|
|
710
765
|
# Global GRPC options
|
|
711
|
-
for p in [
|
|
766
|
+
for p in [
|
|
767
|
+
p_scan,
|
|
768
|
+
c_vulns,
|
|
769
|
+
c_search,
|
|
770
|
+
c_versions,
|
|
771
|
+
c_semgrep,
|
|
772
|
+
c_provenance,
|
|
773
|
+
p_folder_scan,
|
|
774
|
+
p_cs,
|
|
775
|
+
p_crypto_algorithms,
|
|
776
|
+
p_crypto_hints,
|
|
777
|
+
p_crypto_versions_in_range,
|
|
778
|
+
]:
|
|
712
779
|
p.add_argument(
|
|
713
780
|
'--api2url', type=str, help='SCANOSS gRPC API 2.0 URL (optional - default: https://api.osskb.org)'
|
|
714
781
|
)
|
|
@@ -751,7 +818,6 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
751
818
|
p_c_loc,
|
|
752
819
|
p_c_dwnld,
|
|
753
820
|
p_p_proxy,
|
|
754
|
-
c_crypto,
|
|
755
821
|
c_vulns,
|
|
756
822
|
c_search,
|
|
757
823
|
c_versions,
|
|
@@ -763,6 +829,9 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
763
829
|
p_folder_scan,
|
|
764
830
|
p_folder_hash,
|
|
765
831
|
p_cs,
|
|
832
|
+
p_crypto_algorithms,
|
|
833
|
+
p_crypto_hints,
|
|
834
|
+
p_crypto_versions_in_range,
|
|
766
835
|
]:
|
|
767
836
|
p.add_argument('--debug', '-d', action='store_true', help='Enable debug messages')
|
|
768
837
|
p.add_argument('--trace', '-t', action='store_true', help='Enable trace messages, including API posts')
|
|
@@ -775,7 +844,9 @@ def setup_args() -> None: # noqa: PLR0912, PLR0915
|
|
|
775
844
|
if not args.subparser:
|
|
776
845
|
parser.print_help() # No sub command subcommand, print general help
|
|
777
846
|
sys.exit(1)
|
|
778
|
-
elif (
|
|
847
|
+
elif (
|
|
848
|
+
args.subparser in ('utils', 'ut', 'component', 'comp', 'inspect', 'insp', 'ins', 'crypto', 'cr')
|
|
849
|
+
) and not args.subparsercmd:
|
|
779
850
|
parser.parse_args([args.subparser, '--help']) # Force utils helps to be displayed
|
|
780
851
|
sys.exit(1)
|
|
781
852
|
args.func(parser, args) # Execute the function associated with the sub-command
|
|
@@ -1393,9 +1464,9 @@ def get_pac_file(pac: str):
|
|
|
1393
1464
|
return pac_file
|
|
1394
1465
|
|
|
1395
1466
|
|
|
1396
|
-
def
|
|
1467
|
+
def crypto_algorithms(parser, args):
|
|
1397
1468
|
"""
|
|
1398
|
-
Run the "
|
|
1469
|
+
Run the "crypto algorithms" sub-command
|
|
1399
1470
|
Parameters
|
|
1400
1471
|
----------
|
|
1401
1472
|
parser: ArgumentParser
|
|
@@ -1410,22 +1481,112 @@ def comp_crypto(parser, args):
|
|
|
1410
1481
|
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
1411
1482
|
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
1412
1483
|
sys.exit(1)
|
|
1413
|
-
pac_file = get_pac_file(args.pac)
|
|
1414
1484
|
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1485
|
+
try:
|
|
1486
|
+
config = create_cryptography_config_from_args(args)
|
|
1487
|
+
grpc_config = create_grpc_config_from_args(args)
|
|
1488
|
+
if args.pac:
|
|
1489
|
+
grpc_config.pac = get_pac_file(args.pac)
|
|
1490
|
+
if args.header:
|
|
1491
|
+
grpc_config.req_headers = process_req_headers(args.header)
|
|
1492
|
+
client = ScanossGrpc(**asdict(grpc_config))
|
|
1493
|
+
|
|
1494
|
+
cryptography = Cryptography(config=config, client=client)
|
|
1495
|
+
cryptography.get_algorithms()
|
|
1496
|
+
cryptography.present(output_file=args.output)
|
|
1497
|
+
except ScanossGrpcError as e:
|
|
1498
|
+
print_stderr(f'API ERROR: {e}')
|
|
1499
|
+
sys.exit(1)
|
|
1500
|
+
except Exception as e:
|
|
1501
|
+
if args.debug:
|
|
1502
|
+
import traceback
|
|
1503
|
+
|
|
1504
|
+
traceback.print_exc()
|
|
1505
|
+
print_stderr(f'ERROR: {e}')
|
|
1506
|
+
sys.exit(1)
|
|
1507
|
+
|
|
1508
|
+
|
|
1509
|
+
def crypto_hints(parser, args):
|
|
1510
|
+
"""
|
|
1511
|
+
Run the "crypto hints" sub-command
|
|
1512
|
+
Parameters
|
|
1513
|
+
----------
|
|
1514
|
+
parser: ArgumentParser
|
|
1515
|
+
command line parser object
|
|
1516
|
+
args: Namespace
|
|
1517
|
+
Parsed arguments
|
|
1518
|
+
"""
|
|
1519
|
+
if (not args.purl and not args.input) or (args.purl and args.input):
|
|
1520
|
+
print_stderr('Please specify an input file or purl to decorate (--purl or --input)')
|
|
1521
|
+
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
1522
|
+
sys.exit(1)
|
|
1523
|
+
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
1524
|
+
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
1525
|
+
sys.exit(1)
|
|
1526
|
+
|
|
1527
|
+
try:
|
|
1528
|
+
config = create_cryptography_config_from_args(args)
|
|
1529
|
+
grpc_config = create_grpc_config_from_args(args)
|
|
1530
|
+
if args.pac:
|
|
1531
|
+
grpc_config.pac = get_pac_file(args.pac)
|
|
1532
|
+
if args.header:
|
|
1533
|
+
grpc_config.req_headers = process_req_headers(args.header)
|
|
1534
|
+
client = ScanossGrpc(**asdict(grpc_config))
|
|
1535
|
+
|
|
1536
|
+
cryptography = Cryptography(config=config, client=client)
|
|
1537
|
+
cryptography.get_encryption_hints()
|
|
1538
|
+
cryptography.present(output_file=args.output)
|
|
1539
|
+
except ScanossGrpcError as e:
|
|
1540
|
+
print_stderr(f'API ERROR: {e}')
|
|
1541
|
+
sys.exit(1)
|
|
1542
|
+
except Exception as e:
|
|
1543
|
+
if args.debug:
|
|
1544
|
+
import traceback
|
|
1545
|
+
|
|
1546
|
+
traceback.print_exc()
|
|
1547
|
+
print_stderr(f'ERROR: {e}')
|
|
1548
|
+
sys.exit(1)
|
|
1549
|
+
|
|
1550
|
+
|
|
1551
|
+
def crypto_versions_in_range(parser, args):
|
|
1552
|
+
"""
|
|
1553
|
+
Run the "crypto versions-in-range" sub-command
|
|
1554
|
+
Parameters
|
|
1555
|
+
----------
|
|
1556
|
+
parser: ArgumentParser
|
|
1557
|
+
command line parser object
|
|
1558
|
+
args: Namespace
|
|
1559
|
+
Parsed arguments
|
|
1560
|
+
"""
|
|
1561
|
+
if (not args.purl and not args.input) or (args.purl and args.input):
|
|
1562
|
+
print_stderr('Please specify an input file or purl to decorate (--purl or --input)')
|
|
1563
|
+
parser.parse_args([args.subparser, args.subparsercmd, '-h'])
|
|
1564
|
+
sys.exit(1)
|
|
1565
|
+
if args.ca_cert and not os.path.exists(args.ca_cert):
|
|
1566
|
+
print_stderr(f'Error: Certificate file does not exist: {args.ca_cert}.')
|
|
1567
|
+
sys.exit(1)
|
|
1568
|
+
|
|
1569
|
+
try:
|
|
1570
|
+
config = create_cryptography_config_from_args(args)
|
|
1571
|
+
grpc_config = create_grpc_config_from_args(args)
|
|
1572
|
+
if args.pac:
|
|
1573
|
+
grpc_config.pac = get_pac_file(args.pac)
|
|
1574
|
+
if args.header:
|
|
1575
|
+
grpc_config.req_headers = process_req_headers(args.header)
|
|
1576
|
+
client = ScanossGrpc(**asdict(grpc_config))
|
|
1577
|
+
|
|
1578
|
+
cryptography = Cryptography(config=config, client=client)
|
|
1579
|
+
cryptography.get_versions_in_range()
|
|
1580
|
+
cryptography.present(output_file=args.output)
|
|
1581
|
+
except ScanossGrpcError as e:
|
|
1582
|
+
print_stderr(f'API ERROR: {e}')
|
|
1583
|
+
sys.exit(1)
|
|
1584
|
+
except Exception as e:
|
|
1585
|
+
if args.debug:
|
|
1586
|
+
import traceback
|
|
1587
|
+
|
|
1588
|
+
traceback.print_exc()
|
|
1589
|
+
print_stderr(f'ERROR: {e}')
|
|
1429
1590
|
sys.exit(1)
|
|
1430
1591
|
|
|
1431
1592
|
|
scanoss/constants.py
CHANGED
scanoss/cryptography.py
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from scanoss.scanossbase import ScanossBase
|
|
6
|
+
from scanoss.scanossgrpc import ScanossGrpc
|
|
7
|
+
from scanoss.utils.abstract_presenter import AbstractPresenter
|
|
8
|
+
from scanoss.utils.file import validate_json_file
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ScanossCryptographyError(Exception):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
MIN_SPLIT_PARTS = 2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class CryptographyConfig:
|
|
20
|
+
purl: List[str]
|
|
21
|
+
input_file: Optional[str] = None
|
|
22
|
+
output_file: Optional[str] = None
|
|
23
|
+
header: Optional[str] = None
|
|
24
|
+
debug: bool = False
|
|
25
|
+
trace: bool = False
|
|
26
|
+
quiet: bool = False
|
|
27
|
+
with_range: bool = False
|
|
28
|
+
|
|
29
|
+
def __post_init__(self):
|
|
30
|
+
"""
|
|
31
|
+
Validate that the configuration is valid.
|
|
32
|
+
"""
|
|
33
|
+
if self.purl:
|
|
34
|
+
if self.with_range:
|
|
35
|
+
for purl in self.purl:
|
|
36
|
+
parts = purl.split('@')
|
|
37
|
+
if not (len(parts) >= MIN_SPLIT_PARTS and parts[1]):
|
|
38
|
+
raise ScanossCryptographyError(
|
|
39
|
+
f'Invalid PURL format: "{purl}".' f'It must include a version (e.g., pkg:type/name@version)'
|
|
40
|
+
)
|
|
41
|
+
if self.input_file:
|
|
42
|
+
input_file_validation = validate_json_file(self.input_file)
|
|
43
|
+
if not input_file_validation.is_valid:
|
|
44
|
+
raise ScanossCryptographyError(
|
|
45
|
+
f'There was a problem with the purl input file. {input_file_validation.error}'
|
|
46
|
+
)
|
|
47
|
+
if (
|
|
48
|
+
not isinstance(input_file_validation.data, dict)
|
|
49
|
+
or 'purls' not in input_file_validation.data
|
|
50
|
+
or not isinstance(input_file_validation.data['purls'], list)
|
|
51
|
+
or not all(isinstance(p, dict) and 'purl' in p for p in input_file_validation.data['purls'])
|
|
52
|
+
):
|
|
53
|
+
raise ScanossCryptographyError('The supplied input file is not in the correct PurlRequest format.')
|
|
54
|
+
purls = input_file_validation.data['purls']
|
|
55
|
+
purls_with_requirement = []
|
|
56
|
+
if self.with_range:
|
|
57
|
+
if any('requirement' not in p for p in purls):
|
|
58
|
+
raise ScanossCryptographyError(
|
|
59
|
+
f'One or more PURLs in "{self.input_file}" are missing the "requirement" field.'
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
for purl in purls:
|
|
63
|
+
purls_with_requirement.append(f'{purl["purl"]}@{purl["requirement"]}')
|
|
64
|
+
else:
|
|
65
|
+
purls_with_requirement = purls
|
|
66
|
+
self.purl = purls_with_requirement
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def create_cryptography_config_from_args(args) -> CryptographyConfig:
|
|
70
|
+
return CryptographyConfig(
|
|
71
|
+
debug=getattr(args, 'debug', False),
|
|
72
|
+
trace=getattr(args, 'trace', False),
|
|
73
|
+
quiet=getattr(args, 'quiet', False),
|
|
74
|
+
with_range=getattr(args, 'with_range', False),
|
|
75
|
+
purl=getattr(args, 'purl', []),
|
|
76
|
+
input_file=getattr(args, 'input', None),
|
|
77
|
+
output_file=getattr(args, 'output', None),
|
|
78
|
+
header=getattr(args, 'header', None),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Cryptography:
|
|
83
|
+
"""
|
|
84
|
+
Cryptography Class
|
|
85
|
+
|
|
86
|
+
This class is used to decorate purls with cryptography information.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
config: CryptographyConfig,
|
|
92
|
+
client: ScanossGrpc,
|
|
93
|
+
):
|
|
94
|
+
"""
|
|
95
|
+
Initialize the Cryptography.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
config (CryptographyConfig): Configuration parameters for the cryptography.
|
|
99
|
+
client (ScanossGrpc): gRPC client for communicating with the scanning service.
|
|
100
|
+
"""
|
|
101
|
+
self.base = ScanossBase(
|
|
102
|
+
debug=config.debug,
|
|
103
|
+
trace=config.trace,
|
|
104
|
+
quiet=config.quiet,
|
|
105
|
+
)
|
|
106
|
+
self.presenter = CryptographyPresenter(
|
|
107
|
+
self,
|
|
108
|
+
debug=config.debug,
|
|
109
|
+
trace=config.trace,
|
|
110
|
+
quiet=config.quiet,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
self.client = client
|
|
114
|
+
self.config = config
|
|
115
|
+
self.purls_request = self._build_purls_request()
|
|
116
|
+
self.results = None
|
|
117
|
+
|
|
118
|
+
def get_algorithms(self) -> Optional[Dict]:
|
|
119
|
+
"""
|
|
120
|
+
Get the cryptographic algorithms for the provided purl or input file.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Optional[Dict]: The folder hash response from the gRPC client, or None if an error occurs.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
if not self.purls_request:
|
|
127
|
+
raise ScanossCryptographyError('No PURLs supplied. Provide --purl or --input.')
|
|
128
|
+
self.base.print_stderr(
|
|
129
|
+
f'Getting cryptographic algorithms for {", ".join([p["purl"] for p in self.purls_request["purls"]])}'
|
|
130
|
+
)
|
|
131
|
+
if self.config.with_range:
|
|
132
|
+
response = self.client.get_crypto_algorithms_in_range_for_purl(self.purls_request)
|
|
133
|
+
else:
|
|
134
|
+
response = self.client.get_crypto_algorithms_for_purl(self.purls_request)
|
|
135
|
+
if response:
|
|
136
|
+
self.results = response
|
|
137
|
+
|
|
138
|
+
return self.results
|
|
139
|
+
|
|
140
|
+
def get_encryption_hints(self) -> Optional[Dict]:
|
|
141
|
+
"""
|
|
142
|
+
Get the encryption hints for the provided purl or input file.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Optional[Dict]: The encryption hints response from the gRPC client, or None if an error occurs.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
if not self.purls_request:
|
|
149
|
+
raise ScanossCryptographyError('No PURLs supplied. Provide --purl or --input.')
|
|
150
|
+
self.base.print_stderr(
|
|
151
|
+
f'Getting encryption hints '
|
|
152
|
+
f'{"in range" if self.config.with_range else ""} '
|
|
153
|
+
f'for {", ".join([p["purl"] for p in self.purls_request["purls"]])}'
|
|
154
|
+
)
|
|
155
|
+
if self.config.with_range:
|
|
156
|
+
response = self.client.get_encryption_hints_in_range_for_purl(self.purls_request)
|
|
157
|
+
else:
|
|
158
|
+
response = self.client.get_encryption_hints_for_purl(self.purls_request)
|
|
159
|
+
if response:
|
|
160
|
+
self.results = response
|
|
161
|
+
|
|
162
|
+
return self.results
|
|
163
|
+
|
|
164
|
+
def get_versions_in_range(self) -> Optional[Dict]:
|
|
165
|
+
"""
|
|
166
|
+
Given a list of PURLS and version ranges, get a list of versions that do/do not contain cryptographic algorithms
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Optional[Dict]: The versions in range response from the gRPC client, or None if an error occurs.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
if not self.purls_request:
|
|
173
|
+
raise ScanossCryptographyError('No PURLs supplied. Provide --purl or --input.')
|
|
174
|
+
|
|
175
|
+
self.base.print_stderr(
|
|
176
|
+
f'Getting versions in range for {", ".join([p["purl"] for p in self.purls_request["purls"]])}'
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
response = self.client.get_versions_in_range_for_purl(self.purls_request)
|
|
180
|
+
if response:
|
|
181
|
+
self.results = response
|
|
182
|
+
|
|
183
|
+
return self.results
|
|
184
|
+
|
|
185
|
+
def _build_purls_request(
|
|
186
|
+
self,
|
|
187
|
+
) -> Optional[dict]:
|
|
188
|
+
"""
|
|
189
|
+
Load the specified purls from a JSON file or a list of PURLs and return a dictionary
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
json_file (Optional[str], optional): The JSON file containing the PURLs. Defaults to None.
|
|
193
|
+
purls (Optional[List[str]], optional): The list of PURLs. Defaults to None.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Optional[dict]: The dictionary containing the PURLs
|
|
197
|
+
"""
|
|
198
|
+
return {
|
|
199
|
+
'purls': [
|
|
200
|
+
{
|
|
201
|
+
'purl': p,
|
|
202
|
+
'requirement': self._extract_version_from_purl(p),
|
|
203
|
+
}
|
|
204
|
+
for p in self.config.purl
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
def _extract_version_from_purl(self, purl: str) -> str:
|
|
209
|
+
"""
|
|
210
|
+
Extract version from purl
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
purl (str): The purl string to extract the version from
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
str: The extracted version
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
ScanossCryptographyError: If the purl is not in the correct format
|
|
220
|
+
"""
|
|
221
|
+
try:
|
|
222
|
+
return purl.split('@')[-1]
|
|
223
|
+
except IndexError:
|
|
224
|
+
raise ScanossCryptographyError(f'Invalid purl format: {purl}')
|
|
225
|
+
|
|
226
|
+
def present(
|
|
227
|
+
self,
|
|
228
|
+
output_format: Optional[str] = None,
|
|
229
|
+
output_file: Optional[str] = None,
|
|
230
|
+
):
|
|
231
|
+
"""Present the results in the selected format"""
|
|
232
|
+
self.presenter.present(output_format=output_format, output_file=output_file)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class CryptographyPresenter(AbstractPresenter):
|
|
236
|
+
"""
|
|
237
|
+
Cryptography presenter class
|
|
238
|
+
Handles the presentation of the cryptography results
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def __init__(self, cryptography: Cryptography, **kwargs):
|
|
242
|
+
super().__init__(**kwargs)
|
|
243
|
+
self.cryptography = cryptography
|
|
244
|
+
|
|
245
|
+
def _format_json_output(self) -> str:
|
|
246
|
+
"""
|
|
247
|
+
Format the scan output data into a JSON object
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
str: The formatted JSON string
|
|
251
|
+
"""
|
|
252
|
+
return json.dumps(self.cryptography.results, indent=2)
|
|
253
|
+
|
|
254
|
+
def _format_plain_output(self) -> str:
|
|
255
|
+
"""
|
|
256
|
+
Format the scan output data into a plain text string
|
|
257
|
+
"""
|
|
258
|
+
return (
|
|
259
|
+
json.dumps(self.cryptography.results, indent=2)
|
|
260
|
+
if isinstance(self.cryptography.results, dict)
|
|
261
|
+
else str(self.cryptography.results)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def _format_cyclonedx_output(self) -> str:
|
|
265
|
+
raise NotImplementedError('CycloneDX output is not implemented')
|
|
266
|
+
|
|
267
|
+
def _format_spdxlite_output(self) -> str:
|
|
268
|
+
raise NotImplementedError('SPDXlite output is not implemented')
|
|
269
|
+
|
|
270
|
+
def _format_csv_output(self) -> str:
|
|
271
|
+
raise NotImplementedError('CSV output is not implemented')
|
|
272
|
+
|
|
273
|
+
def _format_raw_output(self) -> str:
|
|
274
|
+
raise NotImplementedError('Raw output is not implemented')
|
scanoss/data/build_date.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
date:
|
|
1
|
+
date: 20250610161304, utime: 1749571984
|
|
@@ -26,9 +26,10 @@ import json
|
|
|
26
26
|
import os.path
|
|
27
27
|
from abc import abstractmethod
|
|
28
28
|
from enum import Enum
|
|
29
|
-
from typing import
|
|
30
|
-
|
|
29
|
+
from typing import Any, Callable, Dict, List
|
|
30
|
+
|
|
31
31
|
from ..scanossbase import ScanossBase
|
|
32
|
+
from .utils.license_utils import LicenseUtil
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
class PolicyStatus(Enum):
|
|
@@ -87,7 +88,7 @@ class PolicyCheck(ScanossBase):
|
|
|
87
88
|
|
|
88
89
|
VALID_FORMATS = {'md', 'json', 'jira_md'}
|
|
89
90
|
|
|
90
|
-
def __init__(
|
|
91
|
+
def __init__( # noqa: PLR0913
|
|
91
92
|
self,
|
|
92
93
|
debug: bool = False,
|
|
93
94
|
trace: bool = True,
|
|
@@ -181,10 +182,9 @@ class PolicyCheck(ScanossBase):
|
|
|
181
182
|
:param status: The new component status
|
|
182
183
|
:return: The updated components dictionary
|
|
183
184
|
"""
|
|
184
|
-
|
|
185
185
|
# Determine the component key and purl based on component type
|
|
186
186
|
if id in [ComponentID.FILE.value, ComponentID.SNIPPET.value]:
|
|
187
|
-
purl = new_component['purl'][0] # Take first purl for these component types
|
|
187
|
+
purl = new_component['purl'][0] # Take the first purl for these component types
|
|
188
188
|
else:
|
|
189
189
|
purl = new_component['purl']
|
|
190
190
|
|
|
@@ -195,14 +195,13 @@ class PolicyCheck(ScanossBase):
|
|
|
195
195
|
'licenses': {},
|
|
196
196
|
'status': status,
|
|
197
197
|
}
|
|
198
|
-
|
|
199
198
|
if not new_component.get('licenses'):
|
|
200
|
-
self.
|
|
199
|
+
self.print_debug(f'WARNING: Results missing licenses. Skipping: {new_component}')
|
|
201
200
|
return components
|
|
202
201
|
# Process licenses for this component
|
|
203
|
-
for
|
|
204
|
-
if
|
|
205
|
-
spdxid =
|
|
202
|
+
for license_item in new_component['licenses']:
|
|
203
|
+
if license_item.get('name'):
|
|
204
|
+
spdxid = license_item['name']
|
|
206
205
|
components[component_key]['licenses'][spdxid] = {
|
|
207
206
|
'spdxid': spdxid,
|
|
208
207
|
'copyleft': self.license_util.is_copyleft(spdxid),
|
|
@@ -210,71 +209,103 @@ class PolicyCheck(ScanossBase):
|
|
|
210
209
|
}
|
|
211
210
|
return components
|
|
212
211
|
|
|
213
|
-
def
|
|
212
|
+
def _get_components_data(self, results: Dict[str, Any], components: Dict[str, Any]) -> Dict[str, Any]:
|
|
214
213
|
"""
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
This function iterates through the results dictionary, identifying components from
|
|
218
|
-
different sources (files, snippets, and dependencies). It consolidates this information
|
|
219
|
-
into a list of unique components, each with its associated licenses and other details.
|
|
214
|
+
Extract and process file and snippet components from results.
|
|
220
215
|
|
|
221
216
|
:param results: A dictionary containing the raw results of a component scan
|
|
222
|
-
:
|
|
217
|
+
:param components: Existing components dictionary to update
|
|
218
|
+
:return: Updated components dictionary with file and snippet data
|
|
223
219
|
"""
|
|
224
|
-
if results is None:
|
|
225
|
-
self.print_stderr(f'ERROR: Results cannot be empty')
|
|
226
|
-
return None
|
|
227
|
-
components = {}
|
|
228
220
|
for component in results.values():
|
|
229
221
|
for c in component:
|
|
230
222
|
component_id = c.get('id')
|
|
231
223
|
if not component_id:
|
|
232
|
-
self.
|
|
224
|
+
self.print_debug(f'WARNING: Result missing id. Skipping: {c}')
|
|
233
225
|
continue
|
|
234
226
|
status = c.get('status')
|
|
235
|
-
if not
|
|
236
|
-
self.
|
|
227
|
+
if not status:
|
|
228
|
+
self.print_debug(f'WARNING: Result missing status. Skipping: {c}')
|
|
237
229
|
continue
|
|
238
230
|
if component_id in [ComponentID.FILE.value, ComponentID.SNIPPET.value]:
|
|
239
231
|
if not c.get('purl'):
|
|
240
|
-
self.
|
|
232
|
+
self.print_debug(f'WARNING: Result missing purl. Skipping: {c}')
|
|
241
233
|
continue
|
|
242
234
|
if len(c.get('purl')) <= 0:
|
|
243
|
-
self.
|
|
235
|
+
self.print_debug(f'WARNING: Result missing purls. Skipping: {c}')
|
|
244
236
|
continue
|
|
245
237
|
if not c.get('version'):
|
|
246
|
-
self.
|
|
238
|
+
self.print_msg(f'WARNING: Result missing version. Skipping: {c}')
|
|
247
239
|
continue
|
|
248
240
|
component_key = f'{c["purl"][0]}@{c["version"]}'
|
|
249
|
-
# Initialize or update the component entry
|
|
250
241
|
if component_key not in components:
|
|
251
242
|
components = self._append_component(components, c, component_id, status)
|
|
243
|
+
# End component loop
|
|
244
|
+
# End components loop
|
|
245
|
+
return components
|
|
252
246
|
|
|
253
|
-
|
|
247
|
+
def _get_dependencies_data(self, results: Dict[str, Any], components: Dict[str, Any]) -> Dict[str, Any]:
|
|
248
|
+
"""
|
|
249
|
+
Extract and process dependency components from results.
|
|
250
|
+
|
|
251
|
+
:param results: A dictionary containing the raw results of a component scan
|
|
252
|
+
:param components: Existing components dictionary to update
|
|
253
|
+
:return: Updated components dictionary with dependency data
|
|
254
|
+
"""
|
|
255
|
+
for component in results.values():
|
|
256
|
+
for c in component:
|
|
257
|
+
component_id = c.get('id')
|
|
258
|
+
if not component_id:
|
|
259
|
+
self.print_debug(f'WARNING: Result missing id. Skipping: {c}')
|
|
260
|
+
continue
|
|
261
|
+
status = c.get('status')
|
|
262
|
+
if not status:
|
|
263
|
+
self.print_debug(f'WARNING: Result missing status. Skipping: {c}')
|
|
264
|
+
continue
|
|
265
|
+
if component_id == ComponentID.DEPENDENCY.value:
|
|
254
266
|
if c.get('dependencies') is None:
|
|
255
267
|
continue
|
|
256
|
-
for
|
|
257
|
-
if not
|
|
258
|
-
self.
|
|
259
|
-
continue
|
|
260
|
-
if len(d.get('purl')) <= 0:
|
|
261
|
-
self.print_stderr(f'WARNING: Result missing purls. Skipping.')
|
|
268
|
+
for dependency in c['dependencies']:
|
|
269
|
+
if not dependency.get('purl'):
|
|
270
|
+
self.print_debug(f'WARNING: Dependency result missing purl. Skipping: {dependency}')
|
|
262
271
|
continue
|
|
263
|
-
if not
|
|
264
|
-
self.
|
|
272
|
+
if not dependency.get('version'):
|
|
273
|
+
self.print_msg(f'WARNING: Dependency result missing version. Skipping: {dependency}')
|
|
265
274
|
continue
|
|
266
|
-
component_key = f'{
|
|
275
|
+
component_key = f'{dependency["purl"]}@{dependency["version"]}'
|
|
267
276
|
if component_key not in components:
|
|
268
|
-
components = self._append_component(components,
|
|
269
|
-
# End
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
277
|
+
components = self._append_component(components, dependency, component_id, status)
|
|
278
|
+
# End dependency loop
|
|
279
|
+
# End component loop
|
|
280
|
+
# End of result loop
|
|
281
|
+
return components
|
|
282
|
+
|
|
283
|
+
def _get_components_from_results(self, results: Dict[str, Any]) -> list or None:
|
|
284
|
+
"""
|
|
285
|
+
Process the results dictionary to extract and format component information.
|
|
286
|
+
|
|
287
|
+
This function iterates through the results dictionary, identifying components from
|
|
288
|
+
different sources (files, snippets, and dependencies). It consolidates this information
|
|
289
|
+
into a list of unique components, each with its associated licenses and other details.
|
|
290
|
+
|
|
291
|
+
:param results: A dictionary containing the raw results of a component scan
|
|
292
|
+
:return: A list of dictionaries, each representing a unique component with its details
|
|
293
|
+
"""
|
|
294
|
+
if results is None:
|
|
295
|
+
self.print_stderr('ERROR: Results cannot be empty')
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
components = {}
|
|
299
|
+
# Extract file and snippet components
|
|
300
|
+
components = self._get_components_data(results, components)
|
|
301
|
+
# Extract dependency components
|
|
302
|
+
components = self._get_dependencies_data(results, components)
|
|
303
|
+
# Convert to list and process licenses
|
|
304
|
+
results_list = list(components.values())
|
|
305
|
+
for component in results_list:
|
|
275
306
|
component['licenses'] = list(component['licenses'].values())
|
|
276
307
|
|
|
277
|
-
return
|
|
308
|
+
return results_list
|
|
278
309
|
|
|
279
310
|
def generate_table(self, headers, rows, centered_columns=None):
|
|
280
311
|
"""
|
|
@@ -403,7 +434,6 @@ class PolicyCheck(ScanossBase):
|
|
|
403
434
|
components = self._get_components_from_results(self.results)
|
|
404
435
|
return components
|
|
405
436
|
|
|
406
|
-
|
|
407
437
|
#
|
|
408
438
|
# End of PolicyCheck Class
|
|
409
439
|
#
|
scanoss/scanossgrpc.py
CHANGED
|
@@ -54,7 +54,6 @@ from .api.components.v2.scanoss_components_pb2 import (
|
|
|
54
54
|
CompVersionResponse,
|
|
55
55
|
)
|
|
56
56
|
from .api.components.v2.scanoss_components_pb2_grpc import ComponentsStub
|
|
57
|
-
from .api.cryptography.v2.scanoss_cryptography_pb2 import AlgorithmResponse
|
|
58
57
|
from .api.cryptography.v2.scanoss_cryptography_pb2_grpc import CryptographyStub
|
|
59
58
|
from .api.dependencies.v2.scanoss_dependencies_pb2 import DependencyRequest
|
|
60
59
|
from .api.dependencies.v2.scanoss_dependencies_pb2_grpc import DependenciesStub
|
|
@@ -312,36 +311,6 @@ class ScanossGrpc(ScanossBase):
|
|
|
312
311
|
merged_response['status'] = response['status']
|
|
313
312
|
return merged_response
|
|
314
313
|
|
|
315
|
-
def get_crypto_json(self, purls: dict) -> dict:
|
|
316
|
-
"""
|
|
317
|
-
Client function to call the rpc for Cryptography GetAlgorithms
|
|
318
|
-
:param purls: Message to send to the service
|
|
319
|
-
:return: Server response or None
|
|
320
|
-
"""
|
|
321
|
-
if not purls:
|
|
322
|
-
self.print_stderr('ERROR: No message supplied to send to gRPC service.')
|
|
323
|
-
return None
|
|
324
|
-
request_id = str(uuid.uuid4())
|
|
325
|
-
resp: AlgorithmResponse
|
|
326
|
-
try:
|
|
327
|
-
request = ParseDict(purls, PurlRequest()) # Parse the JSON/Dict into the purl request object
|
|
328
|
-
metadata = self.metadata[:]
|
|
329
|
-
metadata.append(('x-request-id', request_id)) # Set a Request ID
|
|
330
|
-
self.print_debug(f'Sending crypto data for decoration (rqId: {request_id})...')
|
|
331
|
-
resp = self.crypto_stub.GetAlgorithms(request, metadata=metadata, timeout=self.timeout)
|
|
332
|
-
except Exception as e:
|
|
333
|
-
self.print_stderr(
|
|
334
|
-
f'ERROR: {e.__class__.__name__} Problem encountered sending gRPC message (rqId: {request_id}): {e}'
|
|
335
|
-
)
|
|
336
|
-
else:
|
|
337
|
-
if resp:
|
|
338
|
-
if not self._check_status_response(resp.status, request_id):
|
|
339
|
-
return None
|
|
340
|
-
resp_dict = MessageToDict(resp, preserving_proto_field_name=True) # Convert gRPC response to a dict
|
|
341
|
-
del resp_dict['status']
|
|
342
|
-
return resp_dict
|
|
343
|
-
return None
|
|
344
|
-
|
|
345
314
|
def get_vulnerabilities_json(self, purls: dict) -> dict:
|
|
346
315
|
"""
|
|
347
316
|
Client function to call the rpc for Vulnerability GetVulnerabilities
|
|
@@ -607,6 +576,91 @@ class ScanossGrpc(ScanossBase):
|
|
|
607
576
|
'Sending data for provenance origin decoration (rqId: {rqId})...',
|
|
608
577
|
)
|
|
609
578
|
|
|
579
|
+
def get_crypto_algorithms_for_purl(self, request: Dict) -> Optional[Dict]:
|
|
580
|
+
"""
|
|
581
|
+
Client function to call the rpc for GetAlgorithms for a list of purls
|
|
582
|
+
|
|
583
|
+
Args:
|
|
584
|
+
request (Dict): PurlRequest
|
|
585
|
+
|
|
586
|
+
Returns:
|
|
587
|
+
Optional[Dict]: AlgorithmResponse, or None if the request was not successfull
|
|
588
|
+
"""
|
|
589
|
+
return self._call_rpc(
|
|
590
|
+
self.crypto_stub.GetAlgorithms,
|
|
591
|
+
request,
|
|
592
|
+
PurlRequest,
|
|
593
|
+
'Sending data for cryptographic algorithms decoration (rqId: {rqId})...',
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
def get_crypto_algorithms_in_range_for_purl(self, request: Dict) -> Optional[Dict]:
|
|
597
|
+
"""
|
|
598
|
+
Client function to call the rpc for GetAlgorithmsInRange for a list of purls
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
request (Dict): PurlRequest
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
Optional[Dict]: AlgorithmsInRangeResponse, or None if the request was not successfull
|
|
605
|
+
"""
|
|
606
|
+
return self._call_rpc(
|
|
607
|
+
self.crypto_stub.GetAlgorithmsInRange,
|
|
608
|
+
request,
|
|
609
|
+
PurlRequest,
|
|
610
|
+
'Sending data for cryptographic algorithms in range decoration (rqId: {rqId})...',
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
def get_encryption_hints_for_purl(self, request: Dict) -> Optional[Dict]:
|
|
614
|
+
"""
|
|
615
|
+
Client function to call the rpc for GetEncryptionHints for a list of purls
|
|
616
|
+
|
|
617
|
+
Args:
|
|
618
|
+
request (Dict): PurlRequest
|
|
619
|
+
|
|
620
|
+
Returns:
|
|
621
|
+
Optional[Dict]: HintsResponse, or None if the request was not successfull
|
|
622
|
+
"""
|
|
623
|
+
return self._call_rpc(
|
|
624
|
+
self.crypto_stub.GetEncryptionHints,
|
|
625
|
+
request,
|
|
626
|
+
PurlRequest,
|
|
627
|
+
'Sending data for encryption hints decoration (rqId: {rqId})...',
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
def get_encryption_hints_in_range_for_purl(self, request: Dict) -> Optional[Dict]:
|
|
631
|
+
"""
|
|
632
|
+
Client function to call the rpc for GetHintsInRange for a list of purls
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
request (Dict): PurlRequest
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
Optional[Dict]: HintsInRangeResponse, or None if the request was not successfull
|
|
639
|
+
"""
|
|
640
|
+
return self._call_rpc(
|
|
641
|
+
self.crypto_stub.GetHintsInRange,
|
|
642
|
+
request,
|
|
643
|
+
PurlRequest,
|
|
644
|
+
'Sending data for encryption hints in range decoration (rqId: {rqId})...',
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
def get_versions_in_range_for_purl(self, request: Dict) -> Optional[Dict]:
|
|
648
|
+
"""
|
|
649
|
+
Client function to call the rpc for GetVersionsInRange for a list of purls
|
|
650
|
+
|
|
651
|
+
Args:
|
|
652
|
+
request (Dict): PurlRequest
|
|
653
|
+
|
|
654
|
+
Returns:
|
|
655
|
+
Optional[Dict]: VersionsInRangeResponse, or None if the request was not successfull
|
|
656
|
+
"""
|
|
657
|
+
return self._call_rpc(
|
|
658
|
+
self.crypto_stub.GetVersionsInRange,
|
|
659
|
+
request,
|
|
660
|
+
PurlRequest,
|
|
661
|
+
'Sending data for cryptographic versions in range decoration (rqId: {rqId})...',
|
|
662
|
+
)
|
|
663
|
+
|
|
610
664
|
def load_generic_headers(self):
|
|
611
665
|
"""
|
|
612
666
|
Adds custom headers from req_headers to metadata.
|
|
@@ -637,10 +691,11 @@ class GrpcConfig:
|
|
|
637
691
|
quiet: Optional[bool] = False
|
|
638
692
|
ver_details: Optional[str] = None
|
|
639
693
|
ca_cert: Optional[str] = None
|
|
640
|
-
pac: Optional[PACFile] = None
|
|
641
694
|
timeout: Optional[int] = DEFAULT_TIMEOUT
|
|
642
695
|
proxy: Optional[str] = None
|
|
643
696
|
grpc_proxy: Optional[str] = None
|
|
697
|
+
pac: Optional[PACFile] = None
|
|
698
|
+
req_headers: Optional[dict] = None
|
|
644
699
|
|
|
645
700
|
|
|
646
701
|
def create_grpc_config_from_args(args) -> GrpcConfig:
|
|
@@ -652,7 +707,6 @@ def create_grpc_config_from_args(args) -> GrpcConfig:
|
|
|
652
707
|
quiet=getattr(args, 'quiet', False),
|
|
653
708
|
ver_details=getattr(args, 'ver_details', None),
|
|
654
709
|
ca_cert=getattr(args, 'ca_cert', None),
|
|
655
|
-
pac=getattr(args, 'pac', None),
|
|
656
710
|
timeout=getattr(args, 'timeout', DEFAULT_TIMEOUT),
|
|
657
711
|
proxy=getattr(args, 'proxy', None),
|
|
658
712
|
grpc_proxy=getattr(args, 'grpc_proxy', None),
|
scanoss/utils/file.py
CHANGED
|
@@ -49,8 +49,8 @@ def validate_json_file(json_file_path: str) -> JsonValidation:
|
|
|
49
49
|
json_file_path (str): The JSON file to validate
|
|
50
50
|
|
|
51
51
|
Returns:
|
|
52
|
-
|
|
53
|
-
"""
|
|
52
|
+
JsonValidation: A JsonValidation object containing a boolean indicating if the file is valid, the data, error, and error code
|
|
53
|
+
""" # noqa: E501
|
|
54
54
|
if not json_file_path:
|
|
55
55
|
return JsonValidation(is_valid=False, error='No JSON file specified')
|
|
56
56
|
if not os.path.isfile(json_file_path):
|
scanoss/winnowing.py
CHANGED
|
@@ -32,9 +32,10 @@ import hashlib
|
|
|
32
32
|
import pathlib
|
|
33
33
|
import platform
|
|
34
34
|
import re
|
|
35
|
+
from typing import Tuple
|
|
35
36
|
|
|
36
|
-
from crc32c import crc32c
|
|
37
37
|
from binaryornot.check import is_binary
|
|
38
|
+
from crc32c import crc32c
|
|
38
39
|
|
|
39
40
|
from .scanossbase import ScanossBase
|
|
40
41
|
|
|
@@ -157,7 +158,7 @@ class Winnowing(ScanossBase):
|
|
|
157
158
|
a list of WFP fingerprints with their corresponding line numbers.
|
|
158
159
|
"""
|
|
159
160
|
|
|
160
|
-
def __init__(
|
|
161
|
+
def __init__( # noqa: PLR0913
|
|
161
162
|
self,
|
|
162
163
|
size_limit: bool = False,
|
|
163
164
|
debug: bool = False,
|
|
@@ -197,6 +198,7 @@ class Winnowing(ScanossBase):
|
|
|
197
198
|
self.strip_hpsm_ids = strip_hpsm_ids
|
|
198
199
|
self.strip_snippet_ids = strip_snippet_ids
|
|
199
200
|
self.hpsm = hpsm
|
|
201
|
+
self.is_windows = platform.system() == 'Windows'
|
|
200
202
|
if hpsm:
|
|
201
203
|
self.crc8_maxim_dow_table = []
|
|
202
204
|
self.crc8_generate_table()
|
|
@@ -218,11 +220,11 @@ class Winnowing(ScanossBase):
|
|
|
218
220
|
return byte
|
|
219
221
|
if byte >= ASCII_a:
|
|
220
222
|
return byte
|
|
221
|
-
if (byte >=
|
|
223
|
+
if (byte >= ASCII_A) and (byte <= ASCII_Z):
|
|
222
224
|
return byte + 32
|
|
223
225
|
return 0
|
|
224
226
|
|
|
225
|
-
def __skip_snippets(self, file: str, src: str) -> bool:
|
|
227
|
+
def __skip_snippets(self, file: str, src: str) -> bool: # noqa: PLR0911
|
|
226
228
|
"""
|
|
227
229
|
Determine files that are not of interest based on their content or file extension
|
|
228
230
|
Parameters
|
|
@@ -351,7 +353,55 @@ class Winnowing(ScanossBase):
|
|
|
351
353
|
self.print_debug(f'Stripped snippet ids from {file}')
|
|
352
354
|
return wfp
|
|
353
355
|
|
|
354
|
-
def
|
|
356
|
+
def __detect_line_endings(self, contents: bytes) -> Tuple[bool, bool, bool]:
|
|
357
|
+
"""Detect the types of line endings present in file contents.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
contents: File contents as bytes.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
Tuple of (has_crlf, has_lf_only, has_cr_only, has_mixed) indicating which line ending types are present.
|
|
364
|
+
"""
|
|
365
|
+
has_crlf = b'\r\n' in contents
|
|
366
|
+
# For LF detection, we need to find LF that's not part of CRLF
|
|
367
|
+
content_without_crlf = contents.replace(b'\r\n', b'')
|
|
368
|
+
has_standalone_lf = b'\n' in content_without_crlf
|
|
369
|
+
# For CR detection, we need to find CR that's not part of CRLF
|
|
370
|
+
has_standalone_cr = b'\r' in content_without_crlf
|
|
371
|
+
|
|
372
|
+
return has_crlf, has_standalone_lf, has_standalone_cr
|
|
373
|
+
|
|
374
|
+
def __calculate_opposite_line_ending_hash(self, contents: bytes):
|
|
375
|
+
"""Calculate hash for contents with opposite line endings.
|
|
376
|
+
|
|
377
|
+
If the file is primarily Unix (LF), calculates Windows (CRLF) hash.
|
|
378
|
+
If the file is primarily Windows (CRLF), calculates Unix (LF) hash.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
contents: File contents as bytes.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Hash with opposite line endings as hex string, or None if no line endings detected.
|
|
385
|
+
"""
|
|
386
|
+
has_crlf, has_standalone_lf, has_standalone_cr = self.__detect_line_endings(contents)
|
|
387
|
+
|
|
388
|
+
if not has_crlf and not has_standalone_lf and not has_standalone_cr:
|
|
389
|
+
return None
|
|
390
|
+
|
|
391
|
+
# Normalize all line endings to LF first
|
|
392
|
+
normalized = contents.replace(b'\r\n', b'\n').replace(b'\r', b'\n')
|
|
393
|
+
|
|
394
|
+
# Determine the dominant line ending type
|
|
395
|
+
if has_crlf and not has_standalone_lf and not has_standalone_cr:
|
|
396
|
+
# File is Windows (CRLF) - produce Unix (LF) hash
|
|
397
|
+
opposite_contents = normalized
|
|
398
|
+
else:
|
|
399
|
+
# File is Unix (LF/CR) or mixed - produce Windows (CRLF) hash
|
|
400
|
+
opposite_contents = normalized.replace(b'\n', b'\r\n')
|
|
401
|
+
|
|
402
|
+
return hashlib.md5(opposite_contents).hexdigest()
|
|
403
|
+
|
|
404
|
+
def wfp_for_contents(self, file: str, bin_file: bool, contents: bytes) -> str: # noqa: PLR0912, PLR0915
|
|
355
405
|
"""
|
|
356
406
|
Generate a Winnowing fingerprint (WFP) for the given file contents
|
|
357
407
|
Parameters
|
|
@@ -371,7 +421,7 @@ class Winnowing(ScanossBase):
|
|
|
371
421
|
content_length = len(contents)
|
|
372
422
|
original_filename = file
|
|
373
423
|
|
|
374
|
-
if
|
|
424
|
+
if self.is_windows:
|
|
375
425
|
original_filename = file.replace('\\', '/')
|
|
376
426
|
wfp_filename = repr(original_filename).strip("'") # return a utf-8 compatible version of the filename
|
|
377
427
|
if self.obfuscate: # hide the real size of the file and its name, but keep the suffix
|
|
@@ -380,6 +430,13 @@ class Winnowing(ScanossBase):
|
|
|
380
430
|
self.file_map[wfp_filename] = original_filename # Save the file name map for later (reverse lookup)
|
|
381
431
|
|
|
382
432
|
wfp = 'file={0},{1},{2}\n'.format(file_md5, content_length, wfp_filename)
|
|
433
|
+
|
|
434
|
+
# Add opposite line ending hash based on line ending analysis
|
|
435
|
+
if not bin_file:
|
|
436
|
+
opposite_hash = self.__calculate_opposite_line_ending_hash(contents)
|
|
437
|
+
if opposite_hash is not None:
|
|
438
|
+
wfp += f'fh2={opposite_hash}\n'
|
|
439
|
+
|
|
383
440
|
# We don't process snippets for binaries, or other uninteresting files, or if we're requested to skip
|
|
384
441
|
if bin_file or self.skip_snippets or self.__skip_snippets(file, contents.decode('utf-8', 'ignore')):
|
|
385
442
|
return wfp
|
|
@@ -467,7 +524,7 @@ class Winnowing(ScanossBase):
|
|
|
467
524
|
for i, byte in enumerate(content):
|
|
468
525
|
c = byte
|
|
469
526
|
if c == ASCII_LF: # When there is a new line
|
|
470
|
-
if
|
|
527
|
+
if list_normalized:
|
|
471
528
|
crc_lines.append(self.crc8_buffer(list_normalized))
|
|
472
529
|
list_normalized = []
|
|
473
530
|
elif last_line + 1 == i:
|
|
@@ -4,10 +4,11 @@ protoc_gen_swagger/options/annotations_pb2.py,sha256=b25EDD6gssUWnFby9gxgcpLIROT
|
|
|
4
4
|
protoc_gen_swagger/options/annotations_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
|
5
5
|
protoc_gen_swagger/options/openapiv2_pb2.py,sha256=vYElGp8E1vGHszvWqX97zNG9GFJ7u2QcdK9ouq0XdyI,14939
|
|
6
6
|
protoc_gen_swagger/options/openapiv2_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
|
|
7
|
-
scanoss/__init__.py,sha256=
|
|
8
|
-
scanoss/cli.py,sha256=
|
|
7
|
+
scanoss/__init__.py,sha256=T3J1XVw8Hyutct8ClOLn1tqQ_NnHlMfxUSVYfLSuVJg,1146
|
|
8
|
+
scanoss/cli.py,sha256=SAB0xuHjEEw20YtYAPSZwrQaVf4JUsm8NRcVArMzd4U,69099
|
|
9
9
|
scanoss/components.py,sha256=b0R9DdKuXqyQiw5nZZwjQ6NJXBr1U9gyx1RI2FP9ozA,14511
|
|
10
|
-
scanoss/constants.py,sha256=
|
|
10
|
+
scanoss/constants.py,sha256=FWCZG8gQputKwV7XwvW1GuwDXL4wDLQyVRGdwygg578,320
|
|
11
|
+
scanoss/cryptography.py,sha256=Q39MOCscP-OFvrnPXaPOMFFkc8OKnf3mC3SgZYEtCog,9407
|
|
11
12
|
scanoss/csvoutput.py,sha256=qNKRwcChSkgIwLm00kZiVX6iHVQUF4Apl-sMbzJ5Taw,10192
|
|
12
13
|
scanoss/cyclonedx.py,sha256=UktDuqZUbXSggdt864Pg8ziTD7sdEQtLxfYL7vd_ZCE,12756
|
|
13
14
|
scanoss/file_filters.py,sha256=_1Ehb_rLnHw_-6N5Zhh4Es2lz6rlx0LozGPn-u52cok,20338
|
|
@@ -18,13 +19,13 @@ scanoss/scanner.py,sha256=ZL8I8KtVyXgUMcl7Ccbip_Q1IlU9WTgI-mFtSpF1JWw,45223
|
|
|
18
19
|
scanoss/scanoss_settings.py,sha256=393JnWLsEZhvMg5tPUGgxmqnBKp8AcLxYsDRbLP7aV4,10650
|
|
19
20
|
scanoss/scanossapi.py,sha256=v4D9i9Impa82Enw-5hZ7KLlscDIpaILNbGOMj3MJXqs,13067
|
|
20
21
|
scanoss/scanossbase.py,sha256=Dkpwxa8NH8XN1iRl03NM_Mkvby0JQ4qfvCiiUrJ5ul0,3163
|
|
21
|
-
scanoss/scanossgrpc.py,sha256=
|
|
22
|
+
scanoss/scanossgrpc.py,sha256=B0rl676-B-ZxqXRp7blXnqbAGPC5rqLAQHG28NoC32E,30004
|
|
22
23
|
scanoss/scanpostprocessor.py,sha256=-JsThlxrU70r92GHykTMERnicdd-6jmwNsE4PH0MN2o,11063
|
|
23
24
|
scanoss/scantype.py,sha256=gFmyVmKQpHWogN2iCmMj032e_sZo4T92xS3_EH5B3Tc,1310
|
|
24
25
|
scanoss/spdxlite.py,sha256=MQqFgQhIO-yrbRwEAQS77HmRgP5GDxff-2JYLVoceA0,28946
|
|
25
26
|
scanoss/threadeddependencies.py,sha256=CAeZnoYd3d1ayoRvfm_aVjYbaTDLsk6DMWDxkoBPvq0,9866
|
|
26
27
|
scanoss/threadedscanning.py,sha256=38ryN_kZGpzmrd_hkuiY9Sb3tOG248canGCDQDmGEwI,9317
|
|
27
|
-
scanoss/winnowing.py,sha256=
|
|
28
|
+
scanoss/winnowing.py,sha256=RsR9jRTR3TzS1pEeKQ2RuYlIG8Q7RnUQFfgPLog6B-A,21679
|
|
28
29
|
scanoss/api/__init__.py,sha256=hx-P78xbDsh6WQIigewkJ7Y7y1fqc_eYnyHC5IZTKmo,1122
|
|
29
30
|
scanoss/api/common/__init__.py,sha256=hx-P78xbDsh6WQIigewkJ7Y7y1fqc_eYnyHC5IZTKmo,1122
|
|
30
31
|
scanoss/api/common/v2/__init__.py,sha256=hx-P78xbDsh6WQIigewkJ7Y7y1fqc_eYnyHC5IZTKmo,1122
|
|
@@ -56,13 +57,13 @@ scanoss/api/vulnerabilities/__init__.py,sha256=IFrDk_DTJgKSZmmU-nuLXuq_s8sQZlrSC
|
|
|
56
57
|
scanoss/api/vulnerabilities/v2/__init__.py,sha256=IFrDk_DTJgKSZmmU-nuLXuq_s8sQZlrSCHhIDMJT4r0,1122
|
|
57
58
|
scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py,sha256=CFhF80av8tenGvn9AIsGEtRJPuV2dC_syA5JLZb2lDw,5464
|
|
58
59
|
scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py,sha256=HlS4k4Zmx6RIAqaO9I96jD-eyF5yU6Xx04pVm7pdqOg,6864
|
|
59
|
-
scanoss/data/build_date.txt,sha256=
|
|
60
|
+
scanoss/data/build_date.txt,sha256=3dG3QmOR7re5yDaUAaCa2noLbeaUxqlhd4ZYGn2-eo0,40
|
|
60
61
|
scanoss/data/scanoss-settings-schema.json,sha256=ClkRYAkjAN0Sk704G8BE_Ok006oQ6YnIGmX84CF8h9w,8798
|
|
61
62
|
scanoss/data/spdx-exceptions.json,sha256=s7UTYxC7jqQXr11YBlIWYCNwN6lRDFTR33Y8rpN_dA4,17953
|
|
62
63
|
scanoss/data/spdx-licenses.json,sha256=A6Z0q82gaTLtnopBfzeIVZjJFxkdRW1g2TuumQc-lII,228794
|
|
63
64
|
scanoss/inspection/__init__.py,sha256=0hjb5ktavp7utJzFhGMPImPaZiHWgilM2HwvTp5lXJE,1122
|
|
64
65
|
scanoss/inspection/copyleft.py,sha256=VynluuCUVxR7uQUz026sp4_yE0Eh3dwpPK0YJ6FNFFw,7579
|
|
65
|
-
scanoss/inspection/policy_check.py,sha256=
|
|
66
|
+
scanoss/inspection/policy_check.py,sha256=wUUQD2WTBibjf3MOPXKOmB8jFGIX0cd1K3flU0pjTjc,17295
|
|
66
67
|
scanoss/inspection/undeclared_component.py,sha256=0YEiWQ4Q1xu7j4YHLPsS3b5Mf9fplHJdHRXgUoQyF2w,10102
|
|
67
68
|
scanoss/inspection/utils/license_utils.py,sha256=Zb6QLmVJb86lKCwZyBsmwakyAtY1SXa54kUyyKmWMqA,5093
|
|
68
69
|
scanoss/scanners/__init__.py,sha256=D4C0lWLuNp8k_BjQZEc07WZcUgAvriVwQWOk063b0ZU,1122
|
|
@@ -73,11 +74,11 @@ scanoss/scanners/scanner_hfh.py,sha256=KksrC1XnOv7mXSlGyo_AJXwtPfKjqffkttdPoNDc-
|
|
|
73
74
|
scanoss/utils/__init__.py,sha256=0hjb5ktavp7utJzFhGMPImPaZiHWgilM2HwvTp5lXJE,1122
|
|
74
75
|
scanoss/utils/abstract_presenter.py,sha256=teiDTxBj5jBMCk2T8i4l1BJPf_u4zBLWrtCTFHSSECM,3148
|
|
75
76
|
scanoss/utils/crc64.py,sha256=TMrwQimSdE6imhFOUL7oAG6Kxu-8qMpGWMuMg8QpSVs,3169
|
|
76
|
-
scanoss/utils/file.py,sha256=
|
|
77
|
+
scanoss/utils/file.py,sha256=62cA9a17TU9ZvfA3FY5HY4-QOajJeSrc8S6xLA_f-3M,2980
|
|
77
78
|
scanoss/utils/simhash.py,sha256=6iu8DOcecPAY36SZjCOzrrLMT9oIE7-gI6QuYwUQ7B0,5793
|
|
78
|
-
scanoss-1.
|
|
79
|
-
scanoss-1.
|
|
80
|
-
scanoss-1.
|
|
81
|
-
scanoss-1.
|
|
82
|
-
scanoss-1.
|
|
83
|
-
scanoss-1.
|
|
79
|
+
scanoss-1.25.0.dist-info/licenses/LICENSE,sha256=LLUaXoiyOroIbr5ubAyrxBOwSRLTm35ETO2FmLpy8QQ,1074
|
|
80
|
+
scanoss-1.25.0.dist-info/METADATA,sha256=mvajvjxQSlBpII6nFUq44pp7-Kedgf9hXGblfCjqjWU,6060
|
|
81
|
+
scanoss-1.25.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
82
|
+
scanoss-1.25.0.dist-info/entry_points.txt,sha256=Uy28xnaDL5KQ7V77sZD5VLDXPNxYYzSr5tsqtiXVzAs,48
|
|
83
|
+
scanoss-1.25.0.dist-info/top_level.txt,sha256=V11PrQ6Pnrc-nDF9xnisnJ8e6-i7HqSIKVNqduRWcL8,27
|
|
84
|
+
scanoss-1.25.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|