<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Kurrik <<EMAIL>>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# From:
# http://code.google.com/p/kurrik-resources/source/browse/trunk/src/x509/x509.py?r=14

from pyasn1.codec.der import decoder
from pyoauth.crypto.codec.pemder import pem_to_der_certificate

try:
    reduce(lambda a, b: a + b, [1, 2, 3, 4])
except Exception:
    from functools import reduce


# http://tools.ietf.org/html/rfc3279 - Section 2.3.1
ID_RSAENCRYPTION = (1, 2, 840, 113549, 1, 1, 1)


def bits_to_long(bits):
    """
    Converts a bit sequence to a long value.

    :param bits:
        Bit sequence.
    :returns:
        Long value.
    """
    return reduce(lambda x, y: (x << 1) + y, bits)


def parse_certificate_string(certificate):
    """
    Parses the PEM encoded X.509 blob of text into an ASN.1 structure.

    :param certificate:
        PEM-encoded X.509 string.
    :returns:
        ASN.1 structure.
    """
    return decoder.decode(pem_to_der_certificate(certificate))


def parse_subject_public_key_info(certificate_asn1):
    """
    Extracts a SubjectPublicKeyInfo ASN.1 structure from an ASN.1 certificate.

    :param certificate_asn1:
        ASN.1 structure of the X.509 certificate.
    :returns:
        SubjectPublicKeyInfo ASN.1 structure.
    """
    # x509 cert - http://tools.ietf.org/html/rfc3280
    if len(certificate_asn1) < 1:
        raise ValueError("No X.509 certificate found after ASN.1 decoding")

    cert_x509 = certificate_asn1[0]
    if len(cert_x509) < 1:
        raise ValueError("No TBSCertificate structure found.")

    cert_tbs_certificate = cert_x509[0]
    if len(cert_tbs_certificate) < 7:
        raise ValueError("No SubjectPublicKeyInfo structure found.")

    cert_subject_public_key_info = cert_tbs_certificate[6]
    if len(cert_subject_public_key_info) < 2:
        raise ValueError("SubjectPublicKeyInfo structured incorrectly.")

    return cert_subject_public_key_info


def parse_public_rsa_key_bits(public_key_bitstring):
    """
    Extracts the RSA modulus and exponent from a RSA public key bit string.

    :param public_key_bitstring:
        ASN.1 public key bit string.
    :returns:
        Tuple of (modulus, exponent)
    """
    public_key_long = bits_to_long(public_key_bitstring)
    public_key_hex = hex(public_key_long)[2:-1]
    public_key_asn1 = decoder.decode(public_key_hex.decode('hex'))

    if len(public_key_asn1) < 1:
        raise ValueError("Problem ASN.1 decoding public key bytes")

    if len(public_key_asn1[0]) < 2:
        raise ValueError("Couldn't obtain RSA modulus and exponent from public key.")

    return long(public_key_asn1[0][0]), long(public_key_asn1[0][1])


def parse_algorithm_identifier(algorithm_asn1):
    """
    Extracts an ASN.1 algorithm identifier from an AlgorithmIdentifier object.

    :param algorithm_asn1:
        Algorithm ASN.1 structure
    :returns:
        Algorithm identifier.
    """
    if len(algorithm_asn1) < 2:
        raise ValueError("Certificate algorithm was formatted incorrectly.")
    return algorithm_asn1[0]


def extract_public_key(certificate):
    """
    Returns a tuple of the RSA exponent and modulus of the public RSA key
    contained in the PEM-encoded certificate string.

    :param certificate:
        PEM-encoded X.509 certificate string.
    :returns:
        Public key as a tuple of (modulus, exponent).
    """
    certificate = parse_certificate_string(certificate)
    subject_public_key_info = parse_subject_public_key_info(certificate)

    algorithm = parse_algorithm_identifier(subject_public_key_info[0])

    if algorithm != ID_RSAENCRYPTION:
        raise NotImplementedError("Only RSA encryption currently supported.")
    public_key = parse_public_rsa_key_bits(subject_public_key_info[1])
    return public_key


TEST_CERTIFICATES = ("""
-----BEGIN CERTIFICATE-----
<KEY>END CERTIFICATE-----""",
    """
-----BEGIN CERTIFICATE-----
<KEY>
-----END CERTIFICATE-----"""
    )

TEST_PUBLIC_KEYS = (
    (126669640320683290646795148731116725859129871317489646670977486626744987251277308188134951784112892388851824395559423655294483477900467304936849324412630428474313221323982004833431306952809970692055204065814102382627007630050419900189287007179961309761697749877767089292033899335453619375029318017462636143731L,
 65537L),
    (107796453724127466436509607023300853823148671381186269695418299876688451275586863210602390910751033980089586659623213376886118860658943925516474941572267483546063696504972995209865305723609365133051508378295496906014585319487318439832859683449024036092870160203868280352941275868168286901714810544167406768319L,
 65537L),
)
<commit_msg>Documents references in X509.<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Kurrik <<EMAIL>>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# From:
# http://code.google.com/p/kurrik-resources/source/browse/trunk/src/x509/x509.py?r=14
#
# References:
# Basic Certificate Fields: http://tools.ietf.org/html/rfc3280#section-4.1


from pyasn1.codec.der import decoder
from pyoauth.crypto.codec.pemder import pem_to_der_certificate

try:
    reduce(lambda a, b: a + b, [1, 2, 3, 4])
except Exception:
    from functools import reduce


# http://tools.ietf.org/html/rfc3279 - Section 2.3.1
ID_RSAENCRYPTION = (1, 2, 840, 113549, 1, 1, 1)


def bits_to_long(bits):
    """
    Converts a bit sequence to a long value.

    :param bits:
        Bit sequence.
    :returns:
        Long value.
    """
    return reduce(lambda x, y: (x << 1) + y, bits)


def parse_certificate_string(certificate):
    """
    Parses the PEM encoded X.509 blob of text into an ASN.1 structure.

    :param certificate:
        PEM-encoded X.509 string.
    :returns:
        ASN.1 structure.
    """
    return decoder.decode(pem_to_der_certificate(certificate))


def parse_subject_public_key_info(certificate_asn1):
    """
    Extracts a SubjectPublicKeyInfo ASN.1 structure from an ASN.1 certificate.

    :param certificate_asn1:
        ASN.1 structure of the X.509 certificate.
    :returns:
        SubjectPublicKeyInfo ASN.1 structure.
    """
    # x509 cert - http://tools.ietf.org/html/rfc3280
    if len(certificate_asn1) < 1:
        raise ValueError("No X.509 certificate found after ASN.1 decoding")

    cert_x509 = certificate_asn1[0]
    if len(cert_x509) < 1:
        raise ValueError("No TBSCertificate structure found.")

    cert_tbs_certificate = cert_x509[0]
    if len(cert_tbs_certificate) < 7:
        raise ValueError("No SubjectPublicKeyInfo structure found.")

    cert_subject_public_key_info = cert_tbs_certificate[6]
    if len(cert_subject_public_key_info) < 2:
        raise ValueError("SubjectPublicKeyInfo structured incorrectly.")

    return cert_subject_public_key_info


def parse_public_rsa_key_bits(public_key_bitstring):
    """
    Extracts the RSA modulus and exponent from a RSA public key bit string.

    :param public_key_bitstring:
        ASN.1 public key bit string.
    :returns:
        Tuple of (modulus, exponent)
    """
    public_key_long = bits_to_long(public_key_bitstring)
    public_key_hex = hex(public_key_long)[2:-1]
    public_key_asn1 = decoder.decode(public_key_hex.decode('hex'))

    if len(public_key_asn1) < 1:
        raise ValueError("Problem ASN.1 decoding public key bytes")

    if len(public_key_asn1[0]) < 2:
        raise ValueError("Couldn't obtain RSA modulus and exponent from public key.")

    return long(public_key_asn1[0][0]), long(public_key_asn1[0][1])


def parse_algorithm_identifier(algorithm_asn1):
    """
    Extracts an ASN.1 algorithm identifier from an AlgorithmIdentifier object.

    :param algorithm_asn1:
        Algorithm ASN.1 structure
    :returns:
        Algorithm identifier.
    """
    if len(algorithm_asn1) < 2:
        raise ValueError("Certificate algorithm was formatted incorrectly.")
    return algorithm_asn1[0]


def extract_public_key(certificate):
    """
    Returns a tuple of the RSA exponent and modulus of the public RSA key
    contained in the PEM-encoded certificate string::

        SubjectPublicKeyInfo  ::=  SEQUENCE {
                algorithm            AlgorithmIdentifier,
                subjectPublicKey     BIT STRING
        }

    :param certificate:
        PEM-encoded X.509 certificate string.
    :returns:
        Public key as a tuple of (modulus, exponent).
    """
    certificate = parse_certificate_string(certificate)
    subject_public_key_info = parse_subject_public_key_info(certificate)

    algorithm = parse_algorithm_identifier(subject_public_key_info[0])

    if algorithm != ID_RSAENCRYPTION:
        raise NotImplementedError("Only RSA encryption currently supported.")
    public_key = parse_public_rsa_key_bits(subject_public_key_info[1])
    return public_key


TEST_CERTIFICATES = ("""
-----BEGIN CERTIFICATE-----
<KEY>
-----END CERTIFICATE-----""",
    # OAuth 1.0 test case.
    """
-----BEGIN CERTIFICATE-----
MIIBpjCCAQ+gAwIBAgIBATANBgkqhkiG<KEY>
-----END CERTIFICATE-----"""
    )

TEST_PUBLIC_KEYS = (
    (126669640320683290646795148731116725859129871317489646670977486626744987251277308188134951784112892388851824395559423655294483477900467304936849324412630428474313221323982004833431306952809970692055204065814102382627007630050419900189287007179961309761697749877767089292033899335453619375029318017462636143731L,
 65537L),
    (107796453724127466436509607023300853823148671381186269695418299876688451275586863210602390910751033980089586659623213376886118860658943925516474941572267483546063696504972995209865305723609365133051508378295496906014585319487318439832859683449024036092870160203868280352941275868168286901714810544167406768319L,
 65537L),
)

{'fraction_non_alphanumeric': 0.08848405985686401, 'fraction_numerical': 0.1398828887443071, 'mean_word_length': 3.859981933152665, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4530707', 'n_tokens_mistral': 4494, 'n_tokens_neox': 3521, 'n_words': 822}