001/*
002 * Copyright 2017-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2017-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2017-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util.ssl.cert;
037
038
039
040import java.math.BigInteger;
041
042import com.unboundid.asn1.ASN1BitString;
043import com.unboundid.util.Debug;
044import com.unboundid.util.NotMutable;
045import com.unboundid.util.NotNull;
046import com.unboundid.util.Nullable;
047import com.unboundid.util.StaticUtils;
048import com.unboundid.util.ThreadSafety;
049import com.unboundid.util.ThreadSafetyLevel;
050
051import static com.unboundid.util.ssl.cert.CertMessages.*;
052
053
054
055/**
056 * This class provides a data structure for representing the information
057 * contained in an elliptic curve public key in an X.509 certificate.  As per
058 * <A HREF="https://www.ietf.org/rfc/rfc5480.txt">RFC 5480</A> section 2.2,
059 * and the Standards for Efficient Cryptography SEC 1 document.
060 */
061@NotMutable()
062@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
063public final class EllipticCurvePublicKey
064       extends DecodedPublicKey
065{
066  /**
067   * The serial version UID for this serializable class.
068   */
069  private static final long serialVersionUID = 7537378153089968013L;
070
071
072
073  // Indicates whether the y coordinate is even or odd.
074  private final boolean yCoordinateIsEven;
075
076  // The x coordinate for the public key.
077  @NotNull private final BigInteger xCoordinate;
078
079  // The y coordinate for the public key.
080  @Nullable private final BigInteger yCoordinate;
081
082
083
084  /**
085   * Creates a new elliptic curve public key with the provided information.
086   *
087   * @param  xCoordinate  The x coordinate for the public key.  This must not be
088   *                      {@code null}.
089   * @param  yCoordinate  The y coordinate for the public key.  This must not be
090   *                      {@code null}.
091   */
092  EllipticCurvePublicKey(@NotNull final BigInteger xCoordinate,
093                         @NotNull final BigInteger yCoordinate)
094  {
095    this.xCoordinate = xCoordinate;
096    this.yCoordinate = yCoordinate;
097    yCoordinateIsEven =
098         yCoordinate.mod(BigInteger.valueOf(2L)).equals(BigInteger.ZERO);
099  }
100
101
102
103  /**
104   * Creates a new elliptic curve public key with the provided information.
105   *
106   * @param  xCoordinate        The x coordinate for the public key.  This must
107   *                            not be {@code null}.
108   * @param  yCoordinateIsEven  Indicates whether the y coordinate for the
109   *                            public key is even.
110   */
111  EllipticCurvePublicKey(@NotNull final BigInteger xCoordinate,
112                         final boolean yCoordinateIsEven)
113  {
114    this.xCoordinate = xCoordinate;
115    this.yCoordinateIsEven = yCoordinateIsEven;
116
117    yCoordinate = null;
118  }
119
120
121
122  /**
123   * Creates a new elliptic curve decoded public key from the provided bit
124   * string.
125   *
126   * @param  subjectPublicKey  The bit string containing the encoded public key.
127   *
128   * @throws  CertException  If the provided public key cannot be decoded as an
129   *                         elliptic curve public key.
130   */
131  EllipticCurvePublicKey(@NotNull final ASN1BitString subjectPublicKey)
132       throws CertException
133  {
134    try
135    {
136      final byte[] xBytes;
137      final byte[] yBytes;
138      final byte[] keyBytes = subjectPublicKey.getBytes();
139      switch (keyBytes.length)
140      {
141        case 33:
142          yCoordinate = null;
143          if (keyBytes[0] == 0x02)
144          {
145            yCoordinateIsEven = true;
146          }
147          else if (keyBytes[0] == 0x03)
148          {
149            yCoordinateIsEven = false;
150          }
151          else
152          {
153            throw new CertException(
154                 ERR_EC_PUBLIC_KEY_PARSE_UNEXPECTED_COMPRESSED_FIRST_BYTE.get(
155                      keyBytes.length, StaticUtils.toHex(keyBytes[0])));
156          }
157
158          xBytes = new byte[32];
159          System.arraycopy(keyBytes, 1, xBytes, 0, 32);
160          xCoordinate = new BigInteger(xBytes);
161          break;
162
163        case 49:
164          yCoordinate = null;
165          if (keyBytes[0] == 0x02)
166          {
167            yCoordinateIsEven = true;
168          }
169          else if (keyBytes[0] == 0x03)
170          {
171            yCoordinateIsEven = false;
172          }
173          else
174          {
175            throw new CertException(
176                 ERR_EC_PUBLIC_KEY_PARSE_UNEXPECTED_COMPRESSED_FIRST_BYTE.get(
177                      keyBytes.length, StaticUtils.toHex(keyBytes[0])));
178          }
179
180          xBytes = new byte[48];
181          System.arraycopy(keyBytes, 1, xBytes, 0, 48);
182          xCoordinate = new BigInteger(xBytes);
183          break;
184
185        case 65:
186          if (keyBytes[0] != 0x04)
187          {
188            throw new CertException(
189                 ERR_EC_PUBLIC_KEY_PARSE_UNEXPECTED_UNCOMPRESSED_FIRST_BYTE.get(
190                      keyBytes.length, StaticUtils.toHex(keyBytes[0])));
191          }
192
193          xBytes = new byte[32];
194          yBytes = new byte[32];
195          System.arraycopy(keyBytes, 1, xBytes, 0, 32);
196          System.arraycopy(keyBytes, 33, yBytes, 0, 32);
197          xCoordinate = new BigInteger(xBytes);
198          yCoordinate = new BigInteger(yBytes);
199          yCoordinateIsEven = ((keyBytes[64] & 0x01) == 0x00);
200          break;
201
202        case 97:
203          if (keyBytes[0] != 0x04)
204          {
205            throw new CertException(
206                 ERR_EC_PUBLIC_KEY_PARSE_UNEXPECTED_UNCOMPRESSED_FIRST_BYTE.get(
207                      keyBytes.length, StaticUtils.toHex(keyBytes[0])));
208          }
209
210          xBytes = new byte[48];
211          yBytes = new byte[48];
212          System.arraycopy(keyBytes, 1, xBytes, 0, 48);
213          System.arraycopy(keyBytes, 49, yBytes, 0, 48);
214          xCoordinate = new BigInteger(xBytes);
215          yCoordinate = new BigInteger(yBytes);
216          yCoordinateIsEven = ((keyBytes[96] & 0x01) == 0x00);
217          break;
218
219        default:
220          throw new CertException(
221               ERR_EC_PUBLIC_KEY_PARSE_UNEXPECTED_SIZE.get(keyBytes.length));
222      }
223    }
224    catch (final CertException e)
225    {
226      Debug.debugException(e);
227      throw e;
228    }
229    catch (final Exception e)
230    {
231      Debug.debugException(e);
232      throw new CertException(
233           ERR_EC_PUBLIC_KEY_PARSE_ERROR.get(
234                StaticUtils.getExceptionMessage(e)),
235           e);
236    }
237  }
238
239
240
241  /**
242   * Encodes this elliptic curve public key.
243   *
244   * @return  The encoded public key.
245   *
246   * @throws  CertException  If a problem is encountered while encoding this
247   *                         public key.
248   */
249  @NotNull()
250  ASN1BitString encode()
251       throws CertException
252  {
253    final byte[] publicKeyBytes;
254    if (yCoordinate == null)
255    {
256      publicKeyBytes = new byte[33];
257      if (yCoordinateIsEven)
258      {
259        publicKeyBytes[0] = 0x02;
260      }
261      else
262      {
263        publicKeyBytes[0] = 0x03;
264      }
265    }
266    else
267    {
268      publicKeyBytes = new byte[65];
269      publicKeyBytes[0] = 0x04;
270    }
271
272    final byte[] xCoordinateBytes = xCoordinate.toByteArray();
273    if (xCoordinateBytes.length > 32)
274    {
275      throw new CertException(ERR_EC_PUBLIC_KEY_ENCODE_X_TOO_LARGE.get(
276           toString(), xCoordinateBytes.length));
277    }
278
279    final int xStartPos = 33 - xCoordinateBytes.length;
280    System.arraycopy(xCoordinateBytes, 0, publicKeyBytes, xStartPos,
281         xCoordinateBytes.length);
282
283    if (yCoordinate != null)
284    {
285      final byte[] yCoordinateBytes = yCoordinate.toByteArray();
286      if (yCoordinateBytes.length > 32)
287      {
288        throw new CertException(ERR_EC_PUBLIC_KEY_ENCODE_Y_TOO_LARGE.get(
289             toString(), yCoordinateBytes.length));
290      }
291
292      final int yStartPos = 65 - yCoordinateBytes.length;
293      System.arraycopy(yCoordinateBytes, 0, publicKeyBytes, yStartPos,
294           yCoordinateBytes.length);
295    }
296
297    final boolean[] bits = ASN1BitString.getBitsForBytes(publicKeyBytes);
298    return new ASN1BitString(bits);
299  }
300
301
302
303  /**
304   * Indicates whether the public key uses the compressed form (which merely
305   * contains the x coordinate and an indication as to whether the y coordinate
306   * is even or odd) or the uncompressed form (which contains both the x and
307   * y coordinate values).
308   *
309   * @return  {@code true} if the public key uses the compressed form, or
310   *          {@code false} if it uses the uncompressed form.
311   */
312  public boolean usesCompressedForm()
313  {
314    return (yCoordinate == null);
315  }
316
317
318
319  /**
320   * Retrieves the value of the x coordinate.  This will always be available.
321   *
322   * @return  The value of the x coordinate.
323   */
324  @NotNull()
325  public BigInteger getXCoordinate()
326  {
327    return xCoordinate;
328  }
329
330
331
332  /**
333   * Retrieves the value of the y coordinate.  This will only be available if
334   * the key was encoded in the uncompressed form.
335   *
336   * @return  The value of the y coordinate, or {@code null} if the key was
337   *          encoded in the compressed form.
338   */
339  @Nullable()
340  public BigInteger getYCoordinate()
341  {
342    return yCoordinate;
343  }
344
345
346
347  /**
348   * Indicates whether the y coordinate is even or odd.
349   *
350   * @return  {@code true} if the y coordinate is even, or {@code false} if the
351   *          y coordinate is odd.
352   */
353  public boolean yCoordinateIsEven()
354  {
355    return yCoordinateIsEven;
356  }
357
358
359
360  /**
361   * {@inheritDoc}
362   */
363  @Override()
364  public void toString(@NotNull final StringBuilder buffer)
365  {
366    buffer.append("EllipticCurvePublicKey(usesCompressedForm=");
367    buffer.append(yCoordinate == null);
368    buffer.append(", xCoordinate=");
369    buffer.append(xCoordinate);
370
371    if (yCoordinate == null)
372    {
373      buffer.append(", yCoordinateIsEven=");
374      buffer.append(yCoordinateIsEven);
375    }
376    else
377    {
378      buffer.append(", yCoordinate=");
379      buffer.append(yCoordinate);
380    }
381
382    buffer.append(')');
383  }
384}