11/*
2- * Copyright 2012-2020 the original author or authors.
2+ * Copyright 2012-2022 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
2424import java .security .GeneralSecurityException ;
2525import java .security .KeyFactory ;
2626import java .security .PrivateKey ;
27- import java .security .spec .InvalidKeySpecException ;
2827import 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 ;
2932import java .util .regex .Matcher ;
3033import java .util .regex .Pattern ;
3134
@@ -43,21 +46,68 @@ final class PrivateKeyParser {
4346
4447 private static final String PKCS1_FOOTER = "-+END\\ s+RSA\\ s+PRIVATE\\ s+KEY[^-]*-+" ;
4548
49+ private static final String PKCS8_HEADER = "-+BEGIN\\ s+PRIVATE\\ s+KEY[^-]*-+(?:\\ s|\\ r|\\ n)+" ;
50+
4651 private static final String PKCS8_FOOTER = "-+END\\ s+PRIVATE\\ s+KEY[^-]*-+" ;
4752
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[^-]*-+" ;
4956
5057 private static final String BASE64_TEXT = "([a-z0-9+/=\\ r\\ n]+)" ;
5158
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 };
5472
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 };
5782
5883 private PrivateKeyParser () {
5984 }
6085
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+
61111 /**
62112 * Load a private key from the specified file paths.
63113 * @param path the path to the private key file
@@ -66,80 +116,137 @@ private PrivateKeyParser() {
66116 static PrivateKey parse (Path path ) {
67117 try {
68118 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+ }
72124 }
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" );
78126 }
79- catch (GeneralSecurityException | IOException ex ) {
127+ catch (Exception ex ) {
80128 throw new IllegalStateException ("Error loading private key file " + path , ex );
81129 }
82130 }
83131
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 );
87135 }
88136
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 ;
110153 }
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 )));
113158 }
114- }
115159
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 );
120163 }
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+
122176 }
123177
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 ));
129188 }
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 ));
132192 }
133- }
134193
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+ }
139249
140- private static byte [] decodeBase64 (String content ) {
141- byte [] contentBytes = content .replaceAll ("\r " , "" ).replaceAll ("\n " , "" ).getBytes ();
142- return Base64Utils .decode (contentBytes );
143250 }
144251
145252}
0 commit comments