Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.authlete.sd.SDObjectDecoder Maven / Gradle / Ivy
Go to download
A Java library for the "Selective Disclosure for JWTs (SD-JWT)" specification.
/*
* Copyright (C) 2023 Authlete, Inc.
*
* 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
*
* https://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.authlete.sd;
import static com.authlete.sd.SDConstants.DEFAULT_HASH_ALGORITHM;
import static com.authlete.sd.SDConstants.KEY_SD;
import static com.authlete.sd.SDConstants.KEY_SD_ALG;
import static com.authlete.sd.SDConstants.KEY_THREE_DOTS;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* A utility to decode selectively-disclosable elements in a map or a list
* recursively.
*
*
*
* // Original dataset
* //
* // {
* // "key-1" : "value-1" ,
* // "key-2" : "value-2"
* // }
* //
* Map<String, Object> originalMap = Map.of(
* "key-1" , "value-1" ,
* "key-2" , "value-2"
* );
*
* // Encoder
* SDObjectEncoder encoder = new SDObjectEncoder();
*
* // Encode
* Map<String, Object> encodedMap = encoder.encode(originalMap);
*
* // Disclosures yielded as a result of the encoding process.
* List<Disclosure> disclosures = encoder.getDisclosures();
*
* // Disclosures for claims to disclose.
* List<Disclosure> disclosed = disclosures.stream()
* .filter(d -> "key-1" .equals(d.getClaimName()))
* .collect(Collectors.toList());
*
* // Decode the encoded map with the selected disclosures.
* SDObjectDecoder decoder = new SDObjectDecoder();
* Map<String, Object> decodedMap = decoder.decode(encodedMap, disclosed);
*
* // Decoded dataset
* //
* // {
* // "key-1" : "value-1"
* // }
* //
*
*
*
* @since 1.3
*/
public class SDObjectDecoder
{
/**
* Decode the given map with the specified disclosures.
*
* @param encodedMap
* The input map. If {@code null} is given, {@code null} is returned.
*
* @param disclosures
* Disclosures for claims to disclose. If {@code null} is given, it
* means that none of selectively-disclosable elements in the input
* map are disclosed.
*
* @return
* The decoded map.
*/
public Map decode(
Map encodedMap, Collection disclosures)
{
if (encodedMap == null)
{
return null;
}
// Determine the hash algorithm. It is the value specified by
// the "_sd_alg" key in the encoded map, or the default algorithm
// if the key is not found in the encoded map or its value is null.
String hashAlgorithm = determineHashAlgorithm(encodedMap);
// Create mappings from a disclosure digest to a disclosure.
// The digest is computed using the hash algorithm.
Map digestMap =
createDigestMap(hashAlgorithm, disclosures);
// Decode the encoded map.
return decodeMap(digestMap, encodedMap);
}
/**
* Decode the given list with the specified disclosures and the default hash
* algorithm ("{@code sha-256}").
*
* @param encodedList
* The input list. If {@code null} is given, {@code null} is returned.
*
* @param disclosures
* Disclosures for claims to disclose. If {@code null} is given, it
* means that none of selectively-disclosable elements in the input
* list are disclosed.
*
* @return
* The decoded list.
*/
public List decode(
List> encodedList, Collection disclosures)
{
return decode(encodedList, disclosures, DEFAULT_HASH_ALGORITHM);
}
/**
* Decode the given list with the specified disclosures and hash algorithm.
*
* @param encodedList
* The input list. If {@code null} is given, {@code null} is returned.
*
* @param disclosures
* Disclosures for claims to disclose. If {@code null} is given, it
* means that none of selectively-disclosable elements in the input
* list are disclosed.
*
* @param hashAlgorithm
* The hash algorithm for digests. If {@code null} is given, the
* default hash algorithm ("{@code sha-256}") is used.
*
* @return
* The decoded list.
*/
public List decode(
List> encodedList, Collection disclosures, String hashAlgorithm)
{
if (encodedList == null)
{
return null;
}
if (hashAlgorithm == null)
{
// Use the default hash algorithm.
hashAlgorithm = DEFAULT_HASH_ALGORITHM;
}
// Create mappings from a disclosure digest to a disclosure.
// The digest is computed using the hash algorithm.
Map digestMap =
createDigestMap(hashAlgorithm, disclosures);
// Decode the encoded list.
return decodeList(digestMap, encodedList);
}
private static String determineHashAlgorithm(Map encodedMap)
{
// If the map does not contain "_sd_alg".
if (!encodedMap.containsKey(KEY_SD_ALG))
{
// Use the default hash algorithm.
return DEFAULT_HASH_ALGORITHM;
}
// The value of "_sd_alg".
Object alg = encodedMap.get(KEY_SD_ALG);
// If the value of "_sd_alg" is not a string.
if (!(alg instanceof String))
{
throw new IllegalArgumentException(
"The value of '_sd_alg' is not a string.");
}
// The hash algorithm specified by "_sd_alg".
return (String)alg;
}
private static Map createDigestMap(
String hashAlgorithm, Collection disclosures)
{
// Mappings from a disclosure digest to a disclosure.
Map map = new LinkedHashMap<>();
if (disclosures == null)
{
// Return an empty map.
return map;
}
// For each disclosure.
for (Disclosure disclosure : disclosures)
{
if (disclosure == null)
{
// Ignore.
continue;
}
// Compute the digest of the disclosure with the hash algorithm.
// The digest is used as a key.
String key = disclosure.digest(hashAlgorithm);
// Add a mapping from the disclosure digest to the disclosure.
map.put(key, disclosure);
}
// Mappings from a disclosure digest to a disclosure.
return map;
}
private Map decodeMap(
Map digestMap, Map encodedMap)
{
// A map that holds decoded key-value pairs.
Map decodedMap = new LinkedHashMap<>();
// For each key-value pair in the encoded map.
for (Map.Entry entry : encodedMap.entrySet())
{
String key = entry.getKey();
Object value = entry.getValue();
// Decode the key-value pair.
decodeMapEntry(digestMap, key, value, decodedMap);
}
// A map that holds decoded key-value pairs.
return decodedMap;
}
@SuppressWarnings("unchecked")
private void decodeMapEntry(
Map digestMap,
String key, Object value, Map decodedMap)
{
// If the key is "_sd_alg".
if (KEY_SD_ALG.equals(key))
{
// "_sd_alg" does not appear in the decoded map.
return;
}
// If the key is "_sd".
if (KEY_SD.equals(key))
{
// Process the "_sd" array.
decodeSD(digestMap, value, decodedMap);
return;
}
// If the value is a map.
if (value instanceof Map)
{
// Decode the nested map.
value = decodeMap(digestMap, (Map)value);
}
// If the value is a list.
else if (value instanceof List)
{
// Decode the list.
value = decodeList(digestMap, (List>)value);
}
// Add the decoded key-value pair.
decodedMap.put(key, value);
}
private void decodeSD(
Map digestMap,
Object sd, Map decodedMap)
{
// If the value of "_sd" is null.
if (sd == null)
{
// Ignore.
return;
}
// If the value of "_sd" is not a list.
if (!(sd instanceof List))
{
throw new IllegalArgumentException(
"The value of '_sd' is not an array.");
}
// For each element in the "_sd" array.
for (Object element : (List>)sd)
{
// If the element is null.
if (element == null)
{
// Ignore.
continue;
}
// If the element is not a string.
if (!(element instanceof String))
{
throw new IllegalArgumentException(
"An element in the '_sd' array is not a string.");
}
// The value of the element should be the digest of a disclosure.
String digest = (String)element;
// Process the digest.
decodeSDElement(digestMap, digest, decodedMap);
}
}
private void decodeSDElement(
Map digestMap,
String digest, Map decodedMap)
{
// Get a disclosure that corresponds to the digest.
Disclosure disclosure = digestMap.get(digest);
// If the disclosure that corresponds to the digest is not found.
if (disclosure == null)
{
// There are two possibilities.
//
// 1. The claim corresponding to the digest is not disclosed.
// 2. The digest is a decoy digest.
//
// In either case, no key-value pair is added to the decoded map.
return;
}
// The key-value pair that the disclosure holds.
String claimName = disclosure.getClaimName();
Object claimValue = disclosure.getClaimValue();
// If the claim name is null.
if (claimName == null)
{
// That the claim name of a disclosure is null means that the
// disclosure is for an array element, not for an object property.
throw new IllegalArgumentException(
"The digest of a disclosure for an array element is found in the '_sd' array.");
}
// Add the disclosed key-value pair.
decodedMap.put(claimName, claimValue);
}
private List decodeList(
Map digestMap, List> encodedList)
{
// A list that holds decoded elements.
List decodedList = new ArrayList<>();
// For each element in the encoded list.
for (Object element : encodedList)
{
// Process the element.
decodeListElement(digestMap, element, decodedList);
}
// A list that holds decoded elements.
return decodedList;
}
@SuppressWarnings("unchecked")
private void decodeListElement(
Map digestMap,
Object element, List decodedList)
{
if (element instanceof Map)
{
Map map = (Map)element;
// If the map contains the key "..." (three dots).
if (map.containsKey(KEY_THREE_DOTS))
{
// The map represents a selectively-disclosable array element.
decodeListElementMap(digestMap, map, decodedList);
return;
}
else
{
// Decode the encoded map.
element = decodeMap(digestMap, map);
}
}
else if (element instanceof List)
{
// Decode the encoded list.
element = decodeList(digestMap, (List>)element);
}
// Add the element to the decoded list.
decodedList.add(element);
}
private void decodeListElementMap(
Map digestMap,
Map element, List decodedList)
{
// If the map contains other keys than "...".
if (element.size() != 1)
{
throw new IllegalArgumentException(
"An object containing the three-dot key ('...') must not contain other keys.");
}
// The value of "...".
Object dots = element.get(KEY_THREE_DOTS);
// If the value of "..." is null.
if (dots == null)
{
// Ignore.
return;
}
// If the value of "..." is not a string.
if (!(dots instanceof String))
{
throw new IllegalArgumentException(
"The value of the three-dot key ('...') is not a string.");
}
// The value of '...' should be the digest of a disclosure.
String digest = (String)dots;
// Process the digest.
decodeDots(digestMap, digest, decodedList);
}
private void decodeDots(
Map digestMap,
String digest, List decodedList)
{
// Get a disclosure that corresponds to the digest.
Disclosure disclosure = digestMap.get(digest);
// If the disclosure that corresponds to the digest is not found.
if (disclosure == null)
{
// There are two possibilities.
//
// 1. The array element corresponding to the digest is not disclosed.
// 2. The digest is a decoy digest.
//
// In either case, no element is added to the decoded list.
return;
}
// If the disclosure has a claim name.
if (disclosure.getClaimName() != null)
{
// That the claim name of a disclosure is not null means that the
// disclosure is for an object property, not for an array element.
throw new IllegalArgumentException(
"The digest of a disclosure for an object property is specified by '...'.");
}
// Add the disclosed array element.
decodedList.add(disclosure.getClaimValue());
}
}