Skip to content

Commit f9d390f

Browse files
committed
add an ASN1 encoded EC (private) key reader
... including a hack for keeping the curve name oid around
1 parent 2d8569f commit f9d390f

File tree

2 files changed

+134
-5
lines changed

2 files changed

+134
-5
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2016 Karol Bucek.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*/
8+
package org.jruby.ext.openssl.impl;
9+
10+
import java.math.BigInteger;
11+
import java.security.interfaces.ECPrivateKey;
12+
import java.security.spec.ECParameterSpec;
13+
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
14+
15+
/**
16+
* a trick to keep the curve name around
17+
* (since {@link java.security.KeyPair} is final).
18+
*
19+
* @author kares
20+
*/
21+
public final class ECPrivateKeyWithName implements ECPrivateKey {
22+
23+
private final ECPrivateKey realKey;
24+
// private final String curveNameId;
25+
private final ASN1ObjectIdentifier curveNameOID;
26+
27+
public static ECPrivateKeyWithName wrap(ECPrivateKey realKey, ASN1ObjectIdentifier nameOID) {
28+
return new ECPrivateKeyWithName(realKey, nameOID);
29+
}
30+
31+
private ECPrivateKeyWithName(ECPrivateKey realKey, ASN1ObjectIdentifier nameOID) {
32+
this.realKey = realKey; this.curveNameOID = nameOID;
33+
}
34+
35+
//private ECPrivateKeyWithName(ECPrivateKey realKey, String curveNameId) {
36+
// this.realKey = realKey;
37+
// this.curveNameId = curveNameId;
38+
//}
39+
40+
//public String getCurveNameId() {
41+
// return curveNameId;
42+
//}
43+
44+
public ASN1ObjectIdentifier getCurveNameOID() {
45+
return curveNameOID;
46+
}
47+
48+
public ECPrivateKey unwrap() {
49+
return realKey;
50+
}
51+
52+
public BigInteger getS() {
53+
return realKey.getS();
54+
}
55+
56+
public String getAlgorithm() {
57+
return realKey.getAlgorithm();
58+
}
59+
60+
public String getFormat() {
61+
return realKey.getFormat();
62+
}
63+
64+
public byte[] getEncoded() {
65+
return realKey.getEncoded();
66+
}
67+
68+
public ECParameterSpec getParams() {
69+
return realKey.getParams();
70+
}
71+
72+
@Override
73+
public String toString() {
74+
return realKey.toString();
75+
}
76+
77+
}

src/main/java/org/jruby/ext/openssl/impl/PKey.java

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import java.io.IOException;
3131
import java.math.BigInteger;
3232

33-
import java.security.GeneralSecurityException;
3433
import java.security.KeyFactory;
3534
import java.security.KeyPair;
3635
import java.security.NoSuchAlgorithmException;
@@ -39,21 +38,37 @@
3938
import java.security.interfaces.DSAParams;
4039
import java.security.interfaces.DSAPrivateKey;
4140
import java.security.interfaces.DSAPublicKey;
41+
import java.security.interfaces.ECPrivateKey;
42+
import java.security.interfaces.ECPublicKey;
4243
import java.security.interfaces.RSAPrivateCrtKey;
4344
import java.security.interfaces.RSAPublicKey;
4445
import java.security.spec.DSAPrivateKeySpec;
4546
import java.security.spec.DSAPublicKeySpec;
47+
import java.security.spec.ECParameterSpec;
48+
import java.security.spec.ECPrivateKeySpec;
4649
import java.security.spec.InvalidKeySpecException;
4750
import java.security.spec.KeySpec;
51+
import java.security.spec.PKCS8EncodedKeySpec;
4852
import java.security.spec.RSAPrivateCrtKeySpec;
4953
import java.security.spec.RSAPublicKeySpec;
54+
import java.security.spec.X509EncodedKeySpec;
5055
import javax.crypto.spec.DHParameterSpec;
5156

5257
import org.bouncycastle.asn1.ASN1EncodableVector;
5358
import org.bouncycastle.asn1.ASN1InputStream;
5459
import org.bouncycastle.asn1.ASN1Integer;
60+
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
61+
import org.bouncycastle.asn1.ASN1Primitive;
5562
import org.bouncycastle.asn1.ASN1Sequence;
5663
import org.bouncycastle.asn1.DLSequence;
64+
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
65+
import org.bouncycastle.asn1.sec.ECPrivateKeyStructure;
66+
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
67+
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
68+
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
69+
import org.bouncycastle.jce.ECNamedCurveTable;
70+
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
71+
import org.bouncycastle.jce.spec.ECPublicKeySpec;
5772

5873
import org.jruby.ext.openssl.SecurityHelper;
5974

@@ -65,7 +80,8 @@
6580
*/
6681
public class PKey {
6782

68-
public static KeyPair readPrivateKey(byte[] input, String type) throws IOException, GeneralSecurityException {
83+
public static KeyPair readPrivateKey(final byte[] input, final String type)
84+
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
6985
KeySpec pubSpec; KeySpec privSpec;
7086
ASN1Sequence seq = (ASN1Sequence) new ASN1InputStream(input).readObject();
7187
if ( type.equals("RSA") ) {
@@ -80,7 +96,8 @@ public static KeyPair readPrivateKey(byte[] input, String type) throws IOExcepti
8096
pubSpec = new RSAPublicKeySpec(mod.getValue(), pubExp.getValue());
8197
privSpec = new RSAPrivateCrtKeySpec(mod.getValue(), pubExp.getValue(), privExp.getValue(), p1.getValue(), p2.getValue(), exp1.getValue(),
8298
exp2.getValue(), crtCoef.getValue());
83-
} else { // assume "DSA" for now.
99+
}
100+
else if ( type.equals("DSA") ) {
84101
ASN1Integer p = (ASN1Integer) seq.getObjectAt(1);
85102
ASN1Integer q = (ASN1Integer) seq.getObjectAt(2);
86103
ASN1Integer g = (ASN1Integer) seq.getObjectAt(3);
@@ -89,6 +106,12 @@ public static KeyPair readPrivateKey(byte[] input, String type) throws IOExcepti
89106
privSpec = new DSAPrivateKeySpec(x.getValue(), p.getValue(), q.getValue(), g.getValue());
90107
pubSpec = new DSAPublicKeySpec(y.getValue(), p.getValue(), q.getValue(), g.getValue());
91108
}
109+
else if ( type.equals("ECDSA") ) {
110+
return readECPrivateKey(input);
111+
}
112+
else {
113+
throw new IllegalStateException("unsupported type: " + type);
114+
}
92115
KeyFactory fact = SecurityHelper.getKeyFactory(type);
93116
return new KeyPair(fact.generatePublic(pubSpec), fact.generatePrivate(privSpec));
94117
}
@@ -147,7 +170,6 @@ public static KeyPair readRSAPrivateKey(final byte[] input)
147170

148171
public static KeyPair readRSAPrivateKey(final KeyFactory rsaFactory, final byte[] input)
149172
throws IOException, InvalidKeySpecException {
150-
// KeyFactory fact = SecurityHelper.getKeyFactory("RSA");
151173
ASN1Sequence seq = (ASN1Sequence) new ASN1InputStream(input).readObject();
152174
if ( seq.size() == 9 ) {
153175
BigInteger mod = ((ASN1Integer) seq.getObjectAt(1)).getValue();
@@ -232,6 +254,36 @@ public static DHParameterSpec readDHParameter(final byte[] input) throws IOExcep
232254
return new DHParameterSpec(p, g);
233255
}
234256

257+
public static KeyPair readECPrivateKey(final byte[] input)
258+
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
259+
return readECPrivateKey(SecurityHelper.getKeyFactory("ECDSA"), input);
260+
}
261+
262+
public static KeyPair readECPrivateKey(final KeyFactory ecFactory, final byte[] input)
263+
throws IOException, InvalidKeySpecException {
264+
try {
265+
ECPrivateKeyStructure pKey = new ECPrivateKeyStructure((ASN1Sequence) ASN1Primitive.fromByteArray(input));
266+
AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, pKey.getParameters());
267+
PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey.toASN1Primitive());
268+
SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(algId, pKey.getPublicKey().getBytes());
269+
PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privInfo.getEncoded());
270+
X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubInfo.getEncoded());
271+
//KeyFactory fact = KeyFactory.getInstance("ECDSA", provider);
272+
273+
ECPrivateKey privateKey = (ECPrivateKey) ecFactory.generatePrivate(privSpec);
274+
if ( algId.getParameters() instanceof ASN1ObjectIdentifier ) {
275+
privateKey = ECPrivateKeyWithName.wrap(privateKey, (ASN1ObjectIdentifier) algId.getParameters());
276+
}
277+
return new KeyPair(ecFactory.generatePublic(pubSpec), privateKey);
278+
}
279+
catch (ClassCastException ex) {
280+
throw new IOException("wrong ASN.1 object found in stream", ex);
281+
}
282+
//catch (Exception ex) {
283+
// throw new IOException("problem parsing EC private key: " + ex);
284+
//}
285+
}
286+
235287
public static byte[] toDerRSAKey(RSAPublicKey pubKey, RSAPrivateCrtKey privKey) throws IOException {
236288
ASN1EncodableVector vec = new ASN1EncodableVector();
237289
if ( pubKey != null && privKey == null ) {
@@ -268,7 +320,7 @@ public static byte[] toDerDSAKey(DSAPublicKey pubKey, DSAPrivateKey privKey) thr
268320
return new DLSequence(vec).getEncoded();
269321
}
270322
if ( privKey == null ) {
271-
throw new IllegalArgumentException("passed private key as well as public key are null");
323+
throw new IllegalArgumentException("private key as well as public key are null");
272324
}
273325
return privKey.getEncoded();
274326
}

0 commit comments

Comments
 (0)