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