2022-09-21 18:24:42 +00:00
// Copyright (C) 2001-2003, 2011, 2022 VeriSign, Inc.
2009-08-23 19:13:42 +00:00
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
package com.verisignlabs.dnssec.cl ;
import java.io.File ;
import java.io.FileFilter ;
import java.io.IOException ;
2022-09-21 18:24:42 +00:00
import java.time.Instant ;
2009-08-23 19:13:42 +00:00
import java.util.ArrayList ;
2022-09-21 18:24:42 +00:00
import java.util.Collections ;
2009-08-23 19:13:42 +00:00
import java.util.List ;
import org.apache.commons.cli.CommandLine ;
2022-09-21 18:24:42 +00:00
import org.apache.commons.cli.Option ;
2009-08-23 19:13:42 +00:00
import org.apache.commons.cli.Options ;
import org.xbill.DNS.Name ;
2011-02-12 21:25:42 +00:00
import org.xbill.DNS.RRSIGRecord ;
2009-08-23 19:13:42 +00:00
import org.xbill.DNS.RRset ;
import org.xbill.DNS.Record ;
import org.xbill.DNS.Type ;
2022-09-21 18:24:42 +00:00
import com.verisignlabs.dnssec.security.BINDKeyUtils ;
import com.verisignlabs.dnssec.security.DnsKeyPair ;
import com.verisignlabs.dnssec.security.DnsSecVerifier ;
import com.verisignlabs.dnssec.security.JCEDnsSecSigner ;
import com.verisignlabs.dnssec.security.SignUtils ;
import com.verisignlabs.dnssec.security.ZoneUtils ;
2009-08-23 19:13:42 +00:00
/ * *
* This class forms the command line implementation of a DNSSEC keyset signer .
* Instead of being able to sign an entire zone , it will just sign a given
* DNSKEY RRset .
*
2011-02-12 21:25:26 +00:00
* @author David Blacka
2009-08-23 19:13:42 +00:00
* /
2022-09-21 18:24:42 +00:00
public class SignKeyset extends CLBase {
2011-02-12 21:25:26 +00:00
private CLIState state ;
2011-02-09 23:58:56 +00:00
/ * *
* This is an inner class used to hold all of the command line option state .
* /
2022-09-21 18:24:42 +00:00
protected static class CLIState extends CLIStateBase {
public File keyDirectory = null ;
public String [ ] keyFiles = null ;
public Instant start = null ;
public Instant expire = null ;
public String inputfile = null ;
public String outputfile = null ;
public boolean verifySigs = false ;
public CLIState ( ) {
2011-02-12 21:25:26 +00:00
super ( " jdnssec-signkeyset [..options..] dnskeyset_file [key_file ...] " ) ;
2011-02-09 23:58:56 +00:00
}
2009-08-23 19:13:42 +00:00
/ * *
2011-02-09 23:58:56 +00:00
* Set up the command line options .
2009-08-23 19:13:42 +00:00
* /
2022-09-21 18:24:42 +00:00
@Override
protected void setupOptions ( Options opts ) {
2011-02-09 23:58:56 +00:00
// boolean options
opts . addOption ( " a " , " verify " , false , " verify generated signatures> " ) ;
// Argument options
2022-09-21 18:24:42 +00:00
opts . addOption ( Option . builder ( " D " ) . hasArg ( ) . argName ( " dir " ) . longOpt ( " key-directory " ) . desc ( " directory where key files are found (default '.'). " ) . build ( ) ) ;
opts . addOption ( Option . builder ( " s " ) . hasArg ( ) . argName ( " time/offset " ) . longOpt ( " start-time " ) . desc ( " signature starting time (default is now - 1 hour) " ) . build ( ) ) ;
opts . addOption ( Option . builder ( " e " ) . hasArg ( ) . argName ( " time/offset " ) . longOpt ( " expire-time " ) . desc ( " signature expiration time (default is start-time + 30 days) " ) . build ( ) ) ;
opts . addOption ( Option . builder ( " f " ) . hasArg ( ) . argName ( " outfile " ) . desc ( " file the signed keyset is written to " ) . build ( ) ) ;
2011-02-09 23:58:56 +00:00
}
2009-08-23 19:13:42 +00:00
2022-09-21 18:24:42 +00:00
@Override
2011-02-12 21:25:42 +00:00
protected void processOptions ( CommandLine cli )
2022-09-21 18:24:42 +00:00
throws org . apache . commons . cli . ParseException {
2011-02-09 23:58:56 +00:00
String optstr = null ;
2022-09-21 18:24:42 +00:00
if ( cli . hasOption ( 'a' ) )
verifySigs = true ;
2011-02-09 23:58:56 +00:00
2022-09-21 18:24:42 +00:00
if ( ( optstr = cli . getOptionValue ( 'D' ) ) ! = null ) {
2011-02-09 23:58:56 +00:00
keyDirectory = new File ( optstr ) ;
2022-09-21 18:24:42 +00:00
if ( ! keyDirectory . isDirectory ( ) ) {
2011-02-09 23:58:56 +00:00
System . err . println ( " error: " + optstr + " is not a directory " ) ;
usage ( ) ;
2009-08-23 19:13:42 +00:00
}
2011-02-09 23:58:56 +00:00
}
2022-09-21 18:24:42 +00:00
if ( ( optstr = cli . getOptionValue ( 's' ) ) ! = null ) {
2011-02-09 23:58:56 +00:00
start = convertDuration ( null , optstr ) ;
2022-09-21 18:24:42 +00:00
} else {
2011-02-09 23:58:56 +00:00
// default is now - 1 hour.
2022-09-21 18:24:42 +00:00
start = Instant . now ( ) . minusSeconds ( 3600 ) ;
2011-02-09 23:58:56 +00:00
}
2022-09-21 18:24:42 +00:00
if ( ( optstr = cli . getOptionValue ( 'e' ) ) ! = null ) {
2011-02-09 23:58:56 +00:00
expire = convertDuration ( start , optstr ) ;
2022-09-21 18:24:42 +00:00
} else {
2011-02-09 23:58:56 +00:00
expire = convertDuration ( start , " +2592000 " ) ; // 30 days
}
outputfile = cli . getOptionValue ( 'f' ) ;
String [ ] files = cli . getArgs ( ) ;
2022-09-21 18:24:42 +00:00
if ( files . length < 1 ) {
2011-02-09 23:58:56 +00:00
System . err . println ( " error: missing zone file and/or key files " ) ;
usage ( ) ;
}
inputfile = files [ 0 ] ;
2022-09-21 18:24:42 +00:00
if ( files . length > 1 ) {
2011-02-09 23:58:56 +00:00
keyFiles = new String [ files . length - 1 ] ;
System . arraycopy ( files , 1 , keyFiles , 0 , files . length - 1 ) ;
}
}
}
/ * *
* Verify the generated signatures .
2011-02-10 00:25:10 +00:00
*
2011-02-09 23:58:56 +00:00
* @param records
2022-09-21 18:24:42 +00:00
* a list of { @link org . xbill . DNS . Record } s .
2011-02-09 23:58:56 +00:00
* @param keypairs
2022-09-21 18:24:42 +00:00
* a list of keypairs used the sign the zone .
2011-02-09 23:58:56 +00:00
* @return true if all of the signatures validated .
* /
2022-09-21 18:24:42 +00:00
private static boolean verifySigs ( List < Record > records ,
List < DnsKeyPair > keypairs ) {
2011-02-09 23:58:56 +00:00
boolean secure = true ;
DnsSecVerifier verifier = new DnsSecVerifier ( ) ;
2022-09-21 18:24:42 +00:00
for ( DnsKeyPair pair : keypairs ) {
2011-02-12 21:25:42 +00:00
verifier . addTrustedKey ( pair ) ;
2009-08-23 19:13:42 +00:00
}
2011-02-09 23:58:56 +00:00
verifier . setVerifyAllSigs ( true ) ;
2009-08-23 19:13:42 +00:00
2011-02-12 21:25:42 +00:00
List < RRset > rrsets = SignUtils . assembleIntoRRsets ( records ) ;
2009-08-23 19:13:42 +00:00
2022-09-21 18:24:42 +00:00
for ( RRset rrset : rrsets ) {
2011-02-09 23:58:56 +00:00
// skip unsigned rrsets.
2022-09-21 18:24:42 +00:00
if ( rrset . sigs ( ) . isEmpty ( ) )
continue ;
2009-08-23 19:13:42 +00:00
2012-05-26 19:40:15 +00:00
boolean result = verifier . verify ( rrset ) ;
2009-08-23 19:13:42 +00:00
2022-09-21 18:24:42 +00:00
if ( ! result ) {
staticLog . fine ( " Signatures did not verify for RRset: " + rrset ) ;
2011-02-09 23:58:56 +00:00
secure = false ;
}
2009-08-23 19:13:42 +00:00
}
2011-02-09 23:58:56 +00:00
return secure ;
}
/ * *
* Load the key pairs from the key files .
2011-02-10 00:25:10 +00:00
*
2011-02-09 23:58:56 +00:00
* @param keyfiles
2022-09-21 18:24:42 +00:00
* a string array containing the base names or paths of the
* keys
* to be loaded .
* @param startIndex
* the starting index of keyfiles string array to use . This
* allows us to use the straight command line argument array .
2011-02-09 23:58:56 +00:00
* @param inDirectory
2022-09-21 18:24:42 +00:00
* the directory to look in ( may be null ) .
2011-02-09 23:58:56 +00:00
* @return a list of keypair objects .
* /
2022-09-21 18:24:42 +00:00
private static List < DnsKeyPair > getKeys ( String [ ] keyfiles , int startIndex ,
File inDirectory ) throws IOException {
if ( keyfiles = = null )
return Collections . emptyList ( ) ;
2011-02-09 23:58:56 +00:00
2022-09-21 18:24:42 +00:00
int len = keyfiles . length - startIndex ;
if ( len < = 0 )
return Collections . emptyList ( ) ;
2011-02-09 23:58:56 +00:00
2022-09-21 18:24:42 +00:00
ArrayList < DnsKeyPair > keys = new ArrayList < > ( len ) ;
2011-02-09 23:58:56 +00:00
2022-09-21 18:24:42 +00:00
for ( int i = startIndex ; i < keyfiles . length ; i + + ) {
2011-02-09 23:58:56 +00:00
DnsKeyPair k = BINDKeyUtils . loadKeyPair ( keyfiles [ i ] , inDirectory ) ;
2022-09-21 18:24:42 +00:00
if ( k ! = null )
keys . add ( k ) ;
2009-08-23 19:13:42 +00:00
}
2011-02-09 23:58:56 +00:00
return keys ;
}
2009-08-23 19:13:42 +00:00
2022-09-21 18:24:42 +00:00
private static class KeyFileFilter implements FileFilter {
2011-02-09 23:58:56 +00:00
private String prefix ;
2009-08-23 19:13:42 +00:00
2022-09-21 18:24:42 +00:00
public KeyFileFilter ( Name origin ) {
2011-02-09 23:58:56 +00:00
prefix = " K " + origin . toString ( ) ;
2009-08-23 19:13:42 +00:00
}
2022-09-21 18:24:42 +00:00
public boolean accept ( File pathname ) {
if ( ! pathname . isFile ( ) )
return false ;
2011-02-09 23:58:56 +00:00
String name = pathname . getName ( ) ;
2022-09-21 18:24:42 +00:00
return ( name . startsWith ( prefix ) & & name . endsWith ( " .private " ) ) ;
2011-02-09 23:58:56 +00:00
}
}
2009-08-23 19:13:42 +00:00
2011-02-12 21:25:42 +00:00
private static List < DnsKeyPair > findZoneKeys ( File inDirectory , Name zonename )
2022-09-21 18:24:42 +00:00
throws IOException {
if ( inDirectory = = null ) {
2011-02-09 23:58:56 +00:00
inDirectory = new File ( " . " ) ;
}
2009-08-23 19:13:42 +00:00
2011-02-09 23:58:56 +00:00
// get the list of "K<zone>.*.private files.
FileFilter filter = new KeyFileFilter ( zonename ) ;
File [ ] files = inDirectory . listFiles ( filter ) ;
2009-08-23 19:13:42 +00:00
2011-02-09 23:58:56 +00:00
// read in all of the records
2022-09-21 18:24:42 +00:00
ArrayList < DnsKeyPair > keys = new ArrayList < > ( ) ;
for ( int i = 0 ; i < files . length ; i + + ) {
2011-02-09 23:58:56 +00:00
DnsKeyPair p = BINDKeyUtils . loadKeyPair ( files [ i ] . getName ( ) , inDirectory ) ;
keys . add ( p ) ;
2009-08-23 19:13:42 +00:00
}
2022-09-21 18:24:42 +00:00
return keys ;
2011-02-09 23:58:56 +00:00
}
2022-09-21 18:24:42 +00:00
public void execute ( ) throws Exception {
2011-02-09 23:58:56 +00:00
// Read in the zone
2011-02-12 21:25:42 +00:00
List < Record > records = ZoneUtils . readZoneFile ( state . inputfile , null ) ;
2022-09-21 18:24:42 +00:00
if ( records = = null | | records . isEmpty ( ) ) {
2011-02-09 23:58:56 +00:00
System . err . println ( " error: empty keyset file " ) ;
state . usage ( ) ;
2009-08-23 19:13:42 +00:00
}
2011-02-09 23:58:56 +00:00
// Make sure that all records are DNSKEYs with the same name.
2022-09-21 18:24:42 +00:00
Name keysetName = null ;
RRset keyset = new RRset ( ) ;
2011-02-12 21:25:42 +00:00
2022-09-21 18:24:42 +00:00
for ( Record r : records ) {
if ( r . getType ( ) ! = Type . DNSKEY ) {
2011-02-09 23:58:56 +00:00
System . err . println ( " error: Non DNSKEY RR found in keyset: " + r ) ;
continue ;
}
2022-09-21 18:24:42 +00:00
if ( keysetName = = null ) {
2011-02-09 23:58:56 +00:00
keysetName = r . getName ( ) ;
}
2022-09-21 18:24:42 +00:00
if ( ! r . getName ( ) . equals ( keysetName ) ) {
2011-02-09 23:58:56 +00:00
System . err . println ( " error: DNSKEY with a different name found! " ) ;
state . usage ( ) ;
}
keyset . addRR ( r ) ;
}
2009-08-23 19:13:42 +00:00
2022-09-21 18:24:42 +00:00
if ( keyset . size ( ) = = 0 ) {
2011-02-09 23:58:56 +00:00
System . err . println ( " error: No DNSKEYs found in keyset file " ) ;
state . usage ( ) ;
}
2009-08-23 19:13:42 +00:00
2011-02-09 23:58:56 +00:00
// Load the key pairs.
2011-02-12 21:25:42 +00:00
List < DnsKeyPair > keypairs = getKeys ( state . keyFiles , 0 , state . keyDirectory ) ;
2009-08-23 19:13:42 +00:00
2011-02-09 23:58:56 +00:00
// If we *still* don't have any key pairs, look for keys the key
// directory
// that match
2022-09-21 18:24:42 +00:00
if ( keypairs = = null ) {
2011-02-09 23:58:56 +00:00
keypairs = findZoneKeys ( state . keyDirectory , keysetName ) ;
}
2009-08-23 19:13:42 +00:00
2011-02-09 23:58:56 +00:00
// If there *still* aren't any ZSKs defined, bail.
2022-09-21 18:24:42 +00:00
if ( keypairs = = null | | keypairs . isEmpty ( ) | | keysetName = = null ) {
2011-02-09 23:58:56 +00:00
System . err . println ( " error: No signing keys could be determined. " ) ;
state . usage ( ) ;
2022-09-21 18:24:42 +00:00
return ;
2011-02-09 23:58:56 +00:00
}
2009-08-23 19:13:42 +00:00
2011-02-09 23:58:56 +00:00
// default the output file, if not set.
2022-09-21 18:24:42 +00:00
if ( state . outputfile = = null ) {
if ( keysetName . isAbsolute ( ) ) {
2011-02-09 23:58:56 +00:00
state . outputfile = keysetName + " signed_keyset " ;
2022-09-21 18:24:42 +00:00
} else {
2011-02-09 23:58:56 +00:00
state . outputfile = keysetName + " .signed_keyset " ;
}
}
2009-08-23 19:13:42 +00:00
2011-02-09 23:58:56 +00:00
JCEDnsSecSigner signer = new JCEDnsSecSigner ( ) ;
2009-08-23 19:13:42 +00:00
2011-02-12 21:25:42 +00:00
List < RRSIGRecord > sigs = signer . signRRset ( keyset , keypairs , state . start , state . expire ) ;
2022-09-21 18:24:42 +00:00
for ( RRSIGRecord s : sigs ) {
2011-02-12 21:25:42 +00:00
keyset . addRR ( s ) ;
2011-02-09 23:58:56 +00:00
}
2009-08-23 19:13:42 +00:00
2011-02-09 23:58:56 +00:00
// write out the signed RRset
2022-09-21 18:24:42 +00:00
List < Record > signedRecords = new ArrayList < > ( ) ;
for ( Record r : keyset . rrs ( ) ) {
signedRecords . add ( r ) ;
2011-02-09 23:58:56 +00:00
}
2022-09-21 18:24:42 +00:00
for ( RRSIGRecord s : keyset . sigs ( ) ) {
signedRecords . add ( s ) ;
2011-02-09 23:58:56 +00:00
}
2009-08-23 19:13:42 +00:00
2011-02-09 23:58:56 +00:00
// write out the signed zone
2022-09-21 18:24:42 +00:00
ZoneUtils . writeZoneFile ( signedRecords , state . outputfile ) ;
2011-02-09 23:58:56 +00:00
2022-09-21 18:24:42 +00:00
if ( state . verifySigs ) {
2011-02-09 23:58:56 +00:00
log . fine ( " verifying generated signatures " ) ;
2022-09-21 18:24:42 +00:00
boolean res = verifySigs ( signedRecords , keypairs ) ;
2011-02-09 23:58:56 +00:00
2022-09-21 18:24:42 +00:00
if ( res ) {
2011-02-09 23:58:56 +00:00
System . out . println ( " Generated signatures verified " ) ;
2022-09-21 18:24:42 +00:00
} else {
2011-02-09 23:58:56 +00:00
System . out . println ( " Generated signatures did not verify. " ) ;
}
}
2009-08-23 19:13:42 +00:00
2011-02-09 23:58:56 +00:00
}
2009-08-23 19:13:42 +00:00
2022-09-21 18:24:42 +00:00
public static void main ( String [ ] args ) {
2011-02-12 21:25:26 +00:00
SignKeyset tool = new SignKeyset ( ) ;
tool . state = new CLIState ( ) ;
2011-02-12 21:25:42 +00:00
2011-02-12 21:25:26 +00:00
tool . run ( tool . state , args ) ;
2011-02-09 23:58:56 +00:00
}
2009-08-23 19:13:42 +00:00
}