#!/usr/bin/perl

# Written by Simon Josefsson <simon@josefsson.org>.
# Copyright (c) 2009-2013 Yubico AB
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#
#   * Redistributions in binary form must reproduce the above
#     copyright notice, this list of conditions and the following
#     disclaimer in the documentation and/or other materials provided
#     with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use strict;
use POSIX qw(strftime);
use MIME::Base64;

my $device = "/dev/random";

sub usage {
    print "Usage: ykksm-gen-keys [--verbose] [--help] [--urandom] [--progflags PROGFLAGS] [--pskc] START [END]\n";
    print "\n";
    print "Tool to generate keys on the YKKSM-KEYPROV format.\n";
    print "\n";
    print "START: Decimal start point.\n";
    print "\n";
    print "END:   Decimal end point, if absent START is used as END.\n";
    print "\n";
    print "  --urandom:   Use /dev/urandom instead of /dev/random as entropy source.\n";
    print "\n";
    print "  --progflags PROGFLAGS: Add a final personalization configuration string.\n";
    print "\n";
    print "  --pskc: Output keys on the YubiKey PSKC format.\n";
    print "\n";
    print "Usage example:\n";
    print "\n";
    print "  ./ykksm-gen-keys --urandom 1 10 |\n";
    print "     gpg -a --sign --encrypt -r 1D2F473E > keys.txt\n";
    print "\n";
    exit 1;
}

sub hex2modhex {
    $_ = shift;
    tr/0123456789abcdef/cbdefghijklnrtuv/;
    return $_;
}

sub getrand {
    my $cnt = shift;
    my $buf;

    open (FH, $device) or die "Cannot open $device for reading";
    read (FH, $buf, $cnt) or die "Cannot read from $device";
    close FH;

    return $buf;
}

sub gethexrand {
    my $cnt = shift;
    my $buf = getrand ($cnt);
    return lc(unpack("H*", $buf));
}

sub getb64rand {
    my $cnt = shift;
    my $buf = getrand ($cnt);
    return encode_base64($buf, '');
}

# main

if ($#ARGV==-1) {
    usage();
}

my $verbose = 0;
my $pskc = 0;
my $progflags;
my $start = "";
my $end = "";
while (defined($ARGV[0])) {
    my $cmd = shift @ARGV;
    if (($cmd eq "-v") || ($cmd eq "--verbose")) {
	$verbose = 1;
    } elsif (($cmd eq "-h") || ($cmd eq "--help")) {
	usage();
    } elsif ($cmd eq "--urandom") {
	$device = "/dev/urandom";
    } elsif ($cmd eq "--progflags") {
	$progflags = "," . shift;
    } elsif ($cmd eq "--pskc") {
	$pskc = 1;
    } elsif ($cmd =~ m/^[0-9]+/) {
	if ($start eq "") {
	    $start = $cmd;
	} elsif ($end eq "") {
	    $end = $cmd;
	} else {
	    die "Invalid extra argument: $cmd";
	}
    }
}
die "Missing START parameter, try --help" if ($start eq "");
$end = $start if (!$end);

my $now = strftime "%Y-%m-%dT%H:%M:%S", localtime;
if ($pskc) {
    $now .= 'Z';
}
my $ctr;

if ($pskc) {
    print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
    print "<KeyContainer Version=\"1.0\"\n";
    if ($start == $end) {
	print "              Id=\"yk-$start-pskc\"\n";
    } else {
	print "              Id=\"yk-$start-to-$end-pskc\"\n";
    }
    print "              xmlns=\"urn:ietf:params:xml:ns:keyprov:pskc\">\n";
} else {
    print "# ykksm 1\n";
    print "# start $start end $end device $device\n" if ($verbose);
    print "# serialnr,identity,internaluid,aeskey,lockpw,created,accessed[,progflags]\n";
}

$ctr = $start;
while ($ctr <= $end) {
    my $hexctr = sprintf "%012x", $ctr;
    my $modhexctr = hex2modhex($hexctr);
    my $internaluid = gethexrand(6);
    my $aeskey = $pskc ? getb64rand(16) : gethexrand(16);
    my $lockpw = gethexrand(6);

    if ($pskc) {
	print "     <KeyPackage>\n";
	print "       <DeviceInfo>\n";
	print "         <Manufacturer>oath.UB</Manufacturer>\n";
	print "         <SerialNo>$ctr</SerialNo>\n";
	print "         <StartDate>$now</StartDate>\n";
	print "       </DeviceInfo>\n";
	print "       <CryptoModuleInfo>\n";
	print "         <Id>1</Id>\n";
	print "       </CryptoModuleInfo>\n";
	print "       <Key Id=\"yk-key-$ctr-slot-1\"\n";
	print "            Algorithm=\"http://www.yubico.com/#yubikey-aes\">\n";
	print "         <Issuer>Yubico</Issuer>\n";
	print "         <AlgorithmParameters>\n";
	print "           <ResponseFormat Encoding=\"ALPHANUMERIC\" Length=\"44\"/>\n";
	print "         </AlgorithmParameters>\n";
	print "         <Data>\n";
	print "           <Secret>\n";
	print "             <PlainValue>\n";
	print "               $aeskey\n";
	print "             </PlainValue>\n";
	print "           </Secret>\n";
	print "         </Data>\n";
	print "         <UserId>CN=$modhexctr, UID=$internaluid</UserId>\n";
	print "       </Key>\n";
	print "     </KeyPackage>\n";
    } else {
	print "# hexctr $hexctr modhexctr $modhexctr\n" if ($verbose);
	printf "$ctr,$modhexctr,$internaluid,$aeskey,$lockpw,$now,$progflags\n";
    }

    $ctr++;
}

if ($pskc) {
    print "</KeyContainer>\n";
} else {
    print "# the end\n";
}

exit 0;
