|
|
|
|
@ -18,6 +18,7 @@ package org.springframework.boot.ssl.pem;
@@ -18,6 +18,7 @@ package org.springframework.boot.ssl.pem;
|
|
|
|
|
|
|
|
|
|
import java.io.ByteArrayOutputStream; |
|
|
|
|
import java.io.IOException; |
|
|
|
|
import java.nio.ByteBuffer; |
|
|
|
|
import java.security.AlgorithmParameters; |
|
|
|
|
import java.security.GeneralSecurityException; |
|
|
|
|
import java.security.KeyFactory; |
|
|
|
|
@ -38,6 +39,8 @@ import javax.crypto.SecretKey;
@@ -38,6 +39,8 @@ import javax.crypto.SecretKey;
|
|
|
|
|
import javax.crypto.SecretKeyFactory; |
|
|
|
|
import javax.crypto.spec.PBEKeySpec; |
|
|
|
|
|
|
|
|
|
import org.springframework.boot.ssl.pem.PemPrivateKeyParser.DerElement.TagType; |
|
|
|
|
import org.springframework.boot.ssl.pem.PemPrivateKeyParser.DerElement.ValueType; |
|
|
|
|
import org.springframework.util.Assert; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
@ -104,7 +107,38 @@ final class PemPrivateKeyParser {
@@ -104,7 +107,38 @@ final class PemPrivateKeyParser {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static PKCS8EncodedKeySpec createKeySpecForEc(byte[] bytes, String password) { |
|
|
|
|
return createKeySpecForAlgorithm(bytes, EC_ALGORITHM, EC_PARAMETERS); |
|
|
|
|
DerElement ecPrivateKey = DerElement.of(bytes); |
|
|
|
|
Assert.state(ecPrivateKey.isType(ValueType.ENCODED, TagType.SEQUENCE), |
|
|
|
|
"Key spec should be an ASN.1 encoded sequence"); |
|
|
|
|
DerElement version = DerElement.of(ecPrivateKey.getContents()); |
|
|
|
|
Assert.state(version != null && version.isType(ValueType.PRIMITIVE, TagType.INTEGER), |
|
|
|
|
"Key spec should start with version"); |
|
|
|
|
Assert.state(version.getContents().remaining() == 1 && version.getContents().get() == 1, |
|
|
|
|
"Key spec version must be 1"); |
|
|
|
|
DerElement privateKey = DerElement.of(ecPrivateKey.getContents()); |
|
|
|
|
Assert.state(privateKey != null && privateKey.isType(ValueType.PRIMITIVE, TagType.OCTET_STRING), |
|
|
|
|
"Key spec should contain private key"); |
|
|
|
|
DerElement parameters = DerElement.of(ecPrivateKey.getContents()); |
|
|
|
|
return createKeySpecForAlgorithm(bytes, EC_ALGORITHM, getEcParameters(parameters)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static int[] getEcParameters(DerElement parameters) { |
|
|
|
|
if (parameters == null) { |
|
|
|
|
return EC_PARAMETERS; |
|
|
|
|
} |
|
|
|
|
Assert.state(parameters.isType(ValueType.ENCODED), "Key spec should contain encoded parameters"); |
|
|
|
|
DerElement contents = DerElement.of(parameters.getContents()); |
|
|
|
|
Assert.state(contents.isType(ValueType.PRIMITIVE, TagType.OBJECT_IDENTIFIER), |
|
|
|
|
"Key spec parameters should contain object identifier"); |
|
|
|
|
return getEcParameters(contents.getContents()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static int[] getEcParameters(ByteBuffer bytes) { |
|
|
|
|
int[] result = new int[bytes.remaining()]; |
|
|
|
|
for (int i = 0; i < result.length; i++) { |
|
|
|
|
result[i] = bytes.get() & 0xFF; |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static PKCS8EncodedKeySpec createKeySpecForAlgorithm(byte[] bytes, int[] algorithm, int[] parameters) { |
|
|
|
|
@ -114,8 +148,7 @@ final class PemPrivateKeyParser {
@@ -114,8 +148,7 @@ final class PemPrivateKeyParser {
|
|
|
|
|
DerEncoder algorithmIdentifier = new DerEncoder(); |
|
|
|
|
algorithmIdentifier.objectIdentifier(algorithm); |
|
|
|
|
algorithmIdentifier.objectIdentifier(parameters); |
|
|
|
|
byte[] byteArray = algorithmIdentifier.toByteArray(); |
|
|
|
|
encoder.sequence(byteArray); |
|
|
|
|
encoder.sequence(algorithmIdentifier.toByteArray()); |
|
|
|
|
encoder.octetString(bytes); |
|
|
|
|
return new PKCS8EncodedKeySpec(encoder.toSequence()); |
|
|
|
|
} |
|
|
|
|
@ -288,6 +321,107 @@ final class PemPrivateKeyParser {
@@ -288,6 +321,107 @@ final class PemPrivateKeyParser {
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* An ASN.1 DER encoded element. |
|
|
|
|
*/ |
|
|
|
|
static final class DerElement { |
|
|
|
|
|
|
|
|
|
private final ValueType valueType; |
|
|
|
|
|
|
|
|
|
private final long tagType; |
|
|
|
|
|
|
|
|
|
private ByteBuffer contents; |
|
|
|
|
|
|
|
|
|
private DerElement(ByteBuffer bytes) { |
|
|
|
|
byte b = bytes.get(); |
|
|
|
|
this.valueType = ((b & 0x20) == 0) ? ValueType.PRIMITIVE : ValueType.ENCODED; |
|
|
|
|
this.tagType = decodeTagType(b, bytes); |
|
|
|
|
int length = decodeLength(bytes); |
|
|
|
|
bytes.limit(bytes.position() + length); |
|
|
|
|
this.contents = bytes.slice(); |
|
|
|
|
bytes.limit(bytes.capacity()); |
|
|
|
|
bytes.position(bytes.position() + length); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private long decodeTagType(byte b, ByteBuffer bytes) { |
|
|
|
|
long tagType = (b & 0x1F); |
|
|
|
|
if (tagType != 0x1F) { |
|
|
|
|
return tagType; |
|
|
|
|
} |
|
|
|
|
tagType = 0; |
|
|
|
|
b = bytes.get(); |
|
|
|
|
while ((b & 0x80) != 0) { |
|
|
|
|
tagType <<= 7; |
|
|
|
|
tagType = tagType | (b & 0x7F); |
|
|
|
|
b = bytes.get(); |
|
|
|
|
} |
|
|
|
|
return tagType; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private int decodeLength(ByteBuffer bytes) { |
|
|
|
|
byte b = bytes.get(); |
|
|
|
|
if ((b & 0x80) == 0) { |
|
|
|
|
return b & 0x7F; |
|
|
|
|
} |
|
|
|
|
int numberOfLengthBytes = (b & 0x7F); |
|
|
|
|
Assert.state(numberOfLengthBytes != 0, "Infinite length encoding is not supported"); |
|
|
|
|
Assert.state(numberOfLengthBytes != 0x7F, "Reserved length encoding is not supported"); |
|
|
|
|
Assert.state(numberOfLengthBytes <= 4, "Length overflow"); |
|
|
|
|
int length = 0; |
|
|
|
|
for (int i = 0; i < numberOfLengthBytes; i++) { |
|
|
|
|
length <<= 8; |
|
|
|
|
length |= (bytes.get() & 0xFF); |
|
|
|
|
} |
|
|
|
|
return length; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
boolean isType(ValueType valueType) { |
|
|
|
|
return this.valueType == valueType; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
boolean isType(ValueType valueType, TagType tagType) { |
|
|
|
|
return this.valueType == valueType && this.tagType == tagType.getNumber(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ByteBuffer getContents() { |
|
|
|
|
return this.contents; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static DerElement of(byte[] bytes) { |
|
|
|
|
return of(ByteBuffer.wrap(bytes)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static DerElement of(ByteBuffer bytes) { |
|
|
|
|
return (bytes.remaining() > 0) ? new DerElement(bytes) : null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
enum ValueType { |
|
|
|
|
|
|
|
|
|
PRIMITIVE, ENCODED |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
enum TagType { |
|
|
|
|
|
|
|
|
|
INTEGER(0x02), OCTET_STRING(0x04), OBJECT_IDENTIFIER(0x06), SEQUENCE(0x10); |
|
|
|
|
|
|
|
|
|
private final int number; |
|
|
|
|
|
|
|
|
|
TagType(int number) { |
|
|
|
|
this.number = number; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int getNumber() { |
|
|
|
|
return this.number; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Decryptor for PKCS8 encoded private keys. |
|
|
|
|
*/ |
|
|
|
|
static class Pkcs8PrivateKeyDecryptor { |
|
|
|
|
|
|
|
|
|
public static final String PBES2_ALGORITHM = "PBES2"; |
|
|
|
|
|