diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index c5c246a..26555e7 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,12 +1,14 @@
 
 Drupal 6.23-dev, xxxx-xx-xx (development release)
 ----------------------
+- OpenID i-name identities are now resolved to their CanonicalID, so users with
+  such OpenID identities may need to re-add their identity to their account.
 
 Drupal 6.22, 2011-05-25
 ----------------------
 - Made Drupal 6 work better with IIS and Internet Explorer.
 - Fixed .po file imports to work better with custom textgroups.
 - Improved code documentation at various places.
 - Fixed a variety of other bugs.
 
 Drupal 6.21, 2011-05-25
diff --git a/modules/openid/openid.module b/modules/openid/openid.module
index f1885df..cf02505 100644
--- a/modules/openid/openid.module
+++ b/modules/openid/openid.module
@@ -176,26 +176,28 @@ function openid_begin($claimed_id, $return_to = '', $form_values = array()) {
   $_SESSION['openid']['user_login_values'] = $form_values;
 
   $op_endpoint = $services[0]['uri'];
   // If bcmath is present, then create an association
   $assoc_handle = '';
   if (function_exists('bcadd')) {
     $assoc_handle = openid_association($op_endpoint);
   }
 
-  // Now that there is an association created, move on
-  // to request authentication from the IdP
-  // First check for LocalID. If not found, check for Delegate. Fall
-  // back to $claimed_id if neither is found.
+  // Now that there is an association created, move on to request authentication
+  // from the IdP. Use Claimed ID and/or OP-Local Identifier from service
+  // description, if available.
+  if (_openid_is_xri($claimed_id)) {
+    $claimed_id = $services[0]['canonicalid'];
+  }
   if (!empty($services[0]['localid'])) {
     $identity = $services[0]['localid'];
   }
-  else if (!empty($services[0]['delegate'])) {
+  elseif (!empty($services[0]['delegate'])) {
     $identity = $services[0]['delegate'];
   }
   else {
     $identity = $claimed_id;
   }
 
   if (isset($services[0]['types']) && is_array($services[0]['types']) && in_array(OPENID_NS_2_0 .'/server', $services[0]['types'])) {
     $claimed_id = $identity = 'http://specs.openid.net/auth/2.0/identifier_select';
   }
@@ -233,21 +235,30 @@ function openid_complete($response = array()) {
     $claimed_id = $_SESSION['openid']['claimed_id'];
     unset($_SESSION['openid']['service']);
     unset($_SESSION['openid']['claimed_id']);
     if (isset($response['openid.mode'])) {
       if ($response['openid.mode'] == 'cancel') {
         $response['status'] = 'cancel';
       }
       else {
         if (openid_verify_assertion($service, $response)) {
+          // OpenID Authentication, section 7.3.2.3 and Appendix A.5:
+          // The CanonicalID specified in the XRDS document must be used as the
+          // account key. We rely on the XRI proxy resolver to verify that the
+          // provider is authorized to respond on behalf of the specified
+          // identifer (required per Extensible Resource Identifier (XRI)
+          // Resolution Version 2.0, section 14.3).
+          if (!empty($service['claimed_id'])) {
+            $response['openid.claimed_id'] = $service['claimed_id'];
+          }
           // If the returned claimed_id is different from the session claimed_id,
           // then we need to do discovery and make sure the op_endpoint matches.
-          if ($service['version'] == 2 && $response['openid.claimed_id'] != $claimed_id) {
+          elseif ($service['version'] == 2 && $response['openid.claimed_id'] != $claimed_id) {
             $disco = openid_discovery($response['openid.claimed_id']);
             if ($disco[0]['uri'] != $service['uri']) {
               return $response;
             }
           }
           else {
             $response['openid.claimed_id'] = $claimed_id;
           }
           // Verify that openid.return_to matches the current URL (see OpenID
@@ -293,19 +304,21 @@ function openid_complete($response = array()) {
  */
 function openid_discovery($claimed_id) {
   module_load_include('inc', 'openid');
   module_load_include('inc', 'openid', 'xrds');
 
   $services = array();
 
   $xrds_url = $claimed_id;
   if (_openid_is_xri($claimed_id)) {
-    $xrds_url = 'http://xri.net/'. $claimed_id;
+    // Resolve XRI using a proxy resolver (Extensible Resource Identifier (XRI)
+    // Resolution Version 2.0, section 11.2).
+    $xrds_url = variable_get('xri_proxy_resolver', 'http://xri.net/') . rawurlencode($claimed_id) .'?_xrd_r=application/xrds+xml';
   }
   $url = @parse_url($xrds_url);
   if ($url['scheme'] == 'http' || $url['scheme'] == 'https') {
     // For regular URLs, try Yadis resolution first, then HTML-based discovery
     $headers = array('Accept' => 'application/xrds+xml');
     $result = drupal_http_request($xrds_url, $headers);
 
     if (!isset($result->error)) {
       if (isset($result->headers['Content-Type']) && preg_match("/application\/xrds\+xml/", $result->headers['Content-Type'])) {
diff --git a/modules/openid/xrds.inc b/modules/openid/xrds.inc
index 36f5282..68c0b94 100644
--- a/modules/openid/xrds.inc
+++ b/modules/openid/xrds.inc
@@ -1,15 +1,14 @@
 <?php
 
 // Global variables to track parsing state
 $xrds_open_elements = array();
 $xrds_services = array();
-$xrds_current_service = array();
 
 /**
  * Main entry point for parsing XRDS documents
  */
 function xrds_parse($xml) {
   global $xrds_services;
 
   $parser = xml_parser_create_ns();
   xml_set_element_handler($parser, '_xrds_element_start', '_xrds_element_end');
@@ -19,48 +18,65 @@ function xrds_parse($xml) {
   xml_parser_free($parser);
 
   return $xrds_services;
 }
 
 /**
  * Parser callback functions
  */
 function _xrds_element_start(&$parser, $name, $attribs) {
-  global $xrds_open_elements;
+  global $xrds_open_elements, $xrds_current_xrd;
 
   $xrds_open_elements[] = _xrds_strip_namespace($name);
+  $path = strtoupper(implode('/', $xrds_open_elements));
+  switch ($path) {
+    case 'XRDS/XRD':
+      $xrds_current_xrd = array();
+      break;
+    case 'XRDS/XRD/SERVICE':
+      $xrds_current_service = array();
+      break;
+    case 'XRDS/XRD/STATUS':
+      $xrds_current_xrd['status'] = $attribs['CID'];
+      break;
+  }
 }
 
 function _xrds_element_end(&$parser, $name) {
-  global $xrds_open_elements, $xrds_services, $xrds_current_service;
+  global $xrds_open_elements, $xrds_services, $xrds_current_xrd, $xrds_current_service;
 
-  $name = _xrds_strip_namespace($name);
-  if ($name == 'SERVICE') {
+  $path = strtoupper(implode('/', $xrds_open_elements));
+  if ($path == 'XRDS/XRD/SERVICE') {
+    if (!empty($xrds_current_xrd['status']) && $xrds_current_xrd['status'] == 'verified') {
+      $xrds_current_service['canonicalid'] = $xrds_current_xrd['canonicalid'];
+    }
     if (in_array(OPENID_NS_2_0 .'/signon', $xrds_current_service['types']) ||
         in_array(OPENID_NS_2_0 .'/server', $xrds_current_service['types'])) {
       $xrds_current_service['version'] = 2;
     }
     elseif (in_array(OPENID_NS_1_1, $xrds_current_service['types']) ||
             in_array(OPENID_NS_1_0, $xrds_current_service['types'])) {
       $xrds_current_service['version'] = 1;
     }
     if (!empty($xrds_current_service['version'])) {
       $xrds_services[] = $xrds_current_service;
     }
-    $xrds_current_service = array();
   }
   array_pop($xrds_open_elements);
 }
 
 function _xrds_cdata(&$parser, $data) {
-  global $xrds_open_elements, $xrds_services, $xrds_current_service;
+  global $xrds_open_elements, $xrds_services, $xrds_current_xrd, $xrds_current_service;
   $path = strtoupper(implode('/', $xrds_open_elements));
   switch ($path) {
+    case 'XRDS/XRD/CANONICALID':
+      $xrds_current_xrd['canonicalid'] = $data;
+      break;
     case 'XRDS/XRD/SERVICE/TYPE':
       $xrds_current_service['types'][] = $data;
       break;
     case 'XRDS/XRD/SERVICE/URI':
       $xrds_current_service['uri'] = $data;
       break;
     case 'XRDS/XRD/SERVICE/DELEGATE':
       $xrds_current_service['delegate'] = $data;
       break;
@@ -72,10 +88,10 @@ function _xrds_cdata(&$parser, $data) {
 
 function _xrds_strip_namespace($name) {
   // Strip namespacing.
   $pos = strrpos($name, ':');
   if ($pos !== FALSE) {
     $name = substr($name, $pos + 1, strlen($name));
   }
 
   return $name;
-}
\ No newline at end of file
+}
