#
# This file is part of pyasn1-modules software.
#
# Created by Russ Housley with assistance from asn1ate v.0.6.0.
#
# Copyright (c) 2019, Vigil Security, LLC
# License: https://www.pysnmp.com/pyasn1/license.html
#
# PKCS#9: Selected Attribute Types (Version 2.0)
#
# ASN.1 source from:
# https://www.rfc-editor.org/rfc/rfc2985.txt
#

from pyasn1.type import (
    char,
    constraint,
    namedtype,
    namedval,
    opentype,
    tag,
    univ,
    useful,
)

from pyasn1_modules import rfc5280, rfc5652, rfc5958, rfc7292


def _OID(*components):
    output = []
    for x in tuple(components):
        if isinstance(x, univ.ObjectIdentifier):
            output.extend(list(x))
        else:
            output.append(int(x))

    return univ.ObjectIdentifier(output)


MAX = float("inf")


# Imports from RFC 5280

AlgorithmIdentifier = rfc5280.AlgorithmIdentifier

Attribute = rfc5280.Attribute

EmailAddress = rfc5280.EmailAddress

Extensions = rfc5280.Extensions

Time = rfc5280.Time

X520countryName = rfc5280.X520countryName

X520SerialNumber = rfc5280.X520SerialNumber


# Imports from RFC 5652

ContentInfo = rfc5652.ContentInfo

ContentType = rfc5652.ContentType

Countersignature = rfc5652.Countersignature

MessageDigest = rfc5652.MessageDigest

SignerInfo = rfc5652.SignerInfo

SigningTime = rfc5652.SigningTime


# Imports from RFC 5958

EncryptedPrivateKeyInfo = rfc5958.EncryptedPrivateKeyInfo


# Imports from RFC 7292

PFX = rfc7292.PFX


# TODO:
# Need a place to import PKCS15Token; it does not yet appear in an RFC


# SingleAttribute is the same as Attribute in RFC 5280, except that the
# attrValues SET must have one and only one member


class AttributeType(univ.ObjectIdentifier):
    pass


class AttributeValue(univ.Any):
    pass


class AttributeValues(univ.SetOf):
    pass


AttributeValues.componentType = AttributeValue()


class SingleAttributeValues(univ.SetOf):
    pass


SingleAttributeValues.componentType = AttributeValue()


class SingleAttribute(univ.Sequence):
    pass


SingleAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType("type", AttributeType()),
    namedtype.NamedType(
        "values",
        AttributeValues().subtype(sizeSpec=constraint.ValueSizeConstraint(1, 1)),
        openType=opentype.OpenType("type", rfc5280.certificateAttributesMap),
    ),
)


# CMSAttribute is the same as Attribute in RFC 5652, and CMSSingleAttribute
# is the companion where the attrValues SET must have one and only one member

CMSAttribute = rfc5652.Attribute


class CMSSingleAttribute(univ.Sequence):
    pass


CMSSingleAttribute.componentType = namedtype.NamedTypes(
    namedtype.NamedType("attrType", AttributeType()),
    namedtype.NamedType(
        "attrValues",
        AttributeValues().subtype(sizeSpec=constraint.ValueSizeConstraint(1, 1)),
        openType=opentype.OpenType("attrType", rfc5652.cmsAttributesMap),
    ),
)


# DirectoryString is the same as RFC 5280, except the length is limited to 255


class DirectoryString(univ.Choice):
    pass


DirectoryString.componentType = namedtype.NamedTypes(
    namedtype.NamedType(
        "teletexString",
        char.TeletexString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, 255)
        ),
    ),
    namedtype.NamedType(
        "printableString",
        char.PrintableString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, 255)
        ),
    ),
    namedtype.NamedType(
        "universalString",
        char.UniversalString().subtype(
            subtypeSpec=constraint.ValueSizeConstraint(1, 255)
        ),
    ),
    namedtype.NamedType(
        "utf8String",
        char.UTF8String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, 255)),
    ),
    namedtype.NamedType(
        "bmpString",
        char.BMPString().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, 255)),
    ),
)


# PKCS9String is DirectoryString with an additional choice of IA5String,
# and the SIZE is limited to 255


class PKCS9String(univ.Choice):
    pass


PKCS9String.componentType = namedtype.NamedTypes(
    namedtype.NamedType(
        "ia5String",
        char.IA5String().subtype(subtypeSpec=constraint.ValueSizeConstraint(1, 255)),
    ),
    namedtype.NamedType("directoryString", DirectoryString()),
)


# Upper Bounds

pkcs_9_ub_pkcs9String = univ.Integer(255)

pkcs_9_ub_challengePassword = univ.Integer(pkcs_9_ub_pkcs9String)

pkcs_9_ub_emailAddress = univ.Integer(pkcs_9_ub_pkcs9String)

pkcs_9_ub_friendlyName = univ.Integer(pkcs_9_ub_pkcs9String)

pkcs_9_ub_match = univ.Integer(pkcs_9_ub_pkcs9String)

pkcs_9_ub_signingDescription = univ.Integer(pkcs_9_ub_pkcs9String)

pkcs_9_ub_unstructuredAddress = univ.Integer(pkcs_9_ub_pkcs9String)

pkcs_9_ub_unstructuredName = univ.Integer(pkcs_9_ub_pkcs9String)


ub_name = univ.Integer(32768)

pkcs_9_ub_placeOfBirth = univ.Integer(ub_name)

pkcs_9_ub_pseudonym = univ.Integer(ub_name)


# Object Identifier Arcs

ietf_at = _OID(1, 3, 6, 1, 5, 5, 7, 9)

id_at = _OID(2, 5, 4)

pkcs_9 = _OID(1, 2, 840, 113549, 1, 9)

pkcs_9_mo = _OID(pkcs_9, 0)

smime = _OID(pkcs_9, 16)

certTypes = _OID(pkcs_9, 22)

crlTypes = _OID(pkcs_9, 23)

pkcs_9_oc = _OID(pkcs_9, 24)

pkcs_9_at = _OID(pkcs_9, 25)

pkcs_9_sx = _OID(pkcs_9, 26)

pkcs_9_mr = _OID(pkcs_9, 27)


# Object Identifiers for Syntaxes for use with LDAP-accessible directories

pkcs_9_sx_pkcs9String = _OID(pkcs_9_sx, 1)

pkcs_9_sx_signingTime = _OID(pkcs_9_sx, 2)


# Object Identifiers for object classes

pkcs_9_oc_pkcsEntity = _OID(pkcs_9_oc, 1)

pkcs_9_oc_naturalPerson = _OID(pkcs_9_oc, 2)


# Object Identifiers for matching rules

pkcs_9_mr_caseIgnoreMatch = _OID(pkcs_9_mr, 1)

pkcs_9_mr_signingTimeMatch = _OID(pkcs_9_mr, 2)


# PKCS #7 PDU

pkcs_9_at_pkcs7PDU = _OID(pkcs_9_at, 5)

pKCS7PDU = Attribute()
pKCS7PDU["type"] = pkcs_9_at_pkcs7PDU
pKCS7PDU["values"][0] = ContentInfo()


# PKCS #12 token

pkcs_9_at_userPKCS12 = _OID(2, 16, 840, 1, 113730, 3, 1, 216)

userPKCS12 = Attribute()
userPKCS12["type"] = pkcs_9_at_userPKCS12
userPKCS12["values"][0] = PFX()


# PKCS #15 token

pkcs_9_at_pkcs15Token = _OID(pkcs_9_at, 1)

# TODO: Once PKCS15Token can be imported, this can be included
#
# pKCS15Token = Attribute()
# userPKCS12['type'] = pkcs_9_at_pkcs15Token
# userPKCS12['values'][0] = PKCS15Token()


# PKCS #8 encrypted private key information

pkcs_9_at_encryptedPrivateKeyInfo = _OID(pkcs_9_at, 2)

encryptedPrivateKeyInfo = Attribute()
encryptedPrivateKeyInfo["type"] = pkcs_9_at_encryptedPrivateKeyInfo
encryptedPrivateKeyInfo["values"][0] = EncryptedPrivateKeyInfo()


# Electronic-mail address

pkcs_9_at_emailAddress = rfc5280.id_emailAddress

emailAddress = Attribute()
emailAddress["type"] = pkcs_9_at_emailAddress
emailAddress["values"][0] = EmailAddress()


# Unstructured name

pkcs_9_at_unstructuredName = _OID(pkcs_9, 2)

unstructuredName = Attribute()
unstructuredName["type"] = pkcs_9_at_unstructuredName
unstructuredName["values"][0] = PKCS9String()


# Unstructured address

pkcs_9_at_unstructuredAddress = _OID(pkcs_9, 8)

unstructuredAddress = Attribute()
unstructuredAddress["type"] = pkcs_9_at_unstructuredAddress
unstructuredAddress["values"][0] = DirectoryString()


# Date of birth

pkcs_9_at_dateOfBirth = _OID(ietf_at, 1)

dateOfBirth = SingleAttribute()
dateOfBirth["type"] = pkcs_9_at_dateOfBirth
dateOfBirth["values"][0] = useful.GeneralizedTime()


# Place of birth

pkcs_9_at_placeOfBirth = _OID(ietf_at, 2)

placeOfBirth = SingleAttribute()
placeOfBirth["type"] = pkcs_9_at_placeOfBirth
placeOfBirth["values"][0] = DirectoryString()


# Gender


class GenderString(char.PrintableString):
    pass


GenderString.subtypeSpec = constraint.ValueSizeConstraint(1, 1)
GenderString.subtypeSpec = constraint.SingleValueConstraint("M", "F", "m", "f")


pkcs_9_at_gender = _OID(ietf_at, 3)

gender = SingleAttribute()
gender["type"] = pkcs_9_at_gender
gender["values"][0] = GenderString()


# Country of citizenship

pkcs_9_at_countryOfCitizenship = _OID(ietf_at, 4)

countryOfCitizenship = Attribute()
countryOfCitizenship["type"] = pkcs_9_at_countryOfCitizenship
countryOfCitizenship["values"][0] = X520countryName()


#  Country of residence

pkcs_9_at_countryOfResidence = _OID(ietf_at, 5)

countryOfResidence = Attribute()
countryOfResidence["type"] = pkcs_9_at_countryOfResidence
countryOfResidence["values"][0] = X520countryName()


# Pseudonym

id_at_pseudonym = _OID(2, 5, 4, 65)

pseudonym = Attribute()
pseudonym["type"] = id_at_pseudonym
pseudonym["values"][0] = DirectoryString()


# Serial number

id_at_serialNumber = rfc5280.id_at_serialNumber

serialNumber = Attribute()
serialNumber["type"] = id_at_serialNumber
serialNumber["values"][0] = X520SerialNumber()


# Content type

pkcs_9_at_contentType = rfc5652.id_contentType

contentType = CMSSingleAttribute()
contentType["attrType"] = pkcs_9_at_contentType
contentType["attrValues"][0] = ContentType()


# Message digest

pkcs_9_at_messageDigest = rfc5652.id_messageDigest

messageDigest = CMSSingleAttribute()
messageDigest["attrType"] = pkcs_9_at_messageDigest
messageDigest["attrValues"][0] = MessageDigest()


# Signing time

pkcs_9_at_signingTime = rfc5652.id_signingTime

signingTime = CMSSingleAttribute()
signingTime["attrType"] = pkcs_9_at_signingTime
signingTime["attrValues"][0] = SigningTime()


# Random nonce


class RandomNonce(univ.OctetString):
    pass


RandomNonce.subtypeSpec = constraint.ValueSizeConstraint(4, MAX)


pkcs_9_at_randomNonce = _OID(pkcs_9_at, 3)

randomNonce = CMSSingleAttribute()
randomNonce["attrType"] = pkcs_9_at_randomNonce
randomNonce["attrValues"][0] = RandomNonce()


# Sequence number


class SequenceNumber(univ.Integer):
    pass


SequenceNumber.subtypeSpec = constraint.ValueRangeConstraint(1, MAX)


pkcs_9_at_sequenceNumber = _OID(pkcs_9_at, 4)

sequenceNumber = CMSSingleAttribute()
sequenceNumber["attrType"] = pkcs_9_at_sequenceNumber
sequenceNumber["attrValues"][0] = SequenceNumber()


# Countersignature

pkcs_9_at_counterSignature = rfc5652.id_countersignature

counterSignature = CMSAttribute()
counterSignature["attrType"] = pkcs_9_at_counterSignature
counterSignature["attrValues"][0] = Countersignature()


# Challenge password

pkcs_9_at_challengePassword = _OID(pkcs_9, 7)

challengePassword = SingleAttribute()
challengePassword["type"] = pkcs_9_at_challengePassword
challengePassword["values"][0] = DirectoryString()


# Extension request


class ExtensionRequest(Extensions):
    pass


pkcs_9_at_extensionRequest = _OID(pkcs_9, 14)

extensionRequest = SingleAttribute()
extensionRequest["type"] = pkcs_9_at_extensionRequest
extensionRequest["values"][0] = ExtensionRequest()


# Extended-certificate attributes (deprecated)


class AttributeSet(univ.SetOf):
    pass


AttributeSet.componentType = Attribute()


pkcs_9_at_extendedCertificateAttributes = _OID(pkcs_9, 9)

extendedCertificateAttributes = SingleAttribute()
extendedCertificateAttributes["type"] = pkcs_9_at_extendedCertificateAttributes
extendedCertificateAttributes["values"][0] = AttributeSet()


# Friendly name


class FriendlyName(char.BMPString):
    pass


FriendlyName.subtypeSpec = constraint.ValueSizeConstraint(1, pkcs_9_ub_friendlyName)


pkcs_9_at_friendlyName = _OID(pkcs_9, 20)

friendlyName = SingleAttribute()
friendlyName["type"] = pkcs_9_at_friendlyName
friendlyName["values"][0] = FriendlyName()


# Local key identifier

pkcs_9_at_localKeyId = _OID(pkcs_9, 21)

localKeyId = SingleAttribute()
localKeyId["type"] = pkcs_9_at_localKeyId
localKeyId["values"][0] = univ.OctetString()


# Signing description

pkcs_9_at_signingDescription = _OID(pkcs_9, 13)

signingDescription = CMSSingleAttribute()
signingDescription["attrType"] = pkcs_9_at_signingDescription
signingDescription["attrValues"][0] = DirectoryString()


# S/MIME capabilities


class SMIMECapability(AlgorithmIdentifier):
    pass


class SMIMECapabilities(univ.SequenceOf):
    pass


SMIMECapabilities.componentType = SMIMECapability()


pkcs_9_at_smimeCapabilities = _OID(pkcs_9, 15)

smimeCapabilities = CMSSingleAttribute()
smimeCapabilities["attrType"] = pkcs_9_at_smimeCapabilities
smimeCapabilities["attrValues"][0] = SMIMECapabilities()


# Certificate Attribute Map

_certificateAttributesMapUpdate = {
    # Attribute types for use with the "pkcsEntity" object class
    pkcs_9_at_pkcs7PDU: ContentInfo(),
    pkcs_9_at_userPKCS12: PFX(),
    # TODO: Once PKCS15Token can be imported, this can be included
    # pkcs_9_at_pkcs15Token: PKCS15Token(),
    pkcs_9_at_encryptedPrivateKeyInfo: EncryptedPrivateKeyInfo(),
    # Attribute types for use with the "naturalPerson" object class
    pkcs_9_at_emailAddress: EmailAddress(),
    pkcs_9_at_unstructuredName: PKCS9String(),
    pkcs_9_at_unstructuredAddress: DirectoryString(),
    pkcs_9_at_dateOfBirth: useful.GeneralizedTime(),
    pkcs_9_at_placeOfBirth: DirectoryString(),
    pkcs_9_at_gender: GenderString(),
    pkcs_9_at_countryOfCitizenship: X520countryName(),
    pkcs_9_at_countryOfResidence: X520countryName(),
    id_at_pseudonym: DirectoryString(),
    id_at_serialNumber: X520SerialNumber(),
    # Attribute types for use with PKCS #10 certificate requests
    pkcs_9_at_challengePassword: DirectoryString(),
    pkcs_9_at_extensionRequest: ExtensionRequest(),
    pkcs_9_at_extendedCertificateAttributes: AttributeSet(),
}

rfc5280.certificateAttributesMap.update(_certificateAttributesMapUpdate)


# CMS Attribute Map

# Note: pkcs_9_at_smimeCapabilities is not included in the map because
#       the definition in RFC 5751 is preferred, which produces the same
#       encoding, but it allows different parameters for SMIMECapability
#       and AlgorithmIdentifier.

_cmsAttributesMapUpdate = {
    # Attribute types for use in PKCS #7 data (a.k.a. CMS)
    pkcs_9_at_contentType: ContentType(),
    pkcs_9_at_messageDigest: MessageDigest(),
    pkcs_9_at_signingTime: SigningTime(),
    pkcs_9_at_randomNonce: RandomNonce(),
    pkcs_9_at_sequenceNumber: SequenceNumber(),
    pkcs_9_at_counterSignature: Countersignature(),
    # Attributes for use in PKCS #12 "PFX" PDUs or PKCS #15 tokens
    pkcs_9_at_friendlyName: FriendlyName(),
    pkcs_9_at_localKeyId: univ.OctetString(),
    pkcs_9_at_signingDescription: DirectoryString(),
    # pkcs_9_at_smimeCapabilities: SMIMECapabilities(),
}

rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate)
