// --- BEGIN COPYRIGHT BLOCK --- // This program 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; version 2 of the License. // // This program 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, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // // (C) 2007 Red Hat, Inc. // All rights reserved. // --- END COPYRIGHT BLOCK --- package netscape.security.x509; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.Provider; import java.security.PublicKey; import java.security.Security; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import netscape.security.util.DerOutputStream; import netscape.security.util.DerValue; /** * Holds an X.509 key, for example a public key found in an X.509 * certificate. Includes a description of the algorithm to be used * with the key; these keys normally are used as * "SubjectPublicKeyInfo". * *
* While this class can represent any kind of X.509 key, it may be desirable to provide subclasses which understand how
* to parse keying data. For example, RSA public keys have two members, one for the public modulus and one for the prime
* exponent. If such a class is provided, it is used when parsing X.509 keys. If one is not provided, the key still
* parses correctly.
*
* @version 1.74, 97/12/10
* @author David Brownell
*/
public class X509Key implements PublicKey {
/** use serialVersionUID from JDK 1.1. for interoperability */
private static final long serialVersionUID = -5359250853002055002L;
/* The algorithm information (name, parameters, etc). */
protected AlgorithmId algid;
/* The key bytes, without the algorithm information */
protected byte[] key;
/* The encoding for the key. */
protected byte[] encodedKey;
/**
* Default constructor. The key constructed must have its key
* and algorithm initialized before it may be used, for example
* by using decode
.
*/
public X509Key() {
}
/*
* Build and initialize as a "default" key. All X.509 key
* data is stored and transmitted losslessly, but no knowledge
* about this particular algorithm is available.
*/
public X509Key(AlgorithmId algid, byte[] key)
throws InvalidKeyException {
this.algid = algid;
this.key = key;
encode();
}
/**
* Construct X.509 subject public key from a DER value. If
* the runtime environment is configured with a specific class for
* this kind of key, a subclass is returned. Otherwise, a generic
* X509Key object is returned.
*
*
* This mechanism gurantees that keys (and algorithms) may be freely manipulated and transferred, without risk of
* losing information. Also, when a key (or algorithm) needs some special handling, that specific need can be
* accomodated.
*
* @param in the DER-encoded SubjectPublicKeyInfo value
* @exception IOException on data format errors
*/
public static X509Key parse(DerValue in) throws IOException {
AlgorithmId algorithm;
X509Key subjectKey;
if (in.tag != DerValue.tag_Sequence)
throw new IOException("corrupt subject key");
algorithm = AlgorithmId.parse(in.data.getDerValue());
try {
subjectKey = buildX509Key(algorithm, in.data.getBitString());
} catch (InvalidKeyException e) {
throw new IOException("subject key, " + e.getMessage());
}
if (in.data.available() != 0)
throw new IOException("excess subject key");
return subjectKey;
}
/**
* Parse the key bits. This may be redefined by subclasses to take
* advantage of structure within the key. For example, RSA public
* keys encapsulate two unsigned integers (modulus and exponent) as
* DER values within the key
bits; Diffie-Hellman and
* DSS/DSA keys encapsulate a single unsigned integer.
*
*
* This function is called when creating X.509 SubjectPublicKeyInfo values using the X509Key member functions, such
* as parse
and decode
.
*
* @exception IOException on parsing errors.
* @exception InvalidKeyException on invalid key encodings.
*/
protected void parseKeyBits() throws IOException, InvalidKeyException {
encode();
}
/*
* Factory interface, building the kind of key associated with this
* specific algorithm ID or else returning this generic base class.
* See the description above.
*/
static X509Key buildX509Key(AlgorithmId algid, byte[] key)
throws IOException, InvalidKeyException {
/*
* Use the algid and key parameters to produce the ASN.1 encoding
* of the key, which will then be used as the input to the
* key factory.
*/
DerOutputStream x509EncodedKeyStream = new DerOutputStream();
encode(x509EncodedKeyStream, algid, key);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(x509EncodedKeyStream.toByteArray());
try {
// Instantiate the key factory of the appropriate algorithm
KeyFactory keyFac = null;
if (Security.getProvider("Mozilla-JSS") == null) {
keyFac = KeyFactory.getInstance(algid.getName());
} else {
keyFac = KeyFactory.getInstance(algid.getName(),
"Mozilla-JSS");
}
// Generate the public key
PublicKey pubKey = keyFac.generatePublic(x509KeySpec);
if (pubKey instanceof X509Key) {
/*
* Return specialized X509Key, where the structure within the
* key has been parsed
*/
return (X509Key) pubKey;
}
} catch (NoSuchAlgorithmException e) {
// Return generic X509Key with opaque key data (see below)
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e.toString());
} catch (Exception e) {
throw new InvalidKeyException(e.toString());
}
/*
* Try again using JDK1.1-style for backwards compatibility.
*/
String classname = "";
try {
Provider sunProvider;
sunProvider = Security.getProvider("SUN");
if (sunProvider == null)
throw new InstantiationException();
classname = sunProvider.getProperty("PublicKey.X.509." +
algid.getName());
if (classname == null) {
throw new InstantiationException();
}
Class> keyClass = Class.forName(classname);
Object inst;
X509Key result;
inst = keyClass.newInstance();
if (inst instanceof X509Key) {
result = (X509Key) inst;
result.algid = algid;
result.key = key;
result.parseKeyBits();
return result;
}
} catch (ClassNotFoundException e) {
} catch (InstantiationException e) {
} catch (IllegalAccessException e) {
// this should not happen.
throw new IOException(classname + " [internal error]");
}
X509Key result = new X509Key();
result.algid = algid;
result.key = key;
return result;
}
/**
* Returns the algorithm to be used with this key.
*/
public String getAlgorithm() {
return algid.getName();
}
/**
* Returns the algorithm ID to be used with this key.
*/
public AlgorithmId getAlgorithmId() {
return algid;
}
/**
* Encode SubjectPublicKeyInfo sequence on the DER output stream.
*
* @exception IOException on encoding errors.
*/
public final void encode(DerOutputStream out) throws IOException {
encode(out, this.algid, this.key);
}
/**
* Returns the DER-encoded form of the key as a byte array.
*/
public synchronized byte[] getEncoded() {
byte[] result = null;
try {
result = encode();
} catch (InvalidKeyException e) {
}
return result;
}
/**
* Returns the format for this key: "X.509"
*/
public String getFormat() {
return "X.509";
}
/**
* Returns the raw key as a byte array
*/
public byte[] getKey() {
return key;
}
/**
* Returns the DER-encoded form of the key as a byte array.
*
* @exception InvalidKeyException on encoding errors.
*/
public byte[] encode() throws InvalidKeyException {
if (encodedKey == null) {
try {
DerOutputStream out;
out = new DerOutputStream();
encode(out);
encodedKey = out.toByteArray();
} catch (IOException e) {
throw new InvalidKeyException("IOException : " +
e.getMessage());
}
}
return copyEncodedKey(encodedKey);
}
/*
* Returns a printable representation of the key
*/
public String toString() {
netscape.security.util.PrettyPrintFormat pp =
new netscape.security.util.PrettyPrintFormat(" ", 20);
String keybits = pp.toHexString(key);
return "algorithm = " + algid.toString()
+ ", unparsed keybits = \n" + keybits;
}
/**
* Initialize an X509Key object from an input stream. The data on that
* input stream must be encoded using DER, obeying the X.509 SubjectPublicKeyInfo
format. That is, the
* data is a
* sequence consisting of an algorithm ID and a bit string which holds
* the key. (That bit string is often used to encapsulate another DER
* encoded sequence.)
*
*
* Subclasses should not normally redefine this method; they should instead provide a parseKeyBits
* method to parse any fields inside the key
member.
*
*
* The exception to this rule is that since private keys need not be encoded using the X.509
* SubjectPublicKeyInfo
format, private keys may override this method, encode
, and of
* course getFormat
.
*
* @param in an input stream with a DER-encoded X.509
* SubjectPublicKeyInfo value
* @exception InvalidKeyException on parsing errors.
*/
public void decode(InputStream in)
throws InvalidKeyException {
DerValue val;
try {
val = new DerValue(in);
if (val.tag != DerValue.tag_Sequence)
throw new InvalidKeyException("invalid key format");
algid = AlgorithmId.parse(val.data.getDerValue());
key = val.data.getBitString();
parseKeyBits();
if (val.data.available() != 0)
throw new InvalidKeyException("excess key data");
} catch (IOException e) {
// e.printStackTrace ();
throw new InvalidKeyException("IOException : " +
e.getMessage());
}
}
public void decode(byte[] encodedKey) throws InvalidKeyException {
decode(new ByteArrayInputStream(encodedKey));
}
/**
* Serialization write ... X.509 keys serialize as
* themselves, and they're parsed when they get read back.
*/
private synchronized void
writeObject(java.io.ObjectOutputStream stream)
throws IOException {
stream.write(getEncoded());
}
/**
* Serialization read ... X.509 keys serialize as
* themselves, and they're parsed when they get read back.
*/
private synchronized void
readObject(ObjectInputStream stream)
throws IOException {
try {
decode(stream);
} catch (InvalidKeyException e) {
e.printStackTrace();
throw new IOException("deserialized key is invalid: " +
e.getMessage());
}
}
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object instanceof Key) {
Key key = (Key) object;
byte[] b1;
if (encodedKey != null) {
b1 = encodedKey;
} else {
b1 = getEncoded();
}
byte[] b2 = key.getEncoded();
return java.security.MessageDigest.isEqual(b1, b2);
}
return false;
}
/**
* Calculates a hash code value for the object. Objects
* which are equal will also have the same hashcode.
*/
public int hashCode() {
int retval = 0;
byte[] b1 = getEncoded();
for (int i = 1; i < b1.length; i++) {
retval += b1[i] * i;
}
return (retval);
}
/*
* Make a copy of the encoded key.
*/
private byte[] copyEncodedKey(byte[] encodedKey) {
int len = encodedKey.length;
byte[] copy = new byte[len];
System.arraycopy(encodedKey, 0, copy, 0, len);
return copy;
}
/*
* Produce SubjectPublicKey encoding from algorithm id and key material.
*/
static void encode(DerOutputStream out, AlgorithmId algid, byte[] key)
throws IOException {
DerOutputStream tmp = new DerOutputStream();
algid.encode(tmp);
tmp.putBitString(key);
out.write(DerValue.tag_Sequence, tmp);
}
/*
* parsePublicKey returns a PublicKey for use with package JSS from within netscape.security.*.
* This function provide an interim solution for migrating from using the netscape.security.* package
* to using the JSS package.
*/
public static PublicKey parsePublicKey(DerValue in) throws IOException {
AlgorithmId algorithm;
PublicKey subjectKey;
if (in.tag != DerValue.tag_Sequence)
throw new IOException("corrupt subject key");
algorithm = AlgorithmId.parse(in.data.getDerValue());
try {
subjectKey = buildPublicKey(algorithm, in.data.getBitString());
} catch (InvalidKeyException e) {
throw new IOException("subject key, " + e.getMessage());
}
if (in.data.available() != 0)
throw new IOException("excess subject key");
return subjectKey;
}
/* buildPublicKey returns a PublicKey for use with the JSS package from within netscape.security.*.
* This function provide an interim solution for migrating from using the netscape.security.* package
* to using the JSS package.
*/
static PublicKey buildPublicKey(AlgorithmId algid, byte[] key)
throws IOException, InvalidKeyException {
/*
* Use the algid and key parameters to produce the ASN.1 encoding
* of the key, which will then be used as the input to the
* key factory.
*/
DerOutputStream x509EncodedKeyStream = new DerOutputStream();
encode(x509EncodedKeyStream, algid, key);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(x509EncodedKeyStream.toByteArray());
try {
// Instantiate the key factory of the appropriate algorithm
KeyFactory keyFac = null;
if (Security.getProvider("Mozilla-JSS") == null) {
keyFac = KeyFactory.getInstance(algid.getName());
} else {
keyFac = KeyFactory.getInstance(algid.getName(),
"Mozilla-JSS");
}
// Generate the public key
PublicKey pubKey = keyFac.generatePublic(x509KeySpec);
/*
* Return specialized X509Key, where the structure within the
* key has been parsed
*/
return pubKey;
} catch (NoSuchAlgorithmException e) {
// Return generic X509Key with opaque key data (see below)
throw new InvalidKeyException(e.toString());
} catch (InvalidKeySpecException e) {
throw new InvalidKeyException(e.toString());
} catch (Exception e) {
throw new InvalidKeyException(e.toString());
}
}
}