/*
* This file is part of rockframework.
*
* rockframework is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* rockframework is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>;.
*/
package br.net.woodstock.rockframework.security.store.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import javax.crypto.SecretKey;
import org.xml.sax.SAXException;
import br.net.woodstock.rockframework.core.RockFrameworkVersion;
import br.net.woodstock.rockframework.core.utils.Codecs;
import br.net.woodstock.rockframework.core.utils.Collections;
import br.net.woodstock.rockframework.core.utils.Conditions;
import br.net.woodstock.rockframework.core.xml.dom.XmlDocument;
import br.net.woodstock.rockframework.core.xml.dom.XmlElement;
import br.net.woodstock.rockframework.security.Identity;
import br.net.woodstock.rockframework.security.cert.util.Certificates;
import br.net.woodstock.rockframework.security.crypt.KeyPairType;
import br.net.woodstock.rockframework.security.crypt.KeyType;
import br.net.woodstock.rockframework.security.crypt.util.Keys;
import br.net.woodstock.rockframework.security.store.StoreException;
public class XMLStore extends MapStore {
private static final long serialVersionUID = RockFrameworkVersion.VERSION;
private static final Charset CHARSET = Charset.defaultCharset();
private static final String ENCODING = XMLStore.CHARSET.name();
private static final String ALIAS_ATTRIBUTE = "alias";
private static final String ALGORITHM_ATTRIBUTE = "algorithm";
private static final String ENCODING_ATTRIBUTE = "encoding";
private static final String STORE_ELEMENT = "store";
private static final String CERTIFICATES_ELEMENT = "certificates";
private static final String CHAIN_ELEMENT = "chain";
private static final String PRIVATE_KEYS_ELEMENT = "privateKeys";
private static final String PUBLIC_KEYS_ELEMENT = "publicKeys";
private static final String SECRET_KEYS_ELEMENT = "secretKeys";
private static final String CERTIFICATE_ELEMENT = "certificate";
private static final String PRIVATE_KEY_ELEMENT = "privateKey";
private static final String PUBLIC_KEY_ELEMENT = "publicKey";
private static final String SECRET_KEY_ELEMENT = "publicKey";
public XMLStore() {
super();
}
@Override
public void read(final InputStream inputStream, final String password) throws IOException {
try {
XmlDocument document = XmlDocument.read(inputStream);
XmlElement root = document.getRoot();
String encoding = root.getAttribute(XMLStore.ENCODING_ATTRIBUTE);
XmlElement certificates = root.getElement(XMLStore.CERTIFICATES_ELEMENT);
XmlElement privateKeys = root.getElement(XMLStore.PRIVATE_KEYS_ELEMENT);
XmlElement publicKeys = root.getElement(XMLStore.PUBLIC_KEYS_ELEMENT);
XmlElement secretKeys = root.getElement(XMLStore.SECRET_KEYS_ELEMENT);
for (XmlElement certificateElement : certificates.getElements()) {
String alias = certificateElement.getAttribute(XMLStore.ALIAS_ATTRIBUTE);
Certificate certificate = this.getCertificate(certificateElement, encoding);
this.getCertificateMap().put(alias, certificate);
}
for (XmlElement privateKeyElement : privateKeys.getElements()) {
String alias = privateKeyElement.getAttribute(XMLStore.ALIAS_ATTRIBUTE);
Identity holder = this.getPrivateKey(privateKeyElement, encoding);
this.getPrivateKeyMap().put(alias, holder);
}
for (XmlElement publicKeyElement : publicKeys.getElements()) {
String alias = publicKeyElement.getAttribute(XMLStore.ALIAS_ATTRIBUTE);
PublicKey publicKey = this.getPublicKey(publicKeyElement, encoding);
this.getPublicKeyMap().put(alias, publicKey);
}
for (XmlElement secretKeyElement : secretKeys.getElements()) {
String alias = secretKeyElement.getAttribute(XMLStore.ALIAS_ATTRIBUTE);
SecretKey secretKey = this.getSecretKey(secretKeyElement, encoding);
this.getSecretKeyMap().put(alias, secretKey);
}
} catch (GeneralSecurityException e) {
throw new StoreException(e);
} catch (SAXException e) {
throw new StoreException(e);
}
}
@Override
public void write(final OutputStream outputStream, final String password) throws IOException {
try {
XmlDocument document = new XmlDocument(XMLStore.STORE_ELEMENT);
XmlElement root = document.getRoot();
root.setAttribute(XMLStore.ENCODING_ATTRIBUTE, XMLStore.ENCODING);
XmlElement certificates = root.addElement(XMLStore.CERTIFICATES_ELEMENT);
XmlElement privateKeys = root.addElement(XMLStore.PRIVATE_KEYS_ELEMENT);
XmlElement publicKeys = root.addElement(XMLStore.PUBLIC_KEYS_ELEMENT);
XmlElement secretKeys = root.addElement(XMLStore.SECRET_KEYS_ELEMENT);
for (Entry<String, Certificate> entry : this.getCertificateMap().entrySet()) {
String alias = entry.getKey();
Certificate certificate = entry.getValue();
this.addCertificateElement(certificates, alias, certificate);
}
for (Entry<String, Identity> entry : this.getPrivateKeyMap().entrySet()) {
String alias = entry.getKey();
Identity identity = entry.getValue();
this.addPrivateKeyElement(privateKeys, alias, identity);
}
for (Entry<String, PublicKey> entry : this.getPublicKeyMap().entrySet()) {
String alias = entry.getKey();
PublicKey publicKey = entry.getValue();
this.addPublicKeyElement(publicKeys, alias, publicKey);
}
for (Entry<String, SecretKey> entry : this.getSecretKeyMap().entrySet()) {
String alias = entry.getKey();
SecretKey secretKey = entry.getValue();
this.addSecretKeyElement(secretKeys, alias, secretKey);
}
document.write(outputStream);
} catch (GeneralSecurityException e) {
throw new StoreException(e);
}
}
private Certificate getCertificate(final XmlElement e, final String encoding) throws UnsupportedEncodingException {
byte[] data = this.getBase64Data(e, encoding);
Certificate certificate = Certificates.get(data);
return certificate;
}
private Identity getPrivateKey(final XmlElement e, final String encoding) throws NoSuchAlgorithmException, InvalidKeySpecException, UnsupportedEncodingException {
String algorithm = e.getAttribute(XMLStore.ALGORITHM_ATTRIBUTE);
byte[] data = this.getBase64Data(e, encoding);
PrivateKey privateKey = Keys.getPrivateKeyFromPKCS8File(data, KeyPairType.getKeyPairType(algorithm));
List<Certificate> chainList = new ArrayList<Certificate>();
Certificate[] chain = null;
XmlElement chainElement = e.getElement(XMLStore.CHAIN_ELEMENT);
for (XmlElement certificateElement : chainElement.getElements()) {
Certificate certificate = this.getCertificate(certificateElement, encoding);
chainList.add(certificate);
}
if (chainList.size() > 0) {
chain = Collections.toArray(chainList, Certificate.class);
}
Identity identity = new Identity(privateKey, chain);
return identity;
}
private PublicKey getPublicKey(final XmlElement e, final String encoding) throws NoSuchAlgorithmException, InvalidKeySpecException, UnsupportedEncodingException {
String algorithm = e.getAttribute(XMLStore.ALGORITHM_ATTRIBUTE);
byte[] data = this.getBase64Data(e, encoding);
PublicKey publicKey = Keys.getPublicKeyFromX509File(data, KeyPairType.getKeyPairType(algorithm));
return publicKey;
}
private SecretKey getSecretKey(final XmlElement e, final String encoding) throws UnsupportedEncodingException {
String algorithm = e.getAttribute(XMLStore.ALGORITHM_ATTRIBUTE);
byte[] data = this.getBase64Data(e, encoding);
SecretKey secretKey = Keys.getSecretKeyFromFile(data, KeyType.getKeyType(algorithm));
return secretKey;
}
private void addCertificateElement(final XmlElement parent, final String alias, final Certificate certificate) throws CertificateEncodingException, UnsupportedEncodingException {
XmlElement certificateElement = parent.addElement(XMLStore.CERTIFICATE_ELEMENT);
certificateElement.setAttribute(XMLStore.ALIAS_ATTRIBUTE, alias);
certificateElement.setAttribute(XMLStore.ALGORITHM_ATTRIBUTE, certificate.getType());
this.setBase64Data(certificateElement, certificate.getEncoded());
}
private void addPrivateKeyElement(final XmlElement parent, final String alias, final Identity identity) throws CertificateEncodingException, UnsupportedEncodingException {
PrivateKey privateKey = identity.getPrivateKey();
Certificate[] chain = identity.getChain();
XmlElement privateKeyElement = parent.addElement(XMLStore.PRIVATE_KEY_ELEMENT);
XmlElement chainElement = privateKeyElement.addElement(XMLStore.CHAIN_ELEMENT);
privateKeyElement.setAttribute(XMLStore.ALIAS_ATTRIBUTE, alias);
privateKeyElement.setAttribute(XMLStore.ALGORITHM_ATTRIBUTE, privateKey.getAlgorithm());
this.setBase64Data(privateKeyElement, privateKey.getEncoded());
if (Conditions.isNotEmpty(chain)) {
for (Certificate certificate : chain) {
this.addCertificateElement(chainElement, alias, certificate);
}
}
}
private void addPublicKeyElement(final XmlElement parent, final String alias, final PublicKey publicKey) throws UnsupportedEncodingException {
XmlElement publicKeyElement = parent.addElement(XMLStore.PUBLIC_KEY_ELEMENT);
publicKeyElement.setAttribute(XMLStore.ALIAS_ATTRIBUTE, alias);
publicKeyElement.setAttribute(XMLStore.ALGORITHM_ATTRIBUTE, publicKey.getAlgorithm());
this.setBase64Data(publicKeyElement, publicKey.getEncoded());
}
private void addSecretKeyElement(final XmlElement parent, final String alias, final SecretKey secretKey) throws UnsupportedEncodingException {
XmlElement publicKeyElement = parent.addElement(XMLStore.SECRET_KEY_ELEMENT);
publicKeyElement.setAttribute(XMLStore.ALIAS_ATTRIBUTE, alias);
publicKeyElement.setAttribute(XMLStore.ALGORITHM_ATTRIBUTE, secretKey.getAlgorithm());
this.setBase64Data(publicKeyElement, secretKey.getEncoded());
}
private byte[] getBase64Data(final XmlElement e, final String encoding) throws UnsupportedEncodingException {
byte[] data64 = e.getData().getBytes(encoding);
byte[] data = Codecs.fromBase64(data64);
return data;
}
private void setBase64Data(final XmlElement e, final byte[] data) throws UnsupportedEncodingException {
byte[] data64 = Codecs.toBase64(data);
String str = new String(data64, XMLStore.ENCODING);
e.setData(str);
}
}