001/*
002 * Copyright 2015-2024 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), 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(algorithmByte);
144        dos.write(key);
145    }
146
147    @Override
148    public String toString() {
149        return flags + " " + protocol + " " + algorithm + " " + Base64.encodeToString(key);
150    }
151
152    public int getKeyLength() {
153        return key.length;
154    }
155
156    public byte[] getKey() {
157        return key.clone();
158    }
159
160    public DataInputStream getKeyAsDataInputStream() {
161        return new DataInputStream(new ByteArrayInputStream(key));
162    }
163
164    private transient String keyBase64Cache;
165
166    public String getKeyBase64() {
167        if (keyBase64Cache == null) {
168            keyBase64Cache = Base64.encodeToString(key);
169        }
170        return keyBase64Cache;
171    }
172
173    private transient BigInteger keyBigIntegerCache;
174
175    public BigInteger getKeyBigInteger() {
176        if (keyBigIntegerCache == null) {
177            keyBigIntegerCache = new BigInteger(key);
178        }
179        return keyBigIntegerCache;
180    }
181
182    public boolean keyEquals(byte[] otherKey) {
183        return Arrays.equals(key, otherKey);
184    }
185
186    public boolean isSecureEntryPoint() {
187        return (flags & FLAG_SECURE_ENTRY_POINT) == 1;
188    }
189}