Added Groovy support for unit tests to pom with skeleton test.
Added Groovy unit tests for OCSPCertificateValidator.
Implemented positive & negative unit tests with cache injection for valid/revoked OCSP certificate.
Modified pom.xml to support Groovy unit tests with custom variable.

mvn clean test -Dgroovy=test

Added local cache injection into Groovy tests for OCSP certificate validation (see NIFI-1324 and NIFI-1364).
Set Java version to 1.7 for Groovy test src/target.
Moved Groovy unit test profile from nifi-web-security to root pom.
Added null check for algorithm argument in PGPUtil.
Changed buffer length check from ">= 0" to "> -1" because it was confusing other developers.
Resolved contrib-check line length issues.
Fixed contrib-check issues in OpenPGPKeyBasedEncryptorTest.
This closes #163

Signed-off-by: Matt Gilman <matt.c.gilman@gmail.com>
This commit is contained in:
Andy LoPresto 2016-01-26 17:22:33 -05:00 коммит произвёл Matt Gilman
Родитель 0d45f21f4b
Коммит 93aac8cff3
4 изменённых файлов: 459 добавлений и 6 удалений

Просмотреть файл

@ -16,4 +16,4 @@ before_install:
- sed -e "s/^\\(127\\.0\\.0\\.1.*\\)/\\1 $(hostname | cut -c1-63)/" /etc/hosts | sudo tee /etc/hosts
- sed -i.bak -e 's|https://nexus.codehaus.org/snapshots/|https://oss.sonatype.org/content/repositories/codehaus-snapshots/|g' ~/.m2/settings.xml
script: mvn clean install -Pcontrib-check
script: mvn clean install -Pcontrib-check,groovy-unit-test

Просмотреть файл

@ -0,0 +1,364 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.web.security.x509.ocsp
import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader
import com.google.common.cache.LoadingCache
import com.sun.jersey.api.client.Client
import org.apache.nifi.util.NiFiProperties
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.*
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.ContentSigner
import org.bouncycastle.operator.OperatorCreationException
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import org.junit.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.security.*
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import static groovy.test.GroovyAssert.shouldFail
import static org.junit.Assert.fail
public class OcspCertificateValidatorGroovyTest {
private static final Logger logger = LoggerFactory.getLogger(OcspCertificateValidatorGroovyTest.class);
private static final int KEY_SIZE = 2048;
private static final long YESTERDAY = System.currentTimeMillis() - 24 * 60 * 60 * 1000;
private static final long ONE_YEAR_FROM_NOW = System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000;
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
private static final String PROVIDER = "BC";
private static final String SUBJECT_DN = "CN=NiFi Test Server,OU=Security,O=Apache,ST=CA,C=US";
private static final String ISSUER_DN = "CN=NiFi Test CA,OU=Security,O=Apache,ST=CA,C=US";
private NiFiProperties mockProperties
// System under test
OcspCertificateValidator certificateValidator
@BeforeClass
public static void setUpOnce() throws Exception {
Security.addProvider(new BouncyCastleProvider());
}
@Before
public void setUp() throws Exception {
mockProperties = [getProperty: { String propertyName -> return "value_for_${propertyName}" }] as NiFiProperties
}
@After
public void tearDown() throws Exception {
certificateValidator?.metaClass = null
}
/**
* Generates a public/private RSA keypair using the default key size.
*
* @return the keypair
* @throws NoSuchAlgorithmException if the RSA algorithm is not available
*/
private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
/**
* Generates a signed certificate using an on-demand keypair.
*
* @param dn the DN
* @return the certificate
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws CertificateException
* @throws NoSuchProviderException
* @throws SignatureException
* @throws InvalidKeyException
* @throws OperatorCreationException
*/
private
static X509Certificate generateCertificate(String dn) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
KeyPair keyPair = generateKeyPair();
return generateCertificate(dn, keyPair);
}
/**
* Generates a signed certificate with a specific keypair.
*
* @param dn the DN
* @param keyPair the public key will be included in the certificate and the the private key is used to sign the certificate
* @return the certificate
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws CertificateException
* @throws NoSuchProviderException
* @throws SignatureException
* @throws InvalidKeyException
* @throws OperatorCreationException
*/
private
static X509Certificate generateCertificate(String dn, KeyPair keyPair) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
PrivateKey privateKey = keyPair.getPrivate();
ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(privateKey);
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
Date startDate = new Date(YESTERDAY);
Date endDate = new Date(ONE_YEAR_FROM_NOW);
X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
new X500Name(dn),
BigInteger.valueOf(System.currentTimeMillis()),
startDate, endDate,
new X500Name(dn),
subPubKeyInfo);
// Set certificate extensions
// (1) digitalSignature extension
certBuilder.addExtension(X509Extension.keyUsage, true,
new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.keyAgreement));
// (2) extendedKeyUsage extension
Vector<KeyPurposeId> ekUsages = new Vector<>();
ekUsages.add(KeyPurposeId.id_kp_clientAuth);
ekUsages.add(KeyPurposeId.id_kp_serverAuth);
certBuilder.addExtension(X509Extension.extendedKeyUsage, false, new ExtendedKeyUsage(ekUsages));
// Sign the certificate
X509CertificateHolder certificateHolder = certBuilder.build(sigGen);
return new JcaX509CertificateConverter().setProvider(PROVIDER)
.getCertificate(certificateHolder);
}
/**
* Generates a certificate signed by the issuer key.
*
* @param dn the subject DN
* @param issuerDn the issuer DN
* @param issuerKey the issuer private key
* @return the certificate
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws CertificateException
* @throws NoSuchProviderException
* @throws SignatureException
* @throws InvalidKeyException
* @throws OperatorCreationException
*/
private
static X509Certificate generateIssuedCertificate(String dn, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
KeyPair keyPair = generateKeyPair();
return generateIssuedCertificate(dn, keyPair.getPublic(), issuerDn, issuerKey);
}
/**
* Generates a certificate with a specific public key signed by the issuer key.
*
* @param dn the subject DN
* @param publicKey the subject public key
* @param issuerDn the issuer DN
* @param issuerKey the issuer private key
* @return the certificate
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws CertificateException
* @throws NoSuchProviderException
* @throws SignatureException
* @throws InvalidKeyException
* @throws OperatorCreationException
*/
private
static X509Certificate generateIssuedCertificate(String dn, PublicKey publicKey, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(issuerKey);
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
Date startDate = new Date(YESTERDAY);
Date endDate = new Date(ONE_YEAR_FROM_NOW);
X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(
new X500Name(issuerDn),
BigInteger.valueOf(System.currentTimeMillis()),
startDate, endDate,
new X500Name(dn),
subPubKeyInfo);
X509CertificateHolder certificateHolder = v3CertGen.build(sigGen);
return new JcaX509CertificateConverter().setProvider(PROVIDER)
.getCertificate(certificateHolder);
}
private static X509Certificate[] generateCertificateChain(String dn = SUBJECT_DN, String issuerDn = ISSUER_DN) {
final KeyPair issuerKeyPair = generateKeyPair();
final PrivateKey issuerPrivateKey = issuerKeyPair.getPrivate();
final X509Certificate issuerCertificate = generateCertificate(issuerDn, issuerKeyPair);
final X509Certificate certificate = generateIssuedCertificate(dn, issuerDn, issuerPrivateKey);
[certificate, issuerCertificate] as X509Certificate[]
}
@Test
public void testShouldGenerateCertificate() throws Exception {
// Arrange
final String testDn = "CN=This is a test";
// Act
X509Certificate certificate = generateCertificate(testDn);
logger.info("Generated certificate: \n{}", certificate);
// Assert
assert certificate.getSubjectDN().getName().equals(testDn);
assert certificate.getIssuerDN().getName().equals(testDn);
certificate.verify(certificate.getPublicKey());
}
@Test
public void testShouldGenerateCertificateFromKeyPair() throws Exception {
// Arrange
final String testDn = "CN=This is a test";
final KeyPair keyPair = generateKeyPair();
// Act
X509Certificate certificate = generateCertificate(testDn, keyPair);
logger.info("Generated certificate: \n{}", certificate);
// Assert
assert certificate.getPublicKey().equals(keyPair.getPublic());
assert certificate.getSubjectDN().getName().equals(testDn);
assert certificate.getIssuerDN().getName().equals(testDn);
certificate.verify(certificate.getPublicKey());
}
@Test
public void testShouldGenerateIssuedCertificate() throws Exception {
// Arrange
final String testDn = "CN=This is a signed test";
final String issuerDn = "CN=Issuer CA";
final KeyPair issuerKeyPair = generateKeyPair();
final PrivateKey issuerPrivateKey = issuerKeyPair.getPrivate();
final X509Certificate issuerCertificate = generateCertificate(issuerDn, issuerKeyPair);
logger.info("Generated issuer certificate: \n{}", issuerCertificate);
// Act
X509Certificate certificate = generateIssuedCertificate(testDn, issuerDn, issuerPrivateKey);
logger.info("Generated signed certificate: \n{}", certificate);
// Assert
assert issuerCertificate.getPublicKey().equals(issuerKeyPair.getPublic());
assert certificate.getSubjectX500Principal().getName().equals(testDn);
assert certificate.getIssuerX500Principal().getName().equals(issuerDn);
certificate.verify(issuerCertificate.getPublicKey());
try {
certificate.verify(certificate.getPublicKey());
fail("Should have thrown exception");
} catch (Exception e) {
assert e instanceof SignatureException;
assert e.getMessage().contains("certificate does not verify with supplied key");
}
}
@Test
public void testShouldValidateCertificate() throws Exception {
// Arrange
X509Certificate[] certificateChain = generateCertificateChain();
certificateValidator = new OcspCertificateValidator(mockProperties)
// Must populate the client even though it is not used in this check
certificateValidator.client = new Client()
// Form a map of the request to a good status and load it into the cache
OcspRequest revokedRequest = new OcspRequest(certificateChain.first(), certificateChain.last())
OcspStatus revokedStatus = new OcspStatus()
revokedStatus.responseStatus = OcspStatus.ResponseStatus.Successful
revokedStatus.validationStatus = OcspStatus.ValidationStatus.Good
revokedStatus.verificationStatus = OcspStatus.VerificationStatus.Verified
LoadingCache<OcspRequest, OcspStatus> cacheWithRevokedCertificate = buildCacheWithContents([(revokedRequest): revokedStatus])
certificateValidator.ocspCache = cacheWithRevokedCertificate
// Act
certificateValidator.validate(certificateChain)
// Assert
assert true
}
// TODO - NIFI-1364
@Ignore("To be implemented with Groovy test")
@Test
public void testShouldNotValidateEmptyCertificate() throws Exception {
}
@Test
public void testShouldNotValidateRevokedCertificate() throws Exception {
// Arrange
X509Certificate[] certificateChain = generateCertificateChain();
certificateValidator = new OcspCertificateValidator(mockProperties)
// Must populate the client even though it is not used in this check
certificateValidator.client = new Client()
// Form a map of the request to a revoked status and load it into the cache
OcspRequest revokedRequest = new OcspRequest(certificateChain.first(), certificateChain.last())
OcspStatus revokedStatus = new OcspStatus()
revokedStatus.responseStatus = OcspStatus.ResponseStatus.Successful
revokedStatus.validationStatus = OcspStatus.ValidationStatus.Revoked
revokedStatus.verificationStatus = OcspStatus.VerificationStatus.Verified
LoadingCache<OcspRequest, OcspStatus> cacheWithRevokedCertificate = buildCacheWithContents([(revokedRequest): revokedStatus])
certificateValidator.ocspCache = cacheWithRevokedCertificate
// Act
def msg = shouldFail(CertificateStatusException) {
certificateValidator.validate(certificateChain)
}
// Assert
assert msg =~ "is revoked according to the certificate authority"
}
LoadingCache<OcspRequest, OcspStatus> buildCacheWithContents(Map map) {
CacheBuilder.newBuilder().build(new CacheLoader<OcspRequest, OcspStatus>() {
@Override
public OcspStatus load(OcspRequest ocspRequest) throws Exception {
logger.info("Mock cache implementation load(${ocspRequest}) returns ${map.get(ocspRequest)}")
return map.get(ocspRequest) as OcspStatus
}
});
}
// TODO - NIFI-1364
@Ignore("To be implemented with Groovy test")
@Test
public void testValidateShouldHandleUnsignedResponse() throws Exception {
}
// TODO - NIFI-1364
@Ignore("To be implemented with Groovy test")
@Test
public void testValidateShouldHandleResponseWithIncorrectNonce() throws Exception {
}
}

Просмотреть файл

@ -16,6 +16,7 @@
*/
package org.apache.nifi.processors.standard.util;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.processors.standard.EncryptContent;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPCompressedData;
@ -46,9 +47,11 @@ public class PGPUtil {
public static final int BUFFER_SIZE = 65536;
public static final int BLOCK_SIZE = 4096;
public static void encrypt(InputStream in, OutputStream out, String algorithm, String provider, int cipher, String filename,
PGPKeyEncryptionMethodGenerator encryptionMethodGenerator) throws IOException, PGPException {
public static void encrypt(InputStream in, OutputStream out, String algorithm, String provider, int cipher, String filename, PGPKeyEncryptionMethodGenerator encryptionMethodGenerator) throws
IOException, PGPException {
if (StringUtils.isEmpty(algorithm)) {
throw new IllegalArgumentException("The algorithm must be specified");
}
final boolean isArmored = EncryptContent.isPGPArmoredAlgorithm(algorithm);
OutputStream output = out;
if (isArmored) {
@ -76,7 +79,7 @@ public class PGPUtil {
final byte[] buffer = new byte[BLOCK_SIZE];
int len;
while ((len = in.read(buffer)) >= 0) {
while ((len = in.read(buffer)) > -1) {
literalOut.write(buffer, 0, len);
}
}

88
pom.xml
Просмотреть файл

@ -125,7 +125,7 @@ language governing permissions and limitations under the License. -->
</repository>
<repository>
<id>jcenter</id>
<url>http://jcenter.bintray.com </url>
<url>http://jcenter.bintray.com</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
@ -1491,5 +1491,91 @@ language governing permissions and limitations under the License. -->
</pluginManagement>
</build>
</profile>
<profile>
<!-- Custom profile for Groovy tests -->
<id>groovy-unit-test</id>
<activation>
<property>
<name>groovy</name>
<value>test</value>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>src/main/groovy</source>
</sources>
</configuration>
</execution>
<execution>
<id>add-test-source</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/test/groovy</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<compilerId>groovy-eclipse-compiler</compilerId>
<source>1.7</source>
<target>1.7</target>
<includes>
<include>**/*.java</include>
<include>**/*.groovy</include>
</includes>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-compiler</artifactId>
<version>2.9.2-01</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-batch</artifactId>
<version>2.4.3-01</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-compiler</artifactId>
<version>2.9.2-01</version>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>