HOWTO: Use Client-Supplied Certificate Authentication on a WordPress Blog

Client Certificate Authentication; a WordPress Plugin


 <Location /wp-login.php>
    SSLVerifyClient optional
    <IfModule mod_rewrite.c>
        RewriteEngine   on
        RewriteCond  %{HTTP_USER_AGENT}  .*Safari.*
        RewriteCond  %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS
        RewriteRule  .* /wp-admin [redirect,last]
<Location /wp-admin>
    SSLVerifyClient require

Not Covered

Once the plugin is in place, all interfaces that once used passwords will need to use an appropriate client certificate.  For example the WordPress API.

<Location /xmlrpc.php>
    SSLVerifyClient require



Configuration of WordPress is “standard.”  Get that working first.

Plugin: Client Certificate Authentication

Via: instructions, FAQ.

Apache httpd


<VirtualHost *:443>
    SSLEngine On
    SSLCertificateFile      pki/trustanchor/com.baker.STAR.crt
    SSLCertificateKeyFile   pki/trustanchor/com.baker.STAR.key
    SSLCertificateChainFile pki/trustanchor/intermediate.crt
    SSLCACertificateFile    pki/bloggists/all.crt
    SSLCARevocationFile     pki/bloggists/revocation.crl
    # SSLCADNRequestFile (defaults to SSLCACertificateFile)
    # <ifVersion >= 2.4> SSLCARevocationCheck  chain
    DocumentRoot /var/wordpress/t99
    <Directory "/var/wordpress/t99">
	Options Indexes FollowSymLinks
        # AllowOverride is needed to get wordpress permalinks to work (respect .htaccess)
	AllowOverride All
        # This is needed for permalinks to work on WordPress without using .htaccess files
	RewriteEngine On
        RewriteBase /
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteCond %{REQUEST_FILENAME} !-d
	RewriteRule . /index.php [L]
    RedirectMatch permanent ^/credits?(/|/.*)?$$1
    RedirectMatch permanent ^/polic(y|ie)s?(/|/.*)?$$2
    Redirect permanent /robots.txt
    Header set P3P "CP=\"There is no P3P policy. Learn why here:\""
    <Location /wp-login.php>
	SSLVerifyClient optional
	RewriteEngine   on
	RewriteRule  .* /wp-admin [redirect,last]
    <Location /wp-admin>
	SSLVerifyClient require
        SSLOptions +FakeBasicAuth +ExportCertData
	# The plugin consults
	#   SSL_CLIENT_S_DN_CN for the name.
	#   SSL_CLIENT_S_DN_Email for the email
        SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)-/ and %{SSL_CLIENT_S_DN_O} eq "Baker" and %{SSL_CLIENT_S_DN_OU } in {"Bloggist", "Bloggists"} )

Via: backfill

SOLVED[fail]: Android WebView does not support Client Certificates at all

Problem Statement



Partial success…

  • Webware => just works
    • desktop officework browser
      i.e. Firefox 29+
    • mobile browser => “just works”
      i.e. Chrome 40, Blink 537.36, Android 4.4.4 (what is that, Jelly Bean, Key Lime Pie?, Lollipop?)
  • Appware => FAIL
    • Android does not work, cannot be made to work.
    • WordPress Android is unuseable in this mode.

Environment: Webby Officework Desktop

Outcome: just works


  • Firefox (Fedora) “just works”
  • Chrome (Android) “just works”


  • User receives the certificate as a PKCS #12 (a .p12 file)
  • Install fhe certificate
    • … in the browser (Firefox, Linux)
    • … in the operating system (Android)

Environment: WordPress Android

Outcome: FAIL

  • Do not use Android WordPress on these blogs
  • Use the webby interface with Chrome.
  • WordPress Android uses an embedded WebView which does not implement client certificates at all.


Others have tried … but Android does not yet support this concept

HOWTO Move a WordPress Blog to a New Domain, refinements to the recipe

Previously: On Moving a WordPress Blog to a New Domain, 2013-01-02.

On Moving a WordPress Blog to a New Domain

Necessary but not sufficient advice.

What is missing in this recipe is that the graphical content, hyperlinks and banners which are referenced within the blog are not updated to point to the new domain. It seems there are (at least) two cases. The outline here is not fully general but was sufficient for the purposes at hand


Move the blog at; to


  1. Execute the recipe shown above as the admin user.
  2. Clean up the remaining internal pointers the banners in the wp_options table.
  3. Clean up the remaining internal pointers in hyper links within the article content.


$ rpm -q wordpress mysql
$ cat /etc/fedora-release
Fedora release 16 (Verne)


Substantially what we’re looking to do here is the moral equivalent of this perl code:


However we need this code executed on every field of every row of every table in the WordPress MySQL database. There does not seem to be an obvious way to do that, so we approximate and guess which tables and which fields need modification.

$ mysql -u wordpress -p
mysql> show tables;
| Tables_in_org_baker_emerson_admin |
| wp_commentmeta                    |
| wp_comments                       |
| wp_links                          |
| wp_options                        |
| wp_postmeta                       |
| wp_posts                          |
| wp_term_relationships             |
| wp_term_taxonomy                  |
| wp_terms                          |
| wp_usermeta                       |
| wp_users                          |
11 rows in set (0.00 sec)


How bad are the articles?

mysql> select id from wp_posts where post_content like '%//admin.emerson%';
| id |
|  4 |
|  5 |
|  6 |
| 13 |
| 16 |
| 20 |
| 27 |
| 42 |
| 79 |
| 81 |
| 82 |
| 83 |
| 84 |
13 rows in set (0.01 sec)


The articles will have to be cleaned up by hand in the WordPress in-browser editor..


The options includes the configurations for the banners and other internal eye candy.

mysql> select option_id, option_name from wp_options where option_value like '%//admin.emerson%';
| option_id | option_name                                      |
|       113 | dashboard_widget_options                         |
|       161 | theme_mods_twentyeleven                          |
|       581 | _transient_dash_20494a3d90a6669585674ed0eb8dcd8f |
3 rows in set (0.01 sec)

mysql> describe wp_options;
| Field        | Type                | Null | Key | Default | Extra          |
| option_id    | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| option_name  | varchar(64)         | NO   | UNI |         |                |
| option_value | longtext            | NO   |     | NULL    |                |
| autoload     | varchar(20)         | NO   |     | yes     |                |
4 rows in set (0.00 sec)

The options are stored as a giant JSON-flavored TLV blob. Watch out for the very very long lines.

mysql> select option_value from wp_options where option_id = 161;
| option_value                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| a:3:{s:12:"header_image";s:91:"";s:17:"header_image_data";O:8:"stdClass":5:{s:13:"attachment_id";i:6;s:3:"url";s:91:"";s:13:"thumbnail_url";s:91:"";s:6:"height";i:287;s:5:"width";i:1000;}s:18:"nav_menu_locations";a:1:{s:7:"primary";i:0;}} |
1 row in set (0.00 sec)


Being careful to the string lengths when the string lengths change. In this case, the length of the URLs changes from 91 characters to 100 characters.

mysql> update wp_options set option_value = 'a:3:{s:12:"header_image";s:100:"";s:17:"header_image_data";O:8:"stdClass":5:{s:13:"attachment_id";i:6;s:3:"url";s:100:"";s:13:"thumbnail_url";s:100:"";s:6:"height";i:287;s:5:"width";i:1000;}s:18:"nav_menu_locations";a:1:{s:7:"primary";i:0;}}' where 161 = option_id;
Query OK, 1 row affected (0.53 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Update to Firefox 22, and the WordPress text editor arrow keys don’t work (because of Firebug)

Problem Statement

  • The arrow keys and the Home and End keys no longer work in the WordPress wysiwyg text editor.
  • They continue to work in the raw HTML editor.


  • The wysiwyg text editor is nearly unuseable.
  • The only workaround to move the text entry point is to use the mouse.


  • Fedora 18
  • Firefox 22, updated from Firefox 19
  • There are gobs of other plugins in Firefox: including Firebug.


  • It’s Firebug’s fault.


  • Change the keybindings away from Ctrl+Shift+Arrow
  • The OK vs Cancel does not work in Firebug
    • hit Cancel, it does not Cancel
    • Validate that the changes were accepted via Cancel (which means OK)
  • Restart Firefox (yes, you apparently have to do that)



Installation and Configuration of the Google+ Crossposting WordPress Plugin

Google+ Crossposting

Plugin Google+ Crossposting

Problem Definition

What Is Wanted

  1. Syndicate (cross-post) your blog posts out onto Google+ for distribution
  2. Use the Google+ identity system to facilitate login on your WordPress blog
    • Tie comments to actual persons.
    • Mitigate spam.
    • Promote clear attribution.

What is Convenient

  1. Syndicate (cross-post) your Public posts on Google+ onto your own venue (WordPress blog).

What You Get

  • The convenient stuff (Google+ has a readonly API).

What You Don’t Get

  • The wanted stuff (Google+ doe does not have a write API).

Other Solutions

Are unsuitable; they require you to disclose your Google password to an external service, into scriptware, or to purchase opaque plugins and services.  They aren’t Google API calls so much as mechanized versions of Google login flows.  Ick.  And vastly dangerous.

  • How to Auto Post to Google Plus from WordPress Blog; In WPSquare; undated (probably late 2012), no date appears on the page.
    • There is no Google API that does this; the Google+ API is readonly
    • The WPGPlus Plugin copies your credentials into the plugin
    • The gPressor, from WarriorForum, by special forum distribution
    • NextScripts has a for-pay addon script; requires their hosting+service arrangements
  • TwooglePlus; used copied user credentials; has been disabled by themselves and by Google

Particulars & Provenances

You need

  • Your Google+ ID (a very long number)
  • A server-to-server Google API Key

Acquiring Your Google+ ID

  1. Go to your Google+ experience (sign in).
  2. Go to your Posts
  3. See the URL which of the form

Acquiring a Google API Key

  1. Go to
  2. Read it

Actualities of the key acquisition workflow

Warnings and Gotchas

It is a known effect that the plugin destroys (clears, removes) the Google+ ID and the Google API Key from its settings page if the tokens do not operate without error.  The plugin has no better way of signalling an error than to do this.   So you  need to

  1. Store these items safely some where else as primary storage, not in WordPress
  2. Watch, wait and review to ensure the tokens don’t “disappear”
  3. Realize the connection from Google+ into your blog is shaky at best.



From acquiring the Google API Key for Google+ access.

On the use of <span style=”display:none;”> in WordPress comment spam

The existence of comment spam in WordPress makes the whole enterprise of an open loop commentariat problematic (which is delicate indirect fancyspeak for: the advice is to turn off comments).

Here’s a dump of the technique for using <span style=”display:none;”> in the source blog to spam up other blogs to drive traffic back.


  • (factually) on 2012-12-26 the publication of Venkat Rao; ribbonfarm; The Crucible Effect and the Scarcity of Collective Attention occurred in Backfill of ‘Note to Self’.  Factually, because I represent that I did it.
  • (apparently) 2012-12-10, the publication 1000 Raving Fans – Part Two occurred in the blog Work With David Wood which appears to be promoting techniques for Multi-Level Marketing (MLM) on Facebook.  Apparently, because that’s what the dates on the pages say; and we know that WordPress blog posts are easily backdated.
  • (actually) on 2013-01-12 a comment pingback appears at Backfill indicating that the Rao/ribbonfarm/Crucible+Attention post was referenced.  Yet there is no mention of the article in the source blog and the WordPress provenance of the comment are incomprehensible, being raw HTML and JavaScript at the code level.
  • (result) on 2013-01-12 the comment was declared to be spam.


The technique involves using <span style="display:none;"> within the offending referencing article somewhere. This acquires a comment indicator back in the victim source blog but does not offer any material visibility in the offending referencing blog. The HTML code on the offending referencing blog is:

<a href="" rel="nofollow"><span style="display:none;">Venkat Rao; ribbonfarm; The Crucible Effect and the Scarcity of Collective Attention</span></a>

which presented in readable form is:

<a href=””
<span style=”display:none;”>
Venkat Rao; ribbonfarm; The Crucible Effect and the Scarcity of Collective Attention

Comment Approval User Experience

The comment approval user experience does not offer much in the way of support to detect this condition. The snippet given for the comment approval workflow renders raw HTML and JavaScript code.


Use view images on the images to get the high resolution images; the link targets is the actual offending blog post itself.

Many many other blogs are referenced with this technique.  To wit:

<a href="" rel="nofollow"><span style="display:none;">1000 Raving Fans</span></a><a href="" rel="nofollow"><span style="display:none;">Venkat Rao; ribbonfarm; The Crucible Effect and the Scarcity of Collective Attention</span></a><a href="" rel="nofollow"><span style="display:none;">List Building &#8211; Providing a Free Giveaway to Turn Your Prospects Into Clients and Raving Fans &#8211; Jordan High Heels</span></a><a href="" rel="nofollow"><span style="display:none;">Thank You For All Your Efforts – A Raving Fans Testimonial</span></a><a href="" rel="nofollow"><span style="display:none;">Shindigz Internet Marketing Team Looking to Expand</span></a><a href="" rel="nofollow"><span style="display:none;">Affiliate Summit West 2013 Preview; Social Media Wing of School of Internet Marketing</span></a><a href="" rel="nofollow"><span style="display:none;">9 Things To Do To Drive Your Photography Customers Crazy</span></a><a href="" rel="nofollow"><span style="display:none;">Happy Birthday, text! How texting changed internet marketing and social media</span></a><a href="" rel="nofollow"><span style="display:none;">Farewell To One Of My Original Mentors - Zig Ziglar - November 28, 2012 - Revolution Income</span></a><a href="" rel="nofollow"><span style="display:none;">Review for Harmony Gelish Top .5oz Base .5oz &#8220;Set of 2&#8243; High Quality Products. Ship Now Get Now</span></a><link rel="stylesheet" type="text/css" href="" />

Bring up WordPress on Fedora 17


  • apache httpd runs as user apache:apache
  • /var/wordpress/NEWBLOG contains the blog
  • /etc/wordpress/NEWBLOG.php contains the blog configuration (wp-config.php)
  • /var/http/VIRTHOST contains other virtual hosts served by apache httpd
  • /var/www contains the original apache httpd content area (with its SELinux labels)

SELinux Considerations

  • You will have issues as you are installing in nonstandard places.
  • See the recipe herein.
  • Stick with it, you want the protection

Known Problems

Bug 891764 php-simplepie 1.3.1 breaks WordPress

Install Packages

n.b. this may not be the minimal set

  • (sudo) yum install -y mysql-libs mysql mysql-server >& o.yum_install.out
  • (sudo) yum install -y php-{cli,xml,gd,IDNA_Convert,soap,pdo,mysql,simplepie,common} >& o.yum_install.out
  • (sudo) yum install -y wordpress wordpress-plugin-defaults wordpress-plugin-bad-behavior >& o.yum_install.out

Bring up MySQL

  • (sudo) systemctl enable mysqld.service
  • (sudo) systemctl start mysqld.service
  • mysqladmin -u root password $uuid1
  • mysql -u root -p
    • supply password $uuid1
    • create user wordpress identified by '$uuid2';
    • select password('$uuid2');
    • create database NEWBLOG;
    • grant all privileges on NEWBLOG.* to wordpress@localhost identified by password 'hashed-uuid2';
    • quit

Install & Configure WordPress

  • (sudo) mkdir /var/wordpress
  • cd /var/wordpress
  • (sudo) cp -rpc /usr/share/wordpress NEWBLOG/.
    • -r is recursive
    • -p is preserve permissions
    • -c is preserve SELinux context labels
  • (sudo) chown -R apache:apache NEWBLOG/.
  • cd NEWBLOG
  • (sudo) rm wp-config.php
  • (sudo) ln -s ../../../etc/wordpress/NEWBLOG.php wp-config.php
  • (sudo) vi /etc/wordpress/NEWBLOG.php

Hack the Permissions

Permissions on /etc/wordpress

$ ls -la /etc/wordpress
total 24
drwxr-xr-x.   2 root root  4096 Jan 10 16:15 .
drwxr-xr-x. 137 root root 12288 Jan 10 15:01 ..
-rw-r--r--.   1 root root  3178 Jan 10 16:15 NEWBLOG.php
-rw-r--r--.   1 root root  3177 Dec 12 05:55 wp-config.php

$ sudo chown -R apache:apache /etc/wordpress

$ sudo chmod o-rx -R /etc/wordpress 

$ ls -la /etc/wordpress
ls: cannot open directory /etc/wordpress: Permission denied

$ sudo ls -la /etc/wordpress
total 24
drwxr-x---.   2 apache apache  4096 Jan 10 16:15 .
drwxr-xr-x. 137 root   root   12288 Jan 10 15:01 ..
-rw-r-----.   1 apache apache  3178 Jan 10 16:15 NEWBLOG.php
-rw-r-----.   1 apache apache  3177 Dec 12 05:55 wp-config.php

SELinux Labels on /var/wordpress/NEWBLOG

The following label patterns need to be available

semanage -i - <

Then the tree needs to be “relabelable” so that in case a restorecon action happens, the system isn’t broken. So in concept:

  • copy_context “NEWBLOG
  • semanage_patterns “NEWBLOG
  • relabel_tree “NEWBLOG

The packaged script is fixup. To use

    1. (sudo) mkdir /var/wordpress/selinux
    2. download it to /var/wordpress/selinux/fixup
    3. (sudo) chmod a+x /var/wordpress/selinux/fixup
    4. cd /var/wordpress
    5. (sudo) selinux/fixup NEWBLOG

Once done, see

      • /var/wordpress/NEWBLOG/o.chcon.out
      • /var/wordpress/NEWBLOG/o.semanage.out
      • /var/wordpress/NEWBLOG/o.restorecon.out

Once done, inspect the SELinux label patterns:

$ sudo semanage -o -
boolean -D
login -D
login -a -s unconfined_u -r 's0-s0:c0.c1023' __default__
login -a -s unconfined_u -r 's0-s0:c0.c1023' root
login -a -s system_u -r 's0-s0:c0.c1023' system_u
user -D
port -D
interface -D
node -D
fcontext -D
fcontext -a -f 'all files' -t httpd_sys_rw_content_t '/var/wordpress/NEWBLOG/.htaccess'
fcontext -a -f 'all files' -t httpd_sys_rw_content_t '/var/wordpress/NEWBLOG/wp-content'
fcontext -a -f 'all files' -t httpd_sys_rw_content_t '/var/wordpress/NEWBLOG/wp-content/blogs.dir(/.*)?'
fcontext -a -f 'all files' -t httpd_sys_rw_content_t '/var/wordpress/NEWBLOG/wp-content/cache(/.*)?'
fcontext -a -f 'all files' -t httpd_sys_rw_content_t '/var/wordpress/NEWBLOG/wp-content/plugins(/.*)?'
fcontext -a -f 'all files' -t httpd_sys_rw_content_t '/var/wordpress/NEWBLOG/wp-content/themes(/.*)?'
fcontext -a -f 'all files' -t httpd_sys_rw_content_t '/var/wordpress/NEWBLOG/wp-content/upgrade(/.*)?'
fcontext -a -f 'all files' -t httpd_sys_rw_content_t '/var/wordpress/NEWBLOG/wp-content/uploads(/.*)?'
fcontext -a -f 'all files' -t httpd_sys_rw_content_t '/var/wordpress/NEWBLOG/wp-content/wp-cache-config.php'

Configuring Apache httpd

In /etc/httpd/conf/httpd.conf is the declaration of name-based virtual hosting:

NameVirtualHost *:80
Include vhost/*.conf
<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html
    ServerName _default_
    ServerAlias *
    ErrorLog logs/error_log
    CustomLog logs/access_log common

The NEWBLOG entry is constructed with a ServerAlias such that it will always match, and since it appears “First” in the ordering of the VirtualHost declarations, then it will be the only match. The default will never match. And in /etc/httpd/vhost/NEWBLOG.conf is the declaration:

<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/wordpress/NEWBLOG
    ServerName _dummy_
    ServerAlias *
    ErrorLog logs/NEWBLOG/error_log
    CustomLog logs/NEWBLOG/access_log common

Be sure to create the log file directory

  • (sudo) mkdir /var/log/httpd/NEWBLOG

(re)Start the server

$ sudo systemctl restart httpd.service

On Moving a WordPress Blog to a New Domain



  • Is there going to be a problem with one of the blogs being in a subdomain of the other one?
  • The whole system works on HTML4 cookies which are on at a domain-superdomain basis.


Recipe #1

This may be enough … maybe, but see Recipe #2

From Changing The Site URL

  1. Edit the wp-config.php file.
  2. After the define statements (just before the comment line that says “That’s all, stop editing!”), insert a new line, and type: define('RELOCATE',true);
  3. Save your wp-config.php file.
  4. Open a web browser and manually point it to wp-login.php on the new URL.
  5. Login as per normal.
  6. Verify you are at the correct URL.
  7. Login as Admininstrator; e.g. the user admin.
  8. As Administrator, navigate to Settings > General and verify that both the URL settings are correct.
  9. Hit Save Changes.
  10. Once this has been fixed, edit wp-config.php and either completely remove the line that you added (delete the whole line), comment it out (with //) or change the true value to false if you think it’s likely you will be relocating again.

Actualities of Recipe #1

The fragment of edit wp-config.php modified in place.

 * For developers: WordPress debugging mode.
 * Change this to true to enable the display of notices during development.
 * It is strongly recommended that plugin and theme developers use WP_DEBUG
 * in their development environments.
define('WP_DEBUG', true);

define('RELOCATE', false);

/* That's all, stop editing! Happy blogging. */

/** Absolute path to the WordPress directory. */
if ( !defined('ABSPATH') )
	define('ABSPATH', dirname(__FILE__) . '/');

/** Sets up WordPress vars and included files. */
require_once(ABSPATH . 'wp-settings.php');

The WordPress Administration UI at Settings > General

Administrator login at Settings>General showing the update of the WordPress URL and Site URL to the new URL

Recipe #2

The document writers distinguish the various cases in terms of gauzy abstractions which are somewhat hard to grapple pin down with precision in the case of virtual hosting and DNS CNAME trickery.

  • Changing to a different server.
  • Changing the site URL.
  • Moving the WordPress files from one location to another
  • Moving from on one server to another location on another server

Various levels of intervention are required for each of these. It’s not at all clear that they are disjoint cases. For example, see When Your Domain Name or URLs Change within Moving WordPress).

The cases seem to be

  1. When the domain name changes
    e.g. becomes ensconced as
  2. When the path component changes; e.g. grows up and moves out to


Separately, prove that the old URL is no longer referenced in any content or other files of the site.

$ cd backfill
$ grep -e -rI .
no output

No output is good output.

Apache Virtual Hosting Backstory

This virtual hosting stanza must remain in force within httpd.conf … forever.

<VirtualHost *:80>
    Redirect permanent /

DNS CNAME Backstory

This CNAME must remain in force within DNS … forever.	CNAME