Skip to content

Commit ee7380a

Browse files
Syed Ahmedmurali-reddy
authored andcommitted
CLOUDSTACK-5296: Add certificate chain support for netscaler
This patch adds support for trust chains in the netscaler. I initially planned on using the 10.1 API's "bundle" feature but during my testing I found that was not working. So I am doing the chain linking myself. Also NS can have only one entity of a certificate ie lets say two different users try to add the same certificate on the netscaler only one of them will go through. The other one says resouce already exists even though they have different files. This can be a problem in trust chains where the chain can be shared between multiple accounts/certificates. So, I am using the figerprint as an identifier of a certificate and making sure that we delete it only when no one references it.
1 parent 04b48ae commit ee7380a

7 files changed

Lines changed: 272 additions & 50 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
G# Licensed to the Apache Software Foundation (ASF) under one
1+
# Licensed to the Apache Software Foundation (ASF) under one
22
# or more contributor license agreements. See the NOTICE file
33
# distributed with this work for additional information
44
# regarding copyright ownership. The ASF licenses this file

api/src/com/cloud/network/lb/LoadBalancingRule.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,13 +437,15 @@ public static class LbSslCert {
437437
private String key;
438438
private String password = null;
439439
private String chain = null;
440+
private String fingerprint;
440441
private boolean revoked;
441442

442-
public LbSslCert(String cert, String key, String password, String chain, boolean revoked) {
443+
public LbSslCert(String cert, String key, String password, String chain, String fingerprint, boolean revoked) {
443444
this.cert = cert;
444445
this.key = key;
445446
this.password = password;
446447
this.chain = chain;
448+
this.fingerprint = fingerprint;
447449
this.revoked = revoked;
448450
}
449451

@@ -464,6 +466,10 @@ public String getChain() {
464466
return chain;
465467
}
466468

469+
public String getFingerprint() {
470+
return fingerprint;
471+
}
472+
467473
public boolean isRevoked() {
468474
return revoked;
469475
}

engine/schema/src/com/cloud/network/dao/SslCertDaoImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public SslCertDaoImpl() {
3333
listByAccountId = createSearchBuilder();
3434
listByAccountId.and("accountId", listByAccountId.entity().getAccountId(), SearchCriteria.Op.EQ);
3535
listByAccountId.done();
36-
}
36+
}
3737

3838
@Override
3939
public List<SslCertVO> listByAccountId(Long accountId) {

plugins/network-elements/netscaler/src/com/cloud/network/resource/NetscalerResource.java

Lines changed: 200 additions & 40 deletions
Large diffs are not rendered by default.

server/src/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,7 +1074,7 @@ public LbSslCert getLbSslCert(long lbRuleId) {
10741074
return null;
10751075
}
10761076

1077-
return new LbSslCert(certVO.getCertificate(), certVO.getKey(), certVO.getChain(), certVO.getPassword(), lbCertMap.isRevoke());
1077+
return new LbSslCert(certVO.getCertificate(), certVO.getKey(), certVO.getPassword(), certVO.getChain(), certVO.getFingerPrint(), lbCertMap.isRevoke());
10781078
}
10791079

10801080
@Override
@@ -1124,11 +1124,6 @@ public boolean assignCertToLoadBalancer(long lbRuleId, Long certId) {
11241124
LoadBalancerCertMapVO certMap = new LoadBalancerCertMapVO(lbRuleId, certId, false);
11251125
_lbCertMapDao.persist(certMap);
11261126
applyLoadBalancerConfig(loadBalancer.getId());
1127-
/*s_logger.warn("Failed to apply Ssl Cert to LB " + loadBalancer.getId());
1128-
CloudRuntimeException ex = new CloudRuntimeException(
1129-
"Failed to apply Ssl Cert to LB " + loadBalancer.getId());
1130-
ex.addProxyObject(loadBalancer.getUuid(), "loadBalancerId");
1131-
throw ex;*/
11321127
success = true;
11331128
} catch (ResourceUnavailableException e) {
11341129
if (isRollBackAllowedForProvider(loadBalancer)) {

server/src/org/apache/cloudstack/network/lb/CertServiceImpl.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.security.KeyPair;
2626
import java.security.MessageDigest;
2727
import java.security.NoSuchAlgorithmException;
28+
import java.security.NoSuchProviderException;
2829
import java.security.Principal;
2930
import java.security.PrivateKey;
3031
import java.security.PublicKey;
@@ -115,6 +116,7 @@ public SslCertResponse uploadSslCert(UploadSslCertCmd certCmd) {
115116

116117
validate(cert, key, password, chain);
117118
s_logger.debug("Certificate Validation succeeded");
119+
118120
String fingerPrint = generateFingerPrint(parseCertificate(cert));
119121

120122
Long accountId = CallContext.current().getCallingAccount().getId();
@@ -379,14 +381,17 @@ private void validateChain(List<Certificate> chain, Certificate cert) {
379381
params = new PKIXBuilderParameters(anchors, target);
380382
params.setRevocationEnabled(false);
381383
params.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certs)));
382-
CertPathBuilder builder = CertPathBuilder.getInstance("PKIX");
384+
CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", "BC");
383385
builder.build(params);
386+
384387
} catch (InvalidAlgorithmParameterException e) {
385388
throw new IllegalArgumentException("Invalid certificate chain", e);
386389
} catch (CertPathBuilderException e) {
387390
throw new IllegalArgumentException("Invalid certificate chain", e);
388391
} catch (NoSuchAlgorithmException e) {
389392
throw new IllegalArgumentException("Invalid certificate chain", e);
393+
} catch (NoSuchProviderException e) {
394+
throw new CloudRuntimeException("No provider for certificate validation", e);
390395
}
391396

392397
}

utils/src/com/cloud/utils/security/CertificateHelper.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,28 @@
2020
import java.io.ByteArrayInputStream;
2121
import java.io.ByteArrayOutputStream;
2222
import java.io.IOException;
23+
import java.io.StringReader;
2324
import java.security.Key;
2425
import java.security.KeyFactory;
2526
import java.security.KeyStore;
2627
import java.security.KeyStoreException;
28+
import java.security.MessageDigest;
2729
import java.security.NoSuchAlgorithmException;
2830
import java.security.cert.Certificate;
31+
import java.security.cert.CertificateEncodingException;
2932
import java.security.cert.CertificateException;
3033
import java.security.cert.CertificateFactory;
34+
import java.security.cert.X509Certificate;
3135
import java.security.spec.InvalidKeySpecException;
3236
import java.security.spec.PKCS8EncodedKeySpec;
37+
import java.util.ArrayList;
3338
import java.util.List;
3439

40+
import com.cloud.utils.exception.CloudRuntimeException;
3541
import org.apache.commons.codec.binary.Base64;
3642

3743
import com.cloud.utils.Ternary;
44+
import org.bouncycastle.openssl.PEMReader;
3845

3946
public class CertificateHelper {
4047
public static byte[] buildAndSaveKeystore(String alias, String cert, String privateKey, String storePassword) throws KeyStoreException, CertificateException,
@@ -106,4 +113,53 @@ public static Key buildPrivateKey(String base64EncodedKeyContent) throws NoSuchA
106113
PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec(Base64.decodeBase64(base64EncodedKeyContent));
107114
return kf.generatePrivate(keysp);
108115
}
116+
117+
public static List<Certificate> parseChain(String chain) throws IOException {
118+
119+
List<Certificate> certs = new ArrayList<Certificate>();
120+
PEMReader reader = new PEMReader(new StringReader(chain));
121+
122+
Certificate crt = null;
123+
124+
while ((crt = (Certificate)reader.readObject()) != null) {
125+
if (crt instanceof X509Certificate) {
126+
certs.add(crt);
127+
}
128+
}
129+
if (certs.size() == 0)
130+
throw new IllegalArgumentException("Unable to decode certificate chain");
131+
132+
return certs;
133+
}
134+
135+
public static String generateFingerPrint(Certificate cert) {
136+
137+
final char[] HEX = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
138+
139+
StringBuilder buffer = new StringBuilder(60);
140+
try {
141+
142+
MessageDigest md = MessageDigest.getInstance("SHA-1");
143+
byte[] data = md.digest(cert.getEncoded());
144+
145+
for (int i = 0; i < data.length; i++) {
146+
if (buffer.length() > 0) {
147+
buffer.append(":");
148+
}
149+
150+
buffer.append(HEX[(0xF0 & data[i]) >>> 4]);
151+
buffer.append(HEX[0x0F & data[i]]);
152+
}
153+
154+
} catch (CertificateEncodingException e) {
155+
throw new CloudRuntimeException("Bad certificate encoding");
156+
} catch (NoSuchAlgorithmException e) {
157+
throw new CloudRuntimeException("Bad certificate algorithm");
158+
}
159+
160+
return buffer.toString();
161+
}
162+
163+
164+
109165
}

0 commit comments

Comments
 (0)