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}