iOS的cer、p12格式证书解析监控

之前博客写过直接解析ipa包获取mobileprovision文件来监控APP是否过期来,但APP的推送证书还没有做,

大家都知道,iOS的推送证书不会放到ipa包里,只能通过直接解析p12或cer、crt格式。

解析p12格式的话,需要证书导出的密码,p12证书好处是可以授权到多台电脑。解析cer则不需要密码。

1.可以写个函数同时解析p12和cer文件:

# -*- coding = utf-8 -*-
# ------------------------------
# @time: 2021/3/29 5:18 PM
# @Author: drew_gg
# @File: certificate_parsing.py
# @Software: cover_ios_api
# ------------------------------

import os
from OpenSSL import crypto
import datetime
import time
from dateutil import parser
from pkg_common.ipa_monitor import calculation_month as cm


def deal_utc(utc_time):
    """
    UTC时间转换
    :param utc_time:
    :return:
    """
    # UTC时间格式
    utc_format = "%Y-%m-%dT%H:%M:%SZ"
    sta_time = datetime.datetime.strptime(utc_time, utc_format) + datetime.timedelta(hours=8)
    time_array = time.strptime(str(sta_time), "%Y-%m-%d %H:%M:%S")
    time_stamp = int(time.mktime(time_array))
    return sta_time, time_stamp


def parsing_p12(file, f_type, password=''):
    """
    解析p12、cer证书
    :param file:
    :param f_type:
    :param password:
    :return:
    """
    cer = ''
    if f_type == 'p12':
        p12 = crypto.load_pkcs12(open(file, 'rb').read(), password)
        cer = p12.get_certificate()
    if f_type == 'cer':
        crt_f = file.split('.cer')[0] + '.crt'
        # cer转换crt格式
        crt_cmd = "OpenSSL x509 -inform DER -in %s -out %s" % (file, crt_f)
        os.system(crt_cmd)
        with open(crt_f, "r", encoding='ISO-8859-1') as fp:
            crt_data = fp.read()
        cer = crypto.load_certificate(crypto.FILETYPE_PEM, crt_data)
        del_cmd = "rm -rf %s" % crt_f
        os.system(del_cmd)
    cer_dic = {}

    # 解析时间
    z_be_time = parser.parse(cer.get_notBefore().decode("UTF-8")).strftime('%Y-%m-%d %H:%M:%S')
    # 生成UTC时间格式并转换成北京时间
    before_time = deal_utc(z_be_time.split(' ')[0] + 'T' + z_be_time.split(' ')[1] + 'Z')[0]
    # 解析时间
    z_af_time = parser.parse(cer.get_notAfter().decode("UTF-8")).strftime('%Y-%m-%d %H:%M:%S')
    after_time = deal_utc(z_af_time.split(' ')[0] + 'T' + z_af_time.split(' ')[1] + 'Z')[0]
    # 获取时间月数差
    remaining_time = cm.cal_months(datetime.date.today(), after_time)

    cer_dic['before_time'] = before_time
    cer_dic['after_time'] = after_time
    cer_dic['remaining_time'] = remaining_time

    subject = cer.get_subject()
    s_components = subject.get_components()
    # 解析证书相关名称
    for (key, value) in s_components:
        if str(key, encoding='utf-8') == 'CN':
            cer_dic['user_id'] = str(value, encoding='utf-8')
        if str(key, encoding='utf-8') == 'OU':
            cer_dic['group'] = str(value, encoding='utf-8')
        if str(key, encoding='utf-8') == 'O':
            cer_dic['company'] = str(value, encoding='utf-8')
    if 'company' not in cer_dic.keys():
        cer_dic['company'] = ''
    if 'user_id' not in cer_dic.keys():
        cer_dic['user_id'] = ''
    if 'group' not in cer_dic.keys():
        cer_dic['group'] = ''
    return cer_dic


if __name__ == '__main__':

    f_p121 = "/Users/Work/package/notice_montor/xxx推送证书.p12"
    f_p122 = "/Users/Work/package/notice_montor/xx推送证书.p12"
    f_cer = "/Users/Work/package/notice_montor/xxx证书.cer"
    pa = '123456'
    a = parsing_p12(f_p122, 'p12', "123456")
    print(a)

需要注意的地方:

1>.证书解析出的时间是utc格式,需要转换成北京时间,和直接解析ipa获取到的格式不一样,格式里不带“T”,需要自己构建格式后再转换。

2>.cer格式不能直接解析,直接解析会报格式错误,需要先通过命令:"OpenSSL x509 -inform DER -in %s -out %s" % (file, crt_f)转换成crt文件,解析完crt文件后再删除即可

3>.可能有的证书没有‘O’项,需要自己处理。

2.监控时候可以把p12和cer都放一个目录下,写个方法遍历即可:

# -*- coding = utf-8 -*-
# ------------------------------
# @time: 2021/3/29 5:18 PM
# @Author: drew_gg
# @File: notice_monitor.py
# @Software: cover_ios_api
# ------------------------------

import datetime
from pkg_dao import read_sql as rs
from pkg_dao import flask_mysql as fm
from pkg_common.handle_file import find_file as find
from pkg_common.ipa_monitor import common_mail as mail
from pkg_common.ipa_monitor import certificate_parsing as cp
from pkg_common.ipa_monitor import notice_monitor_html as html


def get_certificate_detail(to_find_path, password):
    """
    获取ipa的证书时间与描述文件时间
    :return:
    """
    company_name = ''
    to_file = ["'*.p12'", "'*.cer'"]
    f, f_l = find.find_file_more(to_file, to_find_path)
    all_dic = []
    cer_pa = {}
    for p in f_l:
        if p.split('.')[1] == 'p12':
            company_name = p.split('/')[-1].split('.p12')[0]
            cer_pa = cp.parsing_p12(p, 'p12', password)
        if p.split('.')[1] == 'cer':
            company_name = p.split('/')[-1].split('.cer')[0]
            cer_pa = cp.parsing_p12(p, 'cer')
        cer_pa['company_name'] = company_name
        all_dic.append(cer_pa)
    return all_dic


if __name__ == '__main__':

    # 需要遍历ipa文件等目录
    find_path = '/Users/Work/package/notice_montor'
    pw = 'xxxxx'
    cer_all = get_certificate_detail(find_path, pw)
    # 升序排个序列
    cer_all = sorted(cer_all, key=lambda k: k['remaining_time'])
    for i in cer_all:
        sql_result = rs.deal_mysql("get_certificate_id.sql", list([i['user_id']]))
        update_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        if sql_result:
            up_sal = """
                        UPDATE certificate_parsing 
                        SET `name` = '%s', 
                          company = '%s', 
                          `group` = '%s', 
                          remaining_time = '%s', 
                          before_time = '%s',
                          after_time = '%s', 
                          upload_time = '%s' 
                        WHERE 
                          is_delete = 0 
                        AND user_id = '%s' 
                        LIMIT 1;
                     """ % (i['company_name'], i['company'], i['group'], i['remaining_time'], i['before_time'], i['after_time'],
                             update_time, i['user_id'])
            d = fm.Database()
            d.exec_no_query(up_sal)
        else:
            in_sql = """
                          INSERT INTO `certificate_parsing` (
                              `name`, 
                              `company`, 
                              `group`, 
                              `user_id`, 
                              `remaining_time`, 
                              `before_time`, 
                              `after_time`,
                              `upload_time`
                              )
                          VALUES
                              ( 
                                '%s', 
                                '%s', 
                                '%s', 
                                '%s', 
                                '%s', 
                                '%s', 
                                '%s', 
                                '%s'
                                );
                        """ % (i['company_name'], i['company'], i['group'], i['user_id'], i['remaining_time'], i['before_time'],
                               i['after_time'], update_time)
            d = fm.Database()
            d.exec_no_query(in_sql)

    subject, html = html.deal_html(cer_all)
    try:
        if mail.cs_mail_send(subject, html, 'iOS'):
            print('Send success')
        else:
            print('Send failure')
    except Exception as ex:
        print(ex)

find_file_more方法和简单:

def find_file_more(file_l, path):
    """
    :param file_l:
    :param path:
    :return:
    """
    f = []
    f_a = []
    find_cmd = ''
    for index, f_t in enumerate(file_l):
        if index == 0:
            find_cmd = 'find . -iname %s' % f_t
        else:
            find_cmd += " -o -iname %s" % f_t

    file_list = cmd.run_cmd(find_cmd, path).read().split('./')
    for i in range(len(file_list)):
        if i != 0:
            file_all = path + '/' + file_list[i].strip()
            f_a.append(file_all)
            file = file_list[i].split('/')[-1].split('.')[0].strip()
            f.append(file)
    return f, f_a

拼接find . -iname ** -o -iname ** -o -iname ** 命令。

然后把获取到的证书信息写入数据库和生成邮件告警出来就行,也可以定时每周五执行一次就好。

计算月数的话,之前给的那个比较复杂,不容易懂,新写个方法,比较简单粗暴:

# -*- coding = utf-8 -*-
# ------------------------------
# @time: 2021/2/1 4:13 PM
# @Author: drew_gg
# @File: calculation_month.py
# @Software: Build_Packaging
# ------------------------------


# coding = utf-8
# 计算日期的月份差


import datetime


def cal_months(start_date, end_date):
    """
    计算两个日期的月份差,精确到小数位
    :param start_date: 日期必须为date格式
    :param end_date: 日期必须为date格式
    :return:
    """

    # 计算两个日期相隔月差
    ey = end_date.year
    em = end_date.month
    ed = end_date.day
    sy = start_date.year
    sm = start_date.month
    sd = start_date.day

    if ey < sy:
        raise AssertionError('被减日期大了!')
    elif ey == sy and em < sm:
        raise AssertionError('被减日期大了!')
    elif ey == sy and em == sm and ed < sd:
        raise AssertionError('被减日期大了!')
    else:
        aed = round((ey - sy) * 12 + (em - sm) + (ed - sd)/30.5, 2)
    return aed


if __name__ == '__main__':
    sd = datetime.date(2021, 3, 29)
    ed = datetime.date(2022, 1, 22)
    a = cal_months(sd, ed)

年月平均天数30.5天,按照这个来算月数,是不是简单粗暴,反正相差不大,就这样了。

原文地址:https://www.cnblogs.com/drewgg/p/14611718.html