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