/***** BEGIN LICENSE BLOCK *****
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2006, 2007 Ola Bini <ola@ologix.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.ext.openssl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.text.ParseException;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.bouncycastle.asn1.ASN1Boolean;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1Enumerated;
import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Null;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.ASN1UTCTime;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.BEROctetString;
import org.bouncycastle.asn1.BERSequenceGenerator;
import org.bouncycastle.asn1.BERSet;
import org.bouncycastle.asn1.BERTags;
import org.bouncycastle.asn1.DERApplicationSpecific;
import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.DERBoolean;
import org.bouncycastle.asn1.DEREnumerated;
import org.bouncycastle.asn1.DERGeneralString;
import org.bouncycastle.asn1.DERGeneralizedTime;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERNumericString;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERT61String;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DERUTCTime;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.DERUniversalString;
import org.bouncycastle.asn1.DERVisibleString;
import org.bouncycastle.asn1.DLSet;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyBignum;
import org.jruby.RubyClass;
import org.jruby.RubyInteger;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyObject;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.RubyTime;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.ext.openssl.impl.ASN1Registry;
import static org.jruby.ext.openssl.OpenSSL.*;
import org.jruby.ext.openssl.util.ByteArrayOutputStream;
/**
* @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
*/
public class ASN1 {
private static Map<Ruby, Map<String, ASN1ObjectIdentifier>> SYM_TO_OID = new WeakHashMap<Ruby, Map<String, ASN1ObjectIdentifier>>(8);
private static Map<Ruby, Map<ASN1ObjectIdentifier, String>> OID_TO_SYM = new WeakHashMap<Ruby, Map<ASN1ObjectIdentifier, String>>(8);
private static Map<Ruby, Map<ASN1ObjectIdentifier, Integer>> OID_TO_NID = new WeakHashMap<Ruby, Map<ASN1ObjectIdentifier, Integer>>(8);
private static Map<Ruby, Map<Integer, ASN1ObjectIdentifier>> NID_TO_OID = new WeakHashMap<Ruby, Map<Integer, ASN1ObjectIdentifier>>(8);
private static Map<Ruby, Map<Integer, String>> NID_TO_SN = new WeakHashMap<Ruby, Map<Integer, String>>(8);
private static Map<Ruby, Map<Integer, String>> NID_TO_LN = new WeakHashMap<Ruby, Map<Integer, String>>(8);
@SuppressWarnings("unchecked")
private static synchronized void initMaps(final Ruby runtime) {
final int size = 200; final float fact = 1.0f;
SYM_TO_OID.put(runtime, new HashMap<String, ASN1ObjectIdentifier>(size, fact));
OID_TO_SYM.put(runtime, new HashMap<ASN1ObjectIdentifier, String>(size, fact));
OID_TO_NID.put(runtime, new HashMap<ASN1ObjectIdentifier, Integer>(size, fact));
NID_TO_OID.put(runtime, new HashMap<Integer, ASN1ObjectIdentifier>(size, fact));
NID_TO_SN.put(runtime, new HashMap<Integer, String>(size, fact));
NID_TO_LN.put(runtime, new HashMap<Integer, String>(size, fact));
defaultObjects(runtime);
}
private static void defaultObjects(final Ruby runtime) {
addObject(runtime, 0, null, null,"1.2.840.113549.1.12.1");
addObject(runtime, 1, null, "rsadsi","1.2.840.113549");
addObject(runtime, 2, null, "pkcs","1.2.840.113549.1");
addObject(runtime, 3, "MD2", "md2","1.2.840.113549.2.2");
addObject(runtime, 4, "MD5", "md5","1.2.840.113549.2.5");
addObject(runtime, 5, "RC4", "rc4","1.2.840.113549.3.4");
addObject(runtime, 6, null, "rsaEncryption","1.2.840.113549.1.1.1");
addObject(runtime, 7, "RSA-MD2", "md2WithRSAEncryption","1.2.840.113549.1.1.2");
addObject(runtime, 8, "RSA-MD5", "md5WithRSAEncryption","1.2.840.113549.1.1.4");
addObject(runtime, 9, "PBE-MD2-DES", "pbeWithMD2AndDES-CBC","1.2.840.113549.1.5.1");
addObject(runtime, 10, "PBE-MD5-DES", "pbeWithMD5AndDES-CBC","1.2.840.113549.1.5.3");
addObject(runtime, 11, null, "X500","2.5");
addObject(runtime, 12, null, "X509","2.5.4");
addObject(runtime, 13, "CN", "commonName","2.5.4.3");
addObject(runtime, 14, "C", "countryName","2.5.4.6");
addObject(runtime, 15, "L", "localityName","2.5.4.7");
addObject(runtime, 16, "ST", "stateOrProvinceName","2.5.4.8");
addObject(runtime, 17, "O", "organizationName","2.5.4.10");
addObject(runtime, 18, "OU", "organizationalUnitName","2.5.4.11");
addObject(runtime, 19, "RSA", "rsa","2.5.8.1.1");
addObject(runtime, 20, null, "pkcs7","1.2.840.113549.1.7");
addObject(runtime, 21, null, "pkcs7-data","1.2.840.113549.1.7.1");
addObject(runtime, 22, null, "pkcs7-signedData","1.2.840.113549.1.7.2");
addObject(runtime, 23, null, "pkcs7-envelopedData","1.2.840.113549.1.7.3");
addObject(runtime, 24, null, "pkcs7-signedAndEnvelopedData","1.2.840.113549.1.7.4");
addObject(runtime, 25, null, "pkcs7-digestData","1.2.840.113549.1.7.5");
addObject(runtime, 26, null, "pkcs7-encryptedData","1.2.840.113549.1.7.6");
addObject(runtime, 27, null, "pkcs3","1.2.840.113549.1.3");
addObject(runtime, 28, null, "dhKeyAgreement","1.2.840.113549.1.3.1");
addObject(runtime, 29, "DES-ECB", "des-ecb","1.3.14.3.2.6");
addObject(runtime, 30, "DES-CFB", "des-cfb","1.3.14.3.2.9");
addObject(runtime, 31, "DES-CBC", "des-cbc","1.3.14.3.2.7");
addObject(runtime, 32, "DES-EDE", "des-ede","1.3.14.3.2.17");
addObject(runtime, 33, "DES-EDE3", "des-ede3",null);
addObject(runtime, 34, "IDEA-CBC", "idea-cbc","1.3.6.1.4.1.188.7.1.1.2");
addObject(runtime, 35, "IDEA-CFB", "idea-cfb",null);
addObject(runtime, 36, "IDEA-ECB", "idea-ecb",null);
addObject(runtime, 37, "RC2-CBC", "rc2-cbc","1.2.840.113549.3.2");
addObject(runtime, 38, "RC2-ECB", "rc2-ecb",null);
addObject(runtime, 39, "RC2-CFB", "rc2-cfb",null);
addObject(runtime, 40, "RC2-OFB", "rc2-ofb",null);
addObject(runtime, 41, "SHA", "sha","1.3.14.3.2.18");
addObject(runtime, 42, "RSA-SHA", "shaWithRSAEncryption","1.3.14.3.2.15");
addObject(runtime, 43, "DES-EDE-CBC", "des-ede-cbc",null);
addObject(runtime, 44, "DES-EDE3-CBC", "des-ede3-cbc","1.2.840.113549.3.7");
addObject(runtime, 45, "DES-OFB", "des-ofb","1.3.14.3.2.8");
addObject(runtime, 46, "IDEA-OFB", "idea-ofb",null);
addObject(runtime, 47, null, "pkcs9","1.2.840.113549.1.9");
addObject(runtime, 48, null, "emailAddress","1.2.840.113549.1.9.1");
addObject(runtime, 49, null, "unstructuredName","1.2.840.113549.1.9.2");
addObject(runtime, 50, null, "contentType","1.2.840.113549.1.9.3");
addObject(runtime, 51, null, "messageDigest","1.2.840.113549.1.9.4");
addObject(runtime, 52, null, "signingTime","1.2.840.113549.1.9.5");
addObject(runtime, 53, null, "countersignature","1.2.840.113549.1.9.6");
addObject(runtime, 54, null, "challengePassword","1.2.840.113549.1.9.7");
addObject(runtime, 55, null, "unstructuredAddress","1.2.840.113549.1.9.8");
addObject(runtime, 56, null, "extendedCertificateAttributes","1.2.840.113549.1.9.9");
addObject(runtime, 57, "Netscape", "Netscape Communications Corp.","2.16.840.1.113730");
addObject(runtime, 58, "nsCertExt", "Netscape Certificate Extension","2.16.840.1.113730.1");
addObject(runtime, 59, "nsDataType", "Netscape Data Type","2.16.840.1.113730.2");
addObject(runtime, 60, "DES-EDE-CFB", "des-ede-cfb",null);
addObject(runtime, 61, "DES-EDE3-CFB", "des-ede3-cfb",null);
addObject(runtime, 62, "DES-EDE-OFB", "des-ede-ofb",null);
addObject(runtime, 63, "DES-EDE3-OFB", "des-ede3-ofb",null);
addObject(runtime, 64, "SHA1", "sha1","1.3.14.3.2.26");
addObject(runtime, 65, "RSA-SHA1", "sha1WithRSAEncryption","1.2.840.113549.1.1.5");
addObject(runtime, 66, "DSA-SHA", "dsaWithSHA","1.3.14.3.2.13");
addObject(runtime, 67, "DSA-old", "dsaEncryption-old","1.3.14.3.2.12");
addObject(runtime, 68, "PBE-SHA1-RC2-64", "pbeWithSHA1AndRC2-CBC","1.2.840.113549.1.5.11");
addObject(runtime, 69, null, "PBKDF2","1.2.840.113549.1.5.12");
addObject(runtime, 70, "DSA-SHA1-old", "dsaWithSHA1-old","1.3.14.3.2.27");
addObject(runtime, 71, "nsCertType", "Netscape Cert Type","2.16.840.1.113730.1.1");
addObject(runtime, 72, "nsBaseUrl", "Netscape Base Url","2.16.840.1.113730.1.2");
addObject(runtime, 73, "nsRevocationUrl", "Netscape Revocation Url","2.16.840.1.113730.1.3");
addObject(runtime, 74, "nsCaRevocationUrl", "Netscape CA Revocation Url","2.16.840.1.113730.1.4");
addObject(runtime, 75, "nsRenewalUrl", "Netscape Renewal Url","2.16.840.1.113730.1.7");
addObject(runtime, 76, "nsCaPolicyUrl", "Netscape CA Policy Url","2.16.840.1.113730.1.8");
addObject(runtime, 77, "nsSslServerName", "Netscape SSL Server Name","2.16.840.1.113730.1.12");
addObject(runtime, 78, "nsComment", "Netscape Comment","2.16.840.1.113730.1.13");
addObject(runtime, 79, "nsCertSequence", "Netscape Certificate Sequence","2.16.840.1.113730.2.5");
addObject(runtime, 80, "DESX-CBC", "desx-cbc",null);
addObject(runtime, 81, "id-ce", null,"2.5.29");
addObject(runtime, 82, "subjectKeyIdentifier", "X509v3 Subject Key Identifier","2.5.29.14");
addObject(runtime, 83, "keyUsage", "X509v3 Key Usage","2.5.29.15");
addObject(runtime, 84, "privateKeyUsagePeriod", "X509v3 Private Key Usage Period","2.5.29.16");
addObject(runtime, 85, "subjectAltName", "X509v3 Subject Alternative Name","2.5.29.17");
addObject(runtime, 86, "issuerAltName", "X509v3 Issuer Alternative Name","2.5.29.18");
addObject(runtime, 87, "basicConstraints", "X509v3 Basic Constraints","2.5.29.19");
addObject(runtime, 88, "crlNumber", "X509v3 CRL Number","2.5.29.20");
addObject(runtime, 89, "certificatePolicies", "X509v3 Certificate Policies","2.5.29.32");
addObject(runtime, 90, "authorityKeyIdentifier", "X509v3 Authority Key Identifier","2.5.29.35");
addObject(runtime, 91, "BF-CBC", "bf-cbc","1.3.6.1.4.1.3029.1.2");
addObject(runtime, 92, "BF-ECB", "bf-ecb",null);
addObject(runtime, 93, "BF-CFB", "bf-cfb",null);
addObject(runtime, 94, "BF-OFB", "bf-ofb",null);
addObject(runtime, 95, "MDC2", "mdc2","2.5.8.3.101");
addObject(runtime, 96, "RSA-MDC2", "mdc2withRSA","2.5.8.3.100");
addObject(runtime, 97, "RC4-40", "rc4-40",null);
addObject(runtime, 98, "RC2-40-CBC", "rc2-40-cbc",null);
addObject(runtime, 99, "G", "givenName","2.5.4.42");
addObject(runtime, 100, "S", "surname","2.5.4.4");
addObject(runtime, 101, "I", "initials","2.5.4.43");
addObject(runtime, 102, "UID", "uniqueIdentifier","2.5.4.45"); // BC prefers UID to map to userId ?!
addObject(runtime, 103, "crlDistributionPoints", "X509v3 CRL Distribution Points","2.5.29.31");
addObject(runtime, 104, "RSA-NP-MD5", "md5WithRSA","1.3.14.3.2.3");
addObject(runtime, 105, null, "serialNumber","2.5.4.5");
addObject(runtime, 106, "T", "title","2.5.4.12");
addObject(runtime, 107, "D", "description","2.5.4.13");
addObject(runtime, 108, "CAST5-CBC", "cast5-cbc","1.2.840.113533.7.66.10");
addObject(runtime, 109, "CAST5-ECB", "cast5-ecb",null);
addObject(runtime, 110, "CAST5-CFB", "cast5-cfb",null);
addObject(runtime, 111, "CAST5-OFB", "cast5-ofb",null);
addObject(runtime, 112, null, "pbeWithMD5AndCast5CBC","1.2.840.113533.7.66.12");
addObject(runtime, 113, "DSA-SHA1", "dsaWithSHA1","1.2.840.10040.4.3");
addObject(runtime, 114, "MD5-SHA1", "md5-sha1",null);
addObject(runtime, 115, "RSA-SHA1-2", "sha1WithRSA","1.3.14.3.2.29");
addObject(runtime, 116, "DSA", "dsaEncryption","1.2.840.10040.4.1");
addObject(runtime, 117, "RIPEMD160", "ripemd160","1.3.36.3.2.1");
addObject(runtime, 118, "RSA-RIPEMD160", "ripemd160WithRSA","1.3.36.3.3.1.2");
addObject(runtime, 119, "RC5-CBC", "rc5-cbc","1.2.840.113549.3.8");
addObject(runtime, 120, "RC5-ECB", "rc5-ecb",null);
addObject(runtime, 121, "RC5-CFB", "rc5-cfb",null);
addObject(runtime, 122, "RC5-OFB", "rc5-ofb",null);
addObject(runtime, 123, "RLE", "run length compression","1.1.1.1.666.1");
addObject(runtime, 124, "ZLIB", "zlib compression","1.1.1.1.666.2");
addObject(runtime, 125, "extendedKeyUsage", "X509v3 Extended Key Usage","2.5.29.37");
addObject(runtime, 126, "PKIX", null,"1.3.6.1.5.5.7");
addObject(runtime, 127, "id-kp", null,"1.3.6.1.5.5.7.3");
addObject(runtime, 128, "serverAuth", "TLS Web Server Authentication","1.3.6.1.5.5.7.3.1");
addObject(runtime, 129, "clientAuth", "TLS Web Client Authentication","1.3.6.1.5.5.7.3.2");
addObject(runtime, 130, "codeSigning", "Code Signing","1.3.6.1.5.5.7.3.3");
addObject(runtime, 131, "emailProtection", "E-mail Protection","1.3.6.1.5.5.7.3.4");
addObject(runtime, 132, "timeStamping", "Time Stamping","1.3.6.1.5.5.7.3.8");
addObject(runtime, 133, "msCodeInd", "Microsoft Individual Code Signing","1.3.6.1.4.1.311.2.1.21");
addObject(runtime, 134, "msCodeCom", "Microsoft Commercial Code Signing","1.3.6.1.4.1.311.2.1.22");
addObject(runtime, 135, "msCTLSign", "Microsoft Trust List Signing","1.3.6.1.4.1.311.10.3.1");
addObject(runtime, 136, "msSGC", "Microsoft Server Gated Crypto","1.3.6.1.4.1.311.10.3.3");
addObject(runtime, 137, "msEFS", "Microsoft Encrypted File System","1.3.6.1.4.1.311.10.3.4");
addObject(runtime, 138, "nsSGC", "Netscape Server Gated Crypto","2.16.840.1.113730.4.1");
addObject(runtime, 139, "deltaCRL", "X509v3 Delta CRL Indicator","2.5.29.27");
addObject(runtime, 140, "CRLReason", "CRL Reason Code","2.5.29.21");
addObject(runtime, 141, "invalidityDate", "Invalidity Date","2.5.29.24");
addObject(runtime, 142, "SXNetID", "Strong Extranet ID","1.3.101.1.4.1");
addObject(runtime, 143, "PBE-SHA1-RC4-128", "pbeWithSHA1And128BitRC4","1.2.840.113549.1.12.1.1");
addObject(runtime, 144, "PBE-SHA1-RC4-40", "pbeWithSHA1And40BitRC4","1.2.840.113549.1.12.1.2");
addObject(runtime, 145, "PBE-SHA1-3DES", "pbeWithSHA1And3-KeyTripleDES-CBC","1.2.840.113549.1.12.1.3");
addObject(runtime, 146, "PBE-SHA1-2DES", "pbeWithSHA1And2-KeyTripleDES-CBC","1.2.840.113549.1.12.1.4");
addObject(runtime, 147, "PBE-SHA1-RC2-128", "pbeWithSHA1And128BitRC2-CBC","1.2.840.113549.1.12.1.5");
addObject(runtime, 148, "PBE-SHA1-RC2-40", "pbeWithSHA1And40BitRC2-CBC","1.2.840.113549.1.12.1.6");
addObject(runtime, 149, null, "keyBag","1.2.840.113549.1.12.10.1.1");
addObject(runtime, 150, null, "pkcs8ShroudedKeyBag","1.2.840.113549.1.12.10.1.2");
addObject(runtime, 151, null, "certBag","1.2.840.113549.1.12.10.1.3");
addObject(runtime, 152, null, "crlBag","1.2.840.113549.1.12.10.1.4");
addObject(runtime, 153, null, "secretBag","1.2.840.113549.1.12.10.1.5");
addObject(runtime, 154, null, "safeContentsBag","1.2.840.113549.1.12.10.1.6");
addObject(runtime, 155, null, "PBES2","1.2.840.113549.1.5.13");
addObject(runtime, 156, null, "PBMAC1","1.2.840.113549.1.5.14");
addObject(runtime, 157, null, "hmacWithSHA1","1.2.840.113549.2.7");
addObject(runtime, 158, "id-qt-cps", "Policy Qualifier CPS","1.3.6.1.5.5.7.2.1");
addObject(runtime, 159, "id-qt-unotice", "Policy Qualifier User Notice","1.3.6.1.5.5.7.2.2");
addObject(runtime, 160, "RC2-64-CBC", "rc2-64-cbc",null);
addObject(runtime, 161, "SMIME-CAPS", "S/MIME Capabilities","1.2.840.113549.1.9.15");
addObject(runtime, 162, "PBE-MD2-RC2-64", "pbeWithMD2AndRC2-CBC","1.2.840.113549.1.5.4");
addObject(runtime, 163, "PBE-MD5-RC2-64", "pbeWithMD5AndRC2-CBC","1.2.840.113549.1.5.6");
addObject(runtime, 164, "PBE-SHA1-DES", "pbeWithSHA1AndDES-CBC","1.2.840.113549.1.5.10");
addObject(runtime, 165, "msExtReq", "Microsoft Extension Request","1.3.6.1.4.1.311.2.1.14");
addObject(runtime, 166, "extReq", "Extension Request","1.2.840.113549.1.9.14");
addObject(runtime, 167, "name", "name","2.5.4.41");
addObject(runtime, 168, "dnQualifier", "dnQualifier","2.5.4.46");
addObject(runtime, 169, "id-pe", null,"1.3.6.1.5.5.7.1");
addObject(runtime, 170, "id-ad", null,"1.3.6.1.5.5.7.48");
addObject(runtime, 171, "authorityInfoAccess", "Authority Information Access","1.3.6.1.5.5.7.1.1");
addObject(runtime, 172, "OCSP", "OCSP","1.3.6.1.5.5.7.48.1");
addObject(runtime, 173, "caIssuers", "CA Issuers","1.3.6.1.5.5.7.48.2");
addObject(runtime, 174, "OCSPSigning", "OCSP Signing","1.3.6.1.5.5.7.3.9");
addObject(runtime, 175, "AES-128-EBC", "aes-128-ebc","2.16.840.1.101.3.4.1.1");
addObject(runtime, 176, "AES-128-CBC", "aes-128-cbc","2.16.840.1.101.3.4.1.2");
addObject(runtime, 177, "AES-128-OFB", "aes-128-ofb","2.16.840.1.101.3.4.1.3");
addObject(runtime, 178, "AES-128-CFB", "aes-128-cfb","2.16.840.1.101.3.4.1.4");
addObject(runtime, 179, "AES-192-EBC", "aes-192-ebc","2.16.840.1.101.3.4.1.21");
addObject(runtime, 180, "AES-192-CBC", "aes-192-cbc","2.16.840.1.101.3.4.1.22");
addObject(runtime, 181, "AES-192-OFB", "aes-192-ofb","2.16.840.1.101.3.4.1.23");
addObject(runtime, 182, "AES-192-CFB", "aes-192-cfb","2.16.840.1.101.3.4.1.24");
addObject(runtime, 183, "AES-256-EBC", "aes-256-ebc","2.16.840.1.101.3.4.1.41");
addObject(runtime, 184, "AES-256-CBC", "aes-256-cbc","2.16.840.1.101.3.4.1.42");
addObject(runtime, 185, "AES-256-OFB", "aes-256-ofb","2.16.840.1.101.3.4.1.43");
addObject(runtime, 186, "AES-256-CFB", "aes-256-cfb","2.16.840.1.101.3.4.1.44");
addObject(runtime, 672, "SHA256", "sha256", "2.16.840.1.101.3.4.2.1");
addObject(runtime, 660, "street", "streetAddress", "2.5.4.9");
addObject(runtime, 391, "DC", "domainComponent", "0.9.2342.19200300.100.1.25");
//addObject(runtime, 509, null, "generationQualifier", "2.5.4.44");
//addObject(runtime, 510, null, "pseudonym", "2.5.4.65");
//addObject(runtime, 661, null, "postalCode", "2.5.4.17");
//addObject(runtime, 861, null, "postalAddress", "2.5.4.16");
// NOTE: left-overs from BC's org.bouncycastle.asn1.x509.X509Name
/*
DefaultLookUp.put("uid", UID);
DefaultLookUp.put("dn", DN_QUALIFIER);
DefaultLookUp.put("nameofbirth", NAME_AT_BIRTH);
DefaultLookUp.put("placeofbirth", PLACE_OF_BIRTH);
DefaultLookUp.put("dateofbirth", DATE_OF_BIRTH);gen
DefaultLookUp.put("countryofcitizenship", COUNTRY_OF_CITIZENSHIP);
DefaultLookUp.put("countryofresidence", COUNTRY_OF_RESIDENCE);
DefaultLookUp.put("gender", GENDER);
DefaultLookUp.put("businesscategory", BUSINESS_CATEGORY);
DefaultLookUp.put("telephonenumber", TELEPHONE_NUMBER);
*/
}
private static void addObject(final Ruby runtime, final int nid,
final String sn, final String ln, final String oid) {
if ( oid != null && ( sn != null || ln != null ) ) {
ASN1ObjectIdentifier objectId = new ASN1ObjectIdentifier(oid);
if ( sn != null ) {
symToOid(runtime).put(sn.toLowerCase(), objectId);
}
if ( ln != null ) {
symToOid(runtime).put(ln.toLowerCase(), objectId);
}
oidToSym(runtime).put(objectId, sn == null ? ln : sn);
oidToNid(runtime).put(objectId, nid);
nidToOid(runtime).put(nid, objectId);
nidToSn(runtime).put(nid, sn);
nidToLn(runtime).put(nid, ln);
}
}
private static Map<String, ASN1ObjectIdentifier> symToOid(final Ruby runtime) {
Map<String, ASN1ObjectIdentifier> map = SYM_TO_OID.get(runtime);
if ( map == null ) {
synchronized(ASN1.class) {
map = SYM_TO_OID.get(runtime);
if ( map == null ) {
initMaps(runtime);
map = SYM_TO_OID.get(runtime);
}
}
}
return map;
}
private static Map<ASN1ObjectIdentifier, String> oidToSym(final Ruby runtime) {
Map<ASN1ObjectIdentifier, String> map = OID_TO_SYM.get(runtime);
if ( map == null ) {
synchronized(ASN1.class) {
map = OID_TO_SYM.get(runtime);
if ( map == null ) {
initMaps(runtime);
map = OID_TO_SYM.get(runtime);
}
}
}
return map;
}
private static Map<Integer, ASN1ObjectIdentifier> nidToOid(final Ruby runtime) {
Map<Integer, ASN1ObjectIdentifier> map = NID_TO_OID.get(runtime);
if ( map == null ) {
synchronized(ASN1.class) {
map = NID_TO_OID.get(runtime);
if ( map == null ) {
initMaps(runtime);
map = NID_TO_OID.get(runtime);
}
}
}
return map;
}
private static Map<ASN1ObjectIdentifier, Integer> oidToNid(final Ruby runtime) {
Map<ASN1ObjectIdentifier, Integer> map = OID_TO_NID.get(runtime);
if ( map == null ) {
synchronized(ASN1.class) {
map = OID_TO_NID.get(runtime);
if ( map == null ) {
initMaps(runtime);
map = OID_TO_NID.get(runtime);
}
}
}
return map;
}
private static Map<Integer, String> nidToSn(final Ruby runtime) {
Map<Integer, String> map = NID_TO_SN.get(runtime);
if ( map == null ) {
synchronized(ASN1.class) {
map = NID_TO_SN.get(runtime);
if ( map == null ) {
initMaps(runtime);
map = NID_TO_SN.get(runtime);
}
}
}
return map;
}
private static Map<Integer, String> nidToLn(final Ruby runtime) {
Map<Integer, String> map = NID_TO_LN.get(runtime);
if ( map == null ) {
synchronized(ASN1.class) {
map = NID_TO_LN.get(runtime);
if ( map == null ) {
initMaps(runtime);
map = NID_TO_LN.get(runtime);
}
}
}
return map;
}
static String ln2oid(final Ruby runtime, final String ln) {
Map<String, ASN1ObjectIdentifier> map = symToOid(runtime);
final ASN1ObjectIdentifier val = map.get(ln);
if ( val == null ) {
throw new NullPointerException("oid not found for ln = '" + ln + "' (" + runtime + ")");
}
return val.getId();
}
static Integer oid2nid(final Ruby runtime, final ASN1ObjectIdentifier oid) {
return oidToNid(runtime).get(oid);
}
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);
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);
}
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);
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);
/*
if ( nid == null ) nid = ASN1Registry.oid2nid(oid);
if ( nid == null ) {
if ( silent ) return null;
throw new NullPointerException("nid not found for oid = '" + oid + "' (" + runtime + ")");
}
final String name = nid2sn(runtime, nid, true);
if ( name != null ) return name;
return nid2ln(runtime, nid, true); */
}
static String oid2name(final Ruby runtime, final String oid) {
return oid2name(runtime, new ASN1ObjectIdentifier(oid), false);
}
static String nid2sn(final Ruby runtime, final Integer nid) {
return nid2sn(runtime, nid, true);
}
private static String nid2sn(final Ruby runtime, final Integer nid, boolean fallback) {
final String ln = nidToSn(runtime).get(nid);
if ( ln == null && fallback ) return ASN1Registry.nid2sn(nid);
return ln;
}
static String nid2ln(final Ruby runtime, final Integer nid) {
return nid2ln(runtime, nid, true);
}
private static String nid2ln(final Ruby runtime, final Integer nid, boolean fallback) {
final String ln = nidToLn(runtime).get(nid);
if ( ln == null && fallback ) return ASN1Registry.nid2ln(nid);
return ln;
}
static String oid2Sym(final Ruby runtime, final ASN1ObjectIdentifier oid) {
return oid2Sym(runtime, oid, false);
}
static String oid2Sym(final Ruby runtime, final ASN1ObjectIdentifier oid, final boolean fallback) {
final String sym = getSymLookup(runtime).get(oid);
return ( sym == null && fallback ) ? ASN1Registry.oid2sym(oid) : sym;
}
static ASN1ObjectIdentifier sym2Oid(final Ruby runtime, final String name) {
return getOIDLookup(runtime).get(name);
}
private static Map<String, ASN1ObjectIdentifier> getOIDLookup(final Ruby runtime) {
return symToOid(runtime);
}
private static Map<ASN1ObjectIdentifier, String> getSymLookup(final Ruby runtime) {
return oidToSym(runtime);
}
private final static Object[][] ASN1_INFO = {
{ "EOC", null, "EndOfContent" }, // OpenSSL::ASN1::EOC (0)
{ "BOOLEAN", org.bouncycastle.asn1.DERBoolean.class, "Boolean" },
{ "INTEGER", org.bouncycastle.asn1.DERInteger.class, "Integer" },
{ "BIT_STRING", org.bouncycastle.asn1.DERBitString.class, "BitString" },
{ "OCTET_STRING", org.bouncycastle.asn1.DEROctetString.class, "OctetString" },
{ "NULL", org.bouncycastle.asn1.DERNull.class, "Null" },
// OpenSSL::ASN1::OBJECT (6) :
{ "OBJECT", org.bouncycastle.asn1.DERObjectIdentifier.class, "ObjectId" },
{ "OBJECT_DESCRIPTOR", null, null },
{ "EXTERNAL", null, null },
{ "REAL", null, null },
// OpenSSL::ASN1::ENUMERATED (10) :
{ "ENUMERATED", org.bouncycastle.asn1.DEREnumerated.class, "Enumerated" },
{ "EMBEDDED_PDV", null, null },
// OpenSSL::ASN1::UTF8STRING (12) :
{ "UTF8STRING", org.bouncycastle.asn1.DERUTF8String.class, "UTF8String" },
{ "RELATIVE_OID", null, null },
{ "[UNIVERSAL 14]", null, null },
{ "[UNIVERSAL 15]", null, null },
// OpenSSL::ASN1::SEQUENCE (16) :
// NOTE: org.bouncycastle.asn1.DERSequence does not have a getInstance
//{ "SEQUENCE", org.bouncycastle.asn1.ASN1Sequence.class, "Sequence" },
{ "SEQUENCE", org.bouncycastle.asn1.DERSequence.class, "Sequence" },
// OpenSSL::ASN1::SET (17) :
// NOTE: org.bouncycastle.asn1.DERSet does not have a getInstance
//{ "SET", org.bouncycastle.asn1.ASN1Set.class, "Set" },
{ "SET", org.bouncycastle.asn1.DERSet.class, "Set" },
{ "NUMERICSTRING", org.bouncycastle.asn1.DERNumericString.class, "NumericString" },
{ "PRINTABLESTRING", org.bouncycastle.asn1.DERPrintableString.class, "PrintableString" },
{ "T61STRING", org.bouncycastle.asn1.DERT61String.class, "T61String" },
{ "VIDEOTEXSTRING", null, "VideotexString" },
{ "IA5STRING", org.bouncycastle.asn1.DERIA5String.class, "IA5String" },
{ "UTCTIME", org.bouncycastle.asn1.DERUTCTime.class, "UTCTime" },
{ "GENERALIZEDTIME", org.bouncycastle.asn1.DERGeneralizedTime.class, "GeneralizedTime" },
{ "GRAPHICSTRING", null, "GraphicString" },
{ "ISO64STRING", null, "ISO64String" },
{ "GENERALSTRING", org.bouncycastle.asn1.DERGeneralString.class, "GeneralString" },
// OpenSSL::ASN1::UNIVERSALSTRING (28) :
{ "UNIVERSALSTRING", org.bouncycastle.asn1.DERUniversalString.class, "UniversalString" },
{ "CHARACTER_STRING", null, null },
// OpenSSL::ASN1::BMPSTRING (30) :
{ "BMPSTRING", org.bouncycastle.asn1.DERBMPString.class, "BMPString" }};
private final static Map<Class<?>, Integer> JCLASS_TO_ID = new HashMap<Class<?>, Integer>(24, 1);
private final static Map<String, Integer> RCLASS_TO_ID = new HashMap<String, Integer>(28, 1);
static {
for ( int i = 0; i < ASN1_INFO.length; i++ ) {
final Object[] info = ASN1_INFO[i];
if ( info[1] != null ) {
JCLASS_TO_ID.put((Class) info[1], Integer.valueOf(i));
}
if ( info[2] != null ) {
RCLASS_TO_ID.put((String) info[2], Integer.valueOf(i));
}
}
}
private final static int EOC = 0; // OpenSSL::ASN1::EOC (0)
//private final static int BOOLEAN = 1; // OpenSSL::ASN1::BOOLEAN (1)
//private final static int INTEGER = 2; // OpenSSL::ASN1::INTEGER (2)
private final static int BIT_STRING = 3; // OpenSSL::ASN1::BIT_STRING (3)
private final static int OCTET_STRING = 4; // OpenSSL::ASN1::OCTET_STRING (4)
//private final static int NULL = 5; // OpenSSL::ASN1::NULL (5)
//private final static int OBJECT = 6; // OpenSSL::ASN1::OBJECT (6)
//private final static int ENUMARATED = 10; // OpenSSL::ASN1::ENUMERATED (10)
//private final static int UTFSTRING = 12; // OpenSSL::ASN1::UTF8STRING (12)
private final static int SEQUENCE = 16; // OpenSSL::ASN1::SEQUENCE (16)
private final static int SET = 17; // OpenSSL::ASN1::SET (17)
//private final static int NUMERICSTRING = 18; // OpenSSL::ASN1::NUMERICSTRING (18)
// OpenSSL::ASN1::PRINTABLESTRING (19)
// OpenSSL::ASN1::T61STRING (20)
// OpenSSL::ASN1::VIDEOTEXSTRING (21)
// OpenSSL::ASN1::IA5STRING (22)
// OpenSSL::ASN1::UTCTIME (23)
// OpenSSL::ASN1::GENERALIZEDTIME (24)
// OpenSSL::ASN1::GRAPHICSTRING (25)
// OpenSSL::ASN1::ISO64STRING (26)
// OpenSSL::ASN1::GENERALSTRING (27)
// OpenSSL::ASN1::UNIVERSALSTRING (28)
// OpenSSL::ASN1::BMPSTRING (30)
private static Integer typeId(Class<?> type) {
Integer id = null;
while ( type != Object.class && id == null ) {
id = JCLASS_TO_ID.get(type);
if ( id == null ) type = type.getSuperclass();
}
return id; //return v == null ? -1 : v.intValue();
}
static Integer typeId(final ASN1Encodable obj) {
return typeId( obj.getClass() );
}
private static Integer typeId(final RubyClass metaClass) {
final String name = metaClass.getRealClass().getBaseName();
final Integer id = RCLASS_TO_ID.get(name);
return id == null ? null : id;
}
@SuppressWarnings("unchecked")
static Class<? extends ASN1Encodable> typeClass(final RubyClass metaClass) {
final Integer tag = typeId( metaClass );
if ( tag == null ) return null;
return (Class<? extends ASN1Encodable>) ASN1_INFO[tag][1];
}
@SuppressWarnings("unchecked")
static Class<? extends ASN1Encodable> typeClass(final int typeId) {
return (Class<? extends ASN1Encodable>) ASN1_INFO[typeId][1];
}
static ASN1Encodable typeInstance(Class<? extends ASN1Encodable> type, Object value)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Method getInstance = null;
try {
getInstance = type.getMethod("getInstance", Object.class);
}
catch (NoSuchMethodException e) {
Class superType = type.getSuperclass();
try {
if ( superType != Object.class ) {
getInstance = type.getSuperclass().getMethod("getInstance", Object.class);
}
}
catch (NoSuchMethodException e2) { }
if ( getInstance == null ) throw e;
}
return (ASN1Encodable) getInstance.invoke(null, value);
}
public static void createASN1(final Ruby runtime, final RubyModule OpenSSL) {
final RubyModule ASN1 = OpenSSL.defineModuleUnder("ASN1");
final RubyClass OpenSSLError = OpenSSL.getClass("OpenSSLError");
ASN1.defineClassUnder("ASN1Error", OpenSSLError, OpenSSLError.getAllocator());
ASN1.defineAnnotatedMethods(ASN1.class);
final RubyArray UNIVERSAL_TAG_NAME = runtime.newArray(ASN1_INFO.length);
for ( int i = 0; i < ASN1_INFO.length; i++ ) {
final String name = (String) ASN1_INFO[i][0];
if ( name.charAt(0) != '[' ) {
UNIVERSAL_TAG_NAME.append( runtime.newString(name) );
ASN1.setConstant( name, runtime.newFixnum(i) );
} else {
UNIVERSAL_TAG_NAME.append( runtime.getNil() );
}
}
ASN1.setConstant("UNIVERSAL_TAG_NAME", UNIVERSAL_TAG_NAME);
final ThreadContext context = runtime.getCurrentContext();
final ObjectAllocator asn1DataAllocator = ASN1Data.ALLOCATOR;
RubyClass _ASN1Data = ASN1.defineClassUnder("ASN1Data", runtime.getObject(), asn1DataAllocator);
_ASN1Data.addReadWriteAttribute(context, "value");
_ASN1Data.addReadWriteAttribute(context, "tag");
_ASN1Data.addReadWriteAttribute(context, "tag_class");
_ASN1Data.defineAnnotatedMethods(ASN1Data.class);
final ObjectAllocator primitiveAllocator = Primitive.ALLOCATOR;
RubyClass Primitive = ASN1.defineClassUnder("Primitive", _ASN1Data, primitiveAllocator);
Primitive.addReadWriteAttribute(context, "tagging");
Primitive.addReadAttribute(context, "infinite_length");
Primitive.defineAnnotatedMethods(Primitive.class);
final ObjectAllocator constructiveAllocator = Constructive.ALLOCATOR;
RubyClass Constructive = ASN1.defineClassUnder("Constructive", _ASN1Data, constructiveAllocator);
Constructive.includeModule( runtime.getModule("Enumerable") );
Constructive.addReadWriteAttribute(context, "tagging");
Constructive.addReadWriteAttribute(context, "infinite_length");
Constructive.defineAnnotatedMethods(Constructive.class);
ASN1.defineClassUnder("Boolean", Primitive, primitiveAllocator); // OpenSSL::ASN1::Boolean <=> value is a Boolean
ASN1.defineClassUnder("Integer", Primitive, primitiveAllocator); // OpenSSL::ASN1::Integer <=> value is a Number
ASN1.defineClassUnder("Null", Primitive, primitiveAllocator); // OpenSSL::ASN1::Null <=> value is always nil
ASN1.defineClassUnder("Object", Primitive, primitiveAllocator); // OpenSSL::ASN1::Object <=> value is a String
ASN1.defineClassUnder("Enumerated", Primitive, primitiveAllocator); // OpenSSL::ASN1::Enumerated <=> value is a Number
RubyClass BitString = ASN1.defineClassUnder("BitString", Primitive, primitiveAllocator);
BitString.addReadWriteAttribute(context, "unused_bits");
ASN1.defineClassUnder("OctetString", Primitive, primitiveAllocator);
ASN1.defineClassUnder("UTF8String", Primitive, primitiveAllocator);
ASN1.defineClassUnder("NumericString", Primitive, primitiveAllocator);
ASN1.defineClassUnder("PrintableString", Primitive, primitiveAllocator);
ASN1.defineClassUnder("T61String", Primitive, primitiveAllocator);
ASN1.defineClassUnder("VideotexString", Primitive, primitiveAllocator);
ASN1.defineClassUnder("IA5String", Primitive, primitiveAllocator);
ASN1.defineClassUnder("GraphicString", Primitive, primitiveAllocator);
ASN1.defineClassUnder("ISO64String", Primitive, primitiveAllocator);
ASN1.defineClassUnder("GeneralString", Primitive, primitiveAllocator);
ASN1.defineClassUnder("UniversalString", Primitive, primitiveAllocator);
ASN1.defineClassUnder("BMPString", Primitive, primitiveAllocator);
ASN1.defineClassUnder("UTCTime", Primitive, primitiveAllocator); // OpenSSL::ASN1::UTCTime <=> value is a Time
ASN1.defineClassUnder("GeneralizedTime", Primitive, primitiveAllocator); // OpenSSL::ASN1::GeneralizedTime <=> value is a Time
ASN1.defineClassUnder("EndOfContent", Primitive, primitiveAllocator); // OpenSSL::ASN1::EndOfContent <=> value is always nil
RubyClass ObjectId = ASN1.defineClassUnder("ObjectId", Primitive, primitiveAllocator);
ObjectId.defineAnnotatedMethods(ObjectId.class);
ASN1.defineClassUnder("Sequence", Constructive, Constructive.getAllocator());
ASN1.defineClassUnder("Set", Constructive, Constructive.getAllocator());
}
static ASN1ObjectIdentifier getObjectID(final Ruby runtime, final String nameOrOid)
throws IllegalArgumentException {
final String name = nameOrOid.toLowerCase();
ASN1ObjectIdentifier objectId = getOIDLookup(runtime).get( name );
if ( objectId != null ) return objectId;
final String objectIdStr = ASN1Registry.getOIDLookup().get( name );
if ( objectIdStr != null ) return toObjectID(objectIdStr, false);
return new ASN1ObjectIdentifier( nameOrOid );
}
static ASN1ObjectIdentifier toObjectID(final String oid, final boolean silent)
throws IllegalArgumentException {
try {
return new ASN1ObjectIdentifier(oid);
}
catch (IllegalArgumentException e) {
if ( silent ) return null;
throw e;
}
}
@JRubyMethod(name="Boolean", module=true, rest=true)
public static IRubyObject fact_Boolean(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "Boolean", args);
}
@JRubyMethod(name="Integer", module=true, rest=true)
public static IRubyObject fact_Integer(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "Integer", args);
}
@JRubyMethod(name="Enumerated", module=true, rest=true)
public static IRubyObject fact_Enumerated(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "Enumerated", args);
}
@JRubyMethod(name="BitString", module=true, rest=true)
public static IRubyObject fact_BitString(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "BitString", args);
}
@JRubyMethod(name="OctetString", module=true, rest=true)
public static IRubyObject fact_OctetString(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "OctetString", args);
}
@JRubyMethod(name="UTF8String", module=true, rest=true)
public static IRubyObject fact_UTF8String(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "UTF8String", args);
}
@JRubyMethod(name="NumericString", module=true, rest=true)
public static IRubyObject fact_NumericString(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "NumericString", args);
}
@JRubyMethod(name="PrintableString", module=true, rest=true)
public static IRubyObject fact_PrintableString(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "PrintableString", args);
}
@JRubyMethod(name="T61String", module=true, rest=true)
public static IRubyObject fact_T61String(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "T61String", args);
}
@JRubyMethod(name="VideotexString", module=true, rest=true)
public static IRubyObject fact_VideotexString(IRubyObject recv, IRubyObject[] args) {
return ((RubyModule)recv).getClass("VideotexString").callMethod(recv.getRuntime().getCurrentContext(),"new",args);
}
@JRubyMethod(name="IA5String", module=true, rest=true)
public static IRubyObject fact_IA5String(IRubyObject recv, IRubyObject[] args) {
return ((RubyModule)recv).getClass("IA5String").callMethod(recv.getRuntime().getCurrentContext(),"new",args);
}
@JRubyMethod(name="GraphicString", module=true, rest=true)
public static IRubyObject fact_GraphicString(IRubyObject recv, IRubyObject[] args) {
return ((RubyModule)recv).getClass("GraphicString").callMethod(recv.getRuntime().getCurrentContext(),"new",args);
}
@JRubyMethod(name="ISO64String", module=true, rest=true)
public static IRubyObject fact_ISO64String(IRubyObject recv, IRubyObject[] args) {
return ((RubyModule)recv).getClass("ISO64String").callMethod(recv.getRuntime().getCurrentContext(),"new",args);
}
@JRubyMethod(name="GeneralString", module=true, rest=true)
public static IRubyObject fact_GeneralString(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "GeneralString", args);
}
@JRubyMethod(name="UniversalString", module=true, rest=true)
public static IRubyObject fact_UniversalString(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "UniversalString", args);
}
@JRubyMethod(name="BMPString", module=true, rest=true)
public static IRubyObject fact_BMPString(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "BMPString", args);
}
@JRubyMethod(name="Nul", module=true, rest=true)
public static IRubyObject fact_Null(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "Null", args);
}
@JRubyMethod(name="ObjectId", module=true, rest=true)
public static IRubyObject fact_ObjectId(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "ObjectId", args);
}
@JRubyMethod(name="UTCTime", module=true, rest=true)
public static IRubyObject fact_UTCTime(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "UTCTime", args);
}
@JRubyMethod(name="GeneralizedTime", module=true, rest=true)
public static IRubyObject fact_GeneralizedTime(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "GeneralizedTime", args);
}
@JRubyMethod(name="Sequence", module=true, rest=true)
public static IRubyObject fact_Sequence(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "Sequence", args);
}
@JRubyMethod(name="Set", module=true, rest=true)
public static IRubyObject fact_Set(IRubyObject self, IRubyObject[] args) {
return callClassNew(self, "Set", args);
}
private static IRubyObject callClassNew(final IRubyObject self, final String className, final IRubyObject[] args) {
return ((RubyModule) self).getClass(className).callMethod(self.getRuntime().getCurrentContext(), "new", args);
}
public static class ObjectId {
@JRubyMethod(meta = true, rest = true)
public static IRubyObject register(final IRubyObject self, final IRubyObject[] args) {
final Ruby runtime = self.getRuntime();
final ASN1ObjectIdentifier derOid = new ASN1ObjectIdentifier( args[0].toString() );
final String a1 = args[1].toString();
final String a2 = args[2].toString();
synchronized(ASN1.class) {
Map<String, ASN1ObjectIdentifier> sym2oid = getOIDLookup(runtime);
sym2oid.put( a1.toLowerCase(), derOid );
sym2oid.put( a2.toLowerCase(), derOid );
getSymLookup(runtime).put( derOid, a1 );
}
return runtime.getTrue();
}
@JRubyMethod(name = { "sn", "short_name" })
public static RubyString sn(final ThreadContext context, final IRubyObject self) {
return name(context, self.callMethod(context, "value"), false);
}
@JRubyMethod(name = { "ln", "long_name" })
public static RubyString ln(final ThreadContext context, final IRubyObject self) {
return name(context, self.callMethod(context, "value"), true);
}
@JRubyMethod
public static RubyString oid(final ThreadContext context, final IRubyObject self) {
final Ruby runtime = context.runtime;
return runtime.newString( getObjectID(runtime, self.callMethod(context, "value").toString()).getId() );
}
private static RubyString name(final ThreadContext context, IRubyObject value,
final boolean longName) {
final Ruby runtime = context.runtime;
final String oid = value.toString(); // name or oid
Integer nid = null;
try {
nid = ASN1.oid2nid(runtime, ASN1.getObjectID(runtime, oid));
}
catch (IllegalArgumentException e) { /* ignored */ } // not an oid
if ( nid != null ) {
String val = longName ? nid2ln(runtime, nid) : nid2sn(runtime, nid);
if ( val != null ) return runtime.newString(val);
}
return value.asString();
}
} // ObjectId
static IRubyObject decodeObject(final ThreadContext context,
final RubyModule ASN1, final org.bouncycastle.asn1.ASN1Encodable obj)
throws IOException, IllegalArgumentException {
final Ruby runtime = context.runtime;
if ( obj instanceof ASN1Integer ) {
final BN val = BN.newBN(runtime, ((ASN1Integer) obj).getValue());
return ASN1.getClass("Integer").callMethod(context, "new", val);
}
if ( obj instanceof DERInteger ) {
final BN val = BN.newBN(runtime, ((DERInteger) obj).getValue());
return ASN1.getClass("Integer").callMethod(context, "new", val);
}
if ( obj instanceof DERBitString ) {
final DERBitString derObj = (DERBitString) obj;
RubyString str = runtime.newString( new ByteList(derObj.getBytes(), false) );
IRubyObject bitString = ASN1.getClass("BitString").callMethod(context, "new", str);
bitString.callMethod(context, "unused_bits=", runtime.newFixnum( derObj.getPadBits() ));
return bitString;
}
if ( obj instanceof ASN1String ) {
final Integer typeId = typeId( obj.getClass() );
String type = typeId == null ? null : (String) ( ASN1_INFO[typeId][2] );
final ByteList bytes;
if ( obj instanceof DERUTF8String ) {
if ( type == null ) type = "UTF8String";
bytes = new ByteList(((DERUTF8String) obj).getString().getBytes("UTF-8"), false);
}
else {
if ( type == null ) {
if ( obj instanceof DERNumericString ) {
type = "NumericString";
}
else if ( obj instanceof DERPrintableString ) {
type = "PrintableString";
}
else if ( obj instanceof DERIA5String ) {
type = "IA5String";
}
else if ( obj instanceof DERT61String ) {
type = "T61String";
}
else if ( obj instanceof DERGeneralString ) {
type = "GeneralString";
}
else if ( obj instanceof DERUniversalString ) {
type = "UniversalString";
}
else if ( obj instanceof DERBMPString ) {
type = "BMPString";
}
else {
// NOTE "VideotexString", "GraphicString", "ISO64String" not-handled in BC !
throw new IllegalArgumentException("could not handle ASN1 string type: " + obj + " (" + obj.getClass().getName() + ")");
}
}
bytes = ByteList.create(((ASN1String) obj).getString());
}
return ASN1.getClass(type).callMethod(context, "new", runtime.newString(bytes));
}
//if ( obj instanceof DEROctetString ) {
// byte[] octets = ((ASN1OctetString) obj).getOctets();
// if ( (octets[0] & 0xFF) == 0xD1 ) Thread.dumpStack();
//}
if ( obj instanceof ASN1OctetString ) {
final ByteList octets = new ByteList(((ASN1OctetString) obj).getOctets(), false);
// NOTE: sometimes MRI does include the tag but it really should not ;( !
//final ByteList octets = new ByteList(((ASN1OctetString) obj).getEncoded(ASN1Encoding.DER), false);
return ASN1.getClass("OctetString").callMethod(context, "new", runtime.newString(octets));
}
if ( obj instanceof ASN1Null ) {
return ASN1.getClass("Null").callMethod(context,"new", runtime.getNil());
}
if ( obj instanceof ASN1Boolean ) {
final boolean val = ((ASN1Boolean) obj).isTrue();
return ASN1.getClass("Boolean").callMethod(context, "new", runtime.newBoolean(val));
}
// DERBoolean extends ASN1Boolean only since 1.51 (<= 1.50 the other way around)
if ( obj instanceof DERBoolean ) {
final boolean val = ((DERBoolean) obj).isTrue();
return ASN1.getClass("Boolean").callMethod(context, "new", runtime.newBoolean(val));
}
if ( obj instanceof ASN1UTCTime ) {
final Date adjustedTime;
try { adjustedTime = ((ASN1UTCTime) obj).getAdjustedDate(); }
catch (ParseException e) { throw new IOException(e); }
final RubyTime time = RubyTime.newTime(runtime, adjustedTime.getTime());
return ASN1.getClass("UTCTime").callMethod(context,"new", time);
}
// NOTE: keep for BC versions compatibility ... extends ASN1UTCTime (since BC 1.51)
if ( obj instanceof DERUTCTime ) {
final Date adjustedTime;
try { adjustedTime = ((DERUTCTime) obj).getAdjustedDate(); }
catch (ParseException e) { throw new IOException(e); }
final RubyTime time = RubyTime.newTime(runtime, adjustedTime.getTime());
return ASN1.getClass("UTCTime").callMethod(context,"new", time);
}
if ( obj instanceof ASN1GeneralizedTime ) {
final Date generalTime;
try { generalTime = ((ASN1GeneralizedTime) obj).getDate(); }
catch (ParseException e) { throw new IOException(e); }
final RubyTime time = RubyTime.newTime(runtime, generalTime.getTime());
return ASN1.getClass("GeneralizedTime").callMethod(context,"new", time);
}
// NOTE: keep for BC versions compatibility ... extends ASN1GeneralizedTime (since BC 1.51)
if ( obj instanceof DERGeneralizedTime ) {
final Date generalTime;
try {
generalTime = ((DERGeneralizedTime) obj).getDate();
}
catch (ParseException e) { throw new IOException(e); }
final RubyTime time = RubyTime.newTime(runtime, generalTime.getTime());
return ASN1.getClass("GeneralizedTime").callMethod(context,"new", time);
}
if ( obj instanceof ASN1ObjectIdentifier ) {
final String objId = ((ASN1ObjectIdentifier) obj).getId();
return ASN1.getClass("ObjectId").callMethod(context, "new", runtime.newString(objId));
}
// ASN1ObjectIdentifier extends DERObjectIdentifier < 1.51
// DERObjectIdentifier extends ASN1ObjectIdentifier = 1.51
if ( obj instanceof DERObjectIdentifier ) {
final String objId = ((DERObjectIdentifier) obj).getId();
return ASN1.getClass("ObjectId").callMethod(context, "new", runtime.newString(objId));
}
if ( obj instanceof ASN1TaggedObject ) {
final ASN1TaggedObject taggedObj = (ASN1TaggedObject) obj;
IRubyObject val = decodeObject(context, ASN1, taggedObj.getObject());
IRubyObject tag = runtime.newFixnum( taggedObj.getTagNo() );
IRubyObject tag_class = runtime.newSymbol("CONTEXT_SPECIFIC");
final RubyArray valArr = runtime.newArray(val);
return ASN1.getClass("ASN1Data").callMethod(context, "new",
new IRubyObject[] { valArr, tag, tag_class }
);
}
if ( obj instanceof DERApplicationSpecific ) {
final DERApplicationSpecific appSpecific = (DERApplicationSpecific) obj;
IRubyObject tag = runtime.newFixnum( appSpecific.getApplicationTag() );
IRubyObject tag_class = runtime.newSymbol("APPLICATION");
final ASN1Sequence sequence = (ASN1Sequence) appSpecific.getObject(SEQUENCE);
@SuppressWarnings("unchecked")
final RubyArray valArr = decodeObjects(context, ASN1, sequence.getObjects());
return ASN1.getClass("ASN1Data").callMethod(context, "new",
new IRubyObject[] { valArr, tag, tag_class }
);
}
if ( obj instanceof ASN1Sequence ) {
@SuppressWarnings("unchecked")
RubyArray arr = decodeObjects(context, ASN1, ((ASN1Sequence) obj).getObjects());
return ASN1.getClass("Sequence").callMethod(context, "new", arr);
}
if ( obj instanceof ASN1Set ) {
@SuppressWarnings("unchecked")
RubyArray arr = decodeObjects(context, ASN1, ((ASN1Set) obj).getObjects());
return ASN1.getClass("Set").callMethod(context, "new", arr);
}
if ( obj instanceof ASN1Enumerated ) {
final RubyInteger value = RubyBignum.bignorm(runtime, ((ASN1Enumerated) obj).getValue());
return ASN1.getClass("Enumerated").callMethod(context, "new", value);
}
throw new IllegalArgumentException("unable to decode object: " + obj + " (" + ( obj == null ? "" : obj.getClass().getName() ) + ")");
}
private static RubyArray decodeObjects(final ThreadContext context, final RubyModule ASN1,
final Enumeration<ASN1Encodable> e)
throws IOException {
final RubyArray arr = context.runtime.newArray();
while ( e.hasMoreElements() ) {
arr.append( decodeObject(context, ASN1, e.nextElement()) );
}
return arr;
}
@JRubyMethod(meta = true)
public static IRubyObject decode(final ThreadContext context,
final IRubyObject self, final IRubyObject obj) {
try {
return decodeImpl(context, (RubyModule) self, obj);
}
catch (IOException e) {
//throw context.runtime.newIOErrorFromException(e);
throw newASN1Error(context.runtime, e.getMessage());
}
catch (IllegalArgumentException e) {
debugStackTrace(context.runtime, e);
throw context.runtime.newArgumentError(e.getMessage());
}
//catch (RuntimeException e) {
// final Ruby runtime = context.runtime;
// debugStackTrace(runtime, e);
// throw Utils.newRuntimeError(context.runtime, e);
//}
}
static IRubyObject decodeImpl(final ThreadContext context, IRubyObject obj)
throws IOException, IllegalArgumentException {
return decodeImpl(context, _ASN1(context.runtime), obj);
}
static IRubyObject decodeImpl(final ThreadContext context,
final RubyModule ASN1, IRubyObject obj) throws IOException, IllegalArgumentException {
obj = to_der_if_possible(context, obj);
BytesInputStream in = new BytesInputStream( obj.asString().getByteList() );
final IRubyObject decoded = decodeImpl(context, ASN1, in);
if ( in.available() > 0 ) {
final int read = in.readCount();
throw new IOException("Type mismatch. Total bytes read: "+ read + " Bytes available: " + in.available());
}
return decoded;
}
private static class BytesInputStream extends ByteArrayInputStream {
private BytesInputStream(final ByteList bytes) {
super(bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize());
}
final byte[] bytes() { return buf; }
final int readCount() { return pos - mark; } // since last mark
final int position() { return pos; }
final int offset() { return mark; }
}
private static IRubyObject decodeImpl(final ThreadContext context,
final RubyModule ASN1, final BytesInputStream in) throws IOException, IllegalArgumentException {
// NOTE: need to handle OpenSSL::ASN1::Constructive wrapping by hand :
final Integer tag = getConstructiveTag(in.bytes(), in.offset());
IRubyObject decoded = decodeObject(context, ASN1, readObject( in ));
if ( tag != null ) { // OpenSSL::ASN1::Constructive.new( arg ) :
final String type; List<IRubyObject> value = null;
if ( tag.intValue() == SEQUENCE ) {
//type = "Sequence"; // got a OpenSSL::ASN1::Sequence already :
return Constructive.setInfiniteLength(context, decoded);
}
else if ( tag.intValue() == SET ) {
//type = "Set"; // got a OpenSSL::ASN1::Set already :
return Constructive.setInfiniteLength(context, decoded);
}
else {
type = "Constructive";
}
if ( value == null ) value = Collections.singletonList(decoded);
return Constructive.newInfiniteConstructive(context, type, value, tag);
}
return decoded;
}
@JRubyMethod(meta = true, required = 1)
public static IRubyObject decode_all(final ThreadContext context,
final IRubyObject self, IRubyObject obj) {
obj = to_der_if_possible(context, obj);
BytesInputStream in = new BytesInputStream( obj.asString().getByteList() );
final RubyModule ASN1 = _ASN1(context.runtime);
final RubyArray arr = context.runtime.newArray();
while ( in.available() > 0 ) {
try {
in.mark(0); // set offset() before each object is read
arr.append( decodeImpl(context, ASN1, in) );
}
catch (IOException e) {
//throw context.runtime.newIOErrorFromException(e);
throw newASN1Error(context.runtime, e.getMessage());
}
catch (IllegalArgumentException e) {
debugStackTrace(context.runtime, e);
throw context.runtime.newArgumentError(e.getMessage());
}
}
return arr;
}
@JRubyMethod(meta = true, required = 1)
public static IRubyObject traverse(final ThreadContext context, final IRubyObject self, IRubyObject arg) {
warn(context, "WARNING: unimplemented method called: ASN1#traverse");
return context.runtime.getNil();
}
public static RaiseException newASN1Error(Ruby runtime, String message) {
return Utils.newError(runtime, _ASN1(runtime).getClass("ASN1Error"), message, false);
}
static RubyModule _ASN1(final Ruby runtime) {
return (RubyModule) runtime.getModule("OpenSSL").getConstant("ASN1");
}
static org.bouncycastle.asn1.ASN1Primitive readObject(final byte[] bytes)
throws IOException {
return new ASN1InputStream(new ByteArrayInputStream(bytes)).readObject();
}
private static org.bouncycastle.asn1.ASN1Primitive readObject(final InputStream bytes)
throws IOException {
return new ASN1InputStream(bytes).readObject();
}
// NOTE: BC's ASNInputStream internals "reinvented" a bit :
private static Integer getConstructiveTag(final byte[] asn1, int offset) {
final int tag = asn1[ offset ] & 0xFF;
if ( ( tag & BERTags.CONSTRUCTED ) != 0 ) { // isConstructed
//
// calculate tag number
//
// readTagNumber(asn1, ++offset, tag) :
int tagNo = tag & 0x1f;
//
// with tagged object tag number is bottom 5 bits, or stored at the start of the content
//
if (tagNo == 0x1f)
{
tagNo = 0;
int b = asn1[ ++offset ]; //s.read();
// X.690-0207 8.1.2.4.2
// "c) bits 7 to 1 of the first subsequent octet shall not all be zero."
if ((b & 0x7f) == 0) // Note: -1 will pass
{
return null; //throw new IOException("corrupted stream - invalid high tag number found");
}
while ((b >= 0) && ((b & 0x80) != 0))
{
tagNo |= (b & 0x7f);
tagNo <<= 7;
b = asn1[ ++offset ]; //s.read();
}
if (b < 0)
{
return null; //throw new EOFException("EOF found inside tag value.");
}
tagNo |= (b & 0x7f);
}
//
// calculate length
//
final int length = asn1[ ++offset ] & 0xFF;
if ( length == 0x80 ) {
// return -1; // indefinite-length encoding
}
else {
return null;
}
if ((tag & BERTags.APPLICATION) != 0) {
//return new BERApplicationSpecificParser(tagNo, sp).getLoadedObject();
}
if ((tag & BERTags.TAGGED) != 0) {
//return new BERTaggedObjectParser(true, tagNo, sp).getLoadedObject();
}
//System.out.println(" tagNo = 0x" + Integer.toHexString(tagNo));
// TODO There are other tags that may be constructed (e.g. BIT_STRING)
switch (tagNo) {
case BERTags.SEQUENCE :
//return new BERSequenceParser(sp).getLoadedObject();
return Integer.valueOf( SEQUENCE ); //return "Sequence";
case BERTags.SET :
//return new BERSetParser(sp).getLoadedObject();
return Integer.valueOf( SET ); //return "Set";
case BERTags.OCTET_STRING :
return Integer.valueOf( OCTET_STRING );
//return new BEROctetStringParser(sp).getLoadedObject();
case BERTags.EXTERNAL :
//return new DERExternalParser(sp).getLoadedObject();
default:
return Integer.valueOf( 0 ); //return "Constructive";
//throw new IOException("unknown BER object encountered");
}
}
return null;
}
public static class ASN1Data extends RubyObject {
private static final long serialVersionUID = 6117598347932209839L;
static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new ASN1Data(runtime, klass);
}
};
static final int MAX_TAG_VALUE = ASN1_INFO.length;
public ASN1Data(Ruby runtime, RubyClass type) {
super(runtime,type);
}
@JRubyMethod(visibility = Visibility.PRIVATE)
public IRubyObject initialize(final ThreadContext context,
final IRubyObject value, final IRubyObject tag, final IRubyObject tag_class) {
checkTag(context.runtime, tag, tag_class, "UNIVERSAL");
this.callMethod(context, "tag=", tag);
this.callMethod(context, "value=", value);
this.callMethod(context, "tag_class=", tag_class);
return this;
}
private void checkTag(final Ruby runtime, final IRubyObject tag,
final IRubyObject tagClass, final String expected) {
if ( ! (tagClass instanceof RubySymbol) ) {
throw newASN1Error(runtime, "invalid tag class");
}
if ( tagClass.toString().equals(expected) && RubyNumeric.fix2int(tag) > MAX_TAG_VALUE ) {
throw newASN1Error(runtime, "tag number for :" + expected + " too large");
}
}
boolean isEOC() { return false; }
boolean isExplicitTagging() { return ! isImplicitTagging(); }
boolean isImplicitTagging() { return true; }
int getTag(final ThreadContext context) {
return RubyNumeric.fix2int(callMethod(context, "tag"));
}
ASN1Encodable toASN1(final ThreadContext context) {
return toASN1TaggedObject(context);
}
final ASN1TaggedObject toASN1TaggedObject(final ThreadContext context) {
final int tag = getTag(context);
final IRubyObject val = callMethod(context, "value");
if ( val instanceof RubyArray ) {
final RubyArray arr = (RubyArray) val;
if ( arr.size() > 1 ) {
ASN1EncodableVector vec = new ASN1EncodableVector();
for ( final IRubyObject obj : arr.toJavaArray() ) {
ASN1Encodable data = ((ASN1Data) obj).toASN1(context);
if ( data == null ) break; vec.add( data );
}
return new DERTaggedObject(isExplicitTagging(), tag, new DERSequence(vec));
}
else if ( arr.size() == 1 ) {
ASN1Encodable data = ((ASN1Data) arr.entry(0)).toASN1(context);
return new DERTaggedObject(isExplicitTagging(), tag, data);
}
else {
throw new IllegalStateException("empty array detected");
}
}
return new DERTaggedObject(isExplicitTagging(), tag, ((ASN1Data) val).toASN1(context));
}
@JRubyMethod
public IRubyObject to_der(final ThreadContext context) {
try {
final byte[] encoded = toDER(context);
return context.runtime.newString(new ByteList(encoded, false));
}
catch (IOException e) {
throw newASN1Error(context.runtime, e.getMessage());
}
}
byte[] toDER(final ThreadContext context) throws IOException {
return toASN1(context).toASN1Primitive().getEncoded(ASN1Encoding.DER);
}
protected IRubyObject defaultTag() {
final Integer id = typeId( getMetaClass() );
if ( id == null ) return getRuntime().getNil();
return getRuntime().newFixnum( id.intValue() );
}
final IRubyObject value() {
return value(getRuntime().getCurrentContext());
}
IRubyObject value(final ThreadContext context) {
return callMethod(context, "value");
}
final String getClassBaseName() { return getMetaClass().getBaseName(); }
@Override
public String toString() {
return value().toString();
}
protected final void print() {
print(0);
}
protected void print(int indent) {
final PrintStream out = getRuntime().getOut();
printIndent(out, indent);
final IRubyObject value = value();
out.println("ASN1Data: ");
if ( value instanceof RubyArray ) {
printArray(out, indent, (RubyArray) value);
} else {
((ASN1Data) value).print(indent + 1);
}
}
static void printIndent(final PrintStream out, final int indent) {
for ( int i = 0; i < indent; i++) out.print(" ");
}
static void printArray(final PrintStream out, final int indent, final RubyArray array) {
for ( int i = 0; i < array.size(); i++ ) {
((ASN1Data) array.entry(i)).print(indent + 1);
}
}
static RaiseException createNativeRaiseException(final ThreadContext context,
final Throwable e) {
Throwable cause = e.getCause(); if ( cause == null ) cause = e;
return RaiseException.createNativeRaiseException(context.runtime, cause);
}
}
public static class Primitive extends ASN1Data {
private static final long serialVersionUID = 8489625559339190259L;
static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new Primitive(runtime, klass);
}
};
public Primitive(Ruby runtime, RubyClass type) {
super(runtime,type);
}
@Override
@JRubyMethod
public IRubyObject to_der(final ThreadContext context) {
if ( value(context).isNil() ) {
// MRI compatibility but avoids Java exceptions as well e.g.
// Java::JavaLang::NumberFormatException
// java.math.BigInteger.<init>(BigInteger.java:296)
// java.math.BigInteger.<init>(BigInteger.java:476)
// org.jruby.ext.openssl.ASN1$ASN1Primitive.toASN1(ASN1.java:1287)
// org.jruby.ext.openssl.ASN1$ASN1Data.to_der(ASN1.java:1129)
// org.jruby.ext.openssl.ASN1$ASN1Primitive.to_der(ASN1.java:1202)
throw context.runtime.newTypeError("nil value");
}
return super.to_der(context);
}
@JRubyMethod(required = 0, optional = 4, visibility = Visibility.PRIVATE)
public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) {
initializeImpl(context, this, args);
return this;
}
// shared initialize logic between Primitive and Constructive
static void initializeImpl(final ThreadContext context,
final ASN1Data self, final IRubyObject[] args) {
final Ruby runtime = context.runtime;
final int len = args.length;
IRubyObject value = len == 0 ? runtime.getNil() : args[0];
final IRubyObject tag;
IRubyObject tagging = runtime.getNil();
IRubyObject tag_class = runtime.getNil();
if ( len > 1 ) {
tag = args[1];
if ( len > 2 ) {
tagging = args[2];
if ( len > 3 ) tag_class = args[3];
}
if ( tag.isNil() ) throw newASN1Error(runtime, "must specify tag number");
if ( tagging.isNil() ) tagging = runtime.newSymbol("EXPLICIT");
if ( ! (tagging instanceof RubySymbol) ) {
throw newASN1Error(runtime, "invalid tag default");
}
if ( tag_class.isNil() ) tag_class = runtime.newSymbol("CONTEXT_SPECIFIC");
if ( ! (tag_class instanceof RubySymbol) ) {
throw newASN1Error(runtime, "invalid tag class");
}
if ( tagging.toString().equals("IMPLICIT") && RubyNumeric.fix2int(tag) > MAX_TAG_VALUE ) {
throw newASN1Error(runtime, "tag number for Universal too large");
}
}
else {
tag = self.defaultTag();
tag_class = runtime.newSymbol("UNIVERSAL");
}
// NOTE: Primitive only
final String baseName = self.getMetaClass().getRealClass().getBaseName();
if ( "ObjectId".equals( baseName ) ) {
final String name;
try {
name = oid2Sym( runtime, getObjectID(runtime, value.toString()), true );
}
catch (IllegalArgumentException e) {
// e.g. in case of nil "string not an OID"
throw runtime.newTypeError(e.getMessage());
}
if ( name != null ) value = runtime.newString(name);
}
self.setInstanceVariable("@tag", tag);
self.setInstanceVariable("@value", value);
self.setInstanceVariable("@tag_class", tag_class);
self.setInstanceVariable("@tagging", tagging);
self.setInstanceVariable("@infinite_length", runtime.getFalse());
}
@Override
boolean isExplicitTagging() {
return "EXPLICIT".equals( getInstanceVariable("@tagging").toString() );
}
@Override
boolean isImplicitTagging() {
IRubyObject tagging = getInstanceVariable("@tagging");
if ( tagging.isNil() ) return true;
return "IMPLICIT".equals( tagging.toString() );
}
@Override
boolean isEOC() {
return "EndOfContent".equals( getClassBaseName() );
}
@Override
byte[] toDER(final ThreadContext context) throws IOException {
if ( isEOC() ) return new byte[] { 0x00, 0x00 };
return toASN1(context).toASN1Primitive().getEncoded(ASN1Encoding.DER);
}
static Primitive newInstance(final ThreadContext context, final String type,
final IRubyObject value) {
RubyClass klass = _ASN1(context.runtime).getClass(type);
final Primitive self = new Primitive(context.runtime, klass);
if ( value != null ) self.setInstanceVariable("@value", value);
return self;
}
static Primitive newEndOfContent(final ThreadContext context) {
return newInstance(context, "EndOfContent", null);
}
/*
private static final Class DERBooleanClass;
static {
Class klass;
try {
klass = Class.forName("org.bouncycastle.asn1.DERBoolean");
}
catch(ClassNotFoundException e) { klass = null; }
DERBooleanClass = klass;
} */
@Override
ASN1Encodable toASN1(final ThreadContext context) {
Class<? extends ASN1Encodable> type = typeClass( getMetaClass() );
if ( type == null ) {
final int tag = getTag(context);
if ( tag == 0 ) return null; // TODO pass EOC to BC ?
if ( isExplicitTagging() ) type = typeClass( tag );
if ( type == null ) {
throw new IllegalArgumentException(
"no type for: " + getMetaClass() + " or tag: " + getTag(context)
);
}
}
final IRubyObject val = callMethod(context, "value");
if ( type == ASN1ObjectIdentifier.class || type == DERObjectIdentifier.class ) {
return getObjectID(context.runtime, val.toString());
}
if ( type == DERNull.class || type == ASN1Null.class ) {
return DERNull.INSTANCE;
}
if ( ASN1Boolean.class.isAssignableFrom( type ) ) {
return ASN1Boolean.getInstance(val.isTrue());
}
if ( type == DERBoolean.class ) {
return DERBoolean.getInstance(val.isTrue());
}
if ( type == DERUTCTime.class ) {
if ( val instanceof RubyTime ) {
return new DERUTCTime(((RubyTime) val).getJavaDate());
}
return DERUTCTime.getInstance( val.asString().getBytes() );
}
if ( type == DERGeneralizedTime.class ) {
if ( val instanceof RubyTime ) {
return new DERGeneralizedTime(((RubyTime) val).getJavaDate());
}
return DERGeneralizedTime.getInstance( val.asString().getBytes() );
}
if ( type == DERInteger.class ) {
return new DERInteger( bigIntegerValue(val) );
}
if ( ASN1Integer.class.isAssignableFrom( type ) ) {
return new ASN1Integer( bigIntegerValue(val) );
}
if ( type == DEREnumerated.class ) {
return new DEREnumerated( bigIntegerValue(val) );
}
if ( type == ASN1Enumerated.class ) {
return new ASN1Enumerated( bigIntegerValue(val) );
}
//if ( type == DEROctetString.class ) {
if ( ASN1OctetString.class.isAssignableFrom( type ) ) {
return new DEROctetString( val.asString().getBytes() );
}
if ( type == DERBitString.class ) {
final byte[] bs = val.asString().getBytes();
int unused = 0;
for ( int i = (bs.length - 1); i > -1; i-- ) {
if (bs[i] == 0) unused += 8;
else {
byte v2 = bs[i];
int x = 8;
while ( v2 != 0 ) {
v2 <<= 1;
x--;
}
unused += x;
break;
}
}
return new DERBitString(bs, unused);
}
if ( type == DERIA5String.class ) {
return new DERIA5String( val.asString().toString() );
}
if ( type == DERUTF8String.class ) {
return new DERUTF8String( val.asString().toString() );
}
if ( type == DERBMPString.class ) {
return new DERBMPString( val.asString().toString() );
}
if ( type == DERUniversalString.class ) {
return new DERUniversalString( val.asString().getBytes() );
}
if ( type == DERGeneralString.class ) {
return DERGeneralString.getInstance( val.asString().getBytes() );
}
if ( type == DERVisibleString.class ) {
return DERVisibleString.getInstance( val.asString().getBytes() );
}
if ( type == DERNumericString.class ) {
return DERNumericString.getInstance( val.asString().getBytes() );
}
if ( val instanceof RubyString ) {
try {
return typeInstance(type, ( (RubyString) val ).getBytes());
}
catch (Exception e) { // TODO exception handling
debugStackTrace(context.runtime, e);
throw createNativeRaiseException(context, e);
}
}
// TODO throw an exception here too?
if ( isDebug(context.runtime) ) {
debug(this + " toASN1() could not handle class " + getMetaClass() + " and value " + val.inspect() + " (" + val.getClass().getName() + ")");
}
warn(context, "WARNING: unimplemented method called: ASN1Data#toASN1 (" + type + ")");
return null;
}
private static BigInteger bigIntegerValue(final IRubyObject val) {
if ( val instanceof RubyInteger ) { // RubyBignum
return ((RubyInteger) val).getBigIntegerValue();
}
if ( val instanceof BN ) ((BN) val).getValue();
return new BigInteger( val.asString().getBytes() );
}
@Override
protected void print(int indent) {
final PrintStream out = getRuntime().getOut();
printIndent(out, indent);
out.print(getMetaClass().getRealClass().getBaseName());
out.print(": ");
out.println(value().callMethod(getRuntime().getCurrentContext(), "inspect").toString());
}
}
public static class Constructive extends ASN1Data { // implements ASN1Encodable {
private static final long serialVersionUID = -7166662655104776828L;
static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new Constructive(runtime, klass);
}
};
public Constructive(Ruby runtime, RubyClass type) {
super(runtime, type);
}
@JRubyMethod(required = 1, optional = 3, visibility = Visibility.PRIVATE)
public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) {
Primitive.initializeImpl(context, this, args);
return this;
}
static Constructive newInfiniteConstructive(final ThreadContext context,
final String type, final List<IRubyObject> value, final int defaultTag) {
final Ruby runtime = context.runtime;
final RubyClass klass = _ASN1(context.runtime).getClass(type);
final Constructive self = new Constructive(runtime, klass);
final RubyArray values = runtime.newArray(value.size());
for ( final IRubyObject val : value ) values.append(val);
// values.append( Primitive.newEndOfContent(context) );
self.setInstanceVariable("@tag", runtime.newFixnum(defaultTag));
self.setInstanceVariable("@value", values);
self.setInstanceVariable("@tag_class", runtime.newSymbol("UNIVERSAL"));
self.setInstanceVariable("@tagging", context.nil);
return setInfiniteLength(context, self);
}
static Constructive setInfiniteLength(final ThreadContext context, final IRubyObject constructive) {
final Constructive instance = ((Constructive) constructive);
final IRubyObject eoc = Primitive.newEndOfContent(context);
final IRubyObject value = instance.value(context);
if ( value instanceof RubyArray ) ((RubyArray) value).append(eoc);
else value.callMethod(context, "<<", eoc);
instance.setInstanceVariable("@infinite_length", context.runtime.getTrue());
return instance;
}
private boolean rawConstructive() {
return "Constructive".equals( getClassBaseName() );
}
private boolean isSequence() {
return "Sequence".equals( getClassBaseName() );
}
private boolean isSet() {
return "Set".equals( getClassBaseName() );
}
private boolean isInfiniteLength() {
return getInstanceVariable("@infinite_length").isTrue();
}
@Override
boolean isExplicitTagging() {
IRubyObject tagging = getInstanceVariable("@tagging");
if ( tagging.isNil() ) return true;
return "EXPLICIT".equals( tagging.toString() );
}
@Override
boolean isImplicitTagging() {
return "IMPLICIT".equals( getInstanceVariable("@tagging").toString() );
}
@Override
ASN1Encodable toASN1(final ThreadContext context) {
if ( isInfiniteLength() ) return super.toASN1(context);
if ( isSequence() ) {
return new DERSequence( toASN1EncodableVector(context) );
}
if ( isSet() ) {
return new DLSet( toASN1EncodableVector(context) ); // return new BERSet(values);
//return ASN1Set.getInstance(toASN1TaggedObject(context), isExplicitTagging());
}
switch ( getTag(context) ) { // "raw" Constructive ?!?
case OCTET_STRING:
final ASN1EncodableVector values = toASN1EncodableVector(context);
ASN1OctetString[] octets = new ASN1OctetString[ values.size() ];
for ( int i = 0; i < values.size(); i++ ) {
octets[i] = (ASN1OctetString) values.get(i).toASN1Primitive();
}
return new BEROctetString(octets);
case SEQUENCE:
return new DERSequence( toASN1EncodableVector(context) );
case SET:
return new DLSet( toASN1EncodableVector(context) ); // return new BERSet(values);
//return ASN1Set.getInstance(toASN1TaggedObject(context), isExplicitTagging());
}
throw new UnsupportedOperationException( this.inspect().toString() );
}
@Override
@JRubyMethod
public IRubyObject to_der(final ThreadContext context) {
if ( rawConstructive() ) { // MRI compatibility
if ( ! isInfiniteLength() && ! super.value(context).isNil() ) {
final Ruby runtime = context.runtime;
throw newASN1Error(runtime, "Constructive shall only be used"
+ " with infinite length");
}
}
return super.to_der(context);
}
@Override
byte[] toDER(final ThreadContext context) throws IOException {
if ( isInfiniteLength() ) {
if ( isSequence() ) {
return sequenceToDER(context);
}
else if ( isSet() ) {
return setToDER(context);
}
else { // "raw" Constructive
switch ( getTag(context) ) {
case OCTET_STRING:
return octetStringToDER(context);
case BIT_STRING:
return bitStringToDER(context);
case SEQUENCE:
return sequenceToDER(context);
case SET:
return setToDER(context);
}
throw new UnsupportedOperationException( this.inspect().toString() );
}
}
return super.toDER(context);
}
private byte[] bitStringToDER(final ThreadContext context) throws IOException {
final ASN1EncodableVector values = toASN1EncodableVector(context);
final ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(BERTags.CONSTRUCTED | BERTags.BIT_STRING);
out.write(0x80); // infinite-length
for ( int i = 0; i < values.size(); i++ ) {
out.write( values.get(i).toASN1Primitive().getEncoded() );
}
out.write(0x00); out.write(0x00); // writeBEREnd
return out.toByteArray();
}
private byte[] octetStringToDER(final ThreadContext context) throws IOException {
final ASN1EncodableVector values = toASN1EncodableVector(context);
ASN1OctetString[] octets = new ASN1OctetString[ values.size() ];
for ( int i = 0; i < values.size(); i++ ) {
octets[i] = (ASN1OctetString) values.get(i).toASN1Primitive();
}
return new BEROctetString(octets).getEncoded();
}
private byte[] sequenceToDER(final ThreadContext context) throws IOException {
final ASN1EncodableVector values = toASN1EncodableVector(context);
final ByteArrayOutputStream out = new ByteArrayOutputStream(64);
BERSequenceGenerator sequenceGenerator = new BERSequenceGenerator(out);
for ( int i = 0; i < values.size(); i++ ) {
final ASN1Encodable value = values.get(i);
if ( value instanceof InternalEncodable ) { // HACK
byte[] nested = ((InternalEncodable) value).entry.toDER(context);
out.write(nested, 0, nested.length); continue;
}
sequenceGenerator.addObject( values.get(i) );
}
sequenceGenerator.close();
return out.toByteArray();
}
private byte[] setToDER(final ThreadContext context) throws IOException {
final ASN1EncodableVector values = toASN1EncodableVector(context);
return new BERSet(values).toASN1Primitive().getEncoded();
}
private ASN1EncodableVector toASN1EncodableVector(final ThreadContext context) {
final ASN1EncodableVector vec = new ASN1EncodableVector();
final IRubyObject value = value(context);
if ( value instanceof RubyArray ) {
final RubyArray val = (RubyArray) value;
for ( int i = 0; i < val.size(); i++ ) {
if ( addEntry(context, vec, val.entry(i)) ) break;
}
}
else {
final int size = RubyInteger.num2int(value.callMethod(context, "size"));
for ( int i = 0; i < size; i++ ) {
final RubyInteger idx = context.runtime.newFixnum(i);
IRubyObject entry = value.callMethod(context, "[]", idx);
if ( addEntry(context, vec, entry) ) break;
}
}
return vec;
}
public ASN1Primitive toASN1Primitive() {
throw new UnsupportedOperationException();
}
private static class InternalEncodable implements ASN1Encodable {
final Constructive entry;
InternalEncodable(Constructive entry) { this.entry = entry; }
@Override
public ASN1Primitive toASN1Primitive() {
throw new UnsupportedOperationException();
}
}
private static boolean addEntry(final ThreadContext context,
final ASN1EncodableVector vec, final IRubyObject entry) {
try {
if ( entry instanceof Constructive ) {
final Constructive constructive = (Constructive) entry;
if ( constructive.isInfiniteLength() || constructive.rawConstructive() ) {
vec.add( new InternalEncodable( (Constructive) entry) );
}
else {
vec.add( constructive.toASN1(context) );
}
}
else if ( entry instanceof ASN1Data ) {
final ASN1Data data = ( (ASN1Data) entry );
if ( data.isEOC() ) return true;
vec.add( data.toASN1(context) );
}
else {
vec.add( ( (ASN1Data) decodeImpl(context, entry) ).toASN1(context) );
}
return false;
}
catch (IOException e) { throw Utils.newIOError(context.runtime, e); }
}
@JRubyMethod
public IRubyObject each(final ThreadContext context, final Block block) {
final IRubyObject value = value(context);
if ( value instanceof RubyArray ) {
final RubyArray val = (RubyArray) value;
for ( int i = 0; i < val.size(); i++ ) {
block.yield(context, val.entry(i));
}
}
else {
value.callMethod(context, "each", NULL_ARRAY, block);
//final int size = RubyInteger.num2int(value.callMethod(context, "size"));
//for ( int i = 0; i < size; i++ ) {
// final RubyInteger idx = context.runtime.newFixnum(i);
// block.yield(context, value.callMethod(context, "[]", idx));
//}
}
return context.runtime.getNil();
}
@JRubyMethod
public IRubyObject size(final ThreadContext context) {
final IRubyObject value = value(context);
if ( value instanceof RubyArray ) {
final RubyArray val = (RubyArray) value;
return context.runtime.newFixnum(val.size());
}
else {
return value.callMethod(context, "size");
}
}
@Override
protected void print(int indent) {
final PrintStream out = getRuntime().getOut();
printIndent(out, indent);
out.print(getMetaClass().getRealClass().getBaseName()); out.println(": ");
printArray( out, indent, (RubyArray) value( getRuntime().getCurrentContext() ) );
}
}
}// ASN1