001/*
002 * Copyright 2015-2020 the original author or authors
003 *
004 * This software is licensed under the Apache License, Version 2.0,
005 * the GNU Lesser General Public License version 2 or later ("LGPL")
006 * and the WTFPL.
007 * You may choose either license to govern your use of this software only
008 * upon the condition that you accept all of the terms of either
009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
010 */
011package org.minidns.record;
012
013import org.minidns.constants.DnssecConstants.SignatureAlgorithm;
014import org.minidns.record.Record.TYPE;
015import org.minidns.util.Base64;
016
017import java.io.ByteArrayInputStream;
018import java.io.DataInputStream;
019import java.io.DataOutputStream;
020import java.io.IOException;
021import java.math.BigInteger;
022import java.util.Arrays;
023
024/**
025 * DNSKEY record payload.
026 */
027public class DNSKEY extends Data {
028    /**
029     * Whether the key should be used as a secure entry point key.
030     *
031     * see RFC 3757
032     */
033    public static final short FLAG_SECURE_ENTRY_POINT = 0x1;
034
035    /**
036     * Whether the record holds a revoked key.
037     */
038    public static final short FLAG_REVOKE = 0x80;
039
040    /**
041     * Whether the record holds a DNS zone key.
042     */
043    public static final short FLAG_ZONE = 0x100;
044
045    /**
046     * Use the protocol defined in RFC 4034.
047     */
048    public static final byte PROTOCOL_RFC4034 = 3;
049
050    /**
051     * Bitmap of flags: {@link #FLAG_SECURE_ENTRY_POINT}, {@link #FLAG_REVOKE}, {@link #FLAG_ZONE}.
052     *
053     * @see <a href="https://www.iana.org/assignments/dnskey-flags/dnskey-flags.xhtml">IANA - DNSKEY RR Flags</a>
054     */
055    public final short flags;
056
057    /**
058     * Must be {@link #PROTOCOL_RFC4034}.
059     */
060    public final byte protocol;
061
062    /**
063     * The public key's cryptographic algorithm used.
064     *
065     */
066    public final SignatureAlgorithm algorithm;
067
068    /**
069     * The byte value of the public key's cryptographic algorithm used.
070     *
071     */
072    public final byte algorithmByte;
073
074    /**
075     * The public key material. The format depends on the algorithm of the key being stored.
076     */
077    private final byte[] key;
078
079    /**
080     * This DNSKEY's key tag. Calculated just-in-time when using {@link #getKeyTag()}
081     */
082    private transient Integer keyTag;
083
084    public static DNSKEY parse(DataInputStream dis, int length) throws IOException {
085        short flags = dis.readShort();
086        byte protocol = dis.readByte();
087        byte algorithm = dis.readByte();
088        byte[] key = new byte[length - 4];
089        dis.readFully(key);
090        return new DNSKEY(flags, protocol, algorithm, key);
091    }
092
093    private DNSKEY(short flags, byte protocol, SignatureAlgorithm algorithm, byte algorithmByte, byte[] key) {
094        this.flags = flags;
095        this.protocol = protocol;
096
097        assert algorithmByte == (algorithm != null ? algorithm.number : algorithmByte);
098        this.algorithmByte = algorithmByte;
099        this.algorithm = algorithm != null ? algorithm : SignatureAlgorithm.forByte(algorithmByte);
100
101        this.key = key;
102    }
103
104    public DNSKEY(short flags, byte protocol, byte algorithm, byte[] key) {
105        this(flags, protocol, SignatureAlgorithm.forByte(algorithm), key);
106    }
107
108    public DNSKEY(short flags, byte protocol, SignatureAlgorithm algorithm, byte[] key) {
109        this(flags, protocol, algorithm, algorithm.number, key);
110    }
111
112    @Override
113    public TYPE getType() {
114        return TYPE.DNSKEY;
115    }
116
117    /**
118     * Retrieve the key tag identifying this DNSKEY.
119     * The key tag is used within the DS and RRSIG record to distinguish multiple keys for the same name.
120     *
121     * This implementation is based on the reference implementation shown in RFC 4034 Appendix B.
122     *
123     * @return this DNSKEY's key tag
124     */
125    public /* unsigned short */ int getKeyTag() {
126        if (keyTag == null) {
127            byte[] recordBytes = toByteArray();
128            long ac = 0;
129
130            for (int i = 0; i < recordBytes.length; ++i) {
131                ac += ((i & 1) > 0) ? recordBytes[i] & 0xFFL : ((recordBytes[i] & 0xFFL) << 8);
132            }
133            ac += (ac >> 16) & 0xFFFF;
134            keyTag = (int) (ac & 0xFFFF);
135        }
136        return keyTag;
137    }
138
139    @Override
140    public void serialize(DataOutputStream dos) throws IOException {
141        dos.writeShort(flags);
142        dos.writeByte(protocol);
143        dos.writeByte(algorithm.number);
144        dos.write(key);
145    }
146
147    @Override
148    public String toString() {
149        StringBuilder sb = new StringBuilder()
150                .append(flags).append(' ')
151                .append(protocol).append(' ')
152                .append(algorithm).append(' ')
153                .append(Base64.encodeToString(key));
154        return sb.toString();
155    }
156
157    public int getKeyLength() {
158        return key.length;
159    }
160
161    public byte[] getKey() {
162        return key.clone();
163    }
164
165    public DataInputStream getKeyAsDataInputStream() {
166        return new DataInputStream(new ByteArrayInputStream(key));
167    }
168
169    private transient String keyBase64Cache;
170
171    public String getKeyBase64() {
172        if (keyBase64Cache == null) {
173            keyBase64Cache = Base64.encodeToString(key);
174        }
175        return keyBase64Cache;
176    }
177
178    private transient BigInteger keyBigIntegerCache;
179
180    public BigInteger getKeyBigInteger() {
181        if (keyBigIntegerCache == null) {
182            keyBigIntegerCache = new BigInteger(key);
183        }
184        return keyBigIntegerCache;
185    }
186
187    public boolean keyEquals(byte[] otherKey) {
188        return Arrays.equals(key, otherKey);
189    }
190
191    public boolean isSecureEntryPoint() {
192        return (flags & FLAG_SECURE_ENTRY_POINT) == 1;
193    }
194}