Skip to content

Commit

Permalink
support EC private key only for mTLS auth (#5657)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

---------

Signed-off-by: Marc Nuri <[email protected]>
Co-authored-by: pktxu <[email protected]>
Co-authored-by: pk-aetion <[email protected]>
Co-authored-by: Marc Nuri <[email protected]>
  • Loading branch information
4 people authored Jan 3, 2024
1 parent d0292ae commit 5c1dfae
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Fix #5584: Fix CRD generation when EnumMap is used
* Fix #5626: Prevent memory accumulation from informer usage
* Fix #5527: Unable to transfer file to pod if `/tmp` is read-only
* Fix #5656: Enable EC private key usage for mTLS auth

#### Improvements
* Fix #5429: moved crd generator annotations to generator-annotations instead of crd-generator-api. Using generator-annotations introduces no transitive dependencies.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.utils.Utils;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
Expand Down Expand Up @@ -180,22 +182,27 @@ private static PrivateKey handleECKey(InputStream keyInputStream) {
try {
return new Callable<PrivateKey>() {
@Override
public PrivateKey call() {
try {
if (Security.getProvider("BC") == null && Security.getProvider("BCFIPS") == null) {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}
PEMKeyPair keys = (PEMKeyPair) new PEMParser(new InputStreamReader(keyInputStream)).readObject();
return new JcaPEMKeyConverter().getKeyPair(keys).getPrivate();
} catch (IOException exception) {
exception.printStackTrace();
public PrivateKey call() throws IOException {
if (Security.getProvider("BC") == null && Security.getProvider("BCFIPS") == null) {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}
Object pemObject = new PEMParser(new InputStreamReader(keyInputStream)).readObject();
if (pemObject == null) {
throw new KubernetesClientException("Got null PEM object from EC key's input stream.");
} else if (pemObject instanceof PEMKeyPair) {
return new JcaPEMKeyConverter().getKeyPair((PEMKeyPair) pemObject).getPrivate();
} else if (pemObject instanceof PrivateKeyInfo) {
return BouncyCastleProvider.getPrivateKey((PrivateKeyInfo) pemObject);
} else {
throw new KubernetesClientException("Don't know what to do with a " + pemObject.getClass().getName());
}
return null;
}
}.call();
} catch (NoClassDefFoundError e) {
throw new KubernetesClientException(
"JcaPEMKeyConverter is provided by BouncyCastle, an optional dependency. To use support for EC Keys you must explicitly add this dependency to classpath.");
} catch (IOException e) {
throw new KubernetesClientException(e.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.fabric8.kubernetes.client.internal;

import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.utils.IOHelpers;
import io.fabric8.kubernetes.client.utils.Utils;
import org.junit.jupiter.api.AfterEach;
Expand All @@ -31,12 +32,14 @@
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import java.util.Collections;
import java.util.Objects;
import java.util.Properties;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
Expand Down Expand Up @@ -184,6 +187,48 @@ void getInputStreamFromDataOrFileShouldDecodeBase64EncodedString() throws IOExce
assertEquals(inputStr, certDataReadFromInputStream);
}

@Test
void loadECkeys()
throws InvalidKeySpecException, CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
String privateKeyPath = Utils.filePath(getClass().getResource("/ssl-test/fabric8-ec.paired.key"));
String certPath = Utils.filePath(getClass().getResource("/ssl-test/fabric8-ec.cert"));

KeyStore trustStore = CertUtils.createKeyStore(null, certPath, null, privateKeyPath, "EC", "foo", null, null);

assertEquals(1, trustStore.size());
}

@Test
void loadECPrivateOnlyKey()
throws InvalidKeySpecException, CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
String privateKeyPath = Utils.filePath(getClass().getResource("/ssl-test/fabric8-ec.private-only.key"));
String certPath = Utils.filePath(getClass().getResource("/ssl-test/fabric8-ec.cert"));

KeyStore trustStore = CertUtils.createKeyStore(null, certPath, null, privateKeyPath, "EC", "foo", null, null);

assertEquals(1, trustStore.size());
}

@Test
void loadNothingError() {
String privateKeyPath = Utils.filePath(getClass().getResource("/ssl-test/empty"));
String certPath = Utils.filePath(getClass().getResource("/ssl-test/empty"));

assertThatExceptionOfType(KubernetesClientException.class)
.isThrownBy(() -> CertUtils.createKeyStore(null, certPath, null, privateKeyPath, "EC", "foo", null, null))
.withMessage("Got null PEM object from EC key's input stream.");
}

@Test
void loadUnknownError() {
String privateKeyPath = Utils.filePath(getClass().getResource("/ssl-test/multiple-certs.p7b"));
String certPath = Utils.filePath(getClass().getResource("/ssl-test/multiple-certs.p7b"));

assertThatExceptionOfType(KubernetesClientException.class)
.isThrownBy(() -> CertUtils.createKeyStore(null, certPath, null, privateKeyPath, "EC", "foo", null, null))
.withMessageContaining("Don't know what to do with a");
}

@Test
void storeKeyFallbacksToDefault() throws Exception {
// When
Expand Down
Empty file.
18 changes: 18 additions & 0 deletions kubernetes-client-api/src/test/resources/ssl-test/fabric8-ec.cert
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC7TCCAdWgAwIBAgIUD0Q+lZUpqmvTUxmCbhsPx93hYI4wDQYJKoZIhvcNAQEL
BQAwczELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDVNhbiBGcmFuY2lzY28xCzAJBgNV
BAcTAkNBMRgwFgYDVQQKEw9NeSBDb21wYW55IE5hbWUxEzARBgNVBAsTCk9yZyBV
bml0IDIxEDAOBgNVBAMTB0t1YmUgQ0EwHhcNMTgwMjEzMDc1MzAwWhcNMjMwMjEy
MDc1MzAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UECBMNU2FuIEZyYW5jaXNjbzEL
MAkGA1UEBxMCQ0ExDjAMBgNVBAMTBWFkbWluMFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAEUHBg7OvKkSprAljQcCcpXFns/pMNDkQJZuooj97A0063ipBrZzbdxTcu
VcBjFNJC/Tn2keNSQP+m9QbQmQfmM6N1MHMwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
JQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFMBq0KJaRKoC
+a8R5la/B8tJahD3MB8GA1UdIwQYMBaAFOuZHF+ex0WLX3UX9LJoFHbI9cPNMA0G
CSqGSIb3DQEBCwUAA4IBAQC47D3zYMTg/C9gOYu20xEmw6eTEhJ1wWG9jSdHM9G8
0F3mD4+bG3skx5kaCgtJ3KqbMSGPJ234Ju9VHCNiyiasZS41a8wuagJ6v5pTItLL
BmQ3OhT2HJZz+lDbsb3jLDzesQ5UCct08/e8ST4hnZUcSrz2geD1hSYQEhadsI87
A+wVAvGfhCyiDUiClA2vwosWfomshkWphRzy+s5zFLuxlBAxj8g2oAbCJfhfJS5A
O8W5Nu+ddO5+7PB+y28Bue1GWABIjytmyLdvic0vkKzZkSzPOwqCT1ofZ9HU56h7
jKOjtBacCcl/7pLgvaA8Ng1qfjQTiveDVfI8rNWiEhRm
-----END CERTIFICATE-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIAPhomYs9rdnNgEtr2FIB1rBDYnuKqV4QVAYBX4yRqAEoAoGCCqGSM49
AwEHoUQDQgAEUHBg7OvKkSprAljQcCcpXFns/pMNDkQJZuooj97A0063ipBrZzbd
xTcuVcBjFNJC/Tn2keNSQP+m9QbQmQfmMw==
-----END EC PRIVATE KEY-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQglAqmnWCvaPD+4fCx
rIAuhPoZcToa25FT3pKinym+9w6hRANCAAQr1Kmy52b6tabokBe3M8gO8Vi9N+Bc
BkTWaRt9NijEmB9uJNsJ835nVmvQEdjffk5nErGMcp0ytPJak27040+K
-----END PRIVATE KEY-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
-----BEGIN PKCS7-----
MIIGKQYJKoZIhvcNAQcCoIIGGjCCBhYCAQExADALBgkqhkiG9w0BBwGgggX8MIIC
6jCCAdKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtvcGVuc2hp
ZnQtc2lnbmVyQDE0ODkwNTI0ODEwHhcNMTcwMzA5MDk0MTIxWhcNMjIwMzA4MDk0
MTIyWjAmMSQwIgYDVQQDDBtvcGVuc2hpZnQtc2lnbmVyQDE0ODkwNTI0ODEwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHvEtorBUHnUErPhKKbRrpEd/3
Kl1UeMMowkHNxq9FMHcm6Mczuu7I0O+qtKbuW7arAZyaoSLO2ezW1lLRuGsuMSJ7
qUqiRus+RUCQWDxLeZhu4OXvcebR3T+drzd35lOwkZBqoNysmT6Uqs7RVZ05+jKK
VJpKDya99Zbr/9zTsYH+gzSAg4LOyMs9fd2tZ75pMHVS0MWMAhXLAPtP/JDMBXOx
iBoFMymsnKeHKThztjoA7dzS1XQTq4cGcCJqyj3wdvey76HBaLzMfu9ZaoA10J20
eRnk7K4oQEK9jE1rBof4huFa5vvBumSv8Ds/yMzpOA0qOjXPcmJzx0xwQQunAgMB
AAGjIzAhMA4GA1UdDwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQA3hfVp3yf8k0dPr6p/H/xgDKxedWOUTbdfl3v+AWGgSUvDT30H
ruYqaC34FXCvoUIia4kxzZlDBDjMg9/9zOOCQdwQimgQM2tOujNUFR3HBEfgYLxt
Y106hxzJpLCO+HsYPX9rFOTAsvt2og/jflP10EHAodj40FSymL6HIV671kzbykoq
fM3Hp+fPfUmvldxZSkzCGJAlDylE8pF4NOMd7B0LKPJuQWM2wgFH1uWQqqq0gu6o
q6GoX+39CSlx5bfydd2D660+Xt0G4k2u6z4SFxqb1hKTpi6cGtB8TERa6sxbl/Kl
cfE/R05F+L34hITDjoKYSZUWe5r4weAdMtwpMIIDCjCCAfKgAwIBAgIBATANBgkq
hkiG9w0BAQsFADA2MTQwMgYDVQQDDCtvcGVuc2hpZnQtc2VydmljZS1zZXJ2aW5n
LXNpZ25lckAxNDg5MDUyNDgyMB4XDTE3MDMwOTA5NDEyMVoXDTIyMDMwODA5NDEy
MlowNjE0MDIGA1UEAwwrb3BlbnNoaWZ0LXNlcnZpY2Utc2VydmluZy1zaWduZXJA
MTQ4OTA1MjQ4MjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALKvcK+2
kKix24UR5U9en6IJ5yTYBS1ozLCTsKAKWrD4Ttpec3ON/SycoEfUXY6ao+pN2Q0Q
bH0/hi1+fFQgdHoQxo1yG4/SAwEu+n4uUse5ApCtNWLAv8jO+Y1o33CSIIM4Sm3P
uw7w/qzHAGmad6Nvg5v+ZOxdGNm7UU399zWzY6+igFo/rg8oacZVerchrnLE2cev
pjafguqy6kiuKFbfdIcYGfdmmypyC/SbpwgRABPWLBqk/AUfkNClKVi91Ysbaj5t
lFSXEu2i2z6aAhJCHddjGeq17cQybiYVLhqXPYXtdXH+khYfXY9cupqVftrXgV6c
/xWRzmRxDQyTqdMCAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB/wQF
MAMBAf8wDQYJKoZIhvcNAQELBQADggEBADA5X9iNqR8JmXbHCFMovs5PIOExK6uR
1xDBMmD6op0giDXB4ljnIa4R7euo1BCIOjC9uX6YT0OEXl31IXPAzapZNHzp2sW9
8pyxDXBd78KD/WEeWfeO4rwL4CMACgqUT9LwNCdL01s74NEruNzWIfx74Az1WtlU
8YqE7RroCpcBaqZgLJih9jcBdOVuZN5gK/xJm2P1QminD5Z+YzU2yeCFOnRzueA9
tFveiPVRk454bflfsW8dfixTNeU9MuG3PbtZ9nqR0hBKWtPst1tJwLTOdNcAijt1
T4r6qoZAmGJcEp/EQ5b0beARM1STqNeO3GPOOg1Ec33+jUHYySkQ3JGhADEA
-----END PKCS7-----

0 comments on commit 5c1dfae

Please sign in to comment.