
com.aliyun.oss.crypto.CryptoModuleBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aliyun-sdk-oss-shade Show documentation
Show all versions of aliyun-sdk-oss-shade Show documentation
The Aliyun OSS SDK for Java used for accessing Aliyun Object Storage Service, includes all service and dependent JARs.
The newest version!
/*
* 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 com.aliyun.oss.crypto;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.zip.CheckedInputStream;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import com.aliyun.oss.model.*;
import org.apache.http.protocol.HTTP;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSSEncryptionClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.common.utils.IOUtils;
import com.aliyun.oss.internal.Mimetypes;
import com.aliyun.oss.internal.OSSHeaders;
import com.aliyun.oss.internal.OSSUtils;
import static com.aliyun.oss.common.utils.CodingUtils.assertParameterNotNull;
import static com.aliyun.oss.common.utils.IOUtils.safeClose;
import static com.aliyun.oss.common.utils.LogUtils.logException;
import static com.aliyun.oss.internal.OSSUtils.OSS_RESOURCE_MANAGER;
public abstract class CryptoModuleBase implements CryptoModule {
protected static final int DEFAULT_BUFFER_SIZE = 1024 * 2;
protected final EncryptionMaterials encryptionMaterials;
protected final CryptoScheme contentCryptoScheme;
protected final CryptoConfiguration cryptoConfig;
protected final OSSDirect ossDirect;
protected final String encryptionClientUserAgent;
protected CryptoModuleBase(OSSDirect ossDirect,
EncryptionMaterials encryptionMaterials,
CryptoConfiguration cryptoConfig) {
this.encryptionMaterials = encryptionMaterials;
this.ossDirect = ossDirect;
this.cryptoConfig = cryptoConfig;
this.contentCryptoScheme = getCryptoScheme(cryptoConfig.getContentCryptoMode());
encryptionClientUserAgent = this.ossDirect.getInnerClientConfiguration().getUserAgent()
+ OSSEncryptionClient.USER_AGENT_SUFFIX;
}
private final static CryptoScheme getCryptoScheme(ContentCryptoMode contentCryptoMode) {
switch (contentCryptoMode) {
case AES_CTR_MODE:
default:
return CryptoScheme.AES_CTR;
}
}
abstract byte[] generateIV();
abstract CryptoCipher createCryptoCipherFromContentMaterial(ContentCryptoMaterial cekMaterial,
int cipherMode, long[] cryptoRange, long skipBlock);
/**
* Puts the object with data encrypted.
*
* @param req
* The put object request.
* @return the result of the request.
*/
@Override
public PutObjectResult putObjectSecurely(PutObjectRequest req) {
// Update User-Agent.
setUserAgent(req, encryptionClientUserAgent);
// Build content crypto material.
ContentCryptoMaterial cekMaterial = buildContentCryptoMaterials();
// Update the metadata
ObjectMetadata meta = updateMetadataWithContentCryptoMaterial(req.getMetadata(), req.getFile(), cekMaterial);
req.setMetadata(meta);
// Wraps the object data with a cipher input stream
final File fileOrig = req.getFile();
final InputStream isOrig = req.getInputStream();
PutObjectRequest wrappedReq = wrapPutRequestWithCipher(req, cekMaterial);
// Put the encrypted object by the oss client
try {
return ossDirect.putObject(wrappedReq);
} finally {
safeCloseSource(wrappedReq.getInputStream());
req.setFile(fileOrig);
req.setInputStream(isOrig);
}
}
// Returns the given {@link PutObjectRequest} instance but has the content as
// input stream wrapped with a cipher, and configured with some meta data and
// user metadata.
protected final PutObjectRequest wrapPutRequestWithCipher(final PutObjectRequest request,
ContentCryptoMaterial cekMaterial) {
// Create a new metadata object if there is no metadata already.
ObjectMetadata metadata = request.getMetadata();
if (metadata == null) {
metadata = new ObjectMetadata();
}
// update content md5 and length headers.
updateContentMd5(request, metadata);
updateContentLength(request, metadata);
// Create content crypto cipher.
CryptoCipher cryptoCipher = createCryptoCipherFromContentMaterial(cekMaterial, Cipher.ENCRYPT_MODE, null, 0);
// Treat all encryption requests as input stream upload requests.
request.setInputStream(newOSSCryptoCipherInputStream(request, cryptoCipher));
request.setFile(null);
return request;
}
private void updateContentMd5(final PutObjectRequest request, final ObjectMetadata metadata) {
if (metadata.getContentMD5() != null) {
metadata.addUserMetadata(CryptoHeaders.CRYPTO_UNENCRYPTION_CONTENT_MD5, metadata.getContentMD5());
metadata.removeHeader(OSSHeaders.CONTENT_MD5);
}
Map headers = request.getHeaders();
if (headers.containsKey(OSSHeaders.CONTENT_MD5)) {
metadata.addUserMetadata(CryptoHeaders.CRYPTO_UNENCRYPTION_CONTENT_MD5, headers.get(OSSHeaders.CONTENT_MD5));
headers.remove(OSSHeaders.CONTENT_MD5);
}
request.setMetadata(metadata);
}
private void updateContentLength(final PutObjectRequest request, final ObjectMetadata metadata) {
final long plaintextLength = plaintextLength(request, metadata);
if (plaintextLength >= 0) {
metadata.addUserMetadata(CryptoHeaders.CRYPTO_UNENCRYPTION_CONTENT_LENGTH, Long.toString(plaintextLength));
metadata.setContentLength(plaintextLength);
}
Map headers = request.getHeaders();
if (headers.containsKey(OSSHeaders.CONTENT_LENGTH)) {
metadata.addUserMetadata(CryptoHeaders.CRYPTO_UNENCRYPTION_CONTENT_LENGTH, headers.get(OSSHeaders.CONTENT_LENGTH));
}
request.setMetadata(metadata);
}
/**
* Checks there an encryption info in the metadata.
*
* @param metadata
* the object metadata.
*
* @return True if has encryption info; False if not.
*/
public static boolean hasEncryptionInfo(ObjectMetadata metadata) {
Map userMeta = metadata.getUserMetadata();
return userMeta != null && userMeta.containsKey(CryptoHeaders.CRYPTO_KEY)
&& userMeta.containsKey(CryptoHeaders.CRYPTO_IV);
}
/**
* Gets the object in OSS, if it was an encrypted object then decrypt it and
* return the result, otherwise return the object directly.
*
* @param req The {@link GetObjectRequest} instance.
* @return The {@link OSSObject} instance.
*/
@Override
public OSSObject getObjectSecurely(GetObjectRequest req) {
// Update User-Agent.
setUserAgent(req, encryptionClientUserAgent);
// Adjust range-get
long[] desiredRange = req.getRange();
long[] adjustedCryptoRange = getAdjustedCryptoRange(desiredRange);
if (adjustedCryptoRange != null) {
req.setRange(adjustedCryptoRange[0], adjustedCryptoRange[1]);
}
// Get the object from OSS
OSSObject retrieved = ossDirect.getObject(req);
// Recheck range-get
String contentRange = (String) retrieved.getObjectMetadata().getRawMetadata().get("Content-Range");
if (contentRange == null && adjustedCryptoRange != null) {
desiredRange[0] = 0;
desiredRange[1] = retrieved.getObjectMetadata().getContentLength() - 1;
adjustedCryptoRange = desiredRange.clone();
}
// Convert OSSObject content with cipher insteam
try {
if (hasEncryptionInfo(retrieved.getObjectMetadata())) {
return decipherWithMetadata(req, desiredRange, adjustedCryptoRange, retrieved);
}
OSSObject adjustedOSSObject = adjustToDesiredRange(retrieved, desiredRange);
return adjustedOSSObject;
} catch (Exception e) {
safeCloseSource(retrieved);
throw new ClientException(e);
}
}
// Decrypt the encypted object by the metadata achieved.
protected OSSObject decipherWithMetadata(GetObjectRequest req,
long[] desiredRange,
long[] cryptoRange, OSSObject retrieved) {
// Create ContentCryptoMaterial by parse metadata.
ContentCryptoMaterial cekMaterial = createContentMaterialFromMetadata(retrieved.getObjectMetadata());
// Create crypto cipher by contentCryptoMaterial
CryptoCipher cryptoCipher = createCryptoCipherFromContentMaterial(cekMaterial, Cipher.DECRYPT_MODE, cryptoRange,
0);
// Wrap retrieved object with cipherInputStream.
InputStream objectContent = retrieved.getObjectContent();
retrieved.setObjectContent(
new CipherInputStream(objectContent,
cryptoCipher,
DEFAULT_BUFFER_SIZE));
// Adjust the output to the desired range of bytes.
OSSObject adjusted = adjustToDesiredRange(retrieved, desiredRange);
return adjusted;
}
protected void safeCloseSource(Closeable is) {
if (is != null) {
try {
is.close();
} catch (IOException ex) {
}
}
}
// Adjusts the retrieved OSSObject so that the object contents contain only the
// range of bytes desired by the user. Since encrypted contents can only be
// retrieved in CIPHER_BLOCK_SIZE (16 bytes) chunks, the OSSObject potentially
// contains more bytes than desired, so this method adjusts the contents range.
protected final OSSObject adjustToDesiredRange(OSSObject OSSobject, long[] range) {
if (range == null)
return OSSobject;
try {
InputStream objectContent = OSSobject.getObjectContent();
InputStream adjustedRangeContents = new AdjustedRangeInputStream(objectContent, range[0], range[1]);
OSSobject.setObjectContent(adjustedRangeContents);
return OSSobject;
} catch (IOException e) {
throw new ClientException("Error adjusting output to desired byte range: " + e.getMessage());
}
}
private void checkMultipartContext(MultipartUploadCryptoContext context) {
if (context == null) {
throw new IllegalArgumentException("MultipartUploadCryptoContext should not be null.");
}
if (0 != (context.getPartSize() % CryptoScheme.BLOCK_SIZE) || context.getPartSize() <= 0) {
throw new IllegalArgumentException("MultipartUploadCryptoContext part size is not 16 bytes alignment.");
}
}
/**
* Gets the object in OSS and write it in a file, if it was an encrypted object
* then decrypt it, otherwise wirte the object directly.
* @param getObjectRequest The {@link GetObjectRequest} instance.
* @param file The {@link File} instance to write into.
* @return The {@link ObjectMetadata} instance.
*/
@Override
public ObjectMetadata getObjectSecurely(GetObjectRequest getObjectRequest, File file) {
assertParameterNotNull(file, "file");
OSSObject ossObject = getObjectSecurely(getObjectRequest);
OutputStream outputStream = null;
try {
outputStream = new BufferedOutputStream(new FileOutputStream(file));
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
int bytesRead;
while ((bytesRead = IOUtils.readNBytes(ossObject.getObjectContent(), buffer, 0, buffer.length)) > 0) {
outputStream.write(buffer, 0, bytesRead);
}
if (ossDirect.getInnerClientConfiguration().isCrcCheckEnabled() && getObjectRequest.getRange() == null) {
Long clientCRC = null;
InputStream contentInputStream = ossObject.getObjectContent();
if (contentInputStream instanceof CipherInputStream) {
InputStream subStream = ((CipherInputStream) contentInputStream).getDelegateStream();
if (subStream instanceof CheckedInputStream){
clientCRC = ((CheckedInputStream) subStream).getChecksum().getValue();
}
}
else {
clientCRC = IOUtils.getCRCValue(ossObject.getObjectContent());
}
OSSUtils.checkChecksum(clientCRC, ossObject.getServerCRC(), ossObject.getRequestId());
}
return ossObject.getObjectMetadata();
} catch (IOException ex) {
logException("Cannot read object content stream: ", ex);
throw new ClientException(OSS_RESOURCE_MANAGER.getString("CannotReadContentStream"), ex);
} finally {
safeClose(outputStream);
safeClose(ossObject.getObjectContent());
}
}
/**
* Initiates the multipart upload request, and bulid the crypto context.
* @param req
* The {@link InitiateMultipartUploadRequest} instance.
* @param context
* The multi part crypto context contains the content crypto materials and
* upload information, it should created on the outside with part-size and data-size set done, the
* content crypto materials and other upload information will be filled after initiate request done.
* @return The {@link InitiateMultipartUploadResult} instance.
*/
@Override
public InitiateMultipartUploadResult initiateMultipartUploadSecurely(InitiateMultipartUploadRequest req,
MultipartUploadCryptoContext context) {
checkMultipartContext(context);
// Update User-Agent.
setUserAgent(req, encryptionClientUserAgent);
// Get content crypto material.
ContentCryptoMaterial cekMaterial = buildContentCryptoMaterials();
ObjectMetadata metadata = req.getObjectMetadata();
if (metadata == null) {
metadata = new ObjectMetadata();
}
// Store encryption info in metadata
metadata = updateMetadataWithContentCryptoMaterial(metadata, null, cekMaterial);
metadata = updateMetadataWithUploadContext(metadata, context);
req.setObjectMetadata(metadata);
// Fill context
InitiateMultipartUploadResult result = ossDirect.initiateMultipartUpload(req);
context.setUploadId(result.getUploadId());
context.setContentCryptoMaterial(cekMaterial);
return result;
}
/**
* Uploads the part secured.
* @param req
* The {@link UploadPartRequest} instance.
* @param context
* The multi part crypto context contains the content crypto materials and
* upload information, it should created on the outside with part-size and data-size set done, the
* content crypto materials and other upload information will be filled after initiate request done.
* @return The {@link UploadPartResult} instance.
*/
@Override
public UploadPartResult uploadPartSecurely(UploadPartRequest req, MultipartUploadCryptoContext context) {
final UploadPartResult result;
// Check partsize and context
checkMultipartContext(context);
if (!context.getUploadId().equals(req.getUploadId())) {
throw new ClientException("The multipartUploadCryptoContextcontext input upload id is invalid."
+ "context uploadid:" + context.getUploadId() + ",uploadRequest uploadid:" + req.getUploadId());
}
// Update User-Agent.
setUserAgent(req, encryptionClientUserAgent);
// Create CryptoCipher
long offset = context.getPartSize() * (req.getPartNumber() - 1);
long skipBlock = offset / CryptoScheme.BLOCK_SIZE;
CryptoCipher cryptoCipher = createCryptoCipherFromContentMaterial(context.getContentCryptoMaterial(),
Cipher.ENCRYPT_MODE, null, skipBlock);
// Wrap InputStram to CipherInputStream
final InputStream isOrig = req.getInputStream();
CipherInputStream isCurr = null;
try {
isCurr = new RenewableCipherInputStream(isOrig, cryptoCipher, DEFAULT_BUFFER_SIZE);
req.setInputStream(isCurr);
result = ossDirect.uploadPart(req);
} finally {
safeCloseSource(isCurr);
req.setInputStream(isOrig);
}
return result;
}
// Wraps the inputStream with an crypto cipher.
private CipherInputStream newOSSCryptoCipherInputStream(PutObjectRequest req, CryptoCipher cryptoCipher) {
final File fileOrig = req.getFile();
final InputStream isOrig = req.getInputStream();
InputStream isCurr = isOrig;
try {
if (fileOrig != null) {
isCurr = new FileInputStream(fileOrig);
}
return new RenewableCipherInputStream(isCurr, cryptoCipher, DEFAULT_BUFFER_SIZE);
} catch (Exception e) {
safeCloseSource(isCurr);
req.setFile(fileOrig);
req.setInputStream(isOrig);
throw new ClientException("Unable to create cipher input stream." + e.getMessage(), e);
}
}
// Returns the plaintext length from the request and metadata; or -1 if unknown.
protected final long plaintextLength(PutObjectRequest request, ObjectMetadata metadata) {
if (request.getFile() != null) {
return request.getFile().length();
} else if (request.getInputStream() != null && metadata.getRawMetadata().get("Content-Length") != null) {
return metadata.getContentLength();
}
return -1;
}
// Adjustes the range-get start offset to allgn with cipher block.
long[] getAdjustedCryptoRange(long[] range) {
if (range == null) {
return null;
}
if ((range[0] > range[1]) || (range[0] < 0) || (range[1] <= 0)) {
throw new ClientException("Your input get-range is illegal. + range:" + range[0] + "~" + range[1]);
}
long[] adjustedCryptoRange = new long[2];
adjustedCryptoRange[0] = getCipherBlockLowerBound(range[0]);
adjustedCryptoRange[1] = range[1];
return adjustedCryptoRange;
}
private long getCipherBlockLowerBound(long leftmostBytePosition) {
long cipherBlockSize = CryptoScheme.BLOCK_SIZE;
long offset = leftmostBytePosition % cipherBlockSize;
long lowerBound = leftmostBytePosition - offset;
return lowerBound;
}
// Build a new content crypto material read-only.
protected final ContentCryptoMaterial buildContentCryptoMaterials() {
// Generate random CEK IV
byte[] iv = generateIV();
SecretKey cek = generateCEK();
// Build content crypto Materials by encryptionMaterials.
ContentCryptoMaterialRW contentMaterialRW = new ContentCryptoMaterialRW();
contentMaterialRW.setIV(iv);
contentMaterialRW.setCEK(cek);
contentMaterialRW.setContentCryptoAlgorithm(contentCryptoScheme.getContentChiperAlgorithm());
encryptionMaterials.encryptCEK(contentMaterialRW);
return contentMaterialRW;
}
// Add the upload part info into metadata.
protected final ObjectMetadata updateMetadataWithUploadContext(ObjectMetadata metadata,
MultipartUploadCryptoContext context) {
if (metadata == null) {
metadata = new ObjectMetadata();
}
metadata.addUserMetadata(CryptoHeaders.CRYPTO_PART_SIZE, String.valueOf(context.getPartSize()));
if (context.getDataSize() > 0) {
metadata.addUserMetadata(CryptoHeaders.CRYPTO_DATA_SIZE, String.valueOf(context.getDataSize()));
}
return metadata;
}
// Storages the encrytion materials in the object metadata.
protected final ObjectMetadata updateMetadataWithContentCryptoMaterial(ObjectMetadata metadata, File file,
ContentCryptoMaterial contentCryptoMaterial) {
if (metadata == null)
metadata = new ObjectMetadata();
if (file != null) {
Mimetypes mimetypes = Mimetypes.getInstance();
metadata.setContentType(mimetypes.getMimetype(file));
}
// Put the encrypted content encrypt key into the object meatadata
byte[] encryptedCEK = contentCryptoMaterial.getEncryptedCEK();
metadata.addUserMetadata(CryptoHeaders.CRYPTO_KEY, BinaryUtil.toBase64String(encryptedCEK));
// Put the iv into the object metadata
byte[] encryptedIV = contentCryptoMaterial.getEncryptedIV();
metadata.addUserMetadata(CryptoHeaders.CRYPTO_IV, BinaryUtil.toBase64String(encryptedIV));
// Put the content encrypt key algorithm into the object metadata
String contentCryptoAlgo = contentCryptoMaterial.getContentCryptoAlgorithm();
metadata.addUserMetadata(CryptoHeaders.CRYPTO_CEK_ALG, contentCryptoAlgo);
// Put the key wrap algorithm into the object metadata
String keyWrapAlgo = contentCryptoMaterial.getKeyWrapAlgorithm();
metadata.addUserMetadata(CryptoHeaders.CRYPTO_WRAP_ALG, keyWrapAlgo);
// Put the crypto description into the object metadata
Map materialDesc = contentCryptoMaterial.getMaterialsDescription();
if (materialDesc != null && materialDesc.size() > 0) {
try {
JSONObject descJson = new JSONObject(materialDesc);
String descStr = descJson.toString();
metadata.addUserMetadata(CryptoHeaders.CRYPTO_MATDESC, descStr);
} catch (Exception e) {
}
}
return metadata;
}
// Builds a new content crypto material for decrypting the object achieved.
protected ContentCryptoMaterial createContentMaterialFromMetadata(ObjectMetadata meta) {
Map userMeta = meta.getUserMetadata();
// Encrypted CEK and encrypted IV.
String b64CEK = userMeta.get(CryptoHeaders.CRYPTO_KEY);
String b64IV = userMeta.get(CryptoHeaders.CRYPTO_IV);
if (b64CEK == null || b64IV == null) {
throw new ClientException("Content encrypted key or encrypted iv not found.");
}
byte[] encryptedCEK = BinaryUtil.fromBase64String(b64CEK);
byte[] encryptedIV = BinaryUtil.fromBase64String(b64IV);
// Key wrap algorithm
final String keyWrapAlgo = userMeta.get(CryptoHeaders.CRYPTO_WRAP_ALG);
if (keyWrapAlgo == null)
throw new ClientException("Key wrap algorithm should not be null.");
// CEK algorithm
String cekAlgo = userMeta.get(CryptoHeaders.CRYPTO_CEK_ALG);
// Description
String mateDescString = userMeta.get(CryptoHeaders.CRYPTO_MATDESC);
Map matDesc = getDescFromJsonString(mateDescString);
// Decrypt the secured CEK to CEK.
ContentCryptoMaterialRW contentMaterialRW = new ContentCryptoMaterialRW();
contentMaterialRW.setEncryptedCEK(encryptedCEK);
contentMaterialRW.setEncryptedIV(encryptedIV);
contentMaterialRW.setMaterialsDescription(matDesc);
contentMaterialRW.setContentCryptoAlgorithm(cekAlgo);
contentMaterialRW.setKeyWrapAlgorithm(keyWrapAlgo);
encryptionMaterials.decryptCEK(contentMaterialRW);
// Convert to read-only object.
return contentMaterialRW;
}
// return the corresponding material description from the given json string.
@SuppressWarnings({ "unchecked", "rawtypes" })
protected static Map getDescFromJsonString(String jsonString) {
Map map = new HashMap();
if (jsonString == null) {
return map;
}
try {
JSONObject obj = new JSONObject(jsonString);
Iterator iter = obj.keys();
while (iter.hasNext()) {
String key = (String) iter.next();
String value = obj.getString(key);
map.put(key, value);
}
return map;
} catch (JSONException e) {
throw new ClientException("Unable to parse Json string:" + "json", e);
}
}
// Returns a srcret key for encrypting content.
protected SecretKey generateCEK() {
KeyGenerator generator;
final String keygenAlgo = contentCryptoScheme.getKeyGeneratorAlgorithm();
final int keyLength = contentCryptoScheme.getKeyLengthInBits();
try {
generator = KeyGenerator.getInstance(keygenAlgo);
generator.init(keyLength, cryptoConfig.getSecureRandom());
SecretKey secretKey = generator.generateKey();
for (int retry = 0; retry < 9; retry++) {
secretKey = generator.generateKey();
if (secretKey.getEncoded()[0] != 0)
return secretKey;
}
throw new ClientException("Failed to generate secret key");
} catch (NoSuchAlgorithmException e) {
throw new ClientException("No such algorithm:" + keygenAlgo + ", " + e.getMessage(), e);
}
}
protected void setUserAgent(WebServiceRequest req, String userAgent) {
req.addHeader(HTTP.USER_AGENT, userAgent);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy