/*
* Copyright 2013 MovingBlocks
*
* 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.terasology.identity;
import com.google.common.base.Charsets;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.Objects;
/**
* The public certificate, that can be freely shared to declare identity. Able to encrypt data and verify signatures.
*/
public class PublicIdentityCertificate {
private static final int SIGNATURE_LENGTH = 256;
private String id;
private BigInteger modulus;
private BigInteger exponent;
private BigInteger signature;
public PublicIdentityCertificate(String id, BigInteger modulus, BigInteger exponent, BigInteger signature) {
this.id = id;
this.modulus = modulus;
this.exponent = exponent;
this.signature = signature;
}
public String getId() {
return id;
}
public BigInteger getModulus() {
return modulus;
}
public BigInteger getExponent() {
return exponent;
}
public BigInteger getSignature() {
return signature;
}
public byte[] getSignatureBytes() {
return toBytes(signature, SIGNATURE_LENGTH);
}
private byte[] toBytes(BigInteger value, int length) {
byte[] rawResult = value.toByteArray();
if (rawResult.length < length) {
byte[] result = new byte[length];
System.arraycopy(rawResult, 0, result, result.length - rawResult.length, rawResult.length);
return result;
}
return rawResult;
}
@Override
public String toString() {
return id;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof PublicIdentityCertificate) {
PublicIdentityCertificate other = (PublicIdentityCertificate) obj;
return Objects.equals(id, other.id) && Objects.equals(modulus, other.modulus) && Objects.equals(exponent, other.exponent);
}
return false;
}
@Override
public int hashCode() {
return signature.hashCode();
}
/**
* Encrypts data such that it can only be decrypted by the paired private certificate, which is held by the certificate owner.
* <br><br>
* Note that only a limited amount of data can be encrypted in this fashion - for large exchanges this should be used
* to establish shared symmetric key which can then be used for the main exchange.
*
* @param data
* @return The encrypted data
*/
public byte[] encrypt(byte[] data) {
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent);
try {
KeyFactory keyFactory = KeyFactory.getInstance(IdentityConstants.CERTIFICATE_ALGORITHM);
PublicKey key = keyFactory.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance(IdentityConstants.CERTIFICATE_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException("Insufficient support for '" + IdentityConstants.CERTIFICATE_ALGORITHM + "', required for identity management", e);
} catch (InvalidKeySpecException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
throw new RuntimeException("Unexpected error during encryption", e);
}
}
/**
* Verifies that the certificate is valid (self signed check)
*
* @return Whether the certificate is signed by itself
*/
public boolean verifySelfSigned() {
return verifySignedBy(this);
}
/**
* Verifies that the certificate is signed by the given signer's public key
*
* @param signer
* @return
*/
public boolean verifySignedBy(PublicIdentityCertificate signer) {
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(signer.modulus, signer.exponent);
try {
KeyFactory keyFactory = KeyFactory.getInstance(IdentityConstants.CERTIFICATE_ALGORITHM);
PublicKey key = keyFactory.generatePublic(keySpec);
Signature signatureVerifier = Signature.getInstance(IdentityConstants.SIGNATURE_ALGORITHM);
signatureVerifier.initVerify(key);
signatureVerifier.update(id.getBytes(Charsets.UTF_8));
signatureVerifier.update(modulus.toByteArray());
signatureVerifier.update(exponent.toByteArray());
return signatureVerifier.verify(getSignatureBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Insufficient support for '" + IdentityConstants.CERTIFICATE_ALGORITHM + "', required for identity management", e);
} catch (InvalidKeySpecException e) {
return false;
} catch (SignatureException e) {
return false;
} catch (InvalidKeyException e) {
return false;
}
}
/**
* Verifies that the signedData was created by this certificate's corresponding private certificate, over the
* given data.
*
* @param data
* @param signedData
* @return
*/
public boolean verify(byte[] data, byte[] signedData) {
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent);
try {
KeyFactory keyFactory = KeyFactory.getInstance(IdentityConstants.CERTIFICATE_ALGORITHM);
PublicKey key = keyFactory.generatePublic(keySpec);
Signature signatureVerifier = Signature.getInstance(IdentityConstants.SIGNATURE_ALGORITHM);
signatureVerifier.initVerify(key);
signatureVerifier.update(data);
return signatureVerifier.verify(signedData);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Insufficient support for '" + IdentityConstants.CERTIFICATE_ALGORITHM + "', required for identity management", e);
} catch (InvalidKeySpecException e) {
return false;
} catch (SignatureException e) {
return false;
} catch (InvalidKeyException e) {
return false;
}
}
}