All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.jsonwebtoken.impl.security.EcPrivateJwkFactory Maven / Gradle / Ivy

/*
 * Copyright (C) 2021 jsonwebtoken.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.jsonwebtoken.impl.security;

import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.impl.lang.ParameterReadable;
import io.jsonwebtoken.impl.lang.RequiredParameterReader;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.EcPrivateJwk;
import io.jsonwebtoken.security.EcPublicJwk;
import io.jsonwebtoken.security.InvalidKeyException;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;

class EcPrivateJwkFactory extends AbstractEcJwkFactory {

    private static final String ECPUBKEY_ERR_MSG = "JwkContext publicKey must be an " + ECPublicKey.class.getName() +
            " instance.";

    private static final EcPublicJwkFactory PUB_FACTORY = EcPublicJwkFactory.INSTANCE;

    EcPrivateJwkFactory() {
        super(ECPrivateKey.class, DefaultEcPrivateJwk.PARAMS);
    }

    @Override
    protected boolean supportsKeyValues(JwkContext ctx) {
        return super.supportsKeyValues(ctx) && ctx.containsKey(DefaultEcPrivateJwk.D.getId());
    }

    // visible for testing
    protected ECPublicKey derivePublic(KeyFactory keyFactory, ECPublicKeySpec spec) throws InvalidKeySpecException {
        return (ECPublicKey) keyFactory.generatePublic(spec);
    }

    protected ECPublicKey derivePublic(final JwkContext ctx) {
        final ECPrivateKey key = ctx.getKey();
        return generateKey(ctx, ECPublicKey.class, new CheckedFunction() {
            @Override
            public ECPublicKey apply(KeyFactory kf) {
                try {
                    ECPublicKeySpec spec = ECCurve.publicKeySpec(key);
                    return derivePublic(kf, spec);
                } catch (Exception e) {
                    String msg = "Unable to derive ECPublicKey from ECPrivateKey: " + e.getMessage();
                    throw new InvalidKeyException(msg, e);
                }
            }
        });
    }

    @Override
    protected EcPrivateJwk createJwkFromKey(JwkContext ctx) {

        ECPrivateKey key = ctx.getKey();
        ECPublicKey ecPublicKey;

        PublicKey publicKey = ctx.getPublicKey();
        if (publicKey != null) {
            ecPublicKey = Assert.isInstanceOf(ECPublicKey.class, publicKey, ECPUBKEY_ERR_MSG);
        } else {
            ecPublicKey = derivePublic(ctx);
        }

        // [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2)
        // requires public values to be present in private JWKs, so add them:

        // If a JWK fingerprint has been requested to be the JWK id, ensure we copy over the one computed for the
        // public key per https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1
        boolean copyId = !Strings.hasText(ctx.getId()) && ctx.getIdThumbprintAlgorithm() != null;

        JwkContext pubCtx = PUB_FACTORY.newContext(ctx, ecPublicKey);
        EcPublicJwk pubJwk = PUB_FACTORY.createJwk(pubCtx);
        ctx.putAll(pubJwk); // add public values to private key context
        if (copyId) {
            ctx.setId(pubJwk.getId());
        }

        String d = toOctetString(key.getParams().getCurve(), key.getS());
        ctx.put(DefaultEcPrivateJwk.D.getId(), d);

        return new DefaultEcPrivateJwk(ctx, pubJwk);
    }

    @Override
    protected EcPrivateJwk createJwkFromValues(final JwkContext ctx) {

        ParameterReadable reader = new RequiredParameterReader(ctx);
        String curveId = reader.get(DefaultEcPublicJwk.CRV);
        BigInteger d = reader.get(DefaultEcPrivateJwk.D);

        // We don't actually need the public x,y point coordinates for JVM lookup, but the
        // [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2)
        // requires them to be present and valid for the private key as well, so we assert that here:
        JwkContext pubCtx = new DefaultJwkContext<>(DefaultEcPublicJwk.PARAMS, ctx);
        EcPublicJwk pubJwk = EcPublicJwkFactory.INSTANCE.createJwk(pubCtx);

        ECCurve curve = getCurveByJwaId(curveId);
        final ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(d, curve.toParameterSpec());
        ECPrivateKey key = generateKey(ctx, new CheckedFunction() {
            @Override
            public ECPrivateKey apply(KeyFactory kf) throws Exception {
                return (ECPrivateKey) kf.generatePrivate(privateSpec);
            }
        });

        ctx.setKey(key);

        return new DefaultEcPrivateJwk(ctx, pubJwk);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy