001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2018, Connect2id Ltd.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose.crypto;
019
020
021import com.google.crypto.tink.subtle.Ed25519Sign;
022import com.nimbusds.jose.JOSEException;
023import com.nimbusds.jose.JWSAlgorithm;
024import com.nimbusds.jose.JWSHeader;
025import com.nimbusds.jose.JWSSigner;
026import com.nimbusds.jose.crypto.impl.EdDSAProvider;
027import com.nimbusds.jose.jwk.Curve;
028import com.nimbusds.jose.jwk.OctetKeyPair;
029import com.nimbusds.jose.util.Base64URL;
030import net.jcip.annotations.ThreadSafe;
031
032import java.security.GeneralSecurityException;
033
034
035/**
036 * Ed25519 signer of {@link com.nimbusds.jose.JWSObject JWS objects}.
037 * Expects an {@link OctetKeyPair} with {@code "crv"} Ed25519.
038 * Uses the Edwards-curve Digital Signature Algorithm (EdDSA).
039 *
040 * <p>See <a href="https://tools.ietf.org/html/rfc8037">RFC 8037</a>
041 * for more information.
042 *
043 * <p>This class is thread-safe.
044 *
045 * <p>Supports the following algorithm:
046 *
047 * <ul>
048 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#Ed25519}
049 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#EdDSA} with
050 *         {@link com.nimbusds.jose.jwk.Curve#Ed25519}
051 * </ul>
052 *
053 * @author Tim McLean
054 * @version 2024-05-07
055 */
056@ThreadSafe
057public class Ed25519Signer extends EdDSAProvider implements JWSSigner {
058        
059        
060        private final OctetKeyPair privateKey;
061
062
063        private final Ed25519Sign tinkSigner;
064
065
066        /**
067         * Creates a new Ed25519 signer.
068         *
069         * @param privateKey The private key. Must be of type Ed25519
070         *                   ({@code "crv": "Ed25519"}) and not {@code null}.
071         *
072         * @throws JOSEException If the key subtype is not supported or if the
073         *                       key is not a private key.
074         */
075        public Ed25519Signer(final OctetKeyPair privateKey)
076                throws JOSEException {
077
078                super();
079
080                if (! Curve.Ed25519.equals(privateKey.getCurve())) {
081                        throw new JOSEException("Ed25519Signer only supports OctetKeyPairs with crv=Ed25519");
082                }
083
084                if (! privateKey.isPrivate()) {
085                        throw new JOSEException("The OctetKeyPair doesn't contain a private part");
086                }
087
088                this.privateKey = privateKey;
089
090                try {
091                        tinkSigner = new Ed25519Sign(privateKey.getDecodedD());
092
093                } catch (GeneralSecurityException e) {
094                        // If Tink failed to initialize; generally should not happen
095                        throw new JOSEException(e.getMessage(), e);
096                }
097        }
098
099
100        /**
101         * Gets the Ed25519 private key as an {@code OctetKeyPair}.
102         *
103         * @return The private key.
104         */
105        public OctetKeyPair getPrivateKey() {
106                
107                return privateKey;
108        }
109
110
111        @Override
112        public Base64URL sign(final JWSHeader header, final byte[] signingInput)
113                throws JOSEException {
114
115                // Check alg field in header
116                final JWSAlgorithm alg = header.getAlgorithm();
117                if (! JWSAlgorithm.Ed25519.equals(alg) && ! JWSAlgorithm.EdDSA.equals(alg)) {
118                        throw new JOSEException("Ed25519Verifier requires alg=Ed25519 or alg=EdDSA in JWSHeader");
119                }
120
121                final byte[] jwsSignature;
122
123                try {
124                        jwsSignature = tinkSigner.sign(signingInput);
125
126                } catch (GeneralSecurityException e) {
127
128                        throw new JOSEException(e.getMessage(), e);
129                }
130
131                return Base64URL.encode(jwsSignature);
132        }
133}