/**
* Copyright (c) 2009--2014 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package com.redhat.rhn.common.client;
import com.redhat.rhn.frontend.html.XmlTag;
import org.apache.commons.codec.binary.Hex;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* ClientCertificate class representing the systemid file
* /etc/sysconfig/rhn/systemid.
* @version $Rev$
*/
public class ClientCertificate {
public static final String SYSTEM_ID = "system_id";
public static final String FIELDS = "fields";
private final List<Member> members;
private final Map<String, String[]> byName;
private final Map<String, String> checksumFields;
/**
* Default Constructor
*/
public ClientCertificate() {
members = new ArrayList<Member>();
byName = new HashMap<String, String[]>();
// boy this is some ugly stuff
checksumFields = new HashMap<String, String>();
checksumFields.put("username", "");
checksumFields.put("os_release", "");
checksumFields.put("operating_system", "");
checksumFields.put("architecture", "");
checksumFields.put("system_id", "");
checksumFields.put("type", "");
}
/**
* Add a member to the certificate by name and values.
* @param name Member name
* @param values String array of values for the member.
*/
public void addMember(String name, String[] values) {
Member m = new Member();
m.setName(name);
for (int i = 0; i < values.length; i++) {
m.addValue(values[i]);
}
addMember(m);
}
/**
* Add a member to the certificate by name and value.
* @param name Member name
* @param value Member value
*/
public void addMember(String name, String value) {
String[] values = new String[1];
values[0] = value;
addMember(name, values);
}
/**
* Add a member to the certificate.
* @param member Member to be added.
*/
public void addMember(Member member) {
members.add(member);
byName.put(member.getName(), member.getValues());
}
/**
* Returns the first value for the given field named <code>name</code>.
* @param name field name
* @return the first value for the given field name
*/
public String getValueByName(String name) {
String[] strs = getValuesByName(name);
if (strs != null && strs.length > 0) {
return strs[0];
}
return null;
}
/**
* Returns all the values for the given field named <code>name</code>.
* @param name field name
* @return all the values for the given field name
*/
public String[] getValuesByName(String name) {
return byName.get(name);
}
/**
* Validates the client certificate given the unique server key.
* @param secret unique server secret key.
* @throws InvalidCertificateException thrown if there is a problem
* validating certificate.
*/
public void validate(String secret) throws InvalidCertificateException {
String signature = genSignature(secret);
String checksum = getValueByName("checksum");
if (!signature.equals(checksum)) {
throw new InvalidCertificateException(
"Signature mismatch: computed sig(" + signature +
") != given cert(" + checksum + ")");
}
}
/**
* Returns signature of certificate.
* @param secret Server Secret for this certificate
* @return Signature of this certificate
* @throws InvalidCertificateException thrown if there is a problem
* validating the certificate.
*/
public String genSignature(String secret) throws InvalidCertificateException {
String signature = null;
String[] strs = getValuesByName(FIELDS);
for (int i = 0; i < strs.length; i++) {
if (checksumFields.get(strs[i]) == null) {
throw new InvalidCertificateException("Invalid field " + strs[i] +
" provided while validating certificate");
}
}
try {
MessageDigest md = null;
if (secret.length() == 32) {
md = MessageDigest.getInstance("MD5");
}
else if (secret.length() == 64) {
md = MessageDigest.getInstance("SHA-256");
}
// I'm not one to loop through things more than once
// but this seems to be the algorithm found in Server.pm
// add secret
byte[] secretBytes = secret.getBytes("UTF-8");
md.update(secretBytes);
// add the values for the fields
for (int i = 0; i < strs.length; i++) {
String value = getValueByName(strs[i]);
byte[] valueBytes = value.getBytes("UTF-8");
md.update(valueBytes);
}
// add field names
for (int i = 0; i < strs.length; i++) {
byte[] fieldBytes = strs[i].getBytes("UTF-8");
md.update(fieldBytes);
}
// generate the digest
byte[] digest = md.digest();
// hexify this puppy
signature = new String(Hex.encodeHex(digest));
}
catch (UnsupportedEncodingException e) {
throw new InvalidCertificateException(
"Problem getting bytes for signature", e);
}
catch (NoSuchAlgorithmException e) {
throw new InvalidCertificateException(
"Problem getting MD5 message digest.", e);
}
return signature;
}
/**
* Renders the certificate as an Xml document.
* @return Xml document
*/
public String asXml() {
StringBuilder buf = new StringBuilder(XmlTag.XML_HDR);
XmlTag params = new XmlTag("params");
XmlTag param = new XmlTag("param");
XmlTag value = new XmlTag("value");
XmlTag struct = new XmlTag("struct");
params.addBody(param);
param.addBody(value);
value.addBody(struct);
for (Iterator<Member> itr = members.iterator(); itr.hasNext();) {
Member m = itr.next();
if (!m.getName().equals(FIELDS)) {
String[] values = m.getValues();
struct.addBody(createStringMember(m.getName(),
(values != null && values.length > 0) ? values[0] : ""));
}
else {
struct.addBody(createFieldMember(m.getName(), m.getValues()));
}
}
return buf.append(params.render()).toString();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return asXml();
}
/**
* Utility method for building up XML string
* @param name name of member
* @param value value of member
* @return XmlTag representing a <member>
*/
private XmlTag createStringMember(String name, String value) {
XmlTag member = new XmlTag("member");
XmlTag nTag = new XmlTag("name");
XmlTag vTag = new XmlTag("value");
XmlTag tTag = new XmlTag("string");
tTag.addBody(value);
nTag.addBody(name);
vTag.addBody(tTag);
member.addBody(nTag);
member.addBody(vTag);
return member;
}
private XmlTag createFieldMember(String name, String[] value) {
XmlTag member = new XmlTag("member");
XmlTag nTag = new XmlTag("name");
XmlTag vTag = new XmlTag("value");
XmlTag tTag = new XmlTag("array");
XmlTag dTag = new XmlTag("data");
member.addBody(nTag);
member.addBody(vTag);
nTag.addBody(name);
vTag.addBody(tTag);
tTag.addBody(dTag);
for (int i = 0; i < value.length; i++) {
XmlTag v2 = new XmlTag("value");
XmlTag sTag = new XmlTag("string");
sTag.addBody(value[i]);
v2.addBody(sTag);
dTag.addBody(v2);
}
return member;
}
}