/*
* 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.usergrid.security.crypto;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.apache.usergrid.persistence.CredentialsInfo;
import org.apache.usergrid.security.crypto.command.EncryptionCommand;
import static org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString;
/** @author tnine */
@Service("encryptionService")
public class EncryptionServiceImpl implements EncryptionService {
private String defaultCommandName = EncryptionCommand.BCRYPT;
private static final Charset UTF8 = Charset.forName( "UTF-8" );
private Map<String, EncryptionCommand> commands;
private List<EncryptionCommand> inputCommands;
private EncryptionCommand defaultCommand;
/*
* (non-Javadoc)
*
* @see
* org.apache.usergrid.security.crypto.EncryptionService#encryptSecret(java.lang.
* String, org.apache.usergrid.persistence.CredentialsInfo, java.util.UUID,
* java.util.UUID)
*/
@Override
public boolean verify( String inputSecret, CredentialsInfo creds, UUID userId, UUID applicationId ) {
String[] storedCommands;
// We have the new format of crypto chain. read them and apply them
if ( creds.getCryptoChain() != null && creds.getCryptoChain().length > 0 ) {
storedCommands = creds.getCryptoChain();
}
// no chain was set, fall back to try to use the hashType and our default
// (legacy support)
else if ( creds.getHashType() != null ) {
storedCommands = new String[] { creds.getHashType(), creds.getCipher() };
}
// use the default cipher on the creds
else {
storedCommands = new String[] { creds.getCipher() };
}
byte[] encrypted = inputSecret.getBytes( UTF8 );
// run the bytes through each command sequentially to generate our
// acceptable hashcode
for ( String commandName : storedCommands ) {
EncryptionCommand command = commands.get( commandName );
// verify we have a command to load
if ( command == null ) {
throw new IllegalArgumentException( String.format(
"No command implementation for name %s exists, yet it is persisted on a user's credentials info. This means their credentials either need to be removed, or this command needs to be supported",
commandName ) );
}
encrypted = command.auth( encrypted, creds, userId, applicationId );
}
return encode( encrypted ).equals( creds.getSecret() );
}
@Autowired
public void setCommands( List<EncryptionCommand> inputCommands ) {
this.inputCommands = inputCommands;
}
@PostConstruct
public void init() {
if ( inputCommands == null || inputCommands.size() == 0 ) {
throw new IllegalArgumentException(
String.format( "You must provide %s implementations for this service to function properly",
EncryptionCommand.class ) );
}
commands = new HashMap<>();
/**
* Create the map by name so we can reference them later.
*/
for ( EncryptionCommand command : inputCommands ) {
String name = command.getName();
Assert.notNull( name, "Encryption command name cannot be null" );
EncryptionCommand existing = commands.get( name );
if ( existing != null ) {
throw new IllegalArgumentException( String.format(
"Both class %s and %s implement command '%s'. This is a wiring bug, "
+ "and not allowed. Each instance must define it's own type",
command.getClass().getName(), existing.getClass().getName(), name ) );
}
commands.put( name, command );
}
defaultCommand = commands.get( defaultCommandName );
Assert.notNull( defaultCommand, "Encryption command for type " + defaultCommandName + " must be present" );
}
/** @param defaultCommandName the defaultCommandName to set */
public void setDefaultCommandName( String defaultCommandName ) {
this.defaultCommandName = defaultCommandName;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.usergrid.security.crypto.EncryptionService#plainTextCredentials(java
* .lang.String, org.apache.usergrid.persistence.entities.User, java.util.UUID)
*/
@Override
public CredentialsInfo plainTextCredentials( String secret, UUID userId, UUID applicationId ) {
CredentialsInfo credentials = new CredentialsInfo();
credentials.setRecoverable( true );
credentials.setSecret( secret );
credentials.setCryptoChain( new String[] { EncryptionCommand.PLAINTEXT } );
return credentials;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.usergrid.security.crypto.EncryptionService#defaultEncryptedCredentials
* (java.lang.String, org.apache.usergrid.persistence.entities.User, java.util.UUID)
*/
@Override
public CredentialsInfo defaultEncryptedCredentials( String input, UUID userId, UUID applicationId ) {
CredentialsInfo credentials = new CredentialsInfo();
credentials.setRecoverable( false );
credentials.setEncrypted( true );
credentials.setCryptoChain( new String[] { defaultCommand.getName() } );
credentials.setSecret(
encode( defaultCommand.hash( input.getBytes( UTF8 ), credentials, userId, applicationId ) ) );
return credentials;
}
/* (non-Javadoc)
* @see org.apache.usergrid.security.crypto.EncryptionService#getCommand(java.lang.String)
*/
@Override
public EncryptionCommand getCommand( String name ) {
return commands.get( name );
}
/* (non-Javadoc)
* @see org.apache.usergrid.security.crypto.EncryptionService#getDefaultCommand()
*/
@Override
public EncryptionCommand getDefaultCommand() {
return defaultCommand;
}
protected String encode( byte[] bytes ) {
return encodeBase64URLSafeString( bytes );
}
}