001/*
002 * acme4j - Java ACME client
003 *
004 * Copyright (C) 2021 Richard "Shred" Körber
005 *   http://acme4j.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.acme4j.smime.challenge;
015
016import static org.shredzone.acme4j.toolbox.AcmeUtils.base64UrlEncode;
017import static org.shredzone.acme4j.toolbox.AcmeUtils.sha256hash;
018
019import jakarta.mail.internet.AddressException;
020import jakarta.mail.internet.InternetAddress;
021import org.shredzone.acme4j.Login;
022import org.shredzone.acme4j.challenge.TokenChallenge;
023import org.shredzone.acme4j.exception.AcmeProtocolException;
024import org.shredzone.acme4j.toolbox.JSON;
025
026/**
027 * Implements the {@value TYPE} challenge.
028 *
029 * @see <a href="https://datatracker.ietf.org/doc/html/rfc8823">RFC 8823</a>
030 * @since 2.12
031 */
032public class EmailReply00Challenge extends TokenChallenge {
033    private static final long serialVersionUID = 2502329538019544794L;
034
035    /**
036     * Challenge type name: {@value}
037     */
038    public static final String TYPE = "email-reply-00";
039
040    private static final String KEY_FROM = "from";
041
042    /**
043     * Creates a new generic {@link EmailReply00Challenge} object.
044     *
045     * @param login
046     *            {@link Login} the resource is bound with
047     * @param data
048     *            {@link JSON} challenge data
049     */
050    public EmailReply00Challenge(Login login, JSON data) {
051        super(login, data);
052    }
053
054    /**
055     * Returns the email address in the "from" field of the challenge.
056     *
057     * @return The "from" email address, as String.
058     */
059    public String getFrom() {
060        return getJSON().get(KEY_FROM).asString();
061    }
062
063    /**
064     * Returns the email address of the expected sender of the "challenge" mail.
065     * <p>
066     * This is the same value that is returned by {@link #getFrom()}, but as {@link
067     * InternetAddress} instance.
068     *
069     * @return Expected sender of the challenge email.
070     */
071    public InternetAddress getExpectedSender() {
072        try {
073            return new InternetAddress(getFrom());
074        } catch (AddressException ex) {
075            throw new AcmeProtocolException("bad email address " + getFrom(), ex);
076        }
077    }
078
079    /**
080     * Returns the token, which is a concatenation of the part 1 that is sent by email,
081     * and part 2 that is passed into this callenge via {@link #getTokenPart2()};
082     *
083     * @param part1
084     *         Part 1 of the token, which can be found in the subject of the corresponding
085     *         challenge email.
086     * @return Concatenated token
087     */
088    public String getToken(String part1) {
089        return part1.concat(getTokenPart2());
090    }
091
092    /**
093     * Returns the part 2 of the token to be used for this challenge. Part 2 is sent via
094     * this challenge.
095     */
096    public String getTokenPart2() {
097        return super.getToken();
098    }
099
100    /**
101     * This method is not implemented. Use {@link #getAuthorization(String)} instead.
102     */
103    @Override
104    public String getAuthorization() {
105        throw new UnsupportedOperationException("use getAuthorization(String)");
106    }
107
108    /**
109     * Returns the authorization string.
110     *
111     * @param part1
112     *         Part 1 of the token, which can be found in the subject of the corresponding
113     *         challenge email.
114     */
115    public String getAuthorization(String part1) {
116        var keyAuth = keyAuthorizationFor(getToken(part1));
117        return base64UrlEncode(sha256hash(keyAuth));
118    }
119
120    @Override
121    protected boolean acceptable(String type) {
122        return TYPE.equals(type);
123    }
124
125}