io.r2.simplepemkeystore.spi.PemCertKey Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of simple-pem-keystore Show documentation
Show all versions of simple-pem-keystore Show documentation
A java keystore implementation to use PEM files directly.
package io.r2.simplepemkeystore.spi;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* A certificate chain and private key read from PEM
* Note: intentionally defined as package access only
*/
class PemCertKey {
protected Date creationDate;
protected Key privateKey;
protected List certificateChain;
protected Certificate[] certificateChainPacked;
/**
* Reads a new certificate chain and key from an input stream
* Parses input to get key and certificates.
* CreationDate is set to today
*
* @param input the input stream
* @throws IOException in case if input error
* @throws CertificateException if loading is failed
* @throws NoSuchAlgorithmException if algorithms not available (now only RSA is used)
*/
public PemCertKey(InputStream input) throws IOException, CertificateException, NoSuchAlgorithmException {
this(input, new Date());
}
/**
* Reads a new certificate chain and key from an input stream
* Parses input to get key and certificates.
* Creation Data is set to the specified time (millis from epoch)
*
* @param input the input stream
* @param creationDate the creation date of this entry (for file based certificates, the file modification time)
* @throws IOException in case if input error
* @throws CertificateException if loading is failed
* @throws NoSuchAlgorithmException if algorithms not available (now only RSA is used)
*/
public PemCertKey(InputStream input, Date creationDate) throws IOException, CertificateException, NoSuchAlgorithmException {
this.creationDate = creationDate;
privateKey = null;
certificateChain = new ArrayList<>();
PemStreamParser.parse(input, (type, chunk) -> {
switch (type) {
case certificate:
addCertificate(chunk);
break;
case key:
setPrivateKey(chunk);
break;
}
});
// put to packed structure
certificateChainPacked = certificateChain.toArray(new Certificate[certificateChain.size()]);
}
/**
* Internal method used during parsing : sets the private key in this entry
*
* @param key the chunk containing certificate
* @throws CertificateException if key already exists
*/
private void setPrivateKey(List key) throws CertificateException, NoSuchAlgorithmException {
if (privateKey != null) throw new CertificateException("More than one private key in PEM input");
String b64key = key.subList(1, key.size()-1).stream().collect(Collectors.joining());
byte[] binKey = Base64.getDecoder().decode(b64key);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(binKey);
KeyFactory kf = KeyFactory.getInstance("RSA");
try {
privateKey = kf.generatePrivate(keySpec);
}
catch (InvalidKeySpecException e) {
throw new CertificateException(e);
}
}
/**
* Add a new certificate to the chain
* @param chunk the chunk containing certificate
*/
private void addCertificate(List chunk) throws CertificateException {
InputStream is = new ByteArrayInputStream(
chunk.stream().collect(Collectors.joining("\n")).getBytes(StandardCharsets.UTF_8)
);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
certificateChain.add(cf.generateCertificate(is));
}
/**
* Creation date is unknown in this store, so return object creation date
* @return creation date
*/
public Date getCreationDate() {
return creationDate;
}
/**
* Gets the private key - private keys are not password protected
*
* @return the private key
* @throws UnrecoverableKeyException if password is incorrect
*/
public Key getPrivateKey() throws UnrecoverableKeyException {
return privateKey;
}
/**
* @return certificate chain
*/
public Certificate[] getCertificateChain() {
return certificateChainPacked;
}
/**
* @return the certificate or null if not found in input
*/
public Certificate getCertificate() {
return certificateChainPacked.length > 0 ? certificateChainPacked[0] : null;
}
/**
* @return true if input has a key
*/
public boolean hasKey() {
return privateKey != null;
}
/**
* @return true if input has a certificate
*/
public boolean hasCertificate() {
return certificateChainPacked.length > 0;
}
/**
* @return true if parameter certificate matches this one
*/
public boolean matchesCertificate(Certificate other) {
if (certificateChainPacked.length == 0) return false;
return certificateChainPacked[0].equals(other);
}
}