|
35 | 35 |
|
36 | 36 | import org.bouncycastle.asn1.*; |
37 | 37 | import org.bouncycastle.asn1.x500.X500Name; |
| 38 | +import org.bouncycastle.asn1.x500.X500NameBuilder; |
| 39 | +import org.bouncycastle.asn1.x500.style.BCStyle; |
| 40 | +import org.bouncycastle.asn1.x509.AccessDescription; |
| 41 | +import org.bouncycastle.asn1.x509.DistributionPoint; |
| 42 | +import org.bouncycastle.asn1.x509.DistributionPointName; |
38 | 43 | import org.bouncycastle.asn1.x509.GeneralName; |
39 | 44 | import org.bouncycastle.asn1.x509.GeneralNames; |
40 | 45 |
|
|
50 | 55 | import org.jruby.exceptions.RaiseException; |
51 | 56 | import org.jruby.runtime.Arity; |
52 | 57 | import org.jruby.runtime.Block; |
| 58 | +import org.jruby.runtime.Helpers; |
53 | 59 | import org.jruby.runtime.ThreadContext; |
54 | 60 | import org.jruby.runtime.Visibility; |
55 | 61 | import org.jruby.runtime.builtin.IRubyObject; |
@@ -199,6 +205,12 @@ else if (id.equals("2.16.840.1.113730.1.1")) { // nsCertType |
199 | 205 | else if (id.equals("2.5.29.37")) { // extendedKeyUsage |
200 | 206 | value = parseExtendedKeyUsage(valuex); |
201 | 207 | } |
| 208 | + else if (id.equals("2.5.29.31")) { // crlDistributionPoints |
| 209 | + value = parseCRLDistributionPoints(context, valuex); |
| 210 | + } |
| 211 | + else if (id.equals("1.3.6.1.5.5.7.1.1")) { // authorityInfoAccess |
| 212 | + value = parseAuthorityInfoAccess(valuex); |
| 213 | + } |
202 | 214 | else { |
203 | 215 | value = new DEROctetString(new DEROctetString(ByteList.plain(valuex)).getEncoded(ASN1Encoding.DER)); |
204 | 216 | } |
@@ -610,4 +622,154 @@ private static DLSequence parseExtendedKeyUsage(final String valuex) { |
610 | 622 | return new DLSequence(vector); |
611 | 623 | } |
612 | 624 |
|
| 625 | + private ASN1Sequence parseCRLDistributionPoints(final ThreadContext context, final String valuex) |
| 626 | + throws IOException { |
| 627 | + final ASN1EncodableVector points = new ASN1EncodableVector(); |
| 628 | + |
| 629 | + final String trimmed = valuex.trim(); |
| 630 | + if ( trimmed.startsWith("@") ) { |
| 631 | + addDistributionPointsFromConfigSection(context, trimmed.substring(1).trim(), true, points); |
| 632 | + } |
| 633 | + else if ( trimmed.indexOf(':') == -1 ) { |
| 634 | + final RubyHash section = getConfigSection(context, trimmed); |
| 635 | + if ( section != null ) { |
| 636 | + addDistributionPointsFromConfigSection(context, trimmed, false, points); |
| 637 | + } |
| 638 | + else { |
| 639 | + points.add(new DistributionPoint(new DistributionPointName(parseGeneralNames(context, trimmed)), null, null)); |
| 640 | + } |
| 641 | + } |
| 642 | + else { |
| 643 | + points.add(new DistributionPoint(new DistributionPointName(parseGeneralNames(context, trimmed)), null, null)); |
| 644 | + } |
| 645 | + |
| 646 | + return new DERSequence(points); |
| 647 | + } |
| 648 | + |
| 649 | + private void addDistributionPointsFromConfigSection(final ThreadContext context, |
| 650 | + final String sectionName, final boolean oneNamePerEntry, final ASN1EncodableVector points) throws IOException { |
| 651 | + final RubyHash section = getConfigSection(context, sectionName); |
| 652 | + if ( section == null ) throw new IOException("Malformed CRLDistributionPoints section: " + sectionName + " in @config"); |
| 653 | + |
| 654 | + if ( ! oneNamePerEntry ) { |
| 655 | + final IRubyObject fullName = section.fastARef(StringHelper.newString(context.runtime, "fullname")); |
| 656 | + if ( fullName != null && !fullName.isNil() ) { |
| 657 | + addDistributionPointToVector(context, points, fullName.toString()); |
| 658 | + return; |
| 659 | + } |
| 660 | + } |
| 661 | + |
| 662 | + section.visitAll(new RubyHash.Visitor() { |
| 663 | + public void visit(final IRubyObject key, final IRubyObject value) { |
| 664 | + final String keyName = stripNumericSuffix(key.toString()); |
| 665 | + try { |
| 666 | + addDistributionPointToVector(context, points, keyName + ':' + value); |
| 667 | + } catch (IOException e) { |
| 668 | + Helpers.throwException(e); |
| 669 | + } |
| 670 | + } |
| 671 | + }); |
| 672 | + } |
| 673 | + |
| 674 | + private void addDistributionPointToVector(final ThreadContext context, ASN1EncodableVector points, final String part) |
| 675 | + throws IOException { |
| 676 | + final GeneralNames partNames = new GeneralNames(parseGeneralName(context, part)); |
| 677 | + points.add(new DistributionPoint(new DistributionPointName(partNames), null, null)); |
| 678 | + } |
| 679 | + |
| 680 | + private ASN1Sequence parseAuthorityInfoAccess(final String valuex) throws IOException { |
| 681 | + final ASN1EncodableVector vector = new ASN1EncodableVector(); |
| 682 | + final String[] values = splitGeneralNameParts(valuex); |
| 683 | + for ( int i = 0; i < values.length; i++ ) { |
| 684 | + final String value = values[i]; |
| 685 | + final int index = value.indexOf(';'); |
| 686 | + if ( index <= 0 || index >= value.length() - 1 ) { |
| 687 | + throw new IOException("Malformed AuthorityInfoAccess: " + valuex); |
| 688 | + } |
| 689 | + |
| 690 | + final String accessMethod = value.substring(0, index).trim(); |
| 691 | + final ASN1ObjectIdentifier method = ASN1Registry.sym2oid(accessMethod); |
| 692 | + if ( method == null ) throw new IOException("Unknown AuthorityInfoAccess method: " + accessMethod); |
| 693 | + |
| 694 | + final String accessLocation = value.substring(index + 1).trim(); |
| 695 | + vector.add(new AccessDescription(method, parseGeneralName(accessLocation))); |
| 696 | + } |
| 697 | + return new DERSequence(vector); |
| 698 | + } |
| 699 | + |
| 700 | + private GeneralNames parseGeneralNames(final ThreadContext context, final String valuex) throws IOException { |
| 701 | + final String[] vals = splitGeneralNameParts(valuex); |
| 702 | + final GeneralName[] names = new GeneralName[vals.length]; |
| 703 | + for ( int i = 0; i < vals.length; i++ ) { |
| 704 | + names[i] = parseGeneralName(context, vals[i]); |
| 705 | + } |
| 706 | + return new GeneralNames(names); |
| 707 | + } |
| 708 | + |
| 709 | + private GeneralName parseGeneralName(final ThreadContext context, final String valuex) throws IOException { |
| 710 | + if ( valuex.startsWith("dir") ) { |
| 711 | + final String dir = valuex.substring(dirName_.length()).trim(); |
| 712 | + final RubyHash section = getConfigSection(context, dir); |
| 713 | + if ( section != null ) { |
| 714 | + return new GeneralName(GeneralName.directoryName, buildX500NameFromConfigSection(context, section)); |
| 715 | + } |
| 716 | + } |
| 717 | + return parseGeneralName(valuex); |
| 718 | + } |
| 719 | + |
| 720 | + private X500Name buildX500NameFromConfigSection(final ThreadContext context, final RubyHash section) { |
| 721 | + final X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE); |
| 722 | + section.visitAll(new RubyHash.Visitor() { |
| 723 | + public void visit(final IRubyObject key, final IRubyObject value) { |
| 724 | + final String keyName = stripNumericSuffix(key.toString()); |
| 725 | + try { |
| 726 | + builder.addRDN(ASN1.getObjectID(context.runtime, keyName), value.toString()); |
| 727 | + } |
| 728 | + catch (IllegalArgumentException e) { |
| 729 | + throw newExtensionError(context.runtime, "invalid X509 name field: " + keyName); |
| 730 | + } |
| 731 | + } |
| 732 | + }); |
| 733 | + return builder.build(); |
| 734 | + } |
| 735 | + |
| 736 | + private IRubyObject getConfigValue(final ThreadContext context, final String key) { |
| 737 | + final IRubyObject config = config(); |
| 738 | + if (config == null || config.isNil()) { // TODO: support fallback to DEFAULT_CONFIG |
| 739 | + return null; |
| 740 | + } |
| 741 | + return config.callMethod(context, "[]", StringHelper.newString(context.runtime, key)); |
| 742 | + } |
| 743 | + |
| 744 | + private RubyHash getConfigSection(final ThreadContext context, final String sectionName) { |
| 745 | + final IRubyObject section = getConfigValue(context, sectionName); |
| 746 | + if (section instanceof RubyHash) { |
| 747 | + final RubyHash hash = (RubyHash) section; |
| 748 | + return hash.isEmpty() ? null : hash; |
| 749 | + } |
| 750 | + return null; |
| 751 | + } |
| 752 | + |
| 753 | + private static String[] splitGeneralNameParts(final String valuex) { |
| 754 | + // allow up to three levels of escaping of ',' |
| 755 | + final String[] vals = valuex.split("(?<!(^|[^\\\\])((\\\\\\\\)?\\\\\\\\)?\\\\),"); |
| 756 | + for ( int i = 0; i < vals.length; i++ ) { |
| 757 | + vals[i] = vals[i].replaceAll("\\\\([,\\\\])", "$1").trim(); |
| 758 | + } |
| 759 | + return vals; |
| 760 | + } |
| 761 | + |
| 762 | + private static String stripNumericSuffix(final String key) { |
| 763 | + final int index = key.lastIndexOf('.'); |
| 764 | + if ( index > 0 ) { |
| 765 | + boolean numeric = true; |
| 766 | + for ( int i = index + 1; i < key.length(); i++ ) { |
| 767 | + if ( ! Character.isDigit(key.charAt(i)) ) { |
| 768 | + numeric = false; break; |
| 769 | + } |
| 770 | + } |
| 771 | + if ( numeric ) return key.substring(0, index); |
| 772 | + } |
| 773 | + return key; |
| 774 | + } |
613 | 775 | } |
0 commit comments