Skip to content

Commit 6e69c62

Browse files
authored
Allow PKCS12 certificates to have non-privatekey aliases (#549)
1 parent 7481152 commit 6e69c62

File tree

2 files changed

+104
-12
lines changed

2 files changed

+104
-12
lines changed

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ClientCertificate.java

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
package com.microsoft.aad.msal4j;
55

6+
import lombok.Getter;
7+
import lombok.experimental.Accessors;
8+
69
import java.io.IOException;
710
import java.io.InputStream;
811
import java.lang.reflect.InvocationTargetException;
@@ -19,10 +22,11 @@
1922
import java.security.cert.CertificateException;
2023
import java.security.cert.X509Certificate;
2124
import java.security.interfaces.RSAPrivateKey;
22-
import java.util.*;
23-
24-
import lombok.Getter;
25-
import lombok.experimental.Accessors;
25+
import java.util.ArrayList;
26+
import java.util.Arrays;
27+
import java.util.Base64;
28+
import java.util.Enumeration;
29+
import java.util.List;
2630

2731
final class ClientCertificate implements IClientCertificate {
2832

@@ -97,14 +101,7 @@ static ClientCertificate create(InputStream pkcs12Certificate, String password)
97101
final KeyStore keystore = KeyStore.getInstance("PKCS12");
98102
keystore.load(pkcs12Certificate, password.toCharArray());
99103

100-
final Enumeration<String> aliases = keystore.aliases();
101-
if (!aliases.hasMoreElements()) {
102-
throw new IllegalArgumentException("certificate not loaded from input stream");
103-
}
104-
String alias = aliases.nextElement();
105-
if (aliases.hasMoreElements()) {
106-
throw new IllegalArgumentException("more than one certificate alias found in input stream");
107-
}
104+
String alias = getPrivateKeyAlias(keystore);
108105

109106
ArrayList<X509Certificate> publicKeyCertificateChain = new ArrayList<>();
110107
PrivateKey privateKey = (PrivateKey) keystore.getKey(alias, password.toCharArray());
@@ -123,6 +120,26 @@ static ClientCertificate create(InputStream pkcs12Certificate, String password)
123120
return new ClientCertificate(privateKey, publicKeyCertificateChain);
124121
}
125122

123+
static String getPrivateKeyAlias(KeyStore keystore) throws KeyStoreException {
124+
String alias = null;
125+
final Enumeration<String> aliases = keystore.aliases();
126+
while (aliases.hasMoreElements()) {
127+
String currentAlias = aliases.nextElement();
128+
if (keystore.entryInstanceOf(currentAlias, KeyStore.PrivateKeyEntry.class)) {
129+
if (alias != null) {
130+
throw new IllegalArgumentException("more than one certificate alias found in input stream");
131+
}
132+
alias = currentAlias;
133+
}
134+
}
135+
136+
if (alias == null) {
137+
throw new IllegalArgumentException("certificate not loaded from input stream");
138+
}
139+
140+
return alias;
141+
}
142+
126143
static ClientCertificate create(final PrivateKey key, final X509Certificate publicKeyCertificate) {
127144
return new ClientCertificate(key, Arrays.asList(publicKeyCertificate));
128145
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.aad.msal4j;
5+
6+
import org.easymock.EasyMock;
7+
import org.testng.annotations.BeforeMethod;
8+
import org.testng.annotations.Test;
9+
10+
import java.security.KeyStore;
11+
import java.security.KeyStoreSpi;
12+
import java.util.Arrays;
13+
import java.util.Collections;
14+
15+
import static org.testng.AssertJUnit.assertEquals;
16+
17+
@Test
18+
public class ClientCertificatePkcs12Test extends AbstractMsalTests {
19+
20+
private KeyStoreSpi keyStoreSpi;
21+
private KeyStore keystore;
22+
23+
@BeforeMethod
24+
public void setUp() throws Exception {
25+
keyStoreSpi = EasyMock.createMock(KeyStoreSpi.class);
26+
keystore = new KeyStore(keyStoreSpi, null, "PKCS12") {};
27+
keystore.load(null);
28+
}
29+
30+
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "certificate not loaded from input stream")
31+
public void testNoEntries() throws Exception {
32+
EasyMock.expect(keyStoreSpi.engineAliases())
33+
.andReturn(Collections.enumeration(Collections.emptyList())).times(1);
34+
EasyMock.replay(keyStoreSpi);
35+
36+
ClientCertificate.getPrivateKeyAlias(keystore);
37+
}
38+
39+
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "certificate not loaded from input stream")
40+
public void testNoPrivateKey() throws Exception {
41+
EasyMock.expect(keyStoreSpi.engineAliases())
42+
.andReturn(Collections.enumeration(Arrays.asList("CA_cert1", "CA_cert2"))).times(1);
43+
EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("CA_cert1", KeyStore.PrivateKeyEntry.class)).andReturn(false).times(1);
44+
EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("CA_cert2", KeyStore.PrivateKeyEntry.class)).andReturn(false).times(1);
45+
EasyMock.replay(keyStoreSpi);
46+
47+
ClientCertificate.getPrivateKeyAlias(keystore);
48+
}
49+
50+
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "more than one certificate alias found in input stream")
51+
public void testMultiplePrivateKeyAliases() throws Exception {
52+
EasyMock.expect(keyStoreSpi.engineAliases())
53+
.andReturn(Collections.enumeration(Arrays.asList("private_key1", "private_key2", "CA_cert"))).times(1);
54+
EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("private_key1", KeyStore.PrivateKeyEntry.class)).andReturn(true).times(1);
55+
EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("private_key2", KeyStore.PrivateKeyEntry.class)).andReturn(true).times(1);
56+
EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("CA_cert", KeyStore.PrivateKeyEntry.class)).andReturn(false).times(1);
57+
EasyMock.replay(keyStoreSpi);
58+
59+
ClientCertificate.getPrivateKeyAlias(keystore);
60+
}
61+
62+
@Test
63+
public void testMultipleEntriesButOnlyOnePrivateKey() throws Exception {
64+
EasyMock.expect(keyStoreSpi.engineAliases())
65+
.andReturn(Collections.enumeration(Arrays.asList("CA_cert1", "private_key", "CA_cert2"))).times(1);
66+
EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("CA_cert1", KeyStore.PrivateKeyEntry.class)).andReturn(false).times(1);
67+
EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("private_key", KeyStore.PrivateKeyEntry.class)).andReturn(true).times(1);
68+
EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("CA_cert2", KeyStore.PrivateKeyEntry.class)).andReturn(false).times(1);
69+
EasyMock.replay(keyStoreSpi);
70+
71+
String privateKeyAlias = ClientCertificate.getPrivateKeyAlias(keystore);
72+
assertEquals("private_key", privateKeyAlias);
73+
}
74+
75+
}

0 commit comments

Comments
 (0)