Some time ago I used to use Lastpass (because I do believe in randomly generated passwords and password managers), but ended up switching to PWman when I went all in on the command line / terminal; There was no command line tool for Lastpass - at the time, anyway. Considering how archaic PWman is it’s actually faired me really well - GPG encryption is still good, security through obscurity is actually a thing and it worked “good enough” when mobile via prompt. Fast forward to now and my life is more financially sane (I think; for now) and for the past three years I’ve been using 1Password for work and it’s really good.

So, getting fed up with iMessaging, etc the family Netflix password around and in general my family using insecure passwords (and still forgetting them; iCloud keychain sync is really good… until you forget that one) I thought it would be a good idea for us to try 1Password Families.

There are command line tools - for three(!!!) different NetBSD “ports” (platforms). There is the 1PasswordX browser plugin that works on my NetBSD desktop. And of course a great mobile app with iOS integration.

For migrating my family’s OSX Keychain / Safari passwords the MrC Converter Suite tools were great.

I had all my passwords in PWman though. I decided it would be a good excuse to re-visit Lisp and hopefully re-use some of what I’d written before. Probably I should have just used some tool to go from XML to CSV and had it imported sooner, but never mind. Once I actually found time I cracked out most of it in a day (after I’d remembered how the hell Lisp works). I’ve changed the name of existing lastpass2pwman repository and added the new script. Now available here: pwman-tools

  • I had parsing errors using xmls so used Cxml instead. The docs really suck though. Fortunately I stumbled across this which was just enough to allow me to figure out things.
  • I could not figure out decrypting the PWman file in the script, well, not in a nice way because SBCL doesn’t have a read-passwd. This thread looked promising, but I couldn’t find or figure out anything that worked from that so it’s easier to just decrypt the file up front.
  • Rather than convert PWman’s XML to CSV this does a direct conversion and import to 1Password via the op command line tool; which is pretty neat.
  • Most things map nicely to 1Password login items, but I had a SECURE-NOTES category in PWman from the time I switched from Lastpass so I decided to keep that mapping to 1Password as well, merging all the PWman fields into the notesPlain field in 1Password
  • That’s the only bit were my script is still lacking - it doesn’t properly escape quotes and apostrophes and so I had some errors as a result of trying to create secure notes; However, only on about five out of almost three hundred items so not a big deal; I may or may not fix this at some point.

None of the Lisp I’ve written is particularly good or clever. I decided to use defparameters within loops, etc as opposed to lets for (my) readability; I gather this isn’t a very lispy approach, but it works:

(dom:do-node-list (category *categories*)
	(progn
			(defparameter category-name (dom:get-attribute category "name"))
			(defparameter pwlists (dom:child-nodes category))))
[...]

Probably I’ve used defun where it should be a macro?

(defun template-secure-note
	(content)
		(concatenate 'string "{\"notesPlain\":\" content "\",\"sections\":[]}"))

Who knows? Not me. It does what I want it to do though.

To encode stuff I had to do it via sh -c:

(defparameter extproc (sb-ext:run-program "sh" (list "-c" (concatenate 'string "echo '" template "' | op encode")) :search :environment :output :stream))

as pipes (|) don’t count as arguments; neither do other commands. I.e. you can’t do: (sb-ext:run-program "echo" (list template "|" "op" "encode").... It would probably be best to base64 encode directly within Lisp, but I didn’t because I noticed subtle differences compared with op encode that I didn’t have time/willpower to investigate.

Fortunately I only created items as “Login” items or “Secure Notes” which meant I could get away with an if statement like this:

(defparameter template
	(if (string= op-category "Login")
		(template-login username passwd launch)
		(template-secure-note (concatenate 'string "host: " host "; user: " username "; password: " passwd "; launch: " launch))))

I couldn’t really figure out how to do what I wanted with case and realised I couldn’t use multiple ifs in progns:

(defparameter template
		(if (string= op-category "Login")
			(template-login username passwd launch)
			(progn
				(if (string= op-category "Secure Note")
					(template-secure-note (concatenate 'string "host: " host "; user: " username "; password: " passwd "; launch: " launch))
					(... something else...)))))

because otherwise template picks up a NIL from progn.

Overall the Lisp is actually pretty succinct, just my comments that make it look worse than it is.

When it comes to using 1Password on the command line this is enough to get started:

op list items | jq -r '.[] | [.overview.title, .uuid] | @tsv' | grep Github

To search for items matching “Github” and return UUIDs and then:

op get item [UUID] | jq -r '.details.fields[] | select(.designation=="password").value'

to get the password field for a UUID. Will probably write some little shell aliases/functions for those.

The only uncertainity I have here (asides from whether my family will actually take advantage of this; Youngest seems keen, don’t know about others) is for command line usage: The whole “Own your own data” thing… what happens if 1Password is down?


[EDIT: 2019-07-01] (yes, that’s the same date as the publish date) - Forgot to mention the op and jq stuff.