You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@directory.apache.org by "Stefan Seelmann (Jira)" <ji...@apache.org> on 2020/03/16 13:10:00 UTC

[jira] [Assigned] (DIRSTUDIO-648) Studio should support the Password Modify Extended Operation according to RFC 3062

     [ https://issues.apache.org/jira/browse/DIRSTUDIO-648?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Stefan Seelmann reassigned DIRSTUDIO-648:
-----------------------------------------

    Assignee: Stefan Seelmann

> Studio should support the Password Modify Extended Operation according to RFC 3062
> ----------------------------------------------------------------------------------
>
>                 Key: DIRSTUDIO-648
>                 URL: https://issues.apache.org/jira/browse/DIRSTUDIO-648
>             Project: Directory Studio
>          Issue Type: New Feature
>    Affects Versions: 1.5.3
>         Environment: Apache Directory Studio 1.5.3 against OpenLDAP 2.4.x with slapo-ppolicy enabled; using hashed passwords; on Ubuntu Linux
>            Reporter: Carsten Tolkmit
>            Assignee: Stefan Seelmann
>            Priority: Major
>             Fix For: 2.0.0-M15
>
>
> In my environment I use Directory Studio 1.5.3 to connect to an OpenLDAP 2.4.x-Server with the ppolicy overlay enabled. 
> The policy overlay is used to set individual policies to user accounts, i.e. maximum password age etc, and it is also configured to check for minimum password quality ( pwdCheckQuality is set to 2 in some policies ).
> When I edit a user account's (hashed) userPassword in Directory Studio, a "19 - Password is too simple" is returned in every case, because the server cannot know the real password (because it is hashed) it rejects the new one - this behaviour is expected with hashed passwords, of course. 
> But that's one of the points RFC 3062 was made up for - it passes the cleartext password (via a TLS secured channel in our case) to the server and let's the server hash the password. Sadly, Directory Studio can not / does not support this operation, so currently, I have to do administrative password modifications in two steps:
> 1) set pwdReset to TRUE to allow password modification even if the minimum password age is not reached
> 2) use ldappasswd (on the linux shell) to set the new password
> This is of unnecessary complexity I think, as the Extended Operation is quite easy to implement with JNDI, I give an example using a little bit of Java and Groovy:
> I use the Bouncycastle Crypto Lib for ASN.1 encoding, but since it has some nasty features/bugs, I had to build a special version of the DERTaggedObject (basically a copy&paste version with some changes I don't recall in detail right now), of course other ASN.1 libs might not need this special behaviour:
> ---
> package org.bouncycastle.asn1;
> import java.io.ByteArrayOutputStream;
> import java.io.IOException;
> import java.io.OutputStream;
> /**
>  * DER TaggedObject - in ASN.1 nottation this is any object proceeded by a [n]
>  * where n is some number - these are assume to follow the construction rules
>  * (as with sequences).
>  */
> public class DERLongTaggedObject extends DERTaggedObject {
>         
>         @SuppressWarnings("unused")
>         private final static org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory
>                         .getLog(DERLongTaggedObject.class);
>         
>         protected int hiBits = 0;
>         
>     /**
>      * @param tagNo
>      *            the tag number for this object.
>      * @param obj
>      *            the tagged object.
>      */
>     public DERLongTaggedObject(int hiBits, int tagNo, DEREncodable obj) {
>         super(tagNo, obj);
>         this.hiBits = hiBits;
>     }
>     /**
>      * @param explicit
>      *            true if an explicitly tagged object.
>      * @param tagNo
>      *            the tag number for this object.
>      * @param obj
>      *            the tagged object.
>      */
>     public DERLongTaggedObject(boolean explicit, int hiBits, int tagNo, DEREncodable obj) {
>         super(explicit, tagNo, obj);
>         this.hiBits = hiBits;
>     }
>     /**
>      * create an implicitly tagged object that contains a zero length sequence.
>      */
>     public DERLongTaggedObject(int hiBits, int tagNo) {
>         this(false, hiBits, tagNo, new DERSequence());
>     }
>     void encode(DEROutputStream out) throws IOException {
>         // logger.debug("going to write with tag = "+tagNo+", hiBits = "+hiBits);
>         if (!empty) {
>             ByteArrayOutputStream bOut = new ByteArrayOutputStream();
>             DEROutputStream dOut = new DEROutputStream(bOut);
>             dOut.writeObject(obj);
>             dOut.close();
>             byte[] bytes = bOut.toByteArray();
>             if (tagNo < 31) {
>                 encodeTaggedShort(out, bytes);
>             } else {
>                 encodeTaggedLong(out, bytes);
>             }
>         } else {
>             if (tagNo < 31) {
>                 encodeEmptyTaggedShort(out);
>             } else {
>                 encodeEmptyTaggedLong(out);
>             }
>         }
>     }
>     private void encodeEmptyTaggedLong(DEROutputStream out) throws IOException {
>         out.write(CONSTRUCTED | TAGGED | 31);
>         writeTagNoLong(out);
>         out.write(0); // length
>     }
>     private void encodeEmptyTaggedShort(DEROutputStream out) throws IOException {
>         out.writeEncoded(CONSTRUCTED | TAGGED | tagNo, new byte[0]);
>     }
>     private void encodeTaggedLong(DEROutputStream out, byte[] encodedObject)
>             throws IOException {
>         if (explicit) {
>             // out.writeEncoded(CONSTRUCTED | TAGGED | tagNo, encodedObject);
>             out.write(CONSTRUCTED | TAGGED | 31); // a tag in long format
>             // follows
>             writeTagNoLong(out);
>             writeLength(out, encodedObject.length);
>             out.write(encodedObject);
>         } else {
>             //
>             // need to mark constructed types...
>             //
>             if ((hiBits & CONSTRUCTED  ) != 0 || (encodedObject[0] & CONSTRUCTED) != 0) {
>                 out.write(CONSTRUCTED | TAGGED | 31);
>             } else {
>                 out.write(TAGGED | 31);
>             }
>             writeTagNoLong(out);
>             out.write(encodedObject, 1, encodedObject.length - 1);
>         }
>     }
>     private void writeTagNoLong(DEROutputStream out) throws IOException {
>         long tagNoL = tagNo;
>         boolean writeZero = false;
>         for (int offset = 28; offset >= 0; offset -= 7) {
>             long sevenbits = (tagNoL >>> offset) & 0x7F;
>             if (sevenbits == 0 && !writeZero) {
>                 // leading block is empty, go to next 7 bits
>                 continue;
>             }
>             // from now on, zero value blocks have to be written.
>             writeZero = true;
>             if (offset > 0) {
>                 // set highest bit, because more blocks follow
>                 sevenbits |= 0x80;
>             }
>             out.write((int) sevenbits);
>         }
>     }
>     private void encodeTaggedShort(DEROutputStream out, byte[] encodedObject)
>             throws IOException {
>         if (explicit) {
>             out.writeEncoded(CONSTRUCTED | TAGGED | tagNo, encodedObject);
>         } else {
>             //
>             // need to mark constructed types...
>             //
>             if ((hiBits & CONSTRUCTED  ) != 0 || (encodedObject[0] & CONSTRUCTED) != 0 ) {
>                 encodedObject[0] = (byte) (CONSTRUCTED | TAGGED | tagNo);
>             } else {
>                 encodedObject[0] = (byte) (TAGGED | tagNo);
>             }
>             out.write(encodedObject);
>         }
>     }
>     private void writeLength(OutputStream out, int length) throws IOException {
>         if (length > 127) {
>             int size = 1;
>             int val = length;
>             while ((val >>>= 8) != 0) {
>                 size++;
>             }
>             out.write((byte) (size | 0x80));
>             for (int i = (size - 1) * 8; i >= 0; i -= 8) {
>                 out.write((byte) (length >> i));
>             }
>         } else {
>             out.write((byte) length);
>         }
>     }
> }
> ---
> Now the groovy code:
> ---
> class PasswordModifyResponse implements ExtendedResponse {
>         
>         byte[] encodedValue
>         
>         String id
>         
>         String genPasswd
>         
>         public PasswordModifyResponse(String id, byte[] encodedValue) {
>                 this.id = id
>                 this.encodedValue = encodedValue
>                 performDecoding()
>         }
>         
>         private performDecoding() {
>                 def ev = getEncodedValue()
>                 if ( ev.length == 0 ) {
>                         return
>                 }
>                 def asn1in = new ASN1InputStream(ev)
>                 ASN1Sequence seq = asn1in.readObject()
>                 println "[1] seq: ${seq.class} ${seq}"
>                 for ( int i = 0 ; i < seq.size() ; i++ ) {
>                         def obj = seq.getObjectAt(i)
>                         println "[2] obj: ${obj.class} ${obj} [${obj.tagNo}]"
>                         if ( obj.tagNo == 0 ) {
>                                 genPasswd = new String(obj.object.octets,'UTF-8')
>                         }
>                 }
>         }
>         
>         @Override
>         public byte[] getEncodedValue() {
>                 return encodedValue;
>         }
>         
>         @Override
>         public String getID() {
>                 return id;
>         }
>         
>         @Override
>         public String toString() {
>                 return super.toString() + 'ID:'+getID()+';encodedValue:'+getEncodedValue()+';genPasswd:'+genPasswd
>         }
>         
> }
> ---
> ---
> class PasswordModifyRequest implements ExtendedRequest {
>         
>         static final OID = '1.3.6.1.4.1.4203.1.11.1'
>         
>         String userIdentity
>         String oldPasswd
>         String newPasswd
>         public PasswordModifyRequest(String userIdentity, String oldPasswd,
>                         String newPasswd) {
>                 this.userIdentity = userIdentity;
>                 this.oldPasswd = oldPasswd;
>                 this.newPasswd = newPasswd;
>         }       
>         
>         /*
>          * passwdModifyOID OBJECT IDENTIFIER ::= 1.3.6.1.4.1.4203.1.11.1
>    PasswdModifyRequestValue ::= SEQUENCE {
>      userIdentity    [0]  OCTET STRING OPTIONAL
>      oldPasswd       [1]  OCTET STRING OPTIONAL
>      newPasswd       [2]  OCTET STRING OPTIONAL }
>    PasswdModifyResponseValue ::= SEQUENCE {
>      genPasswd       [0]     OCTET STRING OPTIONAL }
>          */
>         
>         @Override
>         public ExtendedResponse createExtendedResponse(String id, byte[] berValue,
>                         int offset, int length) throws NamingException {
>                 byte[] input = new byte[length]
>                 if ( berValue != null ) {
>                         System.arraycopy(berValue, offset, input, 0, length)
>                 }               
>                 return new PasswordModifyResponse(id, input);
>         }
>         
>         @Override
>         public byte[] getEncodedValue() {
>                 ASN1EncodableVector v = new ASN1EncodableVector()
>                 if ( userIdentity ) {
>                     v.add(new DERLongTaggedObject(false, 0, 0, new DEROctetString(userIdentity.getBytes('UTF-8'))))
>                 }
>                 if ( oldPasswd ) {
>                      v.add(new DERLongTaggedObject(false, 0, 1, new DEROctetString(oldPasswd.getBytes('UTF-8'))))
>                 }
>                 if ( newPasswd ) {
>                      v.add(new DERLongTaggedObject(false, 0, 2, new DEROctetString(newPasswd.getBytes('UTF-8'))))
>                 }    
>                 BERSequence sequence = new BERSequence(v)
>                 def encoded = sequence.getEncoded(BERSequence.BER)
>                 println "encoded: ${encoded}"
>                 File f = new File('/tmp/asn1.content') 
>                 f.delete()
>                 f << encoded
>                 return encoded
>         }
>         
>         @Override
>         public String getID() {
>                 return OID;
>         }
>         
> }
> ---
> Usage will then be as follows (groovy pseudocode as well):
> LdapContext ctx = ...
> PasswordModifyRequest req = new PasswordModifyRequest(userDn, userPass, newPassword)
> PasswordModifyResponse resp = ctx.extendedOperation(req)
> println "resp: ${resp}"
> ---
> Hope this helps!



--
This message was sent by Atlassian Jira
(v8.3.4#803005)

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@directory.apache.org
For additional commands, e-mail: dev-help@directory.apache.org