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天,按照这个来算月数,是不是简单粗暴,反正相差不大,就这样了。