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