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}