All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.shepherdviolet.glacimon.java.crypto.base.NonStandardDataFixer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2022-2024 S.Violet
 *
 * 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.
 *
 * Project GitHub: https://github.com/shepherdviolet/glacimon
 * Email: [email protected]
 */

package com.github.shepherdviolet.glacimon.java.crypto.base;

import org.bouncycastle.asn1.*;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.cert.X509Certificate;

/**
 * 非标准数据修复器
 *
 * @author shepherdviolet
 */
public class NonStandardDataFixer {

    /**
     * (尝试)修复非标准的DER编码的签名数据.
     *
     * 20230320:
     * 发现某签名服务器签名出来的DER格式签名不标准, 当R值偏大时, r[0] >= 0x80, 使得整个大数字变成了负数, 而R和S值是不能
     * 小于1的(见org.bouncycastle.crypto.signers.SM2Signer#verifySignature方法). 在标准的DER格式中, 遇到这种情况, 应该在前面
     * 增加一个字节0x00, 以保证数值为正整数. 顺带一提, RS格式的签名数据, 不会在前面追加0x00, 因为它要严格保证32+32字节.
     *
     * @param der DER编码的签名数据
     * @return 修复后的DER编码的签名数据, 修复失败返回原值
     */
    public static byte[] fixDerSign(byte[] der) {
        if (der == null) {
            return null;
        }
        // 只针对DER长度为70bytes的情况, 减少不必要的开销. 因为不标准的DER格式签名, RS值都只有32字节, 整个DER长度为70字节.
        if (der.length != 70) {
            return der;
        }
        try {
            ASN1Sequence asn1Sequence = DERSequence.getInstance(der);
            byte[] r = ((ASN1Integer) asn1Sequence.getObjectAt(0)).getValue().toByteArray();
            byte[] s = ((ASN1Integer) asn1Sequence.getObjectAt(1)).getValue().toByteArray();
            // 检查RS值是否为负数
            if (r[0] >= 0 && s[0] >= 0) {
                return der;
            }
            // 如果为负数, 则在前面追加0x00, 只需要在创建BigInteger时指定为正数就行
            BigInteger rInteger = new BigInteger(1, r);
            BigInteger sInteger = new BigInteger(1, s);
            ASN1EncodableVector encodableVector = new ASN1EncodableVector();
            encodableVector.add(new ASN1Integer(rInteger));
            encodableVector.add(new ASN1Integer(sInteger));
            return new DERSequence(encodableVector).getEncoded(ASN1Encoding.DER);
        } catch (Exception e) {
            return der;
        }
    }

    /**
     * (尝试)修复非标准的X509证书中的签名数据.
     *
     * 20240820:
     * 发现某RA签发出来的SM2证书本身签名格式不标准(CA私钥对用户证书签名), 截取证书末尾的签名数据(ASN.1), 如下所示:
     * 30 45 02 21 008265......27 02 20 0039e7......43
     * 30表示type=SEQUENCE, 45表示SEQUENCE长度69 (0x45首位为0, 本身即为长度, 若首位为1, 表示长度区域的长度)
     * 02表示type=INTEGER, 21表示INTEGER长度为33, 008265......27为INTEGER的值
     * 02表示type=INTEGER, 20表示INTEGER长度为32, 0039e7......43为INTEGER的值
     * 问题出在0039e7......43, ASN.1标准要求正整数首位为1时, 前面补零(0x00, 防止变成负数),
     * 而0039e7......43的39首位不为1, 不应该补零, 但是这个RA签发的证书却补零了, 所以BouncyCastle解析签名数据失败, 导致证书验签失败.
     *
     * ASN.1格式参考可参考: https://blog.csdn.net/new9232/article/details/139205158
     *
     * @param certX509 X509格式的证书数据(需要修复其中的签名数据)
     * @return 尝试修复后的X509证书数据, 修复失败返回原证书
     */
    public static byte[] fixCertSign(byte[] certX509) {
        // 用ASN.1解析整个证书
        try (ASN1InputStream certAsn1InputStream = new ASN1InputStream(certX509)) {
            DLSequence certDlSequence = (DLSequence)certAsn1InputStream.readObject();
            // 最后一个是签名
            ASN1Encodable signObject = certDlSequence.getObjectAt(certDlSequence.size() - 1);
            byte[] signOctets = ((DERBitString) signObject).getOctets();

            try (ASN1InputStream signAsn1InputStream = new ASN1InputStream(signOctets)){
                // 尝试用ASN.1格式解析签名, 如果签名数据没问题则返回原证书
                signAsn1InputStream.readObject();
                return certX509;
            } catch (Exception e) {
                // 解析失败: 签名格式错误
                // 判断是否是"malformed integer"错误
                if (e.getCause() == null || !"malformed integer".equals(e.getCause().getMessage())) {
                    return certX509;
                }
                // 尝试修复签名
                signOctets = fixSignInCert(signOctets);
                // 重新组装证书
                ASN1EncodableVector certVector = new ASN1EncodableVector();
                for (int i = 0 ; i < certDlSequence.size() - 1 ; i++) {
                    certVector.add(certDlSequence.getObjectAt(i));
                }
                certVector.add(new DERBitString(signOctets));
                byte[] certDerEncoded = new DERSequence(certVector).getEncoded(ASN1Encoding.DER);
                return BaseBCCertificateUtils.parseCertificateByBouncyCastle(
                        new ByteArrayInputStream(certDerEncoded), BaseCertificateUtils.TYPE_X509).getEncoded();
            }
        } catch (Exception ex) {
            return certX509;
        }
    }

    /**
     * (尝试)修复非标准的X509证书中的签名数据.
     *
     * 20240820:
     * 发现某RA签发出来的SM2证书本身签名格式不标准(CA私钥对用户证书签名), 截取证书末尾的签名数据(ASN.1), 如下所示:
     * 30 45 02 21 008265......27 02 20 0039e7......43
     * 30表示type=SEQUENCE, 45表示SEQUENCE长度69 (0x45首位为0, 本身即为长度, 若首位为1, 表示长度区域的长度)
     * 02表示type=INTEGER, 21表示INTEGER长度为33, 008265......27为INTEGER的值
     * 02表示type=INTEGER, 20表示INTEGER长度为32, 0039e7......43为INTEGER的值
     * 问题出在0039e7......43, ASN.1标准要求正整数首位为1时, 前面补零(0x00, 防止变成负数),
     * 而0039e7......43的39首位不为1, 不应该补零, 但是这个RA签发的证书却补零了, 所以BouncyCastle解析签名数据失败, 导致证书验签失败.
     *
     * ASN.1格式参考可参考: https://blog.csdn.net/new9232/article/details/139205158
     *
     * @param cert X509证书(需要修复其中的签名数据)
     * @return 尝试修复后的证书, 修复失败返回原证书
     */
    public static X509Certificate fixCertSign(X509Certificate cert) {
        try (ASN1InputStream signAsn1InputStream = new ASN1InputStream(cert.getSignature())){
            // 尝试用ASN.1格式解析签名, 如果签名数据没问题则返回原证书
            signAsn1InputStream.readObject();
            return cert;
        } catch (Exception e) {
            // 解析失败: 签名格式错误
            // 判断是否是"malformed integer"错误
            if (e.getCause() == null || !"malformed integer".equals(e.getCause().getMessage())) {
                return cert;
            }
            // 用ASN.1解析整个证书
            try (ASN1InputStream certAsn1InputStream = new ASN1InputStream(BaseCertificateUtils.encodeCertificate(cert))) {
                DLSequence certDlSequence = (DLSequence)certAsn1InputStream.readObject();
                // 最后一个是签名
                ASN1Encodable signObject = certDlSequence.getObjectAt(certDlSequence.size() - 1);
                byte[] signOctets = ((DERBitString) signObject).getOctets();
                // 尝试修复签名
                signOctets = fixSignInCert(signOctets);
                // 重新组装证书
                ASN1EncodableVector certVector = new ASN1EncodableVector();
                for (int i = 0 ; i < certDlSequence.size() - 1 ; i++) {
                    certVector.add(certDlSequence.getObjectAt(i));
                }
                certVector.add(new DERBitString(signOctets));
                byte[] certDerEncoded = new DERSequence(certVector).getEncoded(ASN1Encoding.DER);
                return (X509Certificate) BaseBCCertificateUtils.parseCertificateByBouncyCastle(
                        new ByteArrayInputStream(certDerEncoded), BaseCertificateUtils.TYPE_X509);
            } catch (Exception ex) {
                return cert;
            }
        }
    }

    /**
     * 修复证书中的签名数据, 其他环节可以用BC的ASN.1组件解析/组包, 异常数据的读取只能手工进行
     */
    static byte[] fixSignInCert(byte[] signOctets) {
        int index = 0;
        // type=SEQUENCE
        if (signOctets[index++] != 0x30) {
            return signOctets;
        }
        // sign length
        int signLength = signOctets[index++];
        if (signLength < 0) {
            int lengthLen = signLength & 0x7F;
            byte[] lengthBuf = new byte[lengthLen];
            for (int i = 0; i < lengthLen; i++) {
                lengthBuf[i] = signOctets[index++];
            }
            signLength = new BigInteger(1, lengthBuf).intValue();
        }
        // 检查剩余长度与length是否相符
        if (signLength != signOctets.length - index) {
            return signOctets;
        }
        // r: type=INTEGER
        if (signOctets[index++] != 0x02) {
            return signOctets;
        }
        // r length
        int rLength = signOctets[index++];
        if (rLength < 0) {
            int lengthLen = rLength & 0x7F;
            byte[] lengthBuf = new byte[lengthLen];
            for (int i = 0; i < lengthLen; i++) {
                lengthBuf[i] = signOctets[index++];
            }
            rLength = new BigInteger(1, lengthBuf).intValue();
        }
        // 检查剩余长度与length是否相符
        if (rLength >= signOctets.length - index) {
            return signOctets;
        }
        // r value
        byte[] rValue = new byte[rLength];
        for (int i = 0; i < rLength; i++) {
            rValue[i] = signOctets[index++];
        }
        // s: type=INTEGER
        if (signOctets[index++] != 0x02) {
            return signOctets;
        }
        // s length
        int sLength = signOctets[index++];
        if (sLength < 0) {
            int lengthLen = sLength & 0x7F;
            byte[] lengthBuf = new byte[lengthLen];
            for (int i = 0; i < lengthLen; i++) {
                lengthBuf[i] = signOctets[index++];
            }
            sLength = new BigInteger(1, lengthBuf).intValue();
        }
        // 检查剩余长度与length是否相符
        if (sLength != signOctets.length - index) {
            return signOctets;
        }
        // s value
        byte[] sValue = new byte[sLength];
        for (int i = 0; i < sLength; i++) {
            sValue[i] = signOctets[index++];
        }
        // 去掉rs前面不正确的补零(最高位不是1是不用补0的), BigInteger指定1正整数, 前面的填充会标准化
        try {
            BigInteger rInteger = new BigInteger(1, rValue);
            BigInteger sInteger = new BigInteger(1, sValue);
            ASN1EncodableVector signVector = new ASN1EncodableVector();
            signVector.add(new ASN1Integer(rInteger));
            signVector.add(new ASN1Integer(sInteger));
            return new DERSequence(signVector).getEncoded(ASN1Encoding.DER);
        } catch (IOException e) {
            return signOctets;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy