Skip to content

Commit aa51ade

Browse files
committed
[feat] support reading DSA (public key) in full DER
1 parent 14af5e9 commit aa51ade

File tree

4 files changed

+224
-13
lines changed

4 files changed

+224
-13
lines changed

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

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,27 @@ public static PublicKey readDSAPublicKey(final byte[] input)
239239

240240
public static PublicKey readDSAPublicKey(final KeyFactory dsaFactory, final byte[] input)
241241
throws IOException, InvalidKeySpecException {
242-
ASN1Sequence seq = (ASN1Sequence) new ASN1InputStream(input).readObject();
243-
if ( seq.size() == 4 ) {
244-
BigInteger y = ((ASN1Integer) seq.getObjectAt(0)).getValue();
245-
BigInteger p = ((ASN1Integer) seq.getObjectAt(1)).getValue();
246-
BigInteger q = ((ASN1Integer) seq.getObjectAt(2)).getValue();
247-
BigInteger g = ((ASN1Integer) seq.getObjectAt(3)).getValue();
248-
return dsaFactory.generatePublic(new DSAPublicKeySpec(y, p, q, g));
242+
ASN1Sequence seq;
243+
ASN1Primitive obj = new ASN1InputStream(input).readObject();
244+
if (obj instanceof ASN1Sequence) {
245+
seq = (ASN1Sequence) obj;
246+
if (seq.size() == 4) {
247+
BigInteger y = ((ASN1Integer) seq.getObjectAt(0)).getValue();
248+
BigInteger p = ((ASN1Integer) seq.getObjectAt(1)).getValue();
249+
BigInteger q = ((ASN1Integer) seq.getObjectAt(2)).getValue();
250+
BigInteger g = ((ASN1Integer) seq.getObjectAt(3)).getValue();
251+
return dsaFactory.generatePublic(new DSAPublicKeySpec(y, p, q, g));
252+
} else if (seq.size() == 2 && seq.getObjectAt(1) instanceof DERBitString) {
253+
ASN1Integer y = (ASN1Integer)
254+
new ASN1InputStream(((DERBitString) seq.getObjectAt(1)).getBytes()).readObject();
255+
seq = (ASN1Sequence) ((ASN1Sequence) seq.getObjectAt(0)).getObjectAt(1);
256+
BigInteger p = ((ASN1Integer) seq.getObjectAt(0)).getValue();
257+
BigInteger q = ((ASN1Integer) seq.getObjectAt(1)).getValue();
258+
BigInteger g = ((ASN1Integer) seq.getObjectAt(2)).getValue();
259+
//System.out.println(y);
260+
//System.out.println(p);
261+
return dsaFactory.generatePublic(new DSAPublicKeySpec(y.getPositiveValue(), p, q, g));
262+
}
249263
}
250264
return null;
251265
}

src/main/java/org/jruby/ext/openssl/x509store/PEMInputOutput.java

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import java.security.interfaces.ECPublicKey;
5757
import java.security.interfaces.RSAPublicKey;
5858
import java.security.interfaces.RSAPrivateCrtKey;
59+
import java.security.spec.DSAPublicKeySpec;
5960
import java.security.spec.ECParameterSpec;
6061
import java.security.spec.InvalidKeySpecException;
6162
import java.security.spec.KeySpec;
@@ -64,6 +65,7 @@
6465
import java.security.spec.X509EncodedKeySpec;
6566
import java.util.ArrayList;
6667
import java.util.Collections;
68+
import java.util.Enumeration;
6769
import java.util.List;
6870

6971
import java.util.StringTokenizer;
@@ -466,9 +468,17 @@ public static DSAPublicKey readDSAPubKey(Reader in) throws IOException {
466468
final String BEG_STRING_DSA_PUBLIC = BEF_G + PEM_STRING_DSA_PUBLIC;
467469
final BufferedReader reader = makeBuffered(in); String line;
468470
while ( ( line = reader.readLine() ) != null ) {
469-
if ( line.indexOf(BEG_STRING_DSA_PUBLIC) != -1 ) {
471+
if ( line.indexOf(BEG_STRING_PUBLIC) != -1 ) {
470472
try {
471-
return (DSAPublicKey) readPublicKey(reader, "DSA", BEF_E + PEM_STRING_DSA_PUBLIC);
473+
return readDSAPublicKey(reader, BEF_E + PEM_STRING_PUBLIC);
474+
}
475+
catch (Exception e) {
476+
throw mapReadException("problem creating DSA public key: ", e);
477+
}
478+
}
479+
else if ( line.indexOf(BEG_STRING_DSA_PUBLIC) != -1 ) {
480+
try {
481+
return readDSAPublicKey(reader, BEF_E + PEM_STRING_DSA_PUBLIC);
472482
}
473483
catch (Exception e) {
474484
throw mapReadException("problem creating DSA public key: ", e);
@@ -558,7 +568,7 @@ public static RSAPublicKey readRSAPublicKey(Reader in, char[] f)
558568
throw mapReadException("problem creating RSA public key: ", e);
559569
}
560570
}
561-
else if ( line.indexOf(BEF_G+PEM_STRING_RSA_PUBLIC) != -1 ) {
571+
else if ( line.indexOf(BEF_G + PEM_STRING_RSA_PUBLIC) != -1 ) {
562572
try {
563573
return (RSAPublicKey) readPublicKey(reader, "RSA", BEF_E + PEM_STRING_RSA_PUBLIC);
564574
}
@@ -1170,9 +1180,7 @@ private static RSAPublicKey readRSAPublicKey(BufferedReader in, String endMarker
11701180
Object asnObject = new ASN1InputStream(readBase64Bytes(in, endMarker)).readObject();
11711181
ASN1Sequence sequence = (ASN1Sequence) asnObject;
11721182
org.bouncycastle.asn1.pkcs.RSAPublicKey rsaPubStructure = org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(sequence);
1173-
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(
1174-
rsaPubStructure.getModulus(),
1175-
rsaPubStructure.getPublicExponent());
1183+
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(rsaPubStructure.getModulus(), rsaPubStructure.getPublicExponent());
11761184

11771185
try {
11781186
return (RSAPublicKey) SecurityHelper.getKeyFactory("RSA").generatePublic(keySpec);
@@ -1182,6 +1190,26 @@ private static RSAPublicKey readRSAPublicKey(BufferedReader in, String endMarker
11821190
return null;
11831191
}
11841192

1193+
private static DSAPublicKey readDSAPublicKey(BufferedReader in, String endMarker) throws IOException {
1194+
Object asnObject = new ASN1InputStream(readBase64Bytes(in, endMarker)).readObject();
1195+
Enumeration seq = ((ASN1Sequence) asnObject).getObjects();
1196+
ASN1Integer y = ASN1Integer.getInstance(seq.nextElement());
1197+
ASN1Integer p = ASN1Integer.getInstance(seq.nextElement());
1198+
ASN1Integer q = ASN1Integer.getInstance(seq.nextElement());
1199+
ASN1Integer g = ASN1Integer.getInstance(seq.nextElement());
1200+
1201+
DSAPublicKeySpec keySpec = new DSAPublicKeySpec(
1202+
y.getPositiveValue(), p.getPositiveValue(), q.getPositiveValue(), g.getPositiveValue()
1203+
);
1204+
1205+
try {
1206+
return (DSAPublicKey) SecurityHelper.getKeyFactory("DSA").generatePublic(keySpec);
1207+
}
1208+
catch (NoSuchAlgorithmException e) { /* ignore */ }
1209+
catch (InvalidKeySpecException e) { /* ignore */ }
1210+
return null;
1211+
}
1212+
11851213
private static PublicKey readPublicKey(byte[] input, String alg, String endMarker) throws IOException {
11861214
KeySpec keySpec = new X509EncodedKeySpec(input);
11871215
try {

src/test/ruby/dsa/test_dsa.rb

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,165 @@ def test_dsa_sys_sign_verify
7171
assert dsa.sysverify(digest, sig).eql?(true)
7272
end
7373

74+
def test_DSAPrivateKey
75+
# OpenSSL DSAPrivateKey format; similar to RSAPrivateKey
76+
dsa512 = Fixtures.pkey("dsa512")
77+
asn1 = OpenSSL::ASN1::Sequence([
78+
OpenSSL::ASN1::Integer(0),
79+
OpenSSL::ASN1::Integer(dsa512.p),
80+
OpenSSL::ASN1::Integer(dsa512.q),
81+
OpenSSL::ASN1::Integer(dsa512.g),
82+
OpenSSL::ASN1::Integer(dsa512.pub_key),
83+
OpenSSL::ASN1::Integer(dsa512.priv_key)
84+
])
85+
key = OpenSSL::PKey::DSA.new(asn1.to_der)
86+
assert_predicate key, :private?
87+
assert_same_dsa dsa512, key
88+
89+
pem = <<~EOF
90+
-----BEGIN DSA PRIVATE KEY-----
91+
MIH4AgEAAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgTYiEEHaOYhkIxv0Ok
92+
RZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB4DZGH7UyarcaGy6D
93+
AkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqoji3/lHdKoVdTQNuR
94+
S/m6DlCwhjRjiQ/lBRgCLCcaAkEAjN891JBjzpMj4bWgsACmMggFf57DS0Ti+5++
95+
Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S
96+
55jreJD3Se3slps=
97+
-----END DSA PRIVATE KEY-----
98+
EOF
99+
key = OpenSSL::PKey::DSA.new(pem)
100+
assert_same_dsa dsa512, key
101+
102+
assert_equal asn1.to_der, dsa512.to_der
103+
assert_equal pem, dsa512.export
104+
end
105+
106+
def test_DSAPrivateKey_encrypted
107+
# key = abcdef
108+
dsa512 = Fixtures.pkey("dsa512")
109+
pem = <<~EOF
110+
-----BEGIN DSA PRIVATE KEY-----
111+
Proc-Type: 4,ENCRYPTED
112+
DEK-Info: AES-128-CBC,F8BB7BFC7EAB9118AC2E3DA16C8DB1D9
113+
114+
D2sIzsM9MLXBtlF4RW42u2GB9gX3HQ3prtVIjWPLaKBYoToRUiv8WKsjptfZuLSB
115+
74ZPdMS7VITM+W1HIxo/tjS80348Cwc9ou8H/E6WGat8ZUk/igLOUEII+coQS6qw
116+
QpuLMcCIavevX0gjdjEIkojBB81TYDofA1Bp1z1zDI/2Zhw822xapI79ZF7Rmywt
117+
OSyWzFaGipgDpdFsGzvT6//z0jMr0AuJVcZ0VJ5lyPGQZAeVBlbYEI4T72cC5Cz7
118+
XvLiaUtum6/sASD2PQqdDNpgx/WA6Vs1Po2kIUQIM5TIwyJI0GdykZcYm6xIK/ta
119+
Wgx6c8K+qBAIVrilw3EWxw==
120+
-----END DSA PRIVATE KEY-----
121+
EOF
122+
key = OpenSSL::PKey::DSA.new(pem, "abcdef")
123+
assert_same_dsa dsa512, key
124+
key = OpenSSL::PKey::DSA.new(pem) { "abcdef" }
125+
assert_same_dsa dsa512, key
126+
127+
cipher = OpenSSL::Cipher.new("aes-128-cbc")
128+
exported = dsa512.to_pem(cipher, "abcdef\0\1")
129+
assert_same_dsa dsa512, OpenSSL::PKey::DSA.new(exported, "abcdef\0\1")
130+
assert_raise(OpenSSL::PKey::DSAError) {
131+
OpenSSL::PKey::DSA.new(exported, "abcdef")
132+
}
133+
end
134+
135+
def test_PUBKEY
136+
dsa512 = Fixtures.pkey("dsa512")
137+
asn1 = OpenSSL::ASN1::Sequence([
138+
OpenSSL::ASN1::Sequence([
139+
OpenSSL::ASN1::ObjectId("DSA"),
140+
OpenSSL::ASN1::Sequence([
141+
OpenSSL::ASN1::Integer(dsa512.p),
142+
OpenSSL::ASN1::Integer(dsa512.q),
143+
OpenSSL::ASN1::Integer(dsa512.g)
144+
])
145+
]),
146+
OpenSSL::ASN1::BitString(OpenSSL::ASN1::Integer(dsa512.pub_key).to_der)
147+
])
148+
key = OpenSSL::PKey::DSA.new(asn1.to_der)
149+
assert_not_predicate key, :private?
150+
assert_same_dsa dup_public(dsa512), key
151+
152+
##
153+
der = "0\x81\xF10\x81\xA8\x06\a*\x86H\xCE8\x04\x010\x81\x9C\x02A\x00\xE6Px\x1A\xF10\x8E\xBB\f\x94`\xEA\x1A\xCCkn\xA7\x85F\x1E\xA8\xF4\xE5\xAD\xE8X\x13b!\x04\x1D\xA3\x98\x86B1\xBFC\xA4E\x93\xC37\x03\x86\xF7\xDE\xE6\x0E0g\xBC2u\x8B\x9E\x8E\x99-\xCCm9\xE8\xBF\x02\x15\x00\x98.o\xF6\x83\xAD \xC1\xE06F\x1F\xB52j\xB7\x1A\e.\x83\x02@}\x1D\xD1p\xFF{]\xF0\xAE%\xD4VG\xBF\xB4\xC2e\xE2wd\xB6\xFBt]\xD3Z\x81\x94?M7D*\xA8\x8E-\xFF\x94wJ\xA1WS@\xDB\x91K\xF9\xBA\x0EP\xB0\x864c\x89\x0F\xE5\x05\x18\x02,'\x1A\x03D\x00\x02A\x00\x8C\xDF=\xD4\x90c\xCE\x93#\xE1\xB5\xA0\xB0\x00\xA62\b\x05\x7F\x9E\xC3KD\xE2\xFB\x9F\xBECUA\xF2\xA9\t7\xBA\xC0\xEF\xFD\x87\xAC$w\x81;\x165\xBDX\x84\v'\x16\xCA\x1EF\xC7\x02\xF8\xBCW\xA1x\xBD8"
154+
pp OpenSSL::ASN1.decode(key.to_der) if $DEBUG
155+
assert_equal der, key.to_der
156+
157+
pem = <<~EOF
158+
-----BEGIN PUBLIC KEY-----
159+
MIHxMIGoBgcqhkjOOAQBMIGcAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgT
160+
YiEEHaOYhkIxv0OkRZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB
161+
4DZGH7UyarcaGy6DAkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqo
162+
ji3/lHdKoVdTQNuRS/m6DlCwhjRjiQ/lBRgCLCcaA0QAAkEAjN891JBjzpMj4bWg
163+
sACmMggFf57DS0Ti+5++Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxX
164+
oXi9OA==
165+
-----END PUBLIC KEY-----
166+
EOF
167+
key = OpenSSL::PKey::DSA.new(pem)
168+
assert_same_dsa dup_public(dsa512), key
169+
170+
##
171+
assert_equal der, key.to_der
172+
173+
dup_der = dup_public(dsa512).to_der
174+
# pp OpenSSL::ASN1.decode(dup_der)
175+
assert_equal asn1.to_der.size, dup_der.size
176+
assert_equal asn1.to_der.encoding, dup_der.encoding
177+
# TODO smt slightly weird with to_der:
178+
#assert_equal asn1.to_der, dup_der
179+
assert_equal asn1.value[1].value, OpenSSL::ASN1.decode(dup_der).value[1].value
180+
assert_equal asn1.value[0].value[0].value, OpenSSL::ASN1.decode(dup_der).value[0].value[0].value
181+
assert_equal asn1.value[0].value[1].value[0].value, OpenSSL::ASN1.decode(dup_der).value[0].value[1].value[0].value
182+
assert_equal asn1.value[0].value[1].value[1].value, OpenSSL::ASN1.decode(dup_der).value[0].value[1].value[1].value
183+
assert_equal asn1.value[0].value[1].value[2].value, OpenSSL::ASN1.decode(dup_der).value[0].value[1].value[2].value
184+
185+
assert_equal pem, dup_public(dsa512).export
186+
end
187+
188+
def test_read_DSAPublicKey_pem
189+
# NOTE: where is the standard? PKey::DSA.new can read only PEM
190+
p = 12260055936871293565827712385212529106400444521449663325576634579961635627321079536132296996623400607469624537382977152381984332395192110731059176842635699
191+
q = 979494906553787301107832405790107343409973851677
192+
g = 3731695366899846297271147240305742456317979984190506040697507048095553842519347835107669437969086119948785140453492839427038591924536131566350847469993845
193+
y = 10505239074982761504240823422422813362721498896040719759460296306305851824586095328615844661273887569281276387605297130014564808567159023649684010036304695
194+
pem = <<-EOF
195+
-----BEGIN DSA PUBLIC KEY-----
196+
MIHfAkEAyJSJ+g+P/knVcgDwwTzC7Pwg/pWs2EMd/r+lYlXhNfzg0biuXRul8VR4
197+
VUC/phySExY0PdcqItkR/xYAYNMbNwJBAOoV57X0FxKO/PrNa/MkoWzkCKV/hzhE
198+
p0zbFdsicw+hIjJ7S6Sd/FlDlo89HQZ2FuvWJ6wGLM1j00r39+F2qbMCFQCrkhIX
199+
SG+is37hz1IaBeEudjB2HQJAR0AloavBvtsng8obsjLb7EKnB+pSeHr/BdIQ3VH7
200+
fWLOqqkzFeRrYMDzUpl36XktY6Yq8EJYlW9pCMmBVNy/dQ==
201+
-----END DSA PUBLIC KEY-----
202+
EOF
203+
key = OpenSSL::PKey::DSA.new(pem)
204+
assert(key.public?)
205+
assert(!key.private?)
206+
assert_equal(p, key.p)
207+
assert_equal(q, key.q)
208+
assert_equal(g, key.g)
209+
assert_equal(y, key.pub_key)
210+
assert_equal(nil, key.priv_key)
211+
end
212+
213+
private
214+
215+
def assert_same_dsa(expected, key)
216+
check_component(expected, key, [:p, :q, :g, :pub_key, :priv_key])
217+
end
218+
219+
def check_component(base, test, keys)
220+
keys.each { |comp| assert_equal base.send(comp), test.send(comp) }
221+
end
222+
223+
def dup_public(key)
224+
case key
225+
when OpenSSL::PKey::DSA
226+
dsa = OpenSSL::PKey::DSA.new
227+
dsa.set_pqg(key.p, key.q, key.g)
228+
dsa.set_key(key.pub_key, nil)
229+
dsa
230+
else
231+
raise "unknown key type: #{key.class}"
232+
end
233+
end
234+
74235
end

src/test/ruby/fixtures/pkey/dsa512

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-----BEGIN DSA PRIVATE KEY-----
2+
MIH4AgEAAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgTYiEEHaOYhkIxv0Ok
3+
RZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB4DZGH7UyarcaGy6D
4+
AkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqoji3/lHdKoVdTQNuR
5+
S/m6DlCwhjRjiQ/lBRgCLCcaAkEAjN891JBjzpMj4bWgsACmMggFf57DS0Ti+5++
6+
Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S
7+
55jreJD3Se3slps=
8+
-----END DSA PRIVATE KEY-----

0 commit comments

Comments
 (0)