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