Details
-
New Feature
-
Status: Closed
-
Major
-
Resolution: Fixed
-
1.5.3
-
None
-
None
-
Apache Directory Studio 1.5.3 against OpenLDAP 2.4.x with slapo-ppolicy enabled; using hashed passwords; on Ubuntu Linux
Description
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)
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)
else {
//
// need to mark constructed types...
//
if ((hiBits & CONSTRUCTED ) != 0 || (encodedObject[0] & CONSTRUCTED) != 0)
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)
// from now on, zero value blocks have to be written.
writeZero = true;
if (offset > 0)
out.write((int) sevenbits);
}
}
private void encodeTaggedShort(DEROutputStream out, byte[] encodedObject)
throws IOException {
if (explicit)
else {
//
// need to mark constructed types...
//
if ((hiBits & CONSTRUCTED ) != 0 || (encodedObject[0] & CONSTRUCTED) != 0 )
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 )
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
println "[2] obj: ${obj.class} ${obj} [${obj.tagNo}]"
if ( obj.tagNo == 0 )
}
}
@Override
public byte[] getEncodedValue()
@Override
public String getID()
@Override
public String toString()
}
—
—
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)
/*
- 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 )
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()
}
—
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!
Attachments
Attachments
Issue Links
- is blocked by
-
DIRAPI-354 java.lang.NoClassDefFoundError: org/apache/directory/api/i18n/I18n in osgi
- Resolved
- is related to
-
DIRSTUDIO-1122 Password change not working when pwdSafeModify: TRUE
- Closed
-
DIRSTUDIO-820 Menu entry to change password
- Open
- links to