Skip to content

Commit

Permalink
SIDM-8350: Changes required for ForgeRock AM 7.2 (#28)
Browse files Browse the repository at this point in the history
* SIDM-8350: Changes required for ForgeRock AM 7.2. Also - use Java 11.
  • Loading branch information
kremi authored Mar 29, 2023
1 parent 4c10816 commit ed650ac
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 47 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ plugins {
}

group = 'uk.gov.hmcts.reform.security.keyvault'
version = '1.3.7'
sourceCompatibility = 1.8
version = '1.4.0'
sourceCompatibility = 1.11

dependencies {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,14 @@ public boolean[] getSubjectUniqueID() {
}

/**
* @should call delegate
* @should return null
*/
@Override
public boolean[] getKeyUsage() {
return certificate.getKeyUsage();
// Remove Extended Key Usages (EKUs) flags as ForgeRock 7.x is now checking them and might
// refuse to use a certificate if it hasn't been properly configured in KeyVault.
// NO EKUs is equivalent to disabling the checks in AM.
return null;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@
import com.microsoft.azure.keyvault.models.SecretBundle;
import com.microsoft.azure.keyvault.webkey.JsonWebKey;
import com.microsoft.azure.keyvault.webkey.JsonWebKeyType;
import com.sun.crypto.provider.JceKeyStore;

import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
Expand All @@ -28,17 +26,18 @@
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import javax.crypto.spec.SecretKeySpec;

public final class KeyVaultKeyStore extends KeyStoreSpi {

private static final String SMS_TRANSPORT_KEY_DASHES = "sms-transport-key";

private static final String SMS_TRANSPORT_KEY_DOTS = "sms.transport.key";

private KeyVaultService vaultService;
private static final String DS_AME_USER_PWD = "dsameUserPwd";

private KeyStoreSpi localKeyStore = new JceKeyStore();
private static final String CONFIG_STORE_PWD = "configStorePwd";

private KeyVaultService vaultService;

/**
* @should return rsa private key for rsa alias
Expand Down Expand Up @@ -96,11 +95,11 @@ private Key getSmsTransportKey() {
}

/**
* @should throw exception
* @should return a single item array
*/
@Override
public Certificate[] engineGetCertificateChain(final String alias) {
throw new UnsupportedOperationException();
return new Certificate[] { engineGetCertificate(alias) };
}

/**
Expand Down Expand Up @@ -129,11 +128,19 @@ public Certificate engineGetCertificate(final String alias) {
}

/**
* @should return a date
* @should return a date for keys
* @should return a date for secrets
* @should return a date for certificates
*/
@Override
public Date engineGetCreationDate(final String alias) {
return localKeyStore.engineGetCreationDate(alias);
if (engineIsKeyEntry(alias)) {
return vaultService.getKeyByAlias(alias).attributes().created().toDate();
} else if (engineIsCertificateEntry(alias)) {
return vaultService.getCertificateByAlias(alias).attributes().created().toDate();
} else {
return vaultService.getSecretByAlias(alias).attributes().created().toDate();
}
}

/**
Expand All @@ -155,9 +162,16 @@ public void engineSetCertificateEntry(final String alias, final Certificate cert

/**
* @should Call Delegate
* @should never delete dsameUserPwd
* @should never delete configStorePwd
*/
@Override
public void engineDeleteEntry(final String alias) {
if (DS_AME_USER_PWD.equalsIgnoreCase(alias) || CONFIG_STORE_PWD.equalsIgnoreCase(alias)) {
// Do not let AM delete "dsameUserPwd" and "configStorePwd" secrets in KeyVault
// as subsequent boots will fail without any of them.
return;
}
vaultService.deleteSecretByAlias(alias);
}

Expand Down Expand Up @@ -247,13 +261,8 @@ public String engineGetCertificateAlias(final Certificate cert) {
throw new UnsupportedOperationException();
}

/**
* @should try engine store the stream
*/
@Override
public void engineStore(final OutputStream stream, final char[] password)
throws IOException, NoSuchAlgorithmException, CertificateException {
localKeyStore.engineStore(stream, password);
public void engineStore(final OutputStream stream, final char[] password) {
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.math.BigInteger;
import java.security.interfaces.RSAPrivateKey;
import java.util.Random;

final class KeyVaultRSAPrivateKey extends KeyVaultKey implements RSAPrivateKey {

Expand Down Expand Up @@ -34,10 +35,16 @@ public String getFormat() {
}

/**
* @should throw exception
* @should return a 2048-bit integer
*/
@Override
public BigInteger getModulus() {
throw new UnsupportedOperationException();
// AM 7.x disallows the use of keys with length < 2048 bits.
//
// As KeyVault does not give us the real private key, we are using a
// dummy private key modulus to work around the length checks.
return DUMMY_2048bit_MODULUS;
}

private static final BigInteger DUMMY_2048bit_MODULUS = BigInteger.probablePrime(2048, new Random());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uk.gov.hmcts.reform.security.keyvault.KeyVaultProvider
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.security.cert.X509Certificate;
import java.util.Date;

import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.verify;

@RunWith(MockitoJUnitRunner.class)
Expand Down Expand Up @@ -202,13 +203,12 @@ public void getSubjectUniqueID_shouldCallDelegate() {
}

/**
* @verifies call delegate
* @verifies return null
* @see KeyVaultCertificate#getKeyUsage()
*/
@Test
public void getKeyUsage_shouldCallDelegate() {
vaultCertificate.getKeyUsage();
verify(certificate).getKeyUsage();
public void getKeyUsage_shouldReturnNull() {
assertNull(vaultCertificate.getKeyUsage());
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package uk.gov.hmcts.reform.security.keyvault;

import com.google.common.collect.Lists;
import com.microsoft.azure.keyvault.models.CertificateAttributes;
import com.microsoft.azure.keyvault.models.CertificateBundle;
import com.microsoft.azure.keyvault.models.KeyAttributes;
import com.microsoft.azure.keyvault.models.KeyBundle;
import com.microsoft.azure.keyvault.models.SecretAttributes;
import com.microsoft.azure.keyvault.models.SecretBundle;
import com.microsoft.azure.keyvault.webkey.JsonWebKey;
import com.microsoft.azure.keyvault.webkey.JsonWebKeyType;
import org.joda.time.DateTime;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
Expand Down Expand Up @@ -40,6 +45,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

@RunWith(MockitoJUnitRunner.class)
Expand Down Expand Up @@ -178,21 +184,61 @@ public void engineGetCertificate_shouldReturnNullWhenVaultDoesNotContainTheAlias
}

/**
* @verifies throw exception
* @verifies return a single item array
* @see KeyVaultKeyStore#engineGetCertificateChain(String)
*/
@Test(expected = UnsupportedOperationException.class)
public void engineGetCertificateChain_shouldThrowException() {
keyStore.engineGetCertificateChain(ALIAS);
@Test
public void engineGetCertificateChain_shouldReturnASingleItemArray() {
CertificateBundle certificateBundle = mock(CertificateBundle.class);
given(vaultService.getCertificateByAlias(ALIAS)).willReturn(certificateBundle);
given(certificateBundle.cer()).willReturn(Base64.getDecoder().decode(DUMMY_CERT_BASE_64));

Certificate certificate = keyStore.engineGetCertificate(ALIAS);

assertEquals(1, keyStore.engineGetCertificateChain(ALIAS).length);
assertEquals(certificate, keyStore.engineGetCertificateChain(ALIAS)[0]);
}

/**
* @verifies return a date for keys
* @see KeyVaultKeyStore#engineGetCreationDate(String)
*/
@Test
public void engineGetCreationDate_shouldReturnADateForKeys() {
KeyBundle keyBundle = mock(KeyBundle.class);
KeyAttributes keyAttributes = mock(KeyAttributes.class);
given(vaultService.engineKeyAliases()).willReturn(Lists.newArrayList(ALIAS));
given(keyBundle.attributes()).willReturn(keyAttributes);
given(keyAttributes.created()).willReturn(DateTime.now());
given(vaultService.getKeyByAlias(ALIAS)).willReturn(keyBundle);
assertNotNull(keyStore.engineGetCreationDate(ALIAS));
}

/**
* @verifies return a date for secrets
* @see KeyVaultKeyStore#engineGetCreationDate(String)
*/
@Test
public void engineGetCreationDate_shouldReturnADateForSecrets() {
SecretBundle secretBundle = mock(SecretBundle.class);
SecretAttributes secretAttributes = mock(SecretAttributes.class);
given(secretBundle.attributes()).willReturn(secretAttributes);
given(secretAttributes.created()).willReturn(DateTime.now());
given(vaultService.getSecretByAlias(ALIAS)).willReturn(secretBundle);
assertNotNull(keyStore.engineGetCreationDate(ALIAS));
}

/**
* @verifies return a date
* @verifies return a date for certificates
* @see KeyVaultKeyStore#engineGetCreationDate(String)
*/
@Test
public void engineGetCreationDate_shouldReturnADate() {
given(localKeyStore.engineGetCreationDate(any())).willReturn(new Date());
public void engineGetCreationDate_shouldReturnADateForCertificates() {
CertificateBundle certificateBundle = mock(CertificateBundle.class);
CertificateAttributes certificateAttributes = mock(CertificateAttributes.class);
given(certificateBundle.attributes()).willReturn(certificateAttributes);
given(certificateAttributes.created()).willReturn(DateTime.now());
given(vaultService.getCertificateByAlias(ALIAS)).willReturn(certificateBundle);
assertNotNull(keyStore.engineGetCreationDate(ALIAS));
}

Expand Down Expand Up @@ -312,6 +358,26 @@ public void engineDeleteEntry_shouldCallDelegate() throws KeyStoreException {
verify(vaultService).deleteSecretByAlias(ALIAS);
}

/**
* @verifies never delete dsameUserPwdd
* @see KeyVaultKeyStore#engineDeleteEntry(String)
*/
@Test
public void engineDeleteEntry_shouldNeverDeleteDsameUserPwd() throws KeyStoreException {
keyStore.engineDeleteEntry("dsameuserpwd");
verify(vaultService, never()).deleteSecretByAlias("dsameuserpwd");
}

/**
* @verifies never delete configStorePwd
* @see KeyVaultKeyStore#engineDeleteEntry(String)
*/
@Test
public void engineDeleteEntry_shouldNeverDeleteConfigStorePwd() throws KeyStoreException {
keyStore.engineDeleteEntry("configstorepwd");
verify(vaultService, never()).deleteSecretByAlias("configstorepwd");
}

/**
* @verifies return true if certificate is in keyvault
* @see KeyVaultKeyStore#engineIsCertificateEntry(String)
Expand Down Expand Up @@ -427,16 +493,6 @@ public void engineGetKey_shouldTrySaveSecretKeysInLocalStoreToKeyVault() throws
assertNull(keyStore.engineGetKey("A_KEY", "A_PASSWORD".toCharArray()));
}

/**
* @verifies try engine store the stream
* @see KeyVaultKeyStore#engineStore(java.io.OutputStream, char[])
*/
@Test
public void engineStore_shouldTryEngineStoreTheStream() throws Exception {
keyStore.engineStore(mock(OutputStream.class), new char[0]);
verify(localKeyStore).engineStore(any(), any());
}

/**
* @verifies return the engine size
* @see KeyVaultKeyStore#engineSize()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class KeyVaultRSAPrivateKeyTest {

private static final KeyVaultRSAPrivateKey PRIVATE_KEY = new KeyVaultRSAPrivateKey("id", "alg");

/**
* @verifies throw exception
* @verifies return a 2048-bit integer
* @see KeyVaultRSAPrivateKey#getModulus()
*/
@Test(expected = UnsupportedOperationException.class)
public void getModulus_shouldThrowException() {
PRIVATE_KEY.getModulus();
@Test
public void getModulus_shouldReturn2048bitInteger() {
assertEquals(2048, PRIVATE_KEY.getModulus().bitLength());
}

/**
Expand Down

0 comments on commit ed650ac

Please sign in to comment.