Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 64 additions & 23 deletions src/main/java/org/jruby/ext/openssl/ASN1.java
Original file line number Diff line number Diff line change
Expand Up @@ -421,43 +421,32 @@ static String ln2oid(final Ruby runtime, final String ln) {
}

static Integer oid2nid(final Ruby runtime, final ASN1ObjectIdentifier oid) {
return oidToNid(runtime).get(oid);
final Integer nid = oidToNid(runtime).get(oid);
return nid == null ? ASN1Registry.oid2nid(oid) : nid;
}

static String o2a(final Ruby runtime, final ASN1ObjectIdentifier oid) {
return o2a(runtime, oid, false);
}

static String o2a(final Ruby runtime, final ASN1ObjectIdentifier oid, final boolean silent) {
Integer nid = oidToNid(runtime).get(oid);
if ( nid != null ) {
final String name = nid2ln(runtime, nid, false);
return name == null ? nid2sn(runtime, nid, false) : name;
}
nid = ASN1Registry.oid2nid(oid);
final Integer nid = oid2nid(runtime, oid);
if ( nid == null ) {
if ( silent ) return null;
throw new NullPointerException("nid not found for oid = '" + oid + "' (" + runtime + ")");
}
final String name = nid2ln(runtime, nid, false);
if ( name != null ) return name;
return nid2sn(runtime, nid, true);
final String name = nid2ln(runtime, nid);
return name == null ? nid2sn(runtime, nid) : name;
}

static String oid2name(final Ruby runtime, final ASN1ObjectIdentifier oid, final boolean silent) {
Integer nid = oidToNid(runtime).get(oid);
if ( nid != null ) {
final String name = nid2sn(runtime, nid, false);
return name == null ? nid2ln(runtime, nid, false) : name;
}
nid = ASN1Registry.oid2nid(oid);
final Integer nid = oid2nid(runtime, oid);
if ( nid == null ) {
if ( silent ) return null;
throw new NullPointerException("nid not found for oid = '" + oid + "' (" + runtime + ")");
}
final String name = nid2sn(runtime, nid, false);
if ( name != null ) return name;
return nid2ln(runtime, nid, true);
return name == null ? nid2ln(runtime, nid) : name;
/*
if ( nid == null ) nid = ASN1Registry.oid2nid(oid);
if ( nid == null ) {
Expand Down Expand Up @@ -1841,6 +1830,17 @@ byte[] toDER(final ThreadContext context) throws IOException {
return toDERInternal(context, false, false, string);
}

// Special behavior: Encoding universal types with non-default 'tag'
// attribute and nil tagging method - replace the tag byte with the custom tag.
if ( !isTagged() && isUniversal(context) ) {
final IRubyObject defTag = defaultTag();
if ( !defTag.isNil() && getTag(context) != RubyNumeric.fix2int(defTag) ) {
final byte[] encoded = toASN1Primitive(context).toASN1Primitive().getEncoded(ASN1Encoding.DER);
encoded[0] = (byte) getTag(context);
return encoded;
}
}

return toASN1(context).toASN1Primitive().getEncoded(ASN1Encoding.DER);
}

Expand Down Expand Up @@ -2033,11 +2033,18 @@ ASN1Encodable toASN1(final ThreadContext context) {
if ( isInfiniteLength() ) return super.toASN1(context);

if ( isSequence() ) {
return new DERSequence( toASN1EncodableVector(context) );
final ASN1Encodable seq = new DERSequence( toASN1EncodableVector(context) );
if ( isTagged() ) {
return new DERTaggedObject(isExplicitTagging(), getTagClass(context), getTag(context), seq);
}
return seq;
}
if ( isSet() ) {
return new DLSet( toASN1EncodableVector(context) ); // return new BERSet(values);
//return ASN1Set.getInstance(toASN1TaggedObject(context), isExplicitTagging());
final ASN1Encodable set = new DLSet( toASN1EncodableVector(context) );
if ( isTagged() ) {
return new DERTaggedObject(isExplicitTagging(), getTagClass(context), getTag(context), set);
}
return set;
}
switch ( getTag(context) ) { // "raw" Constructive ?!?
case OCTET_STRING:
Expand Down Expand Up @@ -2069,10 +2076,10 @@ byte[] toDER(final ThreadContext context) throws IOException {

if ( isIndefiniteLength ) {
if ( isSequence() || tagNo == SEQUENCE ) {
return sequenceToDER(context);
return applyIndefiniteTagging(context, sequenceToDER(context));
}
if ( isSet() || tagNo == SET) {
return setToDER(context);
return applyIndefiniteTagging(context, setToDER(context));
}
// "raw" Constructive
switch ( getTag(context) ) {
Expand All @@ -2094,6 +2101,18 @@ byte[] toDER(final ThreadContext context) throws IOException {
return toDERInternal(context, true, isIndefiniteLength, valueAsArray(context));
}

// Special behavior: Encoding universal types with non-default 'tag'
// attribute and nil tagging method - replace the tag byte with the custom tag,
// preserving the CONSTRUCTED bit.
if ( !isTagged() && isUniversal(context) ) {
final IRubyObject defTag = defaultTag();
if ( !defTag.isNil() && tagNo != RubyNumeric.fix2int(defTag) ) {
final byte[] encoded = toASN1(context).toASN1Primitive().getEncoded(ASN1Encoding.DER);
encoded[0] = (byte) (BERTags.CONSTRUCTED | tagNo);
return encoded;
}
}

return super.toDER(context);
}

Expand Down Expand Up @@ -2140,6 +2159,28 @@ private byte[] setToDER(final ThreadContext context) throws IOException {
return new BERSet(values).toASN1Primitive().getEncoded();
}

// Applies EXPLICIT or IMPLICIT tagging to an already-encoded indefinite-length
// Sequence or Set byte array. For IMPLICIT, replaces the tag byte in-place.
// For EXPLICIT, wraps the inner bytes with an outer tag + indefinite-length header
// and appends the required outer EOC (0x00 0x00).
private byte[] applyIndefiniteTagging(final ThreadContext context, final byte[] innerBytes) throws IOException {
if ( !isTagged() ) return innerBytes;
final int tag = getTag(context);
final int tagClass = getTagClass(context);
if ( isImplicitTagging() ) {
innerBytes[0] = (byte) (tagClass | BERTags.CONSTRUCTED | tag);
return innerBytes;
} else { // EXPLICIT
final ByteArrayOutputStream out = new ByteArrayOutputStream(innerBytes.length + 4);
writeDERIdentifier(tag, tagClass | BERTags.CONSTRUCTED, out);
out.write(0x80); // indefinite length
out.write(innerBytes);
out.write(0x00); // outer EOC
out.write(0x00);
return out.toByteArray();
}
}

private ASN1EncodableVector toASN1EncodableVector(final ThreadContext context) {
final ASN1EncodableVector vec = new ASN1EncodableVector();
final IRubyObject value = value(context);
Expand Down
7 changes: 3 additions & 4 deletions src/main/java/org/jruby/ext/openssl/HMAC.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,9 @@ static void createHMAC(final Ruby runtime, final RubyModule OpenSSL, final RubyC
HMAC.defineAnnotatedMethods(HMAC.class);
}

private static Mac getMacInstance(final String algorithmName) throws NoSuchAlgorithmException {
// final String algorithmSuffix = algorithmName.replaceAll("-", "");
static Mac getMacInstance(final String algorithmName) throws NoSuchAlgorithmException {
final StringBuilder algName = new StringBuilder(5 + algorithmName.length());
algName.append("HMAC"); // .append(algorithmSuffix);
algName.append("HMAC");
for ( int i = 0; i < algorithmName.length(); i++ ) {
char c = algorithmName.charAt(i);
if ( c != '-' ) algName.append(c);
Expand Down Expand Up @@ -199,7 +198,7 @@ private byte[] getSignatureBytes() {
return mac.doFinal();
}

private static String getDigestAlgorithmName(final IRubyObject digest) {
static String getDigestAlgorithmName(final IRubyObject digest) {
if ( digest instanceof Digest ) {
return ((Digest) digest).getShortAlgorithm();
}
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/org/jruby/ext/openssl/KDF.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;

import org.jruby.*;
import org.jruby.anno.JRubyMethod;
Expand All @@ -50,6 +51,7 @@ static void createKDF(final Ruby runtime, final RubyModule OpenSSL, final RubyCl
}

private static final String[] PBKDF2_ARGS = new String[] { "salt", "iterations", "length", "hash" };
private static final String[] HKDF_ARGS = new String[] { "salt", "info", "length", "hash" };

@JRubyMethod(module = true) // pbkdf2_hmac(pass, salt:, iterations:, length:, hash:)
public static IRubyObject pbkdf2_hmac(ThreadContext context, IRubyObject self, IRubyObject pass, IRubyObject opts) {
Expand All @@ -63,6 +65,61 @@ public static IRubyObject pbkdf2_hmac(ThreadContext context, IRubyObject self, I
}
}

@JRubyMethod(module = true) // hkdf(ikm, salt:, info:, length:, hash:)
public static IRubyObject hkdf(ThreadContext context, IRubyObject self, IRubyObject ikm, IRubyObject opts) {
IRubyObject[] args = extractKeywordArgs(context, (RubyHash) opts, HKDF_ARGS, 0);
try {
return hkdfImpl(context.runtime, ikm, args);
}
catch (NoSuchAlgorithmException|InvalidKeyException e) {
throw newKDFError(context.runtime, e.getMessage());
}
}

static RubyString hkdfImpl(final Ruby runtime, final IRubyObject ikmArg, final IRubyObject[] args)
throws NoSuchAlgorithmException, InvalidKeyException {
final byte[] ikm = ikmArg.convertToString().getBytes();
final byte[] salt = args[0].convertToString().getBytes();
final byte[] info = args[1].convertToString().getBytes();

final long length = RubyNumeric.num2long(args[2]);
if (length < 0) throw runtime.newArgumentError("length must be non-negative");

final Mac mac = getMac(args[3]);
final int macLength = mac.getMacLength();
if (length > 255L * macLength) {
throw newKDFError(runtime, "length must be <= 255 * HashLen");
}

mac.init(new SimpleSecretKey(mac.getAlgorithm(), salt));
final byte[] prk = mac.doFinal(ikm);

mac.init(new SimpleSecretKey(mac.getAlgorithm(), prk));

final byte[] okm = new byte[(int) length];
byte[] block = new byte[0];
int offset = 0;

for (int i = 1; offset < okm.length; i++) {
if (block.length > 0) mac.update(block);
if (info.length > 0) mac.update(info);
mac.update((byte) i);

block = mac.doFinal();

final int copyLength = Math.min(block.length, okm.length - offset);
System.arraycopy(block, 0, okm, offset, copyLength);
offset += copyLength;
}

return StringHelper.newString(runtime, okm);
}

private static Mac getMac(final IRubyObject digest) throws NoSuchAlgorithmException {
final String digestAlg = HMAC.getDigestAlgorithmName(digest);
return HMAC.getMacInstance(digestAlg);
}

static RaiseException newKDFError(Ruby runtime, String message) {
return Utils.newError(runtime, _KDF(runtime).getClass("KDFError"), message);
}
Expand Down
34 changes: 28 additions & 6 deletions src/main/java/org/jruby/ext/openssl/PKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import org.jruby.ext.openssl.x509store.PEMInputOutput;

import static org.jruby.ext.openssl.OpenSSL.*;
import static org.jruby.ext.openssl.Cipher._Cipher;

/**
* @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
Expand Down Expand Up @@ -126,13 +127,22 @@ public static IRubyObject read(final ThreadContext context, IRubyObject recv, IR

final RubyString str = readInitArg(context, data);
KeyPair keyPair;
// d2i_PrivateKey_bio
// d2i_PrivateKey_bio (PEM formats: RSA PRIVATE KEY, DSA PRIVATE KEY, PRIVATE KEY, ENCRYPTED PRIVATE KEY)
try {
keyPair = readPrivateKey(str, pass);
} catch (IOException e) {
debugStackTrace(runtime, "PKey readPrivateKey", e); /* ignore */
keyPair = null;
}
// DER-encoded PKCS#8 PrivateKeyInfo or EncryptedPrivateKeyInfo
if (keyPair == null) {
try {
final byte[] derInput = str.getBytes();
keyPair = PEMInputOutput.readPrivateKeyFromDER(derInput, pass);
} catch (IOException e) {
debugStackTrace(runtime, "PKey readPrivateKeyFromDER", e); /* ignore */
}
}
// PEM_read_bio_PrivateKey
if (keyPair != null) {
final String alg = getAlgorithm(keyPair);
Expand Down Expand Up @@ -278,8 +288,7 @@ public IRubyObject verify(IRubyObject digest, IRubyObject sign, IRubyObject data
final Ruby runtime = getRuntime();
ByteList sigBytes = convertToString(runtime, sign, "OpenSSL::PKey::PKeyError", "invalid signature").getByteList();
ByteList dataBytes = convertToString(runtime, data, "OpenSSL::PKey::PKeyError", "invalid data").getByteList();
String digAlg = (digest instanceof Digest) ? ((Digest) digest).getShortAlgorithm() : digest.asJavaString();
final String algorithm = digAlg + "WITH" + getAlgorithm();
final String algorithm = getDigestAlgName(digest) + "WITH" + getAlgorithm();
try {
return runtime.newBoolean( verify(algorithm, getPublicKey(), dataBytes, sigBytes) );
}
Expand All @@ -294,6 +303,12 @@ public IRubyObject verify(IRubyObject digest, IRubyObject sign, IRubyObject data
}
}

static String getDigestAlgName(IRubyObject digest) {
if (digest.isNil()) return "SHA256";
if (digest instanceof Digest) return ((Digest) digest).getShortAlgorithm();
return digest.asJavaString();
}

static RubyString convertToString(final Ruby runtime, final IRubyObject str, final String errorType, final CharSequence errorMsg) {
try {
return str.convertToString();
Expand Down Expand Up @@ -395,9 +410,16 @@ static void addSplittedAndFormatted(StringBuilder result, CharSequence v) {
}

protected static CipherSpec cipherSpec(final IRubyObject cipher) {
if ( cipher != null && ! cipher.isNil() ) {
final Cipher c = (Cipher) cipher;
return new CipherSpec(c.getCipherInstance(), c.getName(), c.getKeyLength() * 8);
Cipher obj = null;
if (cipher instanceof RubyString) {
final Ruby runtime = cipher.getRuntime();
obj = new Cipher(runtime, _Cipher(runtime));
obj.initializeImpl(runtime, cipher.asString().toString());
} else if (cipher instanceof Cipher) {
obj = (Cipher) cipher;
}
if (obj != null) {
return new CipherSpec(obj.getCipherInstance(), obj.getName(), obj.getKeyLength() * 8);
}
return null;
}
Expand Down
Loading
Loading