Skip to content

Commit 5c1dfae

Browse files
pktxupk-aetionmanusa
authored
support EC private key only for mTLS auth (#5657)
* reintroduce tests originally introduced in #1314 later removed in aetion@9e1d434 * add failing test * fix * update CHANGELOG.md * spotless formatting * test error paths * address PR feedback * spotless whitespace * address PR feedback * review: EC private key support Signed-off-by: Marc Nuri <marc@marcnuri.com> --------- Signed-off-by: Marc Nuri <marc@marcnuri.com> Co-authored-by: pktxu <5881103+pktxu@users.noreply.github.com> Co-authored-by: pk-aetion <98763647+pk-aetion@users.noreply.github.com> Co-authored-by: Marc Nuri <marc@marcnuri.com>
1 parent d0292ae commit 5c1dfae

File tree

8 files changed

+126
-10
lines changed

8 files changed

+126
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* Fix #5584: Fix CRD generation when EnumMap is used
99
* Fix #5626: Prevent memory accumulation from informer usage
1010
* Fix #5527: Unable to transfer file to pod if `/tmp` is read-only
11+
* Fix #5656: Enable EC private key usage for mTLS auth
1112

1213
#### Improvements
1314
* Fix #5429: moved crd generator annotations to generator-annotations instead of crd-generator-api. Using generator-annotations introduces no transitive dependencies.

kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/internal/CertUtils.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import io.fabric8.kubernetes.client.KubernetesClientException;
1919
import io.fabric8.kubernetes.client.utils.Utils;
20+
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
21+
import org.bouncycastle.jce.provider.BouncyCastleProvider;
2022
import org.bouncycastle.openssl.PEMKeyPair;
2123
import org.bouncycastle.openssl.PEMParser;
2224
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
@@ -180,22 +182,27 @@ private static PrivateKey handleECKey(InputStream keyInputStream) {
180182
try {
181183
return new Callable<PrivateKey>() {
182184
@Override
183-
public PrivateKey call() {
184-
try {
185-
if (Security.getProvider("BC") == null && Security.getProvider("BCFIPS") == null) {
186-
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
187-
}
188-
PEMKeyPair keys = (PEMKeyPair) new PEMParser(new InputStreamReader(keyInputStream)).readObject();
189-
return new JcaPEMKeyConverter().getKeyPair(keys).getPrivate();
190-
} catch (IOException exception) {
191-
exception.printStackTrace();
185+
public PrivateKey call() throws IOException {
186+
if (Security.getProvider("BC") == null && Security.getProvider("BCFIPS") == null) {
187+
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
188+
}
189+
Object pemObject = new PEMParser(new InputStreamReader(keyInputStream)).readObject();
190+
if (pemObject == null) {
191+
throw new KubernetesClientException("Got null PEM object from EC key's input stream.");
192+
} else if (pemObject instanceof PEMKeyPair) {
193+
return new JcaPEMKeyConverter().getKeyPair((PEMKeyPair) pemObject).getPrivate();
194+
} else if (pemObject instanceof PrivateKeyInfo) {
195+
return BouncyCastleProvider.getPrivateKey((PrivateKeyInfo) pemObject);
196+
} else {
197+
throw new KubernetesClientException("Don't know what to do with a " + pemObject.getClass().getName());
192198
}
193-
return null;
194199
}
195200
}.call();
196201
} catch (NoClassDefFoundError e) {
197202
throw new KubernetesClientException(
198203
"JcaPEMKeyConverter is provided by BouncyCastle, an optional dependency. To use support for EC Keys you must explicitly add this dependency to classpath.");
204+
} catch (IOException e) {
205+
throw new KubernetesClientException(e.getMessage());
199206
}
200207
}
201208

kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/internal/CertUtilsTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.fabric8.kubernetes.client.internal;
1717

18+
import io.fabric8.kubernetes.client.KubernetesClientException;
1819
import io.fabric8.kubernetes.client.utils.IOHelpers;
1920
import io.fabric8.kubernetes.client.utils.Utils;
2021
import org.junit.jupiter.api.AfterEach;
@@ -31,12 +32,14 @@
3132
import java.security.NoSuchAlgorithmException;
3233
import java.security.cert.Certificate;
3334
import java.security.cert.CertificateException;
35+
import java.security.spec.InvalidKeySpecException;
3436
import java.util.Base64;
3537
import java.util.Collections;
3638
import java.util.Objects;
3739
import java.util.Properties;
3840

3941
import static org.assertj.core.api.Assertions.assertThat;
42+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4043
import static org.junit.Assert.assertNotSame;
4144
import static org.junit.jupiter.api.Assertions.assertEquals;
4245
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -184,6 +187,48 @@ void getInputStreamFromDataOrFileShouldDecodeBase64EncodedString() throws IOExce
184187
assertEquals(inputStr, certDataReadFromInputStream);
185188
}
186189

190+
@Test
191+
void loadECkeys()
192+
throws InvalidKeySpecException, CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
193+
String privateKeyPath = Utils.filePath(getClass().getResource("/ssl-test/fabric8-ec.paired.key"));
194+
String certPath = Utils.filePath(getClass().getResource("/ssl-test/fabric8-ec.cert"));
195+
196+
KeyStore trustStore = CertUtils.createKeyStore(null, certPath, null, privateKeyPath, "EC", "foo", null, null);
197+
198+
assertEquals(1, trustStore.size());
199+
}
200+
201+
@Test
202+
void loadECPrivateOnlyKey()
203+
throws InvalidKeySpecException, CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
204+
String privateKeyPath = Utils.filePath(getClass().getResource("/ssl-test/fabric8-ec.private-only.key"));
205+
String certPath = Utils.filePath(getClass().getResource("/ssl-test/fabric8-ec.cert"));
206+
207+
KeyStore trustStore = CertUtils.createKeyStore(null, certPath, null, privateKeyPath, "EC", "foo", null, null);
208+
209+
assertEquals(1, trustStore.size());
210+
}
211+
212+
@Test
213+
void loadNothingError() {
214+
String privateKeyPath = Utils.filePath(getClass().getResource("/ssl-test/empty"));
215+
String certPath = Utils.filePath(getClass().getResource("/ssl-test/empty"));
216+
217+
assertThatExceptionOfType(KubernetesClientException.class)
218+
.isThrownBy(() -> CertUtils.createKeyStore(null, certPath, null, privateKeyPath, "EC", "foo", null, null))
219+
.withMessage("Got null PEM object from EC key's input stream.");
220+
}
221+
222+
@Test
223+
void loadUnknownError() {
224+
String privateKeyPath = Utils.filePath(getClass().getResource("/ssl-test/multiple-certs.p7b"));
225+
String certPath = Utils.filePath(getClass().getResource("/ssl-test/multiple-certs.p7b"));
226+
227+
assertThatExceptionOfType(KubernetesClientException.class)
228+
.isThrownBy(() -> CertUtils.createKeyStore(null, certPath, null, privateKeyPath, "EC", "foo", null, null))
229+
.withMessageContaining("Don't know what to do with a");
230+
}
231+
187232
@Test
188233
void storeKeyFallbacksToDefault() throws Exception {
189234
// When

kubernetes-client-api/src/test/resources/ssl-test/empty

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIC7TCCAdWgAwIBAgIUD0Q+lZUpqmvTUxmCbhsPx93hYI4wDQYJKoZIhvcNAQEL
3+
BQAwczELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDVNhbiBGcmFuY2lzY28xCzAJBgNV
4+
BAcTAkNBMRgwFgYDVQQKEw9NeSBDb21wYW55IE5hbWUxEzARBgNVBAsTCk9yZyBV
5+
bml0IDIxEDAOBgNVBAMTB0t1YmUgQ0EwHhcNMTgwMjEzMDc1MzAwWhcNMjMwMjEy
6+
MDc1MzAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UECBMNU2FuIEZyYW5jaXNjbzEL
7+
MAkGA1UEBxMCQ0ExDjAMBgNVBAMTBWFkbWluMFkwEwYHKoZIzj0CAQYIKoZIzj0D
8+
AQcDQgAEUHBg7OvKkSprAljQcCcpXFns/pMNDkQJZuooj97A0063ipBrZzbdxTcu
9+
VcBjFNJC/Tn2keNSQP+m9QbQmQfmM6N1MHMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
10+
JQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFMBq0KJaRKoC
11+
+a8R5la/B8tJahD3MB8GA1UdIwQYMBaAFOuZHF+ex0WLX3UX9LJoFHbI9cPNMA0G
12+
CSqGSIb3DQEBCwUAA4IBAQC47D3zYMTg/C9gOYu20xEmw6eTEhJ1wWG9jSdHM9G8
13+
0F3mD4+bG3skx5kaCgtJ3KqbMSGPJ234Ju9VHCNiyiasZS41a8wuagJ6v5pTItLL
14+
BmQ3OhT2HJZz+lDbsb3jLDzesQ5UCct08/e8ST4hnZUcSrz2geD1hSYQEhadsI87
15+
A+wVAvGfhCyiDUiClA2vwosWfomshkWphRzy+s5zFLuxlBAxj8g2oAbCJfhfJS5A
16+
O8W5Nu+ddO5+7PB+y28Bue1GWABIjytmyLdvic0vkKzZkSzPOwqCT1ofZ9HU56h7
17+
jKOjtBacCcl/7pLgvaA8Ng1qfjQTiveDVfI8rNWiEhRm
18+
-----END CERTIFICATE-----
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MHcCAQEEIAPhomYs9rdnNgEtr2FIB1rBDYnuKqV4QVAYBX4yRqAEoAoGCCqGSM49
3+
AwEHoUQDQgAEUHBg7OvKkSprAljQcCcpXFns/pMNDkQJZuooj97A0063ipBrZzbd
4+
xTcuVcBjFNJC/Tn2keNSQP+m9QbQmQfmMw==
5+
-----END EC PRIVATE KEY-----
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQglAqmnWCvaPD+4fCx
3+
rIAuhPoZcToa25FT3pKinym+9w6hRANCAAQr1Kmy52b6tabokBe3M8gO8Vi9N+Bc
4+
BkTWaRt9NijEmB9uJNsJ835nVmvQEdjffk5nErGMcp0ytPJak27040+K
5+
-----END PRIVATE KEY-----
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
-----BEGIN PKCS7-----
2+
MIIGKQYJKoZIhvcNAQcCoIIGGjCCBhYCAQExADALBgkqhkiG9w0BBwGgggX8MIIC
3+
6jCCAdKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtvcGVuc2hp
4+
ZnQtc2lnbmVyQDE0ODkwNTI0ODEwHhcNMTcwMzA5MDk0MTIxWhcNMjIwMzA4MDk0
5+
MTIyWjAmMSQwIgYDVQQDDBtvcGVuc2hpZnQtc2lnbmVyQDE0ODkwNTI0ODEwggEi
6+
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHvEtorBUHnUErPhKKbRrpEd/3
7+
Kl1UeMMowkHNxq9FMHcm6Mczuu7I0O+qtKbuW7arAZyaoSLO2ezW1lLRuGsuMSJ7
8+
qUqiRus+RUCQWDxLeZhu4OXvcebR3T+drzd35lOwkZBqoNysmT6Uqs7RVZ05+jKK
9+
VJpKDya99Zbr/9zTsYH+gzSAg4LOyMs9fd2tZ75pMHVS0MWMAhXLAPtP/JDMBXOx
10+
iBoFMymsnKeHKThztjoA7dzS1XQTq4cGcCJqyj3wdvey76HBaLzMfu9ZaoA10J20
11+
eRnk7K4oQEK9jE1rBof4huFa5vvBumSv8Ds/yMzpOA0qOjXPcmJzx0xwQQunAgMB
12+
AAGjIzAhMA4GA1UdDwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
13+
DQEBCwUAA4IBAQA3hfVp3yf8k0dPr6p/H/xgDKxedWOUTbdfl3v+AWGgSUvDT30H
14+
ruYqaC34FXCvoUIia4kxzZlDBDjMg9/9zOOCQdwQimgQM2tOujNUFR3HBEfgYLxt
15+
Y106hxzJpLCO+HsYPX9rFOTAsvt2og/jflP10EHAodj40FSymL6HIV671kzbykoq
16+
fM3Hp+fPfUmvldxZSkzCGJAlDylE8pF4NOMd7B0LKPJuQWM2wgFH1uWQqqq0gu6o
17+
q6GoX+39CSlx5bfydd2D660+Xt0G4k2u6z4SFxqb1hKTpi6cGtB8TERa6sxbl/Kl
18+
cfE/R05F+L34hITDjoKYSZUWe5r4weAdMtwpMIIDCjCCAfKgAwIBAgIBATANBgkq
19+
hkiG9w0BAQsFADA2MTQwMgYDVQQDDCtvcGVuc2hpZnQtc2VydmljZS1zZXJ2aW5n
20+
LXNpZ25lckAxNDg5MDUyNDgyMB4XDTE3MDMwOTA5NDEyMVoXDTIyMDMwODA5NDEy
21+
MlowNjE0MDIGA1UEAwwrb3BlbnNoaWZ0LXNlcnZpY2Utc2VydmluZy1zaWduZXJA
22+
MTQ4OTA1MjQ4MjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALKvcK+2
23+
kKix24UR5U9en6IJ5yTYBS1ozLCTsKAKWrD4Ttpec3ON/SycoEfUXY6ao+pN2Q0Q
24+
bH0/hi1+fFQgdHoQxo1yG4/SAwEu+n4uUse5ApCtNWLAv8jO+Y1o33CSIIM4Sm3P
25+
uw7w/qzHAGmad6Nvg5v+ZOxdGNm7UU399zWzY6+igFo/rg8oacZVerchrnLE2cev
26+
pjafguqy6kiuKFbfdIcYGfdmmypyC/SbpwgRABPWLBqk/AUfkNClKVi91Ysbaj5t
27+
lFSXEu2i2z6aAhJCHddjGeq17cQybiYVLhqXPYXtdXH+khYfXY9cupqVftrXgV6c
28+
/xWRzmRxDQyTqdMCAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB/wQF
29+
MAMBAf8wDQYJKoZIhvcNAQELBQADggEBADA5X9iNqR8JmXbHCFMovs5PIOExK6uR
30+
1xDBMmD6op0giDXB4ljnIa4R7euo1BCIOjC9uX6YT0OEXl31IXPAzapZNHzp2sW9
31+
8pyxDXBd78KD/WEeWfeO4rwL4CMACgqUT9LwNCdL01s74NEruNzWIfx74Az1WtlU
32+
8YqE7RroCpcBaqZgLJih9jcBdOVuZN5gK/xJm2P1QminD5Z+YzU2yeCFOnRzueA9
33+
tFveiPVRk454bflfsW8dfixTNeU9MuG3PbtZ9nqR0hBKWtPst1tJwLTOdNcAijt1
34+
T4r6qoZAmGJcEp/EQ5b0beARM1STqNeO3GPOOg1Ec33+jUHYySkQ3JGhADEA
35+
-----END PKCS7-----

0 commit comments

Comments
 (0)