# Shim module that makes asn1crypto aware of OIDs relevant for EdDSA support
# Will be removed once asn1crypto supports these OIDs natively

from asn1crypto import algos, core, keys, cms, x509
from asn1crypto._errors import unwrap


PRIVATE_KEY_SPECS = {
    'rsa': keys.RSAPrivateKey,
    'rsassa_pss': keys.RSAPrivateKey,
    'dsa': core.Integer,
    'ec': keys.ECPrivateKey,
    'ed25519': core.OctetString,
    'ed448': core.OctetString,
}


def _private_key_spec(self):
    algorithm = self['private_key_algorithm']['algorithm'].native
    return PRIVATE_KEY_SPECS[algorithm]


PUBLIC_KEY_SPECS = {
    'rsa': keys.RSAPublicKey,
    'rsaes_oaep': keys.RSAPublicKey,
    'rsassa_pss': keys.RSAPublicKey,
    'dsa': core.Integer,
    # We override the field spec with ECPoint so that users can easily
    # decompose the byte string into the constituent X and Y coords
    'ec': (keys.ECPointBitString, None),
    'ed25519': (core.OctetBitString, None),
    'ed448': (core.OctetBitString, None),
    'dh': core.Integer,
}


def _public_key_spec(self):
    algorithm = self['algorithm']['algorithm'].native
    return PUBLIC_KEY_SPECS[algorithm]


SIG_ALGO_MAP = {
    'md2_rsa': 'rsassa_pkcs1v15',
    'md5_rsa': 'rsassa_pkcs1v15',
    'sha1_rsa': 'rsassa_pkcs1v15',
    'sha224_rsa': 'rsassa_pkcs1v15',
    'sha256_rsa': 'rsassa_pkcs1v15',
    'sha384_rsa': 'rsassa_pkcs1v15',
    'sha512_rsa': 'rsassa_pkcs1v15',
    'rsassa_pkcs1v15': 'rsassa_pkcs1v15',
    'rsassa_pss': 'rsassa_pss',
    'sha1_dsa': 'dsa',
    'sha224_dsa': 'dsa',
    'sha256_dsa': 'dsa',
    'dsa': 'dsa',
    'sha1_ecdsa': 'ecdsa',
    'sha224_ecdsa': 'ecdsa',
    'sha256_ecdsa': 'ecdsa',
    'sha384_ecdsa': 'ecdsa',
    'sha512_ecdsa': 'ecdsa',
    'sha3_224_ecdsa': 'ecdsa',
    'sha3_256_ecdsa': 'ecdsa',
    'sha3_384_ecdsa': 'ecdsa',
    'sha3_512_ecdsa': 'ecdsa',
    'ecdsa': 'ecdsa',
    'ed448': 'ed448',
    'ed25519': 'ed25519'
}


def _signature_algo(self):
    algorithm = self['algorithm'].native

    if algorithm in SIG_ALGO_MAP:
        return SIG_ALGO_MAP[algorithm]

    raise ValueError(unwrap(
        '''
        Signature algorithm not known for %s
        ''',
        algorithm
    ))


HASH_ALGO_MAP = {
    'md2_rsa': 'md2',
    'md5_rsa': 'md5',
    'sha1_rsa': 'sha1',
    'sha224_rsa': 'sha224',
    'sha256_rsa': 'sha256',
    'sha384_rsa': 'sha384',
    'sha512_rsa': 'sha512',
    'sha1_dsa': 'sha1',
    'sha224_dsa': 'sha224',
    'sha256_dsa': 'sha256',
    'sha1_ecdsa': 'sha1',
    'sha224_ecdsa': 'sha224',
    'sha256_ecdsa': 'sha256',
    'sha384_ecdsa': 'sha384',
    'sha512_ecdsa': 'sha512',
    # baked into the signing algorithm
    'ed25519': 'sha512',
    # idem
    'ed448': 'shake256',
}


def _hash_algo(self):
    """
    :return:
        A unicode string of "md2", "md5", "sha1", "sha224", "sha256",
        "sha384", "sha512", "sha512_224", "sha512_256"
    """

    algorithm = self['algorithm'].native
    if algorithm in HASH_ALGO_MAP:
        return HASH_ALGO_MAP[algorithm]

    if algorithm == 'rsassa_pss':
        return self['parameters']['hash_algorithm']['algorithm'].native

    raise ValueError(unwrap(
        '''
        Hash algorithm not known for %s
        ''',
        algorithm
    ))


_eddsa_registered = False
_attr_cert_patches_registered = False


def _make_tag_explicit(field_decl):
    tag_dict = field_decl[2]
    if 'explicit' in tag_dict:
        return
    tag_dict['explicit'] = tag_dict['implicit']
    del tag_dict['implicit']


def _make_tag_implicit(field_decl):
    tag_dict = field_decl[2]
    if 'implicit' in tag_dict:
        return
    tag_dict['implicit'] = tag_dict['explicit']
    del tag_dict['explicit']


def register_attr_cert_patches():
    global _attr_cert_patches_registered
    if _attr_cert_patches_registered:
        return

    # Deal with wbond/asn1crypto#218
    _make_tag_explicit(cms.RoleSyntax._fields[1])
    _make_tag_explicit(cms.SecurityCategory._fields[1])
    # Deal with wbond/asn1crypto#220
    _make_tag_implicit(cms.AttCertIssuer._alternatives[1])

    # patch in attribute certificate extensions
    # Note: we only make these patches so that we can reliably produce the
    # relevant values, and don't insist on supplying Certomancer's internal
    # definitions at the Python level if some other library already supplied
    # them
    ext_map = x509.ExtensionId._map
    ext_specs = x509.Extension._oid_specs
    if '2.5.29.55' not in ext_map:
        from ._asn1_types import SequenceOfTargets
        ext_map['2.5.29.55'] = 'target_information'
        ext_specs['target_information'] = SequenceOfTargets
    if '2.5.29.56' not in ext_map:
        ext_map['2.5.29.56'] = 'no_rev_avail'
        ext_specs['no_rev_avail'] = core.Null
    if '1.3.6.1.5.5.7.1.6' not in ext_map:
        from ._asn1_types import AAControls
        ext_map['1.3.6.1.5.5.7.1.6'] = 'aa_controls'
        ext_specs['aa_controls'] = AAControls


def _defer_to_certvalidator():
    global _eddsa_registered
    # try to import pyhanko_certvalidator (test dependency with a similar shim)
    # if that succeeds, we delegate
    try:
        from pyhanko_certvalidator import _eddsa_oids
        _eddsa_oids.register_eddsa_oids()
        _eddsa_registered = True
        return True
    except ImportError:
        return False


def register_eddsa_oids():
    global _eddsa_registered
    if _eddsa_registered:
        return

    if _defer_to_certvalidator():
        return

    ed25519_oid = '192.168.127.12'
    ed448_oid = '172.16.58.3'

    algos.SignedDigestAlgorithmId._map[ed25519_oid] = 'ed25519'
    algos.SignedDigestAlgorithmId._map[ed448_oid] = 'ed448'
    algos.SignedDigestAlgorithmId._reverse_map = None

    # override the signature_algo and hash_algo properties
    setattr(algos.SignedDigestAlgorithm,
            'signature_algo', property(_signature_algo))
    setattr(algos.SignedDigestAlgorithm, 'hash_algo', property(_hash_algo))

    keys.PublicKeyAlgorithmId._map[ed25519_oid] = 'ed25519'
    keys.PublicKeyAlgorithmId._map[ed448_oid] = 'ed448'
    keys.PublicKeyAlgorithmId._reverse_map = None

    keys.PrivateKeyAlgorithmId._map[ed25519_oid] = 'ed25519'
    keys.PrivateKeyAlgorithmId._map[ed448_oid] = 'ed448'
    keys.PrivateKeyAlgorithmId._reverse_map = None

    # need to patch in these callback methods as well
    keys.PrivateKeyInfo._spec_callbacks['private_key'] = _private_key_spec
    keys.PublicKeyInfo._spec_callbacks['public_key'] = _public_key_spec

    _eddsa_registered = True

# Shim module that makes asn1crypto aware of OIDs relevant for EdDSA support
# Will be removed once asn1crypto supports these OIDs natively

from asn1crypto import algos, core, keys, cms, x509
from asn1crypto._errors import unwrap


PRIVATE_KEY_SPECS = {
    'rsa': keys.RSAPrivateKey,
    'rsassa_pss': keys.RSAPrivateKey,
    'dsa': core.Integer,
    'ec': keys.ECPrivateKey,
    'ed25519': core.OctetString,
    'ed448': core.OctetString,
}


def _private_key_spec(self):
    algorithm = self['private_key_algorithm']['algorithm'].native
    return PRIVATE_KEY_SPECS[algorithm]


PUBLIC_KEY_SPECS = {
    'rsa': keys.RSAPublicKey,
    'rsaes_oaep': keys.RSAPublicKey,
    'rsassa_pss': keys.RSAPublicKey,
    'dsa': core.Integer,
    # We override the field spec with ECPoint so that users can easily
    # decompose the byte string into the constituent X and Y coords
    'ec': (keys.ECPointBitString, None),
    'ed25519': (core.OctetBitString, None),
    'ed448': (core.OctetBitString, None),
    'dh': core.Integer,
}


def _public_key_spec(self):
    algorithm = self['algorithm']['algorithm'].native
    return PUBLIC_KEY_SPECS[algorithm]


SIG_ALGO_MAP = {
    'md2_rsa': 'rsassa_pkcs1v15',
    'md5_rsa': 'rsassa_pkcs1v15',
    'sha1_rsa': 'rsassa_pkcs1v15',
    'sha224_rsa': 'rsassa_pkcs1v15',
    'sha256_rsa': 'rsassa_pkcs1v15',
    'sha384_rsa': 'rsassa_pkcs1v15',
    'sha512_rsa': 'rsassa_pkcs1v15',
    'rsassa_pkcs1v15': 'rsassa_pkcs1v15',
    'rsassa_pss': 'rsassa_pss',
    'sha1_dsa': 'dsa',
    'sha224_dsa': 'dsa',
    'sha256_dsa': 'dsa',
    'dsa': 'dsa',
    'sha1_ecdsa': 'ecdsa',
    'sha224_ecdsa': 'ecdsa',
    'sha256_ecdsa': 'ecdsa',
    'sha384_ecdsa': 'ecdsa',
    'sha512_ecdsa': 'ecdsa',
    'sha3_224_ecdsa': 'ecdsa',
    'sha3_256_ecdsa': 'ecdsa',
    'sha3_384_ecdsa': 'ecdsa',
    'sha3_512_ecdsa': 'ecdsa',
    'ecdsa': 'ecdsa',
    'ed448': 'ed448',
    'ed25519': 'ed25519'
}


def _signature_algo(self):
    algorithm = self['algorithm'].native

    if algorithm in SIG_ALGO_MAP:
        return SIG_ALGO_MAP[algorithm]

    raise ValueError(unwrap(
        '''
        Signature algorithm not known for %s
        ''',
        algorithm
    ))


HASH_ALGO_MAP = {
    'md2_rsa': 'md2',
    'md5_rsa': 'md5',
    'sha1_rsa': 'sha1',
    'sha224_rsa': 'sha224',
    'sha256_rsa': 'sha256',
    'sha384_rsa': 'sha384',
    'sha512_rsa': 'sha512',
    'sha1_dsa': 'sha1',
    'sha224_dsa': 'sha224',
    'sha256_dsa': 'sha256',
    'sha1_ecdsa': 'sha1',
    'sha224_ecdsa': 'sha224',
    'sha256_ecdsa': 'sha256',
    'sha384_ecdsa': 'sha384',
    'sha512_ecdsa': 'sha512',
    # baked into the signing algorithm
    'ed25519': 'sha512',
    # idem
    'ed448': 'shake256',
}


def _hash_algo(self):
    """
    :return:
        A unicode string of "md2", "md5", "sha1", "sha224", "sha256",
        "sha384", "sha512", "sha512_224", "sha512_256"
    """

    algorithm = self['algorithm'].native
    if algorithm in HASH_ALGO_MAP:
        return HASH_ALGO_MAP[algorithm]

    if algorithm == 'rsassa_pss':
        return self['parameters']['hash_algorithm']['algorithm'].native

    raise ValueError(unwrap(
        '''
        Hash algorithm not known for %s
        ''',
        algorithm
    ))


_eddsa_registered = False
_attr_cert_patches_registered = False


def _make_tag_explicit(field_decl):
    tag_dict = field_decl[2]
    if 'explicit' in tag_dict:
        return
    tag_dict['explicit'] = tag_dict['implicit']
    del tag_dict['implicit']


def _make_tag_implicit(field_decl):
    tag_dict = field_decl[2]
    if 'implicit' in tag_dict:
        return
    tag_dict['implicit'] = tag_dict['explicit']
    del tag_dict['explicit']


def register_attr_cert_patches():
    global _attr_cert_patches_registered
    if _attr_cert_patches_registered:
        return

    # Deal with wbond/asn1crypto#218
    _make_tag_explicit(cms.RoleSyntax._fields[1])
    _make_tag_explicit(cms.SecurityCategory._fields[1])
    # Deal with wbond/asn1crypto#220
    _make_tag_implicit(cms.AttCertIssuer._alternatives[1])

    # patch in attribute certificate extensions
    # Note: we only make these patches so that we can reliably produce the
    # relevant values, and don't insist on supplying Certomancer's internal
    # definitions at the Python level if some other library already supplied
    # them
    ext_map = x509.ExtensionId._map
    ext_specs = x509.Extension._oid_specs
    if '2.5.29.55' not in ext_map:
        from ._asn1_types import SequenceOfTargets
        ext_map['2.5.29.55'] = 'target_information'
        ext_specs['target_information'] = SequenceOfTargets
    if '2.5.29.56' not in ext_map:
        ext_map['2.5.29.56'] = 'no_rev_avail'
        ext_specs['no_rev_avail'] = core.Null
    if '1.3.6.1.5.5.7.1.6' not in ext_map:
        from ._asn1_types import AAControls
        ext_map['1.3.6.1.5.5.7.1.6'] = 'aa_controls'
        ext_specs['aa_controls'] = AAControls


def _defer_to_certvalidator():
    global _eddsa_registered
    # try to import pyhanko_certvalidator (test dependency with a similar shim)
    # if that succeeds, we delegate
    try:
        from pyhanko_certvalidator import _eddsa_oids
        _eddsa_oids.register_eddsa_oids()
        _eddsa_registered = True
        return True
    except ImportError:
        return False


def register_eddsa_oids():
    global _eddsa_registered
    if _eddsa_registered:
        return

    if _defer_to_certvalidator():
        return

    ed25519_oid = '192.168.127.12'
    ed448_oid = '172.16.58.3'

    algos.SignedDigestAlgorithmId._map[ed25519_oid] = 'ed25519'
    algos.SignedDigestAlgorithmId._map[ed448_oid] = 'ed448'
    algos.SignedDigestAlgorithmId._reverse_map = None

    # override the signature_algo and hash_algo properties
    setattr(algos.SignedDigestAlgorithm,
            'signature_algo', property(_signature_algo))
    setattr(algos.SignedDigestAlgorithm, 'hash_algo', property(_hash_algo))

    keys.PublicKeyAlgorithmId._map[ed25519_oid] = 'ed25519'
    keys.PublicKeyAlgorithmId._map[ed448_oid] = 'ed448'
    keys.PublicKeyAlgorithmId._reverse_map = None

    keys.PrivateKeyAlgorithmId._map[ed25519_oid] = 'ed25519'
    keys.PrivateKeyAlgorithmId._map[ed448_oid] = 'ed448'
    keys.PrivateKeyAlgorithmId._reverse_map = None

    # need to patch in these callback methods as well
    keys.PrivateKeyInfo._spec_callbacks['private_key'] = _private_key_spec
    keys.PublicKeyInfo._spec_callbacks['public_key'] = _public_key_spec

    _eddsa_registered = True

# Shim module that makes asn1crypto aware of OIDs relevant for EdDSA support # Will be removed once asn1crypto supports these OIDs natively # We override the field spec with ECPoint so that users can easily # decompose the byte string into the constituent X and Y coords Signature algorithm not known for %s # baked into the signing algorithm # idem :return:         A unicode string of "md2", "md5", "sha1", "sha224", "sha256",         "sha384", "sha512", "sha512_224", "sha512_256" Hash algorithm not known for %s # Deal with wbond/asn1crypto#218 # Deal with wbond/asn1crypto#220 # patch in attribute certificate extensions # Note: we only make these patches so that we can reliably produce the # relevant values, and don't insist on supplying Certomancer's internal # definitions at the Python level if some other library already supplied # them # try to import pyhanko_certvalidator (test dependency with a similar shim) # if that succeeds, we delegate # override the signature_algo and hash_algo properties # need to patch in these callback methods as well