Sunday, October 7, 2018

Unbricking My TRENDnet TEW-812DRU Wireless Router

Upgrading my TRENDnet TEW-812DRU v2 router with DD-WRT firmware sometimes goes smoothly, and sometimes not. Usually if the upgrade fails on the first try, I can just unplug the router, wait 10 seconds, plug it in again, wait for the web UI to come up again, re-upload the firmware (and wait), and the upgrade will work on the second try.

But sometimes the router won't boot up correctly. All the blinking lights come on as normal, but it doesn't do any actual routing — or provide any DHCP services, which makes the router look bricked, even for devices connected to it physically with an ethernet cord.

But fortunately, it's not actually bricked. The router still grabs its usual local address (192.168.1.1, if you haven't configured it to be something else), and runs its nifty "TRENDnet - Emergency miniWeb Server" on port 80. The emergency page served up allows you to upload a new firmware image — and every time (so far) that I've gotten to that page, I've simply been able to upload the firmware image I've been trying to install (ie the latest trendnet-812dru-webflash.bin file from DD-WRT); and the router accepts it, installs it, and reboots itself, and everything is back to normal and happy in a few minutes.

The trick to accessing the router when its usual networking services are down is to 1) connect a computer to the router via wired ethernet connection (if you don't have one set up that way already), and 2) configure that computer with a static IP on the router's local subnet.

Since I'm running my router at 192.168.1.1, I just set the computer's static IP address to 192.168.1.10, and point its browser to http://192.168.1.1. The emergency web server seems to listen only for a minute or two after booting, though, and then goes way; so if the emergency page won't load, I unplug the router, wait 10 seconds, and plug it in again.

And since that wired computer is running Ubuntu 16.04 (with a wired interface named enp1s2f3 — look it up via a command like ifconfig or ip address etc), I set its static IP address by adding the following to my /etc/network/interfaces:

iface enp1s2f3 inet static address 192.168.1.10 netmask 255.255.255.0 gateway 192.168.1.1 dns-nameservers 192.168.1.1

And then run sudo service network-manager stop to make NetworkManager cool its butt, and sudo service networking restart to use the static IP.

Saturday, July 21, 2018

DD-WRT Firmware for TRENDnet TEW-812DRU Wireless Router

I'm lucky enough to get gigabit internet access at home, from Wave Broadband, with which I'm quite happy. And I don't even need a modem — with my apartment building, I can just jack directly into the Ethernet port in my living room wall. I originally got a Kasada router, but its firmware hasn't been updated in a couple years, so I decided to get a new router that I knew would be updateable with the Free Software DD-WRT firmware.

I got a TRENDnet TEW-812DRU v2, which although it's like 5 years old, is as fast as I need (supporting gigabit ethernet, plus 1.3 Gbps 802.11ac and 450 Mbps 802.11n wireless on separate 5 GHz and 2.4 GHz channels) — and a quarter of the price of comparable new routers. And, importantly, it looked like it was well supported by DD-WRT.

And it did turn out to be well supported by DD-WRT. Having first read through all the forum posts about the TEW-812DRU, I found that, unlike some other routers, you don't need anything special to use DD-WRT on the TEW-812DRU — just upload the new firmware to the router through its web UI and let it do its thing, no tricks needed.

So the first thing I did when I plugged in the router was login to its web UI and flash it with the "Open Source" firmware I downloaded from the TRENDnet TEW-812DRU downloads page. That turned out to be DD-WRT v24-sp2 r23194, compiled on 12/21/2013. I was happy it worked, but that firmware was just way too old.

So next I looked up the TEW-812DRU in the DD-WRT router database, and that prompted me to download something labeled DD-WRT v24-sp2 r23804 (but turned out actually to be r23808), compiled on 3/27/2014. I flashed that firmware through the web UI — but when the router rebooted, it presented me with an "Emergency Web Server" page. I went to look up what that meant via a working internet connection, and when I checked on the router again, the Emergency Web Server page had been replaced with the working DD-WRT web UI. I figure it must have just taken a little extra while for the router to boot everything up, no big deal.

But that firmware was also way older than I was hoping for, so I went searching through the downloads directory of the DD-WRT site — and finally found the latest version of the TEW-812DRU firmware here:

https://download1.dd-wrt.com/dd-wrtv2/downloads/betas/2018/07-16-2018-r36330/trendnet-812DRUv2/

I flashed that firmware through the router's web UI, and was very pleased to see the router reboot with no issues at all, happily running DD-WRT v3.0-r36330 mini — compiled just a few days ago on 7/16/2018. Finally, peace of mind that no bears, pandas, or kittens will be making themselves at home inside my router!

Sunday, June 24, 2018

Skip the Pre-Commit Hook on Git Rebase or Merge

When you want to skip the git pre-commit hook for a single commit, it's easy — you just add the --no-verify flag (or -n for short) to the git commit command:

git commit --no-verify

But to skip multiple commits executed by another git command, like rebase or merge, the --no-verify flag doesn't work. The best way I've found to skip the pre-commit hook in that case is to code the hook to check for a custom environment variable (I like to use NO_VERIFY), and skip the pre-commit logic if it's not empty. For example, the pre-commit.sh script in my Google Java Format Pre-Commit Hook has a block of code like this at the top of the file, which skips the main functionality of the pre-commit hook if the NO_VERIFY environment variable has been set to anything other than an empty string:

if [ "$NO_VERIFY" ]; then
    echo 'pre-commit hook skipped' 1>&2
    exit 0
fi

So when I want to skip that pre-commit hook when doing a complicated rebase or merge, I simply run the following commands in the same shell:

export NO_VERIFY=1
git rebase -i master # or `git merge some-branch` or whatever
export NO_VERIFY=

Monday, June 18, 2018

Google Java Format Pre-Commit Hook

My team decided to standardize on the Google Java Style Guide for formatting Java code; and not finding a drop-in git pre-commit hook for the Google Java Format library, I whipped one up and pushed it to GitHub as the Google Java Format Pre-Commit Hook project.

To use it, clone the repo, and link its pre-commit.sh script as the .git/hooks/pre-commit script in whatever project you want to use it with (or call it from your existing .git/hooks/pre-commit script, if you already have one). The script automatically downloads the Google Java Format library, and runs it over all staged .java files whenever you make a commit (and fails the commit if there are any formatting issues it can't automatically clean up).

You can skip the hook by including the --no-verify flag on an individual commit, or by setting the NO_VERIFY environment variable in your shell to be not empty prior to running a sequence of commits (like a merge or rebase). Full details for install and usage are in the project README.

Sunday, May 20, 2018

OpenDKIM Key Retrieval Failed

I set up OpenDKIM on my mailserver years ago, but while I got it working for signing just fine, I could never get it working for verification. Whenever I'd receive a message signed with DKIM, I'd see an error message like this in my mailserver logs:

May  4 01:20:20 mail opendkim[24874]: 7EE5982132: key retrieval failed (s=20161025, d=gmail.com): '20161025._domainkey.gmail.com' query timed out

When I tried dig on the DNS record listed in the logs, however, I was able to retrieve it just fine:

dig TXT 20161025._domainkey.gmail.com

Recently I set aside some time to "dig" into it further. I found I could use the opendkim-testkey command to at least reproduce the issue (instead of having to keep sending test emails from other accounts to myself). For example, the following command tries to retrieve the DKIM key from the 20161025._domainkey.gmail.com DNS TXT record (20161025 is the DKIM selector and gmail.com is the signing domain):

opendkim-testkey -s 20161025 -d gmail.com

This command gave me the same "query timed out" error message that I saw in my logs. Through a lot of trial and error, I figured out that I could avoid the error by setting the Nameservers property in my /etc/opendkim.conf file to an external DNS server (any external one will do), and restarting the OpenDKIM daemon. I've been running my mailserver on an Ubuntu EC2 instance, and apparently OpenDKIM does not like something about the combination of the Ubuntu DNS resolver and the internal EC2 DNS servers.

So, I added this line to my /etc/opendkim.conf, restarted the OpenDKIM daemon, and now I no longer see the "key retrieval failed" error message in my logs (instead I get a nice Authentication-Results header added by OpenDKIM to my incoming mail)!:

Nameservers 1.1.1.1

1.1.1.1 is Cloudflare's DNS servers — but any external DNS servers should work. One thing to keep in mind with using external DNS servers is that if your network has a stateless firewall, you need to allow inbound access to UDP in the "ephemeral" port range. If you're using EC2's Network ACLs (and not just using the defaults), this means adding a rule like the following to the ACL for the subnet in which your mailserver lives (32768-61000 is Ubuntu's ephemeral port range):

Rule #: [any number lower than your DENY rules]
Type: Custom UDP Rule
Protocol: UDP (17)
Port Range: 32768-61000
Source: 1.1.1.1/32
Allow/Deny: ALLOW

Sunday, April 30, 2017

Jenkins Install-Plugin Remoting Deprecated

Installing and setting up Jenkins through an automated process can be tricky. The new, safer CLI (Command Line Interface) that was implemented for Jenkins 2.54 adds another twist to the process. That twist is the "remoting" mechanism for using the CLI has been deprecated, and turned off by default — but it's still the only way to install plugins via the CLI.

So now, to install plugins through automation, you first have to turn remoting back on. You can do that by changing the enabled element in the jenkins.CLI.xml config file (located in the root of your Jenkins home directory) to true, like so:

<?xml version='1.0' encoding='UTF-8'?>
<jenkins.CLI>
  <enabled>true</enabled>
</jenkins.CLI>

And then restart Jenkins. Now you can use the remoting protocol with the CLI — but it's no longer the default protocol, so you have to specify it explicitly via the -remoting flag, like so (for example to install the ant plugin):

java -jar jenkins-cli.jar -remoting -s http://localhost:8080 \
    install-plugin ant \
    --username admin \
    --password-file secrets/initialAdminPassword

If you don't enable remoting and/or specify the -remoting flag, you'll get an error like this from the CLI:

ERROR: Bad Credentials. Search the server log for 18058afb-86ed-4cc8-856f-b128918cbe8b for more details.

And you'll see this in the Jenkins server log:

INFO: CLI login attempt failed: 18058afb-86ed-4cc8-856f-b128918cbe8b
org.acegisecurity.BadCredentialsException: Failed to read secrets/initialAdminPassword; nested exception is hudson.AbortException: This command is requesting the deprecated -remoting mode. See https://jenkins.io/redirect/cli-command-requires-channel
        at hudson.security.AbstractPasswordBasedSecurityRealm$1.authenticate(AbstractPasswordBasedSecurityRealm.java:74)
        at hudson.cli.CLICommand.main(CLICommand.java:268)
        at hudson.cli.CLIAction$PlainCliEndpointResponse$1.run(CLIAction.java:221)

Once you've got all your plugins installed, you probably will want to go back and disable remoting (by changing the enabled element in the jenkins.CLI.xml config file back to false and restarting Jenkins; or manually via the "Enable CLI over Remoting" checkbox on Jenkins' "Manage Jenkins > Configure Global Security" page).

Sunday, July 10, 2016

JPGPJ: A new Java GPG Library

The Bouncy Castle PGP implementation is the "standard" GPG/PGP library in Java, and it's quite solid — but it's cumbersome to use directly, since it pretty much forces you to learn and use the raw primitives of the OpenPGP spec (RFC 4880). Also, while there is some helpful example code in the Bouncy Castle examples package (and snippets from the same examples have been copied and pasted into a bunch of Stack Overflow answers), the example code is (appropriately?) cryptic, and covers only a limited subset of functionality in each example.

Encrypting with JPGPJ

So I wrote a small library, JPGPJ, to wrap the Bouncy Castle PGP implementation with a simple API for encrypting and decrypting files. It makes interoperating with the standard gpg command-line client (GnuPGP) a breeze. This is all you need to do to encrypt a file with Bob's public key, and sign it with Alice's private key:

new Encryptor(
    new Key(new File("path/to/my/keys/alice-sec.gpg"), "password123"),
    new Key(new File("path/to/my/keys/bob-pub.gpg"))
).encrypt(
    new File("path/to/plaintext.txt"),
    new File("path/to/ciphertext.txt.gpg")
);

The above Java code does the same thing as following gpg command (where Alice has an `alice` secret key and a `bob` public key on her keyring, and enters "password123" when prompted for her passphrase):

gpg --sign --encrypt --local-user alice --recipient alice --recipient bob \
    --output path/to/ciphertext.txt.gpg path/to/plaintext.txt

JPGPJ is set up to do the right thing by default — sign and encrypt — but if you just want to encrypt without signing, that's easy, too — just set the encryptor's signingAlgoritm property to Unsigned:

Encryptor encryptor = new Encryptor(
    new Key(new File("path/to/my/keys/bob-pub.gpg"))
);
encryptor.setSigningAlgorithm(HashingAlgorithm.Unsigned);
encryptor.encrypt(
    new File("path/to/plaintext.txt"),
    new File("path/to/ciphertext.txt.gpg")
);

To encode with ASCII Armor (ie produce Base64-encoded content, instead of binary content), just turn on the encryptor's asciiArmored flag:

Encryptor encryptor = new Encryptor(
    new Key(new File("path/to/my/keys/bob-pub.gpg"))
);
encryptor.setSigningAlgorithm(HashingAlgorithm.Unsigned);
encryptor.setAsciiArmored(true);
encryptor.encrypt(
    new File("path/to/plaintext.txt"),
    new File("path/to/ciphertext.txt.asc")
);

Decrypting with JPGPJ

Decrypting is just as easy. JPGPJ handles signed or unsigned, encrypted or unencrypted, compressed or uncompressed, ascii-armored or binary messages all the same way. Its default setting is to require messages to be signed by a known key; so for example, to decrypt a message signed by Alice's private key and encrypted with Bob's public key (requiring Alice's public key to verify and Bob's private key to decrypt), this is all the Java you need:

new Decryptor(
    new Key(new File("path/to/my/keys/alice-pub.gpg")),
    new Key(new File("path/to/my/keys/bob-sec.gpg"), "b0bru1z!")
).decrypt(
    new File("path/to/ciphertext.txt.gpg"),
    new File("path/back-to/plaintext.txt")
);

The above Java code does the same thing as the following gpg command (where Bob has a `bob` secret key and an `alice` public key on his keyring, and enters "b0bru1z!" when prompted for his passphrase):

gpg --decrypt --output path/back-to/plaintext.txt path/to/ciphertext.txt.gpg

If the message can't be verified by any known key (that is, any key with which the decryptor had been configured), JPGPJ will raise a VerificationException. If the message can't be decrypted by any known private key (that is, any private key with which the decryptor had been configured), JPGPJ will raise a DecryptionException.

To ignore signatures (in other words, decrypt a message successfully regardless of whether it was signed or not), simply turn off the decryptor's verificationRequired flag:

Decryptor decryptor = new Decryptor(
    new Key(new File("path/to/my/keys/bob-sec.gpg"), "b0bru1z!")
);
decryptor.setVerificationRequired(false);
decryptor.decrypt(
    new File("path/to/ciphertext.txt.gpg"),
    new File("path/back-to/plaintext.txt")
);

Keys in JPGPJ

The key data used by JPGPJ is simply what get when you export a key from GnuPG, like with the following gpg command for a public key:

gpg --export alice > path/to/my/keys/alice-pub.gpg

Or this gpg command to export a private key (which exports both the public and private parts of the key, encrypted with the same password that the key has on your GnuPG keyring):

gpg --export-secret-keys bob > path/to/my/keys/bob-sec.gpg

If you encode keys with ASCII Armor when you export them (via the GnuPG --armor flag), you can load them the same way in JPGPJ; and you can also embed ascii-armored keys as strings in your source code, if you find that more convenient than using external files (see the Key Rings wiki page for more details on loading and using keys in JPGPJ).