24
24
import java .security .GeneralSecurityException ;
25
25
import java .security .KeyFactory ;
26
26
import java .security .PrivateKey ;
27
- import java .security .spec .InvalidKeySpecException ;
28
27
import java .security .spec .PKCS8EncodedKeySpec ;
28
+ import java .util .ArrayList ;
29
+ import java .util .Collections ;
30
+ import java .util .List ;
31
+ import java .util .function .Function ;
29
32
import java .util .regex .Matcher ;
30
33
import java .util .regex .Pattern ;
31
34
@@ -45,21 +48,68 @@ final class PrivateKeyParser {
45
48
46
49
private static final String PKCS1_FOOTER = "-+END\\ s+RSA\\ s+PRIVATE\\ s+KEY[^-]*-+" ;
47
50
51
+ private static final String PKCS8_HEADER = "-+BEGIN\\ s+PRIVATE\\ s+KEY[^-]*-+(?:\\ s|\\ r|\\ n)+" ;
52
+
48
53
private static final String PKCS8_FOOTER = "-+END\\ s+PRIVATE\\ s+KEY[^-]*-+" ;
49
54
50
- private static final String PKCS8_HEADER = "-+BEGIN\\ s+PRIVATE\\ s+KEY[^-]*-+(?:\\ s|\\ r|\\ n)+" ;
55
+ private static final String EC_HEADER = "-+BEGIN\\ s+EC\\ s+PRIVATE\\ s+KEY[^-]*-+(?:\\ s|\\ r|\\ n)+" ;
56
+
57
+ private static final String EC_FOOTER = "-+END\\ s+EC\\ s+PRIVATE\\ s+KEY[^-]*-+" ;
51
58
52
59
private static final String BASE64_TEXT = "([a-z0-9+/=\\ r\\ n]+)" ;
53
60
54
- private static final Pattern PKCS1_PATTERN = Pattern .compile (PKCS1_HEADER + BASE64_TEXT + PKCS1_FOOTER ,
55
- Pattern .CASE_INSENSITIVE );
61
+ private static final List <PemParser > PEM_PARSERS ;
62
+ static {
63
+ List <PemParser > parsers = new ArrayList <>();
64
+ parsers .add (new PemParser (PKCS1_HEADER , PKCS1_FOOTER , "RSA" , PrivateKeyParser ::createKeySpecForPkcs1 ));
65
+ parsers .add (new PemParser (EC_HEADER , EC_FOOTER , "EC" , PrivateKeyParser ::createKeySpecForEc ));
66
+ parsers .add (new PemParser (PKCS8_HEADER , PKCS8_FOOTER , "RSA" , PKCS8EncodedKeySpec ::new ));
67
+ PEM_PARSERS = Collections .unmodifiableList (parsers );
68
+ }
69
+
70
+ /**
71
+ * ASN.1 encoded object identifier {@literal 1.2.840.113549.1.1.1}.
72
+ */
73
+ private static final int [] RSA_ALGORITHM = { 0x2A , 0x86 , 0x48 , 0x86 , 0xF7 , 0x0D , 0x01 , 0x01 , 0x01 };
56
74
57
- private static final Pattern PKCS8_KEY_PATTERN = Pattern .compile (PKCS8_HEADER + BASE64_TEXT + PKCS8_FOOTER ,
58
- Pattern .CASE_INSENSITIVE );
75
+ /**
76
+ * ASN.1 encoded object identifier {@literal 1.2.840.10045.2.1}.
77
+ */
78
+ private static final int [] EC_ALGORITHM = { 0x2a , 0x86 , 0x48 , 0xce , 0x3d , 0x02 , 0x01 };
79
+
80
+ /**
81
+ * ASN.1 encoded object identifier {@literal 1.3.132.0.34}.
82
+ */
83
+ private static final int [] EC_PARAMETERS = { 0x2b , 0x81 , 0x04 , 0x00 , 0x22 };
59
84
60
85
private PrivateKeyParser () {
61
86
}
62
87
88
+ private static PKCS8EncodedKeySpec createKeySpecForPkcs1 (byte [] bytes ) {
89
+ return createKeySpecForAlgorithm (bytes , RSA_ALGORITHM , null );
90
+ }
91
+
92
+ private static PKCS8EncodedKeySpec createKeySpecForEc (byte [] bytes ) {
93
+ return createKeySpecForAlgorithm (bytes , EC_ALGORITHM , EC_PARAMETERS );
94
+ }
95
+
96
+ private static PKCS8EncodedKeySpec createKeySpecForAlgorithm (byte [] bytes , int [] algorithm , int [] parameters ) {
97
+ try {
98
+ DerEncoder encoder = new DerEncoder ();
99
+ encoder .integer (0x00 ); // Version 0
100
+ DerEncoder algorithmIdentifier = new DerEncoder ();
101
+ algorithmIdentifier .objectIdentifier (algorithm );
102
+ algorithmIdentifier .objectIdentifier (parameters );
103
+ byte [] byteArray = algorithmIdentifier .toByteArray ();
104
+ encoder .sequence (byteArray );
105
+ encoder .octetString (bytes );
106
+ return new PKCS8EncodedKeySpec (encoder .toSequence ());
107
+ }
108
+ catch (IOException ex ) {
109
+ throw new IllegalStateException (ex );
110
+ }
111
+ }
112
+
63
113
/**
64
114
* Load a private key from the specified resource.
65
115
* @param resource the private key to parse
@@ -68,82 +118,139 @@ private PrivateKeyParser() {
68
118
static PrivateKey parse (String resource ) {
69
119
try {
70
120
String text = readText (resource );
71
- Matcher matcher = PKCS1_PATTERN .matcher (text );
72
- if (matcher .find ()) {
73
- return parsePkcs1 (decodeBase64 (matcher .group (1 )));
121
+ for (PemParser pemParser : PEM_PARSERS ) {
122
+ PrivateKey privateKey = pemParser .parse (text );
123
+ if (privateKey != null ) {
124
+ return privateKey ;
125
+ }
74
126
}
75
- matcher = PKCS8_KEY_PATTERN .matcher (text );
76
- if (matcher .find ()) {
77
- return parsePkcs8 (decodeBase64 (matcher .group (1 )));
78
- }
79
- throw new IllegalStateException ("Unrecognized private key format in " + resource );
127
+ throw new IllegalStateException ("Unrecognized private key format" );
80
128
}
81
- catch (GeneralSecurityException | IOException ex ) {
129
+ catch (Exception ex ) {
82
130
throw new IllegalStateException ("Error loading private key file " + resource , ex );
83
131
}
84
132
}
85
133
86
- private static PrivateKey parsePkcs1 (byte [] privateKeyBytes ) throws GeneralSecurityException {
87
- byte [] pkcs8Bytes = convertPkcs1ToPkcs8 (privateKeyBytes );
88
- return parsePkcs8 (pkcs8Bytes );
134
+ private static String readText (String resource ) throws IOException {
135
+ URL url = ResourceUtils .getURL (resource );
136
+ try (Reader reader = new InputStreamReader (url .openStream ())) {
137
+ return FileCopyUtils .copyToString (reader );
138
+ }
89
139
}
90
140
91
- private static byte [] convertPkcs1ToPkcs8 (byte [] pkcs1 ) {
92
- try {
93
- ByteArrayOutputStream result = new ByteArrayOutputStream ();
94
- int pkcs1Length = pkcs1 .length ;
95
- int totalLength = pkcs1Length + 22 ;
96
- // Sequence + total length
97
- result .write (bytes (0x30 , 0x82 ));
98
- result .write ((totalLength >> 8 ) & 0xff );
99
- result .write (totalLength & 0xff );
100
- // Integer (0)
101
- result .write (bytes (0x02 , 0x01 , 0x00 ));
102
- // Sequence: 1.2.840.113549.1.1.1, NULL
103
- result .write (
104
- bytes (0x30 , 0x0D , 0x06 , 0x09 , 0x2A , 0x86 , 0x48 , 0x86 , 0xF7 , 0x0D , 0x01 , 0x01 , 0x01 , 0x05 , 0x00 ));
105
- // Octet string + length
106
- result .write (bytes (0x04 , 0x82 ));
107
- result .write ((pkcs1Length >> 8 ) & 0xff );
108
- result .write (pkcs1Length & 0xff );
109
- // PKCS1
110
- result .write (pkcs1 );
111
- return result .toByteArray ();
141
+ /**
142
+ * Parser for a specific PEM format.
143
+ */
144
+ private static class PemParser {
145
+
146
+ private final Pattern pattern ;
147
+
148
+ private final String algorithm ;
149
+
150
+ private final Function <byte [], PKCS8EncodedKeySpec > keySpecFactory ;
151
+
152
+ PemParser (String header , String footer , String algorithm ,
153
+ Function <byte [], PKCS8EncodedKeySpec > keySpecFactory ) {
154
+ this .pattern = Pattern .compile (header + BASE64_TEXT + footer , Pattern .CASE_INSENSITIVE );
155
+ this .algorithm = algorithm ;
156
+ this .keySpecFactory = keySpecFactory ;
112
157
}
113
- catch (IOException ex ) {
114
- throw new IllegalStateException (ex );
158
+
159
+ PrivateKey parse (String text ) {
160
+ Matcher matcher = this .pattern .matcher (text );
161
+ return (!matcher .find ()) ? null : parse (decodeBase64 (matcher .group (1 )));
162
+ }
163
+
164
+ private static byte [] decodeBase64 (String content ) {
165
+ byte [] contentBytes = content .replaceAll ("\r " , "" ).replaceAll ("\n " , "" ).getBytes ();
166
+ return Base64Utils .decode (contentBytes );
115
167
}
116
- }
117
168
118
- private static byte [] bytes (int ... elements ) {
119
- byte [] result = new byte [elements .length ];
120
- for (int i = 0 ; i < elements .length ; i ++) {
121
- result [i ] = (byte ) elements [i ];
169
+ private PrivateKey parse (byte [] bytes ) {
170
+ try {
171
+ PKCS8EncodedKeySpec keySpec = this .keySpecFactory .apply (bytes );
172
+ KeyFactory keyFactory = KeyFactory .getInstance (this .algorithm );
173
+ return keyFactory .generatePrivate (keySpec );
174
+ }
175
+ catch (GeneralSecurityException ex ) {
176
+ throw new IllegalArgumentException ("Unexpected key format" , ex );
177
+ }
122
178
}
123
- return result ;
179
+
124
180
}
125
181
126
- private static PrivateKey parsePkcs8 (byte [] privateKeyBytes ) throws GeneralSecurityException {
127
- try {
128
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec (privateKeyBytes );
129
- KeyFactory keyFactory = KeyFactory .getInstance ("RSA" );
130
- return keyFactory .generatePrivate (keySpec );
182
+ /**
183
+ * Simple ASN.1 DER encoder.
184
+ */
185
+ static class DerEncoder {
186
+
187
+ private final ByteArrayOutputStream stream = new ByteArrayOutputStream ();
188
+
189
+ void objectIdentifier (int ... encodedObjectIdentifier ) throws IOException {
190
+ int code = (encodedObjectIdentifier != null ) ? 0x06 : 0x05 ;
191
+ codeLengthBytes (code , bytes (encodedObjectIdentifier ));
131
192
}
132
- catch (InvalidKeySpecException ex ) {
133
- throw new IllegalArgumentException ("Unexpected key format" , ex );
193
+
194
+ void integer (int ... encodedInteger ) throws IOException {
195
+ codeLengthBytes (0x02 , bytes (encodedInteger ));
134
196
}
135
- }
136
197
137
- private static String readText (String resource ) throws IOException {
138
- URL url = ResourceUtils .getURL (resource );
139
- try (Reader reader = new InputStreamReader (url .openStream ())) {
140
- return FileCopyUtils .copyToString (reader );
198
+ void octetString (byte [] bytes ) throws IOException {
199
+ codeLengthBytes (0x04 , bytes );
200
+ }
201
+
202
+ void sequence (int ... elements ) throws IOException {
203
+ sequence (bytes (elements ));
204
+ }
205
+
206
+ void sequence (byte [] bytes ) throws IOException {
207
+ codeLengthBytes (0x30 , bytes );
208
+ }
209
+
210
+ void codeLengthBytes (int code , byte [] bytes ) throws IOException {
211
+ this .stream .write (code );
212
+ int length = (bytes != null ) ? bytes .length : 0 ;
213
+ if (length <= 127 ) {
214
+ this .stream .write (length & 0xFF );
215
+ }
216
+ else {
217
+ ByteArrayOutputStream lengthStream = new ByteArrayOutputStream ();
218
+ while (length != 0 ) {
219
+ lengthStream .write (length & 0xFF );
220
+ length = length >> 8 ;
221
+ }
222
+ byte [] lengthBytes = lengthStream .toByteArray ();
223
+ this .stream .write (0x80 | lengthBytes .length );
224
+ for (int i = lengthBytes .length - 1 ; i >= 0 ; i --) {
225
+ this .stream .write (lengthBytes [i ]);
226
+ }
227
+ }
228
+ if (bytes != null ) {
229
+ this .stream .write (bytes );
230
+ }
231
+ }
232
+
233
+ private static byte [] bytes (int ... elements ) {
234
+ if (elements == null ) {
235
+ return null ;
236
+ }
237
+ byte [] result = new byte [elements .length ];
238
+ for (int i = 0 ; i < elements .length ; i ++) {
239
+ result [i ] = (byte ) elements [i ];
240
+ }
241
+ return result ;
242
+ }
243
+
244
+ byte [] toSequence () throws IOException {
245
+ DerEncoder sequenceEncoder = new DerEncoder ();
246
+ sequenceEncoder .sequence (toByteArray ());
247
+ return sequenceEncoder .toByteArray ();
248
+ }
249
+
250
+ byte [] toByteArray () {
251
+ return this .stream .toByteArray ();
141
252
}
142
- }
143
253
144
- private static byte [] decodeBase64 (String content ) {
145
- byte [] contentBytes = content .replaceAll ("\r " , "" ).replaceAll ("\n " , "" ).getBytes ();
146
- return Base64Utils .decode (contentBytes );
147
254
}
148
255
149
256
}
0 commit comments