Source code for credsweeper.deep_scanner.snk_scanner
import base64
import logging
import struct
from abc import ABC
from typing import List, Optional, Callable
from credsweeper.common.constants import Severity, Confidence, ASCII
from credsweeper.credentials.candidate import Candidate
from credsweeper.deep_scanner.abstract_scanner import AbstractScanner
from credsweeper.file_handler.data_content_provider import DataContentProvider
logger = logging.getLogger(__name__)
[docs]
class SnkScanner(AbstractScanner, ABC):
"""Implements Snk files scanning"""
[docs]
@staticmethod
def match(data: bytes | bytearray) -> bool:
"""https://github.com/dotnet/runtime/blob/main/docs/project/strong-name-signing.md"""
if data.startswith(b"\x07\x02\x00\x00\x00$\x00\x00RSA2"):
return True
return False
[docs]
@staticmethod
def extract(data: bytes) -> None | str:
"""Extracts RSA parameters from .snk file and transform they into JWK representation
Args:
data: content of .snk file
Return:
JWK string if successful format recognized or None if fail
"""
bit_len = struct.unpack_from("<I", data, offset=12)[0]
if 0xF & bit_len or not 64 <= bit_len <= 65536:
logger.warning("Wrong bits length %d", bit_len)
return None
o_size = bit_len // 8
h_size = bit_len // 16
offset = 20
components_size = 9 * h_size
if len(data) != offset + components_size:
logger.warning("Components size %d mismatches buffer %d", components_size, len(data))
return None
_pub_exp = data[16:offset]
pub_exp = struct.unpack_from("<I", _pub_exp)[0]
_modulus = data[offset:offset + o_size]
offset += o_size
modulus = int.from_bytes(_modulus, byteorder="little", signed=False)
_prime1 = data[offset:offset + h_size]
offset += h_size
prime1 = int.from_bytes(_prime1, byteorder="little", signed=False)
_prime2 = data[offset:offset + h_size]
offset += h_size
prime2 = int.from_bytes(_prime2, byteorder="little", signed=False)
_exponent1 = data[offset:offset + h_size]
offset += h_size
exponent1 = int.from_bytes(_exponent1, byteorder="little", signed=False)
_exponent2 = data[offset:offset + h_size]
offset += h_size
exponent2 = int.from_bytes(_exponent2, byteorder="little", signed=False)
_coefficient = data[offset:offset + h_size]
offset += h_size
coefficient = int.from_bytes(_coefficient, byteorder="little", signed=False)
_private_exponent = data[offset:offset + o_size]
private_exponent = int.from_bytes(_private_exponent, byteorder="little", signed=False)
# simple mutual check
if modulus == prime1 * prime2 \
and (phi := (prime1 - 1) * (prime2 - 1)) \
and 1 == (pub_exp * private_exponent) % phi \
and exponent1 == private_exponent % (prime1 - 1) \
and exponent2 == private_exponent % (prime2 - 1) \
and 1 == (coefficient * prime2) % prime1:
# JWK representation
__bytes4jwk: Callable[[bytes], str] = \
lambda x: base64.urlsafe_b64encode(x).rstrip(b"=").decode(ASCII, errors="strict")
return (f'{{"kty":"RSA","e":"{__bytes4jwk(_pub_exp)}",'
f'"n":"{__bytes4jwk(_modulus)}","d":"{__bytes4jwk(_private_exponent)}",'
f'"p":"{__bytes4jwk(_prime1)}","q":"{__bytes4jwk(_prime2)}",'
f'"dp":"{__bytes4jwk(_exponent1)}","dq":"{__bytes4jwk(_exponent2)}",'
f'"qi":"{__bytes4jwk(_coefficient)}"}}')
logger.warning("Components consistency check fail")
return None
[docs]
def data_scan(
self, #
data_provider: DataContentProvider, #
depth: int, #
recursive_limit_size: int) -> Optional[List[Candidate]]:
"""Scan .snk data for full (private+public) keys"""
try:
if value := SnkScanner.extract(data_provider.data):
# RSA key components are consistent
candidate = Candidate.get_dummy_candidate(
self.config, #
data_provider.file_path, #
data_provider.file_type, #
f"{data_provider.info}|SNK", #
"Strong Name Key")
candidate.severity = Severity.HIGH
candidate.confidence = Confidence.STRONG
candidate.line_data_list[0].line = candidate.line_data_list[0].value = value
candidate.line_data_list[0].value_start = 0
candidate.line_data_list[0].value_end = len(value)
return [candidate]
except Exception as exc:
logger.warning(exc)
return None