OPCUA標準java實現 Milo庫 證書的生成和使用

Milo庫中的證書生成和使用

在我的上一篇文章《Milo庫OPCUA協議java實現》中發現比較多人留言說到比較困惑在創建客戶端過程中的證書的生成和使用。下面我就跟大家說一下。

我自己做的一個相關例程:證書處理的例程

使用Milo庫自帶的證書生成工具

在上一篇文章中我們可以看到,OPC UA客戶端對象的創建是需要一個X509Certificate證書對象,和一個KeyPair密鑰對。

下面的代碼用到了Milo庫中的工具對象來讀取或者創建證書文件,代碼會有點長,請耐心看下去。(我會在下面代碼相應的地方作註釋說明)。

class KeyStoreLoader {

    private static final Pattern IP_ADDR_PATTERN = Pattern.compile(
        "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");

    //證書別名
    private static final String CLIENT_ALIAS = "client-ai";
    //獲取私鑰的密碼
    private static final char[] PASSWORD = "password".toCharArray();

    private final Logger logger = LoggerFactory.getLogger(getClass());

    //證書對象
    private X509Certificate clientCertificate;
    //密鑰對對象
    private KeyPair clientKeyPair;

    KeyStoreLoader load(Path baseDir) throws Exception {
        //創建一個使用`PKCS12`加密標準的KeyStore。KeyStore在後面將作爲讀取和生成證書的對象。
        KeyStore keyStore = KeyStore.getInstance("PKCS12");

        //PKCS12的加密標準的文件後綴是.pfx,其中包含了公鑰和私鑰。
        //而其他如.der等的格式只包含公鑰,私鑰在另外的文件中。
        Path serverKeyStore = baseDir.resolve("example-client.pfx");

        logger.info("Loading KeyStore at {}", serverKeyStore);

        //如果文件不存在則創建.pfx證書文件。
        if (!Files.exists(serverKeyStore)) {
            keyStore.load(null, PASSWORD);

            //用2048位的RAS算法。`SelfSignedCertificateGenerator`爲Milo庫的對象。
            KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);

            //`SelfSignedCertificateBuilder`也是Milo庫的對象,用來生成證書。
            //中間所設置的證書屬性可以自行修改。
            SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair)
                .setCommonName("Eclipse Milo Example Client")
                .setOrganization("digitalpetri")
                .setOrganizationalUnit("dev")
                .setLocalityName("Folsom")
                .setStateName("CA")
                .setCountryCode("US")
                .setApplicationUri("urn:eclipse:milo:examples:client")
                .addDnsName("localhost")
                .addIpAddress("127.0.0.1");

            // Get as many hostnames and IP addresses as we can listed in the certificate.
            for (String hostname : HostnameUtil.getHostnames("0.0.0.0")) {
                if (IP_ADDR_PATTERN.matcher(hostname).matches()) {
                    builder.addIpAddress(hostname);
                } else {
                    builder.addDnsName(hostname);
                }
            }
            //創建證書
            X509Certificate certificate = builder.build();

            //設置對應私鑰的別名,密碼,證書鏈
            keyStore.setKeyEntry(CLIENT_ALIAS, keyPair.getPrivate(), PASSWORD, new X509Certificate[]{certificate});
            try (OutputStream out = Files.newOutputStream(serverKeyStore)) {
                //保存證書到輸出流
                keyStore.store(out, PASSWORD);
            }
        } else {
            try (InputStream in = Files.newInputStream(serverKeyStore)) {
                //如果文件存在則讀取
                keyStore.load(in, PASSWORD);
            }
        }

        //用密碼獲取對應別名的私鑰。
        Key serverPrivateKey = keyStore.getKey(CLIENT_ALIAS, PASSWORD);
        if (serverPrivateKey instanceof PrivateKey) {
            //獲取對應別名的證書對象。
            clientCertificate = (X509Certificate) keyStore.getCertificate(CLIENT_ALIAS);
            //獲取公鑰
            PublicKey serverPublicKey = clientCertificate.getPublicKey();
            //創建Keypair對象。
            clientKeyPair = new KeyPair(serverPublicKey, (PrivateKey) serverPrivateKey);
        }

        return this;
    }

    //返回證書
    X509Certificate getClientCertificate() {
        return clientCertificate;
    }

    //返回密鑰對
    KeyPair getClientKeyPair() {
        return clientKeyPair;
    }

}

上面所返回的證書和密鑰對就可以在創建OPC UA客戶端對象中使用了。

上面例子來自Milo例程:Milo證書例程


對現有的.der和.pem文件獲取證書對象和密鑰對

這裏我們先討論怎樣獲取.der,.pem文件的證書和密鑰,關於該類型證書的生成我們下面再來一起討論。

假設如果我們原本有.der.pem文件的話,想要讀取證書和私鑰就會稍稍有點複雜,創建OPC客戶端對象需要證書和密鑰對,那麼自然要獲取證書對象,和公鑰,私鑰對象。讀取.der證書不是什麼難事,難的在於解析.pem文件。私鑰就是存放在.pem文件中。

關於.pem文件的解析我們藉助bouncycastle包來幫助我們解析,首先需要添加依賴:

maven

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.57</version>
</dependency>

gradle:

compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.57'

證書的獲取

獲取證書的代碼如下:

X509Certificate c = null;
File derFilee = new File("\\...");

FileInputStream in = new FileInputStream(derFile);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
c = (X509Certificate) cf.generateCertificate(in);

密鑰的獲取

密鑰的獲取就需要用到bouncycastle包了。

獲取密匙對的代碼如下:

KeyPair keyPair = null;
File pemFile = new File("\\...");
FileInputStream privatekeyfile = new FileInputStream(pemFile);

//添加BouncyCastleProvider
Security.addProvider(new BouncyCastleProvider());
PEMParser pemParser = new PEMParser(new InputStreamReader(privatekeyfile));
Object object = pemParser.readObject();
PEMDecryptorProvider pemDecryptorProvider = new 

//其中password 是讀取私鑰所需要的密碼
JcePEMDecryptorProviderBuilder().build("password".toCharArray());
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
//獲取KeyPair對象。
keyPair = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(pemDecryptorProvider));

至此我們就成功獲取到證書對象和密鑰對了,就可以使用這兩個對象創建OPC客戶端了。


OPC Foundation的OPC庫中提供的證書處理方法

上一篇我文章也說過,OPC Foundation在Github上也是有OPC UA的java庫的,只是因爲相比於Milo學習資源不多,官方的例程也做得不夠Milo好,所以我就沒有選用OPC Foundation的java實現。

但是既然現在說到了關於證書的生成和使用問題,那麼就來看一下OPC Foundation 是怎樣來創建證書和獲取證書的。

//以下程序需要添加OPC Foundation的java實現的依賴
private static KeyPair getOPCCert(String applicationName)
        throws ServiceResultException
{
    File certFile = new File(applicationName + ".der");
    File privKeyFile =  new File(applicationName+ ".pem");
    //嘗試獲取證書和密鑰對。
    try {
        Cert myServerCertificate = Cert.load( certFile );
        PrivKey myServerPrivateKey = PrivKey.load(privKeyFile, "password");
        KeyPair k = new KeyPair(myServerCertificate, myServerPrivateKey);
        return k;
    } catch (CertificateException e) {
        throw new ServiceResultException( e );
    } catch (IOException e) {
        //如果文件不存在的話則創建一個`.der`和`.pem`文件
        try {
            String hostName = InetAddress.getLocalHost().getHostName();
            String applicationUri = "urn:"+hostName+":"+applicationName;
            KeyPair keys = CertificateUtils.createApplicationInstanceCertificate(applicationName, null, applicationUri, 3650, hostName);
            keys.getCertificate().save(certFile);
            PrivKey privKeySecure = keys.getPrivateKey();
            //設置私鑰訪問密碼
            privKeySecure.save(privKeyFile, "password");
            return keys;
        } catch (Exception e1) {
            throw new ServiceResultException( e1 );
        }
    } catch (NoSuchAlgorithmException e) {
        throw new ServiceResultException( e );
    } catch (Exception e) {
        throw new ServiceResultException( e );
    }
}

上面的例程是OPC Foundation 中的一個獲取和創建證書的例子,可以看到在OPC Foundation 中正是讀取或者創建.der.pem,而且代碼更少,更簡單。

爲什麼OPC Foundation 讀取.pem這麼簡單輕鬆?正是因爲上述例程中KeyPair對象的包名是org.opcfoundation.ua.transport.security,也即是由OPC Foundation重新封裝過的KeyPair對象。而在Milo庫中使用的則是在包爲java.security中的KeyPair對象。所以是不能直接用到Milo庫中的。

這兩個對象的轉化如下:

//k 爲org.opcfoundation.ua.transport.security包中的KeyPair
java.security.KeyPair keyPair = new java.security.KeyPair(k.getCertificate().getCertificate().getPublicKey(),k.getPrivateKey().getPrivateKey());

以上例程來自OPC Foundation證書例程


總結

以上就是我目前來說所用到的證書處理方法,本人知識有限,如果還有更好的方法或者有什麼不對的可以提出。最好的方法還是之間去看源碼,什麼都一目瞭然了。

本人公衆號,有空就更新更新,謝謝支持。哈哈
公衆號ChivaStudio

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章