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}