/* * 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. * *
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 Key.
* 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);
}
}