Compare commits
55 Commits
v0.10.1
...
davidb/ref
| Author | SHA1 | Date | |
|---|---|---|---|
| 4478d7e3af | |||
| 1406cd2e68 | |||
| 1727d7c7d8 | |||
| 2876649a4e | |||
| 6118ae718e | |||
| 5fef1dcf24 | |||
| e73b5ddd53 | |||
| 3601676406 | |||
| 69a0a34239 | |||
| e322186112 | |||
| ce1189703f | |||
| bfb5ff45ee | |||
|
|
9fd6c95889 | ||
|
|
9004a33d51 | ||
|
|
0aca329f85 | ||
|
|
507dad3580 | ||
|
|
7d27694d9a | ||
|
|
62b5b0ad23 | ||
|
|
c37f436e72 | ||
|
|
781e775b3b | ||
|
|
d0e85431c5 | ||
|
|
55a139db82 | ||
|
|
b291bb430b | ||
|
|
a9353b3af3 | ||
|
|
7706b73d8c | ||
| 252c44a155 | |||
|
|
a7743fa18c | ||
| 4853426d6c | |||
| de2216f259 | |||
|
|
b19bc5ffa3 | ||
|
|
517975ef93 | ||
|
|
ca2a932485 | ||
|
|
171594a92d | ||
|
|
fb689c046f | ||
|
|
8d3746fc22 | ||
|
|
444601fb2a | ||
|
|
c5896495c7 | ||
|
|
c13d9379b3 | ||
|
|
f170bd170a | ||
|
|
6bbcf38fe1 | ||
|
|
15cb5e2ab7 | ||
|
|
9fad4941a6 | ||
|
|
18df8a8d9e | ||
|
|
a45f5d1df7 | ||
|
|
3da308c4b9 | ||
|
|
efa6dec7f7 | ||
| d3e8c4c913 | |||
| b5775a8fdf | |||
| 69d965cc0f | |||
| ca7f10bd07 | |||
| 25cc81d46a | |||
| 2a90a6ccd9 | |||
| b18a96cbfc | |||
| cc0873a336 | |||
| 4d1acb8918 |
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
build
|
||||||
|
bin/main
|
||||||
|
dist/
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.gradle
|
||||||
|
jdnssec-tools*.tar.gz
|
||||||
|
docs
|
||||||
|
test-zones
|
||||||
|
settings.json
|
||||||
|
.settings
|
||||||
513
ChangeLog
513
ChangeLog
@@ -1,359 +1,494 @@
|
|||||||
|
2024-04-13 David Blacka <david@blacka.com>
|
||||||
|
|
||||||
|
* Remove support for ECC_GOST
|
||||||
|
* Create a new DSAlgorithm class, move DS creation into that
|
||||||
|
* Add support for DS algorithms 3 and 4 -- bouncycastle crypto
|
||||||
|
provider used for DS algoirthm 3 (GOST R 34.11-94)
|
||||||
|
* Moved support for loading the bouncycastle provider to the new
|
||||||
|
DSAlgorithm class
|
||||||
|
|
||||||
|
2024-04-07 David Blacka <david@blacka.com>
|
||||||
|
|
||||||
|
* Released version 0.20
|
||||||
|
* Removed support for Gradle builds since the gradle config was
|
||||||
|
out-of-date
|
||||||
|
* Requires Java 15 or later for EdDSA support
|
||||||
|
* Supports DNSSEC algorithm 16 using the SunEC provider
|
||||||
|
* Supports a java properties-formatted config file. See
|
||||||
|
jdnssec-tools.properties.example for an example
|
||||||
|
* Updated to dnsjava 3.5.3
|
||||||
|
* Updated to commons-cli 1.6.0
|
||||||
|
* Added a "one-jar" distribution method, and a "univeral" CLI to
|
||||||
|
use with it.
|
||||||
|
* Formatting and linter suggestions
|
||||||
|
|
||||||
|
2024-03-25 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
|
* Released version 0.19
|
||||||
|
* Handle duplicate key tags
|
||||||
|
* jdnssec-keygen can now attempt to generate keys with specified key tags
|
||||||
|
|
||||||
|
2023-07-24 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
|
* Released version 0.17.1
|
||||||
|
* Add a `-t` option to jdnssec-verifyzone: verify using specified time.
|
||||||
|
|
||||||
|
2022-09-21 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
|
* Released version 0.17
|
||||||
|
* Updated to dnsjava 3.5.1
|
||||||
|
* Formatting and linter suggestions
|
||||||
|
* Use slf4j instead of log4j.
|
||||||
|
* jdnssec-dstool can now generate CDS records
|
||||||
|
|
||||||
|
2019-07-23 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
|
* Released version 0.16
|
||||||
|
* Updated to dnsjava 2.1.9
|
||||||
|
|
||||||
|
2019-02-26 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
|
* Released version 0.15
|
||||||
|
* Ensure when a command line tool throws an exception it exits
|
||||||
|
with a non-zero exit code.
|
||||||
|
* Update local dnsjava jar to match actual build for
|
||||||
|
jdnssec-dnsjava.
|
||||||
|
|
||||||
|
2018-11-16 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
|
* Released version 0.14
|
||||||
|
|
||||||
|
2018-07-15 Pallavi Aras
|
||||||
|
|
||||||
|
* Add Gradle build support. Adjust ant to use same paths.
|
||||||
|
|
||||||
|
2018-07-15 David Blacka <davidb@versigin.com
|
||||||
|
|
||||||
|
* Add algorithm 15 support. This included adding a public domain
|
||||||
|
EdDSA library to the distribution.
|
||||||
|
* Add minor feature to specify signature inception and expiration
|
||||||
|
times as UNIX epoch time values.
|
||||||
|
|
||||||
|
2017-06-22 Peter van Dijk <peter.van.dijk@powerdns.com>, Kees Monshouwer <mind04@monshouwer.eu>
|
||||||
|
|
||||||
|
* Fix leading zero(s) padding in ECDSA sig conversion
|
||||||
|
|
||||||
|
2017-01-06 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
|
* Released version 0.13
|
||||||
|
|
||||||
|
* ZoneVerifier: detect duplicate RRSIGs as well as other duplicate
|
||||||
|
RRs.
|
||||||
|
* DnsSecVerifier: check that the RRset's TTL <= OrigTTL.
|
||||||
|
|
||||||
|
2016-12-09 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
|
* Add key generation, signing, verification support for elliptic
|
||||||
|
curve algorithms: ECDSA P-256 (13) and ECDSA P-384 (15).
|
||||||
|
- Opportunistically load the bouncycastle provider for ECCGOST
|
||||||
|
support.
|
||||||
|
* DnsKeyAlgorithms: refactoring, new methods to better support
|
||||||
|
elliptic curve, alias, knowing what algorithms are supported.
|
||||||
|
* KeyGen: do not display unsupported algorithms.
|
||||||
|
|
||||||
|
2016-08-22 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
|
* Update internal dnsjava to 2.1.7-vrsn-1.
|
||||||
|
|
||||||
|
2014-04-22 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
|
* ZoneFormat: Make -N also compute original ownernames for empty
|
||||||
|
non-terminal NSEC3 records.
|
||||||
|
|
||||||
|
* ZoneVerifier: Improve the zone verifiers handling of "junk" in a
|
||||||
|
zone (i.e., ignore resource records that aren't actually in the
|
||||||
|
zone itself.)
|
||||||
|
|
||||||
|
2012-07-16 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
|
* Released version 0.12.
|
||||||
|
|
||||||
|
* TypeMap: fix the fromBytes() method, which was incorrect and add
|
||||||
|
a static fromString() method.
|
||||||
|
* ProtoNSEC3: use TypeMap's toString method, rather than fetching
|
||||||
|
the array of types and rendering them directly.
|
||||||
|
|
||||||
|
2012-05-29 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
|
* Released version 0.11.
|
||||||
|
|
||||||
|
2012-05-26 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
|
* Update dnsjava to dnsjava-2.1.3-vrsn-1. Update the code to
|
||||||
|
adjust for API changes in dnsjava-2.1.x. Highlights:
|
||||||
|
- no longer use DNSSEC.Failed, DNSSEC.Secure as those constants
|
||||||
|
are now gone. Instead, any methods returning those constants now
|
||||||
|
return a boolean, true for DNSSEC.Secure, false for DNSSEC.Failed
|
||||||
|
or DNSSEC.Insecure.
|
||||||
|
- No longer use KEYConverter. Instead, uses the new DNSKEYRecord
|
||||||
|
constructor.
|
||||||
|
- The NSEC3 digest type is now an int (rather than a byte)
|
||||||
|
- Algorithm references are now DNSSEC.Algorithm.<alg>
|
||||||
|
|
||||||
|
* jdnssec-verifyzone: Add duplicate RR detection (on by default)
|
||||||
|
and a command line option to disable it.
|
||||||
|
|
||||||
2011-02-14 David Blacka <davidb@verisign.com>
|
2011-02-14 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* Released version 0.10.1.
|
* Released version 0.10.1.
|
||||||
|
|
||||||
2011-02-12 David Blacka <davidb@verisign.com>
|
2011-02-12 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* Use Java 1.5 generic types when possible. DNSJava itself still
|
* Use Java 1.5 generic types when possible. DNSJava itself still
|
||||||
doesn't use them, so we have to suppress warnings when we use
|
doesn't use them, so we have to suppress warnings when we use
|
||||||
RRset.rrs(), etc.
|
RRset.rrs(), etc.
|
||||||
* Update commons-cli to version 1.2.
|
* Update commons-cli to version 1.2.
|
||||||
* Refactor all of the command line classes. A new command line
|
* Refactor all of the command line classes. A new command line
|
||||||
base class has been created to eliminate much of the duplicated
|
base class has been created to eliminate much of the duplicated
|
||||||
code.
|
code.
|
||||||
|
|
||||||
2011-02-09 David Blacka <davidb@verisign.com>
|
2011-02-09 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* Enable reading and writing from stdin/stdout for most tools. To
|
* Enable reading and writing from stdin/stdout for most tools. To
|
||||||
do this, use '-' as the zonefile name.
|
do this, use '-' as the zonefile name.
|
||||||
* jdnssec-signzone, jdnssec-signrrset: remove 'multiline' output
|
* jdnssec-signzone, jdnssec-signrrset: remove 'multiline' output
|
||||||
as the default and add a command line switch (-m) to enable it.
|
as the default and add a command line switch (-m) to enable it.
|
||||||
That is, these tools will output each RR on a single line by
|
That is, these tools will output each RR on a single line by
|
||||||
default, adding -m will restore the prior behavior.
|
default, adding -m will restore the prior behavior.
|
||||||
|
|
||||||
2011-02-08 David Blacka <davidb@verisign.com>
|
2011-02-08 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* Minor cleanups to usage statement printing across most of the tools.
|
* Minor cleanups to usage statement printing across most of the tools.
|
||||||
|
|
||||||
2011-02-03 David Blacka <davidb@verisign.com>
|
2011-02-03 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* Released version 0.10
|
* Released version 0.10
|
||||||
* jdnssec-keygen: update the default algorithm to 8 (instead of 5).
|
* jdnssec-keygen: update the default algorithm to 8 (instead of 5).
|
||||||
* Update logging across all command line tools to use a consistent
|
* Update logging across all command line tools to use a consistent
|
||||||
'-v' option, and consistent, simpler log formatting.
|
'-v' option, and consistent, simpler log formatting.
|
||||||
* jdnssec-verifyzone: resume logging the key information at INFO,
|
* jdnssec-verifyzone: resume logging the key information at INFO,
|
||||||
but make the default log level WARNING. To see the old logging
|
but make the default log level WARNING. To see the old logging
|
||||||
behavior, use -v 4.
|
behavior, use -v 4.
|
||||||
|
|
||||||
2011-02-02 David Blacka <davidb@verisign.com>
|
2011-02-02 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* DnsKeyConverter: support the new BIND 9.7 private key format,
|
* DnsKeyConverter: support the new BIND 9.7 private key format,
|
||||||
which only entails recognizing the new version string, since the
|
which only entails recognizing the new version string, since the
|
||||||
new format is a superset of the old format.
|
new format is a superset of the old format.
|
||||||
|
|
||||||
2011-01-11 David Blacka <davidb@verisign.com>
|
2011-01-11 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* jdnssec-zoneformat: add a -m option for formatting as
|
* jdnssec-zoneformat: add a -m option for formatting as
|
||||||
multiline. Add a -N option for determining the original
|
multiline. Add a -N option for determining the original
|
||||||
ownernames for NSEC3 signed zones.
|
ownernames for NSEC3 signed zones.
|
||||||
|
|
||||||
2010-12-14 David Blacka <davidb@verisign.com>
|
2010-12-14 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* jdnssec-verifyzone: Add options to either fudge or ignore RRSIG
|
* jdnssec-verifyzone: Add options to either fudge or ignore RRSIG
|
||||||
inception and expiration times.
|
inception and expiration times.
|
||||||
|
|
||||||
2010-12-06 David Blacka <davidb@verisign.com>
|
2010-12-06 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* jdnssec-verifyzone: Complete refactored the verification code to
|
* jdnssec-verifyzone: Complete refactored the verification code to
|
||||||
more comprehensively check a zone for DNSSEC validity. Instead of
|
more comprehensively check a zone for DNSSEC validity. Instead of
|
||||||
just verifying signatures, it will also check to see if the NSEC
|
just verifying signatures, it will also check to see if the NSEC
|
||||||
or NSEC3 chains are valid.
|
or NSEC3 chains are valid.
|
||||||
|
|
||||||
2010-12-05 David Blacka <davidb@verisign.com>
|
2010-12-05 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* jdnssec-signzone: Fix a bug that would incorrectly handle
|
* jdnssec-signzone: Fix a bug that would incorrectly handle
|
||||||
delgations below delegations (those should be ignored.)
|
delgations below delegations (those should be ignored.)
|
||||||
|
|
||||||
* jdnssec-signzone: Make the signer ignore junk below a DNAME.
|
* jdnssec-signzone: Make the signer ignore junk below a DNAME.
|
||||||
This differs from BIND's dnssec-signzone behavior (currently), but
|
This differs from BIND's dnssec-signzone behavior (currently), but
|
||||||
is the correct behavior, as stuff below a DNAME doesn't actually
|
is the correct behavior, as stuff below a DNAME doesn't actually
|
||||||
exist in DNS. Note that if a name in a zone has both a DNAME and
|
exist in DNS. Note that if a name in a zone has both a DNAME and
|
||||||
a NS RRset (and is not at the apex), then the behavior is a bit
|
a NS RRset (and is not at the apex), then the behavior is a bit
|
||||||
undefined.
|
undefined.
|
||||||
|
|
||||||
* jdnssec-signzone: Fix a bug that would incorrectly set the RRSIG
|
* jdnssec-signzone: Fix a bug that would incorrectly set the RRSIG
|
||||||
bit for NSEC3 RRs corresponding to insecure delegations.
|
bit for NSEC3 RRs corresponding to insecure delegations.
|
||||||
|
|
||||||
* jdnssec-signzone: add a "verbose signing" option. This will
|
* jdnssec-signzone: add a "verbose signing" option. This will
|
||||||
cause the pre-signed bytes and the raw signature bytes to be
|
cause the pre-signed bytes and the raw signature bytes to be
|
||||||
output when signing.
|
output when signing.
|
||||||
* Other fixes: some minor tweaks and comment fixes.
|
* Other fixes: some minor tweaks and comment fixes.
|
||||||
Unfortunately, also a lot of rewrapping and whitespace changes due
|
Unfortunately, also a lot of rewrapping and whitespace changes due
|
||||||
to Eclipse. Sigh.
|
to Eclipse. Sigh.
|
||||||
|
|
||||||
2010-01-14 David Blacka <davidb@verisign.com>
|
2010-01-14 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* Released version 0.9.6
|
* Released version 0.9.6
|
||||||
|
|
||||||
2010-01-09 David Blacka <davidb@verisign.com>
|
2010-01-09 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* Upgrade to DNSJava 2.0.8 (plus a few local changes). 2.0.8
|
* Upgrade to DNSJava 2.0.8 (plus a few local changes). 2.0.8
|
||||||
fixes a major bug in typemap wire conversion.
|
fixes a major bug in typemap wire conversion.
|
||||||
|
|
||||||
2009-11-02 David Blacka <davidb@verisign.com>
|
2009-11-02 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* Released version 0.9.5
|
* Released version 0.9.5
|
||||||
|
|
||||||
2009-11-01 David Blacka <davidb@verisign.com>
|
2009-11-01 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* Upgrade to DNSJava 2.0.7 (plus a few local changes).
|
* Upgrade to DNSJava 2.0.7 (plus a few local changes).
|
||||||
* DnsKeyAlogorithm: change the RSASHA512 number to 10.
|
* DnsKeyAlogorithm: change the RSASHA512 number to 10.
|
||||||
|
|
||||||
2009-08-23 David Blacka <davidb@verisign.com>
|
2009-08-23 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* Released version 0.9.4
|
* Released version 0.9.4
|
||||||
|
|
||||||
2009-07-15 David Blacka <davidb@verisign.com>
|
2009-07-15 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* SignUtils: Fix major issue where the code that generates that
|
* SignUtils: Fix major issue where the code that generates that
|
||||||
canonical RRset given signature data wasn't obeying the "Orig TTL"
|
canonical RRset given signature data wasn't obeying the "Orig TTL"
|
||||||
and "Labels" fields. This is a major issue with verification,
|
and "Labels" fields. This is a major issue with verification,
|
||||||
although it doesn't affect signature generation.
|
although it doesn't affect signature generation.
|
||||||
|
|
||||||
* VerifyZone: Fix bug where the whole-zone security status was
|
* VerifyZone: Fix bug where the whole-zone security status was
|
||||||
still wrong: unsigned RRsets shouldn't make the zone Bogus.
|
still wrong: unsigned RRsets shouldn't make the zone Bogus.
|
||||||
|
|
||||||
2009-06-12 David Blacka <davidb@verisign.com>
|
2009-06-12 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* VerifyZone: Fix bug in verification logic so that RRsets that
|
* VerifyZone: Fix bug in verification logic so that RRsets that
|
||||||
never find a valid signature (i.e., only have signatures by keys
|
never find a valid signature (i.e., only have signatures by keys
|
||||||
that aren't in the zone) are considered Bogus. Note that
|
that aren't in the zone) are considered Bogus. Note that
|
||||||
VerifyZone still can't tell if a RRset that should be signed
|
VerifyZone still can't tell if a RRset that should be signed
|
||||||
wasn't (or vice versa).
|
wasn't (or vice versa).
|
||||||
|
|
||||||
* dnsjava: Update local copy of dnsjava library. This version
|
* dnsjava: Update local copy of dnsjava library. This version
|
||||||
adds NSEC3 agorithms to DNSSECVerifier and KEYConverter, emulates
|
adds NSEC3 agorithms to DNSSECVerifier and KEYConverter, emulates
|
||||||
DiG's "OPT PSEUDOSECTION" formatting in Message.toString(), and
|
DiG's "OPT PSEUDOSECTION" formatting in Message.toString(), and
|
||||||
adds a minimal DHCIDRecord type. Note that the DNSjava trunk has
|
adds a minimal DHCIDRecord type. Note that the DNSjava trunk has
|
||||||
a different (although functional similar) version of this type.
|
a different (although functional similar) version of this type.
|
||||||
|
|
||||||
2009-06-09 David Blacka <davidb@verisign.com>
|
2009-06-09 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* VerifyZone: Improve the output.
|
* VerifyZone: Improve the output.
|
||||||
|
|
||||||
* SignKeyset: Add a command line tool for just signing DNSKEY RRsets.
|
* SignKeyset: Add a command line tool for just signing DNSKEY RRsets.
|
||||||
|
|
||||||
2009-02-10 David Blacka <davidb@verisign.com>
|
2009-02-10 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* Released version 0.9.0
|
* Released version 0.9.0
|
||||||
|
|
||||||
2009-02-08 David Blacka <davidb@verisign.com>
|
2009-02-08 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* KeyGen: make RSA large exponent the default. Make it possible
|
* KeyGen: make RSA large exponent the default. Make it possible
|
||||||
to select small exponent.
|
to select small exponent.
|
||||||
|
|
||||||
* KeyInfoTool: add more info to the output, handle multiple files
|
* KeyInfoTool: add more info to the output, handle multiple files
|
||||||
on the command line.
|
on the command line.
|
||||||
|
|
||||||
* DnsKeyAlgorithm: use DNSjava constants, BIND 9.6 mnemonics for
|
* DnsKeyAlgorithm: use DNSjava constants, BIND 9.6 mnemonics for
|
||||||
NSEC3 key aliases.
|
NSEC3 key aliases.
|
||||||
|
|
||||||
2009-02-07 David Blacka <davidb@verisign.com>
|
2009-02-07 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* SignZone: add argument for setting the TTL of the NSEC3PARAM
|
* SignZone: add argument for setting the TTL of the NSEC3PARAM
|
||||||
record. This is so we can match current dnssec-signzone
|
record. This is so we can match current dnssec-signzone
|
||||||
(9.6.0-p1) behavior of using a TTL of zero.
|
(9.6.0-p1) behavior of using a TTL of zero.
|
||||||
|
|
||||||
* Update dnsjava to 2.0.6-vrsn-2, commons-cli to 1.1
|
* Update dnsjava to 2.0.6-vrsn-2, commons-cli to 1.1
|
||||||
|
|
||||||
* SignUtils: fix bug where NSEC3 algorithm and flags were transposed.
|
* SignUtils: fix bug where NSEC3 algorithm and flags were transposed.
|
||||||
|
|
||||||
* SignUtils: Make sure to use the SOA minimum value for NSEC TTLs,
|
* SignUtils: Make sure to use the SOA minimum value for NSEC TTLs,
|
||||||
instead of the ttl of the "node".
|
instead of the ttl of the "node".
|
||||||
|
|
||||||
2009-02-04 David Blacka <davidb@verisign.com>
|
2009-02-04 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* update to dnsjava-2.0.1-vrsn-4 (updated typecodes for
|
* update to dnsjava-2.0.1-vrsn-4 (updated typecodes for
|
||||||
NSEC3/NSEC3PARAM).
|
NSEC3/NSEC3PARAM).
|
||||||
|
|
||||||
* SignUtils: use JDK-native SHA-256 code instead of broken
|
* SignUtils: use JDK-native SHA-256 code instead of broken
|
||||||
contributed implementation.
|
contributed implementation.
|
||||||
|
|
||||||
* DnsKeyAlgorithm: Add RSASHA256 and RSASHA512 algorithm, guessing
|
* DnsKeyAlgorithm: Add RSASHA256 and RSASHA512 algorithm, guessing
|
||||||
at the code points. Note, these require Java 5 or later, or an
|
at the code points. Note, these require Java 5 or later, or an
|
||||||
alternate crypto provider.
|
alternate crypto provider.
|
||||||
|
|
||||||
* ZoneUtils: add a method to find specific RRs in a list of RRs
|
* ZoneUtils: add a method to find specific RRs in a list of RRs
|
||||||
or RRsets.
|
or RRsets.
|
||||||
|
|
||||||
* SignZone: make jdnssec-signzone a bit more aggressive in finding
|
* SignZone: make jdnssec-signzone a bit more aggressive in finding
|
||||||
keys. Now it will look for keyfiles matching keys at the zone
|
keys. Now it will look for keyfiles matching keys at the zone
|
||||||
apex, and, failing that, just look for keyfiles named after the
|
apex, and, failing that, just look for keyfiles named after the
|
||||||
zone. Specifying any keys at all on the command line will
|
zone. Specifying any keys at all on the command line will
|
||||||
override this behavior.
|
override this behavior.
|
||||||
|
|
||||||
2009-02-01 David Blacka <davidb@verisign.com>
|
2009-02-01 David Blacka <davidb@verisign.com>
|
||||||
|
|
||||||
* DnsKeyAlgorithm: add official aliases from RFC 5155.
|
* DnsKeyAlgorithm: add official aliases from RFC 5155.
|
||||||
|
|
||||||
* JCEDnsSecSigner: refactor zone signing methods to remove
|
* JCEDnsSecSigner: refactor zone signing methods to remove
|
||||||
duplicate code.
|
duplicate code.
|
||||||
|
|
||||||
* SignZone: move the signZone() methods to JCEDnsSecSigner
|
* SignZone: move the signZone() methods to JCEDnsSecSigner
|
||||||
|
|
||||||
* BINDKeyUtils: close the private key file after reading it.
|
* BINDKeyUtils: close the private key file after reading it.
|
||||||
Patch by Wolfgang Nagele.
|
Patch by Wolfgang Nagele.
|
||||||
|
|
||||||
2006-12-15 David Blacka <davidb@verisignlabs.com>
|
2006-12-15 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Release version 0.8.4
|
* Release version 0.8.4
|
||||||
|
|
||||||
* SignZone: updated internals (and dnsjava lib) to match wire
|
* SignZone: updated internals (and dnsjava lib) to match wire
|
||||||
format changes introduced by the nsec3-08 draft.
|
format changes introduced by the nsec3-08 draft.
|
||||||
|
|
||||||
2006-10-10 David Blacka <davidb@verisignlabs.com>
|
2006-10-10 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Released version 0.8.3
|
* Released version 0.8.3
|
||||||
|
|
||||||
* ZoneFormat: fix RRSIG ordering issue when dealing with multiple
|
* ZoneFormat: fix RRSIG ordering issue when dealing with multiple
|
||||||
RRSIGs for a given RRset.
|
RRSIGs for a given RRset.
|
||||||
|
|
||||||
* ZoneFormat: lowercase all names in the zone.
|
* ZoneFormat: lowercase all names in the zone.
|
||||||
|
|
||||||
* Fix packaging errors.
|
* Fix packaging errors.
|
||||||
|
|
||||||
|
|
||||||
2006-09-12 David Blacka <davidb@verisignlabs.com>
|
2006-09-12 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Released version 0.8.0.
|
* Released version 0.8.0.
|
||||||
|
|
||||||
2006-09-10 David Blacka <davidb@fury.blacka.com>
|
2006-09-10 David Blacka <davidb@fury.blacka.com>
|
||||||
|
|
||||||
* Added the "KeyInfoTool" command line tool as the start of a tool
|
* Added the "KeyInfoTool" command line tool as the start of a tool
|
||||||
for decoding DNSKEY information. Right now, mostly just useful
|
for decoding DNSKEY information. Right now, mostly just useful
|
||||||
for checking the public exponenent of RSA keys.
|
for checking the public exponenent of RSA keys.
|
||||||
|
|
||||||
* Added the "-e" option to jdnssec-keygen, to instruct the key
|
* Added the "-e" option to jdnssec-keygen, to instruct the key
|
||||||
generator to use the (common) large exponent in RSA key
|
generator to use the (common) large exponent in RSA key
|
||||||
generation.
|
generation.
|
||||||
|
|
||||||
2006-08-31 David Blacka <davidb@fury.blacka.com>
|
2006-08-31 David Blacka <davidb@fury.blacka.com>
|
||||||
|
|
||||||
* Modified jdnssec-signzone to set the ttls of NSEC3 records (so
|
* Modified jdnssec-signzone to set the ttls of NSEC3 records (so
|
||||||
far) to the SOA minimum value.
|
far) to the SOA minimum value.
|
||||||
|
|
||||||
* Add NSEC3PARAM support for compatibility with the -07 NSEC3
|
* Add NSEC3PARAM support for compatibility with the -07 NSEC3
|
||||||
draft.
|
draft.
|
||||||
|
|
||||||
2006-05-24 David Blacka <davidb@verisignlabs.com>
|
2006-05-24 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Add some error checking for the NSEC3 command line parameters
|
* Add some error checking for the NSEC3 command line parameters
|
||||||
for jdnssec-signzone.
|
for jdnssec-signzone.
|
||||||
|
|
||||||
* Update local dnsjava build to 2.0.1. This also contains a
|
* Update local dnsjava build to 2.0.1. This also contains a
|
||||||
change to the NSEC3 rdata format (as per the -06pre NSEC3 draft).
|
change to the NSEC3 rdata format (as per the -06pre NSEC3 draft).
|
||||||
The change is the addition of a "next hashed owner name" length
|
The change is the addition of a "next hashed owner name" length
|
||||||
octet.
|
octet.
|
||||||
|
|
||||||
* Modified the jdnssec-* shell wrappers to also use the local
|
* Modified the jdnssec-* shell wrappers to also use the local
|
||||||
build area version of the jdnssec-tools.jar file. This allows the
|
build area version of the jdnssec-tools.jar file. This allows the
|
||||||
standard jdnssec-* wrappers to work right from the build area.
|
standard jdnssec-* wrappers to work right from the build area.
|
||||||
|
|
||||||
* Add support of the SHA256 algorithm for DS records. This uses
|
* Add support of the SHA256 algorithm for DS records. This uses
|
||||||
the SHA256 class that I obtained from Scott Rose (thanks Scott!).
|
the SHA256 class that I obtained from Scott Rose (thanks Scott!).
|
||||||
|
|
||||||
* Change the name of the package and jar file to jdnssec-tools
|
* Change the name of the package and jar file to jdnssec-tools
|
||||||
(from java-dnssec-tools) for consistency.
|
(from java-dnssec-tools) for consistency.
|
||||||
|
|
||||||
* release version 0.7.0.
|
* release version 0.7.0.
|
||||||
|
|
||||||
2006-05-23 David Blacka <davidb@verisignlabs.com>
|
2006-05-23 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Add support for algorithm aliases. This feature is so that the
|
* Add support for algorithm aliases. This feature is so that the
|
||||||
user can declare the DNSKEY algorithm x is the same as algorithm 5
|
user can declare the DNSKEY algorithm x is the same as algorithm 5
|
||||||
(e.g.). So far, this only works with straight integer algorithm
|
(e.g.). So far, this only works with straight integer algorithm
|
||||||
identifiers (no private alg support yet).
|
identifiers (no private alg support yet).
|
||||||
|
|
||||||
* Fix jdnssec-signzone so that you can specify multiple KSKs on
|
* Fix jdnssec-signzone so that you can specify multiple KSKs on
|
||||||
the command line. Apparently, commons-cli actually does handle
|
the command line. Apparently, commons-cli actually does handle
|
||||||
repeating command line options correctly.
|
repeating command line options correctly.
|
||||||
|
|
||||||
2006-05-03 David Blacka <davidb@verisignlabs.com>
|
2006-05-03 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Add preliminary implementation of jdnssec-dstool. This is a
|
* Add preliminary implementation of jdnssec-dstool. This is a
|
||||||
simple command line tool that takes a DNSKEY record and converts
|
simple command line tool that takes a DNSKEY record and converts
|
||||||
it into a DS record (or a DLV record). Right now, it requires
|
it into a DS record (or a DLV record). Right now, it requires
|
||||||
that the key is stored in a file ending with '.key'.
|
that the key is stored in a file ending with '.key'.
|
||||||
|
|
||||||
* release version 0.6.0.
|
* release version 0.6.0.
|
||||||
|
|
||||||
2006-03-15 David Blacka <davidb@verisignlabs.com>
|
2006-03-15 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Type map changes for NSEC3, corresponding to changes in draft
|
* Type map changes for NSEC3, corresponding to changes in draft
|
||||||
-05pre. Essentially: NSEC3 and RRSIG bits are not set for most
|
-05pre. Essentially: NSEC3 and RRSIG bits are not set for most
|
||||||
(all) NSEC3 records any longer.
|
(all) NSEC3 records any longer.
|
||||||
|
|
||||||
2006-03-06 David Blacka <davidb@verisignlabs.com>
|
2006-03-06 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* release version 0.5.0.
|
* release version 0.5.0.
|
||||||
|
|
||||||
2006-02-16 David Blacka <davidb@verisignlabs.com>
|
2006-02-16 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Make RecordComparator also compare RDATA so the removeDuplicates
|
* Make RecordComparator also compare RDATA so the removeDuplicates
|
||||||
step actually works reliabled. This was masked by the dupicate
|
step actually works reliabled. This was masked by the dupicate
|
||||||
suppression in org.xbill.DNS.RRset.
|
suppression in org.xbill.DNS.RRset.
|
||||||
|
|
||||||
* Only allow one command line specified KSK since commons-cli
|
* Only allow one command line specified KSK since commons-cli
|
||||||
doesn't seem to handle multi-arg options correctly.
|
doesn't seem to handle multi-arg options correctly.
|
||||||
|
|
||||||
* Do not croak on the lack of the command-line keys for now.
|
* Do not croak on the lack of the command-line keys for now.
|
||||||
|
|
||||||
* New version of local dnsjava build containing NSEC3 changes
|
* New version of local dnsjava build containing NSEC3 changes
|
||||||
corresponding to the -04pre draft.
|
corresponding to the -04pre draft.
|
||||||
|
|
||||||
2005-11-16 David Blacka <davidb@verisignlabs.com>
|
2005-11-16 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Make jdnssec-verifyzone work with just the zone (which is
|
* Make jdnssec-verifyzone work with just the zone (which is
|
||||||
self-signed anyway).
|
self-signed anyway).
|
||||||
|
|
||||||
* release version 0.4.2.
|
* release version 0.4.2.
|
||||||
|
|
||||||
2005-11-09 David Blacka <davidb@verisignlabs.com>
|
2005-11-09 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Add original ownername comments to the NSEC3 generation.
|
* Add original ownername comments to the NSEC3 generation.
|
||||||
|
|
||||||
2005-11-08 David Blacka <davidb@verisignlabs.com>
|
2005-11-08 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* New zone formatter.
|
* New zone formatter.
|
||||||
|
|
||||||
* Misc bug fixes.
|
* Misc bug fixes.
|
||||||
|
|
||||||
* release version 0.4.1.
|
* release version 0.4.1.
|
||||||
|
|
||||||
2005-11-07 David Blacka <davidb@verisignlabs.com>
|
2005-11-07 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Update the local dnsjava build with a bugfix.
|
* Update the local dnsjava build with a bugfix.
|
||||||
|
|
||||||
* Fix ordering problem with ProtoNSEC3s.
|
* Fix ordering problem with ProtoNSEC3s.
|
||||||
|
|
||||||
2005-11-06 David Blacka <davidb@verisignlabs.com>
|
2005-11-06 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Actually use the --iterations command line option of
|
* Actually use the --iterations command line option of
|
||||||
jdnssec-signzone.
|
jdnssec-signzone.
|
||||||
|
|
||||||
2005-10-27 David Blacka <davidb@verisignlabs.com>
|
2005-10-27 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Add NSEC3 support for jdnssec-signzone.
|
* Add NSEC3 support for jdnssec-signzone.
|
||||||
|
|
||||||
* Remove support for plain Opt-In (until private algorithms work).
|
* Remove support for plain Opt-In (until private algorithms work).
|
||||||
|
|
||||||
* release version 0.4.0.
|
* release version 0.4.0.
|
||||||
|
|
||||||
2005-08-14 David Blacka <davidb@verisignlabs.com>
|
2005-08-14 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Move the signZone function into the SignZone class (from the
|
* Move the signZone function into the SignZone class (from the
|
||||||
SignUtils) class.
|
SignUtils) class.
|
||||||
|
|
||||||
* General cleanup.
|
* General cleanup.
|
||||||
|
|
||||||
* Add local _jdnssec-* shell wrappers. These use build/classes in
|
* Add local _jdnssec-* shell wrappers. These use build/classes in
|
||||||
the classpath so can be used to run the tools right out of the
|
the classpath so can be used to run the tools right out of the
|
||||||
build area.
|
build area.
|
||||||
|
|
||||||
2005-08-13 David Blacka <davidb@verisignlabs.com>
|
2005-08-13 David Blacka <davidb@verisignlabs.com>
|
||||||
|
|
||||||
* Update to DNSjava 2.0.0
|
* Update to DNSjava 2.0.0
|
||||||
|
|
||||||
* Refactor command line parsing.
|
* Refactor command line parsing.
|
||||||
|
|
||||||
* Switch to using java.util.logging for logging.
|
* Switch to using java.util.logging for logging.
|
||||||
|
|||||||
62
README
62
README
@@ -1,62 +0,0 @@
|
|||||||
jdnssec-tools
|
|
||||||
|
|
||||||
http://www.verisignlabs.com/jdnssec-tools/
|
|
||||||
|
|
||||||
Author: David Blacka (davidb@verisign.com)
|
|
||||||
|
|
||||||
This is a collection of DNSSEC tools written in Java. They are
|
|
||||||
intended to be an addition or replacement for the DNSSEC tools that
|
|
||||||
are part of BIND 9.
|
|
||||||
|
|
||||||
These tools depend upon DNSjava (http://www.xbill.org/dnsjava), the
|
|
||||||
Jakarta Commons CLI and Logging libraries (http://jakarta.apache.org),
|
|
||||||
and Sun's Java Cryptography extensions. A copy of each of these
|
|
||||||
libraries is included in the distribution. Currently, these tools use
|
|
||||||
a custom version of the DNSjava library (for NSEC3 support), which is
|
|
||||||
provided.
|
|
||||||
|
|
||||||
See the "licenses" directory for the licensing information of this
|
|
||||||
package and the other packages that are distributed with it.
|
|
||||||
|
|
||||||
Getting started:
|
|
||||||
|
|
||||||
1. Unpack the binary distribution:
|
|
||||||
|
|
||||||
% tar zxvf java-dnssec-tools-x.x.x.tar.gz
|
|
||||||
|
|
||||||
2. Run the various tools from their unpacked location:
|
|
||||||
|
|
||||||
% cd java-dnssec-tools-x.x.x
|
|
||||||
% ./bin/jdnssec-signzone -h
|
|
||||||
|
|
||||||
Building from source:
|
|
||||||
|
|
||||||
1. Unpack the source distribution, preferably into the same directory
|
|
||||||
that the binary distribution was unpacked.
|
|
||||||
|
|
||||||
% tar zxvf java-dnssec-tools-x.x.x-src.tar.gz
|
|
||||||
|
|
||||||
2. Edit the build.properties file to suit your environment.
|
|
||||||
|
|
||||||
3. Run Ant (see http://ant.apache.org for information about the Ant
|
|
||||||
build tool).
|
|
||||||
|
|
||||||
% ant
|
|
||||||
|
|
||||||
4. You can build the distribution tarballs with 'ant dist'. You can
|
|
||||||
run the tools directly from the build area (without building the
|
|
||||||
jdnssec-tools.jar file) by using the ./bin/_jdnssec_* wrappers.
|
|
||||||
|
|
||||||
|
|
||||||
The source for this project is available in subversion, at
|
|
||||||
http://svn.verisignlabs.com/jdnssec/tools/trunk. Source for
|
|
||||||
the modified DNSjava library can be found in subversion at
|
|
||||||
http://svn.verisignlabs.com/jdnssec/dnsjava/trunk.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Questions or comments may be directed to the author
|
|
||||||
(mailto:davidb@verisign.com) or sent to the
|
|
||||||
dnssec@verisignlabs.com mailing list
|
|
||||||
(https://lists.verisignlabs.com/mailman/listinfo/dnssec).
|
|
||||||
|
|
||||||
13
README.TODO.md
Normal file
13
README.TODO.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# jdnssec-tools TODO List
|
||||||
|
|
||||||
|
This bit of code has been around since approximately 2005, and has been in "minimal maintenance" mode for much of that time. But that doesn't mean there aren't features that we *want* to do, if we could arrange time and attention. Here is a partial list:
|
||||||
|
|
||||||
|
* More feature parity with the current BIND 9 tools
|
||||||
|
* Support the "v1.3" private key format. This basically means supporting the timing parameters that BiND 9 added.
|
||||||
|
* Have `jdnssec-signzone` support incremental signing, including key rollovers
|
||||||
|
* Rewrite `jdnssec-signzone` to use a "TreeMap" and arrange the data into a map of RRsets, rather than a sorted list of Record objects. This wouldn't be more efficient, but might be easier to understand.
|
||||||
|
* Allow `jdnssec-signzone` to scale by either:
|
||||||
|
* Allowing for pre-sorted zone data, and/or
|
||||||
|
* allowing for an external sort once the data is shown to be larger than X, and/or
|
||||||
|
* allowing for a memory-constrained internal sort that uses disk, and/or,
|
||||||
|
* figuring out how to let the JVM use *a lot* of memory.
|
||||||
56
README.md
Normal file
56
README.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# jdnssec-tools
|
||||||
|
|
||||||
|
* <https://github.com/dblacka/jdnssec-tools/wiki>
|
||||||
|
|
||||||
|
Author: David Blacka (<davidb@verisign.com>)
|
||||||
|
|
||||||
|
This is a collection of DNSSEC tools written in Java. They are intended to be an addition or replacement for the DNSSEC tools that are part of BIND 9.
|
||||||
|
|
||||||
|
These tools depend upon DNSjava (<https://github.com/dnsjava/dnsjava>), the Jakarta Commons CLI and Logging libraries (<https://commons.apache.org/proper/commons-cli>), slf4j (<https://www.slf4j.org>), and Sun's Java Cryptography extensions. A copy of each of these libraries is included in the distribution.
|
||||||
|
|
||||||
|
See the "[licenses](https://github.com/dblacka/jdnssec-tools/tree/master/licenses)" directory for the licensing information of this package and the other packages that are distributed with it.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Using the binary distribution
|
||||||
|
|
||||||
|
The binary distributions can be downloaded from the [releases](https://github.com/dblacka/jdnssec-tools/releases) page. To use it;
|
||||||
|
|
||||||
|
1. Unpack the binary distribution:
|
||||||
|
|
||||||
|
tar zxvf java-dnssec-tools-x.x.x.tar.gz
|
||||||
|
|
||||||
|
2. Run the various tools from their unpacked location:
|
||||||
|
|
||||||
|
cd java-dnssec-tools-x.x.x
|
||||||
|
./bin/jdnssec-signzone -h
|
||||||
|
|
||||||
|
### Building from source
|
||||||
|
|
||||||
|
There is a source distribution also downloadable from the [releases](https://github.com/dblacka/jdnssec-tools/releases) page, but this should work with a clone of this repo.
|
||||||
|
|
||||||
|
1. (If downloaded) Unpack the source distribution, preferably into the same directory that the binary distribution was unpacked.
|
||||||
|
|
||||||
|
tar zxvf java-dnssec-tools-x.x.x-src.tar.gz
|
||||||
|
|
||||||
|
2. Edit the build.properties file to suit your environment.
|
||||||
|
3. Run Ant (see <http://ant.apache.org> for information about the Ant build tool).
|
||||||
|
|
||||||
|
ant
|
||||||
|
|
||||||
|
4. You can build the distribution tarballs with 'ant dist', although the main `ant` build command will have built the primary jar file.
|
||||||
|
|
||||||
|
The source for this project is available in git on github: <https://github.com/dblacka/jdnssec-tools>
|
||||||
|
|
||||||
|
### Using the one-jar distribution
|
||||||
|
|
||||||
|
As of version 0.20, there is a one-jar (aka an executable jar) as part of the distribution. It can also be downloaded from the [releases](https://github.com/dblacka/jdnssec-tools/releases) page.
|
||||||
|
|
||||||
|
1. Fetch the one-jar distribution.
|
||||||
|
2. Invoke with `java -jar jdnssec-tools-x.x.x.jar`
|
||||||
|
|
||||||
|
java -jar jdnssec-tools-x.x.x.jar signzone -h
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Questions or comments may be directed to the author (<mailto:davidb@verisign.com>), or by creating issues in the [github issue tracker](https://github.com/dblacka/jdnssec-tools/issues).
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#! /bin/sh
|
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
|
||||||
basedir=`cd $thisdir/..; pwd`
|
|
||||||
|
|
||||||
ulimit_max=`ulimit -H -n`
|
|
||||||
if [ $ulimit_max != "unlimited" ]; then
|
|
||||||
ulimit -n $ulimit_max
|
|
||||||
fi
|
|
||||||
|
|
||||||
# set the classpath
|
|
||||||
CLASSPATH=$CLASSPATH:$basedir/build/classes
|
|
||||||
|
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
|
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
|
||||||
done
|
|
||||||
export CLASSPATH
|
|
||||||
|
|
||||||
exec java com.verisignlabs.dnssec.cl.DSTool "$@"
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#! /bin/sh
|
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
|
||||||
basedir=`cd $thisdir/..; pwd`
|
|
||||||
|
|
||||||
ulimit_max=`ulimit -H -n`
|
|
||||||
if [ $ulimit_max != "unlimited" ]; then
|
|
||||||
ulimit -n $ulimit_max
|
|
||||||
fi
|
|
||||||
|
|
||||||
# set the classpath
|
|
||||||
CLASSPATH=$CLASSPATH:$basedir/build/classes
|
|
||||||
|
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
|
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
|
||||||
done
|
|
||||||
export CLASSPATH
|
|
||||||
|
|
||||||
exec java com.verisignlabs.dnssec.cl.KeyGen "$@"
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#! /bin/sh
|
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
|
||||||
basedir=`cd $thisdir/..; pwd`
|
|
||||||
|
|
||||||
ulimit_max=`ulimit -H -n`
|
|
||||||
if [ $ulimit_max != "unlimited" ]; then
|
|
||||||
ulimit -n $ulimit_max
|
|
||||||
fi
|
|
||||||
|
|
||||||
# set the classpath
|
|
||||||
CLASSPATH=$CLASSPATH:$basedir/build/classes
|
|
||||||
|
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
|
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
|
||||||
done
|
|
||||||
export CLASSPATH
|
|
||||||
|
|
||||||
exec java com.verisignlabs.dnssec.cl.KeyInfoTool "$@"
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#! /bin/sh
|
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
|
||||||
basedir=`cd $thisdir/..; pwd`
|
|
||||||
|
|
||||||
ulimit_max=`ulimit -H -n`
|
|
||||||
if [ $ulimit_max != "unlimited" ]; then
|
|
||||||
ulimit -n $ulimit_max
|
|
||||||
fi
|
|
||||||
|
|
||||||
# set the classpath
|
|
||||||
CLASSPATH=$CLASSPATH:$basedir/build/classes
|
|
||||||
|
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
|
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
|
||||||
done
|
|
||||||
export CLASSPATH
|
|
||||||
|
|
||||||
exec java com.verisignlabs.dnssec.cl.SignKeyset "$@"
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#! /bin/sh
|
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
|
||||||
basedir=`cd $thisdir/..; pwd`
|
|
||||||
|
|
||||||
ulimit_max=`ulimit -H -n`
|
|
||||||
if [ $ulimit_max != "unlimited" ]; then
|
|
||||||
ulimit -n $ulimit_max
|
|
||||||
fi
|
|
||||||
|
|
||||||
# set the classpath
|
|
||||||
CLASSPATH=$CLASSPATH:$basedir/build/classes
|
|
||||||
|
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
|
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
|
||||||
done
|
|
||||||
export CLASSPATH
|
|
||||||
|
|
||||||
exec java com.verisignlabs.dnssec.cl.SignZone "$@"
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#! /bin/sh
|
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
|
||||||
basedir=`cd $thisdir/..; pwd`
|
|
||||||
|
|
||||||
ulimit_max=`ulimit -H -n`
|
|
||||||
if [ $ulimit_max != "unlimited" ]; then
|
|
||||||
ulimit -n $ulimit_max
|
|
||||||
fi
|
|
||||||
|
|
||||||
# set the classpath
|
|
||||||
CLASSPATH=$CLASSPATH:$basedir/build/classes
|
|
||||||
|
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
|
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
|
||||||
done
|
|
||||||
export CLASSPATH
|
|
||||||
|
|
||||||
exec java com.verisignlabs.dnssec.cl.VerifyZone "$@"
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#! /bin/sh
|
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
|
||||||
basedir=`cd $thisdir/..; pwd`
|
|
||||||
|
|
||||||
ulimit_max=`ulimit -H -n`
|
|
||||||
if [ $ulimit_max != "unlimited" ]; then
|
|
||||||
ulimit -n $ulimit_max
|
|
||||||
fi
|
|
||||||
|
|
||||||
# set the classpath
|
|
||||||
CLASSPATH=$CLASSPATH:$basedir/build/classes
|
|
||||||
|
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip; do
|
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
|
||||||
done
|
|
||||||
export CLASSPATH
|
|
||||||
|
|
||||||
exec java com.verisignlabs.dnssec.cl.ZoneFormat "$@"
|
|
||||||
@@ -1,16 +1,21 @@
|
|||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
thisdir=$(dirname $0)
|
||||||
basedir=`cd $thisdir/..; pwd`
|
basedir=$(cd $thisdir/.. || exit; pwd)
|
||||||
|
|
||||||
ulimit_max=`ulimit -H -n`
|
ulimit_max=$(ulimit -H -n)
|
||||||
if [ $ulimit_max != "unlimited" ]; then
|
if [ $ulimit_max != "unlimited" ]; then
|
||||||
ulimit -n $ulimit_max
|
ulimit -n $ulimit_max
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# set the classpath
|
# set the classpath
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip $basedir/build/lib/*.jar; do
|
for i in "$basedir"/lib/*.jar "$basedir"/lib/*.zip "$basedir"/build/libs/*.jar; do
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
if ! [ -f $i ]; then continue; fi
|
||||||
|
if [ -z "$CLASSPATH" ]; then
|
||||||
|
CLASSPATH=$i
|
||||||
|
else
|
||||||
|
CLASSPATH="$CLASSPATH":"$i"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
export CLASSPATH
|
export CLASSPATH
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
thisdir=$(dirname $0)
|
||||||
basedir=`cd $thisdir/..; pwd`
|
basedir=$(cd $thisdir/.. || exit; pwd)
|
||||||
|
|
||||||
ulimit_max=`ulimit -H -n`
|
ulimit_max=$(ulimit -H -n)
|
||||||
if [ $ulimit_max != "unlimited" ]; then
|
if [ $ulimit_max != "unlimited" ]; then
|
||||||
ulimit -n $ulimit_max
|
ulimit -n $ulimit_max
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# set the classpath
|
# set the classpath
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip $basedir/build/lib/*.jar; do
|
for i in "$basedir"/lib/*.jar "$basedir"/lib/*.zip "$basedir"/build/libs/*.jar; do
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
if ! [ -f $i ]; then continue; fi
|
||||||
|
if [ -z "$CLASSPATH" ]; then
|
||||||
|
CLASSPATH=$i
|
||||||
|
else
|
||||||
|
CLASSPATH="$CLASSPATH":"$i"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
export CLASSPATH
|
export CLASSPATH
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
thisdir=$(dirname $0)
|
||||||
basedir=`cd $thisdir/..; pwd`
|
basedir=$(cd $thisdir/.. || exit; pwd)
|
||||||
|
|
||||||
ulimit_max=`ulimit -H -n`
|
ulimit_max=$(ulimit -H -n)
|
||||||
if [ $ulimit_max != "unlimited" ]; then
|
if [ $ulimit_max != "unlimited" ]; then
|
||||||
ulimit -n $ulimit_max
|
ulimit -n $ulimit_max
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# set the classpath
|
# set the classpath
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip $basedir/build/lib/*.jar; do
|
for i in "$basedir"/lib/*.jar "$basedir"/lib/*.zip "$basedir"/build/libs/*.jar; do
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
if ! [ -f $i ]; then continue; fi
|
||||||
|
if [ -z "$CLASSPATH" ]; then
|
||||||
|
CLASSPATH=$i
|
||||||
|
else
|
||||||
|
CLASSPATH="$CLASSPATH":"$i"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
export CLASSPATH
|
export CLASSPATH
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
thisdir=$(dirname $0)
|
||||||
basedir=`cd $thisdir/..; pwd`
|
basedir=$(cd $thisdir/.. || exit; pwd)
|
||||||
|
|
||||||
|
ulimit_max=`ulimit -H -n`
|
||||||
|
if [ $ulimit_max != "unlimited" ]; then
|
||||||
|
ulimit -n $ulimit_max
|
||||||
|
fi
|
||||||
|
|
||||||
# set the classpath
|
# set the classpath
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip $basedir/build/lib/*.jar; do
|
for i in "$basedir"/lib/*.jar "$basedir"/lib/*.zip "$basedir"/build/libs/*.jar; do
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
if ! [ -f $i ]; then continue; fi
|
||||||
|
if [ -z "$CLASSPATH" ]; then
|
||||||
|
CLASSPATH=$i
|
||||||
|
else
|
||||||
|
CLASSPATH="$CLASSPATH":"$i"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
export CLASSPATH
|
export CLASSPATH
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
thisdir=$(dirname $0)
|
||||||
basedir=`cd $thisdir/..; pwd`
|
basedir=$(cd $thisdir/.. || exit; pwd)
|
||||||
|
|
||||||
|
ulimit_max=`ulimit -H -n`
|
||||||
|
if [ $ulimit_max != "unlimited" ]; then
|
||||||
|
ulimit -n $ulimit_max
|
||||||
|
fi
|
||||||
|
|
||||||
# set the classpath
|
# set the classpath
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip $basedir/build/lib/*.jar; do
|
for i in "$basedir"/lib/*.jar "$basedir"/lib/*.zip "$basedir"/build/libs/*.jar; do
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
if ! [ -f $i ]; then continue; fi
|
||||||
|
if [ -z "$CLASSPATH" ]; then
|
||||||
|
CLASSPATH=$i
|
||||||
|
else
|
||||||
|
CLASSPATH="$CLASSPATH":"$i"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
export CLASSPATH
|
export CLASSPATH
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
thisdir=$(dirname $0)
|
||||||
basedir=`cd $thisdir/..; pwd`
|
basedir=$(cd $thisdir/.. || exit; pwd)
|
||||||
|
|
||||||
ulimit_max=`ulimit -H -n`
|
ulimit_max=$(ulimit -H -n)
|
||||||
if [ $ulimit_max != "unlimited" ]; then
|
if [ $ulimit_max != "unlimited" ]; then
|
||||||
ulimit -n $ulimit_max
|
ulimit -n $ulimit_max
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# set the classpath
|
# set the classpath
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip $basedir/build/lib/*.jar; do
|
for i in "$basedir"/lib/*.jar "$basedir"/lib/*.zip "$basedir"/build/libs/*.jar; do
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
if ! [ -f $i ]; then continue; fi
|
||||||
|
if [ -z "$CLASSPATH" ]; then
|
||||||
|
CLASSPATH=$i
|
||||||
|
else
|
||||||
|
CLASSPATH="$CLASSPATH":"$i"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
export CLASSPATH
|
export CLASSPATH
|
||||||
|
|
||||||
|
|||||||
22
bin/jdnssec-tools
Executable file
22
bin/jdnssec-tools
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
thisdir=$(dirname $0)
|
||||||
|
basedir=$(cd $thisdir/.. || exit; pwd)
|
||||||
|
|
||||||
|
ulimit_max=$(ulimit -H -n)
|
||||||
|
if [ $ulimit_max != "unlimited" ]; then
|
||||||
|
ulimit -n $ulimit_max
|
||||||
|
fi
|
||||||
|
|
||||||
|
# set the classpath
|
||||||
|
for i in "$basedir"/lib/*.jar "$basedir"/lib/*.zip "$basedir"/build/libs/*.jar; do
|
||||||
|
if ! [ -f $i ]; then continue; fi
|
||||||
|
if [ -z "$CLASSPATH" ]; then
|
||||||
|
CLASSPATH=$i
|
||||||
|
else
|
||||||
|
CLASSPATH="$CLASSPATH":"$i"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
export CLASSPATH
|
||||||
|
|
||||||
|
exec java com.verisignlabs.dnssec.cl.CLI "$@"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
thisdir=$(dirname $0)
|
||||||
basedir=`cd $thisdir/..; pwd`
|
basedir=$(cd $thisdir/.. || exit; pwd)
|
||||||
|
|
||||||
ulimit_max=`ulimit -H -n`
|
ulimit_max=`ulimit -H -n`
|
||||||
if [ $ulimit_max != "unlimited" ]; then
|
if [ $ulimit_max != "unlimited" ]; then
|
||||||
@@ -9,8 +9,13 @@ if [ $ulimit_max != "unlimited" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# set the classpath
|
# set the classpath
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip $basedir/build/lib/*.jar; do
|
for i in "$basedir"/lib/*.jar "$basedir"/lib/*.zip "$basedir"/build/libs/*.jar; do
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
if ! [ -f "$i" ]; then continue; fi
|
||||||
|
if [ -z "$CLASSPATH" ]; then
|
||||||
|
CLASSPATH=$i
|
||||||
|
else
|
||||||
|
CLASSPATH="$CLASSPATH":"$i"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
export CLASSPATH
|
export CLASSPATH
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
|
|
||||||
thisdir=`dirname $0`
|
thisdir=$(dirname $0)
|
||||||
basedir=`cd $thisdir/..; pwd`
|
basedir=$(cd $thisdir/.. || exit; pwd)
|
||||||
|
|
||||||
ulimit_max=`ulimit -H -n`
|
ulimit_max=$(ulimit -H -n)
|
||||||
if [ $ulimit_max != "unlimited" ]; then
|
if [ $ulimit_max != "unlimited" ]; then
|
||||||
ulimit -n $ulimit_max
|
ulimit -n $ulimit_max
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# set the classpath
|
# set the classpath
|
||||||
for i in $basedir/lib/*.jar $basedir/lib/*.zip $basedir/build/lib/*.jar; do
|
for i in "$basedir"/lib/*.jar "$basedir"/lib/*.zip "$basedir"/build/libs/*.jar; do
|
||||||
CLASSPATH="$CLASSPATH":"$i"
|
if ! [ -f $i ]; then continue; fi
|
||||||
|
if [ -z "$CLASSPATH" ]; then
|
||||||
|
CLASSPATH=$i
|
||||||
|
else
|
||||||
|
CLASSPATH="$CLASSPATH":"$i"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
export CLASSPATH
|
export CLASSPATH
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#build.compiler=jikes
|
|
||||||
#build.compiler=modern
|
|
||||||
build.deprecation=true
|
build.deprecation=true
|
||||||
|
build.debug=true
|
||||||
|
build.java_version=17
|
||||||
|
|||||||
106
build.xml
106
build.xml
@@ -10,17 +10,19 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<project default="compile" basedir=".">
|
<project default="build" basedir=".">
|
||||||
|
|
||||||
<property file="build.properties" />
|
<property file="build.properties" />
|
||||||
<property file="VERSION" />
|
<property file="VERSION" />
|
||||||
|
|
||||||
<property name="sectools-distname" value="jdnssec-tools-${version}" />
|
|
||||||
|
|
||||||
<property name="build.dir" value="build" />
|
<property name="build.dir" value="build" />
|
||||||
<property name="build.dest" value="${build.dir}/classes" />
|
<property name="build.dest" value="${build.dir}/classes" />
|
||||||
<property name="build.lib.dest" value="${build.dir}/lib" />
|
<property name="build.lib.dest" value="${build.dir}/libs" />
|
||||||
<property name="build.src" value="src" />
|
<property name="build.src" value="src/main/java" />
|
||||||
|
|
||||||
|
<property name="dist.dir" value="dist"/>
|
||||||
|
<property name="dist.name" value="jdnssec-tools-${version}" />
|
||||||
|
|
||||||
<property name="packages" value="com.verisignlabs.dnssec.*" />
|
<property name="packages" value="com.verisignlabs.dnssec.*" />
|
||||||
<property name="doc.dir" value="docs" />
|
<property name="doc.dir" value="docs" />
|
||||||
@@ -33,6 +35,7 @@
|
|||||||
<pathelement location="${build.dest}" />
|
<pathelement location="${build.dest}" />
|
||||||
<fileset dir="${lib.dir}" includes="*.jar,*.zip" />
|
<fileset dir="${lib.dir}" includes="*.jar,*.zip" />
|
||||||
</path>
|
</path>
|
||||||
|
|
||||||
<property name="project.classpath" refid="project.classpath" />
|
<property name="project.classpath" refid="project.classpath" />
|
||||||
|
|
||||||
<target name="prepare-src">
|
<target name="prepare-src">
|
||||||
@@ -40,25 +43,40 @@
|
|||||||
<mkdir dir="${build.lib.dest}" />
|
<mkdir dir="${build.lib.dest}" />
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="sectools" depends="prepare-src" >
|
<target name="compile" depends="prepare-src" >
|
||||||
<javac srcdir="${build.src}"
|
<javac srcdir="${build.src}"
|
||||||
destdir="${build.dest}"
|
destdir="${build.dest}"
|
||||||
classpathref="project.classpath"
|
classpathref="project.classpath"
|
||||||
source="1.5"
|
deprecation="${build.deprecation}"
|
||||||
target="1.5"
|
|
||||||
deprecation="true"
|
|
||||||
includeantruntime="false"
|
includeantruntime="false"
|
||||||
includes="com/verisignlabs/dnssec/" />
|
includes="com/verisignlabs/dnssec/"
|
||||||
|
debug="${build.debug}"
|
||||||
|
release="${build.java_version}" />
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="sectools-jar" depends="usage,sectools">
|
<target name="build-jar" depends="usage, compile">
|
||||||
<jar jarfile="${build.lib.dest}/jdnssec-tools.jar"
|
<jar jarfile="${build.lib.dest}/jdnssec-tools.jar"
|
||||||
basedir="${build.dest}"
|
basedir="${build.dest}"
|
||||||
includes="com/verisignlabs/dnssec/" />
|
includes="com/verisignlabs/dnssec/" />
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="compile"
|
<target name="build"
|
||||||
depends="usage,sectools-jar">
|
depends="usage,build-jar">
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="build-onejar" depends="compile">
|
||||||
|
<jar destfile="${dist.dir}/${dist.name}.jar">
|
||||||
|
<zipfileset dir="${build.dest}" includes="**/*.class" />
|
||||||
|
|
||||||
|
<zipfileset src="${lib.dir}/dnsjava-3.5.3.jar" />
|
||||||
|
<zipfileset src="${lib.dir}/commons-cli-1.6.0.jar" />
|
||||||
|
<zipfileset src="${lib.dir}/slf4j-api-1.7.36.jar" />
|
||||||
|
<zipfileset src="${lib.dir}/slf4j-simple-1.7.36.jar" />
|
||||||
|
<manifest>
|
||||||
|
<attribute name="Main-Class"
|
||||||
|
value="com.verisignlabs.dnssec.cl.CLI" />
|
||||||
|
</manifest>
|
||||||
|
</jar>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="javadoc" depends="usage">
|
<target name="javadoc" depends="usage">
|
||||||
@@ -70,21 +88,26 @@
|
|||||||
verbose="true" author="true"
|
verbose="true" author="true"
|
||||||
windowtitle="jdnssec-tools-${version}"
|
windowtitle="jdnssec-tools-${version}"
|
||||||
use="true">
|
use="true">
|
||||||
<link href="http://java.sun.com/j2se/1.4.2/docs/api/" />
|
<link href="https://docs.oracle.com/javase/8/docs/api/" />
|
||||||
<link href="http://www.xbill.org/dnsjava/doc/" />
|
<link href="http://www.xbill.org/dnsjava/doc/" />
|
||||||
</javadoc>
|
</javadoc>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
|
|
||||||
<target name="clean" depends="usage">
|
<target name="clean" depends="usage">
|
||||||
<delete dir="${build.dest}" />
|
<delete dir="${build.dest}" />
|
||||||
<delete dir="${build.lib.dest}" />
|
<delete dir="${build.lib.dest}" />
|
||||||
|
<delete dir="${dist.dir}" />
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="sectools-dist-prepare" depends="usage, compile, javadoc">
|
<target name="dist-clean" depends="usage">
|
||||||
<mkdir dir="${sectools-distname}" />
|
<delete dir="${dist.name}" />
|
||||||
|
</target>
|
||||||
|
|
||||||
<copy todir="${sectools-distname}">
|
<target name="dist-prepare" depends="usage, build, javadoc">
|
||||||
|
<mkdir dir="${dist.dir}" />
|
||||||
|
<mkdir dir="${dist.name}" />
|
||||||
|
|
||||||
|
<copy todir="${dist.name}">
|
||||||
<fileset dir=".">
|
<fileset dir=".">
|
||||||
<include name="bin/jdnssec-*" />
|
<include name="bin/jdnssec-*" />
|
||||||
<include name="lib/*.jar" />
|
<include name="lib/*.jar" />
|
||||||
@@ -99,37 +122,33 @@
|
|||||||
</fileset>
|
</fileset>
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
<copy todir="${sectools-distname}/lib">
|
<copy todir="${dist.name}/lib">
|
||||||
<fileset dir="${build.lib.dest}">
|
<fileset dir="${build.lib.dest}">
|
||||||
<include name="*.jar" />
|
<include name="*.jar" />
|
||||||
</fileset>
|
</fileset>
|
||||||
</copy>
|
</copy>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="sectools-dist-clean">
|
|
||||||
<delete dir="${sectools-distname}" />
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<patternset id="exec.files">
|
<patternset id="exec.files">
|
||||||
<include name="${sectools-distname}/bin/jdnssec-*" />
|
<include name="${dist.name}/bin/jdnssec-*" />
|
||||||
</patternset>
|
</patternset>
|
||||||
|
|
||||||
<patternset id="src.files">
|
<patternset id="src.files">
|
||||||
<include name="${sectools-distname}/src/" />
|
<include name="${dist.name}/src/" />
|
||||||
<include name="${sectools-distname}/build.xml" />
|
<include name="${dist.name}/build.xml" />
|
||||||
<include name="${sectools-distname}/build.properties" />
|
<include name="${dist.name}/build.properties" />
|
||||||
</patternset>
|
</patternset>
|
||||||
|
|
||||||
<patternset id="bin.files">
|
<patternset id="bin.files">
|
||||||
<include name="${sectools-distname}/doc/" />
|
<include name="${dist.name}/doc/" />
|
||||||
<include name="${sectools-distname}/lib/" />
|
<include name="${dist.name}/lib/" />
|
||||||
<include name="${sectools-distname}/licenses/" />
|
<include name="${dist.name}/licenses/" />
|
||||||
<include name="${sectools-distname}/VERSION" />
|
<include name="${dist.name}/VERSION" />
|
||||||
<include name="${sectools-distname}/README" />
|
<include name="${dist.name}/README" />
|
||||||
</patternset>
|
</patternset>
|
||||||
|
|
||||||
<target name="sectools-bin-dist" depends="sectools-dist-prepare">
|
<target name="bin-dist" depends="dist-prepare">
|
||||||
<tar destfile="${sectools-distname}.tar.gz" compression="gzip">
|
<tar destfile="${dist.dir}/${dist.name}.tar.gz" compression="gzip">
|
||||||
<tarfileset mode="755" dir=".">
|
<tarfileset mode="755" dir=".">
|
||||||
<patternset refid="exec.files" />
|
<patternset refid="exec.files" />
|
||||||
</tarfileset>
|
</tarfileset>
|
||||||
@@ -139,37 +158,30 @@
|
|||||||
</tar>
|
</tar>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="sectools-src-dist" depends="sectools-dist-prepare">
|
<target name="src-dist" depends="dist-prepare">
|
||||||
<tar destfile="${sectools-distname}-src.tar.gz"
|
<tar destfile="${dist.dir}/${dist.name}-src.tar.gz" compression="gzip">
|
||||||
compression="gzip">
|
|
||||||
<tarfileset dir=".">
|
<tarfileset dir=".">
|
||||||
<patternset refid="src.files" />
|
<patternset refid="src.files" />
|
||||||
</tarfileset>
|
</tarfileset>
|
||||||
</tar>
|
</tar>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="sectools-dist"
|
<target name="dist"
|
||||||
depends="sectools-bin-dist,sectools-src-dist, sectools-dist-clean">
|
depends="bin-dist, src-dist, build-onejar, dist-clean">
|
||||||
</target>
|
|
||||||
|
|
||||||
|
|
||||||
<target name="dist" depends="sectools-dist">
|
|
||||||
<echo message="do not forget to tag the release:" />
|
|
||||||
<echo message=" svn-tag ${version}" />
|
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="usage">
|
<target name="usage">
|
||||||
<echo message=" " />
|
<echo message=" " />
|
||||||
<echo message="SECTOOLS v. ${version} Build System" />
|
<echo message="jdnssec-tools v. ${version} Build System" />
|
||||||
<echo message="--------------------------------" />
|
<echo message="--------------------------------" />
|
||||||
<echo message="Available Targets:" />
|
<echo message="Available Targets:" />
|
||||||
<echo message=" compile (default) - compiles the source code, creates jar" />
|
<echo message=" build (default) - compiles the source code, creates main jar" />
|
||||||
<echo message=" javadoc - create javadoc from source" />
|
<echo message=" javadoc - create javadoc from source" />
|
||||||
<echo message=" clean - delete class files" />
|
<echo message=" clean - delete class files" />
|
||||||
<echo message=" dist - package it up" />
|
<echo message=" dist - package it up" />
|
||||||
|
<echo message=" onejar - build the executable jar" />
|
||||||
<echo message=" usage - this help message" />
|
<echo message=" usage - this help message" />
|
||||||
<echo message=" " />
|
<echo message=" " />
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
||||||
|
|||||||
93
jdnssec-tools.properties.example
Normal file
93
jdnssec-tools.properties.example
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# An example properties file for jdnssec-tools
|
||||||
|
# Properties may be be scoped by the tool name, which is the name minus "jdnssec-"
|
||||||
|
# If unscoped, the same named property will be used by multiple tools
|
||||||
|
|
||||||
|
# Common properties
|
||||||
|
|
||||||
|
# log_level = warning
|
||||||
|
# verbose = true # same as log_level = fine (true) or log_level = warning (false)
|
||||||
|
# multiline = false
|
||||||
|
|
||||||
|
# algorithm aliasing is <scope>.alias.<new-mnemonic> = <orig-alg-id>:<alias-alg-id>
|
||||||
|
# alias.NEWALG = 8:100
|
||||||
|
|
||||||
|
# jdnssec-dstool properties
|
||||||
|
|
||||||
|
## These are all equivalent. Unscoped properties might apply to other tools
|
||||||
|
# dstool.digest_algorithm = 4
|
||||||
|
# digest_algorithm = 4 # applies to jdnssec-signzone, too
|
||||||
|
# dstool.digest_id = 4
|
||||||
|
|
||||||
|
# jdnssec-keygen properties
|
||||||
|
|
||||||
|
# keygen.use_large_exponent = true
|
||||||
|
# keygen.key_directory = .
|
||||||
|
# key_directory = /path/to/dnskey_files # applies to jdnssec-sign*
|
||||||
|
# keygen.algorithm = ED448
|
||||||
|
# keygen.keylength = 2048
|
||||||
|
# keygen.keylen = 2048 # same thing
|
||||||
|
# keygen.ttl = 3600
|
||||||
|
|
||||||
|
# jdnssec-keyinfotool
|
||||||
|
|
||||||
|
# no additional keys
|
||||||
|
|
||||||
|
# jdnssec-signkeyset
|
||||||
|
|
||||||
|
# signkeyset.verify = false
|
||||||
|
# signkeyset.key_directory = .
|
||||||
|
# signkeyset.start = -300
|
||||||
|
# signkeyset.inception = 1712424863
|
||||||
|
# signkeyset.expire = +604800
|
||||||
|
|
||||||
|
# jdnssec-signrrset
|
||||||
|
|
||||||
|
# signrrset.verify_signatures = false
|
||||||
|
# signrrset.verify = false # same thing
|
||||||
|
# signrrset.key_directory = .
|
||||||
|
# signrrset.start = now
|
||||||
|
# signrrset.inception = now # same thing
|
||||||
|
# signrrset.expire = now+3600
|
||||||
|
|
||||||
|
# jdnssec-signzone
|
||||||
|
|
||||||
|
# signzone.verify_signatures = false
|
||||||
|
# signzone.verify = false # same thing
|
||||||
|
# signzone.use_nsec3 = false
|
||||||
|
# signzone.nsec3 = false # same thing
|
||||||
|
# signzone.use_opt_out = false
|
||||||
|
# signzone.opt_out = false # same thing
|
||||||
|
# signzone.verbose_signing = false
|
||||||
|
# signzone.fully_sign_keyset = false
|
||||||
|
# signzone.fully_sign = false # same thing
|
||||||
|
# signzone.key_directory = .
|
||||||
|
# signzone.keydir = . # same thing
|
||||||
|
# signzone.start = now
|
||||||
|
# signzone.inception = now
|
||||||
|
# signzone.expire = now+3600
|
||||||
|
# signzone.nsec3_salt = DEADBEEF
|
||||||
|
# signzone.salt = DEADBEEF # same thing
|
||||||
|
# signzone.nsec3_random_salt_length = 6
|
||||||
|
# signzone.nsec3_salt_length = 6 # same thing
|
||||||
|
# signzone.random_salt_length = 6 # same thing
|
||||||
|
# signzone.nsec3_iterations = 0
|
||||||
|
# signzone.iterations = 0 # same thing
|
||||||
|
# signzone.digest_algorithm = 4
|
||||||
|
# signzone.digest_id = 4 # same thing
|
||||||
|
# signzone.nsec3param_ttl = 86400
|
||||||
|
# signzone.include_names_file = /path/to/include-names
|
||||||
|
# signzone.include_names = /path/to/include-names # same thing
|
||||||
|
|
||||||
|
# jdnssec-verifyzone
|
||||||
|
|
||||||
|
# verifyzone.ignore_time = false
|
||||||
|
# verifyzone.ignore_duplicate_rrs = false
|
||||||
|
# verifyzone.ignore_duplicates = false # same thing
|
||||||
|
# verifyzone.start_fudge = 0
|
||||||
|
# verifyzone.expire_fudge = 0
|
||||||
|
# verifyzone.current_time = now
|
||||||
|
|
||||||
|
# jdnssec-zoneformat
|
||||||
|
|
||||||
|
# zoneformat.assign_nsec3_owners = false
|
||||||
|
# zoneformat.assign_owners = false # same thing
|
||||||
BIN
lib/bcprov-jdk18on-1.78.jar
Normal file
BIN
lib/bcprov-jdk18on-1.78.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
lib/commons-cli-1.6.0.jar
Normal file
BIN
lib/commons-cli-1.6.0.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
lib/dnsjava-3.5.3.jar
Normal file
BIN
lib/dnsjava-3.5.3.jar
Normal file
Binary file not shown.
BIN
lib/slf4j-api-1.7.36.jar
Normal file
BIN
lib/slf4j-api-1.7.36.jar
Normal file
Binary file not shown.
BIN
lib/slf4j-simple-1.7.36.jar
Normal file
BIN
lib/slf4j-simple-1.7.36.jar
Normal file
Binary file not shown.
21
licenses/slf4j-LICENSE.txt
Normal file
21
licenses/slf4j-LICENSE.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
Copyright (c) 2004-2017 QOS.ch
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
package com.verisignlabs.dnssec.cl;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.logging.Formatter;
|
|
||||||
import java.util.logging.Handler;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.LogRecord;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import org.apache.commons.cli.AlreadySelectedException;
|
|
||||||
import org.apache.commons.cli.CommandLine;
|
|
||||||
import org.apache.commons.cli.CommandLineParser;
|
|
||||||
import org.apache.commons.cli.HelpFormatter;
|
|
||||||
import org.apache.commons.cli.OptionBuilder;
|
|
||||||
import org.apache.commons.cli.Options;
|
|
||||||
import org.apache.commons.cli.ParseException;
|
|
||||||
import org.apache.commons.cli.PosixParser;
|
|
||||||
import org.apache.commons.cli.UnrecognizedOptionException;
|
|
||||||
|
|
||||||
import com.verisignlabs.dnssec.security.DnsKeyAlgorithm;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a base class for jdnssec command line tools. Each command line tool
|
|
||||||
* should inherit from this class, create a subclass of CLIStateBase (overriding
|
|
||||||
* setupOptions and processOptions), and implement the execute() method.
|
|
||||||
* Subclasses also have their own main() methods, which should just create the
|
|
||||||
* subclass variant of the CLIState and call run().
|
|
||||||
*/
|
|
||||||
public abstract class CLBase
|
|
||||||
{
|
|
||||||
protected static Logger log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a very simple log formatter that simply outputs the log level and
|
|
||||||
* log string.
|
|
||||||
*/
|
|
||||||
public static class BareLogFormatter extends Formatter
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public String format(LogRecord arg0)
|
|
||||||
{
|
|
||||||
StringBuilder out = new StringBuilder();
|
|
||||||
String lvl = arg0.getLevel().getName();
|
|
||||||
|
|
||||||
out.append(lvl);
|
|
||||||
out.append(": ");
|
|
||||||
out.append(arg0.getMessage());
|
|
||||||
out.append("\n");
|
|
||||||
|
|
||||||
return out.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a base class for command line parsing state. Subclasses should
|
|
||||||
* override setupOptions and processOptions.
|
|
||||||
*/
|
|
||||||
public static class CLIStateBase
|
|
||||||
{
|
|
||||||
protected Options opts;
|
|
||||||
protected String usageStr;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The base constructor. This will setup the command line options.
|
|
||||||
*
|
|
||||||
* @param usage
|
|
||||||
* The command line usage string (e.g.,
|
|
||||||
* "jdnssec-foo [..options..] zonefile")
|
|
||||||
*/
|
|
||||||
public CLIStateBase(String usage)
|
|
||||||
{
|
|
||||||
usageStr = usage;
|
|
||||||
setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** This is the base set of command line options provided to all subclasses. */
|
|
||||||
private void setup()
|
|
||||||
{
|
|
||||||
// Set up the standard set of options that all jdnssec command line tools will implement.
|
|
||||||
opts = new Options();
|
|
||||||
|
|
||||||
// boolean options
|
|
||||||
opts.addOption("h", "help", false, "Print this message.");
|
|
||||||
opts.addOption("m", "multiline", false,
|
|
||||||
"Output DNS records using 'multiline' format");
|
|
||||||
|
|
||||||
OptionBuilder.hasOptionalArg();
|
|
||||||
OptionBuilder.withLongOpt("verbose");
|
|
||||||
OptionBuilder.withArgName("level");
|
|
||||||
OptionBuilder.withDescription("verbosity level -- 0 is silence, 3 is info, "
|
|
||||||
+ "5 is debug information, 6 is trace information. default is level 2 (warning)");
|
|
||||||
opts.addOption(OptionBuilder.create('v'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("alias:original:mnemonic");
|
|
||||||
OptionBuilder.withLongOpt("alg-alias");
|
|
||||||
OptionBuilder.withDescription("Define an alias for an algorithm");
|
|
||||||
opts.addOption(OptionBuilder.create('A'));
|
|
||||||
|
|
||||||
setupOptions(opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an overridable method for subclasses to add their own command
|
|
||||||
* line options.
|
|
||||||
*
|
|
||||||
* @param opts
|
|
||||||
* the options object to add (via OptionBuilder, typically) new
|
|
||||||
* options to.
|
|
||||||
*/
|
|
||||||
protected void setupOptions(Options opts)
|
|
||||||
{
|
|
||||||
// Subclasses generally override this.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the main method for parsing the command line arguments.
|
|
||||||
* Subclasses generally override processOptions() rather than this method.
|
|
||||||
* This method create the parsing objects and processes the standard
|
|
||||||
* options.
|
|
||||||
*
|
|
||||||
* @param args
|
|
||||||
* The command line arguments.
|
|
||||||
* @throws ParseException
|
|
||||||
*/
|
|
||||||
public void parseCommandLine(String args[]) throws ParseException
|
|
||||||
{
|
|
||||||
CommandLineParser cli_parser = new PosixParser();
|
|
||||||
CommandLine cli = cli_parser.parse(opts, args);
|
|
||||||
|
|
||||||
if (cli.hasOption('h')) usage();
|
|
||||||
|
|
||||||
Logger rootLogger = Logger.getLogger("");
|
|
||||||
int value = parseInt(cli.getOptionValue('v'), -1);
|
|
||||||
|
|
||||||
switch (value)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
rootLogger.setLevel(Level.OFF);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
rootLogger.setLevel(Level.SEVERE);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
default:
|
|
||||||
rootLogger.setLevel(Level.WARNING);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
rootLogger.setLevel(Level.INFO);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
rootLogger.setLevel(Level.CONFIG);
|
|
||||||
case 5:
|
|
||||||
rootLogger.setLevel(Level.FINE);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
rootLogger.setLevel(Level.ALL);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// I hate java.util.logging, btw.
|
|
||||||
for (Handler h : rootLogger.getHandlers())
|
|
||||||
{
|
|
||||||
h.setLevel(rootLogger.getLevel());
|
|
||||||
h.setFormatter(new BareLogFormatter());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cli.hasOption('m'))
|
|
||||||
{
|
|
||||||
org.xbill.DNS.Options.set("multiline");
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] optstrs = null;
|
|
||||||
if ((optstrs = cli.getOptionValues('A')) != null)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < optstrs.length; i++)
|
|
||||||
{
|
|
||||||
addArgAlias(optstrs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processOptions(cli);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process additional tool-specific options. Subclasses generally override
|
|
||||||
* this.
|
|
||||||
*
|
|
||||||
* @param cli
|
|
||||||
* The {@link CommandLine} object containing the parsed command
|
|
||||||
* line state.
|
|
||||||
*/
|
|
||||||
protected void processOptions(CommandLine cli) throws ParseException
|
|
||||||
{
|
|
||||||
// Subclasses generally override this.
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Print out the usage and help statements, then quit. */
|
|
||||||
public void usage()
|
|
||||||
{
|
|
||||||
HelpFormatter f = new HelpFormatter();
|
|
||||||
|
|
||||||
PrintWriter out = new PrintWriter(System.err);
|
|
||||||
|
|
||||||
// print our own usage statement:
|
|
||||||
f.printHelp(out, 75, usageStr, null, opts, HelpFormatter.DEFAULT_LEFT_PAD,
|
|
||||||
HelpFormatter.DEFAULT_DESC_PAD, null);
|
|
||||||
|
|
||||||
out.flush();
|
|
||||||
System.exit(64);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void addArgAlias(String s)
|
|
||||||
{
|
|
||||||
if (s == null) return;
|
|
||||||
|
|
||||||
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
|
||||||
|
|
||||||
String[] v = s.split(":");
|
|
||||||
if (v.length < 2) return;
|
|
||||||
|
|
||||||
int alias = parseInt(v[0], -1);
|
|
||||||
if (alias <= 0) return;
|
|
||||||
int orig = parseInt(v[1], -1);
|
|
||||||
if (orig <= 0) return;
|
|
||||||
String mn = null;
|
|
||||||
if (v.length > 2) mn = v[2];
|
|
||||||
|
|
||||||
algs.addAlias(alias, mn, orig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int parseInt(String s, int def)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int v = Integer.parseInt(s);
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
catch (NumberFormatException e)
|
|
||||||
{
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate a date/time from a command line time/offset duration string.
|
|
||||||
*
|
|
||||||
* @param start
|
|
||||||
* the start time to calculate offsets from.
|
|
||||||
* @param duration
|
|
||||||
* the time/offset string to parse.
|
|
||||||
* @return the calculated time.
|
|
||||||
*/
|
|
||||||
public static Date convertDuration(Date start, String duration) throws ParseException
|
|
||||||
{
|
|
||||||
if (start == null) start = new Date();
|
|
||||||
if (duration.startsWith("now"))
|
|
||||||
{
|
|
||||||
start = new Date();
|
|
||||||
if (duration.indexOf("+") < 0) return start;
|
|
||||||
|
|
||||||
duration = duration.substring(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duration.startsWith("+"))
|
|
||||||
{
|
|
||||||
long offset = (long) parseInt(duration.substring(1), 0) * 1000;
|
|
||||||
return new Date(start.getTime() + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMddHHmmss");
|
|
||||||
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return dateFormatter.parse(duration);
|
|
||||||
}
|
|
||||||
catch (java.text.ParseException e)
|
|
||||||
{
|
|
||||||
throw new ParseException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void execute() throws Exception;
|
|
||||||
|
|
||||||
public void run(CLIStateBase state, String[] args)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
state.parseCommandLine(args);
|
|
||||||
}
|
|
||||||
catch (UnrecognizedOptionException e)
|
|
||||||
{
|
|
||||||
System.err.println("error: unknown option encountered: " + e.getMessage());
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
catch (AlreadySelectedException e)
|
|
||||||
{
|
|
||||||
System.err.println("error: mutually exclusive options have "
|
|
||||||
+ "been selected:\n " + e.getMessage());
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
System.err.println("error: unknown command line parsing exception:");
|
|
||||||
e.printStackTrace();
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
log = Logger.getLogger(this.getClass().toString());
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
execute();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
// Copyright (C) 2001-2003, 2011 VeriSign, Inc.
|
|
||||||
//
|
|
||||||
// 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.FileWriter;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
|
|
||||||
import org.apache.commons.cli.*;
|
|
||||||
import org.xbill.DNS.DLVRecord;
|
|
||||||
import org.xbill.DNS.DNSKEYRecord;
|
|
||||||
import org.xbill.DNS.DSRecord;
|
|
||||||
import org.xbill.DNS.Record;
|
|
||||||
|
|
||||||
import com.verisignlabs.dnssec.security.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class forms the command line implementation of a DNSSEC DS/DLV generator
|
|
||||||
*
|
|
||||||
* @author David Blacka
|
|
||||||
*/
|
|
||||||
public class DSTool extends CLBase
|
|
||||||
{
|
|
||||||
private CLIState state;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a small inner class used to hold all of the command line option
|
|
||||||
* state.
|
|
||||||
*/
|
|
||||||
protected static class CLIState extends CLIStateBase
|
|
||||||
{
|
|
||||||
public boolean createDLV = false;
|
|
||||||
public String outputfile = null;
|
|
||||||
public String keyname = null;
|
|
||||||
public int digest_id = DSRecord.SHA1_DIGEST_ID;
|
|
||||||
|
|
||||||
public CLIState()
|
|
||||||
{
|
|
||||||
super("jdnssec-dstool [..options..] keyfile");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the command line options.
|
|
||||||
*
|
|
||||||
* @return a set of command line options.
|
|
||||||
*/
|
|
||||||
protected void setupOptions(Options opts)
|
|
||||||
{
|
|
||||||
OptionBuilder.withLongOpt("dlv");
|
|
||||||
OptionBuilder.withDescription("Generate a DLV record instead.");
|
|
||||||
opts.addOption(OptionBuilder.create());
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withLongOpt("digest");
|
|
||||||
OptionBuilder.withArgName("id");
|
|
||||||
OptionBuilder.withDescription("The Digest ID to use (numerically): either 1 for SHA1 or 2 for SHA256");
|
|
||||||
opts.addOption(OptionBuilder.create('d'));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void processOptions(CommandLine cli)
|
|
||||||
throws org.apache.commons.cli.ParseException
|
|
||||||
{
|
|
||||||
outputfile = cli.getOptionValue('f');
|
|
||||||
createDLV = cli.hasOption("dlv");
|
|
||||||
String optstr = cli.getOptionValue('d');
|
|
||||||
if (optstr != null) digest_id = parseInt(optstr, digest_id);
|
|
||||||
|
|
||||||
String[] cl_args = cli.getArgs();
|
|
||||||
|
|
||||||
if (cl_args.length < 1)
|
|
||||||
{
|
|
||||||
System.err.println("error: missing key file ");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
keyname = cl_args[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void execute() throws Exception
|
|
||||||
{
|
|
||||||
DnsKeyPair key = BINDKeyUtils.loadKey(state.keyname, null);
|
|
||||||
DNSKEYRecord dnskey = key.getDNSKEYRecord();
|
|
||||||
|
|
||||||
if ((dnskey.getFlags() & DNSKEYRecord.Flags.SEP_KEY) == 0)
|
|
||||||
{
|
|
||||||
log.warning("DNSKEY is not an SEP-flagged key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
DSRecord ds = SignUtils.calculateDSRecord(dnskey, state.digest_id, dnskey.getTTL());
|
|
||||||
Record res = ds;
|
|
||||||
|
|
||||||
if (state.createDLV)
|
|
||||||
{
|
|
||||||
log.fine("creating DLV.");
|
|
||||||
DLVRecord dlv = new DLVRecord(ds.getName(), ds.getDClass(), ds.getTTL(),
|
|
||||||
ds.getFootprint(), ds.getAlgorithm(),
|
|
||||||
ds.getDigestID(), ds.getDigest());
|
|
||||||
res = dlv;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.outputfile != null)
|
|
||||||
{
|
|
||||||
PrintWriter out = new PrintWriter(new FileWriter(state.outputfile));
|
|
||||||
out.println(res);
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
System.out.println(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
DSTool tool = new DSTool();
|
|
||||||
tool.state = new CLIState();
|
|
||||||
|
|
||||||
tool.run(tool.state, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
// Copyright (C) 2001-2003, 2011 VeriSign, Inc.
|
|
||||||
//
|
|
||||||
// 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 org.apache.commons.cli.*;
|
|
||||||
import org.xbill.DNS.DClass;
|
|
||||||
import org.xbill.DNS.DNSKEYRecord;
|
|
||||||
import org.xbill.DNS.Name;
|
|
||||||
|
|
||||||
import com.verisignlabs.dnssec.security.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class forms the command line implementation of a DNSSEC key generator
|
|
||||||
*
|
|
||||||
* @author David Blacka
|
|
||||||
*/
|
|
||||||
public class KeyGen extends CLBase
|
|
||||||
{
|
|
||||||
private CLIState state;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a small inner class used to hold all of the command line option
|
|
||||||
* state.
|
|
||||||
*/
|
|
||||||
protected static class CLIState extends CLIStateBase
|
|
||||||
{
|
|
||||||
public int algorithm = 8;
|
|
||||||
public int keylength = 1024;
|
|
||||||
public boolean useLargeE = true;
|
|
||||||
public String outputfile = null;
|
|
||||||
public File keydir = null;
|
|
||||||
public boolean zoneKey = true;
|
|
||||||
public boolean kskFlag = false;
|
|
||||||
public String owner = null;
|
|
||||||
public long ttl = 86400;
|
|
||||||
|
|
||||||
public CLIState()
|
|
||||||
{
|
|
||||||
super("jdnssec-keygen [..options..] name");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the command line options.
|
|
||||||
*/
|
|
||||||
protected void setupOptions(Options opts)
|
|
||||||
{
|
|
||||||
// boolean options
|
|
||||||
opts.addOption("k", "kskflag", false,
|
|
||||||
"Key is a key-signing-key (sets the SEP flag).");
|
|
||||||
opts.addOption("e", "large-exponent", false, "Use large RSA exponent (default)");
|
|
||||||
opts.addOption("E", "small-exponent", false, "Use small RSA exponent");
|
|
||||||
|
|
||||||
// Argument options
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withLongOpt("nametype");
|
|
||||||
OptionBuilder.withArgName("type");
|
|
||||||
OptionBuilder.withDescription("ZONE | OTHER (default ZONE)");
|
|
||||||
opts.addOption(OptionBuilder.create('n'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("algorithm");
|
|
||||||
OptionBuilder.withDescription("RSA | RSASHA1 | RSAMD5 | DH | DSA "
|
|
||||||
+ "| RSA-NSEC3-SHA1 | DSA-NSEC3-SHA1 "
|
|
||||||
+ "| RSASHA256 | RSASHA512 | alias, RSASHA1 is default.");
|
|
||||||
opts.addOption(OptionBuilder.create('a'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("size");
|
|
||||||
OptionBuilder.withDescription("key size, in bits. (default = 1024)\n"
|
|
||||||
+ "RSA: [512..4096]\n" + "DSA: [512..1024]\n" + "DH: [128..4096]");
|
|
||||||
opts.addOption(OptionBuilder.create('b'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("file");
|
|
||||||
OptionBuilder.withLongOpt("output-file");
|
|
||||||
OptionBuilder.withDescription("base filename for the public/private key files");
|
|
||||||
opts.addOption(OptionBuilder.create('f'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withLongOpt("keydir");
|
|
||||||
OptionBuilder.withArgName("dir");
|
|
||||||
OptionBuilder.withDescription("place generated key files in this " + "directory");
|
|
||||||
opts.addOption(OptionBuilder.create('d'));
|
|
||||||
opts.addOption(OptionBuilder.create('A'));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void processOptions(CommandLine cli)
|
|
||||||
throws org.apache.commons.cli.ParseException
|
|
||||||
{
|
|
||||||
String optstr = null;
|
|
||||||
String[] optstrs = null;
|
|
||||||
|
|
||||||
if (cli.hasOption('k')) kskFlag = true;
|
|
||||||
if (cli.hasOption('e')) useLargeE = true;
|
|
||||||
|
|
||||||
outputfile = cli.getOptionValue('f');
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('d')) != null)
|
|
||||||
{
|
|
||||||
keydir = new File(optstr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('n')) != null)
|
|
||||||
{
|
|
||||||
if (!optstr.equalsIgnoreCase("ZONE"))
|
|
||||||
{
|
|
||||||
zoneKey = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstrs = cli.getOptionValues('A')) != null)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < optstrs.length; i++)
|
|
||||||
{
|
|
||||||
addArgAlias(optstrs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('a')) != null)
|
|
||||||
{
|
|
||||||
algorithm = parseAlg(optstr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('b')) != null)
|
|
||||||
{
|
|
||||||
keylength = parseInt(optstr, 1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue("ttl")) != null)
|
|
||||||
{
|
|
||||||
ttl = parseInt(optstr, 86400);
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] cl_args = cli.getArgs();
|
|
||||||
|
|
||||||
if (cl_args.length < 1)
|
|
||||||
{
|
|
||||||
System.err.println("error: missing key owner name");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
owner = cl_args[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static int parseAlg(String s)
|
|
||||||
{
|
|
||||||
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
|
||||||
|
|
||||||
int alg = parseInt(s, -1);
|
|
||||||
if (alg > 0) return alg;
|
|
||||||
|
|
||||||
return algs.stringToAlgorithm(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void execute() throws Exception
|
|
||||||
{
|
|
||||||
JCEDnsSecSigner signer = new JCEDnsSecSigner();
|
|
||||||
|
|
||||||
// Minor hack to make the owner name absolute.
|
|
||||||
if (!state.owner.endsWith("."))
|
|
||||||
{
|
|
||||||
state.owner = state.owner + ".";
|
|
||||||
}
|
|
||||||
|
|
||||||
Name owner_name = Name.fromString(state.owner);
|
|
||||||
|
|
||||||
// Calculate our flags
|
|
||||||
int flags = 0;
|
|
||||||
if (state.zoneKey) flags |= DNSKEYRecord.Flags.ZONE_KEY;
|
|
||||||
if (state.kskFlag) flags |= DNSKEYRecord.Flags.SEP_KEY;
|
|
||||||
|
|
||||||
log.fine("create key pair with (name = " + owner_name + ", ttl = " + state.ttl
|
|
||||||
+ ", alg = " + state.algorithm + ", flags = " + flags + ", length = "
|
|
||||||
+ state.keylength + ")");
|
|
||||||
|
|
||||||
DnsKeyPair pair = signer.generateKey(owner_name, state.ttl, DClass.IN,
|
|
||||||
state.algorithm, flags, state.keylength,
|
|
||||||
state.useLargeE);
|
|
||||||
|
|
||||||
if (state.outputfile != null)
|
|
||||||
{
|
|
||||||
BINDKeyUtils.writeKeyFiles(state.outputfile, pair, state.keydir);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
BINDKeyUtils.writeKeyFiles(pair, state.keydir);
|
|
||||||
System.out.println(BINDKeyUtils.keyFileBase(pair));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
KeyGen tool = new KeyGen();
|
|
||||||
tool.state = new CLIState();
|
|
||||||
|
|
||||||
tool.run(tool.state, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
// Copyright (C) 2001-2003, 2011 VeriSign, Inc.
|
|
||||||
//
|
|
||||||
// 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.security.interfaces.DSAPublicKey;
|
|
||||||
import java.security.interfaces.RSAPublicKey;
|
|
||||||
|
|
||||||
import org.apache.commons.cli.*;
|
|
||||||
import org.xbill.DNS.DNSKEYRecord;
|
|
||||||
|
|
||||||
import com.verisignlabs.dnssec.security.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class forms the command line implementation of a key introspection tool.
|
|
||||||
*
|
|
||||||
* @author David Blacka
|
|
||||||
*/
|
|
||||||
public class KeyInfoTool extends CLBase
|
|
||||||
{
|
|
||||||
private CLIState state;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a small inner class used to hold all of the command line option
|
|
||||||
* state.
|
|
||||||
*/
|
|
||||||
protected static class CLIState extends CLIStateBase
|
|
||||||
{
|
|
||||||
public String[] keynames = null;
|
|
||||||
|
|
||||||
public CLIState()
|
|
||||||
{
|
|
||||||
super("jdnssec-keyinfo [..options..] keyfile");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the command line options.
|
|
||||||
*/
|
|
||||||
protected void setupOptions(Options opts)
|
|
||||||
{
|
|
||||||
// no special options at the moment.
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void processOptions(CommandLine cli) throws ParseException
|
|
||||||
{
|
|
||||||
keynames = cli.getArgs();
|
|
||||||
|
|
||||||
if (keynames.length < 1)
|
|
||||||
{
|
|
||||||
System.err.println("error: missing key file ");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void execute() throws Exception
|
|
||||||
{
|
|
||||||
for (int i = 0; i < state.keynames.length; ++i)
|
|
||||||
{
|
|
||||||
String keyname = state.keynames[i];
|
|
||||||
DnsKeyPair key = BINDKeyUtils.loadKey(keyname, null);
|
|
||||||
DNSKEYRecord dnskey = key.getDNSKEYRecord();
|
|
||||||
DnsKeyAlgorithm dnskeyalg = DnsKeyAlgorithm.getInstance();
|
|
||||||
|
|
||||||
boolean isSEP = (dnskey.getFlags() & DNSKEYRecord.Flags.SEP_KEY) != 0;
|
|
||||||
|
|
||||||
System.out.println(keyname + ":");
|
|
||||||
System.out.println("Name: " + dnskey.getName());
|
|
||||||
System.out.println("SEP: " + isSEP);
|
|
||||||
|
|
||||||
System.out.println("Algorithm: " + dnskeyalg.algToString(dnskey.getAlgorithm())
|
|
||||||
+ " (" + dnskey.getAlgorithm() + ")");
|
|
||||||
System.out.println("ID: " + dnskey.getFootprint());
|
|
||||||
System.out.println("KeyFileBase: " + BINDKeyUtils.keyFileBase(key));
|
|
||||||
int basetype = dnskeyalg.baseType(dnskey.getAlgorithm());
|
|
||||||
switch (basetype)
|
|
||||||
{
|
|
||||||
case DnsKeyAlgorithm.RSA: {
|
|
||||||
RSAPublicKey pub = (RSAPublicKey) key.getPublic();
|
|
||||||
System.out.println("RSA Public Exponent: " + pub.getPublicExponent());
|
|
||||||
System.out.println("RSA Modulus: " + pub.getModulus());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DnsKeyAlgorithm.DSA: {
|
|
||||||
DSAPublicKey pub = (DSAPublicKey) key.getPublic();
|
|
||||||
System.out.println("DSA base (G): " + pub.getParams().getG());
|
|
||||||
System.out.println("DSA prime (P): " + pub.getParams().getP());
|
|
||||||
System.out.println("DSA subprime (Q): " + pub.getParams().getQ());
|
|
||||||
System.out.println("DSA public (Y): " + pub.getY());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (state.keynames.length - i > 1)
|
|
||||||
{
|
|
||||||
System.out.println();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
KeyInfoTool tool = new KeyInfoTool();
|
|
||||||
tool.state = new CLIState();
|
|
||||||
|
|
||||||
tool.run(tool.state, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,394 +0,0 @@
|
|||||||
// Copyright (C) 2001-2003, 2011 VeriSign, Inc.
|
|
||||||
//
|
|
||||||
// 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;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.commons.cli.CommandLine;
|
|
||||||
import org.apache.commons.cli.OptionBuilder;
|
|
||||||
import org.apache.commons.cli.Options;
|
|
||||||
import org.xbill.DNS.DNSSEC;
|
|
||||||
import org.xbill.DNS.Name;
|
|
||||||
import org.xbill.DNS.RRSIGRecord;
|
|
||||||
import org.xbill.DNS.RRset;
|
|
||||||
import org.xbill.DNS.Record;
|
|
||||||
import org.xbill.DNS.Type;
|
|
||||||
|
|
||||||
import com.verisignlabs.dnssec.security.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @author David Blacka
|
|
||||||
*/
|
|
||||||
public class SignKeyset extends CLBase
|
|
||||||
{
|
|
||||||
private CLIState state;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an inner class used to hold all of the command line option state.
|
|
||||||
*/
|
|
||||||
protected static class CLIState extends CLIStateBase
|
|
||||||
{
|
|
||||||
public File keyDirectory = null;
|
|
||||||
public String[] keyFiles = null;
|
|
||||||
public Date start = null;
|
|
||||||
public Date expire = null;
|
|
||||||
public String inputfile = null;
|
|
||||||
public String outputfile = null;
|
|
||||||
public boolean verifySigs = false;
|
|
||||||
|
|
||||||
public CLIState()
|
|
||||||
{
|
|
||||||
super("jdnssec-signkeyset [..options..] dnskeyset_file [key_file ...]");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the command line options.
|
|
||||||
*/
|
|
||||||
protected void setupOptions(Options opts)
|
|
||||||
{
|
|
||||||
// boolean options
|
|
||||||
opts.addOption("a", "verify", false, "verify generated signatures>");
|
|
||||||
|
|
||||||
// Argument options
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("dir");
|
|
||||||
OptionBuilder.withLongOpt("key-directory");
|
|
||||||
OptionBuilder.withDescription("directory to find key files (default '.').");
|
|
||||||
opts.addOption(OptionBuilder.create('D'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("time/offset");
|
|
||||||
OptionBuilder.withLongOpt("start-time");
|
|
||||||
OptionBuilder.withDescription("signature starting time (default is now - 1 hour)");
|
|
||||||
opts.addOption(OptionBuilder.create('s'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("time/offset");
|
|
||||||
OptionBuilder.withLongOpt("expire-time");
|
|
||||||
OptionBuilder.withDescription("signature expiration time (default is start-time + 30 days).");
|
|
||||||
opts.addOption(OptionBuilder.create('e'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("outfile");
|
|
||||||
OptionBuilder.withDescription("file the signed keyset is written to.");
|
|
||||||
opts.addOption(OptionBuilder.create('f'));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void processOptions(CommandLine cli)
|
|
||||||
throws org.apache.commons.cli.ParseException
|
|
||||||
{
|
|
||||||
String optstr = null;
|
|
||||||
|
|
||||||
if (cli.hasOption('a')) verifySigs = true;
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('D')) != null)
|
|
||||||
{
|
|
||||||
keyDirectory = new File(optstr);
|
|
||||||
if (!keyDirectory.isDirectory())
|
|
||||||
{
|
|
||||||
System.err.println("error: " + optstr + " is not a directory");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('s')) != null)
|
|
||||||
{
|
|
||||||
start = convertDuration(null, optstr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// default is now - 1 hour.
|
|
||||||
start = new Date(System.currentTimeMillis() - (3600 * 1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('e')) != null)
|
|
||||||
{
|
|
||||||
expire = convertDuration(start, optstr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
expire = convertDuration(start, "+2592000"); // 30 days
|
|
||||||
}
|
|
||||||
|
|
||||||
outputfile = cli.getOptionValue('f');
|
|
||||||
|
|
||||||
String[] files = cli.getArgs();
|
|
||||||
|
|
||||||
if (files.length < 1)
|
|
||||||
{
|
|
||||||
System.err.println("error: missing zone file and/or key files");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
inputfile = files[0];
|
|
||||||
if (files.length > 1)
|
|
||||||
{
|
|
||||||
keyFiles = new String[files.length - 1];
|
|
||||||
System.arraycopy(files, 1, keyFiles, 0, files.length - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify the generated signatures.
|
|
||||||
*
|
|
||||||
* @param zonename
|
|
||||||
* the origin name of the zone.
|
|
||||||
* @param records
|
|
||||||
* a list of {@link org.xbill.DNS.Record}s.
|
|
||||||
* @param keypairs
|
|
||||||
* a list of keypairs used the sign the zone.
|
|
||||||
* @return true if all of the signatures validated.
|
|
||||||
*/
|
|
||||||
private static boolean verifySigs(Name zonename, List<Record> records,
|
|
||||||
List<DnsKeyPair> keypairs)
|
|
||||||
{
|
|
||||||
boolean secure = true;
|
|
||||||
|
|
||||||
DnsSecVerifier verifier = new DnsSecVerifier();
|
|
||||||
|
|
||||||
for (DnsKeyPair pair : keypairs)
|
|
||||||
{
|
|
||||||
verifier.addTrustedKey(pair);
|
|
||||||
}
|
|
||||||
|
|
||||||
verifier.setVerifyAllSigs(true);
|
|
||||||
|
|
||||||
List<RRset> rrsets = SignUtils.assembleIntoRRsets(records);
|
|
||||||
|
|
||||||
for (RRset rrset : rrsets)
|
|
||||||
{
|
|
||||||
// skip unsigned rrsets.
|
|
||||||
if (!rrset.sigs().hasNext()) continue;
|
|
||||||
|
|
||||||
int result = verifier.verify(rrset, null);
|
|
||||||
|
|
||||||
if (result != DNSSEC.Secure)
|
|
||||||
{
|
|
||||||
log.fine("Signatures did not verify for RRset: (" + result + "): " + rrset);
|
|
||||||
secure = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return secure;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the key pairs from the key files.
|
|
||||||
*
|
|
||||||
* @param keyfiles
|
|
||||||
* a string array containing the base names or paths of the keys
|
|
||||||
* to be loaded.
|
|
||||||
* @param start_index
|
|
||||||
* the starting index of keyfiles string array to use. This
|
|
||||||
* allows us to use the straight command line argument array.
|
|
||||||
* @param inDirectory
|
|
||||||
* the directory to look in (may be null).
|
|
||||||
* @return a list of keypair objects.
|
|
||||||
*/
|
|
||||||
private static List<DnsKeyPair> getKeys(String[] keyfiles, int start_index,
|
|
||||||
File inDirectory) throws IOException
|
|
||||||
{
|
|
||||||
if (keyfiles == null) return null;
|
|
||||||
|
|
||||||
int len = keyfiles.length - start_index;
|
|
||||||
if (len <= 0) return null;
|
|
||||||
|
|
||||||
ArrayList<DnsKeyPair> keys = new ArrayList<DnsKeyPair>(len);
|
|
||||||
|
|
||||||
for (int i = start_index; i < keyfiles.length; i++)
|
|
||||||
{
|
|
||||||
DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory);
|
|
||||||
if (k != null) keys.add(k);
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class KeyFileFilter implements FileFilter
|
|
||||||
{
|
|
||||||
private String prefix;
|
|
||||||
|
|
||||||
public KeyFileFilter(Name origin)
|
|
||||||
{
|
|
||||||
prefix = "K" + origin.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean accept(File pathname)
|
|
||||||
{
|
|
||||||
if (!pathname.isFile()) return false;
|
|
||||||
String name = pathname.getName();
|
|
||||||
if (name.startsWith(prefix) && name.endsWith(".private")) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<DnsKeyPair> findZoneKeys(File inDirectory, Name zonename)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
if (inDirectory == null)
|
|
||||||
{
|
|
||||||
inDirectory = new File(".");
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the list of "K<zone>.*.private files.
|
|
||||||
FileFilter filter = new KeyFileFilter(zonename);
|
|
||||||
File[] files = inDirectory.listFiles(filter);
|
|
||||||
|
|
||||||
// read in all of the records
|
|
||||||
ArrayList<DnsKeyPair> keys = new ArrayList<DnsKeyPair>();
|
|
||||||
for (int i = 0; i < files.length; i++)
|
|
||||||
{
|
|
||||||
DnsKeyPair p = BINDKeyUtils.loadKeyPair(files[i].getName(), inDirectory);
|
|
||||||
keys.add(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keys.size() > 0) return keys;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void execute() throws Exception
|
|
||||||
{
|
|
||||||
// Read in the zone
|
|
||||||
List<Record> records = ZoneUtils.readZoneFile(state.inputfile, null);
|
|
||||||
if (records == null || records.size() == 0)
|
|
||||||
{
|
|
||||||
System.err.println("error: empty keyset file");
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure that all records are DNSKEYs with the same name.
|
|
||||||
Name keysetName = null;
|
|
||||||
RRset keyset = new RRset();
|
|
||||||
|
|
||||||
for (Record r : records)
|
|
||||||
{
|
|
||||||
if (r.getType() != Type.DNSKEY)
|
|
||||||
{
|
|
||||||
System.err.println("error: Non DNSKEY RR found in keyset: " + r);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (keysetName == null)
|
|
||||||
{
|
|
||||||
keysetName = r.getName();
|
|
||||||
}
|
|
||||||
if (!r.getName().equals(keysetName))
|
|
||||||
{
|
|
||||||
System.err.println("error: DNSKEY with a different name found!");
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
keyset.addRR(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyset.size() == 0)
|
|
||||||
{
|
|
||||||
System.err.println("error: No DNSKEYs found in keyset file");
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the key pairs.
|
|
||||||
List<DnsKeyPair> keypairs = getKeys(state.keyFiles, 0, state.keyDirectory);
|
|
||||||
|
|
||||||
// If we *still* don't have any key pairs, look for keys the key
|
|
||||||
// directory
|
|
||||||
// that match
|
|
||||||
if (keypairs == null)
|
|
||||||
{
|
|
||||||
keypairs = findZoneKeys(state.keyDirectory, keysetName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there *still* aren't any ZSKs defined, bail.
|
|
||||||
if (keypairs == null || keypairs.size() == 0)
|
|
||||||
{
|
|
||||||
System.err.println("error: No signing keys could be determined.");
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// default the output file, if not set.
|
|
||||||
if (state.outputfile == null)
|
|
||||||
{
|
|
||||||
if (keysetName.isAbsolute())
|
|
||||||
{
|
|
||||||
state.outputfile = keysetName + "signed_keyset";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state.outputfile = keysetName + ".signed_keyset";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JCEDnsSecSigner signer = new JCEDnsSecSigner();
|
|
||||||
|
|
||||||
List<RRSIGRecord> sigs = signer.signRRset(keyset, keypairs, state.start, state.expire);
|
|
||||||
for (RRSIGRecord s : sigs)
|
|
||||||
{
|
|
||||||
keyset.addRR(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// write out the signed RRset
|
|
||||||
List<Record> signed_records = new ArrayList<Record>();
|
|
||||||
for (Iterator<Record> i = keyset.rrs(); i.hasNext();)
|
|
||||||
{
|
|
||||||
signed_records.add(i.next());
|
|
||||||
}
|
|
||||||
for (Iterator<Record> i = keyset.sigs(); i.hasNext();)
|
|
||||||
{
|
|
||||||
signed_records.add(i.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
// write out the signed zone
|
|
||||||
ZoneUtils.writeZoneFile(signed_records, state.outputfile);
|
|
||||||
|
|
||||||
if (state.verifySigs)
|
|
||||||
{
|
|
||||||
log.fine("verifying generated signatures");
|
|
||||||
boolean res = verifySigs(keysetName, signed_records, keypairs);
|
|
||||||
|
|
||||||
if (res)
|
|
||||||
{
|
|
||||||
System.out.println("Generated signatures verified");
|
|
||||||
// log.info("Generated signatures verified");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
System.out.println("Generated signatures did not verify.");
|
|
||||||
// log.warn("Generated signatures did not verify.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
SignKeyset tool = new SignKeyset();
|
|
||||||
tool.state = new CLIState();
|
|
||||||
|
|
||||||
tool.run(tool.state, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,362 +0,0 @@
|
|||||||
// Copyright (C) 2001-2003, 2011 VeriSign, Inc.
|
|
||||||
//
|
|
||||||
// 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.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.commons.cli.CommandLine;
|
|
||||||
import org.apache.commons.cli.OptionBuilder;
|
|
||||||
import org.apache.commons.cli.Options;
|
|
||||||
|
|
||||||
import org.xbill.DNS.DNSSEC;
|
|
||||||
import org.xbill.DNS.Name;
|
|
||||||
import org.xbill.DNS.RRSIGRecord;
|
|
||||||
import org.xbill.DNS.RRset;
|
|
||||||
import org.xbill.DNS.Record;
|
|
||||||
import org.xbill.DNS.Type;
|
|
||||||
|
|
||||||
import com.verisignlabs.dnssec.security.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class forms the command line implementation of a DNSSEC RRset signer.
|
|
||||||
* Instead of being able to sign an entire zone, it will just sign a given
|
|
||||||
* RRset. Note that it will sign any RRset with any private key without
|
|
||||||
* consideration of whether or not the RRset *should* be signed in the context
|
|
||||||
* of a zone.
|
|
||||||
*
|
|
||||||
* @author David Blacka
|
|
||||||
*/
|
|
||||||
public class SignRRset extends CLBase
|
|
||||||
{
|
|
||||||
private CLIState state;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an inner class used to hold all of the command line option state.
|
|
||||||
*/
|
|
||||||
protected static class CLIState extends CLIStateBase
|
|
||||||
{
|
|
||||||
private File keyDirectory = null;
|
|
||||||
public String[] keyFiles = null;
|
|
||||||
public Date start = null;
|
|
||||||
public Date expire = null;
|
|
||||||
public String inputfile = null;
|
|
||||||
public String outputfile = null;
|
|
||||||
public boolean verifySigs = false;
|
|
||||||
|
|
||||||
public CLIState()
|
|
||||||
{
|
|
||||||
super("jdnssec-signrrset [..options..] rrset_file key_file [key_file ...]");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the command line options.
|
|
||||||
*/
|
|
||||||
protected void setupOptions(Options opts)
|
|
||||||
{
|
|
||||||
// boolean options
|
|
||||||
opts.addOption("a", "verify", false, "verify generated signatures>");
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("dir");
|
|
||||||
OptionBuilder.withLongOpt("key-directory");
|
|
||||||
OptionBuilder.withDescription("directory to find key files (default '.').");
|
|
||||||
opts.addOption(OptionBuilder.create('D'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("time/offset");
|
|
||||||
OptionBuilder.withLongOpt("start-time");
|
|
||||||
OptionBuilder.withDescription("signature starting time (default is now - 1 hour)");
|
|
||||||
opts.addOption(OptionBuilder.create('s'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("time/offset");
|
|
||||||
OptionBuilder.withLongOpt("expire-time");
|
|
||||||
OptionBuilder.withDescription("signature expiration time (default is start-time + 30 days).");
|
|
||||||
opts.addOption(OptionBuilder.create('e'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("outfile");
|
|
||||||
OptionBuilder.withDescription("file the signed rrset is written to.");
|
|
||||||
opts.addOption(OptionBuilder.create('f'));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void processOptions(CommandLine cli) throws org.apache.commons.cli.ParseException
|
|
||||||
{
|
|
||||||
String optstr = null;
|
|
||||||
|
|
||||||
if (cli.hasOption('a')) verifySigs = true;
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('D')) != null)
|
|
||||||
{
|
|
||||||
keyDirectory = new File(optstr);
|
|
||||||
if (!keyDirectory.isDirectory())
|
|
||||||
{
|
|
||||||
System.err.println("error: " + optstr + " is not a directory");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('s')) != null)
|
|
||||||
{
|
|
||||||
start = convertDuration(null, optstr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// default is now - 1 hour.
|
|
||||||
start = new Date(System.currentTimeMillis() - (3600 * 1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('e')) != null)
|
|
||||||
{
|
|
||||||
expire = convertDuration(start, optstr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
expire = convertDuration(start, "+2592000"); // 30 days
|
|
||||||
}
|
|
||||||
|
|
||||||
outputfile = cli.getOptionValue('f');
|
|
||||||
|
|
||||||
String[] files = cli.getArgs();
|
|
||||||
|
|
||||||
if (files.length < 1)
|
|
||||||
{
|
|
||||||
System.err.println("error: missing zone file and/or key files");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
inputfile = files[0];
|
|
||||||
if (files.length > 1)
|
|
||||||
{
|
|
||||||
keyFiles = new String[files.length - 1];
|
|
||||||
System.arraycopy(files, 1, keyFiles, 0, files.length - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify the generated signatures.
|
|
||||||
*
|
|
||||||
* @param zonename
|
|
||||||
* the origin name of the zone.
|
|
||||||
* @param records
|
|
||||||
* a list of {@link org.xbill.DNS.Record}s.
|
|
||||||
* @param keypairs
|
|
||||||
* a list of keypairs used the sign the zone.
|
|
||||||
* @return true if all of the signatures validated.
|
|
||||||
*/
|
|
||||||
private static boolean verifySigs(Name zonename, List<Record> records, List<DnsKeyPair> keypairs)
|
|
||||||
{
|
|
||||||
boolean secure = true;
|
|
||||||
|
|
||||||
DnsSecVerifier verifier = new DnsSecVerifier();
|
|
||||||
|
|
||||||
for (DnsKeyPair pair : keypairs)
|
|
||||||
{
|
|
||||||
verifier.addTrustedKey(pair);
|
|
||||||
}
|
|
||||||
|
|
||||||
verifier.setVerifyAllSigs(true);
|
|
||||||
|
|
||||||
List<RRset> rrsets = SignUtils.assembleIntoRRsets(records);
|
|
||||||
|
|
||||||
for (RRset rrset : rrsets)
|
|
||||||
{
|
|
||||||
// skip unsigned rrsets.
|
|
||||||
if (!rrset.sigs().hasNext()) continue;
|
|
||||||
|
|
||||||
int result = verifier.verify(rrset, null);
|
|
||||||
|
|
||||||
if (result != DNSSEC.Secure)
|
|
||||||
{
|
|
||||||
log.fine("Signatures did not verify for RRset: (" + result + "): " + rrset);
|
|
||||||
secure = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return secure;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the key pairs from the key files.
|
|
||||||
*
|
|
||||||
* @param keyfiles
|
|
||||||
* a string array containing the base names or paths of the keys
|
|
||||||
* to be loaded.
|
|
||||||
* @param start_index
|
|
||||||
* the starting index of keyfiles string array to use. This
|
|
||||||
* allows us to use the straight command line argument array.
|
|
||||||
* @param inDirectory
|
|
||||||
* the directory to look in (may be null).
|
|
||||||
* @return a list of keypair objects.
|
|
||||||
*/
|
|
||||||
private static List<DnsKeyPair> getKeys(String[] keyfiles, int start_index,
|
|
||||||
File inDirectory) throws IOException
|
|
||||||
{
|
|
||||||
if (keyfiles == null) return null;
|
|
||||||
|
|
||||||
int len = keyfiles.length - start_index;
|
|
||||||
if (len <= 0) return null;
|
|
||||||
|
|
||||||
ArrayList<DnsKeyPair> keys = new ArrayList<DnsKeyPair>(len);
|
|
||||||
|
|
||||||
for (int i = start_index; i < keyfiles.length; i++)
|
|
||||||
{
|
|
||||||
DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory);
|
|
||||||
if (k != null) keys.add(k);
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void execute() throws Exception
|
|
||||||
{
|
|
||||||
// Read in the zone
|
|
||||||
List<Record> records = ZoneUtils.readZoneFile(state.inputfile, null);
|
|
||||||
if (records == null || records.size() == 0)
|
|
||||||
{
|
|
||||||
System.err.println("error: empty RRset file");
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
// Construct the RRset. Complain if the records in the input file
|
|
||||||
// consist of more than one RRset.
|
|
||||||
RRset rrset = null;
|
|
||||||
|
|
||||||
for (Record r : records)
|
|
||||||
{
|
|
||||||
// skip RRSIGs
|
|
||||||
if (r.getType() == Type.RRSIG || r.getType() == Type.SIG)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the first record.
|
|
||||||
if (rrset == null)
|
|
||||||
{
|
|
||||||
rrset = new RRset();
|
|
||||||
rrset.addRR(r);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Ensure that the remaining records all belong to the same rrset.
|
|
||||||
if (rrset.getName().equals(r.getName()) && rrset.getType() == r.getType()
|
|
||||||
&& rrset.getDClass() == r.getDClass())
|
|
||||||
{
|
|
||||||
rrset.addRR(r);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
System.err.println("Records do not all belong to the same RRset.");
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rrset.size() == 0)
|
|
||||||
{
|
|
||||||
System.err.println("No records found in inputfile.");
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the key pairs.
|
|
||||||
|
|
||||||
if (state.keyFiles.length == 0)
|
|
||||||
{
|
|
||||||
System.err.println("error: at least one keyfile must be specified");
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DnsKeyPair> keypairs = getKeys(state.keyFiles, 0, state.keyDirectory);
|
|
||||||
|
|
||||||
// Make sure that all the keypairs have the same name.
|
|
||||||
// This will be used as the zone name, too.
|
|
||||||
|
|
||||||
Name keysetName = null;
|
|
||||||
for (DnsKeyPair pair : keypairs)
|
|
||||||
{
|
|
||||||
if (keysetName == null)
|
|
||||||
{
|
|
||||||
keysetName = pair.getDNSKEYName();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!pair.getDNSKEYName().equals(keysetName))
|
|
||||||
{
|
|
||||||
System.err.println("Keys do not all have the same name.");
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// default the output file, if not set.
|
|
||||||
if (state.outputfile == null && !state.inputfile.equals("-"))
|
|
||||||
{
|
|
||||||
state.outputfile = state.inputfile + ".signed";
|
|
||||||
}
|
|
||||||
|
|
||||||
JCEDnsSecSigner signer = new JCEDnsSecSigner();
|
|
||||||
|
|
||||||
List<RRSIGRecord> sigs = signer.signRRset(rrset, keypairs, state.start, state.expire);
|
|
||||||
for (RRSIGRecord s : sigs)
|
|
||||||
{
|
|
||||||
rrset.addRR(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// write out the signed RRset
|
|
||||||
List<Record> signed_records = new ArrayList<Record>();
|
|
||||||
for (Iterator<Record> i = rrset.rrs(); i.hasNext();)
|
|
||||||
{
|
|
||||||
signed_records.add(i.next());
|
|
||||||
}
|
|
||||||
for (Iterator<Record> i = rrset.sigs(); i.hasNext();)
|
|
||||||
{
|
|
||||||
signed_records.add(i.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
// write out the signed zone
|
|
||||||
ZoneUtils.writeZoneFile(signed_records, state.outputfile);
|
|
||||||
|
|
||||||
if (state.verifySigs)
|
|
||||||
{
|
|
||||||
log.fine("verifying generated signatures");
|
|
||||||
boolean res = verifySigs(keysetName, signed_records, keypairs);
|
|
||||||
|
|
||||||
if (res)
|
|
||||||
{
|
|
||||||
System.out.println("Generated signatures verified");
|
|
||||||
// log.info("Generated signatures verified");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
System.out.println("Generated signatures did not verify.");
|
|
||||||
// log.warn("Generated signatures did not verify.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
SignRRset tool = new SignRRset();
|
|
||||||
tool.state = new CLIState();
|
|
||||||
|
|
||||||
tool.run(tool.state, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,755 +0,0 @@
|
|||||||
// Copyright (C) 2001-2003, 2011 VeriSign, Inc.
|
|
||||||
//
|
|
||||||
// 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.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileFilter;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import org.apache.commons.cli.CommandLine;
|
|
||||||
import org.apache.commons.cli.OptionBuilder;
|
|
||||||
import org.apache.commons.cli.Options;
|
|
||||||
import org.apache.commons.cli.ParseException;
|
|
||||||
|
|
||||||
import org.xbill.DNS.DNSKEYRecord;
|
|
||||||
import org.xbill.DNS.DNSSEC;
|
|
||||||
import org.xbill.DNS.DSRecord;
|
|
||||||
import org.xbill.DNS.Name;
|
|
||||||
import org.xbill.DNS.RRset;
|
|
||||||
import org.xbill.DNS.Record;
|
|
||||||
import org.xbill.DNS.TextParseException;
|
|
||||||
import org.xbill.DNS.Type;
|
|
||||||
import org.xbill.DNS.utils.base16;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class forms the command line implementation of a DNSSEC zone signer.
|
|
||||||
*
|
|
||||||
* @author David Blacka
|
|
||||||
*/
|
|
||||||
public class SignZone extends CLBase
|
|
||||||
{
|
|
||||||
private CLIState state;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an inner class used to hold all of the command line option state.
|
|
||||||
*/
|
|
||||||
private static class CLIState extends CLIStateBase
|
|
||||||
{
|
|
||||||
public File keyDirectory = null;
|
|
||||||
public File keysetDirectory = null;
|
|
||||||
public String[] kskFiles = null;
|
|
||||||
public String[] keyFiles = null;
|
|
||||||
public String zonefile = null;
|
|
||||||
public Date start = null;
|
|
||||||
public Date expire = null;
|
|
||||||
public String outputfile = null;
|
|
||||||
public boolean verifySigs = false;
|
|
||||||
public boolean useOptOut = false;
|
|
||||||
public boolean fullySignKeyset = false;
|
|
||||||
public List<Name> includeNames = null;
|
|
||||||
public boolean useNsec3 = false;
|
|
||||||
public byte[] salt = null;
|
|
||||||
public int iterations = 0;
|
|
||||||
public int digest_id = DSRecord.SHA1_DIGEST_ID;
|
|
||||||
public long nsec3paramttl = -1;
|
|
||||||
public boolean verboseSigning = false;
|
|
||||||
|
|
||||||
public CLIState()
|
|
||||||
{
|
|
||||||
super("jdnssec-signzone [..options..] zone_file [key_file ...]");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setupOptions(Options opts)
|
|
||||||
{
|
|
||||||
// boolean options
|
|
||||||
opts.addOption("a", "verify", false, "verify generated signatures>");
|
|
||||||
opts.addOption("F", "fully-sign-keyset", false,
|
|
||||||
"sign the zone apex keyset with all available keys.");
|
|
||||||
opts.addOption("V", "verbose-signing", false, "Display verbose signing activity.");
|
|
||||||
|
|
||||||
// Argument options
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("dir");
|
|
||||||
OptionBuilder.withLongOpt("keyset-directory");
|
|
||||||
OptionBuilder.withDescription("directory to find keyset files (default '.').");
|
|
||||||
opts.addOption(OptionBuilder.create('d'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("dir");
|
|
||||||
OptionBuilder.withLongOpt("key-directory");
|
|
||||||
OptionBuilder.withDescription("directory to find key files (default '.').");
|
|
||||||
opts.addOption(OptionBuilder.create('D'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("time/offset");
|
|
||||||
OptionBuilder.withLongOpt("start-time");
|
|
||||||
OptionBuilder.withDescription("signature starting time (default is now - 1 hour)");
|
|
||||||
opts.addOption(OptionBuilder.create('s'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("time/offset");
|
|
||||||
OptionBuilder.withLongOpt("expire-time");
|
|
||||||
OptionBuilder.withDescription("signature expiration time (default is start-time + 30 days).");
|
|
||||||
opts.addOption(OptionBuilder.create('e'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("outfile");
|
|
||||||
OptionBuilder.withDescription("file the signed zone is written to (default is <origin>.signed).");
|
|
||||||
opts.addOption(OptionBuilder.create('f'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArgs();
|
|
||||||
OptionBuilder.withArgName("KSK file");
|
|
||||||
OptionBuilder.withLongOpt("ksk-file");
|
|
||||||
OptionBuilder.withDescription("this key is a key signing key (may repeat).");
|
|
||||||
opts.addOption(OptionBuilder.create('k'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("file");
|
|
||||||
OptionBuilder.withLongOpt("include-file");
|
|
||||||
OptionBuilder.withDescription("include names in this file in the NSEC/NSEC3 chain.");
|
|
||||||
opts.addOption(OptionBuilder.create('I'));
|
|
||||||
|
|
||||||
// NSEC3 options
|
|
||||||
opts.addOption("3", "use-nsec3", false, "use NSEC3 instead of NSEC");
|
|
||||||
opts.addOption("O", "use-opt-out", false,
|
|
||||||
"generate a fully Opt-Out zone (only valid with NSEC3).");
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withLongOpt("salt");
|
|
||||||
OptionBuilder.withArgName("hex value");
|
|
||||||
OptionBuilder.withDescription("supply a salt value.");
|
|
||||||
opts.addOption(OptionBuilder.create('S'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withLongOpt("random-salt");
|
|
||||||
OptionBuilder.withArgName("length");
|
|
||||||
OptionBuilder.withDescription("generate a random salt.");
|
|
||||||
opts.addOption(OptionBuilder.create('R'));
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withLongOpt("iterations");
|
|
||||||
OptionBuilder.withArgName("value");
|
|
||||||
OptionBuilder.withDescription("use this value for the iterations in NSEC3.");
|
|
||||||
opts.addOption(OptionBuilder.create());
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withLongOpt("nsec3paramttl");
|
|
||||||
OptionBuilder.withArgName("ttl");
|
|
||||||
OptionBuilder.withDescription("use this value for the NSEC3PARAM RR ttl");
|
|
||||||
opts.addOption(OptionBuilder.create());
|
|
||||||
|
|
||||||
OptionBuilder.hasArg();
|
|
||||||
OptionBuilder.withArgName("id");
|
|
||||||
OptionBuilder.withLongOpt("ds-digest");
|
|
||||||
OptionBuilder.withDescription("Digest algorithm to use for generated DSs");
|
|
||||||
opts.addOption(OptionBuilder.create());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void processOptions(CommandLine cli) throws ParseException
|
|
||||||
{
|
|
||||||
String optstr = null;
|
|
||||||
|
|
||||||
if (cli.hasOption('a')) verifySigs = true;
|
|
||||||
if (cli.hasOption('3')) useNsec3 = true;
|
|
||||||
if (cli.hasOption('O')) useOptOut = true;
|
|
||||||
if (cli.hasOption('V')) verboseSigning = true;
|
|
||||||
|
|
||||||
if (useOptOut && !useNsec3)
|
|
||||||
{
|
|
||||||
System.err.println("Opt-Out not supported without NSEC3 -- ignored.");
|
|
||||||
useOptOut = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cli.hasOption('F')) fullySignKeyset = true;
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('d')) != null)
|
|
||||||
{
|
|
||||||
keysetDirectory = new File(optstr);
|
|
||||||
if (!keysetDirectory.isDirectory())
|
|
||||||
{
|
|
||||||
System.err.println("error: " + optstr + " is not a directory");
|
|
||||||
usage();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('D')) != null)
|
|
||||||
{
|
|
||||||
keyDirectory = new File(optstr);
|
|
||||||
if (!keyDirectory.isDirectory())
|
|
||||||
{
|
|
||||||
System.err.println("error: " + optstr + " is not a directory");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('s')) != null)
|
|
||||||
{
|
|
||||||
start = CLBase.convertDuration(null, optstr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// default is now - 1 hour.
|
|
||||||
start = new Date(System.currentTimeMillis() - (3600 * 1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('e')) != null)
|
|
||||||
{
|
|
||||||
expire = CLBase.convertDuration(start, optstr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
expire = CLBase.convertDuration(start, "+2592000"); // 30 days
|
|
||||||
}
|
|
||||||
|
|
||||||
outputfile = cli.getOptionValue('f');
|
|
||||||
|
|
||||||
kskFiles = cli.getOptionValues('k');
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('I')) != null)
|
|
||||||
{
|
|
||||||
File includeNamesFile = new File(optstr);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
includeNames = getNameList(includeNamesFile);
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
throw new ParseException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('S')) != null)
|
|
||||||
{
|
|
||||||
salt = base16.fromString(optstr);
|
|
||||||
if (salt == null && !optstr.equals("-"))
|
|
||||||
{
|
|
||||||
System.err.println("error: salt is not valid hexidecimal.");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('R')) != null)
|
|
||||||
{
|
|
||||||
int length = parseInt(optstr, 0);
|
|
||||||
if (length > 0 && length <= 255)
|
|
||||||
{
|
|
||||||
Random random = new Random();
|
|
||||||
salt = new byte[length];
|
|
||||||
random.nextBytes(salt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue("iterations")) != null)
|
|
||||||
{
|
|
||||||
iterations = parseInt(optstr, iterations);
|
|
||||||
if (iterations < 0 || iterations > 8388607)
|
|
||||||
{
|
|
||||||
System.err.println("error: iterations value is invalid");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue("ds-digest")) != null)
|
|
||||||
{
|
|
||||||
digest_id = parseInt(optstr, -1);
|
|
||||||
if (digest_id < 0)
|
|
||||||
{
|
|
||||||
System.err.println("error: DS digest ID is not a valid identifier");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue("nsec3paramttl")) != null)
|
|
||||||
{
|
|
||||||
nsec3paramttl = parseInt(optstr, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] files = cli.getArgs();
|
|
||||||
|
|
||||||
if (files.length < 1)
|
|
||||||
{
|
|
||||||
System.err.println("error: missing zone file and/or key files");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
zonefile = files[0];
|
|
||||||
if (files.length > 1)
|
|
||||||
{
|
|
||||||
keyFiles = new String[files.length - 1];
|
|
||||||
System.arraycopy(files, 1, keyFiles, 0, files.length - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify the generated signatures.
|
|
||||||
*
|
|
||||||
* @param zonename
|
|
||||||
* the origin name of the zone.
|
|
||||||
* @param records
|
|
||||||
* a list of {@link org.xbill.DNS.Record}s.
|
|
||||||
* @param keypairs
|
|
||||||
* a list of keypairs used the sign the zone.
|
|
||||||
* @return true if all of the signatures validated.
|
|
||||||
*/
|
|
||||||
private static boolean verifyZoneSigs(Name zonename, List<Record> records,
|
|
||||||
List<DnsKeyPair> keypairs)
|
|
||||||
{
|
|
||||||
boolean secure = true;
|
|
||||||
|
|
||||||
DnsSecVerifier verifier = new DnsSecVerifier();
|
|
||||||
|
|
||||||
for (DnsKeyPair pair : keypairs)
|
|
||||||
{
|
|
||||||
verifier.addTrustedKey(pair);
|
|
||||||
}
|
|
||||||
|
|
||||||
verifier.setVerifyAllSigs(true);
|
|
||||||
|
|
||||||
List<RRset> rrsets = SignUtils.assembleIntoRRsets(records);
|
|
||||||
|
|
||||||
for (RRset rrset : rrsets)
|
|
||||||
{
|
|
||||||
// skip unsigned rrsets.
|
|
||||||
if (!rrset.sigs().hasNext()) continue;
|
|
||||||
|
|
||||||
int result = verifier.verify(rrset, null);
|
|
||||||
|
|
||||||
if (result != DNSSEC.Secure)
|
|
||||||
{
|
|
||||||
log.fine("Signatures did not verify for RRset: (" + result + "): " + rrset);
|
|
||||||
secure = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return secure;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the key pairs from the key files.
|
|
||||||
*
|
|
||||||
* @param keyfiles
|
|
||||||
* a string array containing the base names or paths of the keys to
|
|
||||||
* be loaded.
|
|
||||||
* @param start_index
|
|
||||||
* the starting index of keyfiles string array to use. This allows
|
|
||||||
* us
|
|
||||||
* to use the straight command line argument array.
|
|
||||||
* @param inDirectory
|
|
||||||
* the directory to look in (may be null).
|
|
||||||
* @return a list of keypair objects.
|
|
||||||
*/
|
|
||||||
private static List<DnsKeyPair> getKeys(String[] keyfiles, int start_index,
|
|
||||||
File inDirectory) throws IOException
|
|
||||||
{
|
|
||||||
if (keyfiles == null) return null;
|
|
||||||
|
|
||||||
int len = keyfiles.length - start_index;
|
|
||||||
if (len <= 0) return null;
|
|
||||||
|
|
||||||
ArrayList<DnsKeyPair> keys = new ArrayList<DnsKeyPair>(len);
|
|
||||||
|
|
||||||
for (int i = start_index; i < keyfiles.length; i++)
|
|
||||||
{
|
|
||||||
DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory);
|
|
||||||
if (k != null) keys.add(k);
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<DnsKeyPair> getKeys(List<Record> dnskeyrrs, File inDirectory)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
List<DnsKeyPair> res = new ArrayList<DnsKeyPair>();
|
|
||||||
for (Record r : dnskeyrrs)
|
|
||||||
{
|
|
||||||
if (r.getType() != Type.DNSKEY) continue;
|
|
||||||
|
|
||||||
// Construct a public-key-only DnsKeyPair just so we can calculate the
|
|
||||||
// base name.
|
|
||||||
DnsKeyPair pub = new DnsKeyPair((DNSKEYRecord) r);
|
|
||||||
DnsKeyPair pair = BINDKeyUtils.loadKeyPair(BINDKeyUtils.keyFileBase(pub),
|
|
||||||
inDirectory);
|
|
||||||
if (pair != null)
|
|
||||||
{
|
|
||||||
res.add(pair);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.size() > 0) return res;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class KeyFileFilter implements FileFilter
|
|
||||||
{
|
|
||||||
private String prefix;
|
|
||||||
|
|
||||||
public KeyFileFilter(Name origin)
|
|
||||||
{
|
|
||||||
prefix = "K" + origin.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean accept(File pathname)
|
|
||||||
{
|
|
||||||
if (!pathname.isFile()) return false;
|
|
||||||
String name = pathname.getName();
|
|
||||||
if (name.startsWith(prefix) && name.endsWith(".private")) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<DnsKeyPair> findZoneKeys(File inDirectory, Name zonename)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
if (inDirectory == null)
|
|
||||||
{
|
|
||||||
inDirectory = new File(".");
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the list of "K<zone>.*.private files.
|
|
||||||
FileFilter filter = new KeyFileFilter(zonename);
|
|
||||||
File[] files = inDirectory.listFiles(filter);
|
|
||||||
|
|
||||||
// read in all of the records
|
|
||||||
ArrayList<DnsKeyPair> keys = new ArrayList<DnsKeyPair>();
|
|
||||||
for (int i = 0; i < files.length; i++)
|
|
||||||
{
|
|
||||||
DnsKeyPair p = BINDKeyUtils.loadKeyPair(files[i].getName(), inDirectory);
|
|
||||||
keys.add(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keys.size() > 0) return keys;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an implementation of a file filter used for finding BIND 9-style
|
|
||||||
* keyset-* files.
|
|
||||||
*/
|
|
||||||
private static class KeysetFileFilter implements FileFilter
|
|
||||||
{
|
|
||||||
public boolean accept(File pathname)
|
|
||||||
{
|
|
||||||
if (!pathname.isFile()) return false;
|
|
||||||
String name = pathname.getName();
|
|
||||||
if (name.startsWith("keyset-")) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load keysets (which contain delegation point security info).
|
|
||||||
*
|
|
||||||
* @param inDirectory
|
|
||||||
* the directory to look for the keyset files (may be null, in
|
|
||||||
* which
|
|
||||||
* case it defaults to looking in the current working directory).
|
|
||||||
* @param zonename
|
|
||||||
* the name of the zone we are signing, so we can ignore keysets
|
|
||||||
* that
|
|
||||||
* do not belong in the zone.
|
|
||||||
* @return a list of {@link org.xbill.DNS.Record}s found in the keyset
|
|
||||||
* files.
|
|
||||||
*/
|
|
||||||
private static List<Record> getKeysets(File inDirectory, Name zonename)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
if (inDirectory == null)
|
|
||||||
{
|
|
||||||
inDirectory = new File(".");
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the list of "keyset-" files.
|
|
||||||
FileFilter filter = new KeysetFileFilter();
|
|
||||||
File[] files = inDirectory.listFiles(filter);
|
|
||||||
|
|
||||||
// read in all of the records
|
|
||||||
ArrayList<Record> keysetRecords = new ArrayList<Record>();
|
|
||||||
for (int i = 0; i < files.length; i++)
|
|
||||||
{
|
|
||||||
List<Record> l = ZoneUtils.readZoneFile(files[i].getAbsolutePath(), zonename);
|
|
||||||
keysetRecords.addAll(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
// discard records that do not belong to the zone in question.
|
|
||||||
for (Iterator<Record> i = keysetRecords.iterator(); i.hasNext();)
|
|
||||||
{
|
|
||||||
Record r = i.next();
|
|
||||||
if (!r.getName().subdomain(zonename))
|
|
||||||
{
|
|
||||||
i.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return keysetRecords;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a list of DNS names from a file.
|
|
||||||
*
|
|
||||||
* @param nameListFile
|
|
||||||
* the path of a file containing a bare list of DNS names.
|
|
||||||
* @return a list of {@link org.xbill.DNS.Name} objects.
|
|
||||||
*/
|
|
||||||
private static List<Name> getNameList(File nameListFile) throws IOException
|
|
||||||
{
|
|
||||||
BufferedReader br = new BufferedReader(new FileReader(nameListFile));
|
|
||||||
List<Name> res = new ArrayList<Name>();
|
|
||||||
|
|
||||||
String line = null;
|
|
||||||
while ((line = br.readLine()) != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Name n = Name.fromString(line);
|
|
||||||
// force the name to be absolute.
|
|
||||||
// FIXME: we should probably get some fancy logic here to
|
|
||||||
// detect if the name needs the origin appended, or just the
|
|
||||||
// root.
|
|
||||||
if (!n.isAbsolute()) n = Name.concatenate(n, Name.root);
|
|
||||||
|
|
||||||
res.add(n);
|
|
||||||
}
|
|
||||||
catch (TextParseException e)
|
|
||||||
{
|
|
||||||
log.severe("DNS Name parsing error:" + e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.size() == 0) return null;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if the given keypairs can be used to sign the zone.
|
|
||||||
*
|
|
||||||
* @param zonename
|
|
||||||
* the zone origin.
|
|
||||||
* @param keypairs
|
|
||||||
* a list of {@link DnsKeyPair} objects that will be used to sign
|
|
||||||
* the
|
|
||||||
* zone.
|
|
||||||
* @return true if the keypairs valid.
|
|
||||||
*/
|
|
||||||
private static boolean keyPairsValidForZone(Name zonename, List<DnsKeyPair> keypairs)
|
|
||||||
{
|
|
||||||
if (keypairs == null) return true; // technically true, I guess.
|
|
||||||
|
|
||||||
for (DnsKeyPair kp : keypairs)
|
|
||||||
{
|
|
||||||
Name keyname = kp.getDNSKEYRecord().getName();
|
|
||||||
if (!keyname.equals(zonename))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void execute() throws Exception
|
|
||||||
{
|
|
||||||
// Read in the zone
|
|
||||||
List<Record> records = ZoneUtils.readZoneFile(state.zonefile, null);
|
|
||||||
if (records == null || records.size() == 0)
|
|
||||||
{
|
|
||||||
System.err.println("error: empty zone file");
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate the zone name.
|
|
||||||
Name zonename = ZoneUtils.findZoneName(records);
|
|
||||||
if (zonename == null)
|
|
||||||
{
|
|
||||||
System.err.println("error: invalid zone file - no SOA");
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the key pairs.
|
|
||||||
|
|
||||||
List<DnsKeyPair> keypairs = getKeys(state.keyFiles, 0, state.keyDirectory);
|
|
||||||
List<DnsKeyPair> kskpairs = getKeys(state.kskFiles, 0, state.keyDirectory);
|
|
||||||
|
|
||||||
// If we didn't get any keys on the command line, look at the zone apex for
|
|
||||||
// any public keys.
|
|
||||||
if (keypairs == null && kskpairs == null)
|
|
||||||
{
|
|
||||||
List<Record> dnskeys = ZoneUtils.findRRs(records, zonename, Type.DNSKEY);
|
|
||||||
keypairs = getKeys(dnskeys, state.keyDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we *still* don't have any key pairs, look for keys the key directory
|
|
||||||
// that match
|
|
||||||
if (keypairs == null && kskpairs == null)
|
|
||||||
{
|
|
||||||
keypairs = findZoneKeys(state.keyDirectory, zonename);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't have any KSKs, but we do have more than one zone
|
|
||||||
// signing key (presumably), presume that the zone signing keys
|
|
||||||
// are just not differentiated and try to figure out which keys
|
|
||||||
// are actually ksks by looking at the SEP flag.
|
|
||||||
if ((kskpairs == null || kskpairs.size() == 0) && keypairs != null
|
|
||||||
&& keypairs.size() > 1)
|
|
||||||
{
|
|
||||||
for (Iterator<DnsKeyPair> i = keypairs.iterator(); i.hasNext();)
|
|
||||||
{
|
|
||||||
DnsKeyPair pair = i.next();
|
|
||||||
DNSKEYRecord kr = pair.getDNSKEYRecord();
|
|
||||||
if ((kr.getFlags() & DNSKEYRecord.Flags.SEP_KEY) != 0)
|
|
||||||
{
|
|
||||||
if (kskpairs == null) kskpairs = new ArrayList<DnsKeyPair>();
|
|
||||||
kskpairs.add(pair);
|
|
||||||
i.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are no ZSKs defined at this point (yet there are KSKs
|
|
||||||
// provided), all KSKs will be treated as ZSKs, as well.
|
|
||||||
if (keypairs == null || keypairs.size() == 0)
|
|
||||||
{
|
|
||||||
keypairs = kskpairs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there *still* aren't any ZSKs defined, bail.
|
|
||||||
if (keypairs == null || keypairs.size() == 0)
|
|
||||||
{
|
|
||||||
System.err.println("No zone signing keys could be determined.");
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// default the output file, if not set.
|
|
||||||
if (state.outputfile == null && !state.zonefile.equals("-"))
|
|
||||||
{
|
|
||||||
if (zonename.isAbsolute())
|
|
||||||
{
|
|
||||||
state.outputfile = zonename + "signed";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state.outputfile = zonename + ".signed";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the keys can be in the zone.
|
|
||||||
if (!keyPairsValidForZone(zonename, keypairs)
|
|
||||||
|| !keyPairsValidForZone(zonename, kskpairs))
|
|
||||||
{
|
|
||||||
System.err.println("error: specified keypairs are not valid for the zone.");
|
|
||||||
state.usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// We force the signing keys to be in the zone by just appending
|
|
||||||
// them to the zone here. Currently JCEDnsSecSigner.signZone
|
|
||||||
// removes duplicate records.
|
|
||||||
if (kskpairs != null)
|
|
||||||
{
|
|
||||||
for (DnsKeyPair pair : kskpairs)
|
|
||||||
{
|
|
||||||
records.add(pair.getDNSKEYRecord());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (keypairs != null)
|
|
||||||
{
|
|
||||||
for (DnsKeyPair pair : keypairs)
|
|
||||||
{
|
|
||||||
records.add(pair.getDNSKEYRecord());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// read in the keysets, if any.
|
|
||||||
List<Record> keysetrecs = getKeysets(state.keysetDirectory, zonename);
|
|
||||||
if (keysetrecs != null)
|
|
||||||
{
|
|
||||||
records.addAll(keysetrecs);
|
|
||||||
}
|
|
||||||
|
|
||||||
JCEDnsSecSigner signer = new JCEDnsSecSigner(state.verboseSigning);
|
|
||||||
|
|
||||||
// Sign the zone.
|
|
||||||
List<Record> signed_records;
|
|
||||||
|
|
||||||
if (state.useNsec3)
|
|
||||||
{
|
|
||||||
signed_records = signer.signZoneNSEC3(zonename, records, kskpairs, keypairs,
|
|
||||||
state.start, state.expire,
|
|
||||||
state.fullySignKeyset, state.useOptOut,
|
|
||||||
state.includeNames, state.salt,
|
|
||||||
state.iterations, state.digest_id,
|
|
||||||
state.nsec3paramttl);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
signed_records = signer.signZone(zonename, records, kskpairs, keypairs,
|
|
||||||
state.start, state.expire, state.fullySignKeyset,
|
|
||||||
state.digest_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// write out the signed zone
|
|
||||||
ZoneUtils.writeZoneFile(signed_records, state.outputfile);
|
|
||||||
|
|
||||||
if (state.verifySigs)
|
|
||||||
{
|
|
||||||
// FIXME: ugh.
|
|
||||||
if (kskpairs != null)
|
|
||||||
{
|
|
||||||
keypairs.addAll(kskpairs);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.fine("verifying generated signatures");
|
|
||||||
boolean res = verifyZoneSigs(zonename, signed_records, keypairs);
|
|
||||||
|
|
||||||
if (res)
|
|
||||||
{
|
|
||||||
System.out.println("Generated signatures verified");
|
|
||||||
// log.info("Generated signatures verified");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
System.out.println("Generated signatures did not verify.");
|
|
||||||
// log.warn("Generated signatures did not verify.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
SignZone tool = new SignZone();
|
|
||||||
tool.state = new CLIState();
|
|
||||||
|
|
||||||
tool.run(tool.state, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
// Copyright (C) 2011 VeriSign, Inc.
|
|
||||||
//
|
|
||||||
// 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.util.List;
|
|
||||||
|
|
||||||
import org.apache.commons.cli.CommandLine;
|
|
||||||
import org.apache.commons.cli.OptionBuilder;
|
|
||||||
import org.apache.commons.cli.Options;
|
|
||||||
import org.xbill.DNS.Record;
|
|
||||||
|
|
||||||
import com.verisignlabs.dnssec.security.ZoneUtils;
|
|
||||||
import com.verisignlabs.dnssec.security.ZoneVerifier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class forms the command line implementation of a DNSSEC zone validator.
|
|
||||||
*
|
|
||||||
* @author David Blacka
|
|
||||||
*/
|
|
||||||
public class VerifyZone extends CLBase
|
|
||||||
{
|
|
||||||
|
|
||||||
private CLIState state;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a small inner class used to hold all of the command line option
|
|
||||||
* state.
|
|
||||||
*/
|
|
||||||
protected static class CLIState extends CLIStateBase
|
|
||||||
{
|
|
||||||
public String zonefile = null;
|
|
||||||
public String[] keyfiles = null;
|
|
||||||
public int startfudge = 0;
|
|
||||||
public int expirefudge = 0;
|
|
||||||
public boolean ignoreTime = false;
|
|
||||||
|
|
||||||
public CLIState()
|
|
||||||
{
|
|
||||||
super("jdnssec-verifyzone [..options..] zonefile");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setupOptions(Options opts)
|
|
||||||
{
|
|
||||||
OptionBuilder.hasOptionalArg();
|
|
||||||
OptionBuilder.withLongOpt("sig-start-fudge");
|
|
||||||
OptionBuilder.withArgName("seconds");
|
|
||||||
OptionBuilder.withDescription("'fudge' RRSIG inception times by 'seconds' seconds.");
|
|
||||||
opts.addOption(OptionBuilder.create('S'));
|
|
||||||
|
|
||||||
OptionBuilder.hasOptionalArg();
|
|
||||||
OptionBuilder.withLongOpt("sig-expire-fudge");
|
|
||||||
OptionBuilder.withArgName("seconds");
|
|
||||||
OptionBuilder.withDescription("'fudge' RRSIG expiration times by 'seconds' seconds.");
|
|
||||||
opts.addOption(OptionBuilder.create('E'));
|
|
||||||
|
|
||||||
OptionBuilder.withLongOpt("ignore-time");
|
|
||||||
OptionBuilder.withDescription("Ignore RRSIG inception and expiration time errors.");
|
|
||||||
opts.addOption(OptionBuilder.create());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void processOptions(CommandLine cli)
|
|
||||||
{
|
|
||||||
if (cli.hasOption("ignore-time"))
|
|
||||||
{
|
|
||||||
ignoreTime = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
String optstr = null;
|
|
||||||
if ((optstr = cli.getOptionValue('S')) != null)
|
|
||||||
{
|
|
||||||
startfudge = parseInt(optstr, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optstr = cli.getOptionValue('E')) != null)
|
|
||||||
{
|
|
||||||
expirefudge = parseInt(optstr, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] optstrs = null;
|
|
||||||
if ((optstrs = cli.getOptionValues('A')) != null)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < optstrs.length; i++)
|
|
||||||
{
|
|
||||||
addArgAlias(optstrs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] cl_args = cli.getArgs();
|
|
||||||
|
|
||||||
if (cl_args.length < 1)
|
|
||||||
{
|
|
||||||
System.err.println("error: missing zone file");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
zonefile = cl_args[0];
|
|
||||||
|
|
||||||
if (cl_args.length >= 2)
|
|
||||||
{
|
|
||||||
keyfiles = new String[cl_args.length - 1];
|
|
||||||
System.arraycopy(cl_args, 1, keyfiles, 0, keyfiles.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void execute() throws Exception
|
|
||||||
{
|
|
||||||
ZoneVerifier zoneverifier = new ZoneVerifier();
|
|
||||||
zoneverifier.getVerifier().setStartFudge(state.startfudge);
|
|
||||||
zoneverifier.getVerifier().setExpireFudge(state.expirefudge);
|
|
||||||
zoneverifier.getVerifier().setIgnoreTime(state.ignoreTime);
|
|
||||||
|
|
||||||
List<Record> records = ZoneUtils.readZoneFile(state.zonefile, null);
|
|
||||||
|
|
||||||
log.fine("verifying zone...");
|
|
||||||
int errors = zoneverifier.verifyZone(records);
|
|
||||||
log.fine("completed verification process.");
|
|
||||||
|
|
||||||
if (errors > 0)
|
|
||||||
{
|
|
||||||
System.out.println("zone did not verify.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
System.out.println("zone verified.");
|
|
||||||
}
|
|
||||||
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
VerifyZone tool = new VerifyZone();
|
|
||||||
tool.state = new CLIState();
|
|
||||||
|
|
||||||
tool.run(tool.state, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
// Copyright (C) 2011 VeriSign, Inc.
|
|
||||||
//
|
|
||||||
// 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.IOException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ListIterator;
|
|
||||||
|
|
||||||
import org.apache.commons.cli.CommandLine;
|
|
||||||
import org.apache.commons.cli.Options;
|
|
||||||
import org.apache.commons.cli.ParseException;
|
|
||||||
import org.xbill.DNS.Master;
|
|
||||||
import org.xbill.DNS.NSEC3PARAMRecord;
|
|
||||||
import org.xbill.DNS.NSEC3Record;
|
|
||||||
import org.xbill.DNS.Name;
|
|
||||||
import org.xbill.DNS.Record;
|
|
||||||
import org.xbill.DNS.Section;
|
|
||||||
import org.xbill.DNS.Type;
|
|
||||||
import org.xbill.DNS.utils.base32;
|
|
||||||
|
|
||||||
import com.verisignlabs.dnssec.security.RecordComparator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class forms the command line implementation of a zone file normalizer.
|
|
||||||
* That is, a tool to rewrite zones in a consistent, comparable format.
|
|
||||||
*
|
|
||||||
* @author David Blacka (original)
|
|
||||||
* @author $Author: davidb $
|
|
||||||
* @version $Revision: 2218 $
|
|
||||||
*/
|
|
||||||
public class ZoneFormat extends CLBase
|
|
||||||
{
|
|
||||||
private CLIState state;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a small inner class used to hold all of the command line option
|
|
||||||
* state.
|
|
||||||
*/
|
|
||||||
protected static class CLIState extends CLIStateBase
|
|
||||||
{
|
|
||||||
public String file;
|
|
||||||
public boolean assignNSEC3;
|
|
||||||
|
|
||||||
public CLIState()
|
|
||||||
{
|
|
||||||
super("jdnssec-zoneformat [..options..] zonefile");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setupOptions(Options opts)
|
|
||||||
{
|
|
||||||
opts.addOption("N", "nsec3", false,
|
|
||||||
"attempt to determine the original ownernames for NSEC3 RRs.");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void processOptions(CommandLine cli) throws ParseException
|
|
||||||
{
|
|
||||||
if (cli.hasOption('N')) assignNSEC3 = true;
|
|
||||||
|
|
||||||
String[] cl_args = cli.getArgs();
|
|
||||||
|
|
||||||
if (cl_args.length < 1)
|
|
||||||
{
|
|
||||||
System.err.println("error: must specify a zone file");
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
file = cl_args[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Record> readZoneFile(String filename) throws IOException
|
|
||||||
{
|
|
||||||
Master master = new Master(filename);
|
|
||||||
|
|
||||||
List<Record> res = new ArrayList<Record>();
|
|
||||||
Record r = null;
|
|
||||||
|
|
||||||
while ((r = master.nextRecord()) != null)
|
|
||||||
{
|
|
||||||
// Normalize each record by round-tripping it through canonical wire line
|
|
||||||
// format. Mostly this just lowercases names that are subject to it.
|
|
||||||
byte[] wire = r.toWireCanonical();
|
|
||||||
Record canon_record = Record.fromWire(wire, Section.ANSWER);
|
|
||||||
res.add(canon_record);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void formatZone(List<Record> zone)
|
|
||||||
{
|
|
||||||
// Put the zone into a consistent (name and RR type) order.
|
|
||||||
RecordComparator cmp = new RecordComparator();
|
|
||||||
|
|
||||||
Collections.sort(zone, cmp);
|
|
||||||
|
|
||||||
for (Record r : zone)
|
|
||||||
{
|
|
||||||
System.out.println(r.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void determineNSEC3Owners(List<Record> zone)
|
|
||||||
throws NoSuchAlgorithmException
|
|
||||||
{
|
|
||||||
// Put the zone into a consistent (name and RR type) order.
|
|
||||||
Collections.sort(zone, new RecordComparator());
|
|
||||||
|
|
||||||
// first, find the NSEC3PARAM record -- this is an inefficient linear
|
|
||||||
// search.
|
|
||||||
NSEC3PARAMRecord nsec3param = null;
|
|
||||||
HashMap<String, String> map = new HashMap<String, String>();
|
|
||||||
base32 b32 = new base32(base32.Alphabet.BASE32HEX, false, true);
|
|
||||||
|
|
||||||
for (Record r : zone)
|
|
||||||
{
|
|
||||||
if (r.getType() == Type.NSEC3PARAM)
|
|
||||||
{
|
|
||||||
nsec3param = (NSEC3PARAMRecord) r;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there wasn't one, we have nothing to do.
|
|
||||||
if (nsec3param == null) return;
|
|
||||||
|
|
||||||
// Next pass, calculate a mapping between ownernames and hashnames
|
|
||||||
Name last_name = null;
|
|
||||||
for (Record r : zone)
|
|
||||||
{
|
|
||||||
if (r.getName().equals(last_name)) continue;
|
|
||||||
if (r.getType() == Type.NSEC3) continue;
|
|
||||||
|
|
||||||
byte[] hash = nsec3param.hashName(r.getName());
|
|
||||||
String hashname = b32.toString(hash);
|
|
||||||
map.put(hashname, r.getName().toString().toLowerCase());
|
|
||||||
last_name = r.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final pass, assign the names if we can
|
|
||||||
for (ListIterator<Record> i = zone.listIterator(); i.hasNext();)
|
|
||||||
{
|
|
||||||
Record r = i.next();
|
|
||||||
if (r.getType() != Type.NSEC3) continue;
|
|
||||||
NSEC3Record nsec3 = (NSEC3Record) r;
|
|
||||||
String hashname = nsec3.getName().getLabelString(0).toLowerCase();
|
|
||||||
String ownername = (String) map.get(hashname);
|
|
||||||
|
|
||||||
NSEC3Record new_nsec3 = new NSEC3Record(nsec3.getName(), nsec3.getDClass(),
|
|
||||||
nsec3.getTTL(), nsec3.getHashAlgorithm(),
|
|
||||||
nsec3.getFlags(), nsec3.getIterations(),
|
|
||||||
nsec3.getSalt(), nsec3.getNext(),
|
|
||||||
nsec3.getTypes(), ownername);
|
|
||||||
i.set(new_nsec3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void execute() throws IOException, NoSuchAlgorithmException
|
|
||||||
{
|
|
||||||
List<Record> z = readZoneFile(state.file);
|
|
||||||
if (state.assignNSEC3) determineNSEC3Owners(z);
|
|
||||||
formatZone(z);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
ZoneFormat tool = new ZoneFormat();
|
|
||||||
tool.state = new CLIState();
|
|
||||||
|
|
||||||
tool.run(tool.state, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
/*
|
|
||||||
* $Id$
|
|
||||||
*
|
|
||||||
* Copyright (c) 2006 VeriSign. All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer. 2. 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. 3. The name of the author may not
|
|
||||||
* be used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.verisignlabs.dnssec.security;
|
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.Signature;
|
|
||||||
import java.security.spec.RSAKeyGenParameterSpec;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import org.xbill.DNS.DNSSEC;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class handles translating DNS signing algorithm identifiers into various
|
|
||||||
* usable java implementations.
|
|
||||||
*
|
|
||||||
* Besides centralizing the logic surrounding matching a DNSKEY algorithm
|
|
||||||
* identifier with various crypto implementations, it also handles algorithm
|
|
||||||
* aliasing -- that is, defining a new algorithm identifier to be equivalent to
|
|
||||||
* an existing identifier.
|
|
||||||
*
|
|
||||||
* @author David Blacka (orig)
|
|
||||||
* @author $Author: davidb $ (latest)
|
|
||||||
* @version $Revision: 2098 $
|
|
||||||
*/
|
|
||||||
public class DnsKeyAlgorithm
|
|
||||||
{
|
|
||||||
|
|
||||||
public static final int UNKNOWN = -1;
|
|
||||||
public static final int RSA = 1;
|
|
||||||
public static final int DH = 2;
|
|
||||||
public static final int DSA = 3;
|
|
||||||
|
|
||||||
private static class Entry
|
|
||||||
{
|
|
||||||
public String sigName;
|
|
||||||
public int baseType;
|
|
||||||
|
|
||||||
public Entry(String sigName, int baseType)
|
|
||||||
{
|
|
||||||
this.sigName = sigName;
|
|
||||||
this.baseType = baseType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a mapping of algorithm identifier to Entry. The Entry contains the
|
|
||||||
* data needed to map the algorithm to the various crypto implementations.
|
|
||||||
*/
|
|
||||||
private HashMap<Integer, Entry> mAlgorithmMap;
|
|
||||||
/**
|
|
||||||
* This is a mapping of algorithm mnemonics to algorithm identifiers.
|
|
||||||
*/
|
|
||||||
private HashMap<String, Integer> mMnemonicToIdMap;
|
|
||||||
/**
|
|
||||||
* This is a mapping of identifiers to preferred mnemonic -- the preferred one
|
|
||||||
* is the first defined one
|
|
||||||
*/
|
|
||||||
private HashMap<Integer, String> mIdToMnemonicMap;
|
|
||||||
|
|
||||||
/** This is a cached key pair generator for RSA keys. */
|
|
||||||
private KeyPairGenerator mRSAKeyGenerator;
|
|
||||||
/** This is a cache key pair generator for DSA keys. */
|
|
||||||
private KeyPairGenerator mDSAKeyGenerator;
|
|
||||||
|
|
||||||
private Logger log = Logger.getLogger(this.getClass().toString());
|
|
||||||
|
|
||||||
/** This is the global instance for this class. */
|
|
||||||
private static DnsKeyAlgorithm mInstance = null;
|
|
||||||
|
|
||||||
public DnsKeyAlgorithm()
|
|
||||||
{
|
|
||||||
mAlgorithmMap = new HashMap<Integer, Entry>();
|
|
||||||
mMnemonicToIdMap = new HashMap<String, Integer>();
|
|
||||||
mIdToMnemonicMap = new HashMap<Integer, String>();
|
|
||||||
|
|
||||||
// Load the standard DNSSEC algorithms.
|
|
||||||
addAlgorithm(DNSSEC.RSAMD5, new Entry("MD5withRSA", RSA));
|
|
||||||
addMnemonic("RSAMD5", DNSSEC.RSAMD5);
|
|
||||||
|
|
||||||
addAlgorithm(DNSSEC.DH, new Entry("", DH));
|
|
||||||
addMnemonic("DH", DNSSEC.DH);
|
|
||||||
|
|
||||||
addAlgorithm(DNSSEC.DSA, new Entry("SHA1withDSA", DSA));
|
|
||||||
addMnemonic("DSA", DNSSEC.DSA);
|
|
||||||
|
|
||||||
addAlgorithm(DNSSEC.RSASHA1, new Entry("SHA1withRSA", RSA));
|
|
||||||
addMnemonic("RSASHA1", DNSSEC.RSASHA1);
|
|
||||||
addMnemonic("RSA", DNSSEC.RSASHA1);
|
|
||||||
|
|
||||||
// Load the (now) standard aliases
|
|
||||||
addAlias(DNSSEC.DSA_NSEC3_SHA1, "DSA-NSEC3-SHA1", DNSSEC.DSA);
|
|
||||||
addAlias(DNSSEC.RSA_NSEC3_SHA1, "RSA-NSEC3-SHA1", DNSSEC.RSASHA1);
|
|
||||||
// Also recognize the BIND 9.6 mnemonics
|
|
||||||
addMnemonic("NSEC3DSA", DNSSEC.DSA_NSEC3_SHA1);
|
|
||||||
addMnemonic("NSEC3RSASHA1", DNSSEC.RSA_NSEC3_SHA1);
|
|
||||||
|
|
||||||
// Algorithms added by RFC 5702.
|
|
||||||
// NOTE: these algorithms aren't available in Java 1.4's sunprovider
|
|
||||||
// implementation (but are in java 1.5's and later).
|
|
||||||
addAlgorithm(8, new Entry("SHA256withRSA", RSA));
|
|
||||||
addMnemonic("RSASHA256", 8);
|
|
||||||
|
|
||||||
addAlgorithm(10, new Entry("SHA512withRSA", RSA));
|
|
||||||
addMnemonic("RSASHA512", 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAlgorithm(int algorithm, Entry entry)
|
|
||||||
{
|
|
||||||
mAlgorithmMap.put(algorithm, entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addMnemonic(String m, int alg)
|
|
||||||
{
|
|
||||||
mMnemonicToIdMap.put(m.toUpperCase(), alg);
|
|
||||||
if (!mIdToMnemonicMap.containsKey(alg))
|
|
||||||
{
|
|
||||||
mIdToMnemonicMap.put(alg, m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addAlias(int alias, String mnemonic, int original_algorithm)
|
|
||||||
{
|
|
||||||
if (mAlgorithmMap.containsKey(alias))
|
|
||||||
{
|
|
||||||
log.warning("Unable to alias algorithm " + alias + " because it already exists.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mAlgorithmMap.containsKey(original_algorithm))
|
|
||||||
{
|
|
||||||
log.warning("Unable to alias algorith " + alias
|
|
||||||
+ " to unknown algorithm identifier " + original_algorithm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mAlgorithmMap.put(alias, mAlgorithmMap.get(original_algorithm));
|
|
||||||
|
|
||||||
if (mnemonic != null)
|
|
||||||
{
|
|
||||||
addMnemonic(mnemonic, alias);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Entry getEntry(int alg)
|
|
||||||
{
|
|
||||||
return mAlgorithmMap.get(alg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Signature getSignature(int algorithm)
|
|
||||||
{
|
|
||||||
Entry entry = getEntry(algorithm);
|
|
||||||
if (entry == null) return null;
|
|
||||||
|
|
||||||
Signature s = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
s = Signature.getInstance(entry.sigName);
|
|
||||||
}
|
|
||||||
catch (NoSuchAlgorithmException e)
|
|
||||||
{
|
|
||||||
log.severe("Unable to get signature implementation for algorithm " + algorithm
|
|
||||||
+ ": " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int stringToAlgorithm(String s)
|
|
||||||
{
|
|
||||||
Integer alg = mMnemonicToIdMap.get(s.toUpperCase());
|
|
||||||
if (alg != null) return alg.intValue();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String algToString(int algorithm)
|
|
||||||
{
|
|
||||||
return mIdToMnemonicMap.get(algorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int baseType(int algorithm)
|
|
||||||
{
|
|
||||||
Entry entry = getEntry(algorithm);
|
|
||||||
if (entry != null) return entry.baseType;
|
|
||||||
return UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int standardAlgorithm(int algorithm)
|
|
||||||
{
|
|
||||||
switch (baseType(algorithm))
|
|
||||||
{
|
|
||||||
case RSA:
|
|
||||||
return DNSSEC.RSASHA1;
|
|
||||||
case DSA:
|
|
||||||
return DNSSEC.DSA;
|
|
||||||
case DH:
|
|
||||||
return DNSSEC.DH;
|
|
||||||
default:
|
|
||||||
return UNKNOWN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDSA(int algorithm)
|
|
||||||
{
|
|
||||||
return (baseType(algorithm) == DSA);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyPair generateKeyPair(int algorithm, int keysize, boolean useLargeExp)
|
|
||||||
throws NoSuchAlgorithmException
|
|
||||||
{
|
|
||||||
KeyPair pair = null;
|
|
||||||
switch (baseType(algorithm))
|
|
||||||
{
|
|
||||||
case RSA:
|
|
||||||
if (mRSAKeyGenerator == null)
|
|
||||||
{
|
|
||||||
mRSAKeyGenerator = KeyPairGenerator.getInstance("RSA");
|
|
||||||
}
|
|
||||||
|
|
||||||
RSAKeyGenParameterSpec rsa_spec;
|
|
||||||
if (useLargeExp)
|
|
||||||
{
|
|
||||||
rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F4);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rsa_spec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F0);
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mRSAKeyGenerator.initialize(rsa_spec);
|
|
||||||
}
|
|
||||||
catch (InvalidAlgorithmParameterException e)
|
|
||||||
{
|
|
||||||
// Fold the InvalidAlgorithmParameterException into our existing
|
|
||||||
// thrown exception. Ugly, but requires less code change.
|
|
||||||
throw new NoSuchAlgorithmException("invalid key parameter spec");
|
|
||||||
}
|
|
||||||
|
|
||||||
pair = mRSAKeyGenerator.generateKeyPair();
|
|
||||||
break;
|
|
||||||
case DSA:
|
|
||||||
if (mDSAKeyGenerator == null)
|
|
||||||
{
|
|
||||||
mDSAKeyGenerator = KeyPairGenerator.getInstance("DSA");
|
|
||||||
}
|
|
||||||
mDSAKeyGenerator.initialize(keysize);
|
|
||||||
pair = mDSAKeyGenerator.generateKeyPair();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new NoSuchAlgorithmException("Alg " + algorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pair;
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyPair generateKeyPair(int algorithm, int keysize)
|
|
||||||
throws NoSuchAlgorithmException
|
|
||||||
{
|
|
||||||
return generateKeyPair(algorithm, keysize, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DnsKeyAlgorithm getInstance()
|
|
||||||
{
|
|
||||||
if (mInstance == null) mInstance = new DnsKeyAlgorithm();
|
|
||||||
return mInstance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,550 +0,0 @@
|
|||||||
// $Id$
|
|
||||||
//
|
|
||||||
// Copyright (C) 2001-2003 VeriSign, Inc.
|
|
||||||
//
|
|
||||||
// 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.security;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.KeyFactory;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.interfaces.DSAParams;
|
|
||||||
import java.security.interfaces.DSAPrivateKey;
|
|
||||||
import java.security.interfaces.DSAPublicKey;
|
|
||||||
import java.security.interfaces.RSAPrivateCrtKey;
|
|
||||||
import java.security.spec.DSAPrivateKeySpec;
|
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.security.spec.KeySpec;
|
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
|
||||||
import java.security.spec.RSAPrivateCrtKeySpec;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
|
|
||||||
import javax.crypto.interfaces.DHPrivateKey;
|
|
||||||
import javax.crypto.interfaces.DHPublicKey;
|
|
||||||
import javax.crypto.spec.DHParameterSpec;
|
|
||||||
import javax.crypto.spec.DHPrivateKeySpec;
|
|
||||||
|
|
||||||
import org.xbill.DNS.DNSKEYRecord;
|
|
||||||
import org.xbill.DNS.Name;
|
|
||||||
import org.xbill.DNS.Record;
|
|
||||||
import org.xbill.DNS.Type;
|
|
||||||
import org.xbill.DNS.security.KEYConverter;
|
|
||||||
import org.xbill.DNS.utils.base64;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class handles conversions between JCA key formats and DNSSEC and BIND9
|
|
||||||
* key formats.
|
|
||||||
*
|
|
||||||
* @author David Blacka (original)
|
|
||||||
* @author $Author$ (latest)
|
|
||||||
* @version $Revision$
|
|
||||||
*/
|
|
||||||
public class DnsKeyConverter
|
|
||||||
{
|
|
||||||
private KeyFactory mRSAKeyFactory;
|
|
||||||
private KeyFactory mDSAKeyFactory;
|
|
||||||
private KeyFactory mDHKeyFactory;
|
|
||||||
|
|
||||||
public DnsKeyConverter()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a DNS KEY record, return the JCA public key
|
|
||||||
*
|
|
||||||
* @throws NoSuchAlgorithmException
|
|
||||||
*/
|
|
||||||
public PublicKey parseDNSKEYRecord(DNSKEYRecord pKeyRecord)
|
|
||||||
throws NoSuchAlgorithmException
|
|
||||||
{
|
|
||||||
if (pKeyRecord.getKey() == null) return null;
|
|
||||||
|
|
||||||
// For now, instead of re-implementing parseRecord (or adding this stuff
|
|
||||||
// to DNSjava), we will just translate the algorithm back to a standard
|
|
||||||
// algorithm. Note that this will unnecessarily convert RSAMD5 to RSASHA1.
|
|
||||||
|
|
||||||
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
|
||||||
int standard_alg = algs.standardAlgorithm(pKeyRecord.getAlgorithm());
|
|
||||||
|
|
||||||
if (standard_alg <= 0)
|
|
||||||
throw new NoSuchAlgorithmException("DNSKEY algorithm "
|
|
||||||
+ pKeyRecord.getAlgorithm() + " is unrecognized");
|
|
||||||
|
|
||||||
if (pKeyRecord.getAlgorithm() != standard_alg)
|
|
||||||
{
|
|
||||||
pKeyRecord = new DNSKEYRecord(pKeyRecord.getName(),
|
|
||||||
pKeyRecord.getDClass(),
|
|
||||||
pKeyRecord.getTTL(), pKeyRecord.getFlags(),
|
|
||||||
pKeyRecord.getProtocol(), standard_alg,
|
|
||||||
pKeyRecord.getKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
return KEYConverter.parseRecord(pKeyRecord);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a JCA public key and the ancillary data, generate a DNSKEY record.
|
|
||||||
*/
|
|
||||||
public DNSKEYRecord generateDNSKEYRecord(Name name, int dclass, long ttl,
|
|
||||||
int flags, int alg, PublicKey key)
|
|
||||||
{
|
|
||||||
Record kr = KEYConverter.buildRecord(name, Type.DNSKEY, dclass, ttl, flags,
|
|
||||||
DNSKEYRecord.Protocol.DNSSEC, alg, key);
|
|
||||||
|
|
||||||
return (DNSKEYRecord) kr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private Key Specific Parsing routines
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a PKCS#8 encoded private key into a PrivateKey object.
|
|
||||||
*/
|
|
||||||
public PrivateKey convertEncodedPrivateKey(byte[] key, int algorithm)
|
|
||||||
{
|
|
||||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
|
|
||||||
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
switch (algs.baseType(algorithm))
|
|
||||||
{
|
|
||||||
case DnsKeyAlgorithm.RSA:
|
|
||||||
return mRSAKeyFactory.generatePrivate(spec);
|
|
||||||
case DnsKeyAlgorithm.DSA:
|
|
||||||
return mDSAKeyFactory.generatePrivate(spec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (GeneralSecurityException e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int parseInt(String s, int def)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return Integer.parseInt(s);
|
|
||||||
}
|
|
||||||
catch (NumberFormatException e)
|
|
||||||
{
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a JCA private key, given a BIND9-style textual encoding
|
|
||||||
*/
|
|
||||||
public PrivateKey parsePrivateKeyString(String key)
|
|
||||||
throws IOException, NoSuchAlgorithmException
|
|
||||||
{
|
|
||||||
StringTokenizer lines = new StringTokenizer(key, "\n");
|
|
||||||
|
|
||||||
while (lines.hasMoreTokens())
|
|
||||||
{
|
|
||||||
String line = lines.nextToken();
|
|
||||||
if (line == null) continue;
|
|
||||||
|
|
||||||
if (line.startsWith("#")) continue;
|
|
||||||
|
|
||||||
String val = value(line);
|
|
||||||
if (val == null) continue;
|
|
||||||
|
|
||||||
if (line.startsWith("Private-key-format: "))
|
|
||||||
{
|
|
||||||
if (!val.equals("v1.2") && !val.equals("v1.3"))
|
|
||||||
{
|
|
||||||
throw new IOException("unsupported private key format: " + val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (line.startsWith("Algorithm: "))
|
|
||||||
{
|
|
||||||
// here we assume that the value looks like # (MNEM) or just the
|
|
||||||
// number.
|
|
||||||
String[] toks = val.split("\\s", 2);
|
|
||||||
val = toks[0];
|
|
||||||
int alg = parseInt(val, -1);
|
|
||||||
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
|
||||||
|
|
||||||
switch (algs.baseType(alg))
|
|
||||||
{
|
|
||||||
case DnsKeyAlgorithm.RSA:
|
|
||||||
return parsePrivateRSA(lines);
|
|
||||||
case DnsKeyAlgorithm.DSA:
|
|
||||||
return parsePrivateDSA(lines);
|
|
||||||
case DnsKeyAlgorithm.DH:
|
|
||||||
return parsePrivateDH(lines);
|
|
||||||
default:
|
|
||||||
throw new IOException("unsupported private key algorithm: " + val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the value part of an "attribute:value" pair. The value is trimmed.
|
|
||||||
*/
|
|
||||||
private String value(String av)
|
|
||||||
{
|
|
||||||
if (av == null) return null;
|
|
||||||
|
|
||||||
int pos = av.indexOf(':');
|
|
||||||
if (pos < 0) return av;
|
|
||||||
|
|
||||||
if (pos >= av.length()) return null;
|
|
||||||
|
|
||||||
return av.substring(pos + 1).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given the rest of the RSA BIND9 string format private key, parse and
|
|
||||||
* translate into a JCA private key
|
|
||||||
*
|
|
||||||
* @throws NoSuchAlgorithmException
|
|
||||||
* if the RSA algorithm is not available.
|
|
||||||
*/
|
|
||||||
private PrivateKey parsePrivateRSA(StringTokenizer lines)
|
|
||||||
throws NoSuchAlgorithmException
|
|
||||||
{
|
|
||||||
BigInteger modulus = null;
|
|
||||||
BigInteger public_exponent = null;
|
|
||||||
BigInteger private_exponent = null;
|
|
||||||
BigInteger prime_p = null;
|
|
||||||
BigInteger prime_q = null;
|
|
||||||
BigInteger prime_p_exponent = null;
|
|
||||||
BigInteger prime_q_exponent = null;
|
|
||||||
BigInteger coefficient = null;
|
|
||||||
|
|
||||||
while (lines.hasMoreTokens())
|
|
||||||
{
|
|
||||||
String line = lines.nextToken();
|
|
||||||
if (line == null) continue;
|
|
||||||
|
|
||||||
if (line.startsWith("#")) continue;
|
|
||||||
|
|
||||||
String val = value(line);
|
|
||||||
if (val == null) continue;
|
|
||||||
|
|
||||||
byte[] data = base64.fromString(val);
|
|
||||||
|
|
||||||
if (line.startsWith("Modulus: "))
|
|
||||||
{
|
|
||||||
modulus = new BigInteger(1, data);
|
|
||||||
// printBigIntCompare(data, modulus);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("PublicExponent: "))
|
|
||||||
{
|
|
||||||
public_exponent = new BigInteger(1, data);
|
|
||||||
// printBigIntCompare(data, public_exponent);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("PrivateExponent: "))
|
|
||||||
{
|
|
||||||
private_exponent = new BigInteger(1, data);
|
|
||||||
// printBigIntCompare(data, private_exponent);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("Prime1: "))
|
|
||||||
{
|
|
||||||
prime_p = new BigInteger(1, data);
|
|
||||||
// printBigIntCompare(data, prime_p);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("Prime2: "))
|
|
||||||
{
|
|
||||||
prime_q = new BigInteger(1, data);
|
|
||||||
// printBigIntCompare(data, prime_q);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("Exponent1: "))
|
|
||||||
{
|
|
||||||
prime_p_exponent = new BigInteger(1, data);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("Exponent2: "))
|
|
||||||
{
|
|
||||||
prime_q_exponent = new BigInteger(1, data);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("Coefficient: "))
|
|
||||||
{
|
|
||||||
coefficient = new BigInteger(1, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
KeySpec spec = new RSAPrivateCrtKeySpec(modulus, public_exponent,
|
|
||||||
private_exponent, prime_p,
|
|
||||||
prime_q, prime_p_exponent,
|
|
||||||
prime_q_exponent, coefficient);
|
|
||||||
if (mRSAKeyFactory == null)
|
|
||||||
{
|
|
||||||
mRSAKeyFactory = KeyFactory.getInstance("RSA");
|
|
||||||
}
|
|
||||||
return mRSAKeyFactory.generatePrivate(spec);
|
|
||||||
}
|
|
||||||
catch (InvalidKeySpecException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given the remaining lines in a BIND9 style DH private key, parse the key
|
|
||||||
* info and translate it into a JCA private key.
|
|
||||||
*
|
|
||||||
* @throws NoSuchAlgorithmException
|
|
||||||
* if the DH algorithm is not available.
|
|
||||||
*/
|
|
||||||
private PrivateKey parsePrivateDH(StringTokenizer lines)
|
|
||||||
throws NoSuchAlgorithmException
|
|
||||||
{
|
|
||||||
BigInteger p = null;
|
|
||||||
BigInteger x = null;
|
|
||||||
BigInteger g = null;
|
|
||||||
|
|
||||||
while (lines.hasMoreTokens())
|
|
||||||
{
|
|
||||||
String line = lines.nextToken();
|
|
||||||
if (line == null) continue;
|
|
||||||
|
|
||||||
if (line.startsWith("#")) continue;
|
|
||||||
|
|
||||||
String val = value(line);
|
|
||||||
if (val == null) continue;
|
|
||||||
|
|
||||||
byte[] data = base64.fromString(val);
|
|
||||||
|
|
||||||
if (line.startsWith("Prime(p): "))
|
|
||||||
{
|
|
||||||
p = new BigInteger(1, data);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("Generator(g): "))
|
|
||||||
{
|
|
||||||
g = new BigInteger(1, data);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("Private_value(x): "))
|
|
||||||
{
|
|
||||||
x = new BigInteger(1, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
KeySpec spec = new DHPrivateKeySpec(x, p, g);
|
|
||||||
if (mDHKeyFactory == null)
|
|
||||||
{
|
|
||||||
mDHKeyFactory = KeyFactory.getInstance("DH");
|
|
||||||
}
|
|
||||||
return mDHKeyFactory.generatePrivate(spec);
|
|
||||||
}
|
|
||||||
catch (InvalidKeySpecException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given the remaining lines in a BIND9 style DSA private key, parse the key
|
|
||||||
* info and translate it into a JCA private key.
|
|
||||||
*
|
|
||||||
* @throws NoSuchAlgorithmException
|
|
||||||
* if the DSA algorithm is not available.
|
|
||||||
*/
|
|
||||||
private PrivateKey parsePrivateDSA(StringTokenizer lines)
|
|
||||||
throws NoSuchAlgorithmException
|
|
||||||
{
|
|
||||||
BigInteger p = null;
|
|
||||||
BigInteger q = null;
|
|
||||||
BigInteger g = null;
|
|
||||||
BigInteger x = null;
|
|
||||||
|
|
||||||
while (lines.hasMoreTokens())
|
|
||||||
{
|
|
||||||
String line = lines.nextToken();
|
|
||||||
if (line == null) continue;
|
|
||||||
|
|
||||||
if (line.startsWith("#")) continue;
|
|
||||||
|
|
||||||
String val = value(line);
|
|
||||||
if (val == null) continue;
|
|
||||||
|
|
||||||
byte[] data = base64.fromString(val);
|
|
||||||
|
|
||||||
if (line.startsWith("Prime(p): "))
|
|
||||||
{
|
|
||||||
p = new BigInteger(1, data);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("Subprime(q): "))
|
|
||||||
{
|
|
||||||
q = new BigInteger(1, data);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("Base(g): "))
|
|
||||||
{
|
|
||||||
g = new BigInteger(1, data);
|
|
||||||
}
|
|
||||||
else if (line.startsWith("Private_value(x): "))
|
|
||||||
{
|
|
||||||
x = new BigInteger(1, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
KeySpec spec = new DSAPrivateKeySpec(x, p, q, g);
|
|
||||||
if (mDSAKeyFactory == null)
|
|
||||||
{
|
|
||||||
mDSAKeyFactory = KeyFactory.getInstance("DSA");
|
|
||||||
}
|
|
||||||
return mDSAKeyFactory.generatePrivate(spec);
|
|
||||||
}
|
|
||||||
catch (InvalidKeySpecException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a private key and public key, generate the BIND9 style private key
|
|
||||||
* format.
|
|
||||||
*/
|
|
||||||
public String generatePrivateKeyString(PrivateKey priv, PublicKey pub, int alg)
|
|
||||||
{
|
|
||||||
if (priv instanceof RSAPrivateCrtKey)
|
|
||||||
{
|
|
||||||
return generatePrivateRSA((RSAPrivateCrtKey) priv, alg);
|
|
||||||
}
|
|
||||||
else if (priv instanceof DSAPrivateKey && pub instanceof DSAPublicKey)
|
|
||||||
{
|
|
||||||
return generatePrivateDSA((DSAPrivateKey) priv, (DSAPublicKey) pub, alg);
|
|
||||||
}
|
|
||||||
else if (priv instanceof DHPrivateKey && pub instanceof DHPublicKey)
|
|
||||||
{
|
|
||||||
return generatePrivateDH((DHPrivateKey) priv, (DHPublicKey) pub, alg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert from 'unsigned' big integer to original 'signed format' in Base64
|
|
||||||
*/
|
|
||||||
private String b64BigInt(BigInteger i)
|
|
||||||
{
|
|
||||||
byte[] orig_bytes = i.toByteArray();
|
|
||||||
|
|
||||||
if (orig_bytes[0] != 0 || orig_bytes.length == 1)
|
|
||||||
{
|
|
||||||
return base64.toString(orig_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] signed_bytes = new byte[orig_bytes.length - 1];
|
|
||||||
System.arraycopy(orig_bytes, 1, signed_bytes, 0, signed_bytes.length);
|
|
||||||
|
|
||||||
return base64.toString(signed_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a RSA private key (in Crt format), return the BIND9-style text
|
|
||||||
* encoding.
|
|
||||||
*/
|
|
||||||
private String generatePrivateRSA(RSAPrivateCrtKey key, int algorithm)
|
|
||||||
{
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
PrintWriter out = new PrintWriter(sw);
|
|
||||||
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
|
||||||
|
|
||||||
out.println("Private-key-format: v1.2");
|
|
||||||
out.println("Algorithm: " + algorithm + " (" + algs.algToString(algorithm)
|
|
||||||
+ ")");
|
|
||||||
out.print("Modulus: ");
|
|
||||||
out.println(b64BigInt(key.getModulus()));
|
|
||||||
out.print("PublicExponent: ");
|
|
||||||
out.println(b64BigInt(key.getPublicExponent()));
|
|
||||||
out.print("PrivateExponent: ");
|
|
||||||
out.println(b64BigInt(key.getPrivateExponent()));
|
|
||||||
out.print("Prime1: ");
|
|
||||||
out.println(b64BigInt(key.getPrimeP()));
|
|
||||||
out.print("Prime2: ");
|
|
||||||
out.println(b64BigInt(key.getPrimeQ()));
|
|
||||||
out.print("Exponent1: ");
|
|
||||||
out.println(b64BigInt(key.getPrimeExponentP()));
|
|
||||||
out.print("Exponent2: ");
|
|
||||||
out.println(b64BigInt(key.getPrimeExponentQ()));
|
|
||||||
out.print("Coefficient: ");
|
|
||||||
out.println(b64BigInt(key.getCrtCoefficient()));
|
|
||||||
|
|
||||||
return sw.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Given a DH key pair, return the BIND9-style text encoding */
|
|
||||||
private String generatePrivateDH(DHPrivateKey key, DHPublicKey pub,
|
|
||||||
int algorithm)
|
|
||||||
{
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
PrintWriter out = new PrintWriter(sw);
|
|
||||||
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
|
||||||
|
|
||||||
DHParameterSpec p = key.getParams();
|
|
||||||
|
|
||||||
out.println("Private-key-format: v1.2");
|
|
||||||
out.println("Algorithm: " + algorithm + " (" + algs.algToString(algorithm)
|
|
||||||
+ ")");
|
|
||||||
out.print("Prime(p): ");
|
|
||||||
out.println(b64BigInt(p.getP()));
|
|
||||||
out.print("Generator(g): ");
|
|
||||||
out.println(b64BigInt(p.getG()));
|
|
||||||
out.print("Private_value(x): ");
|
|
||||||
out.println(b64BigInt(key.getX()));
|
|
||||||
out.print("Public_value(y): ");
|
|
||||||
out.println(b64BigInt(pub.getY()));
|
|
||||||
|
|
||||||
return sw.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Given a DSA key pair, return the BIND9-style text encoding */
|
|
||||||
private String generatePrivateDSA(DSAPrivateKey key, DSAPublicKey pub,
|
|
||||||
int algorithm)
|
|
||||||
{
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
PrintWriter out = new PrintWriter(sw);
|
|
||||||
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
|
||||||
|
|
||||||
DSAParams p = key.getParams();
|
|
||||||
|
|
||||||
out.println("Private-key-format: v1.2");
|
|
||||||
out.println("Algorithm: " + algorithm + " (" + algs.algToString(algorithm)
|
|
||||||
+ ")");
|
|
||||||
out.print("Prime(p): ");
|
|
||||||
out.println(b64BigInt(p.getP()));
|
|
||||||
out.print("Subprime(q): ");
|
|
||||||
out.println(b64BigInt(p.getQ()));
|
|
||||||
out.print("Base(g): ");
|
|
||||||
out.println(b64BigInt(p.getG()));
|
|
||||||
out.print("Private_value(x): ");
|
|
||||||
out.println(b64BigInt(key.getX()));
|
|
||||||
out.print("Public_value(y): ");
|
|
||||||
out.println(b64BigInt(pub.getY()));
|
|
||||||
|
|
||||||
return sw.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,348 +0,0 @@
|
|||||||
// $Id$
|
|
||||||
//
|
|
||||||
// Copyright (C) 2001-2003 VeriSign, Inc.
|
|
||||||
//
|
|
||||||
// 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.security;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.Signature;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import org.xbill.DNS.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A class for performing basic DNSSEC verification. The DNSJAVA package
|
|
||||||
* contains a similar class. This differs (for the moment, anyway) by allowing
|
|
||||||
* timing "fudge" factors and logging more specifically why an RRset did not
|
|
||||||
* validate.
|
|
||||||
*
|
|
||||||
* @author David Blacka (original)
|
|
||||||
* @author $Author$
|
|
||||||
* @version $Revision$
|
|
||||||
*/
|
|
||||||
public class DnsSecVerifier implements Verifier
|
|
||||||
{
|
|
||||||
|
|
||||||
private class TrustedKeyStore
|
|
||||||
{
|
|
||||||
// for now, this is implemented as a hash table of lists of
|
|
||||||
// DnsKeyPair objects (obviously, all of them will not have
|
|
||||||
// private keys).
|
|
||||||
private HashMap<String, List<DnsKeyPair>> mKeyMap;
|
|
||||||
|
|
||||||
public TrustedKeyStore()
|
|
||||||
{
|
|
||||||
mKeyMap = new HashMap<String, List<DnsKeyPair>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(DnsKeyPair pair)
|
|
||||||
{
|
|
||||||
String n = pair.getDNSKEYName().toString().toLowerCase();
|
|
||||||
List<DnsKeyPair> l = mKeyMap.get(n);
|
|
||||||
if (l == null)
|
|
||||||
{
|
|
||||||
l = new ArrayList<DnsKeyPair>();
|
|
||||||
mKeyMap.put(n, l);
|
|
||||||
}
|
|
||||||
|
|
||||||
l.add(pair);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(DNSKEYRecord keyrec)
|
|
||||||
{
|
|
||||||
DnsKeyPair pair = new DnsKeyPair(keyrec, (PrivateKey) null);
|
|
||||||
add(pair);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(Name name, int algorithm, PublicKey key)
|
|
||||||
{
|
|
||||||
DnsKeyPair pair = new DnsKeyPair(name, algorithm, key, null);
|
|
||||||
add(pair);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DnsKeyPair find(Name name, int algorithm, int keyid)
|
|
||||||
{
|
|
||||||
String n = name.toString().toLowerCase();
|
|
||||||
List<DnsKeyPair> l = mKeyMap.get(n);
|
|
||||||
if (l == null) return null;
|
|
||||||
|
|
||||||
// FIXME: this algorithm assumes that name+alg+footprint is
|
|
||||||
// unique, which isn't necessarily true.
|
|
||||||
for (DnsKeyPair p : l)
|
|
||||||
{
|
|
||||||
if (p.getDNSKEYAlgorithm() == algorithm && p.getDNSKEYFootprint() == keyid)
|
|
||||||
{
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private TrustedKeyStore mKeyStore;
|
|
||||||
private int mStartFudge = 0;
|
|
||||||
private int mExpireFudge = 0;
|
|
||||||
private boolean mVerifyAllSigs = false;
|
|
||||||
private boolean mIgnoreTime = false;
|
|
||||||
|
|
||||||
private Logger log;
|
|
||||||
|
|
||||||
public DnsSecVerifier()
|
|
||||||
{
|
|
||||||
log = Logger.getLogger(this.getClass().toString());
|
|
||||||
|
|
||||||
mKeyStore = new TrustedKeyStore();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addTrustedKey(DNSKEYRecord keyrec)
|
|
||||||
{
|
|
||||||
mKeyStore.add(keyrec);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addTrustedKey(DnsKeyPair pair)
|
|
||||||
{
|
|
||||||
mKeyStore.add(pair);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addTrustedKey(Name name, int algorithm, PublicKey key)
|
|
||||||
{
|
|
||||||
mKeyStore.add(name, algorithm, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addTrustedKey(Name name, PublicKey key)
|
|
||||||
{
|
|
||||||
mKeyStore.add(name, 0, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExpireFudge(int fudge)
|
|
||||||
{
|
|
||||||
mExpireFudge = fudge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStartFudge(int fudge)
|
|
||||||
{
|
|
||||||
mStartFudge = fudge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVerifyAllSigs(boolean v)
|
|
||||||
{
|
|
||||||
mVerifyAllSigs = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIgnoreTime(boolean v)
|
|
||||||
{
|
|
||||||
mIgnoreTime = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private DnsKeyPair findCachedKey(Cache cache, Name name, int algorithm, int footprint)
|
|
||||||
{
|
|
||||||
RRset[] keysets = cache.findAnyRecords(name, Type.KEY);
|
|
||||||
if (keysets == null) return null;
|
|
||||||
|
|
||||||
// look for the particular key
|
|
||||||
// FIXME: this assumes that name+alg+footprint is unique.
|
|
||||||
for (Iterator<Record> i = keysets[0].rrs(); i.hasNext();)
|
|
||||||
{
|
|
||||||
Record r = i.next();
|
|
||||||
if (r.getType() != Type.DNSKEY) continue;
|
|
||||||
DNSKEYRecord keyrec = (DNSKEYRecord) r;
|
|
||||||
if (keyrec.getAlgorithm() == algorithm && keyrec.getFootprint() == footprint)
|
|
||||||
{
|
|
||||||
return new DnsKeyPair(keyrec, (PrivateKey) null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DnsKeyPair findKey(Cache cache, Name name, int algorithm, int footprint)
|
|
||||||
{
|
|
||||||
DnsKeyPair pair = mKeyStore.find(name, algorithm, footprint);
|
|
||||||
if (pair == null && cache != null)
|
|
||||||
{
|
|
||||||
pair = findCachedKey(cache, name, algorithm, footprint);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pair;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte validateSignature(RRset rrset, RRSIGRecord sigrec, List<String> reasons)
|
|
||||||
{
|
|
||||||
if (rrset == null || sigrec == null) return DNSSEC.Failed;
|
|
||||||
if (!rrset.getName().equals(sigrec.getName()))
|
|
||||||
{
|
|
||||||
log.fine("Signature name does not match RRset name");
|
|
||||||
if (reasons != null) reasons.add("Signature name does not match RRset name");
|
|
||||||
return DNSSEC.Failed;
|
|
||||||
}
|
|
||||||
if (rrset.getType() != sigrec.getTypeCovered())
|
|
||||||
{
|
|
||||||
log.fine("Signature type does not match RRset type");
|
|
||||||
if (reasons != null) reasons.add("Signature type does not match RRset type");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mIgnoreTime) return DNSSEC.Secure;
|
|
||||||
|
|
||||||
Date now = new Date();
|
|
||||||
Date start = sigrec.getTimeSigned();
|
|
||||||
Date expire = sigrec.getExpire();
|
|
||||||
|
|
||||||
if (mStartFudge >= 0)
|
|
||||||
{
|
|
||||||
if (mStartFudge > 0)
|
|
||||||
{
|
|
||||||
start = new Date(start.getTime() - ((long) mStartFudge * 1000));
|
|
||||||
}
|
|
||||||
if (now.before(start))
|
|
||||||
{
|
|
||||||
log.fine("Signature is not yet valid");
|
|
||||||
if (reasons != null) reasons.add("Signature not yet valid");
|
|
||||||
return DNSSEC.Failed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mExpireFudge >= 0)
|
|
||||||
{
|
|
||||||
if (mExpireFudge > 0)
|
|
||||||
{
|
|
||||||
expire = new Date(expire.getTime() + ((long) mExpireFudge * 1000));
|
|
||||||
}
|
|
||||||
if (now.after(expire))
|
|
||||||
{
|
|
||||||
log.fine("Signature has expired (now = " + now + ", sig expires = " + expire);
|
|
||||||
if (reasons != null) reasons.add("Signature has expired.");
|
|
||||||
return DNSSEC.Failed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return DNSSEC.Secure;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte verifySignature(RRset rrset, RRSIGRecord sigrec, Cache cache)
|
|
||||||
{
|
|
||||||
return verifySignature(rrset, sigrec, cache, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify an RRset against a particular signature.
|
|
||||||
*
|
|
||||||
* @return DNSSEC.Secure if the signature verified, DNSSEC.Failed if it did
|
|
||||||
* not verify (for any reason), and DNSSEC.Insecure if verification
|
|
||||||
* could not be completed (usually because the public key was not
|
|
||||||
* available).
|
|
||||||
*/
|
|
||||||
public byte verifySignature(RRset rrset, RRSIGRecord sigrec, Cache cache, List<String> reasons)
|
|
||||||
{
|
|
||||||
byte result = validateSignature(rrset, sigrec, reasons);
|
|
||||||
if (result != DNSSEC.Secure) return result;
|
|
||||||
|
|
||||||
DnsKeyPair keypair = findKey(cache, sigrec.getSigner(), sigrec.getAlgorithm(),
|
|
||||||
sigrec.getFootprint());
|
|
||||||
|
|
||||||
if (keypair == null)
|
|
||||||
{
|
|
||||||
if (reasons != null) reasons.add("Could not find matching trusted key");
|
|
||||||
log.fine("could not find matching trusted key");
|
|
||||||
return DNSSEC.Insecure;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
byte[] data = SignUtils.generateSigData(rrset, sigrec);
|
|
||||||
|
|
||||||
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
|
||||||
|
|
||||||
Signature signer = keypair.getVerifier();
|
|
||||||
signer.update(data);
|
|
||||||
|
|
||||||
byte[] sig = sigrec.getSignature();
|
|
||||||
|
|
||||||
if (algs.baseType(sigrec.getAlgorithm()) == DnsKeyAlgorithm.DSA)
|
|
||||||
{
|
|
||||||
sig = SignUtils.convertDSASignature(sig);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!signer.verify(sig))
|
|
||||||
{
|
|
||||||
if (reasons != null) reasons.add("Signature failed to verify cryptographically");
|
|
||||||
log.fine("Signature failed to verify cryptographically");
|
|
||||||
return DNSSEC.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
return DNSSEC.Secure;
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
log.severe("I/O error: " + e);
|
|
||||||
}
|
|
||||||
catch (GeneralSecurityException e)
|
|
||||||
{
|
|
||||||
log.severe("Security error: " + e);
|
|
||||||
}
|
|
||||||
if (reasons != null) reasons.add("Signature failed to verify due to exception");
|
|
||||||
log.fine("Signature failed to verify due to exception");
|
|
||||||
return DNSSEC.Insecure;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies an RRset. This routine does not modify the RRset.
|
|
||||||
*
|
|
||||||
* @return DNSSEC.Secure if the set verified, DNSSEC.Failed if it did not, and
|
|
||||||
* DNSSEC.Insecure if verification could not complete.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public int verify(RRset rrset, Cache cache)
|
|
||||||
{
|
|
||||||
int result = mVerifyAllSigs ? DNSSEC.Secure : DNSSEC.Insecure;
|
|
||||||
|
|
||||||
Iterator i = rrset.sigs();
|
|
||||||
|
|
||||||
if (!i.hasNext())
|
|
||||||
{
|
|
||||||
log.fine("RRset failed to verify due to lack of signatures");
|
|
||||||
return DNSSEC.Insecure;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (i.hasNext())
|
|
||||||
{
|
|
||||||
RRSIGRecord sigrec = (RRSIGRecord) i.next();
|
|
||||||
|
|
||||||
byte res = verifySignature(rrset, sigrec, cache);
|
|
||||||
|
|
||||||
if (!mVerifyAllSigs && res == DNSSEC.Secure) return res;
|
|
||||||
|
|
||||||
if (!mVerifyAllSigs && res < result) result = res;
|
|
||||||
|
|
||||||
if (mVerifyAllSigs && res != DNSSEC.Secure && res < result)
|
|
||||||
{
|
|
||||||
result = res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,600 +0,0 @@
|
|||||||
// $Id$
|
|
||||||
//
|
|
||||||
// Copyright (C) 2001-2003, 2009 VeriSign, Inc.
|
|
||||||
//
|
|
||||||
// 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.security;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.Signature;
|
|
||||||
import java.security.interfaces.DSAPublicKey;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ListIterator;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import org.xbill.DNS.DNSKEYRecord;
|
|
||||||
import org.xbill.DNS.Name;
|
|
||||||
import org.xbill.DNS.RRSIGRecord;
|
|
||||||
import org.xbill.DNS.RRset;
|
|
||||||
import org.xbill.DNS.Record;
|
|
||||||
import org.xbill.DNS.Type;
|
|
||||||
import org.xbill.DNS.utils.hexdump;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class contains routines for signing DNS zones.
|
|
||||||
*
|
|
||||||
* In particular, it contains both an ability to sign an individual RRset and
|
|
||||||
* the ability to sign an entire zone. It primarily glues together the more
|
|
||||||
* basic primitives found in {@link SignUtils}.
|
|
||||||
*
|
|
||||||
* @author David Blacka (original)
|
|
||||||
* @author $Author$
|
|
||||||
* @version $Revision$
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class JCEDnsSecSigner
|
|
||||||
{
|
|
||||||
private DnsKeyConverter mKeyConverter;
|
|
||||||
private boolean mVerboseSigning = false;
|
|
||||||
|
|
||||||
private Logger log;
|
|
||||||
|
|
||||||
public JCEDnsSecSigner()
|
|
||||||
{
|
|
||||||
this.mKeyConverter = null;
|
|
||||||
this.mVerboseSigning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JCEDnsSecSigner(boolean verboseSigning)
|
|
||||||
{
|
|
||||||
this.mKeyConverter = null;
|
|
||||||
this.mVerboseSigning = verboseSigning;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cryptographically generate a new DNSSEC key.
|
|
||||||
*
|
|
||||||
* @param owner
|
|
||||||
* the KEY RR's owner name.
|
|
||||||
* @param ttl
|
|
||||||
* the KEY RR's TTL.
|
|
||||||
* @param dclass
|
|
||||||
* the KEY RR's DNS class.
|
|
||||||
* @param algorithm
|
|
||||||
* the DNSSEC algorithm (RSAMD5, RSASHA1, or DSA).
|
|
||||||
* @param flags
|
|
||||||
* any flags for the KEY RR.
|
|
||||||
* @param keysize
|
|
||||||
* the size of the key to generate.
|
|
||||||
* @param useLargeExponent
|
|
||||||
* if generating an RSA key, use the large exponent.
|
|
||||||
* @return a DnsKeyPair with the public and private keys populated.
|
|
||||||
*/
|
|
||||||
public DnsKeyPair generateKey(Name owner, long ttl, int dclass, int algorithm,
|
|
||||||
int flags, int keysize, boolean useLargeExponent)
|
|
||||||
throws NoSuchAlgorithmException
|
|
||||||
{
|
|
||||||
DnsKeyAlgorithm algorithms = DnsKeyAlgorithm.getInstance();
|
|
||||||
|
|
||||||
if (ttl < 0) ttl = 86400; // set to a reasonable default.
|
|
||||||
|
|
||||||
KeyPair pair = algorithms.generateKeyPair(algorithm, keysize, useLargeExponent);
|
|
||||||
|
|
||||||
if (mKeyConverter == null)
|
|
||||||
{
|
|
||||||
mKeyConverter = new DnsKeyConverter();
|
|
||||||
}
|
|
||||||
|
|
||||||
DNSKEYRecord keyrec = mKeyConverter.generateDNSKEYRecord(owner, dclass, ttl, flags,
|
|
||||||
algorithm, pair.getPublic());
|
|
||||||
|
|
||||||
DnsKeyPair dnspair = new DnsKeyPair();
|
|
||||||
dnspair.setDNSKEYRecord(keyrec);
|
|
||||||
dnspair.setPublic(pair.getPublic()); // keep from conv. the keyrec back.
|
|
||||||
dnspair.setPrivate(pair.getPrivate());
|
|
||||||
|
|
||||||
return dnspair;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sign an RRset.
|
|
||||||
*
|
|
||||||
* @param rrset
|
|
||||||
* the RRset to sign -- any existing signatures are ignored.
|
|
||||||
* @param keypars
|
|
||||||
* a list of DnsKeyPair objects containing private keys.
|
|
||||||
* @param start
|
|
||||||
* the inception time for the resulting RRSIG records.
|
|
||||||
* @param expire
|
|
||||||
* the expiration time for the resulting RRSIG records.
|
|
||||||
* @return a list of RRSIGRecord objects.
|
|
||||||
*/
|
|
||||||
public List<RRSIGRecord> signRRset(RRset rrset, List<DnsKeyPair> keypairs, Date start,
|
|
||||||
Date expire) throws IOException,
|
|
||||||
GeneralSecurityException
|
|
||||||
{
|
|
||||||
if (rrset == null || keypairs == null) return null;
|
|
||||||
|
|
||||||
// default start to now, expire to start + 1 second.
|
|
||||||
if (start == null) start = new Date();
|
|
||||||
if (expire == null) expire = new Date(start.getTime() + 1000L);
|
|
||||||
if (keypairs.size() == 0) return null;
|
|
||||||
|
|
||||||
if (mVerboseSigning)
|
|
||||||
{
|
|
||||||
log.info("Signing RRset:");
|
|
||||||
log.info(ZoneUtils.rrsetToString(rrset, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
// first, pre-calculate the RRset bytes.
|
|
||||||
byte[] rrset_data = SignUtils.generateCanonicalRRsetData(rrset, 0, 0);
|
|
||||||
|
|
||||||
ArrayList<RRSIGRecord> sigs = new ArrayList<RRSIGRecord>(keypairs.size());
|
|
||||||
|
|
||||||
// for each keypair, sign the RRset.
|
|
||||||
for (DnsKeyPair pair : keypairs)
|
|
||||||
{
|
|
||||||
DNSKEYRecord keyrec = pair.getDNSKEYRecord();
|
|
||||||
if (keyrec == null) continue;
|
|
||||||
|
|
||||||
RRSIGRecord presig = SignUtils.generatePreRRSIG(rrset, keyrec, start, expire,
|
|
||||||
rrset.getTTL());
|
|
||||||
byte[] sign_data = SignUtils.generateSigData(rrset_data, presig);
|
|
||||||
|
|
||||||
if (mVerboseSigning)
|
|
||||||
{
|
|
||||||
log.info("Canonical pre-signature data to sign with key "
|
|
||||||
+ keyrec.getName().toString() + "/" + keyrec.getAlgorithm() + "/"
|
|
||||||
+ keyrec.getFootprint() + ":");
|
|
||||||
log.info(hexdump.dump(null, sign_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
Signature signer = pair.getSigner();
|
|
||||||
|
|
||||||
if (signer == null)
|
|
||||||
{
|
|
||||||
// debug
|
|
||||||
log.fine("missing private key that goes with:\n" + pair.getDNSKEYRecord());
|
|
||||||
throw new GeneralSecurityException("cannot sign without a valid Signer "
|
|
||||||
+ "(probably missing private key)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// sign the data.
|
|
||||||
signer.update(sign_data);
|
|
||||||
byte[] sig = signer.sign();
|
|
||||||
|
|
||||||
if (mVerboseSigning)
|
|
||||||
{
|
|
||||||
log.info("Raw Signature:");
|
|
||||||
log.info(hexdump.dump(null, sig));
|
|
||||||
}
|
|
||||||
|
|
||||||
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
|
||||||
// Convert to RFC 2536 format, if necessary.
|
|
||||||
if (algs.baseType(pair.getDNSKEYAlgorithm()) == DnsKeyAlgorithm.DSA)
|
|
||||||
{
|
|
||||||
DSAPublicKey pk = (DSAPublicKey) pair.getPublic();
|
|
||||||
sig = SignUtils.convertDSASignature(pk.getParams(), sig);
|
|
||||||
}
|
|
||||||
RRSIGRecord sigrec = SignUtils.generateRRSIG(sig, presig);
|
|
||||||
if (mVerboseSigning)
|
|
||||||
{
|
|
||||||
log.info("RRSIG:\n" + sigrec);
|
|
||||||
}
|
|
||||||
sigs.add(sigrec);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sigs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a completely self-signed DNSKEY RRset.
|
|
||||||
*
|
|
||||||
* @param keypairs
|
|
||||||
* the public & private keypairs to use in the keyset.
|
|
||||||
* @param start
|
|
||||||
* the RRSIG inception time.
|
|
||||||
* @param expire
|
|
||||||
* the RRSIG expiration time.
|
|
||||||
* @return a signed RRset.
|
|
||||||
*/
|
|
||||||
public RRset makeKeySet(List<DnsKeyPair> keypairs, Date start, Date expire)
|
|
||||||
throws IOException, GeneralSecurityException
|
|
||||||
{
|
|
||||||
// Generate a KEY RR set to sign.
|
|
||||||
|
|
||||||
RRset keyset = new RRset();
|
|
||||||
|
|
||||||
for (DnsKeyPair pair : keypairs)
|
|
||||||
{
|
|
||||||
keyset.addRR(pair.getDNSKEYRecord());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<RRSIGRecord> records = signRRset(keyset, keypairs, start, expire);
|
|
||||||
|
|
||||||
for (RRSIGRecord r : records)
|
|
||||||
{
|
|
||||||
keyset.addRR(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Conditionally sign an RRset and add it to the toList.
|
|
||||||
*
|
|
||||||
* @param toList
|
|
||||||
* the list to which we are adding the processed RRsets.
|
|
||||||
* @param zonename
|
|
||||||
* the zone apex name.
|
|
||||||
* @param rrset
|
|
||||||
* the RRset under consideration.
|
|
||||||
* @param kskpairs
|
|
||||||
* the List of KSKs..
|
|
||||||
* @param zskpairs
|
|
||||||
* the List of zone keys.
|
|
||||||
* @param start
|
|
||||||
* the RRSIG inception time.
|
|
||||||
* @param expire
|
|
||||||
* the RRSIG expiration time.
|
|
||||||
* @param fullySignKeyset
|
|
||||||
* if true, sign the zone apex keyset with both KSKs and ZSKs.
|
|
||||||
* @param last_cut
|
|
||||||
* the name of the last delegation point encountered.
|
|
||||||
*
|
|
||||||
* @return the name of the new last_cut.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private Name addRRset(List<Record> toList, Name zonename, RRset rrset,
|
|
||||||
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs, Date start,
|
|
||||||
Date expire, boolean fullySignKeyset, Name last_cut,
|
|
||||||
Name last_dname) throws IOException, GeneralSecurityException
|
|
||||||
{
|
|
||||||
// add the records themselves
|
|
||||||
for (Iterator<Record> i = rrset.rrs(); i.hasNext();)
|
|
||||||
{
|
|
||||||
toList.add(i.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
int type = SignUtils.recordSecType(zonename, rrset.getName(), rrset.getType(),
|
|
||||||
last_cut, last_dname);
|
|
||||||
|
|
||||||
// we don't sign non-normal sets (delegations, glue, invalid).
|
|
||||||
if (type == SignUtils.RR_DELEGATION)
|
|
||||||
{
|
|
||||||
return rrset.getName();
|
|
||||||
}
|
|
||||||
if (type == SignUtils.RR_GLUE || type == SignUtils.RR_INVALID)
|
|
||||||
{
|
|
||||||
return last_cut;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for the zone apex keyset.
|
|
||||||
if (rrset.getName().equals(zonename) && rrset.getType() == Type.DNSKEY)
|
|
||||||
{
|
|
||||||
// if we have ksks, sign the keyset with them, otherwise we will just sign
|
|
||||||
// them with the zsks.
|
|
||||||
if (kskpairs != null && kskpairs.size() > 0)
|
|
||||||
{
|
|
||||||
List<RRSIGRecord> sigs = signRRset(rrset, kskpairs, start, expire);
|
|
||||||
toList.addAll(sigs);
|
|
||||||
|
|
||||||
// If we aren't going to sign with all the keys, bail out now.
|
|
||||||
if (!fullySignKeyset) return last_cut;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise, we are OK to sign this set.
|
|
||||||
List<RRSIGRecord> sigs = signRRset(rrset, zskpairs, start, expire);
|
|
||||||
toList.addAll(sigs);
|
|
||||||
|
|
||||||
return last_cut;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Various NSEC/NSEC3 generation modes
|
|
||||||
private static final int NSEC_MODE = 0;
|
|
||||||
private static final int NSEC3_MODE = 1;
|
|
||||||
private static final int NSEC3_OPTOUT_MODE = 2;
|
|
||||||
private static final int NSEC_EXP_OPT_IN = 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Master zone signing method. This method handles all of the different zone
|
|
||||||
* signing variants (NSEC with or without Opt-In, NSEC3 with or without
|
|
||||||
* Opt-Out, etc.) External users of this class are expected to use the
|
|
||||||
* appropriate public signZone* methods instead of this.
|
|
||||||
*
|
|
||||||
* @param zonename
|
|
||||||
* The name of the zone
|
|
||||||
* @param records
|
|
||||||
* The records comprising the zone. They do not have to be in any
|
|
||||||
* particular order, as this method will order them as necessary.
|
|
||||||
* @param kskpairs
|
|
||||||
* The key pairs designated as "key signing keys"
|
|
||||||
* @param zskpairs
|
|
||||||
* The key pairs designated as "zone signing keys"
|
|
||||||
* @param start
|
|
||||||
* The RRSIG inception time
|
|
||||||
* @param expire
|
|
||||||
* The RRSIG expiration time
|
|
||||||
* @param fullySignKeyset
|
|
||||||
* If true, all keys (ksk or zsk) will sign the DNSKEY RRset. If
|
|
||||||
* false, only the ksks will sign it.
|
|
||||||
* @param ds_digest_alg
|
|
||||||
* The hash algorithm to use for generating DS records
|
|
||||||
* (DSRecord.SHA1_DIGEST_ID, e.g.)
|
|
||||||
* @param mode
|
|
||||||
* The NSEC/NSEC3 generation mode: NSEC_MODE, NSEC3_MODE,
|
|
||||||
* NSEC3_OPTOUT_MODE, etc.
|
|
||||||
* @param includedNames
|
|
||||||
* When using an Opt-In/Opt-Out mode, the names listed here will be
|
|
||||||
* included in the NSEC/NSEC3 chain regardless
|
|
||||||
* @param salt
|
|
||||||
* When using an NSEC3 mode, use this salt.
|
|
||||||
* @param iterations
|
|
||||||
* When using an NSEC3 mode, use this number of iterations
|
|
||||||
* @param beConservative
|
|
||||||
* If true, then only turn on the Opt-In flag when there are insecure
|
|
||||||
* delegations in the span. Currently this only works for
|
|
||||||
* NSEC_EXP_OPT_IN mode.
|
|
||||||
* @param nsec3paramttl
|
|
||||||
* The TTL to use for the generated NSEC3PARAM record. Negative
|
|
||||||
* values will use the SOA TTL.
|
|
||||||
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
|
|
||||||
* representing the signed zone.
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws GeneralSecurityException
|
|
||||||
*/
|
|
||||||
private List<Record> signZone(Name zonename, List<Record> records,
|
|
||||||
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
|
|
||||||
Date start, Date expire, boolean fullySignKeyset,
|
|
||||||
int ds_digest_alg, int mode, List<Name> includedNames,
|
|
||||||
byte[] salt, int iterations, long nsec3paramttl,
|
|
||||||
boolean beConservative) throws IOException,
|
|
||||||
GeneralSecurityException
|
|
||||||
{
|
|
||||||
// Remove any existing generated DNSSEC records (NSEC, NSEC3, NSEC3PARAM,
|
|
||||||
// RRSIG)
|
|
||||||
SignUtils.removeGeneratedRecords(zonename, records);
|
|
||||||
|
|
||||||
RecordComparator rc = new RecordComparator();
|
|
||||||
// Sort the zone
|
|
||||||
Collections.sort(records, rc);
|
|
||||||
|
|
||||||
// Remove duplicate records
|
|
||||||
SignUtils.removeDuplicateRecords(records);
|
|
||||||
|
|
||||||
// Generate DS records. This replaces any non-zone-apex DNSKEY RRs with DS
|
|
||||||
// RRs.
|
|
||||||
SignUtils.generateDSRecords(zonename, records, ds_digest_alg);
|
|
||||||
|
|
||||||
// Generate the NSEC or NSEC3 records based on 'mode'
|
|
||||||
switch (mode)
|
|
||||||
{
|
|
||||||
case NSEC_MODE:
|
|
||||||
SignUtils.generateNSECRecords(zonename, records);
|
|
||||||
break;
|
|
||||||
case NSEC3_MODE:
|
|
||||||
SignUtils.generateNSEC3Records(zonename, records, salt, iterations, nsec3paramttl);
|
|
||||||
break;
|
|
||||||
case NSEC3_OPTOUT_MODE:
|
|
||||||
SignUtils.generateOptOutNSEC3Records(zonename, records, includedNames, salt,
|
|
||||||
iterations, nsec3paramttl);
|
|
||||||
break;
|
|
||||||
case NSEC_EXP_OPT_IN:
|
|
||||||
SignUtils.generateOptInNSECRecords(zonename, records, includedNames,
|
|
||||||
beConservative);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-sort so we can assemble into rrsets.
|
|
||||||
Collections.sort(records, rc);
|
|
||||||
|
|
||||||
// Assemble into RRsets and sign.
|
|
||||||
RRset rrset = new RRset();
|
|
||||||
ArrayList<Record> signed_records = new ArrayList<Record>();
|
|
||||||
Name last_cut = null;
|
|
||||||
Name last_dname = null;
|
|
||||||
|
|
||||||
for (ListIterator<Record> i = records.listIterator(); i.hasNext();)
|
|
||||||
{
|
|
||||||
Record r = i.next();
|
|
||||||
|
|
||||||
// First record
|
|
||||||
if (rrset.size() == 0)
|
|
||||||
{
|
|
||||||
rrset.addRR(r);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current record is part of the current RRset.
|
|
||||||
if (rrset.getName().equals(r.getName()) && rrset.getDClass() == r.getDClass()
|
|
||||||
&& rrset.getType() == r.getType())
|
|
||||||
{
|
|
||||||
rrset.addRR(r);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, we have completed the RRset
|
|
||||||
// Sign the records
|
|
||||||
|
|
||||||
// add the RRset to the list of signed_records, regardless of
|
|
||||||
// whether or not we actually end up signing the set.
|
|
||||||
last_cut = addRRset(signed_records, zonename, rrset, kskpairs, zskpairs, start,
|
|
||||||
expire, fullySignKeyset, last_cut, last_dname);
|
|
||||||
if (rrset.getType() == Type.DNAME) last_dname = rrset.getName();
|
|
||||||
|
|
||||||
rrset.clear();
|
|
||||||
rrset.addRR(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the last RR set
|
|
||||||
addRRset(signed_records, zonename, rrset, kskpairs, zskpairs, start, expire,
|
|
||||||
fullySignKeyset, last_cut, last_dname);
|
|
||||||
|
|
||||||
return signed_records;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a zone, sign it using standard NSEC records.
|
|
||||||
*
|
|
||||||
* @param zonename
|
|
||||||
* The name of the zone.
|
|
||||||
* @param records
|
|
||||||
* The records comprising the zone. They do not have to be in any
|
|
||||||
* particular order, as this method will order them as necessary.
|
|
||||||
* @param kskpairs
|
|
||||||
* The key pairs that are designated as "key signing keys".
|
|
||||||
* @param zskpairs
|
|
||||||
* This key pairs that are designated as "zone signing keys".
|
|
||||||
* @param start
|
|
||||||
* The RRSIG inception time.
|
|
||||||
* @param expire
|
|
||||||
* The RRSIG expiration time.
|
|
||||||
* @param fullySignKeyset
|
|
||||||
* Sign the zone apex keyset with all available keys (instead of just
|
|
||||||
* the key signing keys).
|
|
||||||
* @param ds_digest_alg
|
|
||||||
* The digest algorithm to use when generating DS records.
|
|
||||||
*
|
|
||||||
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
|
|
||||||
* representing the signed zone.
|
|
||||||
*/
|
|
||||||
public List<Record> signZone(Name zonename, List<Record> records,
|
|
||||||
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
|
|
||||||
Date start, Date expire, boolean fullySignKeyset,
|
|
||||||
int ds_digest_alg) throws IOException,
|
|
||||||
GeneralSecurityException
|
|
||||||
{
|
|
||||||
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
|
|
||||||
fullySignKeyset, ds_digest_alg, NSEC_MODE, null, null, 0, 0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a zone, sign it using NSEC3 records.
|
|
||||||
*
|
|
||||||
* @param signer
|
|
||||||
* A signer (utility) object used to actually sign stuff.
|
|
||||||
* @param zonename
|
|
||||||
* The name of the zone being signed.
|
|
||||||
* @param records
|
|
||||||
* The records comprising the zone. They do not have to be in any
|
|
||||||
* particular order, as this method will order them as necessary.
|
|
||||||
* @param kskpairs
|
|
||||||
* The key pairs that are designated as "key signing keys".
|
|
||||||
* @param zskpairs
|
|
||||||
* This key pairs that are designated as "zone signing keys".
|
|
||||||
* @param start
|
|
||||||
* The RRSIG inception time.
|
|
||||||
* @param expire
|
|
||||||
* The RRSIG expiration time.
|
|
||||||
* @param fullySignKeyset
|
|
||||||
* If true then the DNSKEY RRset will be signed by all available
|
|
||||||
* keys, if false, only the key signing keys.
|
|
||||||
* @param useOptOut
|
|
||||||
* If true, insecure delegations will be omitted from the NSEC3
|
|
||||||
* chain, and all NSEC3 records will have the Opt-Out flag set.
|
|
||||||
* @param includedNames
|
|
||||||
* A list of names to include in the NSEC3 chain regardless.
|
|
||||||
* @param salt
|
|
||||||
* The salt to use for the NSEC3 hashing. null means no salt.
|
|
||||||
* @param iterations
|
|
||||||
* The number of iterations to use for the NSEC3 hashing.
|
|
||||||
* @param ds_digest_alg
|
|
||||||
* The digest algorithm to use when generating DS records.
|
|
||||||
* @param nsec3paramttl
|
|
||||||
* The TTL to use for the generated NSEC3PARAM record. Negative
|
|
||||||
* values will use the SOA TTL.
|
|
||||||
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
|
|
||||||
* representing the signed zone.
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws GeneralSecurityException
|
|
||||||
*/
|
|
||||||
public List<Record> signZoneNSEC3(Name zonename, List<Record> records,
|
|
||||||
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
|
|
||||||
Date start, Date expire, boolean fullySignKeyset,
|
|
||||||
boolean useOptOut, List<Name> includedNames,
|
|
||||||
byte[] salt, int iterations, int ds_digest_alg,
|
|
||||||
long nsec3paramttl) throws IOException,
|
|
||||||
GeneralSecurityException
|
|
||||||
{
|
|
||||||
if (useOptOut)
|
|
||||||
{
|
|
||||||
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
|
|
||||||
fullySignKeyset, ds_digest_alg, NSEC3_OPTOUT_MODE, includedNames,
|
|
||||||
salt, iterations, nsec3paramttl, false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
|
|
||||||
fullySignKeyset, ds_digest_alg, NSEC3_MODE, null, salt, iterations,
|
|
||||||
nsec3paramttl, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a zone, sign it using experimental Opt-In NSEC records (see RFC
|
|
||||||
* 4956).
|
|
||||||
*
|
|
||||||
* @param zonename
|
|
||||||
* the name of the zone.
|
|
||||||
* @param records
|
|
||||||
* the records comprising the zone. They do not have to be in any
|
|
||||||
* particular order, as this method will order them as necessary.
|
|
||||||
* @param kskpairs
|
|
||||||
* the key pairs that are designated as "key signing keys".
|
|
||||||
* @param zskpairs
|
|
||||||
* this key pairs that are designated as "zone signing keys".
|
|
||||||
* @param start
|
|
||||||
* the RRSIG inception time.
|
|
||||||
* @param expire
|
|
||||||
* the RRSIG expiration time.
|
|
||||||
* @param useConservativeOptIn
|
|
||||||
* if true, Opt-In NSEC records will only be generated if there are
|
|
||||||
* insecure, unsigned delegations in the span.
|
|
||||||
* @param fullySignKeyset
|
|
||||||
* sign the zone apex keyset with all available keys.
|
|
||||||
* @param ds_digest_alg
|
|
||||||
* The digest algorithm to use when generating DS records.
|
|
||||||
* @param NSECIncludeNames
|
|
||||||
* names that are to be included in the NSEC chain regardless. This
|
|
||||||
* may be null.
|
|
||||||
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
|
|
||||||
* representing the signed zone.
|
|
||||||
*/
|
|
||||||
public List<Record> signZoneOptIn(Name zonename, List<Record> records,
|
|
||||||
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
|
|
||||||
Date start, Date expire,
|
|
||||||
boolean useConservativeOptIn,
|
|
||||||
boolean fullySignKeyset, List<Name> NSECIncludeNames,
|
|
||||||
int ds_digest_alg) throws IOException,
|
|
||||||
GeneralSecurityException
|
|
||||||
{
|
|
||||||
|
|
||||||
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
|
|
||||||
fullySignKeyset, ds_digest_alg, NSEC_EXP_OPT_IN, NSECIncludeNames,
|
|
||||||
null, 0, 0, useConservativeOptIn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
// $Id$
|
|
||||||
//
|
|
||||||
// Copyright (C) 2000-2003 Network Solutions, Inc.
|
|
||||||
//
|
|
||||||
// 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.security;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
import org.xbill.DNS.RRSIGRecord;
|
|
||||||
import org.xbill.DNS.Record;
|
|
||||||
import org.xbill.DNS.Type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class implements a comparison operator for {@link org.xbill.DNS.Record}
|
|
||||||
* objects. It imposes a canonical order consistent with DNSSEC. It does not put
|
|
||||||
* records within a RRset into canonical order: see {@link ByteArrayComparator}.
|
|
||||||
*
|
|
||||||
* @author David Blacka (original)
|
|
||||||
* @author $Author$
|
|
||||||
* @version $Revision$
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class RecordComparator implements Comparator<Record>
|
|
||||||
{
|
|
||||||
public RecordComparator()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In general, types are compared numerically. However, SOA, NS, and DNAME are ordered
|
|
||||||
* before the rest.
|
|
||||||
*/
|
|
||||||
private int compareTypes(int a, int b)
|
|
||||||
{
|
|
||||||
if (a == b) return 0;
|
|
||||||
if (a == Type.SOA) return -1;
|
|
||||||
if (b == Type.SOA) return 1;
|
|
||||||
|
|
||||||
if (a == Type.NS) return -1;
|
|
||||||
if (b == Type.NS) return 1;
|
|
||||||
|
|
||||||
if (a == Type.DNAME) return -1;
|
|
||||||
if (b == Type.DNAME) return 1;
|
|
||||||
|
|
||||||
if (a < b) return -1;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int compareRDATA(Record a, Record b)
|
|
||||||
{
|
|
||||||
byte[] a_rdata = a.rdataToWireCanonical();
|
|
||||||
byte[] b_rdata = b.rdataToWireCanonical();
|
|
||||||
|
|
||||||
for (int i = 0; i < a_rdata.length && i < b_rdata.length; i++)
|
|
||||||
{
|
|
||||||
int n = (a_rdata[i] & 0xFF) - (b_rdata[i] & 0xFF);
|
|
||||||
if (n != 0) return n;
|
|
||||||
}
|
|
||||||
return (a_rdata.length - b_rdata.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int compare(Record a, Record b)
|
|
||||||
{
|
|
||||||
if (a == null && b == null) return 0;
|
|
||||||
if (a == null) return 1;
|
|
||||||
if (b == null) return -1;
|
|
||||||
|
|
||||||
int res = a.getName().compareTo(b.getName());
|
|
||||||
if (res != 0) return res;
|
|
||||||
|
|
||||||
int a_type = a.getType();
|
|
||||||
int b_type = b.getType();
|
|
||||||
int sig_type = 0;
|
|
||||||
|
|
||||||
if (a_type == Type.RRSIG)
|
|
||||||
{
|
|
||||||
a_type = ((RRSIGRecord) a).getTypeCovered();
|
|
||||||
if (b_type != Type.RRSIG) sig_type = 1;
|
|
||||||
}
|
|
||||||
if (b_type == Type.RRSIG)
|
|
||||||
{
|
|
||||||
b_type = ((RRSIGRecord) b).getTypeCovered();
|
|
||||||
if (a.getType() != Type.RRSIG) sig_type = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
res = compareTypes(a_type, b_type);
|
|
||||||
if (res != 0) return res;
|
|
||||||
|
|
||||||
if (sig_type != 0) return sig_type;
|
|
||||||
|
|
||||||
return compareRDATA(a, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,189 +0,0 @@
|
|||||||
// $Id$
|
|
||||||
//
|
|
||||||
// Copyright (C) 2004 Verisign, Inc.
|
|
||||||
|
|
||||||
package com.verisignlabs.dnssec.security;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.xbill.DNS.DNSOutput;
|
|
||||||
import org.xbill.DNS.Type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class represents the multiple type maps of the NSEC record. Currently it
|
|
||||||
* is just used to convert the wire format type map to the int array that
|
|
||||||
* org.xbill.DNS.NSECRecord uses. Note that there is now a very similar class in
|
|
||||||
* DNSjava: {@link org.xbill.DNS.TypeBitmap}.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class TypeMap
|
|
||||||
{
|
|
||||||
private static final Integer[] integerArray = new Integer[0];
|
|
||||||
|
|
||||||
private Set<Integer> typeSet;
|
|
||||||
|
|
||||||
public TypeMap()
|
|
||||||
{
|
|
||||||
this.typeSet = new HashSet<Integer>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Add the given type to the typemap. */
|
|
||||||
public void set(int type)
|
|
||||||
{
|
|
||||||
typeSet.add(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Remove the given type from the type map. */
|
|
||||||
public void clear(int type)
|
|
||||||
{
|
|
||||||
typeSet.remove(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return true if the given type is present in the type map. */
|
|
||||||
public boolean get(int type)
|
|
||||||
{
|
|
||||||
return typeSet.contains(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TypeMap fromTypes(int[] types)
|
|
||||||
{
|
|
||||||
TypeMap m = new TypeMap();
|
|
||||||
if (types == null) return m;
|
|
||||||
for (int i = 0; i < types.length; i++)
|
|
||||||
{
|
|
||||||
m.set(types[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given an array of bytes representing a wire-format type map, construct the
|
|
||||||
* TypeMap object.
|
|
||||||
*/
|
|
||||||
public static TypeMap fromBytes(byte[] map)
|
|
||||||
{
|
|
||||||
int m = 0;
|
|
||||||
TypeMap typemap = new TypeMap();
|
|
||||||
|
|
||||||
int map_number;
|
|
||||||
int byte_length;
|
|
||||||
|
|
||||||
while (m < map.length)
|
|
||||||
{
|
|
||||||
map_number = map[m++];
|
|
||||||
byte_length = map[m++];
|
|
||||||
|
|
||||||
for (int i = 0; i < byte_length; i++)
|
|
||||||
{
|
|
||||||
for (int j = 0; j < 8; j++)
|
|
||||||
{
|
|
||||||
if ((map[m + i] & (1 << (7 - j))) != 0)
|
|
||||||
{
|
|
||||||
typemap.set(map_number * 8 + j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m += byte_length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return typemap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return the normal string representation of the typemap. */
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
int[] types = getTypes();
|
|
||||||
Arrays.sort(types);
|
|
||||||
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
|
|
||||||
for (int i = 0; i < types.length; i++)
|
|
||||||
{
|
|
||||||
sb.append(" ");
|
|
||||||
sb.append(Type.string(types[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void mapToWire(DNSOutput out, int[] types, int base, int start, int end)
|
|
||||||
{
|
|
||||||
// calculate the length of this map by looking at the largest
|
|
||||||
// typecode in this section.
|
|
||||||
int max_type = types[end - 1] & 0xFF;
|
|
||||||
int map_length = (max_type / 8) + 1;
|
|
||||||
|
|
||||||
// write the map "header" -- the base and the length of the map.
|
|
||||||
out.writeU8(base & 0xFF);
|
|
||||||
out.writeU8(map_length & 0xFF);
|
|
||||||
|
|
||||||
// allocate a temporary scratch space for caculating the actual
|
|
||||||
// bitmap.
|
|
||||||
byte[] map = new byte[map_length];
|
|
||||||
|
|
||||||
// for each type in our sub-array, set its corresponding bit in the map.
|
|
||||||
for (int i = start; i < end; i++)
|
|
||||||
{
|
|
||||||
map[(types[i] & 0xFF) / 8] |= (1 << (7 - types[i] % 8));
|
|
||||||
}
|
|
||||||
// write out the resulting binary bitmap.
|
|
||||||
for (int i = 0; i < map.length; i++)
|
|
||||||
{
|
|
||||||
out.writeU8(map[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] toWire()
|
|
||||||
{
|
|
||||||
int[] types = getTypes();
|
|
||||||
|
|
||||||
Arrays.sort(types);
|
|
||||||
|
|
||||||
int mapbase = -1;
|
|
||||||
int mapstart = -1;
|
|
||||||
|
|
||||||
DNSOutput out = new DNSOutput();
|
|
||||||
|
|
||||||
for (int i = 0; i < types.length; i++)
|
|
||||||
{
|
|
||||||
int base = types[i] >> 8;
|
|
||||||
if (base == mapbase) continue;
|
|
||||||
if (mapstart >= 0)
|
|
||||||
{
|
|
||||||
mapToWire(out, types, mapbase, mapstart, i);
|
|
||||||
}
|
|
||||||
mapbase = base;
|
|
||||||
mapstart = i;
|
|
||||||
}
|
|
||||||
mapToWire(out, types, mapbase, mapstart, types.length);
|
|
||||||
|
|
||||||
return out.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int[] getTypes()
|
|
||||||
{
|
|
||||||
Integer[] a = (Integer[]) typeSet.toArray(integerArray);
|
|
||||||
|
|
||||||
int[] res = new int[a.length];
|
|
||||||
for (int i = 0; i < res.length; i++)
|
|
||||||
{
|
|
||||||
res[i] = a[i].intValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int[] fromWireToTypes(byte[] wire_fmt)
|
|
||||||
{
|
|
||||||
return TypeMap.fromBytes(wire_fmt).getTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] fromTypesToWire(int[] types)
|
|
||||||
{
|
|
||||||
return TypeMap.fromTypes(types).toWire();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
406
src/main/java/com/verisignlabs/dnssec/cl/CLBase.java
Normal file
406
src/main/java/com/verisignlabs/dnssec/cl/CLBase.java
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
// Copyright (C) 2022 Verisign, Inc.
|
||||||
|
//
|
||||||
|
// 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.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.logging.Formatter;
|
||||||
|
import java.util.logging.Handler;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.apache.commons.cli.AlreadySelectedException;
|
||||||
|
import org.apache.commons.cli.CommandLine;
|
||||||
|
import org.apache.commons.cli.CommandLineParser;
|
||||||
|
import org.apache.commons.cli.DefaultParser;
|
||||||
|
import org.apache.commons.cli.HelpFormatter;
|
||||||
|
import org.apache.commons.cli.Option;
|
||||||
|
import org.apache.commons.cli.Options;
|
||||||
|
import org.apache.commons.cli.ParseException;
|
||||||
|
import org.apache.commons.cli.UnrecognizedOptionException;
|
||||||
|
|
||||||
|
import com.verisignlabs.dnssec.security.DnsKeyAlgorithm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a base class for jdnssec command line tools. Each command line tool
|
||||||
|
* should inherit from this class, create a subclass of CLIStateBase (overriding
|
||||||
|
* setupOptions and processOptions), and implement the execute() method.
|
||||||
|
* Subclasses also have their own main() methods, which should just create the
|
||||||
|
* subclass variant of the CLIState and call run().
|
||||||
|
*/
|
||||||
|
public abstract class CLBase {
|
||||||
|
protected Logger log = Logger.getLogger(this.getClass().toString());
|
||||||
|
protected Options opts;
|
||||||
|
protected String name;
|
||||||
|
protected String usageStr;
|
||||||
|
protected Properties props;
|
||||||
|
protected CommandLine cli;
|
||||||
|
|
||||||
|
protected CLBase(String name, String usageStr) {
|
||||||
|
this.name = name;
|
||||||
|
this.usageStr = usageStr;
|
||||||
|
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a very simple log formatter that simply outputs the log level and
|
||||||
|
* log string.
|
||||||
|
*/
|
||||||
|
public static class BareLogFormatter extends Formatter {
|
||||||
|
|
||||||
|
public String format(LogRecord arg0) {
|
||||||
|
StringBuilder out = new StringBuilder();
|
||||||
|
String lvl = arg0.getLevel().getName();
|
||||||
|
|
||||||
|
out.append(lvl);
|
||||||
|
out.append(": ");
|
||||||
|
out.append(arg0.getMessage());
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This is the base set of command line options provided to all subclasses. */
|
||||||
|
private void setupCommonOptions() {
|
||||||
|
// Set up the standard set of options that all jdnssec command line tools will
|
||||||
|
// implement.
|
||||||
|
|
||||||
|
// boolean options
|
||||||
|
opts.addOption("h", "help", false, "Print this message.");
|
||||||
|
opts.addOption("m", "multiline", false,
|
||||||
|
"Output DNS records using 'multiline' format");
|
||||||
|
|
||||||
|
opts.addOption(Option.builder("l").longOpt("log-level").argName("level").hasArg()
|
||||||
|
.desc("set the logging level with either java.util.logging levels, or 0-6").build());
|
||||||
|
opts.addOption(Option.builder("v").longOpt("verbose").desc(
|
||||||
|
"set as verbose (log-level = fine)").build());
|
||||||
|
|
||||||
|
opts.addOption(Option.builder("c").longOpt("config").argName("file").hasArg()
|
||||||
|
.desc("configuration file (format: java properties)").build());
|
||||||
|
|
||||||
|
opts.addOption(Option.builder("A").hasArg().argName("alias:original:mnemonic").longOpt("alg-alias")
|
||||||
|
.desc("Define an alias for an algorithm").build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an overridable method for subclasses to add their own command line
|
||||||
|
* options.
|
||||||
|
*/
|
||||||
|
protected abstract void setupOptions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the command line options
|
||||||
|
*/
|
||||||
|
public void setup() {
|
||||||
|
opts = new Options();
|
||||||
|
setupCommonOptions();
|
||||||
|
setupOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the main method for parsing the command line arguments. Subclasses
|
||||||
|
* generally override processOptions() rather than this method. This method
|
||||||
|
* creates the parsing objects and processes the common options.
|
||||||
|
*
|
||||||
|
* @param args The command line arguments.
|
||||||
|
*/
|
||||||
|
public void parseCommandLine(String[] args) {
|
||||||
|
String[] logLevelOptionKeys = { "log_level", "log-level" };
|
||||||
|
String[] multilineOptionKeys = { "multiline" };
|
||||||
|
CommandLineParser parser = new DefaultParser();
|
||||||
|
|
||||||
|
try {
|
||||||
|
cli = parser.parse(opts, args);
|
||||||
|
} catch (UnrecognizedOptionException e) {
|
||||||
|
fail("unknown option encountered: " + e.getMessage());
|
||||||
|
} catch (AlreadySelectedException e) {
|
||||||
|
fail("mutually exclusive options have been selected:\n " + e.getMessage());
|
||||||
|
} catch (ParseException e) {
|
||||||
|
fail("unable to parse command line: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cli.hasOption('h')) {
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
String loadedConfig = loadConfig(cli.getOptionValue('c'));
|
||||||
|
|
||||||
|
Logger rootLogger = Logger.getLogger("");
|
||||||
|
|
||||||
|
// we set log level with both --log-level and -v/--verbose.
|
||||||
|
String logLevel = cliOption("log-level", logLevelOptionKeys, null);
|
||||||
|
if (logLevel == null) {
|
||||||
|
logLevel = cli.hasOption("v") ? "fine" : "warning";
|
||||||
|
}
|
||||||
|
setLogLevel(rootLogger, logLevel);
|
||||||
|
|
||||||
|
for (Handler h : rootLogger.getHandlers()) {
|
||||||
|
h.setLevel(rootLogger.getLevel());
|
||||||
|
h.setFormatter(new BareLogFormatter());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadedConfig != null) {
|
||||||
|
log.info("Loaded config file: " + loadedConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cliBooleanOption("m", multilineOptionKeys, false)) {
|
||||||
|
org.xbill.DNS.Options.set("multiline");
|
||||||
|
}
|
||||||
|
|
||||||
|
processAliasOptions();
|
||||||
|
|
||||||
|
processOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process additional tool-specific options. Subclasses generally override
|
||||||
|
* this.
|
||||||
|
*/
|
||||||
|
protected abstract void processOptions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a configuration (java properties) file for jdnssec-tools. Returns
|
||||||
|
* the path of the loaded file.
|
||||||
|
*
|
||||||
|
* @param configFile a given path to a config file. This will be considered
|
||||||
|
* first.
|
||||||
|
* @return The path of the file that was actually loaded, or null if no config
|
||||||
|
* file was loaded.
|
||||||
|
*/
|
||||||
|
protected String loadConfig(String configFile) {
|
||||||
|
// Do not load config files twice
|
||||||
|
if (props != null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
props = new Properties();
|
||||||
|
String[] configFiles = { configFile, "jdnssec-tools.properties", ".jdnssec-tools.properties",
|
||||||
|
System.getProperty("user.home") + "/.jdnssec-tools.properties" };
|
||||||
|
|
||||||
|
File f = null;
|
||||||
|
|
||||||
|
for (String fname : configFiles) {
|
||||||
|
if (fname == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
f = new File(fname);
|
||||||
|
if (!f.canRead()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileInputStream stream = new FileInputStream(f)) {
|
||||||
|
props.load(stream);
|
||||||
|
break; // load the first config file found in our list
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warning("Could not read config file " + f.getName() + ": " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f != null) {
|
||||||
|
return f.getPath();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void fail(String errorMessage) {
|
||||||
|
log.severe(errorMessage);
|
||||||
|
System.exit(64);
|
||||||
|
}
|
||||||
|
/** Print out the usage and help statements, then quit. */
|
||||||
|
public void usage() {
|
||||||
|
HelpFormatter f = new HelpFormatter();
|
||||||
|
|
||||||
|
PrintWriter out = new PrintWriter(System.err);
|
||||||
|
|
||||||
|
// print our own usage statement:
|
||||||
|
f.printHelp(out, 120, usageStr, null, opts, HelpFormatter.DEFAULT_LEFT_PAD,
|
||||||
|
HelpFormatter.DEFAULT_DESC_PAD, null);
|
||||||
|
|
||||||
|
out.flush();
|
||||||
|
System.exit(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the logging level based on a string value
|
||||||
|
*
|
||||||
|
* @param logger The logger to set -- usually the rootLogger
|
||||||
|
* @param levelStr A level string that is either an integer from 0 to 6, or a
|
||||||
|
* java.util.logging log level string (severe, warning, info,
|
||||||
|
* fine, finer,
|
||||||
|
* finest).
|
||||||
|
*/
|
||||||
|
private void setLogLevel(Logger logger, String levelStr) {
|
||||||
|
Level level;
|
||||||
|
int internalLogLevel = Utils.parseInt(levelStr, -1);
|
||||||
|
if (internalLogLevel != -1) {
|
||||||
|
switch (internalLogLevel) {
|
||||||
|
case 0:
|
||||||
|
level = Level.OFF;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
level = Level.SEVERE;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
default:
|
||||||
|
level = Level.WARNING;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
level = Level.INFO;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
level = Level.FINE;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
level = Level.ALL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
level = Level.parse(levelStr.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
System.err.println("Verbosity level '" + levelStr + "' not recognized");
|
||||||
|
level = Level.WARNING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.setLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process both property file based alias definitions and command line alias
|
||||||
|
* definitions
|
||||||
|
*/
|
||||||
|
protected void processAliasOptions() {
|
||||||
|
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
||||||
|
// First parse any command line options
|
||||||
|
// those look like '-A <alias-num>:<orig-num>:<mnemonic>', e.g., '-A
|
||||||
|
// 21:13:ECDSAP256-NSEC6'
|
||||||
|
String[] optstrs = null;
|
||||||
|
if ((optstrs = cli.getOptionValues('A')) != null) {
|
||||||
|
for (String value : optstrs) {
|
||||||
|
String[] valueComponents = value.split(":");
|
||||||
|
int aliasAlg = Utils.parseInt(valueComponents[0], -1);
|
||||||
|
int origAlg = Utils.parseInt(valueComponents[1], -1);
|
||||||
|
String mnemonic = valueComponents[2];
|
||||||
|
|
||||||
|
if (mnemonic != null && origAlg >= 0 && aliasAlg >= 0) {
|
||||||
|
algs.addAlias(aliasAlg, mnemonic, origAlg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next see if we have any alias options in properties
|
||||||
|
// Those look like 'signzone.alias.<alias-mnemonic> =
|
||||||
|
// <orig-alg-num>:<alias-alg-num>'
|
||||||
|
for (String key : props.stringPropertyNames()) {
|
||||||
|
if (key.startsWith(name + ".alias.") || key.startsWith("alias.")) {
|
||||||
|
String[] keyComponents = key.split("\\.");
|
||||||
|
String mnemonic = keyComponents[keyComponents.length - 1];
|
||||||
|
String[] valueComponents = props.getProperty(key).split(":");
|
||||||
|
int origAlg = Utils.parseInt(valueComponents[0], -1);
|
||||||
|
int aliasAlg = Utils.parseInt(valueComponents[1], -1);
|
||||||
|
|
||||||
|
if (mnemonic != null && origAlg >= 0 && aliasAlg >= 0) {
|
||||||
|
algs.addAlias(aliasAlg, mnemonic, origAlg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a parsed command line, option, and list of possible config
|
||||||
|
* properties, and a default value, determine value for the option
|
||||||
|
*
|
||||||
|
* @param option The option name
|
||||||
|
* @param properties A list of configuration parameters that we would like
|
||||||
|
* to use for this option, from most preferred to least.
|
||||||
|
* @param defaultValue A default value to return if either the option or
|
||||||
|
* config value cannot be parsed, or neither are present.
|
||||||
|
* @return The found value, or the default value.
|
||||||
|
*/
|
||||||
|
protected String cliOption(String option, String[] properties, String defaultValue) {
|
||||||
|
if (cli.hasOption(option)) {
|
||||||
|
return cli.getOptionValue(option);
|
||||||
|
}
|
||||||
|
for (String property : properties) {
|
||||||
|
// first look up the scoped version of the property
|
||||||
|
String value = props.getProperty(name + "." + property);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
value = props.getProperty(property);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a parsed command line, option, and list of possible config
|
||||||
|
* properties, determine the value for the option, converting the value to
|
||||||
|
* long.
|
||||||
|
*/
|
||||||
|
protected long cliLongOption(String option, String[] properties, long defaultValue) {
|
||||||
|
String value = cliOption(option, properties, Long.toString(defaultValue));
|
||||||
|
return Utils.parseLong(value, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a parsed command line, option, and list of possible config
|
||||||
|
* properties, determine the value for the option, converting the value to
|
||||||
|
* int.
|
||||||
|
*/
|
||||||
|
protected int cliIntOption(String option, String[] properties, int defaultValue) {
|
||||||
|
String value = cliOption(option, properties, Integer.toString(defaultValue));
|
||||||
|
return Utils.parseInt(value, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a parsed command line, option, and list of possible config
|
||||||
|
* properties, determine the value for the option, converting the value to
|
||||||
|
* a boolean.
|
||||||
|
*/
|
||||||
|
protected boolean cliBooleanOption(String option, String[] properties, boolean defaultValue) {
|
||||||
|
if (cli.hasOption(option)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String value = cliOption(option, properties, Boolean.toString(defaultValue));
|
||||||
|
return Boolean.parseBoolean(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void execute() throws Exception;
|
||||||
|
|
||||||
|
public void run(String[] args) {
|
||||||
|
|
||||||
|
parseCommandLine(args);
|
||||||
|
log = Logger.getLogger(this.getClass().toString());
|
||||||
|
|
||||||
|
try {
|
||||||
|
execute();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
84
src/main/java/com/verisignlabs/dnssec/cl/CLI.java
Normal file
84
src/main/java/com/verisignlabs/dnssec/cl/CLI.java
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package com.verisignlabs.dnssec.cl;
|
||||||
|
|
||||||
|
public class CLI {
|
||||||
|
private SubCommandType subCommand = null;
|
||||||
|
private String commandSetStr = null;
|
||||||
|
|
||||||
|
enum SubCommandType {
|
||||||
|
DSTOOL, KEYGEN, KEYINFO, SIGNKEYSET, SIGNRRSET, SIGNZONE, VERIFYZONE, ZONEFORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CLI(String name, String usageStr) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (SubCommandType type : SubCommandType.class.getEnumConstants()) {
|
||||||
|
sb.append(type.toString().toLowerCase());
|
||||||
|
sb.append(" ");
|
||||||
|
}
|
||||||
|
commandSetStr = sb.toString().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fail(String errorMessage){
|
||||||
|
System.err.println("ERROR: " + errorMessage);
|
||||||
|
System.exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(String[] args) {
|
||||||
|
String[] subCommandArgs = null;
|
||||||
|
if (args.length < 1) {
|
||||||
|
fail("missing command: must be one of: " + commandSetStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
String command = args[0];
|
||||||
|
if (command.equals("-h")) {
|
||||||
|
System.err.println("usage: jdnssec-tools <command> <command args..>");
|
||||||
|
System.err.println(" <command> is one of: " + commandSetStr);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
subCommand = SubCommandType.valueOf(command.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
fail("unrecognized command '" + command + "': must be one of: " + commandSetStr);
|
||||||
|
}
|
||||||
|
subCommandArgs = new String[args.length - 1];
|
||||||
|
System.arraycopy(args, 1, subCommandArgs, 0, args.length - 1);
|
||||||
|
|
||||||
|
CLBase cmd = null;
|
||||||
|
|
||||||
|
switch(subCommand) {
|
||||||
|
case DSTOOL:
|
||||||
|
cmd = new DSTool("dstool", "jdnssec-tools dstool [..options..] keyfile [keyfile..]");
|
||||||
|
break;
|
||||||
|
case KEYGEN:
|
||||||
|
cmd = new KeyGen("keygen", "jdnssec-tools keygen [..options..] zonename");
|
||||||
|
break;
|
||||||
|
case KEYINFO:
|
||||||
|
cmd = new KeyInfoTool("keyinfotool", "jdnssec-tools keyinfo [..options..] keyfile");
|
||||||
|
break;
|
||||||
|
case SIGNKEYSET:
|
||||||
|
cmd = new SignKeyset("signkeyset", "jdnssec-tools signkeyset [..options..] dnskeyset_file [key_file ...]");
|
||||||
|
break;
|
||||||
|
case SIGNRRSET:
|
||||||
|
cmd = new SignRRset("signrrset", "jdnssec-tools signrrset [..options..] rrset_file key_file [key_file ...]");
|
||||||
|
break;
|
||||||
|
case SIGNZONE:
|
||||||
|
cmd = new SignZone("signzone", "jdnssec-tools signzone [..options..] zone_file [key_file ...]");
|
||||||
|
break;
|
||||||
|
case VERIFYZONE:
|
||||||
|
cmd = new VerifyZone("verifyzone", "jdnssec-tools verifyzone [..options..] zonefile");
|
||||||
|
break;
|
||||||
|
case ZONEFORMAT:
|
||||||
|
cmd = new ZoneFormat("zoneformat", "jdnssec-tools zoneformat [..options..] zonefile");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fail("commmand " + command + " has not been implemented.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cmd.run(subCommandArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
CLI cli = new CLI("cli", "jdnssec-tools <command> [..args..]");
|
||||||
|
cli.run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
148
src/main/java/com/verisignlabs/dnssec/cl/DSTool.java
Normal file
148
src/main/java/com/verisignlabs/dnssec/cl/DSTool.java
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
// Copyright (C) 2001-2003, 2011, 2022 VeriSign, Inc.
|
||||||
|
//
|
||||||
|
// 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.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
import org.apache.commons.cli.Option;
|
||||||
|
import org.xbill.DNS.DNSKEYRecord;
|
||||||
|
import org.xbill.DNS.DNSSEC;
|
||||||
|
import org.xbill.DNS.DSRecord;
|
||||||
|
import org.xbill.DNS.Record;
|
||||||
|
|
||||||
|
import com.verisignlabs.dnssec.security.BINDKeyUtils;
|
||||||
|
import com.verisignlabs.dnssec.security.DSAlgorithm;
|
||||||
|
import com.verisignlabs.dnssec.security.DnsKeyPair;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class forms the command line implementation of a DNSSEC DS/DLV generator
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
public class DSTool extends CLBase {
|
||||||
|
private dsType createType = dsType.DS;
|
||||||
|
private String outputfile = null;
|
||||||
|
private String[] keynames = null;
|
||||||
|
private int digestId = DNSSEC.Digest.SHA256;
|
||||||
|
private long dsTTL = -1;
|
||||||
|
|
||||||
|
public DSTool(String name, String usageStr) {
|
||||||
|
super(name, usageStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** There are several records that are based on DS. */
|
||||||
|
protected enum dsType {
|
||||||
|
DS, CDS, DLV;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a small inner class used to hold all of the command line option
|
||||||
|
* state.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the command line options.
|
||||||
|
*
|
||||||
|
* @return a set of command line options.
|
||||||
|
*/
|
||||||
|
protected void setupOptions() {
|
||||||
|
opts.addOption(Option.builder("D").longOpt("dlv").desc("Generate a DLV record instead.").build());
|
||||||
|
opts.addOption(Option.builder("C").longOpt("cds").desc("Generate a CDS record instead").build());
|
||||||
|
String[] algStrings = DSAlgorithm.getInstance().supportedAlgorithmMnemonics();
|
||||||
|
String algStringSet = String.join(" | ", algStrings);
|
||||||
|
opts.addOption(
|
||||||
|
Option.builder("d").hasArg().argName("id").longOpt("digest").desc(algStringSet + ": default is SHA256")
|
||||||
|
.build());
|
||||||
|
opts.addOption(Option.builder("f").hasArg().argName("file").longOpt("output").desc("output to file").build());
|
||||||
|
opts.addOption(Option.builder("T").longOpt("ttl").hasArg().desc("TTL to use for generated DS/CDS record").build());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processOptions() {
|
||||||
|
String[] digestAlgOptionKeys = { "digest_algorithm", "digest_id" };
|
||||||
|
String[] dsTTLOptionKeys = { "ds_ttl", "ttl" };
|
||||||
|
|
||||||
|
outputfile = cli.getOptionValue('f');
|
||||||
|
if (cli.hasOption("dlv")) {
|
||||||
|
createType = dsType.DLV;
|
||||||
|
} else if (cli.hasOption("cds")) {
|
||||||
|
createType = dsType.CDS;
|
||||||
|
}
|
||||||
|
String digestValue = cliOption("d", digestAlgOptionKeys, Integer.toString(digestId));
|
||||||
|
digestId = DNSSEC.Digest.value(digestValue);
|
||||||
|
|
||||||
|
dsTTL = cliLongOption("ttl", dsTTLOptionKeys, dsTTL);
|
||||||
|
|
||||||
|
String[] args = cli.getArgs();
|
||||||
|
|
||||||
|
if (args.length < 1) {
|
||||||
|
fail("missing key file");
|
||||||
|
}
|
||||||
|
|
||||||
|
keynames = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createDS(String keyname) throws IOException {
|
||||||
|
DSAlgorithm dsAlgorithm = DSAlgorithm.getInstance();
|
||||||
|
DnsKeyPair key = BINDKeyUtils.loadKey(keyname, null);
|
||||||
|
DNSKEYRecord dnskey = key.getDNSKEYRecord();
|
||||||
|
|
||||||
|
if ((dnskey.getFlags() & DNSKEYRecord.Flags.SEP_KEY) == 0) {
|
||||||
|
log.warning("DNSKEY " + keyname + " is not an SEP-flagged key.");
|
||||||
|
}
|
||||||
|
|
||||||
|
long ttl = dsTTL < 0 ? dnskey.getTTL() : dsTTL;
|
||||||
|
DSRecord ds = dsAlgorithm.calculateDSRecord(dnskey, digestId, ttl);
|
||||||
|
Record res;
|
||||||
|
|
||||||
|
switch (createType) {
|
||||||
|
case DLV:
|
||||||
|
log.fine("creating DLV.");
|
||||||
|
res = dsAlgorithm.dsToDLV(ds);
|
||||||
|
break;
|
||||||
|
case CDS:
|
||||||
|
log.fine("creating CDS.");
|
||||||
|
res = dsAlgorithm.dstoCDS(ds);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
res = ds;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputfile != null && !outputfile.equals("-")) {
|
||||||
|
try (PrintWriter out = new PrintWriter(new FileWriter(outputfile))) {
|
||||||
|
out.println(res);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() throws Exception {
|
||||||
|
for (String keyname : keynames) {
|
||||||
|
createDS(keyname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
DSTool tool = new DSTool("dstool", "jdnssec-dstool [..options..] keyfile [keyfile..]");
|
||||||
|
|
||||||
|
tool.run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
178
src/main/java/com/verisignlabs/dnssec/cl/KeyGen.java
Normal file
178
src/main/java/com/verisignlabs/dnssec/cl/KeyGen.java
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
// Copyright (C) 2001-2003, 2011, 2022 VeriSign, Inc.
|
||||||
|
//
|
||||||
|
// 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 org.apache.commons.cli.Option;
|
||||||
|
import org.xbill.DNS.DClass;
|
||||||
|
import org.xbill.DNS.DNSKEYRecord;
|
||||||
|
import org.xbill.DNS.Name;
|
||||||
|
|
||||||
|
import com.verisignlabs.dnssec.security.BINDKeyUtils;
|
||||||
|
import com.verisignlabs.dnssec.security.DnsKeyAlgorithm;
|
||||||
|
import com.verisignlabs.dnssec.security.DnsKeyPair;
|
||||||
|
import com.verisignlabs.dnssec.security.JCEDnsSecSigner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class forms the command line implementation of a DNSSEC key generator
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
public class KeyGen extends CLBase {
|
||||||
|
private int algorithm = 13;
|
||||||
|
private int keylength = 2048;
|
||||||
|
private boolean useLargeE = true;
|
||||||
|
private String outputfile = null;
|
||||||
|
private File keydir = null;
|
||||||
|
private boolean zoneKey = true;
|
||||||
|
private boolean kskFlag = false;
|
||||||
|
private String owner = null;
|
||||||
|
private long ttl = 86400;
|
||||||
|
private int givenKeyTag = -1;
|
||||||
|
|
||||||
|
public KeyGen(String name, String usageStr) {
|
||||||
|
super(name, usageStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the command line options.
|
||||||
|
*/
|
||||||
|
protected void setupOptions() {
|
||||||
|
// boolean options
|
||||||
|
opts.addOption("k", "kskflag", false,
|
||||||
|
"Key is a key-signing-key (sets the SEP flag).");
|
||||||
|
opts.addOption("e", "large-exponent", false, "Use large RSA exponent (default)");
|
||||||
|
opts.addOption("E", "small-exponent", false, "Use small RSA exponent");
|
||||||
|
|
||||||
|
// Argument options
|
||||||
|
opts.addOption(
|
||||||
|
Option.builder("n").longOpt("nametype").hasArg().argName("type").desc("ZONE | OTHER (default ZONE)").build());
|
||||||
|
|
||||||
|
String[] algStrings = DnsKeyAlgorithm.getInstance().supportedAlgMnemonics();
|
||||||
|
String algStringSet = String.join(" | ", algStrings);
|
||||||
|
opts.addOption(Option.builder("a").hasArg().argName("algorithm")
|
||||||
|
.desc(algStringSet + " | aliases, ECDSAP256SHA256 is default.").build());
|
||||||
|
|
||||||
|
opts.addOption(Option.builder("b").hasArg().argName("size").desc(
|
||||||
|
"key size, in bits (default 2048). RSA: [512..4096], DSA: [512..1024], DH: [128..4096], ECDSA: ignored, EdDSA: ignored")
|
||||||
|
.build());
|
||||||
|
opts.addOption(Option.builder("f").hasArg().argName("file").longOpt("output-file")
|
||||||
|
.desc("base filename from the public/private key files").build());
|
||||||
|
opts.addOption(Option.builder("d").hasArg().argName("dir").longOpt("keydir")
|
||||||
|
.desc("generated keyfiles are written to this directory").build());
|
||||||
|
opts.addOption(Option.builder("T").hasArg().argName("ttl").longOpt("ttl")
|
||||||
|
.desc("use this TTL for the generated DNSKEY records (default: 86400").build());
|
||||||
|
opts.addOption(Option.builder().hasArg().argName("tag").longOpt("with-tag")
|
||||||
|
.desc("Generate keys until tag is the given value.").build());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processOptions() {
|
||||||
|
String[] useLargeEOptionKeys = { "use_large_exponent", "use_large_e" };
|
||||||
|
String[] keyDirectoryOptionKeys = { "key_directory", "keydir" };
|
||||||
|
String[] algorithmOptionKeys = { "algorithm", "alg " };
|
||||||
|
String[] keyLengthOptionKeys = { "key_length", "keylen" };
|
||||||
|
String[] ttlOptionKeys = { "dnskey_ttl", "ttl" };
|
||||||
|
|
||||||
|
if (cli.hasOption('k')) {
|
||||||
|
kskFlag = true;
|
||||||
|
}
|
||||||
|
useLargeE = cli.hasOption('e'); // explicit command line option for the large exponent
|
||||||
|
useLargeE = !cli.hasOption('E'); // explicit command line option for the small exponent
|
||||||
|
String optstr = cliOption("e", useLargeEOptionKeys, Boolean.toString(useLargeE)); // get any config file properties
|
||||||
|
if (optstr != null) {
|
||||||
|
useLargeE = Boolean.parseBoolean(optstr);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputfile = cli.getOptionValue('f');
|
||||||
|
|
||||||
|
String keydirName = cliOption("d", keyDirectoryOptionKeys, null);
|
||||||
|
if (keydirName != null) {
|
||||||
|
keydir = new File(keydirName);
|
||||||
|
}
|
||||||
|
|
||||||
|
String algString = cliOption("a", algorithmOptionKeys, Integer.toString(algorithm));
|
||||||
|
algorithm = Utils.parseAlg(algString);
|
||||||
|
if (algorithm < 0) {
|
||||||
|
fail("DNSSEC algorithm " + algString + " is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
keylength = cliIntOption("b", keyLengthOptionKeys, keylength);
|
||||||
|
ttl = cliLongOption("ttl", ttlOptionKeys, ttl);
|
||||||
|
givenKeyTag = Utils.parseInt(cli.getOptionValue("with-tag"), -1);
|
||||||
|
|
||||||
|
String[] args = cli.getArgs();
|
||||||
|
|
||||||
|
if (args.length < 1) {
|
||||||
|
fail("missing key owner name");
|
||||||
|
}
|
||||||
|
|
||||||
|
owner = args[0];
|
||||||
|
|
||||||
|
log.fine("keygen options => algorithm: " + algorithm + ", keylength: " + keylength +
|
||||||
|
", useLargeE: " + useLargeE + ", kskFlag: " + kskFlag + ", ttl: " + ttl + ", givenKeyTag: " + givenKeyTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() throws Exception {
|
||||||
|
JCEDnsSecSigner signer = new JCEDnsSecSigner();
|
||||||
|
|
||||||
|
// Minor hack to make the owner name absolute.
|
||||||
|
if (!owner.endsWith(".")) {
|
||||||
|
owner = owner + ".";
|
||||||
|
}
|
||||||
|
|
||||||
|
Name ownerName = Name.fromString(owner);
|
||||||
|
|
||||||
|
// Calculate our flags
|
||||||
|
int flags = 0;
|
||||||
|
if (zoneKey) {
|
||||||
|
flags |= DNSKEYRecord.Flags.ZONE_KEY;
|
||||||
|
}
|
||||||
|
if (kskFlag) {
|
||||||
|
flags |= DNSKEYRecord.Flags.SEP_KEY;
|
||||||
|
}
|
||||||
|
log.fine("create key pair with (name = " + ownerName + ", ttl = " + ttl
|
||||||
|
+ ", alg = " + algorithm + ", flags = " + flags + ", length = "
|
||||||
|
+ keylength + ")");
|
||||||
|
|
||||||
|
DnsKeyPair pair = signer.generateKey(ownerName, ttl, DClass.IN,
|
||||||
|
algorithm, flags, keylength,
|
||||||
|
useLargeE);
|
||||||
|
|
||||||
|
// If we were asked to generate a duplicate keytag, keep trying until we get one
|
||||||
|
// This can take a long time, depending on our key generation speed
|
||||||
|
while (givenKeyTag >= 0 && pair.getDNSKEYFootprint() != givenKeyTag) {
|
||||||
|
pair = signer.generateKey(ownerName, ttl, DClass.IN, algorithm, flags, keylength,
|
||||||
|
useLargeE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputfile != null) {
|
||||||
|
BINDKeyUtils.writeKeyFiles(outputfile, pair, keydir);
|
||||||
|
} else {
|
||||||
|
BINDKeyUtils.writeKeyFiles(pair, keydir);
|
||||||
|
System.out.println(BINDKeyUtils.keyFileBase(pair));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
KeyGen tool = new KeyGen("keygen", "jdnssec-keygen [..options..] zonename");
|
||||||
|
|
||||||
|
tool.run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
99
src/main/java/com/verisignlabs/dnssec/cl/KeyInfoTool.java
Normal file
99
src/main/java/com/verisignlabs/dnssec/cl/KeyInfoTool.java
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// Copyright (C) 2001-2003, 2011, 2022 VeriSign, Inc.
|
||||||
|
//
|
||||||
|
// 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.security.interfaces.DSAPublicKey;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
|
||||||
|
import org.xbill.DNS.DNSKEYRecord;
|
||||||
|
|
||||||
|
import com.verisignlabs.dnssec.security.BINDKeyUtils;
|
||||||
|
import com.verisignlabs.dnssec.security.DnsKeyAlgorithm;
|
||||||
|
import com.verisignlabs.dnssec.security.DnsKeyPair;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class forms the command line implementation of a key introspection tool.
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
public class KeyInfoTool extends CLBase {
|
||||||
|
private String[] keynames = null;
|
||||||
|
|
||||||
|
public KeyInfoTool(String name, String usageStr) {
|
||||||
|
super(name, usageStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the command line options.
|
||||||
|
*/
|
||||||
|
protected void setupOptions() {
|
||||||
|
// no special options at the moment.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void processOptions() {
|
||||||
|
keynames = cli.getArgs();
|
||||||
|
|
||||||
|
if (keynames.length < 1) {
|
||||||
|
fail("missing key file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void execute() throws Exception {
|
||||||
|
for (int i = 0; i < keynames.length; ++i) {
|
||||||
|
String keyname = keynames[i];
|
||||||
|
DnsKeyPair key = BINDKeyUtils.loadKey(keyname, null);
|
||||||
|
DNSKEYRecord dnskey = key.getDNSKEYRecord();
|
||||||
|
DnsKeyAlgorithm dnskeyalg = DnsKeyAlgorithm.getInstance();
|
||||||
|
|
||||||
|
boolean isSEP = (dnskey.getFlags() & DNSKEYRecord.Flags.SEP_KEY) != 0;
|
||||||
|
|
||||||
|
System.out.println(keyname + ":");
|
||||||
|
System.out.println("Name: " + dnskey.getName());
|
||||||
|
System.out.println("SEP: " + isSEP);
|
||||||
|
|
||||||
|
System.out.println("Algorithm: " + dnskeyalg.algToString(dnskey.getAlgorithm())
|
||||||
|
+ " (" + dnskey.getAlgorithm() + ")");
|
||||||
|
System.out.println("ID: " + dnskey.getFootprint());
|
||||||
|
System.out.println("KeyFileBase: " + BINDKeyUtils.keyFileBase(key));
|
||||||
|
DnsKeyAlgorithm.BaseAlgorithm basetype = dnskeyalg.baseType(dnskey.getAlgorithm());
|
||||||
|
|
||||||
|
if (basetype == DnsKeyAlgorithm.BaseAlgorithm.RSA) {
|
||||||
|
RSAPublicKey pub = (RSAPublicKey) key.getPublic();
|
||||||
|
System.out.println("RSA Public Exponent: " + pub.getPublicExponent());
|
||||||
|
System.out.println("RSA Modulus: " + pub.getModulus());
|
||||||
|
} else if (basetype == DnsKeyAlgorithm.BaseAlgorithm.DSA) {
|
||||||
|
DSAPublicKey pub = (DSAPublicKey) key.getPublic();
|
||||||
|
System.out.println("DSA base (G): " + pub.getParams().getG());
|
||||||
|
System.out.println("DSA prime (P): " + pub.getParams().getP());
|
||||||
|
System.out.println("DSA subprime (Q): " + pub.getParams().getQ());
|
||||||
|
System.out.println("DSA public (Y): " + pub.getY());
|
||||||
|
}
|
||||||
|
if (keynames.length - i > 1) {
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
KeyInfoTool tool = new KeyInfoTool("keyinfotool", "jdnssec-keyinfo [..options..] keyfile");
|
||||||
|
|
||||||
|
tool.run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
331
src/main/java/com/verisignlabs/dnssec/cl/SignKeyset.java
Normal file
331
src/main/java/com/verisignlabs/dnssec/cl/SignKeyset.java
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
// Copyright (C) 2001-2003, 2011, 2022 VeriSign, Inc.
|
||||||
|
//
|
||||||
|
// 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;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.cli.Option;
|
||||||
|
import org.xbill.DNS.Name;
|
||||||
|
import org.xbill.DNS.RRSIGRecord;
|
||||||
|
import org.xbill.DNS.RRset;
|
||||||
|
import org.xbill.DNS.Record;
|
||||||
|
import org.xbill.DNS.Type;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
public class SignKeyset extends CLBase {
|
||||||
|
private File keyDirectory = null;
|
||||||
|
private String[] keyFiles = null;
|
||||||
|
private Instant start = null;
|
||||||
|
private Instant expire = null;
|
||||||
|
private String inputfile = null;
|
||||||
|
private String outputfile = null;
|
||||||
|
private boolean verifySigs = false;
|
||||||
|
|
||||||
|
public SignKeyset(String name, String usageStr) {
|
||||||
|
super(name, usageStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the command line options.
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected void setupOptions() {
|
||||||
|
// boolean options
|
||||||
|
opts.addOption("a", "verify", false, "verify generated signatures>");
|
||||||
|
|
||||||
|
// Argument options
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void processOptions() {
|
||||||
|
String[] verifyOptionKeys = { "verify_signatures", "verify" };
|
||||||
|
String[] keyDirectoryOptionKeys = { "key_directory", "keydir" };
|
||||||
|
String[] inceptionOptionKeys = { "inception", "start" };
|
||||||
|
String[] expireOptionKeys = { "expire" };
|
||||||
|
|
||||||
|
String optstr = null;
|
||||||
|
|
||||||
|
verifySigs = cliBooleanOption("a", verifyOptionKeys, false);
|
||||||
|
|
||||||
|
String keyDirectoryName = cliOption("D", keyDirectoryOptionKeys, null);
|
||||||
|
if (keyDirectoryName != null) {
|
||||||
|
keyDirectory = new File(optstr);
|
||||||
|
if (!keyDirectory.isDirectory()) {
|
||||||
|
fail("key directory " + optstr + " is not a directory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
optstr = cliOption("s", inceptionOptionKeys, null);
|
||||||
|
if (optstr != null) {
|
||||||
|
start = Utils.convertDuration(null, optstr);
|
||||||
|
} else {
|
||||||
|
// default is now - 1 hour.
|
||||||
|
start = Instant.now().minusSeconds(3600);
|
||||||
|
}
|
||||||
|
} catch (java.text.ParseException e) {
|
||||||
|
fail("Unable to parse start time specifiction: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
optstr = cliOption("e", expireOptionKeys, null);
|
||||||
|
if (optstr != null) {
|
||||||
|
expire = Utils.convertDuration(start, optstr);
|
||||||
|
} else {
|
||||||
|
expire = Utils.convertDuration(start, "+2592000"); // 30 days
|
||||||
|
}
|
||||||
|
} catch (java.text.ParseException e) {
|
||||||
|
fail("Unable to parse expire time specification: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputfile = cli.getOptionValue('f');
|
||||||
|
|
||||||
|
String[] files = cli.getArgs();
|
||||||
|
|
||||||
|
if (files.length < 1) {
|
||||||
|
fail("missing zone file and/or key files");
|
||||||
|
}
|
||||||
|
|
||||||
|
inputfile = files[0];
|
||||||
|
if (files.length > 1) {
|
||||||
|
keyFiles = new String[files.length - 1];
|
||||||
|
System.arraycopy(files, 1, keyFiles, 0, files.length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the generated signatures.
|
||||||
|
*
|
||||||
|
* @param records a list of {@link org.xbill.DNS.Record}s.
|
||||||
|
* @param keypairs a list of keypairs used the sign the zone.
|
||||||
|
* @return true if all of the signatures validated.
|
||||||
|
*/
|
||||||
|
private boolean verifySigs(List<Record> records,
|
||||||
|
List<DnsKeyPair> keypairs) {
|
||||||
|
boolean secure = true;
|
||||||
|
|
||||||
|
DnsSecVerifier verifier = new DnsSecVerifier();
|
||||||
|
|
||||||
|
for (DnsKeyPair pair : keypairs) {
|
||||||
|
verifier.addTrustedKey(pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
verifier.setVerifyAllSigs(true);
|
||||||
|
|
||||||
|
List<RRset> rrsets = SignUtils.assembleIntoRRsets(records);
|
||||||
|
|
||||||
|
for (RRset rrset : rrsets) {
|
||||||
|
// skip unsigned rrsets.
|
||||||
|
if (rrset.sigs().isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
boolean result = verifier.verify(rrset);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
log.fine("Signatures did not verify for RRset: " + rrset);
|
||||||
|
secure = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return secure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the key pairs from the key files.
|
||||||
|
*
|
||||||
|
* @param keyfiles 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.
|
||||||
|
* @param inDirectory the directory to look in (may be null).
|
||||||
|
* @return a list of keypair objects.
|
||||||
|
*/
|
||||||
|
private List<DnsKeyPair> getKeys(String[] keyfiles, int startIndex,
|
||||||
|
File inDirectory) throws IOException {
|
||||||
|
if (keyfiles == null)
|
||||||
|
return new ArrayList<>();
|
||||||
|
|
||||||
|
int len = keyfiles.length - startIndex;
|
||||||
|
if (len <= 0)
|
||||||
|
return new ArrayList<>();
|
||||||
|
|
||||||
|
ArrayList<DnsKeyPair> keys = new ArrayList<>(len);
|
||||||
|
|
||||||
|
for (int i = startIndex; i < keyfiles.length; i++) {
|
||||||
|
DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory);
|
||||||
|
if (k != null)
|
||||||
|
keys.add(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class KeyFileFilter implements FileFilter {
|
||||||
|
private String prefix;
|
||||||
|
|
||||||
|
public KeyFileFilter(Name origin) {
|
||||||
|
prefix = "K" + origin.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean accept(File pathname) {
|
||||||
|
if (!pathname.isFile())
|
||||||
|
return false;
|
||||||
|
String name = pathname.getName();
|
||||||
|
return (name.startsWith(prefix) && name.endsWith(".private"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<DnsKeyPair> findZoneKeys(File inDirectory, Name zonename)
|
||||||
|
throws IOException {
|
||||||
|
if (inDirectory == null) {
|
||||||
|
inDirectory = new File(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the list of "K<zone>.*.private files.
|
||||||
|
FileFilter filter = new KeyFileFilter(zonename);
|
||||||
|
File[] files = inDirectory.listFiles(filter);
|
||||||
|
|
||||||
|
// read in all of the records
|
||||||
|
ArrayList<DnsKeyPair> keys = new ArrayList<>();
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
DnsKeyPair p = BINDKeyUtils.loadKeyPair(files[i].getName(), inDirectory);
|
||||||
|
keys.add(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() throws Exception {
|
||||||
|
// Read in the zone
|
||||||
|
List<Record> records = ZoneUtils.readZoneFile(inputfile, null);
|
||||||
|
if (records == null || records.isEmpty()) {
|
||||||
|
fail("empty keyset file");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that all records are DNSKEYs with the same name.
|
||||||
|
Name keysetName = null;
|
||||||
|
RRset keyset = new RRset();
|
||||||
|
|
||||||
|
for (Record r : records) {
|
||||||
|
if (r.getType() != Type.DNSKEY) {
|
||||||
|
System.err.println("error: Non DNSKEY RR found in keyset: " + r);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (keysetName == null) {
|
||||||
|
keysetName = r.getName();
|
||||||
|
}
|
||||||
|
if (!r.getName().equals(keysetName)) {
|
||||||
|
fail("DNSKEY with a different name found!");
|
||||||
|
}
|
||||||
|
keyset.addRR(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyset.size() == 0) {
|
||||||
|
fail("error: No DNSKEYs found in keyset file");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the key pairs.
|
||||||
|
List<DnsKeyPair> keypairs = getKeys(keyFiles, 0, keyDirectory);
|
||||||
|
|
||||||
|
// If we *still* don't have any key pairs, look for keys the key
|
||||||
|
// directory
|
||||||
|
// that match
|
||||||
|
if (keypairs == null) {
|
||||||
|
keypairs = findZoneKeys(keyDirectory, keysetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there *still* aren't any ZSKs defined, bail.
|
||||||
|
if (keypairs == null || keypairs.isEmpty() || keysetName == null) {
|
||||||
|
fail("no signing keys could be determined.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// default the output file, if not set.
|
||||||
|
if (outputfile == null) {
|
||||||
|
if (keysetName.isAbsolute()) {
|
||||||
|
outputfile = keysetName + "signed_keyset";
|
||||||
|
} else {
|
||||||
|
outputfile = keysetName + ".signed_keyset";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JCEDnsSecSigner signer = new JCEDnsSecSigner();
|
||||||
|
|
||||||
|
List<RRSIGRecord> sigs = signer.signRRset(keyset, keypairs, start, expire);
|
||||||
|
for (RRSIGRecord s : sigs) {
|
||||||
|
keyset.addRR(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out the signed RRset
|
||||||
|
List<Record> signedRecords = new ArrayList<>();
|
||||||
|
for (Record r : keyset.rrs()) {
|
||||||
|
signedRecords.add(r);
|
||||||
|
}
|
||||||
|
for (RRSIGRecord s : keyset.sigs()) {
|
||||||
|
signedRecords.add(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out the signed zone
|
||||||
|
ZoneUtils.writeZoneFile(signedRecords, outputfile);
|
||||||
|
|
||||||
|
if (verifySigs) {
|
||||||
|
log.fine("verifying generated signatures");
|
||||||
|
boolean res = verifySigs(signedRecords, keypairs);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
System.out.println("Generated signatures verified");
|
||||||
|
} else {
|
||||||
|
System.out.println("Generated signatures did not verify.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SignKeyset tool = new SignKeyset("signkeyset", "jdnssec-signkeyset [..options..] dnskeyset_file [key_file ...]");
|
||||||
|
|
||||||
|
tool.run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
311
src/main/java/com/verisignlabs/dnssec/cl/SignRRset.java
Normal file
311
src/main/java/com/verisignlabs/dnssec/cl/SignRRset.java
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
// Copyright (C) 2001-2003, 2011, 2022 VeriSign, Inc.
|
||||||
|
//
|
||||||
|
// 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.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.cli.Option;
|
||||||
|
import org.xbill.DNS.Name;
|
||||||
|
import org.xbill.DNS.RRSIGRecord;
|
||||||
|
import org.xbill.DNS.RRset;
|
||||||
|
import org.xbill.DNS.Record;
|
||||||
|
import org.xbill.DNS.Type;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class forms the command line implementation of a DNSSEC RRset signer.
|
||||||
|
* Instead of being able to sign an entire zone, it will just sign a given
|
||||||
|
* RRset. Note that it will sign any RRset with any private key without
|
||||||
|
* consideration of whether or not the RRset *should* be signed in the context
|
||||||
|
* of a zone.
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
public class SignRRset extends CLBase {
|
||||||
|
private File keyDirectory = null;
|
||||||
|
private String[] keyFiles = null;
|
||||||
|
private Instant start = null;
|
||||||
|
private Instant expire = null;
|
||||||
|
private String inputfile = null;
|
||||||
|
private String outputfile = null;
|
||||||
|
private boolean verifySigs = false;
|
||||||
|
private boolean verboseSigning = false;
|
||||||
|
|
||||||
|
|
||||||
|
public SignRRset(String name, String usageStr) {
|
||||||
|
super(name, usageStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the command line options.
|
||||||
|
*/
|
||||||
|
protected void setupOptions() {
|
||||||
|
// boolean options
|
||||||
|
opts.addOption("a", "verify", false, "verify generated signatures>");
|
||||||
|
opts.addOption("V", "verbose-signing", false, "Display verbose signing activity.");
|
||||||
|
|
||||||
|
opts.addOption(Option.builder("D").hasArg().argName("dir").longOpt("key-directory")
|
||||||
|
.desc("directory to find key files (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 the signed rrset is written to").build());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processOptions() {
|
||||||
|
String[] verifyOptionKeys = { "verify_signatures", "verify" };
|
||||||
|
String[] verboseSigningOptionKeys = { "verbose_signing" };
|
||||||
|
String[] keyDirectoryOptionKeys = { "key_directory", "keydir" };
|
||||||
|
String[] inceptionOptionKeys = { "inception", "start" };
|
||||||
|
String[] expireOptionKeys = { "expire" };
|
||||||
|
|
||||||
|
String optstr = null;
|
||||||
|
|
||||||
|
verifySigs = cliBooleanOption("a", verifyOptionKeys, false);
|
||||||
|
|
||||||
|
verboseSigning = cliBooleanOption("V", verboseSigningOptionKeys, false);
|
||||||
|
|
||||||
|
optstr = cliOption("D", keyDirectoryOptionKeys, null);
|
||||||
|
if (optstr != null) {
|
||||||
|
keyDirectory = new File(optstr);
|
||||||
|
if (!keyDirectory.isDirectory()) {
|
||||||
|
fail("key directory " + optstr + " is not a directory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
optstr = cliOption("s", inceptionOptionKeys, null);
|
||||||
|
if (optstr != null) {
|
||||||
|
start = Utils.convertDuration(null, optstr);
|
||||||
|
} else {
|
||||||
|
// default is now - 1 hour.
|
||||||
|
start = Instant.now().minusSeconds(3600);
|
||||||
|
}
|
||||||
|
} catch (java.text.ParseException e) {
|
||||||
|
fail("unable to parse start time specifiction: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
optstr = cliOption("e", expireOptionKeys, null);
|
||||||
|
if (optstr != null) {
|
||||||
|
expire = Utils.convertDuration(start, optstr);
|
||||||
|
} else {
|
||||||
|
expire = Utils.convertDuration(start, "+2592000"); // 30 days
|
||||||
|
}
|
||||||
|
} catch (java.text.ParseException e) {
|
||||||
|
fail("Unable to parse expire time specification: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputfile = cli.getOptionValue('f');
|
||||||
|
|
||||||
|
String[] files = cli.getArgs();
|
||||||
|
|
||||||
|
if (files.length < 1) {
|
||||||
|
fail("missing zone file and/or key files");
|
||||||
|
}
|
||||||
|
|
||||||
|
inputfile = files[0];
|
||||||
|
if (files.length > 1) {
|
||||||
|
keyFiles = new String[files.length - 1];
|
||||||
|
System.arraycopy(files, 1, keyFiles, 0, files.length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the generated signatures.
|
||||||
|
*
|
||||||
|
* @param records a list of {@link org.xbill.DNS.Record}s.
|
||||||
|
* @param keypairs a list of keypairs used the sign the zone.
|
||||||
|
* @return true if all of the signatures validated.
|
||||||
|
*/
|
||||||
|
private boolean verifySigs(List<Record> records, List<DnsKeyPair> keypairs) {
|
||||||
|
boolean secure = true;
|
||||||
|
|
||||||
|
DnsSecVerifier verifier = new DnsSecVerifier();
|
||||||
|
|
||||||
|
for (DnsKeyPair pair : keypairs) {
|
||||||
|
verifier.addTrustedKey(pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
verifier.setVerifyAllSigs(true);
|
||||||
|
|
||||||
|
List<RRset> rrsets = SignUtils.assembleIntoRRsets(records);
|
||||||
|
|
||||||
|
for (RRset rrset : rrsets) {
|
||||||
|
// skip unsigned rrsets.
|
||||||
|
if (rrset.sigs().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean result = verifier.verify(rrset);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
log.fine("Signatures did not verify for RRset: " + rrset);
|
||||||
|
secure = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return secure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the key pairs from the key files.
|
||||||
|
*
|
||||||
|
* @param keyfiles 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.
|
||||||
|
* @param inDirectory the directory to look in (may be null).
|
||||||
|
* @return a list of keypair objects.
|
||||||
|
*/
|
||||||
|
private List<DnsKeyPair> getKeys(String[] keyfiles, int startIndex,
|
||||||
|
File inDirectory) throws IOException {
|
||||||
|
if (keyfiles == null)
|
||||||
|
return new ArrayList<>();
|
||||||
|
|
||||||
|
int len = keyfiles.length - startIndex;
|
||||||
|
if (len <= 0)
|
||||||
|
return new ArrayList<>();
|
||||||
|
|
||||||
|
ArrayList<DnsKeyPair> keys = new ArrayList<>(len);
|
||||||
|
|
||||||
|
for (int i = startIndex; i < keyfiles.length; i++) {
|
||||||
|
DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory);
|
||||||
|
if (k != null)
|
||||||
|
keys.add(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() throws Exception {
|
||||||
|
// Read in the zone
|
||||||
|
List<Record> records = ZoneUtils.readZoneFile(inputfile, null);
|
||||||
|
if (records == null || records.isEmpty()) {
|
||||||
|
fail("empty RRset file");
|
||||||
|
}
|
||||||
|
// Construct the RRset. Complain if the records in the input file
|
||||||
|
// consist of more than one RRset.
|
||||||
|
RRset rrset = null;
|
||||||
|
|
||||||
|
for (Record r : records) {
|
||||||
|
// skip RRSIGs
|
||||||
|
if (r.getType() == Type.RRSIG || r.getType() == Type.SIG) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the first record.
|
||||||
|
if (rrset == null) {
|
||||||
|
rrset = new RRset();
|
||||||
|
rrset.addRR(r);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Ensure that the remaining records all belong to the same rrset.
|
||||||
|
if (rrset.getName().equals(r.getName()) && rrset.getType() == r.getType()
|
||||||
|
&& rrset.getDClass() == r.getDClass()) {
|
||||||
|
rrset.addRR(r);
|
||||||
|
} else {
|
||||||
|
fail("records do not all belong to the same RRset");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rrset == null || rrset.size() == 0) {
|
||||||
|
fail("no records found in inputfile");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the key pairs.
|
||||||
|
|
||||||
|
if (keyFiles.length == 0) {
|
||||||
|
fail("at least one keyfile must be specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DnsKeyPair> keypairs = getKeys(keyFiles, 0, keyDirectory);
|
||||||
|
|
||||||
|
// Make sure that all the keypairs have the same name.
|
||||||
|
// This will be used as the zone name, too.
|
||||||
|
|
||||||
|
Name keysetName = null;
|
||||||
|
for (DnsKeyPair pair : keypairs) {
|
||||||
|
if (keysetName == null) {
|
||||||
|
keysetName = pair.getDNSKEYName();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!pair.getDNSKEYName().equals(keysetName)) {
|
||||||
|
fail("keys do not all have the same name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default the output file, if not set.
|
||||||
|
if (outputfile == null && !inputfile.equals("-")) {
|
||||||
|
outputfile = inputfile + ".signed";
|
||||||
|
}
|
||||||
|
|
||||||
|
JCEDnsSecSigner signer = new JCEDnsSecSigner(verboseSigning);
|
||||||
|
|
||||||
|
List<RRSIGRecord> sigs = signer.signRRset(rrset, keypairs, start, expire);
|
||||||
|
for (RRSIGRecord s : sigs) {
|
||||||
|
rrset.addRR(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out the signed RRset
|
||||||
|
List<Record> signedRecords = new ArrayList<>();
|
||||||
|
for (Record r : rrset.rrs()) {
|
||||||
|
signedRecords.add(r);
|
||||||
|
}
|
||||||
|
for (RRSIGRecord sigrec : rrset.sigs()) {
|
||||||
|
signedRecords.add(sigrec);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out the signed zone
|
||||||
|
ZoneUtils.writeZoneFile(signedRecords, outputfile);
|
||||||
|
|
||||||
|
if (verifySigs) {
|
||||||
|
log.fine("verifying generated signatures");
|
||||||
|
boolean res = verifySigs(signedRecords, keypairs);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
System.out.println("Generated signatures verified");
|
||||||
|
} else {
|
||||||
|
System.out.println("Generated signatures did not verify.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SignRRset tool = new SignRRset("signrrset", "jdnssec-signrrset [..options..] rrset_file key_file [key_file ...]");
|
||||||
|
|
||||||
|
tool.run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
634
src/main/java/com/verisignlabs/dnssec/cl/SignZone.java
Normal file
634
src/main/java/com/verisignlabs/dnssec/cl/SignZone.java
Normal file
@@ -0,0 +1,634 @@
|
|||||||
|
// Copyright (C) 2001-2003, 2011, 2022 VeriSign, Inc.
|
||||||
|
//
|
||||||
|
// 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.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import org.apache.commons.cli.Option;
|
||||||
|
import org.xbill.DNS.DNSKEYRecord;
|
||||||
|
import org.xbill.DNS.DNSSEC;
|
||||||
|
import org.xbill.DNS.Name;
|
||||||
|
import org.xbill.DNS.RRset;
|
||||||
|
import org.xbill.DNS.Record;
|
||||||
|
import org.xbill.DNS.TextParseException;
|
||||||
|
import org.xbill.DNS.Type;
|
||||||
|
import org.xbill.DNS.utils.base16;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class forms the command line implementation of a DNSSEC zone signer.
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
public class SignZone extends CLBase {
|
||||||
|
private File keyDirectory = null;
|
||||||
|
private File keysetDirectory = null;
|
||||||
|
private String[] kskFiles = null;
|
||||||
|
private String[] keyFiles = null;
|
||||||
|
private String zonefile = null;
|
||||||
|
private Instant start = null;
|
||||||
|
private Instant expire = null;
|
||||||
|
private String outputfile = null;
|
||||||
|
private boolean verifySigs = false;
|
||||||
|
private boolean useOptOut = false;
|
||||||
|
private boolean fullySignKeyset = false;
|
||||||
|
private List<Name> includeNames = null;
|
||||||
|
private boolean useNsec3 = false;
|
||||||
|
private byte[] salt = null;
|
||||||
|
private int iterations = 0;
|
||||||
|
private int digestId = DNSSEC.Digest.SHA256;
|
||||||
|
private long nsec3paramttl = -1;
|
||||||
|
private boolean verboseSigning = false;
|
||||||
|
|
||||||
|
private static final Random rand = new Random();
|
||||||
|
|
||||||
|
public SignZone(String name, String usageStr) {
|
||||||
|
super(name, usageStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupOptions() {
|
||||||
|
// boolean options
|
||||||
|
opts.addOption("a", "verify", false, "verify generated signatures>");
|
||||||
|
opts.addOption("F", "fully-sign-keyset", false,
|
||||||
|
"sign the zone apex keyset with all available keys.");
|
||||||
|
opts.addOption("V", "verbose-signing", false, "Display verbose signing activity.");
|
||||||
|
|
||||||
|
opts.addOption(Option.builder("d").hasArg().argName("dir").longOpt("keyset-directory")
|
||||||
|
.desc("directory to find keyset files (default '.')").build());
|
||||||
|
opts.addOption(Option.builder("D").hasArg().argName("dir").longOpt("key-directory")
|
||||||
|
.desc("directory to find key files (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 the signed rrset is written to").build());
|
||||||
|
opts.addOption(Option.builder("k").hasArgs().argName("KSK file").longOpt("ksk-file")
|
||||||
|
.desc("This key is a Key-Signing Key (may repeat)").build());
|
||||||
|
opts.addOption(Option.builder("I").hasArg().argName("file").longOpt("include-file")
|
||||||
|
.desc("include names in the file in the NSEC/NSEC3 chain").build());
|
||||||
|
|
||||||
|
// NSEC3 options
|
||||||
|
opts.addOption("3", "use-nsec3", false, "use NSEC3 instead of NSEC");
|
||||||
|
opts.addOption("O", "use-opt-out", false,
|
||||||
|
"generate a fully Opt-Out zone (only valid with NSEC3).");
|
||||||
|
opts.addOption(
|
||||||
|
Option.builder("S").hasArg().argName("hex value").longOpt("salt").desc("Supply a salt value").build());
|
||||||
|
opts.addOption(Option.builder("R").hasArg().argName("length").longOpt("random-salt")
|
||||||
|
.desc("Generate a random salt of <length>").build());
|
||||||
|
opts.addOption(Option.builder("H").hasArg().argName("count").longOpt("iterations")
|
||||||
|
.desc("Use this many addtional iterations in NSEC3 (default 0)").build());
|
||||||
|
opts.addOption(Option.builder().hasArg().longOpt("nsec3paramttl").argName("ttl")
|
||||||
|
.desc("Use this TTL for the NSEC3PARAM record (default is min(soa.min, soa.ttl))").build());
|
||||||
|
opts.addOption(Option.builder().hasArg().argName("id").longOpt("ds-digest")
|
||||||
|
.desc("Digest algorithm to use for generated DS records").build());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processOptions() {
|
||||||
|
String[] verifyOptionKeys = { "verify_signatures", "verify" };
|
||||||
|
String[] nsec3OptionKeys = { "use_nsec3", "nsec3" };
|
||||||
|
String[] optOutOptionKeys = { "use_opt_out", "opt_out" };
|
||||||
|
String[] verboseSigningOptionKeys = { "verbose_signing" };
|
||||||
|
String[] fullySignKeysetOptionKeys = { "fully_sign_keyset", "fully_sign" };
|
||||||
|
String[] keyDirectoryOptionKeys = { "key_directory", "keydir" };
|
||||||
|
String[] inceptionOptionKeys = { "inception", "start" };
|
||||||
|
String[] expireOptionKeys = { "expire" };
|
||||||
|
String[] nsec3SaltOptionKeys = { "nsec3_salt", "salt" };
|
||||||
|
String[] randomSaltOptionKeys = { "nsec3_random_salt_length", "nsec3_salt_length", "random_salt_length" };
|
||||||
|
String[] nsec3IterationsOptionKeys = { "nsec3_iterations", "iterations" };
|
||||||
|
String[] digestAlgOptionKeys = { "digest_algorithm", "digest_id" };
|
||||||
|
String[] nsec3paramTTLOptionKeys = { "nsec3param_ttl" };
|
||||||
|
String[] incudeNamesOptionKeys = { "include_names_file", "include_names" };
|
||||||
|
|
||||||
|
String optstr = null;
|
||||||
|
|
||||||
|
verifySigs = cliBooleanOption("a", verifyOptionKeys, false);
|
||||||
|
useNsec3 = cliBooleanOption("3", nsec3OptionKeys, false);
|
||||||
|
useOptOut = cliBooleanOption("O", optOutOptionKeys, false);
|
||||||
|
verboseSigning = cliBooleanOption("V", verboseSigningOptionKeys, false);
|
||||||
|
|
||||||
|
if (useOptOut && !useNsec3) {
|
||||||
|
System.err.println("Opt-Out not supported without NSEC3 -- ignored.");
|
||||||
|
useOptOut = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fullySignKeyset = cliBooleanOption("F", fullySignKeysetOptionKeys, false);
|
||||||
|
|
||||||
|
optstr = cliOption("D", keyDirectoryOptionKeys, null);
|
||||||
|
if (optstr != null) {
|
||||||
|
keyDirectory = new File(optstr);
|
||||||
|
if (!keyDirectory.isDirectory()) {
|
||||||
|
fail("key directory " + optstr + " is not a directory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
optstr = cliOption("s", inceptionOptionKeys, null);
|
||||||
|
if (optstr != null) {
|
||||||
|
start = Utils.convertDuration(null, optstr);
|
||||||
|
} else {
|
||||||
|
// default is now - 1 hour.
|
||||||
|
start = Instant.now().minusSeconds(3600);
|
||||||
|
}
|
||||||
|
} catch (java.text.ParseException e) {
|
||||||
|
fail("unable to parse start time specifiction: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
optstr = cliOption("e", expireOptionKeys, null);
|
||||||
|
if (optstr != null) {
|
||||||
|
expire = Utils.convertDuration(start, optstr);
|
||||||
|
} else {
|
||||||
|
expire = Utils.convertDuration(start, "+2592000"); // 30 days
|
||||||
|
}
|
||||||
|
} catch (java.text.ParseException e) {
|
||||||
|
fail("missing zone file and/or key files");
|
||||||
|
}
|
||||||
|
|
||||||
|
outputfile = cli.getOptionValue('f');
|
||||||
|
|
||||||
|
kskFiles = cli.getOptionValues('k');
|
||||||
|
|
||||||
|
optstr = cliOption("S", nsec3SaltOptionKeys, null);
|
||||||
|
if (optstr != null) {
|
||||||
|
salt = base16.fromString(optstr);
|
||||||
|
if (salt == null && !optstr.equals("-")) {
|
||||||
|
fail("salt is not valid hexidecimal");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
optstr = cliOption("R", randomSaltOptionKeys, null);
|
||||||
|
if (optstr != null) {
|
||||||
|
int length = Utils.parseInt(optstr, 0);
|
||||||
|
if (length > 0 && length <= 255) {
|
||||||
|
salt = new byte[length];
|
||||||
|
rand.nextBytes(salt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iterations = cliIntOption("iterations", nsec3IterationsOptionKeys, 0);
|
||||||
|
if (iterations > 150) {
|
||||||
|
log.warning("NSEC3 iterations value is too high for normal use: " + iterations
|
||||||
|
+ " is greater than current accepted threshold of 150");
|
||||||
|
}
|
||||||
|
|
||||||
|
optstr = cliOption("ds-digest", digestAlgOptionKeys, Integer.toString(digestId));
|
||||||
|
digestId = DNSSEC.Digest.value(optstr);
|
||||||
|
|
||||||
|
nsec3paramttl = cliIntOption("nsec3paramttl", nsec3paramTTLOptionKeys, -1);
|
||||||
|
|
||||||
|
optstr = cliOption("I", incudeNamesOptionKeys, null);
|
||||||
|
if (optstr != null) {
|
||||||
|
File includeNamesFile = new File(optstr);
|
||||||
|
try {
|
||||||
|
includeNames = getNameList(includeNamesFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
fail("unable to load include-names file: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] files = cli.getArgs();
|
||||||
|
|
||||||
|
if (files.length < 1) {
|
||||||
|
fail("missing zone file and/or key files");
|
||||||
|
}
|
||||||
|
|
||||||
|
zonefile = files[0];
|
||||||
|
if (files.length > 1) {
|
||||||
|
keyFiles = new String[files.length - 1];
|
||||||
|
System.arraycopy(files, 1, keyFiles, 0, files.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.fine("SignZone settings => key_directory: " + keyDirectory +
|
||||||
|
", keyset_directory: " + keysetDirectory +
|
||||||
|
", start: " + start.getEpochSecond() +
|
||||||
|
", expire: " + expire.getEpochSecond() +
|
||||||
|
", verify_sigs: " + verifySigs +
|
||||||
|
", use_nsec3: " + useNsec3 +
|
||||||
|
", use_opt_out = " + useOptOut +
|
||||||
|
", salt: " + DnsKeyPair.toHex(salt) +
|
||||||
|
", iterations: " + iterations +
|
||||||
|
", nsec3param_ttl: " + nsec3paramttl +
|
||||||
|
", fully_sign_keyset: " + fullySignKeyset +
|
||||||
|
", digest_id: " + digestId +
|
||||||
|
", verbose_signing: " + verboseSigning);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a list of DNS names from a file.
|
||||||
|
*
|
||||||
|
* @param nameListFile the path of a file containing a bare list of DNS
|
||||||
|
* names.
|
||||||
|
* @return a list of {@link org.xbill.DNS.Name} objects.
|
||||||
|
*/
|
||||||
|
private List<Name> getNameList(File nameListFile) throws IOException {
|
||||||
|
try (BufferedReader br = new BufferedReader(new FileReader(nameListFile))) {
|
||||||
|
List<Name> res = new ArrayList<>();
|
||||||
|
|
||||||
|
String line = null;
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
try {
|
||||||
|
Name n = Name.fromString(line);
|
||||||
|
// force the name to be absolute.
|
||||||
|
if (!n.isAbsolute())
|
||||||
|
n = Name.concatenate(n, Name.root);
|
||||||
|
|
||||||
|
res.add(n);
|
||||||
|
} catch (TextParseException e) {
|
||||||
|
log.severe("DNS Name parsing error:" + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the generated signatures.
|
||||||
|
*
|
||||||
|
* @param records a list of {@link org.xbill.DNS.Record}s.
|
||||||
|
* @param keypairs a list of keypairs used the sign the zone.
|
||||||
|
* @return true if all of the signatures validated.
|
||||||
|
*/
|
||||||
|
private boolean verifyZoneSigs(List<Record> records,
|
||||||
|
List<DnsKeyPair> keypairs, List<DnsKeyPair> kskpairs) {
|
||||||
|
boolean secure = true;
|
||||||
|
|
||||||
|
DnsSecVerifier verifier = new DnsSecVerifier();
|
||||||
|
|
||||||
|
for (DnsKeyPair pair : keypairs) {
|
||||||
|
verifier.addTrustedKey(pair);
|
||||||
|
}
|
||||||
|
for (DnsKeyPair pair : kskpairs) {
|
||||||
|
verifier.addTrustedKey(pair);
|
||||||
|
}
|
||||||
|
verifier.setVerifyAllSigs(true);
|
||||||
|
|
||||||
|
List<RRset> rrsets = SignUtils.assembleIntoRRsets(records);
|
||||||
|
|
||||||
|
for (RRset rrset : rrsets) {
|
||||||
|
// skip unsigned rrsets.
|
||||||
|
if (rrset.sigs().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean result = verifier.verify(rrset);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
System.err.println("Signatures did not verify for RRset: " + rrset);
|
||||||
|
log.fine("Signatures did not verify for RRset: " + rrset);
|
||||||
|
secure = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return secure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the key pairs from the key files.
|
||||||
|
*
|
||||||
|
* @param keyfiles 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.
|
||||||
|
* @param inDirectory the directory to look in (may be null).
|
||||||
|
* @return a list of keypair objects.
|
||||||
|
*/
|
||||||
|
private List<DnsKeyPair> getKeys(String[] keyfiles, int startIndex,
|
||||||
|
File inDirectory) throws IOException {
|
||||||
|
if (keyfiles == null)
|
||||||
|
return new ArrayList<>();
|
||||||
|
|
||||||
|
int len = keyfiles.length - startIndex;
|
||||||
|
if (len <= 0)
|
||||||
|
return new ArrayList<>();
|
||||||
|
|
||||||
|
ArrayList<DnsKeyPair> keys = new ArrayList<>(len);
|
||||||
|
|
||||||
|
for (int i = startIndex; i < keyfiles.length; i++) {
|
||||||
|
DnsKeyPair k = BINDKeyUtils.loadKeyPair(keyfiles[i], inDirectory);
|
||||||
|
if (k != null) {
|
||||||
|
keys.add(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DnsKeyPair> getKeys(List<Record> dnskeyrrs, File inDirectory)
|
||||||
|
throws IOException {
|
||||||
|
List<DnsKeyPair> res = new ArrayList<>();
|
||||||
|
for (Record r : dnskeyrrs) {
|
||||||
|
if (r.getType() != Type.DNSKEY)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Construct a public-key-only DnsKeyPair just so we can calculate the
|
||||||
|
// base name.
|
||||||
|
DnsKeyPair pub = new DnsKeyPair((DNSKEYRecord) r);
|
||||||
|
DnsKeyPair pair = BINDKeyUtils.loadKeyPair(BINDKeyUtils.keyFileBase(pub),
|
||||||
|
inDirectory);
|
||||||
|
if (pair != null) {
|
||||||
|
res.add(pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class KeyFileFilter implements FileFilter {
|
||||||
|
private String prefix;
|
||||||
|
|
||||||
|
public KeyFileFilter(Name origin) {
|
||||||
|
prefix = "K" + origin.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean accept(File pathname) {
|
||||||
|
if (!pathname.isFile())
|
||||||
|
return false;
|
||||||
|
String name = pathname.getName();
|
||||||
|
return (name.startsWith(prefix) && name.endsWith(".private"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DnsKeyPair> findZoneKeys(File inDirectory, Name zonename)
|
||||||
|
throws IOException {
|
||||||
|
if (inDirectory == null) {
|
||||||
|
inDirectory = new File(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the list of "K<zone>.*.private files.
|
||||||
|
FileFilter filter = new KeyFileFilter(zonename);
|
||||||
|
File[] files = inDirectory.listFiles(filter);
|
||||||
|
|
||||||
|
// read in all of the records
|
||||||
|
ArrayList<DnsKeyPair> keys = new ArrayList<>();
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
DnsKeyPair p = BINDKeyUtils.loadKeyPair(files[i].getName(), inDirectory);
|
||||||
|
keys.add(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an implementation of a file filter used for finding BIND 9-style
|
||||||
|
* keyset-* files.
|
||||||
|
*/
|
||||||
|
private static class KeysetFileFilter implements FileFilter {
|
||||||
|
public boolean accept(File pathname) {
|
||||||
|
if (!pathname.isFile())
|
||||||
|
return false;
|
||||||
|
String name = pathname.getName();
|
||||||
|
return (name.startsWith("keyset-"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load keysets (which contain delegation point security info).
|
||||||
|
*
|
||||||
|
* @param inDirectory the directory to look for the keyset files (may be null,
|
||||||
|
* in which case it defaults to looking in the current
|
||||||
|
* working directory).
|
||||||
|
* @param zonename the name of the zone we are signing, so we can ignore
|
||||||
|
* keysets that do not belong in the zone.
|
||||||
|
* @return a list of {@link org.xbill.DNS.Record}s found in the keyset files.
|
||||||
|
*/
|
||||||
|
private List<Record> getKeysets(File inDirectory, Name zonename)
|
||||||
|
throws IOException {
|
||||||
|
if (inDirectory == null) {
|
||||||
|
inDirectory = new File(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the list of "keyset-" files.
|
||||||
|
FileFilter filter = new KeysetFileFilter();
|
||||||
|
File[] files = inDirectory.listFiles(filter);
|
||||||
|
|
||||||
|
// read in all of the records
|
||||||
|
ArrayList<Record> keysetRecords = new ArrayList<>();
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
List<Record> l = ZoneUtils.readZoneFile(files[i].getAbsolutePath(), zonename);
|
||||||
|
keysetRecords.addAll(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
// discard records that do not belong to the zone in question.
|
||||||
|
for (Iterator<Record> i = keysetRecords.iterator(); i.hasNext();) {
|
||||||
|
Record r = i.next();
|
||||||
|
if (!r.getName().subdomain(zonename)) {
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keysetRecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the given keypairs can be used to sign the zone.
|
||||||
|
*
|
||||||
|
* @param zonename the zone origin.
|
||||||
|
* @param keypairs a list of {@link DnsKeyPair} objects that will be used to
|
||||||
|
* sign the zone.
|
||||||
|
* @return true if the keypairs valid.
|
||||||
|
*/
|
||||||
|
private static boolean keyPairsValidForZone(Name zonename, List<DnsKeyPair> keypairs) {
|
||||||
|
if (keypairs == null)
|
||||||
|
return true; // technically true, I guess.
|
||||||
|
|
||||||
|
for (DnsKeyPair kp : keypairs) {
|
||||||
|
Name keyname = kp.getDNSKEYRecord().getName();
|
||||||
|
if (!keyname.equals(zonename)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() throws Exception {
|
||||||
|
// Do a basic existence check for the zonefile first.
|
||||||
|
if (!zonefile.equals("-")) {
|
||||||
|
File f = new File(zonefile);
|
||||||
|
if (!f.exists()) {
|
||||||
|
fail("zonefile '" + zonefile + "' does not exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read in the zone
|
||||||
|
List<Record> records = ZoneUtils.readZoneFile(zonefile, null);
|
||||||
|
if (records == null || records.isEmpty()) {
|
||||||
|
fail("empty zone file");
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the zone name.
|
||||||
|
Name zonename = ZoneUtils.findZoneName(records);
|
||||||
|
if (zonename == null) {
|
||||||
|
fail("invalid zone file - no SOA");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the key pairs. Note that getKeys() always returns an ArrayList,
|
||||||
|
// which may be empty.
|
||||||
|
List<DnsKeyPair> keypairs = getKeys(keyFiles, 0, keyDirectory);
|
||||||
|
List<DnsKeyPair> kskpairs = getKeys(kskFiles, 0, keyDirectory);
|
||||||
|
|
||||||
|
// If we didn't get any keys on the command line, look at the zone apex for
|
||||||
|
// any public keys.
|
||||||
|
if (keypairs.isEmpty()) {
|
||||||
|
List<Record> dnskeys = ZoneUtils.findRRs(records, zonename, Type.DNSKEY);
|
||||||
|
keypairs = getKeys(dnskeys, keyDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we *still* don't have any key pairs, look for keys the key directory
|
||||||
|
// that match
|
||||||
|
if (keypairs.isEmpty()) {
|
||||||
|
keypairs = findZoneKeys(keyDirectory, zonename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have any KSKs, but we do have more than one zone
|
||||||
|
// signing key (presumably), presume that the zone signing keys
|
||||||
|
// are just not differentiated and try to figure out which keys
|
||||||
|
// are actually KSKs by looking at the SEP flag.
|
||||||
|
if (kskpairs.isEmpty() && !keypairs.isEmpty()) {
|
||||||
|
for (Iterator<DnsKeyPair> i = keypairs.iterator(); i.hasNext();) {
|
||||||
|
DnsKeyPair pair = i.next();
|
||||||
|
DNSKEYRecord kr = pair.getDNSKEYRecord();
|
||||||
|
if ((kr.getFlags() & DNSKEYRecord.Flags.SEP_KEY) != 0) {
|
||||||
|
kskpairs.add(pair);
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have zero keypairs at all, we are stuck.
|
||||||
|
if (keypairs.isEmpty() && kskpairs.isEmpty()) {
|
||||||
|
fail("no zone signing keys could be determined");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we only have one type of key (all ZSKs or all KSKs), then these are
|
||||||
|
// "CSKs" -- Combined signing keys, so assign one set to the other.
|
||||||
|
if (keypairs.isEmpty()) {
|
||||||
|
keypairs = kskpairs;
|
||||||
|
} else if (kskpairs.isEmpty()) {
|
||||||
|
kskpairs = keypairs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output what keys we are using for what
|
||||||
|
if (keypairs == kskpairs) {
|
||||||
|
System.out.println("CSKs: ");
|
||||||
|
for (DnsKeyPair kp : keypairs) {
|
||||||
|
System.out.println(" - " + kp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("KSKs: ");
|
||||||
|
for (DnsKeyPair kp : kskpairs) {
|
||||||
|
System.out.println(" - " + kp);
|
||||||
|
}
|
||||||
|
System.out.println("ZSKs: ");
|
||||||
|
for (DnsKeyPair kp : keypairs) {
|
||||||
|
System.out.println(" - " + kp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default the output file, if not set.
|
||||||
|
if (outputfile == null && !zonefile.equals("-")) {
|
||||||
|
if (zonename.isAbsolute()) {
|
||||||
|
outputfile = zonename + "signed";
|
||||||
|
} else {
|
||||||
|
outputfile = zonename + ".signed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the keys can be in the zone.
|
||||||
|
if (!keyPairsValidForZone(zonename, keypairs)
|
||||||
|
|| !keyPairsValidForZone(zonename, kskpairs)) {
|
||||||
|
fail("specified keypairs are not valid for the zone.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// We force the signing keys to be in the zone by just appending
|
||||||
|
// them to the zone here. Currently JCEDnsSecSigner.signZone
|
||||||
|
// removes duplicate records.
|
||||||
|
if (!kskpairs.isEmpty()) {
|
||||||
|
for (DnsKeyPair pair : kskpairs) {
|
||||||
|
records.add(pair.getDNSKEYRecord());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!keypairs.isEmpty()) {
|
||||||
|
for (DnsKeyPair pair : keypairs) {
|
||||||
|
records.add(pair.getDNSKEYRecord());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read in the keysets, if any.
|
||||||
|
List<Record> keysetrecs = getKeysets(keysetDirectory, zonename);
|
||||||
|
if (keysetrecs != null) {
|
||||||
|
records.addAll(keysetrecs);
|
||||||
|
}
|
||||||
|
|
||||||
|
JCEDnsSecSigner signer = new JCEDnsSecSigner(verboseSigning);
|
||||||
|
|
||||||
|
// Sign the zone.
|
||||||
|
List<Record> signedRecords;
|
||||||
|
|
||||||
|
if (useNsec3) {
|
||||||
|
signedRecords = signer.signZoneNSEC3(zonename, records, kskpairs, keypairs,
|
||||||
|
start, expire,
|
||||||
|
fullySignKeyset, useOptOut,
|
||||||
|
includeNames, salt,
|
||||||
|
iterations, digestId,
|
||||||
|
nsec3paramttl);
|
||||||
|
} else {
|
||||||
|
signedRecords = signer.signZone(zonename, records, kskpairs, keypairs,
|
||||||
|
start, expire, fullySignKeyset,
|
||||||
|
digestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out the signed zone
|
||||||
|
ZoneUtils.writeZoneFile(signedRecords, outputfile);
|
||||||
|
System.out.println("zone signing complete");
|
||||||
|
|
||||||
|
if (verifySigs) {
|
||||||
|
log.fine("verifying generated signatures");
|
||||||
|
boolean res = verifyZoneSigs(signedRecords, keypairs, kskpairs);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
System.out.println("Generated signatures verified");
|
||||||
|
} else {
|
||||||
|
System.out.println("Generated signatures did not verify.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SignZone tool = new SignZone("signzone", "jdnssec-signzone [..options..] zone_file [key_file ...]");
|
||||||
|
|
||||||
|
tool.run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/main/java/com/verisignlabs/dnssec/cl/Utils.java
Normal file
103
src/main/java/com/verisignlabs/dnssec/cl/Utils.java
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package com.verisignlabs.dnssec.cl;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import com.verisignlabs.dnssec.security.DnsKeyAlgorithm;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
|
private Utils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a string into an integer safely, using a default if the value does not
|
||||||
|
* parse cleanly
|
||||||
|
*
|
||||||
|
* @param s The string to parse
|
||||||
|
* @param def The default value
|
||||||
|
* @return either the parsed int or the default
|
||||||
|
*/
|
||||||
|
public static int parseInt(String s, int def) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(s);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a string into a long safely, using a default if the value does not
|
||||||
|
* parse cleanly
|
||||||
|
*
|
||||||
|
* @param s The string to parse
|
||||||
|
* @param def The default value
|
||||||
|
* @return either the parsed long or the default
|
||||||
|
*/
|
||||||
|
public static long parseLong(String s, long def) {
|
||||||
|
try {
|
||||||
|
return Long.parseLong(s);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a DNSSEC algorithm number of mnemonic into the official algorithm number.
|
||||||
|
* @param s The arge value
|
||||||
|
* @return A DNSSEC algorithm number, or -1 if unrecognized.
|
||||||
|
*/
|
||||||
|
public static int parseAlg(String s) {
|
||||||
|
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
||||||
|
|
||||||
|
int alg = Utils.parseInt(s, -1);
|
||||||
|
if (alg > 0) {
|
||||||
|
if (algs.supportedAlgorithm(alg))
|
||||||
|
return alg;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return algs.stringToAlgorithm(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate a date/time from a command line time/offset duration string.
|
||||||
|
*
|
||||||
|
* @param start the start time to calculate offsets from.
|
||||||
|
* @param duration the time/offset string to parse.
|
||||||
|
* @return the calculated time.
|
||||||
|
*/
|
||||||
|
public static Instant convertDuration(Instant start, String duration) throws java.text.ParseException {
|
||||||
|
if (start == null) {
|
||||||
|
start = Instant.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duration.startsWith("now")) {
|
||||||
|
start = Instant.now();
|
||||||
|
if (duration.indexOf("+") < 0)
|
||||||
|
return start;
|
||||||
|
|
||||||
|
duration = duration.substring(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duration.startsWith("+")) {
|
||||||
|
long offset = parseLong(duration.substring(1), 0);
|
||||||
|
return start.plusSeconds(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a heuristic to distinguish UNIX epoch times from the zone file
|
||||||
|
// format standard (which is length == 14)
|
||||||
|
if (duration.length() <= 10) {
|
||||||
|
long epoch = parseLong(duration, 0);
|
||||||
|
return Instant.ofEpochSecond(epoch);
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMddHHmmss");
|
||||||
|
dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||||
|
Date parsedDate = dateFormatter.parse(duration);
|
||||||
|
return parsedDate.toInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
123
src/main/java/com/verisignlabs/dnssec/cl/VerifyZone.java
Normal file
123
src/main/java/com/verisignlabs/dnssec/cl/VerifyZone.java
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
// Copyright (C) 2011, 2022 VeriSign, Inc.
|
||||||
|
//
|
||||||
|
// 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.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.cli.Option;
|
||||||
|
import org.xbill.DNS.Record;
|
||||||
|
|
||||||
|
import com.verisignlabs.dnssec.security.ZoneUtils;
|
||||||
|
import com.verisignlabs.dnssec.security.ZoneVerifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class forms the command line implementation of a DNSSEC zone validator.
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
public class VerifyZone extends CLBase {
|
||||||
|
private String zonefile = null;
|
||||||
|
private String[] keyfiles = null;
|
||||||
|
private int startfudge = 0;
|
||||||
|
private int expirefudge = 0;
|
||||||
|
private boolean ignoreTime = false;
|
||||||
|
private boolean ignoreDups = false;
|
||||||
|
private Instant currentTime = null;
|
||||||
|
|
||||||
|
public VerifyZone(String name, String usageStr) {
|
||||||
|
super(name, usageStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupOptions() {
|
||||||
|
opts.addOption(Option.builder("S").hasArg().argName("seconds").longOpt("sig-start-fudge")
|
||||||
|
.desc("'fudge' RRSIG inception ties by 'seconds'").build());
|
||||||
|
opts.addOption(Option.builder("E").hasArg().argName("seconds").longOpt("sig-expire-fudge")
|
||||||
|
.desc("'fudge' RRSIG expiration times by 'seconds'").build());
|
||||||
|
opts.addOption(Option.builder("t").hasArg().argName("time").longOpt("use-time")
|
||||||
|
.desc("Use 'time' as the time for verification purposes.").build());
|
||||||
|
|
||||||
|
opts.addOption(
|
||||||
|
Option.builder().longOpt("ignore-time").desc("Ignore RRSIG inception and expiration time errors.").build());
|
||||||
|
opts.addOption(Option.builder().longOpt("ignore-duplicate-rrs").desc("Ignore duplicate record errors.").build());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processOptions() {
|
||||||
|
String[] ignoreTimeOptionKeys = { "ignore_time" };
|
||||||
|
String[] ignoreDuplicateOptionKeys = { "ingore_duplicate_rrs", "ignore_duplicates" };
|
||||||
|
String[] startFudgeOptionKeys = { "start_fudge" };
|
||||||
|
String[] expireFudgeOptionKeys = { "expire_fudge" };
|
||||||
|
String[] currentTimeOptionKeys = { "current_time" };
|
||||||
|
|
||||||
|
ignoreTime = cliBooleanOption("ignore-time", ignoreTimeOptionKeys, false);
|
||||||
|
ignoreDups = cliBooleanOption("ignore-duplicate-rrs", ignoreDuplicateOptionKeys, false);
|
||||||
|
startfudge = cliIntOption("S", startFudgeOptionKeys, 0);
|
||||||
|
expirefudge = cliIntOption("E", expireFudgeOptionKeys, 0);
|
||||||
|
|
||||||
|
String optstr = cliOption("t", currentTimeOptionKeys, null);
|
||||||
|
if (optstr != null) {
|
||||||
|
try {
|
||||||
|
currentTime = Utils.convertDuration(null, optstr);
|
||||||
|
} catch (java.text.ParseException e) {
|
||||||
|
fail("could not parse timespec");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] args = cli.getArgs();
|
||||||
|
|
||||||
|
if (args.length < 1) {
|
||||||
|
fail("missing zone file");
|
||||||
|
}
|
||||||
|
|
||||||
|
zonefile = args[0];
|
||||||
|
|
||||||
|
if (args.length >= 2) {
|
||||||
|
keyfiles = new String[args.length - 1];
|
||||||
|
System.arraycopy(args, 1, keyfiles, 0, keyfiles.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() throws Exception {
|
||||||
|
ZoneVerifier zoneverifier = new ZoneVerifier();
|
||||||
|
zoneverifier.getVerifier().setStartFudge(startfudge);
|
||||||
|
zoneverifier.getVerifier().setExpireFudge(expirefudge);
|
||||||
|
zoneverifier.getVerifier().setIgnoreTime(ignoreTime);
|
||||||
|
zoneverifier.getVerifier().setCurrentTime(currentTime);
|
||||||
|
zoneverifier.setIgnoreDuplicateRRs(ignoreDups);
|
||||||
|
|
||||||
|
List<Record> records = ZoneUtils.readZoneFile(zonefile, null);
|
||||||
|
|
||||||
|
log.fine("verifying zone...");
|
||||||
|
int errors = zoneverifier.verifyZone(records);
|
||||||
|
log.fine("completed verification process.");
|
||||||
|
|
||||||
|
if (errors > 0) {
|
||||||
|
System.out.println("zone did not verify.");
|
||||||
|
System.exit(1);
|
||||||
|
} else {
|
||||||
|
System.out.println("zone verified.");
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
VerifyZone tool = new VerifyZone("verifyzone", "jdnssec-verifyzone [..options..] zonefile");
|
||||||
|
|
||||||
|
tool.run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
180
src/main/java/com/verisignlabs/dnssec/cl/ZoneFormat.java
Normal file
180
src/main/java/com/verisignlabs/dnssec/cl/ZoneFormat.java
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
// Copyright (C) 2011, 2022 VeriSign, Inc.
|
||||||
|
//
|
||||||
|
// 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.IOException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.xbill.DNS.Master;
|
||||||
|
import org.xbill.DNS.NSEC3PARAMRecord;
|
||||||
|
import org.xbill.DNS.NSEC3Record;
|
||||||
|
import org.xbill.DNS.Name;
|
||||||
|
import org.xbill.DNS.Record;
|
||||||
|
import org.xbill.DNS.Section;
|
||||||
|
import org.xbill.DNS.Type;
|
||||||
|
import org.xbill.DNS.utils.base32;
|
||||||
|
|
||||||
|
import com.verisignlabs.dnssec.security.RecordComparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class forms the command line implementation of a zone file normalizer.
|
||||||
|
* That is, a tool to rewrite zones in a consistent, comparable format.
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
public class ZoneFormat extends CLBase {
|
||||||
|
private String file;
|
||||||
|
private boolean assignNSEC3;
|
||||||
|
|
||||||
|
public ZoneFormat(String name, String usageStr) {
|
||||||
|
super(name, usageStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupOptions() {
|
||||||
|
opts.addOption("N", "nsec3", false,
|
||||||
|
"attempt to determine the original ownernames for NSEC3 RRs.");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processOptions() {
|
||||||
|
String[] assignNsec3OwnersOptionKeys = { "assign_nsec3_owners", "assign_owners" };
|
||||||
|
|
||||||
|
assignNSEC3 = cliBooleanOption("N", assignNsec3OwnersOptionKeys, false);
|
||||||
|
|
||||||
|
String[] args = cli.getArgs();
|
||||||
|
|
||||||
|
if (args.length < 1) {
|
||||||
|
fail("must specify a zone file");
|
||||||
|
}
|
||||||
|
|
||||||
|
file = args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Record> readZoneFile(String filename) throws IOException {
|
||||||
|
try (Master master = new Master(filename)) {
|
||||||
|
List<Record> res = new ArrayList<>();
|
||||||
|
Record r = null;
|
||||||
|
|
||||||
|
while ((r = master.nextRecord()) != null) {
|
||||||
|
// Normalize each record by round-tripping it through canonical wire line
|
||||||
|
// format. Mostly this just lowercases names that are subject to it.
|
||||||
|
byte[] wire = r.toWireCanonical();
|
||||||
|
Record canonRec = Record.fromWire(wire, Section.ANSWER);
|
||||||
|
res.add(canonRec);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void formatZone(List<Record> zone) {
|
||||||
|
|
||||||
|
for (Record r : zone) {
|
||||||
|
System.out.println(r.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void determineNSEC3Owners(List<Record> zone)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
|
||||||
|
// first, find the NSEC3PARAM record -- this is an inefficient linear
|
||||||
|
// search, although it should be near the head of the list.
|
||||||
|
NSEC3PARAMRecord nsec3param = null;
|
||||||
|
HashMap<String, String> map = new HashMap<>();
|
||||||
|
base32 b32 = new base32(base32.Alphabet.BASE32HEX, false, true);
|
||||||
|
Name zonename = null;
|
||||||
|
|
||||||
|
for (Record r : zone) {
|
||||||
|
if (r.getType() == Type.SOA) {
|
||||||
|
zonename = r.getName();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.getType() == Type.NSEC3PARAM) {
|
||||||
|
nsec3param = (NSEC3PARAMRecord) r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we couldn't determine a zone name, we have an issue.
|
||||||
|
if (zonename == null || nsec3param == null) {
|
||||||
|
formatZone(zone);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next pass, calculate a mapping between ownernames and hashnames
|
||||||
|
Name lastName = null;
|
||||||
|
for (Record r : zone) {
|
||||||
|
if (r.getName().equals(lastName))
|
||||||
|
continue;
|
||||||
|
if (r.getType() == Type.NSEC3)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Name n = r.getName();
|
||||||
|
byte[] hash = nsec3param.hashName(n);
|
||||||
|
String hashname = b32.toString(hash);
|
||||||
|
map.put(hashname, n.toString().toLowerCase());
|
||||||
|
lastName = n;
|
||||||
|
|
||||||
|
// inefficiently create hashes for the possible ancestor ENTs
|
||||||
|
for (int i = zonename.labels() + 1; i < n.labels(); ++i) {
|
||||||
|
Name parent = new Name(n, n.labels() - i);
|
||||||
|
byte[] parentHash = nsec3param.hashName(parent);
|
||||||
|
String parentHashName = b32.toString(parentHash);
|
||||||
|
if (!map.containsKey(parentHashName)) {
|
||||||
|
map.put(parentHashName, parent.toString().toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final pass, output the zone with added comments for the NSEC3 records
|
||||||
|
for (Record r : zone) {
|
||||||
|
if (r.getType() != Type.NSEC3) {
|
||||||
|
System.out.println(r.toString());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSEC3Record nsec3 = (NSEC3Record) r;
|
||||||
|
String hashname = nsec3.getName().getLabelString(0).toLowerCase();
|
||||||
|
String ownername = map.get(hashname);
|
||||||
|
System.out.println(r.toString() + " ; " + ownername);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() throws IOException, NoSuchAlgorithmException {
|
||||||
|
List<Record> z = readZoneFile(file);
|
||||||
|
// Put the zone into a consistent (name and RR type) order.
|
||||||
|
Collections.sort(z, new RecordComparator());
|
||||||
|
|
||||||
|
if (assignNSEC3) {
|
||||||
|
determineNSEC3Owners(z);
|
||||||
|
} else {
|
||||||
|
formatZone(z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
ZoneFormat tool = new ZoneFormat("zoneformat", "jdnssec-zoneformat [..options..] zonefile");
|
||||||
|
|
||||||
|
tool.run(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
// $Id$
|
// Copyright (C) 2001-2003, 2022 VeriSign, Inc.
|
||||||
//
|
|
||||||
// Copyright (C) 2001-2003 VeriSign, Inc.
|
|
||||||
//
|
//
|
||||||
// This library is free software; you can redistribute it and/or
|
// This library is free software; you can redistribute it and/or
|
||||||
// modify it under the terms of the GNU Lesser General Public
|
// modify it under the terms of the GNU Lesser General Public
|
||||||
@@ -28,7 +26,6 @@ import java.io.PrintWriter;
|
|||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.text.NumberFormat;
|
|
||||||
|
|
||||||
import org.xbill.DNS.DNSKEYRecord;
|
import org.xbill.DNS.DNSKEYRecord;
|
||||||
import org.xbill.DNS.Master;
|
import org.xbill.DNS.Master;
|
||||||
@@ -45,92 +42,61 @@ import org.xbill.DNS.utils.base64;
|
|||||||
* In this class, the "base" key path or name is the file name without the
|
* In this class, the "base" key path or name is the file name without the
|
||||||
* trailing ".key" or ".private".
|
* trailing ".key" or ".private".
|
||||||
*
|
*
|
||||||
* @author David Blacka (original)
|
* @author David Blacka
|
||||||
* @author $Author$
|
|
||||||
* @version $Revision$
|
|
||||||
*/
|
*/
|
||||||
public class BINDKeyUtils
|
public class BINDKeyUtils {
|
||||||
{
|
|
||||||
// formatters used to generated the BIND key file names
|
private BINDKeyUtils() {
|
||||||
private static NumberFormat mAlgNumberFormatter;
|
}
|
||||||
private static NumberFormat mKeyIdNumberFormatter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the BIND9 key file base name (i.e., without the ".key" or
|
* Calculate the BIND9 key file base name (i.e., without the ".key" or
|
||||||
* ".private" extensions)
|
* ".private" extensions)
|
||||||
*/
|
*/
|
||||||
private static String getKeyFileBase(Name signer, int algorithm, int keyid)
|
private static String getKeyFileBase(Name signer, int algorithm, int keyid) {
|
||||||
{
|
|
||||||
if (mAlgNumberFormatter == null)
|
|
||||||
{
|
|
||||||
mAlgNumberFormatter = NumberFormat.getNumberInstance();
|
|
||||||
mAlgNumberFormatter.setMaximumIntegerDigits(3);
|
|
||||||
mAlgNumberFormatter.setMinimumIntegerDigits(3);
|
|
||||||
}
|
|
||||||
if (mKeyIdNumberFormatter == null)
|
|
||||||
{
|
|
||||||
mKeyIdNumberFormatter = NumberFormat.getNumberInstance();
|
|
||||||
mKeyIdNumberFormatter.setMaximumIntegerDigits(5);
|
|
||||||
mKeyIdNumberFormatter.setMinimumIntegerDigits(5);
|
|
||||||
mKeyIdNumberFormatter.setGroupingUsed(false);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
keyid &= 0xFFFF;
|
keyid &= 0xFFFF;
|
||||||
|
return String.format("K%1$s+%2$03d+%3$05d", signer, algorithm, keyid);
|
||||||
String fn = "K" + signer + "+" + mAlgNumberFormatter.format(algorithm)
|
|
||||||
+ "+" + mKeyIdNumberFormatter.format(keyid);
|
|
||||||
|
|
||||||
return fn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Reads in the DNSKEYRecord from the public key file */
|
/** Reads in the DNSKEYRecord from the public key file */
|
||||||
private static DNSKEYRecord loadPublicKeyFile(File publicKeyFile)
|
private static DNSKEYRecord loadPublicKeyFile(File publicKeyFile)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
try (Master m = new Master(publicKeyFile.getAbsolutePath(), null, 600)) {
|
||||||
Master m = new Master(publicKeyFile.getAbsolutePath(), null, 600);
|
Record r;
|
||||||
|
DNSKEYRecord result = null;
|
||||||
|
|
||||||
Record r;
|
while ((r = m.nextRecord()) != null) {
|
||||||
DNSKEYRecord result = null;
|
if (r.getType() == Type.DNSKEY) {
|
||||||
|
result = (DNSKEYRecord) r;
|
||||||
while ((r = m.nextRecord()) != null)
|
}
|
||||||
{
|
|
||||||
if (r.getType() == Type.DNSKEY)
|
|
||||||
{
|
|
||||||
result = (DNSKEYRecord) r;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Reads in the private key verbatim from the private key file */
|
/** Reads in the private key verbatim from the private key file */
|
||||||
private static String loadPrivateKeyFile(File privateKeyFile)
|
private static String loadPrivateKeyFile(File privateKeyFile)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
try (BufferedReader in = new BufferedReader(new FileReader(privateKeyFile))) {
|
||||||
BufferedReader in = new BufferedReader(new FileReader(privateKeyFile));
|
StringBuilder keybuf = new StringBuilder();
|
||||||
StringBuffer key_buf = new StringBuffer();
|
String line;
|
||||||
|
|
||||||
String line;
|
while ((line = in.readLine()) != null) {
|
||||||
|
keybuf.append(line);
|
||||||
while ((line = in.readLine()) != null)
|
keybuf.append('\n');
|
||||||
{
|
}
|
||||||
key_buf.append(line);
|
return keybuf.toString().trim();
|
||||||
key_buf.append('\n');
|
|
||||||
}
|
}
|
||||||
in.close();
|
|
||||||
|
|
||||||
return key_buf.toString().trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an actual path for one of the key files, return the base name
|
* Given an actual path for one of the key files, return the base name
|
||||||
*/
|
*/
|
||||||
private static String fixKeyFileBasePath(String basePath)
|
private static String fixKeyFileBasePath(String basePath) {
|
||||||
{
|
if (basePath == null)
|
||||||
if (basePath == null) throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
if (basePath.endsWith(".key") || basePath.endsWith(".private"))
|
if (basePath.endsWith(".key") || basePath.endsWith(".private")) {
|
||||||
{
|
|
||||||
basePath = basePath.substring(0, basePath.lastIndexOf("."));
|
basePath = basePath.substring(0, basePath.lastIndexOf("."));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,21 +107,15 @@ public class BINDKeyUtils
|
|||||||
* Given the information necessary to construct the path to a BIND9 generated
|
* Given the information necessary to construct the path to a BIND9 generated
|
||||||
* key pair, load the key pair.
|
* key pair, load the key pair.
|
||||||
*
|
*
|
||||||
* @param signer
|
* @param signer the DNS name of the key.
|
||||||
* the DNS name of the key.
|
* @param algorithm the DNSSEC algorithm of the key.
|
||||||
* @param algorithm
|
* @param keyid the DNSSEC key footprint.
|
||||||
* the DNSSEC algorithm of the key.
|
* @param inDirectory the directory to look for the files (may be null).
|
||||||
* @param keyid
|
|
||||||
* the DNSSEC key footprint.
|
|
||||||
* @param inDirectory
|
|
||||||
* the directory to look for the files (may be null).
|
|
||||||
* @return the loaded key pair.
|
* @return the loaded key pair.
|
||||||
* @throws IOException
|
* @throws IOException if there was a problem reading the BIND9 files.
|
||||||
* if there was a problem reading the BIND9 files.
|
|
||||||
*/
|
*/
|
||||||
public static DnsKeyPair loadKeyPair(Name signer, int algorithm, int keyid,
|
public static DnsKeyPair loadKeyPair(Name signer, int algorithm, int keyid,
|
||||||
File inDirectory) throws IOException
|
File inDirectory) throws IOException {
|
||||||
{
|
|
||||||
String keyFileBase = getKeyFileBase(signer, algorithm, keyid);
|
String keyFileBase = getKeyFileBase(signer, algorithm, keyid);
|
||||||
|
|
||||||
return loadKeyPair(keyFileBase, inDirectory);
|
return loadKeyPair(keyFileBase, inDirectory);
|
||||||
@@ -164,21 +124,17 @@ public class BINDKeyUtils
|
|||||||
/**
|
/**
|
||||||
* Given a base path to a BIND9 key pair, load the key pair.
|
* Given a base path to a BIND9 key pair, load the key pair.
|
||||||
*
|
*
|
||||||
* @param keyFileBasePath
|
* @param keyFileBasePath the base filename (or real filename for either the
|
||||||
* the base filename (or real filename for either the public or
|
* public or private key) of the key.
|
||||||
* private key) of the key.
|
* @param inDirectory the directory to look in, if the keyFileBasePath is
|
||||||
* @param inDirectory
|
* relative.
|
||||||
* the directory to look in, if the keyFileBasePath is relative.
|
|
||||||
* @return the loaded key pair.
|
* @return the loaded key pair.
|
||||||
* @throws IOException
|
* @throws IOException if there was a problem reading the files
|
||||||
* if there was a problem reading the files
|
|
||||||
*/
|
*/
|
||||||
public static DnsKeyPair loadKeyPair(String keyFileBasePath, File inDirectory)
|
public static DnsKeyPair loadKeyPair(String keyFileBasePath, File inDirectory)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
|
||||||
keyFileBasePath = fixKeyFileBasePath(keyFileBasePath);
|
keyFileBasePath = fixKeyFileBasePath(keyFileBasePath);
|
||||||
// FIXME: should we throw the IOException when one of the files
|
|
||||||
// cannot be found, or just when both cannot be found?
|
|
||||||
File publicKeyFile = new File(inDirectory, keyFileBasePath + ".key");
|
File publicKeyFile = new File(inDirectory, keyFileBasePath + ".key");
|
||||||
File privateKeyFile = new File(inDirectory, keyFileBasePath + ".private");
|
File privateKeyFile = new File(inDirectory, keyFileBasePath + ".private");
|
||||||
|
|
||||||
@@ -197,17 +153,15 @@ public class BINDKeyUtils
|
|||||||
* Given a base path to a BIND9 key pair, load the public part (only) of the
|
* Given a base path to a BIND9 key pair, load the public part (only) of the
|
||||||
* key pair
|
* key pair
|
||||||
*
|
*
|
||||||
* @param keyFileBasePath
|
* @param keyFileBasePath the base or real path to the public part of a key
|
||||||
* the base or real path to the public part of a key pair.
|
* pair.
|
||||||
* @param inDirectory
|
* @param inDirectory the directory to look in if the path is relative
|
||||||
* the directory to look in if the path is relative (may be null).
|
* (may be null).
|
||||||
* @return a {@link DnsKeyPair} containing just the public key information.
|
* @return a {@link DnsKeyPair} containing just the public key information.
|
||||||
* @throws IOException
|
* @throws IOException if there was a problem reading the public key file.
|
||||||
* if there was a problem reading the public key file.
|
|
||||||
*/
|
*/
|
||||||
public static DnsKeyPair loadKey(String keyFileBasePath, File inDirectory)
|
public static DnsKeyPair loadKey(String keyFileBasePath, File inDirectory)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
|
||||||
keyFileBasePath = fixKeyFileBasePath(keyFileBasePath);
|
keyFileBasePath = fixKeyFileBasePath(keyFileBasePath);
|
||||||
File publicKeyFile = new File(inDirectory, keyFileBasePath + ".key");
|
File publicKeyFile = new File(inDirectory, keyFileBasePath + ".key");
|
||||||
|
|
||||||
@@ -224,56 +178,51 @@ public class BINDKeyUtils
|
|||||||
* "keyset-[signer]." where [signer] is the DNS owner name of the key. The
|
* "keyset-[signer]." where [signer] is the DNS owner name of the key. The
|
||||||
* keyset may be signed, but doesn't have to be.
|
* keyset may be signed, but doesn't have to be.
|
||||||
*
|
*
|
||||||
* @param keysetFileName
|
* @param keysetFileName the name of the keyset file.
|
||||||
* the name of the keyset file.
|
* @param inDirectory the directory to look in if the path is relative (may
|
||||||
* @param inDirectory
|
* be null, defaults to the current working directory).
|
||||||
* the directory to look in if the path is relative (may be null,
|
|
||||||
* defaults to the current working directory).
|
|
||||||
* @return a RRset contain the KEY records and any associated SIG records.
|
* @return a RRset contain the KEY records and any associated SIG records.
|
||||||
* @throws IOException
|
* @throws IOException if there was a problem reading the keyset file.
|
||||||
* if there was a problem reading the keyset file.
|
|
||||||
*/
|
*/
|
||||||
public static RRset loadKeySet(String keysetFileName, File inDirectory)
|
public static RRset loadKeySet(String keysetFileName, File inDirectory)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
|
||||||
File keysetFile = new File(inDirectory, keysetFileName);
|
File keysetFile = new File(inDirectory, keysetFileName);
|
||||||
|
|
||||||
Master m = new Master(keysetFile.getAbsolutePath());
|
try (Master m = new Master(keysetFile.getAbsolutePath())) {
|
||||||
|
Record r;
|
||||||
|
RRset keyset = new RRset();
|
||||||
|
while ((r = m.nextRecord()) != null) {
|
||||||
|
keyset.addRR(r);
|
||||||
|
}
|
||||||
|
|
||||||
Record r;
|
return keyset;
|
||||||
RRset keyset = new RRset();
|
|
||||||
while ((r = m.nextRecord()) != null)
|
|
||||||
{
|
|
||||||
keyset.addRR(r);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the key file base for this key pair.
|
* Calculate the key file base for this key pair.
|
||||||
*
|
*
|
||||||
* @param pair
|
* @param pair the {@link DnsKeyPair} to work from. It only needs a public
|
||||||
* the {@link DnsKeyPair} to work from. It only needs a public key.
|
* key.
|
||||||
* @return the base name of the key files.
|
* @return the base name of the key files.
|
||||||
*/
|
*/
|
||||||
public static String keyFileBase(DnsKeyPair pair)
|
public static String keyFileBase(DnsKeyPair pair) {
|
||||||
{
|
|
||||||
DNSKEYRecord keyrec = pair.getDNSKEYRecord();
|
DNSKEYRecord keyrec = pair.getDNSKEYRecord();
|
||||||
if (keyrec == null) return null;
|
if (keyrec == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
return getKeyFileBase(keyrec.getName(), keyrec.getAlgorithm(),
|
return getKeyFileBase(keyrec.getName(), keyrec.getAlgorithm(),
|
||||||
keyrec.getFootprint());
|
keyrec.getFootprint());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a {@link java.io.File} object representing the BIND9 public key
|
* @return a {@link java.io.File} object representing the BIND9 public key
|
||||||
* file.
|
* file.
|
||||||
*/
|
*/
|
||||||
public static File getPublicKeyFile(DnsKeyPair pair, File inDirectory)
|
public static File getPublicKeyFile(DnsKeyPair pair, File inDirectory) {
|
||||||
{
|
|
||||||
String keyfilebase = keyFileBase(pair);
|
String keyfilebase = keyFileBase(pair);
|
||||||
if (keyfilebase == null) return null;
|
if (keyfilebase == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
return new File(inDirectory, keyfilebase + ".key");
|
return new File(inDirectory, keyfilebase + ".key");
|
||||||
}
|
}
|
||||||
@@ -282,10 +231,10 @@ public class BINDKeyUtils
|
|||||||
* @return a {@link java.io.File} object representing the BIND9 private key
|
* @return a {@link java.io.File} object representing the BIND9 private key
|
||||||
* file
|
* file
|
||||||
*/
|
*/
|
||||||
public static File getPrivateKeyFile(DnsKeyPair pair, File inDirectory)
|
public static File getPrivateKeyFile(DnsKeyPair pair, File inDirectory) {
|
||||||
{
|
|
||||||
String keyfilebase = keyFileBase(pair);
|
String keyfilebase = keyFileBase(pair);
|
||||||
if (keyfilebase == null) return null;
|
if (keyfilebase == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
return new File(inDirectory, keyfilebase + ".private");
|
return new File(inDirectory, keyfilebase + ".private");
|
||||||
}
|
}
|
||||||
@@ -294,27 +243,17 @@ public class BINDKeyUtils
|
|||||||
* Given a the contents of a BIND9 private key file, convert it into a native
|
* Given a the contents of a BIND9 private key file, convert it into a native
|
||||||
* {@link java.security.PrivateKey} object.
|
* {@link java.security.PrivateKey} object.
|
||||||
*
|
*
|
||||||
* @param privateKeyString
|
* @param privateKeyString the contents of a BIND9 key file in string form.
|
||||||
* the contents of a BIND9 key file in string form.
|
|
||||||
* @return a {@link java.security.PrivateKey}
|
* @return a {@link java.security.PrivateKey}
|
||||||
*/
|
*/
|
||||||
public static PrivateKey convertPrivateKeyString(String privateKeyString)
|
public static PrivateKey convertPrivateKeyString(String privateKeyString) {
|
||||||
{
|
if (privateKeyString == null)
|
||||||
if (privateKeyString == null) return null;
|
return null;
|
||||||
|
|
||||||
// FIXME: should this swallow exceptions or actually propagate
|
try {
|
||||||
// them?
|
|
||||||
try
|
|
||||||
{
|
|
||||||
DnsKeyConverter conv = new DnsKeyConverter();
|
DnsKeyConverter conv = new DnsKeyConverter();
|
||||||
return conv.parsePrivateKeyString(privateKeyString);
|
return conv.parsePrivateKeyString(privateKeyString);
|
||||||
}
|
} catch (IOException | NoSuchAlgorithmException e) {
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
catch (NoSuchAlgorithmException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,21 +264,15 @@ public class BINDKeyUtils
|
|||||||
* Given a native private key, convert it into a BIND9 private key file
|
* Given a native private key, convert it into a BIND9 private key file
|
||||||
* format.
|
* format.
|
||||||
*
|
*
|
||||||
* @param priv
|
* @param priv the private key to convert.
|
||||||
* the private key to convert.
|
* @param pub the private key's corresponding public key. Some algorithms
|
||||||
* @param pub
|
* require information from both.
|
||||||
* the private key's corresponding public key. Some algorithms
|
|
||||||
* require information from both.
|
|
||||||
* @return a string containing the contents of a BIND9 private key file.
|
* @return a string containing the contents of a BIND9 private key file.
|
||||||
*/
|
*/
|
||||||
public static String convertPrivateKey(PrivateKey priv, PublicKey pub, int alg)
|
public static String convertPrivateKey(PrivateKey priv, PublicKey pub, int alg) {
|
||||||
{
|
if (priv != null) {
|
||||||
if (priv != null)
|
|
||||||
{
|
|
||||||
DnsKeyConverter keyconv = new DnsKeyConverter();
|
DnsKeyConverter keyconv = new DnsKeyConverter();
|
||||||
String priv_string = keyconv.generatePrivateKeyString(priv, pub, alg);
|
return keyconv.generatePrivateKeyString(priv, pub, alg);
|
||||||
|
|
||||||
return priv_string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -350,11 +283,14 @@ public class BINDKeyUtils
|
|||||||
* routines need. Currently, the DNSJAVA package uses a multiline mode for its
|
* routines need. Currently, the DNSJAVA package uses a multiline mode for its
|
||||||
* record formatting. The BIND9 tools require everything on a single line.
|
* record formatting. The BIND9 tools require everything on a single line.
|
||||||
*/
|
*/
|
||||||
private static String DNSKEYtoString(DNSKEYRecord rec)
|
private static String DNSKEYtoString(DNSKEYRecord rec) {
|
||||||
{
|
StringBuilder buf = new StringBuilder();
|
||||||
StringBuffer buf = new StringBuffer();
|
|
||||||
|
|
||||||
buf.append(rec.getName());
|
buf.append(rec.getName());
|
||||||
|
if (rec.getTTL() > 0) {
|
||||||
|
buf.append(" ");
|
||||||
|
buf.append(rec.getTTL());
|
||||||
|
}
|
||||||
buf.append(" IN DNSKEY ");
|
buf.append(" IN DNSKEY ");
|
||||||
buf.append(rec.getFlags() & 0xFFFF);
|
buf.append(rec.getFlags() & 0xFFFF);
|
||||||
buf.append(" ");
|
buf.append(" ");
|
||||||
@@ -370,29 +306,24 @@ public class BINDKeyUtils
|
|||||||
/**
|
/**
|
||||||
* This routine will write out the BIND9 dnssec-* tool compatible files.
|
* This routine will write out the BIND9 dnssec-* tool compatible files.
|
||||||
*
|
*
|
||||||
* @param baseFileName
|
* @param baseFileName use this base file name. If null, the standard BIND9
|
||||||
* use this base file name. If null, the standard BIND9 base file
|
* base file name will be computed.
|
||||||
* name will be computed.
|
* @param pair the keypair in question.
|
||||||
* @param pair
|
* @param inDirectory the directory to write to (may be null).
|
||||||
* the keypair in question.
|
* @throws IOException if there is a problem writing the files.
|
||||||
* @param inDirectory
|
|
||||||
* the directory to write to (may be null).
|
|
||||||
* @throws IOException
|
|
||||||
* if there is a problem writing the files.
|
|
||||||
*/
|
*/
|
||||||
public static void writeKeyFiles(String baseFileName, DnsKeyPair pair,
|
public static void writeKeyFiles(String baseFileName, DnsKeyPair pair,
|
||||||
File inDirectory) throws IOException
|
File inDirectory) throws IOException {
|
||||||
{
|
|
||||||
DNSKEYRecord pub = pair.getDNSKEYRecord();
|
DNSKEYRecord pub = pair.getDNSKEYRecord();
|
||||||
String priv = pair.getPrivateKeyString();
|
String priv = pair.getPrivateKeyString();
|
||||||
|
|
||||||
if (priv == null)
|
if (priv == null) {
|
||||||
{
|
|
||||||
priv = convertPrivateKey(pair.getPrivate(), pair.getPublic(),
|
priv = convertPrivateKey(pair.getPrivate(), pair.getPublic(),
|
||||||
pair.getDNSKEYAlgorithm());
|
pair.getDNSKEYAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pub == null || priv == null) return;
|
if (pub == null || priv == null)
|
||||||
|
return;
|
||||||
|
|
||||||
// Write the public key file
|
// Write the public key file
|
||||||
File pubkeyfile = new File(inDirectory, baseFileName + ".key");
|
File pubkeyfile = new File(inDirectory, baseFileName + ".key");
|
||||||
@@ -412,14 +343,11 @@ public class BINDKeyUtils
|
|||||||
* This routine will write out the BIND9 dnssec-* tool compatible files to the
|
* This routine will write out the BIND9 dnssec-* tool compatible files to the
|
||||||
* standard file names.
|
* standard file names.
|
||||||
*
|
*
|
||||||
* @param pair
|
* @param pair the key pair in question.
|
||||||
* the key pair in question.
|
* @param inDirectory the directory to write to (may be null).
|
||||||
* @param inDirectory
|
|
||||||
* the directory to write to (may be null).
|
|
||||||
*/
|
*/
|
||||||
public static void writeKeyFiles(DnsKeyPair pair, File inDirectory)
|
public static void writeKeyFiles(DnsKeyPair pair, File inDirectory)
|
||||||
throws IOException
|
throws IOException {
|
||||||
{
|
|
||||||
String base = keyFileBase(pair);
|
String base = keyFileBase(pair);
|
||||||
writeKeyFiles(base, pair, inDirectory);
|
writeKeyFiles(base, pair, inDirectory);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
// $Id$
|
// Copyright (C) 2001-2003, 2022 VeriSign, Inc.
|
||||||
//
|
|
||||||
// Copyright (C) 2001-2003 VeriSign, Inc.
|
|
||||||
//
|
//
|
||||||
// This library is free software; you can redistribute it and/or
|
// This library is free software; you can redistribute it and/or
|
||||||
// modify it under the terms of the GNU Lesser General Public
|
// modify it under the terms of the GNU Lesser General Public
|
||||||
@@ -26,34 +24,25 @@ import java.util.logging.Logger;
|
|||||||
* useful for comparing RDATA portions of DNS records in doing DNSSEC canonical
|
* useful for comparing RDATA portions of DNS records in doing DNSSEC canonical
|
||||||
* ordering.
|
* ordering.
|
||||||
*
|
*
|
||||||
* @author David Blacka (original)
|
* @author David Blacka
|
||||||
* @author $Author$
|
|
||||||
* @version $Revision$
|
|
||||||
*/
|
*/
|
||||||
public class ByteArrayComparator implements Comparator<byte[]>
|
public class ByteArrayComparator implements Comparator<byte[]> {
|
||||||
{
|
private int mOffset = 0;
|
||||||
private int mOffset = 0;
|
private boolean mDebug = false;
|
||||||
private boolean mDebug = false;
|
|
||||||
private Logger log;
|
private Logger log;
|
||||||
|
|
||||||
public ByteArrayComparator()
|
public ByteArrayComparator() {
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteArrayComparator(int offset, boolean debug)
|
public ByteArrayComparator(int offset, boolean debug) {
|
||||||
{
|
|
||||||
mOffset = offset;
|
mOffset = offset;
|
||||||
mDebug = debug;
|
mDebug = debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int compare(byte[] b1, byte[] b2)
|
public int compare(byte[] b1, byte[] b2) {
|
||||||
{
|
for (int i = mOffset; i < b1.length && i < b2.length; i++) {
|
||||||
for (int i = mOffset; i < b1.length && i < b2.length; i++)
|
if (b1[i] != b2[i]) {
|
||||||
{
|
if (mDebug) {
|
||||||
if (b1[i] != b2[i])
|
|
||||||
{
|
|
||||||
if (mDebug)
|
|
||||||
{
|
|
||||||
log.info("offset " + i + " differs (this is "
|
log.info("offset " + i + " differs (this is "
|
||||||
+ (i - mOffset) + " bytes in from our offset.)");
|
+ (i - mOffset) + " bytes in from our offset.)");
|
||||||
}
|
}
|
||||||
167
src/main/java/com/verisignlabs/dnssec/security/DSAlgorithm.java
Normal file
167
src/main/java/com/verisignlabs/dnssec/security/DSAlgorithm.java
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2006, 2022 Verisign. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer. 2. 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. 3. The name of the author may not
|
||||||
|
* be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.verisignlabs.dnssec.security;
|
||||||
|
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.Security;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.xbill.DNS.CDSRecord;
|
||||||
|
import org.xbill.DNS.DLVRecord;
|
||||||
|
import org.xbill.DNS.DNSKEYRecord;
|
||||||
|
import org.xbill.DNS.DNSOutput;
|
||||||
|
import org.xbill.DNS.DNSSEC;
|
||||||
|
import org.xbill.DNS.DSRecord;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles the implementation behind converting DNSKEYs into
|
||||||
|
* DSRecords. It primarily exists to bootstrap whatever crypto libraries we
|
||||||
|
* might need to do so.
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
public class DSAlgorithm {
|
||||||
|
|
||||||
|
private Logger log = Logger.getLogger(this.getClass().toString());
|
||||||
|
|
||||||
|
HashSet<Integer> mSupportedAlgorithms = null;
|
||||||
|
|
||||||
|
private static DSAlgorithm mInstance = null;
|
||||||
|
|
||||||
|
public DSAlgorithm() {
|
||||||
|
mSupportedAlgorithms = new HashSet<>();
|
||||||
|
mSupportedAlgorithms.add(DNSSEC.Digest.SHA1);
|
||||||
|
mSupportedAlgorithms.add(DNSSEC.Digest.SHA256);
|
||||||
|
mSupportedAlgorithms.add(DNSSEC.Digest.SHA384);
|
||||||
|
// Attempt to add the bouncycastle provider. This is so we can use this
|
||||||
|
// provider if it is available, but not require the user to add it as one of
|
||||||
|
// the java.security providers.
|
||||||
|
try {
|
||||||
|
Class<?> bcProviderClass = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
|
||||||
|
Provider bcProvider = (Provider) bcProviderClass.getDeclaredConstructor().newInstance();
|
||||||
|
Security.addProvider(bcProvider);
|
||||||
|
log.fine("bouncycastle crypto provider loaded");
|
||||||
|
mSupportedAlgorithms.add(DNSSEC.Digest.GOST3411);
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
// do nothing, this is the normal case
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] supportedAlgorithmMnemonics() {
|
||||||
|
ArrayList<String> algs = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int digestId : mSupportedAlgorithms) {
|
||||||
|
algs.add(DNSSEC.Digest.string(digestId));
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] result = new String[algs.size()];
|
||||||
|
return algs.toArray(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a DNSKEY record, generate the DS record from it.
|
||||||
|
*
|
||||||
|
* @param keyrec the KEY record in question.
|
||||||
|
* @param digestAlg The digest algorithm (SHA-1, SHA-256, etc.).
|
||||||
|
* @param ttl the desired TTL for the generated DS record. If zero, or
|
||||||
|
* negative, the original KEY RR's TTL will be used.
|
||||||
|
* @return the corresponding {@link org.xbill.DNS.DSRecord}
|
||||||
|
*/
|
||||||
|
public DSRecord calculateDSRecord(DNSKEYRecord keyrec, int digestAlg, long ttl) {
|
||||||
|
if (keyrec == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (ttl <= 0)
|
||||||
|
ttl = keyrec.getTTL();
|
||||||
|
|
||||||
|
DNSOutput os = new DNSOutput();
|
||||||
|
|
||||||
|
os.writeByteArray(keyrec.getName().toWireCanonical());
|
||||||
|
os.writeByteArray(keyrec.rdataToWireCanonical());
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] digest;
|
||||||
|
MessageDigest md;
|
||||||
|
|
||||||
|
switch (digestAlg) {
|
||||||
|
case DNSSEC.Digest.SHA1:
|
||||||
|
md = MessageDigest.getInstance("SHA");
|
||||||
|
digest = md.digest(os.toByteArray());
|
||||||
|
break;
|
||||||
|
case DNSSEC.Digest.SHA256:
|
||||||
|
md = MessageDigest.getInstance("SHA-256");
|
||||||
|
digest = md.digest(os.toByteArray());
|
||||||
|
break;
|
||||||
|
case DNSSEC.Digest.GOST3411:
|
||||||
|
// The standard Java crypto providers don't have this, but bouncycastle does
|
||||||
|
if (java.security.Security.getProviders("MessageDigest.GOST3411") != null) {
|
||||||
|
md = MessageDigest.getInstance("GOST3411");
|
||||||
|
digest = md.digest(os.toByteArray());
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unsupported digest id: " + digestAlg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DNSSEC.Digest.SHA384:
|
||||||
|
md = MessageDigest.getInstance("SHA-384");
|
||||||
|
digest = md.digest(os.toByteArray());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown digest id: " + digestAlg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DSRecord(keyrec.getName(), keyrec.getDClass(), ttl,
|
||||||
|
keyrec.getFootprint(), keyrec.getAlgorithm(), digestAlg,
|
||||||
|
digest);
|
||||||
|
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
log.severe(e.toString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DLVRecord dsToDLV(DSRecord ds) {
|
||||||
|
return new DLVRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
|
||||||
|
ds.getDigestID(), ds.getDigest());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CDSRecord dstoCDS(DSRecord ds) {
|
||||||
|
return new CDSRecord(ds.getName(), ds.getDClass(), ds.getTTL(), ds.getFootprint(), ds.getAlgorithm(),
|
||||||
|
ds.getDClass(), ds.getDigest());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DSAlgorithm getInstance() {
|
||||||
|
if (mInstance == null) {
|
||||||
|
mInstance = new DSAlgorithm();
|
||||||
|
}
|
||||||
|
return mInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,526 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2006, 2022 Verisign. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer. 2. 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. 3. The name of the author may not
|
||||||
|
* be used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.verisignlabs.dnssec.security;
|
||||||
|
|
||||||
|
import java.security.AlgorithmParameters;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.security.spec.ECGenParameterSpec;
|
||||||
|
import java.security.spec.ECParameterSpec;
|
||||||
|
import java.security.spec.InvalidParameterSpecException;
|
||||||
|
import java.security.spec.NamedParameterSpec;
|
||||||
|
import java.security.spec.RSAKeyGenParameterSpec;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.xbill.DNS.DNSSEC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles translating DNS signing algorithm identifiers into various
|
||||||
|
* usable java implementations.
|
||||||
|
*
|
||||||
|
* Besides centralizing the logic surrounding matching a DNSKEY algorithm
|
||||||
|
* identifier with various crypto implementations, it also handles algorithm
|
||||||
|
* aliasing -- that is, defining a new algorithm identifier to be equivalent to
|
||||||
|
* an existing identifier.
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
public class DnsKeyAlgorithm {
|
||||||
|
|
||||||
|
// Our base algorithm numbers. This is a normalization of the DNSSEC
|
||||||
|
// algorithms (which are really signature algorithms). Thus RSASHA1,
|
||||||
|
// RSASHA256, etc. all boil down to 'RSA' here. Similarly, ECDSAP256SHA256 and
|
||||||
|
// ECDSAP384SHA384 both become 'ECDSA'.
|
||||||
|
public enum BaseAlgorithm {
|
||||||
|
UNKNOWN,
|
||||||
|
RSA,
|
||||||
|
DH,
|
||||||
|
DSA,
|
||||||
|
ECDSA,
|
||||||
|
EDDSA;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AlgEntry {
|
||||||
|
public int dnssecAlgorithm;
|
||||||
|
public String sigName;
|
||||||
|
public BaseAlgorithm baseType;
|
||||||
|
|
||||||
|
public AlgEntry(int algorithm, String sigName, BaseAlgorithm baseType) {
|
||||||
|
this.dnssecAlgorithm = algorithm;
|
||||||
|
this.sigName = sigName;
|
||||||
|
this.baseType = baseType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ECAlgEntry extends AlgEntry {
|
||||||
|
public ECParameterSpec ecSpec;
|
||||||
|
|
||||||
|
public ECAlgEntry(int algorithm, String sigName, BaseAlgorithm baseType, ECParameterSpec spec) {
|
||||||
|
super(algorithm, sigName, baseType);
|
||||||
|
this.ecSpec = spec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class EdAlgEntry extends AlgEntry {
|
||||||
|
public String curveName;
|
||||||
|
public NamedParameterSpec paramSpec;
|
||||||
|
|
||||||
|
public EdAlgEntry(int algorithm, String sigName, BaseAlgorithm baseType, String curveName) {
|
||||||
|
super(algorithm, sigName, baseType);
|
||||||
|
this.curveName = curveName;
|
||||||
|
this.paramSpec = new NamedParameterSpec(curveName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a mapping of algorithm identifier to Entry. The Entry contains the
|
||||||
|
* data needed to map the algorithm to the various crypto implementations.
|
||||||
|
*/
|
||||||
|
private HashMap<Integer, AlgEntry> mAlgorithmMap;
|
||||||
|
/**
|
||||||
|
* This is a mapping of algorithm mnemonics to algorithm identifiers.
|
||||||
|
*/
|
||||||
|
private HashMap<String, Integer> mMnemonicToIdMap;
|
||||||
|
/**
|
||||||
|
* This is a mapping of identifiers to preferred mnemonic -- the preferred one
|
||||||
|
* is the first defined one
|
||||||
|
*/
|
||||||
|
private HashMap<Integer, String> mIdToMnemonicMap;
|
||||||
|
|
||||||
|
/** This is a cached key pair generator for RSA keys. */
|
||||||
|
private KeyPairGenerator mRSAKeyGenerator;
|
||||||
|
/** This is a cached key pair generator for DSA keys. */
|
||||||
|
private KeyPairGenerator mDSAKeyGenerator;
|
||||||
|
/** This is a cached key pair generator for ECDSA_P256 keys. */
|
||||||
|
private KeyPairGenerator mECKeyGenerator;
|
||||||
|
|
||||||
|
private Logger log = Logger.getLogger(this.getClass().toString());
|
||||||
|
|
||||||
|
/** This is the global instance for this class. */
|
||||||
|
private static DnsKeyAlgorithm mInstance = null;
|
||||||
|
|
||||||
|
public DnsKeyAlgorithm() {
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initialize() {
|
||||||
|
mAlgorithmMap = new HashMap<>();
|
||||||
|
mMnemonicToIdMap = new HashMap<>();
|
||||||
|
mIdToMnemonicMap = new HashMap<>();
|
||||||
|
|
||||||
|
// Load the standard DNSSEC algorithms.
|
||||||
|
addAlgorithm(DNSSEC.Algorithm.RSAMD5, "MD5withRSA", BaseAlgorithm.RSA);
|
||||||
|
addMnemonic("RSAMD5", DNSSEC.Algorithm.RSAMD5);
|
||||||
|
|
||||||
|
addAlgorithm(DNSSEC.Algorithm.DH, "", BaseAlgorithm.DH);
|
||||||
|
addMnemonic("DH", DNSSEC.Algorithm.DH);
|
||||||
|
|
||||||
|
addAlgorithm(DNSSEC.Algorithm.DSA, "SHA1withDSA", BaseAlgorithm.DSA);
|
||||||
|
addMnemonic("DSA", DNSSEC.Algorithm.DSA);
|
||||||
|
|
||||||
|
addAlgorithm(DNSSEC.Algorithm.RSASHA1, "SHA1withRSA", BaseAlgorithm.RSA);
|
||||||
|
addMnemonic("RSASHA1", DNSSEC.Algorithm.RSASHA1);
|
||||||
|
addMnemonic("RSA", DNSSEC.Algorithm.RSASHA1);
|
||||||
|
|
||||||
|
// Load the (now) standard aliases
|
||||||
|
addAlias(DNSSEC.Algorithm.DSA_NSEC3_SHA1, "DSA-NSEC3-SHA1", DNSSEC.Algorithm.DSA);
|
||||||
|
addAlias(DNSSEC.Algorithm.RSA_NSEC3_SHA1, "RSA-NSEC3-SHA1", DNSSEC.Algorithm.RSASHA1);
|
||||||
|
// Also recognize the BIND 9.6 mnemonics
|
||||||
|
addMnemonic("NSEC3DSA", DNSSEC.Algorithm.DSA_NSEC3_SHA1);
|
||||||
|
addMnemonic("NSEC3RSASHA1", DNSSEC.Algorithm.RSA_NSEC3_SHA1);
|
||||||
|
|
||||||
|
// Algorithms added by RFC 5702.
|
||||||
|
addAlgorithm(DNSSEC.Algorithm.RSASHA256, "SHA256withRSA", BaseAlgorithm.RSA);
|
||||||
|
addMnemonic("RSASHA256", DNSSEC.Algorithm.RSASHA256);
|
||||||
|
|
||||||
|
addAlgorithm(DNSSEC.Algorithm.RSASHA512, "SHA512withRSA", BaseAlgorithm.RSA);
|
||||||
|
addMnemonic("RSASHA512", DNSSEC.Algorithm.RSASHA512);
|
||||||
|
|
||||||
|
addAlgorithm(DNSSEC.Algorithm.ECDSAP256SHA256, "SHA256withECDSA", BaseAlgorithm.ECDSA, "secp256r1");
|
||||||
|
addMnemonic("ECDSAP256SHA256", DNSSEC.Algorithm.ECDSAP256SHA256);
|
||||||
|
addMnemonic("ECDSA-P256", DNSSEC.Algorithm.ECDSAP256SHA256);
|
||||||
|
|
||||||
|
addAlgorithm(DNSSEC.Algorithm.ECDSAP384SHA384, "SHA384withECDSA", BaseAlgorithm.ECDSA, "secp384r1");
|
||||||
|
addMnemonic("ECDSAP384SHA384", DNSSEC.Algorithm.ECDSAP384SHA384);
|
||||||
|
addMnemonic("ECDSA-P384", DNSSEC.Algorithm.ECDSAP384SHA384);
|
||||||
|
|
||||||
|
// For the Edwards Curve implementations, we just initialize Signature and
|
||||||
|
// KeyPairGenerator with the curve name.
|
||||||
|
addAlgorithm(15, "Ed25519", BaseAlgorithm.EDDSA, "Ed25519");
|
||||||
|
addMnemonic("ED25519", 15);
|
||||||
|
addAlgorithm(16, "Ed448", BaseAlgorithm.EDDSA, "Ed448");
|
||||||
|
addMnemonic(("ED448"), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAlgorithm(int algorithm, String sigName, BaseAlgorithm baseType) {
|
||||||
|
mAlgorithmMap.put(algorithm, new AlgEntry(algorithm, sigName, baseType));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a ECDSA (algorithms 13/14) to the set, looking up the curve names.
|
||||||
|
*
|
||||||
|
* @param algorithm the DNSSEC algorithm number.
|
||||||
|
* @param sigName the name of the signature scheme.
|
||||||
|
* @param curveName the official name of the elliptic curve in our crypto
|
||||||
|
* library (SunEC).
|
||||||
|
*/
|
||||||
|
private void addECDSAAlgorithm(int algorithm, String sigName, String curveName) {
|
||||||
|
ECParameterSpec ecSpec = ECSpecFromName(curveName);
|
||||||
|
if (ecSpec == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check to see if we can get a Signature object for this algorithm.
|
||||||
|
try {
|
||||||
|
Signature.getInstance(sigName);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
// for now, let's find out
|
||||||
|
log.severe("could not get signature for " + sigName + ": " + e.getMessage());
|
||||||
|
// If not, do not add the algorithm.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ECAlgEntry entry = new ECAlgEntry(algorithm, sigName, BaseAlgorithm.ECDSA, ecSpec);
|
||||||
|
mAlgorithmMap.put(algorithm, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an EdDSA (Edwards curve algorithms, DNSSEC algorithms 15/16), looking up
|
||||||
|
* the curve.
|
||||||
|
*
|
||||||
|
* @param algorithm the DNSSEC algorithm numer.
|
||||||
|
* @param sigName the name of the signing scheme. For EdDSA, this is the same
|
||||||
|
* as the curve.
|
||||||
|
* @param curveName the name of the curve.
|
||||||
|
*/
|
||||||
|
private void addEdDSAAlgorithm(int algorithm, String sigName, String curveName) {
|
||||||
|
// Check to see if we can get a Signature object for this algorithm.
|
||||||
|
try {
|
||||||
|
Signature.getInstance(sigName);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
// for now, let's find out
|
||||||
|
log.severe("could not get signature for EdDSA curve" + curveName + ": " + e.getMessage());
|
||||||
|
// If not, do not add the algorithm.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EdAlgEntry entry = new EdAlgEntry(algorithm, sigName, BaseAlgorithm.EDDSA, curveName);
|
||||||
|
mAlgorithmMap.put(algorithm, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an Elliptic Curve algorithm given a signing scheme and curve name.
|
||||||
|
*
|
||||||
|
* @param algorithm the DNSSEC algorithm number
|
||||||
|
* @param sigName the signature scheme (e.g., which crypto hash function are
|
||||||
|
* we using?)
|
||||||
|
* @param baseType the base type (either ECDSA or EDDSA).
|
||||||
|
* @param curveName the name of the curve.
|
||||||
|
*/
|
||||||
|
private void addAlgorithm(int algorithm, String sigName, BaseAlgorithm baseType, String curveName) {
|
||||||
|
switch (baseType) {
|
||||||
|
case ECDSA:
|
||||||
|
addECDSAAlgorithm(algorithm, sigName, curveName);
|
||||||
|
break;
|
||||||
|
case EDDSA:
|
||||||
|
addEdDSAAlgorithm(algorithm, sigName, curveName);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Non-Ellipic curve algorithm passed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an alternate mnemonic for an algorithm.
|
||||||
|
*
|
||||||
|
* @param m the new mnemonic.
|
||||||
|
* @param alg the DNSSEC algorithm number.
|
||||||
|
*/
|
||||||
|
private void addMnemonic(String m, int alg) {
|
||||||
|
// Do not add mnemonics for algorithms that ended up not actually being
|
||||||
|
// supported.
|
||||||
|
if (!mAlgorithmMap.containsKey(alg))
|
||||||
|
return;
|
||||||
|
|
||||||
|
mMnemonicToIdMap.put(m.toUpperCase(), alg);
|
||||||
|
mIdToMnemonicMap.computeIfAbsent(alg, k -> m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAlias(int alias, String mnemonic, int origAlgorithm) {
|
||||||
|
if (mAlgorithmMap.containsKey(alias)) {
|
||||||
|
log.warning("Unable to alias algorithm " + alias + " because it already exists.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mAlgorithmMap.containsKey(origAlgorithm)) {
|
||||||
|
log.warning("Unable to alias algorithm " + alias
|
||||||
|
+ " to unknown algorithm identifier " + origAlgorithm);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mAlgorithmMap.put(alias, mAlgorithmMap.get(origAlgorithm));
|
||||||
|
|
||||||
|
if (mnemonic != null) {
|
||||||
|
addMnemonic(mnemonic, alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AlgEntry getEntry(int alg) {
|
||||||
|
return mAlgorithmMap.get(alg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the curve parameters from a named ECDSA curve.
|
||||||
|
private ECParameterSpec ECSpecFromName(String stdName) {
|
||||||
|
try {
|
||||||
|
AlgorithmParameters ap = AlgorithmParameters.getInstance("EC");
|
||||||
|
ECGenParameterSpec ecgSpec = new ECGenParameterSpec(stdName);
|
||||||
|
ap.init(ecgSpec);
|
||||||
|
return ap.getParameterSpec(ECParameterSpec.class);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
log.info("Elliptic Curve not supported by any crypto provider: " + e.getMessage());
|
||||||
|
} catch (InvalidParameterSpecException e) {
|
||||||
|
log.info("Elliptic Curve " + stdName + " not supported");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] supportedAlgMnemonics() {
|
||||||
|
Set<Integer> keyset = mAlgorithmMap.keySet();
|
||||||
|
Integer[] algs = keyset.toArray(new Integer[keyset.size()]);
|
||||||
|
Arrays.sort(algs);
|
||||||
|
|
||||||
|
String[] result = new String[algs.length];
|
||||||
|
for (int i = 0; i < algs.length; i++) {
|
||||||
|
result[i] = mIdToMnemonicMap.get(algs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a Signature object for the specified DNSSEC algorithm.
|
||||||
|
*
|
||||||
|
* @param algorithm The DNSSEC algorithm (by number).
|
||||||
|
* @return a Signature object.
|
||||||
|
*/
|
||||||
|
public Signature getSignature(int algorithm) {
|
||||||
|
AlgEntry entry = getEntry(algorithm);
|
||||||
|
if (entry == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Signature s = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
s = Signature.getInstance(entry.sigName);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
log.severe("Unable to get signature implementation for algorithm " + algorithm
|
||||||
|
+ ": " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given one of the ECDSA algorithms (ECDSAP256SHA256, etc.) return the
|
||||||
|
* elliptic curve parameters.
|
||||||
|
*
|
||||||
|
* @param algorithm The DNSSEC algorithm number.
|
||||||
|
* @return The calculated JCA ECParameterSpec for that DNSSEC algorithm, or
|
||||||
|
* null if not a recognized/supported EC algorithm.
|
||||||
|
*/
|
||||||
|
public ECParameterSpec getEllipticCurveParams(int algorithm) {
|
||||||
|
AlgEntry entry = getEntry(algorithm);
|
||||||
|
if (entry == null)
|
||||||
|
return null;
|
||||||
|
if (!(entry instanceof ECAlgEntry))
|
||||||
|
return null;
|
||||||
|
ECAlgEntry ecEntry = (ECAlgEntry) entry;
|
||||||
|
|
||||||
|
return ecEntry.ecSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given one of the EdDSA algorithms (ED25519 or ED448), return the named
|
||||||
|
* parameter spec.
|
||||||
|
*
|
||||||
|
* @param algorithm The DNSSEC algorithm number.
|
||||||
|
* @return The NamedParameterSpec for that DNSSEC algorithm, nor null if the
|
||||||
|
* algorithm wasn't a supported EdDSA algorithm.
|
||||||
|
*/
|
||||||
|
public NamedParameterSpec getEdwardsCurveSpec(int algorithm) {
|
||||||
|
AlgEntry entry = getEntry(algorithm);
|
||||||
|
if (entry == null)
|
||||||
|
return null;
|
||||||
|
if (!(entry instanceof EdAlgEntry))
|
||||||
|
return null;
|
||||||
|
EdAlgEntry edEntry = (EdAlgEntry) entry;
|
||||||
|
|
||||||
|
return edEntry.paramSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate a possible algorithm alias back to the original DNSSEC algorithm
|
||||||
|
* number
|
||||||
|
*
|
||||||
|
* @param algorithm a DNSSEC algorithm that may be an alias.
|
||||||
|
* @return -1 if the algorithm isn't recognised, the orignal algorithm number
|
||||||
|
* if it is.
|
||||||
|
*/
|
||||||
|
public int originalAlgorithm(int algorithm) {
|
||||||
|
AlgEntry entry = getEntry(algorithm);
|
||||||
|
if (entry == null)
|
||||||
|
return -1;
|
||||||
|
return entry.dnssecAlgorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if a given algorithm is supported.
|
||||||
|
*
|
||||||
|
* @param algorithm The DNSSEC algorithm number.
|
||||||
|
* @return true if the algorithm is a recognized and supported algorithm or
|
||||||
|
* alias.
|
||||||
|
*/
|
||||||
|
public boolean supportedAlgorithm(int algorithm) {
|
||||||
|
return mAlgorithmMap.containsKey(algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an algorithm mnemonic, convert the mnemonic to a DNSSEC algorithm
|
||||||
|
* number.
|
||||||
|
*
|
||||||
|
* @param s The mnemonic string. This is case-insensitive.
|
||||||
|
* @return -1 if the mnemonic isn't recognized or supported, the algorithm
|
||||||
|
* number if it is.
|
||||||
|
*/
|
||||||
|
public int stringToAlgorithm(String s) {
|
||||||
|
Integer alg = mMnemonicToIdMap.get(s.toUpperCase());
|
||||||
|
if (alg != null)
|
||||||
|
return alg.intValue();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a DNSSEC algorithm number, return the "preferred" mnemonic.
|
||||||
|
*
|
||||||
|
* @param algorithm A DNSSEC algorithm number.
|
||||||
|
* @return The preferred mnemonic string, or null if not supported or
|
||||||
|
* recognized.
|
||||||
|
*/
|
||||||
|
public String algToString(int algorithm) {
|
||||||
|
return mIdToMnemonicMap.get(algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseAlgorithm baseType(int algorithm) {
|
||||||
|
AlgEntry entry = getEntry(algorithm);
|
||||||
|
if (entry != null)
|
||||||
|
return entry.baseType;
|
||||||
|
return BaseAlgorithm.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDSA(int algorithm) {
|
||||||
|
return (baseType(algorithm) == BaseAlgorithm.DSA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyPair generateKeyPair(int algorithm, int keysize, boolean useLargeExp)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
KeyPair pair = null;
|
||||||
|
switch (baseType(algorithm)) {
|
||||||
|
case RSA: {
|
||||||
|
if (mRSAKeyGenerator == null) {
|
||||||
|
mRSAKeyGenerator = KeyPairGenerator.getInstance("RSA");
|
||||||
|
}
|
||||||
|
|
||||||
|
RSAKeyGenParameterSpec rsaSpec;
|
||||||
|
if (useLargeExp) {
|
||||||
|
rsaSpec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F4);
|
||||||
|
} else {
|
||||||
|
rsaSpec = new RSAKeyGenParameterSpec(keysize, RSAKeyGenParameterSpec.F0);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mRSAKeyGenerator.initialize(rsaSpec);
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
// Fold the InvalidAlgorithmParameterException into our existing
|
||||||
|
// thrown exception. Ugly, but requires less code change.
|
||||||
|
throw new NoSuchAlgorithmException("invalid key parameter spec");
|
||||||
|
}
|
||||||
|
|
||||||
|
pair = mRSAKeyGenerator.generateKeyPair();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DSA: {
|
||||||
|
if (mDSAKeyGenerator == null) {
|
||||||
|
mDSAKeyGenerator = KeyPairGenerator.getInstance("DSA");
|
||||||
|
}
|
||||||
|
mDSAKeyGenerator.initialize(keysize);
|
||||||
|
pair = mDSAKeyGenerator.generateKeyPair();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ECDSA: {
|
||||||
|
if (mECKeyGenerator == null) {
|
||||||
|
mECKeyGenerator = KeyPairGenerator.getInstance("EC");
|
||||||
|
}
|
||||||
|
|
||||||
|
ECParameterSpec ecSpec = getEllipticCurveParams(algorithm);
|
||||||
|
try {
|
||||||
|
mECKeyGenerator.initialize(ecSpec);
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
// Fold the InvalidAlgorithmParameterException into our existing
|
||||||
|
// thrown exception. Ugly, but requires less code change.
|
||||||
|
throw new NoSuchAlgorithmException("invalid key parameter spec");
|
||||||
|
}
|
||||||
|
pair = mECKeyGenerator.generateKeyPair();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EDDSA: {
|
||||||
|
EdAlgEntry entry = (EdAlgEntry) getEntry(algorithm);
|
||||||
|
KeyPairGenerator edKeyGenerator = KeyPairGenerator.getInstance(entry.curveName);
|
||||||
|
|
||||||
|
pair = edKeyGenerator.generateKeyPair();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new NoSuchAlgorithmException("Alg " + algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pair;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyPair generateKeyPair(int algorithm, int keysize)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
return generateKeyPair(algorithm, keysize, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DnsKeyAlgorithm getInstance() {
|
||||||
|
if (mInstance == null)
|
||||||
|
mInstance = new DnsKeyAlgorithm();
|
||||||
|
return mInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,648 @@
|
|||||||
|
// Copyright (C) 2001-2003, 2022 VeriSign, Inc.
|
||||||
|
//
|
||||||
|
// 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.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.interfaces.DSAParams;
|
||||||
|
import java.security.interfaces.DSAPrivateKey;
|
||||||
|
import java.security.interfaces.DSAPublicKey;
|
||||||
|
import java.security.interfaces.ECPrivateKey;
|
||||||
|
import java.security.interfaces.ECPublicKey;
|
||||||
|
import java.security.interfaces.EdECPrivateKey;
|
||||||
|
import java.security.interfaces.EdECPublicKey;
|
||||||
|
import java.security.interfaces.RSAPrivateCrtKey;
|
||||||
|
import java.security.spec.DSAPrivateKeySpec;
|
||||||
|
import java.security.spec.ECParameterSpec;
|
||||||
|
import java.security.spec.ECPrivateKeySpec;
|
||||||
|
import java.security.spec.EdECPrivateKeySpec;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.KeySpec;
|
||||||
|
import java.security.spec.NamedParameterSpec;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.security.spec.RSAPrivateCrtKeySpec;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.crypto.interfaces.DHPrivateKey;
|
||||||
|
import javax.crypto.interfaces.DHPublicKey;
|
||||||
|
import javax.crypto.spec.DHParameterSpec;
|
||||||
|
import javax.crypto.spec.DHPrivateKeySpec;
|
||||||
|
|
||||||
|
import org.xbill.DNS.DNSKEYRecord;
|
||||||
|
import org.xbill.DNS.DNSSEC.DNSSECException;
|
||||||
|
import org.xbill.DNS.Name;
|
||||||
|
import org.xbill.DNS.utils.base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles conversions between JCA key formats and DNSSEC and BIND9
|
||||||
|
* key formats.
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
public class DnsKeyConverter {
|
||||||
|
private KeyFactory mRSAKeyFactory;
|
||||||
|
private KeyFactory mDSAKeyFactory;
|
||||||
|
private KeyFactory mDHKeyFactory;
|
||||||
|
private KeyFactory mECKeyFactory;
|
||||||
|
private KeyFactory mEdKeyFactory;
|
||||||
|
private DnsKeyAlgorithm mAlgorithms;
|
||||||
|
|
||||||
|
private Logger log = Logger.getLogger(this.getClass().toString());
|
||||||
|
|
||||||
|
public DnsKeyConverter() {
|
||||||
|
mAlgorithms = DnsKeyAlgorithm.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a DNS KEY record, return the JCA public key
|
||||||
|
*
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
*/
|
||||||
|
public PublicKey parseDNSKEYRecord(DNSKEYRecord pKeyRecord)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
if (pKeyRecord.getKey() == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Because we have arbitrarily aliased algorithms, we need to possibly
|
||||||
|
// translate the aliased algorithm back to the actual algorithm.
|
||||||
|
int originalAlgorithm = mAlgorithms.originalAlgorithm(pKeyRecord.getAlgorithm());
|
||||||
|
|
||||||
|
if (originalAlgorithm <= 0)
|
||||||
|
throw new NoSuchAlgorithmException("DNSKEY algorithm "
|
||||||
|
+ pKeyRecord.getAlgorithm() + " is unrecognized");
|
||||||
|
|
||||||
|
if (pKeyRecord.getAlgorithm() != originalAlgorithm) {
|
||||||
|
pKeyRecord = new DNSKEYRecord(pKeyRecord.getName(), pKeyRecord.getDClass(),
|
||||||
|
pKeyRecord.getTTL(), pKeyRecord.getFlags(),
|
||||||
|
pKeyRecord.getProtocol(), originalAlgorithm,
|
||||||
|
pKeyRecord.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// This uses DNSJava's DNSSEC.toPublicKey() method.
|
||||||
|
return pKeyRecord.getPublicKey();
|
||||||
|
} catch (DNSSECException e) {
|
||||||
|
throw new NoSuchAlgorithmException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a JCA public key and the ancillary data, generate a DNSKEY record.
|
||||||
|
*/
|
||||||
|
public DNSKEYRecord generateDNSKEYRecord(Name name, int dclass, long ttl,
|
||||||
|
int flags, int alg, PublicKey key) {
|
||||||
|
try {
|
||||||
|
int origAlgorithm = mAlgorithms.originalAlgorithm(alg);
|
||||||
|
DNSKEYRecord keyrec = new DNSKEYRecord(name, dclass, ttl, flags, DNSKEYRecord.Protocol.DNSSEC, origAlgorithm,
|
||||||
|
key);
|
||||||
|
if (origAlgorithm == alg) {
|
||||||
|
return keyrec;
|
||||||
|
}
|
||||||
|
return new DNSKEYRecord(name, dclass, ttl, flags, DNSKEYRecord.Protocol.DNSSEC, alg, keyrec.getKey());
|
||||||
|
} catch (DNSSECException e) {
|
||||||
|
log.severe("Unable to generated a DNSKEYRecord: " + e);
|
||||||
|
// This mimics the behavior of KEYConverter.buildRecord(), which would
|
||||||
|
// return null if the algorithm was unknown.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private Key Specific Parsing routines
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a PKCS#8 encoded private key into a PrivateKey object.
|
||||||
|
*/
|
||||||
|
public PrivateKey convertEncodedPrivateKey(byte[] key, int algorithm) {
|
||||||
|
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
|
||||||
|
try {
|
||||||
|
switch (mAlgorithms.baseType(algorithm)) {
|
||||||
|
case RSA:
|
||||||
|
return mRSAKeyFactory.generatePrivate(spec);
|
||||||
|
case DSA:
|
||||||
|
return mDSAKeyFactory.generatePrivate(spec);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple wrapper for parsing integers; parse failures result in the
|
||||||
|
* supplied default.
|
||||||
|
*/
|
||||||
|
private static int parseInt(String s, int def) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(s);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a JCA private key, given a BIND9-style textual encoding
|
||||||
|
*/
|
||||||
|
public PrivateKey parsePrivateKeyString(String key)
|
||||||
|
throws IOException, NoSuchAlgorithmException {
|
||||||
|
StringTokenizer lines = new StringTokenizer(key, "\n");
|
||||||
|
|
||||||
|
while (lines.hasMoreTokens()) {
|
||||||
|
String line = lines.nextToken();
|
||||||
|
if (line == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (line.startsWith("#"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String val = value(line);
|
||||||
|
if (val == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (line.startsWith("Private-key-format: ")) {
|
||||||
|
if (!val.equals("v1.2") && !val.equals("v1.3")) {
|
||||||
|
throw new IOException("unsupported private key format: " + val);
|
||||||
|
}
|
||||||
|
} else if (line.startsWith("Algorithm: ")) {
|
||||||
|
// here we assume that the value looks like # (MNEM) or just the
|
||||||
|
// number.
|
||||||
|
String[] toks = val.split("\\s", 2);
|
||||||
|
val = toks[0];
|
||||||
|
int alg = parseInt(val, -1);
|
||||||
|
|
||||||
|
switch (mAlgorithms.baseType(alg)) {
|
||||||
|
case RSA:
|
||||||
|
return parsePrivateRSA(lines);
|
||||||
|
case DSA:
|
||||||
|
return parsePrivateDSA(lines);
|
||||||
|
case DH:
|
||||||
|
return parsePrivateDH(lines);
|
||||||
|
case ECDSA:
|
||||||
|
return parsePrivateECDSA(lines, alg);
|
||||||
|
case EDDSA:
|
||||||
|
return parsePrivateEdDSA(lines, alg);
|
||||||
|
default:
|
||||||
|
throw new IOException("unsupported private key algorithm: " + val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value part of an "attribute:value" pair. The value is trimmed.
|
||||||
|
*/
|
||||||
|
private static String value(String av) {
|
||||||
|
if (av == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
int pos = av.indexOf(':');
|
||||||
|
if (pos < 0)
|
||||||
|
return av;
|
||||||
|
|
||||||
|
if (pos >= av.length())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return av.substring(pos + 1).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the rest of the RSA BIND9 string format private key, parse and
|
||||||
|
* translate into a JCA private key
|
||||||
|
*
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
* if the RSA algorithm is not available.
|
||||||
|
*/
|
||||||
|
private PrivateKey parsePrivateRSA(StringTokenizer lines)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
BigInteger modulus = null;
|
||||||
|
BigInteger publicExponent = null;
|
||||||
|
BigInteger privateExponent = null;
|
||||||
|
BigInteger primeP = null;
|
||||||
|
BigInteger primeQ = null;
|
||||||
|
BigInteger primePExponent = null;
|
||||||
|
BigInteger primeQExponent = null;
|
||||||
|
BigInteger coefficient = null;
|
||||||
|
|
||||||
|
while (lines.hasMoreTokens()) {
|
||||||
|
String line = lines.nextToken();
|
||||||
|
if (line == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (line.startsWith("#"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String val = value(line);
|
||||||
|
if (val == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
byte[] data = base64.fromString(val);
|
||||||
|
|
||||||
|
if (line.startsWith("Modulus: ")) {
|
||||||
|
modulus = new BigInteger(1, data);
|
||||||
|
} else if (line.startsWith("PublicExponent: ")) {
|
||||||
|
publicExponent = new BigInteger(1, data);
|
||||||
|
} else if (line.startsWith("PrivateExponent: ")) {
|
||||||
|
privateExponent = new BigInteger(1, data);
|
||||||
|
} else if (line.startsWith("Prime1: ")) {
|
||||||
|
primeP = new BigInteger(1, data);
|
||||||
|
} else if (line.startsWith("Prime2: ")) {
|
||||||
|
primeQ = new BigInteger(1, data);
|
||||||
|
} else if (line.startsWith("Exponent1: ")) {
|
||||||
|
primePExponent = new BigInteger(1, data);
|
||||||
|
} else if (line.startsWith("Exponent2: ")) {
|
||||||
|
primeQExponent = new BigInteger(1, data);
|
||||||
|
} else if (line.startsWith("Coefficient: ")) {
|
||||||
|
coefficient = new BigInteger(1, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
KeySpec spec = new RSAPrivateCrtKeySpec(modulus, publicExponent,
|
||||||
|
privateExponent, primeP,
|
||||||
|
primeQ, primePExponent,
|
||||||
|
primeQExponent, coefficient);
|
||||||
|
if (mRSAKeyFactory == null) {
|
||||||
|
mRSAKeyFactory = KeyFactory.getInstance("RSA");
|
||||||
|
}
|
||||||
|
return mRSAKeyFactory.generatePrivate(spec);
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the remaining lines in a BIND9 style DH private key, parse the key
|
||||||
|
* info and translate it into a JCA private key.
|
||||||
|
*
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
* if the DH algorithm is not available.
|
||||||
|
*/
|
||||||
|
private PrivateKey parsePrivateDH(StringTokenizer lines)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
BigInteger p = null;
|
||||||
|
BigInteger x = null;
|
||||||
|
BigInteger g = null;
|
||||||
|
|
||||||
|
while (lines.hasMoreTokens()) {
|
||||||
|
String line = lines.nextToken();
|
||||||
|
if (line == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (line.startsWith("#"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String val = value(line);
|
||||||
|
if (val == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
byte[] data = base64.fromString(val);
|
||||||
|
|
||||||
|
if (line.startsWith("Prime(p): ")) {
|
||||||
|
p = new BigInteger(1, data);
|
||||||
|
} else if (line.startsWith("Generator(g): ")) {
|
||||||
|
g = new BigInteger(1, data);
|
||||||
|
} else if (line.startsWith("Private_value(x): ")) {
|
||||||
|
x = new BigInteger(1, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
KeySpec spec = new DHPrivateKeySpec(x, p, g);
|
||||||
|
if (mDHKeyFactory == null) {
|
||||||
|
mDHKeyFactory = KeyFactory.getInstance("DH");
|
||||||
|
}
|
||||||
|
return mDHKeyFactory.generatePrivate(spec);
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the remaining lines in a BIND9 style DSA private key, parse the key
|
||||||
|
* info and translate it into a JCA private key.
|
||||||
|
*
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
* if the DSA algorithm is not available.
|
||||||
|
*/
|
||||||
|
private PrivateKey parsePrivateDSA(StringTokenizer lines)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
BigInteger p = null;
|
||||||
|
BigInteger q = null;
|
||||||
|
BigInteger g = null;
|
||||||
|
BigInteger x = null;
|
||||||
|
|
||||||
|
while (lines.hasMoreTokens()) {
|
||||||
|
String line = lines.nextToken();
|
||||||
|
if (line == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (line.startsWith("#"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String val = value(line);
|
||||||
|
if (val == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
byte[] data = base64.fromString(val);
|
||||||
|
|
||||||
|
if (line.startsWith("Prime(p): ")) {
|
||||||
|
p = new BigInteger(1, data);
|
||||||
|
} else if (line.startsWith("Subprime(q): ")) {
|
||||||
|
q = new BigInteger(1, data);
|
||||||
|
} else if (line.startsWith("Base(g): ")) {
|
||||||
|
g = new BigInteger(1, data);
|
||||||
|
} else if (line.startsWith("Private_value(x): ")) {
|
||||||
|
x = new BigInteger(1, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
KeySpec spec = new DSAPrivateKeySpec(x, p, q, g);
|
||||||
|
if (mDSAKeyFactory == null) {
|
||||||
|
mDSAKeyFactory = KeyFactory.getInstance("DSA");
|
||||||
|
}
|
||||||
|
return mDSAKeyFactory.generatePrivate(spec);
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the remaining lines in a BIND9-style ECDSA private key, parse the key
|
||||||
|
* info and translate it into a JCA private key object.
|
||||||
|
*
|
||||||
|
* @param lines The remaining lines in a private key file (after
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
* If elliptic curve is not available.
|
||||||
|
*/
|
||||||
|
private PrivateKey parsePrivateECDSA(StringTokenizer lines, int algorithm)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
BigInteger s = null;
|
||||||
|
|
||||||
|
while (lines.hasMoreTokens()) {
|
||||||
|
String line = lines.nextToken();
|
||||||
|
if (line == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (line.startsWith("#"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String val = value(line);
|
||||||
|
if (val == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
byte[] data = base64.fromString(val);
|
||||||
|
|
||||||
|
if (line.startsWith("PrivateKey: ")) {
|
||||||
|
s = new BigInteger(1, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mECKeyFactory == null) {
|
||||||
|
mECKeyFactory = KeyFactory.getInstance("EC");
|
||||||
|
}
|
||||||
|
ECParameterSpec ecSpec = mAlgorithms.getEllipticCurveParams(algorithm);
|
||||||
|
if (ecSpec == null) {
|
||||||
|
throw new NoSuchAlgorithmException("DNSSEC algorithm " + algorithm +
|
||||||
|
" is not a recognized Elliptic Curve algorithm");
|
||||||
|
}
|
||||||
|
|
||||||
|
KeySpec spec = new ECPrivateKeySpec(s, ecSpec);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return mECKeyFactory.generatePrivate(spec);
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the remaining lines in a BIND9-style ECDSA private key, parse the key
|
||||||
|
* info and translate it into a JCA private key object.
|
||||||
|
*
|
||||||
|
* @param lines The remaining lines in a private key file (after
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
* If elliptic curve is not available.
|
||||||
|
*/
|
||||||
|
private PrivateKey parsePrivateEdDSA(StringTokenizer lines, int algorithm)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
byte[] seed = null;
|
||||||
|
|
||||||
|
while (lines.hasMoreTokens()) {
|
||||||
|
String line = lines.nextToken();
|
||||||
|
if (line == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (line.startsWith("#"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String val = value(line);
|
||||||
|
if (val == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
byte[] data = base64.fromString(val);
|
||||||
|
|
||||||
|
if (line.startsWith("PrivateKey: ")) {
|
||||||
|
seed = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mEdKeyFactory == null) {
|
||||||
|
mEdKeyFactory = KeyFactory.getInstance("EdDSA");
|
||||||
|
}
|
||||||
|
NamedParameterSpec namedSpec = mAlgorithms.getEdwardsCurveSpec(algorithm);
|
||||||
|
if (namedSpec == null) {
|
||||||
|
throw new NoSuchAlgorithmException("DNSSEC algorithm " + algorithm +
|
||||||
|
" is not a recognized Edwards Curve algorithm");
|
||||||
|
}
|
||||||
|
|
||||||
|
EdECPrivateKeySpec spec = new EdECPrivateKeySpec(namedSpec, seed);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return mEdKeyFactory.generatePrivate(spec);
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a private key and public key, generate the BIND9 style private key
|
||||||
|
* format.
|
||||||
|
*/
|
||||||
|
public String generatePrivateKeyString(PrivateKey priv, PublicKey pub, int alg) {
|
||||||
|
if (priv instanceof RSAPrivateCrtKey) {
|
||||||
|
return generatePrivateRSA((RSAPrivateCrtKey) priv, alg);
|
||||||
|
} else if (priv instanceof DSAPrivateKey && pub instanceof DSAPublicKey) {
|
||||||
|
return generatePrivateDSA((DSAPrivateKey) priv, (DSAPublicKey) pub, alg);
|
||||||
|
} else if (priv instanceof DHPrivateKey && pub instanceof DHPublicKey) {
|
||||||
|
return generatePrivateDH((DHPrivateKey) priv, (DHPublicKey) pub, alg);
|
||||||
|
} else if (priv instanceof ECPrivateKey && pub instanceof ECPublicKey) {
|
||||||
|
return generatePrivateEC((ECPrivateKey) priv, (ECPublicKey) pub, alg);
|
||||||
|
} else if (priv instanceof EdECPrivateKey && pub instanceof EdECPublicKey) {
|
||||||
|
return generatePrivateED((EdECPrivateKey) priv, (EdECPublicKey) pub, alg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert from 'unsigned' big integer to original 'signed format' in Base64
|
||||||
|
*/
|
||||||
|
private static String b64BigInt(BigInteger i) {
|
||||||
|
byte[] origBytes = i.toByteArray();
|
||||||
|
|
||||||
|
if (origBytes[0] != 0 || origBytes.length == 1) {
|
||||||
|
return base64.toString(origBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] signedBytes = new byte[origBytes.length - 1];
|
||||||
|
System.arraycopy(origBytes, 1, signedBytes, 0, signedBytes.length);
|
||||||
|
|
||||||
|
return base64.toString(signedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a RSA private key (in Crt format), return the BIND9-style text
|
||||||
|
* encoding.
|
||||||
|
*/
|
||||||
|
private String generatePrivateRSA(RSAPrivateCrtKey key, int algorithm) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
PrintWriter out = new PrintWriter(sw);
|
||||||
|
|
||||||
|
out.println("Private-key-format: v1.2");
|
||||||
|
out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
|
||||||
|
+ ")");
|
||||||
|
out.print("Modulus: ");
|
||||||
|
out.println(b64BigInt(key.getModulus()));
|
||||||
|
out.print("PublicExponent: ");
|
||||||
|
out.println(b64BigInt(key.getPublicExponent()));
|
||||||
|
out.print("PrivateExponent: ");
|
||||||
|
out.println(b64BigInt(key.getPrivateExponent()));
|
||||||
|
out.print("Prime1: ");
|
||||||
|
out.println(b64BigInt(key.getPrimeP()));
|
||||||
|
out.print("Prime2: ");
|
||||||
|
out.println(b64BigInt(key.getPrimeQ()));
|
||||||
|
out.print("Exponent1: ");
|
||||||
|
out.println(b64BigInt(key.getPrimeExponentP()));
|
||||||
|
out.print("Exponent2: ");
|
||||||
|
out.println(b64BigInt(key.getPrimeExponentQ()));
|
||||||
|
out.print("Coefficient: ");
|
||||||
|
out.println(b64BigInt(key.getCrtCoefficient()));
|
||||||
|
|
||||||
|
return sw.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Given a DH key pair, return the BIND9-style text encoding */
|
||||||
|
private String generatePrivateDH(DHPrivateKey key, DHPublicKey pub,
|
||||||
|
int algorithm) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
PrintWriter out = new PrintWriter(sw);
|
||||||
|
|
||||||
|
DHParameterSpec p = key.getParams();
|
||||||
|
|
||||||
|
out.println("Private-key-format: v1.2");
|
||||||
|
out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
|
||||||
|
+ ")");
|
||||||
|
out.print("Prime(p): ");
|
||||||
|
out.println(b64BigInt(p.getP()));
|
||||||
|
out.print("Generator(g): ");
|
||||||
|
out.println(b64BigInt(p.getG()));
|
||||||
|
out.print("Private_value(x): ");
|
||||||
|
out.println(b64BigInt(key.getX()));
|
||||||
|
out.print("Public_value(y): ");
|
||||||
|
out.println(b64BigInt(pub.getY()));
|
||||||
|
|
||||||
|
return sw.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Given a DSA key pair, return the BIND9-style text encoding */
|
||||||
|
private String generatePrivateDSA(DSAPrivateKey key, DSAPublicKey pub,
|
||||||
|
int algorithm) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
PrintWriter out = new PrintWriter(sw);
|
||||||
|
|
||||||
|
DSAParams p = key.getParams();
|
||||||
|
|
||||||
|
out.println("Private-key-format: v1.2");
|
||||||
|
out.println("Algorithm: " + algorithm + " (" + mAlgorithms.algToString(algorithm)
|
||||||
|
+ ")");
|
||||||
|
out.print("Prime(p): ");
|
||||||
|
out.println(b64BigInt(p.getP()));
|
||||||
|
out.print("Subprime(q): ");
|
||||||
|
out.println(b64BigInt(p.getQ()));
|
||||||
|
out.print("Base(g): ");
|
||||||
|
out.println(b64BigInt(p.getG()));
|
||||||
|
out.print("Private_value(x): ");
|
||||||
|
out.println(b64BigInt(key.getX()));
|
||||||
|
out.print("Public_value(y): ");
|
||||||
|
out.println(b64BigInt(pub.getY()));
|
||||||
|
|
||||||
|
return sw.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an elliptic curve key pair, and the actual algorithm (which will
|
||||||
|
* describe the curve used), return the BIND9-style text encoding.
|
||||||
|
*/
|
||||||
|
private String generatePrivateEC(ECPrivateKey priv, ECPublicKey pub, int alg) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
PrintWriter out = new PrintWriter(sw);
|
||||||
|
|
||||||
|
out.println("Private-key-format: v1.2");
|
||||||
|
out.println("Algorithm: " + alg + " (" + mAlgorithms.algToString(alg)
|
||||||
|
+ ")");
|
||||||
|
out.print("PrivateKey: ");
|
||||||
|
out.println(b64BigInt(priv.getS()));
|
||||||
|
|
||||||
|
return sw.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an edwards curve key pair, and the actual algorithm (which will
|
||||||
|
* describe the curve used), return the BIND9-style text encoding.
|
||||||
|
*/
|
||||||
|
private String generatePrivateED(EdECPrivateKey priv, EdECPublicKey pub, int alg) {
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
PrintWriter out = new PrintWriter(sw);
|
||||||
|
|
||||||
|
out.println("Private-key-format: v1.2");
|
||||||
|
out.println("Algorithm: " + alg + " (" + mAlgorithms.algToString(alg)
|
||||||
|
+ ")");
|
||||||
|
out.print("PrivateKey: ");
|
||||||
|
byte[] keyBytes = priv.getBytes().orElse("null".getBytes());
|
||||||
|
out.println(base64.toString(keyBytes));
|
||||||
|
|
||||||
|
return sw.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
// $Id$
|
// Copyright (C) 2001-2003, 2022 VeriSign, Inc.
|
||||||
//
|
|
||||||
// Copyright (C) 2001-2003 VeriSign, Inc.
|
|
||||||
//
|
//
|
||||||
// This library is free software; you can redistribute it and/or
|
// This library is free software; you can redistribute it and/or
|
||||||
// modify it under the terms of the GNU Lesser General Public
|
// modify it under the terms of the GNU Lesser General Public
|
||||||
@@ -18,11 +16,22 @@
|
|||||||
|
|
||||||
package com.verisignlabs.dnssec.security;
|
package com.verisignlabs.dnssec.security;
|
||||||
|
|
||||||
import java.security.*;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.interfaces.*;
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.security.interfaces.DSAPrivateKey;
|
||||||
|
import java.security.interfaces.DSAPublicKey;
|
||||||
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.xbill.DNS.*;
|
import org.xbill.DNS.DClass;
|
||||||
|
import org.xbill.DNS.DNSKEYRecord;
|
||||||
|
import org.xbill.DNS.DNSSEC;
|
||||||
|
import org.xbill.DNS.Name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class forms the basis for representing public/private key pairs in a
|
* This class forms the basis for representing public/private key pairs in a
|
||||||
@@ -33,36 +42,33 @@ import org.xbill.DNS.*;
|
|||||||
*
|
*
|
||||||
* JCA == Java Cryptography Architecture.
|
* JCA == Java Cryptography Architecture.
|
||||||
*
|
*
|
||||||
* @author David Blacka (orig)
|
* @author David Blacka
|
||||||
* @author $Author$ (latest)
|
|
||||||
* @version $Revision$
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// NOTE: this class is designed to do "lazy" evaluation of it's
|
// NOTE: this class is designed to do "lazy" evaluation of it's
|
||||||
// various cached objects and format conversions, so methods should
|
// various cached objects and format conversions, so methods should
|
||||||
// avoid direct access to the member variables.
|
// avoid direct access to the member variables.
|
||||||
public class DnsKeyPair
|
public class DnsKeyPair {
|
||||||
{
|
|
||||||
/** This is the real (base) encoding of the public key. */
|
/** This is the real (base) encoding of the public key. */
|
||||||
protected DNSKEYRecord mPublicKeyRecord;
|
protected DNSKEYRecord mPublicKeyRecord;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a pre-calculated cache of the DNSKEYRecord converted into a JCA
|
* This is a pre-calculated cache of the DNSKEYRecord converted into a JCA
|
||||||
* public key.
|
* public key.
|
||||||
*/
|
*/
|
||||||
private PublicKey mPublicKey;
|
private PublicKey mPublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The private key in Base64 encoded format. This version is presumed to be
|
* The private key in Base64 encoded format. This version is presumed to be
|
||||||
* opaque, so no attempts will be made to convert it to a JCA private key.
|
* opaque, so no attempts will be made to convert it to a JCA private key.
|
||||||
*/
|
*/
|
||||||
protected String mPrivateKeyString;
|
protected String mPrivateKeyString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The private key in JCA format. This is the base encoding for instances where
|
* The private key in JCA format. This is the base encoding for instances where
|
||||||
* JCA private keys are used.
|
* JCA private keys are used.
|
||||||
*/
|
*/
|
||||||
protected PrivateKey mPrivateKey;
|
protected PrivateKey mPrivateKey;
|
||||||
|
|
||||||
/** The local key converter. */
|
/** The local key converter. */
|
||||||
protected DnsKeyConverter mKeyConverter;
|
protected DnsKeyConverter mKeyConverter;
|
||||||
@@ -70,57 +76,51 @@ public class DnsKeyPair
|
|||||||
/**
|
/**
|
||||||
* a cached Signature used for signing (initialized with the private key)
|
* a cached Signature used for signing (initialized with the private key)
|
||||||
*/
|
*/
|
||||||
protected Signature mSigner;
|
protected Signature mSigner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a caches Signature used for verifying (initialized with the public key)
|
* a caches Signature used for verifying (initialized with the public key)
|
||||||
*/
|
*/
|
||||||
protected Signature mVerifier;
|
protected Signature mVerifier;
|
||||||
|
|
||||||
private Logger log;
|
private Logger log;
|
||||||
|
|
||||||
public DnsKeyPair()
|
public DnsKeyPair() {
|
||||||
{
|
|
||||||
log = Logger.getLogger(this.getClass().toString());
|
log = Logger.getLogger(this.getClass().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public DnsKeyPair(DNSKEYRecord keyRecord, PrivateKey privateKey)
|
public DnsKeyPair(DNSKEYRecord keyRecord, PrivateKey privateKey) {
|
||||||
{
|
|
||||||
this();
|
this();
|
||||||
|
|
||||||
setDNSKEYRecord(keyRecord);
|
setDNSKEYRecord(keyRecord);
|
||||||
setPrivate(privateKey);
|
setPrivate(privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DnsKeyPair(DNSKEYRecord keyRecord, String privateKeyString)
|
public DnsKeyPair(DNSKEYRecord keyRecord, String privateKeyString) {
|
||||||
{
|
|
||||||
this();
|
this();
|
||||||
|
|
||||||
setDNSKEYRecord(keyRecord);
|
setDNSKEYRecord(keyRecord);
|
||||||
setPrivateKeyString(privateKeyString);
|
setPrivateKeyString(privateKeyString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DnsKeyPair(DNSKEYRecord keyRecord)
|
public DnsKeyPair(DNSKEYRecord keyRecord) {
|
||||||
{
|
|
||||||
this();
|
this();
|
||||||
setDNSKEYRecord(keyRecord);
|
setDNSKEYRecord(keyRecord);
|
||||||
setPrivateKeyString(null);
|
setPrivateKeyString(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DnsKeyPair(Name keyName, int algorithm, PublicKey publicKey,
|
public DnsKeyPair(Name keyName, int algorithm, PublicKey publicKey,
|
||||||
PrivateKey privateKey)
|
PrivateKey privateKey) {
|
||||||
{
|
|
||||||
this();
|
this();
|
||||||
|
|
||||||
DnsKeyConverter conv = new DnsKeyConverter();
|
DnsKeyConverter conv = new DnsKeyConverter();
|
||||||
DNSKEYRecord keyrec = conv.generateDNSKEYRecord(keyName, DClass.IN, 0, 0,
|
DNSKEYRecord keyrec = conv.generateDNSKEYRecord(keyName, DClass.IN, 0, 0,
|
||||||
algorithm, publicKey);
|
algorithm, publicKey);
|
||||||
setDNSKEYRecord(keyrec);
|
setDNSKEYRecord(keyrec);
|
||||||
setPrivate(privateKey);
|
setPrivate(privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DnsKeyPair(DnsKeyPair pair)
|
public DnsKeyPair(DnsKeyPair pair) {
|
||||||
{
|
|
||||||
this();
|
this();
|
||||||
|
|
||||||
setDNSKEYRecord(pair.getDNSKEYRecord());
|
setDNSKEYRecord(pair.getDNSKEYRecord());
|
||||||
@@ -128,11 +128,15 @@ public class DnsKeyPair
|
|||||||
setPrivateKeyString(pair.getPrivateKeyString());
|
setPrivateKeyString(pair.getPrivateKeyString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
|
||||||
|
return this.getDNSKEYName() + "/" + this.getDNSKEYAlgorithm() + "/" + this.getDNSKEYFootprint() + "/"
|
||||||
|
+ this.getDNSKEYPublicPrefix(6);
|
||||||
|
}
|
||||||
|
|
||||||
/** @return cached DnsKeyConverter object. */
|
/** @return cached DnsKeyConverter object. */
|
||||||
protected DnsKeyConverter getKeyConverter()
|
protected DnsKeyConverter getKeyConverter() {
|
||||||
{
|
if (mKeyConverter == null) {
|
||||||
if (mKeyConverter == null)
|
|
||||||
{
|
|
||||||
mKeyConverter = new DnsKeyConverter();
|
mKeyConverter = new DnsKeyConverter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,8 +144,7 @@ public class DnsKeyPair
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @return the appropriate Signature object for this keypair. */
|
/** @return the appropriate Signature object for this keypair. */
|
||||||
protected Signature getSignature()
|
protected Signature getSignature() {
|
||||||
{
|
|
||||||
DnsKeyAlgorithm algorithms = DnsKeyAlgorithm.getInstance();
|
DnsKeyAlgorithm algorithms = DnsKeyAlgorithm.getInstance();
|
||||||
return algorithms.getSignature(getDNSKEYAlgorithm());
|
return algorithms.getSignature(getDNSKEYAlgorithm());
|
||||||
}
|
}
|
||||||
@@ -149,17 +152,12 @@ public class DnsKeyPair
|
|||||||
/**
|
/**
|
||||||
* @return the public key, translated from the KEYRecord, if necessary.
|
* @return the public key, translated from the KEYRecord, if necessary.
|
||||||
*/
|
*/
|
||||||
public PublicKey getPublic()
|
public PublicKey getPublic() {
|
||||||
{
|
if (mPublicKey == null && getDNSKEYRecord() != null) {
|
||||||
if (mPublicKey == null && getDNSKEYRecord() != null)
|
try {
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
DnsKeyConverter conv = getKeyConverter();
|
DnsKeyConverter conv = getKeyConverter();
|
||||||
setPublic(conv.parseDNSKEYRecord(getDNSKEYRecord()));
|
setPublic(conv.parseDNSKEYRecord(getDNSKEYRecord()));
|
||||||
}
|
} catch (NoSuchAlgorithmException e) {
|
||||||
catch (NoSuchAlgorithmException e)
|
|
||||||
{
|
|
||||||
log.severe(e.toString());
|
log.severe(e.toString());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -171,18 +169,15 @@ public class DnsKeyPair
|
|||||||
/**
|
/**
|
||||||
* sets the public key. This method is generally not used directly.
|
* sets the public key. This method is generally not used directly.
|
||||||
*/
|
*/
|
||||||
protected void setPublic(PublicKey k)
|
protected void setPublic(PublicKey k) {
|
||||||
{
|
|
||||||
mPublicKey = k;
|
mPublicKey = k;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return the private key. */
|
/** @return the private key. */
|
||||||
public PrivateKey getPrivate()
|
public PrivateKey getPrivate() {
|
||||||
{
|
|
||||||
// attempt to convert the private key string format into a JCA
|
// attempt to convert the private key string format into a JCA
|
||||||
// private key.
|
// private key.
|
||||||
if (mPrivateKey == null && mPrivateKeyString != null)
|
if (mPrivateKey == null && mPrivateKeyString != null) {
|
||||||
{
|
|
||||||
mPrivateKey = BINDKeyUtils.convertPrivateKeyString(mPrivateKeyString);
|
mPrivateKey = BINDKeyUtils.convertPrivateKeyString(mPrivateKeyString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,8 +185,7 @@ public class DnsKeyPair
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** sets the private key */
|
/** sets the private key */
|
||||||
public void setPrivate(PrivateKey k)
|
public void setPrivate(PrivateKey k) {
|
||||||
{
|
|
||||||
mPrivateKey = k;
|
mPrivateKey = k;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,30 +193,27 @@ public class DnsKeyPair
|
|||||||
* @return the opaque private key string, null if one doesn't exist.
|
* @return the opaque private key string, null if one doesn't exist.
|
||||||
* @throws NoSuchAlgorithmException
|
* @throws NoSuchAlgorithmException
|
||||||
*/
|
*/
|
||||||
public String getPrivateKeyString()
|
public String getPrivateKeyString() {
|
||||||
{
|
if (mPrivateKeyString == null && mPrivateKey != null) {
|
||||||
if (mPrivateKeyString == null && mPrivateKey != null)
|
|
||||||
{
|
|
||||||
PublicKey pub = getPublic();
|
PublicKey pub = getPublic();
|
||||||
mPrivateKeyString = BINDKeyUtils.convertPrivateKey(mPrivateKey, pub,
|
mPrivateKeyString = BINDKeyUtils.convertPrivateKey(mPrivateKey, pub,
|
||||||
getDNSKEYAlgorithm());
|
getDNSKEYAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mPrivateKeyString;
|
return mPrivateKeyString;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** sets the opaque private key string. */
|
/** sets the opaque private key string. */
|
||||||
public void setPrivateKeyString(String p)
|
public void setPrivateKeyString(String p) {
|
||||||
{
|
|
||||||
mPrivateKeyString = p;
|
mPrivateKeyString = p;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return the private key in an encoded form (normally PKCS#8). */
|
/** @return the private key in an encoded form (normally PKCS#8). */
|
||||||
public byte[] getEncodedPrivate()
|
public byte[] getEncodedPrivate() {
|
||||||
{
|
|
||||||
PrivateKey priv = getPrivate();
|
PrivateKey priv = getPrivate();
|
||||||
if (priv != null) return priv.getEncoded();
|
if (priv != null)
|
||||||
return null;
|
return priv.getEncoded();
|
||||||
|
return new byte[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -230,20 +221,17 @@ public class DnsKeyPair
|
|||||||
* that the public key already be assigned. Currently it can only handle DSA
|
* that the public key already be assigned. Currently it can only handle DSA
|
||||||
* and RSA keys.
|
* and RSA keys.
|
||||||
*/
|
*/
|
||||||
public void setEncodedPrivate(byte[] encoded)
|
public void setEncodedPrivate(byte[] encoded) {
|
||||||
{
|
|
||||||
int alg = getDNSKEYAlgorithm();
|
int alg = getDNSKEYAlgorithm();
|
||||||
|
|
||||||
if (alg >= 0)
|
if (alg >= 0) {
|
||||||
{
|
|
||||||
DnsKeyConverter conv = getKeyConverter();
|
DnsKeyConverter conv = getKeyConverter();
|
||||||
setPrivate(conv.convertEncodedPrivateKey(encoded, alg));
|
setPrivate(conv.convertEncodedPrivateKey(encoded, alg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return the public DNSKEY record */
|
/** @return the public DNSKEY record */
|
||||||
public DNSKEYRecord getDNSKEYRecord()
|
public DNSKEYRecord getDNSKEYRecord() {
|
||||||
{
|
|
||||||
return mPublicKeyRecord;
|
return mPublicKeyRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,25 +239,17 @@ public class DnsKeyPair
|
|||||||
* @return a Signature object initialized for signing, or null if this key
|
* @return a Signature object initialized for signing, or null if this key
|
||||||
* pair does not have a valid private key.
|
* pair does not have a valid private key.
|
||||||
*/
|
*/
|
||||||
public Signature getSigner()
|
public Signature getSigner() {
|
||||||
{
|
if (mSigner == null) {
|
||||||
if (mSigner == null)
|
|
||||||
{
|
|
||||||
mSigner = getSignature();
|
mSigner = getSignature();
|
||||||
PrivateKey priv = getPrivate();
|
PrivateKey priv = getPrivate();
|
||||||
if (mSigner != null && priv != null)
|
if (mSigner != null && priv != null) {
|
||||||
{
|
try {
|
||||||
try
|
|
||||||
{
|
|
||||||
mSigner.initSign(priv);
|
mSigner.initSign(priv);
|
||||||
}
|
} catch (InvalidKeyException e) {
|
||||||
catch (InvalidKeyException e)
|
|
||||||
{
|
|
||||||
log.severe("Signature error: " + e);
|
log.severe("Signature error: " + e);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// do not return an uninitialized signer.
|
// do not return an uninitialized signer.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -283,74 +263,94 @@ public class DnsKeyPair
|
|||||||
* pair does not have a valid public key.
|
* pair does not have a valid public key.
|
||||||
* @throws NoSuchAlgorithmException
|
* @throws NoSuchAlgorithmException
|
||||||
*/
|
*/
|
||||||
public Signature getVerifier()
|
public Signature getVerifier() {
|
||||||
{
|
if (mVerifier != null)
|
||||||
if (mVerifier == null)
|
return mVerifier;
|
||||||
{
|
|
||||||
mVerifier = getSignature();
|
mVerifier = getSignature();
|
||||||
PublicKey pk = getPublic();
|
PublicKey pk = getPublic();
|
||||||
if (mVerifier != null && pk != null)
|
|
||||||
{
|
if (mVerifier == null || pk == null) {
|
||||||
try
|
log.warning("Could not get a Signature object for this key pair" + this);
|
||||||
{
|
return null;
|
||||||
mVerifier.initVerify(pk);
|
}
|
||||||
}
|
|
||||||
catch (InvalidKeyException e)
|
try {
|
||||||
{
|
mVerifier.initVerify(pk);
|
||||||
}
|
} catch (InvalidKeyException e) {
|
||||||
}
|
log.warning("Key pair cannot initialize with public key: " + this);
|
||||||
else
|
return null;
|
||||||
{
|
|
||||||
// do not return an uninitialized verifier
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mVerifier;
|
return mVerifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** sets the public key record */
|
/** sets the public key record */
|
||||||
public void setDNSKEYRecord(DNSKEYRecord r)
|
public void setDNSKEYRecord(DNSKEYRecord r) {
|
||||||
{
|
|
||||||
mPublicKeyRecord = r;
|
mPublicKeyRecord = r;
|
||||||
// force the conversion to PublicKey:
|
// force the conversion to PublicKey:
|
||||||
mPublicKey = null;
|
mPublicKey = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Name getDNSKEYName()
|
public Name getDNSKEYName() {
|
||||||
{
|
|
||||||
DNSKEYRecord kr = getDNSKEYRecord();
|
DNSKEYRecord kr = getDNSKEYRecord();
|
||||||
if (kr != null) return kr.getName();
|
if (kr != null)
|
||||||
|
return kr.getName();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDNSKEYAlgorithm()
|
public int getDNSKEYAlgorithm() {
|
||||||
{
|
|
||||||
DNSKEYRecord kr = getDNSKEYRecord();
|
DNSKEYRecord kr = getDNSKEYRecord();
|
||||||
if (kr != null) return kr.getAlgorithm();
|
if (kr != null)
|
||||||
|
return kr.getAlgorithm();
|
||||||
|
|
||||||
PublicKey pk = getPublic();
|
PublicKey pk = getPublic();
|
||||||
if (pk != null)
|
if (pk != null) {
|
||||||
{
|
|
||||||
// currently, alg 5 is the default over alg 1 (RSASHA1).
|
// currently, alg 5 is the default over alg 1 (RSASHA1).
|
||||||
if (pk instanceof RSAPublicKey) return DNSSEC.RSASHA1;
|
if (pk instanceof RSAPublicKey)
|
||||||
if (pk instanceof DSAPublicKey) return DNSSEC.DSA;
|
return DNSSEC.Algorithm.RSASHA1;
|
||||||
|
if (pk instanceof DSAPublicKey)
|
||||||
|
return DNSSEC.Algorithm.DSA;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivateKey priv = getPrivate();
|
PrivateKey priv = getPrivate();
|
||||||
if (priv != null)
|
if (priv != null) {
|
||||||
{
|
if (priv instanceof RSAPrivateKey)
|
||||||
if (priv instanceof RSAPrivateKey) return DNSSEC.RSASHA1;
|
return DNSSEC.Algorithm.RSASHA1;
|
||||||
if (priv instanceof DSAPrivateKey) return DNSSEC.DSA;
|
if (priv instanceof DSAPrivateKey)
|
||||||
|
return DNSSEC.Algorithm.DSA;
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDNSKEYFootprint()
|
public int getDNSKEYFootprint() {
|
||||||
{
|
|
||||||
DNSKEYRecord kr = getDNSKEYRecord();
|
DNSKEYRecord kr = getDNSKEYRecord();
|
||||||
if (kr != null) return kr.getFootprint();
|
if (kr != null)
|
||||||
|
return kr.getFootprint();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is from a StackOverflow answer. There are number of bytes-to-hex
|
||||||
|
// converters in the ecosystem, but this avoid extra dependencies
|
||||||
|
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
|
||||||
|
|
||||||
|
public static String toHex(byte[] bytes) {
|
||||||
|
byte[] hexChars = new byte[bytes.length * 2];
|
||||||
|
for (int j = 0; j < bytes.length; j++) {
|
||||||
|
int v = bytes[j] & 0xFF;
|
||||||
|
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||||
|
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||||
|
}
|
||||||
|
return new String(hexChars, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDNSKEYPublicPrefix(int length) {
|
||||||
|
DNSKEYRecord kr = getDNSKEYRecord();
|
||||||
|
if (kr == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String hexKey = toHex(kr.getKey());
|
||||||
|
return hexKey.substring(0, length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
// Copyright (C) 2001-2003, 2022 VeriSign, Inc.
|
||||||
|
//
|
||||||
|
// 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.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.xbill.DNS.DNSKEYRecord;
|
||||||
|
import org.xbill.DNS.DNSSEC;
|
||||||
|
import org.xbill.DNS.Name;
|
||||||
|
import org.xbill.DNS.RRSIGRecord;
|
||||||
|
import org.xbill.DNS.RRset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for performing basic DNSSEC verification. The DNSJAVA package
|
||||||
|
* contains a similar class. This differs (for the moment, anyway) by allowing
|
||||||
|
* timing "fudge" factors and logging more specifically why an RRset did not
|
||||||
|
* validate.
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
public class DnsSecVerifier {
|
||||||
|
|
||||||
|
private class TrustedKeyStore {
|
||||||
|
// for now, this is implemented as a hash table of lists of
|
||||||
|
// DnsKeyPair objects (obviously, all of them will not have
|
||||||
|
// private keys).
|
||||||
|
private HashMap<String, List<DnsKeyPair>> mKeyMap;
|
||||||
|
|
||||||
|
public TrustedKeyStore() {
|
||||||
|
mKeyMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(DnsKeyPair pair) {
|
||||||
|
String n = pair.getDNSKEYName().toString().toLowerCase();
|
||||||
|
List<DnsKeyPair> l = mKeyMap.computeIfAbsent(n, k -> new ArrayList<>());
|
||||||
|
l.add(pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(DNSKEYRecord keyrec) {
|
||||||
|
DnsKeyPair pair = new DnsKeyPair(keyrec, (PrivateKey) null);
|
||||||
|
add(pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Name name, int algorithm, PublicKey key) {
|
||||||
|
DnsKeyPair pair = new DnsKeyPair(name, algorithm, key, null);
|
||||||
|
add(pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DnsKeyPair> find(Name name, int algorithm, int keyid) {
|
||||||
|
String n = name.toString().toLowerCase();
|
||||||
|
List<DnsKeyPair> l = mKeyMap.get(n);
|
||||||
|
List<DnsKeyPair> result = new ArrayList<>();
|
||||||
|
if (l == null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
for (DnsKeyPair p : l) {
|
||||||
|
if (p.getDNSKEYAlgorithm() == algorithm && p.getDNSKEYFootprint() == keyid) {
|
||||||
|
result.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TrustedKeyStore mKeyStore;
|
||||||
|
private int mStartFudge = 0;
|
||||||
|
private int mExpireFudge = 0;
|
||||||
|
private boolean mVerifyAllSigs = false;
|
||||||
|
private boolean mIgnoreTime = false;
|
||||||
|
private Instant mCurrentTime = null;
|
||||||
|
|
||||||
|
private Logger log;
|
||||||
|
|
||||||
|
public DnsSecVerifier() {
|
||||||
|
log = Logger.getLogger(this.getClass().toString());
|
||||||
|
|
||||||
|
mKeyStore = new TrustedKeyStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTrustedKey(DNSKEYRecord keyrec) {
|
||||||
|
mKeyStore.add(keyrec);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTrustedKey(DnsKeyPair pair) {
|
||||||
|
mKeyStore.add(pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTrustedKey(Name name, int algorithm, PublicKey key) {
|
||||||
|
mKeyStore.add(name, algorithm, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTrustedKey(Name name, PublicKey key) {
|
||||||
|
mKeyStore.add(name, 0, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpireFudge(int fudge) {
|
||||||
|
mExpireFudge = fudge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartFudge(int fudge) {
|
||||||
|
mStartFudge = fudge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVerifyAllSigs(boolean v) {
|
||||||
|
mVerifyAllSigs = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIgnoreTime(boolean v) {
|
||||||
|
mIgnoreTime = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentTime(Instant time) {
|
||||||
|
mCurrentTime = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DnsKeyPair> findKey(Name name, int algorithm, int footprint) {
|
||||||
|
return mKeyStore.find(name, algorithm, footprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateSignature(RRset rrset, RRSIGRecord sigrec, List<String> reasons) {
|
||||||
|
if (rrset == null || sigrec == null)
|
||||||
|
return false;
|
||||||
|
if (!rrset.getName().equals(sigrec.getName())) {
|
||||||
|
log.fine("Signature name does not match RRset name");
|
||||||
|
if (reasons != null)
|
||||||
|
reasons.add("Signature name does not match RRset name");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (rrset.getType() != sigrec.getTypeCovered()) {
|
||||||
|
log.fine("Signature type does not match RRset type");
|
||||||
|
if (reasons != null)
|
||||||
|
reasons.add("Signature type does not match RRset type");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIgnoreTime)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Instant now;
|
||||||
|
if (mCurrentTime != null) {
|
||||||
|
now = mCurrentTime;
|
||||||
|
} else {
|
||||||
|
now = Instant.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
Instant start = sigrec.getTimeSigned();
|
||||||
|
Instant expire = sigrec.getExpire();
|
||||||
|
|
||||||
|
if (mStartFudge >= 0) {
|
||||||
|
if (mStartFudge > 0) {
|
||||||
|
start = start.minusSeconds(mStartFudge);
|
||||||
|
}
|
||||||
|
if (now.isBefore(start)) {
|
||||||
|
log.fine("Signature is not yet valid");
|
||||||
|
if (reasons != null)
|
||||||
|
reasons.add("Signature not yet valid");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mExpireFudge >= 0) {
|
||||||
|
if (mExpireFudge > 0) {
|
||||||
|
expire = expire.plusSeconds(mExpireFudge);
|
||||||
|
}
|
||||||
|
if (now.isAfter(expire)) {
|
||||||
|
log.fine("Signature has expired (now = " + now + ", sig expires = " + expire);
|
||||||
|
if (reasons != null)
|
||||||
|
reasons.add("Signature has expired.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rrset.getTTL() > sigrec.getOrigTTL()) {
|
||||||
|
log.fine("RRset's TTL is greater than the Signature's orignal TTL");
|
||||||
|
if (reasons != null)
|
||||||
|
reasons.add("RRset TTL greater than RRSIG origTTL");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean verifySignature(RRset rrset, RRSIGRecord sigrec) {
|
||||||
|
return verifySignature(rrset, sigrec, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify an RRset against a particular signature.
|
||||||
|
*
|
||||||
|
* @return true if the signature verified, false if it did
|
||||||
|
* not verify (for any reason, including not finding the DNSKEY.)
|
||||||
|
*/
|
||||||
|
public boolean verifySignature(RRset rrset, RRSIGRecord sigrec, List<String> reasons) {
|
||||||
|
boolean result = validateSignature(rrset, sigrec, reasons);
|
||||||
|
if (!result)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
List<DnsKeyPair> keypairs = findKey(sigrec.getSigner(), sigrec.getAlgorithm(),
|
||||||
|
sigrec.getFootprint());
|
||||||
|
|
||||||
|
if (keypairs.isEmpty()) {
|
||||||
|
if (reasons != null)
|
||||||
|
reasons.add("Could not find matching trusted key");
|
||||||
|
log.fine("could not find matching trusted key");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
byte[] data = SignUtils.generateSigData(rrset, sigrec);
|
||||||
|
|
||||||
|
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
||||||
|
|
||||||
|
// Tolerate duplicate keytags, so we can have more than one DnsKeyPair
|
||||||
|
List<String> localReasons = new ArrayList<>();
|
||||||
|
boolean validated = false;
|
||||||
|
for (DnsKeyPair keypair : keypairs) {
|
||||||
|
Signature signer = keypair.getVerifier();
|
||||||
|
signer.update(data);
|
||||||
|
|
||||||
|
byte[] sig = sigrec.getSignature();
|
||||||
|
|
||||||
|
if (algs.baseType(sigrec.getAlgorithm()) == DnsKeyAlgorithm.BaseAlgorithm.DSA) {
|
||||||
|
sig = SignUtils.convertDSASignature(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP256SHA256 ||
|
||||||
|
sigrec.getAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384) {
|
||||||
|
sig = SignUtils.convertECDSASignature(sig);
|
||||||
|
}
|
||||||
|
if (signer.verify(sig)) {
|
||||||
|
validated = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
log.fine("Signature failed to validate cryptographically with " + keypair);
|
||||||
|
if (localReasons != null) {
|
||||||
|
localReasons.add("Signature failed to verify cryptographically with " + keypair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validated) {
|
||||||
|
reasons.addAll(localReasons);
|
||||||
|
}
|
||||||
|
return validated;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.severe("I/O error: " + e);
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
log.severe("Security error: " + e);
|
||||||
|
}
|
||||||
|
if (reasons != null)
|
||||||
|
reasons.add("Signature failed to verify due to exception");
|
||||||
|
log.fine("Signature failed to verify due to exception");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies an RRset. This routine does not modify the RRset.
|
||||||
|
*
|
||||||
|
* @return true if the set verified, false if it did not.
|
||||||
|
*/
|
||||||
|
public boolean verify(RRset rrset) {
|
||||||
|
boolean result = mVerifyAllSigs;
|
||||||
|
|
||||||
|
if (rrset.sigs().isEmpty()) {
|
||||||
|
log.fine("RRset failed to verify due to lack of signatures");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (RRSIGRecord sigrec : rrset.sigs()) {
|
||||||
|
|
||||||
|
boolean res = verifySignature(rrset, sigrec);
|
||||||
|
|
||||||
|
// If not requiring all signature to validate, then any successful validation is
|
||||||
|
// sufficient.
|
||||||
|
if (!mVerifyAllSigs && res)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
// Otherwise, note if a signature failed to validate.
|
||||||
|
if (mVerifyAllSigs && !res) {
|
||||||
|
result = res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,529 @@
|
|||||||
|
// Copyright (C) 2001-2003, 2009, 2022 Verisign, Inc.
|
||||||
|
//
|
||||||
|
// 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.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.security.interfaces.DSAPublicKey;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.xbill.DNS.DNSKEYRecord;
|
||||||
|
import org.xbill.DNS.DNSSEC;
|
||||||
|
import org.xbill.DNS.Name;
|
||||||
|
import org.xbill.DNS.RRSIGRecord;
|
||||||
|
import org.xbill.DNS.RRset;
|
||||||
|
import org.xbill.DNS.Record;
|
||||||
|
import org.xbill.DNS.Type;
|
||||||
|
import org.xbill.DNS.utils.hexdump;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains routines for signing DNS zones.
|
||||||
|
*
|
||||||
|
* In particular, it contains both an ability to sign an individual RRset and
|
||||||
|
* the ability to sign an entire zone. It primarily glues together the more
|
||||||
|
* basic primitives found in {@link SignUtils}.
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class JCEDnsSecSigner {
|
||||||
|
private DnsKeyConverter mKeyConverter;
|
||||||
|
private boolean mVerboseSigning = false;
|
||||||
|
|
||||||
|
private Logger log = Logger.getLogger(this.getClass().toString());
|
||||||
|
|
||||||
|
public JCEDnsSecSigner() {
|
||||||
|
this.mKeyConverter = null;
|
||||||
|
this.mVerboseSigning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JCEDnsSecSigner(boolean verboseSigning) {
|
||||||
|
super();
|
||||||
|
this.mVerboseSigning = verboseSigning;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cryptographically generate a new DNSSEC key.
|
||||||
|
*
|
||||||
|
* @param owner the KEY RR's owner name.
|
||||||
|
* @param ttl the KEY RR's TTL.
|
||||||
|
* @param dclass the KEY RR's DNS class.
|
||||||
|
* @param algorithm the DNSSEC algorithm (RSASHA258, RSASHA512,
|
||||||
|
* ECDSAP256, etc.)
|
||||||
|
* @param flags any flags for the KEY RR.
|
||||||
|
* @param keysize the size of the key to generate.
|
||||||
|
* @param useLargeExponent if generating an RSA key, use the large exponent.
|
||||||
|
* @return a DnsKeyPair with the public and private keys populated.
|
||||||
|
*/
|
||||||
|
public DnsKeyPair generateKey(Name owner, long ttl, int dclass, int algorithm,
|
||||||
|
int flags, int keysize, boolean useLargeExponent)
|
||||||
|
throws NoSuchAlgorithmException {
|
||||||
|
DnsKeyAlgorithm algorithms = DnsKeyAlgorithm.getInstance();
|
||||||
|
|
||||||
|
if (ttl < 0)
|
||||||
|
ttl = 86400; // set to a reasonable default.
|
||||||
|
|
||||||
|
KeyPair pair = algorithms.generateKeyPair(algorithm, keysize, useLargeExponent);
|
||||||
|
|
||||||
|
if (mKeyConverter == null) {
|
||||||
|
mKeyConverter = new DnsKeyConverter();
|
||||||
|
}
|
||||||
|
|
||||||
|
DNSKEYRecord keyrec = mKeyConverter.generateDNSKEYRecord(owner, dclass, ttl, flags,
|
||||||
|
algorithm, pair.getPublic());
|
||||||
|
|
||||||
|
DnsKeyPair dnspair = new DnsKeyPair();
|
||||||
|
dnspair.setDNSKEYRecord(keyrec);
|
||||||
|
dnspair.setPublic(pair.getPublic()); // keep from conv. the keyrec back.
|
||||||
|
dnspair.setPrivate(pair.getPrivate());
|
||||||
|
|
||||||
|
return dnspair;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign an RRset.
|
||||||
|
*
|
||||||
|
* @param rrset the RRset to sign -- any existing signatures are ignored.
|
||||||
|
* @param keypars a list of DnsKeyPair objects containing private keys.
|
||||||
|
* @param start the inception time for the resulting RRSIG records.
|
||||||
|
* @param expire the expiration time for the resulting RRSIG records.
|
||||||
|
* @return a list of RRSIGRecord objects.
|
||||||
|
*/
|
||||||
|
public List<RRSIGRecord> signRRset(RRset rrset, List<DnsKeyPair> keypairs, Instant start,
|
||||||
|
Instant expire) throws IOException,
|
||||||
|
GeneralSecurityException {
|
||||||
|
if (rrset == null || keypairs == null)
|
||||||
|
return new ArrayList<>();
|
||||||
|
|
||||||
|
// default start to now, expire to start + 1 second.
|
||||||
|
if (start == null)
|
||||||
|
start = Instant.now();
|
||||||
|
if (expire == null)
|
||||||
|
expire = start.plusSeconds(1);
|
||||||
|
if (keypairs.isEmpty())
|
||||||
|
return new ArrayList<>();
|
||||||
|
|
||||||
|
if (mVerboseSigning) {
|
||||||
|
log.info("Signing RRset:");
|
||||||
|
log.info(ZoneUtils.rrsetToString(rrset, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// first, pre-calculate the RRset bytes.
|
||||||
|
byte[] rrsetData = SignUtils.generateCanonicalRRsetData(rrset, 0, 0);
|
||||||
|
|
||||||
|
ArrayList<RRSIGRecord> sigs = new ArrayList<>(keypairs.size());
|
||||||
|
|
||||||
|
// for each keypair, sign the RRset.
|
||||||
|
for (DnsKeyPair pair : keypairs) {
|
||||||
|
DNSKEYRecord keyrec = pair.getDNSKEYRecord();
|
||||||
|
if (keyrec == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
RRSIGRecord presig = SignUtils.generatePreRRSIG(rrset, keyrec, start, expire,
|
||||||
|
rrset.getTTL());
|
||||||
|
byte[] signData = SignUtils.generateSigData(rrsetData, presig);
|
||||||
|
|
||||||
|
if (mVerboseSigning) {
|
||||||
|
log.info("Canonical pre-signature data to sign with key "
|
||||||
|
+ keyrec.getName().toString() + "/" + keyrec.getAlgorithm() + "/"
|
||||||
|
+ keyrec.getFootprint() + ":");
|
||||||
|
log.info(hexdump.dump(null, signData));
|
||||||
|
}
|
||||||
|
|
||||||
|
Signature signer = pair.getSigner();
|
||||||
|
|
||||||
|
if (signer == null) {
|
||||||
|
// debug
|
||||||
|
log.fine("missing private key that goes with:\n" + pair.getDNSKEYRecord());
|
||||||
|
throw new GeneralSecurityException("cannot sign without a valid Signer "
|
||||||
|
+ "(probably missing private key)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// sign the data.
|
||||||
|
signer.update(signData);
|
||||||
|
byte[] sig = signer.sign();
|
||||||
|
|
||||||
|
if (mVerboseSigning) {
|
||||||
|
log.info("Raw Signature:");
|
||||||
|
log.info(hexdump.dump(null, sig));
|
||||||
|
}
|
||||||
|
|
||||||
|
DnsKeyAlgorithm algs = DnsKeyAlgorithm.getInstance();
|
||||||
|
// Convert to RFC 2536 format, if necessary.
|
||||||
|
if (algs.baseType(pair.getDNSKEYAlgorithm()) == DnsKeyAlgorithm.BaseAlgorithm.DSA) {
|
||||||
|
DSAPublicKey pk = (DSAPublicKey) pair.getPublic();
|
||||||
|
sig = SignUtils.convertDSASignature(pk.getParams(), sig);
|
||||||
|
}
|
||||||
|
// Convert to RFC 6605, etc format
|
||||||
|
if (pair.getDNSKEYAlgorithm() == DNSSEC.Algorithm.ECDSAP256SHA256 ||
|
||||||
|
pair.getDNSKEYAlgorithm() == DNSSEC.Algorithm.ECDSAP384SHA384) {
|
||||||
|
sig = SignUtils.convertECDSASignature(pair.getDNSKEYAlgorithm(), sig);
|
||||||
|
}
|
||||||
|
RRSIGRecord sigrec = SignUtils.generateRRSIG(sig, presig);
|
||||||
|
if (mVerboseSigning) {
|
||||||
|
log.info("RRSIG:\n" + sigrec);
|
||||||
|
}
|
||||||
|
sigs.add(sigrec);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sigs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a completely self-signed DNSKEY RRset.
|
||||||
|
*
|
||||||
|
* @param keypairs the public & private keypairs to use in the keyset.
|
||||||
|
* @param start the RRSIG inception time.
|
||||||
|
* @param expire the RRSIG expiration time.
|
||||||
|
* @return a signed RRset.
|
||||||
|
*/
|
||||||
|
public RRset makeKeySet(List<DnsKeyPair> keypairs, Instant start, Instant expire)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
// Generate a KEY RR set to sign.
|
||||||
|
|
||||||
|
RRset keyset = new RRset();
|
||||||
|
|
||||||
|
for (DnsKeyPair pair : keypairs) {
|
||||||
|
keyset.addRR(pair.getDNSKEYRecord());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RRSIGRecord> records = signRRset(keyset, keypairs, start, expire);
|
||||||
|
|
||||||
|
for (RRSIGRecord r : records) {
|
||||||
|
keyset.addRR(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditionally sign an RRset and add it to the toList.
|
||||||
|
*
|
||||||
|
* @param toList the list to which we are adding the processed RRsets.
|
||||||
|
* @param zonename the zone apex name.
|
||||||
|
* @param rrset the RRset under consideration.
|
||||||
|
* @param kskpairs the List of KSKs..
|
||||||
|
* @param zskpairs the List of zone keys.
|
||||||
|
* @param start the RRSIG inception time.
|
||||||
|
* @param expire the RRSIG expiration time.
|
||||||
|
* @param fullySignKeyset if true, sign the zone apex keyset with both KSKs
|
||||||
|
* and ZSKs.
|
||||||
|
* @param lastCut the name of the last delegation point encountered.
|
||||||
|
*
|
||||||
|
* @return the name of the new last_cut.
|
||||||
|
*/
|
||||||
|
private Name addRRset(List<Record> toList, Name zonename, RRset rrset,
|
||||||
|
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs, Instant start,
|
||||||
|
Instant expire, boolean fullySignKeyset, Name lastCut,
|
||||||
|
Name lastDname) throws IOException, GeneralSecurityException {
|
||||||
|
// add the records themselves
|
||||||
|
rrset.rrs().forEach(toList::add);
|
||||||
|
|
||||||
|
int type = SignUtils.recordSecType(zonename, rrset.getName(), rrset.getType(),
|
||||||
|
lastCut, lastDname);
|
||||||
|
|
||||||
|
// we don't sign non-normal sets (delegations, glue, invalid).
|
||||||
|
if (type == SignUtils.RR_DELEGATION) {
|
||||||
|
return rrset.getName();
|
||||||
|
}
|
||||||
|
if (type == SignUtils.RR_GLUE || type == SignUtils.RR_INVALID) {
|
||||||
|
return lastCut;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for the zone apex keyset.
|
||||||
|
if (rrset.getName().equals(zonename) && rrset.getType() == Type.DNSKEY && kskpairs != null && !kskpairs.isEmpty()) {
|
||||||
|
// if we have ksks, sign the keyset with them, otherwise we will just sign
|
||||||
|
// them with the zsks.
|
||||||
|
List<RRSIGRecord> sigs = signRRset(rrset, kskpairs, start, expire);
|
||||||
|
toList.addAll(sigs);
|
||||||
|
|
||||||
|
// If we aren't going to sign with all the keys, bail out now.
|
||||||
|
if (!fullySignKeyset)
|
||||||
|
return lastCut;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, we are OK to sign this set.
|
||||||
|
List<RRSIGRecord> sigs = signRRset(rrset, zskpairs, start, expire);
|
||||||
|
toList.addAll(sigs);
|
||||||
|
|
||||||
|
return lastCut;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Various NSEC/NSEC3 generation modes
|
||||||
|
private static final int NSEC_MODE = 0;
|
||||||
|
private static final int NSEC3_MODE = 1;
|
||||||
|
private static final int NSEC3_OPTOUT_MODE = 2;
|
||||||
|
private static final int NSEC_EXP_OPT_IN = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Master zone signing method. This method handles all of the different zone
|
||||||
|
* signing variants (NSEC with or without Opt-In, NSEC3 with or without
|
||||||
|
* Opt-Out, etc.) External users of this class are expected to use the
|
||||||
|
* appropriate public signZone* methods instead of this.
|
||||||
|
*
|
||||||
|
* @param zonename The name of the zone
|
||||||
|
* @param records The records comprising the zone. They do not have to
|
||||||
|
* be in any particular order, as this method will
|
||||||
|
* order them as necessary.
|
||||||
|
* @param kskpairs The key pairs designated as "key signing keys"
|
||||||
|
* @param zskpairs The key pairs designated as "zone signing keys"
|
||||||
|
* @param start The RRSIG inception time
|
||||||
|
* @param expire The RRSIG expiration time
|
||||||
|
* @param fullySignKeyset If true, all keys (ksk or zsk) will sign the DNSKEY
|
||||||
|
* RRset. If false, only the ksks will sign it.
|
||||||
|
* @param dsDigestAlg The hash algorithm to use for generating DS records
|
||||||
|
* (DSRecord.SHA1_DIGEST_ID, e.g.)
|
||||||
|
* @param mode The NSEC/NSEC3 generation mode: NSEC_MODE,
|
||||||
|
* NSEC3_MODE, NSEC3_OPTOUT_MODE, etc.
|
||||||
|
* @param includedNames When using an Opt-In/Opt-Out mode, the names listed
|
||||||
|
* here will be included in the NSEC/NSEC3 chain
|
||||||
|
* regardless
|
||||||
|
* @param salt When using an NSEC3 mode, use this salt.
|
||||||
|
* @param iterations When using an NSEC3 mode, use this number of
|
||||||
|
* iterations
|
||||||
|
* @param beConservative If true, then only turn on the Opt-In flag when
|
||||||
|
* there are insecure delegations in the span.
|
||||||
|
* Currently this only works for NSEC_EXP_OPT_IN mode.
|
||||||
|
* @param nsec3paramttl The TTL to use for the generated NSEC3PARAM record.
|
||||||
|
* Negative values will use the SOA TTL.
|
||||||
|
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
|
||||||
|
* representing the signed zone.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws GeneralSecurityException
|
||||||
|
*/
|
||||||
|
private List<Record> signZone(Name zonename, List<Record> records,
|
||||||
|
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
|
||||||
|
Instant start, Instant expire, boolean fullySignKeyset,
|
||||||
|
int dsDigestAlg, int mode, List<Name> includedNames,
|
||||||
|
byte[] salt, int iterations, long nsec3paramttl,
|
||||||
|
boolean beConservative) throws IOException,
|
||||||
|
GeneralSecurityException {
|
||||||
|
// Remove any existing generated DNSSEC records (NSEC, NSEC3, NSEC3PARAM,
|
||||||
|
// RRSIG)
|
||||||
|
SignUtils.removeGeneratedRecords(zonename, records);
|
||||||
|
|
||||||
|
RecordComparator rc = new RecordComparator();
|
||||||
|
// Sort the zone
|
||||||
|
Collections.sort(records, rc);
|
||||||
|
|
||||||
|
// Remove duplicate records
|
||||||
|
SignUtils.removeDuplicateRecords(records);
|
||||||
|
|
||||||
|
// Generate DS records. This replaces any non-zone-apex DNSKEY RRs with DS RRs.
|
||||||
|
// This is not a common practice, so this step may be dropped in the future.
|
||||||
|
SignUtils.generateDSRecords(zonename, records, dsDigestAlg);
|
||||||
|
|
||||||
|
// Generate the NSEC or NSEC3 records based on 'mode'
|
||||||
|
switch (mode) {
|
||||||
|
case NSEC_MODE:
|
||||||
|
SignUtils.generateNSECRecords(zonename, records);
|
||||||
|
break;
|
||||||
|
case NSEC3_MODE:
|
||||||
|
SignUtils.generateNSEC3Records(zonename, records, salt, iterations, nsec3paramttl);
|
||||||
|
break;
|
||||||
|
case NSEC3_OPTOUT_MODE:
|
||||||
|
SignUtils.generateOptOutNSEC3Records(zonename, records, includedNames, salt,
|
||||||
|
iterations, nsec3paramttl);
|
||||||
|
break;
|
||||||
|
case NSEC_EXP_OPT_IN:
|
||||||
|
SignUtils.generateOptInNSECRecords(zonename, records, includedNames,
|
||||||
|
beConservative);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NoSuchAlgorithmException("Unknown NSEC/NSEC3 mode: " + mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-sort so we can assemble into rrsets.
|
||||||
|
Collections.sort(records, rc);
|
||||||
|
|
||||||
|
// Assemble into RRsets and sign.
|
||||||
|
RRset rrset = new RRset();
|
||||||
|
ArrayList<Record> signedRecords = new ArrayList<>();
|
||||||
|
Name lastCut = null;
|
||||||
|
Name lastDname = null;
|
||||||
|
|
||||||
|
for (ListIterator<Record> i = records.listIterator(); i.hasNext();) {
|
||||||
|
Record r = i.next();
|
||||||
|
|
||||||
|
// First record
|
||||||
|
if (rrset.size() == 0) {
|
||||||
|
rrset.addRR(r);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current record is part of the current RRset.
|
||||||
|
if (rrset.getName().equals(r.getName()) && rrset.getDClass() == r.getDClass()
|
||||||
|
&& rrset.getType() == r.getType()) {
|
||||||
|
rrset.addRR(r);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we have completed the RRset
|
||||||
|
// Sign the records
|
||||||
|
|
||||||
|
// add the RRset to the list of signed_records, regardless of
|
||||||
|
// whether or not we actually end up signing the set.
|
||||||
|
lastCut = addRRset(signedRecords, zonename, rrset, kskpairs, zskpairs, start,
|
||||||
|
expire, fullySignKeyset, lastCut, lastDname);
|
||||||
|
if (rrset.getType() == Type.DNAME)
|
||||||
|
lastDname = rrset.getName();
|
||||||
|
|
||||||
|
rrset.clear();
|
||||||
|
rrset.addRR(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the last RR set
|
||||||
|
addRRset(signedRecords, zonename, rrset, kskpairs, zskpairs, start, expire,
|
||||||
|
fullySignKeyset, lastCut, lastDname);
|
||||||
|
|
||||||
|
return signedRecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a zone, sign it using standard NSEC records.
|
||||||
|
*
|
||||||
|
* @param zonename The name of the zone.
|
||||||
|
* @param records The records comprising the zone. They do not have to
|
||||||
|
* be in any particular order, as this method will
|
||||||
|
* order them as necessary.
|
||||||
|
* @param kskpairs The key pairs that are designated as "key signing
|
||||||
|
* keys".
|
||||||
|
* @param zskpairs This key pairs that are designated as "zone signing
|
||||||
|
* keys".
|
||||||
|
* @param start The RRSIG inception time.
|
||||||
|
* @param expire The RRSIG expiration time.
|
||||||
|
* @param fullySignKeyset Sign the zone apex keyset with all available keys
|
||||||
|
* (instead of just the key signing keys).
|
||||||
|
* @param dsDigestAlg The digest algorithm to use when generating DS
|
||||||
|
* records.
|
||||||
|
*
|
||||||
|
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
|
||||||
|
* representing the signed zone.
|
||||||
|
*/
|
||||||
|
public List<Record> signZone(Name zonename, List<Record> records,
|
||||||
|
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
|
||||||
|
Instant start, Instant expire, boolean fullySignKeyset,
|
||||||
|
int dsDigestAlg) throws IOException,
|
||||||
|
GeneralSecurityException {
|
||||||
|
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
|
||||||
|
fullySignKeyset, dsDigestAlg, NSEC_MODE, null, null, 0, 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a zone, sign it using NSEC3 records.
|
||||||
|
*
|
||||||
|
* @param signer A signer (utility) object used to actually sign
|
||||||
|
* stuff.
|
||||||
|
* @param zonename The name of the zone being signed.
|
||||||
|
* @param records The records comprising the zone. They do not have to
|
||||||
|
* be in any particular order, as this method will
|
||||||
|
* order them as necessary.
|
||||||
|
* @param kskpairs The key pairs that are designated as "key signing
|
||||||
|
* keys".
|
||||||
|
* @param zskpairs This key pairs that are designated as "zone signing
|
||||||
|
* keys".
|
||||||
|
* @param start The RRSIG inception time.
|
||||||
|
* @param expire The RRSIG expiration time.
|
||||||
|
* @param fullySignKeyset If true then the DNSKEY RRset will be signed by all
|
||||||
|
* available keys, if false, only the key signing keys.
|
||||||
|
* @param useOptOut If true, insecure delegations will be omitted from
|
||||||
|
* the NSEC3 chain, and all NSEC3 records will have the
|
||||||
|
* Opt-Out flag set.
|
||||||
|
* @param includedNames A list of names to include in the NSEC3 chain
|
||||||
|
* regardless.
|
||||||
|
* @param salt The salt to use for the NSEC3 hashing. null means no
|
||||||
|
* salt.
|
||||||
|
* @param iterations The number of iterations to use for the NSEC3
|
||||||
|
* hashing.
|
||||||
|
* @param dsDigestAlg The digest algorithm to use when generating DS
|
||||||
|
* records.
|
||||||
|
* @param nsec3paramttl The TTL to use for the generated NSEC3PARAM record.
|
||||||
|
* Negative values will use the SOA TTL.
|
||||||
|
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
|
||||||
|
* representing the signed zone.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
* @throws GeneralSecurityException
|
||||||
|
*/
|
||||||
|
public List<Record> signZoneNSEC3(Name zonename, List<Record> records,
|
||||||
|
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
|
||||||
|
Instant start, Instant expire, boolean fullySignKeyset,
|
||||||
|
boolean useOptOut, List<Name> includedNames,
|
||||||
|
byte[] salt, int iterations, int dsDigestAlg,
|
||||||
|
long nsec3paramttl) throws IOException,
|
||||||
|
GeneralSecurityException {
|
||||||
|
if (useOptOut) {
|
||||||
|
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
|
||||||
|
fullySignKeyset, dsDigestAlg, NSEC3_OPTOUT_MODE, includedNames,
|
||||||
|
salt, iterations, nsec3paramttl, false);
|
||||||
|
} else {
|
||||||
|
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
|
||||||
|
fullySignKeyset, dsDigestAlg, NSEC3_MODE, null, salt, iterations,
|
||||||
|
nsec3paramttl, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a zone, sign it using experimental Opt-In NSEC records (see RFC
|
||||||
|
* 4956).
|
||||||
|
*
|
||||||
|
* @param zonename the name of the zone.
|
||||||
|
* @param records the records comprising the zone. They do not
|
||||||
|
* have to be in any particular order, as this
|
||||||
|
* method will order them as necessary.
|
||||||
|
* @param kskpairs the key pairs that are designated as "key
|
||||||
|
* signing keys".
|
||||||
|
* @param zskpairs this key pairs that are designated as "zone
|
||||||
|
* signing keys".
|
||||||
|
* @param start the RRSIG inception time.
|
||||||
|
* @param expire the RRSIG expiration time.
|
||||||
|
* @param useConservativeOptIn if true, Opt-In NSEC records will only be
|
||||||
|
* generated if there are insecure, unsigned
|
||||||
|
* delegations in the span.
|
||||||
|
* @param fullySignKeyset sign the zone apex keyset with all available
|
||||||
|
* keys.
|
||||||
|
* @param dsDigestAlg The digest algorithm to use when generating DS
|
||||||
|
* records.
|
||||||
|
* @param nsecIncludeNames names that are to be included in the NSEC chain
|
||||||
|
* regardless. This may be null.
|
||||||
|
* @return an ordered list of {@link org.xbill.DNS.Record} objects,
|
||||||
|
* representing the signed zone.
|
||||||
|
*/
|
||||||
|
public List<Record> signZoneOptIn(Name zonename, List<Record> records,
|
||||||
|
List<DnsKeyPair> kskpairs, List<DnsKeyPair> zskpairs,
|
||||||
|
Instant start, Instant expire,
|
||||||
|
boolean useConservativeOptIn,
|
||||||
|
boolean fullySignKeyset, List<Name> nsecIncludeNames,
|
||||||
|
int dsDigestAlg) throws IOException,
|
||||||
|
GeneralSecurityException {
|
||||||
|
|
||||||
|
return signZone(zonename, records, kskpairs, zskpairs, start, expire,
|
||||||
|
fullySignKeyset, dsDigestAlg, NSEC_EXP_OPT_IN, nsecIncludeNames,
|
||||||
|
null, 0, 0, useConservativeOptIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* $Id$
|
* Copyright (c) 2005, 2022 Verisign. All rights reserved.
|
||||||
*
|
|
||||||
* Copyright (c) 2005 VeriSign. All rights reserved.
|
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions are met:
|
* modification, are permitted provided that the following conditions are met:
|
||||||
@@ -29,7 +27,10 @@
|
|||||||
|
|
||||||
package com.verisignlabs.dnssec.security;
|
package com.verisignlabs.dnssec.security;
|
||||||
|
|
||||||
import org.xbill.DNS.*;
|
import org.xbill.DNS.DClass;
|
||||||
|
import org.xbill.DNS.NSEC3Record;
|
||||||
|
import org.xbill.DNS.Name;
|
||||||
|
import org.xbill.DNS.TextParseException;
|
||||||
import org.xbill.DNS.utils.base16;
|
import org.xbill.DNS.utils.base16;
|
||||||
import org.xbill.DNS.utils.base32;
|
import org.xbill.DNS.utils.base32;
|
||||||
|
|
||||||
@@ -38,24 +39,21 @@ import org.xbill.DNS.utils.base32;
|
|||||||
* used as an intermediate stage (in zone signing) between determining the list
|
* used as an intermediate stage (in zone signing) between determining the list
|
||||||
* of NSEC3 records and forming them into a viable chain.
|
* of NSEC3 records and forming them into a viable chain.
|
||||||
*
|
*
|
||||||
* @author David Blacka (original)
|
* @author David Blacka
|
||||||
* @author $Author: davidb $
|
|
||||||
* @version $Revision: 183 $
|
|
||||||
*/
|
*/
|
||||||
public class ProtoNSEC3
|
public class ProtoNSEC3 {
|
||||||
{
|
private Name originalOwner;
|
||||||
private Name originalOwner;
|
private int hashAlg;
|
||||||
private byte hashAlg;
|
private byte flags;
|
||||||
private byte flags;
|
private int iterations;
|
||||||
private int iterations;
|
private byte[] salt;
|
||||||
private byte[] salt;
|
private byte[] next;
|
||||||
private byte[] next;
|
private byte[] owner; // cached numerical owner value.
|
||||||
private byte[] owner; // cached numerical owner value.
|
private TypeMap typemap;
|
||||||
private TypeMap typemap;
|
private Name zone;
|
||||||
private Name zone;
|
private Name name;
|
||||||
private Name name;
|
private int dclass;
|
||||||
private int dclass;
|
private long ttl;
|
||||||
private long ttl;
|
|
||||||
|
|
||||||
private static final base32 b32 = new base32(base32.Alphabet.BASE32HEX, false, false);
|
private static final base32 b32 = new base32(base32.Alphabet.BASE32HEX, false, false);
|
||||||
|
|
||||||
@@ -63,9 +61,8 @@ public class ProtoNSEC3
|
|||||||
* Creates an NSEC3 Record from the given data.
|
* Creates an NSEC3 Record from the given data.
|
||||||
*/
|
*/
|
||||||
public ProtoNSEC3(byte[] owner, Name originalOwner, Name zone, int dclass, long ttl,
|
public ProtoNSEC3(byte[] owner, Name originalOwner, Name zone, int dclass, long ttl,
|
||||||
byte hashAlg, byte flags, int iterations, byte[] salt, byte[] next,
|
int hashAlg, byte flags, int iterations, byte[] salt, byte[] next,
|
||||||
TypeMap typemap)
|
TypeMap typemap) {
|
||||||
{
|
|
||||||
this.zone = zone;
|
this.zone = zone;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.dclass = dclass;
|
this.dclass = dclass;
|
||||||
@@ -80,29 +77,23 @@ public class ProtoNSEC3
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ProtoNSEC3(byte[] owner, Name originalOwner, Name zone, int dclass, long ttl,
|
public ProtoNSEC3(byte[] owner, Name originalOwner, Name zone, int dclass, long ttl,
|
||||||
byte hashAlg, byte flags, int iterations, byte[] salt, byte[] next,
|
int hashAlg, byte flags, int iterations, byte[] salt, byte[] next,
|
||||||
int[] types)
|
int[] types) {
|
||||||
{
|
|
||||||
this(owner, originalOwner, zone, dclass, ttl, hashAlg, flags, iterations, salt, next,
|
this(owner, originalOwner, zone, dclass, ttl, hashAlg, flags, iterations, salt, next,
|
||||||
TypeMap.fromTypes(types));
|
TypeMap.fromTypes(types));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String hashToString(byte[] hash)
|
private String hashToString(byte[] hash) {
|
||||||
{
|
if (hash == null)
|
||||||
if (hash == null) return null;
|
return null;
|
||||||
return b32.toString(hash);
|
return b32.toString(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Name getName()
|
public Name getName() {
|
||||||
{
|
if (name == null) {
|
||||||
if (name == null)
|
try {
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
name = new Name(hashToString(owner), zone);
|
name = new Name(hashToString(owner), zone);
|
||||||
}
|
} catch (TextParseException e) {
|
||||||
catch (TextParseException e)
|
|
||||||
{
|
|
||||||
// This isn't going to happen.
|
// This isn't going to happen.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,122 +101,104 @@ public class ProtoNSEC3
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getNext()
|
public Name getOriginalOwnerName() {
|
||||||
{
|
return this.originalOwner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getNext() {
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNext(byte[] next)
|
public void setNext(byte[] next) {
|
||||||
{
|
|
||||||
this.next = next;
|
this.next = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte getFlags()
|
public byte getFlags() {
|
||||||
{
|
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getOptOutFlag()
|
public boolean getOptOutFlag() {
|
||||||
{
|
|
||||||
return (flags & NSEC3Record.Flags.OPT_OUT) != 0;
|
return (flags & NSEC3Record.Flags.OPT_OUT) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOptOutFlag(boolean optOutFlag)
|
public void setOptOutFlag(boolean optOutFlag) {
|
||||||
{
|
|
||||||
if (optOutFlag)
|
if (optOutFlag)
|
||||||
this.flags |= NSEC3Record.Flags.OPT_OUT;
|
this.flags |= NSEC3Record.Flags.OPT_OUT;
|
||||||
else
|
else
|
||||||
this.flags &= ~NSEC3Record.Flags.OPT_OUT;
|
this.flags &= ~NSEC3Record.Flags.OPT_OUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTTL()
|
public long getTTL() {
|
||||||
{
|
|
||||||
return ttl;
|
return ttl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTTL(long ttl)
|
public void setTTL(long ttl) {
|
||||||
{
|
|
||||||
this.ttl = ttl;
|
this.ttl = ttl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypeMap getTypemap()
|
public TypeMap getTypemap() {
|
||||||
{
|
|
||||||
return typemap;
|
return typemap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] getTypes()
|
public int[] getTypes() {
|
||||||
{
|
|
||||||
return typemap.getTypes();
|
return typemap.getTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTypemap(TypeMap typemap)
|
public void setTypemap(TypeMap typemap) {
|
||||||
{
|
|
||||||
this.typemap = typemap;
|
this.typemap = typemap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getDClass()
|
public int getDClass() {
|
||||||
{
|
|
||||||
return dclass;
|
return dclass;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte getHashAlgorithm()
|
public int getHashAlgorithm() {
|
||||||
{
|
|
||||||
return hashAlg;
|
return hashAlg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getIterations()
|
public int getIterations() {
|
||||||
{
|
|
||||||
return iterations;
|
return iterations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getOwner()
|
public byte[] getOwner() {
|
||||||
{
|
|
||||||
return owner;
|
return owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getSalt()
|
public byte[] getSalt() {
|
||||||
{
|
|
||||||
return salt;
|
return salt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Name getZone()
|
public Name getZone() {
|
||||||
{
|
|
||||||
return zone;
|
return zone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NSEC3Record getNSEC3Record()
|
public NSEC3Record getNSEC3Record() {
|
||||||
{
|
return new NSEC3Record(getName(), dclass, ttl, hashAlg, flags, iterations, salt, next, getTypes());
|
||||||
String comment = (originalOwner == null) ? "(unknown original ownername)"
|
|
||||||
: originalOwner.toString();
|
|
||||||
return new NSEC3Record(getName(), dclass, ttl, hashAlg, flags, iterations, salt,
|
|
||||||
next, getTypes(), comment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void mergeTypes(TypeMap new_types)
|
public void mergeTypes(TypeMap newTypes) {
|
||||||
{
|
int[] nt = newTypes.getTypes();
|
||||||
int[] nt = new_types.getTypes();
|
for (int i = 0; i < nt.length; i++) {
|
||||||
for (int i = 0; i < nt.length; i++)
|
if (!typemap.get(nt[i]))
|
||||||
{
|
typemap.set(nt[i]);
|
||||||
if (!typemap.get(nt[i])) typemap.set(nt[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int compareTo(ProtoNSEC3 o)
|
public int compareTo(ProtoNSEC3 o) {
|
||||||
{
|
if (o == null)
|
||||||
if (o == null) return 1;
|
return 1;
|
||||||
byte[] o_owner = o.getOwner();
|
byte[] origOwner = o.getOwner();
|
||||||
int len = owner.length < o_owner.length ? o_owner.length : owner.length;
|
int len = owner.length < origOwner.length ? origOwner.length : owner.length;
|
||||||
for (int i = 0; i < len; i++)
|
for (int i = 0; i < len; i++) {
|
||||||
{
|
int d = ((owner[i] & 0xFF) - (origOwner[i] & 0xFF));
|
||||||
int d = ((owner[i] & 0xFF) - (o_owner[i] & 0xFF));
|
if (d != 0)
|
||||||
if (d != 0) return d;
|
return d;
|
||||||
}
|
}
|
||||||
return owner.length - o_owner.length;
|
return owner.length - origOwner.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString()
|
public String toString() {
|
||||||
{
|
StringBuilder sb = new StringBuilder();
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
sb.append(getName());
|
sb.append(getName());
|
||||||
sb.append(' ');
|
sb.append(' ');
|
||||||
sb.append(ttl);
|
sb.append(ttl);
|
||||||
@@ -242,22 +215,14 @@ public class ProtoNSEC3
|
|||||||
sb.append(' ');
|
sb.append(' ');
|
||||||
String nextstr = (next == null) ? "(null)" : b32.toString(next);
|
String nextstr = (next == null) ? "(null)" : b32.toString(next);
|
||||||
sb.append(nextstr);
|
sb.append(nextstr);
|
||||||
|
sb.append(' ');
|
||||||
int[] types = getTypes();
|
sb.append(typemap.toString());
|
||||||
for (int i = 0; i < types.length; i++)
|
|
||||||
{
|
|
||||||
sb.append(" ");
|
|
||||||
sb.append(Type.string(types[i]));
|
|
||||||
}
|
|
||||||
if (originalOwner != null) sb.append(" ; " + originalOwner);
|
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Comparator implements java.util.Comparator<ProtoNSEC3>
|
public static class Comparator implements java.util.Comparator<ProtoNSEC3> {
|
||||||
{
|
public int compare(ProtoNSEC3 a, ProtoNSEC3 b) {
|
||||||
public int compare(ProtoNSEC3 a, ProtoNSEC3 b)
|
|
||||||
{
|
|
||||||
return a.compareTo(b);
|
return a.compareTo(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
// Copyright (C) 2000-2003 Network Solutions, Inc., 2022 Verisign, Inc.
|
||||||
|
//
|
||||||
|
// 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.security;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import org.xbill.DNS.RRSIGRecord;
|
||||||
|
import org.xbill.DNS.Record;
|
||||||
|
import org.xbill.DNS.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class implements a comparison operator for {@link org.xbill.DNS.Record}
|
||||||
|
* objects. It imposes a canonical order consistent with DNSSEC. It does not put
|
||||||
|
* records within a RRset into canonical order: see {@link ByteArrayComparator}.
|
||||||
|
*
|
||||||
|
* @author David Blacka
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class RecordComparator implements Comparator<Record> {
|
||||||
|
public RecordComparator() {
|
||||||
|
// nothing to initialize
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In general, types are compared numerically. However, SOA, NS, and DNAME are
|
||||||
|
* ordered
|
||||||
|
* before the rest.
|
||||||
|
*/
|
||||||
|
private int compareTypes(int a, int b) {
|
||||||
|
if (a == b)
|
||||||
|
return 0;
|
||||||
|
if (a == Type.SOA)
|
||||||
|
return -1;
|
||||||
|
if (b == Type.SOA)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (a == Type.NS)
|
||||||
|
return -1;
|
||||||
|
if (b == Type.NS)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (a == Type.DNAME)
|
||||||
|
return -1;
|
||||||
|
if (b == Type.DNAME)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (a < b)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int compareRDATA(Record a, Record b) {
|
||||||
|
byte[] aRdata = a.rdataToWireCanonical();
|
||||||
|
byte[] bRdata = b.rdataToWireCanonical();
|
||||||
|
|
||||||
|
for (int i = 0; i < aRdata.length && i < bRdata.length; i++) {
|
||||||
|
int n = (aRdata[i] & 0xFF) - (bRdata[i] & 0xFF);
|
||||||
|
if (n != 0)
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
return (aRdata.length - bRdata.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compare(Record a, Record b) {
|
||||||
|
if (a == null && b == null)
|
||||||
|
return 0;
|
||||||
|
if (a == null)
|
||||||
|
return 1;
|
||||||
|
if (b == null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int res = a.getName().compareTo(b.getName());
|
||||||
|
if (res != 0)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
int aType = a.getType();
|
||||||
|
int bType = b.getType();
|
||||||
|
int sigType = 0;
|
||||||
|
|
||||||
|
if (aType == Type.RRSIG) {
|
||||||
|
aType = ((RRSIGRecord) a).getTypeCovered();
|
||||||
|
if (bType != Type.RRSIG)
|
||||||
|
sigType = 1;
|
||||||
|
}
|
||||||
|
if (bType == Type.RRSIG) {
|
||||||
|
bType = ((RRSIGRecord) b).getTypeCovered();
|
||||||
|
if (a.getType() != Type.RRSIG)
|
||||||
|
sigType = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = compareTypes(aType, bType);
|
||||||
|
if (res != 0)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (sigType != 0)
|
||||||
|
return sigType;
|
||||||
|
|
||||||
|
return compareRDATA(a, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
1454
src/main/java/com/verisignlabs/dnssec/security/SignUtils.java
Normal file
1454
src/main/java/com/verisignlabs/dnssec/security/SignUtils.java
Normal file
File diff suppressed because it is too large
Load Diff
200
src/main/java/com/verisignlabs/dnssec/security/TypeMap.java
Normal file
200
src/main/java/com/verisignlabs/dnssec/security/TypeMap.java
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
// Copyright (C) 2004, 2022 Verisign, Inc.
|
||||||
|
//
|
||||||
|
// 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.security;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.xbill.DNS.DNSOutput;
|
||||||
|
import org.xbill.DNS.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents the multiple type maps of the NSEC record. Currently it
|
||||||
|
* is just used to convert the wire format type map to the int array that
|
||||||
|
* org.xbill.DNS.NSECRecord uses. Note that there is now a very similar class in
|
||||||
|
* DNSjava: {@link org.xbill.DNS.TypeBitmap}.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class TypeMap {
|
||||||
|
private static final Integer[] integerArray = new Integer[0];
|
||||||
|
private static final byte[] emptyBitmap = new byte[0];
|
||||||
|
|
||||||
|
private Set<Integer> typeSet;
|
||||||
|
|
||||||
|
public TypeMap() {
|
||||||
|
this.typeSet = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add the given type to the typemap. */
|
||||||
|
public void set(int type) {
|
||||||
|
typeSet.add(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove the given type from the type map. */
|
||||||
|
public void clear(int type) {
|
||||||
|
typeSet.remove(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return true if the given type is present in the type map. */
|
||||||
|
public boolean get(int type) {
|
||||||
|
return typeSet.contains(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of DNS type code, construct a TypeMap object.
|
||||||
|
*/
|
||||||
|
public static TypeMap fromTypes(int[] types) {
|
||||||
|
TypeMap m = new TypeMap();
|
||||||
|
if (types == null)
|
||||||
|
return m;
|
||||||
|
for (int i = 0; i < types.length; i++) {
|
||||||
|
m.set(types[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of bytes representing a wire-format type map, construct the
|
||||||
|
* TypeMap object.
|
||||||
|
*/
|
||||||
|
public static TypeMap fromBytes(byte[] map) {
|
||||||
|
int m = 0;
|
||||||
|
TypeMap typemap = new TypeMap();
|
||||||
|
|
||||||
|
int page;
|
||||||
|
int byteLength;
|
||||||
|
|
||||||
|
while (m < map.length) {
|
||||||
|
page = map[m++];
|
||||||
|
byteLength = map[m++];
|
||||||
|
|
||||||
|
for (int i = 0; i < byteLength; i++) {
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
if (((map[m + i] & 0xFF) & (1 << (7 - j))) != 0) {
|
||||||
|
typemap.set((page << 8) + (i * 8) + j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m += byteLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
return typemap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given list of type mnemonics, construct a TypeMap object.
|
||||||
|
*/
|
||||||
|
public static TypeMap fromString(String types) {
|
||||||
|
TypeMap typemap = new TypeMap();
|
||||||
|
|
||||||
|
for (String type : types.split("\\s+")) {
|
||||||
|
typemap.set(Type.value(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
return typemap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the normal string representation of the typemap. */
|
||||||
|
public String toString() {
|
||||||
|
int[] types = getTypes();
|
||||||
|
Arrays.sort(types);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < types.length; i++) {
|
||||||
|
if (i > 0)
|
||||||
|
sb.append(" ");
|
||||||
|
sb.append(Type.string(types[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void mapToWire(DNSOutput out, int[] types, int base, int start, int end) {
|
||||||
|
// calculate the length of this map by looking at the largest
|
||||||
|
// typecode in this section.
|
||||||
|
int maxType = types[end - 1] & 0xFF;
|
||||||
|
int mapLength = (maxType / 8) + 1;
|
||||||
|
|
||||||
|
// write the map "header" -- the base and the length of the map.
|
||||||
|
out.writeU8(base & 0xFF);
|
||||||
|
out.writeU8(mapLength & 0xFF);
|
||||||
|
|
||||||
|
// allocate a temporary scratch space for caculating the actual
|
||||||
|
// bitmap.
|
||||||
|
byte[] map = new byte[mapLength];
|
||||||
|
|
||||||
|
// for each type in our sub-array, set its corresponding bit in the map.
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
|
map[(types[i] & 0xFF) / 8] |= (1 << (7 - types[i] % 8));
|
||||||
|
}
|
||||||
|
// write out the resulting binary bitmap.
|
||||||
|
for (int i = 0; i < map.length; i++) {
|
||||||
|
out.writeU8(map[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] toWire() {
|
||||||
|
int[] types = getTypes();
|
||||||
|
|
||||||
|
if (types.length == 0)
|
||||||
|
return emptyBitmap;
|
||||||
|
|
||||||
|
Arrays.sort(types);
|
||||||
|
|
||||||
|
int mapbase = -1;
|
||||||
|
int mapstart = -1;
|
||||||
|
|
||||||
|
DNSOutput out = new DNSOutput();
|
||||||
|
|
||||||
|
for (int i = 0; i < types.length; i++) {
|
||||||
|
int base = (types[i] >> 8) & 0xFF;
|
||||||
|
if (base == mapbase)
|
||||||
|
continue;
|
||||||
|
if (mapstart >= 0) {
|
||||||
|
mapToWire(out, types, mapbase, mapstart, i);
|
||||||
|
}
|
||||||
|
mapbase = base;
|
||||||
|
mapstart = i;
|
||||||
|
}
|
||||||
|
mapToWire(out, types, mapbase, mapstart, types.length);
|
||||||
|
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getTypes() {
|
||||||
|
Integer[] a = typeSet.toArray(integerArray);
|
||||||
|
|
||||||
|
int[] res = new int[a.length];
|
||||||
|
for (int i = 0; i < res.length; i++) {
|
||||||
|
res[i] = a[i].intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] fromWireToTypes(byte[] wireFmt) {
|
||||||
|
return TypeMap.fromBytes(wireFmt).getTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] fromTypesToWire(int[] types) {
|
||||||
|
return TypeMap.fromTypes(types).toWire();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
397
src/main/java/com/verisignlabs/dnssec/security/ZoneData.java
Normal file
397
src/main/java/com/verisignlabs/dnssec/security/ZoneData.java
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
package com.verisignlabs.dnssec.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.SortedMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.xbill.DNS.Master;
|
||||||
|
import org.xbill.DNS.NSEC3PARAMRecord;
|
||||||
|
import org.xbill.DNS.NSEC3Record;
|
||||||
|
import org.xbill.DNS.Name;
|
||||||
|
import org.xbill.DNS.RRSIGRecord;
|
||||||
|
import org.xbill.DNS.RRset;
|
||||||
|
import org.xbill.DNS.Record;
|
||||||
|
import org.xbill.DNS.SOARecord;
|
||||||
|
import org.xbill.DNS.Type;
|
||||||
|
|
||||||
|
public class ZoneData {
|
||||||
|
private SortedMap<Name, Set<Integer>> mNodeMap;
|
||||||
|
private HashMap<String, RRset> mRRsetMap;
|
||||||
|
private SortedMap<Name, MarkRRset> mNSECMap;
|
||||||
|
private SortedMap<Name, MarkRRset> mNSEC3Map;
|
||||||
|
private Name mZoneName;
|
||||||
|
private DNSSECType mDNSSECType;
|
||||||
|
private NSEC3PARAMRecord mNSEC3params;
|
||||||
|
// Configuration parameters
|
||||||
|
private boolean mIgnoreDuplicateRRs;
|
||||||
|
private boolean mStripDNSSEC;
|
||||||
|
|
||||||
|
private Logger log = Logger.getLogger(ZoneData.class.toString());
|
||||||
|
|
||||||
|
// The various types of signed zones.
|
||||||
|
enum DNSSECType {
|
||||||
|
UNSIGNED, NSEC, NSEC3, NSEC3_OPTOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The types of nodes (a node consists of all RRs with the same name).
|
||||||
|
enum NodeType {
|
||||||
|
NORMAL, DELEGATION, GLUE, DNAME, INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if mStripDNSSEC is true, then RRsets of the following types will be skipped
|
||||||
|
// on load, or stripped.
|
||||||
|
private static final Set<Integer> GENTYPES_SET = Set.of(Type.RRSIG, Type.NSEC, Type.NSEC3, Type.NSEC3PARAM);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a subclass of {@link org.xbill.DNS.RRset} that adds a "mark".
|
||||||
|
*/
|
||||||
|
public class MarkRRset extends RRset {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private boolean mIsMarked = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return super.equals(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return super.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean getMark() {
|
||||||
|
return mIsMarked;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMark(boolean value) {
|
||||||
|
mIsMarked = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZoneData() {
|
||||||
|
mNodeMap = new TreeMap<>();
|
||||||
|
mRRsetMap = new HashMap<>();
|
||||||
|
mNSECMap = new TreeMap<>();
|
||||||
|
mNSEC3Map = new TreeMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZoneData(boolean stripDNSSEC, boolean ignoreDuplicates) {
|
||||||
|
super();
|
||||||
|
setStripDNSSEC(stripDNSSEC);
|
||||||
|
setIgnoreDuplicateRRs(ignoreDuplicates);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStripDNSSEC(boolean value) {
|
||||||
|
this.mStripDNSSEC = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIgnoreDuplicateRRs(boolean value) {
|
||||||
|
this.mIgnoreDuplicateRRs = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The detected DNSSEC "type" (what form of negative proof it uses), or
|
||||||
|
* UNSIGNED if the zone is unsigned or nothing has been processed yet.
|
||||||
|
*/
|
||||||
|
public DNSSECType getDNSSECType() {
|
||||||
|
return mDNSSECType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Any detected NSEC3PARAM record in the processed zone, or null if
|
||||||
|
* there wasn't one or no zone has been processed yet.
|
||||||
|
*/
|
||||||
|
public NSEC3PARAMRecord getNSEC3Params() {
|
||||||
|
return mNSEC3params;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Name zoneName() {
|
||||||
|
return mZoneName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SOARecord getSOA() {
|
||||||
|
assert mRRsetMap != null;
|
||||||
|
|
||||||
|
RRset r = mRRsetMap.get(key(mZoneName, Type.SOA));
|
||||||
|
assert r != null;
|
||||||
|
|
||||||
|
return (SOARecord) r.first();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @return The computed "node map" -- this is a mapping of
|
||||||
|
* {@link org.xbill.DNS.Name} objects to simple sets of DNS typecodes.
|
||||||
|
* This will assert if no zone has been processed yet.
|
||||||
|
*/
|
||||||
|
public SortedMap<Name, Set<Integer>> nodeMap() {
|
||||||
|
assert mNodeMap != null;
|
||||||
|
return mNodeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The computed NSEC map.
|
||||||
|
*/
|
||||||
|
public SortedMap<Name, MarkRRset> getNSECMap() {
|
||||||
|
assert mNSECMap != null;
|
||||||
|
|
||||||
|
return mNSECMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the computed NSEC3 map
|
||||||
|
*/
|
||||||
|
public SortedMap<Name, MarkRRset> getNSEC3Map() {
|
||||||
|
assert mNSEC3Map != null;
|
||||||
|
|
||||||
|
return mNSEC3Map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return an RRset from our internal map with the given name and type, or null if not found.
|
||||||
|
*/
|
||||||
|
public RRset getRRset(Name n, int type) {
|
||||||
|
assert mRRsetMap != null;
|
||||||
|
|
||||||
|
return mRRsetMap.get(key(n, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formulate a unique key for an RRset in the RRsetMap. Since an RRset is
|
||||||
|
* defined to be the set of Records that all the same ownername and type, we can
|
||||||
|
* formulate a valid key by combining them.
|
||||||
|
*
|
||||||
|
* @param n the ownername of the RRset
|
||||||
|
* @param type The (integer) type of the RRset
|
||||||
|
* @return the key, as a string.
|
||||||
|
*/
|
||||||
|
public static String key(Name n, int type) {
|
||||||
|
return n.toString() + ':' + type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an Record (RRSIG or otherwise) to a given RRset
|
||||||
|
*
|
||||||
|
* @param rrset The rrset to add to
|
||||||
|
* @param rr The record to add
|
||||||
|
* @return true if the record was actually added, false if not.
|
||||||
|
*/
|
||||||
|
private boolean addRRtoRRset(RRset rrset, Record rr) {
|
||||||
|
if (mIgnoreDuplicateRRs) {
|
||||||
|
rrset.addRR(rr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rr instanceof RRSIGRecord) {
|
||||||
|
for (RRSIGRecord sigrec : rrset.sigs()) {
|
||||||
|
if (rr.equals(sigrec)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Record rec : rrset.rrs()) {
|
||||||
|
if (rr.equals(rec)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rrset.addRR(rr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a record to the various maps.
|
||||||
|
*
|
||||||
|
* @param r the record to add
|
||||||
|
* @return true if the RR was added, false if it wasn't (because it was a
|
||||||
|
* duplicate)
|
||||||
|
*/
|
||||||
|
private boolean addRR(Record r) {
|
||||||
|
assert mNSEC3Map != null;
|
||||||
|
assert mRRsetMap != null;
|
||||||
|
|
||||||
|
Name n = r.getName();
|
||||||
|
int t = r.getType();
|
||||||
|
if (t == Type.RRSIG)
|
||||||
|
t = ((RRSIGRecord) r).getTypeCovered();
|
||||||
|
|
||||||
|
// Add NSEC and NSEC3 RRs to their respective maps
|
||||||
|
if (t == Type.NSEC) {
|
||||||
|
MarkRRset rrset = mNSECMap.computeIfAbsent(n, k -> new MarkRRset());
|
||||||
|
return addRRtoRRset(rrset, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t == Type.NSEC3) {
|
||||||
|
MarkRRset rrset = mNSEC3Map.computeIfAbsent(n, k -> new MarkRRset());
|
||||||
|
return addRRtoRRset(rrset, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the name and type to the node map
|
||||||
|
Set<Integer> typeset = mNodeMap.computeIfAbsent(n, k -> new HashSet<>());
|
||||||
|
typeset.add(r.getType()); // add the original type
|
||||||
|
|
||||||
|
// Add the record to the RRset map
|
||||||
|
String k = key(n, t);
|
||||||
|
RRset rrset = mRRsetMap.computeIfAbsent(k, k2 -> new RRset());
|
||||||
|
return addRRtoRRset(rrset, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a record, determine the DNSSEC signing type. If the record doesn't
|
||||||
|
* determine that, DNSSECType.UNSIGNED is returned
|
||||||
|
*
|
||||||
|
* @param r the record to examine
|
||||||
|
* @return a DNSSECType of UNKNOWN, unless r is an NSEC or NSEC3 record, in
|
||||||
|
* which case, the type corresponding to the
|
||||||
|
*/
|
||||||
|
private DNSSECType determineDNSSECType(Record r) {
|
||||||
|
if (r.getType() == Type.NSEC)
|
||||||
|
return DNSSECType.NSEC;
|
||||||
|
if (r.getType() == Type.NSEC3) {
|
||||||
|
NSEC3Record nsec3 = (NSEC3Record) r;
|
||||||
|
if ((nsec3.getFlags() & NSEC3Record.Flags.OPT_OUT) == NSEC3Record.Flags.OPT_OUT) {
|
||||||
|
return DNSSECType.NSEC3_OPTOUT;
|
||||||
|
}
|
||||||
|
return DNSSECType.NSEC3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DNSSECType.UNSIGNED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a resource record to the node map and rrsets map.
|
||||||
|
* @param r The record
|
||||||
|
* @return the number of errors encountered.
|
||||||
|
*/
|
||||||
|
private int addRecord(Record r) {
|
||||||
|
int errors = 0;
|
||||||
|
Name n = r.getName();
|
||||||
|
int t = r.getType();
|
||||||
|
|
||||||
|
// skip any generated DNSSEC records if we are skipping
|
||||||
|
if (mStripDNSSEC && GENTYPES_SET.contains(t)) {
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the record to the various maps.
|
||||||
|
boolean res = addRR(r);
|
||||||
|
if (!res) {
|
||||||
|
log.warning("Record '" + r + "' detected as a duplicate");
|
||||||
|
errors++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Learn some things about the zone as we do this pass.
|
||||||
|
if (t == Type.SOA)
|
||||||
|
mZoneName = n;
|
||||||
|
if (t == Type.NSEC3PARAM)
|
||||||
|
mNSEC3params = (NSEC3PARAMRecord) r;
|
||||||
|
if (mDNSSECType == DNSSECType.UNSIGNED)
|
||||||
|
mDNSSECType = determineDNSSECType(r);
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a zone file, load the node and rrset maps, as well as
|
||||||
|
* determine the NSEC3 parameters and signing type.
|
||||||
|
*
|
||||||
|
* @param master A {@link org.xbill.DNS.Master} object.
|
||||||
|
* @return the number of errors encountered.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public int calculateNodes(String zoneFileName, Name origin) {
|
||||||
|
// The zone is unsigned until we get a clue otherwise.
|
||||||
|
mDNSSECType = DNSSECType.UNSIGNED;
|
||||||
|
|
||||||
|
int errors = 0;
|
||||||
|
try (Master m = zoneFileName.equals("-") ? new Master(System.in) : new Master(zoneFileName, origin)) {
|
||||||
|
Record r = null;
|
||||||
|
while ((r = m.nextRecord()) != null) {
|
||||||
|
errors += addRecord(r);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
errors++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an unsorted list of records, load the node and rrset maps, as well as
|
||||||
|
* determine the NSEC3 parameters and signing type.
|
||||||
|
*
|
||||||
|
* @param records A list of {@link org.xbill.DNS.Record} objects, unsorted.
|
||||||
|
* @return the number of errors encountered.
|
||||||
|
*/
|
||||||
|
public int calculateNodes(List<Record> records) {
|
||||||
|
mNodeMap = new TreeMap<>();
|
||||||
|
mRRsetMap = new HashMap<>();
|
||||||
|
|
||||||
|
// The zone is unsigned until we get a clue otherwise.
|
||||||
|
mDNSSECType = DNSSECType.UNSIGNED;
|
||||||
|
|
||||||
|
int errors = 0;
|
||||||
|
for (Record r : records) {
|
||||||
|
errors += addRecord(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a name, typeset, and name of the last zone cut, determine the node
|
||||||
|
* type.
|
||||||
|
*
|
||||||
|
* @param n The owner name for the node (a collection of RRsets with all
|
||||||
|
* the same owner)
|
||||||
|
* @param typeset The (simple) set of types present at that node
|
||||||
|
* @param lastCut The name of the last "zone cut". This is the last name (if
|
||||||
|
* encountering the name in DNSSEC canonical name order) that was
|
||||||
|
* either a delegation or a DNAME.
|
||||||
|
*/
|
||||||
|
public NodeType determineNodeType(Name n, Set<Integer> typeset, Name lastCut, Name lastDname) {
|
||||||
|
// Names not at or below the zone name are "invalid" -- they shouldn't
|
||||||
|
// be in the zone at all, normally. In practice, they are the same as
|
||||||
|
// glue.
|
||||||
|
if (!n.subdomain(mZoneName)) {
|
||||||
|
return NodeType.INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All RRs at the zone apex are normal
|
||||||
|
if (n.equals(mZoneName))
|
||||||
|
return NodeType.NORMAL;
|
||||||
|
|
||||||
|
// If the node is not below the zone itself, we will treat it as glue (it is
|
||||||
|
// really junk).
|
||||||
|
if (!n.subdomain(mZoneName)) {
|
||||||
|
return NodeType.GLUE;
|
||||||
|
}
|
||||||
|
// If the node is below a zone cut (either a delegation or DNAME), it is
|
||||||
|
// glue.
|
||||||
|
if (lastCut != null && n.subdomain(lastCut) && !n.equals(lastCut)) {
|
||||||
|
return NodeType.GLUE;
|
||||||
|
}
|
||||||
|
if (lastDname != null && n.subdomain(lastDname) && !n.equals(lastDname)) {
|
||||||
|
return NodeType.GLUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the node has a NS record it is a delegation.
|
||||||
|
if (typeset.contains(Integer.valueOf(Type.NS)))
|
||||||
|
return NodeType.DELEGATION;
|
||||||
|
|
||||||
|
// If the node has a DNAME record, is similar to a delegation
|
||||||
|
if (typeset.contains(Integer.valueOf(Type.DNAME))) {
|
||||||
|
return NodeType.DNAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
// everything else is normal
|
||||||
|
return NodeType.NORMAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
// $Id$
|
// Copyright (C) 2003, 2022 Verisign, Inc.
|
||||||
//
|
|
||||||
// Copyright (C) 2003 VeriSign, Inc.
|
|
||||||
//
|
//
|
||||||
// This library is free software; you can redistribute it and/or
|
// This library is free software; you can redistribute it and/or
|
||||||
// modify it under the terms of the GNU Lesser General Public
|
// modify it under the terms of the GNU Lesser General Public
|
||||||
@@ -24,11 +22,11 @@ import java.io.FileWriter;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.xbill.DNS.Master;
|
import org.xbill.DNS.Master;
|
||||||
import org.xbill.DNS.Name;
|
import org.xbill.DNS.Name;
|
||||||
|
import org.xbill.DNS.RRSIGRecord;
|
||||||
import org.xbill.DNS.RRset;
|
import org.xbill.DNS.RRset;
|
||||||
import org.xbill.DNS.Record;
|
import org.xbill.DNS.Record;
|
||||||
import org.xbill.DNS.Type;
|
import org.xbill.DNS.Type;
|
||||||
@@ -37,43 +35,36 @@ import org.xbill.DNS.Type;
|
|||||||
* This class contains a bunch of utility methods that are generally useful in
|
* This class contains a bunch of utility methods that are generally useful in
|
||||||
* manipulating zones.
|
* manipulating zones.
|
||||||
*
|
*
|
||||||
* @author David Blacka (original)
|
* @author David Blacka
|
||||||
* @author $Author$
|
|
||||||
* @version $Revision$
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class ZoneUtils
|
public class ZoneUtils {
|
||||||
{
|
|
||||||
|
private ZoneUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a zone file.
|
* Load a zone file.
|
||||||
*
|
*
|
||||||
* @param zonefile
|
* @param zonefile
|
||||||
* the filename/path of the zonefile to read.
|
* the filename/path of the zonefile to read.
|
||||||
* @param origin
|
* @param origin
|
||||||
* the origin to use for the zonefile (may be null if the origin is
|
* the origin to use for the zonefile (may be null if the origin
|
||||||
* specified in the zone file itself).
|
* is
|
||||||
|
* specified in the zone file itself).
|
||||||
* @return a {@link java.util.List} of {@link org.xbill.DNS.Record} objects.
|
* @return a {@link java.util.List} of {@link org.xbill.DNS.Record} objects.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* if something goes wrong reading the zone file.
|
* if something goes wrong reading the zone file.
|
||||||
*/
|
*/
|
||||||
public static List<Record> readZoneFile(String zonefile, Name origin) throws IOException
|
public static List<Record> readZoneFile(String zonefile, Name origin) throws IOException {
|
||||||
{
|
ArrayList<Record> records = new ArrayList<>();
|
||||||
ArrayList<Record> records = new ArrayList<Record>();
|
try (Master m = zonefile.equals("-") ? new Master(System.in) : new Master(zonefile, origin)) {
|
||||||
Master m;
|
Record r = null;
|
||||||
if (zonefile.equals("-"))
|
while ((r = m.nextRecord()) != null) {
|
||||||
{
|
records.add(r);
|
||||||
m = new Master(System.in);
|
}
|
||||||
}
|
} catch (IOException e) {
|
||||||
else
|
e.printStackTrace();
|
||||||
{
|
|
||||||
m = new Master(zonefile, origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
Record r = null;
|
|
||||||
|
|
||||||
while ((r = m.nextRecord()) != null)
|
|
||||||
{
|
|
||||||
records.add(r);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return records;
|
return records;
|
||||||
@@ -83,26 +74,23 @@ public class ZoneUtils
|
|||||||
* Write the records out into a zone file.
|
* Write the records out into a zone file.
|
||||||
*
|
*
|
||||||
* @param records
|
* @param records
|
||||||
* a {@link java.util.List} of {@link org.xbill.DNS.Record} objects
|
* a {@link java.util.List} of {@link org.xbill.DNS.Record}
|
||||||
* forming a zone.
|
* objects
|
||||||
|
* forming a zone.
|
||||||
* @param zonefile
|
* @param zonefile
|
||||||
* the file to write to. If null or equal to "-", System.out is used.
|
* the file to write to. If null or equal to "-", System.out is
|
||||||
|
* used.
|
||||||
*/
|
*/
|
||||||
public static void writeZoneFile(List<Record> records, String zonefile) throws IOException
|
public static void writeZoneFile(List<Record> records, String zonefile) throws IOException {
|
||||||
{
|
|
||||||
PrintWriter out = null;
|
PrintWriter out = null;
|
||||||
|
|
||||||
if (zonefile == null || zonefile.equals("-"))
|
if (zonefile == null || zonefile.equals("-")) {
|
||||||
{
|
|
||||||
out = new PrintWriter(System.out);
|
out = new PrintWriter(System.out);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
out = new PrintWriter(new BufferedWriter(new FileWriter(zonefile)));
|
out = new PrintWriter(new BufferedWriter(new FileWriter(zonefile)));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Record r : records)
|
for (Record r : records) {
|
||||||
{
|
|
||||||
out.println(r);
|
out.println(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,54 +101,41 @@ public class ZoneUtils
|
|||||||
* Given just the list of records, determine the zone name (origin).
|
* Given just the list of records, determine the zone name (origin).
|
||||||
*
|
*
|
||||||
* @param records
|
* @param records
|
||||||
* a list of {@link org.xbill.DNS.Record} objects.
|
* a list of {@link org.xbill.DNS.Record} objects.
|
||||||
* @return the zone name, if found. null if one couldn't be found.
|
* @return the zone name, if found. null if one couldn't be found.
|
||||||
*/
|
*/
|
||||||
public static Name findZoneName(List<Record> records)
|
public static Name findZoneName(List<Record> records) {
|
||||||
{
|
for (Record r : records) {
|
||||||
for (Record r : records)
|
if (r.getType() == Type.SOA) {
|
||||||
{
|
return r.getName();
|
||||||
int type = r.getType();
|
}
|
||||||
|
|
||||||
if (type == Type.SOA) return r.getName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Record> findRRs(List<Record> records, Name name, int type)
|
public static List<Record> findRRs(List<Record> records, Name name, int type) {
|
||||||
{
|
List<Record> res = new ArrayList<>();
|
||||||
List<Record> res = new ArrayList<Record>();
|
for (Record r : records) {
|
||||||
for (Record r : records)
|
if (r.getName().equals(name) && r.getType() == type) {
|
||||||
{
|
|
||||||
if (r.getName().equals(name) && r.getType() == type)
|
|
||||||
{
|
|
||||||
res.add(r);
|
res.add(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This is an alternate way to format an RRset into a string */
|
/** This is an alternate way to format an RRset into a string */
|
||||||
@SuppressWarnings("unchecked")
|
public static String rrsetToString(RRset rrset, boolean includeSigs) {
|
||||||
public static String rrsetToString(RRset rrset, boolean includeSigs)
|
|
||||||
{
|
|
||||||
StringBuilder out = new StringBuilder();
|
StringBuilder out = new StringBuilder();
|
||||||
|
|
||||||
for (Iterator<Record> i = rrset.rrs(false); i.hasNext();)
|
for (Record r : rrset.rrs(false)) {
|
||||||
{
|
|
||||||
Record r = i.next();
|
|
||||||
out.append(r.toString());
|
out.append(r.toString());
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeSigs)
|
if (includeSigs) {
|
||||||
{
|
for (RRSIGRecord s : rrset.sigs()) {
|
||||||
for (Iterator<Record> i = rrset.sigs(); i.hasNext();)
|
out.append(s.toString());
|
||||||
{
|
|
||||||
Record r = i.next();
|
|
||||||
out.append(r.toString());
|
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
// $Id: DnsSecVerifier.java 172 2009-08-23 19:13:42Z davidb $
|
// Copyright (C) 2010, 2022 Verisign, Inc.
|
||||||
//
|
|
||||||
// Copyright (C) 2010 Verisign, Inc.
|
|
||||||
//
|
//
|
||||||
// This library is free software; you can redistribute it and/or
|
// This library is free software; you can redistribute it and/or
|
||||||
// modify it under the terms of the GNU Lesser General Public
|
// modify it under the terms of the GNU Lesser General Public
|
||||||
@@ -33,7 +31,6 @@ import java.util.TreeMap;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.xbill.DNS.DNSKEYRecord;
|
import org.xbill.DNS.DNSKEYRecord;
|
||||||
import org.xbill.DNS.DNSSEC;
|
|
||||||
import org.xbill.DNS.NSEC3PARAMRecord;
|
import org.xbill.DNS.NSEC3PARAMRecord;
|
||||||
import org.xbill.DNS.NSEC3Record;
|
import org.xbill.DNS.NSEC3Record;
|
||||||
import org.xbill.DNS.NSECRecord;
|
import org.xbill.DNS.NSECRecord;
|
||||||
@@ -50,144 +47,156 @@ import org.xbill.DNS.utils.base32;
|
|||||||
* verifying signatures, this class will also detect invalid NSEC and NSEC3
|
* verifying signatures, this class will also detect invalid NSEC and NSEC3
|
||||||
* chains.
|
* chains.
|
||||||
*
|
*
|
||||||
* @author David Blacka (original)
|
* @author David Blacka
|
||||||
* @author $Author: davidb $
|
|
||||||
* @version $Revision: 172 $
|
|
||||||
*/
|
*/
|
||||||
public class ZoneVerifier
|
public class ZoneVerifier {
|
||||||
{
|
|
||||||
|
|
||||||
private SortedMap<Name, Set<Integer>> mNodeMap;
|
private SortedMap<Name, Set<Integer>> mNodeMap;
|
||||||
private HashMap<String, RRset> mRRsetMap;
|
private HashMap<String, RRset> mRRsetMap;
|
||||||
private SortedMap<Name, MarkRRset> mNSECMap;
|
private SortedMap<Name, MarkRRset> mNSECMap;
|
||||||
private SortedMap<Name, MarkRRset> mNSEC3Map;
|
private SortedMap<Name, MarkRRset> mNSEC3Map;
|
||||||
private Name mZoneName;
|
private Name mZoneName;
|
||||||
private DNSSECType mDNSSECType;
|
private DNSSECType mDNSSECType;
|
||||||
private NSEC3PARAMRecord mNSEC3params;
|
private NSEC3PARAMRecord mNSEC3params;
|
||||||
|
private boolean mIgnoreDuplicateRRs;
|
||||||
|
|
||||||
private DnsSecVerifier mVerifier;
|
private DnsSecVerifier mVerifier;
|
||||||
private base32 mBase32;
|
private base32 mBase32;
|
||||||
private ByteArrayComparator mBAcmp;
|
private ByteArrayComparator mBAcmp;
|
||||||
|
|
||||||
private Logger log = Logger.getLogger("ZoneVerifier");
|
private Logger log = Logger.getLogger("ZoneVerifier");
|
||||||
|
|
||||||
// The various types of signed zones.
|
// The various types of signed zones.
|
||||||
enum DNSSECType
|
enum DNSSECType {
|
||||||
{
|
|
||||||
UNSIGNED, NSEC, NSEC3, NSEC3_OPTOUT;
|
UNSIGNED, NSEC, NSEC3, NSEC3_OPTOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The types of nodes (a node consists of all RRs with the same name).
|
// The types of nodes (a node consists of all RRs with the same name).
|
||||||
enum NodeType
|
enum NodeType {
|
||||||
{
|
|
||||||
NORMAL, DELEGATION, GLUE;
|
NORMAL, DELEGATION, GLUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a subclass of {@link org.xbill.DNS.RRset} that adds a "mark".
|
* This is a subclass of {@link org.xbill.DNS.RRset} that adds a "mark".
|
||||||
*/
|
*/
|
||||||
private class MarkRRset extends RRset
|
private class MarkRRset extends RRset {
|
||||||
{
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private boolean mIsMarked = false;
|
private boolean mIsMarked = false;
|
||||||
|
|
||||||
boolean getMark()
|
@Override
|
||||||
{
|
public boolean equals(Object o) {
|
||||||
|
return super.equals(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return super.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean getMark() {
|
||||||
return mIsMarked;
|
return mIsMarked;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMark(boolean value)
|
void setMark(boolean value) {
|
||||||
{
|
|
||||||
mIsMarked = value;
|
mIsMarked = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZoneVerifier()
|
public ZoneVerifier() {
|
||||||
{
|
|
||||||
mVerifier = new DnsSecVerifier();
|
mVerifier = new DnsSecVerifier();
|
||||||
mBase32 = new base32(base32.Alphabet.BASE32HEX, false, true);
|
mBase32 = new base32(base32.Alphabet.BASE32HEX, false, true);
|
||||||
mBAcmp = new ByteArrayComparator();
|
mBAcmp = new ByteArrayComparator();
|
||||||
|
mIgnoreDuplicateRRs = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DnsSecVerifier getVerifier()
|
/** @return the DnsSecVerifier object used to verify individual RRsets. */
|
||||||
{
|
public DnsSecVerifier getVerifier() {
|
||||||
return mVerifier;
|
return mVerifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String key(Name n, int type)
|
public void setIgnoreDuplicateRRs(boolean value) {
|
||||||
{
|
mIgnoreDuplicateRRs = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String key(Name n, int type) {
|
||||||
return n.toString() + ':' + type;
|
return n.toString() + ':' + type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean addRRtoRRset(RRset rrset, Record rr) {
|
||||||
|
if (mIgnoreDuplicateRRs) {
|
||||||
|
rrset.addRR(rr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rr instanceof RRSIGRecord) {
|
||||||
|
for (RRSIGRecord sigrec : rrset.sigs()) {
|
||||||
|
if (rr.equals(sigrec)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Record rec : rrset.rrs()) {
|
||||||
|
if (rr.equals(rec)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rrset.addRR(rr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a record to the various maps.
|
* Add a record to the various maps.
|
||||||
|
*
|
||||||
|
* @return true if the RR was added, false if it wasn't (because it was a
|
||||||
|
* duplicate)
|
||||||
*/
|
*/
|
||||||
private void addRR(Record r)
|
private boolean addRR(Record r) {
|
||||||
{
|
Name n = r.getName();
|
||||||
Name r_name = r.getName();
|
int t = r.getType();
|
||||||
int r_type = r.getType();
|
if (t == Type.RRSIG)
|
||||||
if (r_type == Type.RRSIG) r_type = ((RRSIGRecord) r).getTypeCovered();
|
t = ((RRSIGRecord) r).getTypeCovered();
|
||||||
|
|
||||||
// Add NSEC and NSEC3 RRs to their respective maps
|
// Add NSEC and NSEC3 RRs to their respective maps
|
||||||
if (r_type == Type.NSEC)
|
if (t == Type.NSEC) {
|
||||||
{
|
if (mNSECMap == null) {
|
||||||
if (mNSECMap == null) mNSECMap = new TreeMap<Name, MarkRRset>();
|
mNSECMap = new TreeMap<>();
|
||||||
MarkRRset rrset = mNSECMap.get(r_name);
|
|
||||||
if (rrset == null)
|
|
||||||
{
|
|
||||||
rrset = new MarkRRset();
|
|
||||||
mNSECMap.put(r_name, rrset);
|
|
||||||
}
|
}
|
||||||
rrset.addRR(r);
|
MarkRRset rrset = mNSECMap.computeIfAbsent(n, k -> new MarkRRset());
|
||||||
return;
|
return addRRtoRRset(rrset, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r_type == Type.NSEC3)
|
if (t == Type.NSEC3) {
|
||||||
{
|
if (mNSEC3Map == null) {
|
||||||
if (mNSEC3Map == null) mNSEC3Map = new TreeMap<Name, MarkRRset>();
|
mNSEC3Map = new TreeMap<>();
|
||||||
MarkRRset rrset = mNSEC3Map.get(r_name);
|
|
||||||
if (rrset == null)
|
|
||||||
{
|
|
||||||
rrset = new MarkRRset();
|
|
||||||
mNSEC3Map.put(r_name, rrset);
|
|
||||||
}
|
}
|
||||||
rrset.addRR(r);
|
MarkRRset rrset = mNSEC3Map.computeIfAbsent(n, k -> new MarkRRset());
|
||||||
return;
|
|
||||||
|
return addRRtoRRset(rrset, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the name and type to the node map
|
// Add the name and type to the node map
|
||||||
Set<Integer> typeset = mNodeMap.get(r_name);
|
Set<Integer> typeset = mNodeMap.computeIfAbsent(n, k -> new HashSet<>());
|
||||||
if (typeset == null)
|
|
||||||
{
|
|
||||||
typeset = new HashSet<Integer>();
|
|
||||||
mNodeMap.put(r_name, typeset);
|
|
||||||
}
|
|
||||||
typeset.add(r.getType()); // add the original type
|
typeset.add(r.getType()); // add the original type
|
||||||
|
|
||||||
// Add the record to the RRset map
|
// Add the record to the RRset map
|
||||||
String k = key(r_name, r_type);
|
String k = key(n, t);
|
||||||
RRset rrset = mRRsetMap.get(k);
|
RRset rrset = mRRsetMap.computeIfAbsent(k, k2 -> new RRset());
|
||||||
if (rrset == null)
|
return addRRtoRRset(rrset, r);
|
||||||
{
|
|
||||||
rrset = new RRset();
|
|
||||||
mRRsetMap.put(k, rrset);
|
|
||||||
}
|
|
||||||
rrset.addRR(r);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a record, determine the DNSSEC signing type. If the record doesn't
|
* Given a record, determine the DNSSEC signing type. If the record doesn't
|
||||||
* determine that, DNSSECType.UNSIGNED is returned
|
* determine that, DNSSECType.UNSIGNED is returned
|
||||||
*/
|
*/
|
||||||
private DNSSECType determineDNSSECType(Record r)
|
private DNSSECType determineDNSSECType(Record r) {
|
||||||
{
|
if (r.getType() == Type.NSEC)
|
||||||
if (r.getType() == Type.NSEC) return DNSSECType.NSEC;
|
return DNSSECType.NSEC;
|
||||||
if (r.getType() == Type.NSEC3)
|
if (r.getType() == Type.NSEC3) {
|
||||||
{
|
|
||||||
NSEC3Record nsec3 = (NSEC3Record) r;
|
NSEC3Record nsec3 = (NSEC3Record) r;
|
||||||
if ((nsec3.getFlags() & NSEC3Record.Flags.OPT_OUT) == NSEC3Record.Flags.OPT_OUT)
|
if ((nsec3.getFlags() & NSEC3Record.Flags.OPT_OUT) == NSEC3Record.Flags.OPT_OUT) {
|
||||||
{
|
|
||||||
return DNSSECType.NSEC3_OPTOUT;
|
return DNSSECType.NSEC3_OPTOUT;
|
||||||
}
|
}
|
||||||
return DNSSECType.NSEC3;
|
return DNSSECType.NSEC3;
|
||||||
@@ -200,68 +209,85 @@ public class ZoneVerifier
|
|||||||
* Given an unsorted list of records, load the node and rrset maps, as well as
|
* Given an unsorted list of records, load the node and rrset maps, as well as
|
||||||
* determine the NSEC3 parameters and signing type.
|
* determine the NSEC3 parameters and signing type.
|
||||||
*
|
*
|
||||||
* @param records
|
* @param records an unsorted list of {@link org.xbill.DNS.Record} objects.
|
||||||
|
* @return the number of errors encountered.
|
||||||
*/
|
*/
|
||||||
private void calculateNodes(List<Record> records)
|
private int calculateNodes(List<Record> records) {
|
||||||
{
|
mNodeMap = new TreeMap<>();
|
||||||
mNodeMap = new TreeMap<Name, Set<Integer>>();
|
mRRsetMap = new HashMap<>();
|
||||||
mRRsetMap = new HashMap<String, RRset>();
|
|
||||||
|
|
||||||
// The zone is unsigned until we get a clue otherwise.
|
// The zone is unsigned until we get a clue otherwise.
|
||||||
mDNSSECType = DNSSECType.UNSIGNED;
|
mDNSSECType = DNSSECType.UNSIGNED;
|
||||||
|
|
||||||
for (Record r : records)
|
int errors = 0;
|
||||||
{
|
for (Record r : records) {
|
||||||
Name r_name = r.getName();
|
Name n = r.getName();
|
||||||
int r_type = r.getType();
|
int t = r.getType();
|
||||||
|
|
||||||
// Add the record to the various maps.
|
// Add the record to the various maps.
|
||||||
addRR(r);
|
boolean res = addRR(r);
|
||||||
|
if (!res) {
|
||||||
|
log.warning("Record '" + r + "' detected as a duplicate");
|
||||||
|
errors++;
|
||||||
|
}
|
||||||
|
|
||||||
// Learn some things about the zone as we do this pass.
|
// Learn some things about the zone as we do this pass.
|
||||||
if (r_type == Type.SOA) mZoneName = r_name;
|
if (t == Type.SOA)
|
||||||
if (r_type == Type.NSEC3PARAM) mNSEC3params = (NSEC3PARAMRecord) r;
|
mZoneName = n;
|
||||||
if (r_type == Type.DNSKEY) {
|
if (t == Type.NSEC3PARAM)
|
||||||
|
mNSEC3params = (NSEC3PARAMRecord) r;
|
||||||
|
if (t == Type.DNSKEY) {
|
||||||
DNSKEYRecord dnskey = (DNSKEYRecord) r;
|
DNSKEYRecord dnskey = (DNSKEYRecord) r;
|
||||||
mVerifier.addTrustedKey(dnskey);
|
mVerifier.addTrustedKey(dnskey);
|
||||||
log.info("Adding trusted key: " + dnskey + " ; keytag = "
|
log.info("Adding trusted key: " + dnskey + " ; keytag = "
|
||||||
+ dnskey.getFootprint());
|
+ dnskey.getFootprint());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mDNSSECType == DNSSECType.UNSIGNED) mDNSSECType = determineDNSSECType(r);
|
if (mDNSSECType == DNSSECType.UNSIGNED)
|
||||||
|
mDNSSECType = determineDNSSECType(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a name, typeset, and name of the last zone cut, determine the node
|
* Given a name, typeset, and name of the last zone cut, determine the node
|
||||||
* type.
|
* type.
|
||||||
*/
|
*/
|
||||||
private NodeType determineNodeType(Name n, Set<Integer> typeset, Name last_cut)
|
private NodeType determineNodeType(Name n, Set<Integer> typeset, Name lastCut) {
|
||||||
{
|
|
||||||
// All RRs at the zone apex are normal
|
// All RRs at the zone apex are normal
|
||||||
if (n.equals(mZoneName)) return NodeType.NORMAL;
|
if (n.equals(mZoneName))
|
||||||
|
return NodeType.NORMAL;
|
||||||
|
|
||||||
|
// If the node is not below the zone itself, we will treat it as glue (it is
|
||||||
|
// really junk).
|
||||||
|
if (!n.subdomain(mZoneName)) {
|
||||||
|
return NodeType.GLUE;
|
||||||
|
}
|
||||||
// If the node is below a zone cut (either a delegation or DNAME), it is
|
// If the node is below a zone cut (either a delegation or DNAME), it is
|
||||||
// glue.
|
// glue.
|
||||||
if (last_cut != null && n.subdomain(last_cut) && !n.equals(last_cut))
|
if (lastCut != null && n.subdomain(lastCut) && !n.equals(lastCut)) {
|
||||||
{
|
|
||||||
return NodeType.GLUE;
|
return NodeType.GLUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the node has a NS record it is a delegation.
|
// If the node has a NS record it is a delegation.
|
||||||
if (typeset.contains(new Integer(Type.NS))) return NodeType.DELEGATION;
|
if (typeset.contains(Integer.valueOf(Type.NS)))
|
||||||
|
return NodeType.DELEGATION;
|
||||||
|
|
||||||
return NodeType.NORMAL;
|
return NodeType.NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<Integer> cleanupDelegationTypeset(Set<Integer> typeset)
|
private Set<Integer> cleanupDelegationTypeset(Set<Integer> typeset) {
|
||||||
{
|
Set<Integer> t = new HashSet<>();
|
||||||
Set<Integer> t = new HashSet<Integer>();
|
if (typeset.contains(Type.NS))
|
||||||
if (typeset.contains(Type.NS)) t.add(Type.NS);
|
t.add(Type.NS);
|
||||||
if (typeset.contains(Type.DS)) t.add(Type.DS);
|
if (typeset.contains(Type.DS))
|
||||||
if (typeset.contains(Type.RRSIG)) t.add(Type.RRSIG);
|
t.add(Type.DS);
|
||||||
|
if (typeset.contains(Type.RRSIG))
|
||||||
|
t.add(Type.RRSIG);
|
||||||
|
|
||||||
if (!typeset.equals(t)) return t;
|
if (!typeset.equals(t))
|
||||||
|
return t;
|
||||||
return typeset;
|
return typeset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,35 +295,34 @@ public class ZoneVerifier
|
|||||||
* For each node, determine which RRsets should be signed, verify those, and
|
* For each node, determine which RRsets should be signed, verify those, and
|
||||||
* determine which nodes get NSEC or NSEC3 RRs and verify those.
|
* determine which nodes get NSEC or NSEC3 RRs and verify those.
|
||||||
*/
|
*/
|
||||||
private int processNodes() throws NoSuchAlgorithmException, TextParseException
|
private int processNodes() throws NoSuchAlgorithmException, TextParseException {
|
||||||
{
|
|
||||||
int errors = 0;
|
int errors = 0;
|
||||||
Name last_cut = null;
|
Name lastCut = null;
|
||||||
|
|
||||||
for (Map.Entry<Name, Set<Integer>> entry : mNodeMap.entrySet())
|
for (Map.Entry<Name, Set<Integer>> entry : mNodeMap.entrySet()) {
|
||||||
{
|
|
||||||
Name n = entry.getKey();
|
Name n = entry.getKey();
|
||||||
Set<Integer> typeset = entry.getValue();
|
Set<Integer> typeset = entry.getValue();
|
||||||
|
|
||||||
NodeType ntype = determineNodeType(n, typeset, last_cut);
|
NodeType ntype = determineNodeType(n, typeset, lastCut);
|
||||||
log.finest("Node " + n + " is type " + ntype);
|
log.finest("Node " + n + " is type " + ntype);
|
||||||
|
|
||||||
// we can ignore glue/invalid RRs.
|
// we can ignore glue/invalid RRs.
|
||||||
if (ntype == NodeType.GLUE) continue;
|
if (ntype == NodeType.GLUE)
|
||||||
|
continue;
|
||||||
|
|
||||||
// record the last zone cut if this node is a zone cut.
|
// record the last zone cut if this node is a zone cut.
|
||||||
if (ntype == NodeType.DELEGATION || typeset.contains(Type.DNAME))
|
if (ntype == NodeType.DELEGATION || typeset.contains(Type.DNAME)) {
|
||||||
{
|
lastCut = n;
|
||||||
last_cut = n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check all of the RRset that should be signed
|
// check all of the RRsets that should be signed
|
||||||
for (int type : typeset)
|
for (int type : typeset) {
|
||||||
{
|
if (type == Type.RRSIG)
|
||||||
if (type == Type.RRSIG) continue;
|
continue;
|
||||||
// at delegation points, only DS RRs are signed (and NSEC, but those are
|
// at delegation points, only DS RRs are signed (and NSEC, but those are
|
||||||
// checked separately)
|
// checked separately)
|
||||||
if (ntype == NodeType.DELEGATION && type != Type.DS) continue;
|
if (ntype == NodeType.DELEGATION && type != Type.DS)
|
||||||
|
continue;
|
||||||
// otherwise, verify the RRset.
|
// otherwise, verify the RRset.
|
||||||
String k = key(n, type);
|
String k = key(n, type);
|
||||||
RRset rrset = mRRsetMap.get(k);
|
RRset rrset = mRRsetMap.get(k);
|
||||||
@@ -307,13 +332,13 @@ public class ZoneVerifier
|
|||||||
|
|
||||||
// cleanup the typesets of delegation nodes.
|
// cleanup the typesets of delegation nodes.
|
||||||
// the only types that should be there are NS, DS and RRSIG.
|
// the only types that should be there are NS, DS and RRSIG.
|
||||||
if (ntype == NodeType.DELEGATION)
|
if (ntype == NodeType.DELEGATION) {
|
||||||
{
|
|
||||||
typeset = cleanupDelegationTypeset(typeset);
|
typeset = cleanupDelegationTypeset(typeset);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (mDNSSECType)
|
switch (mDNSSECType) {
|
||||||
{
|
case UNSIGNED:
|
||||||
|
throw new IllegalArgumentException("Cannot process Unsigned zone");
|
||||||
case NSEC:
|
case NSEC:
|
||||||
// all nodes with NSEC records have NSEC and RRSIG types
|
// all nodes with NSEC records have NSEC and RRSIG types
|
||||||
typeset.add(Type.NSEC);
|
typeset.add(Type.NSEC);
|
||||||
@@ -325,8 +350,7 @@ public class ZoneVerifier
|
|||||||
break;
|
break;
|
||||||
case NSEC3_OPTOUT:
|
case NSEC3_OPTOUT:
|
||||||
if (ntype == NodeType.NORMAL
|
if (ntype == NodeType.NORMAL
|
||||||
|| (ntype == NodeType.DELEGATION && typeset.contains(Type.DS)))
|
|| (ntype == NodeType.DELEGATION && typeset.contains(Type.DS))) {
|
||||||
{
|
|
||||||
errors += processNSEC3(n, typeset, ntype);
|
errors += processNSEC3(n, typeset, ntype);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -337,99 +361,87 @@ public class ZoneVerifier
|
|||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String reasonListToString(List<String> reasons)
|
private static String reasonListToString(List<String> reasons) {
|
||||||
{
|
if (reasons == null)
|
||||||
if (reasons == null) return "";
|
return "";
|
||||||
StringBuffer out = new StringBuffer();
|
StringBuilder out = new StringBuilder();
|
||||||
for (Iterator<String> i = reasons.iterator(); i.hasNext();)
|
for (Iterator<String> i = reasons.iterator(); i.hasNext();) {
|
||||||
{
|
|
||||||
out.append("Reason: ");
|
out.append("Reason: ");
|
||||||
out.append(i.next());
|
out.append(i.next());
|
||||||
if (i.hasNext()) out.append("\n");
|
if (i.hasNext())
|
||||||
|
out.append("\n");
|
||||||
}
|
}
|
||||||
return out.toString();
|
return out.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
private int processRRset(RRset rrset) {
|
||||||
private int processRRset(RRset rrset)
|
List<String> reasons = new ArrayList<>();
|
||||||
{
|
boolean result = false;
|
||||||
List<String> reasons = new ArrayList<String>();
|
|
||||||
int result = DNSSEC.Failed;
|
|
||||||
|
|
||||||
for (Iterator<Record> i = rrset.sigs(); i.hasNext();)
|
for (RRSIGRecord sigrec : rrset.sigs()) {
|
||||||
{
|
boolean res = mVerifier.verifySignature(rrset, sigrec, reasons);
|
||||||
RRSIGRecord sigrec = (RRSIGRecord) i.next();
|
if (!res) {
|
||||||
byte res = mVerifier.verifySignature(rrset, sigrec, null, reasons);
|
|
||||||
if (res != DNSSEC.Secure)
|
|
||||||
{
|
|
||||||
log.warning("Signature failed to verify RRset:\n rr: "
|
log.warning("Signature failed to verify RRset:\n rr: "
|
||||||
+ ZoneUtils.rrsetToString(rrset, false) + "\n sig: " + sigrec + "\n"
|
+ ZoneUtils.rrsetToString(rrset, false) + "\n sig: " + sigrec + "\n"
|
||||||
+ reasonListToString(reasons));
|
+ reasonListToString(reasons));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res > result) result = res;
|
if (res)
|
||||||
|
result = res;
|
||||||
}
|
}
|
||||||
|
|
||||||
String rrsetname = rrset.getName() + "/" + Type.string(rrset.getType());
|
String rrsetname = rrset.getName() + "/" + Type.string(rrset.getType());
|
||||||
if (result == DNSSEC.Secure)
|
if (result) {
|
||||||
{
|
|
||||||
log.fine("RRset " + rrsetname + " verified.");
|
log.fine("RRset " + rrsetname + " verified.");
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
log.warning("RRset " + rrsetname + " did not verify.");
|
log.warning("RRset " + rrsetname + " did not verify.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return result == DNSSEC.Secure ? 0 : 1;
|
return result ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String typesToString(int[] types)
|
private String typesToString(int[] types) {
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
Arrays.sort(types);
|
Arrays.sort(types);
|
||||||
|
|
||||||
for (int i = 0; i < types.length; ++i)
|
for (int i = 0; i < types.length; ++i) {
|
||||||
{
|
if (i != 0)
|
||||||
if (i != 0) sb.append(' ');
|
sb.append(' ');
|
||||||
sb.append(Type.string(types[i]));
|
sb.append(Type.string(types[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String typesetToString(Set<Integer> typeset)
|
private String typesetToString(Set<Integer> typeset) {
|
||||||
{
|
if (typeset == null)
|
||||||
if (typeset == null) return "";
|
return "";
|
||||||
|
|
||||||
int[] types = new int[typeset.size()];
|
int[] types = new int[typeset.size()];
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (int type : typeset)
|
for (int type : typeset) {
|
||||||
{
|
|
||||||
types[i++] = type;
|
types[i++] = type;
|
||||||
}
|
}
|
||||||
return typesToString(types);
|
return typesToString(types);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkTypeMap(Set<Integer> typeset, int[] types)
|
private boolean checkTypeMap(Set<Integer> typeset, int[] types) {
|
||||||
{
|
|
||||||
// a null typeset means that we are expecting the typemap of an ENT, which
|
// a null typeset means that we are expecting the typemap of an ENT, which
|
||||||
// should be empty.
|
// should be empty.
|
||||||
if (typeset == null) return types.length == 0;
|
if (typeset == null)
|
||||||
|
return types.length == 0;
|
||||||
|
|
||||||
Set<Integer> compareTypeset = new HashSet<Integer>();
|
Set<Integer> compareTypeset = new HashSet<>();
|
||||||
for (int i = 0; i < types.length; ++i)
|
for (int i = 0; i < types.length; ++i) {
|
||||||
{
|
|
||||||
compareTypeset.add(types[i]);
|
compareTypeset.add(types[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return typeset.equals(compareTypeset);
|
return typeset.equals(compareTypeset);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int processNSEC(Name n, Set<Integer> typeset)
|
private int processNSEC(Name n, Set<Integer> typeset) {
|
||||||
{
|
|
||||||
MarkRRset rrset = mNSECMap.get(n);
|
MarkRRset rrset = mNSECMap.get(n);
|
||||||
if (n == null)
|
if (n == null) {
|
||||||
{
|
|
||||||
log.warning("Missing NSEC for " + n);
|
log.warning("Missing NSEC for " + n);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -441,8 +453,7 @@ public class ZoneVerifier
|
|||||||
NSECRecord nsec = (NSECRecord) rrset.first();
|
NSECRecord nsec = (NSECRecord) rrset.first();
|
||||||
|
|
||||||
// check typemap
|
// check typemap
|
||||||
if (!checkTypeMap(typeset, nsec.getTypes()))
|
if (!checkTypeMap(typeset, nsec.getTypes())) {
|
||||||
{
|
|
||||||
log.warning("Typemap for NSEC RR " + n
|
log.warning("Typemap for NSEC RR " + n
|
||||||
+ " did not match what was expected. Expected '" + typesetToString(typeset)
|
+ " did not match what was expected. Expected '" + typesetToString(typeset)
|
||||||
+ "', got '" + typesToString(nsec.getTypes()));
|
+ "', got '" + typesToString(nsec.getTypes()));
|
||||||
@@ -455,32 +466,27 @@ public class ZoneVerifier
|
|||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldCheckENTs(Name n, Set<Integer> typeset, NodeType ntype)
|
private boolean shouldCheckENTs(Name n, Set<Integer> typeset, NodeType ntype) {
|
||||||
{
|
|
||||||
// if we are just one (or zero) labels longer than the zonename, the node
|
// if we are just one (or zero) labels longer than the zonename, the node
|
||||||
// can't create a ENT
|
// can't create a ENT
|
||||||
if (n.labels() <= mZoneName.labels() + 1) return false;
|
if (n.labels() <= mZoneName.labels() + 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
// we probably won't ever get called for a GLUE node
|
// we probably won't ever get called for a GLUE node
|
||||||
if (ntype == NodeType.GLUE) return false;
|
if (ntype == NodeType.GLUE)
|
||||||
|
return false;
|
||||||
|
|
||||||
// if we aren't doing opt-out, then all possible ENTs must be checked.
|
// if we aren't doing opt-out, then all possible ENTs must be checked.
|
||||||
if (mDNSSECType == DNSSECType.NSEC3) return true;
|
if (mDNSSECType == DNSSECType.NSEC3)
|
||||||
|
return true;
|
||||||
|
|
||||||
// if we are opt-out, and the node is an insecure delegation, don't check
|
// if we are opt-out, and the node is an insecure delegation, don't check
|
||||||
// ENTs.
|
// ENTs.
|
||||||
if (ntype == NodeType.DELEGATION && !typeset.contains(Type.DS))
|
return !(ntype == NodeType.DELEGATION && !typeset.contains(Type.DS));
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise, check ENTs.
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int processNSEC3(Name n, Set<Integer> typeset, NodeType ntype)
|
private int processNSEC3(Name n, Set<Integer> typeset, NodeType ntype)
|
||||||
throws NoSuchAlgorithmException, TextParseException
|
throws NoSuchAlgorithmException, TextParseException {
|
||||||
{
|
|
||||||
// calculate the NSEC3 RR name
|
// calculate the NSEC3 RR name
|
||||||
byte[] hash = mNSEC3params.hashName(n);
|
byte[] hash = mNSEC3params.hashName(n);
|
||||||
|
|
||||||
@@ -488,8 +494,7 @@ public class ZoneVerifier
|
|||||||
Name hashname = new Name(hashstr, mZoneName);
|
Name hashname = new Name(hashstr, mZoneName);
|
||||||
|
|
||||||
MarkRRset rrset = mNSEC3Map.get(hashname);
|
MarkRRset rrset = mNSEC3Map.get(hashname);
|
||||||
if (rrset == null)
|
if (rrset == null) {
|
||||||
{
|
|
||||||
log.warning("Missing NSEC3 for " + hashname + " corresponding to " + n);
|
log.warning("Missing NSEC3 for " + hashname + " corresponding to " + n);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -501,8 +506,7 @@ public class ZoneVerifier
|
|||||||
NSEC3Record nsec3 = (NSEC3Record) rrset.first();
|
NSEC3Record nsec3 = (NSEC3Record) rrset.first();
|
||||||
|
|
||||||
// check typemap
|
// check typemap
|
||||||
if (!checkTypeMap(typeset, nsec3.getTypes()))
|
if (!checkTypeMap(typeset, nsec3.getTypes())) {
|
||||||
{
|
|
||||||
log.warning("Typemap for NSEC3 RR " + hashname + " for " + n
|
log.warning("Typemap for NSEC3 RR " + hashname + " for " + n
|
||||||
+ " did not match what was expected. Expected '" + typesetToString(typeset)
|
+ " did not match what was expected. Expected '" + typesetToString(typeset)
|
||||||
+ "', got '" + typesToString(nsec3.getTypes()) + "'");
|
+ "', got '" + typesToString(nsec3.getTypes()) + "'");
|
||||||
@@ -514,11 +518,9 @@ public class ZoneVerifier
|
|||||||
|
|
||||||
// check NSEC3 RRs for empty non-terminals.
|
// check NSEC3 RRs for empty non-terminals.
|
||||||
// this is recursive.
|
// this is recursive.
|
||||||
if (shouldCheckENTs(n, typeset, ntype))
|
if (shouldCheckENTs(n, typeset, ntype)) {
|
||||||
{
|
|
||||||
Name ent = new Name(n, 1);
|
Name ent = new Name(n, 1);
|
||||||
if (mNodeMap.get(ent) == null)
|
if (mNodeMap.get(ent) == null) {
|
||||||
{
|
|
||||||
errors += processNSEC3(ent, null, NodeType.NORMAL);
|
errors += processNSEC3(ent, null, NodeType.NORMAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -526,24 +528,18 @@ public class ZoneVerifier
|
|||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int processNSECChain()
|
private int processNSECChain() {
|
||||||
{
|
|
||||||
int errors = 0;
|
int errors = 0;
|
||||||
NSECRecord lastNSEC = null;
|
NSECRecord lastNSEC = null;
|
||||||
|
|
||||||
for (Iterator<Map.Entry<Name, MarkRRset>> i = mNSECMap.entrySet().iterator(); i.hasNext();)
|
for (Iterator<Map.Entry<Name, MarkRRset>> i = mNSECMap.entrySet().iterator(); i.hasNext();) {
|
||||||
{
|
|
||||||
// check the internal ordering of the previous NSEC record. This avoids
|
// check the internal ordering of the previous NSEC record. This avoids
|
||||||
// looking at the last one,
|
// looking at the last one,
|
||||||
// which is different.
|
// which is different.
|
||||||
if (lastNSEC != null)
|
if (lastNSEC != null && lastNSEC.getName().compareTo(lastNSEC.getNext()) >= 0) {
|
||||||
{
|
log.warning("NSEC for " + lastNSEC.getName()
|
||||||
if (lastNSEC.getName().compareTo(lastNSEC.getNext()) >= 0)
|
+ " has next name >= owner but is not the last NSEC in the chain.");
|
||||||
{
|
errors++;
|
||||||
log.warning("NSEC for " + lastNSEC.getName()
|
|
||||||
+ " has next name >= owner but is not the last NSEC in the chain.");
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map.Entry<Name, MarkRRset> entry = i.next();
|
Map.Entry<Name, MarkRRset> entry = i.next();
|
||||||
@@ -552,8 +548,7 @@ public class ZoneVerifier
|
|||||||
|
|
||||||
// check to see if the NSEC is marked. If not, it was not correlated to a
|
// check to see if the NSEC is marked. If not, it was not correlated to a
|
||||||
// signed node.
|
// signed node.
|
||||||
if (!rrset.getMark())
|
if (!rrset.getMark()) {
|
||||||
{
|
|
||||||
log.warning("NSEC RR for " + n + " appears to be extra.");
|
log.warning("NSEC RR for " + n + " appears to be extra.");
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
@@ -563,30 +558,24 @@ public class ZoneVerifier
|
|||||||
// This is just a sanity check. If this isn't true, we are constructing
|
// This is just a sanity check. If this isn't true, we are constructing
|
||||||
// the
|
// the
|
||||||
// nsec map incorrectly.
|
// nsec map incorrectly.
|
||||||
if (!n.equals(nsec.getName()))
|
if (!n.equals(nsec.getName())) {
|
||||||
{
|
|
||||||
log.warning("The NSEC in the map for name " + n + " has name " + nsec.getName());
|
log.warning("The NSEC in the map for name " + n + " has name " + nsec.getName());
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is the first row, ensure that the owner name equals the zone
|
// If this is the first row, ensure that the owner name equals the zone
|
||||||
// name
|
// name
|
||||||
if (lastNSEC == null && !n.equals(mZoneName))
|
if (lastNSEC == null && !n.equals(mZoneName)) {
|
||||||
{
|
|
||||||
log.warning("The first NSEC in the chain does not match the zone name: name = "
|
log.warning("The first NSEC in the chain does not match the zone name: name = "
|
||||||
+ n + " zonename = " + mZoneName);
|
+ n + " zonename = " + mZoneName);
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the prior NSEC's next name equals this rows owner name.
|
// Check that the prior NSEC's next name equals this rows owner name.
|
||||||
if (lastNSEC != null)
|
if (lastNSEC != null && !lastNSEC.getNext().equals(nsec.getName())) {
|
||||||
{
|
log.warning("NSEC for " + lastNSEC.getName()
|
||||||
if (!lastNSEC.getNext().equals(nsec.getName()))
|
+ " does not point to the next NSEC in the chain: " + n);
|
||||||
{
|
errors++;
|
||||||
log.warning("NSEC for " + lastNSEC.getName()
|
|
||||||
+ " does not point to the next NSEC in the chain: " + n);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastNSEC = nsec;
|
lastNSEC = nsec;
|
||||||
@@ -594,16 +583,14 @@ public class ZoneVerifier
|
|||||||
|
|
||||||
// check the internal ordering of the last NSEC in the chain
|
// check the internal ordering of the last NSEC in the chain
|
||||||
// the ownername should be >= next name.
|
// the ownername should be >= next name.
|
||||||
if (lastNSEC.getName().compareTo(lastNSEC.getNext()) < 0)
|
if (lastNSEC.getName().compareTo(lastNSEC.getNext()) < 0) {
|
||||||
{
|
|
||||||
log.warning("The last NSEC RR in the chain did not have an owner >= next: owner = "
|
log.warning("The last NSEC RR in the chain did not have an owner >= next: owner = "
|
||||||
+ lastNSEC.getName() + " next = " + lastNSEC.getNext());
|
+ lastNSEC.getName() + " next = " + lastNSEC.getNext());
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check to make sure it links to the first NSEC in the chain
|
// check to make sure it links to the first NSEC in the chain
|
||||||
if (!lastNSEC.getNext().equals(mZoneName))
|
if (!lastNSEC.getNext().equals(mZoneName)) {
|
||||||
{
|
|
||||||
log.warning("The last NSEC RR in the chain did not link to the first NSEC");
|
log.warning("The last NSEC RR in the chain did not link to the first NSEC");
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
@@ -611,8 +598,7 @@ public class ZoneVerifier
|
|||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int compareNSEC3Hashes(Name owner, byte[] hash)
|
private int compareNSEC3Hashes(Name owner, byte[] hash) {
|
||||||
{
|
|
||||||
// we will compare the binary images
|
// we will compare the binary images
|
||||||
String ownerhashstr = owner.getLabelString(0);
|
String ownerhashstr = owner.getLabelString(0);
|
||||||
byte[] ownerhash = mBase32.fromString(ownerhashstr);
|
byte[] ownerhash = mBase32.fromString(ownerhashstr);
|
||||||
@@ -620,25 +606,19 @@ public class ZoneVerifier
|
|||||||
return mBAcmp.compare(ownerhash, hash);
|
return mBAcmp.compare(ownerhash, hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int processNSEC3Chain()
|
private int processNSEC3Chain() {
|
||||||
{
|
|
||||||
int errors = 0;
|
int errors = 0;
|
||||||
NSEC3Record lastNSEC3 = null;
|
NSEC3Record lastNSEC3 = null;
|
||||||
NSEC3Record firstNSEC3 = null;
|
NSEC3Record firstNSEC3 = null;
|
||||||
|
|
||||||
for (Iterator<Map.Entry<Name, MarkRRset>> i = mNSEC3Map.entrySet().iterator(); i.hasNext();)
|
for (Iterator<Map.Entry<Name, MarkRRset>> i = mNSEC3Map.entrySet().iterator(); i.hasNext();) {
|
||||||
{
|
|
||||||
// check the internal ordering of the previous NSEC3 record. This avoids
|
// check the internal ordering of the previous NSEC3 record. This avoids
|
||||||
// looking at the last one,
|
// looking at the last one,
|
||||||
// which is different.
|
// which is different.
|
||||||
if (lastNSEC3 != null)
|
if (lastNSEC3 != null && compareNSEC3Hashes(lastNSEC3.getName(), lastNSEC3.getNext()) >= 0) {
|
||||||
{
|
log.warning("NSEC3 for " + lastNSEC3.getName()
|
||||||
if (compareNSEC3Hashes(lastNSEC3.getName(), lastNSEC3.getNext()) >= 0)
|
+ " has next name >= owner but is not the last NSEC3 in the chain.");
|
||||||
{
|
errors++;
|
||||||
log.warning("NSEC3 for " + lastNSEC3.getName()
|
|
||||||
+ " has next name >= owner but is not the last NSEC3 in the chain.");
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map.Entry<Name, MarkRRset> entry = i.next();
|
Map.Entry<Name, MarkRRset> entry = i.next();
|
||||||
@@ -647,8 +627,7 @@ public class ZoneVerifier
|
|||||||
|
|
||||||
// check to see if the NSEC is marked. If not, it was not correlated to a
|
// check to see if the NSEC is marked. If not, it was not correlated to a
|
||||||
// signed node.
|
// signed node.
|
||||||
if (!rrset.getMark())
|
if (!rrset.getMark()) {
|
||||||
{
|
|
||||||
log.warning("NSEC3 RR for " + n + " appears to be extra.");
|
log.warning("NSEC3 RR for " + n + " appears to be extra.");
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
@@ -658,23 +637,19 @@ public class ZoneVerifier
|
|||||||
// This is just a sanity check. If this isn't true, we are constructing
|
// This is just a sanity check. If this isn't true, we are constructing
|
||||||
// the
|
// the
|
||||||
// nsec3 map incorrectly.
|
// nsec3 map incorrectly.
|
||||||
if (!n.equals(nsec3.getName()))
|
if (!n.equals(nsec3.getName())) {
|
||||||
{
|
|
||||||
log.severe("The NSEC3 in the map for name " + n + " has name " + nsec3.getName());
|
log.severe("The NSEC3 in the map for name " + n + " has name " + nsec3.getName());
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// note the first NSEC3 in the chain.
|
// note the first NSEC3 in the chain.
|
||||||
if (lastNSEC3 == null)
|
if (lastNSEC3 == null) {
|
||||||
{
|
|
||||||
firstNSEC3 = nsec3;
|
firstNSEC3 = nsec3;
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
// Check that the prior NSEC3's next hashed name equals this row's hashed
|
// Check that the prior NSEC3's next hashed name equals this row's hashed
|
||||||
// owner name.
|
// owner name.
|
||||||
{
|
{
|
||||||
if (compareNSEC3Hashes(nsec3.getName(), lastNSEC3.getNext()) != 0)
|
if (compareNSEC3Hashes(nsec3.getName(), lastNSEC3.getNext()) != 0) {
|
||||||
{
|
|
||||||
String nextstr = mBase32.toString(lastNSEC3.getNext());
|
String nextstr = mBase32.toString(lastNSEC3.getNext());
|
||||||
log.warning("NSEC3 for " + lastNSEC3.getName()
|
log.warning("NSEC3 for " + lastNSEC3.getName()
|
||||||
+ " does not point to the next NSEC3 in the chain: " + nsec3.getName()
|
+ " does not point to the next NSEC3 in the chain: " + nsec3.getName()
|
||||||
@@ -688,8 +663,7 @@ public class ZoneVerifier
|
|||||||
|
|
||||||
// check the internal ordering of the last NSEC in the chain
|
// check the internal ordering of the last NSEC in the chain
|
||||||
// the ownername should be >= next name.
|
// the ownername should be >= next name.
|
||||||
if (compareNSEC3Hashes(lastNSEC3.getName(), lastNSEC3.getNext()) < 0)
|
if (compareNSEC3Hashes(lastNSEC3.getName(), lastNSEC3.getNext()) < 0) {
|
||||||
{
|
|
||||||
String nextstr = mBase32.toString(lastNSEC3.getNext());
|
String nextstr = mBase32.toString(lastNSEC3.getNext());
|
||||||
log.warning("The last NSEC3 RR in the chain did not have an owner >= next: owner = "
|
log.warning("The last NSEC3 RR in the chain did not have an owner >= next: owner = "
|
||||||
+ lastNSEC3.getName() + " next = " + nextstr);
|
+ lastNSEC3.getName() + " next = " + nextstr);
|
||||||
@@ -697,8 +671,7 @@ public class ZoneVerifier
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check to make sure it links to the first NSEC in the chain
|
// check to make sure it links to the first NSEC in the chain
|
||||||
if (compareNSEC3Hashes(firstNSEC3.getName(), lastNSEC3.getNext()) != 0)
|
if (compareNSEC3Hashes(firstNSEC3.getName(), lastNSEC3.getNext()) != 0) {
|
||||||
{
|
|
||||||
log.warning("The last NSEC3 RR in the chain did not link to the first NSEC3");
|
log.warning("The last NSEC3 RR in the chain did not link to the first NSEC3");
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
@@ -706,29 +679,22 @@ public class ZoneVerifier
|
|||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int verifyZone(List<Record> records) throws NoSuchAlgorithmException, TextParseException
|
public int verifyZone(List<Record> records) throws NoSuchAlgorithmException, TextParseException {
|
||||||
{
|
|
||||||
int errors = 0;
|
int errors = 0;
|
||||||
|
|
||||||
calculateNodes(records);
|
errors += calculateNodes(records);
|
||||||
|
|
||||||
errors = processNodes();
|
errors += processNodes();
|
||||||
|
|
||||||
if (mDNSSECType == DNSSECType.NSEC)
|
if (mDNSSECType == DNSSECType.NSEC) {
|
||||||
{
|
|
||||||
errors += processNSECChain();
|
errors += processNSECChain();
|
||||||
}
|
} else if (mDNSSECType == DNSSECType.NSEC3 || mDNSSECType == DNSSECType.NSEC3_OPTOUT) {
|
||||||
else if (mDNSSECType == DNSSECType.NSEC3 || mDNSSECType == DNSSECType.NSEC3_OPTOUT)
|
|
||||||
{
|
|
||||||
errors += processNSEC3Chain();
|
errors += processNSEC3Chain();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors > 0)
|
if (errors > 0) {
|
||||||
{
|
|
||||||
log.info("Zone " + mZoneName + " failed verification with " + errors + " errors");
|
log.info("Zone " + mZoneName + " failed verification with " + errors + " errors");
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
log.info("Zone " + mZoneName + " verified with 0 errors");
|
log.info("Zone " + mZoneName + " verified with 0 errors");
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user