6161import org .bouncycastle .asn1 .x509 .AuthorityInformationAccess ;
6262import org .bouncycastle .asn1 .x509 .GeneralName ;
6363import org .bouncycastle .asn1 .x509 .GeneralNames ;
64+ import org .bouncycastle .asn1 .x509 .GeneralSubtree ;
65+ import org .bouncycastle .asn1 .x509 .NameConstraints ;
6466import org .bouncycastle .util .encoders .Hex ;
6567
6668import org .jruby .Ruby ;
@@ -315,9 +317,6 @@ public IRubyObject set_oid(final ThreadContext context, IRubyObject arg) {
315317 private static final byte [] Object_Signing_CA = {'O' , 'b' , 'j' , 'e' , 'c' , 't' , ' ' , 'S' , 'i' , 'g' , 'n' , 'i' , 'n' , 'g' , ' ' , 'C' , 'A' };
316318 private static final byte [] Unused = {'U' , 'n' , 'u' , 's' , 'e' , 'd' };
317319 private static final byte [] Unspecified = {'U' , 'n' , 's' , 'p' , 'e' , 'c' , 'i' , 'f' , 'i' , 'e' , 'd' };
318- //private static final byte[] Key_Compromise = { 'K','e','y',' ','C','o','m','p','r','o','m','i','s','e' };
319- //private static final byte[] CA_Compromise = { 'C','A',' ','C','o','m','p','r','o','m','i','s','e' };
320- //private static final byte[] Affiliation_Changed = { 'A','f','f','i','l','i','a','t','i','o','n',' ','C','h','a','n','g','e','d' };
321320 private static final byte [] keyid_ = {'k' , 'e' , 'y' , 'i' , 'd' , ':' };
322321
323322 @ JRubyMethod
@@ -329,6 +328,18 @@ public RubyString value(final ThreadContext context) {
329328 final Ruby runtime = context .runtime ;
330329 final String oid = getRealObjectID ().getId ();
331330 try {
331+ if ( oid .equals ("2.5.29.30" ) ) { // nameConstraints
332+ ASN1Encodable value = getRealValue ();
333+ if ( value instanceof ASN1OctetString ) {
334+ value = ASN1 .readObject ( ((ASN1OctetString ) value ).getOctets () );
335+ }
336+ final NameConstraints nameConstraints = NameConstraints .getInstance (value );
337+ final ByteList val = new ByteList (64 );
338+ appendGeneralSubtrees (val , "Permitted" , nameConstraints .getPermittedSubtrees ());
339+ appendGeneralSubtrees (val , "Excluded" , nameConstraints .getExcludedSubtrees ());
340+ return runtime .newString ( val );
341+ }
342+
332343 if ( oid .equals ("2.5.29.19" ) ) { // basicConstraints
333344 ASN1Sequence seq2 = (ASN1Sequence ) ASN1 .readObject ( getRealValueEncoded () );
334345 final ByteList val = new ByteList (32 );
@@ -709,6 +720,50 @@ private static String accessDescriptionMethodName(final Ruby runtime, final Acce
709720 return method .getId ();
710721 }
711722
723+ static final String MS_UPN_OID = "1.3.6.1.4.1.311.20.2.3" ; // otherName type used by AD
724+
725+ // C: "Permitted:\n DNS:example.com\n DNS:other.com" (and/or an "Excluded:" block)
726+ private static void appendGeneralSubtrees (final ByteList out , final String label ,
727+ final GeneralSubtree [] subtrees ) {
728+ if ( subtrees == null || subtrees .length == 0 ) return ;
729+
730+ if ( out .length () > 0 ) out .append ('\n' );
731+ out .append ( ByteList .plain (label ) );
732+ out .append (':' );
733+ for ( int i = 0 ; i < subtrees .length ; i ++ ) {
734+ out .append ('\n' ).append (' ' ).append (' ' );
735+ final GeneralName base = subtrees [i ].getBase ();
736+ // a constrained iPAddress carries address *and* mask (8 or 32 octets)
737+ if ( base .getTagNo () == GeneralName .iPAddress ) {
738+ final byte [] ip = ((ASN1OctetString ) base .getName ()).getOctets ();
739+ if ( ip .length == 8 || ip .length == 32 ) {
740+ out .append ('I' ).append ('P' ).append (':' );
741+ final int half = ip .length / 2 ;
742+ appendIPAddress (out , ip , 0 , half );
743+ out .append ('/' );
744+ appendIPAddress (out , ip , half , half );
745+ continue ;
746+ }
747+ }
748+ formatGeneralName (base , out , false );
749+ }
750+ }
751+
752+ private static void appendIPAddress (final ByteList out , final byte [] ip , final int off , final int len ) {
753+ if ( len == 4 ) {
754+ for ( int i = 0 ; i < len ; i ++ ) {
755+ out .append ( ConvertBytes .intToCharBytes ( ((int ) ip [off + i ]) & 0xff ) );
756+ if ( i != len - 1 ) out .append ('.' );
757+ }
758+ }
759+ else {
760+ for ( int i = 0 ; i < len ; i += 2 ) {
761+ out .append ( ConvertBytes .intToHexBytes ( ((ip [off +i ] & 0xff ) << 8 | (ip [off +i +1 ] & 0xff )) ) );
762+ if ( i != len - 2 ) out .append (':' );
763+ }
764+ }
765+ }
766+
712767 @ SuppressWarnings ("unchecked" )
713768 private static boolean formatGeneralName (final GeneralName name , final ByteList out , final boolean slashed ) {
714769 final ASN1Encodable obj = name .getName ();
@@ -762,11 +817,24 @@ private static boolean formatGeneralName(final GeneralName name, final ByteList
762817 }
763818 break ;
764819 case GeneralName .otherName :
765- out .append ('o' ).append ('t' ).append ('h' ).append ('e' ).append ('r' ).append ('N' ).append ('a' ).append ('m' ).append ('e' ).
766- append (':' );
767- out .append ( ByteList .plain ( obj .toString () ) );
768- return true ;
769- //tagged = true;
820+ // OtherName ::= SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }
821+ // C: "othername: UPN:<value>" for the MS UPN type, "othername: <oid>:<value>" otherwise
822+ out .append ( ByteList .plain ("othername: " ) );
823+ final ASN1Sequence otherName = ASN1Sequence .getInstance (obj );
824+ final String typeId = ASN1ObjectIdentifier .getInstance (otherName .getObjectAt (0 )).getId ();
825+ out .append ( ByteList .plain ( MS_UPN_OID .equals (typeId ) ? "UPN" : typeId ) );
826+ out .append (':' );
827+ ASN1Encodable otherValue = otherName .getObjectAt (1 );
828+ if ( otherValue instanceof ASN1TaggedObject ) { // [0] EXPLICIT
829+ otherValue = ASN1Shim .getTaggedObject ((ASN1TaggedObject ) otherValue );
830+ }
831+ if ( otherValue instanceof ASN1String ) {
832+ out .append ( ByteList .plain ( ((ASN1String ) otherValue ).getString () ) );
833+ }
834+ else {
835+ out .append ( ByteList .plain ( otherValue .toString () ) );
836+ }
837+ break ; // NOTE: not a ';' separated entry (OpenSSL separates every name with ", ")
770838 case GeneralName .registeredID :
771839 out .append ('R' ).append ('I' ).append ('D' ).
772840 append (':' );
0 commit comments