/**
* Copyright 2009 Google Inc.
*
* Licensed 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.waveprotocol.wave.crypto;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.protobuf.ByteString;
import org.waveprotocol.wave.federation.Proto.ProtocolSignerInfo;
import org.waveprotocol.wave.federation.Proto.ProtocolSignerInfo.HashAlgorithm;
import java.io.ByteArrayInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertPath;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.List;
/**
* Class representing a signer (basically, a cert chain).
*/
public class SignerInfo {
static private final String PKI_PATH_ENCODING = "PkiPath";
static private final String X509 = "X.509";
// certificate chain. Signer cert first, CA cert last.
private final List<X509Certificate> certChain;
private final ProtocolSignerInfo protobuf;
// id of the signer, which is the Base-64 encoded hash of the PkiPath-ecncoded
// certificate chain.
private final byte[] signerId;
/**
* Public constructor.
* @param hashAlg The hash algorithm to use to calculate the signer id (which
* is the base-64-encoding of the hash of the PkiPath-encoding of the cert
* chain).
* @param certs the cert chain used by this signer. Cert of the signer is
* first, and cert of the CA is last.
* @param domain the domain that the certificates are issued to. This should
* match the CN in the target certificate.
* @throws SignatureException if the certs couldn't be parsed into a cert
* chain, or if the hash couldn't be calculated.
*/
public SignerInfo(HashAlgorithm hashAlg, List<X509Certificate> certs,
String domain) throws SignatureException {
Preconditions.checkArgument(certs.size() > 0, "need at least one" +
"cert in the chain");
try {
this.protobuf = ProtocolSignerInfo.newBuilder()
.setHashAlgorithm(hashAlg)
.setDomain(domain)
.addAllCertificate(getCertificatesAsListOfByteArrays(certs))
.build();
} catch (CertificateEncodingException e) {
throw new SignatureException("couldn't parse certificates", e);
}
this.certChain = ImmutableList.copyOf(certs);
this.signerId = calculateSignerId(this.certChain);
}
/**
* Public constructor from a protobuf.
*
* @param protobuf
* @throws SignatureException
*/
public SignerInfo(ProtocolSignerInfo protobuf) throws SignatureException {
this.protobuf = protobuf;
this.certChain = getCertificatesFromListOfByteArrays(
protobuf.getCertificateList());
this.signerId = calculateSignerId(this.certChain);
}
private List<ByteString> getCertificatesAsListOfByteArrays(
List<X509Certificate> certs) throws CertificateEncodingException {
List<ByteString> result = Lists.newArrayList();
for (X509Certificate cert : certs) {
result.add(ByteString.copyFrom(cert.getEncoded()));
}
return result;
}
private List<X509Certificate> getCertificatesFromListOfByteArrays(
List<? extends ByteString> certs) throws SignatureException {
try {
List<X509Certificate> result = Lists.newArrayList();
CertificateFactory certFactory = CertificateFactory.getInstance(X509);
for (ByteString certAsBytes : certs) {
result.add((X509Certificate) certFactory.generateCertificate(
new ByteArrayInputStream(certAsBytes.toByteArray())));
}
return result;
} catch (CertificateException e) {
throw new SignatureException(e);
}
}
/**
* Returns the cert chain used by this signer. Cert of the signer is
* first, and cert of the CA is last.
*/
public List<X509Certificate> getCertificates() {
return certChain;
}
/**
* Returns the hash algorithm used to calculate the signer id from the cert
* chain.
*/
public HashAlgorithm getHashAlgorithm() {
return protobuf.getHashAlgorithm();
}
/**
* The domain that this signer claims to belong to. It is the responsibility
* of the client of this interface to verify that the domain matches the
* principal to which the target certificate of the certificate chain was
* issued.
*/
public String getDomain() {
return protobuf.getDomain();
}
/**
* Returns the id of this signer (cert chain). The signer id is the
* base-64-encoding of the hash of the PkiPath-encoding of the cert chain.
*/
public byte[] getSignerId() {
return signerId;
}
public ProtocolSignerInfo toProtoBuf() {
return protobuf;
}
private byte[] calculateSignerId(List<? extends X509Certificate> certs)
throws SignatureException {
try {
CertificateFactory certFactory = CertificateFactory.getInstance(X509);
CertPath path = certFactory.generateCertPath(certs);
byte[] encodedCertPath = path.getEncoded(PKI_PATH_ENCODING);
MessageDigest digest = MessageDigest.getInstance(
AlgorithmUtil.getJceName(getHashAlgorithm()));
return digest.digest(encodedCertPath);
} catch (CertificateException e) {
throw new SignatureException("could not parse certificate chain", e);
} catch (NoSuchAlgorithmException e) {
throw new SignatureException("could not calculate hash of cert chain", e);
}
}
}