lib/util: add generate_random_machine_password() function
authorStefan Metzmacher <metze@samba.org>
Tue, 23 Aug 2016 07:30:05 +0000 (09:30 +0200)
committerStefan Metzmacher <metze@samba.org>
Sat, 25 Feb 2017 00:35:16 +0000 (01:35 +0100)
It generates more random password for the use as machine password,
restricted to codepoints <= 0xFFFF in order to be compatible
with MIT krb5 and Heimdal.

Note: the fallback to ascii if 'unix charset' is not 'utf8'.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12262

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
(cherry picked from commit ad12cfae42cc592166d6a1c1ee323f1aae82f235)

lib/util/genrand_util.c
lib/util/samba_util.h

index fbd99989ac2af9baf2bd065f97d84d23c655747c..76b7cd918908bab93ee54298f029f55d49f4069a 100644 (file)
@@ -210,7 +210,7 @@ again:
 }
 
 /**
- * Generate a random text password.
+ * Generate a random text password (based on printable ascii characters).
  */
 
 _PUBLIC_ char *generate_random_password(TALLOC_CTX *mem_ctx, size_t min, size_t max)
@@ -257,6 +257,172 @@ again:
        return retstr;
 }
 
+/**
+ * Generate a random machine password (based on random utf16 characters,
+ * converted to utf8). min must be at least 14, max must be at most 255.
+ *
+ * If 'unix charset' is not utf8, the password consist of random ascii
+ * values!
+ */
+
+_PUBLIC_ char *generate_random_machine_password(TALLOC_CTX *mem_ctx, size_t min, size_t max)
+{
+       TALLOC_CTX *frame = NULL;
+       struct generate_random_machine_password_state {
+               uint8_t password_buffer[256 * 2];
+               uint8_t tmp;
+       } *state;
+       char *new_pw = NULL;
+       size_t len = max;
+       char *utf8_pw = NULL;
+       size_t utf8_len = 0;
+       char *unix_pw = NULL;
+       size_t unix_len = 0;
+       size_t diff;
+       size_t i;
+       bool ok;
+       int cmp;
+
+       if (max > 255) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       if (min < 14) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       if (min > max) {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       frame = talloc_stackframe_pool(2048);
+       state = talloc_zero(frame, struct generate_random_machine_password_state);
+
+       diff = max - min;
+
+       if (diff > 0) {
+               size_t tmp;
+
+               generate_random_buffer((uint8_t *)&tmp, sizeof(tmp));
+
+               tmp %= diff;
+
+               len = min + tmp;
+       }
+
+       /*
+        * Create a random machine account password
+        * We create a random buffer and convert that to utf8.
+        * This is similar to what windows is doing.
+        *
+        * In future we may store the raw random buffer,
+        * but for now we need to pass the password as
+        * char pointer through some layers.
+        *
+        * As most kerberos keys are derived from the
+        * utf8 password we need to fallback to
+        * ASCII passwords if "unix charset" is not utf8.
+        */
+       generate_secret_buffer(state->password_buffer, len * 2);
+       for (i = 0; i < len; i++) {
+               size_t idx = i*2;
+               uint16_t c;
+
+               /*
+                * both MIT krb5 and HEIMDAL only
+                * handle codepoints up to 0xffff.
+                *
+                * It means we need to avoid
+                * 0xD800 - 0xDBFF (high surrogate)
+                * and
+                * 0xDC00 - 0xDFFF (low surrogate)
+                * in the random utf16 data.
+                *
+                * 55296 0xD800 0154000 0b1101100000000000
+                * 57343 0xDFFF 0157777 0b1101111111111111
+                * 8192  0x2000  020000   0b10000000000000
+                *
+                * The above values show that we can check
+                * for 0xD800 and just add 0x2000 to avoid
+                * the surrogate ranges.
+                *
+                * The rest will be handled by CH_UTF16MUNGED
+                * see utf16_munged_pull().
+                */
+               c = SVAL(state->password_buffer, idx);
+               if (c & 0xD800) {
+                       c |= 0x2000;
+               }
+               SSVAL(state->password_buffer, idx, c);
+       }
+       ok = convert_string_talloc(frame,
+                                  CH_UTF16MUNGED, CH_UTF8,
+                                  state->password_buffer, len * 2,
+                                  (void *)&utf8_pw, &utf8_len);
+       if (!ok) {
+               DEBUG(0, ("%s: convert_string_talloc() failed\n",
+                         __func__));
+               TALLOC_FREE(frame);
+               return NULL;
+       }
+
+       ok = convert_string_talloc(frame,
+                                  CH_UTF16MUNGED, CH_UNIX,
+                                  state->password_buffer, len * 2,
+                                  (void *)&unix_pw, &unix_len);
+       if (!ok) {
+               goto ascii_fallback;
+       }
+
+       if (utf8_len != unix_len) {
+               goto ascii_fallback;
+       }
+
+       cmp = memcmp((const uint8_t *)utf8_pw,
+                    (const uint8_t *)unix_pw,
+                    utf8_len);
+       if (cmp != 0) {
+               goto ascii_fallback;
+       }
+
+       new_pw = talloc_strdup(mem_ctx, utf8_pw);
+       if (new_pw == NULL) {
+               TALLOC_FREE(frame);
+               return NULL;
+       }
+       talloc_set_name_const(new_pw, __func__);
+       TALLOC_FREE(frame);
+       return new_pw;
+
+ascii_fallback:
+       for (i = 0; i < len; i++) {
+               /*
+                * truncate to ascii
+                */
+               state->tmp = state->password_buffer[i] & 0x7f;
+               if (state->tmp == 0) {
+                       state->tmp = state->password_buffer[i] >> 1;
+               }
+               if (state->tmp == 0) {
+                       state->tmp = 0x01;
+               }
+               state->password_buffer[i] = state->tmp;
+       }
+       state->password_buffer[i] = '\0';
+
+       new_pw = talloc_strdup(mem_ctx, (const char *)state->password_buffer);
+       if (new_pw == NULL) {
+               TALLOC_FREE(frame);
+               return NULL;
+       }
+       talloc_set_name_const(new_pw, __func__);
+       TALLOC_FREE(frame);
+       return new_pw;
+}
+
 /**
  * Generate an array of unique text strings all of the same length.
  * The returned string will be allocated.
index 1f265e8490b7f6722498bf055419646907e0a9f9..89f0535c9533dbc1c1f4ba65d05014e5605b592f 100644 (file)
@@ -103,10 +103,40 @@ _PUBLIC_ uint32_t generate_random(void);
 _PUBLIC_ bool check_password_quality(const char *s);
 
 /**
- * Generate a random text password.
+ * Generate a random text password (based on printable ascii characters).
+ * This function is designed to provide a password that
+ * meats the complexity requirements of UF_NORMAL_ACCOUNT objects
+ * and they should be human readable and writeable on any keyboard layout.
+ *
+ * Characters used are:
+ * ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+_-#.,@$%&!?:;<=>()[]~
  */
 _PUBLIC_ char *generate_random_password(TALLOC_CTX *mem_ctx, size_t min, size_t max);
 
+/**
+ * Generate a random machine password
+ *
+ * min and max are the number of utf16 characters used
+ * to generate on utf8 compatible password.
+ *
+ * Note: if 'unix charset' is not 'utf8' (the default)
+ * then each utf16 character is only filled with
+ * values from 0x01 to 0x7f (ascii values without 0x00).
+ * This is important as the password neets to be
+ * a valid value as utf8 string and at the same time
+ * a valid value in the 'unix charset'.
+ *
+ * If 'unix charset' is 'utf8' (the default) then
+ * each utf16 character is a random value from 0x0000
+ * 0xFFFF (exluding the surrogate ranges from 0xD800-0xDFFF)
+ * while the translation from CH_UTF16MUNGED
+ * to CH_UTF8 replaces invalid values (see utf16_munged_pull()).
+ *
+ * Note: these passwords may not pass the complexity requirements
+ * for UF_NORMAL_ACCOUNT objects (except krbtgt accounts).
+ */
+_PUBLIC_ char *generate_random_machine_password(TALLOC_CTX *mem_ctx, size_t min, size_t max);
+
 /**
  Use the random number generator to generate a random string.
 **/