mobile/android/base/browserid/DSACryptoImplementation.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 package org.mozilla.gecko.browserid;
michael@0 6
michael@0 7 import java.math.BigInteger;
michael@0 8 import java.security.GeneralSecurityException;
michael@0 9 import java.security.KeyFactory;
michael@0 10 import java.security.KeyPair;
michael@0 11 import java.security.KeyPairGenerator;
michael@0 12 import java.security.NoSuchAlgorithmException;
michael@0 13 import java.security.Signature;
michael@0 14 import java.security.interfaces.DSAParams;
michael@0 15 import java.security.interfaces.DSAPrivateKey;
michael@0 16 import java.security.interfaces.DSAPublicKey;
michael@0 17 import java.security.spec.DSAPrivateKeySpec;
michael@0 18 import java.security.spec.DSAPublicKeySpec;
michael@0 19 import java.security.spec.InvalidKeySpecException;
michael@0 20 import java.security.spec.KeySpec;
michael@0 21
michael@0 22 import org.mozilla.gecko.sync.ExtendedJSONObject;
michael@0 23 import org.mozilla.gecko.sync.NonObjectJSONException;
michael@0 24 import org.mozilla.gecko.sync.Utils;
michael@0 25
michael@0 26 public class DSACryptoImplementation {
michael@0 27 public static final String SIGNATURE_ALGORITHM = "SHA1withDSA";
michael@0 28 public static final int SIGNATURE_LENGTH_BYTES = 40; // DSA signatures are always 40 bytes long.
michael@0 29
michael@0 30 /**
michael@0 31 * Parameters are serialized as hex strings. Hex-versus-decimal was
michael@0 32 * reverse-engineered from what the Persona public verifier accepted. We
michael@0 33 * expect to follow the JOSE/JWT spec as it solidifies, and that will probably
michael@0 34 * mean unifying this base.
michael@0 35 */
michael@0 36 protected static final int SERIALIZATION_BASE = 16;
michael@0 37
michael@0 38 protected static class DSAVerifyingPublicKey implements VerifyingPublicKey {
michael@0 39 protected final DSAPublicKey publicKey;
michael@0 40
michael@0 41 public DSAVerifyingPublicKey(DSAPublicKey publicKey) {
michael@0 42 this.publicKey = publicKey;
michael@0 43 }
michael@0 44
michael@0 45 /**
michael@0 46 * Serialize to a JSON object.
michael@0 47 * <p>
michael@0 48 * Parameters are serialized as hex strings. Hex-versus-decimal was
michael@0 49 * reverse-engineered from what the Persona public verifier accepted.
michael@0 50 */
michael@0 51 @Override
michael@0 52 public ExtendedJSONObject toJSONObject() {
michael@0 53 DSAParams params = publicKey.getParams();
michael@0 54 ExtendedJSONObject o = new ExtendedJSONObject();
michael@0 55 o.put("algorithm", "DS");
michael@0 56 o.put("y", publicKey.getY().toString(SERIALIZATION_BASE));
michael@0 57 o.put("g", params.getG().toString(SERIALIZATION_BASE));
michael@0 58 o.put("p", params.getP().toString(SERIALIZATION_BASE));
michael@0 59 o.put("q", params.getQ().toString(SERIALIZATION_BASE));
michael@0 60 return o;
michael@0 61 }
michael@0 62
michael@0 63 @Override
michael@0 64 public boolean verifyMessage(byte[] bytes, byte[] signature)
michael@0 65 throws GeneralSecurityException {
michael@0 66 if (bytes == null) {
michael@0 67 throw new IllegalArgumentException("bytes must not be null");
michael@0 68 }
michael@0 69 if (signature == null) {
michael@0 70 throw new IllegalArgumentException("signature must not be null");
michael@0 71 }
michael@0 72 if (signature.length != SIGNATURE_LENGTH_BYTES) {
michael@0 73 return false;
michael@0 74 }
michael@0 75 byte[] first = new byte[signature.length / 2];
michael@0 76 byte[] second = new byte[signature.length / 2];
michael@0 77 System.arraycopy(signature, 0, first, 0, first.length);
michael@0 78 System.arraycopy(signature, first.length, second, 0, second.length);
michael@0 79 BigInteger r = new BigInteger(Utils.byte2Hex(first), 16);
michael@0 80 BigInteger s = new BigInteger(Utils.byte2Hex(second), 16);
michael@0 81 // This is awful, but encoding an extra 0 byte works better on devices.
michael@0 82 byte[] encoded = ASNUtils.encodeTwoArraysToASN1(
michael@0 83 Utils.hex2Byte(r.toString(16), 1 + SIGNATURE_LENGTH_BYTES / 2),
michael@0 84 Utils.hex2Byte(s.toString(16), 1 + SIGNATURE_LENGTH_BYTES / 2));
michael@0 85
michael@0 86 final Signature signer = Signature.getInstance(SIGNATURE_ALGORITHM);
michael@0 87 signer.initVerify(publicKey);
michael@0 88 signer.update(bytes);
michael@0 89 return signer.verify(encoded);
michael@0 90 }
michael@0 91 }
michael@0 92
michael@0 93 protected static class DSASigningPrivateKey implements SigningPrivateKey {
michael@0 94 protected final DSAPrivateKey privateKey;
michael@0 95
michael@0 96 public DSASigningPrivateKey(DSAPrivateKey privateKey) {
michael@0 97 this.privateKey = privateKey;
michael@0 98 }
michael@0 99
michael@0 100 @Override
michael@0 101 public String getAlgorithm() {
michael@0 102 return "DS" + (privateKey.getParams().getP().bitLength() + 7)/8;
michael@0 103 }
michael@0 104
michael@0 105 /**
michael@0 106 * Serialize to a JSON object.
michael@0 107 * <p>
michael@0 108 * Parameters are serialized as decimal strings. Hex-versus-decimal was
michael@0 109 * reverse-engineered from what the Persona public verifier accepted.
michael@0 110 */
michael@0 111 @Override
michael@0 112 public ExtendedJSONObject toJSONObject() {
michael@0 113 DSAParams params = privateKey.getParams();
michael@0 114 ExtendedJSONObject o = new ExtendedJSONObject();
michael@0 115 o.put("algorithm", "DS");
michael@0 116 o.put("x", privateKey.getX().toString(SERIALIZATION_BASE));
michael@0 117 o.put("g", params.getG().toString(SERIALIZATION_BASE));
michael@0 118 o.put("p", params.getP().toString(SERIALIZATION_BASE));
michael@0 119 o.put("q", params.getQ().toString(SERIALIZATION_BASE));
michael@0 120 return o;
michael@0 121 }
michael@0 122
michael@0 123 @Override
michael@0 124 public byte[] signMessage(byte[] bytes)
michael@0 125 throws GeneralSecurityException {
michael@0 126 if (bytes == null) {
michael@0 127 throw new IllegalArgumentException("bytes must not be null");
michael@0 128 }
michael@0 129 final Signature signer = Signature.getInstance(SIGNATURE_ALGORITHM);
michael@0 130 signer.initSign(privateKey);
michael@0 131 signer.update(bytes);
michael@0 132 final byte[] signature = signer.sign();
michael@0 133
michael@0 134 final byte[][] arrays = ASNUtils.decodeTwoArraysFromASN1(signature);
michael@0 135 BigInteger r = new BigInteger(arrays[0]);
michael@0 136 BigInteger s = new BigInteger(arrays[1]);
michael@0 137 // This is awful, but signatures are always 40 bytes long.
michael@0 138 byte[] decoded = Utils.concatAll(
michael@0 139 Utils.hex2Byte(r.toString(16), SIGNATURE_LENGTH_BYTES / 2),
michael@0 140 Utils.hex2Byte(s.toString(16), SIGNATURE_LENGTH_BYTES / 2));
michael@0 141 return decoded;
michael@0 142 }
michael@0 143 }
michael@0 144
michael@0 145 public static BrowserIDKeyPair generateKeyPair(int keysize)
michael@0 146 throws NoSuchAlgorithmException {
michael@0 147 final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
michael@0 148 keyPairGenerator.initialize(keysize);
michael@0 149 final KeyPair keyPair = keyPairGenerator.generateKeyPair();
michael@0 150 DSAPrivateKey privateKey = (DSAPrivateKey) keyPair.getPrivate();
michael@0 151 DSAPublicKey publicKey = (DSAPublicKey) keyPair.getPublic();
michael@0 152 return new BrowserIDKeyPair(new DSASigningPrivateKey(privateKey), new DSAVerifyingPublicKey(publicKey));
michael@0 153 }
michael@0 154
michael@0 155 public static SigningPrivateKey createPrivateKey(BigInteger x, BigInteger p, BigInteger q, BigInteger g) throws NoSuchAlgorithmException, InvalidKeySpecException {
michael@0 156 if (x == null) {
michael@0 157 throw new IllegalArgumentException("x must not be null");
michael@0 158 }
michael@0 159 if (p == null) {
michael@0 160 throw new IllegalArgumentException("p must not be null");
michael@0 161 }
michael@0 162 if (q == null) {
michael@0 163 throw new IllegalArgumentException("q must not be null");
michael@0 164 }
michael@0 165 if (g == null) {
michael@0 166 throw new IllegalArgumentException("g must not be null");
michael@0 167 }
michael@0 168 KeySpec keySpec = new DSAPrivateKeySpec(x, p, q, g);
michael@0 169 KeyFactory keyFactory = KeyFactory.getInstance("DSA");
michael@0 170 DSAPrivateKey privateKey = (DSAPrivateKey) keyFactory.generatePrivate(keySpec);
michael@0 171 return new DSASigningPrivateKey(privateKey);
michael@0 172 }
michael@0 173
michael@0 174 public static VerifyingPublicKey createPublicKey(BigInteger y, BigInteger p, BigInteger q, BigInteger g) throws NoSuchAlgorithmException, InvalidKeySpecException {
michael@0 175 if (y == null) {
michael@0 176 throw new IllegalArgumentException("n must not be null");
michael@0 177 }
michael@0 178 if (p == null) {
michael@0 179 throw new IllegalArgumentException("p must not be null");
michael@0 180 }
michael@0 181 if (q == null) {
michael@0 182 throw new IllegalArgumentException("q must not be null");
michael@0 183 }
michael@0 184 if (g == null) {
michael@0 185 throw new IllegalArgumentException("g must not be null");
michael@0 186 }
michael@0 187 KeySpec keySpec = new DSAPublicKeySpec(y, p, q, g);
michael@0 188 KeyFactory keyFactory = KeyFactory.getInstance("DSA");
michael@0 189 DSAPublicKey publicKey = (DSAPublicKey) keyFactory.generatePublic(keySpec);
michael@0 190 return new DSAVerifyingPublicKey(publicKey);
michael@0 191 }
michael@0 192
michael@0 193 public static SigningPrivateKey createPrivateKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
michael@0 194 String algorithm = o.getString("algorithm");
michael@0 195 if (!"DS".equals(algorithm)) {
michael@0 196 throw new InvalidKeySpecException("algorithm must equal DS, was " + algorithm);
michael@0 197 }
michael@0 198 try {
michael@0 199 BigInteger x = new BigInteger(o.getString("x"), SERIALIZATION_BASE);
michael@0 200 BigInteger p = new BigInteger(o.getString("p"), SERIALIZATION_BASE);
michael@0 201 BigInteger q = new BigInteger(o.getString("q"), SERIALIZATION_BASE);
michael@0 202 BigInteger g = new BigInteger(o.getString("g"), SERIALIZATION_BASE);
michael@0 203 return createPrivateKey(x, p, q, g);
michael@0 204 } catch (NullPointerException e) {
michael@0 205 throw new InvalidKeySpecException("x, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE);
michael@0 206 } catch (NumberFormatException e) {
michael@0 207 throw new InvalidKeySpecException("x, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE);
michael@0 208 }
michael@0 209 }
michael@0 210
michael@0 211 public static VerifyingPublicKey createPublicKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
michael@0 212 String algorithm = o.getString("algorithm");
michael@0 213 if (!"DS".equals(algorithm)) {
michael@0 214 throw new InvalidKeySpecException("algorithm must equal DS, was " + algorithm);
michael@0 215 }
michael@0 216 try {
michael@0 217 BigInteger y = new BigInteger(o.getString("y"), SERIALIZATION_BASE);
michael@0 218 BigInteger p = new BigInteger(o.getString("p"), SERIALIZATION_BASE);
michael@0 219 BigInteger q = new BigInteger(o.getString("q"), SERIALIZATION_BASE);
michael@0 220 BigInteger g = new BigInteger(o.getString("g"), SERIALIZATION_BASE);
michael@0 221 return createPublicKey(y, p, q, g);
michael@0 222 } catch (NullPointerException e) {
michael@0 223 throw new InvalidKeySpecException("y, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE);
michael@0 224 } catch (NumberFormatException e) {
michael@0 225 throw new InvalidKeySpecException("y, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE);
michael@0 226 }
michael@0 227 }
michael@0 228
michael@0 229 public static BrowserIDKeyPair fromJSONObject(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
michael@0 230 try {
michael@0 231 ExtendedJSONObject privateKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PRIVATEKEY);
michael@0 232 ExtendedJSONObject publicKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PUBLICKEY);
michael@0 233 if (privateKey == null) {
michael@0 234 throw new InvalidKeySpecException("privateKey must not be null");
michael@0 235 }
michael@0 236 if (publicKey == null) {
michael@0 237 throw new InvalidKeySpecException("publicKey must not be null");
michael@0 238 }
michael@0 239 return new BrowserIDKeyPair(createPrivateKey(privateKey), createPublicKey(publicKey));
michael@0 240 } catch (NonObjectJSONException e) {
michael@0 241 throw new InvalidKeySpecException("privateKey and publicKey must be JSON objects");
michael@0 242 }
michael@0 243 }
michael@0 244 }

mercurial