001: /*
002:
003: Derby - Class org.apache.derby.client.am.EncryptionManager
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to You under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.client.am;
023:
024: import java.security.Provider;
025: import java.security.Security;
026: import org.apache.derby.shared.common.reference.SQLState;
027: import org.apache.derby.shared.common.sanity.SanityManager;
028:
029: // This class is get used when using encrypted password and/or userid mechanism.
030: // The <b>EncryptionManager</b> classs uses Diffie_Hellman algorithm to get the publick key and
031: // secret key, and then DES encryption is done using certain token (based on security
032: // mechanism) and server side's public key. Basically, this class is called when using
033: // security mechanism User ID and encrypted password (usrencpwd) and Encrypted user ID and password
034: // (eusridpwd).
035: // This class uses JCE provider to do Diffie_Hellman algorithm and DES encryption,
036: // obtainPublicKey(), calculateEncryptionToken(int, byte[]) and encryptData(byte[], int, byte[], byte[])
037: // The agreed public value for the Diffie-Hellman prime is 256 bits
038: // and hence the encrytion will work only if the jce provider supports a 256 bits prime
039: //
040: // This class also have methods for the SECMEC_USRSSBPWD security mechanism.
041:
042: public class EncryptionManager {
043: transient Agent agent_; // for obtaining an exception log writer only
044:
045: // PROTOCOL's Diffie-Hellman agreed public value: prime.
046: private static final byte modulusBytes__[] = { (byte) 0xC6,
047: (byte) 0x21, (byte) 0x12, (byte) 0xD7, (byte) 0x3E,
048: (byte) 0xE6, (byte) 0x13, (byte) 0xF0, (byte) 0x94,
049: (byte) 0x7A, (byte) 0xB3, (byte) 0x1F, (byte) 0x0F,
050: (byte) 0x68, (byte) 0x46, (byte) 0xA1, (byte) 0xBF,
051: (byte) 0xF5, (byte) 0xB3, (byte) 0xA4, (byte) 0xCA,
052: (byte) 0x0D, (byte) 0x60, (byte) 0xBC, (byte) 0x1E,
053: (byte) 0x4C, (byte) 0x7A, (byte) 0x0D, (byte) 0x8C,
054: (byte) 0x16, (byte) 0xB3, (byte) 0xE3 };
055:
056: //the prime value in BigInteger form. It has to be in BigInteger form because this
057: //is the form used in JCE library.
058: private static final java.math.BigInteger modulus__ = new java.math.BigInteger(
059: 1, modulusBytes__);
060:
061: // PROTOCOL's Diffie-Hellman agreed public value: base.
062: private static final byte baseBytes__[] = { (byte) 0x46,
063: (byte) 0x90, (byte) 0xFA, (byte) 0x1F, (byte) 0x7B,
064: (byte) 0x9E, (byte) 0x1D, (byte) 0x44, (byte) 0x42,
065: (byte) 0xC8, (byte) 0x6C, (byte) 0x91, (byte) 0x14,
066: (byte) 0x60, (byte) 0x3F, (byte) 0xDE, (byte) 0xCF,
067: (byte) 0x07, (byte) 0x1E, (byte) 0xDC, (byte) 0xEC,
068: (byte) 0x5F, (byte) 0x62, (byte) 0x6E, (byte) 0x21,
069: (byte) 0xE2, (byte) 0x56, (byte) 0xAE, (byte) 0xD9,
070: (byte) 0xEA, (byte) 0x34, (byte) 0xE4 };
071:
072: // The base value in BigInteger form.
073: private static final java.math.BigInteger base__ = new java.math.BigInteger(
074: 1, baseBytes__);
075:
076: //PROTOCOL's Diffie-Hellman agreed exponential length
077: private static final int exponential_length__ = 255;
078:
079: private javax.crypto.spec.DHParameterSpec paramSpec_;
080: private java.security.KeyPairGenerator keyPairGenerator_;
081: private java.security.KeyPair keyPair_;
082: private javax.crypto.KeyAgreement keyAgreement_;
083:
084: private byte[] token_; // init vector
085: private byte[] secKey_; // security key
086: private javax.crypto.SecretKeyFactory secretKeyFactory_ = null;
087: private String providerName; // security provider name
088: private Provider provider;
089:
090: // Required for SECMEC_USRSSBPWD DRDA security mechanism
091: // NOTE: In a next incarnation, these constants are being moved
092: // to a dedicated/specialized SecMec_USRSSBPWD class implementing
093: // a SecurityMechanism interface.
094: private java.security.MessageDigest messageDigest = null;
095: private java.security.SecureRandom secureRandom = null;
096: private final static int SECMEC_USRSSBPWD_SEED_LEN = 8; // Seed length
097: // PWSEQs's 8-byte value constant - See DRDA Vol 3
098: private static final byte SECMEC_USRSSBPWD_PWDSEQS[] = {
099: (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
100: (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01 };
101: // Random Number Generator (PRNG) Algorithm
102: private final static String SHA_1_PRNG_ALGORITHM = "SHA1PRNG";
103: public final static String SHA_1_DIGEST_ALGORITHM = "SHA-1";
104:
105: // EncryptionManager constructor. In this constructor,DHParameterSpec,
106: // KeyPairGenerator, KeyPair, and KeyAgreement are initialized.
107: public EncryptionManager(Agent agent) throws SqlException {
108: agent_ = agent;
109: try {
110: // get a security provider that supports the diffie helman key agreement algorithm
111: Provider[] list = Security.getProviders("KeyAgreement.DH");
112: if (list == null) {
113: throw new java.security.NoSuchProviderException();
114: }
115: provider = list[0];
116: providerName = provider.getName();
117: paramSpec_ = new javax.crypto.spec.DHParameterSpec(
118: modulus__, base__, exponential_length__);
119: keyPairGenerator_ = java.security.KeyPairGenerator
120: .getInstance("DH", providerName);
121: keyPairGenerator_.initialize(paramSpec_);
122: keyPair_ = keyPairGenerator_.generateKeyPair();
123: keyAgreement_ = javax.crypto.KeyAgreement.getInstance("DH",
124: providerName);
125: keyAgreement_.init(keyPair_.getPrivate());
126: } catch (java.security.GeneralSecurityException e) {
127: throw new SqlException(agent_.logWriter_,
128: new ClientMessageId(
129: SQLState.SECURITY_EXCEPTION_ENCOUNTERED), e);
130: }
131: }
132:
133: // Retrieve a particular instance of the Encryption manager for a given
134: // (Messsage Digest) algorithm. This is currently required for the
135: // SECMEC_USRSSBPWD (strong password substitute) security mechanism.
136: //
137: // NOTE: This is temporary logic as the encryption manager is being
138: // rewritten into a DRDASecurityManager and have some of the
139: // client/engine common logic moved to the Derby 'shared' package.
140: public EncryptionManager(Agent agent, String algorithm)
141: throws SqlException {
142: agent_ = agent;
143: try {
144: // Instantiate the encryption manager for the passed-in security
145: // algorithm and this from the default provider
146: // NOTE: We're only dealing with Message Digest algorithms for now.
147: messageDigest = java.security.MessageDigest
148: .getInstance(algorithm);
149: // We're also verifying that we can instantiate a randon number
150: // generator (PRNG).
151: secureRandom = java.security.SecureRandom
152: .getInstance(SHA_1_PRNG_ALGORITHM);
153: } catch (java.security.NoSuchAlgorithmException nsae) {
154: // The following exception should not be raised for SHA-1 type of
155: // message digest as we've already verified during boot-up that this
156: // algorithm was available as part of the JRE (since BUILT-IN
157: // authentication requires it); but we still raise the exception if
158: // a client were to request a different algorithm.
159: throw new SqlException(agent_.logWriter_,
160: new ClientMessageId(
161: SQLState.SECURITY_EXCEPTION_ENCOUNTERED),
162: nsae);
163: }
164: }
165:
166: // This method generates the public key and returns it. This
167: // shared public key is the application requester's connection key and will
168: // be exchanged with the application server's connection key. This connection
169: // key will be put in the sectkn in ACCSEC command and send to the application
170: // server.
171: // @param null
172: // @return a byte array that is the application requester's public key
173: public byte[] obtainPublicKey() {
174:
175: //we need to get the plain form public key because PROTOCOL accepts plain form
176: //public key only.
177: java.math.BigInteger aPub = ((javax.crypto.interfaces.DHPublicKey) keyPair_
178: .getPublic()).getY();
179: byte[] aPubKey = aPub.toByteArray();
180:
181: //the following lines of code is to adjust the length of the key. PublicKey
182: //in JCE is in the form of BigInteger and it's a signed value. When tranformed
183: //to a Byte array form, normally this array is 32 bytes. However, if the
184: //value happens to take up all 32 X 8 bits and it is positive, an extra
185: //bit is needed and then a 33 byte array will be returned. Since PROTOCOL can't
186: //recogize the 33 byte key, we check the length here, if the length is 33,
187: //we will just trim off the first byte (0) and get the rest of 32 bytes.
188: if (aPubKey.length == 33 && aPubKey[0] == 0) {
189: //System.out.println ("Adjust length");
190: byte[] newKey = new byte[32];
191: for (int i = 0; i < newKey.length; i++) {
192: newKey[i] = aPubKey[i + 1];
193: }
194: return newKey;
195: }
196:
197: //the following lines of code is to adjust the length of the key. Occasionally,
198: //the length of the public key is less than 32, the reason of this is that the 0 byte
199: //in the beginning is somehow not returned. So we check the length here, if the length
200: //is less than 32, we will pad 0 in the beginning to make the public key 32 bytes
201: if (aPubKey.length < 32) {
202: byte[] newKey = new byte[32];
203: int i;
204: for (i = 0; i < 32 - aPubKey.length; i++) {
205: newKey[i] = 0;
206: }
207: for (int j = i; j < newKey.length; j++) {
208: newKey[j] = aPubKey[j - i];
209: }
210: return newKey;
211: }
212: return aPubKey;
213: }
214:
215: // This method is used to calculate the encryption token. DES encrypts the
216: // data using a token and the generated shared private key. The token used
217: // depends on the type of security mechanism being used:
218: // USRENCPWD - The userid is used as the token. The USRID is zero-padded to
219: // 8 bytes if less than 8 bytes or truncated to 8 bytes if greater than 8 bytes.
220: // EUSRIDPWD - The middle 8 bytes of the server's connection key is used as
221: // the token.
222: // @param int securityMechanism
223: // @param byte[] userid or server's connection key
224: // @return byte[] the encryption token
225: private byte[] calculateEncryptionToken(int securityMechanism,
226: byte[] initVector) {
227: byte[] token = new byte[8];
228:
229: //USRENCPWD, the userid is used as token
230: if (securityMechanism == 7) {
231: if (initVector.length < 8) { //shorter than 8 bytes, zero padded to 8 bytes
232: for (int i = 0; i < initVector.length; i++) {
233: token[i] = initVector[i];
234: }
235: for (int i = initVector.length; i < 8; i++) {
236: token[i] = 0;
237: }
238: } else { //longer than 8 bytes, truncated to 8 bytes
239: for (int i = 0; i < 8; i++) {
240: token[i] = initVector[i];
241: }
242: }
243: }
244: //EUSRIDPWD - The middle 8 bytes of the server's connection key is used as
245: //the token.
246: else if (securityMechanism == 9) {
247: for (int i = 0; i < 8; i++) {
248: token[i] = initVector[i + 12];
249: }
250: }
251: return token;
252: }
253:
254: //JDK 1.4 has a parity check on the DES encryption key. Each byte needs to have an odd number
255: //of "1"s in it, and this is required by DES. Otherwise JDK 1.4 throws InvalidKeyException.
256: //Older JDK doesn't check this. In order to make encryption work with JDK1.4, we are going to
257: //check each of the 8 byte of our key and flip the last bit if it has even number of 1s.
258: private void keyParityCheck(byte[] key) throws SqlException {
259: byte temp;
260: int changeParity;
261: if (key.length != 8) {
262: throw new SqlException(agent_.logWriter_,
263: new ClientMessageId(
264: SQLState.DES_KEY_HAS_WRONG_LENGTH),
265: new Integer(8), new Integer(key.length));
266:
267: }
268: for (int i = 0; i < 8; i++) {
269: temp = key[i];
270: changeParity = 1;
271: for (int j = 0; j < 8; j++) {
272: if (temp < 0) {
273: changeParity = 1 - changeParity;
274: }
275: temp = (byte) (temp << 1);
276: }
277: if (changeParity == 1) {
278: if ((key[i] & 1) != 0) {
279: key[i] &= 0xfe;
280: } else {
281: key[i] |= 1;
282: }
283: }
284: }
285: }
286:
287: // This method generates a secret key using the application server's
288: // public key
289: private byte[] generatePrivateKey(byte[] targetPublicKey)
290: throws SqlException {
291: try {
292:
293: //initiate a Diffie_Hellman KeyFactory object.
294: java.security.KeyFactory keyFac = java.security.KeyFactory
295: .getInstance("DH", provider);
296:
297: //Use server's public key to initiate a DHPublicKeySpec and then use
298: //this DHPublicKeySpec to initiate a publicKey object
299: java.math.BigInteger publicKey = new java.math.BigInteger(
300: 1, targetPublicKey);
301: javax.crypto.spec.DHPublicKeySpec dhKeySpec = new javax.crypto.spec.DHPublicKeySpec(
302: publicKey, modulus__, base__);
303: java.security.PublicKey pubKey = keyFac
304: .generatePublic(dhKeySpec);
305:
306: //Execute the first phase of DH keyagreement protocal.
307: keyAgreement_.doPhase(pubKey, true);
308:
309: //generate the shared secret key. The application requestor's shared secret
310: //key should be exactly the same as the application server's shared secret
311: //key
312: byte[] sharedSecret = keyAgreement_.generateSecret();
313: byte[] newKey = new byte[32];
314:
315: //We adjust the length here. If the length of secret key is 33 and the first byte is 0,
316: //we trim off the frist byte. If the length of secret key is less than 32, we will
317: //pad 0 to the beginning of the byte array tho make the secret key 32 bytes.
318: if (sharedSecret.length == 33 && sharedSecret[0] == 0) {
319: for (int i = 0; i < newKey.length; i++) {
320: newKey[i] = sharedSecret[i + 1];
321: }
322:
323: }
324: if (sharedSecret.length < 32) {
325: int i;
326: for (i = 0; i < (32 - sharedSecret.length); i++) {
327: newKey[i] = 0;
328: }
329: for (int j = i; j < sharedSecret.length; j++) {
330: newKey[j] = sharedSecret[j - i];
331: }
332: }
333:
334: //The Data Encryption Standard (DES) is going to be used to encrypt userid
335: //and password. DES is a block cipher; it encrypts data in 64-bit blocks.
336: //PROTOCOL encryption uses DES CBC mode as defined by the FIPS standard
337: //DES CBC requires an encryption key and an 8 byte token to encrypt the data.
338: //The middle 8 bytes of Diffie-Hellman shared private key is used as the
339: //encryption key. The following code retrieves middle 8 bytes of the shared
340: //private key.
341: byte[] key = new byte[8];
342:
343: //if secret key is not 32, we will use the adjust length secret key
344: if (sharedSecret.length == 32) {
345: for (int i = 0; i < 8; i++) {
346: key[i] = sharedSecret[i + 12];
347: }
348: } else if (sharedSecret.length == 33
349: || sharedSecret.length < 32) {
350: for (int i = 0; i < 8; i++) {
351: key[i] = newKey[i + 12];
352: }
353: } else {
354: throw new SqlException(agent_.logWriter_,
355: new ClientMessageId(
356: SQLState.SHARED_KEY_LENGTH_ERROR),
357: new Integer(sharedSecret.length));
358: }
359:
360: //we do parity check here and flip the parity bit if the byte has even number of 1s
361: keyParityCheck(key);
362: return key;
363: } catch (java.security.GeneralSecurityException e) {
364: throw new SqlException(agent_.logWriter_,
365: new ClientMessageId(
366: SQLState.SECURITY_EXCEPTION_ENCOUNTERED), e);
367: }
368: }
369:
370: // This method encrypts the usreid/password with the middle 8 bytes of
371: // the generated secret key and an encryption token. Then it returns the
372: // encrypted data in a byte array.
373: // plainText The byte array form userid/password to encrypt.
374: // initVector The byte array which is used to calculate the
375: // encryption token.
376: // targetPublicKey DERBY' public key.
377: // Returns the encrypted data in a byte array.
378: public byte[] encryptData(byte[] plainText, int securityMechanism,
379: byte[] initVector, byte[] targetPublicKey)
380: throws SqlException {
381:
382: byte[] cipherText = null;
383: java.security.Key key = null;
384:
385: if (token_ == null) {
386: token_ = calculateEncryptionToken(securityMechanism,
387: initVector);
388: }
389:
390: try {
391: if (secKey_ == null) {
392: //use this encryption key to initiate a SecretKeySpec object
393: secKey_ = generatePrivateKey(targetPublicKey);
394: javax.crypto.spec.SecretKeySpec desKey = new javax.crypto.spec.SecretKeySpec(
395: secKey_, "DES");
396: key = desKey;
397: } else {
398: //use this encryption key to initiate a SecretKeySpec object
399: javax.crypto.spec.DESKeySpec desKey = new javax.crypto.spec.DESKeySpec(
400: secKey_);
401: if (secretKeyFactory_ == null) {
402: secretKeyFactory_ = javax.crypto.SecretKeyFactory
403: .getInstance("DES", providerName);
404: }
405: key = secretKeyFactory_.generateSecret(desKey);
406: }
407:
408: //We use DES in CBC mode because this is the mode used in PROTOCOL. The
409: //encryption mode has to be consistent for encryption and decryption.
410: //CBC mode requires an initialization vector(IV) parameter. In CBC mode
411: //we need to initialize the Cipher object with an IV, which can be supplied
412: // using the javax.crypto.spec.IvParameterSpec class.
413: javax.crypto.Cipher cipher = javax.crypto.Cipher
414: .getInstance("DES/CBC/PKCS5Padding", providerName);
415:
416: //generate a IVParameterSpec object and use it to initiate the
417: //Cipher object.
418: javax.crypto.spec.IvParameterSpec ivParam = new javax.crypto.spec.IvParameterSpec(
419: token_);
420:
421: //initiate the Cipher using encryption mode, encryption key and the
422: //IV parameter.
423: cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key, ivParam);
424:
425: //Execute the final phase of encryption
426: cipherText = cipher.doFinal(plainText);
427: } catch (javax.crypto.NoSuchPaddingException e) {
428: throw new SqlException(
429: agent_.logWriter_,
430: new ClientMessageId(SQLState.CRYPTO_NO_SUCH_PADDING));
431: } catch (javax.crypto.BadPaddingException e) {
432: throw new SqlException(agent_.logWriter_,
433: new ClientMessageId(SQLState.CRYPTO_BAD_PADDING));
434: } catch (javax.crypto.IllegalBlockSizeException e) {
435: throw new SqlException(agent_.logWriter_,
436: new ClientMessageId(
437: SQLState.CRYPTO_ILLEGAL_BLOCK_SIZE));
438: } catch (java.security.GeneralSecurityException e) {
439: throw new SqlException(agent_.logWriter_,
440: new ClientMessageId(
441: SQLState.SECURITY_EXCEPTION_ENCOUNTERED), e);
442: }
443:
444: return cipherText;
445: }
446:
447: // This method decrypts the usreid/password with the middle 8 bytes of
448: // the generated secret key and an encryption token. Then it returns the
449: // decrypted data in a byte array.
450: // plainText The byte array form userid/password to encrypt.
451: // initVector The byte array which is used to calculate the
452: // encryption token.
453: // targetPublicKey DERBY' public key.
454: // Returns the decrypted data in a byte array.
455: public byte[] decryptData(byte[] cipherText, int securityMechanism,
456: byte[] initVector, byte[] targetPublicKey)
457: throws SqlException {
458:
459: byte[] plainText = null;
460: java.security.Key key = null;
461:
462: if (token_ == null) {
463: token_ = calculateEncryptionToken(securityMechanism,
464: initVector);
465: }
466:
467: try {
468: if (secKey_ == null) {
469: //use this encryption key to initiate a SecretKeySpec object
470: secKey_ = generatePrivateKey(targetPublicKey);
471: javax.crypto.spec.SecretKeySpec desKey = new javax.crypto.spec.SecretKeySpec(
472: secKey_, "DES");
473: key = desKey;
474: } else {
475: //use this encryption key to initiate a SecretKeySpec object
476: javax.crypto.spec.DESKeySpec desKey = new javax.crypto.spec.DESKeySpec(
477: secKey_);
478: if (secretKeyFactory_ == null) {
479: secretKeyFactory_ = javax.crypto.SecretKeyFactory
480: .getInstance("DES", providerName);
481: }
482: key = secretKeyFactory_.generateSecret(desKey);
483: }
484:
485: //We use DES in CBC mode because this is the mode used in PROTOCOL. The
486: //encryption mode has to be consistent for encryption and decryption.
487: //CBC mode requires an initialization vector(IV) parameter. In CBC mode
488: //we need to initialize the Cipher object with an IV, which can be supplied
489: // using the javax.crypto.spec.IvParameterSpec class.
490: javax.crypto.Cipher cipher = javax.crypto.Cipher
491: .getInstance("DES/CBC/PKCS5Padding", providerName);
492:
493: //generate a IVParameterSpec object and use it to initiate the
494: //Cipher object.
495: javax.crypto.spec.IvParameterSpec ivParam = new javax.crypto.spec.IvParameterSpec(
496: token_);
497:
498: //initiate the Cipher using encryption mode, encryption key and the
499: //IV parameter.
500: cipher.init(javax.crypto.Cipher.DECRYPT_MODE, key, ivParam);
501:
502: //Execute the final phase of encryption
503: plainText = cipher.doFinal(cipherText);
504: } catch (javax.crypto.NoSuchPaddingException e) {
505: throw new SqlException(
506: agent_.logWriter_,
507: new ClientMessageId(SQLState.CRYPTO_NO_SUCH_PADDING));
508: } catch (javax.crypto.BadPaddingException e) {
509: throw new SqlException(agent_.logWriter_,
510: new ClientMessageId(SQLState.CRYPTO_BAD_PADDING));
511: } catch (javax.crypto.IllegalBlockSizeException e) {
512: throw new SqlException(agent_.logWriter_,
513: new ClientMessageId(
514: SQLState.CRYPTO_ILLEGAL_BLOCK_SIZE));
515: } catch (java.security.GeneralSecurityException e) {
516: throw new SqlException(agent_.logWriter_,
517: new ClientMessageId(
518: SQLState.SECURITY_EXCEPTION_ENCOUNTERED), e);
519: }
520: return plainText;
521: }
522:
523: public void setInitVector(byte[] initVector) {
524: token_ = initVector;
525: }
526:
527: public void setSecKey(byte[] secKey) {
528: secKey_ = secKey;
529: }
530:
531: public void resetSecurityKeys() {
532: token_ = null;
533: secKey_ = null;
534: }
535:
536: /****************************************************************
537: * Below are methods for the SECMEC_USRSSBPWD security mechanism.
538: ****************************************************************/
539:
540: /**
541: * This method generates an 8-Byte random seed for the client (source).
542: *
543: * @return a random 8-Byte seed.
544: */
545: public byte[] generateSeed() {
546: byte randomSeedBytes[] = new byte[SECMEC_USRSSBPWD_SEED_LEN];
547: secureRandom.setSeed(secureRandom
548: .generateSeed(SECMEC_USRSSBPWD_SEED_LEN));
549: secureRandom.nextBytes(randomSeedBytes);
550: return randomSeedBytes;
551: }
552:
553: /**
554: * Strong Password Substitution (USRSSBPWD).
555: *
556: * This method generate a password subtitute to send to the target
557: * server.
558: *
559: * Substitution algorithm works as follow:
560: *
561: * PW_TOKEN = SHA-1(PW, ID)
562: * The password (PW) and user name (ID) can be of any length greater
563: * than or equal to 1 byte.
564: * The client generates a 20-byte password substitute (PW_SUB) as follows:
565: * PW_SUB = SHA-1(PW_TOKEN, RDr, RDs, ID, PWSEQs)
566: *
567: * w/ (RDs) as the random client seed and (RDr) as the server one.
568: *
569: * See PWDSSB - Strong Password Substitution Security Mechanism
570: * (DRDA Vol.3 - P.650)
571: *
572: * @param userName The user's name
573: * @param password The user's password
574: * @param sourceSeed_ random client seed (RDs)
575: * @param targetSeed_ random server seed (RDr)
576: *
577: * @return a password substitute.
578: */
579: public byte[] substitutePassword(String userName, String password,
580: byte[] sourceSeed_, byte[] targetSeed_) throws SqlException {
581:
582: // Pattern that is prefixed to the BUILTIN encrypted password
583: String ID_PATTERN_NEW_SCHEME = "3b60";
584:
585: // Generated password substitute
586: byte[] passwordSubstitute;
587:
588: // Assert we have a SHA-1 Message Digest already instantiated
589: if (SanityManager.DEBUG) {
590: SanityManager.ASSERT((messageDigest != null)
591: && (SHA_1_DIGEST_ALGORITHM.equals(messageDigest
592: .getAlgorithm())));
593: }
594:
595: // IMPORTANT NOTE: As the password is stored single-hashed in the
596: // database on the target side, it is impossible for the target to
597: // decrypt the password and recompute a substitute to compare with
598: // one generated on the source side - Hence, for now we have to
599: // single-hash and encrypt the password the same way the target is
600: // doing it and we will still generate a substitute obviously - The
601: // password, even pre-hashed will never make it across the wire as
602: // a substitute is generated. In other words, if the target cannot
603: // figure what the original password is (because of not being able
604: // to decrypt it or not being able to retrieve it (i.e. LDAP), then
605: // It may be problematic - so in a way, Strong Password Substitution
606: // (USRSSBPWD) cannot be supported for targets which can't access or
607: // decrypt some password on their side.
608: //
609: // So in short, SECMEC_USRSSBPWD is only supported if the
610: // authentication provider on the target side is NONE or Derby's
611: // BUILTIN one and if using Derby's Client Network driver (for now).
612: //
613: // Encrypt the password as it is done by the derby engine - Note that
614: // this code (logic) is not shared yet - will be in next revision.
615: messageDigest.reset();
616:
617: messageDigest.update(this .toHexByte(password, 0, password
618: .length()));
619: byte[] encryptVal = messageDigest.digest();
620: String hexString = ID_PATTERN_NEW_SCHEME
621: + this .toHexString(encryptVal, 0, encryptVal.length);
622:
623: // Generate some 20-byte password token
624: byte[] userBytes = this .toHexByte(userName, 0, userName
625: .length());
626: messageDigest.update(userBytes);
627: messageDigest.update(this .toHexByte(hexString, 0, hexString
628: .length()));
629: byte[] passwordToken = messageDigest.digest();
630:
631: // Now we generate the 20-byte password substitute
632: messageDigest.update(passwordToken);
633: messageDigest.update(targetSeed_);
634: messageDigest.update(sourceSeed_);
635: messageDigest.update(userBytes);
636: messageDigest.update(SECMEC_USRSSBPWD_PWDSEQS);
637:
638: passwordSubstitute = messageDigest.digest();
639:
640: return passwordSubstitute;
641: }
642:
643: /*********************************************************************
644: * RESOLVE: *
645: * The methods and static vars below should go into some 'shared' *
646: * package when the capability is put back in (StringUtil.java). *
647: *********************************************************************/
648:
649: private static char[] hex_table = { '0', '1', '2', '3', '4', '5',
650: '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
651:
652: /**
653: Convert a byte array to a String with a hexidecimal format.
654: The String may be converted back to a byte array using fromHexString.
655: <BR>
656: For each byte (b) two characaters are generated, the first character
657: represents the high nibble (4 bits) in hexidecimal (<code>b & 0xf0</code>),
658: the second character represents the low nibble (<code>b & 0x0f</code>).
659: <BR>
660: The byte at <code>data[offset]</code> is represented by the first two
661: characters in the returned String.
662:
663: @param data byte array
664: @param offset starting byte (zero based) to convert.
665: @param length number of bytes to convert.
666:
667: @return the String (with hexidecimal format) form of the byte array
668: */
669: private String toHexString(byte[] data, int offset, int length) {
670: StringBuffer s = new StringBuffer(length * 2);
671: int end = offset + length;
672:
673: for (int i = offset; i < end; i++) {
674: int high_nibble = (data[i] & 0xf0) >>> 4;
675: int low_nibble = (data[i] & 0x0f);
676: s.append(hex_table[high_nibble]);
677: s.append(hex_table[low_nibble]);
678: }
679:
680: return s.toString();
681: }
682:
683: /**
684:
685: Convert a string into a byte array in hex format.
686: <BR>
687: For each character (b) two bytes are generated, the first byte
688: represents the high nibble (4 bits) in hexidecimal (<code>b & 0xf0</code>),
689: the second byte represents the low nibble (<code>b & 0x0f</code>).
690: <BR>
691: The character at <code>str.charAt(0)</code> is represented by the first two bytes
692: in the returned String.
693:
694: @param str string
695: @param offset starting character (zero based) to convert.
696: @param length number of characters to convert.
697:
698: @return the byte[] (with hexidecimal format) form of the string (str)
699: */
700: private byte[] toHexByte(String str, int offset, int length) {
701: byte[] data = new byte[(length - offset) * 2];
702: int end = offset + length;
703:
704: for (int i = offset; i < end; i++) {
705: char ch = str.charAt(i);
706: int high_nibble = (ch & 0xf0) >>> 4;
707: int low_nibble = (ch & 0x0f);
708: data[i] = (byte) high_nibble;
709: data[i + 1] = (byte) low_nibble;
710: }
711: return data;
712: }
713: }
|