Path: blob/master/src/java.base/share/classes/sun/security/pkcs/SignerInfo.java
67773 views
/*1* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.pkcs;2627import java.io.OutputStream;28import java.io.IOException;29import java.math.BigInteger;30import java.security.cert.CertPathValidatorException;31import java.security.cert.CertificateException;32import java.security.cert.CertificateFactory;33import java.security.cert.CertPath;34import java.security.cert.X509Certificate;35import java.security.*;36import java.security.spec.PSSParameterSpec;37import java.util.ArrayList;38import java.util.Collections;39import java.util.Date;40import java.util.HashMap;41import java.util.HashSet;42import java.util.Map;43import java.util.Set;4445import sun.security.provider.SHAKE256;46import sun.security.timestamp.TimestampToken;47import sun.security.util.*;48import sun.security.x509.AlgorithmId;49import sun.security.x509.X500Name;50import sun.security.x509.KeyUsageExtension;5152/**53* A SignerInfo, as defined in PKCS#7's signedData type.54*55* @author Benjamin Renaud56*/57public class SignerInfo implements DerEncoder {5859private static final DisabledAlgorithmConstraints JAR_DISABLED_CHECK =60DisabledAlgorithmConstraints.jarConstraints();6162BigInteger version;63X500Name issuerName;64BigInteger certificateSerialNumber;65AlgorithmId digestAlgorithmId;66AlgorithmId digestEncryptionAlgorithmId;67byte[] encryptedDigest;68Timestamp timestamp;69private boolean hasTimestamp = true;70private static final Debug debug = Debug.getInstance("jar");7172PKCS9Attributes authenticatedAttributes;73PKCS9Attributes unauthenticatedAttributes;7475/**76* A map containing the algorithms in this SignerInfo. This is used to77* avoid checking algorithms to see if they are disabled more than once.78* The key is the AlgorithmId of the algorithm, and the value is the name of79* the field or attribute.80*/81private Map<AlgorithmId, String> algorithms = new HashMap<>();8283public SignerInfo(X500Name issuerName,84BigInteger serial,85AlgorithmId digestAlgorithmId,86AlgorithmId digestEncryptionAlgorithmId,87byte[] encryptedDigest) {88this(issuerName, serial, digestAlgorithmId, null,89digestEncryptionAlgorithmId, encryptedDigest, null);90}9192public SignerInfo(X500Name issuerName,93BigInteger serial,94AlgorithmId digestAlgorithmId,95PKCS9Attributes authenticatedAttributes,96AlgorithmId digestEncryptionAlgorithmId,97byte[] encryptedDigest,98PKCS9Attributes unauthenticatedAttributes) {99this.version = BigInteger.ONE;100this.issuerName = issuerName;101this.certificateSerialNumber = serial;102this.digestAlgorithmId = digestAlgorithmId;103this.authenticatedAttributes = authenticatedAttributes;104this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;105this.encryptedDigest = encryptedDigest;106this.unauthenticatedAttributes = unauthenticatedAttributes;107}108109/**110* Parses a PKCS#7 signer info.111*/112public SignerInfo(DerInputStream derin)113throws IOException, ParsingException114{115this(derin, false);116}117118/**119* Parses a PKCS#7 signer info.120*121* <p>This constructor is used only for backwards compatibility with122* PKCS#7 blocks that were generated using JDK1.1.x.123*124* @param derin the ASN.1 encoding of the signer info.125* @param oldStyle flag indicating whether or not the given signer info126* is encoded according to JDK1.1.x.127*/128public SignerInfo(DerInputStream derin, boolean oldStyle)129throws IOException, ParsingException130{131// version132version = derin.getBigInteger();133134// issuerAndSerialNumber135DerValue[] issuerAndSerialNumber = derin.getSequence(2);136if (issuerAndSerialNumber.length != 2) {137throw new ParsingException("Invalid length for IssuerAndSerialNumber");138}139byte[] issuerBytes = issuerAndSerialNumber[0].toByteArray();140issuerName = new X500Name(new DerValue(DerValue.tag_Sequence,141issuerBytes));142certificateSerialNumber = issuerAndSerialNumber[1].getBigInteger();143144// digestAlgorithmId145DerValue tmp = derin.getDerValue();146147digestAlgorithmId = AlgorithmId.parse(tmp);148149// authenticatedAttributes150if (oldStyle) {151// In JDK1.1.x, the authenticatedAttributes are always present,152// encoded as an empty Set (Set of length zero)153derin.getSet(0);154} else {155// check if set of auth attributes (implicit tag) is provided156// (auth attributes are OPTIONAL)157if ((byte)(derin.peekByte()) == (byte)0xA0) {158authenticatedAttributes = new PKCS9Attributes(derin);159}160}161162// digestEncryptionAlgorithmId - little RSA naming scheme -163// signature == encryption...164tmp = derin.getDerValue();165166digestEncryptionAlgorithmId = AlgorithmId.parse(tmp);167168// encryptedDigest169encryptedDigest = derin.getOctetString();170171// unauthenticatedAttributes172if (oldStyle) {173// In JDK1.1.x, the unauthenticatedAttributes are always present,174// encoded as an empty Set (Set of length zero)175derin.getSet(0);176} else {177// check if set of unauth attributes (implicit tag) is provided178// (unauth attributes are OPTIONAL)179if (derin.available() != 0180&& (byte)(derin.peekByte()) == (byte)0xA1) {181unauthenticatedAttributes =182new PKCS9Attributes(derin, true);// ignore unsupported attrs183}184}185186// all done187if (derin.available() != 0) {188throw new ParsingException("extra data at the end");189}190191// verify CMSAlgorithmProtection192checkCMSAlgorithmProtection();193}194195// CMSAlgorithmProtection verification as described in RFC 6211196private void checkCMSAlgorithmProtection() throws IOException {197if (authenticatedAttributes == null) {198return;199}200PKCS9Attribute ap = authenticatedAttributes.getAttribute(201PKCS9Attribute.CMS_ALGORITHM_PROTECTION_OID);202if (ap == null) {203return;204}205DerValue dv = new DerValue((byte[])ap.getValue());206DerInputStream data = dv.data();207AlgorithmId d = AlgorithmId.parse(data.getDerValue());208DerValue ds = data.getDerValue();209if (data.available() > 0) {210throw new IOException("Unknown field in CMSAlgorithmProtection");211}212if (!ds.isContextSpecific((byte)1)) {213throw new IOException("No signature algorithm in CMSAlgorithmProtection");214}215AlgorithmId s = AlgorithmId.parse(ds.withTag(DerValue.tag_Sequence));216if (!s.equals(digestEncryptionAlgorithmId)217|| !d.equals(digestAlgorithmId)) {218throw new IOException("CMSAlgorithmProtection check failed");219}220}221222public void encode(DerOutputStream out) throws IOException {223224derEncode(out);225}226227/**228* DER encode this object onto an output stream.229* Implements the {@code DerEncoder} interface.230*231* @param out232* the output stream on which to write the DER encoding.233*234* @exception IOException on encoding error.235*/236public void derEncode(OutputStream out) throws IOException {237DerOutputStream seq = new DerOutputStream();238seq.putInteger(version);239DerOutputStream issuerAndSerialNumber = new DerOutputStream();240issuerName.encode(issuerAndSerialNumber);241issuerAndSerialNumber.putInteger(certificateSerialNumber);242seq.write(DerValue.tag_Sequence, issuerAndSerialNumber);243244digestAlgorithmId.encode(seq);245246// encode authenticated attributes if there are any247if (authenticatedAttributes != null)248authenticatedAttributes.encode((byte)0xA0, seq);249250digestEncryptionAlgorithmId.encode(seq);251252seq.putOctetString(encryptedDigest);253254// encode unauthenticated attributes if there are any255if (unauthenticatedAttributes != null)256unauthenticatedAttributes.encode((byte)0xA1, seq);257258DerOutputStream tmp = new DerOutputStream();259tmp.write(DerValue.tag_Sequence, seq);260261out.write(tmp.toByteArray());262}263264/*265* Returns the (user) certificate pertaining to this SignerInfo.266*/267public X509Certificate getCertificate(PKCS7 block)268throws IOException269{270return block.getCertificate(certificateSerialNumber, issuerName);271}272273/*274* Returns the certificate chain pertaining to this SignerInfo.275*/276public ArrayList<X509Certificate> getCertificateChain(PKCS7 block)277throws IOException278{279X509Certificate userCert;280userCert = block.getCertificate(certificateSerialNumber, issuerName);281if (userCert == null)282return null;283284ArrayList<X509Certificate> certList = new ArrayList<>();285certList.add(userCert);286287X509Certificate[] pkcsCerts = block.getCertificates();288if (pkcsCerts == null289|| userCert.getSubjectX500Principal().equals(userCert.getIssuerX500Principal())) {290return certList;291}292293Principal issuer = userCert.getIssuerX500Principal();294int start = 0;295while (true) {296boolean match = false;297int i = start;298while (i < pkcsCerts.length) {299if (issuer.equals(pkcsCerts[i].getSubjectX500Principal())) {300// next cert in chain found301certList.add(pkcsCerts[i]);302// if selected cert is self-signed, we're done303// constructing the chain304if (pkcsCerts[i].getSubjectX500Principal().equals(305pkcsCerts[i].getIssuerX500Principal())) {306start = pkcsCerts.length;307} else {308issuer = pkcsCerts[i].getIssuerX500Principal();309X509Certificate tmpCert = pkcsCerts[start];310pkcsCerts[start] = pkcsCerts[i];311pkcsCerts[i] = tmpCert;312start++;313}314match = true;315break;316} else {317i++;318}319}320if (!match)321break;322}323324return certList;325}326327/* Returns null if verify fails, this signerInfo if328verify succeeds. */329SignerInfo verify(PKCS7 block, byte[] data)330throws NoSuchAlgorithmException, SignatureException {331332try {333Timestamp timestamp = null;334try {335timestamp = getTimestamp();336} catch (Exception e) {337// Log exception and continue. This allows for the case338// where, if there are no other errors, the code is339// signed but w/o a timestamp.340if (debug != null) {341debug.println("Unexpected exception while getting" +342" timestamp: " + e);343}344}345346ContentInfo content = block.getContentInfo();347if (data == null) {348data = content.getContentBytes();349}350351String digestAlgName = digestAlgorithmId.getName();352algorithms.put(digestAlgorithmId, "SignerInfo digestAlgorithm field");353354byte[] dataSigned;355356// if there are authenticate attributes, get the message357// digest and compare it with the digest of data358if (authenticatedAttributes == null) {359dataSigned = data;360} else {361362// first, check content type363ObjectIdentifier contentType = (ObjectIdentifier)364authenticatedAttributes.getAttributeValue(365PKCS9Attribute.CONTENT_TYPE_OID);366if (contentType == null ||367!contentType.equals(content.contentType))368return null; // contentType does not match, bad SignerInfo369370// now, check message digest371byte[] messageDigest = (byte[])372authenticatedAttributes.getAttributeValue(373PKCS9Attribute.MESSAGE_DIGEST_OID);374375if (messageDigest == null) // fail if there is no message digest376return null;377378byte[] computedMessageDigest;379if (digestAlgName.equals("SHAKE256")380|| digestAlgName.equals("SHAKE256-LEN")) {381if (digestAlgName.equals("SHAKE256-LEN")) {382// RFC8419: for EdDSA in CMS, the id-shake256-len383// algorithm id must contain parameter value 512384// encoded as a positive integer value385byte[] params = digestAlgorithmId.getEncodedParams();386if (params == null) {387throw new SignatureException(388"id-shake256-len oid missing length");389}390int v = new DerValue(params).getInteger();391if (v != 512) {392throw new SignatureException(393"Unsupported id-shake256-" + v);394}395}396var md = new SHAKE256(64);397md.update(data, 0, data.length);398computedMessageDigest = md.digest();399} else {400MessageDigest md = MessageDigest.getInstance(digestAlgName);401computedMessageDigest = md.digest(data);402}403404if (!MessageDigest.isEqual(messageDigest, computedMessageDigest)) {405return null;406}407408// message digest attribute matched409// digest of original data410411// the data actually signed is the DER encoding of412// the authenticated attributes (tagged with413// the "SET OF" tag, not 0xA0).414dataSigned = authenticatedAttributes.getDerEncoding();415}416417// put together digest algorithm and encryption algorithm418// to form signing algorithm. See makeSigAlg for details.419String sigAlgName = makeSigAlg(420digestAlgorithmId,421digestEncryptionAlgorithmId,422authenticatedAttributes == null);423424KnownOIDs oid = KnownOIDs.findMatch(sigAlgName);425if (oid != null) {426AlgorithmId sigAlgId =427new AlgorithmId(ObjectIdentifier.of(oid),428digestEncryptionAlgorithmId.getParameters());429algorithms.put(sigAlgId,430"SignerInfo digestEncryptionAlgorithm field");431}432433X509Certificate cert = getCertificate(block);434if (cert == null) {435return null;436}437PublicKey key = cert.getPublicKey();438439if (cert.hasUnsupportedCriticalExtension()) {440throw new SignatureException("Certificate has unsupported "441+ "critical extension(s)");442}443444// Make sure that if the usage of the key in the certificate is445// restricted, it can be used for digital signatures.446// XXX We may want to check for additional extensions in the447// future.448boolean[] keyUsageBits = cert.getKeyUsage();449if (keyUsageBits != null) {450KeyUsageExtension keyUsage;451try {452// We don't care whether or not this extension was marked453// critical in the certificate.454// We're interested only in its value (i.e., the bits set)455// and treat the extension as critical.456keyUsage = new KeyUsageExtension(keyUsageBits);457} catch (IOException ioe) {458throw new SignatureException("Failed to parse keyUsage "459+ "extension");460}461462boolean digSigAllowed463= keyUsage.get(KeyUsageExtension.DIGITAL_SIGNATURE);464465boolean nonRepuAllowed466= keyUsage.get(KeyUsageExtension.NON_REPUDIATION);467468if (!digSigAllowed && !nonRepuAllowed) {469throw new SignatureException("Key usage restricted: "470+ "cannot be used for "471+ "digital signatures");472}473}474475Signature sig = Signature.getInstance(sigAlgName);476477AlgorithmParameters ap =478digestEncryptionAlgorithmId.getParameters();479try {480SignatureUtil.initVerifyWithParam(sig, key,481SignatureUtil.getParamSpec(sigAlgName, ap));482} catch (ProviderException | InvalidAlgorithmParameterException |483InvalidKeyException e) {484throw new SignatureException(e.getMessage(), e);485}486487sig.update(dataSigned);488if (sig.verify(encryptedDigest)) {489return this;490}491} catch (IOException e) {492throw new SignatureException("Error verifying signature", e);493}494return null;495}496497/**498* Derives the signature algorithm name from the digest algorithm499* and the encryption algorithm inside a PKCS7 SignerInfo.500*501* The digest algorithm is in the form "DIG", and the encryption502* algorithm can be in any of the 3 forms:503*504* 1. Old style key algorithm like RSA, DSA, EC, this method returns505* DIGwithKEY.506* 2. New style signature algorithm in the form of HASHwithKEY, this507* method returns DIGwithKEY. Please note this is not HASHwithKEY.508* 3. Modern signature algorithm like RSASSA-PSS and EdDSA, this method509* returns the signature algorithm itself but ensures digAlgId is510* compatible with the algorithm as described in RFC 4056 and 8419.511*512* @param digAlgId the digest algorithm513* @param encAlgId the encryption algorithm514* @param directSign whether the signature is calculated on the content515* directly. This makes difference for Ed448.516*/517public static String makeSigAlg(AlgorithmId digAlgId, AlgorithmId encAlgId,518boolean directSign) throws NoSuchAlgorithmException {519String encAlg = encAlgId.getName();520switch (encAlg) {521case "RSASSA-PSS":522PSSParameterSpec spec = (PSSParameterSpec)523SignatureUtil.getParamSpec(encAlg, encAlgId.getParameters());524/*525* RFC 4056 section 3 for Signed-data:526* signatureAlgorithm MUST contain id-RSASSA-PSS. The algorithm527* parameters field MUST contain RSASSA-PSS-params.528*/529if (spec == null) {530throw new NoSuchAlgorithmException("Missing PSSParameterSpec for RSASSA-PSS algorithm");531}532533if (!AlgorithmId.get(spec.getDigestAlgorithm()).equals(digAlgId)) {534throw new NoSuchAlgorithmException("Incompatible digest algorithm");535}536return encAlg;537case "Ed25519":538if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.sha512)) {539throw new NoSuchAlgorithmException("Incompatible digest algorithm");540}541return encAlg;542case "Ed448":543if (directSign) {544if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.shake256)) {545throw new NoSuchAlgorithmException("Incompatible digest algorithm");546}547} else {548if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.shake256$512)) {549throw new NoSuchAlgorithmException("Incompatible digest algorithm");550}551}552return encAlg;553default:554String digAlg = digAlgId.getName();555String keyAlg = SignatureUtil.extractKeyAlgFromDwithE(encAlg);556if (keyAlg == null) {557// The encAlg used to be only the key alg558keyAlg = encAlg;559}560if (digAlg.startsWith("SHA-")) {561digAlg = "SHA" + digAlg.substring(4);562}563if (keyAlg.equals("EC")) keyAlg = "ECDSA";564return digAlg + "with" + keyAlg;565}566}567568/* Verify the content of the pkcs7 block. */569SignerInfo verify(PKCS7 block)570throws NoSuchAlgorithmException, SignatureException {571return verify(block, null);572}573574public BigInteger getVersion() {575return version;576}577578public X500Name getIssuerName() {579return issuerName;580}581582public BigInteger getCertificateSerialNumber() {583return certificateSerialNumber;584}585586public AlgorithmId getDigestAlgorithmId() {587return digestAlgorithmId;588}589590public PKCS9Attributes getAuthenticatedAttributes() {591return authenticatedAttributes;592}593594public AlgorithmId getDigestEncryptionAlgorithmId() {595return digestEncryptionAlgorithmId;596}597598public byte[] getEncryptedDigest() {599return encryptedDigest;600}601602public PKCS9Attributes getUnauthenticatedAttributes() {603return unauthenticatedAttributes;604}605606/**607* Returns the timestamp PKCS7 data unverified.608* @return a PKCS7 object609*/610public PKCS7 getTsToken() throws IOException {611if (unauthenticatedAttributes == null) {612return null;613}614PKCS9Attribute tsTokenAttr =615unauthenticatedAttributes.getAttribute(616PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID);617if (tsTokenAttr == null) {618return null;619}620return new PKCS7((byte[])tsTokenAttr.getValue());621}622623/*624* Extracts a timestamp from a PKCS7 SignerInfo.625*626* Examines the signer's unsigned attributes for a627* {@code signatureTimestampToken} attribute. If present,628* then it is parsed to extract the date and time at which the629* timestamp was generated.630*631* @param info A signer information element of a PKCS 7 block.632*633* @return A timestamp token or null if none is present.634* @throws IOException if an error is encountered while parsing the635* PKCS7 data.636* @throws NoSuchAlgorithmException if an error is encountered while637* verifying the PKCS7 object.638* @throws SignatureException if an error is encountered while639* verifying the PKCS7 object.640* @throws CertificateException if an error is encountered while generating641* the TSA's certpath.642*/643public Timestamp getTimestamp()644throws IOException, NoSuchAlgorithmException, SignatureException,645CertificateException646{647if (timestamp != null || !hasTimestamp)648return timestamp;649650PKCS7 tsToken = getTsToken();651if (tsToken == null) {652hasTimestamp = false;653return null;654}655656// Extract the content (an encoded timestamp token info)657byte[] encTsTokenInfo = tsToken.getContentInfo().getData();658// Extract the signer (the Timestamping Authority)659// while verifying the content660SignerInfo[] tsa = tsToken.verify(encTsTokenInfo);661if (tsa == null || tsa.length == 0) {662throw new SignatureException("Unable to verify timestamp");663}664// Expect only one signer665ArrayList<X509Certificate> chain = tsa[0].getCertificateChain(tsToken);666CertificateFactory cf = CertificateFactory.getInstance("X.509");667CertPath tsaChain = cf.generateCertPath(chain);668// Create a timestamp token info object669TimestampToken tsTokenInfo = new TimestampToken(encTsTokenInfo);670// Check that the signature timestamp applies to this signature671verifyTimestamp(tsTokenInfo);672algorithms.putAll(tsa[0].algorithms);673// Create a timestamp object674timestamp = new Timestamp(tsTokenInfo.getDate(), tsaChain);675return timestamp;676}677678/*679* Check that the signature timestamp applies to this signature.680* Match the hash present in the signature timestamp token against the hash681* of this signature.682*/683private void verifyTimestamp(TimestampToken token)684throws NoSuchAlgorithmException, SignatureException {685686AlgorithmId digestAlgId = token.getHashAlgorithm();687algorithms.put(digestAlgId, "TimestampToken digestAlgorithm field");688689MessageDigest md = MessageDigest.getInstance(digestAlgId.getName());690691if (!MessageDigest.isEqual(token.getHashedMessage(),692md.digest(encryptedDigest))) {693694throw new SignatureException("Signature timestamp (#" +695token.getSerialNumber() + ") generated on " + token.getDate() +696" is inapplicable");697}698699if (debug != null) {700debug.println();701debug.println("Detected signature timestamp (#" +702token.getSerialNumber() + ") generated on " + token.getDate());703debug.println();704}705}706707public String toString() {708HexDumpEncoder hexDump = new HexDumpEncoder();709710String out = "";711712out += "Signer Info for (issuer): " + issuerName + "\n";713out += "\tversion: " + Debug.toHexString(version) + "\n";714out += "\tcertificateSerialNumber: " +715Debug.toHexString(certificateSerialNumber) + "\n";716out += "\tdigestAlgorithmId: " + digestAlgorithmId + "\n";717if (authenticatedAttributes != null) {718out += "\tauthenticatedAttributes: " + authenticatedAttributes +719"\n";720}721out += "\tdigestEncryptionAlgorithmId: " + digestEncryptionAlgorithmId +722"\n";723724out += "\tencryptedDigest: " + "\n" +725hexDump.encodeBuffer(encryptedDigest) + "\n";726if (unauthenticatedAttributes != null) {727out += "\tunauthenticatedAttributes: " +728unauthenticatedAttributes + "\n";729}730return out;731}732733/**734* Verify all of the algorithms in the array of SignerInfos against the735* constraints in the jdk.jar.disabledAlgorithms security property.736*737* @param infos array of SignerInfos738* @param params constraint parameters739* @param name the name of the signer's PKCS7 file740* @return a set of algorithms that passed the checks and are not disabled741*/742public static Set<String> verifyAlgorithms(SignerInfo[] infos,743JarConstraintsParameters params, String name) throws SignatureException {744Map<AlgorithmId, String> algorithms = new HashMap<>();745for (SignerInfo info : infos) {746algorithms.putAll(info.algorithms);747}748749Set<String> enabledAlgorithms = new HashSet<>();750try {751for (Map.Entry<AlgorithmId, String> algorithm : algorithms.entrySet()) {752params.setExtendedExceptionMsg(name, algorithm.getValue());753AlgorithmId algId = algorithm.getKey();754JAR_DISABLED_CHECK.permits(algId.getName(),755algId.getParameters(), params);756enabledAlgorithms.add(algId.getName());757}758} catch (CertPathValidatorException e) {759throw new SignatureException(e);760}761return enabledAlgorithms;762}763}764765766