/* * Copyright (c) 1996, 2025, 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 jdk.internal.access.SharedSecrets; import sun.security.util.*; import sun.security.x509.AlgorithmId; import sun.security.x509.X509Key; import java.io.IOException; import java.io.ObjectInputStream; import java.security.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Arrays; /** * 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. */ public class PKCS8Key implements PrivateKey, InternalPrivateKey { /** 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 private key OctetString for the algorithm subclasses to decode */ protected byte[] privKeyMaterial; /* The pkcs8 encoding of this key(s). Created on demand. */ protected byte[] encodedKey; /* The encoded x509 public key for v2 */ protected byte[] pubKeyEncoded = null; /* ASN.1 Attributes */ private byte[] attributes; /* PKCS8 version of the PEM */ private int version; /* The version for this key */ public static final int V1 = 0; public static final int V2 = 1; /** * Default constructor. Constructors in subclasses that create a new key * from its components require this. These constructors must initialize * {@link #algid} and {@link #privKeyMaterial}. */ protected PKCS8Key() { } /** * Another constructor. Constructors in subclasses 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. */ public PKCS8Key(byte[] input) throws InvalidKeyException { try { decode(new DerValue(input)); } catch (IOException e) { throw new InvalidKeyException("Unable to decode key", e); } } private PKCS8Key(byte[] privEncoding, byte[] pubEncoding) throws InvalidKeyException { this(privEncoding); pubKeyEncoded = pubEncoding; version = V2; } public int getVersion() { return version; } /** * Method for decoding PKCS8 v1 and v2 formats. Decoded values are stored * in this class, key material remains in DER format for algorithm * subclasses to decode. */ private void decode(DerValue val) throws InvalidKeyException { try { if (val.tag != DerValue.tag_Sequence) { throw new InvalidKeyException("invalid key format"); } // Support check for V1, aka 0, and V2, aka 1. version = val.data.getInteger(); if (version != V1 && version != V2) { throw new InvalidKeyException("unknown version: " + version); } // Parse and store AlgorithmID algid = AlgorithmId.parse(val.data.getDerValue()); // Store key material for subclasses to parse privKeyMaterial = val.data.getOctetString(); // PKCS8 v1 typically ends here if (val.data.available() == 0) { return; } // OPTIONAL Context tag 0 for Attributes for PKCS8 v1 & v2 // Uses 0xA0 context-specific/constructed or 0x80 // context-specific/primitive. DerValue v = val.data.getDerValue(); if (v.isContextSpecific((byte)0)) { attributes = v.getDataBytes(); // Save DER sequence if (val.data.available() == 0) { return; } v = val.data.getDerValue(); } // OPTIONAL context tag 1 for Public Key for PKCS8 v2 only if (version == V2) { if (v.isContextSpecific((byte)1)) { DerValue bits = v.withTag(DerValue.tag_BitString); pubKeyEncoded = new X509Key(algid, bits.getUnalignedBitString()).getEncoded(); } else { throw new InvalidKeyException("Invalid context tag"); } if (val.data.available() == 0) { return; } } throw new InvalidKeyException("Extra bytes"); } catch (IOException e) { throw new InvalidKeyException("Unable to decode key", e); } 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 InvalidKeyException on data format errors */ public static PrivateKey parseKey(byte[] encoded) throws InvalidKeyException { return parseKey(encoded, null); } public static PrivateKey parseKey(byte[] encoded, Provider provider) throws InvalidKeyException { try { PKCS8Key rawKey = new PKCS8Key(encoded); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(rawKey.generateEncoding()); PrivateKey result = null; try { if (provider == null) { result = KeyFactory.getInstance(rawKey.algid.getName()) .generatePrivate(pkcs8KeySpec); } else { result = KeyFactory.getInstance(rawKey.algid.getName(), provider).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 (IOException e) { throw new InvalidKeyException(e); } } /** * Returns the algorithm to be used with this key. */ public String getAlgorithm() { return algid.getName(); } public byte[] getPubKeyEncoded() { return pubKeyEncoded; } public boolean hasPublicKey() { return (pubKeyEncoded != null); } /** * 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) ? b.clone() : null; } /** * Returns the format for this key: "PKCS#8" */ public String getFormat() { return "PKCS#8"; } /** * With a given encoded Public and Private key, generate and return a * PKCS8v2 DER-encoded byte[]. * * @param pubKeyEncoded DER-encoded PublicKey * @param privKeyEncoded DER-encoded PrivateKey * @return DER-encoded byte array * @throws IOException thrown on encoding failure */ public static byte[] getEncoded(byte[] pubKeyEncoded, byte[] privKeyEncoded) throws IOException { try { return new PKCS8Key(privKeyEncoded, pubKeyEncoded). generateEncoding(); } catch (InvalidKeyException e) { throw new IOException(e); } } /** * DER-encodes this key as a byte array stored inside this object * and return it. * * @return the encoding */ private synchronized byte[] getEncodedInternal() { if (encodedKey == null) { try { encodedKey = generateEncoding(); } catch (IOException e) { return null; } } return encodedKey; } private byte[] generateEncoding() throws IOException { DerOutputStream out = new DerOutputStream(); out.putInteger(version); algid.encode(out); out.putOctetString(privKeyMaterial); if (version == V2) { if (attributes != null) { out.writeImplicit( DerValue.createTag((byte) (DerValue.TAG_CONTEXT | DerValue.TAG_CONSTRUCT), false, (byte) 0), new DerOutputStream().putOctetString(attributes)); } if (pubKeyEncoded != null) { X509Key x = new X509Key(); try { x.decode(pubKeyEncoded); } catch (InvalidKeyException e) { throw new IOException(e); } // X509Key x = X509Key.parse(pubKeyEncoded); DerOutputStream pubOut = new DerOutputStream(); pubOut.putUnalignedBitString(x.getKey()); out.writeImplicit( DerValue.createTag(DerValue.TAG_CONTEXT, false, (byte) 1), pubOut); } } DerValue val = DerValue.wrap(DerValue.tag_Sequence, out); encodedKey = val.toByteArray(); val.clear(); 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(new DerValue(stream)); } catch (InvalidKeyException e) { throw new IOException("deserialized key is invalid", e); } } /** * 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. */ @Override 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. */ @Override public int hashCode() { return Arrays.hashCode(getEncodedInternal()); } public void clear() { if (encodedKey != null) { Arrays.fill(encodedKey, (byte)0); } Arrays.fill(privKeyMaterial, (byte)0); } }