#!/usr/bin/env python3
"""Искусственные локальные тесты программы, не свидетельства владельца."""
import base64,copy,datetime as dt,hashlib,importlib.util,json,sys
from pathlib import Path
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
spec=importlib.util.spec_from_file_location('verifier',Path(__file__).with_name('проверить-свидетельство-владельца.py'));v=importlib.util.module_from_spec(spec);spec.loader.exec_module(v)
private=Ed25519PrivateKey.generate();pub=private.public_key().public_bytes(serialization.Encoding.PEM,serialization.PublicFormat.SubjectPublicKeyInfo)
manifest=json.dumps({'редакция':v.RELEASE,'публичное_имя':'война.com','канонический_адрес':'https://xn--80advqd.com/','файлы':[{'путь':'index.html','искусственный':True}]},ensure_ascii=False).encode();now=dt.datetime(2026,9,4,23,tzinfo=dt.timezone.utc)
r={k:'ИСКУССТВЕННЫЙ ТЕСТ' for k in v.FIELDS};r.update({'формат':'война-свидетельство-владельца-2','редакция':v.RELEASE,'публичное_имя':'война.com','канонический_адрес':'https://xn--80advqd.com/','рубеж':'ВР-01','состояние':'заявлено исполнение','время_наблюдения':'2026-09-04T12:00:00Z','ша256_приложения':'a'*64,'ша256_манифеста_выпуска':hashlib.sha256(manifest).hexdigest(),'ша256_ключа':v.public_key(pub)[1],'адрес_публикации_ключа':'https://example.org/test-key.pem'})
def sign(x):
 x=copy.deepcopy(x);x.pop('подпись',None);x.pop('отпечаток_записи',None);raw=v.canonical(x);x['отпечаток_записи']=hashlib.sha256(raw).hexdigest();x['подпись']=base64.b64encode(private.sign(raw)).decode();return x
signed=sign(r);tests=[]
def check(name,predicate):
 try:ok=bool(predicate());tests.append({'проверка':name,'риск':name,'вход':'Искусственный вход из этого сценария','пройдено':ok,'предел':'Локальная машина; внешняя публикация подменена; не независимое исполнение.','команда':'python инструменты/испытать-приёмник-владельца.py доказательства/редакция-4.11.0/приёмник-владельца.json'})
 except Exception as e:tests.append({'проверка':name,'пройдено':False,'ошибка':str(e)})
mock=lambda u:(pub,u)
verify=lambda x,**kwargs:v.verify(x,pub,manifest,now=now,**kwargs)
check('Настоящая Ed25519-подпись искусственного входа проверяется локально',lambda:verify(signed)['подпись_проверена'])
check('Локальная подпись не закрывает внешнюю публикацию',lambda:not verify(signed)['допуск_к_содержательной_проверке'])
check('Имитированная публикация даёт лишь допуск, не закрытие рубежа',lambda:(lambda x:x['допуск_к_содержательной_проверке'] and not x['рубеж_закрыт'])(verify(signed,network=True,fetcher=mock)))
for name,k,val in [('Непустая строка не является подписью','подпись','подписано'),('Подмена текста','результат','подмена'),('Другой отпечаток записи','отпечаток_записи','b'*64)]:
 x={**signed,k:val};check(name,lambda x=x:not verify(x)['подпись_проверена'])
for name,k,val in [('Другая редакция','редакция','4.2.0-wip'),('Другой проект','публичное_имя','другой.com'),('Будущая дата','время_наблюдения','2099-01-01T00:00:00Z'),('Не UTC','время_наблюдения','2026-09-04T12:00:00+03:00'),('Ключ на проверяемом сайте','адрес_публикации_ключа','https://xn--80advqd.com/key'),('Нет HTTPS','адрес_публикации_ключа','http://example.org/key'),('Ключ на частном адресе','адрес_публикации_ключа','https://127.0.0.1/key'),('Неизвестный рубеж','рубеж','ВР-99'),('Неподходящий манифест','ша256_манифеста_выпуска','e'*64)]:
 x=sign({**r,k:val});check(name,lambda x=x:not verify(x)['подпись_проверена'])
other=Ed25519PrivateKey.generate().public_key().public_bytes(serialization.Encoding.PEM,serialization.PublicFormat.SubjectPublicKeyInfo)
check('Чужой локальный ключ',lambda:not v.verify(signed,other,manifest,now=now)['подпись_проверена'])
check('Чужой опубликованный ключ',lambda:not verify(signed,network=True,fetcher=lambda u:(other,u))['допуск_к_содержательной_проверке'])
def fail(u):raise OSError('Искусственный отказ сети')
check('Отказ сети сохраняет открытый рубеж',lambda:not verify(signed,network=True,fetcher=fail)['допуск_к_содержательной_проверке'])
check('Посторонние поля не принимаются',lambda:not verify({**signed,'проверено':True})['подпись_проверена'])
check('Закрытый ключ не нужен при проверке',lambda:len(pub)>32 and verify(signed)['подпись_проверена'])
for value in [None,[],1,'строка']:
 check('Корень не является объектом: '+repr(value),lambda value=value:not verify(value)['подпись_проверена'])
check('Перенаправление на другой общедоступный домен не подтверждает публикацию',lambda:not verify(signed,network=True,fetcher=lambda u:(pub,'https://example.net/key'))['допуск_к_содержательной_проверке'])
check('Перенаправление на проверяемый домен запрещено',lambda:not verify(signed,network=True,fetcher=lambda u:(pub,'https://xn--80advqd.com/key'))['допуск_к_содержательной_проверке'])
def redirect_blocked():
 try:v.PublicRedirect().redirect_request(None,None,302,'Found',{},'https://example.net/key');return False
 except ValueError:return True
check('Обработчик HTTP-перенаправления прекращает загрузку',redirect_blocked)
for value in ['2026-09-04T12:00:00+00:00','2026-09-04 12:00:00Z','2026-09-04T12:00:00Z\n']:
 check('Неоднозначная запись времени не принимается: '+repr(value),lambda value=value:not verify(sign({**r,'время_наблюдения':value}))['подпись_проверена'])
wrong_manifest=json.dumps({'редакция':'4.3.0-wip','публичное_имя':'война.com','канонический_адрес':'https://xn--80advqd.com/','файлы':[{'путь':'index.html'}]},ensure_ascii=False).encode()
wrong_record=sign({**r,'ша256_манифеста_выпуска':hashlib.sha256(wrong_manifest).hexdigest()})
check('Совпадение SHA-256 старого манифеста не делает его текущим',lambda:not v.verify(wrong_record,pub,wrong_manifest,now=now)['подпись_проверена'])
out={'редакция':v.RELEASE,'независимая_машина':'НЕ ИСПЫТАНО: другая машина и владелец не предоставлены','граница':'Только искусственные локальные входы. Внешняя публикация имитируется; реальных владельческих свидетельств нет. Закрытые испытательные ключи не сохранены.','всего':len(tests),'пройдено':sum(x['пройдено'] for x in tests),'проверки':tests}
text=json.dumps(out,ensure_ascii=False,indent=2)+'\n'
if len(sys.argv)>1:Path(sys.argv[1]).write_text(text)
print(text);sys.exit(0 if all(x['пройдено'] for x in tests) else 1)
