4949import org .bouncycastle .asn1 .ASN1Primitive ;
5050import org .bouncycastle .asn1 .ASN1String ;
5151import org .bouncycastle .asn1 .BERTags ;
52+ import org .bouncycastle .asn1 .DERUTF8String ;
5253import org .bouncycastle .asn1 .DLSequence ;
5354import org .bouncycastle .asn1 .DLSet ;
5455import org .bouncycastle .asn1 .x500 .AttributeTypeAndValue ;
@@ -176,6 +177,7 @@ public X509Name(Ruby runtime, RubyClass type) {
176177 private final List <RubyInteger > types ;
177178
178179 private transient X500Name name ;
180+ private transient X500Name canonicalName ;
179181
180182 private void fromASN1Sequence (final byte [] encoded ) {
181183 try {
@@ -242,6 +244,7 @@ private void addValue(final ASN1Encodable value) {
242244 @ SuppressWarnings ("unchecked" )
243245 private void addType (final Ruby runtime , final ASN1Encodable value ) {
244246 this .name = null ; // NOTE: each fromX factory calls this ...
247+ this .canonicalName = null ;
245248 final Integer type = ASN1 .typeId (value );
246249 if ( type == null ) {
247250 warn (runtime .getCurrentContext (), this + " addType() could not resolve type for: " +
@@ -256,6 +259,7 @@ private void addType(final Ruby runtime, final ASN1Encodable value) {
256259 private void addEntry (ASN1ObjectIdentifier oid , RubyString value , RubyInteger type )
257260 throws IOException {
258261 this .name = null ;
262+ this .canonicalName = null ;
259263 this .oids .add (oid );
260264 final ASN1Encodable convertedValue = getNameEntryConverted ().
261265 getConvertedValue (oid , value .toString ()
@@ -515,6 +519,50 @@ final X500Name getX500Name() {
515519 return name = builder .build ();
516520 }
517521
522+ final X500Name getCanonicalX500Name () {
523+ if ( canonicalName != null ) return canonicalName ;
524+
525+ final X500NameBuilder builder = new X500NameBuilder ( BCStyle .INSTANCE );
526+ for ( int i = 0 ; i < oids .size (); i ++ ) {
527+ ASN1Encodable value = values .get (i );
528+ value = canonicalize (value );
529+ builder .addRDN ( oids .get (i ), value );
530+ }
531+ return canonicalName = builder .build ();
532+ }
533+
534+ private ASN1Encodable canonicalize (ASN1Encodable value ) {
535+ if (value instanceof ASN1String ) {
536+ ASN1String string = (ASN1String ) value ;
537+ return new DERUTF8String (canonicalize (string .getString ()));
538+ }
539+ return value ;
540+ }
541+
542+ private String canonicalize (String string ) {
543+ //asn1_string_canon (trim, to lower case, collapse multiple spaces)
544+ string = string .trim ();
545+ if (string .length () == 0 ) {
546+ return string ;
547+ }
548+
549+ StringBuilder out = new StringBuilder ();
550+ int i = 0 ;
551+ while (i < string .length ()) {
552+ char c = string .charAt (i );
553+ if (Character .isWhitespace (c )){
554+ out .append (' ' );
555+ while (i < string .length () && Character .isWhitespace (string .charAt (i ))) {
556+ i ++;
557+ }
558+ } else {
559+ out .append (Character .toLowerCase (c ));
560+ i ++;
561+ }
562+ }
563+ return out .toString ();
564+ }
565+
518566 @ JRubyMethod (name = { "cmp" , "<=>" })
519567 public RubyFixnum cmp (IRubyObject other ) {
520568 if ( equals (other ) ) {
@@ -523,8 +571,8 @@ public RubyFixnum cmp(IRubyObject other) {
523571 // TODO: do we really need cmp - if so what order huh?
524572 if ( other instanceof X509Name ) {
525573 final X509Name that = (X509Name ) other ;
526- final X500Name thisName = this .getX500Name ();
527- final X500Name thatName = that .getX500Name ();
574+ final X500Name thisName = this .getCanonicalX500Name ();
575+ final X500Name thatName = that .getCanonicalX500Name ();
528576 int cmp = thisName .toString ().compareTo ( thatName .toString () );
529577 return RubyFixnum .newFixnum ( getRuntime (), cmp );
530578 }
@@ -536,8 +584,8 @@ public boolean equals(Object other) {
536584 if ( this == other ) return true ;
537585 if ( other instanceof X509Name ) {
538586 final X509Name that = (X509Name ) other ;
539- final X500Name thisName = this .getX500Name ();
540- final X500Name thatName = that .getX500Name ();
587+ final X500Name thisName = this .getCanonicalX500Name ();
588+ final X500Name thatName = that .getCanonicalX500Name ();
541589 return thisName .equals (thatName );
542590 }
543591 return false ;
@@ -546,15 +594,14 @@ public boolean equals(Object other) {
546594 @ Override
547595 public int hashCode () {
548596 try {
549- return Name .hash ( getX500Name () );
597+ return ( int ) Name .hash ( getCanonicalX500Name () );
550598 }
551599 catch (IOException e ) {
552600 debugStackTrace (getRuntime (), e ); return 0 ;
553601 }
554602 catch (RuntimeException e ) {
555603 debugStackTrace (getRuntime (), e ); return 0 ;
556604 }
557- // return 41 * this.oids.hashCode();
558605 }
559606
560607 @ JRubyMethod (name = "eql?" )
@@ -571,7 +618,32 @@ public IRubyObject eql_p(final IRubyObject obj) {
571618 @ Override
572619 @ JRubyMethod
573620 public RubyFixnum hash () {
574- return getRuntime ().newFixnum ( hashCode () );
621+ long hash ;
622+ try {
623+ hash = Name .hash ( getCanonicalX500Name () );
624+ }
625+ catch (IOException e ) {
626+ debugStackTrace (getRuntime (), e ); hash = 0 ;
627+ }
628+ catch (RuntimeException e ) {
629+ debugStackTrace (getRuntime (), e ); hash = 0 ;
630+ }
631+ return getRuntime ().newFixnum (hash );
632+ }
633+
634+ @ JRubyMethod
635+ public RubyFixnum hash_old () {
636+ int hash ;
637+ try {
638+ hash = Name .hashOld ( getX500Name () );
639+ }
640+ catch (IOException e ) {
641+ debugStackTrace (getRuntime (), e ); hash = 0 ;
642+ }
643+ catch (RuntimeException e ) {
644+ debugStackTrace (getRuntime (), e ); hash = 0 ;
645+ }
646+ return getRuntime ().newFixnum ( hash );
575647 }
576648
577649 @ JRubyMethod
0 commit comments