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