| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.sshd.common.signature; |
| |
| import java.nio.charset.StandardCharsets; |
| import java.security.GeneralSecurityException; |
| import java.security.Key; |
| import java.security.PrivateKey; |
| import java.security.PublicKey; |
| import java.security.SignatureException; |
| import java.util.AbstractMap.SimpleImmutableEntry; |
| import java.util.Collection; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.function.Predicate; |
| |
| import org.apache.sshd.common.session.SessionContext; |
| import org.apache.sshd.common.util.GenericUtils; |
| import org.apache.sshd.common.util.NumberUtils; |
| import org.apache.sshd.common.util.ValidateUtils; |
| import org.apache.sshd.common.util.buffer.BufferUtils; |
| import org.apache.sshd.common.util.security.SecurityUtils; |
| |
| /** |
| * Useful base class for {@link Signature} implementation |
| * |
| * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> |
| */ |
| public abstract class AbstractSignature implements Signature { |
| private java.security.Signature signatureInstance; |
| private final String algorithm; |
| |
| protected AbstractSignature(String algorithm) { |
| this.algorithm = ValidateUtils.checkNotNullAndNotEmpty(algorithm, "No signature algorithm specified"); |
| } |
| |
| @Override |
| public final String getAlgorithm() { |
| return algorithm; |
| } |
| |
| /** |
| * Initializes the internal signature instance |
| * |
| * @param session The {@link SessionContext} for calling this method - may be {@code null} if not |
| * called within a session context |
| * @param algo The signature's algorithm name |
| * @param key the {@link Key} that is provided for initialization - a {@link PrivateKey} for |
| * signing and a {@link PublicKey} for verification |
| * @param forSigning If {@code true} then it is being initialized for signing, otherwise for |
| * verifying a signature |
| * @return The {@link java.security.Signature} instance |
| * @throws GeneralSecurityException if failed to initialize |
| */ |
| protected java.security.Signature doInitSignature( |
| SessionContext session, String algo, Key key, boolean forSigning) |
| throws GeneralSecurityException { |
| return SecurityUtils.getSignature(algo); |
| } |
| |
| /** |
| * @return The current {@link java.security.Signature} instance - {@code null} if not initialized |
| * @see #doInitSignature(SessionContext, String, Key, boolean) |
| */ |
| protected java.security.Signature getSignature() { |
| return signatureInstance; |
| } |
| |
| @Override |
| public byte[] sign(SessionContext session) throws Exception { |
| java.security.Signature signature = Objects.requireNonNull(getSignature(), "Signature not initialized"); |
| return signature.sign(); |
| } |
| |
| @Override |
| public void initVerifier(SessionContext session, PublicKey key) throws Exception { |
| String algo = getAlgorithm(); |
| signatureInstance = Objects.requireNonNull( |
| doInitSignature(session, algo, key, false), "No signature instance create"); |
| signatureInstance.initVerify(Objects.requireNonNull(key, "No public key provided")); |
| } |
| |
| @Override |
| public void initSigner(SessionContext session, PrivateKey key) throws Exception { |
| String algo = getAlgorithm(); |
| signatureInstance = Objects.requireNonNull( |
| doInitSignature(session, algo, key, true), "No signature instance create"); |
| signatureInstance.initSign(Objects.requireNonNull(key, "No private key provided")); |
| } |
| |
| @Override |
| public void update(SessionContext session, byte[] hash, int off, int len) throws Exception { |
| java.security.Signature signature = Objects.requireNonNull(getSignature(), "Signature not initialized"); |
| signature.update(hash, off, len); |
| } |
| |
| /** |
| * Makes an attempt to detect if the signature is encoded or pure data |
| * |
| * @param sig The original signature |
| * @param expectedTypes The expected encoded key types |
| * @return A {@link SimpleImmutableEntry} where first value is the key type and second value is the |
| * data - {@code null} if not encoded |
| */ |
| protected Map.Entry<String, byte[]> extractEncodedSignature(byte[] sig, Collection<String> expectedTypes) { |
| return GenericUtils.isEmpty(expectedTypes) ? null : extractEncodedSignature(sig, expectedTypes::contains); |
| } |
| |
| protected Map.Entry<String, byte[]> extractEncodedSignature(byte[] sig, Predicate<? super String> typeSelector) { |
| int dataLen = NumberUtils.length(sig); |
| // if it is encoded then we must have at least 2 UINT32 values |
| if (dataLen < (2 * Integer.BYTES)) { |
| return null; |
| } |
| |
| long keyTypeLen = BufferUtils.getUInt(sig, 0, dataLen); |
| // after the key type we MUST have data bytes |
| if (keyTypeLen >= (dataLen - Integer.BYTES)) { |
| return null; |
| } |
| |
| int keyTypeStartPos = Integer.BYTES; |
| int keyTypeEndPos = keyTypeStartPos + (int) keyTypeLen; |
| int remainLen = dataLen - keyTypeEndPos; |
| // must have UINT32 with the data bytes length |
| if (remainLen < Integer.BYTES) { |
| return null; |
| } |
| |
| long dataBytesLen = BufferUtils.getUInt(sig, keyTypeEndPos, remainLen); |
| // make sure reported number of bytes does not exceed available |
| if (dataBytesLen > (remainLen - Integer.BYTES)) { |
| return null; |
| } |
| |
| String keyType = new String(sig, keyTypeStartPos, (int) keyTypeLen, StandardCharsets.UTF_8); |
| if (!typeSelector.test(keyType)) { |
| return null; |
| } |
| |
| byte[] data = new byte[(int) dataBytesLen]; |
| System.arraycopy(sig, keyTypeEndPos + Integer.BYTES, data, 0, (int) dataBytesLen); |
| return new SimpleImmutableEntry<>(keyType, data); |
| } |
| |
| protected boolean doVerify(byte[] data) throws SignatureException { |
| java.security.Signature signature = Objects.requireNonNull(getSignature(), "Signature not initialized"); |
| return signature.verify(data); |
| } |
| |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + "[" + getAlgorithm() + "]"; |
| } |
| } |