MQTT研究之EMQ:【JAVA代碼構建X509證書【續集】】 MQTT研究之EMQ:【JAVA代碼構建X509證書】

openssl建立私鑰,獲取公鑰,建立證書都是比較簡單的,就幾個指令,很快就能夠搞定,之因此說簡單,是由於證書裏面的基本參數配置不須要咱們組裝,只須要將命令行裏面須要的幾個參數配置進去便可。可是呢,用java代碼,原生建立證書,其實須要咱們瞭解的內容就要稍微多點,去填充建立證書裏面的所須要的參數,逐行填充。html

 

openssl證書的格式默認是PEM的,即Privacy Enhanced Mail,說白了,就是將建立後的證書元素數據通過Base64編碼,而後添加相似----BEGIN CERTIFICATE----、----END CERTIFICATE----這樣的頭和尾,是ASCII編碼的純字符串格式。java

 

如今完整的介紹一下java建立PEM證書的邏輯。都遵循X509的協議,建立證書,和openssl邏輯有點不一樣的是,java建立證書不須要構建CSR(Certificate Sign Request)這個步驟,一樣須要建立私鑰/公鑰,建立CA證書,構建設備證書。算法

 

本博文是對前敘的博文的一個補充 MQTT研究之EMQ:【JAVA代碼構建X509證書】,內容涉及的話題相同,可是角度有些不同。安全

 

1. 私鑰/公鑰建立dom

/**
     * 建立私鑰和公鑰的數據,以一個map的形式返回。
     *
     * @param keySize 私鑰的長度
     * @param keyAlgo 建立私鑰的算法,例如RSA,DSA等
     * @return map 私鑰和公鑰對信息
     */
    public static Map<String, String> createKeys(int keySize, String keyAlgo){
        //爲RSA算法建立一個KeyPairGenerator對象
        KeyPairGenerator kpg;
        try{
            kpg = KeyPairGenerator.getInstance(keyAlgo);
        }catch(NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("No such algorithm-->[" + keyAlgo + "]");
        }

        //初始化KeyPairGenerator對象,密鑰長度
        kpg.initialize(keySize);
        //生成密匙對
        KeyPair keyPair = kpg.generateKeyPair();
        //獲得公鑰
        Key publicKey = keyPair.getPublic();
        String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
        //獲得私鑰
        Key privateKey = keyPair.getPrivate();
        String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
        Map<String, String> keyPairMap = new HashMap<String, String>();
        keyPairMap.put(PUBLIC_KEY, publicKeyStr);
        keyPairMap.put(PRIVATE_KEY, privateKeyStr);
        return keyPairMap;
    }

 

2. 建立CA自簽名證書ide

/**
     * 建立根證書, 並保存根證書到指定路徑的文件中, crt和key分開存儲文件。
     * 建立SSL根證書的邏輯,很重要,此函數調用頻次不高,建立根證書,也就是自簽名證書。
     *
     * @param algorithm 私鑰安全算法,e.g. RSA
     * @param keySize 私鑰長度,越長越安全,RSA要求不能小於512, e.g. 2048
     * @param digestSignAlgo 信息摘要以及簽名算法 e.g. SHA256withRSA
     * @param subj 證書全部者信息描述,e.g. CN=iotp,OU=tkcloud,O=TanKang,L=wuhan,S=hubei,C=CN
     * @param validDays 證書有效期天數,e.g. 3650即10年
     * @param rootCACrtPath 根證書所要存入的全路徑,e.g. /opt/certs/iot/rootCA.crt
     * @param rootCAKeyPath 根證書對應祕鑰key所要存入的全路徑,e.g. /opt/certs/iot/rootCA.key
     * @return 私鑰和證書對的map對象
     */
    public static HashMap<String, Object> createRootCA(String algorithm, int keySize, String digestSignAlgo,
                                               String subj, long validDays, String rootCACrtPath, String rootCAKeyPath) {

        //參數分別爲 公鑰算法 簽名算法 providerName(由於不知道確切的 只好使用null 既使用默認的provider)
        CertAndKeyGen cak = null;
        try {
            cak = new CertAndKeyGen(algorithm, digestSignAlgo,null);
            //生成一對key 參數爲key的長度 對於rsa不能小於512
            cak.generate(keySize);
            cak.setRandom(new SecureRandom());

            //證書擁有者subject的描述name
            X500Name subject = new X500Name(subj);

            //給證書配置擴展信息
            PublicKey publicKey = cak.getPublicKey();
            PrivateKey privateKey = cak.getPrivateKey();
            CertificateExtensions certExts = new CertificateExtensions();
            certExts.set(SubjectKeyIdentifierExtension.NAME, new SubjectKeyIdentifierExtension((new KeyIdentifier(publicKey)).getIdentifier()));
            certExts.set(AuthorityKeyIdentifierExtension.NAME, new AuthorityKeyIdentifierExtension(new KeyIdentifier(publicKey), null, null));
            //設置是否根證書
            BasicConstraintsExtension bce = new BasicConstraintsExtension(true, -1);
            certExts.set(BasicConstraintsExtension.NAME, new BasicConstraintsExtension(false, bce.getExtensionValue()));

            //配置證書的有效期,並生成根證書(自簽名證書)
            X509Certificate certificate = cak.getSelfCertificate(subject, new Date(),validDays * 24L * 60L * 60L, certExts);

            HashMap<String, Object> rootCA = new HashMap<>();
            rootCA.put("key", privateKey);
            rootCA.put("crt", certificate);

            exportCrt(certificate, rootCACrtPath);
            exportKey(privateKey, rootCAKeyPath);

            return rootCA;

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

 

3. 建立用戶證書函數

/**
     * 建立X509的證書, 由ca證書完成簽名。
     *
     * subject,issuer都遵循X500Principle規範,
     * 即: X500Principal由可分辨名稱表示,例如「CN = Duke,OU = JavaSoft,O = Sun Microsystems,C = US」。
     *
     * @param ca 根證書對象
     * @param caKey CA證書對應的私鑰對象
     * @param publicKey 待簽發證書的公鑰對象
     * @param subj 證書擁有者的主題信息,簽發者和主題擁有者名稱都轉寫X500Principle規範,格式:CN=country,ST=state,L=Locality,OU=OrganizationUnit,O=Organization
     * @param validDays 證書有效期天數
     * @param sginAlgo 證書籤名算法, e.g. SHA256withRSA
     *
     * @return security 新建立獲得的X509證書
     */
    public static X509Certificate createUserCert(X509Certificate ca, PrivateKey caKey, PublicKey publicKey, String subj, long validDays, String sginAlgo)  {

        //獲取ca證書
        X509Certificate caCert = ca;

        X509CertInfo x509CertInfo = new X509CertInfo();

        try {
            //設置證書的版本號
            x509CertInfo.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));

            //設置證書的序列號,基於當前時間計算
            x509CertInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber((int) (System.currentTimeMillis() / 1000L)));

            /**
             * 下面這個設置算法ID的代碼,是錯誤的,會致使證書驗證失敗,可是報錯不是很明確。 若將生成的證書存爲keystore,讓後keytool轉換
             * 會出現異常。
             *
             * 重點: AlgorithmId的參數設置要和後面的證書籤名中用到的算法信息一致。
             *
             * AlgorithmId algorithmId = new AlgorithmId(AlgorithmId.SHA256_oid);
             */
            AlgorithmId algorithmId = AlgorithmId.get(sginAlgo);
            x509CertInfo.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algorithmId));

            //設置證書的簽發者信息
            X500Name issuer = new X500Name(caCert.getIssuerX500Principal().toString());
            x509CertInfo.set(X509CertInfo.ISSUER, issuer);

            //設置證書的擁有者信息
            X500Name subject = new X500Name(subj);
            x509CertInfo.set(X509CertInfo.SUBJECT, subject);

            //設置證書的公鑰
            x509CertInfo.set(X509CertInfo.KEY, new CertificateX509Key(publicKey));

            //設置證書有效期
            Date beginDate = new Date();
            Date endDate = new Date(beginDate.getTime() + validDays * 24 * 60 * 60 * 1000L);
            CertificateValidity cv = new CertificateValidity(beginDate, endDate);
            x509CertInfo.set(X509CertInfo.VALIDITY, cv);

            CertificateExtensions exts = new CertificateExtensions();

            exts.set(SubjectKeyIdentifierExtension.NAME, new SubjectKeyIdentifierExtension((new KeyIdentifier(publicKey)).getIdentifier()));
            exts.set(AuthorityKeyIdentifierExtension.NAME, new AuthorityKeyIdentifierExtension(new KeyIdentifier(ca.getPublicKey()), null, null));
            exts.set(BasicConstraintsExtension.NAME, new BasicConstraintsExtension(false,false,-1));
            x509CertInfo.set(CertificateExtensions.NAME, exts);

        } catch (CertificateException cee) {
            cee.printStackTrace();
        } catch (IOException eio) {
            eio.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        // 獲取CA私鑰
        PrivateKey caPrivateKey = caKey;
        //用CA的私鑰給當前證書進行簽名,獲取最終的下游證書(證書鏈的下一節點)
        X509CertImpl cert = new X509CertImpl(x509CertInfo);
        try {
            cert.sign(caPrivateKey, sginAlgo);
        } catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException | NoSuchProviderException | SignatureException e3) {
            e3.printStackTrace();
        }
        return cert;
    }

 

4. demo程序
1) 從根證書開始建立,包含自簽名證書,設備證書以及服務節點證書工具

private static void demoGenFull(String basePath, String devName, String emqHost) throws InvalidKeySpecException, NoSuchAlgorithmException {
        String subjCA = "CN=IOTPlatform,OU=TanKang,O=TKCloud,L=Wuhan,S=Hubei,C=CN";
        String rootCACrtPath = basePath + "sccCA0.crt";
        String rootCAKeyPath = basePath + "sccCA0.key";

        String subjDev = "OU=TanKang,O=TKCloud,L=Wuhan,ST=Hubei,C=CN,CN=IOTDevice" + devName;
        String devCrtPath = basePath + "sccDev" + devName + ".crt";
        String devKeyPath = basePath + "sccDev" + devName + ".key";

        String subjEmq = "OU=TanKang,O=TKCloud,L=Wuhan,ST=Hubei,C=CN,CN=" + emqHost;
        String emqCommName = emqHost.replace(".", "-");
        String emqCrtPath = basePath + "sccEmq" + emqCommName + ".crt";
        String emqKeyPath = basePath + "sccEmq" + emqCommName + ".key";

        /**
         * 建立根證書,即自簽名證書
         */
        HashMap<String, Object> rootCA = MySSL.createRootCA("RSA",2048, MSG_DIGEST_SIGN_ALGO,  subjCA, 3650);
        X509Certificate caCrt = (X509Certificate) rootCA.get(CERTIFICATE);
        PrivateKey caKey = (PrivateKey)rootCA.get(PRIVATE_KEY);
        MySSL.exportCrt(caCrt, rootCACrtPath);
        MySSL.exportKey(caKey, rootCAKeyPath);

        /**
         * 建立公鑰和私鑰對,而後基於自簽名證書籤發設備證書,即客戶端證書
         */
        Map<String, String> keyDev = MySSL.createKeys(2048, RSA_ALGORITHM);
        PublicKey devPubKey = MySSL.getPublicKey(keyDev.get(PUBLIC_KEY));
        PrivateKey devPriKey = MySSL.getPrivateKey(keyDev.get(PRIVATE_KEY));
        X509Certificate devCrt = MySSL.createUserCert(caCrt, caKey, devPubKey, subjDev, 3650, MSG_DIGEST_SIGN_ALGO);
        MySSL.exportCrt(devCrt, devCrtPath);
        MySSL.exportKey(devPriKey, devKeyPath);

        /**
         * 建立公鑰和私鑰對,而後基於自簽名證書籤發EMQ證書,即服務端證書。
         */
        Map<String, String> keyEmq = MySSL.createKeys(2048, RSA_ALGORITHM);
        PublicKey emqPubKey = MySSL.getPublicKey(keyEmq.get(PUBLIC_KEY));
        PrivateKey emqPriKey = MySSL.getPrivateKey(keyEmq.get(PRIVATE_KEY));
        X509Certificate emqCrt = MySSL.createUserCert(caCrt, caKey, emqPubKey, subjEmq, 3650, MSG_DIGEST_SIGN_ALGO);
        MySSL.exportCrt(emqCrt, emqCrtPath);
        MySSL.exportKey(emqPriKey, emqKeyPath);
    }

2) 根證書已經建立了,經過加載根證書的方式,簽發設備證書以及服務端節點證書post

private static void demoGenUserCertWithExistedCA(String basePath, String devName, String emqHost) throws InvalidKeySpecException, NoSuchAlgorithmException {
        String rootCACrtPath = basePath + "sccCA0.crt";
        String rootCAKeyPath = basePath + "sccCA0.key";

        String subjDev = "OU=TanKang,O=TKCloud,L=Wuhan,ST=Hubei,C=CN,CN=IOTDevice" + devName;
        String devCrtPath = basePath + "sccDev" + devName + ".crt";
        String devKeyPath = basePath + "sccDev" + devName + ".key";

        String subjEmq = "OU=TanKang,O=TKCloud,L=Wuhan,ST=Hubei,C=CN,CN=" + emqHost;
        String emqCommName = emqHost.replace(".", "-");
        String emqCrtPath = basePath + "sccEmq" + emqCommName + ".crt";
        String emqKeyPath = basePath + "sccEmq" + emqCommName + ".key";

        /**
         * 從指定的文件加載構建根證書以及對應的私鑰
         */
        X509Certificate caCrt = MySSL.getCertficate(new File(rootCACrtPath));
        PrivateKey caKey = MySSL.getPrivateKey(new File(rootCAKeyPath));

        /**
         * 建立公鑰和私鑰對,而後基於自簽名證書籤發設備證書,即客戶端證書
         */
        Map<String, String> keyDev = MySSL.createKeys(2048, RSA_ALGORITHM);
        PublicKey devPubKey = MySSL.getPublicKey(keyDev.get(PUBLIC_KEY));
        PrivateKey devPriKey = MySSL.getPrivateKey(keyDev.get(PRIVATE_KEY));
        X509Certificate devCrt = MySSL.createUserCert(caCrt, caKey, devPubKey, subjDev, 3650, MSG_DIGEST_SIGN_ALGO);
        MySSL.exportCrt(devCrt, devCrtPath);
        MySSL.exportKey(devPriKey, devKeyPath);

        /**
         * 建立公鑰和私鑰對,而後基於自簽名證書籤發EMQ證書,即服務端證書。
         */
        Map<String, String> keyEmq = MySSL.createKeys(2048, RSA_ALGORITHM);
        PublicKey emqPubKey = MySSL.getPublicKey(keyEmq.get(PUBLIC_KEY));
        PrivateKey emqPriKey = MySSL.getPrivateKey(keyEmq.get(PRIVATE_KEY));
        X509Certificate emqCrt = MySSL.createUserCert(caCrt, caKey, emqPubKey, subjEmq, 3650, MSG_DIGEST_SIGN_ALGO);
        MySSL.exportCrt(emqCrt, emqCrtPath);
        MySSL.exportKey(emqPriKey, emqKeyPath);
    }

 

其中,經過文件重構私鑰的函數getPrivateKey(File file)的函數以下:編碼

   /**
     * 利用開源的工具類BC解析私鑰,例如openssl私鑰文件格式爲pem,須要去除頁眉頁腳後才能被java讀取
     *
     * @param file 私鑰文件
     * @return 私鑰對象
     */
    public static PrivateKey getPrivateKey(File file) {
        if (file == null) {
            return null;
        }
        PrivateKey privKey = null;
        PemReader pemReader = null;
        try {
            pemReader = new PemReader(new FileReader(file));
            PemObject pemObject = pemReader.readPemObject();
            byte[] pemContent = pemObject.getContent();
            //支持從PKCS#1或PKCS#8 格式的私鑰文件中提取私鑰, PKCS#1的私鑰,主要是openssl默認生成的編碼格式
            if (pemObject.getType().endsWith("RSA PRIVATE KEY")) {
                /*
                 * 取得私鑰  for PKCS#1
                 * openssl genrsa 默認生成的私鑰就是PKCS1的編碼
                 */
                org.bouncycastle.asn1.pkcs.RSAPrivateKey asn1PrivateKey = org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(pemContent);
                RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(asn1PrivateKey.getModulus(), asn1PrivateKey.getPrivateExponent());
                KeyFactory keyFactory= KeyFactory.getInstance(RSA_ALGORITHM);
                privKey= keyFactory.generatePrivate(rsaPrivateKeySpec);
            } else if (pemObject.getType().endsWith("PRIVATE KEY")) {
                /*
                 * java建立的私鑰,默認是PKCS#8格式
                 *
                 * 經過openssl pkcs8 -topk8轉換爲pkcs8,例如(-nocrypt不作額外加密操做):
                 * openssl pkcs8 -topk8 -in pri.key -out pri8.key -nocrypt
                 *
                 * 取得私鑰 for PKCS#8
                 */
                PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(pemContent);
                KeyFactory kf = KeyFactory.getInstance(RSA_ALGORITHM);
                privKey = kf.generatePrivate(privKeySpec);
            }
        } catch (FileNotFoundException e) {
            logger.error("read private key fail,the reason is the file not exist");
            e.printStackTrace();
        } catch (IOException e) {
            logger.error("read private key fail,the reason is :"+e.getMessage());
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            logger.error("read private key fail,the reason is :"+e.getMessage());
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            logger.error("read private key fail,the reason is :"+e.getMessage());
            e.printStackTrace();
        }  finally {
            try {
                if (pemReader != null) {
                    pemReader.close();
                }
            } catch (IOException e) {
                logger.error(e.getMessage());
            }
        }
        return privKey;
    }

 

這個補充的博文,不作過多解釋,全部的代碼,都淺顯易懂,建立的證書等文件,經過openssl工具,當作PEM格式的文件進行查看或者其餘操做,都是能夠的。如有什麼不清楚,請關注個人博客,給我留言,一塊兒探討!

相關文章
相關標籤/搜索