/* |
|
* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved. |
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
* |
|
* This code is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License version 2 only, as |
|
* published by the Free Software Foundation. Oracle designates this |
|
* particular file as subject to the "Classpath" exception as provided |
|
* by Oracle in the LICENSE file that accompanied this code. |
|
* |
|
* This code 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 |
|
* version 2 for more details (a copy is included in the LICENSE file that |
|
* accompanied this code). |
|
* |
|
* You should have received a copy of the GNU General Public License version |
|
* 2 along with this work; if not, write to the Free Software Foundation, |
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
* |
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
* or visit www.oracle.com if you need additional information or have any |
|
* questions. |
|
*/ |
|
package sun.security.pkcs; |
|
import java.io.*; |
|
import java.security.Key; |
|
import java.security.KeyRep; |
|
import java.security.PrivateKey; |
|
import java.security.KeyFactory; |
|
import java.security.MessageDigest; |
|
import java.security.InvalidKeyException; |
|
import java.security.NoSuchAlgorithmException; |
|
import java.security.spec.InvalidKeySpecException; |
|
import java.security.spec.PKCS8EncodedKeySpec; |
|
import java.util.Arrays; |
|
import jdk.internal.access.SharedSecrets; |
|
import sun.security.x509.*; |
|
import sun.security.util.*; |
|
/** |
|
* Holds a PKCS#8 key, for example a private key |
|
* |
|
* According to https://tools.ietf.org/html/rfc5958: |
|
* |
|
* OneAsymmetricKey ::= SEQUENCE { |
|
* version Version, |
|
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, |
|
* privateKey PrivateKey, |
|
* attributes [0] Attributes OPTIONAL, |
|
* ..., |
|
* [[2: publicKey [1] PublicKey OPTIONAL ]], |
|
* ... |
|
* } |
|
* |
|
* We support this format but do not parse attributes and publicKey now. |
|
*/ |
|
public class PKCS8Key implements PrivateKey { |
|
/** use serialVersionUID from JDK 1.1. for interoperability */ |
|
@java.io.Serial |
|
private static final long serialVersionUID = -3836890099307167124L; |
|
/* The algorithm information (name, parameters, etc). */ |
|
protected AlgorithmId algid; |
|
/* The key bytes, without the algorithm information */ |
|
protected byte[] key; |
|
/* The encoded for the key. Created on demand by encode(). */ |
|
protected byte[] encodedKey; |
|
/* The version for this key */ |
|
private static final int V1 = 0; |
|
private static final int V2 = 1; |
|
/** |
|
* Default constructor. Constructors in sub-classes that create a new key |
|
* from its components require this. These constructors must initialize |
|
* {@link #algid} and {@link #key}. |
|
*/ |
|
protected PKCS8Key() { } |
|
/** |
|
* Another constructor. Constructors in sub-classes that create a new key |
|
* from an encoded byte array require this. We do not assign this |
|
* encoding to {@link #encodedKey} directly. |
|
* |
|
* This method is also used by {@link #parseKey} to create a raw key. |
|
*/ |
|
protected PKCS8Key(byte[] input) throws InvalidKeyException { |
|
decode(new ByteArrayInputStream(input)); |
|
} |
|
private void decode(InputStream is) throws InvalidKeyException { |
|
DerValue val = null; |
|
try { |
|
val = new DerValue(is); |
|
if (val.tag != DerValue.tag_Sequence) { |
|
throw new InvalidKeyException("invalid key format"); |
|
} |
|
int version = val.data.getInteger(); |
|
if (version != V1 && version != V2) { |
|
throw new InvalidKeyException("unknown version: " + version); |
|
} |
|
algid = AlgorithmId.parse (val.data.getDerValue ()); |
|
key = val.data.getOctetString(); |
|
DerValue next; |
|
if (val.data.available() == 0) { |
|
return; |
|
} |
|
next = val.data.getDerValue(); |
|
if (next.isContextSpecific((byte)0)) { |
|
if (val.data.available() == 0) { |
|
return; |
|
} |
|
next = val.data.getDerValue(); |
|
} |
|
if (next.isContextSpecific((byte)1)) { |
|
if (version == V1) { |
|
throw new InvalidKeyException("publicKey seen in v1"); |
|
} |
|
if (val.data.available() == 0) { |
|
return; |
|
} |
|
} |
|
throw new InvalidKeyException("Extra bytes"); |
|
} catch (IOException e) { |
|
throw new InvalidKeyException("IOException : " + e.getMessage()); |
|
} finally { |
|
if (val != null) { |
|
val.clear(); |
|
} |
|
} |
|
} |
|
/** |
|
* Construct PKCS#8 subject public key from a DER encoding. If a |
|
* security provider supports the key algorithm with a specific class, |
|
* a PrivateKey from the provider is returned. Otherwise, a raw |
|
* PKCS8Key object is returned. |
|
* |
|
* <P>This mechanism guarantees 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 accommodated. |
|
* |
|
* @param encoded the DER-encoded SubjectPublicKeyInfo value |
|
* @exception IOException on data format errors |
|
*/ |
|
public static PrivateKey parseKey(byte[] encoded) throws IOException { |
|
try { |
|
PKCS8Key rawKey = new PKCS8Key(encoded); |
|
byte[] internal = rawKey.getEncodedInternal(); |
|
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(internal); |
|
PrivateKey result = null; |
|
try { |
|
result = KeyFactory.getInstance(rawKey.algid.getName()) |
|
.generatePrivate(pkcs8KeySpec); |
|
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) { |
|
// Ignore and return raw key |
|
result = rawKey; |
|
} finally { |
|
if (result != rawKey) { |
|
rawKey.clear(); |
|
} |
|
SharedSecrets.getJavaSecuritySpecAccess() |
|
.clearEncodedKeySpec(pkcs8KeySpec); |
|
} |
|
return result; |
|
} catch (InvalidKeyException e) { |
|
throw new IOException("corrupt private key", e); |
|
} |
|
} |
|
/** |
|
* 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; |
|
} |
|
/** |
|
* Returns the DER-encoded form of the key as a byte array, |
|
* or {@code null} if an encoding error occurs. |
|
*/ |
|
public byte[] getEncoded() { |
|
byte[] b = getEncodedInternal(); |
|
return (b == null) ? null : b.clone(); |
|
} |
|
/** |
|
* Returns the format for this key: "PKCS#8" |
|
*/ |
|
public String getFormat() { |
|
return "PKCS#8"; |
|
} |
|
/** |
|
* DER-encodes this key as a byte array stored inside this object |
|
* and return it. |
|
* |
|
* @return the encoding, or null if there is an I/O error. |
|
*/ |
|
private synchronized byte[] getEncodedInternal() { |
|
if (encodedKey == null) { |
|
try { |
|
DerOutputStream tmp = new DerOutputStream(); |
|
tmp.putInteger(V1); |
|
algid.encode(tmp); |
|
tmp.putOctetString(key); |
|
DerValue out = DerValue.wrap(DerValue.tag_Sequence, tmp); |
|
encodedKey = out.toByteArray(); |
|
out.clear(); |
|
} catch (IOException e) { |
|
// encodedKey is still null |
|
} |
|
} |
|
return encodedKey; |
|
} |
|
@java.io.Serial |
|
protected Object writeReplace() throws java.io.ObjectStreamException { |
|
return new KeyRep(KeyRep.Type.PRIVATE, |
|
getAlgorithm(), |
|
getFormat(), |
|
getEncodedInternal()); |
|
} |
|
/** |
|
* We used to serialize a PKCS8Key as itself (instead of a KeyRep). |
|
*/ |
|
@java.io.Serial |
|
private void readObject(ObjectInputStream stream) throws IOException { |
|
try { |
|
decode(stream); |
|
} catch (InvalidKeyException e) { |
|
throw new IOException("deserialized key is invalid: " + |
|
e.getMessage()); |
|
} |
|
} |
|
/** |
|
* Compares two private keys. This returns false if the object with which |
|
* to compare is not of type <code>Key</code>. |
|
* Otherwise, the encoding of this key object is compared with the |
|
* encoding of the given key object. |
|
* |
|
* @param object the object with which to compare |
|
* @return {@code true} if this key has the same encoding as the |
|
* object argument; {@code false} otherwise. |
|
*/ |
|
public boolean equals(Object object) { |
|
if (this == object) { |
|
return true; |
|
} |
|
if (object instanceof PKCS8Key) { |
|
// time-constant comparison |
|
return MessageDigest.isEqual( |
|
getEncodedInternal(), |
|
((PKCS8Key)object).getEncodedInternal()); |
|
} else if (object instanceof Key) { |
|
// time-constant comparison |
|
byte[] otherEncoded = ((Key)object).getEncoded(); |
|
try { |
|
return MessageDigest.isEqual( |
|
getEncodedInternal(), |
|
otherEncoded); |
|
} finally { |
|
if (otherEncoded != null) { |
|
Arrays.fill(otherEncoded, (byte) 0); |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
/** |
|
* Calculates a hash code value for this object. Objects |
|
* which are equal will also have the same hashcode. |
|
*/ |
|
public int hashCode() { |
|
return Arrays.hashCode(getEncodedInternal()); |
|
} |
|
public void clear() { |
|
if (encodedKey != null) { |
|
Arrays.fill(encodedKey, (byte)0); |
|
} |
|
Arrays.fill(key, (byte)0); |
|
} |
|
} |