import
javax
.
xml
.
bind
.
DatatypeConverter
;
import
java
.
io
.
ByteArrayInputStream
;
import
java
.
io
.
IOException
;
import
java
.
io
.
InputStream
;
import
java
.
math
.
BigInteger
;
import
java
.
security
.
KeyFactory
;
import
java
.
security
.
NoSuchAlgorithmException
;
import
java
.
security
.
PrivateKey
;
import
java
.
security
.
spec
.
EncodedKeySpec
;
import
java
.
security
.
spec
.
InvalidKeySpecException
;
import
java
.
security
.
spec
.
PKCS8EncodedKeySpec
;
import
java
.
security
.
spec
.
RSAPrivateCrtKeySpec
;
* Class for reading RSA private key from PEM formatted text.
* <p/>It can read PEM files with PKCS#8 or PKCS#1 encodings.
* It doesn't support encrypted PEM files.
// Private key file using PKCS #1 encoding
public
static
final
String
P1_BEGIN_MARKER
=
"-----BEGIN RSA PRIVATE KEY"
;
//$NON-NLS-1$
public
static
final
String
P1_END_MARKER
=
"-----END RSA PRIVATE KEY"
;
//$NON-NLS-1$
// Private key file using PKCS #8 encoding
public
static
final
String
P8_BEGIN_MARKER
=
"-----BEGIN PRIVATE KEY"
;
//$NON-NLS-1$
public
static
final
String
P8_END_MARKER
=
"-----END PRIVATE KEY"
;
//$NON-NLS-1$
protected
final
String
keyString
;
* Create a PEM private key file reader.
* @param keyString The name of the PEM string
public
PrivateKeyReader
(
String
keyString
) {
this
.
keyString
=
keyString
;
* Read the PEM string and return the key
public
PrivateKey
read
()
throws
IOException
{
factory
=
KeyFactory
.
getInstance
(
"RSA"
);
//$NON-NLS-1$
}
catch
(
NoSuchAlgorithmException
e
) {
throw
new
IOException
(
"JCE error: "
+
e
.
getMessage
());
//$NON-NLS-1$
if
(
keyString
.
contains
(
P1_BEGIN_MARKER
))
byte
[]
keyBytes
=
readKeyMaterial
(
P1_BEGIN_MARKER
,
P1_END_MARKER
);
RSAPrivateCrtKeySpec
keySpec
=
getRSAKeySpec
(
keyBytes
);
return
factory
.
generatePrivate
(
keySpec
);
}
catch
(
InvalidKeySpecException
e
) {
throw
new
IOException
(
"Invalid PKCS#1 PEM file: "
+
e
.
getMessage
());
//$NON-NLS-1$
if
(
keyString
.
contains
(
P8_BEGIN_MARKER
))
byte
[]
keyBytes
=
readKeyMaterial
(
P8_BEGIN_MARKER
,
P8_END_MARKER
);
EncodedKeySpec
keySpec
=
new
PKCS8EncodedKeySpec
(
keyBytes
);
return
factory
.
generatePrivate
(
keySpec
);
}
catch
(
InvalidKeySpecException
e
) {
throw
new
IOException
(
"Invalid PKCS#8 PEM file: "
+
e
.
getMessage
());
//$NON-NLS-1$
throw
new
IOException
(
"Invalid PEM file: no begin marker"
);
//$NON-NLS-1$
* Read the PEM file and convert it into binary DER stream
private
byte
[]
readKeyMaterial
(
String
beginMarker
,
String
endMarker
)
throws
IOException
StringBuffer
buf
=
new
StringBuffer
();
Scanner
scanner
=
new
Scanner
(
keyString
);
while
(
scanner
.
hasNextLine
())
line
=
scanner
.
nextLine
();
if
(
line
.
contains
(
beginMarker
)){
if
(
line
.
contains
(
endMarker
)) {
return
DatatypeConverter
.
parseBase64Binary
(
buf
.
toString
());
buf
.
append
(
line
.
trim
());
throw
new
IOException
(
"Invalid PEM file: No end marker"
);
//$NON-NLS-1$
* Convert PKCS#1 encoded private key into RSAPrivateCrtKeySpec.
* <p/>The ASN.1 syntax for the private key with CRT is
* -- Representation of RSA private key with information for the CRT algorithm.
* RSAPrivateKey ::= SEQUENCE {
* publicExponent INTEGER, -- e
* privateExponent INTEGER, -- d
* exponent1 INTEGER, -- d mod (p-1)
* exponent2 INTEGER, -- d mod (q-1)
* coefficient INTEGER, -- (inverse of q) mod p
* otherPrimeInfos OtherPrimeInfos OPTIONAL
* @param keyBytes PKCS#1 encoded key
public
RSAPrivateCrtKeySpec
getRSAKeySpec
(
byte
[]
keyBytes
)
throws
IOException
{
DerParser
parser
=
new
DerParser
(
keyBytes
);
Asn1Object
sequence
=
parser
.
read
();
if
(
sequence
.
getType
() !=
DerParser
.
SEQUENCE
)
throw
new
IOException
(
"Invalid DER: not a sequence"
);
//$NON-NLS-1$
// Parse inside the sequence
parser
=
sequence
.
getParser
();
parser
.
read
();
// Skip version
BigInteger
modulus
=
parser
.
read
().
getInteger
();
BigInteger
publicExp
=
parser
.
read
().
getInteger
();
BigInteger
privateExp
=
parser
.
read
().
getInteger
();
BigInteger
prime1
=
parser
.
read
().
getInteger
();
BigInteger
prime2
=
parser
.
read
().
getInteger
();
BigInteger
exp1
=
parser
.
read
().
getInteger
();
BigInteger
exp2
=
parser
.
read
().
getInteger
();
BigInteger
crtCoef
=
parser
.
read
().
getInteger
();
RSAPrivateCrtKeySpec
keySpec
=
new
RSAPrivateCrtKeySpec
(
modulus
,
publicExp
,
privateExp
,
prime1
,
prime2
,
* A bare-minimum ASN.1 DER decoder, just having enough functions to
* decode PKCS#1 private keys. Especially, it doesn't handle explicitly
* tagged types with an outer tag.
* <p/>This parser can only handle one layer. To parse nested constructs,
* get a new parser for each layer using <code>Asn1Object.getParser()</code>.
* <p/>There are many DER decoders in JRE but using them will tie this
* program to a specific JCE/JVM.
public
final
static
int
UNIVERSAL
=
0x00
;
public
final
static
int
APPLICATION
=
0x40
;
public
final
static
int
CONTEXT
=
0x80
;
public
final
static
int
PRIVATE
=
0xC0
;
public
final
static
int
CONSTRUCTED
=
0x20
;
public
final
static
int
ANY
=
0x00
;
public
final
static
int
BOOLEAN
=
0x01
;
public
final
static
int
INTEGER
=
0x02
;
public
final
static
int
BIT_STRING
=
0x03
;
public
final
static
int
OCTET_STRING
=
0x04
;
public
final
static
int
NULL
=
0x05
;
public
final
static
int
OBJECT_IDENTIFIER
=
0x06
;
public
final
static
int
REAL
=
0x09
;
public
final
static
int
ENUMERATED
=
0x0a
;
public
final
static
int
RELATIVE_OID
=
0x0d
;
public
final
static
int
SEQUENCE
=
0x10
;
public
final
static
int
SET
=
0x11
;
public
final
static
int
NUMERIC_STRING
=
0x12
;
public
final
static
int
PRINTABLE_STRING
=
0x13
;
public
final
static
int
T61_STRING
=
0x14
;
public
final
static
int
VIDEOTEX_STRING
=
0x15
;
public
final
static
int
IA5_STRING
=
0x16
;
public
final
static
int
GRAPHIC_STRING
=
0x19
;
public
final
static
int
ISO646_STRING
=
0x1A
;
public
final
static
int
GENERAL_STRING
=
0x1B
;
public
final
static
int
UTF8_STRING
=
0x0C
;
public
final
static
int
UNIVERSAL_STRING
=
0x1C
;
public
final
static
int
BMP_STRING
=
0x1E
;
public
final
static
int
UTC_TIME
=
0x17
;
public
final
static
int
GENERALIZED_TIME
=
0x18
;
protected
InputStream
in
;
* Create a new DER decoder from an input stream.
public
DerParser
(
InputStream
in
)
throws
IOException
{
* Create a new DER decoder from a byte array.
public
DerParser
(
byte
[]
bytes
)
throws
IOException
{
this
(
new
ByteArrayInputStream
(
bytes
));
* Read next object. If it's constructed, the value holds
* encoded content and it should be parsed by a new
* parser from <code>Asn1Object.getParser</code>.
public
Asn1Object
read
()
throws
IOException
{
throw
new
IOException
(
"Invalid DER: stream too short, missing tag"
);
//$NON-NLS-1$
int
length
=
getLength
();
byte
[]
value
=
new
byte
[
length
];
int
n
=
in
.
read
(
value
);
throw
new
IOException
(
"Invalid DER: stream too short, missing value"
);
//$NON-NLS-1$
Asn1Object
o
=
new
Asn1Object
(
tag
,
length
,
value
);
* Decode the length of the field. Can only support length
* encoding up to 4 octets.
* <p/>In BER/DER encoding, length can be encoded in 2 forms,
* <li>Short form. One octet. Bit 8 has value "0" and bits 7-1
* <li>Long form. Two to 127 octets (only 4 is supported here).
* Bit 8 of first octet has value "1" and bits 7-1 give the
* number of additional length octets. Second and following
* octets give the length, base 256, most significant digit first.
* @return The length as integer
private
int
getLength
()
throws
IOException
{
throw
new
IOException
(
"Invalid DER: length missing"
);
//$NON-NLS-1$
// A single byte short length
if
((
i
& ~
0x7F
) ==
0
)
// We can't handle length longer than 4 bytes
if
(
i
>=
0xFF
||
num
>
4
)
throw
new
IOException
(
"Invalid DER: length field too big ("
//$NON-NLS-1$
+
i
+
")"
);
//$NON-NLS-1$
byte
[]
bytes
=
new
byte
[
num
];
int
n
=
in
.
read
(
bytes
);
throw
new
IOException
(
"Invalid DER: length too short"
);
//$NON-NLS-1$
return
new
BigInteger
(
1
,
bytes
).
intValue
();
* An ASN.1 TLV. The object is not parsed. It can
* only handle integers and strings.
protected
final
int
type
;
protected
final
int
length
;
protected
final
byte
[]
value
;
protected
final
int
tag
;
* Construct a ASN.1 TLV. The TLV could be either a
* constructed or primitive entity.
* <p/>The first byte in DER encoding is made of following fields,
*-------------------------------------------------
*|Bit 8|Bit 7|Bit 6|Bit 5|Bit 4|Bit 3|Bit 2|Bit 1|
*-------------------------------------------------
*-------------------------------------------------
* <li>Class: Universal, Application, Context or Private
* <li>CF: Constructed flag. If 1, the field is constructed.
* <li>Type: This is actually called tag in ASN.1. It
* indicates data type (Integer, String) or a construct
* (sequence, choice, set).
* @param tag Tag or Identifier
* @param length Length of the field
* @param value Encoded octet string for the field.
public
Asn1Object
(
int
tag
,
int
length
,
byte
[]
value
) {
this
.
type
=
tag
&
0x1F
;
public
int
getLength
() {
public
byte
[]
getValue
() {
public
boolean
isConstructed
() {
return
(
tag
&
DerParser
.
CONSTRUCTED
) ==
DerParser
.
CONSTRUCTED
;
* For constructed field, return a parser for its content.
* @return A parser for the construct.
public
DerParser
getParser
()
throws
IOException
{
throw
new
IOException
(
"Invalid DER: can't parse primitive entity"
);
//$NON-NLS-1$
return
new
DerParser
(
value
);
* Get the value as integer
public
BigInteger
getInteger
()
throws
IOException
{
if
(
type
!=
DerParser
.
INTEGER
)
throw
new
IOException
(
"Invalid DER: object is not integer"
);
//$NON-NLS-1$
return
new
BigInteger
(
value
);
* Get value as string. Most strings are treated
public
String
getString
()
throws
IOException
{
// Not all are Latin-1 but it's the closest thing
case
DerParser
.
NUMERIC_STRING
:
case
DerParser
.
PRINTABLE_STRING
:
case
DerParser
.
VIDEOTEX_STRING
:
case
DerParser
.
IA5_STRING
:
case
DerParser
.
GRAPHIC_STRING
:
case
DerParser
.
ISO646_STRING
:
case
DerParser
.
GENERAL_STRING
:
encoding
=
"ISO-8859-1"
;
//$NON-NLS-1$
case
DerParser
.
BMP_STRING
:
encoding
=
"UTF-16BE"
;
//$NON-NLS-1$
case
DerParser
.
UTF8_STRING
:
encoding
=
"UTF-8"
;
//$NON-NLS-1$
case
DerParser
.
UNIVERSAL_STRING
:
throw
new
IOException
(
"Invalid DER: can't handle UCS-4 string"
);
//$NON-NLS-1$
throw
new
IOException
(
"Invalid DER: object is not a string"
);
//$NON-NLS-1$
return
new
String
(
value
,
encoding
);