mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 956816 - Implement (de)serializing Android BrowserIDKeyPair instances. r=rnewman
This commit is contained in:
parent
39b8f0d06d
commit
f6fa542334
@ -4,7 +4,12 @@
|
||||
|
||||
package org.mozilla.gecko.browserid;
|
||||
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
|
||||
public class BrowserIDKeyPair {
|
||||
public static final String JSON_KEY_PRIVATEKEY = "privateKey";
|
||||
public static final String JSON_KEY_PUBLICKEY = "publicKey";
|
||||
|
||||
protected final SigningPrivateKey privateKey;
|
||||
protected final VerifyingPublicKey publicKey;
|
||||
|
||||
@ -20,4 +25,11 @@ public class BrowserIDKeyPair {
|
||||
public VerifyingPublicKey getPublic() {
|
||||
return this.publicKey;
|
||||
}
|
||||
|
||||
public ExtendedJSONObject toJSONObject() {
|
||||
ExtendedJSONObject o = new ExtendedJSONObject();
|
||||
o.put(JSON_KEY_PRIVATEKEY, privateKey.toJSONObject());
|
||||
o.put(JSON_KEY_PUBLICKEY, publicKey.toJSONObject());
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,21 @@ import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.NonObjectJSONException;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
|
||||
public class DSACryptoImplementation {
|
||||
public static final String SIGNATURE_ALGORITHM = "SHA1withDSA";
|
||||
public static final int SIGNATURE_LENGTH_BYTES = 40; // DSA signatures are always 40 bytes long.
|
||||
|
||||
/**
|
||||
* Parameters are serialized as hex strings. Hex-versus-decimal was
|
||||
* reverse-engineered from what the Persona public verifier accepted. We
|
||||
* expect to follow the JOSE/JWT spec as it solidifies, and that will probably
|
||||
* mean unifying this base.
|
||||
*/
|
||||
protected static final int SERIALIZATION_BASE = 16;
|
||||
|
||||
protected static class DSAVerifyingPublicKey implements VerifyingPublicKey {
|
||||
protected final DSAPublicKey publicKey;
|
||||
|
||||
@ -33,16 +42,22 @@ public class DSACryptoImplementation {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize to a JSON object.
|
||||
* <p>
|
||||
* Parameters are serialized as hex strings. Hex-versus-decimal was
|
||||
* reverse-engineered from what the Persona public verifier accepted.
|
||||
*/
|
||||
@Override
|
||||
public String serialize() {
|
||||
public ExtendedJSONObject toJSONObject() {
|
||||
DSAParams params = publicKey.getParams();
|
||||
ExtendedJSONObject o = new ExtendedJSONObject();
|
||||
o.put("algorithm", "DS");
|
||||
o.put("y", publicKey.getY().toString(16));
|
||||
o.put("g", params.getG().toString(16));
|
||||
o.put("p", params.getP().toString(16));
|
||||
o.put("q", params.getQ().toString(16));
|
||||
return o.toJSONString();
|
||||
o.put("y", publicKey.getY().toString(SERIALIZATION_BASE));
|
||||
o.put("g", params.getG().toString(SERIALIZATION_BASE));
|
||||
o.put("p", params.getP().toString(SERIALIZATION_BASE));
|
||||
o.put("q", params.getQ().toString(SERIALIZATION_BASE));
|
||||
return o;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -87,16 +102,22 @@ public class DSACryptoImplementation {
|
||||
return "DS" + (privateKey.getParams().getP().bitLength() + 7)/8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize to a JSON object.
|
||||
* <p>
|
||||
* Parameters are serialized as decimal strings. Hex-versus-decimal was
|
||||
* reverse-engineered from what the Persona public verifier accepted.
|
||||
*/
|
||||
@Override
|
||||
public String serialize() {
|
||||
public ExtendedJSONObject toJSONObject() {
|
||||
DSAParams params = privateKey.getParams();
|
||||
ExtendedJSONObject o = new ExtendedJSONObject();
|
||||
o.put("algorithm", "DS");
|
||||
o.put("x", privateKey.getX().toString(16));
|
||||
o.put("g", params.getG().toString(16));
|
||||
o.put("p", params.getP().toString(16));
|
||||
o.put("q", params.getQ().toString(16));
|
||||
return o.toJSONString();
|
||||
o.put("x", privateKey.getX().toString(SERIALIZATION_BASE));
|
||||
o.put("g", params.getG().toString(SERIALIZATION_BASE));
|
||||
o.put("p", params.getP().toString(SERIALIZATION_BASE));
|
||||
o.put("q", params.getQ().toString(SERIALIZATION_BASE));
|
||||
return o;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -121,7 +142,7 @@ public class DSACryptoImplementation {
|
||||
}
|
||||
}
|
||||
|
||||
public static BrowserIDKeyPair generateKeypair(int keysize)
|
||||
public static BrowserIDKeyPair generateKeyPair(int keysize)
|
||||
throws NoSuchAlgorithmException {
|
||||
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
|
||||
keyPairGenerator.initialize(keysize);
|
||||
@ -168,4 +189,56 @@ public class DSACryptoImplementation {
|
||||
DSAPublicKey publicKey = (DSAPublicKey) keyFactory.generatePublic(keySpec);
|
||||
return new DSAVerifyingPublicKey(publicKey);
|
||||
}
|
||||
|
||||
public static SigningPrivateKey createPrivateKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
|
||||
String algorithm = o.getString("algorithm");
|
||||
if (!"DS".equals(algorithm)) {
|
||||
throw new InvalidKeySpecException("algorithm must equal DS, was " + algorithm);
|
||||
}
|
||||
try {
|
||||
BigInteger x = new BigInteger(o.getString("x"), SERIALIZATION_BASE);
|
||||
BigInteger p = new BigInteger(o.getString("p"), SERIALIZATION_BASE);
|
||||
BigInteger q = new BigInteger(o.getString("q"), SERIALIZATION_BASE);
|
||||
BigInteger g = new BigInteger(o.getString("g"), SERIALIZATION_BASE);
|
||||
return createPrivateKey(x, p, q, g);
|
||||
} catch (NullPointerException e) {
|
||||
throw new InvalidKeySpecException("x, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidKeySpecException("x, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE);
|
||||
}
|
||||
}
|
||||
|
||||
public static VerifyingPublicKey createPublicKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
|
||||
String algorithm = o.getString("algorithm");
|
||||
if (!"DS".equals(algorithm)) {
|
||||
throw new InvalidKeySpecException("algorithm must equal DS, was " + algorithm);
|
||||
}
|
||||
try {
|
||||
BigInteger y = new BigInteger(o.getString("y"), SERIALIZATION_BASE);
|
||||
BigInteger p = new BigInteger(o.getString("p"), SERIALIZATION_BASE);
|
||||
BigInteger q = new BigInteger(o.getString("q"), SERIALIZATION_BASE);
|
||||
BigInteger g = new BigInteger(o.getString("g"), SERIALIZATION_BASE);
|
||||
return createPublicKey(y, p, q, g);
|
||||
} catch (NullPointerException e) {
|
||||
throw new InvalidKeySpecException("y, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidKeySpecException("y, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE);
|
||||
}
|
||||
}
|
||||
|
||||
public static BrowserIDKeyPair fromJSONObject(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
|
||||
try {
|
||||
ExtendedJSONObject privateKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PRIVATEKEY);
|
||||
ExtendedJSONObject publicKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PUBLICKEY);
|
||||
if (privateKey == null) {
|
||||
throw new InvalidKeySpecException("privateKey must not be null");
|
||||
}
|
||||
if (publicKey == null) {
|
||||
throw new InvalidKeySpecException("publicKey must not be null");
|
||||
}
|
||||
return new BrowserIDKeyPair(createPrivateKey(privateKey), createPublicKey(publicKey));
|
||||
} catch (NonObjectJSONException e) {
|
||||
throw new InvalidKeySpecException("privateKey and publicKey must be JSON objects");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ public class JSONWebTokenUtils {
|
||||
ExtendedJSONObject principal = new ExtendedJSONObject();
|
||||
principal.put("email", email);
|
||||
payload.put("principal", principal);
|
||||
payload.put("public-key", new ExtendedJSONObject(publicKeyToSign.serialize()));
|
||||
payload.put("public-key", publicKeyToSign.toJSONObject());
|
||||
return payload.toJSONString();
|
||||
}
|
||||
|
||||
|
@ -19,10 +19,19 @@ import java.security.spec.RSAPrivateKeySpec;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.NonObjectJSONException;
|
||||
|
||||
public class RSACryptoImplementation {
|
||||
public static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
|
||||
|
||||
/**
|
||||
* Parameters are serialized as decimal strings. Hex-versus-decimal was
|
||||
* reverse-engineered from what the Persona public verifier accepted. We
|
||||
* expect to follow the JOSE/JWT spec as it solidifies, and that will probably
|
||||
* mean unifying this base.
|
||||
*/
|
||||
protected static final int SERIALIZATION_BASE = 10;
|
||||
|
||||
protected static class RSAVerifyingPublicKey implements VerifyingPublicKey {
|
||||
protected final RSAPublicKey publicKey;
|
||||
|
||||
@ -30,13 +39,19 @@ public class RSACryptoImplementation {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize to a JSON object.
|
||||
* <p>
|
||||
* Parameters are serialized as decimal strings. Hex-versus-decimal was
|
||||
* reverse-engineered from what the Persona public verifier accepted.
|
||||
*/
|
||||
@Override
|
||||
public String serialize() {
|
||||
public ExtendedJSONObject toJSONObject() {
|
||||
ExtendedJSONObject o = new ExtendedJSONObject();
|
||||
o.put("algorithm", "RS");
|
||||
o.put("n", publicKey.getModulus().toString(10));
|
||||
o.put("e", publicKey.getPublicExponent().toString(10));
|
||||
return o.toJSONString();
|
||||
o.put("n", publicKey.getModulus().toString(SERIALIZATION_BASE));
|
||||
o.put("e", publicKey.getPublicExponent().toString(SERIALIZATION_BASE));
|
||||
return o;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -61,13 +76,19 @@ public class RSACryptoImplementation {
|
||||
return "RS" + (privateKey.getModulus().bitLength() + 7)/8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize to a JSON object.
|
||||
* <p>
|
||||
* Parameters are serialized as decimal strings. Hex-versus-decimal was
|
||||
* reverse-engineered from what the Persona public verifier accepted.
|
||||
*/
|
||||
@Override
|
||||
public String serialize() {
|
||||
public ExtendedJSONObject toJSONObject() {
|
||||
ExtendedJSONObject o = new ExtendedJSONObject();
|
||||
o.put("algorithm", "RS");
|
||||
o.put("n", privateKey.getModulus().toString(10));
|
||||
o.put("e", privateKey.getPrivateExponent().toString(10));
|
||||
return o.toJSONString();
|
||||
o.put("n", privateKey.getModulus().toString(SERIALIZATION_BASE));
|
||||
o.put("d", privateKey.getPrivateExponent().toString(SERIALIZATION_BASE));
|
||||
return o;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -80,7 +101,7 @@ public class RSACryptoImplementation {
|
||||
}
|
||||
}
|
||||
|
||||
public static BrowserIDKeyPair generateKeypair(final int keysize) throws NoSuchAlgorithmException {
|
||||
public static BrowserIDKeyPair generateKeyPair(final int keysize) throws NoSuchAlgorithmException {
|
||||
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(keysize);
|
||||
final KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||
@ -114,4 +135,52 @@ public class RSACryptoImplementation {
|
||||
RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
|
||||
return new RSAVerifyingPublicKey(publicKey);
|
||||
}
|
||||
|
||||
public static SigningPrivateKey createPrivateKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
|
||||
String algorithm = o.getString("algorithm");
|
||||
if (!"RS".equals(algorithm)) {
|
||||
throw new InvalidKeySpecException("algorithm must equal RS, was " + algorithm);
|
||||
}
|
||||
try {
|
||||
BigInteger n = new BigInteger(o.getString("n"), SERIALIZATION_BASE);
|
||||
BigInteger d = new BigInteger(o.getString("d"), SERIALIZATION_BASE);
|
||||
return createPrivateKey(n, d);
|
||||
} catch (NullPointerException e) {
|
||||
throw new InvalidKeySpecException("n and d must be integers encoded as strings, base " + SERIALIZATION_BASE);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidKeySpecException("n and d must be integers encoded as strings, base " + SERIALIZATION_BASE);
|
||||
}
|
||||
}
|
||||
|
||||
public static VerifyingPublicKey createPublicKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
|
||||
String algorithm = o.getString("algorithm");
|
||||
if (!"RS".equals(algorithm)) {
|
||||
throw new InvalidKeySpecException("algorithm must equal RS, was " + algorithm);
|
||||
}
|
||||
try {
|
||||
BigInteger n = new BigInteger(o.getString("n"), SERIALIZATION_BASE);
|
||||
BigInteger e = new BigInteger(o.getString("e"), SERIALIZATION_BASE);
|
||||
return createPublicKey(n, e);
|
||||
} catch (NullPointerException e) {
|
||||
throw new InvalidKeySpecException("n and e must be integers encoded as strings, base " + SERIALIZATION_BASE);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidKeySpecException("n and e must be integers encoded as strings, base " + SERIALIZATION_BASE);
|
||||
}
|
||||
}
|
||||
|
||||
public static BrowserIDKeyPair fromJSONObject(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
|
||||
try {
|
||||
ExtendedJSONObject privateKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PRIVATEKEY);
|
||||
ExtendedJSONObject publicKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PUBLICKEY);
|
||||
if (privateKey == null) {
|
||||
throw new InvalidKeySpecException("privateKey must not be null");
|
||||
}
|
||||
if (publicKey == null) {
|
||||
throw new InvalidKeySpecException("publicKey must not be null");
|
||||
}
|
||||
return new BrowserIDKeyPair(createPrivateKey(privateKey), createPublicKey(publicKey));
|
||||
} catch (NonObjectJSONException e) {
|
||||
throw new InvalidKeySpecException("privateKey and publicKey must be JSON objects");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ package org.mozilla.gecko.browserid;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
|
||||
public interface SigningPrivateKey {
|
||||
/**
|
||||
* Return the JSON Web Token "alg" header corresponding to this private key.
|
||||
@ -18,16 +20,16 @@ public interface SigningPrivateKey {
|
||||
public String getAlgorithm();
|
||||
|
||||
/**
|
||||
* Generate a printable representation of a private key.
|
||||
* Generate a JSON representation of a private key.
|
||||
* <p>
|
||||
* <b>This should only be used for debugging. No private keys should go over
|
||||
* the wire at any time.</b>
|
||||
*
|
||||
* @param privateKey
|
||||
* to represent.
|
||||
* @return printable representation.
|
||||
* @return JSON representation.
|
||||
*/
|
||||
public String serialize();
|
||||
public ExtendedJSONObject toJSONObject();
|
||||
|
||||
/**
|
||||
* Sign a message.
|
||||
|
@ -6,16 +6,18 @@ package org.mozilla.gecko.browserid;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
|
||||
|
||||
public interface VerifyingPublicKey {
|
||||
/**
|
||||
* Generate a printable representation of a public key.
|
||||
* Generate a JSON representation of a public key.
|
||||
*
|
||||
* @param publicKey
|
||||
* to represent.
|
||||
* @return printable representation.
|
||||
* @return JSON representation.
|
||||
*/
|
||||
public String serialize();
|
||||
public ExtendedJSONObject toJSONObject();
|
||||
|
||||
/**
|
||||
* Verify a signature.
|
||||
|
@ -155,9 +155,9 @@ public class FxAccount {
|
||||
*/
|
||||
public void login(final Context context, final String tokenServerEndpoint,
|
||||
final BrowserIDKeyPair keyPair, final Delegate delegate) {
|
||||
ExtendedJSONObject keyPairObject;
|
||||
ExtendedJSONObject publicKeyObject;
|
||||
try {
|
||||
keyPairObject = new ExtendedJSONObject(keyPair.getPublic().serialize());
|
||||
publicKeyObject = keyPair.getPublic().toJSONObject();
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
return;
|
||||
@ -168,7 +168,7 @@ public class FxAccount {
|
||||
// inner FxAccountClient delegate, the outer TokenServerClient delegate, and
|
||||
// the user supplied delegate.
|
||||
FxAccountClient fxAccountClient = new FxAccountClient(idpEndpoint, executor);
|
||||
fxAccountClient.sign(sessionTokenBytes, keyPairObject,
|
||||
fxAccountClient.sign(sessionTokenBytes, publicKeyObject,
|
||||
JSONWebTokenUtils.DEFAULT_CERTIFICATE_DURATION_IN_MILLISECONDS,
|
||||
new InnerFxAccountClientRequestDelegate(executor, authEndpoint, tokenServerEndpoint, keyPair, delegate));
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
try {
|
||||
final BrowserIDKeyPair keyPair = RSACryptoImplementation.generateKeypair(1024);
|
||||
final BrowserIDKeyPair keyPair = RSACryptoImplementation.generateKeyPair(1024);
|
||||
Logger.info(LOG_TAG, "Generated keypair. ");
|
||||
|
||||
final FxAccount fxAccount = FxAccountAuthenticator.fromAndroidAccount(getContext(), account);
|
||||
|
@ -38,12 +38,12 @@ public class TestBrowserIDKeyPairGeneration extends AndroidSyncTestCase {
|
||||
}
|
||||
|
||||
public void testEncodeDecodeSuccessRSA() throws Exception {
|
||||
doTestEncodeDecode(RSACryptoImplementation.generateKeypair(1024));
|
||||
doTestEncodeDecode(RSACryptoImplementation.generateKeypair(2048));
|
||||
doTestEncodeDecode(RSACryptoImplementation.generateKeyPair(1024));
|
||||
doTestEncodeDecode(RSACryptoImplementation.generateKeyPair(2048));
|
||||
}
|
||||
|
||||
public void testEncodeDecodeSuccessDSA() throws Exception {
|
||||
doTestEncodeDecode(DSACryptoImplementation.generateKeypair(512));
|
||||
doTestEncodeDecode(DSACryptoImplementation.generateKeypair(1024));
|
||||
doTestEncodeDecode(DSACryptoImplementation.generateKeyPair(512));
|
||||
doTestEncodeDecode(DSACryptoImplementation.generateKeyPair(1024));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user