directory-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Carsten Tolkmit (JIRA)" <j...@apache.org>
Subject [jira] Created: (DIRSTUDIO-648) Studio should support the Password Modify Extended Operation according to RFC 3062
Date Fri, 16 Apr 2010 08:51:26 GMT
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
            Priority: Minor


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 is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: https://issues.apache.org/jira/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

        

Mime
View raw message