Skip to content

Commit

Permalink
Updating all BouncyCastle references to newer library
Browse files Browse the repository at this point in the history
Github issue #168
  • Loading branch information
timtay-microsoft committed Dec 5, 2017
1 parent 4d4bab2 commit 56850b8
Show file tree
Hide file tree
Showing 12 changed files with 506 additions and 145 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# PemUtilities Requirements

## Overview

Utility class for converting PEM formatted strings into public X509 certificates and private keys.

## References

## Exposed API

```java
public final class PemUtilities
{
public static PrivateKey parsePrivateKey(String privateKeyString) throws CertificateException;
public static X509Certificate parsePublicKey(String publicKeyCertificateString) throws CertificateException;
}
```

### parsePrivateKey
```java
public static PrivateKey parsePrivateKey(String privateKeyString) throws CertificateException;
```

**SRS_PEMUTILITIES_21_001: [**This function shall return a Private Key instance created by the provided PEM formatted privateKeyString.**]**

**SRS_PEMUTILITIES_21_002: [**If any exception is encountered while attempting to create the private key instance, this function shall throw a CertificateException.**]**


### parsePublicKeyCertificate()
```java
public static X509Certificate parsePublicKeyCertificate(String publicKeyCertificateString) throws CertificateException;
```

**SRS_PEMUTILITIES_21_003: [**This function shall return an X509Certificate instance created by the provided PEM formatted publicKeyCertificateString.**]**

**SRS_PEMUTILITIES_21_004: [**If any exception is encountered while attempting to create the public key certificate instance, this function shall throw a CertificateException.**]**
5 changes: 5 additions & 0 deletions deps/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
</properties>

<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcmail-jdk15on</artifactId>
<version>1.53</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project root for full license information.
*/

package com.microsoft.azure.sdk.iot.deps.util;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMException;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;

import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

public class PemUtilities
{
public static PrivateKey parsePrivateKey(String privateKeyString) throws CertificateException
{
try
{
// Codes_SRS_PEMUTILITIES_21_001: [This function shall return a Private Key instance created by the provided PEM formatted privateKeyString.]
Security.addProvider(new BouncyCastleProvider());
PEMParser privateKeyParser = new PEMParser(new StringReader(privateKeyString));
Object possiblePrivateKey = privateKeyParser.readObject();
PEMKeyPair ukp = (PEMKeyPair) possiblePrivateKey;
return getPrivateKey(ukp);
}
catch (Exception e)
{
// Codes_SRS_PEMUTILITIES_21_002: [If any exception is encountered while attempting to create the private key instance, this function shall throw a CertificateException.]
throw new CertificateException(e);
}
}

public static X509Certificate parsePublicKeyCertificate(String publicKeyCertificateString) throws CertificateException
{
try
{
// Codes_SRS_PEMUTILITIES_21_003: [This function shall return an X509Certificate instance created by the provided PEM formatted publicKeyCertificateString.]
Security.addProvider(new BouncyCastleProvider());
PemReader publicKeyCertificateReader = new PemReader(new StringReader(publicKeyCertificateString));
PemObject possiblePublicKeyCertificate = publicKeyCertificateReader.readPemObject();
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(possiblePublicKeyCertificate.getContent()));
}
catch (Exception e)
{
// Codes_SRS_PEMUTILITIES_21_004: [If any exception is encountered while attempting to create the public key certificate instance, this function shall throw a CertificateException.]
throw new CertificateException(e);
}
}

private static PrivateKey getPrivateKey(PEMKeyPair ukp) throws PEMException
{
return new JcaPEMKeyConverter().setProvider("BC").getKeyPair(ukp).getPrivate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project root for full license information.
*/

package tests.unit.com.microsoft.azure.sdk.iot.deps.util;

import com.microsoft.azure.sdk.iot.deps.util.PemUtilities;
import mockit.Deencapsulation;
import mockit.Mocked;
import mockit.NonStrictExpectations;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import static org.junit.Assert.assertEquals;

/**
* Unit Tests for PemUtilities.java
* Methods: 66%
* Lines: 93%
* Notes: One of the three methods (getPrivateKey(...)) is uncovered because one of the
* Bouncycastle objects (JcaPEMKeyConverter) cannot be mocked. As such, it could not be unit tested.
*/
public class PemUtilitiesTest
{
private static final String expectedPrivateKeyString = "some private key string";
private static final String expectedPublicKeyCertificateString = "some public key certificate string";

@Mocked PrivateKey mockPrivateKey;
@Mocked X509Certificate mockX509Certificate;
@Mocked PEMKeyPair mockPEMKeyPair;
@Mocked PEMParser mockPEMParser;
@Mocked PemObject mockPemObject;
@Mocked PemReader mockPemReader;
@Mocked StringReader mockStringReader;
@Mocked KeyPair mockKeyPair;
@Mocked CertificateFactory mockCertificateFactory;

// Tests_SRS_PEMUTILITIES_21_001: [This function shall return a Private Key instance created by the provided PEM formatted privateKeyString.]
@Test
public void parsePrivateKeySuccess() throws CertificateException, IOException
{
//arrange
final PemUtilities pemUtilities = new PemUtilities();
new NonStrictExpectations(pemUtilities)
{
{
new StringReader(expectedPrivateKeyString);
result = mockStringReader;

new PEMParser(mockStringReader);
result = mockPEMParser;

mockPEMParser.readObject();
result = mockPEMKeyPair;

//Doing this instead of just mocking JCA converter because trying to mock the JCA converter causes strange errors to be thrown.
Deencapsulation.invoke(pemUtilities, "getPrivateKey", new Class[] {PEMKeyPair.class}, mockPEMKeyPair);
result = mockPrivateKey;
}
};

//act
PrivateKey actualPrivateKey = PemUtilities.parsePrivateKey(expectedPrivateKeyString);

//assert
assertEquals(mockPrivateKey, actualPrivateKey);
}

// Tests_SRS_PEMUTILITIES_21_002: [If any exception is encountered while attempting to create the private key instance, this function shall throw a CertificateException.]
@Test (expected = CertificateException.class)
public void parsePrivateKeyExceptionsWrappedInCertificateException() throws CertificateException, IOException
{
//arrange
final PemUtilities pemUtilities = new PemUtilities();
new NonStrictExpectations(pemUtilities)
{
{
new StringReader(expectedPrivateKeyString);
result = new IOException();
}
};

//act
PrivateKey actualPrivateKey = PemUtilities.parsePrivateKey(expectedPrivateKeyString);
}

// Tests_SRS_PEMUTILITIES_21_003: [This function shall return an X509Certificate instance created by the provided PEM formatted publicKeyCertificateString.]
@Test
public void parsePublicKeyCertificateSuccess() throws CertificateException, IOException
{
//arrange
new NonStrictExpectations()
{
{
new PemReader(new StringReader(expectedPublicKeyCertificateString));
result = mockPemReader;

mockPemReader.readPemObject();
result = mockPemObject;

CertificateFactory.getInstance("X.509");
result = mockCertificateFactory;

mockCertificateFactory.generateCertificate(new ByteArrayInputStream(mockPemObject.getContent()));
result = mockX509Certificate;
}
};

//act
X509Certificate actualPublicKeyCertificate = PemUtilities.parsePublicKeyCertificate(expectedPublicKeyCertificateString);

//assert
assertEquals(mockX509Certificate, actualPublicKeyCertificate);
}

// Tests_SRS_PEMUTILITIES_21_004: [If any exception is encountered while attempting to create the public key certificate instance, this function shall throw a CertificateException.]
@Test (expected = CertificateException.class)
public void parsePublicKeyCertificateExceptionsWrappedInCertificateException() throws CertificateException, IOException
{
//arrange
new NonStrictExpectations()
{
{
new PemReader(new StringReader(expectedPublicKeyCertificateString));
result = new IOException();
}
};

//act
X509Certificate actualPublicKeyCertificate = PemUtilities.parsePublicKeyCertificate(expectedPublicKeyCertificateString);

//assert
assertEquals(mockX509Certificate, actualPublicKeyCertificate);
}
}
5 changes: 0 additions & 5 deletions device/iot-device-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,6 @@
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
<dependency>
<groupId>com.microsoft.azure.sdk.iot.provisioning.security</groupId>
<artifactId>security-provider</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@

package com.microsoft.azure.sdk.iot.device.auth;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import com.microsoft.azure.sdk.iot.deps.util.PemUtilities;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.StringReader;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
Expand Down Expand Up @@ -164,24 +162,8 @@ public class IotHubSSLContext
private void generateSSLContextWithKeys(String publicKeyCertificateString, String privateKeyString, IotHubCertificateManager certificateManager)
throws KeyManagementException, IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException
{
Security.addProvider(new BouncyCastleProvider());

PEMReader privateKeyReader = new PEMReader(new StringReader(privateKeyString));
PEMReader publicKeyReader = new PEMReader(new StringReader(publicKeyCertificateString));

Object possiblePrivateKey = privateKeyReader.readObject();
if (!(possiblePrivateKey instanceof KeyPair))
{
throw new CertificateException("Unexpected format for private key");
}
KeyPair certPairWithPrivate = (KeyPair) possiblePrivateKey;

Object possiblePublicKey = publicKeyReader.readObject();
if (!(possiblePublicKey instanceof X509Certificate))
{
throw new CertificateException("Unexpected format for public key certificate");
}
X509Certificate certPairWithPublic = (X509Certificate) possiblePublicKey;
PrivateKey privateKey = PemUtilities.parsePrivateKey(privateKeyString);
X509Certificate certPairWithPublic = PemUtilities.parsePublicKeyCertificate(publicKeyCertificateString);


//Codes_SRS_IOTHUBSSLCONTEXT_34_018: [This constructor shall generate a temporary password to protect the created keystore holding the private key.]
Expand All @@ -191,7 +173,7 @@ private void generateSSLContextWithKeys(String publicKeyCertificateString, Strin
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(null);
keystore.setCertificateEntry(CERTIFICATE_ALIAS, certPairWithPublic);
keystore.setKeyEntry(PRIVATE_KEY_ALIAS, certPairWithPrivate.getPrivate(), temporaryPassword, new Certificate[] {certPairWithPublic});
keystore.setKeyEntry(PRIVATE_KEY_ALIAS, privateKey, temporaryPassword, new Certificate[] {certPairWithPublic});

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keystore, temporaryPassword);
Expand Down
Loading

0 comments on commit 56850b8

Please sign in to comment.