Kevin's Table of OpenVPN Keys, Certificates, and Authorities

Kevin’s Big List o’ Files

Sometimes I run OpenVPN on a Linux server. Sometimes I run OpenVPN on a consumer-grade router with a built-in OpenVPN. Just to be “helpful”, they don’t use identical terminology. Here’s where I keep it straight.

You’re going to need a bunch of files with certificates.  You’ll give some of these to the server, some to the clients, and some you’ll keep secretly filed away. (See also this .)

File Name

Merlin’s Name

Needed By

Purpose

Secret

config.ovpn

(settings from checkboxes, textboxes, etc.)

server

configuration options

No

ca.crt

Certificate Authority

server + clients

CA Root Certificate

No

ca.key

n.a.

key signing machine only

Root CA private key

Yes

dh{n}.pem

Diffie Hellman parameters

server

Diffie Hellman parameters

No

server.crt

Server Certificate

server

Server Certificate

No

server.key

Server Key

server

Server Key

Yes

user-{n}.crt

n.a.

user-{n}

Client Certificate

No

client{n}.key

n.a.

client{n}

Client Key

Yes

ta.key (static.key)

Static Key

server + clients

Extra, optional security for connecting

Yes

crl.pem

Certificate Revocation List

server

Blacklisting of certificates

No

?

Extra Chain Certificates

?

Merlin has this field. I don't know what it's for.

No

.csr files

(not referenced)

key generation machine

Intermediate files for building certificates

No

Details of files:

  • ca.crt

    • All of your keys/certificates will be signed by a root certificate authority (CA). This is the certificate of your CA. You can act as your own CA or you can use a commercial CA. This document assumes you act as your own CA. Any (non-revoked) client certificate signed by this CA will be able to login to your VPN, so you may want a unique CA certificate for your VPN.

  • ca.key

    • This is the private key of your CA. You need it on you key generation machine. You do not need it on your VPN server.

  • dh{n}.pem, dh.pem

    • The Diffie Hellman algorithm generates a unique *session* key via a well-known algorithm. This is a seed for that algorithm. It allows you to "create an encryption key with someone, and then start encrypting your traffic with that key. And even if the traffic is recorded and later analyzed, there's absolutely no way to figure out what the key was.” Contrary to my expectation, you don’t have to keep this secret because parts of the DH exchange are random values. Sometimes this file is named dh2048.pem (or dh4096.pem); sometimes it is named dh.pem.

  • server.crt

    • This identifies your VPN server and contains its public key. Merlin names it server.crt. I generate two server certificates: server-raspi.crt and server-router.crt

  • server.key

    • This is your VPN server’s private key. Merlin names it server.key. I generate two server certificates: server-raspi.key and server-router.key

  • user-{n}.crt, user-pass-{n}

    • This identifies your client and contains its public key. There is one per client. Some of these are named user-pass-{n}. Those users have passworded keys.

  • user-{n}.key, user-pass-{n}

    • This is your client’s private key. There is one per client. Some of these are named user-pass-{n}. Those users have passworded keys.

  • ta.key (static.key)

    • Used for "Extra HMAC authorization” (Merlin’s name) or the “tls-auth” configuration line in OpenVPN.  This is an optional, *extra* key, used to authenticate the TLS handshake.  When your client makes its initial connection to the server, this information gets passed.  If the client doesn’t pass it, the server doesn’t respond, protecting against some attacks. OpenVPN has 2 authentication modes.  “Static Key” and “TLS”. Merlin overloads this field and uses it for BOTH methods’ static key. Merlin calls it "static.key". In my Linux config, I name it "ta.key". With Merlin, you must set “TLS control channel security” = 0, or this file won't be created.

  • crl.pem

    • Used to revoke or blacklist certificates. You revoke a certificate when someone loses the laptop/phone with that certificate. Or maybe you revoke your ex-spouse's certificate when the divorce is final. ;-)

  • .csr files

    • In order to create a certificate, you collect some data and get it signed by your Certificate Authority. The .csr files are input to the CA signing. The signed .crt files are the output. Once you have the .crt, you don't need the .csr.

I hope that helps to keep everything straight. It can be confusing!

Certificates, Keys, and Authorities

Certificates

VPNs use certificates and keys. We’re going to spend some time building certificates and keys, so we should get clear on what they are.

  • A certificate is used to prove who you are. (More accurately, it proves that a computer is who it says it is.) A driver’s license is an example of a non-computer certificate.
  • A key is used to lock/unlock something. (More accurately, it encrypts or decrypts something.) The key to your front door is an example of a non-computer key.
  • A certificate authority is used to mark certificates and keys as being ‘official.’

Certificates and encryption are what make OpenVPN secure. Managing client certificates is a big part of managing any VPN server. I’m going to generate a server certificate and I’m going to generate many client certificates. It will be more than I think I’ll need, but I’ll go ahead and generate them all now, before I forget how. I’ll use a tool called easy-rsa.


Return to Surf Safe at Starbucks

Build the Keys and Certificates You Need for Your OpenVPN Server and Clients

  • Summary: Install easy-rsa, configure it, generate CA+key+certs

We’re going to use a tool called easy-rsa to build your keys and certificates. You can install easy-rsa on your Pi and run it there, but you only need easy-rsa when you’re building keys/certificates – not when you are using them (and using your VPN). I prefer to build them on my Mac. If you are a Windows user, you can build your keys on a Windows PC, but you’ll find it easier to build them on your Pi. Generating keys is slow on a Pi, but it is faster to run easy-rsa on a slow Pi than it is to figure out how to make it work on a fast Windows PC.

I’m going to refer to your ‘key generation machine’ and your ‘VPN server’. Your key generation machine is the machine you’re using to run easy-rsa, whether you decided to use your Pi, a Mac, a Windows PC, etc.

Install Software

If your key generation machine is your Pi:

sudo apt-get install openvpn
mkdir ~/Packages
cp -r /usr/share/easy-rsa ~/Packages

If your key generation machine is a Mac, browse to the OpenVPN github site and download EasyRSA-2.2.2.tgz. (There are some significant changes with v3, so you’ll have some challenges if you use my instructions with 3.x. As near as I can tell, there’s nothing obsolete about the keys/certificates which 2.2.2 generates. 3.x just provides a different model for generating certs – and breaks all the old commands.) Then, in the directory where you downloaded it:

    tar -zxvf EasyRSA-2.2.2.tgz
    mkdir ~/Packages
    rm EasyRSA-2.2.2.tgz
    mv EasyRSA-2.2.2 easy-rsa
    mv easy-rsa ~/Packages/

If you’re running it on Windows, install easy-rsa 2.2.2 from https://github.com/OpenVPN/easy-rsa/releases. I’m not going to provide instructions for Windows. If you’re determined enough to make it work on Windows, you don’t need my help!

Note: Throughout these instructions, I’ll refer to your home directory as /Users/kevin. This is probably NOT your home directory. You should substitute YOUR home directory. On a Pi, your home directory is probably /home/pi. I’m running easy-rsa from /Users/kevin/Packages/easy-rsa. You should substitute the path where you installed easy-rsa.


When we’re done with certificates:

  • ~/Packages/easy-rsa/keys_xxxxx on the key generation machine will contain your ‘master’ copy of keys and certificates. Preserve this data for future redeploys in case your VPN server’s disk fails or one of your client’s disk gets wiped! You want to keep this directory secure, because anyone with access to it can use your VPN. You should probably NOT leave this data on your OpenVPN server because if someone got ahold of the CA key, they could generate all the client keys they wanted, and you might not notice.
    • It it common to simply use ~/easy-rsa/keys. For bonus points, I’m building keys for TWO VPNs at once. I plan to run one VPN on a Raspberry Pi and the other on a consumer router that includes OpenVPN. So I need two ‘keys’ directories. Also, I have many subfolders of my home folder, and I group code-plus-data packages into ~/Packages.
  • /etc/openvpn/server1 on your VPN server will contain your server’s private key, in addition to some less confidential keys and certificates. You want to keep this directory secure, because anyone with access to it can masquerade as your VPN server.

Configuring easy-rsa

Continuing, execute the following on your key generation machine:

  • If you have a file named ~/.profile, add the following to that file. If you don’t have a .profile and you do have a .bash_profile, add the line to it.

      alias easy-rsa="cd /Users/kevin/Packages/easy-rsa; source vars"
    
  • Then source .profile or .bash_profile, as appropriate. e.g.

      source .bash_profile
    
  • Then run it:

       easy-rsa
    
  • Edit ./vars and change these variables. Use values that match your situation. These apply to me but may not to you!

      export KEY_DIR=keys_raspivpn            # TODO: Later, use keys_routervpn
      export EASY_RSA="/Users/kevin/Packages/easy-rsa"
      export KEY_SIZE=2048
      export CA_EXPIRE=10000
      export KEY_EXPIRE=10000  
      export KEY_COUNTRY="US"
      export KEY_PROVINCE="GA"
      export KEY_CITY="Atlanta"
      export KEY_ORG="KleinfelterFamily"
      export KEY_EMAIL="kevin@example.com"    #TODO: Changeme
      export KEY_OU="RaspiVPN"                #TODO: Later, use RouterVPN
    
  • Estimates are that a 2048 bit key is good to the year 2030. I’d prefer not to come back and re-do this in 15 years, so I’d like a 4096-bit key, but there are stories that performance with a key that large can be poor on some devices. I plan to use this with cell phones with limited CPU and battery, so I’m going with 2048.
  • I’m also setting expirations to 10,000 days which is 27 years. I’ve never seen the merit of date-limited CRLs. I would have gone higher, but when I used 12,000, it said that the CRL was already expired, whenever I used it or when I revoked a cert.
  • Edit openssl*.cnf and set:

      default_crl_days=10000
    
  • If you don’t do this, your CRL will expire in a month and when your CRL expires, the server will refuse ALL logins.
  • Then run:

      source vars
      mkdir "$KEY_DIR"
    
  • Mac only: If you are on Linux, openvpn should already be on your path. If you are on Mac, you may have to add openvpn to your path. OpenVPN is bundled with Tunnelblick. If you install a different version of Tunnelblick than the one I used, you may have to tinker with PATH. After you run the “ls”, adjust the PATH statement below to use your latest openvpn-x-openssl-y.
    • ls -l /Applications/Tunnelblick.app/Contents/Resources/openvpn/
    • Edit your ~/.bash_profile and add something like:

        TBVPN="/Applications/Tunnelblick.app/Contents/Resources/openvpn"
        TBSSL="$TBVPN/openvpn-2.4.0-openssl-1.0.2k"
        export PATH="$PATH:$TBSSL"
      
    • Be sure to “source ~/.bash_profile”.

Build CA and Server Key

  • Enough setup! It is time to build your OpenVPN data. Begin by emptying your easy-rsa keys folder. You don’t really have to do this the first time, but this step is here in case you come back to re-do your keys. (I did.)

      ./clean-all
    
  • Build your Certificate Authority. Your keys are all signed by this CA.
    • This isn’t a globally trusted CA, but that’s OK because you’re the only person who has to trust it.
    • If you’re building two VPNs, take all the defaults EXCEPT ONE when it asks. (If you’re building just one, you can take ALL the defaults):
      • For “Common Name (eg, your name or your server’s hostname) [KleinfelterFamily CA]”, tell it
        • KleinfelterFamily.RaspiVPN CA (when you’re building keys for your Pi-based VPN.
        • KleinfelterFamily.RouterVPN CA (when you’re building keys for your router-based VPN.
    • Run this:

        ./build-ca
      
      • Keep responding to build-ca prompts with enter (except on Common Name) until you’re back at the shell prompt.
      • Note: Since I’m building two VPNs, I will end up creating two CA. I need two CA because I need one VPN server to trust one CA and the other VPN server to trust the other CA. If I used just one CA, all of my users could use both of my VPNs.
  • Build your server key and name it with the given name. Take all the defaults when it asks. When it asks whether to sign it, say yes. Ditto for committing your changes. I’m naming my cert “server-raspi”. You might want to name yours something more suitable for you.

      ./build-key-server server-raspi      # TODO: Later, use server-router
    
  • We’ll use the TLS authentication key to harden your server against DDOS attacks which make excessive connections to your server. Connections to the OpenVPN port don’t happen without a valid ta.key.

      openvpn --genkey --secret "$KEY_DIR/ta.key"
    
  • Build the Diffie-Hellman data. DH data plus some random data is used to generate session keys. This took 20 minutes on my Pi. You might consider running it with nohup. One time, it ran for over 4 hours without completing. I killed it and restarted it. Since ‘random’ is involved, that may explain the difference, but I’m not certain.

      ./build-dh
    

Client Keys

We’re going to build 40 keys, permitting up to 40 clients. You can come back and build more keys later, but by then I will have forgotten how. Build a nice big supply of them now, and store them somewhere safe. Distribute them as needed. Make a little readme.txt in your keys folder to track where you use each one.

Note that the script below will create some unpassworded keys. With one of these keys, you can login to the VPN without a password. Passworded keys are built with ./build-key-pass instead of ./build-key. (When it asks for PEM pass phrase, assign it a password. This is the password you will use to unlock this key.) If one of your VPN ‘clients’ is an unattended server, that doesn’t work smoothly with a passworded key. My keys will go on my family’s computers and phones. These are all passworded devices and if someone loses one, I’ll revoke the key before the finder can crack the password..

If you use ./build-key-pass, you’ll want to enter the desired password for PEM passphrase. ‘‘Do NOT enter a value for “challenge” password.’’

If you built your keys on your Mac, execute the following commands on your Mac. If you built your keys on your Pi, execute these commands on your Pi. (It is probably wise to put these in a shell script and run it.)

You must respond ‘y’ to the prompt to sign the certificate and the prompt to commit. Everything else can be left at the default.

Note: easy-rsa is an interface designed for low volume. We’re going to push a lot of requests through it and if we get one response wrong, we create a mess. Open up a text note and put exactly 10 newlines in it. Copy them into your clipboard. (Check via paste into another document, to be sure you have exactly 10.) After pressing enter to respond to the prompt about entering a suffix, paste your 10 newlines. This should take you right up to the prompt to commit.

If you make a mistake and you see, “CERTIFICATE WILL NOT BE CERTIFIED”, press control-C to interrupt, delete that key, edit your script to start with that key, and continue. Nothing is permanent in the ‘database’ until you commit the change. If you commit a bad change, you have to start over at “./build-ca”. (I think.)

  • After responding to the suffix prompt, paste your 10 newlines.

For myself, I created the following NAMED un-passworded: kevin-mac, kevin-windows, kevin-cell

And the following NAMED passworded: wife-pc, wife-cell, kid1-pc, kid1-cell, kid2-pc, kid2-cell

easy-rsa
function getsuffix {
    echo  "Building $1.  You can attach a suffix to the key name like 'Jane'" 1>&2
    echo -n "Enter your suffix or press enter for no suffix:" 1>&2
    read suffix
    if [ "." = ".$suffix" ] ; then
        echo "$1"
    else
        echo "$suffix"
    fi
}
echo "BEGIN unpassworded keys"
for i in {01..15} 01-bogus 02-bogus initialize-crl ; do
    ./build-key "user-$(getsuffix $i)"
done
echo "END unpassworded keys"
echo "BEGIN passworded keys"
for i in {15..30} ; do
    ./build-key-pass "user-pass-$(getsuffix $i)"
done

In this example, we created unpassworded keys and passworded keys, plus we’re creating bogus keys so we can practice/validate our CRL (Certificate Revocation List). We will use “initialize-crl” to seed our CRL later. Save your passwords in your keys folder in a readme.txt and “chmod go-rw readme.txt”. You’ll have to provide these passwords to your users and they won’t be able to change them, so you might want to come up with unique passwords for each user.

Reminder: Your client keys and certificates do NOT need to be stored on your VPN server.

Initialize the CRL

./revoke-full initialize-crl

Old Cell Phones

Note: For some older iOS and Android devices, there’s a rumor that you’ll need to convert the key to a triple-DES (3DES) key. You do that with a line like the following, but I’ve not got devices old enough to require DES, so I didn’t use it. If you used build-key-pass, you’ll provide the desired password at the “Enter PEM pass phrase” prompt.

openssl rsa -in Client1.key -des3 -out Client1.3des.key # Do not run unless you know you need it.

If you’re me, and you’re building TWO servers, go back and do everything on this page again, for the second server, using the second server’s name at the TODO items, beginning with “Configuring easy-rsa”.

After you’ve created two sets of keys, edit the “vars” file and replace this:

export KEY_DIR=keys_routervpn

with this

echo -n "Do you want KEY_DIR to be $EASY_RSA/keys_routervpn? (y/n)[y]:"
read response
if [ ".$response" != ".n" ] ; then
    choice=keys_routervpn
else
    echo -n "Do you want KEY_DIR to be $EASY_RSA/keys_raspivpn? (y/n)[y]:"
    read response
    if [ ".$response" != ".n" ] ; then
        choice=keys_raspivpn
else
        echo -n "What sub-directory of $EASY_RSA will you use for KEY_DIR? [keys]:"
        read response
        if [ "." = "$response" ] ; then response=keys ; fi
        choice="$response"
    fi
fi
echo "Using KEY_DIR=$EASY_RSA/$choice"

What Have I Created?

See OpenVPN Keys, Certificates, and Authorities for a reference.

Keep all of the .key files secure and confidential. Use “chmod go-rwx” on your keys folder.


Back to Surf Safe at Starbucks

Keys, Certificates, Certificate Authorities, are Like Your U.S. Passport

  • Summary: Server can accept any CA-approved cert without a list of authorized users

When I found out that my OpenVPN server didn’t need a list of valid user IDs, I was nonplussed. How can you have a server without a list of authorized users? Let me draw an analogy…

When you travel abroad, you take your passport. When you return to the country, you present your passport to Customs. They check your passport to see if it is valid. Unless you are on a blacklist, they let you in (if you haven’t stuffed a pound of cocaine into your underwear!)

Customs does not have a list of citizens and they don’t check to see if you are on the list of people to admit. They only check to see if your credentials are good and if you are on a blacklist. They can do this because we all agree that the State Department has the authority to issue passports.

When your VPN and your client certificates are created, they are signed by a Certificate Authority (CA). Both your client and the server have a copy of the CA’s certificate and its public key.

When you login to the VPN server, your VPN client sends your certificate. The server looks at your certificate and sees that it was issued by the CA. It uses the CA’s public key to validate your certificate as having been issued by that CA. Part of your certificate was encrypted by the CA’s private key, so only the CA’s public key can decrypt it. That’s how the server knows that your certificate really was issued by the CA.

Your certificate is like your passport. The server looks at the certificate, just like Customs looks at your passport. If your certificate is issued by a CA your server trusts, it lets you in – just like Customs lets you in if your passport is issued by the State Department. It also checks to see if you’re on a blacklist (a Certificate Revocation List, or CRL). If you’re not on the CRL, you’re logged in.

In addition, your client sends its public key to the server. Your client uses your private key to encrypt traffic, so the server needs your public key in order to decrypt it.

Note: You can choose to set up your server to require BOTH a certificate and to also have a list of authorized ID/password pairs. It’s your choice.


Return to Safe Surfing at Starbucks

Use Tasker to Auto-enable VPN on Android

Install and Launch Tasker

  • Download and install Tasker via the Play Store icon on your phone. (It is called “Tasker” and it is by “Crafty Apps EU”.)
    • Tasker allows you to create many rules of the form, “If this is the case, do that.” You can tell it to run OpenVPN if you are on WiFi and the WiFi has the ‘wrong’ name.
  • Launch Tasker

Add a “Pause a Few Sec” task

  • From the Profiles/Tasks/Scenes page in Tasker, choose Tasks, press “+”, and name your new task “Pause a Few Sec”. This will give your phone a few seconds to finish connecting to the underlying WiFi before trying to launch VPN.
  • You’ll see “Pause a Few Sec” at the top of the screen.
  • Press “+” to add an action: Choose Task and then Wait
    • Set Seconds to 5, leave other values at 0, and press the back button to return to the Task Edit screen.

Add a “VPN Connect” task

  • From the Profiles/Tasks/Scenes page in Tasker, choose Tasks, press “+”, and name your new task “VPN Connect”.
  • Press “+” to add an action: Choose System and then Send Intent. Give it the following values, and then press the back button to return to the Task Edit screen. Check your spelling and your upper/lower case. You have to get this exactly right.
    • Action: android.intent.action.VIEW
    • Extra (the first one): net.openvpn.openvpn.AUTOSTART_PROFILE_NAME:open_vpn_profile_name
      • Replace open_vpn_profile_name with ‘‘home-vpn-1’’ (or whatever you named your profile, if you didn’t follow my instructions).
    • Package: net.openvpn.openvpn
    • Class: net.openvpn.openvpn.OpenVPNClient
    • Target: Activity

Add a “VPN Disconnect” task

  • From the Profiles/Tasks/Scenes page in Tasker, choose Tasks, press “+”, and name your new task “VPN Disconnect”.
  • Press “+” to add an action: Choose System and then Send Intent. Give it the following values and then press the back button to return to the Task Edit screen.
  • Action: android.intent.action.VIEW
  • Package: net.openvpn.openvpn
  • Class: net.openvpn.openvpn.OpenVPNDisconnect
  • Target: Activity

Add a “Foreign Wifi Connected” profile

  • From the Profiles/Tasks/Scenes page in Tasker, choose Profiles, press “+”, choose Context = “State”, Category = “Net”, then “Wifi Connected”.
  • Give it the following values and then press the back button to return to the Task Edit screen.
    • SSID: your_home_ssid
      • Replace your_home_ssid with your home’s WiFi SSID name.
    • Inverted: checked
    • Press the back button, to return to the Profiles screen.
    • It will demand a Task. Choose VPN Connect.
    • Press and hold “Not Wifi Connected your_home_ssid”
    • Choose “Add” and add another State.
      • State = Net, Net State = WiFi Connected. Leave all values blank.
    • Press the back button to return to the Profiles screen.
  • At this point you have a profile which says:
    • If you are WiFi connected and it isn’t your home WiFi, run the VPN Connect task.

Add a “Not Home Wi-Fi Connected” Profile

  • From the Profiles/Tasks/Scenes page in Tasker, choose Profiles, press “+”, choose Context = “State”, Category = “Net”, then “Wifi Connected”.
  • Give it the following values and then press the back button to return to the Task Edit screen.
    • (all fields blank)
    • Inverted: checked
    • Press the back button, to return to the Profiles screen.
  • It will demand a Task. Choose VPN Disconnect.
    • Press the back button to return to the Profiles screen.
  • At this point you have a profile which says:
    • If you aren’t connected to WiFi, run VPN Disconnect.
    • We’re relying on the assumption that you can’t go from a foreign WiFi to your home WiFi, without at least a brief disconnect.

Note: In many WiFi hotspots, your browser is redirected to a “captive portal”, where you must accept terms and conditions. Your VPN will probably not connect until after you do this.


Back to Surf Safe at Starbucks

Install OpenVPN for Windows

  • Go to the OpenVPN download page
  • Download “Installer, Windows Vista and later” (unless you need Windows XP, in which case you should scroll down and find “Installer (32-bit), Windows XP”).
  • Run the downloaded .exe.
  • Click your way through the installer, choosing the affirmative button as necessary.
    • Caution: While the installer is running, don’t try to do anything else. It will pop up various dialogs and if you are typing into another app, you may dismiss a dialog by mistake. (I did, and it was a problem!)
    • This is a particularly slow installer. It may say “Target was appended to PATH” for a few minutes. Have faith.
  • Transfer the ‘‘.ovpn’’ file you built for that user from ~/easy-rsa/keys (on the machine where you built your keys – in my case that is my Macbook at ~/Packages/easy-rsa/keys).
    • To transfer the file you can use scp, sftp, sneakernet, etc. You should choose a secure method because you don’t want someone else to get that file.
    • Don’t copy the .crt or the .csr or the .key files – just the .ovpn. easy-rsa bundled those other files into the .ovpn for your convenience.
  • Launch “OpenVPN GUI”. (Start button » All Programs » OpenVPN » “OpenVPN GUI”. Or launch it from the icon it added to your desktop.)
    • If you’ve never used OpenVPN before, it will complain about not finding any config files. Press “OK.” We’ll take care of that next.
    • '’Where did it go?’’ It won’t display a nice big window. It will add a small icon to your tray. If you don’t see it in your tray:
      • You may have to click the “»” in your tray to “Show hidden icons.”
      • You’re looking for an icon with a CRT with a padlock. It looks like this: <img src=”/files/openvpn-windows-icon.png#overlay-context=content/install-openvpn-windows” height=”32”; width=”38”; style=”float: right; margin: 0 0 2px 2px”; >
      • You might want to click on the “Show hidden icons” button in the tray, select Customize, and tell Windows to “Show icon and notifications” for this icon, so it won’t hide it in the future.
  • Once you find the Tray icon, right-click it and choose “Import file…” Navigate to the .ovpn file you copied and import it. OpenVPN should give you a success message when you import it.
  • After you import it, delete it from your USB drive, or the temp folder where you copied. You really want to keep this file secure because it will grant anyone who has it access your VPN.
  • I recommend that you right-click on the icon, choose Settings, and enable “Launch on Windows setup,” so that the icon will always be in your tray.

Testing It

  • If you want to test your config at this point, right-click on the OpenVPN GUI icon in your tray and select “Connect”.
    • If you are on a Windows Domain, you might get “Connecting to management interface failed”. You can talk with your domain administrator about adding you to an “OpenVPN Administrators” group, as OpenVPN recommends, or you can just make a habit of launching OpenVPN by right-clicking on its icon and selecting “Run as administrator”. This should not be an issue on a typical home network (unless you set up a domain at home).
  • You might get a warning that your IP address didn’t change, if you are testing from your home LAN. This is OK ‘‘only’’ if you are testing from home. If you are in Starbucks, your IP ought to change from Starbucks’s IP to your home IP. At home, it will “change” from your home IP to your home IP, resulting in no difference.
  • The icon will turn green to let you know it is connected.
  • If you open a Windows Command prompt and type “tracert google.com”, the trace ought to show that the first hop is “10.16.0.1”, then your home’s router(s), and then it heads off to the internet.
  • You should be able to do web browsing via your VPN.
  • To disconnect, right-click on the OpenVPN icon in the Tray and select “Disconnect”.

Return to Surf Safe at Starbucks

Install OpenVPN for Android

  • Transfer the ‘‘.ovpn’’ file you built for that user from ~/easy-rsa/keys (on the machine where you built your keys – in my case that is my Macbook at ~/Packages/easy-rsa/keys).
    • Don’t copy the .crt or the .csr or the .key files – just the .ovpn. easy-rsa bundled those other files into the .ovpn for your convenience.
    • This is hard to do on an Android, if you’ve never transferred a file before.
    • Here are instructions for using a Windows PC to transfer the file. (Of course, you’ll have to first put the .ovpn file somewhere on your Windows PC!)
      • Connect your phone to your PC with a USB cable. If this is the first time you’ve done so, you will see an installation message on your PC. Wait for the driver install to complete.
      • On your phone, pull down the notification/settings. You’ll see a “USB for charging” notification. Tap it.
      • Choose “Transfer files (MTP)”
      • On your computer, you might have a new Windows Explorer window. If not, open Windows Explorer and click on the new “removable disk” icon for your cell phone. On my PC it shows up as a little cell icon labeled “XT1095”, but unless you have the same model of phone, your label will be different.
      • You should see “Internal storage” inside that Explorer window. Open “Internal storage”. Then, open the “Download” folder inside it.
      • Copy the .ovpn file from your PC to the Download file on your phone using Windows Explorer.
      • Delete the .ovpn file from your PC. If you used a USB drive to transfer the .ovpn file, delete it there too. You really want to keep this file confidential.

The rest of these instructions are done on your phone unless otherwise stated:

  • Open “Play Store” on your Android phone.
  • Search for “OpenVPN Connect”.
  • Install it.
  • Open it.
  • From the menu (the three dots stacked vertically, in the upper-right corner), choose “Import Profile from SD card” (even if it isn’t on an SD card).
  • Navigate to your Download folder, tap the .ovpn file, and press Select. You should see (on a very busy page) “Profile successfully imported”.
  • Using your PC, delete the .ovpn file from your phone’s Download folder. You don’t want to leave extra copies of this file sitting around.
  • Press Connect. It will tell you it is about to connect. Press OK. You should see “OpenVPN: Connected”.
  • Press Disconnect.
  • From the menu, choose Settings and enable Battery Saver and also Notifications.
  • To the right of “OpenVPN Profile”, you should see a Notepad icon. Tap it.
  • Choose Rename Profile. Rename it to “my-home-vpn-1”.
  • Tap the Notepad icon again and choose “
  • Select “Create Connect Shortcut”
  • Give it a name of your choosing. I like “My Home VPN”. Tap “Create Shortcut”.
  • Return to your phone’s launcher screen (typically by pressing the circle icon at the bottom of your screen).
  • Unplug your phone from your computer.

In normal use, here’s how you will run OpenVPN:

  • Tap the “My Home VPN” icon (or whatever you decided to call it) on your phone’s launch screen.
  • When you want to disconnect, tap the icon again and tap “Disconnect”.
  • That’s it!

If you want to confirm that you’re really using VPN:

  • Install PingTools Network Utilities from Play Store. (You want the one by “StreamSoft”.)
  • While you’re on VPN, confirm that you can ping the IP address of your home router, and do a Traceroute to google.com and confirm it goes via your 10.16.x.x VPN subnet.

Return to Surf Safe at Starbucks

TagLibrary.scptd

I don’t know where this came from, but it is helpful in dealing with tags on Mac OS X.

use framework "Foundation"

-- Must be stored in ~/Library/Script Libraries as a .scptd file
-- Sample use from a script:
-- 
-- use theLib : script "TagLibrary"
-- use scripting additions
-- set fileName to "/Users/kevin/Temp/image-and-text.pdf"
-- theLib's setTags:{"tag1", "tag2"} forPath:(fileName)
-- theLib's addTags:{"tag3", "tag4"} forPath:(fileName)
-- set s to theLib's returnTagsFor:(fileName)
-- repeat with x in s
-- 	display dialog x
-- end repeat

on returnTagsFor:posixPath -- get the tags
    set aURL to current application's |NSURL|'s fileURLWithPath:posixPath -- make URL
    set {theResult, theTags} to aURL's getResourceValue:(reference) forKey:(current application's NSURLTagNamesKey) |error|:(missing value)
    if theTags = missing value then return {} -- because when there are none, it returns missing value
    return theTags as list
end returnTagsFor:

on setTags:tagList forPath:posixPath -- set the tags, replacing any existing
    set aURL to current application's |NSURL|'s fileURLWithPath:posixPath -- make URL
    aURL's setResourceValue:tagList forKey:(current application's NSURLTagNamesKey) |error|:(missing value)
end setTags:forPath:

on addTags:tagList forPath:posixPath -- add to existing tags
    set aURL to current application's |NSURL|'s fileURLWithPath:posixPath -- make URL
    -- get existing tags
    set {theResult, theTags} to aURL's getResourceValue:(reference) forKey:(current application's NSURLTagNamesKey) |error|:(missing value)
    if theTags ≠ missing value then -- add new tags
        set tagList to (theTags as list) & tagList
        set tagList to (current application's NSOrderedSet's orderedSetWithArray:tagList)'s allObjects() -- delete any duplicates
    end if
    aURL's setResourceValue:tagList forKey:(current application's NSURLTagNamesKey) |error|:(missing value)
end addTags:forPath:

Export Everything From Evernote

I’m leaving Evernote. Here’s a script I wrote to export everything from Evernote.

-- Evernote-Export-All: Export everything from Evernote.
-- Copyright (c) 2017 by Kevin P. Kleinfelter

-- **********************************************************
-- Assumptions:
--   * If you have notes with PDF attachments, they have only one PDF attachment and no body. (Error message if not true.)
--      If this isn't the case, edit your notes to make it the case.  ENML editor is helpful.
--      You could print the note body to PDF, then combine the body-PDF with the attached PDF into a 
--      new single PDF, but many of my notes-with-PDF had a body with a blank line.  (You might
--      think you could delete the blank line with Evernote, but EN adds a bunch of markup
--      which it does not remove when you delete the blank line, so use ENML editor.)
--      For notes where you really want the note body followed by PDF attachment(s),
--      print the note to PDF, which will produce multiple PDFs, and then manually combine
--      the PDFs into  a single PDF.
--   * Must copy your TagLibrary.scptd source file (which was in my ~/Documents/code/AppleScripts folder, 
--      as of December 2016) to "~/Library/Script Libraries".  You may have to mkdir "Script Libraries"
--
-- Learnings/disappointments:
--   * AppleScript does not have access to Evernote Stacks.  "Use the full API" says Support.
-- **********************************************************

-- **********************************************************
use tagLib : script "TagLibrary"
use scripting additions

-- ************************************************************

-- Dev and Debug options
set limitToNotebook to ""
set scanOnly to false -- don't write anything.  Just report metrics and problems.
set ensureDataQuality to true
-- ************************************************************

set exportFolder to "/Users/kevin/Temp/Evernote-export"
set totalNoteCount to 0
set onePdfCount to 0 -- Notes with a single PDF attached.
set oneImageCount to 0 -- Notes with a single image attached and no body.
set htmlCount to 0 -- Notes exported as HTML
set simpleCount to 0 -- Notes exported as single file
set newline to "
"

-- **********************************************************


-- **********************************************************
-- **********************************************************
set kount to 0
display dialog "Have you cleared the old export folder and are you showing log messages?"

with timeout of (1000 * 60) seconds
    if ensureDataQuality then
        fixBadNoteNames()
        findDuplicateNoteNames()
    else
        display dialog "Re-enable data quality checks"
    end if

    tell application id "com.evernote.evernote"
    
        if notebook named "temp_export_notebook" exists then delete notebook "temp_export_notebook"
        create notebook "temp_export_notebook" with type local only
    
        if my limitToNotebook = "" then
            set allNotebooks to every notebook
        else
            display dialog "limited to notebook " & limitToNotebook
            set allNotebooks to notebook named limitToNotebook
        end if
    
        repeat with currentNoteBook in allNotebooks
            set notebookName to (the name of currentNoteBook)
            log "0, NOTEBOOK, " & notebookName
        
            set allNotes to every note in notebook notebookName
            repeat with currentNote in allNotes
                set totalNoteCount to (totalNoteCount + 1)
                set theHtml to HTML content of currentNote
                set noteTitle to (the title of currentNote)
            
                if (count of attachments of currentNote) = 0 then
                    my exportSingleFile(currentNote, notebookName)
                else if (count of attachments of currentNote) = 1 then
                    set myMime to mime of first attachment of currentNote
                
                    if myMime = "application/pdf" then
                        if (my countSubstring(theHtml, "</div>")) > 1 then
                            log "Notes with PDF attachments must have no body. Violation:" & noteTitle
                            display dialog theHtml
                            my die("Notes with PDF attachments must have no body.  Violation:" & noteTitle)
                        end if
                    
                        my exportToPdf(currentNote, noteTitle, notebookName)
                    else if (my isSingleImageOnly(myMime, theHtml)) then
                        my exportToImage(currentNote, noteTitle, notebookName, my mimeToFileType(myMime))
                    else
                        my exportToHtml(currentNote, notebookName)
                    end if
                else -- Multiple attachments
                    repeat with theAttachment in (attachments of currentNote)
                        if mime of theAttachment = "application/pdf" then
                            my die("Not implemented: Multiple attachments with a PDF. " & noteTitle)
                        end if
                    end repeat
                
                    my exportToHtml(currentNote, notebookName)
                end if
            end repeat -- repeat with currentNoteBook in allNotebooks
        end repeat
        if notebook named "temp_export_notebook" exists then delete notebook "temp_export_notebook"
    end tell
    my die("Successfully completed")
end timeout


-- Sample of image-only note:
-- <div id="en-note"><img src="?hash=12345" id="en-media:image/png:12345:none:none" class="en-media"/></div>
on isSingleImageOnly(myMime, theHtml)
    if myMime is not equal to "image/png" and myMime is not equal to "image/jpeg" then return false

    set prefix to "<div id=\"en-note\"><img src=\"?hash="

    if theHtml starts with prefix or theHtml starts with (my newline & prefix) then
        -- continue
    else
        return false
    end if

    if (countSubstring(theHtml, "</div>")) ≠ 1 then return false

    set s to RemoveFromString(theHtml, "<div id=\"en-note\"><img src=\"?hash=")
    set s to RemoveFromString(s, "</div>")

    if (countSubstring(s, "<") > 0) then return false
    if (countSubstring(s, ">") > 1) then return false

    if (my countSubstring(theHtml, "en-media:image/png")) = 1 then return true
    if (my countSubstring(theHtml, "en-media:image/jpeg")) = 1 then return true

    return false
end isSingleImageOnly


on findDuplicateNoteNames()
    do shell script "rm -f /tmp/evernote-titles.txt"
    set foundDuplicates to false
    tell application "Evernote"
        set EVNotebooks to every notebook
        repeat with EVnotebook in EVNotebooks
            log "0, message, Finding duplicate titles in " & name of EVnotebook
            set allNotes to {}
            set allNotes to every note in EVnotebook
            repeat with aNote in allNotes
                set aTitle to title of aNote
                set aTitle to my filenameSafe(aTitle) -- comment
                do shell script "echo '" & aTitle & "' >> /tmp/evernote-titles.txt"
            end repeat
        end repeat
    end tell

    do shell script "sort /tmp/evernote-titles.txt | uniq -d > /tmp/evernote-duplicates.txt"
    set s to do shell script "cat /tmp/evernote-duplicates.txt"
    if length of s > 4 then
        with timeout of 30000 seconds -- wait 500 minutes
            display dialog "Duplicate note names found.  Please fix. " & s
        end timeout
    end if
end findDuplicateNoteNames



on fixBadNoteNames()
    tell application "Evernote"
        set EVNotebooks to every notebook
        repeat with EVnotebook in EVNotebooks
            log "0, message, Fixing note titles in " & name of EVnotebook
            set allNotes to {}
            set allNotes to every note in EVnotebook
            repeat with aNote in allNotes
                set originalTitle to title of aNote
                set newTitle to my filenameSafe(originalTitle)
                if newTitle ≠ originalTitle then
                    --	display dialog "Renaming '" & originalTitle & "' to '" & newTitle & "'"
                    set title of aNote to newTitle
                end if
            end repeat
        end repeat
    end tell
end fixBadNoteNames


-- **********************************************************
-- **********************************************************
on mkDir(pathName)
    do shell script "mkdir -p '" & pathName & "'"
end mkDir


-- **********************************************************
-- From https://geert.vanderkelen.org/2010/splitting-as-string-and-joining-a-list-using-applescript/
-- **********************************************************
to joinList(aList, delimiter)
    set retVal to ""
    set prevDelimiter to AppleScript's text item delimiters
    set AppleScript's text item delimiters to delimiter
    set retVal to aList as string
    set AppleScript's text item delimiters to prevDelimiter
    return retVal
end joinList


-- **********************************************************
-- I tried asking Evernote for the Note's HTML and writing that to a file, but I found some notes would contain 
-- a byte with hex CA that Evernote rendered as a space but HTML viewers and file editors showed that byte as an E with an accent.
-- Telling Evernote to *export* the note did not have that problem.  However, exporting the note
-- puts the HTML into a folder, and I'd prefer that simple HTML notes not go into a folder which will contain only
-- the one file.
-- 
-- Unfortunately, the note exports without the note TITLE when you tell Evernote to export it.
-- That note title renders very visibly in Evernote so I think of it as part of the note.
-- Adding a title to the output was easy when I wrote the HTML directly, because Evernote gave me 
-- the HTML for the BODY of the page (without the <html> or the <body> tags) so I could just prepend the title.
-- When I ask Evernote to do the export, I need to add the title before exporting.
--
-- OK, Evernote got really slow when I started creating temp notes to add the title.  I'm going to export the note
-- and then "sed" (or similar) the html to add a title.
--
-- I'm going to convert these from HTML to RTF.  I can get away with this because they have no attachments/images.
-- By converting them to RTF, if I double-click on the resultant file, it will open with an editor, and if it is a
-- hand-created note (as much of these are), it is probably some kind of list or brainstorming document that I want
-- to edit as often as to view.
--
-- Nope.  RTF is a bad idea.  The conversion to RTF loses column-widths, which some web site captured data
-- uses.  Best to stick with HTML and I'll manually convert files I want to routinely edit.  
-- (Or I can add an HTML editor to the open-with list.)
-- **********************************************************
on exportSingleFile(currentNote, notebookName)
    set my simpleCount to ((my simpleCount) + 1)
    if my scanOnly then return

    set sanitizedNotebookName to my filenameSafe(notebookName)
    set htmlFolderPath to my exportFolder & "/" & sanitizedNotebookName
    mkDir(htmlFolderPath)
    set noteAsList to {}

    tell application id "com.evernote.evernote"
        set noteTitle to title of currentNote
        set safeNoteTitle to my filenameSafe(noteTitle)
        set exportFilename to htmlFolderPath & "/" & safeNoteTitle & ".html"
    end tell
    set beginning of noteAsList to currentNote

    if not my scanOnly then
        log my totalNoteCount & " Single:" & exportFilename
        tell application id "com.evernote.evernote" to export noteAsList to "/tmp/tmp_evernote_export" format HTML with tags
    
        do shell script "mv /tmp/tmp_evernote_export/* '/tmp/tmp_evernote_export/" & safeNoteTitle & ".html'"
    
    
        -- ****** Add the note title to the note ******
        set perlCmd to "perl -pi -e 's/<(body.*?)>/<\\1>"
        set perlCmd to perlCmd & "<div style=\"font-size:16pt;line-height: 140%;font-family:Helvetica Neue, Helvetica, Arial, sans-serif\">"
        set perlCmd to perlCmd & safeNoteTitle
        set perlCmd to perlCmd & "<\\/div><br>"
        set perlCmd to perlCmd & "/' " & "'/tmp/tmp_evernote_export/" & safeNoteTitle & ".html'"
    
        set perlCmd to perlCmd & " 2>>/tmp/tmp_evernote_perl.err"
        do shell script perlCmd
    
    
        -- ***** Apply a default body font ------
    
        set perlCmd to "perl -pi -e 's/(<\\/head><body)/\\1"
        set perlCmd to perlCmd & " style=\"font-weight:380;font-size:10.5pt;line-height: 130%;font-family:Helvetica Neue, Helvetica, Arial, sans-serif\""
        set perlCmd to perlCmd & "/' " & "'/tmp/tmp_evernote_export/" & safeNoteTitle & ".html'"
    
        set perlCmd to perlCmd & " 2>>/tmp/tmp_evernote_perl.err"
        do shell script perlCmd
    
    
        do shell script "mv /tmp/tmp_evernote_export/*.html '" & exportFilename & "'"
    
        applyAttributesToFile(currentNote, exportFilename)
    
        -- do shell script "textutil -convert rtf '/tmp/tmp_evernote_export/" & my filenameSafe(noteTitle) & ".html' -o '" & exportFilename & "'"
    
    end if

end exportSingleFile


-- Update the file metadata with metadata from Evernote.
on applyAttributesToFile(currentNote, exportFilename)
    if my scanOnly then return

    -- Tags
    tell application "Evernote"
        set noteTags to the tags of currentNote
        if (count of (tags of currentNote)) > 0 then
            set tagStrings to my tagsToStrings(noteTags)
            tagLib's setTags:tagStrings forPath:(exportFilename)
        end if
    end tell

    -- Creation Date and Modification Date
    tell application "Evernote" to set cdate to creation date of currentNote
    tell application "Evernote" to set mdate to modification date of currentNote

    set s_cdate to "\"" & timestampReformat(cdate) & "\""
    set s_mdate to "\"" & timestampReformat(mdate) & "\""

    do shell script "SetFile -d " & s_cdate & " \"" & exportFilename & "\""
    do shell script "SetFile -m " & s_mdate & " \"" & exportFilename & "\""

end applyAttributesToFile

on tagsToStrings(noteTags)
    set ret to {}
    repeat with t in noteTags
        copy (name of t) to the end of the ret
    end repeat
    return ret
end tagsToStrings

-- **********************************************************
-- **********************************************************
on exportToImage(currentNote, noteTitle, notebookName, fileType)
    set my oneImageCount to ((my oneImageCount) + 1)

    if my scanOnly then return

    tell application id "com.evernote.evernote" to set theAttachment to first attachment of currentNote
    set sanitizedNotebookName to my filenameSafe(notebookName)
    set imageFolderPath to my exportFolder & "/" & sanitizedNotebookName
    mkDir(imageFolderPath)

    set exportFilename to imageFolderPath & "/" & my filenameSafe(noteTitle) & fileType
    try
        do shell script "rm '" & exportFilename & "'"
    end try
    tell application id "com.evernote.evernote"
        write theAttachment to exportFilename
    end tell
    applyAttributesToFile(currentNote, exportFilename)

    log my totalNoteCount & "Image:" & exportFilename

end exportToImage

-- **********************************************************
-- **********************************************************
on exportToPdf(currentNote, noteTitle, notebookName)
    set my onePdfCount to ((my onePdfCount) + 1)
    if my scanOnly then return

    tell application id "com.evernote.evernote" to set theAttachment to first attachment of currentNote
    set sanitizedNotebookName to my filenameSafe(notebookName)
    set pdfFolderPath to my exportFolder & "/" & sanitizedNotebookName
    mkDir(pdfFolderPath)

    set exportFilename to pdfFolderPath & "/" & my filenameSafe(noteTitle) & ".pdf"
    set exportFilename to replaceString(exportFilename, ".pdf.pdf", ".pdf")
    set exportFilename to replaceString(exportFilename, ".PDF.pdf", ".pdf")
    set exportFilename to replaceString(exportFilename, ".pdf.PDF", ".pdf")
    set exportFilename to replaceString(exportFilename, ".PDF.PDF", ".pdf")
    try
        do shell script "rm '" & exportFilename & "'"
    end try
    tell application id "com.evernote.evernote"
        write theAttachment to exportFilename
    end tell
    applyAttributesToFile(currentNote, exportFilename)

    log my totalNoteCount & "PDF:" & exportFilename

end exportToPdf



-- **********************************************************
-- **********************************************************
on exportToHtml(currentNote, notebookName)
    if my scanOnly then return
    set noteAsList to {}
    set beginning of noteAsList to currentNote
    set sanitizedNotebookName to my filenameSafe(notebookName)
    set htmlFolderPath to my exportFolder & "/" & sanitizedNotebookName
    mkDir(htmlFolderPath)

    set my htmlCount to ((my htmlCount) + 1)
    tell application id "com.evernote.evernote"
        set exportFilename to htmlFolderPath & "/" & my filenameSafe(title of currentNote)
    
        -- Do something with note with multiple attachments
        -- Looks like export as HTML is the best option. This will create a
        -- folder named to match the note tile, containing a single HTML file
        -- named to match the note title, with a sub-folder containing
        -- all of the attachments (which are almost certain to be images).
        if not my scanOnly then
            log my totalNoteCount & "HTML:" & exportFilename
            export noteAsList to exportFilename format HTML with tags
        end if
    end tell
    applyAttributesToFile(currentNote, getHtmlFileIn(exportFilename)) -- exportFilename is really the folder where the html got written.

end exportToHtml

on getHtmlFileIn(folderName)
    set ret to folderName
    set fld to POSIX file folderName
    tell application "Finder" to set folder_list to items of folder fld
    repeat with f in folder_list
        if name of f ends with ".html" then
            set ret to folderName & "/" & name of f
        end if
    end repeat
    return ret
end getHtmlFileIn


-- **********************************************************
-- Ensure that the name is safe for a file or folder name
-- **********************************************************
on filenameSafe(fileSystemName)
    set tempName to fileSystemName
    set tempName to replaceString(tempName, "\\", "")
    set tempName to replaceString(tempName, "/", "-")
    set tempName to replaceString(tempName, "&", "+")
    set tempName to replaceString(tempName, "|", "-")
    set tempName to replaceString(tempName, ":", "-")
    set tempName to replaceString(tempName, "*", "_")

    set tempName to RemoveListFromString(tempName, {"'", "|", "&", "\"", ">", "<", "?", "$"}) -- do NOT replace \ because I ADD it in front of stuff before this line
    return tempName
end filenameSafe



-- **********************************************************
-- **********************************************************
on die(theMessage)
    log "-1, Message, " & theMessage
    log "-1, Simple note count, " & my simpleCount
    log "-1, PDF-only note count, " & my onePdfCount
    log "-1, HTML-export count, " & my htmlCount
    log "-1, Image-only count, " & my oneImageCount
    log "-1, Total note count:" & my totalNoteCount

    with timeout of (1000 * 60) seconds
        display dialog theMessage
    end timeout
    if not my scanOnly then
        error number -128
    end if
end die



-- **********************************************************
-- Example: replaceString("Hello hello", "hello", "Bye") produces "Hello Bye"
-- From http://applescript.bratis-lover.net/library/string/
-- **********************************************************
on replaceString(theText, oldString, newString)
    local ASTID, theText, oldString, newString, lst
    set ASTID to AppleScript's text item delimiters
    try
        considering case
            set AppleScript's text item delimiters to oldString
            set lst to every text item of theText
            set AppleScript's text item delimiters to newString
            set theText to lst as string
        end considering
        set AppleScript's text item delimiters to ASTID
        return theText
    on error eMsg number eNum
        set AppleScript's text item delimiters to ASTID
        error "Can't replaceString: " & eMsg number eNum
    end try
end replaceString


-- **********************************************************
-- From http://applescript.bratis-lover.net/library/string/
-- **********************************************************
on RemoveFromString(theText, CharOrString)
    local ASTID, theText, CharOrString, lst
    set ASTID to AppleScript's text item delimiters
    try
        considering case
            if theText does not contain CharOrString then ¬
                return theText
            set AppleScript's text item delimiters to CharOrString
            set lst to theText's text items
        end considering
        set AppleScript's text item delimiters to ASTID
        return lst as text
    on error eMsg number eNum
        set AppleScript's text item delimiters to ASTID
        error "Can't RemoveFromString: " & eMsg number eNum
    end try
end RemoveFromString



-- **********************************************************
-- Remove items from a string.  e.g. RemoveListFromString("Hello hello2", {"hello2"})
-- From http://applescript.bratis-lover.net/library/string/
-- **********************************************************
on RemoveListFromString(theText, listOfCharsOrStrings)
    local ASTID, theText, CharOrString, lst
    try
        script k
            property l : listOfCharsOrStrings
        end script
        set len to count k's l
        repeat with i from 1 to len
            set cur_ to k's l's item i
            set theText to my RemoveFromString(theText, cur_)
        end repeat
        return theText
    on error eMsg number eNum
        error "Can't RemoveListFromString: " & eMsg number eNum
    end try
end RemoveListFromString



-- **********************************************************
-- How many times does theSubstring appear in theText?
-- From http://applescript.bratis-lover.net/library/string/
-- **********************************************************
on countSubstring(theText, theSubstring)
    local ASTID, theText, theSubstring, i
    set ASTID to AppleScript's text item delimiters
    try
        set AppleScript's text item delimiters to theSubstring
        set i to (count theText's text items) - 1
        set AppleScript's text item delimiters to ASTID
        return i
    on error eMsg number eNum
        set AppleScript's text item delimiters to ASTID
        error "Can't countSubstring: " & eMsg number eNum
    end try
end countSubstring

on mimeToFileType(myMime)
    if myMime = "image/png" then
        return ".png"
    else if myMime = "image/jpeg" then
        return ".jpg"
    else
        display dialog "Unexpected mime type: " & myMime
    end if
end mimeToFileType


-- Need to get a string with "mm/dd/yyyy hh:mm:ss PM"
on timestampReformat(t)
    tell (t) to get ("" & (its month as integer) & "/" & its day as text) & "/" & its year as text
    set theDate to result & " " & time string of t
    return theDate
end timestampReformat